Agent guidance for codellm-devkit/codeanalyzer-clang (canclang).
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.
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:
- materialize (
core.__enter__) — locate acompile_commands.jsoncompilation database (accurate include paths/flags) viasyntactic_analysis/clang_loader.py; degrade to a language default when absent. - 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. - call graph (
semantic_analysis/call_graph.py) — at-a 2, aggregate the resolved call sites into identity-only edges (provenance=["clang"]);--svfmerges level-2 points-to edges (semantic_analysis/svf/, stubbed). - classify externals + run_pipeline (
analysis/registry.py) — pluggable passes contribute entrypoints/synthetic edges; the base app is cached, pass output is not. - cache (
.codeanalyzer-clang/analysis_cache.json) — content-hash per-file cache; re-analysis only rebuilds changed files. - output (
__main__.py,neo4j/) —analysis.json, agraph.cyphersnapshot, 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.
| 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 |
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.
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.
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 insymbol_table_builder.pyas 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.
- Think before coding. State assumptions explicitly; ask rather than guess. Push back when a simpler approach exists. Stop when confused.
- Simplicity first. Guide me toward the minimum idiomatic code that solves the problem. Nothing speculative; no abstractions for single-use code.
- 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. - Guard the contract. Changes to
schema/or Neo4j output must keep parity with the sibling analyzers and pass the schema conformance test. - Never add AI-authorship trailers /
🤖signoffs to commits or PRs.
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.
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:
- Find it.
gh release view --json tagName,publishedAt; then list recent discussions viagh api graphql(repository → discussions), match categoryAnnouncementsand titlevX.Y.Z. Keep the discussion nodeidand read itsbody. - Skip if already done. If the body starts with
<!-- cleaned-up -->(or already reads as a clear, human-written announcement), do nothing. - Otherwise rewrite it into a clear, user-facing announcement, grounded in
CHANGELOG.mdand 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", orbrew upgrade codellm-devkit/homebrew-tap/codeanalyzer-clang; - links to the GitHub release and
CHANGELOG.md.
- Update in place. Edit the discussion body with the GraphQL
updateDiscussionmutation (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.