@optave/codegraph 3.0.4 → 3.1.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.
- package/README.md +59 -52
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +9 -10
- 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.js +15 -28
- package/src/audit.js +4 -5
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +84 -79
- package/src/builder.js +274 -159
- package/src/cfg.js +111 -341
- package/src/check.js +3 -3
- package/src/cli.js +122 -167
- package/src/cochange.js +1 -1
- package/src/communities.js +13 -16
- package/src/complexity.js +196 -1239
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -697
- 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.js +134 -0
- package/src/db.js +19 -392
- package/src/embedder.js +145 -141
- package/src/export.js +1 -1
- package/src/flow.js +160 -228
- package/src/index.js +36 -2
- package/src/kinds.js +49 -0
- package/src/manifesto.js +3 -8
- package/src/mcp.js +97 -20
- package/src/owners.js +132 -132
- package/src/parser.js +58 -131
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1356 -2261
- package/src/resolve.js +11 -2
- package/src/result-formatter.js +21 -0
- package/src/sequence.js +364 -0
- package/src/structure.js +200 -199
- package/src/test-filter.js +7 -0
- package/src/triage.js +120 -162
- package/src/viewer.js +1 -1
package/src/cli.js
CHANGED
|
@@ -25,12 +25,11 @@ import {
|
|
|
25
25
|
exportNeo4jCSV,
|
|
26
26
|
} from './export.js';
|
|
27
27
|
import { setVerbose } from './logger.js';
|
|
28
|
-
import {
|
|
28
|
+
import { EVERY_SYMBOL_KIND, VALID_ROLES } from './queries.js';
|
|
29
29
|
import {
|
|
30
30
|
children,
|
|
31
31
|
context,
|
|
32
32
|
diffImpact,
|
|
33
|
-
EVERY_SYMBOL_KIND,
|
|
34
33
|
explain,
|
|
35
34
|
fileDeps,
|
|
36
35
|
fileExports,
|
|
@@ -41,9 +40,8 @@ import {
|
|
|
41
40
|
roles,
|
|
42
41
|
stats,
|
|
43
42
|
symbolPath,
|
|
44
|
-
VALID_ROLES,
|
|
45
43
|
where,
|
|
46
|
-
} from './queries.js';
|
|
44
|
+
} from './queries-cli.js';
|
|
47
45
|
import {
|
|
48
46
|
listRepos,
|
|
49
47
|
pruneRegistry,
|
|
@@ -51,6 +49,7 @@ import {
|
|
|
51
49
|
registerRepo,
|
|
52
50
|
unregisterRepo,
|
|
53
51
|
} from './registry.js';
|
|
52
|
+
import { outputResult } from './result-formatter.js';
|
|
54
53
|
import { snapshotDelete, snapshotList, snapshotRestore, snapshotSave } from './snapshot.js';
|
|
55
54
|
import { checkForUpdates, printUpdateNotification } from './update-check.js';
|
|
56
55
|
import { watchProject } from './watcher.js';
|
|
@@ -95,6 +94,17 @@ function resolveNoTests(opts) {
|
|
|
95
94
|
return config.query?.excludeTests || false;
|
|
96
95
|
}
|
|
97
96
|
|
|
97
|
+
/** Attach the common query options shared by most analysis commands. */
|
|
98
|
+
const QUERY_OPTS = (cmd) =>
|
|
99
|
+
cmd
|
|
100
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
101
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
102
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
103
|
+
.option('-j, --json', 'Output as JSON')
|
|
104
|
+
.option('--limit <number>', 'Max results to return')
|
|
105
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
106
|
+
.option('--ndjson', 'Newline-delimited JSON output');
|
|
107
|
+
|
|
98
108
|
function formatSize(bytes) {
|
|
99
109
|
if (bytes < 1024) return `${bytes} B`;
|
|
100
110
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -122,10 +132,11 @@ program
|
|
|
122
132
|
});
|
|
123
133
|
});
|
|
124
134
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
QUERY_OPTS(
|
|
136
|
+
program
|
|
137
|
+
.command('query <name>')
|
|
138
|
+
.description('Function-level dependency chain or shortest path between symbols'),
|
|
139
|
+
)
|
|
129
140
|
.option('--depth <n>', 'Transitive caller depth', '3')
|
|
130
141
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
131
142
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
@@ -134,12 +145,6 @@ program
|
|
|
134
145
|
.option('--reverse', 'Path mode: follow edges backward')
|
|
135
146
|
.option('--from-file <path>', 'Path mode: disambiguate source symbol by file')
|
|
136
147
|
.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
148
|
.action((name, opts) => {
|
|
144
149
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
145
150
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -201,25 +206,17 @@ program
|
|
|
201
206
|
});
|
|
202
207
|
});
|
|
203
208
|
|
|
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
|
-
});
|
|
209
|
+
QUERY_OPTS(
|
|
210
|
+
program.command('impact <file>').description('Show what depends on this file (transitive)'),
|
|
211
|
+
).action((file, opts) => {
|
|
212
|
+
impactAnalysis(file, opts.db, {
|
|
213
|
+
noTests: resolveNoTests(opts),
|
|
214
|
+
json: opts.json,
|
|
215
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
216
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
217
|
+
ndjson: opts.ndjson,
|
|
222
218
|
});
|
|
219
|
+
});
|
|
223
220
|
|
|
224
221
|
program
|
|
225
222
|
.command('map')
|
|
@@ -247,59 +244,43 @@ program
|
|
|
247
244
|
await stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
248
245
|
});
|
|
249
246
|
|
|
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
|
-
});
|
|
247
|
+
QUERY_OPTS(
|
|
248
|
+
program.command('deps <file>').description('Show what this file imports and what imports it'),
|
|
249
|
+
).action((file, opts) => {
|
|
250
|
+
fileDeps(file, opts.db, {
|
|
251
|
+
noTests: resolveNoTests(opts),
|
|
252
|
+
json: opts.json,
|
|
253
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
254
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
255
|
+
ndjson: opts.ndjson,
|
|
268
256
|
});
|
|
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')
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
QUERY_OPTS(
|
|
260
|
+
program
|
|
261
|
+
.command('exports <file>')
|
|
262
|
+
.description('Show exported symbols with per-symbol consumers (who calls each export)'),
|
|
263
|
+
)
|
|
264
|
+
.option('--unused', 'Show only exports with zero consumers (dead exports)')
|
|
280
265
|
.action((file, opts) => {
|
|
281
266
|
fileExports(file, opts.db, {
|
|
282
267
|
noTests: resolveNoTests(opts),
|
|
283
268
|
json: opts.json,
|
|
269
|
+
unused: opts.unused || false,
|
|
284
270
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
285
271
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
286
272
|
ndjson: opts.ndjson,
|
|
287
273
|
});
|
|
288
274
|
});
|
|
289
275
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
276
|
+
QUERY_OPTS(
|
|
277
|
+
program
|
|
278
|
+
.command('fn-impact <name>')
|
|
279
|
+
.description('Function-level impact: what functions break if this one changes'),
|
|
280
|
+
)
|
|
294
281
|
.option('--depth <n>', 'Max transitive depth', '5')
|
|
295
282
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
296
283
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
297
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
298
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
299
|
-
.option('-j, --json', 'Output as JSON')
|
|
300
|
-
.option('--limit <number>', 'Max results to return')
|
|
301
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
302
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
303
284
|
.action((name, opts) => {
|
|
304
285
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
305
286
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -317,21 +298,16 @@ program
|
|
|
317
298
|
});
|
|
318
299
|
});
|
|
319
300
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
301
|
+
QUERY_OPTS(
|
|
302
|
+
program
|
|
303
|
+
.command('context <name>')
|
|
304
|
+
.description('Full context for a function: source, deps, callers, tests, signature'),
|
|
305
|
+
)
|
|
324
306
|
.option('--depth <n>', 'Include callee source up to N levels deep', '0')
|
|
325
307
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
326
308
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
327
309
|
.option('--no-source', 'Metadata only (skip source extraction)')
|
|
328
310
|
.option('--with-test-source', 'Include test source code')
|
|
329
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
330
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
331
|
-
.option('-j, --json', 'Output as JSON')
|
|
332
|
-
.option('--limit <number>', 'Max results to return')
|
|
333
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
334
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
335
311
|
.action((name, opts) => {
|
|
336
312
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
337
313
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -415,17 +391,12 @@ program
|
|
|
415
391
|
});
|
|
416
392
|
});
|
|
417
393
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
394
|
+
QUERY_OPTS(
|
|
395
|
+
program
|
|
396
|
+
.command('where [name]')
|
|
397
|
+
.description('Find where a symbol is defined and used (minimal, fast lookup)'),
|
|
398
|
+
)
|
|
422
399
|
.option('-f, --file <path>', 'File overview: list symbols, imports, exports')
|
|
423
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
424
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
425
|
-
.option('-j, --json', 'Output as JSON')
|
|
426
|
-
.option('--limit <number>', 'Max results to return')
|
|
427
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
428
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
429
400
|
.action((name, opts) => {
|
|
430
401
|
if (!name && !opts.file) {
|
|
431
402
|
console.error('Provide a symbol name or use --file <path>');
|
|
@@ -446,15 +417,14 @@ program
|
|
|
446
417
|
.command('diff-impact [ref]')
|
|
447
418
|
.description('Show impact of git changes (unstaged, staged, or vs a ref)')
|
|
448
419
|
.option('-d, --db <path>', 'Path to graph.db')
|
|
449
|
-
.option('--staged', 'Analyze staged changes instead of unstaged')
|
|
450
|
-
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
451
420
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
452
421
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
453
|
-
.option('-j, --json', 'Output as JSON')
|
|
454
|
-
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
455
422
|
.option('--limit <number>', 'Max results to return')
|
|
456
423
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
457
424
|
.option('--ndjson', 'Newline-delimited JSON output')
|
|
425
|
+
.option('--staged', 'Analyze staged changes instead of unstaged')
|
|
426
|
+
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
427
|
+
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
458
428
|
.action((ref, opts) => {
|
|
459
429
|
diffImpact(opts.db, {
|
|
460
430
|
ref,
|
|
@@ -992,11 +962,7 @@ program
|
|
|
992
962
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
993
963
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
994
964
|
});
|
|
995
|
-
if (opts
|
|
996
|
-
printNdjson(data, 'directories');
|
|
997
|
-
} else if (opts.json) {
|
|
998
|
-
console.log(JSON.stringify(data, null, 2));
|
|
999
|
-
} else {
|
|
965
|
+
if (!outputResult(data, 'directories', opts)) {
|
|
1000
966
|
console.log(formatStructure(data));
|
|
1001
967
|
}
|
|
1002
968
|
});
|
|
@@ -1079,41 +1045,28 @@ program
|
|
|
1079
1045
|
|
|
1080
1046
|
if (file) {
|
|
1081
1047
|
const data = coChangeData(file, opts.db, queryOpts);
|
|
1082
|
-
if (opts
|
|
1083
|
-
printNdjson(data, 'partners');
|
|
1084
|
-
} else if (opts.json) {
|
|
1085
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1086
|
-
} else {
|
|
1048
|
+
if (!outputResult(data, 'partners', opts)) {
|
|
1087
1049
|
console.log(formatCoChange(data));
|
|
1088
1050
|
}
|
|
1089
1051
|
} else {
|
|
1090
1052
|
const data = coChangeTopData(opts.db, queryOpts);
|
|
1091
|
-
if (opts
|
|
1092
|
-
printNdjson(data, 'pairs');
|
|
1093
|
-
} else if (opts.json) {
|
|
1094
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1095
|
-
} else {
|
|
1053
|
+
if (!outputResult(data, 'pairs', opts)) {
|
|
1096
1054
|
console.log(formatCoChangeTop(data));
|
|
1097
1055
|
}
|
|
1098
1056
|
}
|
|
1099
1057
|
});
|
|
1100
1058
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1059
|
+
QUERY_OPTS(
|
|
1060
|
+
program
|
|
1061
|
+
.command('flow [name]')
|
|
1062
|
+
.description(
|
|
1063
|
+
'Trace execution flow forward from an entry point (route, command, event) through callees to leaves',
|
|
1064
|
+
),
|
|
1065
|
+
)
|
|
1106
1066
|
.option('--list', 'List all entry points grouped by type')
|
|
1107
1067
|
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1108
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1109
1068
|
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1110
1069
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1111
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1112
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1113
|
-
.option('-j, --json', 'Output as JSON')
|
|
1114
|
-
.option('--limit <number>', 'Max results to return')
|
|
1115
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1116
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1117
1070
|
.action(async (name, opts) => {
|
|
1118
1071
|
if (!name && !opts.list) {
|
|
1119
1072
|
console.error('Provide a function/entry point name or use --list to see all entry points.');
|
|
@@ -1137,18 +1090,43 @@ program
|
|
|
1137
1090
|
});
|
|
1138
1091
|
});
|
|
1139
1092
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1093
|
+
QUERY_OPTS(
|
|
1094
|
+
program
|
|
1095
|
+
.command('sequence <name>')
|
|
1096
|
+
.description(
|
|
1097
|
+
'Generate a Mermaid sequence diagram from call graph edges (participants = files)',
|
|
1098
|
+
),
|
|
1099
|
+
)
|
|
1100
|
+
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1101
|
+
.option('--dataflow', 'Annotate with parameter names and return arrows from dataflow table')
|
|
1102
|
+
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1103
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1104
|
+
.action(async (name, opts) => {
|
|
1105
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1106
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1107
|
+
process.exit(1);
|
|
1108
|
+
}
|
|
1109
|
+
const { sequence } = await import('./sequence.js');
|
|
1110
|
+
sequence(name, opts.db, {
|
|
1111
|
+
depth: parseInt(opts.depth, 10),
|
|
1112
|
+
file: opts.file,
|
|
1113
|
+
kind: opts.kind,
|
|
1114
|
+
noTests: resolveNoTests(opts),
|
|
1115
|
+
json: opts.json,
|
|
1116
|
+
dataflow: opts.dataflow,
|
|
1117
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
1118
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
1119
|
+
ndjson: opts.ndjson,
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
QUERY_OPTS(
|
|
1124
|
+
program
|
|
1125
|
+
.command('dataflow <name>')
|
|
1126
|
+
.description('Show data flow for a function: parameters, return consumers, mutations'),
|
|
1127
|
+
)
|
|
1144
1128
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1145
1129
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1146
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1147
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1148
|
-
.option('-j, --json', 'Output as JSON')
|
|
1149
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1150
|
-
.option('--limit <number>', 'Max results to return')
|
|
1151
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1152
1130
|
.option('--impact', 'Show data-dependent blast radius')
|
|
1153
1131
|
.option('--depth <n>', 'Max traversal depth', '5')
|
|
1154
1132
|
.action(async (name, opts) => {
|
|
@@ -1170,19 +1148,10 @@ program
|
|
|
1170
1148
|
});
|
|
1171
1149
|
});
|
|
1172
1150
|
|
|
1173
|
-
program
|
|
1174
|
-
.command('cfg <name>')
|
|
1175
|
-
.description('Show control flow graph for a function')
|
|
1176
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1151
|
+
QUERY_OPTS(program.command('cfg <name>').description('Show control flow graph for a function'))
|
|
1177
1152
|
.option('--format <fmt>', 'Output format: text, dot, mermaid', 'text')
|
|
1178
1153
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1179
1154
|
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1180
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1181
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1182
|
-
.option('-j, --json', 'Output as JSON')
|
|
1183
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1184
|
-
.option('--limit <number>', 'Max results to return')
|
|
1185
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1186
1155
|
.action(async (name, opts) => {
|
|
1187
1156
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1188
1157
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -1241,18 +1210,13 @@ program
|
|
|
1241
1210
|
});
|
|
1242
1211
|
});
|
|
1243
1212
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1213
|
+
QUERY_OPTS(
|
|
1214
|
+
program
|
|
1215
|
+
.command('ast [pattern]')
|
|
1216
|
+
.description('Search stored AST nodes (calls, new, string, regex, throw, await) by pattern'),
|
|
1217
|
+
)
|
|
1248
1218
|
.option('-k, --kind <kind>', 'Filter by AST node kind (call, new, string, regex, throw, await)')
|
|
1249
1219
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1250
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1251
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1252
|
-
.option('-j, --json', 'Output as JSON')
|
|
1253
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1254
|
-
.option('--limit <number>', 'Max results to return')
|
|
1255
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1256
1220
|
.action(async (pattern, opts) => {
|
|
1257
1221
|
const { AST_NODE_KINDS, astQuery } = await import('./ast.js');
|
|
1258
1222
|
if (opts.kind && !AST_NODE_KINDS.includes(opts.kind)) {
|
|
@@ -1270,19 +1234,14 @@ program
|
|
|
1270
1234
|
});
|
|
1271
1235
|
});
|
|
1272
1236
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1237
|
+
QUERY_OPTS(
|
|
1238
|
+
program
|
|
1239
|
+
.command('communities')
|
|
1240
|
+
.description('Detect natural module boundaries using Louvain community detection'),
|
|
1241
|
+
)
|
|
1276
1242
|
.option('--functions', 'Function-level instead of file-level')
|
|
1277
1243
|
.option('--resolution <n>', 'Louvain resolution parameter (default 1.0)', '1.0')
|
|
1278
1244
|
.option('--drift', 'Show only drift analysis')
|
|
1279
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1280
|
-
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1281
|
-
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1282
|
-
.option('-j, --json', 'Output as JSON')
|
|
1283
|
-
.option('--limit <number>', 'Max results to return')
|
|
1284
|
-
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1285
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1286
1245
|
.action(async (opts) => {
|
|
1287
1246
|
const { communities } = await import('./communities.js');
|
|
1288
1247
|
communities(opts.db, {
|
|
@@ -1336,11 +1295,7 @@ program
|
|
|
1336
1295
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
1337
1296
|
noTests: resolveNoTests(opts),
|
|
1338
1297
|
});
|
|
1339
|
-
if (opts
|
|
1340
|
-
printNdjson(data, 'hotspots');
|
|
1341
|
-
} else if (opts.json) {
|
|
1342
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1343
|
-
} else {
|
|
1298
|
+
if (!outputResult(data, 'hotspots', opts)) {
|
|
1344
1299
|
console.log(formatHotspots(data));
|
|
1345
1300
|
}
|
|
1346
1301
|
return;
|
package/src/cochange.js
CHANGED
|
@@ -12,7 +12,7 @@ import { normalizePath } from './constants.js';
|
|
|
12
12
|
import { closeDb, findDbPath, initSchema, openDb, openReadonlyOrFail } from './db.js';
|
|
13
13
|
import { warn } from './logger.js';
|
|
14
14
|
import { paginateResult } from './paginate.js';
|
|
15
|
-
import { isTestFile } from './
|
|
15
|
+
import { isTestFile } from './test-filter.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Scan git history and return parsed commit data.
|
package/src/communities.js
CHANGED
|
@@ -2,8 +2,9 @@ import path from 'node:path';
|
|
|
2
2
|
import Graph from 'graphology';
|
|
3
3
|
import louvain from 'graphology-communities-louvain';
|
|
4
4
|
import { openReadonlyOrFail } from './db.js';
|
|
5
|
-
import { paginateResult
|
|
6
|
-
import {
|
|
5
|
+
import { paginateResult } from './paginate.js';
|
|
6
|
+
import { outputResult } from './result-formatter.js';
|
|
7
|
+
import { isTestFile } from './test-filter.js';
|
|
7
8
|
|
|
8
9
|
// ─── Graph Construction ───────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -96,12 +97,15 @@ function getDirectory(filePath) {
|
|
|
96
97
|
export function communitiesData(customDbPath, opts = {}) {
|
|
97
98
|
const db = openReadonlyOrFail(customDbPath);
|
|
98
99
|
const resolution = opts.resolution ?? 1.0;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
let graph;
|
|
101
|
+
try {
|
|
102
|
+
graph = buildGraphologyGraph(db, {
|
|
103
|
+
functions: opts.functions,
|
|
104
|
+
noTests: opts.noTests,
|
|
105
|
+
});
|
|
106
|
+
} finally {
|
|
107
|
+
db.close();
|
|
108
|
+
}
|
|
105
109
|
|
|
106
110
|
// Handle empty or trivial graphs
|
|
107
111
|
if (graph.order === 0 || graph.size === 0) {
|
|
@@ -240,14 +244,7 @@ export function communitySummaryForStats(customDbPath, opts = {}) {
|
|
|
240
244
|
export function communities(customDbPath, opts = {}) {
|
|
241
245
|
const data = communitiesData(customDbPath, opts);
|
|
242
246
|
|
|
243
|
-
if (opts
|
|
244
|
-
printNdjson(data, 'communities');
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (opts.json) {
|
|
248
|
-
console.log(JSON.stringify(data, null, 2));
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
247
|
+
if (outputResult(data, 'communities', opts)) return;
|
|
251
248
|
|
|
252
249
|
if (data.summary.communityCount === 0) {
|
|
253
250
|
console.log(
|