@optave/codegraph 2.4.0 → 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 +64 -10
- package/package.json +15 -5
- package/src/builder.js +183 -22
- package/src/cli.js +239 -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 +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
|
|
@@ -470,6 +520,7 @@ program
|
|
|
470
520
|
`Embedding strategy: ${EMBEDDING_STRATEGIES.join(', ')}. "structured" uses graph context (callers/callees), "source" embeds raw code`,
|
|
471
521
|
'structured',
|
|
472
522
|
)
|
|
523
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
473
524
|
.action(async (dir, opts) => {
|
|
474
525
|
if (!EMBEDDING_STRATEGIES.includes(opts.strategy)) {
|
|
475
526
|
console.error(
|
|
@@ -479,7 +530,7 @@ program
|
|
|
479
530
|
}
|
|
480
531
|
const root = path.resolve(dir || '.');
|
|
481
532
|
const model = opts.model || config.embeddings?.model || DEFAULT_MODEL;
|
|
482
|
-
await buildEmbeddings(root, model,
|
|
533
|
+
await buildEmbeddings(root, model, opts.db, { strategy: opts.strategy });
|
|
483
534
|
});
|
|
484
535
|
|
|
485
536
|
program
|
|
@@ -516,6 +567,7 @@ program
|
|
|
516
567
|
.option('-d, --db <path>', 'Path to graph.db')
|
|
517
568
|
.option('--depth <n>', 'Max directory depth')
|
|
518
569
|
.option('--sort <metric>', 'Sort by: cohesion | fan-in | fan-out | density | files', 'files')
|
|
570
|
+
.option('--full', 'Show all files without limit')
|
|
519
571
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
520
572
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
521
573
|
.option('-j, --json', 'Output as JSON')
|
|
@@ -525,6 +577,7 @@ program
|
|
|
525
577
|
directory: dir,
|
|
526
578
|
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
527
579
|
sort: opts.sort,
|
|
580
|
+
full: opts.full,
|
|
528
581
|
noTests: resolveNoTests(opts),
|
|
529
582
|
});
|
|
530
583
|
if (opts.json) {
|
|
@@ -570,6 +623,9 @@ program
|
|
|
570
623
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
571
624
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
572
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')
|
|
573
629
|
.action((opts) => {
|
|
574
630
|
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
575
631
|
console.error(`Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.join(', ')}`);
|
|
@@ -580,6 +636,9 @@ program
|
|
|
580
636
|
file: opts.file,
|
|
581
637
|
noTests: resolveNoTests(opts),
|
|
582
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,
|
|
583
642
|
});
|
|
584
643
|
});
|
|
585
644
|
|
|
@@ -645,6 +704,144 @@ program
|
|
|
645
704
|
}
|
|
646
705
|
});
|
|
647
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
|
+
|
|
648
845
|
program
|
|
649
846
|
.command('watch [dir]')
|
|
650
847
|
.description('Watch project for file changes and incrementally update the graph')
|
|
@@ -680,6 +877,43 @@ program
|
|
|
680
877
|
console.log(` Engine flag : --engine ${engine}`);
|
|
681
878
|
console.log(` Active engine : ${activeName}${activeVersion ? ` (v${activeVersion})` : ''}`);
|
|
682
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
|
+
}
|
|
683
917
|
});
|
|
684
918
|
|
|
685
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
|
}
|