@optave/codegraph 1.1.0 → 1.3.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/LICENSE +190 -190
- package/README.md +480 -311
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-hcl.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +90 -69
- package/src/builder.js +161 -162
- package/src/cli.js +284 -224
- package/src/config.js +61 -55
- package/src/constants.js +41 -28
- package/src/cycles.js +125 -104
- package/src/db.js +129 -117
- package/src/embedder.js +253 -59
- package/src/export.js +150 -138
- package/src/index.js +50 -39
- package/src/logger.js +24 -20
- package/src/mcp.js +145 -139
- package/src/native.js +68 -0
- package/src/parser.js +2214 -573
- package/src/queries.js +304 -128
- package/src/resolve.js +171 -0
- package/src/watcher.js +81 -53
package/src/constants.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'.
|
|
11
|
-
'.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { SUPPORTED_EXTENSIONS } from './parser.js';
|
|
3
|
+
|
|
4
|
+
export const IGNORE_DIRS = new Set([
|
|
5
|
+
'node_modules',
|
|
6
|
+
'.git',
|
|
7
|
+
'dist',
|
|
8
|
+
'build',
|
|
9
|
+
'.next',
|
|
10
|
+
'.nuxt',
|
|
11
|
+
'.svelte-kit',
|
|
12
|
+
'coverage',
|
|
13
|
+
'.codegraph',
|
|
14
|
+
'__pycache__',
|
|
15
|
+
'.tox',
|
|
16
|
+
'vendor',
|
|
17
|
+
'.venv',
|
|
18
|
+
'venv',
|
|
19
|
+
'env',
|
|
20
|
+
'.env',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
// Re-export as an indirect binding to avoid TDZ in the circular
|
|
24
|
+
// parser.js ↔ constants.js import (no value read at evaluation time).
|
|
25
|
+
export { SUPPORTED_EXTENSIONS as EXTENSIONS };
|
|
26
|
+
|
|
27
|
+
export function shouldIgnore(dirName) {
|
|
28
|
+
return IGNORE_DIRS.has(dirName) || dirName.startsWith('.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isSupportedFile(filePath) {
|
|
32
|
+
return SUPPORTED_EXTENSIONS.has(path.extname(filePath));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a file path to always use forward slashes.
|
|
37
|
+
* Ensures cross-platform consistency in the SQLite database.
|
|
38
|
+
*/
|
|
39
|
+
export function normalizePath(filePath) {
|
|
40
|
+
return filePath.split(path.sep).join('/');
|
|
41
|
+
}
|
package/src/cycles.js
CHANGED
|
@@ -1,104 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
}
|
|
1
|
+
import { loadNative } from './native.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detect circular dependencies in the codebase using Tarjan's SCC algorithm.
|
|
5
|
+
* Dispatches to native Rust implementation when available, falls back to JS.
|
|
6
|
+
* @param {object} db - Open SQLite database
|
|
7
|
+
* @param {object} opts - { fileLevel: true }
|
|
8
|
+
* @returns {string[][]} Array of cycles, each cycle is an array of file paths
|
|
9
|
+
*/
|
|
10
|
+
export function findCycles(db, opts = {}) {
|
|
11
|
+
const fileLevel = opts.fileLevel !== false;
|
|
12
|
+
|
|
13
|
+
// Build adjacency list from SQLite (stays in JS — only the algorithm can move to Rust)
|
|
14
|
+
let edges;
|
|
15
|
+
if (fileLevel) {
|
|
16
|
+
edges = db
|
|
17
|
+
.prepare(`
|
|
18
|
+
SELECT DISTINCT n1.file AS source, n2.file AS target
|
|
19
|
+
FROM edges e
|
|
20
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
21
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
22
|
+
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type')
|
|
23
|
+
`)
|
|
24
|
+
.all();
|
|
25
|
+
} else {
|
|
26
|
+
edges = db
|
|
27
|
+
.prepare(`
|
|
28
|
+
SELECT DISTINCT
|
|
29
|
+
(n1.name || '|' || n1.file) AS source,
|
|
30
|
+
(n2.name || '|' || n2.file) AS target
|
|
31
|
+
FROM edges e
|
|
32
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
33
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
34
|
+
WHERE n1.kind IN ('function', 'method', 'class')
|
|
35
|
+
AND n2.kind IN ('function', 'method', 'class')
|
|
36
|
+
AND e.kind = 'calls'
|
|
37
|
+
AND n1.id != n2.id
|
|
38
|
+
`)
|
|
39
|
+
.all();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Try native Rust implementation
|
|
43
|
+
const native = loadNative();
|
|
44
|
+
if (native) {
|
|
45
|
+
return native.detectCycles(edges);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback: JS Tarjan
|
|
49
|
+
return findCyclesJS(edges);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Pure-JS Tarjan's SCC implementation.
|
|
54
|
+
*/
|
|
55
|
+
export function findCyclesJS(edges) {
|
|
56
|
+
const graph = new Map();
|
|
57
|
+
for (const { source, target } of edges) {
|
|
58
|
+
if (!graph.has(source)) graph.set(source, []);
|
|
59
|
+
graph.get(source).push(target);
|
|
60
|
+
if (!graph.has(target)) graph.set(target, []);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Tarjan's strongly connected components algorithm
|
|
64
|
+
let index = 0;
|
|
65
|
+
const stack = [];
|
|
66
|
+
const onStack = new Set();
|
|
67
|
+
const indices = new Map();
|
|
68
|
+
const lowlinks = new Map();
|
|
69
|
+
const sccs = [];
|
|
70
|
+
|
|
71
|
+
function strongconnect(v) {
|
|
72
|
+
indices.set(v, index);
|
|
73
|
+
lowlinks.set(v, index);
|
|
74
|
+
index++;
|
|
75
|
+
stack.push(v);
|
|
76
|
+
onStack.add(v);
|
|
77
|
+
|
|
78
|
+
for (const w of graph.get(v) || []) {
|
|
79
|
+
if (!indices.has(w)) {
|
|
80
|
+
strongconnect(w);
|
|
81
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
82
|
+
} else if (onStack.has(w)) {
|
|
83
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
88
|
+
const scc = [];
|
|
89
|
+
let w;
|
|
90
|
+
do {
|
|
91
|
+
w = stack.pop();
|
|
92
|
+
onStack.delete(w);
|
|
93
|
+
scc.push(w);
|
|
94
|
+
} while (w !== v);
|
|
95
|
+
if (scc.length > 1) sccs.push(scc);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const node of graph.keys()) {
|
|
100
|
+
if (!indices.has(node)) strongconnect(node);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sccs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format cycles for human-readable output.
|
|
108
|
+
*/
|
|
109
|
+
export function formatCycles(cycles) {
|
|
110
|
+
if (cycles.length === 0) {
|
|
111
|
+
return 'No circular dependencies detected.';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lines = [`Found ${cycles.length} circular dependency cycle(s):\n`];
|
|
115
|
+
for (let i = 0; i < cycles.length; i++) {
|
|
116
|
+
const cycle = cycles[i];
|
|
117
|
+
lines.push(` Cycle ${i + 1} (${cycle.length} files):`);
|
|
118
|
+
for (const file of cycle) {
|
|
119
|
+
lines.push(` -> ${file}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push(` -> ${cycle[0]} (back to start)`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
package/src/db.js
CHANGED
|
@@ -1,117 +1,129 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
// ─── Schema Migrations ─────────────────────────────────────────────────
|
|
7
|
-
export const MIGRATIONS = [
|
|
8
|
-
{
|
|
9
|
-
version: 1,
|
|
10
|
-
up: `
|
|
11
|
-
CREATE TABLE IF NOT EXISTS nodes (
|
|
12
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
-
name TEXT NOT NULL,
|
|
14
|
-
kind TEXT NOT NULL,
|
|
15
|
-
file TEXT NOT NULL,
|
|
16
|
-
line INTEGER,
|
|
17
|
-
end_line INTEGER,
|
|
18
|
-
UNIQUE(name, kind, file, line)
|
|
19
|
-
);
|
|
20
|
-
CREATE TABLE IF NOT EXISTS edges (
|
|
21
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
22
|
-
source_id INTEGER NOT NULL,
|
|
23
|
-
target_id INTEGER NOT NULL,
|
|
24
|
-
kind TEXT NOT NULL,
|
|
25
|
-
confidence REAL DEFAULT 1.0,
|
|
26
|
-
dynamic INTEGER DEFAULT 0,
|
|
27
|
-
FOREIGN KEY(source_id) REFERENCES nodes(id),
|
|
28
|
-
FOREIGN KEY(target_id) REFERENCES nodes(id)
|
|
29
|
-
);
|
|
30
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
|
|
31
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file);
|
|
32
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
|
|
33
|
-
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
34
|
-
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
35
|
-
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
version: 2,
|
|
40
|
-
up: `
|
|
41
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_name_kind_file ON nodes(name, kind, file);
|
|
42
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_file_kind ON nodes(file, kind);
|
|
43
|
-
CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source_id, kind);
|
|
44
|
-
CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target_id, kind);
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
version: 3,
|
|
49
|
-
up: `
|
|
50
|
-
CREATE TABLE IF NOT EXISTS file_hashes (
|
|
51
|
-
file TEXT PRIMARY KEY,
|
|
52
|
-
hash TEXT NOT NULL,
|
|
53
|
-
mtime INTEGER NOT NULL
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
export function openDb(dbPath) {
|
|
60
|
-
const dir = path.dirname(dbPath);
|
|
61
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
62
|
-
const db = new Database(dbPath);
|
|
63
|
-
db.pragma('journal_mode = WAL');
|
|
64
|
-
return db;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function initSchema(db) {
|
|
68
|
-
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`);
|
|
69
|
-
|
|
70
|
-
const row = db.prepare('SELECT version FROM schema_version').get();
|
|
71
|
-
let currentVersion = row ? row.version : 0;
|
|
72
|
-
|
|
73
|
-
if (!row) {
|
|
74
|
-
db.prepare('INSERT INTO schema_version (version) VALUES (0)').run();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (const migration of MIGRATIONS) {
|
|
78
|
-
if (migration.version > currentVersion) {
|
|
79
|
-
debug(`Running migration v${migration.version}`);
|
|
80
|
-
db.exec(migration.up);
|
|
81
|
-
db.prepare('UPDATE schema_version SET version = ?').run(migration.version);
|
|
82
|
-
currentVersion = migration.version;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import { debug } from './logger.js';
|
|
5
|
+
|
|
6
|
+
// ─── Schema Migrations ─────────────────────────────────────────────────
|
|
7
|
+
export const MIGRATIONS = [
|
|
8
|
+
{
|
|
9
|
+
version: 1,
|
|
10
|
+
up: `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
name TEXT NOT NULL,
|
|
14
|
+
kind TEXT NOT NULL,
|
|
15
|
+
file TEXT NOT NULL,
|
|
16
|
+
line INTEGER,
|
|
17
|
+
end_line INTEGER,
|
|
18
|
+
UNIQUE(name, kind, file, line)
|
|
19
|
+
);
|
|
20
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
21
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
22
|
+
source_id INTEGER NOT NULL,
|
|
23
|
+
target_id INTEGER NOT NULL,
|
|
24
|
+
kind TEXT NOT NULL,
|
|
25
|
+
confidence REAL DEFAULT 1.0,
|
|
26
|
+
dynamic INTEGER DEFAULT 0,
|
|
27
|
+
FOREIGN KEY(source_id) REFERENCES nodes(id),
|
|
28
|
+
FOREIGN KEY(target_id) REFERENCES nodes(id)
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
|
|
36
|
+
`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
version: 2,
|
|
40
|
+
up: `
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_name_kind_file ON nodes(name, kind, file);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file_kind ON nodes(file, kind);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source_id, kind);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target_id, kind);
|
|
45
|
+
`,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
version: 3,
|
|
49
|
+
up: `
|
|
50
|
+
CREATE TABLE IF NOT EXISTS file_hashes (
|
|
51
|
+
file TEXT PRIMARY KEY,
|
|
52
|
+
hash TEXT NOT NULL,
|
|
53
|
+
mtime INTEGER NOT NULL
|
|
54
|
+
);
|
|
55
|
+
`,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export function openDb(dbPath) {
|
|
60
|
+
const dir = path.dirname(dbPath);
|
|
61
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
const db = new Database(dbPath);
|
|
63
|
+
db.pragma('journal_mode = WAL');
|
|
64
|
+
return db;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function initSchema(db) {
|
|
68
|
+
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`);
|
|
69
|
+
|
|
70
|
+
const row = db.prepare('SELECT version FROM schema_version').get();
|
|
71
|
+
let currentVersion = row ? row.version : 0;
|
|
72
|
+
|
|
73
|
+
if (!row) {
|
|
74
|
+
db.prepare('INSERT INTO schema_version (version) VALUES (0)').run();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const migration of MIGRATIONS) {
|
|
78
|
+
if (migration.version > currentVersion) {
|
|
79
|
+
debug(`Running migration v${migration.version}`);
|
|
80
|
+
db.exec(migration.up);
|
|
81
|
+
db.prepare('UPDATE schema_version SET version = ?').run(migration.version);
|
|
82
|
+
currentVersion = migration.version;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
db.exec('ALTER TABLE nodes ADD COLUMN end_line INTEGER');
|
|
88
|
+
} catch {
|
|
89
|
+
/* already exists */
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
db.exec('ALTER TABLE edges ADD COLUMN confidence REAL DEFAULT 1.0');
|
|
93
|
+
} catch {
|
|
94
|
+
/* already exists */
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
db.exec('ALTER TABLE edges ADD COLUMN dynamic INTEGER DEFAULT 0');
|
|
98
|
+
} catch {
|
|
99
|
+
/* already exists */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function findDbPath(customPath) {
|
|
104
|
+
if (customPath) return path.resolve(customPath);
|
|
105
|
+
let dir = process.cwd();
|
|
106
|
+
while (true) {
|
|
107
|
+
const candidate = path.join(dir, '.codegraph', 'graph.db');
|
|
108
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
109
|
+
const parent = path.dirname(dir);
|
|
110
|
+
if (parent === dir) break;
|
|
111
|
+
dir = parent;
|
|
112
|
+
}
|
|
113
|
+
return path.join(process.cwd(), '.codegraph', 'graph.db');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Open a database in readonly mode, with a user-friendly error if the DB doesn't exist.
|
|
118
|
+
*/
|
|
119
|
+
export function openReadonlyOrFail(customPath) {
|
|
120
|
+
const dbPath = findDbPath(customPath);
|
|
121
|
+
if (!fs.existsSync(dbPath)) {
|
|
122
|
+
console.error(
|
|
123
|
+
`No codegraph database found at ${dbPath}.\n` +
|
|
124
|
+
`Run "codegraph build" first to analyze your codebase.`,
|
|
125
|
+
);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
return new Database(dbPath, { readonly: true });
|
|
129
|
+
}
|