@optave/codegraph 3.1.0 → 3.1.2

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 (83) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/engine.js +365 -0
  5. package/src/ast-analysis/metrics.js +118 -0
  6. package/src/ast-analysis/rules/csharp.js +201 -0
  7. package/src/ast-analysis/rules/go.js +182 -0
  8. package/src/ast-analysis/rules/index.js +82 -0
  9. package/src/ast-analysis/rules/java.js +175 -0
  10. package/src/ast-analysis/rules/javascript.js +246 -0
  11. package/src/ast-analysis/rules/php.js +219 -0
  12. package/src/ast-analysis/rules/python.js +196 -0
  13. package/src/ast-analysis/rules/ruby.js +204 -0
  14. package/src/ast-analysis/rules/rust.js +173 -0
  15. package/src/ast-analysis/shared.js +223 -0
  16. package/src/ast-analysis/visitor-utils.js +176 -0
  17. package/src/ast-analysis/visitor.js +162 -0
  18. package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
  19. package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
  20. package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
  21. package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
  22. package/src/ast.js +26 -166
  23. package/src/audit.js +2 -88
  24. package/src/batch.js +0 -25
  25. package/src/boundaries.js +1 -1
  26. package/src/branch-compare.js +82 -172
  27. package/src/builder.js +48 -184
  28. package/src/cfg.js +148 -1174
  29. package/src/check.js +1 -84
  30. package/src/cli.js +118 -197
  31. package/src/cochange.js +1 -39
  32. package/src/commands/audit.js +88 -0
  33. package/src/commands/batch.js +26 -0
  34. package/src/commands/branch-compare.js +97 -0
  35. package/src/commands/cfg.js +55 -0
  36. package/src/commands/check.js +82 -0
  37. package/src/commands/cochange.js +37 -0
  38. package/src/commands/communities.js +69 -0
  39. package/src/commands/complexity.js +77 -0
  40. package/src/commands/dataflow.js +110 -0
  41. package/src/commands/flow.js +70 -0
  42. package/src/commands/manifesto.js +77 -0
  43. package/src/commands/owners.js +52 -0
  44. package/src/commands/query.js +21 -0
  45. package/src/commands/sequence.js +33 -0
  46. package/src/commands/structure.js +64 -0
  47. package/src/commands/triage.js +49 -0
  48. package/src/communities.js +22 -96
  49. package/src/complexity.js +234 -1591
  50. package/src/cycles.js +1 -1
  51. package/src/dataflow.js +274 -1352
  52. package/src/db/connection.js +88 -0
  53. package/src/db/migrations.js +312 -0
  54. package/src/db/query-builder.js +280 -0
  55. package/src/db/repository/build-stmts.js +104 -0
  56. package/src/db/repository/cfg.js +83 -0
  57. package/src/db/repository/cochange.js +41 -0
  58. package/src/db/repository/complexity.js +15 -0
  59. package/src/db/repository/dataflow.js +12 -0
  60. package/src/db/repository/edges.js +259 -0
  61. package/src/db/repository/embeddings.js +40 -0
  62. package/src/db/repository/graph-read.js +39 -0
  63. package/src/db/repository/index.js +42 -0
  64. package/src/db/repository/nodes.js +236 -0
  65. package/src/db.js +58 -399
  66. package/src/embedder.js +158 -174
  67. package/src/export.js +1 -1
  68. package/src/extractors/javascript.js +130 -5
  69. package/src/flow.js +153 -222
  70. package/src/index.js +53 -16
  71. package/src/infrastructure/result-formatter.js +21 -0
  72. package/src/infrastructure/test-filter.js +7 -0
  73. package/src/kinds.js +50 -0
  74. package/src/manifesto.js +1 -82
  75. package/src/mcp.js +37 -20
  76. package/src/owners.js +127 -182
  77. package/src/queries-cli.js +866 -0
  78. package/src/queries.js +1271 -2416
  79. package/src/sequence.js +179 -223
  80. package/src/structure.js +211 -269
  81. package/src/triage.js +117 -212
  82. package/src/viewer.js +1 -1
  83. package/src/watcher.js +7 -4
package/src/flow.js CHANGED
@@ -6,8 +6,9 @@
6
6
  */
7
7
 
8
8
  import { openReadonlyOrFail } from './db.js';
