@optave/codegraph 3.1.0 → 3.1.2
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/engine.js +365 -0
- package/src/ast-analysis/metrics.js +118 -0
- 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-analysis/visitor-utils.js +176 -0
- package/src/ast-analysis/visitor.js +162 -0
- package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
- package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
- package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
- package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
- package/src/ast.js +26 -166
- package/src/audit.js +2 -88
- package/src/batch.js +0 -25
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +82 -172
- package/src/builder.js +48 -184
- package/src/cfg.js +148 -1174
- package/src/check.js +1 -84
- package/src/cli.js +118 -197
- package/src/cochange.js +1 -39
- package/src/commands/audit.js +88 -0
- package/src/commands/batch.js +26 -0
- package/src/commands/branch-compare.js +97 -0
- package/src/commands/cfg.js +55 -0
- package/src/commands/check.js +82 -0
- package/src/commands/cochange.js +37 -0
- package/src/commands/communities.js +69 -0
- package/src/commands/complexity.js +77 -0
- package/src/commands/dataflow.js +110 -0
- package/src/commands/flow.js +70 -0
- package/src/commands/manifesto.js +77 -0
- package/src/commands/owners.js +52 -0
- package/src/commands/query.js +21 -0
- package/src/commands/sequence.js +33 -0
- package/src/commands/structure.js +64 -0
- package/src/commands/triage.js +49 -0
- package/src/communities.js +22 -96
- package/src/complexity.js +234 -1591
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -1352
- 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/build-stmts.js +104 -0
- package/src/db/repository/cfg.js +83 -0
- package/src/db/repository/cochange.js +41 -0
- package/src/db/repository/complexity.js +15 -0
- package/src/db/repository/dataflow.js +12 -0
- package/src/db/repository/edges.js +259 -0
- package/src/db/repository/embeddings.js +40 -0
- package/src/db/repository/graph-read.js +39 -0
- package/src/db/repository/index.js +42 -0
- package/src/db/repository/nodes.js +236 -0
- package/src/db.js +58 -399
- package/src/embedder.js +158 -174
- package/src/export.js +1 -1
- package/src/extractors/javascript.js +130 -5
- package/src/flow.js +153 -222
- package/src/index.js +53 -16
- package/src/infrastructure/result-formatter.js +21 -0
- package/src/infrastructure/test-filter.js +7 -0
- package/src/kinds.js +50 -0
- package/src/manifesto.js +1 -82
- package/src/mcp.js +37 -20
- package/src/owners.js +127 -182
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1271 -2416
- package/src/sequence.js +179 -223
- package/src/structure.js +211 -269
- package/src/triage.js +117 -212
- package/src/viewer.js +1 -1
- package/src/watcher.js +7 -4
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../kinds.js';
|
|
2
|
+
import { NodeQuery } from '../query-builder.js';
|
|
3
|
+
|
|
4
|
+
// ─── Query-builder based lookups (moved from src/db/repository.js) ─────
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find nodes matching a name pattern, with fan-in count.
|
|
8
|
+
* @param {object} db
|
|
9
|
+
* @param {string} namePattern - LIKE pattern (already wrapped with %)
|
|
10
|
+
* @param {object} [opts]
|
|
11
|
+
* @param {string[]} [opts.kinds]
|
|
12
|
+
* @param {string} [opts.file]
|
|
13
|
+
* @returns {object[]}
|
|
14
|
+
*/
|
|
15
|
+
export function findNodesWithFanIn(db, namePattern, opts = {}) {
|
|
16
|
+
const q = new NodeQuery()
|
|
17
|
+
.select('n.*, COALESCE(fi.cnt, 0) AS fan_in')
|
|
18
|
+
.withFanIn()
|
|
19
|
+
.where('n.name LIKE ?', namePattern);
|
|
20
|
+
|
|
21
|
+
if (opts.kinds) {
|
|
22
|
+
q.kinds(opts.kinds);
|
|
23
|
+
}
|
|
24
|
+
if (opts.file) {
|
|
25
|
+
q.fileFilter(opts.file);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return q.all(db);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetch nodes for triage scoring: fan-in + complexity + churn.
|
|
33
|
+
* @param {object} db
|
|
34
|
+
* @param {object} [opts]
|
|
35
|
+
* @returns {object[]}
|
|
36
|
+
*/
|
|
37
|
+
export function findNodesForTriage(db, opts = {}) {
|
|
38
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
39
|
+
throw new Error(`Invalid kind: ${opts.kind} (expected one of ${EVERY_SYMBOL_KIND.join(', ')})`);
|
|
40
|
+
}
|
|
41
|
+
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
42
|
+
throw new Error(`Invalid role: ${opts.role} (expected one of ${VALID_ROLES.join(', ')})`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const kindsToUse = opts.kind ? [opts.kind] : ['function', 'method', 'class'];
|
|
46
|
+
const q = new NodeQuery()
|
|
47
|
+
.select(
|
|
48
|
+
`n.id, n.name, n.kind, n.file, n.line, n.end_line, n.role,
|
|
49
|
+
COALESCE(fi.cnt, 0) AS fan_in,
|
|
50
|
+
COALESCE(fc.cognitive, 0) AS cognitive,
|
|
51
|
+
COALESCE(fc.maintainability_index, 0) AS mi,
|
|
52
|
+
COALESCE(fc.cyclomatic, 0) AS cyclomatic,
|
|
53
|
+
COALESCE(fc.max_nesting, 0) AS max_nesting,
|
|
54
|
+
COALESCE(fcc.commit_count, 0) AS churn`,
|
|
55
|
+
)
|
|
56
|
+
.kinds(kindsToUse)
|
|
57
|
+
.withFanIn()
|
|
58
|
+
.withComplexity()
|
|
59
|
+
.withChurn()
|
|
60
|
+
.excludeTests(opts.noTests)
|
|
61
|
+
.fileFilter(opts.file)
|
|
62
|
+
.roleFilter(opts.role)
|
|
63
|
+
.orderBy('n.file, n.line');
|
|
64
|
+
|
|
65
|
+
return q.all(db);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shared query builder for function/method/class node listing.
|
|
70
|
+
* @param {object} [opts]
|
|
71
|
+
* @returns {NodeQuery}
|
|
72
|
+
*/
|
|
73
|
+
function _functionNodeQuery(opts = {}) {
|
|
74
|
+
return new NodeQuery()
|
|
75
|
+
.select('name, kind, file, line, end_line, role')
|
|
76
|
+
.kinds(['function', 'method', 'class'])
|
|
77
|
+
.fileFilter(opts.file)
|
|
78
|
+
.nameLike(opts.pattern)
|
|
79
|
+
.excludeTests(opts.noTests)
|
|
80
|
+
.orderBy('file, line');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List function/method/class nodes with basic info.
|
|
85
|
+
* @param {object} db
|
|
86
|
+
* @param {object} [opts]
|
|
87
|
+
* @returns {object[]}
|
|
88
|
+
*/
|
|
89
|
+
export function listFunctionNodes(db, opts = {}) {
|
|
90
|
+
return _functionNodeQuery(opts).all(db);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Iterator version of listFunctionNodes for memory efficiency.
|
|
95
|
+
* @param {object} db
|
|
96
|
+
* @param {object} [opts]
|
|
97
|
+
* @returns {IterableIterator}
|
|
98
|
+
*/
|
|
99
|
+
export function iterateFunctionNodes(db, opts = {}) {
|
|
100
|
+
return _functionNodeQuery(opts).iterate(db);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Count total nodes.
|
|
105
|
+
* @param {object} db
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
export function countNodes(db) {
|
|
109
|
+
return db.prepare('SELECT COUNT(*) AS cnt FROM nodes').get().cnt;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Count total edges.
|
|
114
|
+
* @param {object} db
|
|
115
|
+
* @returns {number}
|
|
116
|
+
*/
|
|
117
|
+
export function countEdges(db) {
|
|
118
|
+
return db.prepare('SELECT COUNT(*) AS cnt FROM edges').get().cnt;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Count distinct files.
|
|
123
|
+
* @param {object} db
|
|
124
|
+
* @returns {number}
|
|
125
|
+
*/
|
|
126
|
+
export function countFiles(db) {
|
|
127
|
+
return db.prepare('SELECT COUNT(DISTINCT file) AS cnt FROM nodes').get().cnt;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Shared node lookups ───────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Find a single node by ID.
|
|
134
|
+
* @param {object} db
|
|
135
|
+
* @param {number} id
|
|
136
|
+
* @returns {object|undefined}
|
|
137
|
+
*/
|
|
138
|
+
export function findNodeById(db, id) {
|
|
139
|
+
return db.prepare('SELECT * FROM nodes WHERE id = ?').get(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Find non-file nodes for a given file path (exact match), ordered by line.
|
|
144
|
+
* @param {object} db
|
|
145
|
+
* @param {string} file - Exact file path
|
|
146
|
+
* @returns {object[]}
|
|
147
|
+
*/
|
|
148
|
+
export function findNodesByFile(db, file) {
|
|
149
|
+
return db
|
|
150
|
+
.prepare("SELECT * FROM nodes WHERE file = ? AND kind != 'file' ORDER BY line")
|
|
151
|
+
.all(file);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find file-kind nodes matching a LIKE pattern.
|
|
156
|
+
* @param {object} db
|
|
157
|
+
* @param {string} fileLike - LIKE pattern (caller wraps with %)
|
|
158
|
+
* @returns {object[]}
|
|
159
|
+
*/
|
|
160
|
+
export function findFileNodes(db, fileLike) {
|
|
161
|
+
return db.prepare("SELECT * FROM nodes WHERE file LIKE ? AND kind = 'file'").all(fileLike);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── Statement caches (one prepared statement per db instance) ────────────
|
|
165
|
+
// WeakMap keys on the db object so statements are GC'd when the db closes.
|
|
166
|
+
const _getNodeIdStmt = new WeakMap();
|
|
167
|
+
const _getFunctionNodeIdStmt = new WeakMap();
|
|
168
|
+
const _bulkNodeIdsByFileStmt = new WeakMap();
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Look up a node's ID by its unique (name, kind, file, line) tuple.
|
|
172
|
+
* Shared by builder, watcher, structure, complexity, cfg, engine.
|
|
173
|
+
* @param {object} db
|
|
174
|
+
* @param {string} name
|
|
175
|
+
* @param {string} kind
|
|
176
|
+
* @param {string} file
|
|
177
|
+
* @param {number} line
|
|
178
|
+
* @returns {number|undefined}
|
|
179
|
+
*/
|
|
180
|
+
export function getNodeId(db, name, kind, file, line) {
|
|
181
|
+
let stmt = _getNodeIdStmt.get(db);
|
|
182
|
+
if (!stmt) {
|
|
183
|
+
stmt = db.prepare('SELECT id FROM nodes WHERE name = ? AND kind = ? AND file = ? AND line = ?');
|
|
184
|
+
_getNodeIdStmt.set(db, stmt);
|
|
185
|
+
}
|
|
186
|
+
return stmt.get(name, kind, file, line)?.id;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Look up a function/method node's ID (kind-restricted variant of getNodeId).
|
|
191
|
+
* Used by complexity.js, cfg.js where only function/method kinds are expected.
|
|
192
|
+
* @param {object} db
|
|
193
|
+
* @param {string} name
|
|
194
|
+
* @param {string} file
|
|
195
|
+
* @param {number} line
|
|
196
|
+
* @returns {number|undefined}
|
|
197
|
+
*/
|
|
198
|
+
export function getFunctionNodeId(db, name, file, line) {
|
|
199
|
+
let stmt = _getFunctionNodeIdStmt.get(db);
|
|
200
|
+
if (!stmt) {
|
|
201
|
+
stmt = db.prepare(
|
|
202
|
+
"SELECT id FROM nodes WHERE name = ? AND kind IN ('function','method') AND file = ? AND line = ?",
|
|
203
|
+
);
|
|
204
|
+
_getFunctionNodeIdStmt.set(db, stmt);
|
|
205
|
+
}
|
|
206
|
+
return stmt.get(name, file, line)?.id;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Bulk-fetch all node IDs for a file in one query.
|
|
211
|
+
* Returns rows suitable for building a `name|kind|line -> id` lookup map.
|
|
212
|
+
* Shared by builder, ast.js, ast-analysis/engine.js.
|
|
213
|
+
* @param {object} db
|
|
214
|
+
* @param {string} file
|
|
215
|
+
* @returns {{ id: number, name: string, kind: string, line: number }[]}
|
|
216
|
+
*/
|
|
217
|
+
export function bulkNodeIdsByFile(db, file) {
|
|
218
|
+
let stmt = _bulkNodeIdsByFileStmt.get(db);
|
|
219
|
+
if (!stmt) {
|
|
220
|
+
stmt = db.prepare('SELECT id, name, kind, line FROM nodes WHERE file = ?');
|
|
221
|
+
_bulkNodeIdsByFileStmt.set(db, stmt);
|
|
222
|
+
}
|
|
223
|
+
return stmt.all(file);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Find child nodes (parameters, properties, constants) of a parent.
|
|
228
|
+
* @param {object} db
|
|
229
|
+
* @param {number} parentId
|
|
230
|
+
* @returns {{ name: string, kind: string, line: number, end_line: number|null }[]}
|
|
231
|
+
*/
|
|
232
|
+
export function findNodeChildren(db, parentId) {
|
|
233
|
+
return db
|
|
234
|
+
.prepare('SELECT name, kind, line, end_line FROM nodes WHERE parent_id = ? ORDER BY line')
|
|
235
|
+
.all(parentId);
|
|
236
|
+
}
|
package/src/db.js
CHANGED
|
@@ -1,399 +1,58 @@
|
|
|
1
|
-
import
|
|
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
|
-
version: 3,
|
|
62
|
-
up: `
|
|
63
|
-
CREATE TABLE IF NOT EXISTS file_hashes (
|
|
64
|
-
file TEXT PRIMARY KEY,
|
|
65
|
-
hash TEXT NOT NULL,
|
|
66
|
-
mtime INTEGER NOT NULL
|
|
67
|
-
);
|
|
68
|
-
`,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
version: 4,
|
|
72
|
-
up: `ALTER TABLE file_hashes ADD COLUMN size INTEGER DEFAULT 0;`,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
version: 5,
|
|
76
|
-
up: `
|
|
77
|
-
CREATE TABLE IF NOT EXISTS co_changes (
|
|
78
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
-
file_a TEXT NOT NULL,
|
|
80
|
-
file_b TEXT NOT NULL,
|
|
81
|
-
commit_count INTEGER NOT NULL,
|
|
82
|
-
jaccard REAL NOT NULL,
|
|
83
|
-
last_commit_epoch INTEGER,
|
|
84
|
-
UNIQUE(file_a, file_b)
|
|
85
|
-
);
|
|
86
|
-
CREATE INDEX IF NOT EXISTS idx_co_changes_file_a ON co_changes(file_a);
|
|
87
|
-
CREATE INDEX IF NOT EXISTS idx_co_changes_file_b ON co_changes(file_b);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_co_changes_jaccard ON co_changes(jaccard DESC);
|
|
89
|
-
CREATE TABLE IF NOT EXISTS co_change_meta (
|
|
90
|
-
key TEXT PRIMARY KEY,
|
|
91
|
-
value TEXT NOT NULL
|
|
92
|
-
);
|
|
93
|
-
`,
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
version: 6,
|
|
97
|
-
up: `
|
|
98
|
-
CREATE TABLE IF NOT EXISTS file_commit_counts (
|
|
99
|
-
file TEXT PRIMARY KEY,
|
|
100
|
-
commit_count INTEGER NOT NULL DEFAULT 0
|
|
101
|
-
);
|
|
102
|
-
`,
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
version: 7,
|
|
106
|
-
up: `
|
|
107
|
-
CREATE TABLE IF NOT EXISTS build_meta (
|
|
108
|
-
key TEXT PRIMARY KEY,
|
|
109
|
-
value TEXT NOT NULL
|
|
110
|
-
);
|
|
111
|
-
`,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
version: 8,
|
|
115
|
-
up: `
|
|
116
|
-
CREATE TABLE IF NOT EXISTS function_complexity (
|
|
117
|
-
node_id INTEGER PRIMARY KEY,
|
|
118
|
-
cognitive INTEGER NOT NULL,
|
|
119
|
-
cyclomatic INTEGER NOT NULL,
|
|
120
|
-
max_nesting INTEGER NOT NULL,
|
|
121
|
-
FOREIGN KEY(node_id) REFERENCES nodes(id)
|
|
122
|
-
);
|
|
123
|
-
CREATE INDEX IF NOT EXISTS idx_fc_cognitive ON function_complexity(cognitive DESC);
|
|
124
|
-
CREATE INDEX IF NOT EXISTS idx_fc_cyclomatic ON function_complexity(cyclomatic DESC);
|
|
125
|
-
`,
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
version: 9,
|
|
129
|
-
up: `
|
|
130
|
-
ALTER TABLE function_complexity ADD COLUMN loc INTEGER DEFAULT 0;
|
|
131
|
-
ALTER TABLE function_complexity ADD COLUMN sloc INTEGER DEFAULT 0;
|
|
132
|
-
ALTER TABLE function_complexity ADD COLUMN comment_lines INTEGER DEFAULT 0;
|
|
133
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_n1 INTEGER DEFAULT 0;
|
|
134
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_n2 INTEGER DEFAULT 0;
|
|
135
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_big_n1 INTEGER DEFAULT 0;
|
|
136
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_big_n2 INTEGER DEFAULT 0;
|
|
137
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_vocabulary INTEGER DEFAULT 0;
|
|
138
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_length INTEGER DEFAULT 0;
|
|
139
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_volume REAL DEFAULT 0;
|
|
140
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_difficulty REAL DEFAULT 0;
|
|
141
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_effort REAL DEFAULT 0;
|
|
142
|
-
ALTER TABLE function_complexity ADD COLUMN halstead_bugs REAL DEFAULT 0;
|
|
143
|
-
ALTER TABLE function_complexity ADD COLUMN maintainability_index REAL DEFAULT 0;
|
|
144
|
-
CREATE INDEX IF NOT EXISTS idx_fc_mi ON function_complexity(maintainability_index ASC);
|
|
145
|
-
`,
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
version: 10,
|
|
149
|
-
up: `
|
|
150
|
-
CREATE TABLE IF NOT EXISTS dataflow (
|
|
151
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
152
|
-
source_id INTEGER NOT NULL,
|
|
153
|
-
target_id INTEGER NOT NULL,
|
|
154
|
-
kind TEXT NOT NULL,
|
|
155
|
-
param_index INTEGER,
|
|
156
|
-
expression TEXT,
|
|
157
|
-
line INTEGER,
|
|
158
|
-
confidence REAL DEFAULT 1.0,
|
|
159
|
-
FOREIGN KEY(source_id) REFERENCES nodes(id),
|
|
160
|
-
FOREIGN KEY(target_id) REFERENCES nodes(id)
|
|
161
|
-
);
|
|
162
|
-
CREATE INDEX IF NOT EXISTS idx_dataflow_source ON dataflow(source_id);
|
|
163
|
-
CREATE INDEX IF NOT EXISTS idx_dataflow_target ON dataflow(target_id);
|
|
164
|
-
CREATE INDEX IF NOT EXISTS idx_dataflow_kind ON dataflow(kind);
|
|
165
|
-
CREATE INDEX IF NOT EXISTS idx_dataflow_source_kind ON dataflow(source_id, kind);
|
|
166
|
-
`,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
version: 11,
|
|
170
|
-
up: `
|
|
171
|
-
ALTER TABLE nodes ADD COLUMN parent_id INTEGER REFERENCES nodes(id);
|
|
172
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent_id);
|
|
173
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_kind_parent ON nodes(kind, parent_id);
|
|
174
|
-
`,
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
version: 12,
|
|
178
|
-
up: `
|
|
179
|
-
CREATE TABLE IF NOT EXISTS cfg_blocks (
|
|
180
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
181
|
-
function_node_id INTEGER NOT NULL,
|
|
182
|
-
block_index INTEGER NOT NULL,
|
|
183
|
-
block_type TEXT NOT NULL,
|
|
184
|
-
start_line INTEGER,
|
|
185
|
-
end_line INTEGER,
|
|
186
|
-
label TEXT,
|
|
187
|
-
FOREIGN KEY(function_node_id) REFERENCES nodes(id),
|
|
188
|
-
UNIQUE(function_node_id, block_index)
|
|
189
|
-
);
|
|
190
|
-
CREATE INDEX IF NOT EXISTS idx_cfg_blocks_fn ON cfg_blocks(function_node_id);
|
|
191
|
-
|
|
192
|
-
CREATE TABLE IF NOT EXISTS cfg_edges (
|
|
193
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
194
|
-
function_node_id INTEGER NOT NULL,
|
|
195
|
-
source_block_id INTEGER NOT NULL,
|
|
196
|
-
target_block_id INTEGER NOT NULL,
|
|
197
|
-
kind TEXT NOT NULL,
|
|
198
|
-
FOREIGN KEY(function_node_id) REFERENCES nodes(id),
|
|
199
|
-
FOREIGN KEY(source_block_id) REFERENCES cfg_blocks(id),
|
|
200
|
-
FOREIGN KEY(target_block_id) REFERENCES cfg_blocks(id)
|
|
201
|
-
);
|
|
202
|
-
CREATE INDEX IF NOT EXISTS idx_cfg_edges_fn ON cfg_edges(function_node_id);
|
|
203
|
-
CREATE INDEX IF NOT EXISTS idx_cfg_edges_src ON cfg_edges(source_block_id);
|
|
204
|
-
CREATE INDEX IF NOT EXISTS idx_cfg_edges_tgt ON cfg_edges(target_block_id);
|
|
205
|
-
`,
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
version: 13,
|
|
209
|
-
up: `
|
|
210
|
-
CREATE TABLE IF NOT EXISTS ast_nodes (
|
|
211
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
212
|
-
file TEXT NOT NULL,
|
|
213
|
-
line INTEGER NOT NULL,
|
|
214
|
-
kind TEXT NOT NULL,
|
|
215
|
-
name TEXT NOT NULL,
|
|
216
|
-
text TEXT,
|
|
217
|
-
receiver TEXT,
|
|
218
|
-
parent_node_id INTEGER,
|
|
219
|
-
FOREIGN KEY(parent_node_id) REFERENCES nodes(id)
|
|
220
|
-
);
|
|
221
|
-
CREATE INDEX IF NOT EXISTS idx_ast_kind ON ast_nodes(kind);
|
|
222
|
-
CREATE INDEX IF NOT EXISTS idx_ast_name ON ast_nodes(name);
|
|
223
|
-
CREATE INDEX IF NOT EXISTS idx_ast_file ON ast_nodes(file);
|
|
224
|
-
CREATE INDEX IF NOT EXISTS idx_ast_parent ON ast_nodes(parent_node_id);
|
|
225
|
-
CREATE INDEX IF NOT EXISTS idx_ast_kind_name ON ast_nodes(kind, name);
|
|
226
|
-
`,
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
version: 14,
|
|
230
|
-
up: `
|
|
231
|
-
ALTER TABLE nodes ADD COLUMN exported INTEGER DEFAULT 0;
|
|
232
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_exported ON nodes(exported);
|
|
233
|
-
`,
|
|
234
|
-
},
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
export function getBuildMeta(db, key) {
|
|
238
|
-
try {
|
|
239
|
-
const row = db.prepare('SELECT value FROM build_meta WHERE key = ?').get(key);
|
|
240
|
-
return row ? row.value : null;
|
|
241
|
-
} catch {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export function setBuildMeta(db, entries) {
|
|
247
|
-
const upsert = db.prepare('INSERT OR REPLACE INTO build_meta (key, value) VALUES (?, ?)');
|
|
248
|
-
const tx = db.transaction(() => {
|
|
249
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
250
|
-
upsert.run(key, String(value));
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
tx();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function openDb(dbPath) {
|
|
257
|
-
const dir = path.dirname(dbPath);
|
|
258
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
259
|
-
acquireAdvisoryLock(dbPath);
|
|
260
|
-
const db = new Database(dbPath);
|
|
261
|
-
db.pragma('journal_mode = WAL');
|
|
262
|
-
db.pragma('busy_timeout = 5000');
|
|
263
|
-
db.__lockPath = `${dbPath}.lock`;
|
|
264
|
-
return db;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function closeDb(db) {
|
|
268
|
-
db.close();
|
|
269
|
-
if (db.__lockPath) releaseAdvisoryLock(db.__lockPath);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function isProcessAlive(pid) {
|
|
273
|
-
try {
|
|
274
|
-
process.kill(pid, 0);
|
|
275
|
-
return true;
|
|
276
|
-
} catch {
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function acquireAdvisoryLock(dbPath) {
|
|
282
|
-
const lockPath = `${dbPath}.lock`;
|
|
283
|
-
try {
|
|
284
|
-
if (fs.existsSync(lockPath)) {
|
|
285
|
-
const content = fs.readFileSync(lockPath, 'utf-8').trim();
|
|
286
|
-
const pid = Number(content);
|
|
287
|
-
if (pid && pid !== process.pid && isProcessAlive(pid)) {
|
|
288
|
-
warn(`Another process (PID ${pid}) may be using this database. Proceeding with caution.`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} catch {
|
|
292
|
-
/* ignore read errors */
|
|
293
|
-
}
|
|
294
|
-
try {
|
|
295
|
-
fs.writeFileSync(lockPath, String(process.pid), 'utf-8');
|
|
296
|
-
} catch {
|
|
297
|
-
/* best-effort */
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function releaseAdvisoryLock(lockPath) {
|
|
302
|
-
try {
|
|
303
|
-
const content = fs.readFileSync(lockPath, 'utf-8').trim();
|
|
304
|
-
if (Number(content) === process.pid) {
|
|
305
|
-
fs.unlinkSync(lockPath);
|
|
306
|
-
}
|
|
307
|
-
} catch {
|
|
308
|
-
/* ignore */
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export function initSchema(db) {
|
|
313
|
-
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`);
|
|
314
|
-
|
|
315
|
-
const row = db.prepare('SELECT version FROM schema_version').get();
|
|
316
|
-
let currentVersion = row ? row.version : 0;
|
|
317
|
-
|
|
318
|
-
if (!row) {
|
|
319
|
-
db.prepare('INSERT INTO schema_version (version) VALUES (0)').run();
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
for (const migration of MIGRATIONS) {
|
|
323
|
-
if (migration.version > currentVersion) {
|
|
324
|
-
debug(`Running migration v${migration.version}`);
|
|
325
|
-
db.exec(migration.up);
|
|
326
|
-
db.prepare('UPDATE schema_version SET version = ?').run(migration.version);
|
|
327
|
-
currentVersion = migration.version;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
try {
|
|
332
|
-
db.exec('ALTER TABLE nodes ADD COLUMN end_line INTEGER');
|
|
333
|
-
} catch {
|
|
334
|
-
/* already exists */
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
db.exec('ALTER TABLE edges ADD COLUMN confidence REAL DEFAULT 1.0');
|
|
338
|
-
} catch {
|
|
339
|
-
/* already exists */
|
|
340
|
-
}
|
|
341
|
-
try {
|
|
342
|
-
db.exec('ALTER TABLE edges ADD COLUMN dynamic INTEGER DEFAULT 0');
|
|
343
|
-
} catch {
|
|
344
|
-
/* already exists */
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
db.exec('ALTER TABLE nodes ADD COLUMN role TEXT');
|
|
348
|
-
} catch {
|
|
349
|
-
/* already exists */
|
|
350
|
-
}
|
|
351
|
-
try {
|
|
352
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_role ON nodes(role)');
|
|
353
|
-
} catch {
|
|
354
|
-
/* already exists */
|
|
355
|
-
}
|
|
356
|
-
try {
|
|
357
|
-
db.exec('ALTER TABLE nodes ADD COLUMN parent_id INTEGER REFERENCES nodes(id)');
|
|
358
|
-
} catch {
|
|
359
|
-
/* already exists */
|
|
360
|
-
}
|
|
361
|
-
try {
|
|
362
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent_id)');
|
|
363
|
-
} catch {
|
|
364
|
-
/* already exists */
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_kind_parent ON nodes(kind, parent_id)');
|
|
368
|
-
} catch {
|
|
369
|
-
/* already exists */
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function findDbPath(customPath) {
|
|
374
|
-
if (customPath) return path.resolve(customPath);
|
|
375
|
-
let dir = process.cwd();
|
|
376
|
-
while (true) {
|
|
377
|
-
const candidate = path.join(dir, '.codegraph', 'graph.db');
|
|
378
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
379
|
-
const parent = path.dirname(dir);
|
|
380
|
-
if (parent === dir) break;
|
|
381
|
-
dir = parent;
|
|
382
|
-
}
|
|
383
|
-
return path.join(process.cwd(), '.codegraph', 'graph.db');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Open a database in readonly mode, with a user-friendly error if the DB doesn't exist.
|
|
388
|
-
*/
|
|
389
|
-
export function openReadonlyOrFail(customPath) {
|
|
390
|
-
const dbPath = findDbPath(customPath);
|
|
391
|
-
if (!fs.existsSync(dbPath)) {
|
|
392
|
-
console.error(
|
|
393
|
-
`No codegraph database found at ${dbPath}.\n` +
|
|
394
|
-
`Run "codegraph build" first to analyze your codebase.`,
|
|
395
|
-
);
|
|
396
|
-
process.exit(1);
|
|
397
|
-
}
|
|
398
|
-
return new Database(dbPath, { readonly: true });
|
|
399
|
-
}
|
|
1
|
+
// Barrel re-export — keeps all existing `import { ... } from './db.js'` working.
|
|
2
|
+
export { closeDb, findDbPath, openDb, openReadonlyOrFail } from './db/connection.js';
|
|
3
|
+
export { getBuildMeta, initSchema, MIGRATIONS, setBuildMeta } from './db/migrations.js';
|
|
4
|
+
export {
|
|
5
|
+
fanInJoinSQL,
|
|
6
|
+
fanOutJoinSQL,
|
|
7
|
+
kindInClause,
|
|
8
|
+
NodeQuery,
|
|
9
|
+
testFilterSQL,
|
|
10
|
+
} from './db/query-builder.js';
|
|
11
|
+
export {
|
|
12
|
+
bulkNodeIdsByFile,
|
|
13
|
+
countCrossFileCallers,
|
|
14
|
+
countEdges,
|
|
15
|
+
countFiles,
|
|
16
|
+
countNodes,
|
|
17
|
+
deleteCfgForNode,
|
|
18
|
+
findAllIncomingEdges,
|
|
19
|
+
findAllOutgoingEdges,
|
|
20
|
+
findCalleeNames,
|
|
21
|
+
findCallees,
|
|
22
|
+
findCallerNames,
|
|
23
|
+
findCallers,
|
|
24
|
+
findCrossFileCallTargets,
|
|
25
|
+
findDistinctCallers,
|
|
26
|
+
findFileNodes,
|
|
27
|
+
findImportDependents,
|
|
28
|
+
findImportSources,
|
|
29
|
+
findImportTargets,
|
|
30
|
+
findIntraFileCallEdges,
|
|
31
|
+
findNodeById,
|
|
32
|
+
findNodeChildren,
|
|
33
|
+
findNodesByFile,
|
|
34
|
+
findNodesForTriage,
|
|
35
|
+
findNodesWithFanIn,
|
|
36
|
+
getCallableNodes,
|
|
37
|
+
getCallEdges,
|
|
38
|
+
getCfgBlocks,
|
|
39
|
+
getCfgEdges,
|
|
40
|
+
getClassHierarchy,
|
|
41
|
+
getCoChangeMeta,
|
|
42
|
+
getComplexityForNode,
|
|
43
|
+
getEmbeddingCount,
|
|
44
|
+
getEmbeddingMeta,
|
|
45
|
+
getFileNodesAll,
|
|
46
|
+
getFunctionNodeId,
|
|
47
|
+
getImportEdges,
|
|
48
|
+
getNodeId,
|
|
49
|
+
hasCfgTables,
|
|
50
|
+
hasCoChanges,
|
|
51
|
+
hasDataflowTable,
|
|
52
|
+
hasEmbeddings,
|
|
53
|
+
iterateFunctionNodes,
|
|
54
|
+
listFunctionNodes,
|
|
55
|
+
purgeFileData,
|
|
56
|
+
purgeFilesData,
|
|
57
|
+
upsertCoChangeMeta,
|
|
58
|
+
} from './db/repository/index.js';
|