@optave/codegraph 3.1.4 → 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 (181) hide show
  1. package/README.md +26 -70
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +32 -12
  4. package/src/ast-analysis/shared.js +2 -2
  5. package/src/cli/commands/ast.js +2 -6
  6. package/src/cli/commands/audit.js +9 -10
  7. package/src/cli/commands/batch.js +4 -4
  8. package/src/cli/commands/branch-compare.js +1 -1
  9. package/src/cli/commands/build.js +1 -1
  10. package/src/cli/commands/cfg.js +3 -7
  11. package/src/cli/commands/check.js +12 -17
  12. package/src/cli/commands/children.js +3 -6
  13. package/src/cli/commands/co-change.js +5 -3
  14. package/src/cli/commands/communities.js +2 -6
  15. package/src/cli/commands/complexity.js +3 -2
  16. package/src/cli/commands/context.js +3 -7
  17. package/src/cli/commands/cycles.js +12 -8
  18. package/src/cli/commands/dataflow.js +3 -7
  19. package/src/cli/commands/deps.js +2 -6
  20. package/src/cli/commands/diff-impact.js +2 -6
  21. package/src/cli/commands/embed.js +1 -1
  22. package/src/cli/commands/export.js +34 -31
  23. package/src/cli/commands/exports.js +2 -6
  24. package/src/cli/commands/flow.js +3 -7
  25. package/src/cli/commands/fn-impact.js +3 -7
  26. package/src/cli/commands/impact.js +2 -6
  27. package/src/cli/commands/info.js +2 -2
  28. package/src/cli/commands/map.js +1 -1
  29. package/src/cli/commands/mcp.js +1 -1
  30. package/src/cli/commands/models.js +1 -1
  31. package/src/cli/commands/owners.js +1 -1
  32. package/src/cli/commands/path.js +2 -2
  33. package/src/cli/commands/plot.js +40 -31
  34. package/src/cli/commands/query.js +3 -7
  35. package/src/cli/commands/registry.js +2 -2
  36. package/src/cli/commands/roles.js +3 -7
  37. package/src/cli/commands/search.js +1 -1
  38. package/src/cli/commands/sequence.js +3 -7
  39. package/src/cli/commands/snapshot.js +6 -1
  40. package/src/cli/commands/stats.js +1 -1
  41. package/src/cli/commands/structure.js +5 -4
  42. package/src/cli/commands/triage.js +4 -4
  43. package/src/cli/commands/watch.js +1 -1
  44. package/src/cli/commands/where.js +2 -6
  45. package/src/cli/index.js +11 -5
  46. package/src/cli/shared/open-graph.js +13 -0
  47. package/src/cli/shared/options.js +22 -2
  48. package/src/cli.js +1 -1
  49. package/src/db/connection.js +127 -4
  50. package/src/{db.js → db/index.js} +12 -5
  51. package/src/db/migrations.js +1 -1
  52. package/src/db/query-builder.js +15 -8
  53. package/src/db/repository/base.js +1 -1
  54. package/src/db/repository/graph-read.js +3 -3
  55. package/src/db/repository/in-memory-repository.js +4 -13
  56. package/src/db/repository/nodes.js +3 -8
  57. package/src/{analysis → domain/analysis}/context.js +6 -6
  58. package/src/{analysis → domain/analysis}/dependencies.js +5 -5
  59. package/src/{analysis → domain/analysis}/exports.js +8 -4
  60. package/src/{analysis → domain/analysis}/impact.js +61 -58
  61. package/src/{analysis → domain/analysis}/module-map.js +3 -3
  62. package/src/{analysis → domain/analysis}/roles.js +4 -4
  63. package/src/{analysis → domain/analysis}/symbol-lookup.js +13 -7
  64. package/src/{builder → domain/graph/builder}/helpers.js +3 -3
  65. package/src/{builder → domain/graph/builder}/incremental.js +3 -3
  66. package/src/{builder → domain/graph/builder}/pipeline.js +4 -4
  67. package/src/{builder → domain/graph/builder}/stages/build-edges.js +2 -2
  68. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  69. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  70. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +6 -6
  71. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  72. package/src/{builder → domain/graph/builder}/stages/insert-nodes.js +1 -1
  73. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  74. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  75. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  76. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  77. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  78. package/src/{journal.js → domain/graph/journal.js} +1 -1
  79. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  80. package/src/{watcher.js → domain/graph/watcher.js} +5 -5
  81. package/src/{parser.js → domain/parser.js} +5 -5
  82. package/src/{queries.js → domain/queries.js} +16 -16
  83. package/src/{embeddings → domain/search}/generator.js +3 -3
  84. package/src/{embeddings → domain/search}/models.js +2 -2
  85. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  86. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  87. package/src/{embeddings → domain/search}/search/keyword.js +1 -1
  88. package/src/{embeddings → domain/search}/search/prepare.js +2 -2
  89. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  90. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  91. package/src/extractors/javascript.js +1 -1
  92. package/src/{ast.js → features/ast.js} +8 -8
  93. package/src/{audit.js → features/audit.js} +16 -44
  94. package/src/{batch.js → features/batch.js} +5 -5
  95. package/src/{boundaries.js → features/boundaries.js} +2 -2
  96. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  97. package/src/{cfg.js → features/cfg.js} +10 -10
  98. package/src/{check.js → features/check.js} +13 -30
  99. package/src/{cochange.js → features/cochange.js} +5 -5
  100. package/src/{communities.js → features/communities.js} +7 -7
  101. package/src/{complexity.js → features/complexity.js} +13 -13
  102. package/src/{dataflow.js → features/dataflow.js} +11 -11
  103. package/src/{export.js → features/export.js} +3 -3
  104. package/src/{flow.js → features/flow.js} +4 -4
  105. package/src/{viewer.js → features/graph-enrichment.js} +6 -6
  106. package/src/{manifesto.js → features/manifesto.js} +6 -6
  107. package/src/{owners.js → features/owners.js} +2 -2
  108. package/src/{sequence.js → features/sequence.js} +15 -15
  109. package/src/{snapshot.js → features/snapshot.js} +3 -3
  110. package/src/{structure.js → features/structure.js} +7 -7
  111. package/src/{triage.js → features/triage.js} +8 -8
  112. package/src/graph/builders/dependency.js +33 -14
  113. package/src/index.cjs +16 -0
  114. package/src/index.js +39 -39
  115. package/src/{native.js → infrastructure/native.js} +1 -1
  116. package/src/mcp/middleware.js +1 -1
  117. package/src/mcp/server.js +5 -5
  118. package/src/mcp/tool-registry.js +2 -2
  119. package/src/mcp/tools/ast-query.js +1 -1
  120. package/src/mcp/tools/audit.js +1 -1
  121. package/src/mcp/tools/batch-query.js +1 -1
  122. package/src/mcp/tools/branch-compare.js +3 -1
  123. package/src/mcp/tools/cfg.js +1 -1
  124. package/src/mcp/tools/check.js +3 -3
  125. package/src/mcp/tools/co-changes.js +1 -1
  126. package/src/mcp/tools/code-owners.js +1 -1
  127. package/src/mcp/tools/communities.js +1 -1
  128. package/src/mcp/tools/complexity.js +1 -1
  129. package/src/mcp/tools/dataflow.js +2 -2
  130. package/src/mcp/tools/execution-flow.js +2 -2
  131. package/src/mcp/tools/export-graph.js +2 -2
  132. package/src/mcp/tools/find-cycles.js +2 -2
  133. package/src/mcp/tools/list-repos.js +1 -1
  134. package/src/mcp/tools/sequence.js +1 -1
  135. package/src/mcp/tools/structure.js +1 -1
  136. package/src/mcp/tools/triage.js +2 -2
  137. package/src/{commands → presentation}/audit.js +2 -2
  138. package/src/{commands → presentation}/batch.js +1 -1
  139. package/src/{commands → presentation}/branch-compare.js +2 -2
  140. package/src/{commands → presentation}/cfg.js +1 -1
  141. package/src/{commands → presentation}/check.js +2 -2
  142. package/src/{commands → presentation}/communities.js +1 -1
  143. package/src/{commands → presentation}/complexity.js +1 -1
  144. package/src/{commands → presentation}/dataflow.js +1 -1
  145. package/src/{commands → presentation}/flow.js +2 -2
  146. package/src/{commands → presentation}/manifesto.js +1 -1
  147. package/src/{commands → presentation}/owners.js +1 -1
  148. package/src/presentation/queries-cli/exports.js +46 -0
  149. package/src/presentation/queries-cli/impact.js +198 -0
  150. package/src/presentation/queries-cli/index.js +5 -0
  151. package/src/presentation/queries-cli/inspect.js +334 -0
  152. package/src/presentation/queries-cli/overview.js +197 -0
  153. package/src/presentation/queries-cli/path.js +58 -0
  154. package/src/presentation/queries-cli.js +27 -0
  155. package/src/{commands → presentation}/query.js +1 -1
  156. package/src/presentation/result-formatter.js +126 -3
  157. package/src/{commands → presentation}/sequence.js +2 -2
  158. package/src/{commands → presentation}/structure.js +1 -1
  159. package/src/{commands → presentation}/triage.js +1 -1
  160. package/src/{constants.js → shared/constants.js} +1 -1
  161. package/src/shared/file-utils.js +2 -2
  162. package/src/shared/generators.js +2 -2
  163. package/src/shared/hierarchy.js +1 -1
  164. package/src/mcp.js +0 -2
  165. package/src/queries-cli.js +0 -866
  166. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  167. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  168. /package/src/{embeddings → domain/search}/index.js +0 -0
  169. /package/src/{embeddings → domain/search}/search/filters.js +0 -0
  170. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  171. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  172. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  173. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  174. /package/src/{config.js → infrastructure/config.js} +0 -0
  175. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  176. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  177. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  178. /package/src/{commands → presentation}/cochange.js +0 -0
  179. /package/src/{errors.js → shared/errors.js} +0 -0
  180. /package/src/{kinds.js → shared/kinds.js} +0 -0
  181. /package/src/{paginate.js → shared/paginate.js} +0 -0
