@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.
- package/README.md +5 -5
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +8 -9
- package/src/ast-analysis/engine.js +365 -0
- package/src/ast-analysis/metrics.js +118 -0
- package/src/ast-analysis/rules/csharp.js +201 -0
- package/src/ast-analysis/rules/go.js +182 -0
- package/src/ast-analysis/rules/index.js +82 -0
- package/src/ast-analysis/rules/java.js +175 -0
- package/src/ast-analysis/rules/javascript.js +246 -0
- package/src/ast-analysis/rules/php.js +219 -0
- package/src/ast-analysis/rules/python.js +196 -0
- package/src/ast-analysis/rules/ruby.js +204 -0
- package/src/ast-analysis/rules/rust.js +173 -0
- package/src/ast-analysis/shared.js +223 -0
- package/src/ast-analysis/visitor-utils.js +176 -0
- package/src/ast-analysis/visitor.js +162 -0
- package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
- package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
- package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
- package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
- package/src/ast.js +26 -166
- package/src/audit.js +2 -88
- package/src/batch.js +0 -25
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +82 -172
- package/src/builder.js +48 -184
- package/src/cfg.js +148 -1174
- package/src/check.js +1 -84
- package/src/cli.js +118 -197
- package/src/cochange.js +1 -39
- package/src/commands/audit.js +88 -0
- package/src/commands/batch.js +26 -0
- package/src/commands/branch-compare.js +97 -0
- package/src/commands/cfg.js +55 -0
- package/src/commands/check.js +82 -0
- package/src/commands/cochange.js +37 -0
- package/src/commands/communities.js +69 -0
- package/src/commands/complexity.js +77 -0
- package/src/commands/dataflow.js +110 -0
- package/src/commands/flow.js +70 -0
- package/src/commands/manifesto.js +77 -0
- package/src/commands/owners.js +52 -0
- package/src/commands/query.js +21 -0
- package/src/commands/sequence.js +33 -0
- package/src/commands/structure.js +64 -0
- package/src/commands/triage.js +49 -0
- package/src/communities.js +22 -96
- package/src/complexity.js +234 -1591
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -1352
- package/src/db/connection.js +88 -0
- package/src/db/migrations.js +312 -0
- package/src/db/query-builder.js +280 -0
- package/src/db/repository/build-stmts.js +104 -0
- package/src/db/repository/cfg.js +83 -0
- package/src/db/repository/cochange.js +41 -0
- package/src/db/repository/complexity.js +15 -0
- package/src/db/repository/dataflow.js +12 -0
- package/src/db/repository/edges.js +259 -0
- package/src/db/repository/embeddings.js +40 -0
- package/src/db/repository/graph-read.js +39 -0
- package/src/db/repository/index.js +42 -0
- package/src/db/repository/nodes.js +236 -0
- package/src/db.js +58 -399
- package/src/embedder.js +158 -174
- package/src/export.js +1 -1
- package/src/extractors/javascript.js +130 -5
- package/src/flow.js +153 -222
- package/src/index.js +53 -16
- package/src/infrastructure/result-formatter.js +21 -0
- package/src/infrastructure/test-filter.js +7 -0
- package/src/kinds.js +50 -0
- package/src/manifesto.js +1 -82
- package/src/mcp.js +37 -20
- package/src/owners.js +127 -182
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1271 -2416
- package/src/sequence.js +179 -223
- package/src/structure.js +211 -269
- package/src/triage.js +117 -212
- package/src/viewer.js +1 -1
- package/src/watcher.js +7 -4
package/src/cli.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { Command } from 'commander';
|
|
6
|
-
import {
|
|
7
|
-
import { BATCH_COMMANDS, batch, multiBatchData, splitTargets } from './batch.js';
|
|
6
|
+
import { BATCH_COMMANDS, multiBatchData, splitTargets } from './batch.js';
|
|
8
7
|
import { buildGraph } from './builder.js';
|
|
8
|
+
import { audit } from './commands/audit.js';
|
|
9
|
+
import { batch } from './commands/batch.js';
|
|
9
10
|
import { loadConfig } from './config.js';
|
|
10
11
|
import { findCycles, formatCycles } from './cycles.js';
|
|
11
12
|
import { openReadonlyOrFail } from './db.js';
|
|
@@ -24,13 +25,13 @@ import {
|
|
|
24
25
|
exportMermaid,
|
|
25
26
|
exportNeo4jCSV,
|
|
26
27
|
} from './export.js';
|
|
28
|
+
import { outputResult } from './infrastructure/result-formatter.js';
|
|
27
29
|
import { setVerbose } from './logger.js';
|
|
28
|
-
import {
|
|
30
|
+
import { EVERY_SYMBOL_KIND, VALID_ROLES } from './queries.js';
|
|
29
31
|
import {
|
|
30
32
|
children,
|
|
31
33
|
context,
|
|
32
34
|
diffImpact,
|
|
33
|
-
EVERY_SYMBOL_KIND,
|
|
34
35
|
explain,
|
|
35
36
|
fileDeps,
|
|
36
37
|
fileExports,
|
|
@@ -41,9 +42,8 @@ import {
|
|
|
41
42
|
roles,
|
|
42
43
|
stats,
|
|
43
44
|
symbolPath,
|
|
44
|
-
VALID_ROLES,
|
|
45
45
|
where,
|
|
46
|
-
} from './queries.js';
|
|
46
|
+
} from './queries-cli.js';
|
|
47
47
|
import {
|
|
48
48
|
listRepos,
|
|
49
49
|
pruneRegistry,
|
|
@@ -95,6 +95,17 @@ function resolveNoTests(opts) {
|
|
|
95
95
|
return config.query?.excludeTests || false;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** Attach the common query options shared by most analysis commands. */
|
|
99
|
+
const QUERY_OPTS = (cmd) =>
|
|
100
|
+
cmd
|
|
101
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
102
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
103
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
104
|
+
.option('-j, --json', 'Output as JSON')
|
|
105
|
+
.option('--limit <number>', 'Max results to return')
|
|
106
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
107
|
+
.option('--ndjson', 'Newline-delimited JSON output');
|
|
108
|
+
|
|
98
109
|
function formatSize(bytes) {
|
|
99
110
|
if (bytes < 1024) return `${bytes} B`;
|
|
100
111
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -122,10 +133,11 @@ program
|
|
|
122
133
|
});
|
|
123
134
|
});
|
|
124
135
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
QUERY_OPTS(
|
|
137
|
+
program
|
|
138
|
+
.command('query <name>')
|
|
139
|
+
.description('Function-level dependency chain or shortest path between symbols'),
|
|
140
|
+
)
|
|
129
141
|
.option('--depth <n>', 'Transitive caller depth', '3')
|
|
130
142
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
131
143
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
@@ -134,12 +146,6 @@ program
|
|
|
134
146
|
.option('--reverse', 'Path mode: follow edges backward')
|
|
135
147
|
.option('--from-file <path>', 'Path mode: disambiguate source symbol by file')
|
|
136
148
|
.option('--to-file <path>', 'Path mode: disambiguate target symbol by file')
|
|
137
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
138
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
139
|
-
.option('-j, --json', 'Output as JSON')
|
|
140
|
-
.option('--limit <number>', 'Max results to return')
|
|
141
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
142
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
143
149
|
.action((name, opts) => {
|
|
144
150
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
145
151
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -201,25 +207,17 @@ program
|
|
|
201
207
|
});
|
|
202
208
|
});
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
.command('impact <file>')
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
214
|
-
.action((file, opts) => {
|
|
215
|
-
impactAnalysis(file, opts.db, {
|
|
216
|
-
noTests: resolveNoTests(opts),
|
|
217
|
-
json: opts.json,
|
|
218
|
-
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
219
|
-
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
220
|
-
ndjson: opts.ndjson,
|
|
221
|
-
});
|
|
210
|
+
QUERY_OPTS(
|
|
211
|
+
program.command('impact <file>').description('Show what depends on this file (transitive)'),
|
|
212
|
+
).action((file, opts) => {
|
|
213
|
+
impactAnalysis(file, opts.db, {
|
|
214
|
+
noTests: resolveNoTests(opts),
|
|
215
|
+
json: opts.json,
|
|
216
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
217
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
218
|
+
ndjson: opts.ndjson,
|
|
222
219
|
});
|
|
220
|
+
});
|
|
223
221
|
|
|
224
222
|
program
|
|
225
223
|
.command('map')
|
|
@@ -247,61 +245,43 @@ program
|
|
|
247
245
|
await stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
248
246
|
});
|
|
249
247
|
|
|
250
|
-
|
|
251
|
-
.command('deps <file>')
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
260
|
-
.action((file, opts) => {
|
|
261
|
-
fileDeps(file, opts.db, {
|
|
262
|
-
noTests: resolveNoTests(opts),
|
|
263
|
-
json: opts.json,
|
|
264
|
-
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
265
|
-
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
266
|
-
ndjson: opts.ndjson,
|
|
267
|
-
});
|
|
248
|
+
QUERY_OPTS(
|
|
249
|
+
program.command('deps <file>').description('Show what this file imports and what imports it'),
|
|
250
|
+
).action((file, opts) => {
|
|
251
|
+
fileDeps(file, opts.db, {
|
|
252
|
+
noTests: resolveNoTests(opts),
|
|
253
|
+
json: opts.json,
|
|
254
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
255
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
256
|
+
ndjson: opts.ndjson,
|
|
268
257
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
.option('
|
|
277
|
-
.option('--limit <number>', 'Max results to return')
|
|
278
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
279
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
280
|
-
.option('--unused', 'Show only exports with zero consumers')
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
QUERY_OPTS(
|
|
261
|
+
program
|
|
262
|
+
.command('exports <file>')
|
|
263
|
+
.description('Show exported symbols with per-symbol consumers (who calls each export)'),
|
|
264
|
+
)
|
|
265
|
+
.option('--unused', 'Show only exports with zero consumers (dead exports)')
|
|
281
266
|
.action((file, opts) => {
|
|
282
267
|
fileExports(file, opts.db, {
|
|
283
268
|
noTests: resolveNoTests(opts),
|
|
284
269
|
json: opts.json,
|
|
270
|
+
unused: opts.unused || false,
|
|
285
271
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
286
272
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
287
273
|
ndjson: opts.ndjson,
|
|
288
|
-
unused: opts.unused,
|
|
289
274
|
});
|
|
290
275
|
});
|
|
291
276
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
277
|
+
QUERY_OPTS(
|
|
278
|
+
program
|
|
279
|
+
.command('fn-impact <name>')
|
|
280
|
+
.description('Function-level impact: what functions break if this one changes'),
|
|
281
|
+
)
|
|
296
282
|
.option('--depth <n>', 'Max transitive depth', '5')
|
|
297
283
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
298
284
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
299
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
300
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
301
|
-
.option('-j, --json', 'Output as JSON')
|
|
302
|
-
.option('--limit <number>', 'Max results to return')
|
|
303
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
304
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
305
285
|
.action((name, opts) => {
|
|
306
286
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
307
287
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -319,21 +299,16 @@ program
|
|
|
319
299
|
});
|
|
320
300
|
});
|
|
321
301
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
302
|
+
QUERY_OPTS(
|
|
303
|
+
program
|
|
304
|
+
.command('context <name>')
|
|
305
|
+
.description('Full context for a function: source, deps, callers, tests, signature'),
|
|
306
|
+
)
|
|
326
307
|
.option('--depth <n>', 'Include callee source up to N levels deep', '0')
|
|
327
308
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
328
309
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
329
310
|
.option('--no-source', 'Metadata only (skip source extraction)')
|
|
330
311
|
.option('--with-test-source', 'Include test source code')
|
|
331
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
332
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
333
|
-
.option('-j, --json', 'Output as JSON')
|
|
334
|
-
.option('--limit <number>', 'Max results to return')
|
|
335
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
336
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
337
312
|
.action((name, opts) => {
|
|
338
313
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
339
314
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -417,17 +392,12 @@ program
|
|
|
417
392
|
});
|
|
418
393
|
});
|
|
419
394
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
395
|
+
QUERY_OPTS(
|
|
396
|
+
program
|
|
397
|
+
.command('where [name]')
|
|
398
|
+
.description('Find where a symbol is defined and used (minimal, fast lookup)'),
|
|
399
|
+
)
|
|
424
400
|
.option('-f, --file <path>', 'File overview: list symbols, imports, exports')
|
|
425
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
426
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
427
|
-
.option('-j, --json', 'Output as JSON')
|
|
428
|
-
.option('--limit <number>', 'Max results to return')
|
|
429
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
430
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
431
401
|
.action((name, opts) => {
|
|
432
402
|
if (!name && !opts.file) {
|
|
433
403
|
console.error('Provide a symbol name or use --file <path>');
|
|
@@ -448,15 +418,14 @@ program
|
|
|
448
418
|
.command('diff-impact [ref]')
|
|
449
419
|
.description('Show impact of git changes (unstaged, staged, or vs a ref)')
|
|
450
420
|
.option('-d, --db <path>', 'Path to graph.db')
|
|
451
|
-
.option('--staged', 'Analyze staged changes instead of unstaged')
|
|
452
|
-
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
453
421
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
454
422
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
455
|
-
.option('-j, --json', 'Output as JSON')
|
|
456
|
-
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
457
423
|
.option('--limit <number>', 'Max results to return')
|
|
458
424
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
459
425
|
.option('--ndjson', 'Newline-delimited JSON output')
|
|
426
|
+
.option('--staged', 'Analyze staged changes instead of unstaged')
|
|
427
|
+
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
428
|
+
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
460
429
|
.action((ref, opts) => {
|
|
461
430
|
diffImpact(opts.db, {
|
|
462
431
|
ref,
|
|
@@ -501,7 +470,7 @@ program
|
|
|
501
470
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
502
471
|
process.exit(1);
|
|
503
472
|
}
|
|
504
|
-
const { manifesto } = await import('./manifesto.js');
|
|
473
|
+
const { manifesto } = await import('./commands/manifesto.js');
|
|
505
474
|
manifesto(opts.db, {
|
|
506
475
|
file: opts.file,
|
|
507
476
|
kind: opts.kind,
|
|
@@ -515,7 +484,7 @@ program
|
|
|
515
484
|
}
|
|
516
485
|
|
|
517
486
|
// Diff predicates mode
|
|
518
|
-
const { check } = await import('./check.js');
|
|
487
|
+
const { check } = await import('./commands/check.js');
|
|
519
488
|
check(opts.db, {
|
|
520
489
|
ref,
|
|
521
490
|
staged: opts.staged,
|
|
@@ -534,7 +503,7 @@ program
|
|
|
534
503
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
535
504
|
process.exit(1);
|
|
536
505
|
}
|
|
537
|
-
const { manifesto } = await import('./manifesto.js');
|
|
506
|
+
const { manifesto } = await import('./commands/manifesto.js');
|
|
538
507
|
manifesto(opts.db, {
|
|
539
508
|
file: opts.file,
|
|
540
509
|
kind: opts.kind,
|
|
@@ -984,7 +953,7 @@ program
|
|
|
984
953
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
985
954
|
.option('--ndjson', 'Newline-delimited JSON output')
|
|
986
955
|
.action(async (dir, opts) => {
|
|
987
|
-
const { structureData, formatStructure } = await import('./structure.js');
|
|
956
|
+
const { structureData, formatStructure } = await import('./commands/structure.js');
|
|
988
957
|
const data = structureData(opts.db, {
|
|
989
958
|
directory: dir,
|
|
990
959
|
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
@@ -994,11 +963,7 @@ program
|
|
|
994
963
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
995
964
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
996
965
|
});
|
|
997
|
-
if (opts
|
|
998
|
-
printNdjson(data, 'directories');
|
|
999
|
-
} else if (opts.json) {
|
|
1000
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1001
|
-
} else {
|
|
966
|
+
if (!outputResult(data, 'directories', opts)) {
|
|
1002
967
|
console.log(formatStructure(data));
|
|
1003
968
|
}
|
|
1004
969
|
});
|
|
@@ -1049,8 +1014,8 @@ program
|
|
|
1049
1014
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1050
1015
|
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1051
1016
|
.action(async (file, opts) => {
|
|
1052
|
-
const { analyzeCoChanges, coChangeData, coChangeTopData
|
|
1053
|
-
|
|
1017
|
+
const { analyzeCoChanges, coChangeData, coChangeTopData } = await import('./cochange.js');
|
|
1018
|
+
const { formatCoChange, formatCoChangeTop } = await import('./commands/cochange.js');
|
|
1054
1019
|
|
|
1055
1020
|
if (opts.analyze) {
|
|
1056
1021
|
const result = analyzeCoChanges(opts.db, {
|
|
@@ -1081,41 +1046,28 @@ program
|
|
|
1081
1046
|
|
|
1082
1047
|
if (file) {
|
|
1083
1048
|
const data = coChangeData(file, opts.db, queryOpts);
|
|
1084
|
-
if (opts
|
|
1085
|
-
printNdjson(data, 'partners');
|
|
1086
|
-
} else if (opts.json) {
|
|
1087
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1088
|
-
} else {
|
|
1049
|
+
if (!outputResult(data, 'partners', opts)) {
|
|
1089
1050
|
console.log(formatCoChange(data));
|
|
1090
1051
|
}
|
|
1091
1052
|
} else {
|
|
1092
1053
|
const data = coChangeTopData(opts.db, queryOpts);
|
|
1093
|
-
if (opts
|
|
1094
|
-
printNdjson(data, 'pairs');
|
|
1095
|
-
} else if (opts.json) {
|
|
1096
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1097
|
-
} else {
|
|
1054
|
+
if (!outputResult(data, 'pairs', opts)) {
|
|
1098
1055
|
console.log(formatCoChangeTop(data));
|
|
1099
1056
|
}
|
|
1100
1057
|
}
|
|
1101
1058
|
});
|
|
1102
1059
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1060
|
+
QUERY_OPTS(
|
|
1061
|
+
program
|
|
1062
|
+
.command('flow [name]')
|
|
1063
|
+
.description(
|
|
1064
|
+
'Trace execution flow forward from an entry point (route, command, event) through callees to leaves',
|
|
1065
|
+
),
|
|
1066
|
+
)
|
|
1108
1067
|
.option('--list', 'List all entry points grouped by type')
|
|
1109
1068
|
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1110
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1111
1069
|
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1112
1070
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1113
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1114
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1115
|
-
.option('-j, --json', 'Output as JSON')
|
|
1116
|
-
.option('--limit <number>', 'Max results to return')
|
|
1117
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1118
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1119
1071
|
.action(async (name, opts) => {
|
|
1120
1072
|
if (!name && !opts.list) {
|
|
1121
1073
|
console.error('Provide a function/entry point name or use --list to see all entry points.');
|
|
@@ -1125,7 +1077,7 @@ program
|
|
|
1125
1077
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1126
1078
|
process.exit(1);
|
|
1127
1079
|
}
|
|
1128
|
-
const { flow } = await import('./flow.js');
|
|
1080
|
+
const { flow } = await import('./commands/flow.js');
|
|
1129
1081
|
flow(name, opts.db, {
|
|
1130
1082
|
list: opts.list,
|
|
1131
1083
|
depth: parseInt(opts.depth, 10),
|
|
@@ -1139,26 +1091,23 @@ program
|
|
|
1139
1091
|
});
|
|
1140
1092
|
});
|
|
1141
1093
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1094
|
+
QUERY_OPTS(
|
|
1095
|
+
program
|
|
1096
|
+
.command('sequence <name>')
|
|
1097
|
+
.description(
|
|
1098
|
+
'Generate a Mermaid sequence diagram from call graph edges (participants = files)',
|
|
1099
|
+
),
|
|
1100
|
+
)
|
|
1145
1101
|
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1146
1102
|
.option('--dataflow', 'Annotate with parameter names and return arrows from dataflow table')
|
|
1147
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1148
1103
|
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1149
1104
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1150
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1151
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1152
|
-
.option('-j, --json', 'Output as JSON')
|
|
1153
|
-
.option('--limit <number>', 'Max results to return')
|
|
1154
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1155
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1156
1105
|
.action(async (name, opts) => {
|
|
1157
1106
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1158
1107
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1159
1108
|
process.exit(1);
|
|
1160
1109
|
}
|
|
1161
|
-
const { sequence } = await import('./sequence.js');
|
|
1110
|
+
const { sequence } = await import('./commands/sequence.js');
|
|
1162
1111
|
sequence(name, opts.db, {
|
|
1163
1112
|
depth: parseInt(opts.depth, 10),
|
|
1164
1113
|
file: opts.file,
|
|
@@ -1172,18 +1121,13 @@ program
|
|
|
1172
1121
|
});
|
|
1173
1122
|
});
|
|
1174
1123
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1124
|
+
QUERY_OPTS(
|
|
1125
|
+
program
|
|
1126
|
+
.command('dataflow <name>')
|
|
1127
|
+
.description('Show data flow for a function: parameters, return consumers, mutations'),
|
|
1128
|
+
)
|
|
1179
1129
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1180
1130
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1181
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1182
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1183
|
-
.option('-j, --json', 'Output as JSON')
|
|
1184
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1185
|
-
.option('--limit <number>', 'Max results to return')
|
|
1186
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1187
1131
|
.option('--impact', 'Show data-dependent blast radius')
|
|
1188
1132
|
.option('--depth <n>', 'Max traversal depth', '5')
|
|
1189
1133
|
.action(async (name, opts) => {
|
|
@@ -1191,7 +1135,7 @@ program
|
|
|
1191
1135
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1192
1136
|
process.exit(1);
|
|
1193
1137
|
}
|
|
1194
|
-
const { dataflow } = await import('./dataflow.js');
|
|
1138
|
+
const { dataflow } = await import('./commands/dataflow.js');
|
|
1195
1139
|
dataflow(name, opts.db, {
|
|
1196
1140
|
file: opts.file,
|
|
1197
1141
|
kind: opts.kind,
|
|
@@ -1205,25 +1149,16 @@ program
|
|
|
1205
1149
|
});
|
|
1206
1150
|
});
|
|
1207
1151
|
|
|
1208
|
-
program
|
|
1209
|
-
.command('cfg <name>')
|
|
1210
|
-
.description('Show control flow graph for a function')
|
|
1211
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1152
|
+
QUERY_OPTS(program.command('cfg <name>').description('Show control flow graph for a function'))
|
|
1212
1153
|
.option('--format <fmt>', 'Output format: text, dot, mermaid', 'text')
|
|
1213
1154
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1214
1155
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1215
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1216
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1217
|
-
.option('-j, --json', 'Output as JSON')
|
|
1218
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1219
|
-
.option('--limit <number>', 'Max results to return')
|
|
1220
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1221
1156
|
.action(async (name, opts) => {
|
|
1222
1157
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1223
1158
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1224
1159
|
process.exit(1);
|
|
1225
1160
|
}
|
|
1226
|
-
const { cfg } = await import('./cfg.js');
|
|
1161
|
+
const { cfg } = await import('./commands/cfg.js');
|
|
1227
1162
|
cfg(name, opts.db, {
|
|
1228
1163
|
format: opts.format,
|
|
1229
1164
|
file: opts.file,
|
|
@@ -1260,7 +1195,7 @@ program
|
|
|
1260
1195
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1261
1196
|
process.exit(1);
|
|
1262
1197
|
}
|
|
1263
|
-
const { complexity } = await import('./complexity.js');
|
|
1198
|
+
const { complexity } = await import('./commands/complexity.js');
|
|
1264
1199
|
complexity(opts.db, {
|
|
1265
1200
|
target,
|
|
1266
1201
|
limit: parseInt(opts.limit, 10),
|
|
@@ -1276,18 +1211,13 @@ program
|
|
|
1276
1211
|
});
|
|
1277
1212
|
});
|
|
1278
1213
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1214
|
+
QUERY_OPTS(
|
|
1215
|
+
program
|
|
1216
|
+
.command('ast [pattern]')
|
|
1217
|
+
.description('Search stored AST nodes (calls, new, string, regex, throw, await) by pattern'),
|
|
1218
|
+
)
|
|
1283
1219
|
.option('-k, --kind <kind>', 'Filter by AST node kind (call, new, string, regex, throw, await)')
|
|
1284
1220
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1285
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1286
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1287
|
-
.option('-j, --json', 'Output as JSON')
|
|
1288
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1289
|
-
.option('--limit <number>', 'Max results to return')
|
|
1290
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1291
1221
|
.action(async (pattern, opts) => {
|
|
1292
1222
|
const { AST_NODE_KINDS, astQuery } = await import('./ast.js');
|
|
1293
1223
|
if (opts.kind && !AST_NODE_KINDS.includes(opts.kind)) {
|
|
@@ -1305,21 +1235,16 @@ program
|
|
|
1305
1235
|
});
|
|
1306
1236
|
});
|
|
1307
1237
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1238
|
+
QUERY_OPTS(
|
|
1239
|
+
program
|
|
1240
|
+
.command('communities')
|
|
1241
|
+
.description('Detect natural module boundaries using Louvain community detection'),
|
|
1242
|
+
)
|
|
1311
1243
|
.option('--functions', 'Function-level instead of file-level')
|
|
1312
1244
|
.option('--resolution <n>', 'Louvain resolution parameter (default 1.0)', '1.0')
|
|
1313
1245
|
.option('--drift', 'Show only drift analysis')
|
|
1314
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1315
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1316
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1317
|
-
.option('-j, --json', 'Output as JSON')
|
|
1318
|
-
.option('--limit <number>', 'Max results to return')
|
|
1319
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1320
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1321
1246
|
.action(async (opts) => {
|
|
1322
|
-
const { communities } = await import('./communities.js');
|
|
1247
|
+
const { communities } = await import('./commands/communities.js');
|
|
1323
1248
|
communities(opts.db, {
|
|
1324
1249
|
functions: opts.functions,
|
|
1325
1250
|
resolution: parseFloat(opts.resolution),
|
|
@@ -1362,7 +1287,7 @@ program
|
|
|
1362
1287
|
.action(async (opts) => {
|
|
1363
1288
|
if (opts.level === 'file' || opts.level === 'directory') {
|
|
1364
1289
|
// Delegate to hotspots for file/directory level
|
|
1365
|
-
const { hotspotsData, formatHotspots } = await import('./structure.js');
|
|
1290
|
+
const { hotspotsData, formatHotspots } = await import('./commands/structure.js');
|
|
1366
1291
|
const metric = opts.sort === 'risk' ? 'fan-in' : opts.sort;
|
|
1367
1292
|
const data = hotspotsData(opts.db, {
|
|
1368
1293
|
metric,
|
|
@@ -1371,11 +1296,7 @@ program
|
|
|
1371
1296
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
1372
1297
|
noTests: resolveNoTests(opts),
|
|
1373
1298
|
});
|
|
1374
|
-
if (opts
|
|
1375
|
-
printNdjson(data, 'hotspots');
|
|
1376
|
-
} else if (opts.json) {
|
|
1377
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1378
|
-
} else {
|
|
1299
|
+
if (!outputResult(data, 'hotspots', opts)) {
|
|
1379
1300
|
console.log(formatHotspots(data));
|
|
1380
1301
|
}
|
|
1381
1302
|
return;
|
|
@@ -1398,7 +1319,7 @@ program
|
|
|
1398
1319
|
process.exit(1);
|
|
1399
1320
|
}
|
|
1400
1321
|
}
|
|
1401
|
-
const { triage } = await import('./triage.js');
|
|
1322
|
+
const { triage } = await import('./commands/triage.js');
|
|
1402
1323
|
triage(opts.db, {
|
|
1403
1324
|
limit: parseInt(opts.limit, 10),
|
|
1404
1325
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
@@ -1426,7 +1347,7 @@ program
|
|
|
1426
1347
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1427
1348
|
.option('-j, --json', 'Output as JSON')
|
|
1428
1349
|
.action(async (target, opts) => {
|
|
1429
|
-
const { owners } = await import('./owners.js');
|
|
1350
|
+
const { owners } = await import('./commands/owners.js');
|
|
1430
1351
|
owners(opts.db, {
|
|
1431
1352
|
owner: opts.owner,
|
|
1432
1353
|
boundary: opts.boundary,
|
|
@@ -1446,7 +1367,7 @@ program
|
|
|
1446
1367
|
.option('-j, --json', 'Output as JSON')
|
|
1447
1368
|
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
1448
1369
|
.action(async (base, target, opts) => {
|
|
1449
|
-
const { branchCompare } = await import('./branch-compare.js');
|
|
1370
|
+
const { branchCompare } = await import('./commands/branch-compare.js');
|
|
1450
1371
|
await branchCompare(base, target, {
|
|
1451
1372
|
engine: program.opts().engine,
|
|
1452
1373
|
depth: parseInt(opts.depth, 10),
|
package/src/cochange.js
CHANGED
|
@@ -10,9 +10,9 @@ import fs from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { normalizePath } from './constants.js';
|
|
12
12
|
import { closeDb, findDbPath, initSchema, openDb, openReadonlyOrFail } from './db.js';
|
|
13
|
+
import { isTestFile } from './infrastructure/test-filter.js';
|
|
13
14
|
import { warn } from './logger.js';
|
|
14
15
|
import { paginateResult } from './paginate.js';
|
|
15
|
-
import { isTestFile } from './queries.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Scan git history and return parsed commit data.
|
|
@@ -419,44 +419,6 @@ export function coChangeForFiles(files, db, opts = {}) {
|
|
|
419
419
|
return results;
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
/**
|
|
423
|
-
* Format co-change data for CLI output (single file).
|
|
424
|
-
*/
|
|
425
|
-
export function formatCoChange(data) {
|
|
426
|
-
if (data.error) return data.error;
|
|
427
|
-
if (data.partners.length === 0) return `No co-change partners found for ${data.file}`;
|
|
428
|
-
|
|
429
|
-
const lines = [`\nCo-change partners for ${data.file}:\n`];
|
|
430
|
-
for (const p of data.partners) {
|
|
431
|
-
const pct = `${(p.jaccard * 100).toFixed(0)}%`.padStart(4);
|
|
432
|
-
const commits = `${p.commitCount} commits`.padStart(12);
|
|
433
|
-
lines.push(` ${pct} ${commits} ${p.file}`);
|
|
434
|
-
}
|
|
435
|
-
if (data.meta?.analyzedAt) {
|
|
436
|
-
lines.push(`\n Analyzed: ${data.meta.analyzedAt} | Window: ${data.meta.since || 'all'}`);
|
|
437
|
-
}
|
|
438
|
-
return lines.join('\n');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Format top co-change pairs for CLI output (global view).
|
|
443
|
-
*/
|
|
444
|
-
export function formatCoChangeTop(data) {
|
|
445
|
-
if (data.error) return data.error;
|
|
446
|
-
if (data.pairs.length === 0) return 'No co-change pairs found.';
|
|
447
|
-
|
|
448
|
-
const lines = ['\nTop co-change pairs:\n'];
|
|
449
|
-
for (const p of data.pairs) {
|
|
450
|
-
const pct = `${(p.jaccard * 100).toFixed(0)}%`.padStart(4);
|
|
451
|
-
const commits = `${p.commitCount} commits`.padStart(12);
|
|
452
|
-
lines.push(` ${pct} ${commits} ${p.fileA} <-> ${p.fileB}`);
|
|
453
|
-
}
|
|
454
|
-
if (data.meta?.analyzedAt) {
|
|
455
|
-
lines.push(`\n Analyzed: ${data.meta.analyzedAt} | Window: ${data.meta.since || 'all'}`);
|
|
456
|
-
}
|
|
457
|
-
return lines.join('\n');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
422
|
// ─── Internal Helpers ────────────────────────────────────────────────────
|
|
461
423
|
|
|
462
424
|
function resolveCoChangeFile(db, file) {
|