@optave/codegraph 3.1.0 → 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 +5 -5
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +8 -9
- 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 +0 -5
- package/src/cfg.js +106 -338
- package/src/check.js +3 -3
- package/src/cli.js +99 -179
- 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 +269 -694
- 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 -399
- package/src/embedder.js +145 -141
- package/src/export.js +1 -1
- package/src/flow.js +161 -162
- package/src/index.js +34 -1
- package/src/kinds.js +49 -0
- package/src/manifesto.js +3 -8
- package/src/mcp.js +37 -20
- package/src/owners.js +132 -132
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1323 -2267
- package/src/result-formatter.js +21 -0
- package/src/sequence.js +177 -182
- 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,61 +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')
|
|
280
|
-
.option('--unused', 'Show only exports with zero consumers')
|
|
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)')
|
|
281
265
|
.action((file, opts) => {
|
|
282
266
|
fileExports(file, opts.db, {
|
|
283
267
|
noTests: resolveNoTests(opts),
|
|
284
268
|
json: opts.json,
|
|
269
|
+
unused: opts.unused || false,
|
|
285
270
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
286
271
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
287
272
|
ndjson: opts.ndjson,
|
|
288
|
-
unused: opts.unused,
|
|
289
273
|
});
|
|
290
274
|
});
|
|
291
275
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
276
|
+
QUERY_OPTS(
|
|
277
|
+
program
|
|
278
|
+
.command('fn-impact <name>')
|
|
279
|
+
.description('Function-level impact: what functions break if this one changes'),
|
|
280
|
+
)
|
|
296
281
|
.option('--depth <n>', 'Max transitive depth', '5')
|
|
297
282
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
298
283
|
.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
284
|
.action((name, opts) => {
|
|
306
285
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
307
286
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -319,21 +298,16 @@ program
|
|
|
319
298
|
});
|
|
320
299
|
});
|
|
321
300
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
301
|
+
QUERY_OPTS(
|
|
302
|
+
program
|
|
303
|
+
.command('context <name>')
|
|
304
|
+
.description('Full context for a function: source, deps, callers, tests, signature'),
|
|
305
|
+
)
|
|
326
306
|
.option('--depth <n>', 'Include callee source up to N levels deep', '0')
|
|
327
307
|
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
|
|
328
308
|
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
|
|
329
309
|
.option('--no-source', 'Metadata only (skip source extraction)')
|
|
330
310
|
.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
311
|
.action((name, opts) => {
|
|
338
312
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
339
313
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -417,17 +391,12 @@ program
|
|
|
417
391
|
});
|
|
418
392
|
});
|
|
419
393
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
394
|
+
QUERY_OPTS(
|
|
395
|
+
program
|
|
396
|
+
.command('where [name]')
|
|
397
|
+
.description('Find where a symbol is defined and used (minimal, fast lookup)'),
|
|
398
|
+
)
|
|
424
399
|
.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
400
|
.action((name, opts) => {
|
|
432
401
|
if (!name && !opts.file) {
|
|
433
402
|
console.error('Provide a symbol name or use --file <path>');
|
|
@@ -448,15 +417,14 @@ program
|
|
|
448
417
|
.command('diff-impact [ref]')
|
|
449
418
|
.description('Show impact of git changes (unstaged, staged, or vs a ref)')
|
|
450
419
|
.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
420
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
454
421
|
.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
422
|
.option('--limit <number>', 'Max results to return')
|
|
458
423
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
459
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')
|
|
460
428
|
.action((ref, opts) => {
|
|
461
429
|
diffImpact(opts.db, {
|
|
462
430
|
ref,
|
|
@@ -994,11 +962,7 @@ program
|
|
|
994
962
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
995
963
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
996
964
|
});
|
|
997
|
-
if (opts
|
|
998
|
-
printNdjson(data, 'directories');
|
|
999
|
-
} else if (opts.json) {
|
|
1000
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1001
|
-
} else {
|
|
965
|
+
if (!outputResult(data, 'directories', opts)) {
|
|
1002
966
|
console.log(formatStructure(data));
|
|
1003
967
|
}
|
|
1004
968
|
});
|
|
@@ -1081,41 +1045,28 @@ program
|
|
|
1081
1045
|
|
|
1082
1046
|
if (file) {
|
|
1083
1047
|
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 {
|
|
1048
|
+
if (!outputResult(data, 'partners', opts)) {
|
|
1089
1049
|
console.log(formatCoChange(data));
|
|
1090
1050
|
}
|
|
1091
1051
|
} else {
|
|
1092
1052
|
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 {
|
|
1053
|
+
if (!outputResult(data, 'pairs', opts)) {
|
|
1098
1054
|
console.log(formatCoChangeTop(data));
|
|
1099
1055
|
}
|
|
1100
1056
|
}
|
|
1101
1057
|
});
|
|
1102
1058
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
+
)
|
|
1108
1066
|
.option('--list', 'List all entry points grouped by type')
|
|
1109
1067
|
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1110
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1111
1068
|
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1112
1069
|
.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
1070
|
.action(async (name, opts) => {
|
|
1120
1071
|
if (!name && !opts.list) {
|
|
1121
1072
|
console.error('Provide a function/entry point name or use --list to see all entry points.');
|
|
@@ -1139,20 +1090,17 @@ program
|
|
|
1139
1090
|
});
|
|
1140
1091
|
});
|
|
1141
1092
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
+
)
|
|
1145
1100
|
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1146
1101
|
.option('--dataflow', 'Annotate with parameter names and return arrows from dataflow table')
|
|
1147
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1148
1102
|
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1149
1103
|
.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
1104
|
.action(async (name, opts) => {
|
|
1157
1105
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1158
1106
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -1172,18 +1120,13 @@ program
|
|
|
1172
1120
|
});
|
|
1173
1121
|
});
|
|
1174
1122
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1123
|
+
QUERY_OPTS(
|
|
1124
|
+
program
|
|
1125
|
+
.command('dataflow <name>')
|
|
1126
|
+
.description('Show data flow for a function: parameters, return consumers, mutations'),
|
|
1127
|
+
)
|
|
1179
1128
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1180
1129
|
.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
1130
|
.option('--impact', 'Show data-dependent blast radius')
|
|
1188
1131
|
.option('--depth <n>', 'Max traversal depth', '5')
|
|
1189
1132
|
.action(async (name, opts) => {
|
|
@@ -1205,19 +1148,10 @@ program
|
|
|
1205
1148
|
});
|
|
1206
1149
|
});
|
|
1207
1150
|
|
|
1208
|
-
program
|
|
1209
|
-
.command('cfg <name>')
|
|
1210
|
-
.description('Show control flow graph for a function')
|
|
1211
|
-
.option('-d, --db <path>', 'Path to graph.db')
|
|
1151
|
+
QUERY_OPTS(program.command('cfg <name>').description('Show control flow graph for a function'))
|
|
1212
1152
|
.option('--format <fmt>', 'Output format: text, dot, mermaid', 'text')
|
|
1213
1153
|
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
1214
1154
|
.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
1155
|
.action(async (name, opts) => {
|
|
1222
1156
|
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1223
1157
|
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
@@ -1276,18 +1210,13 @@ program
|
|
|
1276
1210
|
});
|
|
1277
1211
|
});
|
|
1278
1212
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1213
|
+
QUERY_OPTS(
|
|
1214
|
+
program
|
|
1215
|
+
.command('ast [pattern]')
|
|
1216
|
+
.description('Search stored AST nodes (calls, new, string, regex, throw, await) by pattern'),
|
|
1217
|
+
)
|
|
1283
1218
|
.option('-k, --kind <kind>', 'Filter by AST node kind (call, new, string, regex, throw, await)')
|
|
1284
1219
|
.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
1220
|
.action(async (pattern, opts) => {
|
|
1292
1221
|
const { AST_NODE_KINDS, astQuery } = await import('./ast.js');
|
|
1293
1222
|
if (opts.kind && !AST_NODE_KINDS.includes(opts.kind)) {
|
|
@@ -1305,19 +1234,14 @@ program
|
|
|
1305
1234
|
});
|
|
1306
1235
|
});
|
|
1307
1236
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1237
|
+
QUERY_OPTS(
|
|
1238
|
+
program
|
|
1239
|
+
.command('communities')
|
|
1240
|
+
.description('Detect natural module boundaries using Louvain community detection'),
|
|
1241
|
+
)
|
|
1311
1242
|
.option('--functions', 'Function-level instead of file-level')
|
|
1312
1243
|
.option('--resolution <n>', 'Louvain resolution parameter (default 1.0)', '1.0')
|
|
1313
1244
|
.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
1245
|
.action(async (opts) => {
|
|
1322
1246
|
const { communities } = await import('./communities.js');
|
|
1323
1247
|
communities(opts.db, {
|
|
@@ -1371,11 +1295,7 @@ program
|
|
|
1371
1295
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
1372
1296
|
noTests: resolveNoTests(opts),
|
|
1373
1297
|
});
|
|
1374
|
-
if (opts
|
|
1375
|
-
printNdjson(data, 'hotspots');
|
|
1376
|
-
} else if (opts.json) {
|
|
1377
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1378
|
-
} else {
|
|
1298
|
+
if (!outputResult(data, 'hotspots', opts)) {
|
|
1379
1299
|
console.log(formatHotspots(data));
|
|
1380
1300
|
}
|
|
1381
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(
|