package/README.md CHANGED
@@ -477,6 +477,8 @@ codegraph registry remove <name> # Unregister
477
477
  | `-f, --file <path>` | Scope to a specific file (`fn`, `context`, `where`) |
478
478
  | `--mode <mode>` | Search mode: `hybrid` (default), `semantic`, or `keyword` (`search`) |
479
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) |
480
482
  | `--limit <n>` | Limit number of results |
481
483
  | `--offset <n>` | Skip first N results (pagination) |
482
484
  | `--rrf-k <n>` | RRF smoothing constant for multi-query search (default 60) |
@@ -560,14 +562,14 @@ Self-measured on every release via CI ([build benchmarks](generated/benchmarks/B
560
562
 
561
563
  | Metric | Latest |
562
564
  |---|---|
563
- | Build speed (native) | **5.1 ms/file** |
564
- | Build speed (WASM) | **14.6 ms/file** |
565
- | Query time | **4ms** |
566
- | No-op rebuild (native) | **6ms** |
567
- | 1-file rebuild (native) | **282ms** |
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** |
568
570
  | Query: fn-deps | **0.9ms** |
569
- | Query: path | **0.8ms** |
570
- | ~50,000 files (est.) | **~255.0s build** |
571
+ | Query: path | **0.9ms** |
572
+ | ~50,000 files (est.) | **~175.0s build** |
571
573
 
572
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.
573
575
 
@@ -603,16 +605,16 @@ codegraph mcp --repos a,b # Restrict to specific repos (implies --multi-rep
603
605
 
604
606
  ### CLAUDE.md / Agent Instructions
605
607
 
606
- 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).
607
609
 
608
610
  ```markdown
609
- ## Code Navigation
611
+ ## Codegraph
610
612
 
611
- 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`.
612
614
 
613
- ### Before modifying code, always:
615
+ ### Before modifying code:
614
616
  1. `codegraph where <name>` — find where the symbol lives
615
- 2. `codegraph audit <file-or-function> --quick` — understand the structure
617
+ 2. `codegraph audit --quick <target>` — understand the structure
616
618
  3. `codegraph context <name> -T` — get full context (source, deps, callers)
617
619
  4. `codegraph fn-impact <name> -T` — check blast radius before editing
618
620
 
@@ -620,65 +622,19 @@ This project uses codegraph. The database is at `.codegraph/graph.db`.
620
622
  5. `codegraph diff-impact --staged -T` — verify impact before committing
621
623
 
622
624
  ### Other useful commands
623
- - `codegraph build .` — rebuild the graph (incremental by default)
624
- - `codegraph map` — module overview
625
- - `codegraph query <name> -T` — function call chain (callers + callees)
626
- - `codegraph path <from> <to> -T` — shortest call path between two symbols
627
- - `codegraph deps <file>`file-level dependencies
628
- - `codegraph roles --role dead -T` — find dead code (unreferenced symbols)
629
- - `codegraph roles --role core -T` — find core symbols (high fan-in)
630
- - `codegraph co-change <file>` files that historically change together
631
- - `codegraph complexity -T` — per-function complexity metrics (cognitive, cyclomatic, MI)
632
- - `codegraph communities --drift -T` — module boundary drift analysis
633
- - `codegraph check -T` — pass/fail rule check (CI gate, exit code 1 on fail)
634
- - `codegraph audit <target> -T` — combined structural summary + impact + health in one report
635
- - `codegraph triage -T` — ranked audit priority queue
636
- - `codegraph triage --level file -T` — file-level hotspot analysis
637
- - `codegraph check --staged` — CI validation predicates (exit code 0/1)
638
- - `codegraph batch target1 target2` — batch query multiple targets at once
639
- - `codegraph owners [target]` — CODEOWNERS mapping for symbols
640
- - `codegraph snapshot save <name>` — checkpoint the graph DB before refactoring
641
- - `codegraph branch-compare main HEAD -T` — structural diff between two refs (added/removed/changed symbols)
642
- - `codegraph exports <file>` — per-symbol consumer analysis (who calls each export)
643
- - `codegraph children <name>` — list parameters, properties, constants of a symbol
644
- - `codegraph dataflow <name>` — data flow edges (flows_to, returns, mutates)
645
- - `codegraph cfg <name>` — intraprocedural control flow graph
646
- - `codegraph ast <pattern>` — search stored AST nodes (calls, new, string, regex, throw, await)
647
- - `codegraph plot` — interactive HTML dependency graph viewer
648
- - `codegraph search "<query>"` — hybrid search (requires `codegraph embed`)
649
- - `codegraph search "<query>" --mode keyword` — BM25 keyword search
650
- - `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
651
634
 
652
635
  ### Flags
653
- - `-T` / `--no-tests` — exclude test files (use by default)
654
- - `-j` / `--json`JSON output for programmatic use
655
- - `-f, --file <path>` — scope to a specific file
656
- - `-k, --kind <kind>` — filter by symbol kind
657
-
658
- ### Semantic search
659
-
660
- Use `codegraph search` to find functions by intent rather than exact name.
661
- When a single query might miss results, combine multiple angles with `;`:
662
-
663
- codegraph search "validate auth; check token; verify JWT"
664
- codegraph search "parse config; load settings" --kind function
665
-
666
- Multi-query search uses Reciprocal Rank Fusion — functions that rank
667
- highly across several queries surface first. This is especially useful
668
- when you're not sure what naming convention the codebase uses.
669
-
670
- When writing multi-queries, use 2-4 sub-queries (2-4 words each) that
671
- attack the problem from different angles. Pick from these strategies:
672
- - **Naming variants**: cover synonyms the author might have used
673
- ("send email; notify user; deliver message")
674
- - **Abstraction levels**: pair high-level intent with low-level operation
675
- ("handle payment; charge credit card")
676
- - **Input/output sides**: cover the read half and write half
677
- ("parse config; apply settings")
678
- - **Domain + technical**: bridge business language and implementation
679
- ("onboard tenant; create organization; provision workspace")
680
-
681
- 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
682
638
  ```
683
639
 
684
640
  ## 📋 Recommended Practices
@@ -821,7 +777,7 @@ See **[ROADMAP.md](docs/roadmap/ROADMAP.md)** for the full development roadmap a
821
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
822
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
823
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
824
- 4. **Architectural Refactoring** — **In Progress** (v3.1.4) — unified AST analysis, composable MCP, domain errors, builder pipeline, embedder subsystem, graph model, qualified names, presentation layer, InMemoryRepository (11/14 tasks complete)
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
825
781
  5. **Natural Language Queries** — `codegraph ask` command, conversational sessions
826
782
  6. **Expanded Language Support** — 8 new languages (12 → 20)
827
783
  7. **GitHub Integration & CI** — reusable GitHub Action, PR review, SARIF output
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@optave/codegraph",
3
- "version": "3.1.4",
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"
10
12
  },
