@optave/codegraph 3.1.3 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +38 -84
  2. package/package.json +13 -8
  3. package/src/ast-analysis/engine.js +32 -12
  4. package/src/ast-analysis/shared.js +6 -5
  5. package/src/cli/commands/ast.js +22 -0
  6. package/src/cli/commands/audit.js +45 -0
  7. package/src/cli/commands/batch.js +68 -0
  8. package/src/cli/commands/branch-compare.js +21 -0
  9. package/src/cli/commands/build.js +26 -0
  10. package/src/cli/commands/cfg.js +26 -0
  11. package/src/cli/commands/check.js +74 -0
  12. package/src/cli/commands/children.js +28 -0
  13. package/src/cli/commands/co-change.js +67 -0
  14. package/src/cli/commands/communities.js +19 -0
  15. package/src/cli/commands/complexity.js +46 -0
  16. package/src/cli/commands/context.js +30 -0
  17. package/src/cli/commands/cycles.js +32 -0
  18. package/src/cli/commands/dataflow.js +28 -0
  19. package/src/cli/commands/deps.js +12 -0
  20. package/src/cli/commands/diff-impact.js +26 -0
  21. package/src/cli/commands/embed.js +30 -0
  22. package/src/cli/commands/export.js +78 -0
  23. package/src/cli/commands/exports.js +14 -0
  24. package/src/cli/commands/flow.js +32 -0
  25. package/src/cli/commands/fn-impact.js +26 -0
  26. package/src/cli/commands/impact.js +12 -0
  27. package/src/cli/commands/info.js +76 -0
  28. package/src/cli/commands/map.js +19 -0
  29. package/src/cli/commands/mcp.js +18 -0
  30. package/src/cli/commands/models.js +19 -0
  31. package/src/cli/commands/owners.js +25 -0
  32. package/src/cli/commands/path.js +36 -0
  33. package/src/cli/commands/plot.js +89 -0
  34. package/src/cli/commands/query.js +45 -0
  35. package/src/cli/commands/registry.js +100 -0
  36. package/src/cli/commands/roles.js +30 -0
  37. package/src/cli/commands/search.js +42 -0
  38. package/src/cli/commands/sequence.js +28 -0
  39. package/src/cli/commands/snapshot.js +66 -0
  40. package/src/cli/commands/stats.js +15 -0
  41. package/src/cli/commands/structure.js +33 -0
  42. package/src/cli/commands/triage.js +78 -0
  43. package/src/cli/commands/watch.js +12 -0
  44. package/src/cli/commands/where.js +20 -0
  45. package/src/cli/index.js +124 -0
  46. package/src/cli/shared/open-graph.js +13 -0
  47. package/src/cli/shared/options.js +59 -0
  48. package/src/cli/shared/output.js +1 -0
  49. package/src/cli.js +11 -1522
  50. package/src/db/connection.js +130 -7
  51. package/src/{db.js → db/index.js} +17 -5
  52. package/src/db/migrations.js +42 -1
  53. package/src/db/query-builder.js +20 -12
  54. package/src/db/repository/base.js +201 -0
  55. package/src/db/repository/graph-read.js +7 -4
  56. package/src/db/repository/in-memory-repository.js +575 -0
  57. package/src/db/repository/index.js +5 -1
  58. package/src/db/repository/nodes.js +60 -6
  59. package/src/db/repository/sqlite-repository.js +219 -0
  60. package/src/domain/analysis/context.js +408 -0
  61. package/src/domain/analysis/dependencies.js +341 -0
  62. package/src/domain/analysis/exports.js +134 -0
  63. package/src/domain/analysis/impact.js +466 -0
  64. package/src/domain/analysis/module-map.js +322 -0
  65. package/src/domain/analysis/roles.js +45 -0
  66. package/src/domain/analysis/symbol-lookup.js +238 -0
  67. package/src/domain/graph/builder/context.js +85 -0
  68. package/src/domain/graph/builder/helpers.js +218 -0
  69. package/src/domain/graph/builder/incremental.js +178 -0
  70. package/src/domain/graph/builder/pipeline.js +130 -0
  71. package/src/domain/graph/builder/stages/build-edges.js +297 -0
  72. package/src/domain/graph/builder/stages/build-structure.js +113 -0
  73. package/src/domain/graph/builder/stages/collect-files.js +44 -0
  74. package/src/domain/graph/builder/stages/detect-changes.js +413 -0
  75. package/src/domain/graph/builder/stages/finalize.js +139 -0
  76. package/src/domain/graph/builder/stages/insert-nodes.js +195 -0
  77. package/src/domain/graph/builder/stages/parse-files.js +28 -0
  78. package/src/domain/graph/builder/stages/resolve-imports.js +143 -0
  79. package/src/domain/graph/builder/stages/run-analyses.js +44 -0
  80. package/src/domain/graph/builder.js +11 -0
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/domain/graph/cycles.js +82 -0
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +3 -3
  85. package/src/{watcher.js → domain/graph/watcher.js} +10 -150
  86. package/src/{parser.js → domain/parser.js} +5 -5
  87. package/src/domain/queries.js +48 -0
  88. package/src/domain/search/generator.js +163 -0
  89. package/src/domain/search/index.js +13 -0
  90. package/src/domain/search/models.js +218 -0
  91. package/src/domain/search/search/cli-formatter.js +151 -0
  92. package/src/domain/search/search/filters.js +46 -0
  93. package/src/domain/search/search/hybrid.js +121 -0
  94. package/src/domain/search/search/keyword.js +68 -0
  95. package/src/domain/search/search/prepare.js +66 -0
  96. package/src/domain/search/search/semantic.js +145 -0
  97. package/src/domain/search/stores/fts5.js +27 -0
  98. package/src/domain/search/stores/sqlite-blob.js +24 -0
  99. package/src/domain/search/strategies/source.js +14 -0
  100. package/src/domain/search/strategies/structured.js +43 -0
  101. package/src/domain/search/strategies/text-utils.js +43 -0
  102. package/src/extractors/csharp.js +10 -2
  103. package/src/extractors/go.js +3 -1
  104. package/src/extractors/helpers.js +71 -0
  105. package/src/extractors/java.js +9 -2
  106. package/src/extractors/javascript.js +39 -2
  107. package/src/extractors/php.js +3 -1
  108. package/src/extractors/python.js +14 -3
  109. package/src/extractors/rust.js +3 -1
  110. package/src/{ast.js → features/ast.js} +8 -8
  111. package/src/{audit.js → features/audit.js} +16 -44
  112. package/src/{batch.js → features/batch.js} +6 -5
  113. package/src/{boundaries.js → features/boundaries.js} +2 -2
  114. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  115. package/src/{cfg.js → features/cfg.js} +11 -12
  116. package/src/{check.js → features/check.js} +13 -30
  117. package/src/{cochange.js → features/cochange.js} +5 -5
  118. package/src/{communities.js → features/communities.js} +18 -90
  119. package/src/{complexity.js → features/complexity.js} +13 -13
  120. package/src/{dataflow.js → features/dataflow.js} +12 -13
  121. package/src/features/export.js +378 -0
  122. package/src/{flow.js → features/flow.js} +4 -4
  123. package/src/features/graph-enrichment.js +327 -0
  124. package/src/{manifesto.js → features/manifesto.js} +6 -6
  125. package/src/{owners.js → features/owners.js} +2 -2
  126. package/src/{sequence.js → features/sequence.js} +16 -52
  127. package/src/{snapshot.js → features/snapshot.js} +8 -7
  128. package/src/{structure.js → features/structure.js} +20 -45
  129. package/src/{triage.js → features/triage.js} +27 -79
  130. package/src/graph/algorithms/bfs.js +49 -0
  131. package/src/graph/algorithms/centrality.js +16 -0
  132. package/src/graph/algorithms/index.js +5 -0
  133. package/src/graph/algorithms/louvain.js +26 -0
  134. package/src/graph/algorithms/shortest-path.js +41 -0
  135. package/src/graph/algorithms/tarjan.js +49 -0
  136. package/src/graph/builders/dependency.js +110 -0
  137. package/src/graph/builders/index.js +3 -0
  138. package/src/graph/builders/structure.js +40 -0
  139. package/src/graph/builders/temporal.js +33 -0
  140. package/src/graph/classifiers/index.js +2 -0
  141. package/src/graph/classifiers/risk.js +85 -0
  142. package/src/graph/classifiers/roles.js +64 -0
  143. package/src/graph/index.js +13 -0
  144. package/src/graph/model.js +230 -0
  145. package/src/index.cjs +16 -0
  146. package/src/index.js +42 -219
  147. package/src/{native.js → infrastructure/native.js} +3 -1
  148. package/src/infrastructure/result-formatter.js +2 -21
  149. package/src/mcp/index.js +2 -0
  150. package/src/mcp/middleware.js +26 -0
  151. package/src/mcp/server.js +128 -0
  152. package/src/{mcp.js → mcp/tool-registry.js} +6 -675
  153. package/src/mcp/tools/ast-query.js +14 -0
  154. package/src/mcp/tools/audit.js +21 -0
  155. package/src/mcp/tools/batch-query.js +11 -0
  156. package/src/mcp/tools/branch-compare.js +12 -0
  157. package/src/mcp/tools/cfg.js +21 -0
  158. package/src/mcp/tools/check.js +43 -0
  159. package/src/mcp/tools/co-changes.js +20 -0
  160. package/src/mcp/tools/code-owners.js +12 -0
  161. package/src/mcp/tools/communities.js +15 -0
  162. package/src/mcp/tools/complexity.js +18 -0
  163. package/src/mcp/tools/context.js +17 -0
  164. package/src/mcp/tools/dataflow.js +26 -0
  165. package/src/mcp/tools/diff-impact.js +24 -0
  166. package/src/mcp/tools/execution-flow.js +26 -0
  167. package/src/mcp/tools/export-graph.js +57 -0
  168. package/src/mcp/tools/file-deps.js +12 -0
  169. package/src/mcp/tools/file-exports.js +13 -0
  170. package/src/mcp/tools/find-cycles.js +15 -0
  171. package/src/mcp/tools/fn-impact.js +15 -0
  172. package/src/mcp/tools/impact-analysis.js +12 -0
  173. package/src/mcp/tools/index.js +71 -0
  174. package/src/mcp/tools/list-functions.js +14 -0
  175. package/src/mcp/tools/list-repos.js +11 -0
  176. package/src/mcp/tools/module-map.js +6 -0
  177. package/src/mcp/tools/node-roles.js +14 -0
  178. package/src/mcp/tools/path.js +12 -0
  179. package/src/mcp/tools/query.js +30 -0
  180. package/src/mcp/tools/semantic-search.js +65 -0
  181. package/src/mcp/tools/sequence.js +17 -0
  182. package/src/mcp/tools/structure.js +15 -0
  183. package/src/mcp/tools/symbol-children.js +14 -0
  184. package/src/mcp/tools/triage.js +35 -0
  185. package/src/mcp/tools/where.js +13 -0
  186. package/src/{commands → presentation}/audit.js +2 -2
  187. package/src/{commands → presentation}/batch.js +1 -1
  188. package/src/{commands → presentation}/branch-compare.js +2 -2
  189. package/src/{commands → presentation}/cfg.js +1 -1
  190. package/src/{commands → presentation}/check.js +6 -6
  191. package/src/presentation/colors.js +44 -0
  192. package/src/{commands → presentation}/communities.js +1 -1
  193. package/src/{commands → presentation}/complexity.js +1 -1
  194. package/src/{commands → presentation}/dataflow.js +1 -1
  195. package/src/presentation/export.js +444 -0
  196. package/src/{commands → presentation}/flow.js +2 -2
  197. package/src/{commands → presentation}/manifesto.js +4 -4
  198. package/src/{commands → presentation}/owners.js +1 -1
  199. package/src/presentation/queries-cli/exports.js +46 -0
  200. package/src/presentation/queries-cli/impact.js +198 -0
  201. package/src/presentation/queries-cli/index.js +5 -0
  202. package/src/presentation/queries-cli/inspect.js +334 -0
  203. package/src/presentation/queries-cli/overview.js +197 -0
  204. package/src/presentation/queries-cli/path.js +58 -0
  205. package/src/presentation/queries-cli.js +27 -0
  206. package/src/{commands → presentation}/query.js +1 -1
  207. package/src/presentation/result-formatter.js +144 -0
  208. package/src/presentation/sequence-renderer.js +43 -0
  209. package/src/{commands → presentation}/sequence.js +2 -2
  210. package/src/{commands → presentation}/structure.js +2 -2
  211. package/src/presentation/table.js +47 -0
  212. package/src/{commands → presentation}/triage.js +1 -1
  213. package/src/{viewer.js → presentation/viewer.js} +68 -382
  214. package/src/{constants.js → shared/constants.js} +1 -1
  215. package/src/shared/errors.js +78 -0
  216. package/src/shared/file-utils.js +153 -0
  217. package/src/shared/generators.js +125 -0
  218. package/src/shared/hierarchy.js +27 -0
  219. package/src/shared/normalize.js +59 -0
  220. package/src/builder.js +0 -1486
  221. package/src/cycles.js +0 -137
  222. package/src/embedder.js +0 -1097
  223. package/src/export.js +0 -681
  224. package/src/queries-cli.js +0 -866
  225. package/src/queries.js +0 -2289
  226. /package/src/{config.js → infrastructure/config.js} +0 -0
  227. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  228. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  229. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  230. /package/src/{commands → presentation}/cochange.js +0 -0
  231. /package/src/{kinds.js → shared/kinds.js} +0 -0
  232. /package/src/{paginate.js → shared/paginate.js} +0 -0
