Skip to content

Latest commit

 

History

History
161 lines (132 loc) · 9.57 KB

File metadata and controls

161 lines (132 loc) · 9.57 KB

CLAUDE.md

Agent guidance for codellm-devkit/codeanalyzer-clang (canclang).

What this project is

canclang is a C/C++ static analyzer built on LLVM/Clang via libclang (clang.cindex). It is the CLDK C/C++ backend: it emits the canonical CLDK analysis.json — a symbol table plus a resolver-based call graph — and can project that same analysis into a Neo4j property graph. It mirrors its Python (canpy), TypeScript (cants), and Java sibling analyzers, so output-shape parity with them is a first-class concern. The locked backend decisions are in the README's Architecture & Tooling section.

libclang is the Clang front end, so structural parsing and resolution are the same tool: the level-1 call graph is resolved from the Clang AST (cursor.referenced) — constructors, overloads, and virtual dispatch included — not a shallow name match.

Architecture — follow the pipeline

The whole analyzer is one orchestration method: analyze() in codeanalyzer_clang/core.py. Read it first; everything else is a stage it delegates to, in order:

  1. materialize (core.__enter__) — locate a compile_commands.json compilation database (accurate include paths/flags) via syntactic_analysis/clang_loader.py; degrade to a language default when absent.
  2. build symbol table (syntactic_analysis/symbol_table_builder.py) — per translation unit: classes/structs/unions, methods, free functions, ctors/dtors, fields, globals, enums, typedefs, macros, includes, doc comments, and the unresolved call sites, with precise spans + C/C++ flags.
  3. call graph (semantic_analysis/call_graph.py) — at -a 2, aggregate the resolved call sites into identity-only edges (provenance=["clang"]); --svf merges level-2 points-to edges (semantic_analysis/svf/, stubbed).
  4. classify externals + run_pipeline (analysis/registry.py) — pluggable passes contribute entrypoints/synthetic edges; the base app is cached, pass output is not.
  5. cache (.codeanalyzer-clang/analysis_cache.json) — content-hash per-file cache; re-analysis only rebuilds changed files.
  6. output (__main__.py, neo4j/) — analysis.json, a graph.cypher snapshot, or an incremental Bolt push.

