@optave/codegraph 3.11.0 → 3.11.1

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 (223) hide show
  1. package/README.md +38 -31
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +91 -60
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitor-utils.d.ts +3 -0
  6. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  7. package/dist/ast-analysis/visitor-utils.js +83 -49
  8. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
  11. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  12. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
  14. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  15. package/dist/cli/commands/embed.d.ts.map +1 -1
  16. package/dist/cli/commands/embed.js +49 -4
  17. package/dist/cli/commands/embed.js.map +1 -1
  18. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  19. package/dist/domain/analysis/dependencies.js +106 -80
  20. package/dist/domain/analysis/dependencies.js.map +1 -1
  21. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  22. package/dist/domain/analysis/fn-impact.js +77 -52
  23. package/dist/domain/analysis/fn-impact.js.map +1 -1
  24. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  25. package/dist/domain/analysis/module-map.js +132 -121
  26. package/dist/domain/analysis/module-map.js.map +1 -1
  27. package/dist/domain/graph/builder/helpers.d.ts +4 -4
  28. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  29. package/dist/domain/graph/builder/helpers.js +47 -33
  30. package/dist/domain/graph/builder/helpers.js.map +1 -1
  31. package/dist/domain/graph/builder/incremental.d.ts +6 -0
  32. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  33. package/dist/domain/graph/builder/incremental.js +142 -76
  34. package/dist/domain/graph/builder/incremental.js.map +1 -1
  35. package/dist/domain/graph/builder/pipeline.d.ts +1 -44
  36. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  37. package/dist/domain/graph/builder/pipeline.js +10 -766
  38. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  39. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  40. package/dist/domain/graph/builder/stages/build-edges.js +133 -96
  41. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  42. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  43. package/dist/domain/graph/builder/stages/build-structure.js +82 -65
  44. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  45. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  46. package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
  47. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  48. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  49. package/dist/domain/graph/builder/stages/finalize.js +60 -51
  50. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  51. package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
  52. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  53. package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
  54. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  55. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
  56. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
  57. package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
  58. package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
  59. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
  60. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
  61. package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
  62. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
  63. package/dist/domain/graph/cycles.d.ts +6 -4
  64. package/dist/domain/graph/cycles.d.ts.map +1 -1
  65. package/dist/domain/graph/cycles.js +50 -55
  66. package/dist/domain/graph/cycles.js.map +1 -1
  67. package/dist/domain/graph/journal.d.ts.map +1 -1
  68. package/dist/domain/graph/journal.js +89 -70
  69. package/dist/domain/graph/journal.js.map +1 -1
  70. package/dist/domain/graph/watcher.d.ts.map +1 -1
  71. package/dist/domain/graph/watcher.js +5 -2
  72. package/dist/domain/graph/watcher.js.map +1 -1
  73. package/dist/domain/parser.d.ts +12 -23
  74. package/dist/domain/parser.d.ts.map +1 -1
  75. package/dist/domain/parser.js +126 -79
  76. package/dist/domain/parser.js.map +1 -1
  77. package/dist/domain/search/generator.d.ts +3 -1
  78. package/dist/domain/search/generator.d.ts.map +1 -1
  79. package/dist/domain/search/generator.js +68 -45
  80. package/dist/domain/search/generator.js.map +1 -1
  81. package/dist/domain/search/models.d.ts +2 -0
  82. package/dist/domain/search/models.d.ts.map +1 -1
  83. package/dist/domain/search/models.js +37 -3
  84. package/dist/domain/search/models.js.map +1 -1
  85. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  86. package/dist/domain/search/search/hybrid.js +49 -40
  87. package/dist/domain/search/search/hybrid.js.map +1 -1
  88. package/dist/domain/search/search/semantic.d.ts.map +1 -1
  89. package/dist/domain/search/search/semantic.js +69 -49
  90. package/dist/domain/search/search/semantic.js.map +1 -1
  91. package/dist/domain/wasm-worker-entry.js +201 -136
  92. package/dist/domain/wasm-worker-entry.js.map +1 -1
  93. package/dist/extractors/elixir.js +95 -71
  94. package/dist/extractors/elixir.js.map +1 -1
  95. package/dist/extractors/gleam.d.ts.map +1 -1
  96. package/dist/extractors/gleam.js +23 -31
  97. package/dist/extractors/gleam.js.map +1 -1
  98. package/dist/extractors/helpers.d.ts +79 -1
  99. package/dist/extractors/helpers.d.ts.map +1 -1
  100. package/dist/extractors/helpers.js +137 -0
  101. package/dist/extractors/helpers.js.map +1 -1
  102. package/dist/extractors/java.d.ts.map +1 -1
  103. package/dist/extractors/java.js +37 -49
  104. package/dist/extractors/java.js.map +1 -1
  105. package/dist/extractors/javascript.d.ts.map +1 -1
  106. package/dist/extractors/javascript.js +44 -44
  107. package/dist/extractors/javascript.js.map +1 -1
  108. package/dist/extractors/julia.js +27 -34
  109. package/dist/extractors/julia.js.map +1 -1
  110. package/dist/extractors/r.d.ts.map +1 -1
  111. package/dist/extractors/r.js +33 -58
  112. package/dist/extractors/r.js.map +1 -1
  113. package/dist/extractors/solidity.d.ts.map +1 -1
  114. package/dist/extractors/solidity.js +38 -61
  115. package/dist/extractors/solidity.js.map +1 -1
  116. package/dist/features/boundaries.d.ts.map +1 -1
  117. package/dist/features/boundaries.js +49 -39
  118. package/dist/features/boundaries.js.map +1 -1
  119. package/dist/features/cfg.d.ts.map +1 -1
  120. package/dist/features/cfg.js +90 -63
  121. package/dist/features/cfg.js.map +1 -1
  122. package/dist/features/check.d.ts.map +1 -1
  123. package/dist/features/check.js +43 -34
  124. package/dist/features/check.js.map +1 -1
  125. package/dist/features/cochange.d.ts.map +1 -1
  126. package/dist/features/cochange.js +68 -56
  127. package/dist/features/cochange.js.map +1 -1
  128. package/dist/features/complexity.d.ts.map +1 -1
  129. package/dist/features/complexity.js +105 -75
  130. package/dist/features/complexity.js.map +1 -1
  131. package/dist/features/dataflow.d.ts.map +1 -1
  132. package/dist/features/dataflow.js +37 -29
  133. package/dist/features/dataflow.js.map +1 -1
  134. package/dist/features/flow.d.ts.map +1 -1
  135. package/dist/features/flow.js +31 -22
  136. package/dist/features/flow.js.map +1 -1
  137. package/dist/features/graph-enrichment.d.ts.map +1 -1
  138. package/dist/features/graph-enrichment.js +77 -70
  139. package/dist/features/graph-enrichment.js.map +1 -1
  140. package/dist/features/owners.d.ts +17 -26
  141. package/dist/features/owners.d.ts.map +1 -1
  142. package/dist/features/owners.js +120 -109
  143. package/dist/features/owners.js.map +1 -1
  144. package/dist/features/sequence.d.ts.map +1 -1
  145. package/dist/features/sequence.js +59 -54
  146. package/dist/features/sequence.js.map +1 -1
  147. package/dist/features/structure-query.d.ts.map +1 -1
  148. package/dist/features/structure-query.js +60 -60
  149. package/dist/features/structure-query.js.map +1 -1
  150. package/dist/features/structure.js +28 -36
  151. package/dist/features/structure.js.map +1 -1
  152. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  153. package/dist/graph/algorithms/leiden/optimiser.js +100 -69
  154. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  155. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  156. package/dist/graph/classifiers/roles.js +63 -59
  157. package/dist/graph/classifiers/roles.js.map +1 -1
  158. package/dist/infrastructure/config.d.ts +1 -1
  159. package/dist/infrastructure/config.d.ts.map +1 -1
  160. package/dist/infrastructure/config.js +1 -1
  161. package/dist/infrastructure/config.js.map +1 -1
  162. package/dist/presentation/cfg.d.ts.map +1 -1
  163. package/dist/presentation/cfg.js +44 -29
  164. package/dist/presentation/cfg.js.map +1 -1
  165. package/dist/presentation/flow.d.ts.map +1 -1
  166. package/dist/presentation/flow.js +58 -38
  167. package/dist/presentation/flow.js.map +1 -1
  168. package/dist/types.d.ts +1 -1
  169. package/dist/types.d.ts.map +1 -1
  170. package/package.json +7 -7
  171. package/src/ast-analysis/engine.ts +145 -61
  172. package/src/ast-analysis/visitor-utils.ts +86 -46
  173. package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
  174. package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
  175. package/src/cli/commands/embed.ts +54 -4
  176. package/src/domain/analysis/dependencies.ts +166 -85
  177. package/src/domain/analysis/fn-impact.ts +120 -50
  178. package/src/domain/analysis/module-map.ts +175 -140
  179. package/src/domain/graph/builder/helpers.ts +85 -76
  180. package/src/domain/graph/builder/incremental.ts +217 -90
  181. package/src/domain/graph/builder/pipeline.ts +19 -957
  182. package/src/domain/graph/builder/stages/build-edges.ts +198 -140
  183. package/src/domain/graph/builder/stages/build-structure.ts +115 -82
  184. package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
  185. package/src/domain/graph/builder/stages/finalize.ts +72 -70
  186. package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
  187. package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
  188. package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
  189. package/src/domain/graph/cycles.ts +51 -49
  190. package/src/domain/graph/journal.ts +84 -69
  191. package/src/domain/graph/watcher.ts +8 -2
  192. package/src/domain/parser.ts +143 -66
  193. package/src/domain/search/generator.ts +132 -74
  194. package/src/domain/search/models.ts +39 -3
  195. package/src/domain/search/search/hybrid.ts +53 -42
  196. package/src/domain/search/search/semantic.ts +105 -65
  197. package/src/domain/wasm-worker-entry.ts +235 -152
  198. package/src/extractors/elixir.ts +91 -64
  199. package/src/extractors/gleam.ts +33 -37
  200. package/src/extractors/helpers.ts +205 -1
  201. package/src/extractors/java.ts +42 -45
  202. package/src/extractors/javascript.ts +44 -43
  203. package/src/extractors/julia.ts +28 -35
  204. package/src/extractors/r.ts +38 -56
  205. package/src/extractors/solidity.ts +43 -71
  206. package/src/features/boundaries.ts +64 -46
  207. package/src/features/cfg.ts +145 -74
  208. package/src/features/check.ts +60 -43
  209. package/src/features/cochange.ts +95 -72
  210. package/src/features/complexity.ts +134 -79
  211. package/src/features/dataflow.ts +57 -34
  212. package/src/features/flow.ts +48 -24
  213. package/src/features/graph-enrichment.ts +105 -70
  214. package/src/features/owners.ts +186 -146
  215. package/src/features/sequence.ts +99 -69
  216. package/src/features/structure-query.ts +94 -79
  217. package/src/features/structure.ts +56 -56
  218. package/src/graph/algorithms/leiden/optimiser.ts +142 -87
  219. package/src/graph/classifiers/roles.ts +64 -54
  220. package/src/infrastructure/config.ts +1 -1
  221. package/src/presentation/cfg.ts +48 -32
  222. package/src/presentation/flow.ts +100 -52
  223. package/src/types.ts +1 -1
