codellm-devkit/codeanalyzer-typescript/analyzing-cants-graphs
Claude Code and agent skill by codellm-devkit/codeanalyzer-typescript
- community
Checking that the SKILL.md file is still in the repository.
Use when querying the cants (codeanalyzer-typescript) Neo4j graph — call-graph, structure, inheritance, control/data-flow, dependency/SBOM, or configuration-use questions, or when writing any Cypher over schema v2's projection.
Install
npx vskill@latest install codellm-devkit/codeanalyzer-typescript/analyzing-cants-graphsSKILL.md
Analyzing cants graphs (schema v2, Neo4j projection)
One additive tree + typed edge overlays, projected as a property graph (--emit neo4j → graph.cypher snapshot or live Bolt push). Vocabulary is fixed — never guess a label, property, or key — it is all in references/vocabulary.md, generated from src/build/neo4j/schema.ts and enforced by test/neo4j-schema.test.ts. The recipe catalogue (structure, calls, inheritance, control/data-flow, slicing, dependencies/artifacts/SBOM, configuration) is references/analyses.md; every recipe states its minimum -a level.
This skill mirrors codeanalyzer-python's docs/skills/analyzing-canpy-graphs/, which documents field mechanics. The traps below go further: each one is a place where a mechanically-correct query answers the wrong question.
What exists at which level
-a | tree | edges | |
|---|---|---|---|
| 1 | callables + call/config_access body nodes (callee null) | TS_DECLARES, TS_HAS_METHOD, TS_HAS_FIELD, TS_HAS_BODY_NODE, TS_EXTENDS, TS_IMPLEMENTS | |
| 2 | callee resolved | TS_CALLS (prov tsc/defuse/import), TS_RESOLVES_TO, TS_USES_CONFIG (literal tier) | |
| 3 | full statement body, @entry/@exit | TS_CFG_NEXT, TS_CDG, TS_DDG (prov reaching-defs); TS_USES_CONFIG widens (+dataflow, intra) | |
| 4 | @formal_in:N/@formal_out/actual_in:N/actual_out vertices | TS_PARAM_IN, TS_PARAM_OUT, TS_SUMMARY, TS_DDG widened (+points-to); TS_USES_CONFIG widens further (+dataflow, interprocedural — same prov tag as intra) |
The repository-artifact layer — Artifact/Package/ConfigKey and every edge among them (HAS_ARTIFACT, DECLARES_DEPENDENCY, LOCKS, DEFINES_CONFIG, TS_PROVIDES, TS_UNRESOLVED_IMPORT) — is L1 data, level-free: identical at every -a. So are the module binding edges TS_IMPORTS / TS_RE_EXPORTS, exports_json on TSModule, and parameters_json on TSCallable. TS_READS_CONFIG_UNRESOLVED exists from -a 2 and SHRINKS as the level rises (a read resolved at a higher tier moves to TS_USES_CONFIG). Neo4j is always projected full-depth for the level actually analyzed (--emit neo4j + -a/--graphs together is a CLI error, not a partial graph).
Identity in 20 seconds
- `can://` ids are opaque — match on properties, never delimiter-split an id.
- Code ids are two-tier: durable at callable depth and above —
can://<app>/<lang>/<fileKeyWithExt>/<memberPath>, e.g. can://artifacts-app/typescript/src/config.ts/readHost — and ordinal below it, appended with @: line:col for statements/calls/config_access, @entry/@exit/@formal_in:N/ @formal_out for synthetic vertices, <callsite-local>/actual_in:N/<callsite-local>/actual_out for actuals. Example: can://artifacts-app/typescript/src/config.ts/readHost@12:3.
- The application is the OUTERMOST segment, so
can://<app>/is one prefix over both language
namespaces. Scope a query with (n:TSCanNode) WHERE n.id STARTS WITH 'can://<app>/' — one predicate, whole application. Never enumerate can://typescript/… and can://javascript/…: that shape is gone, and naming only one of the two markers answered for half the graph.
- `Artifact.id` is language-neutral:
can://<app>/artifact/<path>—artifactsits where a
language would, so a TS and a Python analysis of one monorepo mint the same id for the same file (see the --app-name trap below). Artifact nodes carry NO marker label: they are shared with the sibling analyzers, so no analyzer's scoped delete may sweep them.
- The first segment is the app, not the language. An application named
typescriptis legal
and yields can://typescript/javascript/…. Read the language positionally (segment 2) or, better, from node properties — never by splitting on the first segment.
- `Package.id` is a purl:
pkg:npm/<name>, scopedpkg:npm/%40scope/<name>— the
cross-language SBOM join key.
- `ConfigKey.id` is
<artifactId>@key/<dotted>(numeric segments for array indices, e.g.
services.web.ports.0). The key property is always the bare dotted name; the id alone can carry an internal arg./env. disambiguation prefix (see the ARG trap below) — never match on a key substring expecting to see that prefix.
Standing traps
`direct` — dependency surface vs. supply chain.
Your dependency surface and your dependency supply chain are different questions."What does this app declare?" filtersdirect: true. "What actually ships / where doesCVE-XXXX live?" needs the transitives — a vulnerable package four levels down is in yourbundle whether or not you named it.
Both recipes live side by side in references/analyses.md §6. Scoping note: every direct: false record is a top-level lock entry carrying kind: "runtime" — a lock file does not record why a package is present, so this layer asserts the safe default instead of inferring one.
Measured, so the magnitude is concrete, not asserted (this repo, codeanalyzer-typescript itself, -a 1 — an illustrative sample, not a universal constant): 185 dependency records, 16 direct: true / 169 direct: false — 91% of records are transitive, invisible to the declared-surface query. That is why the two recipes above return wildly different answers on the same repo. Keeping them is cheap: dependencies[] is ~41 KB of a 2.67 MB payload (~1.5%), so the transitive records are not what drives payload growth here — verbatim artifact source text is.
`config_reads` shrinks as `-a` rises — deliberately. It is the layer's one non-monotonic section (config_uses is the opposite: asserted superset-monotonic, L2 ⊆ L3 ⊆ L4 — verified on this branch's fixture at 21/25/29 uses and 10/9/8 reads across L2/L3/L4). A read unresolved at the literal tier can close at a higher dataflow tier, so it moves from config_reads into config_uses as the level climbs. Diffing two levels and seeing a config_reads record vanish means "resolved at the higher tier," never "fixed in the code." Pair the two: config_uses is TS_USES_CONFIG (body node → key); config_reads is TS_READS_CONFIG_UNRESOLVED (application → the read root's ghost or the resolved callee), one edge per distinct (target, key, reason) — several sites collapse onto it, so its count is a floor on the JSON list's, never equal (analyses.md §7).
`--app-name` is the cross-analyzer join precondition. Artifact ids are language-neutral specifically so a TS and a Python analysis of one repository MERGE onto the same :Artifact node — but only if both runs pinned the same --app-name. Two analyzers pointed at different subdirectories of one monorepo, or run with mismatched app names, will silently disagree on every artifact and package id and never merge.
`source` is the whole file, in both projections. There is no byte cap: an artifact's text is captured complete or, under --no-artifact-text, not at all (source: ""). Artifact carries source in Neo4j as well as in analysis.json, matching codeanalyzer-python — so WHERE a.source CONTAINS "..." works against the graph. Compare on sha256 when you want identity rather than content; it is always the hash of the full file.
`value` is present by default and absent under `--no-artifact-text` — an absent ConfigKey.value is a capture setting, not an empty or unset config key. Check references/vocabulary.md's extraction field before treating a missing value as "this key has no value."
Dockerfile `ARG` is not bindable. ENV mints an env-namespace key that a process.env read can join; ARG mints a dockerfile-namespace key that deliberately never joins one — it exists at build time only. A query that unions the two namespaces (or filters ConfigKey without checking namespace) will report build-time values as runtime configuration. This is also why the config-key id sometimes carries an arg./env. prefix (see Identity above): the same bare name can mint twice on one artifact — a Dockerfile's ARG VERSION beside its own ENV VERSION=$VERSION — and only the id, never the key property, disambiguates them.
`config_access` nodes are reads, not calls. They carry root/key? (JSON only — not projected to Neo4j) but no callee at any level, so joining them through TS_RESOLVES_TO finds nothing — that relationship only ever targets a call node's resolution. Follow TS_USES_CONFIG from the body node straight to the ConfigKey instead.
`TSExternal` ghosts come in two grains that do not share an id. Call-graph targets (.../@external/<module>/<name>) and dependency/import-hygiene ghosts (.../@external/<module>, no name segment) sit on the same label but different ids — join them by the module property when a query needs both (references/vocabulary.md, "External ghosts").
The import graph is aggregated, the export list is not. TS_IMPORTS / TS_RE_EXPORTS carry ONE relationship per (module, target) with every binding folded into spellings[] / imported_names[] (exported_names[]) / aliases[] / type_only_names[] — per-binding spans and kinds are not on the edge. exports_json on TSModule is the verbatim exports[] list (including export { x as y } locals, which are no edge); imports have no such property — read analysis.json for per-binding detail. An unresolved relative import (./missing) has no node to land on and is absent from the graph while present in JSON; externals land on the same @external/<package root> ghost TS_PROVIDES / TS_UNRESOLVED_IMPORT address, builtins under their own spelling (@external/node:fs). A "reachable from the entrypoints" query still needs a root set: is_entrypoint on TSCallable/TSClass (analyses.md §2).
Every trap above is a silent-empty-result failure, not an error: Cypher does not reject a query naming a nonexistent label, property, or relationship — it just returns nothing. Cross-check references/vocabulary.md whenever a query returns an empty result you did not expect.
Security
- Security scanPassed, 93 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingdocs/skills/analyzing-cants-graphs/SKILL.md
Popularity
- Repository stars
- 63
- Repository forks
- 9
- 7-day trend score
- 21
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/codellm-devkit/codeanalyzer-typescript/analyzing-cants-graphs)