Modularity invariants a change must preserve (see the reference analyzer's structure): core only delegates — no inlined parsing, and it never hardcodes entrypoints: {} (they come from discovered passes); the symbol-table builder is one cohesive class split by node kind (_build_class, _build_callable, _build_enum, …), not a flat function pile; the level-2 backend stays isolated in semantic_analysis/svf/ with its own loader/driver/query/ error seams; and the analysis/ (pass registry) + frameworks/ (finder base) layer stays present and wired even while empty. ClangApplication in schema/clang_schema.py is the output contract, and signature_of() (syntactic_analysis/signatures.py) is the single id canonicalizer — caller-side and callee-side ids must come from it.

Directory map

Path Responsibility
codeanalyzer_clang/__main__.py Entry point + Typer CLI (canclang)
codeanalyzer_clang/core.py analyze() orchestrator — the spine
codeanalyzer_clang/options Parsed CLI options / AnalysisOptions
codeanalyzer_clang/config Static config: file extensions, skip dirs, libclang paths
codeanalyzer_clang/schema ClangApplication models — the output contract
codeanalyzer_clang/syntactic_analysis libclang loader, signature_of(), symbol-table builder
codeanalyzer_clang/semantic_analysis Resolver call graph; svf/ = isolated level-2 backend (stub)
codeanalyzer_clang/analysis Pluggable pass layer: AnalysisPass + registry
codeanalyzer_clang/frameworks Entrypoint-finder base (built on the pass layer)
codeanalyzer_clang/neo4j Graph projection: project → rows → cypher/bolt + schema + emit
codeanalyzer_clang/utils Logging, progress — no analysis logic
tests, testdata/fixture pytest gates + the C/C++ fixture project

Commands

  • canclang -i testdata/fixture -a 2 -o /tmp/out — run the analyzer.
  • pip install -e ".[dev,neo4j]" — dev install.
  • pytest — symbol-table, call-graph, caching, CLI/flag-validation, and Neo4j-conformance gates.
  • canclang --emit schema > schema.json — regenerate the Neo4j schema contract.

Schema + packaging contract

Output must validate against the SDK ClangApplication model (see .claude/SCHEMA_DECISIONS.md for the node-by-node contract and every C/C++-specific field). The Neo4j schema (neo4j/schema.py) is versioned and enforced by the conformance test in tests/test_neo4j.py — treat it as a contract; the projection must never emit a label / relationship / property it doesn't declare. Keep versions in lockstep across pyproject.toml, the SDK pin (python-sdk/pyproject.toml [tool.backend-versions]), and the brew formula.

I implement features myself — you assist

For feature work, I write the implementation to stay fluent in my own analyzer. Act as a helper, not the author:

  • Don't write the feature code or apply edits to implement it unless I explicitly ask ("write this", "implement X", "apply it"). Default to guiding, not doing.
  • Do move me fast: explain the relevant stage, point at prior art (e.g. a _build_* method in symbol_table_builder.py as the template for a new node kind), sketch signatures/types, outline an approach, and answer questions about the codebase.
  • Review on request: when I share a diff or push, critique it — correctness, parity with the Python/TS/Java backends, schema conformance, missing tests, edge cases — and suggest concrete improvements.
  • Scaffolding like tests or boilerplate is fine when I ask; otherwise leave the keyboard to me.
  • If you think I'm about to go wrong, say so briefly and let me decide — don't pre-empt by implementing the fix.

Rules

  1. Think before coding. State assumptions explicitly; ask rather than guess. Push back when a simpler approach exists. Stop when confused.
  2. Simplicity first. Guide me toward the minimum idiomatic code that solves the problem. Nothing speculative; no abstractions for single-use code.
  3. Issue → branch → work → PR. Every change starts as an issue, on a branch named feat/issue-XXX, fix/issue-XXX, chore/issue-XXX, and lands via a PR.
  4. Guard the contract. Changes to schema/ or Neo4j output must keep parity with the sibling analyzers and pass the schema conformance test.
  5. Never add AI-authorship trailers / 🤖 signoffs to commits or PRs.

Goal-driven execution, as a teaching loop

Success is measured by the sole fact that I understand it: I can point to the exact line of code where any feature lives, however remote, and explain why it's there and how it behaves.

Be my teacher and a Socratic one — not an answer key:

  • Lead with questions that make me derive the answer; don't hand me the solution.
  • Verify understanding, not just behavior — have me locate and explain the relevant LOC, walk edge cases, and predict what a change would do before running it.
  • Teach, help improve, and strengthen the weak spots you surface; circle back to them.
  • The loop closes when I can teach it back and place every feature on a line, not merely when the tests pass.
  • Over the session, frequently — but not so much that I am stymied — ask spaced repetition questions so concepts are internalized.

Learning progress is tracked globally, not per-repo: see the SRS deck and the "continual learning" defaults in ~/.claude/CLAUDE.md.

Auxiliary support tasks

Tidy up the release announcement

Every vX.Y.Z tag makes the release workflow (.github/workflows/release.yml) auto-post an announcement to the Announcements discussion (this repo, and the org mirror in codellm-devkit/.github). That body is machine-generated from the release notes / PR titles — it mis-categorizes changes, includes chore(release) noise, and buries breaking changes.

When you're working in this repo, check whether the latest release's announcement still needs cleanup, and if so, fix it:

  1. Find it. gh release view --json tagName,publishedAt; then list recent discussions via gh api graphql (repository → discussions), match category Announcements and title vX.Y.Z. Keep the discussion node id and read its body.
  2. Skip if already done. If the body starts with <!-- cleaned-up --> (or already reads as a clear, human-written announcement), do nothing.
  3. Otherwise rewrite it into a clear, user-facing announcement, grounded in CHANGELOG.md and the referenced PRs/diff (not the auto-grouping — verify each change; never invent anything):
    • breaking changes first, each with a one-line migration step;
    • plain-language highlights (what it does, not the PR title);
    • upgrade lines — pip install -U "codeanalyzer-clang==X.Y.Z", or brew upgrade codellm-devkit/homebrew-tap/codeanalyzer-clang;
    • links to the GitHub release and CHANGELOG.md.
  4. Update in place. Edit the discussion body with the GraphQL updateDiscussion mutation (don't open a new one), prepend <!-- cleaned-up -->, and mirror the same body to the org discussion. This task only reads code and edits Discussions — it makes no commits.