@@ -78,6 +78,68 @@ export interface RoleClassificationNode {
78
78
  hasActiveFileSiblings?: boolean;
79
79
  }
80
80
 
81
+ /**
82
+ * Compute median fan-in and fan-out across nodes with non-zero values.
83
+ * Used as thresholds for "high" fan-in/out classification.
84
+ */
85
+ function computeFanMedians(nodes: RoleClassificationNode[]): { fanIn: number; fanOut: number } {
86
+ const nonZeroFanIn = nodes
87
+ .filter((n) => n.fanIn > 0)
88
+ .map((n) => n.fanIn)
89
+ .sort((a, b) => a - b);
90
+ const nonZeroFanOut = nodes
91
+ .filter((n) => n.fanOut > 0)
92
+ .map((n) => n.fanOut)
93
+ .sort((a, b) => a - b);
94
+ return { fanIn: median(nonZeroFanIn), fanOut: median(nonZeroFanOut) };
95
+ }
96
+
97
+ /**
98
+ * Classify a node with `fanIn === 0` that is not exported.
99
+ * Covers framework-active constants, test-only callables, and the dead-* family.
100
+ */
101
+ function classifyUnreferencedNode(node: RoleClassificationNode): Role {
102
+ if (node.kind === 'constant' && node.hasActiveFileSiblings) {
103
+ // Constants consumed via identifier reference (not calls) have no
104
+ // inbound call edges. If the same file has active callables, the
105
+ // constant is almost certainly used locally — classify as leaf.
106
+ return 'leaf';
107
+ }
108
+ if (node.testOnlyFanIn != null && node.testOnlyFanIn > 0) return 'test-only';
109
+ return classifyDeadSubRole(node);
110
+ }
111
+
112
+ /**
113
+ * Pick a role from fan-in/fan-out shape: core/utility/adapter/leaf.
114
+ * Called after entry/test-only/dead cases have been ruled out.
115
+ */
116
+ function classifyByFanShape(highIn: boolean, highOut: boolean): Role {
117
+ if (highIn && !highOut) return 'core';
118
+ if (highIn && highOut) return 'utility';
119
+ if (!highIn && highOut) return 'adapter';
120
+ return 'leaf';
121
+ }
122
+
123
+ /**
124
+ * Apply role-classification rules to a single node.
125
+ * Order matters — framework entries are tagged first, then dead/test cases,
126
+ * then the fan-in/fan-out shape decides among the structural roles.
127
+ */
128
+ function classifyNodeRole(node: RoleClassificationNode, medFanIn: number, medFanOut: number): Role {
129
+ if (FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p))) return 'entry';
130
+
131
+ if (node.fanIn === 0) {
132
+ return node.isExported ? 'entry' : classifyUnreferencedNode(node);
133
+ }
134
+
135
+ const hasProdFanIn = typeof node.productionFanIn === 'number';
136
+ if (hasProdFanIn && node.productionFanIn === 0 && !node.isExported) return 'test-only';
137
+
138
+ const highIn = node.fanIn >= medFanIn;
139
+ const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
140
+ return classifyByFanShape(highIn, highOut);
141
+ }
142
+
81
143
  /**
82
144
  * Classify nodes into architectural roles based on fan-in/fan-out metrics.
83
145
  */
