project-librarian 0.3.0 → 0.4.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.ko.md +42 -11
- package/README.md +39 -9
- package/SKILL.md +1 -0
- package/dist/args.js +20 -3
- package/dist/code-index/evidence.js +79 -0
- package/dist/code-index/extractors/config.js +58 -0
- package/dist/code-index/extractors/light-languages.js +52 -0
- package/dist/code-index/extractors/registry.js +137 -0
- package/dist/code-index/extractors/shared.js +36 -0
- package/dist/code-index/extractors/tree-sitter.js +319 -0
- package/dist/code-index/extractors/types.js +2 -0
- package/dist/code-index/extractors/typescript.js +211 -0
- package/dist/code-index/incremental.js +16 -0
- package/dist/code-index/index-health.js +164 -0
- package/dist/code-index/modes.js +303 -0
- package/dist/code-index/ownership.js +183 -0
- package/dist/code-index/reports.js +322 -0
- package/dist/code-index/schema.js +196 -0
- package/dist/code-index/search.js +153 -0
- package/dist/code-index-file-policy.js +21 -27
- package/dist/code-index.js +224 -1657
- package/dist/init-project-wiki.js +65 -37
- package/dist/mcp-server.js +325 -11
- package/dist/migration.js +10 -6
- package/dist/modes.js +139 -54
- package/dist/path-ignore-policy.js +30 -0
- package/dist/retrieval-eval.js +68 -0
- package/dist/wiki-concepts.js +49 -0
- package/dist/wiki-corpus.js +25 -0
- package/dist/wiki-diagnostics.js +19 -0
- package/dist/wiki-files.js +107 -1
- package/dist/wiki-graph.js +26 -2
- package/dist/wiki-visualizer.js +558 -0
- package/package.json +11 -4
package/dist/code-index.js
CHANGED
|
@@ -33,87 +33,50 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CodeEvidenceIndexUnavailableError = void 0;
|
|
36
|
+
exports.CodeEvidenceIndexUnavailableError = exports.codeContextPackTruncationNotice = exports.codeContextPackCharCap = exports.codeIndexSnapshot = exports.workspaceSummary = exports.workspaceDependencyGraph = exports.searchSymbols = exports.ownershipInfo = exports.ownershipContext = exports.matchedCodeownerRules = exports.evidenceCoverage = exports.codeownerRules = void 0;
|
|
37
37
|
exports.codeIndexStaleness = codeIndexStaleness;
|
|
38
|
-
exports.
|
|
39
|
-
exports.matchedCodeownerRules = matchedCodeownerRules;
|
|
40
|
-
exports.ownershipContext = ownershipContext;
|
|
41
|
-
exports.ownershipInfo = ownershipInfo;
|
|
42
|
-
exports.evidenceCoverage = evidenceCoverage;
|
|
43
|
-
exports.workspaceSummary = workspaceSummary;
|
|
44
|
-
exports.workspaceDependencyGraph = workspaceDependencyGraph;
|
|
45
|
-
exports.codeReportMetadata = codeReportMetadata;
|
|
38
|
+
exports.codeIndexHealth = codeIndexHealth;
|
|
46
39
|
exports.codeImpact = codeImpact;
|
|
47
|
-
exports.
|
|
40
|
+
exports.codeContextPack = codeContextPack;
|
|
48
41
|
exports.openCodeEvidenceDatabaseForServing = openCodeEvidenceDatabaseForServing;
|
|
49
42
|
exports.runCodeIndexMode = runCodeIndexMode;
|
|
50
43
|
exports.runCodeQueryMode = runCodeQueryMode;
|
|
51
44
|
exports.runCodeReportMode = runCodeReportMode;
|
|
52
45
|
exports.runCodeStatusMode = runCodeStatusMode;
|
|
46
|
+
exports.runCodeIndexHealthMode = runCodeIndexHealthMode;
|
|
53
47
|
exports.runCodeFilesMode = runCodeFilesMode;
|
|
54
48
|
exports.runCodeImpactMode = runCodeImpactMode;
|
|
49
|
+
exports.runCodeContextPackMode = runCodeContextPackMode;
|
|
55
50
|
exports.runCodeSearchSymbolMode = runCodeSearchSymbolMode;
|
|
56
51
|
exports.isCodeEvidenceMode = isCodeEvidenceMode;
|
|
57
52
|
exports.isCodeEvidenceModeFor = isCodeEvidenceModeFor;
|
|
58
53
|
const crypto = __importStar(require("node:crypto"));
|
|
59
54
|
const fs = __importStar(require("node:fs"));
|
|
60
55
|
const path = __importStar(require("node:path"));
|
|
61
|
-
const ts = __importStar(require("typescript"));
|
|
62
56
|
const args_1 = require("./args");
|
|
63
57
|
const code_index_db_1 = require("./code-index-db");
|
|
64
58
|
const code_index_file_policy_1 = require("./code-index-file-policy");
|
|
65
|
-
const
|
|
59
|
+
const evidence_1 = require("./code-index/evidence");
|
|
60
|
+
const registry_1 = require("./code-index/extractors/registry");
|
|
61
|
+
const shared_1 = require("./code-index/extractors/shared");
|
|
62
|
+
const index_health_1 = require("./code-index/index-health");
|
|
63
|
+
const modes_1 = require("./code-index/modes");
|
|
64
|
+
const ownership_1 = require("./code-index/ownership");
|
|
65
|
+
Object.defineProperty(exports, "codeownerRules", { enumerable: true, get: function () { return ownership_1.codeownerRules; } });
|
|
66
|
+
Object.defineProperty(exports, "matchedCodeownerRules", { enumerable: true, get: function () { return ownership_1.matchedCodeownerRules; } });
|
|
67
|
+
Object.defineProperty(exports, "ownershipContext", { enumerable: true, get: function () { return ownership_1.ownershipContext; } });
|
|
68
|
+
Object.defineProperty(exports, "ownershipInfo", { enumerable: true, get: function () { return ownership_1.ownershipInfo; } });
|
|
69
|
+
const reports_1 = require("./code-index/reports");
|
|
70
|
+
Object.defineProperty(exports, "evidenceCoverage", { enumerable: true, get: function () { return reports_1.evidenceCoverage; } });
|
|
71
|
+
Object.defineProperty(exports, "workspaceDependencyGraph", { enumerable: true, get: function () { return reports_1.workspaceDependencyGraph; } });
|
|
72
|
+
Object.defineProperty(exports, "workspaceSummary", { enumerable: true, get: function () { return reports_1.workspaceSummary; } });
|
|
73
|
+
const schema_1 = require("./code-index/schema");
|
|
74
|
+
Object.defineProperty(exports, "codeIndexSnapshot", { enumerable: true, get: function () { return schema_1.codeIndexSnapshot; } });
|
|
75
|
+
const search_1 = require("./code-index/search");
|
|
76
|
+
Object.defineProperty(exports, "searchSymbols", { enumerable: true, get: function () { return search_1.searchSymbols; } });
|
|
66
77
|
const workspace_1 = require("./workspace");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const treeSitterGrammarPackages = {
|
|
70
|
-
"tree-sitter-c": "@sengac/tree-sitter-c",
|
|
71
|
-
"tree-sitter-cpp": "@sengac/tree-sitter-cpp",
|
|
72
|
-
"tree-sitter-csharp": "@sengac/tree-sitter-c-sharp",
|
|
73
|
-
"tree-sitter-go": "@sengac/tree-sitter-go",
|
|
74
|
-
"tree-sitter-java": "@sengac/tree-sitter-java",
|
|
75
|
-
"tree-sitter-javascript": "@sengac/tree-sitter-javascript",
|
|
76
|
-
"tree-sitter-kotlin": "@sengac/tree-sitter-kotlin",
|
|
77
|
-
"tree-sitter-php": "@sengac/tree-sitter-php",
|
|
78
|
-
"tree-sitter-python": "@sengac/tree-sitter-python",
|
|
79
|
-
"tree-sitter-rust": "@sengac/tree-sitter-rust",
|
|
80
|
-
"tree-sitter-swift": "@sengac/tree-sitter-swift",
|
|
81
|
-
};
|
|
82
|
-
const codeReportSectionAliases = {
|
|
83
|
-
config: "configs",
|
|
84
|
-
configs: "configs",
|
|
85
|
-
coverage: "coverage",
|
|
86
|
-
dependencies: "hotspots",
|
|
87
|
-
dependency: "hotspots",
|
|
88
|
-
dependency_hotspots: "hotspots",
|
|
89
|
-
edge: "edges",
|
|
90
|
-
edge_summary: "edges",
|
|
91
|
-
edges: "edges",
|
|
92
|
-
evidence: "coverage",
|
|
93
|
-
evidence_coverage: "coverage",
|
|
94
|
-
hotspot: "hotspots",
|
|
95
|
-
hotspots: "hotspots",
|
|
96
|
-
language: "languages",
|
|
97
|
-
language_profile_summary: "languages",
|
|
98
|
-
languages: "languages",
|
|
99
|
-
ownership: "ownership",
|
|
100
|
-
ownership_summary: "ownership",
|
|
101
|
-
parser: "parsers",
|
|
102
|
-
parser_backend_summary: "parsers",
|
|
103
|
-
parser_backends: "parsers",
|
|
104
|
-
parsers: "parsers",
|
|
105
|
-
route: "routes",
|
|
106
|
-
route_inventory: "routes",
|
|
107
|
-
routes: "routes",
|
|
108
|
-
workspace: "workspaces",
|
|
109
|
-
workspace_graph: "workspace-graph",
|
|
110
|
-
"workspace-graph": "workspace-graph",
|
|
111
|
-
workspacegraph: "workspace-graph",
|
|
112
|
-
monorepo: "workspace-graph",
|
|
113
|
-
monorepo_graph: "workspace-graph",
|
|
114
|
-
workspace_summary: "workspaces",
|
|
115
|
-
workspaces: "workspaces",
|
|
116
|
-
};
|
|
78
|
+
exports.codeContextPackCharCap = 4000;
|
|
79
|
+
exports.codeContextPackTruncationNotice = "[truncated - refine the query]";
|
|
117
80
|
function fail(message) {
|
|
118
81
|
console.error(message);
|
|
119
82
|
process.exit(1);
|
|
@@ -147,907 +110,48 @@ function selectedCodeParserMode() {
|
|
|
147
110
|
return "tree-sitter";
|
|
148
111
|
fail(`invalid --code-parser: ${args_1.codeParser}; expected one of: default, tree-sitter`);
|
|
149
112
|
}
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
const language = (0, code_index_file_policy_1.fileLanguage)(relativePath);
|
|
153
|
-
if (language === "c")
|
|
154
|
-
return "tree-sitter-c";
|
|
155
|
-
if (language === "cpp")
|
|
156
|
-
return "tree-sitter-cpp";
|
|
157
|
-
if (language === "csharp")
|
|
158
|
-
return "tree-sitter-csharp";
|
|
159
|
-
if ([".js", ".jsx", ".cjs", ".mjs"].includes(extension))
|
|
160
|
-
return "tree-sitter-javascript";
|
|
161
|
-
if ([".ts", ".mts", ".cts"].includes(extension))
|
|
162
|
-
return "tree-sitter-typescript";
|
|
163
|
-
if (extension === ".tsx")
|
|
164
|
-
return "tree-sitter-tsx";
|
|
165
|
-
if (language === "java")
|
|
166
|
-
return "tree-sitter-java";
|
|
167
|
-
if (language === "kotlin")
|
|
168
|
-
return "tree-sitter-kotlin";
|
|
169
|
-
if (language === "php")
|
|
170
|
-
return "tree-sitter-php";
|
|
171
|
-
if (language === "python")
|
|
172
|
-
return "tree-sitter-python";
|
|
173
|
-
if (language === "go")
|
|
174
|
-
return "tree-sitter-go";
|
|
175
|
-
if (language === "rust")
|
|
176
|
-
return "tree-sitter-rust";
|
|
177
|
-
if (language === "swift")
|
|
178
|
-
return "tree-sitter-swift";
|
|
179
|
-
if (language === "config")
|
|
180
|
-
return "config";
|
|
181
|
-
return "inventory-only";
|
|
113
|
+
function normalizedMtimeMs(stat) {
|
|
114
|
+
return Number(stat.mtimeMs.toFixed(3));
|
|
182
115
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if ((0, code_index_file_policy_1.fileLanguage)(relativePath) === "go")
|
|
191
|
-
return "go-light";
|
|
192
|
-
if ((0, code_index_file_policy_1.fileLanguage)(relativePath) === "config")
|
|
193
|
-
return "config";
|
|
194
|
-
return "inventory-only";
|
|
116
|
+
function readCodeFileFingerprint(relativePath) {
|
|
117
|
+
const stat = fs.statSync((0, workspace_1.abs)(relativePath));
|
|
118
|
+
return {
|
|
119
|
+
mtimeMs: normalizedMtimeMs(stat),
|
|
120
|
+
path: relativePath,
|
|
121
|
+
size: stat.size,
|
|
122
|
+
};
|
|
195
123
|
}
|
|
196
124
|
function readCodeFile(relativePath, parserMode = "default") {
|
|
197
125
|
const text = fs.readFileSync((0, workspace_1.abs)(relativePath), "utf8");
|
|
126
|
+
const fingerprint = readCodeFileFingerprint(relativePath);
|
|
127
|
+
const language = (0, code_index_file_policy_1.fileLanguage)(relativePath) || "config";
|
|
198
128
|
return {
|
|
199
|
-
bytes:
|
|
129
|
+
bytes: fingerprint.size,
|
|
200
130
|
hash: crypto.createHash("sha256").update(text).digest("hex"),
|
|
201
|
-
language
|
|
131
|
+
language,
|
|
202
132
|
lines: text.length === 0 ? 0 : text.split(/\r?\n/).length,
|
|
133
|
+
mtimeMs: fingerprint.mtimeMs,
|
|
203
134
|
path: relativePath,
|
|
204
|
-
profile: extractionProfile(relativePath, parserMode),
|
|
135
|
+
profile: (0, registry_1.extractionProfile)(relativePath, language, parserMode),
|
|
136
|
+
size: fingerprint.size,
|
|
205
137
|
text,
|
|
206
138
|
};
|
|
207
139
|
}
|
|
208
|
-
|
|
209
|
-
return text.slice(0, index).split(/\r?\n/).length;
|
|
210
|
-
}
|
|
211
|
-
function scriptKindForPath(relativePath) {
|
|
212
|
-
const extension = path.extname(relativePath).toLowerCase();
|
|
213
|
-
if (extension === ".tsx")
|
|
214
|
-
return ts.ScriptKind.TSX;
|
|
215
|
-
if (extension === ".jsx")
|
|
216
|
-
return ts.ScriptKind.JSX;
|
|
217
|
-
if ([".ts", ".mts", ".cts"].includes(extension))
|
|
218
|
-
return ts.ScriptKind.TS;
|
|
219
|
-
return ts.ScriptKind.JS;
|
|
220
|
-
}
|
|
221
|
-
function setupDatabase(database) {
|
|
222
|
-
database.exec(`
|
|
223
|
-
PRAGMA journal_mode = WAL;
|
|
224
|
-
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
225
|
-
CREATE TABLE files (
|
|
226
|
-
path TEXT PRIMARY KEY,
|
|
227
|
-
language TEXT NOT NULL,
|
|
228
|
-
profile TEXT NOT NULL,
|
|
229
|
-
kind TEXT NOT NULL,
|
|
230
|
-
bytes INTEGER NOT NULL,
|
|
231
|
-
lines INTEGER NOT NULL,
|
|
232
|
-
hash TEXT NOT NULL
|
|
233
|
-
);
|
|
234
|
-
CREATE TABLE symbols (
|
|
235
|
-
id INTEGER PRIMARY KEY,
|
|
236
|
-
name TEXT NOT NULL,
|
|
237
|
-
kind TEXT NOT NULL,
|
|
238
|
-
file_path TEXT NOT NULL,
|
|
239
|
-
line INTEGER NOT NULL,
|
|
240
|
-
signature TEXT NOT NULL
|
|
241
|
-
);
|
|
242
|
-
CREATE TABLE imports (
|
|
243
|
-
id INTEGER PRIMARY KEY,
|
|
244
|
-
from_file TEXT NOT NULL,
|
|
245
|
-
to_ref TEXT NOT NULL,
|
|
246
|
-
imported TEXT NOT NULL,
|
|
247
|
-
line INTEGER NOT NULL,
|
|
248
|
-
raw TEXT NOT NULL
|
|
249
|
-
);
|
|
250
|
-
CREATE TABLE routes (
|
|
251
|
-
id INTEGER PRIMARY KEY,
|
|
252
|
-
method TEXT NOT NULL,
|
|
253
|
-
route TEXT NOT NULL,
|
|
254
|
-
file_path TEXT NOT NULL,
|
|
255
|
-
line INTEGER NOT NULL,
|
|
256
|
-
handler TEXT NOT NULL
|
|
257
|
-
);
|
|
258
|
-
CREATE TABLE configs (
|
|
259
|
-
id INTEGER PRIMARY KEY,
|
|
260
|
-
key TEXT NOT NULL,
|
|
261
|
-
value TEXT NOT NULL,
|
|
262
|
-
file_path TEXT NOT NULL,
|
|
263
|
-
line INTEGER NOT NULL
|
|
264
|
-
);
|
|
265
|
-
CREATE TABLE edges (
|
|
266
|
-
id INTEGER PRIMARY KEY,
|
|
267
|
-
kind TEXT NOT NULL,
|
|
268
|
-
source_kind TEXT NOT NULL,
|
|
269
|
-
source TEXT NOT NULL,
|
|
270
|
-
target_kind TEXT NOT NULL,
|
|
271
|
-
target TEXT NOT NULL,
|
|
272
|
-
file_path TEXT NOT NULL,
|
|
273
|
-
line INTEGER NOT NULL,
|
|
274
|
-
evidence TEXT NOT NULL
|
|
275
|
-
);
|
|
276
|
-
CREATE VIRTUAL TABLE files_fts USING fts5(path, language, profile, content);
|
|
277
|
-
CREATE VIRTUAL TABLE symbols_fts USING fts5(name, kind, file_path, signature);
|
|
278
|
-
CREATE INDEX idx_symbols_file ON symbols(file_path);
|
|
279
|
-
CREATE INDEX idx_symbols_name ON symbols(name);
|
|
280
|
-
CREATE INDEX idx_imports_from ON imports(from_file);
|
|
281
|
-
CREATE INDEX idx_routes_path ON routes(route);
|
|
282
|
-
CREATE INDEX idx_configs_file ON configs(file_path);
|
|
283
|
-
CREATE INDEX idx_edges_source ON edges(source_kind, source);
|
|
284
|
-
CREATE INDEX idx_edges_target ON edges(target_kind, target);
|
|
285
|
-
CREATE INDEX idx_edges_kind ON edges(kind);
|
|
286
|
-
`);
|
|
287
|
-
}
|
|
288
|
-
function createIndexStatements(database) {
|
|
289
|
-
return {
|
|
290
|
-
deleteConfig: database.prepare("DELETE FROM configs WHERE file_path = ?"),
|
|
291
|
-
deleteEdge: database.prepare("DELETE FROM edges WHERE file_path = ?"),
|
|
292
|
-
deleteFile: database.prepare("DELETE FROM files WHERE path = ?"),
|
|
293
|
-
deleteFileFts: database.prepare("DELETE FROM files_fts WHERE path = ?"),
|
|
294
|
-
deleteImport: database.prepare("DELETE FROM imports WHERE from_file = ?"),
|
|
295
|
-
deleteRoute: database.prepare("DELETE FROM routes WHERE file_path = ?"),
|
|
296
|
-
deleteSymbol: database.prepare("DELETE FROM symbols WHERE file_path = ?"),
|
|
297
|
-
deleteSymbolFts: database.prepare("DELETE FROM symbols_fts WHERE file_path = ?"),
|
|
298
|
-
insertConfig: database.prepare("INSERT INTO configs (key, value, file_path, line) VALUES (?, ?, ?, ?)"),
|
|
299
|
-
insertEdge: database.prepare("INSERT INTO edges (kind, source_kind, source, target_kind, target, file_path, line, evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"),
|
|
300
|
-
insertFile: database.prepare("INSERT INTO files (path, language, profile, kind, bytes, lines, hash) VALUES (?, ?, ?, ?, ?, ?, ?)"),
|
|
301
|
-
insertFileFts: database.prepare("INSERT INTO files_fts (path, language, profile, content) VALUES (?, ?, ?, ?)"),
|
|
302
|
-
insertImport: database.prepare("INSERT INTO imports (from_file, to_ref, imported, line, raw) VALUES (?, ?, ?, ?, ?)"),
|
|
303
|
-
insertMeta: database.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)"),
|
|
304
|
-
insertRoute: database.prepare("INSERT INTO routes (method, route, file_path, line, handler) VALUES (?, ?, ?, ?, ?)"),
|
|
305
|
-
insertSymbol: database.prepare("INSERT INTO symbols (name, kind, file_path, line, signature) VALUES (?, ?, ?, ?, ?)"),
|
|
306
|
-
insertSymbolFts: database.prepare("INSERT INTO symbols_fts (name, kind, file_path, signature) VALUES (?, ?, ?, ?)"),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
function removeIndexedFile(filePath, statements) {
|
|
310
|
-
statements.deleteConfig.run(filePath);
|
|
311
|
-
statements.deleteEdge.run(filePath);
|
|
312
|
-
statements.deleteImport.run(filePath);
|
|
313
|
-
statements.deleteRoute.run(filePath);
|
|
314
|
-
statements.deleteSymbol.run(filePath);
|
|
315
|
-
statements.deleteSymbolFts.run(filePath);
|
|
316
|
-
statements.deleteFileFts.run(filePath);
|
|
317
|
-
statements.deleteFile.run(filePath);
|
|
318
|
-
}
|
|
319
|
-
const treeSitterParsers = new Map();
|
|
320
|
-
function requireTreeSitterPackage(packageName) {
|
|
321
|
-
try {
|
|
322
|
-
return require(packageName);
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
326
|
-
fail(`--code-parser tree-sitter requires optional package ${packageName}; install project optional dependencies with npm install. Error: ${message}`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function treeSitterGrammarForProfile(profile) {
|
|
330
|
-
if (profile === "tree-sitter-typescript" || profile === "tree-sitter-tsx") {
|
|
331
|
-
const grammars = requireTreeSitterPackage("@sengac/tree-sitter-typescript");
|
|
332
|
-
const grammar = profile === "tree-sitter-tsx" ? grammars.tsx : grammars.typescript;
|
|
333
|
-
if (!grammar)
|
|
334
|
-
fail(`tree-sitter-typescript did not expose the expected ${profile === "tree-sitter-tsx" ? "tsx" : "typescript"} grammar`);
|
|
335
|
-
return grammar;
|
|
336
|
-
}
|
|
337
|
-
const packageName = treeSitterGrammarPackages[profile];
|
|
338
|
-
if (packageName) {
|
|
339
|
-
const grammarModule = requireTreeSitterPackage(packageName);
|
|
340
|
-
const grammar = profile === "tree-sitter-php"
|
|
341
|
-
? grammarModule.php ?? grammarModule.php_only
|
|
342
|
-
: grammarModule;
|
|
343
|
-
if (!grammar)
|
|
344
|
-
fail(`${packageName} did not expose a Tree-sitter grammar for ${profile}`);
|
|
345
|
-
return grammar;
|
|
346
|
-
}
|
|
347
|
-
fail(`missing Tree-sitter grammar for profile: ${profile}`);
|
|
348
|
-
}
|
|
349
|
-
function treeSitterParserForProfile(profile) {
|
|
350
|
-
const cached = treeSitterParsers.get(profile);
|
|
351
|
-
if (cached)
|
|
352
|
-
return cached;
|
|
353
|
-
const Parser = requireTreeSitterPackage("@sengac/tree-sitter");
|
|
354
|
-
const parser = new Parser();
|
|
355
|
-
parser.setLanguage(treeSitterGrammarForProfile(profile));
|
|
356
|
-
treeSitterParsers.set(profile, parser);
|
|
357
|
-
return parser;
|
|
358
|
-
}
|
|
359
|
-
function treeSitterLine(node) {
|
|
360
|
-
return node.startPosition.row + 1;
|
|
361
|
-
}
|
|
362
|
-
function treeSitterFieldText(node, fieldName) {
|
|
363
|
-
return node.childForFieldName(fieldName)?.text ?? "";
|
|
364
|
-
}
|
|
365
|
-
function treeSitterSignature(node) {
|
|
366
|
-
return oneLine(node.text);
|
|
367
|
-
}
|
|
368
|
-
function unquoteLiteral(text) {
|
|
369
|
-
const trimmed = text.trim();
|
|
370
|
-
if (trimmed.length >= 2) {
|
|
371
|
-
const first = trimmed[0];
|
|
372
|
-
const last = trimmed[trimmed.length - 1];
|
|
373
|
-
if ((first === "\"" && last === "\"") || (first === "'" && last === "'") || (first === "`" && last === "`"))
|
|
374
|
-
return trimmed.slice(1, -1);
|
|
375
|
-
}
|
|
376
|
-
return trimmed;
|
|
377
|
-
}
|
|
378
|
-
function forEachNamedTreeSitterNode(node, visit) {
|
|
379
|
-
visit(node);
|
|
380
|
-
for (let index = 0; index < node.namedChildCount; index += 1) {
|
|
381
|
-
const child = node.namedChild(index);
|
|
382
|
-
if (child)
|
|
383
|
-
forEachNamedTreeSitterNode(child, visit);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function firstNamedChildOfType(node, type) {
|
|
387
|
-
for (let index = 0; index < node.namedChildCount; index += 1) {
|
|
388
|
-
const child = node.namedChild(index);
|
|
389
|
-
if (child?.type === type)
|
|
390
|
-
return child;
|
|
391
|
-
}
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
function treeSitterModuleSpecifier(node) {
|
|
395
|
-
const source = treeSitterFieldText(node, "source");
|
|
396
|
-
if (source)
|
|
397
|
-
return unquoteLiteral(source);
|
|
398
|
-
const raw = node.text;
|
|
399
|
-
return raw.match(/\bfrom\s*["'`]([^"'`]+)["'`]/)?.[1]
|
|
400
|
-
?? raw.match(/^\s*import\s*["'`]([^"'`]+)["'`]/)?.[1]
|
|
401
|
-
?? raw.match(/\brequire\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/)?.[1]
|
|
402
|
-
?? "";
|
|
403
|
-
}
|
|
404
|
-
function indexTreeSitterJavaScriptLike(file, statements) {
|
|
405
|
-
const tree = treeSitterParserForProfile(file.profile).parse(file.text);
|
|
406
|
-
function visit(node, context) {
|
|
407
|
-
let nextContext = context;
|
|
408
|
-
if (node.type === "function_declaration") {
|
|
409
|
-
const name = treeSitterFieldText(node, "name");
|
|
410
|
-
insertSymbol(statements, name, "function", file, treeSitterLine(node), treeSitterSignature(node));
|
|
411
|
-
if (name)
|
|
412
|
-
nextContext = name;
|
|
413
|
-
}
|
|
414
|
-
else if (node.type === "class_declaration") {
|
|
415
|
-
const name = treeSitterFieldText(node, "name");
|
|
416
|
-
insertSymbol(statements, name, "class", file, treeSitterLine(node), treeSitterSignature(node));
|
|
417
|
-
if (name)
|
|
418
|
-
nextContext = name;
|
|
419
|
-
}
|
|
420
|
-
else if (node.type === "method_definition" || node.type === "method_signature") {
|
|
421
|
-
const name = treeSitterFieldText(node, "name");
|
|
422
|
-
insertSymbol(statements, name, "method", file, treeSitterLine(node), treeSitterSignature(node));
|
|
423
|
-
if (name)
|
|
424
|
-
nextContext = name;
|
|
425
|
-
}
|
|
426
|
-
else if (node.type === "interface_declaration") {
|
|
427
|
-
insertSymbol(statements, treeSitterFieldText(node, "name"), "interface", file, treeSitterLine(node), treeSitterSignature(node));
|
|
428
|
-
}
|
|
429
|
-
else if (node.type === "type_alias_declaration") {
|
|
430
|
-
insertSymbol(statements, treeSitterFieldText(node, "name"), "type", file, treeSitterLine(node), treeSitterSignature(node));
|
|
431
|
-
}
|
|
432
|
-
else if (node.type === "enum_declaration") {
|
|
433
|
-
insertSymbol(statements, treeSitterFieldText(node, "name"), "enum", file, treeSitterLine(node), treeSitterSignature(node));
|
|
434
|
-
}
|
|
435
|
-
else if (node.type === "variable_declarator") {
|
|
436
|
-
const name = treeSitterFieldText(node, "name");
|
|
437
|
-
const valueType = node.childForFieldName("value")?.type ?? "";
|
|
438
|
-
const symbolKind = ["arrow_function", "function", "function_expression"].includes(valueType) ? "function" : "variable";
|
|
439
|
-
insertSymbol(statements, name, symbolKind, file, treeSitterLine(node), treeSitterSignature(node));
|
|
440
|
-
if (symbolKind === "function" && name)
|
|
441
|
-
nextContext = name;
|
|
442
|
-
}
|
|
443
|
-
else if (node.type === "import_statement" || node.type === "export_statement") {
|
|
444
|
-
const toRef = treeSitterModuleSpecifier(node);
|
|
445
|
-
if (toRef) {
|
|
446
|
-
const imported = node.text.match(/^\s*import\s+(.+?)\s+from\s*["'`]/)?.[1] ?? node.text.match(/^\s*export\s+(.+?)\s+from\s*["'`]/)?.[1] ?? "";
|
|
447
|
-
statements.insertImport.run(file.path, toRef, oneLine(imported), treeSitterLine(node), treeSitterSignature(node));
|
|
448
|
-
insertEdge(statements, node.type === "export_statement" ? "export" : "import", "file", file.path, "module", toRef, file, treeSitterLine(node), treeSitterSignature(node));
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else if (node.type === "call_expression") {
|
|
452
|
-
const raw = node.text;
|
|
453
|
-
const routeMatch = raw.match(/^(?:app|router|server)\.(get|post|put|patch|delete|all)\s*\(\s*["'`]([^"'`]+)["'`]\s*,\s*([^,)]+)/);
|
|
454
|
-
if (routeMatch) {
|
|
455
|
-
const method = (routeMatch[1] ?? "").toUpperCase();
|
|
456
|
-
const route = routeMatch[2] ?? "";
|
|
457
|
-
const handler = oneLine(routeMatch[3] ?? "");
|
|
458
|
-
statements.insertRoute.run(method, route, file.path, treeSitterLine(node), handler);
|
|
459
|
-
insertEdge(statements, "route_to_handler", "route", `${method} ${route}`, "symbol", handler, file, treeSitterLine(node), treeSitterSignature(node));
|
|
460
|
-
}
|
|
461
|
-
const requireRef = treeSitterModuleSpecifier(node);
|
|
462
|
-
if (requireRef) {
|
|
463
|
-
statements.insertImport.run(file.path, requireRef, "", treeSitterLine(node), treeSitterSignature(node));
|
|
464
|
-
insertEdge(statements, "import", "file", file.path, "module", requireRef, file, treeSitterLine(node), treeSitterSignature(node));
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
const target = treeSitterFieldText(node, "function");
|
|
468
|
-
insertEdge(statements, "call", context ? "symbol" : "file", context || file.path, "symbol", target, file, treeSitterLine(node), treeSitterSignature(node));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
for (let index = 0; index < node.namedChildCount; index += 1) {
|
|
472
|
-
const child = node.namedChild(index);
|
|
473
|
-
if (child)
|
|
474
|
-
visit(child, nextContext);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
visit(tree.rootNode, "");
|
|
478
|
-
}
|
|
479
|
-
function indexTreeSitterPython(file, statements) {
|
|
480
|
-
const tree = treeSitterParserForProfile(file.profile).parse(file.text);
|
|
481
|
-
forEachNamedTreeSitterNode(tree.rootNode, (node) => {
|
|
482
|
-
if (node.type === "function_definition") {
|
|
483
|
-
const name = treeSitterFieldText(node, "name");
|
|
484
|
-
insertSymbol(statements, name, "function", file, treeSitterLine(node), treeSitterSignature(node));
|
|
485
|
-
}
|
|
486
|
-
else if (node.type === "class_definition") {
|
|
487
|
-
const name = treeSitterFieldText(node, "name");
|
|
488
|
-
insertSymbol(statements, name, "class", file, treeSitterLine(node), treeSitterSignature(node));
|
|
489
|
-
}
|
|
490
|
-
else if (node.type === "import_statement" || node.type === "import_from_statement") {
|
|
491
|
-
const raw = node.text.trim();
|
|
492
|
-
const fromMatch = raw.match(/^from\s+([A-Za-z0-9_.$]+)\s+import\s+(.+)$/);
|
|
493
|
-
const importMatch = raw.match(/^import\s+(.+)$/);
|
|
494
|
-
const toRef = fromMatch?.[1] ?? importMatch?.[1] ?? "";
|
|
495
|
-
const imported = fromMatch?.[2] ?? "";
|
|
496
|
-
if (toRef) {
|
|
497
|
-
statements.insertImport.run(file.path, toRef, imported.trim(), treeSitterLine(node), raw);
|
|
498
|
-
insertEdge(statements, "import", "file", file.path, "module", toRef, file, treeSitterLine(node), raw);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
function indexTreeSitterGo(file, statements) {
|
|
504
|
-
const tree = treeSitterParserForProfile(file.profile).parse(file.text);
|
|
505
|
-
forEachNamedTreeSitterNode(tree.rootNode, (node) => {
|
|
506
|
-
if (node.type === "function_declaration") {
|
|
507
|
-
const name = treeSitterFieldText(node, "name");
|
|
508
|
-
insertSymbol(statements, name, "function", file, treeSitterLine(node), treeSitterSignature(node));
|
|
509
|
-
}
|
|
510
|
-
else if (node.type === "method_declaration") {
|
|
511
|
-
const name = treeSitterFieldText(node, "name");
|
|
512
|
-
insertSymbol(statements, name, "method", file, treeSitterLine(node), treeSitterSignature(node));
|
|
513
|
-
}
|
|
514
|
-
else if (node.type === "type_declaration") {
|
|
515
|
-
const spec = firstNamedChildOfType(node, "type_spec");
|
|
516
|
-
const name = spec ? treeSitterFieldText(spec, "name") : "";
|
|
517
|
-
insertSymbol(statements, name, "type", file, treeSitterLine(node), treeSitterSignature(node));
|
|
518
|
-
}
|
|
519
|
-
else if (node.type === "const_declaration" || node.type === "var_declaration") {
|
|
520
|
-
const spec = firstNamedChildOfType(node, node.type === "const_declaration" ? "const_spec" : "var_spec");
|
|
521
|
-
const name = spec ? treeSitterFieldText(spec, "name") : "";
|
|
522
|
-
insertSymbol(statements, name, node.type === "const_declaration" ? "constant" : "variable", file, treeSitterLine(node), treeSitterSignature(node));
|
|
523
|
-
}
|
|
524
|
-
else if (node.type === "import_spec") {
|
|
525
|
-
const toRef = unquoteLiteral(treeSitterFieldText(node, "path") || (node.text.match(/"([^"]+)"/)?.[1] ?? ""));
|
|
526
|
-
const imported = treeSitterFieldText(node, "name");
|
|
527
|
-
insertGoImport(file, statements, toRef, imported, treeSitterLine(node), treeSitterSignature(node));
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
function treeSitterGenericLanguage(file) {
|
|
532
|
-
const normalized = file.profile.replace(/^tree-sitter-/, "");
|
|
533
|
-
if (["c", "cpp", "csharp", "java", "kotlin", "php", "rust", "swift"].includes(normalized))
|
|
534
|
-
return normalized;
|
|
535
|
-
fail(`unsupported generic Tree-sitter profile: ${file.profile}`);
|
|
536
|
-
}
|
|
537
|
-
function symbolNameFromPatterns(text, patterns) {
|
|
538
|
-
for (const pattern of patterns) {
|
|
539
|
-
const match = text.match(pattern);
|
|
540
|
-
if (match?.[1])
|
|
541
|
-
return match[1];
|
|
542
|
-
}
|
|
543
|
-
return "";
|
|
544
|
-
}
|
|
545
|
-
function treeSitterGenericSymbol(node, language) {
|
|
546
|
-
const raw = node.text;
|
|
547
|
-
const fieldName = treeSitterFieldText(node, "name");
|
|
548
|
-
const patterns = {
|
|
549
|
-
c: [
|
|
550
|
-
[["function_definition"], "function", [/\b([A-Za-z_]\w*)\s*\([^;{}]*\)\s*\{/]],
|
|
551
|
-
[["struct_specifier"], "struct", [/\bstruct\s+([A-Za-z_]\w*)/]],
|
|
552
|
-
[["enum_specifier"], "enum", [/\benum\s+([A-Za-z_]\w*)/]],
|
|
553
|
-
],
|
|
554
|
-
cpp: [
|
|
555
|
-
[["function_definition"], "function", [/\b([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:const\s*)?\{/]],
|
|
556
|
-
[["class_specifier"], "class", [/\bclass\s+([A-Za-z_]\w*)/, /\bstruct\s+([A-Za-z_]\w*)/]],
|
|
557
|
-
[["namespace_definition"], "namespace", [/\bnamespace\s+([A-Za-z_]\w*)/]],
|
|
558
|
-
[["enum_specifier"], "enum", [/\benum(?:\s+class)?\s+([A-Za-z_]\w*)/]],
|
|
559
|
-
],
|
|
560
|
-
csharp: [
|
|
561
|
-
[["method_declaration", "constructor_declaration"], "method", [/\b([A-Za-z_]\w*)\s*\(/]],
|
|
562
|
-
[["class_declaration"], "class", [/\bclass\s+([A-Za-z_]\w*)/]],
|
|
563
|
-
[["interface_declaration"], "interface", [/\binterface\s+([A-Za-z_]\w*)/]],
|
|
564
|
-
[["struct_declaration"], "struct", [/\bstruct\s+([A-Za-z_]\w*)/]],
|
|
565
|
-
[["enum_declaration"], "enum", [/\benum\s+([A-Za-z_]\w*)/]],
|
|
566
|
-
],
|
|
567
|
-
java: [
|
|
568
|
-
[["method_declaration", "constructor_declaration"], "method", [/\b([A-Za-z_]\w*)\s*\(/]],
|
|
569
|
-
[["class_declaration"], "class", [/\bclass\s+([A-Za-z_]\w*)/]],
|
|
570
|
-
[["interface_declaration"], "interface", [/\binterface\s+([A-Za-z_]\w*)/]],
|
|
571
|
-
[["enum_declaration"], "enum", [/\benum\s+([A-Za-z_]\w*)/]],
|
|
572
|
-
],
|
|
573
|
-
kotlin: [
|
|
574
|
-
[["function_declaration"], "function", [/\bfun\s+([A-Za-z_]\w*)/]],
|
|
575
|
-
[["class_declaration"], "class", [/\bclass\s+([A-Za-z_]\w*)/, /\binterface\s+([A-Za-z_]\w*)/]],
|
|
576
|
-
[["object_declaration"], "object", [/\bobject\s+([A-Za-z_]\w*)/]],
|
|
577
|
-
],
|
|
578
|
-
php: [
|
|
579
|
-
[["function_definition"], "function", [/\bfunction\s+([A-Za-z_]\w*)/]],
|
|
580
|
-
[["method_declaration"], "method", [/\bfunction\s+([A-Za-z_]\w*)/]],
|
|
581
|
-
[["class_declaration"], "class", [/\bclass\s+([A-Za-z_]\w*)/]],
|
|
582
|
-
[["interface_declaration"], "interface", [/\binterface\s+([A-Za-z_]\w*)/]],
|
|
583
|
-
[["trait_declaration"], "trait", [/\btrait\s+([A-Za-z_]\w*)/]],
|
|
584
|
-
],
|
|
585
|
-
rust: [
|
|
586
|
-
[["function_item"], "function", [/\bfn\s+([A-Za-z_]\w*)/]],
|
|
587
|
-
[["struct_item"], "struct", [/\bstruct\s+([A-Za-z_]\w*)/]],
|
|
588
|
-
[["enum_item"], "enum", [/\benum\s+([A-Za-z_]\w*)/]],
|
|
589
|
-
[["trait_item"], "trait", [/\btrait\s+([A-Za-z_]\w*)/]],
|
|
590
|
-
[["impl_item"], "impl", [/\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/]],
|
|
591
|
-
],
|
|
592
|
-
swift: [
|
|
593
|
-
[["function_declaration"], "function", [/\bfunc\s+([A-Za-z_]\w*)/]],
|
|
594
|
-
[["class_declaration"], "class", [/\bclass\s+([A-Za-z_]\w*)/]],
|
|
595
|
-
[["struct_declaration"], "struct", [/\bstruct\s+([A-Za-z_]\w*)/]],
|
|
596
|
-
[["protocol_declaration"], "protocol", [/\bprotocol\s+([A-Za-z_]\w*)/]],
|
|
597
|
-
[["enum_declaration"], "enum", [/\benum\s+([A-Za-z_]\w*)/]],
|
|
598
|
-
],
|
|
599
|
-
};
|
|
600
|
-
for (const [types, kind, regexes] of patterns[language] ?? []) {
|
|
601
|
-
if (!types.includes(node.type))
|
|
602
|
-
continue;
|
|
603
|
-
const name = fieldName || symbolNameFromPatterns(raw, regexes);
|
|
604
|
-
return name ? { kind, name } : null;
|
|
605
|
-
}
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
function treeSitterGenericImport(node, language) {
|
|
609
|
-
const raw = node.text.trim();
|
|
610
|
-
const importTypes = {
|
|
611
|
-
c: ["preproc_include"],
|
|
612
|
-
cpp: ["preproc_include", "using_declaration", "namespace_alias_definition"],
|
|
613
|
-
csharp: ["using_directive"],
|
|
614
|
-
java: ["import_declaration"],
|
|
615
|
-
kotlin: ["import_header"],
|
|
616
|
-
php: ["namespace_use_declaration"],
|
|
617
|
-
rust: ["use_declaration"],
|
|
618
|
-
swift: ["import_declaration"],
|
|
619
|
-
};
|
|
620
|
-
if (!(importTypes[language] ?? []).includes(node.type))
|
|
621
|
-
return null;
|
|
622
|
-
const toRef = raw.match(/#include\s*[<"]([^>"]+)[>"]/)?.[1]
|
|
623
|
-
?? raw.match(/\bimport\s+([A-Za-z0-9_.*.$\\/-]+)/)?.[1]
|
|
624
|
-
?? raw.match(/\busing\s+([A-Za-z0-9_.*.$\\/-]+)/)?.[1]
|
|
625
|
-
?? raw.match(/\buse\s+([A-Za-z0-9_:{}*,\s]+);?/)?.[1]?.replace(/\s+/g, " ").trim()
|
|
626
|
-
?? "";
|
|
627
|
-
return toRef ? { imported: "", kind: language === "rust" ? "use" : "import", toRef } : null;
|
|
628
|
-
}
|
|
629
|
-
function indexTreeSitterGeneric(file, statements) {
|
|
630
|
-
const language = treeSitterGenericLanguage(file);
|
|
631
|
-
const tree = treeSitterParserForProfile(file.profile).parse(file.text);
|
|
632
|
-
forEachNamedTreeSitterNode(tree.rootNode, (node) => {
|
|
633
|
-
const symbol = treeSitterGenericSymbol(node, language);
|
|
634
|
-
if (symbol)
|
|
635
|
-
insertSymbol(statements, symbol.name, symbol.kind, file, treeSitterLine(node), treeSitterSignature(node));
|
|
636
|
-
const imported = treeSitterGenericImport(node, language);
|
|
637
|
-
if (imported) {
|
|
638
|
-
statements.insertImport.run(file.path, imported.toRef, imported.imported, treeSitterLine(node), treeSitterSignature(node));
|
|
639
|
-
insertEdge(statements, imported.kind, "file", file.path, "module", imported.toRef, file, treeSitterLine(node), treeSitterSignature(node));
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
const extractionBackends = [
|
|
644
|
-
{
|
|
645
|
-
id: "typescript-compiler",
|
|
646
|
-
index: indexJavaScriptLike,
|
|
647
|
-
label: "TypeScript compiler API",
|
|
648
|
-
profile: "typescript-ast",
|
|
649
|
-
strength: "structural",
|
|
650
|
-
},
|
|
651
|
-
{
|
|
652
|
-
id: "regex-light",
|
|
653
|
-
index: indexPythonLight,
|
|
654
|
-
label: "Python lightweight regex",
|
|
655
|
-
profile: "python-light",
|
|
656
|
-
strength: "light",
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
id: "regex-light",
|
|
660
|
-
index: indexGoLight,
|
|
661
|
-
label: "Go lightweight regex",
|
|
662
|
-
profile: "go-light",
|
|
663
|
-
strength: "light",
|
|
664
|
-
},
|
|
665
|
-
{
|
|
666
|
-
id: "tree-sitter-javascript",
|
|
667
|
-
index: indexTreeSitterJavaScriptLike,
|
|
668
|
-
label: "Tree-sitter JavaScript grammar",
|
|
669
|
-
profile: "tree-sitter-javascript",
|
|
670
|
-
strength: "structural",
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
id: "tree-sitter-typescript",
|
|
674
|
-
index: indexTreeSitterJavaScriptLike,
|
|
675
|
-
label: "Tree-sitter TypeScript grammar",
|
|
676
|
-
profile: "tree-sitter-typescript",
|
|
677
|
-
strength: "structural",
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
id: "tree-sitter-typescript",
|
|
681
|
-
index: indexTreeSitterJavaScriptLike,
|
|
682
|
-
label: "Tree-sitter TSX grammar",
|
|
683
|
-
profile: "tree-sitter-tsx",
|
|
684
|
-
strength: "structural",
|
|
685
|
-
},
|
|
686
|
-
{
|
|
687
|
-
id: "tree-sitter-python",
|
|
688
|
-
index: indexTreeSitterPython,
|
|
689
|
-
label: "Tree-sitter Python grammar",
|
|
690
|
-
profile: "tree-sitter-python",
|
|
691
|
-
strength: "structural",
|
|
692
|
-
},
|
|
693
|
-
{
|
|
694
|
-
id: "tree-sitter-go",
|
|
695
|
-
index: indexTreeSitterGo,
|
|
696
|
-
label: "Tree-sitter Go grammar",
|
|
697
|
-
profile: "tree-sitter-go",
|
|
698
|
-
strength: "structural",
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
id: "tree-sitter-c",
|
|
702
|
-
index: indexTreeSitterGeneric,
|
|
703
|
-
label: "Tree-sitter C grammar",
|
|
704
|
-
profile: "tree-sitter-c",
|
|
705
|
-
strength: "structural",
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
id: "tree-sitter-cpp",
|
|
709
|
-
index: indexTreeSitterGeneric,
|
|
710
|
-
label: "Tree-sitter C++ grammar",
|
|
711
|
-
profile: "tree-sitter-cpp",
|
|
712
|
-
strength: "structural",
|
|
713
|
-
},
|
|
714
|
-
{
|
|
715
|
-
id: "tree-sitter-csharp",
|
|
716
|
-
index: indexTreeSitterGeneric,
|
|
717
|
-
label: "Tree-sitter C# grammar",
|
|
718
|
-
profile: "tree-sitter-csharp",
|
|
719
|
-
strength: "structural",
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
id: "tree-sitter-java",
|
|
723
|
-
index: indexTreeSitterGeneric,
|
|
724
|
-
label: "Tree-sitter Java grammar",
|
|
725
|
-
profile: "tree-sitter-java",
|
|
726
|
-
strength: "structural",
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
id: "tree-sitter-kotlin",
|
|
730
|
-
index: indexTreeSitterGeneric,
|
|
731
|
-
label: "Tree-sitter Kotlin grammar",
|
|
732
|
-
profile: "tree-sitter-kotlin",
|
|
733
|
-
strength: "structural",
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
id: "tree-sitter-php",
|
|
737
|
-
index: indexTreeSitterGeneric,
|
|
738
|
-
label: "Tree-sitter PHP grammar",
|
|
739
|
-
profile: "tree-sitter-php",
|
|
740
|
-
strength: "structural",
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
id: "tree-sitter-rust",
|
|
744
|
-
index: indexTreeSitterGeneric,
|
|
745
|
-
label: "Tree-sitter Rust grammar",
|
|
746
|
-
profile: "tree-sitter-rust",
|
|
747
|
-
strength: "structural",
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
id: "tree-sitter-swift",
|
|
751
|
-
index: indexTreeSitterGeneric,
|
|
752
|
-
label: "Tree-sitter Swift grammar",
|
|
753
|
-
profile: "tree-sitter-swift",
|
|
754
|
-
strength: "structural",
|
|
755
|
-
},
|
|
756
|
-
{
|
|
757
|
-
id: "config-key-value",
|
|
758
|
-
index: (file, statements) => indexConfigs(file, statements.insertConfig),
|
|
759
|
-
label: "Configuration key/value extractor",
|
|
760
|
-
profile: "config",
|
|
761
|
-
strength: "config",
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
id: "inventory-only",
|
|
765
|
-
index: () => undefined,
|
|
766
|
-
label: "Inventory-only file listing",
|
|
767
|
-
profile: "inventory-only",
|
|
768
|
-
strength: "inventory",
|
|
769
|
-
},
|
|
770
|
-
];
|
|
771
|
-
const extractionBackendsByProfile = new Map(extractionBackends.map((backend) => [backend.profile, backend]));
|
|
140
|
+
const extractionBackendRegistry = (0, registry_1.createExtractionBackendRegistry)(fail);
|
|
772
141
|
function extractionBackendForProfile(profile) {
|
|
773
|
-
|
|
774
|
-
if (!backend)
|
|
775
|
-
fail(`missing extraction backend for profile: ${profile}`);
|
|
776
|
-
return backend;
|
|
142
|
+
return extractionBackendRegistry.backendForProfile(profile);
|
|
777
143
|
}
|
|
778
144
|
function indexCodeFile(file, statements) {
|
|
779
|
-
statements.insertFile.run(file.path, file.language, file.profile, file.language === "config" ? "config" : "source", file.bytes, file.lines, file.hash);
|
|
145
|
+
statements.insertFile.run(file.path, file.language, file.profile, file.language === "config" ? "config" : "source", file.bytes, file.lines, file.hash, file.mtimeMs, file.size);
|
|
780
146
|
statements.insertFileFts.run(file.path, file.language, file.profile, file.text);
|
|
781
147
|
extractionBackendForProfile(file.profile).index(file, statements);
|
|
782
148
|
}
|
|
783
|
-
function writeIndexMetadata(scopes, parserMode, statements) {
|
|
784
|
-
statements.insertMeta.run("schema_version", codeIndexSchemaVersion);
|
|
785
|
-
statements.insertMeta.run("updated_at", new Date().toISOString());
|
|
786
|
-
statements.insertMeta.run("root", workspace_1.root);
|
|
787
|
-
statements.insertMeta.run("scopes", scopes.join(", "));
|
|
788
|
-
statements.insertMeta.run("scopes_json", JSON.stringify(scopes));
|
|
789
|
-
statements.insertMeta.run("parser_mode", parserMode);
|
|
790
|
-
statements.insertMeta.run("terminology", "code evidence index");
|
|
791
|
-
}
|
|
792
|
-
function oneLine(text) {
|
|
793
|
-
return text.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
794
|
-
}
|
|
795
149
|
function printRows(rows) {
|
|
796
150
|
console.log(JSON.stringify(rows, null, 2));
|
|
797
151
|
}
|
|
798
152
|
function printJson(value) {
|
|
799
153
|
console.log(JSON.stringify(value, null, 2));
|
|
800
154
|
}
|
|
801
|
-
function tsLine(sourceFile, node) {
|
|
802
|
-
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
803
|
-
}
|
|
804
|
-
function nodeName(node, sourceFile) {
|
|
805
|
-
if (ts.isIdentifier(node))
|
|
806
|
-
return node.text;
|
|
807
|
-
if (ts.isStringLiteral(node) || ts.isNumericLiteral(node))
|
|
808
|
-
return node.text;
|
|
809
|
-
if (ts.isPrivateIdentifier(node))
|
|
810
|
-
return node.text;
|
|
811
|
-
return oneLine(node.getText(sourceFile));
|
|
812
|
-
}
|
|
813
|
-
function propertyNameText(name, sourceFile) {
|
|
814
|
-
if (!name)
|
|
815
|
-
return "";
|
|
816
|
-
return nodeName(name, sourceFile);
|
|
817
|
-
}
|
|
818
|
-
function callTarget(expression, sourceFile) {
|
|
819
|
-
if (ts.isIdentifier(expression))
|
|
820
|
-
return expression.text;
|
|
821
|
-
if (ts.isPropertyAccessExpression(expression))
|
|
822
|
-
return oneLine(expression.getText(sourceFile));
|
|
823
|
-
if (ts.isElementAccessExpression(expression))
|
|
824
|
-
return oneLine(expression.getText(sourceFile));
|
|
825
|
-
return oneLine(expression.getText(sourceFile));
|
|
826
|
-
}
|
|
827
|
-
function insertSymbol(statements, name, kind, file, line, signature) {
|
|
828
|
-
if (!name)
|
|
829
|
-
return;
|
|
830
|
-
statements.insertSymbol.run(name, kind, file.path, line, signature);
|
|
831
|
-
statements.insertSymbolFts.run(name, kind, file.path, signature);
|
|
832
|
-
}
|
|
833
|
-
function insertEdge(statements, kind, sourceKind, source, targetKind, target, file, line, evidence) {
|
|
834
|
-
if (!target)
|
|
835
|
-
return;
|
|
836
|
-
statements.insertEdge.run(kind, sourceKind, source, targetKind, target, file.path, line, evidence);
|
|
837
|
-
}
|
|
838
|
-
function importBindingText(importClause, sourceFile) {
|
|
839
|
-
if (!importClause)
|
|
840
|
-
return "";
|
|
841
|
-
const names = [];
|
|
842
|
-
if (importClause.name)
|
|
843
|
-
names.push(importClause.name.text);
|
|
844
|
-
const namedBindings = importClause.namedBindings;
|
|
845
|
-
if (namedBindings && ts.isNamespaceImport(namedBindings))
|
|
846
|
-
names.push(`* as ${namedBindings.name.text}`);
|
|
847
|
-
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
848
|
-
for (const element of namedBindings.elements)
|
|
849
|
-
names.push(element.name.text);
|
|
850
|
-
}
|
|
851
|
-
return names.join(", ") || oneLine(importClause.getText(sourceFile));
|
|
852
|
-
}
|
|
853
|
-
function stringArg(node) {
|
|
854
|
-
if (!node)
|
|
855
|
-
return "";
|
|
856
|
-
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) ? node.text : "";
|
|
857
|
-
}
|
|
858
|
-
function handlerArg(node, sourceFile) {
|
|
859
|
-
if (!node)
|
|
860
|
-
return "";
|
|
861
|
-
return callTarget(node, sourceFile);
|
|
862
|
-
}
|
|
863
|
-
function routeFromCall(node, sourceFile) {
|
|
864
|
-
if (!ts.isPropertyAccessExpression(node.expression))
|
|
865
|
-
return null;
|
|
866
|
-
const method = node.expression.name.text.toLowerCase();
|
|
867
|
-
if (!httpMethods.has(method))
|
|
868
|
-
return null;
|
|
869
|
-
const receiver = node.expression.expression;
|
|
870
|
-
if (!ts.isIdentifier(receiver) || !["app", "router", "server"].includes(receiver.text))
|
|
871
|
-
return null;
|
|
872
|
-
const route = stringArg(node.arguments[0]);
|
|
873
|
-
if (!route)
|
|
874
|
-
return null;
|
|
875
|
-
return {
|
|
876
|
-
handler: handlerArg(node.arguments[1], sourceFile),
|
|
877
|
-
method: method.toUpperCase(),
|
|
878
|
-
route,
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
function routeFromDecorator(node, sourceFile) {
|
|
882
|
-
const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
|
|
883
|
-
const routes = [];
|
|
884
|
-
for (const decorator of decorators) {
|
|
885
|
-
const expression = decorator.expression;
|
|
886
|
-
if (!ts.isCallExpression(expression))
|
|
887
|
-
continue;
|
|
888
|
-
const callee = expression.expression;
|
|
889
|
-
if (!ts.isIdentifier(callee))
|
|
890
|
-
continue;
|
|
891
|
-
const method = callee.text.toLowerCase();
|
|
892
|
-
if (!httpMethods.has(method))
|
|
893
|
-
continue;
|
|
894
|
-
routes.push({ method: method.toUpperCase(), route: stringArg(expression.arguments[0]) || "/" });
|
|
895
|
-
}
|
|
896
|
-
return routes;
|
|
897
|
-
}
|
|
898
|
-
function signatureFor(node, sourceFile) {
|
|
899
|
-
return oneLine(node.getText(sourceFile));
|
|
900
|
-
}
|
|
901
|
-
function indexJavaScriptLike(file, statements) {
|
|
902
|
-
const sourceFile = ts.createSourceFile(file.path, file.text, ts.ScriptTarget.Latest, true, scriptKindForPath(file.path));
|
|
903
|
-
function visit(node, context) {
|
|
904
|
-
let nextContext = context;
|
|
905
|
-
if (ts.isFunctionDeclaration(node)) {
|
|
906
|
-
const name = node.name?.text ?? "";
|
|
907
|
-
insertSymbol(statements, name, "function", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
908
|
-
if (name)
|
|
909
|
-
nextContext = name;
|
|
910
|
-
}
|
|
911
|
-
else if (ts.isClassDeclaration(node)) {
|
|
912
|
-
const name = node.name?.text ?? "";
|
|
913
|
-
insertSymbol(statements, name, "class", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
914
|
-
if (name)
|
|
915
|
-
nextContext = name;
|
|
916
|
-
}
|
|
917
|
-
else if (ts.isInterfaceDeclaration(node)) {
|
|
918
|
-
insertSymbol(statements, node.name.text, "interface", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
919
|
-
}
|
|
920
|
-
else if (ts.isTypeAliasDeclaration(node)) {
|
|
921
|
-
insertSymbol(statements, node.name.text, "type", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
922
|
-
}
|
|
923
|
-
else if (ts.isEnumDeclaration(node)) {
|
|
924
|
-
insertSymbol(statements, node.name.text, "enum", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
925
|
-
}
|
|
926
|
-
else if (ts.isMethodDeclaration(node)) {
|
|
927
|
-
const name = propertyNameText(node.name, sourceFile);
|
|
928
|
-
insertSymbol(statements, name, "method", file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
929
|
-
for (const route of routeFromDecorator(node, sourceFile)) {
|
|
930
|
-
statements.insertRoute.run(route.method, route.route, file.path, tsLine(sourceFile, node), name);
|
|
931
|
-
insertEdge(statements, "route_to_handler", "route", `${route.method} ${route.route}`, "symbol", name, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
932
|
-
}
|
|
933
|
-
if (name)
|
|
934
|
-
nextContext = name;
|
|
935
|
-
}
|
|
936
|
-
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
937
|
-
const symbolKind = node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) ? "function" : "variable";
|
|
938
|
-
insertSymbol(statements, node.name.text, symbolKind, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
939
|
-
if (symbolKind === "function")
|
|
940
|
-
nextContext = node.name.text;
|
|
941
|
-
}
|
|
942
|
-
else if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
943
|
-
const imported = importBindingText(node.importClause, sourceFile);
|
|
944
|
-
statements.insertImport.run(file.path, node.moduleSpecifier.text, imported, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
945
|
-
insertEdge(statements, "import", "file", file.path, "module", node.moduleSpecifier.text, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
946
|
-
}
|
|
947
|
-
else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
948
|
-
const exported = node.exportClause ? oneLine(node.exportClause.getText(sourceFile)) : "";
|
|
949
|
-
statements.insertImport.run(file.path, node.moduleSpecifier.text, exported, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
950
|
-
insertEdge(statements, "export", "file", file.path, "module", node.moduleSpecifier.text, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
951
|
-
}
|
|
952
|
-
else if (ts.isCallExpression(node)) {
|
|
953
|
-
const route = routeFromCall(node, sourceFile);
|
|
954
|
-
if (route) {
|
|
955
|
-
statements.insertRoute.run(route.method, route.route, file.path, tsLine(sourceFile, node), route.handler);
|
|
956
|
-
insertEdge(statements, "route_to_handler", "route", `${route.method} ${route.route}`, "symbol", route.handler, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
957
|
-
}
|
|
958
|
-
if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
959
|
-
const moduleName = stringArg(node.arguments[0]);
|
|
960
|
-
if (moduleName) {
|
|
961
|
-
statements.insertImport.run(file.path, moduleName, "", tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
962
|
-
insertEdge(statements, "import", "file", file.path, "module", moduleName, file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
else {
|
|
966
|
-
insertEdge(statements, "call", context ? "symbol" : "file", context || file.path, "symbol", callTarget(node.expression, sourceFile), file, tsLine(sourceFile, node), signatureFor(node, sourceFile));
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
ts.forEachChild(node, (child) => visit(child, nextContext));
|
|
970
|
-
}
|
|
971
|
-
visit(sourceFile, "");
|
|
972
|
-
}
|
|
973
|
-
function insertMatches(file, regex, insert) {
|
|
974
|
-
for (const match of file.text.matchAll(regex)) {
|
|
975
|
-
insert(match, lineNumber(file.text, match.index ?? 0));
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
function indexPythonLight(file, statements) {
|
|
979
|
-
const symbolPatterns = [
|
|
980
|
-
[/^\s*def\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "function", (match) => `def ${match[1] ?? ""}(${match[2] ?? ""})`],
|
|
981
|
-
[/^\s*class\s+([A-Za-z_]\w*)/gm, "class", (match) => `class ${match[1] ?? ""}`],
|
|
982
|
-
];
|
|
983
|
-
for (const [regex, kind, signature] of symbolPatterns) {
|
|
984
|
-
insertMatches(file, regex, (match, line) => insertSymbol(statements, match[1] ?? "", kind, file, line, signature(match)));
|
|
985
|
-
}
|
|
986
|
-
const importPatterns = [
|
|
987
|
-
[/^\s*from\s+([A-Za-z0-9_.$]+)\s+import\s+(.+)$/gm, (match) => [match[1] ?? "", match[2] ?? ""]],
|
|
988
|
-
[/^\s*import\s+([A-Za-z0-9_.$,\s]+)$/gm, (match) => [match[1] ?? "", ""]],
|
|
989
|
-
];
|
|
990
|
-
for (const [regex, fields] of importPatterns) {
|
|
991
|
-
insertMatches(file, regex, (match, line) => {
|
|
992
|
-
const [toRef, imported] = fields(match);
|
|
993
|
-
statements.insertImport.run(file.path, toRef, imported.trim(), line, match[0].trim());
|
|
994
|
-
insertEdge(statements, "import", "file", file.path, "module", toRef, file, line, match[0].trim());
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
function insertGoImport(file, statements, toRef, imported, line, raw) {
|
|
999
|
-
if (!toRef)
|
|
1000
|
-
return;
|
|
1001
|
-
statements.insertImport.run(file.path, toRef, imported, line, raw);
|
|
1002
|
-
insertEdge(statements, "import", "file", file.path, "module", toRef, file, line, raw);
|
|
1003
|
-
}
|
|
1004
|
-
function indexGoLight(file, statements) {
|
|
1005
|
-
const symbolPatterns = [
|
|
1006
|
-
[/^\s*func\s*\(\s*[^)]*\)\s*([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "method", (match) => match[1] ?? "", (match) => `func (...) ${match[1] ?? ""}(${match[2] ?? ""})`],
|
|
1007
|
-
[/^\s*func\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "function", (match) => match[1] ?? "", (match) => `func ${match[1] ?? ""}(${match[2] ?? ""})`],
|
|
1008
|
-
[/^\s*type\s+([A-Za-z_]\w*)\s+(struct|interface)?/gm, "type", (match) => match[1] ?? "", (match) => `type ${match[1] ?? ""} ${match[2] ?? ""}`.trim()],
|
|
1009
|
-
[/^\s*const\s+([A-Za-z_]\w*)\b/gm, "constant", (match) => match[1] ?? "", (match) => `const ${match[1] ?? ""}`],
|
|
1010
|
-
[/^\s*var\s+([A-Za-z_]\w*)\b/gm, "variable", (match) => match[1] ?? "", (match) => `var ${match[1] ?? ""}`],
|
|
1011
|
-
];
|
|
1012
|
-
for (const [regex, kind, name, signature] of symbolPatterns) {
|
|
1013
|
-
insertMatches(file, regex, (match, line) => insertSymbol(statements, name(match), kind, file, line, signature(match)));
|
|
1014
|
-
}
|
|
1015
|
-
insertMatches(file, /^\s*import\s+(?:(?:([A-Za-z_]\w*|[_.])\s+)?\"([^\"]+)\"|`([^`]+)`)/gm, (match, line) => {
|
|
1016
|
-
const imported = match[1] ?? "";
|
|
1017
|
-
const toRef = match[2] ?? match[3] ?? "";
|
|
1018
|
-
insertGoImport(file, statements, toRef, imported, line, match[0].trim());
|
|
1019
|
-
});
|
|
1020
|
-
insertMatches(file, /^\s*import\s*\(([\s\S]*?)^\s*\)/gm, (blockMatch) => {
|
|
1021
|
-
const block = blockMatch[1] ?? "";
|
|
1022
|
-
const blockStart = blockMatch.index ?? 0;
|
|
1023
|
-
for (const lineMatch of block.matchAll(/^\s*(?:([A-Za-z_]\w*|[_.])\s+)?\"([^\"]+)\"/gm)) {
|
|
1024
|
-
const imported = lineMatch[1] ?? "";
|
|
1025
|
-
const toRef = lineMatch[2] ?? "";
|
|
1026
|
-
const line = lineNumber(file.text, blockStart + (lineMatch.index ?? 0));
|
|
1027
|
-
insertGoImport(file, statements, toRef, imported, line, lineMatch[0].trim());
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
function indexConfigs(file, insertConfig) {
|
|
1032
|
-
if (path.basename(file.path) === "package.json") {
|
|
1033
|
-
try {
|
|
1034
|
-
const parsed = JSON.parse(file.text);
|
|
1035
|
-
for (const [name, value] of Object.entries(parsed.scripts ?? {}))
|
|
1036
|
-
insertConfig.run(`script:${name}`, value, file.path, 1);
|
|
1037
|
-
for (const [name, value] of Object.entries(parsed.dependencies ?? {}))
|
|
1038
|
-
insertConfig.run(`dependency:${name}`, value, file.path, 1);
|
|
1039
|
-
for (const [name, value] of Object.entries(parsed.devDependencies ?? {}))
|
|
1040
|
-
insertConfig.run(`devDependency:${name}`, value, file.path, 1);
|
|
1041
|
-
}
|
|
1042
|
-
catch {
|
|
1043
|
-
insertConfig.run("parse-error", "package.json is not valid JSON", file.path, 1);
|
|
1044
|
-
}
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
insertMatches(file, /^\s*([A-Za-z0-9_.-]+)\s*[:=]\s*(.+)$/gm, (match, line) => {
|
|
1048
|
-
insertConfig.run(match[1] ?? "", (match[2] ?? "").trim(), file.path, line);
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
155
|
function codeScopes() {
|
|
1052
156
|
const scopes = args_1.codeIndexScopes.length > 0 ? args_1.codeIndexScopes : ["."];
|
|
1053
157
|
return scopes.map((scope) => normalizeProjectRelative(scope, "--code-scope"));
|
|
@@ -1062,59 +166,6 @@ function requireExistingIndex() {
|
|
|
1062
166
|
process.exit(1);
|
|
1063
167
|
}
|
|
1064
168
|
}
|
|
1065
|
-
function readMetaValue(database, key) {
|
|
1066
|
-
const rows = database.prepare("SELECT value FROM meta WHERE key = ?").all(key);
|
|
1067
|
-
const value = rows[0]?.value;
|
|
1068
|
-
return typeof value === "string" ? value : "";
|
|
1069
|
-
}
|
|
1070
|
-
function indexedScopes(database) {
|
|
1071
|
-
const scopesJson = readMetaValue(database, "scopes_json");
|
|
1072
|
-
if (scopesJson) {
|
|
1073
|
-
try {
|
|
1074
|
-
const parsed = JSON.parse(scopesJson);
|
|
1075
|
-
if (Array.isArray(parsed) && parsed.every((scope) => typeof scope === "string"))
|
|
1076
|
-
return parsed;
|
|
1077
|
-
}
|
|
1078
|
-
catch {
|
|
1079
|
-
// Fall back to the legacy comma-separated scope metadata below.
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return readMetaValue(database, "scopes")
|
|
1083
|
-
.split(",")
|
|
1084
|
-
.map((scope) => scope.trim())
|
|
1085
|
-
.filter(Boolean);
|
|
1086
|
-
}
|
|
1087
|
-
function indexedParserMode(database) {
|
|
1088
|
-
const mode = readMetaValue(database, "parser_mode");
|
|
1089
|
-
return mode === "tree-sitter" ? "tree-sitter" : "default";
|
|
1090
|
-
}
|
|
1091
|
-
function scopesMatch(left, right) {
|
|
1092
|
-
return left.length === right.length && left.every((scope, index) => scope === right[index]);
|
|
1093
|
-
}
|
|
1094
|
-
function incrementalCompatibility(database, scopes, parserMode) {
|
|
1095
|
-
const existingSchemaVersion = readMetaValue(database, "schema_version");
|
|
1096
|
-
if (existingSchemaVersion !== codeIndexSchemaVersion) {
|
|
1097
|
-
return {
|
|
1098
|
-
compatible: false,
|
|
1099
|
-
reason: `existing schema version ${existingSchemaVersion || "(missing)"} does not match ${codeIndexSchemaVersion}`,
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
const existingScopes = indexedScopes(database);
|
|
1103
|
-
if (!scopesMatch(existingScopes, scopes)) {
|
|
1104
|
-
return {
|
|
1105
|
-
compatible: false,
|
|
1106
|
-
reason: `indexed scopes do not match requested scopes: indexed [${existingScopes.join(", ")}], requested [${scopes.join(", ")}]`,
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
const existingParserMode = indexedParserMode(database);
|
|
1110
|
-
if (existingParserMode !== parserMode) {
|
|
1111
|
-
return {
|
|
1112
|
-
compatible: false,
|
|
1113
|
-
reason: `indexed parser mode ${existingParserMode} does not match requested parser mode ${parserMode}`,
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
return { compatible: true, reason: "" };
|
|
1117
|
-
}
|
|
1118
169
|
function removeDatabaseFiles(databasePath) {
|
|
1119
170
|
for (const filePath of [databasePath, `${databasePath}-wal`, `${databasePath}-shm`]) {
|
|
1120
171
|
if (fs.existsSync(filePath))
|
|
@@ -1122,27 +173,30 @@ function removeDatabaseFiles(databasePath) {
|
|
|
1122
173
|
}
|
|
1123
174
|
}
|
|
1124
175
|
function codeIndexStaleness(database) {
|
|
1125
|
-
const scopes = indexedScopes(database);
|
|
1126
|
-
const parserMode = indexedParserMode(database);
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const currentHash = current.get(filePath);
|
|
1136
|
-
if (!currentHash)
|
|
1137
|
-
deleted += 1;
|
|
1138
|
-
else if (currentHash !== hash)
|
|
1139
|
-
changed += 1;
|
|
1140
|
-
}
|
|
176
|
+
const scopes = (0, schema_1.indexedScopes)(database);
|
|
177
|
+
const parserMode = (0, schema_1.indexedParserMode)(database);
|
|
178
|
+
const currentFiles = (0, code_index_file_policy_1.discoverCodeFiles)(scopes.length > 0 ? scopes : ["."]).map(readCodeFileFingerprint);
|
|
179
|
+
const currentPaths = new Set(currentFiles.map((file) => file.path));
|
|
180
|
+
const indexedRows = database.prepare("SELECT path, hash, mtime_ms, size FROM files").all();
|
|
181
|
+
const indexed = new Map(indexedRows.map((row) => [String(row.path), {
|
|
182
|
+
hash: String(row.hash),
|
|
183
|
+
mtimeMs: Number(row.mtime_ms),
|
|
184
|
+
size: Number(row.size),
|
|
185
|
+
}]));
|
|
1141
186
|
let added = 0;
|
|
1142
|
-
|
|
1143
|
-
|
|
187
|
+
let changed = 0;
|
|
188
|
+
for (const file of currentFiles) {
|
|
189
|
+
const existing = indexed.get(file.path);
|
|
190
|
+
if (!existing) {
|
|
1144
191
|
added += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (existing.mtimeMs === file.mtimeMs && existing.size === file.size)
|
|
195
|
+
continue;
|
|
196
|
+
if (readCodeFile(file.path, parserMode).hash !== existing.hash)
|
|
197
|
+
changed += 1;
|
|
1145
198
|
}
|
|
199
|
+
const deleted = indexedRows.filter((row) => !currentPaths.has(String(row.path))).length;
|
|
1146
200
|
return {
|
|
1147
201
|
added,
|
|
1148
202
|
changed,
|
|
@@ -1150,499 +204,143 @@ function codeIndexStaleness(database) {
|
|
|
1150
204
|
stale: added > 0 || changed > 0 || deleted > 0,
|
|
1151
205
|
};
|
|
1152
206
|
}
|
|
1153
|
-
function
|
|
1154
|
-
const
|
|
207
|
+
function codeIndexHealth() {
|
|
208
|
+
const databasePath = codeEvidenceDatabasePath();
|
|
209
|
+
return (0, index_health_1.inspectCodeIndexHealth)({
|
|
210
|
+
absolutePath: databasePath.absolutePath,
|
|
211
|
+
defaultScopes: codeScopes(),
|
|
212
|
+
discoverCodeFiles: code_index_file_policy_1.discoverCodeFiles,
|
|
213
|
+
expectedSchemaVersion: schema_1.codeIndexSchemaVersion,
|
|
214
|
+
openDatabase,
|
|
215
|
+
relativePath: databasePath.relativePath,
|
|
216
|
+
smallRepoThreshold: code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function warnIfCodeIndexStale(database, staleness = codeIndexStaleness(database)) {
|
|
1155
220
|
if (!staleness.stale)
|
|
1156
221
|
return;
|
|
1157
222
|
console.error(`code evidence index may be stale: ${staleness.changed} changed, ${staleness.added} added, ${staleness.deleted} deleted; rerun --code-index`);
|
|
1158
223
|
}
|
|
1159
|
-
function
|
|
1160
|
-
const parts = (0, workspace_1.normalizePath)(filePath).split("/").filter(Boolean);
|
|
1161
|
-
if (parts.length === 0)
|
|
1162
|
-
return ".";
|
|
1163
|
-
if (["apps", "libs", "packages", "services"].includes(parts[0] ?? "") && parts[1])
|
|
1164
|
-
return `${parts[0]}/${parts[1]}`;
|
|
1165
|
-
return parts[0] ?? ".";
|
|
1166
|
-
}
|
|
1167
|
-
function readJsonObject(relativePath) {
|
|
1168
|
-
try {
|
|
1169
|
-
const parsed = JSON.parse(fs.readFileSync((0, workspace_1.abs)(relativePath), "utf8"));
|
|
1170
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1171
|
-
}
|
|
1172
|
-
catch {
|
|
1173
|
-
return null;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
function workspacePatternsFromRootPackage() {
|
|
1177
|
-
const rootPackage = readJsonObject("package.json");
|
|
1178
|
-
const workspaces = rootPackage?.workspaces;
|
|
1179
|
-
if (Array.isArray(workspaces))
|
|
1180
|
-
return workspaces.filter((value) => typeof value === "string");
|
|
1181
|
-
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
1182
|
-
const packages = workspaces.packages;
|
|
1183
|
-
if (Array.isArray(packages))
|
|
1184
|
-
return packages.filter((value) => typeof value === "string");
|
|
1185
|
-
}
|
|
1186
|
-
return [];
|
|
1187
|
-
}
|
|
1188
|
-
function workspacePatternCandidates(pattern) {
|
|
1189
|
-
const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1190
|
-
if (!normalized || normalized.includes(".."))
|
|
1191
|
-
return [];
|
|
1192
|
-
if (!normalized.includes("*"))
|
|
1193
|
-
return [normalized];
|
|
1194
|
-
const starIndex = normalized.indexOf("*");
|
|
1195
|
-
const prefix = normalized.slice(0, starIndex).replace(/\/+$/, "");
|
|
1196
|
-
const suffix = normalized.slice(starIndex + 1).replace(/^\/+/, "");
|
|
1197
|
-
const base = prefix || ".";
|
|
1198
|
-
const basePath = (0, workspace_1.abs)(base);
|
|
1199
|
-
if (!fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory())
|
|
1200
|
-
return [];
|
|
1201
|
-
return fs.readdirSync(basePath, { withFileTypes: true })
|
|
1202
|
-
.filter((entry) => entry.isDirectory())
|
|
1203
|
-
.map((entry) => (0, workspace_1.normalizePath)(path.join(base, entry.name, suffix)))
|
|
1204
|
-
.filter((candidate) => fs.existsSync((0, workspace_1.abs)(candidate)) && fs.statSync((0, workspace_1.abs)(candidate)).isDirectory());
|
|
1205
|
-
}
|
|
1206
|
-
function workspacePackages() {
|
|
1207
|
-
const packages = new Map();
|
|
1208
|
-
for (const pattern of workspacePatternsFromRootPackage()) {
|
|
1209
|
-
for (const candidate of workspacePatternCandidates(pattern)) {
|
|
1210
|
-
const packageJsonPath = (0, workspace_1.normalizePath)(path.join(candidate, "package.json"));
|
|
1211
|
-
if (!fs.existsSync((0, workspace_1.abs)(packageJsonPath)))
|
|
1212
|
-
continue;
|
|
1213
|
-
const packageJson = readJsonObject(packageJsonPath);
|
|
1214
|
-
const packageName = typeof packageJson?.name === "string" ? packageJson.name : candidate;
|
|
1215
|
-
packages.set(candidate, {
|
|
1216
|
-
name: packageName,
|
|
1217
|
-
root: candidate,
|
|
1218
|
-
source: "package.json workspaces",
|
|
1219
|
-
workspace_pattern: pattern,
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
return Array.from(packages.values()).sort((left, right) => left.root.localeCompare(right.root));
|
|
1224
|
-
}
|
|
1225
|
-
function matchingWorkspace(filePath, workspaces) {
|
|
1226
|
-
const normalized = (0, workspace_1.normalizePath)(filePath);
|
|
1227
|
-
return workspaces
|
|
1228
|
-
.filter((workspace) => normalized === workspace.root || normalized.startsWith(`${workspace.root}/`))
|
|
1229
|
-
.sort((left, right) => right.root.length - left.root.length)[0] ?? null;
|
|
1230
|
-
}
|
|
1231
|
-
function codeownerRules() {
|
|
1232
|
-
const files = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"];
|
|
1233
|
-
const rules = [];
|
|
1234
|
-
for (const filePath of files) {
|
|
1235
|
-
if (!fs.existsSync((0, workspace_1.abs)(filePath)))
|
|
1236
|
-
continue;
|
|
1237
|
-
const lines = fs.readFileSync((0, workspace_1.abs)(filePath), "utf8").split(/\r?\n/);
|
|
1238
|
-
lines.forEach((lineText, index) => {
|
|
1239
|
-
const trimmed = lineText.trim();
|
|
1240
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
1241
|
-
return;
|
|
1242
|
-
const parts = trimmed.split(/\s+/);
|
|
1243
|
-
const pattern = parts[0] ?? "";
|
|
1244
|
-
const owners = parts.slice(1);
|
|
1245
|
-
if (!pattern || owners.length === 0)
|
|
1246
|
-
return;
|
|
1247
|
-
rules.push({ file_path: filePath, line: index + 1, owners, pattern });
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
return rules;
|
|
1251
|
-
}
|
|
1252
|
-
function codeownerPatternRegex(pattern) {
|
|
1253
|
-
const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "");
|
|
1254
|
-
const source = normalized
|
|
1255
|
-
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
1256
|
-
.replace(/\*\*/g, ".*")
|
|
1257
|
-
.replace(/\*/g, "[^/]*");
|
|
1258
|
-
if (normalized.endsWith("/"))
|
|
1259
|
-
return new RegExp(`^${source}.*$`);
|
|
1260
|
-
return new RegExp(`^${source}(?:/.*)?$`);
|
|
1261
|
-
}
|
|
1262
|
-
function codeownerPatternMatches(pattern, filePath) {
|
|
1263
|
-
const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "");
|
|
1264
|
-
const target = (0, workspace_1.normalizePath)(filePath);
|
|
1265
|
-
if (normalized === "*")
|
|
1266
|
-
return true;
|
|
1267
|
-
if (normalized.startsWith("*."))
|
|
1268
|
-
return path.basename(target).endsWith(normalized.slice(1));
|
|
1269
|
-
return codeownerPatternRegex(normalized).test(target);
|
|
1270
|
-
}
|
|
1271
|
-
function matchingCodeowners(filePath, rules) {
|
|
1272
|
-
const matches = rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
|
|
1273
|
-
return matches[matches.length - 1]?.owners ?? [];
|
|
1274
|
-
}
|
|
1275
|
-
// Return every CODEOWNERS rule that matches a path, in file order. The last entry
|
|
1276
|
-
// is the effective owner under last-match-wins; earlier entries are overridden.
|
|
1277
|
-
// Reuses the same matcher as matchingCodeowners so precedence answers stay
|
|
1278
|
-
// consistent with --code-report / --code-impact.
|
|
1279
|
-
function matchedCodeownerRules(filePath, rules) {
|
|
1280
|
-
return rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
|
|
1281
|
-
}
|
|
1282
|
-
function ownershipContext() {
|
|
1283
|
-
return {
|
|
1284
|
-
codeownerRules: codeownerRules(),
|
|
1285
|
-
workspaces: workspacePackages(),
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
function ownershipInfo(filePath, context) {
|
|
1289
|
-
const workspace = matchingWorkspace(filePath, context.workspaces);
|
|
1290
|
-
const owners = matchingCodeowners(filePath, context.codeownerRules);
|
|
1291
|
-
return {
|
|
1292
|
-
codeowners: owners.join(", "),
|
|
1293
|
-
owner: workspace?.root ?? pathOwnerKey(filePath),
|
|
1294
|
-
owner_source: workspace ? "workspace" : "path",
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
function incrementOwnerField(owners, context, filePath, field, increment = 1) {
|
|
1298
|
-
const info = ownershipInfo(filePath, context);
|
|
1299
|
-
const key = info.owner;
|
|
1300
|
-
const current = owners.get(key) ?? {
|
|
1301
|
-
bytes: 0,
|
|
1302
|
-
codeowners: info.codeowners,
|
|
1303
|
-
configs: 0,
|
|
1304
|
-
file_count: 0,
|
|
1305
|
-
imports: 0,
|
|
1306
|
-
languages: "",
|
|
1307
|
-
lines: 0,
|
|
1308
|
-
owner: key,
|
|
1309
|
-
owner_source: info.owner_source,
|
|
1310
|
-
routes: 0,
|
|
1311
|
-
symbols: 0,
|
|
1312
|
-
};
|
|
1313
|
-
if (info.codeowners && !current.codeowners.split(", ").includes(info.codeowners))
|
|
1314
|
-
current.codeowners = current.codeowners ? `${current.codeowners}; ${info.codeowners}` : info.codeowners;
|
|
1315
|
-
current[field] += increment;
|
|
1316
|
-
owners.set(key, current);
|
|
1317
|
-
}
|
|
1318
|
-
function evidenceCoverage(database) {
|
|
1319
|
-
const rows = database.prepare(`
|
|
1320
|
-
SELECT 'files' AS table_name, count(*) AS rows FROM files
|
|
1321
|
-
UNION ALL SELECT 'symbols', count(*) FROM symbols
|
|
1322
|
-
UNION ALL SELECT 'imports', count(*) FROM imports
|
|
1323
|
-
UNION ALL SELECT 'routes', count(*) FROM routes
|
|
1324
|
-
UNION ALL SELECT 'configs', count(*) FROM configs
|
|
1325
|
-
UNION ALL SELECT 'edges', count(*) FROM edges
|
|
1326
|
-
`).all();
|
|
1327
|
-
return Object.fromEntries(rows.map((row) => [String(row.table_name), Number(row.rows ?? 0)]));
|
|
1328
|
-
}
|
|
1329
|
-
function ownershipSummary(database) {
|
|
1330
|
-
const files = database.prepare("SELECT path, language, profile, lines, bytes FROM files ORDER BY path").all();
|
|
1331
|
-
const context = ownershipContext();
|
|
1332
|
-
const owners = new Map();
|
|
1333
|
-
const ownerLanguages = new Map();
|
|
1334
|
-
for (const row of files) {
|
|
1335
|
-
const filePath = String(row.path);
|
|
1336
|
-
const key = ownershipInfo(filePath, context).owner;
|
|
1337
|
-
incrementOwnerField(owners, context, filePath, "file_count");
|
|
1338
|
-
incrementOwnerField(owners, context, filePath, "lines", Number(row.lines ?? 0));
|
|
1339
|
-
incrementOwnerField(owners, context, filePath, "bytes", Number(row.bytes ?? 0));
|
|
1340
|
-
const languages = ownerLanguages.get(key) ?? new Set();
|
|
1341
|
-
languages.add(String(row.language));
|
|
1342
|
-
ownerLanguages.set(key, languages);
|
|
1343
|
-
}
|
|
1344
|
-
for (const row of database.prepare("SELECT file_path, count(*) AS count FROM symbols GROUP BY file_path").all())
|
|
1345
|
-
incrementOwnerField(owners, context, String(row.file_path), "symbols", Number(row.count ?? 0));
|
|
1346
|
-
for (const row of database.prepare("SELECT file_path, count(*) AS count FROM routes GROUP BY file_path").all())
|
|
1347
|
-
incrementOwnerField(owners, context, String(row.file_path), "routes", Number(row.count ?? 0));
|
|
1348
|
-
for (const row of database.prepare("SELECT from_file, count(*) AS count FROM imports GROUP BY from_file").all())
|
|
1349
|
-
incrementOwnerField(owners, context, String(row.from_file), "imports", Number(row.count ?? 0));
|
|
1350
|
-
for (const row of database.prepare("SELECT file_path, count(*) AS count FROM configs GROUP BY file_path").all())
|
|
1351
|
-
incrementOwnerField(owners, context, String(row.file_path), "configs", Number(row.count ?? 0));
|
|
1352
|
-
return Array.from(owners.values()).map((owner) => ({
|
|
1353
|
-
...owner,
|
|
1354
|
-
languages: Array.from(ownerLanguages.get(String(owner.owner)) ?? []).sort().join(", "),
|
|
1355
|
-
})).sort((left, right) => right.file_count - left.file_count || left.owner.localeCompare(right.owner)).slice(0, 25);
|
|
1356
|
-
}
|
|
1357
|
-
function languageProfileSummary(database) {
|
|
1358
|
-
return database.prepare("SELECT language, profile, count(*) AS files, sum(lines) AS lines, sum(bytes) AS bytes FROM files GROUP BY language, profile ORDER BY files DESC, language").all();
|
|
1359
|
-
}
|
|
1360
|
-
function parserBackendSummary(database) {
|
|
1361
|
-
return database.prepare("SELECT language, profile, count(*) AS files, sum(lines) AS lines, sum(bytes) AS bytes FROM files GROUP BY language, profile ORDER BY files DESC, language").all().map((row) => {
|
|
1362
|
-
const profile = String(row.profile);
|
|
1363
|
-
const backend = extractionBackendForProfile(profile);
|
|
1364
|
-
return {
|
|
1365
|
-
language: row.language,
|
|
1366
|
-
profile,
|
|
1367
|
-
backend: backend.id,
|
|
1368
|
-
label: backend.label,
|
|
1369
|
-
extraction_strength: backend.strength,
|
|
1370
|
-
files: row.files,
|
|
1371
|
-
lines: row.lines,
|
|
1372
|
-
bytes: row.bytes,
|
|
1373
|
-
};
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
function workspaceSummary(database) {
|
|
1377
|
-
const context = ownershipContext();
|
|
1378
|
-
const counts = new Map();
|
|
1379
|
-
for (const workspace of context.workspaces) {
|
|
1380
|
-
counts.set(workspace.root, { ...workspace, bytes: 0, files: 0, lines: 0 });
|
|
1381
|
-
}
|
|
1382
|
-
for (const row of database.prepare("SELECT path, lines, bytes FROM files ORDER BY path").all()) {
|
|
1383
|
-
const workspace = matchingWorkspace(String(row.path), context.workspaces);
|
|
1384
|
-
if (!workspace)
|
|
1385
|
-
continue;
|
|
1386
|
-
const current = counts.get(workspace.root) ?? { ...workspace, bytes: 0, files: 0, lines: 0 };
|
|
1387
|
-
current.files += 1;
|
|
1388
|
-
current.lines += Number(row.lines ?? 0);
|
|
1389
|
-
current.bytes += Number(row.bytes ?? 0);
|
|
1390
|
-
counts.set(workspace.root, current);
|
|
1391
|
-
}
|
|
1392
|
-
return {
|
|
1393
|
-
workspace_packages: Array.from(counts.values()).sort((left, right) => left.root.localeCompare(right.root)),
|
|
1394
|
-
codeowners: context.codeownerRules.map((rule) => ({
|
|
1395
|
-
file_path: rule.file_path,
|
|
1396
|
-
line: rule.line,
|
|
1397
|
-
pattern: rule.pattern,
|
|
1398
|
-
owners: rule.owners.join(", "),
|
|
1399
|
-
})),
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
function packageManagerFromLockfile(filePath) {
|
|
1403
|
-
const base = path.basename(filePath);
|
|
1404
|
-
if (base === "package-lock.json" || base === "npm-shrinkwrap.json")
|
|
1405
|
-
return "npm";
|
|
1406
|
-
if (base === "pnpm-lock.yaml")
|
|
1407
|
-
return "pnpm";
|
|
1408
|
-
if (base === "yarn.lock")
|
|
1409
|
-
return "yarn";
|
|
1410
|
-
if (base === "bun.lockb" || base === "bun.lock")
|
|
1411
|
-
return "bun";
|
|
1412
|
-
return "unknown";
|
|
1413
|
-
}
|
|
1414
|
-
function workspaceDependencyGraph() {
|
|
1415
|
-
const workspaces = workspacePackages();
|
|
1416
|
-
const byName = new Map(workspaces.map((workspace) => [workspace.name, workspace]));
|
|
1417
|
-
const lockfiles = ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"]
|
|
1418
|
-
.filter((filePath) => fs.existsSync((0, workspace_1.abs)(filePath)))
|
|
1419
|
-
.map((filePath) => ({ file_path: filePath, package_manager: packageManagerFromLockfile(filePath), scope: "root" }));
|
|
1420
|
-
const workspaceRows = [];
|
|
1421
|
-
const internalEdges = [];
|
|
1422
|
-
const externalDependencies = new Map();
|
|
1423
|
-
for (const workspace of workspaces) {
|
|
1424
|
-
const packageJsonPath = (0, workspace_1.normalizePath)(path.join(workspace.root, "package.json"));
|
|
1425
|
-
const packageJson = readJsonObject(packageJsonPath);
|
|
1426
|
-
const dependencyFields = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
1427
|
-
const dependencyCounts = {};
|
|
1428
|
-
const workspaceInternalEdges = [];
|
|
1429
|
-
for (const field of dependencyFields) {
|
|
1430
|
-
const dependencies = packageJson?.[field];
|
|
1431
|
-
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies))
|
|
1432
|
-
continue;
|
|
1433
|
-
for (const [dependencyName, version] of Object.entries(dependencies)) {
|
|
1434
|
-
dependencyCounts[field] = (dependencyCounts[field] ?? 0) + 1;
|
|
1435
|
-
const target = byName.get(dependencyName);
|
|
1436
|
-
if (target) {
|
|
1437
|
-
const edge = {
|
|
1438
|
-
from_workspace: workspace.root,
|
|
1439
|
-
from_package: workspace.name,
|
|
1440
|
-
to_workspace: target.root,
|
|
1441
|
-
to_package: target.name,
|
|
1442
|
-
dependency_type: field,
|
|
1443
|
-
version: typeof version === "string" ? version : String(version),
|
|
1444
|
-
};
|
|
1445
|
-
internalEdges.push(edge);
|
|
1446
|
-
workspaceInternalEdges.push(edge);
|
|
1447
|
-
}
|
|
1448
|
-
else {
|
|
1449
|
-
const key = `${dependencyName}\0${field}`;
|
|
1450
|
-
const current = externalDependencies.get(key) ?? { dependency: dependencyName, dependency_type: field, workspaces: new Set() };
|
|
1451
|
-
current.workspaces.add(workspace.root);
|
|
1452
|
-
externalDependencies.set(key, current);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
for (const lockfileName of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"]) {
|
|
1457
|
-
const lockfilePath = (0, workspace_1.normalizePath)(path.join(workspace.root, lockfileName));
|
|
1458
|
-
if (fs.existsSync((0, workspace_1.abs)(lockfilePath))) {
|
|
1459
|
-
lockfiles.push({ file_path: lockfilePath, package_manager: packageManagerFromLockfile(lockfilePath), scope: workspace.root });
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
workspaceRows.push({
|
|
1463
|
-
name: workspace.name,
|
|
1464
|
-
root: workspace.root,
|
|
1465
|
-
dependency_counts: dependencyCounts,
|
|
1466
|
-
internal_dependency_count: workspaceInternalEdges.length,
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
return {
|
|
1470
|
-
workspace_count: workspaces.length,
|
|
1471
|
-
package_managers: Array.from(new Set(lockfiles.map((lockfile) => lockfile.package_manager))).sort(),
|
|
1472
|
-
lockfiles,
|
|
1473
|
-
workspaces: workspaceRows.sort((left, right) => String(left.root).localeCompare(String(right.root))),
|
|
1474
|
-
internal_dependencies: internalEdges.sort((left, right) => String(left.from_workspace).localeCompare(String(right.from_workspace)) || String(left.to_workspace).localeCompare(String(right.to_workspace))),
|
|
1475
|
-
external_dependency_hotspots: Array.from(externalDependencies.values()).map((entry) => ({
|
|
1476
|
-
dependency: entry.dependency,
|
|
1477
|
-
dependency_type: entry.dependency_type,
|
|
1478
|
-
workspace_count: entry.workspaces.size,
|
|
1479
|
-
workspaces: Array.from(entry.workspaces).sort().join(", "),
|
|
1480
|
-
})).sort((left, right) => right.workspace_count - left.workspace_count || left.dependency.localeCompare(right.dependency)).slice(0, 100),
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
function routeInventory(database) {
|
|
1484
|
-
return database.prepare("SELECT method, route, file_path, line, handler FROM routes ORDER BY file_path, line LIMIT 100").all();
|
|
1485
|
-
}
|
|
1486
|
-
function dependencyHotspots(database) {
|
|
1487
|
-
return {
|
|
1488
|
-
imports: database.prepare("SELECT to_ref, count(DISTINCT from_file) AS importing_files, count(*) AS reference_count FROM imports GROUP BY to_ref ORDER BY importing_files DESC, reference_count DESC, to_ref LIMIT 50").all(),
|
|
1489
|
-
package_dependencies: database.prepare("SELECT substr(key, 12) AS package, value AS version, file_path FROM configs WHERE key LIKE 'dependency:%' ORDER BY file_path, package LIMIT 100").all(),
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
function configInventory(database) {
|
|
1493
|
-
return database.prepare("SELECT key, value, file_path, line FROM configs WHERE key LIKE 'script:%' OR key LIKE 'dependency:%' OR key LIKE 'devDependency:%' ORDER BY file_path, key LIMIT 150").all();
|
|
1494
|
-
}
|
|
1495
|
-
function edgeSummary(database) {
|
|
1496
|
-
return {
|
|
1497
|
-
by_kind: database.prepare("SELECT kind, count(*) AS edges FROM edges GROUP BY kind ORDER BY edges DESC, kind").all(),
|
|
1498
|
-
fanout: database.prepare("SELECT source_kind, source, kind, count(DISTINCT target) AS targets, file_path FROM edges GROUP BY source_kind, source, kind, file_path ORDER BY targets DESC, source LIMIT 50").all(),
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
function codeReportSectionData(database, section) {
|
|
1502
|
-
switch (section) {
|
|
1503
|
-
case "coverage":
|
|
1504
|
-
return evidenceCoverage(database);
|
|
1505
|
-
case "ownership":
|
|
1506
|
-
return ownershipSummary(database);
|
|
1507
|
-
case "languages":
|
|
1508
|
-
return languageProfileSummary(database);
|
|
1509
|
-
case "parsers":
|
|
1510
|
-
return parserBackendSummary(database);
|
|
1511
|
-
case "workspaces":
|
|
1512
|
-
return workspaceSummary(database);
|
|
1513
|
-
case "workspace-graph":
|
|
1514
|
-
return workspaceDependencyGraph();
|
|
1515
|
-
case "routes":
|
|
1516
|
-
return routeInventory(database);
|
|
1517
|
-
case "hotspots":
|
|
1518
|
-
return dependencyHotspots(database);
|
|
1519
|
-
case "configs":
|
|
1520
|
-
return configInventory(database);
|
|
1521
|
-
case "edges":
|
|
1522
|
-
return edgeSummary(database);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
function codeReportMetadata(database) {
|
|
224
|
+
function codeReportRuntime(database, options = {}) {
|
|
1526
225
|
const databasePath = codeEvidenceDatabasePath();
|
|
1527
|
-
const staleness = codeIndexStaleness(database);
|
|
1528
226
|
return {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
added: staleness.added,
|
|
1538
|
-
deleted: staleness.deleted,
|
|
227
|
+
databaseRelativePath: databasePath.relativePath,
|
|
228
|
+
parserBackendForProfile: (profile) => {
|
|
229
|
+
const backend = extractionBackendForProfile(profile);
|
|
230
|
+
return {
|
|
231
|
+
id: backend.id,
|
|
232
|
+
label: backend.label,
|
|
233
|
+
strength: backend.strength,
|
|
234
|
+
};
|
|
1539
235
|
},
|
|
236
|
+
staleness: options.staleness ?? codeIndexStaleness(database),
|
|
1540
237
|
};
|
|
1541
238
|
}
|
|
1542
|
-
function
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
function selectedCodeReportSection() {
|
|
1559
|
-
const requested = args_1.codeReportSection.trim().toLowerCase();
|
|
1560
|
-
if (!requested || requested === "all" || requested === "full")
|
|
1561
|
-
return "";
|
|
1562
|
-
const section = codeReportSectionAliases[requested];
|
|
1563
|
-
if (!section) {
|
|
1564
|
-
const valid = ["coverage", "ownership", "languages", "parsers", "workspaces", "workspace-graph", "routes", "hotspots", "configs", "edges"].join(", ");
|
|
1565
|
-
fail(`invalid --code-report-section: ${args_1.codeReportSection}; expected one of: ${valid}`);
|
|
1566
|
-
}
|
|
1567
|
-
return section;
|
|
1568
|
-
}
|
|
1569
|
-
function codeReportForRequestedSection(database) {
|
|
1570
|
-
const section = selectedCodeReportSection();
|
|
1571
|
-
if (!section)
|
|
1572
|
-
return codeReport(database);
|
|
1573
|
-
return {
|
|
1574
|
-
...codeReportMetadata(database),
|
|
1575
|
-
section,
|
|
1576
|
-
data: codeReportSectionData(database, section),
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
function codeImpact(database, target) {
|
|
1580
|
-
const like = `%${target}%`;
|
|
1581
|
-
const fileMatches = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ORDER BY path LIMIT 25").all(like);
|
|
1582
|
-
const symbolMatches = database.prepare("SELECT name, kind, file_path, line, signature FROM symbols WHERE name LIKE ? OR signature LIKE ? OR file_path LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like, like);
|
|
1583
|
-
const routeMatches = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? OR handler LIKE ? OR file_path LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like, like);
|
|
1584
|
-
const importMatches = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? OR to_ref LIKE ? OR imported LIKE ? ORDER BY from_file, line LIMIT 75").all(like, like, like);
|
|
1585
|
-
const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? OR source LIKE ? ORDER BY file_path, line LIMIT 100").all(like, like);
|
|
1586
|
-
const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ORDER BY file_path, line LIMIT 100").all(like);
|
|
1587
|
-
const routeTargets = routeMatches.map((row) => `${String(row.method)} ${String(row.route)}`);
|
|
1588
|
-
const routeEdges = routeTargets.length === 0 ? [] : database.prepare(`SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE source IN (${routeTargets.map(() => "?").join(", ")}) ORDER BY file_path, line LIMIT 100`).all(...routeTargets);
|
|
1589
|
-
const relatedFilePaths = Array.from(new Set([
|
|
1590
|
-
...fileMatches.map((row) => String(row.path)),
|
|
1591
|
-
...symbolMatches.map((row) => String(row.file_path)),
|
|
1592
|
-
...routeMatches.map((row) => String(row.file_path)),
|
|
1593
|
-
...importMatches.map((row) => String(row.from_file)),
|
|
1594
|
-
...outgoingEdges.map((row) => String(row.file_path)),
|
|
1595
|
-
...incomingEdges.map((row) => String(row.file_path)),
|
|
1596
|
-
...routeEdges.map((row) => String(row.file_path)),
|
|
1597
|
-
].filter(Boolean))).sort();
|
|
1598
|
-
const ownership = ownershipContext();
|
|
1599
|
-
const impactedOwners = new Map();
|
|
1600
|
-
for (const filePath of relatedFilePaths) {
|
|
1601
|
-
const info = ownershipInfo(filePath, ownership);
|
|
1602
|
-
const current = impactedOwners.get(info.owner) ?? {
|
|
1603
|
-
codeowners: new Set(),
|
|
1604
|
-
files: 0,
|
|
1605
|
-
owner: info.owner,
|
|
1606
|
-
owner_source: info.owner_source,
|
|
1607
|
-
sample_files: [],
|
|
1608
|
-
};
|
|
1609
|
-
current.files += 1;
|
|
1610
|
-
if (current.sample_files.length < 10)
|
|
1611
|
-
current.sample_files.push(filePath);
|
|
1612
|
-
if (info.codeowners) {
|
|
1613
|
-
for (const owner of info.codeowners.split(", ").filter(Boolean))
|
|
1614
|
-
current.codeowners.add(owner);
|
|
1615
|
-
}
|
|
1616
|
-
impactedOwners.set(info.owner, current);
|
|
1617
|
-
}
|
|
239
|
+
function codeImpact(database, target, options = {}) {
|
|
240
|
+
const normalized = target.trim();
|
|
241
|
+
const evidence = (0, evidence_1.collectCodeEvidence)(database, normalized, {
|
|
242
|
+
edgeLimit: 100,
|
|
243
|
+
fileLimit: 25,
|
|
244
|
+
includeEdgeEvidenceMatches: false,
|
|
245
|
+
includeOwnerCodeowners: true,
|
|
246
|
+
includeRouteEdges: true,
|
|
247
|
+
importLimit: 75,
|
|
248
|
+
ownerSampleLimit: 10,
|
|
249
|
+
routeEdgeLimit: 100,
|
|
250
|
+
routeLimit: 50,
|
|
251
|
+
symbolLimit: 50,
|
|
252
|
+
});
|
|
1618
253
|
return {
|
|
1619
|
-
...codeReportMetadata(database),
|
|
254
|
+
...(0, reports_1.codeReportMetadata)(database, codeReportRuntime(database, options)),
|
|
1620
255
|
target,
|
|
1621
256
|
matches: {
|
|
1622
|
-
files:
|
|
1623
|
-
symbols:
|
|
1624
|
-
routes:
|
|
1625
|
-
imports:
|
|
257
|
+
files: evidence.files,
|
|
258
|
+
symbols: evidence.symbols,
|
|
259
|
+
routes: evidence.routes,
|
|
260
|
+
imports: evidence.imports,
|
|
1626
261
|
},
|
|
1627
262
|
edges: {
|
|
1628
|
-
outgoing: outgoingEdges,
|
|
1629
|
-
incoming: incomingEdges,
|
|
1630
|
-
routes: routeEdges,
|
|
263
|
+
outgoing: evidence.outgoingEdges,
|
|
264
|
+
incoming: evidence.incomingEdges,
|
|
265
|
+
routes: evidence.routeEdges,
|
|
1631
266
|
},
|
|
1632
|
-
impacted_owners:
|
|
1633
|
-
owner: owner.owner,
|
|
1634
|
-
owner_source: owner.owner_source,
|
|
1635
|
-
files: owner.files,
|
|
1636
|
-
codeowners: Array.from(owner.codeowners).sort().join(", "),
|
|
1637
|
-
sample_files: owner.sample_files,
|
|
1638
|
-
})).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner)),
|
|
267
|
+
impacted_owners: evidence.owners,
|
|
1639
268
|
};
|
|
1640
269
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
return
|
|
270
|
+
function sampleLines(items, limit, render) {
|
|
271
|
+
const lines = items.slice(0, limit).map(render);
|
|
272
|
+
if (items.length > limit)
|
|
273
|
+
lines.push(` ...+${items.length - limit} more`);
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
function pushBudgetedLine(lines, line) {
|
|
277
|
+
const candidate = [...lines, line].join("\n");
|
|
278
|
+
if (candidate.length > exports.codeContextPackCharCap)
|
|
279
|
+
return false;
|
|
280
|
+
lines.push(line);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
function pushBudgetedSection(lines, title, items, limit, render) {
|
|
284
|
+
if (items.length === 0)
|
|
285
|
+
return;
|
|
286
|
+
if (!pushBudgetedLine(lines, title))
|
|
287
|
+
return;
|
|
288
|
+
for (const line of sampleLines(items, limit, render)) {
|
|
289
|
+
if (!pushBudgetedLine(lines, line)) {
|
|
290
|
+
pushBudgetedLine(lines, " ...more omitted; refine the query");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function finalizeCodeContextPack(body) {
|
|
296
|
+
if (body.length <= exports.codeContextPackCharCap)
|
|
297
|
+
return body;
|
|
298
|
+
const budget = exports.codeContextPackCharCap - exports.codeContextPackTruncationNotice.length - 1;
|
|
299
|
+
return `${body.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${exports.codeContextPackTruncationNotice}`;
|
|
300
|
+
}
|
|
301
|
+
function codeContextScaleLine(fileCount) {
|
|
302
|
+
return fileCount < code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD
|
|
303
|
+
? `scale small (${fileCount} indexed files < ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); direct reads are usually cheaper for simple lookups`
|
|
304
|
+
: `scale large (${fileCount} indexed files >= ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); indexed traversal is useful for impact-style context`;
|
|
305
|
+
}
|
|
306
|
+
function structuralSignature(value) {
|
|
307
|
+
const signature = (0, shared_1.oneLine)(String(value ?? ""));
|
|
308
|
+
const bodyStart = signature.indexOf("{");
|
|
309
|
+
return bodyStart >= 0 ? signature.slice(0, bodyStart).trimEnd() : signature;
|
|
310
|
+
}
|
|
311
|
+
function codeContextPack(database, query, options = {}) {
|
|
312
|
+
const normalized = query.trim();
|
|
313
|
+
if (!normalized)
|
|
314
|
+
return 'Code context pack: missing query; use --code-context-pack "path-or-symbol-or-route".';
|
|
315
|
+
const evidence = (0, evidence_1.collectCodeEvidence)(database, normalized, {
|
|
316
|
+
edgeLimit: 30,
|
|
317
|
+
fileLimit: 12,
|
|
318
|
+
includeEdgeEvidenceMatches: true,
|
|
319
|
+
includeOwnerCodeowners: false,
|
|
320
|
+
includeRouteEdges: false,
|
|
321
|
+
importLimit: 30,
|
|
322
|
+
ownerSampleLimit: 4,
|
|
323
|
+
routeEdgeLimit: 0,
|
|
324
|
+
routeLimit: 20,
|
|
325
|
+
symbolLimit: 20,
|
|
326
|
+
});
|
|
327
|
+
const staleness = options.staleness ?? codeIndexStaleness(database);
|
|
328
|
+
const coverage = (0, reports_1.evidenceCoverage)(database);
|
|
329
|
+
const staleLabel = staleness.stale
|
|
330
|
+
? `STALE ${staleness.changed} changed, ${staleness.added} added, ${staleness.deleted} deleted`
|
|
331
|
+
: "fresh";
|
|
332
|
+
const lines = [
|
|
333
|
+
`Code context pack "${normalized}": ${evidence.files.length} file matches, ${evidence.symbols.length} symbols, ${evidence.routes.length} routes, ${evidence.imports.length} imports, ${evidence.incomingEdges.length} incoming / ${evidence.outgoingEdges.length} outgoing edges; index ${staleLabel}; ${codeContextScaleLine(Number(coverage.files ?? 0))}.`,
|
|
334
|
+
"Evidence is structural only: paths, lines, signatures, routes, imports, edges, and owners; no source snippets are included.",
|
|
335
|
+
];
|
|
336
|
+
pushBudgetedSection(lines, "Files:", evidence.files, 8, (row) => ` file-match ${String(row.path)} (${String(row.language)}, ${String(row.profile)}, ${Number(row.lines ?? 0)} lines)`);
|
|
337
|
+
pushBudgetedSection(lines, "Symbols:", evidence.symbols, 12, (row) => ` symbol-match ${String(row.file_path)}:${String(row.line)} ${String(row.kind)} ${String(row.name)} - ${structuralSignature(row.signature)}`);
|
|
338
|
+
pushBudgetedSection(lines, "Routes:", evidence.routes, 8, (row) => ` route-match ${String(row.method)} ${String(row.route)} -> ${String(row.handler)} (${String(row.file_path)}:${String(row.line)})`);
|
|
339
|
+
pushBudgetedSection(lines, "Imports:", evidence.imports, 8, (row) => ` import-match ${String(row.from_file)}:${String(row.line)} -> ${String(row.to_ref)}${row.imported ? ` (${String(row.imported)})` : ""}`);
|
|
340
|
+
pushBudgetedSection(lines, "Incoming edges:", evidence.incomingEdges, 8, (row) => ` edge-in ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
341
|
+
pushBudgetedSection(lines, "Outgoing edges:", evidence.outgoingEdges, 8, (row) => ` edge-out ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
342
|
+
pushBudgetedSection(lines, "Owners:", evidence.owners, 6, (row) => ` owner ${row.owner} (${row.owner_source}, ${row.files} files): ${row.sample_files.join(", ")}`);
|
|
343
|
+
return finalizeCodeContextPack(lines.join("\n"));
|
|
1646
344
|
}
|
|
1647
345
|
// Error thrown when the code-evidence index is missing or schema-incompatible.
|
|
1648
346
|
// The MCP server catches this to return an isError tool result (tools/list still
|
|
@@ -1667,16 +365,16 @@ function openCodeEvidenceDatabaseForServing() {
|
|
|
1667
365
|
const database = openDatabase(databasePath.absolutePath);
|
|
1668
366
|
let schemaVersion = "";
|
|
1669
367
|
try {
|
|
1670
|
-
schemaVersion = readMetaValue(database, "schema_version");
|
|
368
|
+
schemaVersion = (0, schema_1.readMetaValue)(database, "schema_version");
|
|
1671
369
|
}
|
|
1672
370
|
catch (error) {
|
|
1673
371
|
database.close();
|
|
1674
372
|
const message = error instanceof Error ? error.message : String(error);
|
|
1675
373
|
throw new CodeEvidenceIndexUnavailableError(`code evidence index at ${databasePath.relativePath} is not readable; rebuild with \`project-librarian --code-index\`. Error: ${message}`);
|
|
1676
374
|
}
|
|
1677
|
-
if (schemaVersion !== codeIndexSchemaVersion) {
|
|
375
|
+
if (schemaVersion !== schema_1.codeIndexSchemaVersion) {
|
|
1678
376
|
database.close();
|
|
1679
|
-
throw new CodeEvidenceIndexUnavailableError(
|
|
377
|
+
throw new CodeEvidenceIndexUnavailableError((0, index_health_1.formatCodeIndexHealthRemediation)(codeIndexHealth()));
|
|
1680
378
|
}
|
|
1681
379
|
database.exec("PRAGMA query_only = ON");
|
|
1682
380
|
return { database, relativePath: databasePath.relativePath };
|
|
@@ -1687,188 +385,57 @@ function prepareOutputPath() {
|
|
|
1687
385
|
(0, workspace_1.mkdirp)(code_index_file_policy_1.codeEvidenceDirectory);
|
|
1688
386
|
fs.writeFileSync((0, workspace_1.abs)(`${code_index_file_policy_1.codeEvidenceDirectory}/.gitignore`), "*\n!.gitignore\n");
|
|
1689
387
|
}
|
|
388
|
+
function codeIndexModeRuntime() {
|
|
389
|
+
return {
|
|
390
|
+
codeContextPack,
|
|
391
|
+
codeEvidenceDatabasePath,
|
|
392
|
+
codeImpact,
|
|
393
|
+
codeIndexHealth,
|
|
394
|
+
codeIndexStaleness,
|
|
395
|
+
codeReportForRequestedSection: (database, requestedSection, options) => (0, reports_1.codeReportForRequestedSection)(database, requestedSection, codeReportRuntime(database, options)),
|
|
396
|
+
codeScopes,
|
|
397
|
+
fail,
|
|
398
|
+
indexCodeFile,
|
|
399
|
+
openDatabase,
|
|
400
|
+
prepareOutputPath,
|
|
401
|
+
readCodeFileFingerprint,
|
|
402
|
+
readCodeFile,
|
|
403
|
+
removeDatabaseFiles,
|
|
404
|
+
requireExistingIndex,
|
|
405
|
+
selectedCodeParserMode,
|
|
406
|
+
warnIfCodeIndexStale,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
1690
409
|
function runCodeIndexMode() {
|
|
1691
|
-
|
|
1692
|
-
const scopes = codeScopes();
|
|
1693
|
-
const parserMode = selectedCodeParserMode();
|
|
1694
|
-
// Scale gate before ANY write or database work: below the measured threshold
|
|
1695
|
-
// the build halts with the evidence-citing warning unless --acknowledge-small-repo
|
|
1696
|
-
// was passed (2026-06-12 scale-aware guidance decision).
|
|
1697
|
-
const discoveredFiles = (0, code_index_file_policy_1.discoverCodeFiles)(scopes);
|
|
1698
|
-
const scaleGate = (0, code_index_file_policy_1.smallRepoCodeIndexGate)(discoveredFiles.length, args_1.acknowledgeSmallRepoMode);
|
|
1699
|
-
if (!scaleGate.proceed)
|
|
1700
|
-
fail(scaleGate.warning);
|
|
1701
|
-
const existingIndex = fs.existsSync(databasePath.absolutePath);
|
|
1702
|
-
if (args_1.codeIndexIncrementalMode && !existingIndex) {
|
|
1703
|
-
fail(`--incremental requires an existing compatible code evidence index: ${databasePath.relativePath}`);
|
|
1704
|
-
}
|
|
1705
|
-
let incremental = false;
|
|
1706
|
-
if (existingIndex && !args_1.codeIndexFullMode) {
|
|
1707
|
-
let compatibility = { compatible: false, reason: "compatibility was not checked" };
|
|
1708
|
-
const existingDatabase = openDatabase(databasePath.absolutePath);
|
|
1709
|
-
try {
|
|
1710
|
-
compatibility = incrementalCompatibility(existingDatabase, scopes, parserMode);
|
|
1711
|
-
}
|
|
1712
|
-
finally {
|
|
1713
|
-
existingDatabase.close();
|
|
1714
|
-
}
|
|
1715
|
-
incremental = !args_1.codeIndexFullMode && compatibility.compatible;
|
|
1716
|
-
if (args_1.codeIndexIncrementalMode && !compatibility.compatible)
|
|
1717
|
-
fail(`--incremental cannot update ${databasePath.relativePath}: ${compatibility.reason}`);
|
|
1718
|
-
}
|
|
1719
|
-
prepareOutputPath();
|
|
1720
|
-
if (!incremental)
|
|
1721
|
-
removeDatabaseFiles(databasePath.absolutePath);
|
|
1722
|
-
const database = openDatabase(databasePath.absolutePath);
|
|
1723
|
-
try {
|
|
1724
|
-
if (!incremental)
|
|
1725
|
-
setupDatabase(database);
|
|
1726
|
-
const statements = createIndexStatements(database);
|
|
1727
|
-
const currentFiles = discoveredFiles.map((filePath) => readCodeFile(filePath, parserMode));
|
|
1728
|
-
const currentByPath = new Map(currentFiles.map((file) => [file.path, file]));
|
|
1729
|
-
const indexed = incremental ? new Map(database.prepare("SELECT path, hash FROM files").all().map((row) => [String(row.path), String(row.hash)])) : new Map();
|
|
1730
|
-
const deletedPaths = incremental ? Array.from(indexed.keys()).filter((filePath) => !currentByPath.has(filePath)) : [];
|
|
1731
|
-
const reindexedFiles = incremental
|
|
1732
|
-
? currentFiles.filter((file) => indexed.get(file.path) !== file.hash)
|
|
1733
|
-
: currentFiles;
|
|
1734
|
-
const unchangedFiles = incremental ? currentFiles.length - reindexedFiles.length : 0;
|
|
1735
|
-
database.exec("BEGIN");
|
|
1736
|
-
if (!incremental)
|
|
1737
|
-
statements.insertMeta.run("created_at", new Date().toISOString());
|
|
1738
|
-
writeIndexMetadata(scopes, parserMode, statements);
|
|
1739
|
-
for (const filePath of deletedPaths)
|
|
1740
|
-
removeIndexedFile(filePath, statements);
|
|
1741
|
-
for (const file of reindexedFiles) {
|
|
1742
|
-
if (incremental && indexed.has(file.path))
|
|
1743
|
-
removeIndexedFile(file.path, statements);
|
|
1744
|
-
indexCodeFile(file, statements);
|
|
1745
|
-
}
|
|
1746
|
-
database.exec("COMMIT");
|
|
1747
|
-
console.log("Project wiki code evidence index complete.");
|
|
1748
|
-
console.log(`database: ${databasePath.relativePath}`);
|
|
1749
|
-
console.log(`mode: ${incremental ? "incremental" : "full"}`);
|
|
1750
|
-
console.log(`parser_mode: ${parserMode}`);
|
|
1751
|
-
console.log(`scopes: ${scopes.join(", ")}`);
|
|
1752
|
-
console.log(`files: ${currentFiles.length}`);
|
|
1753
|
-
console.log(`reindexed_files: ${reindexedFiles.length}`);
|
|
1754
|
-
console.log(`deleted_files: ${deletedPaths.length}`);
|
|
1755
|
-
console.log(`unchanged_files: ${unchangedFiles}`);
|
|
1756
|
-
}
|
|
1757
|
-
catch (error) {
|
|
1758
|
-
try {
|
|
1759
|
-
database.exec("ROLLBACK");
|
|
1760
|
-
}
|
|
1761
|
-
catch {
|
|
1762
|
-
// Ignore rollback failures after setup errors.
|
|
1763
|
-
}
|
|
1764
|
-
throw error;
|
|
1765
|
-
}
|
|
1766
|
-
finally {
|
|
1767
|
-
database.close();
|
|
1768
|
-
}
|
|
410
|
+
(0, modes_1.runCodeIndexMode)(codeIndexModeRuntime());
|
|
1769
411
|
}
|
|
1770
412
|
function runCodeQueryMode() {
|
|
1771
|
-
|
|
1772
|
-
console.error("missing SQL: use --code-query \"select ...\"");
|
|
1773
|
-
process.exit(1);
|
|
1774
|
-
}
|
|
1775
|
-
requireExistingIndex();
|
|
1776
|
-
if (!(0, code_index_sql_1.isReadOnlySql)(args_1.codeQuerySql)) {
|
|
1777
|
-
console.error("code queries must be read-only SQL starting with SELECT or WITH");
|
|
1778
|
-
process.exit(1);
|
|
1779
|
-
}
|
|
1780
|
-
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1781
|
-
try {
|
|
1782
|
-
database.exec("PRAGMA query_only = ON");
|
|
1783
|
-
warnIfCodeIndexStale(database);
|
|
1784
|
-
printRows(database.prepare(args_1.codeQuerySql).all());
|
|
1785
|
-
}
|
|
1786
|
-
finally {
|
|
1787
|
-
database.close();
|
|
1788
|
-
}
|
|
413
|
+
(0, modes_1.runCodeQueryMode)(codeIndexModeRuntime());
|
|
1789
414
|
}
|
|
1790
415
|
function runCodeReportMode() {
|
|
1791
|
-
|
|
1792
|
-
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1793
|
-
try {
|
|
1794
|
-
warnIfCodeIndexStale(database);
|
|
1795
|
-
printJson(codeReportForRequestedSection(database));
|
|
1796
|
-
}
|
|
1797
|
-
finally {
|
|
1798
|
-
database.close();
|
|
1799
|
-
}
|
|
416
|
+
(0, modes_1.runCodeReportMode)(codeIndexModeRuntime());
|
|
1800
417
|
}
|
|
1801
418
|
function runCodeStatusMode() {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
SELECT 'files' AS metric, count(*) AS value FROM files
|
|
1807
|
-
UNION ALL SELECT 'symbols', count(*) FROM symbols
|
|
1808
|
-
UNION ALL SELECT 'imports', count(*) FROM imports
|
|
1809
|
-
UNION ALL SELECT 'routes', count(*) FROM routes
|
|
1810
|
-
UNION ALL SELECT 'edges', count(*) FROM edges
|
|
1811
|
-
UNION ALL SELECT 'configs', count(*) FROM configs
|
|
1812
|
-
`).all();
|
|
1813
|
-
const staleness = codeIndexStaleness(database);
|
|
1814
|
-
rows.push({ metric: "stale_files", value: staleness.added + staleness.changed + staleness.deleted }, { metric: "stale_changed_files", value: staleness.changed }, { metric: "stale_added_files", value: staleness.added }, { metric: "stale_deleted_files", value: staleness.deleted });
|
|
1815
|
-
printRows(rows);
|
|
1816
|
-
}
|
|
1817
|
-
finally {
|
|
1818
|
-
database.close();
|
|
1819
|
-
}
|
|
419
|
+
(0, modes_1.runCodeStatusMode)(codeIndexModeRuntime());
|
|
420
|
+
}
|
|
421
|
+
function runCodeIndexHealthMode() {
|
|
422
|
+
(0, modes_1.runCodeIndexHealthMode)(codeIndexModeRuntime());
|
|
1820
423
|
}
|
|
1821
424
|
function runCodeFilesMode() {
|
|
1822
|
-
|
|
1823
|
-
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1824
|
-
try {
|
|
1825
|
-
warnIfCodeIndexStale(database);
|
|
1826
|
-
printRows(database.prepare("SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path").all());
|
|
1827
|
-
}
|
|
1828
|
-
finally {
|
|
1829
|
-
database.close();
|
|
1830
|
-
}
|
|
425
|
+
(0, modes_1.runCodeFilesMode)(codeIndexModeRuntime());
|
|
1831
426
|
}
|
|
1832
427
|
function runCodeImpactMode() {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
requireExistingIndex();
|
|
1838
|
-
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1839
|
-
try {
|
|
1840
|
-
warnIfCodeIndexStale(database);
|
|
1841
|
-
printJson(codeImpact(database, args_1.codeImpactTarget.trim()));
|
|
1842
|
-
}
|
|
1843
|
-
finally {
|
|
1844
|
-
database.close();
|
|
1845
|
-
}
|
|
428
|
+
(0, modes_1.runCodeImpactMode)(codeIndexModeRuntime());
|
|
429
|
+
}
|
|
430
|
+
function runCodeContextPackMode() {
|
|
431
|
+
(0, modes_1.runCodeContextPackMode)(codeIndexModeRuntime());
|
|
1846
432
|
}
|
|
1847
433
|
function runCodeSearchSymbolMode() {
|
|
1848
|
-
|
|
1849
|
-
console.error("missing symbol search term: use --code-search-symbol \"term\"");
|
|
1850
|
-
process.exit(1);
|
|
1851
|
-
}
|
|
1852
|
-
requireExistingIndex();
|
|
1853
|
-
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1854
|
-
try {
|
|
1855
|
-
warnIfCodeIndexStale(database);
|
|
1856
|
-
const like = `%${args_1.codeSearchSymbol}%`;
|
|
1857
|
-
printRows(database.prepare("SELECT name, kind, file_path, line, signature FROM symbols WHERE name LIKE ? OR signature LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like));
|
|
1858
|
-
}
|
|
1859
|
-
finally {
|
|
1860
|
-
database.close();
|
|
1861
|
-
}
|
|
434
|
+
(0, modes_1.runCodeSearchSymbolMode)(codeIndexModeRuntime());
|
|
1862
435
|
}
|
|
1863
436
|
function isCodeEvidenceMode() {
|
|
1864
|
-
return
|
|
437
|
+
return (0, modes_1.isCodeEvidenceMode)();
|
|
1865
438
|
}
|
|
1866
439
|
function isCodeEvidenceModeFor(flags) {
|
|
1867
|
-
return flags
|
|
1868
|
-
|| Boolean(flags.codeQuerySql)
|
|
1869
|
-
|| flags.codeReportMode
|
|
1870
|
-
|| flags.codeStatusMode
|
|
1871
|
-
|| flags.codeFilesMode
|
|
1872
|
-
|| flags.codeImpactMode
|
|
1873
|
-
|| Boolean(flags.codeSearchSymbol);
|
|
440
|
+
return (0, modes_1.isCodeEvidenceModeFor)(flags);
|
|
1874
441
|
}
|