package/README.md CHANGED
@@ -31,13 +31,11 @@
31
31
 
32
32
  ## The Problem
33
33
 
34
- AI agents are the primary interface to large codebasesand they're flying blind.
34
+ AI agents face an impossible trade-off. They either spend thousands of tokens reading files to understand a codebase's structure blowing up their context window until quality degrades — or they assume how things work, and the assumptions are often wrong. Either way, things break. The larger the codebase, the worse it gets.
35
35
 
36
- An agent burns a great portion of its token budget on `grep`, `find`, `cat`re-discovering the same structure every session. It modifies `parseConfig()` without knowing 9 files import it. It hallucinates a function signature because it never saw the real one. Multiply that by every session, every developer, every repo.
36
+ An agent modifies a function without knowing 9 files import it. It misreads what a helper does and builds logic on top of that misunderstanding. It leaves dead code behind after a refactor. The PR gets opened, and your reviewer human or automated — flags the same structural issues again and again: _"this breaks 14 callers,"_ _"that function already exists,"_ _"this export is now dead."_ If the reviewer catches it, that's multiple rounds of back-and-forth. If they don't, it can ship to production. Multiply that by every PR, every developer, every repo.
37
37
 
38
- Developers aren't much better off. They inherit projects and spend days grepping to understand what calls what. Architects draw boundary rules that erode within weeks because nothing enforces them. CI catches test failures but can't tell you _"this change silently affects 14 callers across 9 files."_
39
-
40
- The information exists — it's in the code itself. But without a structured map, agents hallucinate, developers guess, and architecture degrades one unreviewed change at a time.
38
+ The information to prevent these issues exists it's in the code itself. But without a structured map, agents lack the context to get it right consistently, reviewers waste cycles on preventable issues, and architecture degrades one unreviewed change at a time.
41
39
 