@@ -87,63 +149,11 @@ export function classifyRoles(
87
149
  ): Map<string, Role> {
88
150
  if (nodes.length === 0) return new Map();
89
151
 
90
- let medFanIn: number;
91
- let medFanOut: number;
92
- if (medianOverrides) {
93
- medFanIn = medianOverrides.fanIn;
94
- medFanOut = medianOverrides.fanOut;
95
- } else {
96
- const nonZeroFanIn = nodes
97
- .filter((n) => n.fanIn > 0)
98
- .map((n) => n.fanIn)
99
- .sort((a, b) => a - b);
100
- const nonZeroFanOut = nodes
101
- .filter((n) => n.fanOut > 0)
102
- .map((n) => n.fanOut)
103
- .sort((a, b) => a - b);
104
- medFanIn = median(nonZeroFanIn);
105
- medFanOut = median(nonZeroFanOut);
106
- }
152
+ const { fanIn: medFanIn, fanOut: medFanOut } = medianOverrides ?? computeFanMedians(nodes);
107
153
 
108
154
  const result = new Map<string, Role>();
109
-
110
155
  for (const node of nodes) {
111
- const highIn = node.fanIn >= medFanIn && node.fanIn > 0;
112
- const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
113
- const hasProdFanIn = typeof node.productionFanIn === 'number';
114
-
115
- let role: Role;
116
- const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
117
- if (isFrameworkEntry) {
118
- role = 'entry';
119
- } else if (node.fanIn === 0 && !node.isExported) {
120
- if (node.kind === 'constant' && node.hasActiveFileSiblings) {
121
- // Constants consumed via identifier reference (not calls) have no
122
- // inbound call edges. If the same file has active callables, the
123
- // constant is almost certainly used locally — classify as leaf.
124
- role = 'leaf';
125
- } else {
126
- role =
127
- node.testOnlyFanIn != null && node.testOnlyFanIn > 0
128
- ? 'test-only'
129
- : classifyDeadSubRole(node);
130
- }
131
- } else if (node.fanIn === 0 && node.isExported) {
132
- role = 'entry';
133
- } else if (hasProdFanIn && node.fanIn > 0 && node.productionFanIn === 0 && !node.isExported) {
134
- role = 'test-only';
135
- } else if (highIn && !highOut) {
136
- role = 'core';
137
- } else if (highIn && highOut) {
138
- role = 'utility';
139
- } else if (!highIn && highOut) {
140
- role = 'adapter';
141
- } else {
142
- role = 'leaf';
143
- }
144
-
145
- result.set(node.id, role);
156
+ result.set(node.id, classifyNodeRole(node, medFanIn, medFanOut));
146
157
  }
147
-
148
158
  return result;
149
159
  }
