@optave/codegraph 3.1.4 → 3.2.0

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 (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -0,0 +1,51 @@
1
+ import { briefData } from '../domain/analysis/brief.js';
2
+ import { outputResult } from './result-formatter.js';
3
+
4
+ /**
5
+ * Format a compact brief for hook context injection.
6
+ * Single-block, token-efficient output.
7
+ *
8
+ * Example:
9
+ * src/domain/graph/builder.js [HIGH RISK]
10
+ * Symbols: buildGraph [core, 12 callers], collectFiles [leaf, 2 callers]
11
+ * Imports: src/db/index.js, src/domain/parser.js
12
+ * Imported by: src/cli/commands/build.js (+8 transitive)
13
+ */
14
+ export function brief(file, customDbPath, opts = {}) {
15
+ const data = briefData(file, customDbPath, opts);
16
+ if (outputResult(data, 'results', opts)) return;
17
+
18
+ if (data.results.length === 0) {
19
+ console.log(`No file matching "${file}" in graph`);
20
+ return;
21
+ }
22
+
23
+ for (const r of data.results) {
24
+ console.log(`${r.file} [${r.risk.toUpperCase()} RISK]`);
25
+
26
+ // Symbols line
27
+ if (r.symbols.length > 0) {
28
+ const parts = r.symbols.map((s) => {
29
+ const tags = [];
30
+ if (s.role) tags.push(s.role);
31
+ tags.push(`${s.callerCount} caller${s.callerCount !== 1 ? 's' : ''}`);
32
+ return `${s.name} [${tags.join(', ')}]`;
33
+ });
34
+ console.log(` Symbols: ${parts.join(', ')}`);
35
+ }
36
+
37
+ // Imports line
38
+ if (r.imports.length > 0) {
39
+ console.log(` Imports: ${r.imports.join(', ')}`);
40
+ }
41
+
42
+ // Imported by line with transitive count
43
+ if (r.importedBy.length > 0) {
44
+ const transitive = r.totalImporterCount - r.importedBy.length;
45
+ const suffix = transitive > 0 ? ` (+${transitive} transitive)` : '';
46
+ console.log(` Imported by: ${r.importedBy.join(', ')}${suffix}`);
47
+ } else if (r.totalImporterCount > 0) {
48
+ console.log(` Imported by: ${r.totalImporterCount} transitive importers`);
49
+ }
50
+ }
51
+ }
@@ -1,4 +1,4 @@
1
- import { cfgData, cfgToDOT, cfgToMermaid } from '../cfg.js';
1
+ import { cfgData, cfgToDOT, cfgToMermaid } from '../features/cfg.js';
2
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
- import { checkData } from '../check.js';
2
- import { AnalysisError } from '../errors.js';
1
+ import { checkData } from '../features/check.js';
3
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
+ import { AnalysisError } from '../shared/errors.js';
4
4
 
5
5
  /**
6
6
  * CLI formatter — prints check results and sets exitCode 1 on failure.
@@ -1,4 +1,4 @@
1
- import { communitiesData } from '../communities.js';
1
+ import { communitiesData } from '../features/communities.js';
2
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { complexityData } from '../complexity.js';
1
+ import { complexityData } from '../features/complexity.js';
2
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { dataflowData, dataflowImpactData } from '../dataflow.js';
1
+ import { dataflowData, dataflowImpactData } from '../features/dataflow.js';
2
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
- import { flowData, listEntryPointsData } from '../flow.js';
1
+ import { kindIcon } from '../domain/queries.js';
2
+ import { flowData, listEntryPointsData } from '../features/flow.js';
2
3
  import { outputResult } from '../infrastructure/result-formatter.js';
3
- import { kindIcon } from '../queries.js';
4
4
 
5
5
  /**
6
6
  * CLI formatter — text or JSON output.
@@ -1,5 +1,5 @@
1
+ import { manifestoData } from '../features/manifesto.js';
1
2
  import { outputResult } from '../infrastructure/result-formatter.js';
2
- import { manifestoData } from '../manifesto.js';
3
3
 
4
4
  /**
5
5
  * CLI formatter — prints manifesto results and sets exitCode 1 on failure.
@@ -1,5 +1,5 @@
1
+ import { ownersData } from '../features/owners.js';
1
2
  import { outputResult } from '../infrastructure/result-formatter.js';
2
- import { ownersData } from '../owners.js';
3
3
 
4
4
  /**
5
5
  * CLI display function for the `owners` command.
@@ -0,0 +1,53 @@
1
+ import { exportsData, kindIcon } from '../../domain/queries.js';
2
+ import { outputResult } from '../../infrastructure/result-formatter.js';
3
+
4
+ function printExportHeader(data, opts) {
5
+ if (opts.unused) {
6
+ console.log(
7
+ `\n# ${data.file} — ${data.totalUnused} unused export${data.totalUnused !== 1 ? 's' : ''} (of ${data.totalExported} exported)\n`,
8
+ );
9
+ } else {
10
+ const unusedNote = data.totalUnused > 0 ? ` (${data.totalUnused} unused)` : '';
11
+ console.log(
12
+ `\n# ${data.file} — ${data.totalExported} exported${unusedNote}, ${data.totalInternal} internal\n`,
13
+ );
14
+ }
15
+ }
16
+
17
+ function printExportSymbols(results) {
18
+ for (const sym of results) {
19
+ const icon = kindIcon(sym.kind);
20
+ const sig = sym.signature?.params ? `(${sym.signature.params})` : '';
21
+ const role = sym.role ? ` [${sym.role}]` : '';
22
+ console.log(` ${icon} ${sym.name}${sig}${role} :${sym.line}`);
23
+ if (sym.consumers.length === 0) {
24
+ console.log(' (no consumers)');
25
+ } else {
26
+ for (const c of sym.consumers) {
27
+ console.log(` <- ${c.name} (${c.file}:${c.line})`);
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ export function fileExports(file, customDbPath, opts = {}) {
34
+ const data = exportsData(file, customDbPath, opts);
35
+ if (outputResult(data, 'results', opts)) return;
36
+
37
+ if (data.results.length === 0) {
38
+ if (opts.unused) {
39
+ console.log(`No unused exports found for "${file}".`);
40
+ } else {
41
+ console.log(`No exported symbols found for "${file}". Run "codegraph build" first.`);
42
+ }
43
+ return;
44
+ }
45
+
46
+ printExportHeader(data, opts);
47
+ printExportSymbols(data.results);
48
+
49
+ if (data.reexports.length > 0) {
50
+ console.log(`\n Re-exports: ${data.reexports.map((r) => r.file).join(', ')}`);
51
+ }
52
+ console.log();
53
+ }
@@ -0,0 +1,214 @@
1
+ import {
2
+ diffImpactData,
3
+ diffImpactMermaid,
4
+ fileDepsData,
5
+ fnDepsData,
6
+ fnImpactData,
7
+ impactAnalysisData,
8
+ kindIcon,
9
+ } from '../../domain/queries.js';
10
+ import { outputResult } from '../../infrastructure/result-formatter.js';
11
+
12
+ export function fileDeps(file, customDbPath, opts = {}) {
13
+ const data = fileDepsData(file, customDbPath, opts);
14
+ if (outputResult(data, 'results', opts)) return;
15
+
16
+ if (data.results.length === 0) {
17
+ console.log(`No file matching "${file}" in graph`);
18
+ return;
19
+ }
20
+
21
+ for (const r of data.results) {
22
+ console.log(`\n# ${r.file}\n`);
23
+ console.log(` -> Imports (${r.imports.length}):`);
24
+ for (const i of r.imports) {
25
+ const typeTag = i.typeOnly ? ' (type-only)' : '';
26
+ console.log(` -> ${i.file}${typeTag}`);
27
+ }
28
+ console.log(`\n <- Imported by (${r.importedBy.length}):`);
29
+ for (const i of r.importedBy) console.log(` <- ${i.file}`);
30
+ if (r.definitions.length > 0) {
31
+ console.log(`\n Definitions (${r.definitions.length}):`);
32
+ for (const d of r.definitions.slice(0, 30))
33
+ console.log(` ${kindIcon(d.kind)} ${d.name} :${d.line}`);
34
+ if (r.definitions.length > 30) console.log(` ... and ${r.definitions.length - 30} more`);
35
+ }
36
+ console.log();
37
+ }
38
+ }
39
+
40
+ export function fnDeps(name, customDbPath, opts = {}) {
41
+ const data = fnDepsData(name, customDbPath, opts);
42
+ if (outputResult(data, 'results', opts)) return;
43
+
44
+ if (data.results.length === 0) {
45
+ console.log(`No function/method/class matching "${name}"`);
46
+ return;
47
+ }
48
+
49
+ for (const r of data.results) {
50
+ console.log(`\n${kindIcon(r.kind)} ${r.name} (${r.kind}) -- ${r.file}:${r.line}\n`);
51
+ if (r.callees.length > 0) {
52
+ console.log(` -> Calls (${r.callees.length}):`);
53
+ for (const c of r.callees)
54
+ console.log(` -> ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
55
+ }
56
+ if (r.callers.length > 0) {
57
+ console.log(`\n <- Called by (${r.callers.length}):`);
58
+ for (const c of r.callers) {
59
+ const via = c.viaHierarchy ? ` (via ${c.viaHierarchy})` : '';
60
+ console.log(` <- ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${via}`);
61
+ }
62
+ }
63
+ for (const [d, fns] of Object.entries(r.transitiveCallers)) {
64
+ console.log(
65
+ `\n ${'<-'.repeat(parseInt(d, 10))} Transitive callers (depth ${d}, ${fns.length}):`,
66
+ );
67
+ for (const n of fns.slice(0, 20))
68
+ console.log(
69
+ ` ${' '.repeat(parseInt(d, 10) - 1)}<- ${kindIcon(n.kind)} ${n.name} ${n.file}:${n.line}`,
70
+ );
71
+ if (fns.length > 20) console.log(` ... and ${fns.length - 20} more`);
72
+ }
73
+ if (r.callees.length === 0 && r.callers.length === 0) {
74
+ console.log(` (no call edges found -- may be invoked dynamically or via re-exports)`);
75
+ }
76
+ console.log();
77
+ }
78
+ }
79
+
80
+ export function impactAnalysis(file, customDbPath, opts = {}) {
81
+ const data = impactAnalysisData(file, customDbPath, opts);
82
+ if (outputResult(data, 'sources', opts)) return;
83
+
84
+ if (data.sources.length === 0) {
85
+ console.log(`No file matching "${file}" in graph`);
86
+ return;
87
+ }
88
+
89
+ console.log(`\nImpact analysis for files matching "${file}":\n`);
90
+ for (const s of data.sources) console.log(` # ${s} (source)`);
91
+
92
+ const levels = data.levels;
93
+ if (Object.keys(levels).length === 0) {
94
+ console.log(` No dependents found.`);
95
+ } else {
96
+ for (const level of Object.keys(levels).sort((a, b) => a - b)) {
97
+ const nodes = levels[level];
98
+ console.log(
99
+ `\n ${'--'.repeat(parseInt(level, 10))} Level ${level} (${nodes.length} files):`,
100
+ );
101
+ for (const n of nodes.slice(0, 30))
102
+ console.log(` ${' '.repeat(parseInt(level, 10))}^ ${n.file}`);
103
+ if (nodes.length > 30) console.log(` ... and ${nodes.length - 30} more`);
104
+ }
105
+ }
106
+ console.log(`\n Total: ${data.totalDependents} files transitively depend on "${file}"\n`);
107
+ }
108
+
109
+ export function fnImpact(name, customDbPath, opts = {}) {
110
+ const data = fnImpactData(name, customDbPath, opts);
111
+ if (outputResult(data, 'results', opts)) return;
112
+
113
+ if (data.results.length === 0) {
114
+ console.log(`No function/method/class matching "${name}"`);
115
+ return;
116
+ }
117
+
118
+ for (const r of data.results) {
119
+ console.log(`\nFunction impact: ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}\n`);
120
+ if (Object.keys(r.levels).length === 0) {
121
+ console.log(` No callers found.`);
122
+ } else {
123
+ for (const [level, fns] of Object.entries(r.levels).sort((a, b) => a[0] - b[0])) {
124
+ const l = parseInt(level, 10);
125
+ console.log(` ${'--'.repeat(l)} Level ${level} (${fns.length} functions):`);
126
+ for (const f of fns.slice(0, 20))
127
+ console.log(` ${' '.repeat(l)}^ ${kindIcon(f.kind)} ${f.name} ${f.file}:${f.line}`);
128
+ if (fns.length > 20) console.log(` ... and ${fns.length - 20} more`);
129
+ }
130
+ }
131
+ console.log(`\n Total: ${r.totalDependents} functions transitively depend on ${r.name}\n`);
132
+ }
133
+ }
134
+
135
+ function printDiffFunctions(data) {
136
+ console.log(`\ndiff-impact: ${data.changedFiles} files changed\n`);
137
+ console.log(` ${data.affectedFunctions.length} functions changed:\n`);
138
+ for (const fn of data.affectedFunctions) {
139
+ console.log(` ${kindIcon(fn.kind)} ${fn.name} -- ${fn.file}:${fn.line}`);
140
+ if (fn.transitiveCallers > 0) console.log(` ^ ${fn.transitiveCallers} transitive callers`);
141
+ }
142
+ }
143
+
144
+ function printDiffCoupled(data) {
145
+ if (!data.historicallyCoupled?.length) return;
146
+ console.log('\n Historically coupled (not in static graph):\n');
147
+ for (const c of data.historicallyCoupled) {
148
+ const pct = `${(c.jaccard * 100).toFixed(0)}%`;
149
+ console.log(
150
+ ` ${c.file} <- coupled with ${c.coupledWith} (${pct}, ${c.commitCount} commits)`,
151
+ );
152
+ }
153
+ }
154
+
155
+ function printDiffOwnership(data) {
156
+ if (!data.ownership) return;
157
+ console.log(`\n Affected owners: ${data.ownership.affectedOwners.join(', ')}`);
158
+ console.log(` Suggested reviewers: ${data.ownership.suggestedReviewers.join(', ')}`);
159
+ }
160
+
161
+ function printDiffBoundaries(data) {
162
+ if (!data.boundaryViolations?.length) return;
163
+ console.log(`\n Boundary violations (${data.boundaryViolationCount}):\n`);
164
+ for (const v of data.boundaryViolations) {
165
+ console.log(` [${v.name}] ${v.file} -> ${v.targetFile}`);
166
+ if (v.message) console.log(` ${v.message}`);
167
+ }
168
+ }
169
+
170
+ function printDiffSummary(summary) {
171
+ if (!summary) return;
172
+ let line = `\n Summary: ${summary.functionsChanged} functions changed -> ${summary.callersAffected} callers affected across ${summary.filesAffected} files`;
173
+ if (summary.historicallyCoupledCount > 0) {
174
+ line += `, ${summary.historicallyCoupledCount} historically coupled`;
175
+ }
176
+ if (summary.ownersAffected > 0) {
177
+ line += `, ${summary.ownersAffected} owners affected`;
178
+ }
179
+ if (summary.boundaryViolationCount > 0) {
180
+ line += `, ${summary.boundaryViolationCount} boundary violations`;
181
+ }
182
+ console.log(`${line}\n`);
183
+ }
184
+
185
+ export function diffImpact(customDbPath, opts = {}) {
186
+ if (opts.format === 'mermaid') {
187
+ console.log(diffImpactMermaid(customDbPath, opts));
188
+ return;
189
+ }
190
+ const data = diffImpactData(customDbPath, opts);
191
+ if (opts.format === 'json') opts = { ...opts, json: true };
192
+ if (outputResult(data, 'affectedFunctions', opts)) return;
193
+
194
+ if (data.error) {
195
+ console.log(data.error);
196
+ return;
197
+ }
198
+ if (data.changedFiles === 0) {
199
+ console.log('No changes detected.');
200
+ return;
201
+ }
202
+ if (data.affectedFunctions.length === 0) {
203
+ console.log(
204
+ ' No function-level changes detected (changes may be in imports, types, or config).',
205
+ );
206
+ return;
207
+ }
208
+
209
+ printDiffFunctions(data);
210
+ printDiffCoupled(data);
211
+ printDiffOwnership(data);
212
+ printDiffBoundaries(data);
213
+ printDiffSummary(data.summary);
214
+ }
@@ -0,0 +1,5 @@
1
+ export { fileExports } from './exports.js';
2
+ export { diffImpact, fileDeps, fnDeps, fnImpact, impactAnalysis } from './impact.js';
3
+ export { children, context, explain, queryName, where } from './inspect.js';
4
+ export { moduleMap, roles, stats } from './overview.js';
5
+ export { symbolPath } from './path.js';