9
- import { paginateResult, printNdjson } from './paginate.js';
10
- import { findMatchingNodes, isTestFile, kindIcon } from './queries.js';
9
+ import { isTestFile } from './infrastructure/test-filter.js';
10
+ import { paginateResult } from './paginate.js';
11
+ import { CORE_SYMBOL_KINDS, findMatchingNodes } from './queries.js';
11
12
  import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
12
13
 
13
14
  /**
@@ -35,46 +36,49 @@ export function entryPointType(name) {
35
36
  */
36
37
  export function listEntryPointsData(dbPath, opts = {}) {
37
38
  const db = openReadonlyOrFail(dbPath);
38
- const noTests = opts.noTests || false;
39
+ try {
40
+ const noTests = opts.noTests || false;
39
41
 
40
- // Find all framework-prefixed nodes
41
- const prefixConditions = FRAMEWORK_ENTRY_PREFIXES.map(() => 'n.name LIKE ?').join(' OR ');
42
- const prefixParams = FRAMEWORK_ENTRY_PREFIXES.map((p) => `${p}%`);
42
+ // Find all framework-prefixed nodes
43
+ const prefixConditions = FRAMEWORK_ENTRY_PREFIXES.map(() => 'n.name LIKE ?').join(' OR ');
44
+ const prefixParams = FRAMEWORK_ENTRY_PREFIXES.map((p) => `${p}%`);
43
45
 
44
- let rows = db
45
- .prepare(
46
- `SELECT n.name, n.kind, n.file, n.line, n.role
47
- FROM nodes n
48
- WHERE (
49
- (${prefixConditions})
50
- OR n.role = 'entry'
51
- )
52
- AND n.kind NOT IN ('file', 'directory')
53
- ORDER BY n.name`,
54
- )
55
- .all(...prefixParams);
56
-
57
- if (noTests) rows = rows.filter((r) => !isTestFile(r.file));
58
-
59
- const entries = rows.map((r) => ({
60
- name: r.name,
61
- kind: r.kind,
62
- file: r.file,
63
- line: r.line,
64
- role: r.role,
65
- type: entryPointType(r.name) || (r.role === 'entry' ? 'exported' : null),
66
- }));
46
+ let rows = db
47
+ .prepare(
48
+ `SELECT n.name, n.kind, n.file, n.line, n.role
49
+ FROM nodes n
50
+ WHERE (
51
+ (${prefixConditions})
52
+ OR n.role = 'entry'
53
+ )
54
+ AND n.kind NOT IN ('file', 'directory')
55
+ ORDER BY n.name`,
56
+ )
57
+ .all(...prefixParams);
58
+
59
+ if (noTests) rows = rows.filter((r) => !isTestFile(r.file));
60
+
61
+ const entries = rows.map((r) => ({
62
+ name: r.name,
63
+ kind: r.kind,
64
+ file: r.file,
65
+ line: r.line,
66
+ role: r.role,
67
+ type: entryPointType(r.name) || (r.role === 'entry' ? 'exported' : null),
68
+ }));
69
+
70
+ const byType = {};
71
+ for (const e of entries) {
72
+ const t = e.type || 'other';
73
+ if (!byType[t]) byType[t] = [];
74
+ byType[t].push(e);
75
+ }
67
76
 
68
- const byType = {};
69
- for (const e of entries) {
70
- const t = e.type || 'other';
71
- if (!byType[t]) byType[t] = [];
72
- byType[t].push(e);
77
+ const base = { entries, byType, count: entries.length };
78
+ return paginateResult(base, 'entries', { limit: opts.limit, offset: opts.offset });
79
+ } finally {
80
+ db.close();
73
81
  }
74
-
75
- db.close();
76
- const base = { entries, byType, count: entries.length };
77
- return paginateResult(base, 'entries', { limit: opts.limit, offset: opts.offset });
78
82
  }
79
83
 
80
84
  /**
@@ -91,207 +95,134 @@ export function listEntryPointsData(dbPath, opts = {}) {
91
95
  */
92
96
  export function flowData(name, dbPath, opts = {}) {
93
97
  const db = openReadonlyOrFail(dbPath);
94
- const maxDepth = opts.depth || 10;
95
- const noTests = opts.noTests || false;
96
-
97
- // Phase 1: Direct LIKE match on full name
98
- let matchNode = findMatchingNodes(db, name, opts)[0] ?? null;
98
+ try {
99
+ const maxDepth = opts.depth || 10;
100
+ const noTests = opts.noTests || false;
101
+ const flowOpts = { ...opts, kinds: opts.kind ? [opts.kind] : CORE_SYMBOL_KINDS };
102
+
103
+ // Phase 1: Direct LIKE match on full name (use all 10 core symbol kinds,
104
+ // not just FUNCTION_KINDS, so flow can trace from interfaces/types/structs/etc.)
105
+ let matchNode = findMatchingNodes(db, name, flowOpts)[0] ?? null;
106
+
107
+ // Phase 2: Prefix-stripped matching — try adding framework prefixes
108
+ if (!matchNode) {
109
+ for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
110
+ matchNode = findMatchingNodes(db, `${prefix}${name}`, flowOpts)[0] ?? null;
111
+ if (matchNode) break;
112
+ }
113
+ }
99
114
 
100
- // Phase 2: Prefix-stripped matching — try adding framework prefixes
101
- if (!matchNode) {
102
- for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
103
- matchNode = findMatchingNodes(db, `${prefix}${name}`, opts)[0] ?? null;
104
- if (matchNode) break;
115
+ if (!matchNode) {
116
+ return {
117
+ entry: null,
118
+ depth: maxDepth,
119
+ steps: [],
120
+ leaves: [],
121
+ cycles: [],
122
+ totalReached: 0,
123
+ truncated: false,
124
+ };
105
125
  }
106
- }
107
126
 
108
- if (!matchNode) {
109
- db.close();
110
- return {
111
- entry: null,
112
- depth: maxDepth,
113
- steps: [],
114
- leaves: [],
115
- cycles: [],
116
- totalReached: 0,
117
- truncated: false,
127
+ const epType = entryPointType(matchNode.name);
128
+ const entry = {
129
+ name: matchNode.name,
130
+ kind: matchNode.kind,
131
+ file: matchNode.file,
132
+ line: matchNode.line,
133
+ type: epType || 'exported',
134
+ role: matchNode.role,
118
135
  };
119
- }
120
-
121
- const epType = entryPointType(matchNode.name);
122
- const entry = {
123
- name: matchNode.name,
124
- kind: matchNode.kind,
125
- file: matchNode.file,
126
- line: matchNode.line,
127
- type: epType || 'exported',
128
- role: matchNode.role,
129
- };
130
-
131
- // Forward BFS through callees
132
- const visited = new Set([matchNode.id]);
133
- let frontier = [matchNode.id];
134
- const steps = [];
135
- const cycles = [];
136
- let truncated = false;
137
136
 
138
- // Track which nodes are at each depth and their depth for leaf detection
139
- const nodeDepths = new Map();
140
- const idToNode = new Map();
141
- idToNode.set(matchNode.id, entry);
142
-
143
- for (let d = 1; d <= maxDepth; d++) {
144
- const nextFrontier = [];
145
- const levelNodes = [];
146
-
147
- for (const fid of frontier) {
148
- const callees = db
149
- .prepare(
150
- `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.role
151
- FROM edges e JOIN nodes n ON e.target_id = n.id
152
- WHERE e.source_id = ? AND e.kind = 'calls'`,
153
- )
154
- .all(fid);
155
-
156
- for (const c of callees) {
157
- if (noTests && isTestFile(c.file)) continue;
158
-
159
- if (visited.has(c.id)) {
160
- // Cycle detected
161
- const fromNode = idToNode.get(fid);
162
- if (fromNode) {
163
- cycles.push({ from: fromNode.name, to: c.name, depth: d });
137
+ // Forward BFS through callees
138
+ const visited = new Set([matchNode.id]);
139
+ let frontier = [matchNode.id];
140
+ const steps = [];
141
+ const cycles = [];
142
+ let truncated = false;
143
+
144
+ // Track which nodes are at each depth and their depth for leaf detection
145
+ const nodeDepths = new Map();
146
+ const idToNode = new Map();
147
+ idToNode.set(matchNode.id, entry);
148
+
149
+ for (let d = 1; d <= maxDepth; d++) {
150
+ const nextFrontier = [];
151
+ const levelNodes = [];
152
+
153
+ for (const fid of frontier) {
154
+ const callees = db
155
+ .prepare(
156
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.role
157
+ FROM edges e JOIN nodes n ON e.target_id = n.id
158
+ WHERE e.source_id = ? AND e.kind = 'calls'`,
159
+ )
160
+ .all(fid);
161
+
162
+ for (const c of callees) {
163
+ if (noTests && isTestFile(c.file)) continue;
164
+
165
+ if (visited.has(c.id)) {
166
+ // Cycle detected
167
+ const fromNode = idToNode.get(fid);
168
+ if (fromNode) {
169
+ cycles.push({ from: fromNode.name, to: c.name, depth: d });
170
+ }
171
+ continue;
164
172
  }
165
- continue;
166
- }
167
173
 
168
- visited.add(c.id);
169
- nextFrontier.push(c.id);
170
- const nodeInfo = { name: c.name, kind: c.kind, file: c.file, line: c.line };
171
- levelNodes.push(nodeInfo);
172
- nodeDepths.set(c.id, d);
173
- idToNode.set(c.id, nodeInfo);
174
+ visited.add(c.id);
175
+ nextFrontier.push(c.id);
176
+ const nodeInfo = { name: c.name, kind: c.kind, file: c.file, line: c.line };
177
+ levelNodes.push(nodeInfo);
178
+ nodeDepths.set(c.id, d);
179
+ idToNode.set(c.id, nodeInfo);
180
+ }
174
181
  }
175
- }
176
-
177
- if (levelNodes.length > 0) {
178
- steps.push({ depth: d, nodes: levelNodes });
179
- }
180
-
181
- frontier = nextFrontier;
182
- if (frontier.length === 0) break;
183
182
 
184
- if (d === maxDepth && frontier.length > 0) {
185
- truncated = true;
186
- }
187
- }
188
-
189
- // Identify leaves: visited nodes that have no outgoing 'calls' edges to other visited nodes
190
- // (or no outgoing calls at all)
191
- const leaves = [];
192
- for (const [id, depth] of nodeDepths) {
193
- const outgoing = db
194
- .prepare(
195
- `SELECT DISTINCT n.id
196
- FROM edges e JOIN nodes n ON e.target_id = n.id
197
- WHERE e.source_id = ? AND e.kind = 'calls'`,
198
- )
199
- .all(id);
200
-
201
- if (outgoing.length === 0) {
202
- const node = idToNode.get(id);
203
- if (node) {
204
- leaves.push({ ...node, depth });
183
+ if (levelNodes.length > 0) {
184
+ steps.push({ depth: d, nodes: levelNodes });
205
185
  }
206
- }
207
- }
208
186
 
209
- db.close();
210
- const base = {
211
- entry,
212
- depth: maxDepth,
213
- steps,
214
- leaves,
215
- cycles,
216
- totalReached: visited.size - 1, // exclude the entry node itself
217
- truncated,
218
- };
219
- return paginateResult(base, 'steps', { limit: opts.limit, offset: opts.offset });
220
- }
187
+ frontier = nextFrontier;
188
+ if (frontier.length === 0) break;
221
189
 
222
- /**
223
- * CLI formatter — text or JSON output.
224
- */
225
- export function flow(name, dbPath, opts = {}) {
226
- if (opts.list) {
227
- const data = listEntryPointsData(dbPath, {
228
- noTests: opts.noTests,
229
- limit: opts.limit,
230
- offset: opts.offset,
231
- });
232
- if (opts.ndjson) {
233
- printNdjson(data, 'entries');
234
- return;
235
- }
236
- if (opts.json) {
237
- console.log(JSON.stringify(data, null, 2));
238
- return;
239
- }
240
- if (data.count === 0) {
241
- console.log('No entry points found. Run "codegraph build" first.');
242
- return;
243
- }
244
- console.log(`\nEntry points (${data.count} total):\n`);
245
- for (const [type, entries] of Object.entries(data.byType)) {
246
- console.log(` ${type} (${entries.length}):`);
247
- for (const e of entries) {
248
- console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
190
+ if (d === maxDepth && frontier.length > 0) {
191
+ truncated = true;
249
192
  }
250
- console.log();
251
193
  }
252
- return;
253
- }
254
-
255
- const data = flowData(name, dbPath, opts);
256
- if (opts.json) {
257
- console.log(JSON.stringify(data, null, 2));
258
- return;
259
- }
260
194
 
261
- if (!data.entry) {
262
- console.log(`No matching entry point or function found for "${name}".`);
263
- return;
264
- }
265
-
266
- const e = data.entry;
267
- const typeTag = e.type !== 'exported' ? ` (${e.type})` : '';
268
- console.log(`\nFlow from: [${kindIcon(e.kind)}] ${e.name}${typeTag} ${e.file}:${e.line}`);
269
- console.log(
270
- `Depth: ${data.depth} Reached: ${data.totalReached} nodes Leaves: ${data.leaves.length}`,
271
- );
272
- if (data.truncated) {
273
- console.log(` (truncated at depth ${data.depth})`);
274
- }
275
- console.log();
276
-
277
- if (data.steps.length === 0) {
278
- console.log(' (leaf node — no callees)');
279
- return;
280
- }
195
+ // Identify leaves: visited nodes that have no outgoing 'calls' edges to other visited nodes
196
+ // (or no outgoing calls at all)
197
+ const leaves = [];
198
+ for (const [id, depth] of nodeDepths) {
199
+ const outgoing = db
200
+ .prepare(
201
+ `SELECT DISTINCT n.id
202
+ FROM edges e JOIN nodes n ON e.target_id = n.id
203
+ WHERE e.source_id = ? AND e.kind = 'calls'`,
204
+ )
205
+ .all(id);
281
206
 
282
- for (const step of data.steps) {
283
- console.log(` depth ${step.depth}:`);
284
- for (const n of step.nodes) {
285
- const isLeaf = data.leaves.some((l) => l.name === n.name && l.file === n.file);
286
- const leafTag = isLeaf ? ' [leaf]' : '';
287
- console.log(` [${kindIcon(n.kind)}] ${n.name} ${n.file}:${n.line}${leafTag}`);
207
+ if (outgoing.length === 0) {
208
+ const node = idToNode.get(id);
209
+ if (node) {
210
+ leaves.push({ ...node, depth });
211
+ }
212
+ }
288
213
  }
289
- }
290
214
 
291
- if (data.cycles.length > 0) {
292
- console.log('\n Cycles detected:');
293
- for (const c of data.cycles) {
294
- console.log(` ${c.from} -> ${c.to} (at depth ${c.depth})`);
295
- }
215
+ const base = {
216
+ entry,
217
+ depth: maxDepth,
218
+ steps,
219
+ leaves,
220
+ cycles,
221
+ totalReached: visited.size - 1, // exclude the entry node itself
222
+ truncated,
223
+ };
224
+ return paginateResult(base, 'steps', { limit: opts.limit, offset: opts.offset });
225
+ } finally {
226
+ db.close();
296
227
  }
297
228
  }
package/src/index.js CHANGED
@@ -8,13 +8,11 @@
8
8
  // AST node queries
9
9
  export { AST_NODE_KINDS, astQuery, astQueryData } from './ast.js';
10
10
  // Audit (composite report)
11
- export { audit, auditData } from './audit.js';
11
+ export { auditData } from './audit.js';
12
12
  // Batch querying
13
13
  export {
14
14
  BATCH_COMMANDS,
15
- batch,
16
15
  batchData,
17
- batchQuery,
18
16
  multiBatchData,
19
17
  splitTargets,
20
18
  } from './batch.js';
@@ -29,13 +27,12 @@ export {
29
27
  buildCFGData,
30
28
  buildFunctionCFG,
31
29
  CFG_RULES,
32
- cfg,
33
30
  cfgData,
34
31
  cfgToDOT,
35
32
  cfgToMermaid,
36
33
  } from './cfg.js';
37
34
  // Check (CI validation predicates)
38
- export { check, checkData } from './check.js';
35
+ export { checkData } from './check.js';
39
36
  // Co-change analysis
40
37
  export {
41
38
  analyzeCoChanges,
@@ -45,12 +42,23 @@ export {
45
42
  computeCoChanges,
46
43
  scanGitHistory,
47
44
  } from './cochange.js';
45
+ export { audit } from './commands/audit.js';
46
+ export { batch, batchQuery } from './commands/batch.js';
47
+ export { cfg } from './commands/cfg.js';
48
+ export { check } from './commands/check.js';
49
+ export { communities } from './commands/communities.js';
50
+ export { complexity } from './commands/complexity.js';
51
+ export { dataflow } from './commands/dataflow.js';
52
+ export { manifesto } from './commands/manifesto.js';
53
+ export { owners } from './commands/owners.js';
54
+ export { sequence } from './commands/sequence.js';
55
+ export { formatHotspots, formatModuleBoundaries, formatStructure } from './commands/structure.js';
56
+ export { triage } from './commands/triage.js';
48
57
  // Community detection
49
- export { communities, communitiesData, communitySummaryForStats } from './communities.js';
58
+ export { communitiesData, communitySummaryForStats } from './communities.js';
50
59
  // Complexity metrics
51
60
  export {
52
61
  COMPLEXITY_RULES,
53
- complexity,
54
62
  complexityData,
55
63
  computeFunctionComplexity,
56
64
  computeHalsteadMetrics,
@@ -69,7 +77,6 @@ export { findCycles, formatCycles } from './cycles.js';
69
77
  // Dataflow analysis
70
78
  export {
71
79
  buildDataflowEdges,
72
- dataflow,
73
80
  dataflowData,
74
81
  dataflowImpactData,
75
82
  dataflowPathData,
@@ -77,12 +84,24 @@ export {
77
84
  } from './dataflow.js';
78
85
  // Database utilities
79
86
  export {
87
+ countEdges,
88
+ countFiles,
89
+ countNodes,
90
+ fanInJoinSQL,
91
+ fanOutJoinSQL,
80
92
  findDbPath,
93
+ findNodesForTriage,
94
+ findNodesWithFanIn,
81
95
  getBuildMeta,
82
96
  initSchema,
97
+ iterateFunctionNodes,
98
+ kindInClause,
99
+ listFunctionNodes,
100
+ NodeQuery,
83
101
  openDb,
84
102
  openReadonlyOrFail,
85
103
  setBuildMeta,
104
+ testFilterSQL,
86
105
  } from './db.js';
87
106
  // Embeddings
88
107
  export {
@@ -111,14 +130,18 @@ export {
111
130
  } from './export.js';
112
131
  // Execution flow tracing
113
132
  export { entryPointType, flowData, listEntryPointsData } from './flow.js';
133
+ // Result formatting
134
+ export { outputResult } from './infrastructure/result-formatter.js';
135
+ // Test file detection
136
+ export { isTestFile, TEST_PATTERN } from './infrastructure/test-filter.js';
114
137
  // Logger
115
138
  export { setVerbose } from './logger.js';
116
139
  // Manifesto rule engine
117
- export { manifesto, manifestoData, RULE_DEFS } from './manifesto.js';
140
+ export { manifestoData, RULE_DEFS } from './manifesto.js';
118
141
  // Native engine
119
142
  export { isNativeAvailable } from './native.js';
120
143
  // Ownership (CODEOWNERS)
121
- export { matchOwners, owners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
144
+ export { matchOwners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
122
145
  // Pagination utilities
123
146
  export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult, printNdjson } from './paginate.js';
124
147
  // Unified parser API
@@ -140,7 +163,6 @@ export {
140
163
  FALSE_POSITIVE_CALLER_THRESHOLD,
141
164
  FALSE_POSITIVE_NAMES,
142
165
  fileDepsData,
143
- fileExports,
144
166
  fnDepsData,
145
167
  fnImpactData,
146
168
  impactAnalysisData,
@@ -158,6 +180,24 @@ export {
158
180
  VALID_ROLES,
159
181
  whereData,
160
182
  } from './queries.js';
183
+ // Query CLI display wrappers
184
+ export {
185
+ children,
186
+ context,
187
+ diffImpact,
188
+ explain,
189
+ fileDeps,
190
+ fileExports,
191
+ fnDeps,
192
+ fnImpact,
193
+ impactAnalysis,
194
+ moduleMap,
195
+ queryName,
196
+ roles,
197
+ stats,
198
+ symbolPath,
199
+ where,
200
+ } from './queries-cli.js';
161
201
  // Registry (multi-repo)
162
202
  export {
163
203
  listRepos,
@@ -170,7 +210,7 @@ export {
170
210
  unregisterRepo,
171
211
  } from './registry.js';
172
212
  // Sequence diagram generation
173
- export { sequence, sequenceData, sequenceToMermaid } from './sequence.js';
213
+ export { sequenceData, sequenceToMermaid } from './sequence.js';
174
214
  // Snapshot management
175
215
  export {
176
216
  snapshotDelete,
@@ -185,15 +225,12 @@ export {
185
225
  buildStructure,
186
226
  classifyNodeRoles,
187
227
  FRAMEWORK_ENTRY_PREFIXES,
188
- formatHotspots,
189
- formatModuleBoundaries,
190
- formatStructure,
191
228
  hotspotsData,
192
229
  moduleBoundariesData,
193
230
  structureData,
194
231
  } from './structure.js';
195
232
  // Triage — composite risk audit
196
- export { triage, triageData } from './triage.js';
233
+ export { triageData } from './triage.js';
197
234
  // Interactive HTML viewer
198
235
  export { generatePlotHTML, loadPlotConfig } from './viewer.js';
199
236
  // Watch mode
@@ -0,0 +1,21 @@
1
+ import { printNdjson } from '../paginate.js';
2
+
3
+ /**
4
+ * Shared JSON / NDJSON output dispatch for CLI wrappers.
5
+ *
6
+ * @param {object} data - Result object from a *Data() function
7
+ * @param {string} field - Array field name for NDJSON streaming (e.g. 'results')
8
+ * @param {object} opts - CLI options ({ json?, ndjson? })
9
+ * @returns {boolean} true if output was handled (caller should return early)
10
+ */
11
+ export function outputResult(data, field, opts) {
12
+ if (opts.ndjson) {
13
+ printNdjson(data, field);
14
+ return true;
15
+ }
16
+ if (opts.json) {
17
+ console.log(JSON.stringify(data, null, 2));
18
+ return true;
19
+ }
20
+ return false;
21
+ }
@@ -0,0 +1,7 @@
1
+ /** Pattern matching test/spec/stories files. */
2
+ export const TEST_PATTERN = /\.(test|spec)\.|__test__|__tests__|\.stories\./;
3
+
4
+ /** Check whether a file path looks like a test file. */
5
+ export function isTestFile(filePath) {
6
+ return TEST_PATTERN.test(filePath);
7
+ }
package/src/kinds.js ADDED
@@ -0,0 +1,50 @@
1
+ // ── Symbol kind constants ───────────────────────────────────────────
2
+ // Original 10 kinds — used as default query scope
3
+ export const CORE_SYMBOL_KINDS = [
4
+ 'function',
5
+ 'method',
6
+ 'class',
7
+ 'interface',
8
+ 'type',
9
+ 'struct',
10
+ 'enum',
11
+ 'trait',
12
+ 'record',
13
+ 'module',
14
+ ];
15
+
16
+ // Sub-declaration kinds (Phase 1)
17
+ export const EXTENDED_SYMBOL_KINDS = [
18
+ 'parameter',
19
+ 'property',
20
+ 'constant',
21
+ // Phase 2 (reserved, not yet extracted):
22
+ // 'constructor', 'namespace', 'decorator', 'getter', 'setter',
23
+ ];
24
+
25
+ // Full set for --kind validation and MCP enum
26
+ export const EVERY_SYMBOL_KIND = [...CORE_SYMBOL_KINDS, ...EXTENDED_SYMBOL_KINDS];
27
+
28
+ // Backward compat: ALL_SYMBOL_KINDS stays as the core 10
29
+ export const ALL_SYMBOL_KINDS = CORE_SYMBOL_KINDS;
30
+
31
+ // ── Edge kind constants ─────────────────────────────────────────────
32
+ // Core edge kinds — coupling and dependency relationships
33
+ export const CORE_EDGE_KINDS = [
34
+ 'imports',
35
+ 'imports-type',
36
+ 'dynamic-imports',
37
+ 'reexports',
38
+ 'calls',
39
+ 'extends',
40
+ 'implements',
41
+ 'contains',
42
+ ];
43
+
44
+ // Structural edge kinds — parent/child and type relationships
45
+ export const STRUCTURAL_EDGE_KINDS = ['parameter_of', 'receiver'];
46
+
47
+ // Full set for MCP enum and validation
48
+ export const EVERY_EDGE_KIND = [...CORE_EDGE_KINDS, ...STRUCTURAL_EDGE_KINDS];
49
+
50
+ export const VALID_ROLES = ['entry', 'core', 'utility', 'adapter', 'dead', 'leaf'];