11
13
  "./cli": {
12
14
  "import": "./src/cli.js"
@@ -74,12 +76,12 @@
74
76
  },
75
77
  "optionalDependencies": {
76
78
  "@modelcontextprotocol/sdk": "^1.0.0",
77
- "@optave/codegraph-darwin-arm64": "3.1.4",
78
- "@optave/codegraph-darwin-x64": "3.1.4",
79
- "@optave/codegraph-linux-arm64-gnu": "3.1.4",
80
- "@optave/codegraph-linux-x64-gnu": "3.1.4",
81
- "@optave/codegraph-linux-x64-musl": "3.1.4",
82
- "@optave/codegraph-win32-x64-msvc": "3.1.4"
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"
83
85
  },
84
86
  "devDependencies": {
85
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,8 +2,8 @@
2
2
  * Shared utilities for AST analysis modules (complexity, CFG, dataflow, AST nodes).
3
3
  */
4
4
 
5
- import { ConfigError } from '../errors.js';
6
- import { LANGUAGE_REGISTRY } from '../parser.js';
5
+ import { LANGUAGE_REGISTRY } from '../domain/parser.js';
6
+ import { ConfigError } from '../shared/errors.js';
7
7
 
8
8
  // ─── Generic Rule Factory ─────────────────────────────────────────────────
9
9
 
@@ -1,4 +1,4 @@
1
- import { ConfigError } from '../../errors.js';
1
+ import { ConfigError } from '../../shared/errors.js';
2
2
 
3
3
  export const command = {
4
4
  name: 'ast [pattern]',
@@ -16,11 +16,7 @@ export const command = {
16
16
  astQuery(pattern, opts.db, {
17
17
  kind: opts.kind,
18
18
  file: opts.file,
19
- noTests: ctx.resolveNoTests(opts),
20
- json: opts.json,
21
- ndjson: opts.ndjson,
22
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
23
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
19
+ ...ctx.resolveQueryOpts(opts),
24
20
  });
25
21
  },
26
22
  };
@@ -1,6 +1,7 @@
1
- import { audit } from '../../commands/audit.js';
2
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
3
- import { explain } from '../../queries-cli.js';
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';
4
5
 
5
6
  export const command = {
6
7
  name: 'audit <target>',
@@ -24,14 +25,11 @@ export const command = {
24
25
  }
25
26
  },
26
27
  execute([target], opts, ctx) {
28
+ const qOpts = ctx.resolveQueryOpts(opts);
27
29
  if (opts.quick) {
28
30
  explain(target, opts.db, {
29
31
  depth: parseInt(opts.depth, 10),
30
- noTests: ctx.resolveNoTests(opts),
31
- json: opts.json,
32
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
33
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
34
- ndjson: opts.ndjson,
32
+ ...qOpts,
35
33
  });
36
34
  return;
37
35
  }
@@ -39,8 +37,9 @@ export const command = {
39
37
  depth: parseInt(opts.depth, 10),
40
38
  file: opts.file,
41
39
  kind: opts.kind,
42
- noTests: ctx.resolveNoTests(opts),
43
- json: opts.json,
40
+ noTests: qOpts.noTests,
41
+ json: qOpts.json,
42
+ config,
44
43
  });
45
44
  },
