@optave/codegraph 2.3.1-dev.1aeea34 → 2.5.0
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 -6
- package/src/builder.js +183 -22
- package/src/cli.js +251 -5
- 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/structure.js +88 -24
- package/src/update-check.js +160 -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';
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
registerRepo,
|
|
40
41
|
unregisterRepo,
|
|
41
42
|
} from './registry.js';
|
|
43
|
+
import { checkForUpdates, printUpdateNotification } from './update-check.js';
|
|
42
44
|
import { watchProject } from './watcher.js';
|
|
43
45
|
|
|
44
46
|
const __cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
|
|
@@ -56,6 +58,17 @@ program
|
|
|
56
58
|
.hook('preAction', (thisCommand) => {
|
|
57
59
|
const opts = thisCommand.opts();
|
|
58
60
|
if (opts.verbose) setVerbose(true);
|
|
61
|
+
})
|
|
62
|
+
.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
63
|
+
const name = actionCommand.name();
|
|
64
|
+
if (name === 'mcp' || name === 'watch') return;
|
|
65
|
+
if (actionCommand.opts().json) return;
|
|
66
|
+
try {
|
|
67
|
+
const result = await checkForUpdates(pkg.version);
|
|
68
|
+
if (result) printUpdateNotification(result.current, result.latest);
|
|
69
|
+
} catch {
|
|
70
|
+
/* never break CLI */
|
|
71
|
+
}
|
|
59
72
|
});
|
|
60
73
|
|
|
61
74
|
/**
|
|
@@ -87,8 +100,17 @@ program
|
|
|
87
100
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
88
101
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
89
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')
|
|
90
106
|
.action((name, opts) => {
|
|
91
|
-
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
|
+
});
|
|
92
114
|
});
|
|
93
115
|
|
|
94
116
|
program
|
|
@@ -124,8 +146,8 @@ program
|
|
|
124
146
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
125
147
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
126
148
|
.option('-j, --json', 'Output as JSON')
|
|
127
|
-
.action((opts) => {
|
|
128
|
-
stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
149
|
+
.action(async (opts) => {
|
|
150
|
+
await stats(opts.db, { noTests: resolveNoTests(opts), json: opts.json });
|
|
129
151
|
});
|
|
130
152
|
|
|
131
153
|
program
|
|
@@ -187,6 +209,36 @@ program
|
|
|
187
209
|
});
|
|
188
210
|
});
|
|
189
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
|
+
|
|
190
242
|
program
|
|
191
243
|
.command('context <name>')
|
|
192
244
|
.description('Full context for a function: source, deps, callers, tests, signature')
|
|
@@ -239,13 +291,23 @@ program
|
|
|
239
291
|
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
240
292
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
241
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')
|
|
242
297
|
.action((name, opts) => {
|
|
243
298
|
if (!name && !opts.file) {
|
|
244
299
|
console.error('Provide a symbol name or use --file <path>');
|
|
245
300
|
process.exit(1);
|
|
246
301
|
}
|
|
247
302
|
const target = opts.file || name;
|
|
248
|
-
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
|
+
});
|
|
249
311
|
});
|
|
250
312
|
|
|
251
313
|
program
|
|
@@ -458,6 +520,7 @@ program
|
|
|
458
520
|
`Embedding strategy: ${EMBEDDING_STRATEGIES.join(', ')}. "structured" uses graph context (callers/callees), "source" embeds raw code`,
|
|
459
521
|
'structured',
|
|
460
522
|
)
|
|
523
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
461
524
|
.action(async (dir, opts) => {
|
|
462
525
|
if (!EMBEDDING_STRATEGIES.includes(opts.strategy)) {
|
|
463
526
|
console.error(
|
|
@@ -467,7 +530,7 @@ program
|
|
|
467
530
|
}
|
|
468
531
|
const root = path.resolve(dir || '.');
|
|
469
532
|
const model = opts.model || config.embeddings?.model || DEFAULT_MODEL;
|
|
470
|
-
await buildEmbeddings(root, model,
|
|
533
|
+
await buildEmbeddings(root, model, opts.db, { strategy: opts.strategy });
|
|
471
534
|
});
|
|
472
535
|
|
|
473
536
|
program
|
|
@@ -504,6 +567,7 @@ program
|
|
|
504
567
|
.option('-d, --db <path>', 'Path to graph.db')
|
|
505
568
|
.option('--depth <n>', 'Max directory depth')
|
|
506
569
|
.option('--sort <metric>', 'Sort by: cohesion | fan-in | fan-out | density | files', 'files')
|
|
570
|
+
.option('--full', 'Show all files without limit')
|
|
507
571
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
508
572
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
509
573
|
.option('-j, --json', 'Output as JSON')
|
|
@@ -513,6 +577,7 @@ program
|
|
|
513
577
|
directory: dir,
|
|
514
578
|
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
515
579
|
sort: opts.sort,
|
|
580
|
+
full: opts.full,
|
|
516
581
|
noTests: resolveNoTests(opts),
|
|
517
582
|
});
|
|
518
583
|
if (opts.json) {
|
|
@@ -558,6 +623,9 @@ program
|
|
|
558
623
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
559
624
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
560
625
|
.option('-j, --json', 'Output as JSON')
|
|
626
|
+
.option('--limit <number>', 'Max results to return')
|
|
627
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
628
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
561
629
|
.action((opts) => {
|
|
562
630
|
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
563
631
|
console.error(`Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.join(', ')}`);
|
|
@@ -568,6 +636,9 @@ program
|
|
|
568
636
|
file: opts.file,
|
|
569
637
|
noTests: resolveNoTests(opts),
|
|
570
638
|
json: opts.json,
|
|
639
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
640
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
641
|
+
ndjson: opts.ndjson,
|
|
571
642
|
});
|
|
572
643
|
});
|
|
573
644
|
|
|
@@ -633,6 +704,144 @@ program
|
|
|
633
704
|
}
|
|
634
705
|
});
|
|
635
706
|
|
|
707
|
+
program
|
|
708
|
+
.command('flow [name]')
|
|
709
|
+
.description(
|
|
710
|
+
'Trace execution flow forward from an entry point (route, command, event) through callees to leaves',
|
|
711
|
+
)
|
|
712
|
+
.option('--list', 'List all entry points grouped by type')
|
|
713
|
+
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
714
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
715
|
+
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
716
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
717
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
718
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
719
|
+
.option('-j, --json', 'Output as JSON')
|
|
720
|
+
.option('--limit <number>', 'Max results to return')
|
|
721
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
722
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
723
|
+
.action(async (name, opts) => {
|
|
724
|
+
if (!name && !opts.list) {
|
|
725
|
+
console.error('Provide a function/entry point name or use --list to see all entry points.');
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
729
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
const { flow } = await import('./flow.js');
|
|
733
|
+
flow(name, opts.db, {
|
|
734
|
+
list: opts.list,
|
|
735
|
+
depth: parseInt(opts.depth, 10),
|
|
736
|
+
file: opts.file,
|
|
737
|
+
kind: opts.kind,
|
|
738
|
+
noTests: resolveNoTests(opts),
|
|
739
|
+
json: opts.json,
|
|
740
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
741
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
742
|
+
ndjson: opts.ndjson,
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
program
|
|
747
|
+
.command('complexity [target]')
|
|
748
|
+
.description('Show per-function complexity metrics (cognitive, cyclomatic, nesting depth, MI)')
|
|
749
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
750
|
+
.option('-n, --limit <number>', 'Max results', '20')
|
|
751
|
+
.option(
|
|
752
|
+
'--sort <metric>',
|
|
753
|
+
'Sort by: cognitive | cyclomatic | nesting | mi | volume | effort | bugs | loc',
|
|
754
|
+
'cognitive',
|
|
755
|
+
)
|
|
756
|
+
.option('--above-threshold', 'Only functions exceeding warn thresholds')
|
|
757
|
+
.option('--health', 'Show health metrics (Halstead, MI) columns')
|
|
758
|
+
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
759
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
760
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
761
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
762
|
+
.option('-j, --json', 'Output as JSON')
|
|
763
|
+
.action(async (target, opts) => {
|
|
764
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
765
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
const { complexity } = await import('./complexity.js');
|
|
769
|
+
complexity(opts.db, {
|
|
770
|
+
target,
|
|
771
|
+
limit: parseInt(opts.limit, 10),
|
|
772
|
+
sort: opts.sort,
|
|
773
|
+
aboveThreshold: opts.aboveThreshold,
|
|
774
|
+
health: opts.health,
|
|
775
|
+
file: opts.file,
|
|
776
|
+
kind: opts.kind,
|
|
777
|
+
noTests: resolveNoTests(opts),
|
|
778
|
+
json: opts.json,
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
program
|
|
783
|
+
.command('manifesto')
|
|
784
|
+
.description('Evaluate manifesto rules (pass/fail verdicts for code health)')
|
|
785
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
786
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
787
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
788
|
+
.option('-f, --file <path>', 'Scope to file (partial match)')
|
|
789
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
790
|
+
.option('-j, --json', 'Output as JSON')
|
|
791
|
+
.action(async (opts) => {
|
|
792
|
+
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
|
|
793
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
const { manifesto } = await import('./manifesto.js');
|
|
797
|
+
manifesto(opts.db, {
|
|
798
|
+
file: opts.file,
|
|
799
|
+
kind: opts.kind,
|
|
800
|
+
noTests: resolveNoTests(opts),
|
|
801
|
+
json: opts.json,
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
program
|
|
806
|
+
.command('communities')
|
|
807
|
+
.description('Detect natural module boundaries using Louvain community detection')
|
|
808
|
+
.option('--functions', 'Function-level instead of file-level')
|
|
809
|
+
.option('--resolution <n>', 'Louvain resolution parameter (default 1.0)', '1.0')
|
|
810
|
+
.option('--drift', 'Show only drift analysis')
|
|
811
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
812
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
813
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
814
|
+
.option('-j, --json', 'Output as JSON')
|
|
815
|
+
.action(async (opts) => {
|
|
816
|
+
const { communities } = await import('./communities.js');
|
|
817
|
+
communities(opts.db, {
|
|
818
|
+
functions: opts.functions,
|
|
819
|
+
resolution: parseFloat(opts.resolution),
|
|
820
|
+
drift: opts.drift,
|
|
821
|
+
noTests: resolveNoTests(opts),
|
|
822
|
+
json: opts.json,
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
program
|
|
827
|
+
.command('branch-compare <base> <target>')
|
|
828
|
+
.description('Compare code structure between two branches/refs')
|
|
829
|
+
.option('--depth <n>', 'Max transitive caller depth', '3')
|
|
830
|
+
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
831
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
832
|
+
.option('-j, --json', 'Output as JSON')
|
|
833
|
+
.option('-f, --format <format>', 'Output format: text, mermaid, json', 'text')
|
|
834
|
+
.action(async (base, target, opts) => {
|
|
835
|
+
const { branchCompare } = await import('./branch-compare.js');
|
|
836
|
+
await branchCompare(base, target, {
|
|
837
|
+
engine: program.opts().engine,
|
|
838
|
+
depth: parseInt(opts.depth, 10),
|
|
839
|
+
noTests: resolveNoTests(opts),
|
|
840
|
+
json: opts.json,
|
|
841
|
+
format: opts.format,
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
|
|
636
845
|
program
|
|
637
846
|
.command('watch [dir]')
|
|
638
847
|
.description('Watch project for file changes and incrementally update the graph')
|
|
@@ -668,6 +877,43 @@ program
|
|
|
668
877
|
console.log(` Engine flag : --engine ${engine}`);
|
|
669
878
|
console.log(` Active engine : ${activeName}${activeVersion ? ` (v${activeVersion})` : ''}`);
|
|
670
879
|
console.log();
|
|
880
|
+
|
|
881
|
+
// Build metadata from DB
|
|
882
|
+
try {
|
|
883
|
+
const { findDbPath, getBuildMeta } = await import('./db.js');
|
|
884
|
+
const Database = (await import('better-sqlite3')).default;
|
|
885
|
+
const dbPath = findDbPath();
|
|
886
|
+
const fs = await import('node:fs');
|
|
887
|
+
if (fs.existsSync(dbPath)) {
|
|
888
|
+
const db = new Database(dbPath, { readonly: true });
|
|
889
|
+
const buildEngine = getBuildMeta(db, 'engine');
|
|
890
|
+
const buildVersion = getBuildMeta(db, 'codegraph_version');
|
|
891
|
+
const builtAt = getBuildMeta(db, 'built_at');
|
|
892
|
+
db.close();
|
|
893
|
+
|
|
894
|
+
if (buildEngine || buildVersion || builtAt) {
|
|
895
|
+
console.log('Build metadata');
|
|
896
|
+
console.log('──────────────');
|
|
897
|
+
if (buildEngine) console.log(` Engine : ${buildEngine}`);
|
|
898
|
+
if (buildVersion) console.log(` Version : ${buildVersion}`);
|
|
899
|
+
if (builtAt) console.log(` Built at : ${builtAt}`);
|
|
900
|
+
|
|
901
|
+
if (buildVersion && buildVersion !== program.version()) {
|
|
902
|
+
console.log(
|
|
903
|
+
` ⚠ DB was built with v${buildVersion}, current is v${program.version()}. Consider: codegraph build --no-incremental`,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
if (buildEngine && buildEngine !== activeName) {
|
|
907
|
+
console.log(
|
|
908
|
+
` ⚠ DB was built with ${buildEngine} engine, active is ${activeName}. Consider: codegraph build --no-incremental`,
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
console.log();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
} catch {
|
|
915
|
+
/* diagnostics must never crash */
|
|
916
|
+
}
|
|
671
917
|
});
|
|
672
918
|
|
|
673
919
|
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
|
}
|