@optave/codegraph 2.4.0 → 2.5.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 +66 -10
- package/package.json +15 -5
- package/src/branch-compare.js +568 -0
- package/src/builder.js +183 -22
- package/src/cli.js +253 -8
- package/src/cochange.js +8 -8
- package/src/communities.js +303 -0
- package/src/complexity.js +2056 -0
- package/src/config.js +20 -1
- package/src/db.js +111 -1
- package/src/embedder.js +49 -12
- package/src/export.js +25 -1
- package/src/flow.js +361 -0
- package/src/index.js +32 -2
- package/src/manifesto.js +442 -0
- package/src/mcp.js +244 -5
- package/src/paginate.js +70 -0
- package/src/parser.js +21 -5
- package/src/queries.js +396 -7
- package/src/registry.js +6 -3
- package/src/structure.js +88 -24
- package/src/update-check.js +1 -0
- package/src/watcher.js +2 -2
package/src/cli.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
queryName,
|
|
30
30
|
roles,
|
|
31
31
|
stats,
|
|
32
|
+
symbolPath,
|
|
32
33
|
VALID_ROLES,
|
|
33
34
|
where,
|
|
34
35
|
} from './queries.js';
|
|
@@ -99,8 +100,17 @@ program
|
|
|
99
100
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
100
101
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
101
102
|
.option('-j, --json', 'Output as JSON')
|
|
103
|
+
.option('--limit <number>', 'Max results to return')
|
|
104
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
105
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
102
106
|
.action((name, opts) => {
|
|
103
|
-
queryName(name, opts.db, {
|
|
107
|
+
queryName(name, opts.db, {
|
|
108
|
+
noTests: resolveNoTests(opts),
|
|
109
|
+
json: opts.json,
|
|
110
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
111
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
112
|
+
ndjson: opts.ndjson,
|
|
113
|
+
});
|
|
104
114
|
});
|
|
105
115
|
|
|
106
116
|
program
|
|
@@ -136,8 +146,8 @@ program
|
|
|
136
146
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
137
147
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
138
148
|
.option('-j, --json', 'Output as JSON')
|
|
139
|
-
.action((opts) => {
|
|
140
|
-
stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
149
|
+
.action(async (opts) => {
|
|
150
|
+
await stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
141
151
|
});
|
|
142
152
|
|
|
143
153
|
program
|
|
@@ -199,6 +209,36 @@ program
|
|
|
199
209
|
});
|
|
200
210
|
});
|
|
201
211
|
|
|
212
|
+
program
|
|
213
|
+
.command('path <from> <to>')
|
|
214
|
+
.description('Find shortest path between two symbols (A calls...calls B)')
|
|
215
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
216
|
+
.option('--max-depth <n>', 'Maximum BFS depth', '10')
|
|
217
|
+
.option('--kinds <kinds>', 'Comma-separated edge kinds to follow (default: calls)')
|
|
218
|
+
.option('--reverse', 'Follow edges backward (B is called by...called by A)')
|
|
219
|
+
.option('--from-file <path>', 'Disambiguate source symbol by file (partial match)')
|
|
220
|
+
.option('--to-file <path>', 'Disambiguate target symbol by file (partial match)')
|
|
221
|
+
.option('-k, --kind <kind>', 'Filter both symbols by kind')
|
|
222
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
223
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
224
|
+
.option('-j, --json', 'Output as JSON')
|
|
225
|
+
.action((from, to, opts) => {
|
|
226
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
227
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
symbolPath(from, to, opts.db, {
|
|
231
|
+
maxDepth: parseInt(opts.maxDepth, 10),
|
|
232
|
+
edgeKinds: opts.kinds ? opts.kinds.split(',').map((s) => s.trim()) : undefined,
|
|
233
|
+
reverse: opts.reverse,
|
|
234
|
+
fromFile: opts.fromFile,
|
|
235
|
+
toFile: opts.toFile,
|
|
236
|
+
kind: opts.kind,
|
|
237
|
+
noTests: resolveNoTests(opts),
|
|
238
|
+
json: opts.json,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
202
242
|
program
|
|
203
243
|
.command('context <name>')
|
|
204
244
|
.description('Full context for a function: source, deps, callers, tests, signature')
|
|
@@ -251,13 +291,23 @@ program
|
|
|
251
291
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
252
292
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
253
293
|
.option('-j, --json', 'Output as JSON')
|
|
294
|
+
.option('--limit <number>', 'Max results to return')
|
|
295
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
296
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
254
297
|
.action((name, opts) => {
|
|
255
298
|
if (!name && !opts.file) {
|
|
256
299
|
console.error('Provide a symbol name or use --file <path>');
|
|
257
300
|
process.exit(1);
|
|
258
301
|
}
|
|
259
302
|
const target = opts.file || name;
|
|
260
|
-
where(target, opts.db, {
|
|
303
|
+
where(target, opts.db, {
|
|
304
|
+
file: !!opts.file,
|
|
305
|
+
noTests: resolveNoTests(opts),
|
|
306
|
+
json: opts.json,
|
|
307
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
308
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
309
|
+
ndjson: opts.ndjson,
|
|
310
|
+
});
|
|
261
311
|
});
|
|
262
312
|
|
|
263
313
|
program
|
|
@@ -418,6 +468,7 @@ registry
|
|
|
418
468
|
.description('Remove stale registry entries (missing directories or idle beyond TTL)')
|
|
419
469
|
.option('--ttl <days>', 'Days of inactivity before pruning (default: 30)', '30')
|
|
420
470
|
.option('--exclude <names>', 'Comma-separated repo names to preserve from pruning')
|
|
471
|
+
.option('--dry-run', 'Show what would be pruned without removing anything')
|
|
421
472
|
.action((opts) => {
|
|
422
473
|
const excludeNames = opts.exclude
|
|
423
474
|
? opts.exclude
|
|
@@ -425,15 +476,25 @@ registry
|
|
|
425
476
|
.map((s) => s.trim())
|
|
426
477
|
.filter((s) => s.length > 0)
|
|
427
478
|
: [];
|
|
428
|
-
const
|
|
479
|
+
const dryRun = !!opts.dryRun;
|
|
480
|
+
const pruned = pruneRegistry(undefined, parseInt(opts.ttl, 10), excludeNames, dryRun);
|
|
429
481
|
if (pruned.length === 0) {
|
|
430
482
|
console.log('No stale entries found.');
|
|
431
483
|
} else {
|
|
484
|
+
const prefix = dryRun ? 'Would prune' : 'Pruned';
|
|
432
485
|
for (const entry of pruned) {
|
|
433
486
|
const tag = entry.reason === 'expired' ? 'expired' : 'missing';
|
|
434
|
-
console.log(
|
|
487
|
+
console.log(`${prefix} "${entry.name}" (${entry.path}) [${tag}]`);
|
|
488
|
+
}
|
|
489
|
+
if (dryRun) {
|
|
490
|
+
console.log(
|
|
491
|
+
`\nDry run: ${pruned.length} ${pruned.length === 1 ? 'entry' : 'entries'} would be removed.`,
|
|
492
|
+
);
|
|
493
|
+
} else {
|
|
494
|
+
console.log(
|
|
495
|
+
`\nRemoved ${pruned.length} stale ${pruned.length === 1 ? 'entry' : 'entries'}.`,
|
|
496
|
+
);
|
|
435
497
|
}
|
|
436
|
-
console.log(`\nRemoved ${pruned.length} stale ${pruned.length === 1 ? 'entry' : 'entries'}.`);
|
|
437
498
|
}
|
|
438
499
|
});
|
|
439
500
|
|
|
@@ -470,6 +531,7 @@ program
|
|
|
470
531
|
`Embedding strategy: ${EMBEDDING_STRATEGIES.join(', ')}. "structured" uses graph context (callers/callees), "source" embeds raw code`,
|
|
471
532
|
'structured',
|
|
472
533
|
)
|
|
534
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
473
535
|
.action(async (dir, opts) => {
|
|
474
536
|
if (!EMBEDDING_STRATEGIES.includes(opts.strategy)) {
|
|
475
537
|
console.error(
|
|
@@ -479,7 +541,7 @@ program
|
|
|
479
541
|
}
|
|
480
542
|
const root = path.resolve(dir || '.');
|
|
481
543
|
const model = opts.model || config.embeddings?.model || DEFAULT_MODEL;
|
|
482
|
-
await buildEmbeddings(root, model,
|
|
544
|
+
await buildEmbeddings(root, model, opts.db, { strategy: opts.strategy });
|
|
483
545
|
});
|
|
484
546
|
|
|
485
547
|
program
|
|
@@ -516,6 +578,7 @@ program
|
|
|
516
578
|
.option('-d, --db <path>', 'Path to graph.db')
|
|
517
579
|
.option('--depth <n>', 'Max directory depth')
|
|
518
580
|
.option('--sort <metric>', 'Sort by: cohesion | fan-in | fan-out | density | files', 'files')
|
|
581
|
+
.option('--full', 'Show all files without limit')
|
|
519
582
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
520
583
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
521
584
|
.option('-j, --json', 'Output as JSON')
|
|
@@ -525,6 +588,7 @@ program
|
|
|
525
588
|
directory: dir,
|
|
526
589
|
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
527
590
|
sort: opts.sort,
|
|
591
|
+
full: opts.full,
|
|
528
592
|
noTests: resolveNoTests(opts),
|
|
529
593
|
});
|
|
530
594
|
if (opts.json) {
|
|
@@ -570,6 +634,9 @@ program
|
|
|
570
634
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
571
635
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
572
636
|
.option('-j, --json', 'Output as JSON')
|
|
637
|
+
.option('--limit <number>', 'Max results to return')
|
|
638
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
639
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
573
640
|
.action((opts) => {
|
|
574
641
|
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
575
642
|
console.error(`Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.join(', ')}`);
|
|
@@ -580,6 +647,9 @@ program
|
|
|
580
647
|
file: opts.file,
|
|
581
648
|
noTests: resolveNoTests(opts),
|
|
582
649
|
json: opts.json,
|
|
650
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
651
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
652
|
+
ndjson: opts.ndjson,
|
|
583
653
|
});
|
|
584
654
|
});
|
|
585
655
|
|
|
@@ -645,6 +715,144 @@ program
|
|
|
645
715
|
}
|
|
646
716
|
});
|
|
647
717
|
|
|
718
|
+
program
|
|
719
|
+
.command('flow [name]')
|
|
720
|
+
.description(
|
|
721
|
+
'Trace execution flow forward from an entry point (route, command, event) through callees to leaves',
|
|
722
|
+
)
|
|
723
|
+
.option('--list', 'List all entry points grouped by type')
|
|
724
|
+
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
725
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
726
|
+
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
727
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
728
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
729
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
730
|
+
.option('-j, --json', 'Output as JSON')
|
|
731
|
+
.option('--limit <number>', 'Max results to return')
|
|
732
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
733
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
734
|
+
.action(async (name, opts) => {
|
|
735
|
+
if (!name && !opts.list) {
|
|
736
|
+
console.error('Provide a function/entry point name or use --list to see all entry points.');
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
740
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
const { flow } = await import('./flow.js');
|
|
744
|
+
flow(name, opts.db, {
|
|
745
|
+
list: opts.list,
|
|
746
|
+
depth: parseInt(opts.depth, 10),
|
|
747
|
+
file: opts.file,
|
|
748
|
+
kind: opts.kind,
|
|
749
|
+
noTests: resolveNoTests(opts),
|
|
750
|
+
json: opts.json,
|
|
751
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
752
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
753
|
+
ndjson: opts.ndjson,
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
program
|
|
758
|
+
.command('complexity [target]')
|
|
759
|
+
.description('Show per-function complexity metrics (cognitive, cyclomatic, nesting depth, MI)')
|
|
760
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
761
|
+
.option('-n, --limit <number>', 'Max results', '20')
|
|
762
|
+
.option(
|
|
763
|
+
'--sort <metric>',
|
|
764
|
+
'Sort by: cognitive | cyclomatic | nesting | mi | volume | effort | bugs | loc',
|
|
765
|
+
'cognitive',
|
|
766
|
+
)
|
|
767
|
+
.option('--above-threshold', 'Only functions exceeding warn thresholds')
|
|
768
|
+
.option('--health', 'Show health metrics (Halstead, MI) columns')
|
|
769
|
+
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
770
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
771
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
772
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
773
|
+
.option('-j, --json', 'Output as JSON')
|
|
774
|
+
.action(async (target, opts) => {
|
|
775
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
776
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
const { complexity } = await import('./complexity.js');
|
|
780
|
+
complexity(opts.db, {
|
|
781
|
+
target,
|
|
782
|
+
limit: parseInt(opts.limit, 10),
|
|
783
|
+
sort: opts.sort,
|
|
784
|
+
aboveThreshold: opts.aboveThreshold,
|
|
785
|
+
health: opts.health,
|
|
786
|
+
file: opts.file,
|
|
787
|
+
kind: opts.kind,
|
|
788
|
+
noTests: resolveNoTests(opts),
|
|
789
|
+
json: opts.json,
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
program
|
|
794
|
+
.command('manifesto')
|
|
795
|
+
.description('Evaluate manifesto rules (pass/fail verdicts for code health)')
|
|
796
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
797
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
798
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
799
|
+
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
800
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
801
|
+
.option('-j, --json', 'Output as JSON')
|
|
802
|
+
.action(async (opts) => {
|
|
803
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
804
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
const { manifesto } = await import('./manifesto.js');
|
|
808
|
+
manifesto(opts.db, {
|
|
809
|
+
file: opts.file,
|
|
810
|
+
kind: opts.kind,
|
|
811
|
+
noTests: resolveNoTests(opts),
|
|
812
|
+
json: opts.json,
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
program
|
|
817
|
+
.command('communities')
|
|
818
|
+
.description('Detect natural module boundaries using Louvain community detection')
|
|
819
|
+
.option('--functions', 'Function-level instead of file-level')
|
|
820
|
+
.option('--resolution <n>', 'Louvain resolution parameter (default 1.0)', '1.0')
|
|
821
|
+
.option('--drift', 'Show only drift analysis')
|
|
822
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
823
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
824
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
825
|
+
.option('-j, --json', 'Output as JSON')
|
|
826
|
+
.action(async (opts) => {
|
|
827
|
+
const { communities } = await import('./communities.js');
|
|
828
|
+
communities(opts.db, {
|
|
829
|
+
functions: opts.functions,
|
|
830
|
+
resolution: parseFloat(opts.resolution),
|
|
831
|
+
drift: opts.drift,
|
|
832
|
+
noTests: resolveNoTests(opts),
|
|
833
|
+
json: opts.json,
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
program
|
|
838
|
+
.command('branch-compare <base> <target>')
|
|
839
|
+
.description('Compare code structure between two branches/refs')
|
|
840
|
+
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
841
|
+
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
842
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
843
|
+
.option('-j, --json', 'Output as JSON')
|
|
844
|
+
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
845
|
+
.action(async (base, target, opts) => {
|
|
846
|
+
const { branchCompare } = await import('./branch-compare.js');
|
|
847
|
+
await branchCompare(base, target, {
|
|
848
|
+
engine: program.opts().engine,
|
|
849
|
+
depth: parseInt(opts.depth, 10),
|
|
850
|
+
noTests: resolveNoTests(opts),
|
|
851
|
+
json: opts.json,
|
|
852
|
+
format: opts.format,
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
|
|
648
856
|
program
|
|
649
857
|
.command('watch [dir]')
|
|
650
858
|
.description('Watch project for file changes and incrementally update the graph')
|
|
@@ -680,6 +888,43 @@ program
|
|
|
680
888
|
console.log(` Engine flag : --engine ${engine}`);
|
|
681
889
|
console.log(` Active engine : ${activeName}${activeVersion ? ` (v${activeVersion})` : ''}`);
|
|
682
890
|
console.log();
|
|
891
|
+
|
|
892
|
+
// Build metadata from DB
|
|
893
|
+
try {
|
|
894
|
+
const { findDbPath, getBuildMeta } = await import('./db.js');
|
|
895
|
+
const Database = (await import('better-sqlite3')).default;
|
|
896
|
+
const dbPath = findDbPath();
|
|
897
|
+
const fs = await import('node:fs');
|
|
898
|
+
if (fs.existsSync(dbPath)) {
|
|
899
|
+
const db = new Database(dbPath, { readonly: true });
|
|
900
|
+
const buildEngine = getBuildMeta(db, 'engine');
|
|
901
|
+
const buildVersion = getBuildMeta(db, 'codegraph_version');
|
|
902
|
+
const builtAt = getBuildMeta(db, 'built_at');
|
|
903
|
+
db.close();
|
|
904
|
+
|
|
905
|
+
if (buildEngine || buildVersion || builtAt) {
|
|
906
|
+
console.log('Build metadata');
|
|
907
|
+
console.log('──────────────');
|
|
908
|
+
if (buildEngine) console.log(` Engine : ${buildEngine}`);
|
|
909
|
+
if (buildVersion) console.log(` Version : ${buildVersion}`);
|
|
910
|
+
if (builtAt) console.log(` Built at : ${builtAt}`);
|
|
911
|
+
|
|
912
|
+
if (buildVersion && buildVersion !== program.version()) {
|
|
913
|
+
console.log(
|
|
914
|
+
` ⚠ DB was built with v${buildVersion}, current is v${program.version()}. Consider: codegraph build --no-incremental`,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
if (buildEngine && buildEngine !== activeName) {
|
|
918
|
+
console.log(
|
|
919
|
+
` ⚠ DB was built with ${buildEngine} engine, active is ${activeName}. Consider: codegraph build --no-incremental`,
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
console.log();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
} catch {
|
|
926
|
+
/* diagnostics must never crash */
|
|
927
|
+
}
|
|
683
928
|
});
|
|
684
929
|
|
|
685
930
|
program.parse();
|
package/src/cochange.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { normalizePath } from './constants.js';
|
|
12
|
-
import { findDbPath, initSchema, openDb, openReadonlyOrFail } from './db.js';
|
|
12
|
+
import { closeDb, findDbPath, initSchema, openDb, openReadonlyOrFail } from './db.js';
|
|
13
13
|
import { warn } from './logger.js';
|
|
14
14
|
import { isTestFile } from './queries.js';
|
|
15
15
|
|
|
@@ -145,7 +145,7 @@ export function analyzeCoChanges(customDbPath, opts = {}) {
|
|
|
145
145
|
const repoRoot = path.resolve(path.dirname(dbPath), '..');
|
|
146
146
|
|
|
147
147
|
if (!fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
148
|
-
db
|
|
148
|
+
closeDb(db);
|
|
149
149
|
return { error: `Not a git repository: ${repoRoot}` };
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -245,7 +245,7 @@ export function analyzeCoChanges(customDbPath, opts = {}) {
|
|
|
245
245
|
|
|
246
246
|
const totalPairs = db.prepare('SELECT COUNT(*) as cnt FROM co_changes').get().cnt;
|
|
247
247
|
|
|
248
|
-
db
|
|
248
|
+
closeDb(db);
|
|
249
249
|
|
|
250
250
|
return {
|
|
251
251
|
pairsFound: totalPairs,
|
|
@@ -275,14 +275,14 @@ export function coChangeData(file, customDbPath, opts = {}) {
|
|
|
275
275
|
try {
|
|
276
276
|
db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
|
|
277
277
|
} catch {
|
|
278
|
-
db
|
|
278
|
+
closeDb(db);
|
|
279
279
|
return { error: 'No co-change data found. Run `codegraph co-change --analyze` first.' };
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// Resolve file via partial match
|
|
283
283
|
const resolvedFile = resolveCoChangeFile(db, file);
|
|
284
284
|
if (!resolvedFile) {
|
|
285
|
-
db
|
|
285
|
+
closeDb(db);
|
|
286
286
|
return { error: `No co-change data found for file matching "${file}"` };
|
|
287
287
|
}
|
|
288
288
|
|
|
@@ -311,7 +311,7 @@ export function coChangeData(file, customDbPath, opts = {}) {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
const meta = getCoChangeMeta(db);
|
|
314
|
-
db
|
|
314
|
+
closeDb(db);
|
|
315
315
|
|
|
316
316
|
return { file: resolvedFile, partners, meta };
|
|
317
317
|
}
|
|
@@ -334,7 +334,7 @@ export function coChangeTopData(customDbPath, opts = {}) {
|
|
|
334
334
|
try {
|
|
335
335
|
db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
|
|
336
336
|
} catch {
|
|
337
|
-
db
|
|
337
|
+
closeDb(db);
|
|
338
338
|
return { error: 'No co-change data found. Run `codegraph co-change --analyze` first.' };
|
|
339
339
|
}
|
|
340
340
|
|
|
@@ -363,7 +363,7 @@ export function coChangeTopData(customDbPath, opts = {}) {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
const meta = getCoChangeMeta(db);
|
|
366
|
-
db
|
|
366
|
+
closeDb(db);
|
|
367
367
|
|
|
368
368
|
return { pairs, meta };
|
|
369
369
|
}
|