46
45
  };
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import { BATCH_COMMANDS, multiBatchData, splitTargets } from '../../batch.js';
3
- import { batch } from '../../commands/batch.js';
4
- import { ConfigError } from '../../errors.js';
5
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
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
6
 
7
7
  export const command = {
8
8
  name: 'batch <command> [targets...]',
@@ -9,7 +9,7 @@ export const command = {
9
9
  ['-f, --format <format>', 'Output format: text, mermaid, json', 'text'],
10
10
  ],
11
11
  async execute([base, target], opts, ctx) {
12
- const { branchCompare } = await import('../../commands/branch-compare.js');
12
+ const { branchCompare } = await import('../../presentation/branch-compare.js');
13
13
  await branchCompare(base, target, {
14
14
  engine: ctx.program.opts().engine,
15
15
  depth: parseInt(opts.depth, 10),
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { buildGraph } from '../../builder.js';
2
+ import { buildGraph } from '../../domain/graph/builder.js';
3
3
 
4
4
  export const command = {
5
5
  name: 'build [dir]',
@@ -1,4 +1,4 @@
1
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
2
 
3
3
  export const command = {
4
4
  name: 'cfg <name>',
@@ -15,16 +15,12 @@ export const command = {
15
15
  }
16
16
  },
17
17
  async execute([name], opts, ctx) {
18
- const { cfg } = await import('../../commands/cfg.js');
18
+ const { cfg } = await import('../../presentation/cfg.js');
19
19
  cfg(name, opts.db, {
20
20
  format: opts.format,
21
21
  file: opts.file,
22
22
  kind: opts.kind,
23
- noTests: ctx.resolveNoTests(opts),
24
- json: opts.json,
25
- ndjson: opts.ndjson,
26
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
27
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
23
+ ...ctx.resolveQueryOpts(opts),
28
24
  });
29
25
  },
30
26
  };
@@ -1,5 +1,6 @@
1
- import { ConfigError } from '../../errors.js';
2
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
+ import { ConfigError } from '../../shared/errors.js';
3
+ import { config } from '../shared/options.js';
3
4
 
4
5
  export const command = {
5
6
  name: 'check [ref]',
@@ -25,6 +26,7 @@ export const command = {
25
26
  ],
26
27
  async execute([ref], opts, ctx) {
27
28
  const isDiffMode = ref || opts.staged;
29
+ const qOpts = ctx.resolveQueryOpts(opts);
28
30
 
29
31
  if (!isDiffMode && !opts.rules) {
30
32
  if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
@@ -32,20 +34,16 @@ export const command = {
32
34
  `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`,
33
35
  );
34
36
  }
35
- const { manifesto } = await import('../../commands/manifesto.js');
37
+ const { manifesto } = await import('../../presentation/manifesto.js');
36
38
  manifesto(opts.db, {
37
39
  file: opts.file,
38
40
  kind: opts.kind,
39
- noTests: ctx.resolveNoTests(opts),
40
- json: opts.json,
41
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
42
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
43
- ndjson: opts.ndjson,
41
+ ...qOpts,
44
42
  });
45
43
  return;
46
44
  }
47
45
 
48
- const { check } = await import('../../commands/check.js');
46
+ const { check } = await import('../../presentation/check.js');
49
47
  check(opts.db, {
50
48
  ref,
51
49
  staged: opts.staged,
@@ -54,8 +52,9 @@ export const command = {
54
52
  signatures: opts.signatures || undefined,
55
53
  boundaries: opts.boundaries || undefined,
56
54
  depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
57
- noTests: ctx.resolveNoTests(opts),
58
- json: opts.json,
55
+ noTests: qOpts.noTests,
56
+ json: qOpts.json,
57
+ config,
59
58
  });
60
59
 
61
60
  if (opts.rules) {
@@ -64,15 +63,11 @@ export const command = {
64
63
  `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`,
65
64
  );
66
65
  }
67
- const { manifesto } = await import('../../commands/manifesto.js');
66
+ const { manifesto } = await import('../../presentation/manifesto.js');
68
67
  manifesto(opts.db, {
69
68
  file: opts.file,
70
69
  kind: opts.kind,
71
- noTests: ctx.resolveNoTests(opts),
72
- json: opts.json,
73
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
74
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
75
- ndjson: opts.ndjson,
70
+ ...qOpts,
76
71
  });
77
72
  }
78
73
  },
@@ -1,5 +1,5 @@
1
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
- import { children } from '../../queries-cli.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
+ import { children } from '../../presentation/queries-cli.js';
3
3
 
4
4
  export const command = {
5
5
  name: 'children <name>',
@@ -22,10 +22,7 @@ export const command = {
22
22
  children(name, opts.db, {
23
23
  file: opts.file,
24
24
  kind: opts.kind,
25
- noTests: ctx.resolveNoTests(opts),
26
- json: opts.json,
27
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
28
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
25
+ ...ctx.resolveQueryOpts(opts),
29
26
  });
30
27
  },
31
28
  };
@@ -1,4 +1,4 @@
1
- import { AnalysisError } from '../../errors.js';
1
+ import { AnalysisError } from '../../shared/errors.js';
2
2
 
3
3
  export const command = {
4
4
  name: 'co-change [file]',
@@ -19,8 +19,10 @@ export const command = {
19
19
  ['--ndjson', 'Newline-delimited JSON output'],
20
20
  ],
21
21
  async execute([file], opts, ctx) {
22
- const { analyzeCoChanges, coChangeData, coChangeTopData } = await import('../../cochange.js');
23
- const { formatCoChange, formatCoChangeTop } = await import('../../commands/cochange.js');
22
+ const { analyzeCoChanges, coChangeData, coChangeTopData } = await import(
23
+ '../../features/cochange.js'
24
+ );
25
+ const { formatCoChange, formatCoChangeTop } = await import('../../presentation/cochange.js');
24
26
 
25
27
  if (opts.analyze) {
26
28
  const result = analyzeCoChanges(opts.db, {
@@ -8,16 +8,12 @@ export const command = {
8
8
  ['--drift', 'Show only drift analysis'],
9
9
  ],
10
10
  async execute(_args, opts, ctx) {
11
- const { communities } = await import('../../commands/communities.js');
11
+ const { communities } = await import('../../presentation/communities.js');
12
12
  communities(opts.db, {
13
13
  functions: opts.functions,
14
14
  resolution: parseFloat(opts.resolution),
15
15
  drift: opts.drift,
16
- noTests: ctx.resolveNoTests(opts),
17
- json: opts.json,
18
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
19
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
20
- ndjson: opts.ndjson,
16
+ ...ctx.resolveQueryOpts(opts),
21
17
  });
22
18
  },
23
19
  };
@@ -1,4 +1,4 @@
1
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
2
 
3
3
  export const command = {
4
4
  name: 'complexity [target]',
@@ -27,7 +27,7 @@ export const command = {
27
27
  }
28
28
  },
29
29
  async execute([target], opts, ctx) {
30
- const { complexity } = await import('../../commands/complexity.js');
30
+ const { complexity } = await import('../../presentation/complexity.js');
31
31
  complexity(opts.db, {
32
32
  target,
33
33
  limit: parseInt(opts.limit, 10),
@@ -40,6 +40,7 @@ export const command = {
40
40
  noTests: ctx.resolveNoTests(opts),
41
41
  json: opts.json,
42
42
  ndjson: opts.ndjson,
43
+ config: ctx.config,
43
44
  });
44
45
  },
45
46
  };
@@ -1,5 +1,5 @@
1
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
- import { context } from '../../queries-cli.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
+ import { context } from '../../presentation/queries-cli.js';
3
3
 
4
4
  export const command = {
5
5
  name: 'context <name>',
@@ -23,12 +23,8 @@ export const command = {
23
23
  file: opts.file,
24
24
  kind: opts.kind,
25
25
  noSource: !opts.source,
26
- noTests: ctx.resolveNoTests(opts),
27
26
  includeTests: opts.withTestSource,
28
- json: opts.json,
29
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
30
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
31
- ndjson: opts.ndjson,
27
+ ...ctx.resolveQueryOpts(opts),
32
28
  });
33
29
  },
34
30
  };
@@ -1,5 +1,5 @@
1
- import { findCycles, formatCycles } from '../../cycles.js';
2
- import { openReadonlyOrFail } from '../../db.js';
1
+ import { findCycles, formatCycles } from '../../domain/graph/cycles.js';
2
+ import { openGraph } from '../shared/open-graph.js';
3
3
 
4
4
  export const command = {
5
5
  name: 'cycles',
@@ -12,12 +12,16 @@ export const command = {
12
12
  ['-j, --json', 'Output as JSON'],
13
13
  ],
14
14
  execute(_args, opts, ctx) {
15
- const db = openReadonlyOrFail(opts.db);
16
- const cycles = findCycles(db, {
17
- fileLevel: !opts.functions,
18
- noTests: ctx.resolveNoTests(opts),
19
- });
20
- db.close();
15
+ const { db, close } = openGraph(opts);
16
+ let cycles;
17
+ try {
18
+ cycles = findCycles(db, {
19
+ fileLevel: !opts.functions,
20
+ noTests: ctx.resolveNoTests(opts),
21
+ });
22
+ } finally {
23
+ close();
24
+ }
21
25
 
22
26
  if (opts.json) {
23
27
  console.log(JSON.stringify({ cycles, count: cycles.length }, null, 2));
@@ -1,4 +1,4 @@
1
- import { EVERY_SYMBOL_KIND } from '../../queries.js';
1
+ import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
2
2
 
3
3
  export const command = {
4
4
  name: 'dataflow <name>',
@@ -16,17 +16,13 @@ export const command = {
16
16
  }
17
17
  },
18
18
  async execute([name], opts, ctx) {
19
- const { dataflow } = await import('../../commands/dataflow.js');
19
+ const { dataflow } = await import('../../presentation/dataflow.js');
20
20
  dataflow(name, opts.db, {
21
21
  file: opts.file,
22
22
  kind: opts.kind,
23
- noTests: ctx.resolveNoTests(opts),
24
- json: opts.json,
25
- ndjson: opts.ndjson,
26
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
27
- offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
28
23
  impact: opts.impact,
29
24
  depth: parseInt(opts.depth, 10),
25
+ ...ctx.resolveQueryOpts(opts),
30
26
  });
31
27
  },
32
28
  };