@@ -30,7 +30,7 @@ export const DEFAULTS = {
30
30
  defaultLimit: 20,
31
31
  excludeTests: false,
32
32
  },
33
- embeddings: { model: 'nomic-v1.5', llmProvider: null as string | null },
33
+ embeddings: { model: null as string | null, llmProvider: null as string | null },
34
34
  llm: {
35
35
  provider: null as string | null,
36
36
  model: null as string | null,
@@ -1,6 +1,8 @@
1
1
  import { cfgData, cfgToDOT, cfgToMermaid } from '../features/cfg.js';
2
2
  import { outputResult } from '../infrastructure/result-formatter.js';
3
3
 
4
+ type CfgData = ReturnType<typeof cfgData>;
5
+
4
6
  interface CfgCliOpts {
5
7
  json?: boolean;
6
8
  ndjson?: boolean;
@@ -36,13 +38,56 @@ interface CfgResultEntry {
36
38
  edges: CfgEdge[];
37
39
  }
38
40
 
41
+ function renderBlockLocation(b: CfgBlock): string {
42
+ if (!b.startLine) return '';
43
+ const endSuffix = b.endLine && b.endLine !== b.startLine ? `-${b.endLine}` : '';
44
+ return ` L${b.startLine}${endSuffix}`;
45
+ }
46
+
47
+ function printCfgBlocks(blocks: CfgBlock[]): void {
48
+ if (blocks.length === 0) return;
49
+ console.log('\n Blocks:');
50
+ for (const b of blocks) {
51
+ const label = b.label ? ` (${b.label})` : '';
52
+ console.log(` [${b.index}] ${b.type}${label}${renderBlockLocation(b)}`);
53
+ }
54
+ }
55
+
56
+ function printCfgEdges(edges: CfgEdge[]): void {
57
+ if (edges.length === 0) return;
58
+ console.log('\n Edges:');
59
+ for (const e of edges) {
60
+ console.log(` B${e.source} → B${e.target} [${e.kind}]`);
61
+ }
62
+ }
63
+
64
+ function printCfgEntry(r: CfgResultEntry): void {
65
+ console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
66
+ console.log('─'.repeat(60));
67
+ console.log(` Blocks: ${r.summary.blockCount} Edges: ${r.summary.edgeCount}`);
68
+ printCfgBlocks(r.blocks);
69
+ printCfgEdges(r.edges);
70
+ }
71
+
72
+ function tryRenderGraphFormat(format: string, data: CfgData): boolean {
73
+ if (format === 'dot') {
74
+ console.log(cfgToDOT(data));
75
+ return true;
76
+ }
77
+ if (format === 'mermaid') {
78
+ console.log(cfgToMermaid(data));
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+
39
84
  export function cfg(name: string, customDbPath: string | undefined, opts: CfgCliOpts = {}): void {
40
85
  const data = cfgData(name, customDbPath, opts);
41
86
 
42
87
  if (outputResult(data, 'results', opts)) return;
43
88
 
44
89
  if (data.warning) {
45
- console.log(`\u26A0 ${data.warning}`);
90
+ console.log(`⚠ ${data.warning}`);
46
91
  return;
47
92
  }
48
93
  if (data.results.length === 0) {
@@ -50,38 +95,9 @@ export function cfg(name: string, customDbPath: string | undefined, opts: CfgCli
50
95
  return;
51
96
  }
52
97
 
53
- const format = opts.format || 'text';
54
- if (format === 'dot') {
55
- console.log(cfgToDOT(data));
56
- return;
57
- }
58
- if (format === 'mermaid') {
59
- console.log(cfgToMermaid(data));
60
- return;
61
- }
98
+ if (tryRenderGraphFormat(opts.format || 'text', data)) return;
62
99
 
63
- // Text format
64
100
  for (const r of data.results as CfgResultEntry[]) {
65
- console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
66
- console.log('\u2500'.repeat(60));
67
- console.log(` Blocks: ${r.summary.blockCount} Edges: ${r.summary.edgeCount}`);
68
-
69
- if (r.blocks.length > 0) {
70
- console.log('\n Blocks:');
71
- for (const b of r.blocks) {
72
- const loc = b.startLine
73
- ? ` L${b.startLine}${b.endLine && b.endLine !== b.startLine ? `-${b.endLine}` : ''}`
74
- : '';
75
- const label = b.label ? ` (${b.label})` : '';
76
- console.log(` [${b.index}] ${b.type}${label}${loc}`);
77
- }
78
- }
79
-
80
- if (r.edges.length > 0) {
81
- console.log('\n Edges:');
82
- for (const e of r.edges) {
83
- console.log(` B${e.source} \u2192 B${e.target} [${e.kind}]`);
84
- }
85
- }
101
+ printCfgEntry(r);
86
102
  }
87
103
  }
@@ -16,54 +16,65 @@ interface FlowOpts {
16
16
  csv?: boolean;
17
17
  }
18
18
 
19
- export function flow(
20
- name: string | undefined,
21
- dbPath: string | undefined,
22
- opts: FlowOpts = {},
23
- ): void {
24
- if (opts.list) {
25
- const data = listEntryPointsData(dbPath, {
26
- noTests: opts.noTests,
27
- limit: opts.limit,
28
- offset: opts.offset,
29
- }) as any;
30
- if (outputResult(data, 'entries', opts)) return;
31
- if (data.count === 0) {
32
- console.log('No entry points found. Run "codegraph build" first.');
33
- return;
34
- }
35
- console.log(`\nEntry points (${data.count} total):\n`);
36
- for (const [type, entries] of Object.entries(
37
- data.byType as Record<
38
- string,
39
- Array<{ kind: string; name: string; file: string; line: number }>
40
- >,
41
- )) {
42
- console.log(` ${type} (${entries.length}):`);
43
- for (const e of entries) {
44
- console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
45
- }
46
- console.log();
47
- }
48
- return;
49
- }
19
+ interface EntryPoint {
20
+ kind: string;
21
+ name: string;
22
+ file: string;
23
+ line: number;
24
+ }
50
25
 
51
- if (!name) {
52
- console.log(
53
- 'Please provide a function or entry-point name. Use --list to see available entry points.',
54
- );
55
- return;
56
- }
26
+ interface FlowNode {
27
+ kind: string;
28
+ name: string;
29
+ file: string;
30
+ line: number;
31
+ }
57
32
 
58
- const data = flowData(name, dbPath, opts) as any;
59
- if (outputResult(data, 'steps', opts)) return;
33
+ interface FlowStep {
34
+ depth: number;
35
+ nodes: FlowNode[];
36
+ }
60
37
 
61
- if (!data.entry) {
62
- console.log(`No matching entry point or function found for "${name}".`);
38
+ interface FlowCycle {
39
+ from: string;
40
+ to: string;
41
+ depth: number;
42
+ }
43
+
44
+ interface FlowResult {
45
+ entry?: { kind: string; name: string; type: string; file: string; line: number };
46
+ depth: number;
47
+ totalReached: number;
48
+ leaves: Array<{ name: string; file: string }>;
49
+ steps: FlowStep[];
50
+ cycles: FlowCycle[];
51
+ truncated?: boolean;
52
+ }
53
+
54
+ function runListEntryPoints(dbPath: string | undefined, opts: FlowOpts): void {
55
+ const data = listEntryPointsData(dbPath, {
56
+ noTests: opts.noTests,
57
+ limit: opts.limit,
58
+ offset: opts.offset,
59
+ }) as { count: number; byType: Record<string, EntryPoint[]> };
60
+ if (outputResult(data, 'entries', opts)) return;
61
+ if (data.count === 0) {
62
+ console.log('No entry points found. Run "codegraph build" first.');
63
63
  return;
64
64
  }
65
+ console.log(`\nEntry points (${data.count} total):\n`);
66
+ for (const [type, entries] of Object.entries(data.byType)) {
67
+ console.log(` ${type} (${entries.length}):`);
68
+ for (const e of entries) {
69
+ console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
70
+ }
71
+ console.log();
72
+ }
73
+ }
65
74
 
75
+ function printFlowHeader(data: FlowResult): void {
66
76
  const e = data.entry;
77
+ if (!e) return;
67
78
  const typeTag = e.type !== 'exported' ? ` (${e.type})` : '';
68
79
  console.log(`\nFlow from: [${kindIcon(e.kind)}] ${e.name}${typeTag} ${e.file}:${e.line}`);
69
80
  console.log(
@@ -73,27 +84,64 @@ export function flow(
73
84
  console.log(` (truncated at depth ${data.depth})`);
74
85
  }
75
86
  console.log();
87
+ }
88
+
89
+ function isLeafNode(n: FlowNode, leaves: Array<{ name: string; file: string }>): boolean {
90
+ return leaves.some((l) => l.name === n.name && l.file === n.file);
91
+ }
76
92
 
93
+ /** Returns true when the node is a leaf (no steps); caller should skip cycle output. */
94
+ function printFlowSteps(data: FlowResult): boolean {
77
95
  if (data.steps.length === 0) {
78
96
  console.log(' (leaf node — no callees)');
79
- return;
97
+ return true;
80
98
  }
81
-
82
99
  for (const step of data.steps) {
83
100
  console.log(` depth ${step.depth}:`);
84
101
  for (const n of step.nodes) {
85
- const isLeaf = data.leaves.some(
86
- (l: { name: string; file: string }) => l.name === n.name && l.file === n.file,
87
- );
88
- const leafTag = isLeaf ? ' [leaf]' : '';
102
+ const leafTag = isLeafNode(n, data.leaves) ? ' [leaf]' : '';
89
103
  console.log(` [${kindIcon(n.kind)}] ${n.name} ${n.file}:${n.line}${leafTag}`);
90
104
  }
91
105
  }
106
+ return false;
107
+ }
108
+
109
+ function printFlowCycles(cycles: FlowCycle[]): void {
110
+ if (cycles.length === 0) return;
111
+ console.log('\n Cycles detected:');
112
+ for (const c of cycles) {
113
+ console.log(` ${c.from} -> ${c.to} (at depth ${c.depth})`);
114
+ }
115
+ }
92
116
 
93
- if (data.cycles.length > 0) {
94
- console.log('\n Cycles detected:');
95
- for (const c of data.cycles) {
96
- console.log(` ${c.from} -> ${c.to} (at depth ${c.depth})`);
97
- }
117
+ export function flow(
118
+ name: string | undefined,
119
+ dbPath: string | undefined,
120
+ opts: FlowOpts = {},
121
+ ): void {
122
+ if (opts.list) {
123
+ runListEntryPoints(dbPath, opts);
124
+ return;
125
+ }
126
+
127
+ if (!name) {
128
+ console.log(
129
+ 'Please provide a function or entry-point name. Use --list to see available entry points.',
130
+ );
131
+ return;
132
+ }
133
+
134
+ const data = flowData(name, dbPath, opts) as unknown as FlowResult;
135
+ if (outputResult(data, 'steps', opts)) return;
136
+
137
+ if (!data.entry) {
138
+ console.log(`No matching entry point or function found for "${name}".`);
139
+ return;
140
+ }
141
+
142
+ printFlowHeader(data);
143
+ const isLeaf = printFlowSteps(data);
144
+ if (!isLeaf) {
145
+ printFlowCycles(data.cycles);
98
146
  }
99
147
  }
package/src/types.ts CHANGED
@@ -1128,7 +1128,7 @@ export interface CodegraphConfig {
1128
1128
  };
1129
1129
 
1130
1130
  embeddings: {
1131
- model: string;
1131
+ model: string | null;
1132
1132
  llmProvider: string | null;
1133
1133
  };
1134
1134