42
40
  ## What Codegraph Does
43
41
 
@@ -50,7 +48,7 @@ It parses your code with [tree-sitter](https://tree-sitter.github.io/) (native R
50
48
  - **CI gates** — `check` and `manifesto` commands enforce quality thresholds with exit codes
51
49
  - **Programmatic API** — embed codegraph in your own tools via `npm install`
52
50
 
53
- Instead of an agent burning 30 tool calls to maybe discover half your dependencies, it gets _"this function has 14 callers across 9 files"_ in one MCP call. Instead of hoping architecture rules are followed, you enforce them. Instead of finding breakage in production, `diff-impact --staged` catches it before you commit.
51
+ Instead of an agent editing code without structural context and letting reviewers catch the fallout, it knows _"this function has 14 callers across 9 files"_ before it touches anything. Dead exports, circular dependencies, and boundary violations surface during development not during review. The result: PRs that need fewer review rounds.
54
52
 
55
53
  **Free. Open source. Fully local.** Zero network calls, zero telemetry. Your code stays on your machine. When you want deeper intelligence, bring your own LLM provider — your code only goes where you choose to send it.
56
54
 
@@ -62,18 +60,18 @@ cd your-project
62
60
  codegraph build
63
61
  ```
64
62
 
65
- No config files, no Docker, no JVM, no API keys, no accounts. Point your agent at the MCP server and it has full structural awareness of your codebase.
63
+ No config files, no Docker, no JVM, no API keys, no accounts. Point your agent at the MCP server and it has structural awareness of your codebase.
66
64
 
67
65
  ### Why it matters
68
66
 
69
67
  | | Without codegraph | With codegraph |
70
68
  |---|---|---|
71
- | **AI agents** | Spend 20+ tool calls per session re-discovering code structure | Get full dependency context in one MCP call |
72
- | **AI agents** | Modify `parseConfig()` without knowing 9 files import it | `fn-impact parseConfig` shows every caller before the edit |
73
- | **AI agents** | Hallucinate function signatures and miss callers | `context <name> -T` returns source, deps, callers, and tests no guessing |
69
+ | **Code review** | Reviewers flag broken callers, dead code, and boundary violations round after round | Structural issues are caught during development PRs pass review with fewer rounds |
70
+ | **AI agents** | Modify `parseConfig()` without knowing 9 files import it — reviewer catches it | `fn-impact parseConfig` shows every caller before the edit — agent fixes it proactively |
71
+ | **AI agents** | Leave dead exports and duplicate helpers behind after refactors | Dead code, cycles, and duplicates surface in real time via hooks and MCP queries |
72
+ | **AI agents** | Produce code that works but doesn't fit the codebase structure | `context <name> -T` returns source, deps, callers, and tests — the agent writes code that fits |
74
73
  | **CI pipelines** | Catch test failures but miss structural degradation | `check --staged` fails the build when blast radius or complexity thresholds are exceeded |
75
74
  | **Developers** | Inherit a codebase and grep for hours to understand what calls what | `context handleAuth -T` gives the same structured view agents use |
76
- | **Developers** | Rename a function, break 14 call sites silently | `diff-impact --staged` catches breakage before you commit |
77
75
  | **Architects** | Draw boundary rules that erode within weeks | `manifesto` and `boundaries` enforce architecture rules on every commit |
78
76
 
79
77
  ### Feature comparison
@@ -117,9 +115,9 @@ No config files, no Docker, no JVM, no API keys, no accounts. Point your agent a
117
115
  | | Differentiator | In practice |
118
116
  |---|---|---|
119
117
  | **🤖** | **AI-first architecture** | 30-tool [MCP server](https://modelcontextprotocol.io/) — agents query the graph directly instead of scraping the filesystem. One call replaces 20+ grep/find/cat invocations |
120
- | **🏷️** | **Role classification** | Every symbol auto-tagged as `entry`/`core`/`utility`/`adapter`/`dead`/`leaf` — agents instantly know what they're looking at without reading the code |
118
+ | **🏷️** | **Role classification** | Every symbol auto-tagged as `entry`/`core`/`utility`/`adapter`/`dead`/`leaf` — agents understand a symbol's architectural role without reading surrounding code |
121
119
  | **🔬** | **Function-level, not just files** | Traces `handleAuth()` → `validateToken()` → `decryptJWT()` and shows 14 callers across 9 files break if `decryptJWT` changes |
122
- | **⚡** | **Always-fresh graph** | Three-tier change detection: journal (O(changed)) → mtime+size (O(n) stats) → hash (O(changed) reads). Sub-second rebuilds — agents always work with current data |
120
+ | **⚡** | **Always-fresh graph** | Three-tier change detection: journal (O(changed)) → mtime+size (O(n) stats) → hash (O(changed) reads). Sub-second rebuilds — agents work with current data |
123
121
  | **💥** | **Git diff impact** | `codegraph diff-impact` shows changed functions, their callers, and full blast radius — enriched with historically coupled files from git co-change analysis. Ships with a GitHub Actions workflow |
124
122
  | **🌐** | **Multi-language, one graph** | JS/TS + Python + Go + Rust + Java + C# + PHP + Ruby + HCL in a single graph — agents don't need per-language tools |
125
123
  | **🧠** | **Hybrid search** | BM25 keyword + semantic embeddings fused via RRF — `hybrid` (default), `semantic`, or `keyword` mode; multi-query via `"auth; token; JWT"` |
@@ -479,6 +477,8 @@ codegraph registry remove <name> # Unregister
479
477
  | `-f, --file <path>` | Scope to a specific file (`fn`, `context`, `where`) |
480
478
  | `--mode <mode>` | Search mode: `hybrid` (default), `semantic`, or `keyword` (`search`) |
481
479
  | `--ndjson` | Output as newline-delimited JSON (one object per line) |
480
+ | `--table` | Output as auto-column aligned table |
481
+ | `--csv` | Output as CSV (RFC 4180, nested objects flattened) |
482
482
  | `--limit <n>` | Limit number of results |
483
483
  | `--offset <n>` | Skip first N results (pagination) |
484
484
  | `--rrf-k <n>` | RRF smoothing constant for multi-query search (default 60) |
@@ -562,14 +562,14 @@ Self-measured on every release via CI ([build benchmarks](generated/benchmarks/B
562
562
 
563
563
  | Metric | Latest |
564
564
  |---|---|
565
- | Build speed (native) | **5.2 ms/file** |
566
- | Build speed (WASM) | **15 ms/file** |
567
- | Query time | **4ms** |
568
- | No-op rebuild (native) | **6ms** |
569
- | 1-file rebuild (native) | **296ms** |
570
- | Query: fn-deps | **0.8ms** |
571
- | Query: path | **0.8ms** |
572
- | ~50,000 files (est.) | **~260.0s build** |
565
+ | Build speed (native) | **3.5 ms/file** |
566
+ | Build speed (WASM) | **9.6 ms/file** |
567
+ | Query time | **3ms** |
568
+ | No-op rebuild (native) | **9ms** |
569
+ | 1-file rebuild (native) | **265ms** |
570
+ | Query: fn-deps | **0.9ms** |
571
+ | Query: path | **0.9ms** |
572
+ | ~50,000 files (est.) | **~175.0s build** |
573
573
 
574
574
  Metrics are normalized per file for cross-version comparability. Times above are for a full initial build — incremental rebuilds only re-parse changed files.
575
575
 
@@ -605,16 +605,16 @@ codegraph mcp --repos a,b # Restrict to specific repos (implies --multi-rep
605
605
 
606
606
  ### CLAUDE.md / Agent Instructions
607
607
 
608
- Add this to your project's `CLAUDE.md` to help AI agents use codegraph (full template in the [AI Agent Guide](docs/guides/ai-agent-guide.md#claudemd-template)):
608
+ Add this to your project's `CLAUDE.md` to help AI agents use codegraph. Full template with all commands in the [AI Agent Guide](docs/guides/ai-agent-guide.md#claudemd-template).
609
609
 
610
610
  ```markdown
611
- ## Code Navigation
611
+ ## Codegraph
612
612
 
613
- This project uses codegraph. The database is at `.codegraph/graph.db`.
613
+ This project uses codegraph for dependency analysis. The graph is at `.codegraph/graph.db`.
614
614
 
615
- ### Before modifying code, always:
615
+ ### Before modifying code:
616
616
  1. `codegraph where <name>` — find where the symbol lives
617
- 2. `codegraph audit <file-or-function> --quick` — understand the structure
617
+ 2. `codegraph audit --quick <target>` — understand the structure
618
618
  3. `codegraph context <name> -T` — get full context (source, deps, callers)
619
619
  4. `codegraph fn-impact <name> -T` — check blast radius before editing
620
620
 
@@ -622,65 +622,19 @@ This project uses codegraph. The database is at `.codegraph/graph.db`.
622
622
  5. `codegraph diff-impact --staged -T` — verify impact before committing
623
623
 
624
624
  ### Other useful commands
625
- - `codegraph build .` — rebuild the graph (incremental by default)
626
- - `codegraph map` — module overview
627
- - `codegraph query <name> -T` — function call chain (callers + callees)
628
- - `codegraph path <from> <to> -T` — shortest call path between two symbols
629
- - `codegraph deps <file>`file-level dependencies
630
- - `codegraph roles --role dead -T` — find dead code (unreferenced symbols)
631
- - `codegraph roles --role core -T` — find core symbols (high fan-in)
632
- - `codegraph co-change <file>` files that historically change together
633
- - `codegraph complexity -T` — per-function complexity metrics (cognitive, cyclomatic, MI)
634
- - `codegraph communities --drift -T` — module boundary drift analysis
635
- - `codegraph check -T` — pass/fail rule check (CI gate, exit code 1 on fail)
636
- - `codegraph audit <target> -T` — combined structural summary + impact + health in one report
637
- - `codegraph triage -T` — ranked audit priority queue
638
- - `codegraph triage --level file -T` — file-level hotspot analysis
639
- - `codegraph check --staged` — CI validation predicates (exit code 0/1)
640
- - `codegraph batch target1 target2` — batch query multiple targets at once
641
- - `codegraph owners [target]` — CODEOWNERS mapping for symbols
642
- - `codegraph snapshot save <name>` — checkpoint the graph DB before refactoring
643
- - `codegraph branch-compare main HEAD -T` — structural diff between two refs (added/removed/changed symbols)
644
- - `codegraph exports <file>` — per-symbol consumer analysis (who calls each export)
645
- - `codegraph children <name>` — list parameters, properties, constants of a symbol
646
- - `codegraph dataflow <name>` — data flow edges (flows_to, returns, mutates)
647
- - `codegraph cfg <name>` — intraprocedural control flow graph
648
- - `codegraph ast <pattern>` — search stored AST nodes (calls, new, string, regex, throw, await)
649
- - `codegraph plot` — interactive HTML dependency graph viewer
650
- - `codegraph search "<query>"` — hybrid search (requires `codegraph embed`)
651
- - `codegraph search "<query>" --mode keyword` — BM25 keyword search
652
- - `codegraph cycles` — check for circular dependencies
625
+ - `codegraph build .` — rebuild graph (incremental by default)
626
+ - `codegraph map` — module overview · `codegraph stats` — graph health
627
+ - `codegraph query <name> -T` — call chain · `codegraph path <from> <to> -T` — shortest path
628
+ - `codegraph deps <file>` — file deps · `codegraph exports <file> -T` — export consumers
629
+ - `codegraph audit <target> -T` full risk report · `codegraph triage -T` — priority queue
630
+ - `codegraph check --staged` CI gate · `codegraph batch t1 t2 -T --json` — batch query
631
+ - `codegraph search "<query>"` — semantic search · `codegraph cycles` — cycle detection
632
+ - `codegraph roles --role dead -T`dead code · `codegraph complexity -T` — metrics
633
+ - `codegraph dataflow <name> -T` — data flow · `codegraph cfg <name> -T` — control flow
653
634
 
654
635
  ### Flags
655
- - `-T` / `--no-tests` — exclude test files (use by default)
656
- - `-j` / `--json`JSON output for programmatic use
657
- - `-f, --file <path>` — scope to a specific file
658
- - `-k, --kind <kind>` — filter by symbol kind
659
-
660
- ### Semantic search
661
-
662
- Use `codegraph search` to find functions by intent rather than exact name.
663
- When a single query might miss results, combine multiple angles with `;`:
664
-
665
- codegraph search "validate auth; check token; verify JWT"
666
- codegraph search "parse config; load settings" --kind function
667
-
668
- Multi-query search uses Reciprocal Rank Fusion — functions that rank
669
- highly across several queries surface first. This is especially useful
670
- when you're not sure what naming convention the codebase uses.
671
-
672
- When writing multi-queries, use 2-4 sub-queries (2-4 words each) that
673
- attack the problem from different angles. Pick from these strategies:
674
- - **Naming variants**: cover synonyms the author might have used
675
- ("send email; notify user; deliver message")
676
- - **Abstraction levels**: pair high-level intent with low-level operation
677
- ("handle payment; charge credit card")
678
- - **Input/output sides**: cover the read half and write half
679
- ("parse config; apply settings")
680
- - **Domain + technical**: bridge business language and implementation
681
- ("onboard tenant; create organization; provision workspace")
682
-
683
- Use `--kind function` to cut noise. Use `--file <pattern>` to scope.
636
+ - `-T` — exclude test files (use by default) · `-j` — JSON output
637
+ - `-f, --file <path>`scope to file · `-k, --kind <kind>` — filter kind
684
638
  ```
685
639
 
686
640
  ## 📋 Recommended Practices
@@ -823,7 +777,7 @@ See **[ROADMAP.md](docs/roadmap/ROADMAP.md)** for the full development roadmap a
823
777
  1. ~~**Rust Core**~~ — **Complete** (v1.3.0) — native tree-sitter parsing via napi-rs, parallel multi-core parsing, incremental re-parsing, import resolution & cycle detection in Rust
824
778
  2. ~~**Foundation Hardening**~~ — **Complete** (v1.4.0) — parser registry, 12-tool MCP server with multi-repo support, test coverage 62%→75%, `apiKeyCommand` secret resolution, global repo registry
825
779
  3. ~~**Deep Analysis**~~ — **Complete** (v3.0.0) — dataflow analysis (flows_to, returns, mutates), intraprocedural CFG for all 11 languages, stored AST nodes, expanded node/edge types (parameter, property, constant, contains, parameter_of, receiver), GraphML/GraphSON/Neo4j CSV export, interactive HTML viewer, CLI consolidation, stable JSON schema
826
- 4. **Architectural Refactoring** — parser plugin system, repository pattern, pipeline builder, engine strategy, domain errors, curated API
780
+ 4. ~~**Architectural Refactoring**~~ — **Complete** (v3.1.5) unified AST analysis, composable MCP, domain errors, builder pipeline, embedder subsystem, graph model, qualified names, presentation layer, InMemoryRepository, domain directory grouping, CLI composability
827
781
  5. **Natural Language Queries** — `codegraph ask` command, conversational sessions
828
782
  6. **Expanded Language Support** — 8 new languages (12 → 20)
829
783
  7. **GitHub Integration & CI** — reusable GitHub Action, PR review, SARIF output
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@optave/codegraph",
3
- "version": "3.1.3",
3
+ "version": "3.1.5",
4
4
  "description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": {
9
- "import": "./src/index.js"
9
+ "import": "./src/index.js",
10
+ "require": "./src/index.cjs",
11
+ "default": "./src/index.cjs"
12
+ },
13
+ "./cli": {
14
+ "import": "./src/cli.js"
10
15
  },
11
16
  "./package.json": "./package.json"
12
17
  },
@@ -71,12 +76,12 @@
71
76
  },
72
77
  "optionalDependencies": {
73
78
  "@modelcontextprotocol/sdk": "^1.0.0",
74
- "@optave/codegraph-darwin-arm64": "3.1.3",
75
- "@optave/codegraph-darwin-x64": "3.1.3",
76
- "@optave/codegraph-linux-arm64-gnu": "3.1.3",
77
- "@optave/codegraph-linux-x64-gnu": "3.1.3",
78
- "@optave/codegraph-linux-x64-musl": "3.1.3",
79
- "@optave/codegraph-win32-x64-msvc": "3.1.3"
79
+ "@optave/codegraph-darwin-arm64": "3.1.5",
80
+ "@optave/codegraph-darwin-x64": "3.1.5",
81
+ "@optave/codegraph-linux-arm64-gnu": "3.1.5",
82
+ "@optave/codegraph-linux-x64-gnu": "3.1.5",
83
+ "@optave/codegraph-linux-x64-musl": "3.1.5",
84
+ "@optave/codegraph-win32-x64-msvc": "3.1.5"
80
85
  },
81
86
  "devDependencies": {
82
87
  "@biomejs/biome": "^2.4.4",
@@ -17,8 +17,8 @@
17
17
 
18
18
  import path from 'node:path';
19
19
  import { performance } from 'node:perf_hooks';
20
- import { bulkNodeIdsByFile } from '../db.js';
21
- import { debug } from '../logger.js';
20
+ import { bulkNodeIdsByFile } from '../db/index.js';
21
+ import { debug } from '../infrastructure/logger.js';
22
22
  import { computeLOCMetrics, computeMaintainabilityIndex } from './metrics.js';
23
23
  import {
24
24
  AST_TYPE_MAPS,
@@ -38,6 +38,7 @@ import { createDataflowVisitor } from './visitors/dataflow-visitor.js';
38
38
  // ─── Extension sets for quick language-support checks ────────────────────
39
39
 
40
40
  const CFG_EXTENSIONS = buildExtensionSet(CFG_RULES);
41
+ const COMPLEXITY_EXTENSIONS = buildExtensionSet(COMPLEXITY_RULES);
41
42
  const DATAFLOW_EXTENSIONS = buildExtensionSet(DATAFLOW_RULES);
42
43
  const WALK_EXTENSIONS = buildExtensionSet(AST_TYPE_MAPS);
43
44
 
@@ -45,7 +46,7 @@ const WALK_EXTENSIONS = buildExtensionSet(AST_TYPE_MAPS);
45
46
 
46
47
  let _parserModule = null;
47
48
  async function getParserModule() {
48
- if (!_parserModule) _parserModule = await import('../parser.js');
49
+ if (!_parserModule) _parserModule = await import('../domain/parser.js');
49
50
  return _parserModule;
50
51
  }
51
52
 
@@ -74,15 +75,34 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
74
75
  const extToLang = buildExtToLangMap();
75
76
 
76
77
  // ── WASM pre-parse for files that need it ───────────────────────────
77
- // CFG now runs as a visitor in the unified walk, so only dataflow
78
- // triggers WASM pre-parse when no tree exists.
79
- if (doDataflow) {
78
+ // The native engine only handles parsing (symbols, calls, imports).
79
+ // Complexity, CFG, and dataflow all require a WASM tree-sitter tree
80
+ // for their visitor walks. Without this, incremental rebuilds on the
81
+ // native engine silently lose these analyses for changed files (#468).
82
+ if (doComplexity || doCfg || doDataflow) {
80
83
  let needsWasmTrees = false;
81
84
  for (const [relPath, symbols] of fileSymbols) {
82
85
  if (symbols._tree) continue;
83
86
  const ext = path.extname(relPath).toLowerCase();
84
-
85
- if (!symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext)) {
87
+ const defs = symbols.definitions || [];
88
+
89
+ const needsComplexity =
90
+ doComplexity &&
91
+ COMPLEXITY_EXTENSIONS.has(ext) &&
92
+ defs.some((d) => (d.kind === 'function' || d.kind === 'method') && d.line && !d.complexity);
93
+ const needsCfg =
94
+ doCfg &&
95
+ CFG_EXTENSIONS.has(ext) &&
96
+ defs.some(
97
+ (d) =>
98
+ (d.kind === 'function' || d.kind === 'method') &&
99
+ d.line &&
100
+ d.cfg !== null &&
101
+ !Array.isArray(d.cfg?.blocks),
102
+ );
103
+ const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
104
+
105
+ if (needsComplexity || needsCfg || needsDataflow) {
86
106
  needsWasmTrees = true;
87
107
  break;
88
108
  }
@@ -320,7 +340,7 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
320
340
  if (doAst) {
321
341
  const t0 = performance.now();
322
342
  try {
323
- const { buildAstNodes } = await import('../ast.js');
343
+ const { buildAstNodes } = await import('../features/ast.js');
324
344
  await buildAstNodes(db, fileSymbols, rootDir, engineOpts);
325
345
  } catch (err) {
326
346
  debug(`buildAstNodes failed: ${err.message}`);
@@ -331,7 +351,7 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
331
351
  if (doComplexity) {
332
352
  const t0 = performance.now();
333
353
  try {
334
- const { buildComplexityMetrics } = await import('../complexity.js');
354
+ const { buildComplexityMetrics } = await import('../features/complexity.js');
335
355
  await buildComplexityMetrics(db, fileSymbols, rootDir, engineOpts);
336
356
  } catch (err) {
337
357
  debug(`buildComplexityMetrics failed: ${err.message}`);
@@ -342,7 +362,7 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
342
362
  if (doCfg) {
343
363
  const t0 = performance.now();
344
364
  try {
345
- const { buildCFGData } = await import('../cfg.js');
365
+ const { buildCFGData } = await import('../features/cfg.js');
346
366
  await buildCFGData(db, fileSymbols, rootDir, engineOpts);
347
367
  } catch (err) {
348
368
  debug(`buildCFGData failed: ${err.message}`);
@@ -353,7 +373,7 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
353
373
  if (doDataflow) {
354
374
  const t0 = performance.now();
355
375
  try {
356
- const { buildDataflowEdges } = await import('../dataflow.js');
376
+ const { buildDataflowEdges } = await import('../features/dataflow.js');
357
377
  await buildDataflowEdges(db, fileSymbols, rootDir, engineOpts);
358
378
  } catch (err) {
359
379
  debug(`buildDataflowEdges failed: ${err.message}`);
@@ -2,7 +2,8 @@
2
2
  * Shared utilities for AST analysis modules (complexity, CFG, dataflow, AST nodes).
3
3
  */
4
4
 
5
- import { LANGUAGE_REGISTRY } from '../parser.js';
5
+ import { LANGUAGE_REGISTRY } from '../domain/parser.js';
6
+ import { ConfigError } from '../shared/errors.js';
6
7
 
7
8
  // ─── Generic Rule Factory ─────────────────────────────────────────────────
8
9
 
@@ -18,7 +19,7 @@ export function makeRules(defaults, overrides, label) {
18
19
  const validKeys = new Set(Object.keys(defaults));
19
20
  for (const key of Object.keys(overrides)) {
20
21
  if (!validKeys.has(key)) {
21
- throw new Error(`${label} rules: unknown key "${key}"`);
22
+ throw new ConfigError(`${label} rules: unknown key "${key}"`);
22
23
  }
23
24
  }
24
25
  return { ...defaults, ...overrides };
@@ -61,10 +62,10 @@ export const CFG_DEFAULTS = {
61
62
  export function makeCfgRules(overrides) {
62
63
  const rules = makeRules(CFG_DEFAULTS, overrides, 'CFG');
63
64
  if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
64
- throw new Error('CFG rules: functionNodes must be a non-empty Set');
65
+ throw new ConfigError('CFG rules: functionNodes must be a non-empty Set');
65
66
  }
66
67
  if (!(rules.forNodes instanceof Set)) {
67
- throw new Error('CFG rules: forNodes must be a Set');
68
+ throw new ConfigError('CFG rules: forNodes must be a Set');
68
69
  }
69
70
  return rules;
70
71
  }
@@ -136,7 +137,7 @@ export const DATAFLOW_DEFAULTS = {
136
137
  export function makeDataflowRules(overrides) {
137
138
  const rules = makeRules(DATAFLOW_DEFAULTS, overrides, 'Dataflow');
138
139
  if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
139
- throw new Error('Dataflow rules: functionNodes must be a non-empty Set');
140
+ throw new ConfigError('Dataflow rules: functionNodes must be a non-empty Set');
140
141
  }
141
142
  return rules;
142
143
  }
@@ -0,0 +1,22 @@
1
+ import { ConfigError } from '../../shared/errors.js';
2
+
3
+ export const command = {
4
+ name: 'ast [pattern]',
5
+ description: 'Search stored AST nodes (calls, new, string, regex, throw, await) by pattern',
6
+ queryOpts: true,
7
+ options: [
8
+ ['-k, --kind <kind>', 'Filter by AST node kind (call, new, string, regex, throw, await)'],
9
+ ['-f, --file <path>', 'Scope to file (partial match)'],
10
+ ],
11
+ async execute([pattern], opts, ctx) {
12
+ const { AST_NODE_KINDS, astQuery } = await import('../../ast.js');
13
+ if (opts.kind && !AST_NODE_KINDS.includes(opts.kind)) {
14
+ throw new ConfigError(`Invalid AST kind "${opts.kind}". Valid: ${AST_NODE_KINDS.join(', ')}`);
15
+ }
16
+ astQuery(pattern, opts.db, {
17
+ kind: opts.kind,
18
+ file: opts.file,
19
+ ...ctx.resolveQueryOpts(opts),
20
+ });
21
+ },
22
+ };
@@ -0,0 +1,45 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
+ import { audit } from '../../presentation/audit.js';
3
+ import { explain } from '../../presentation/queries-cli.js';
4
+ import { config } from '../shared/options.js';
5
+
6
+ export const command = {
7
+ name: 'audit <target>',
8
+ description: 'Composite report: explain + impact + health metrics per function',
9
+ options: [
10
+ ['-d, --db <path>', 'Path to graph.db'],
11
+ ['--quick', 'Structural summary only (skip impact analysis and health metrics)'],
12
+ ['--depth <n>', 'Impact/explain depth', '3'],
13
+ ['-f, --file <path>', 'Scope to file (partial match)'],
14
+ ['-k, --kind <kind>', 'Filter by symbol kind'],
15
+ ['-T, --no-tests', 'Exclude test/spec files from results'],
16
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
17
+ ['-j, --json', 'Output as JSON'],
18
+ ['--limit <number>', 'Max results to return (quick mode)'],
19
+ ['--offset <number>', 'Skip N results (quick mode)'],
20
+ ['--ndjson', 'Newline-delimited JSON output (quick mode)'],
21
+ ],
22
+ validate([_target], opts) {
23
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
24
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
25
+ }
26
+ },
27
+ execute([target], opts, ctx) {
28
+ const qOpts = ctx.resolveQueryOpts(opts);
29
+ if (opts.quick) {
30
+ explain(target, opts.db, {
31
+ depth: parseInt(opts.depth, 10),
32
+ ...qOpts,
33
+ });
34
+ return;
35
+ }
36
+ audit(target, opts.db, {
37
+ depth: parseInt(opts.depth, 10),
38
+ file: opts.file,
39
+ kind: opts.kind,
40
+ noTests: qOpts.noTests,
41
+ json: qOpts.json,
42
+ config,
43
+ });
44
+ },
45
+ };
@@ -0,0 +1,68 @@
1
+ import fs from 'node:fs';
2
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
3
+ import { BATCH_COMMANDS, multiBatchData, splitTargets } from '../../features/batch.js';
4
+ import { batch } from '../../presentation/batch.js';
5
+ import { ConfigError } from '../../shared/errors.js';
6
+
7
+ export const command = {
8
+ name: 'batch <command> [targets...]',
9
+ description: `Run a query against multiple targets in one call. Output is always JSON.\nValid commands: ${Object.keys(BATCH_COMMANDS).join(', ')}`,
10
+ options: [
11
+ ['-d, --db <path>', 'Path to graph.db'],
12
+ ['--from-file <path>', 'Read targets from file (JSON array or newline-delimited)'],
13
+ ['--stdin', 'Read targets from stdin (JSON array)'],
14
+ ['--depth <n>', 'Traversal depth passed to underlying command'],
15
+ ['-f, --file <path>', 'Scope to file (partial match)'],
16
+ ['-k, --kind <kind>', 'Filter by symbol kind'],
17
+ ['-T, --no-tests', 'Exclude test/spec files from results'],
18
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
19
+ ],
20
+ validate([_command, _targets], opts) {
21
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
22
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
23
+ }
24
+ },
25
+ async execute([command, positionalTargets], opts, ctx) {
26
+ let targets;
27
+ try {
28
+ if (opts.fromFile) {
29
+ const raw = fs.readFileSync(opts.fromFile, 'utf-8').trim();
30
+ if (raw.startsWith('[')) {
31
+ targets = JSON.parse(raw);
32
+ } else {
33
+ targets = raw.split(/\r?\n/).filter(Boolean);
34
+ }
35
+ } else if (opts.stdin) {
36
+ const chunks = [];
37
+ for await (const chunk of process.stdin) chunks.push(chunk);
38
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
39
+ targets = raw.startsWith('[') ? JSON.parse(raw) : raw.split(/\r?\n/).filter(Boolean);
40
+ } else {
41
+ targets = splitTargets(positionalTargets);
42
+ }
43
+ } catch (err) {
44
+ throw new ConfigError(`Failed to parse targets: ${err.message}`, { cause: err });
45
+ }
46
+
47
+ if (!targets || targets.length === 0) {
48
+ throw new ConfigError(
49
+ 'No targets provided. Pass targets as arguments, --from-file, or --stdin.',
50
+ );
51
+ }
52
+
53
+ const batchOpts = {
54
+ depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
55
+ file: opts.file,
56
+ kind: opts.kind,
57
+ noTests: ctx.resolveNoTests(opts),
58
+ };
59
+
60
+ const isMulti = targets.length > 0 && typeof targets[0] === 'object' && targets[0].command;
61
+ if (isMulti) {
62
+ const data = multiBatchData(targets, opts.db, batchOpts);
63
+ console.log(JSON.stringify(data, null, 2));
64
+ } else {
65
+ batch(command, targets, opts.db, batchOpts);
66
+ }
67
+ },
68
+ };
@@ -0,0 +1,21 @@
1
+ export const command = {
2
+ name: 'branch-compare <base> <target>',
3
+ description: 'Compare code structure between two branches/refs',
4
+ options: [
5
+ ['--depth <n>', 'Max transitive caller depth', '3'],
6
+ ['-T, --no-tests', 'Exclude test/spec files'],
7
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
8
+ ['-j, --json', 'Output as JSON'],
9
+ ['-f, --format <format>', 'Output format: text, mermaid, json', 'text'],
10
+ ],
11
+ async execute([base, target], opts, ctx) {
12
+ const { branchCompare } = await import('../../presentation/branch-compare.js');
13
+ await branchCompare(base, target, {
14
+ engine: ctx.program.opts().engine,
15
+ depth: parseInt(opts.depth, 10),
16
+ noTests: ctx.resolveNoTests(opts),
17
+ json: opts.json,
18
+ format: opts.format,
19
+ });
20
+ },
21
+ };
@@ -0,0 +1,26 @@
1
+ import path from 'node:path';
2
+ import { buildGraph } from '../../domain/graph/builder.js';
3
+
4
+ export const command = {
5
+ name: 'build [dir]',
6
+ description: 'Parse repo and build graph in .codegraph/graph.db',
7
+ options: [
8
+ ['--no-incremental', 'Force full rebuild (ignore file hashes)'],
9
+ ['--no-ast', 'Skip AST node extraction (calls, new, string, regex, throw, await)'],
10
+ ['--no-complexity', 'Skip complexity metrics computation'],
11
+ ['--no-dataflow', 'Skip data flow edge extraction'],
12
+ ['--no-cfg', 'Skip control flow graph building'],
13
+ ],
14
+ async execute([dir], opts, ctx) {
15
+ const root = path.resolve(dir || '.');
16
+ const engine = ctx.program.opts().engine;
17
+ await buildGraph(root, {
18
+ incremental: opts.incremental,
19
+ ast: opts.ast,
20
+ complexity: opts.complexity,
21
+ engine,
22
+ dataflow: opts.dataflow,
23
+ cfg: opts.cfg,
24
+ });
25
+ },
26
+ };
@@ -0,0 +1,26 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
+
3
+ export const command = {
4
+ name: 'cfg <name>',
5
+ description: 'Show control flow graph for a function',
6
+ queryOpts: true,
7
+ options: [
8
+ ['--format <fmt>', 'Output format: text, dot, mermaid', 'text'],
9
+ ['-f, --file <path>', 'Scope to file (partial match)'],
10
+ ['-k, --kind <kind>', 'Filter by symbol kind'],
11
+ ],
12
+ validate([_name], opts) {
13
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
14
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
15
+ }
16
+ },
17
+ async execute([name], opts, ctx) {
18
+ const { cfg } = await import('../../presentation/cfg.js');
19
+ cfg(name, opts.db, {
20
+ format: opts.format,
21
+ file: opts.file,
22
+ kind: opts.kind,
23
+ ...ctx.resolveQueryOpts(opts),
24
+ });
25
+ },
26
+ };