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.
@@ -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.codeownerRules = codeownerRules;
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.searchSymbols = searchSymbols;
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 code_index_sql_1 = require("./code-index-sql");
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
- const codeIndexSchemaVersion = "3";
68
- const httpMethods = new Set(["all", "delete", "get", "patch", "post", "put"]);
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 treeSitterProfile(relativePath) {
151
- const extension = path.extname(relativePath).toLowerCase();
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 extractionProfile(relativePath, parserMode) {
184
- if (parserMode === "tree-sitter")
185
- return treeSitterProfile(relativePath);
186
- if ((0, code_index_file_policy_1.isJavaScriptLike)(relativePath))
187
- return "typescript-ast";
188
- if ((0, code_index_file_policy_1.fileLanguage)(relativePath) === "python")
189
- return "python-light";
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: Buffer.byteLength(text),
129
+ bytes: fingerprint.size,
200
130
  hash: crypto.createHash("sha256").update(text).digest("hex"),
201
- language: (0, code_index_file_policy_1.fileLanguage)(relativePath) || "config",
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
- function lineNumber(text, index) {
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
- const backend = extractionBackendsByProfile.get(profile);
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 current = new Map((0, code_index_file_policy_1.discoverCodeFiles)(scopes.length > 0 ? scopes : ["."]).map((file) => {
1128
- const codeFile = readCodeFile(file, parserMode);
1129
- return [codeFile.path, codeFile.hash];
1130
- }));
1131
- const indexed = new Map(database.prepare("SELECT path, hash FROM files").all().map((row) => [String(row.path), String(row.hash)]));
1132
- let changed = 0;
1133
- let deleted = 0;
1134
- for (const [filePath, hash] of indexed) {
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
- for (const filePath of current.keys()) {
1143
- if (!indexed.has(filePath))
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 warnIfCodeIndexStale(database) {
1154
- const staleness = codeIndexStaleness(database);
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 pathOwnerKey(filePath) {
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
- schema_version: 1,
1530
- generated_at: new Date().toISOString(),
1531
- database: databasePath.relativePath,
1532
- scopes: indexedScopes(database),
1533
- parser_mode: indexedParserMode(database),
1534
- stale: {
1535
- files: staleness.added + staleness.changed + staleness.deleted,
1536
- changed: staleness.changed,
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 codeReport(database) {
1543
- return {
1544
- ...codeReportMetadata(database),
1545
- report_sections: ["evidence_coverage", "ownership_summary", "language_profile_summary", "parser_backend_summary", "workspace_summary", "workspace_dependency_graph", "route_inventory", "dependency_hotspots", "config_inventory", "edge_summary"],
1546
- evidence_coverage: evidenceCoverage(database),
1547
- ownership_summary: ownershipSummary(database),
1548
- language_profile_summary: languageProfileSummary(database),
1549
- parser_backend_summary: parserBackendSummary(database),
1550
- workspace_summary: workspaceSummary(database),
1551
- workspace_dependency_graph: workspaceDependencyGraph(),
1552
- route_inventory: routeInventory(database),
1553
- dependency_hotspots: dependencyHotspots(database),
1554
- config_inventory: configInventory(database),
1555
- edge_summary: edgeSummary(database),
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: fileMatches,
1623
- symbols: symbolMatches,
1624
- routes: routeMatches,
1625
- imports: importMatches,
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: Array.from(impactedOwners.values()).map((owner) => ({
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
- // Symbol search reusing the exact SELECT behind --code-search-symbol so the MCP
1642
- // code_search tool returns identical rows without duplicating the query string.
1643
- function searchSymbols(database, term) {
1644
- const like = `%${term}%`;
1645
- return 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);
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(`code evidence index schema version ${schemaVersion || "(missing)"} is incompatible with ${codeIndexSchemaVersion}; rebuild with \`project-librarian --code-index\``);
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
- const databasePath = codeEvidenceDatabasePath();
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
- if (!args_1.codeQuerySql.trim()) {
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
- requireExistingIndex();
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
- requireExistingIndex();
1803
- const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
1804
- try {
1805
- const rows = database.prepare(`
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
- requireExistingIndex();
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
- if (!args_1.codeImpactTarget.trim()) {
1834
- console.error("missing impact target: use --code-impact \"path-or-symbol-or-module\"");
1835
- process.exit(1);
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
- if (!args_1.codeSearchSymbol.trim()) {
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 isCodeEvidenceModeFor({ codeFilesMode: args_1.codeFilesMode, codeImpactMode: args_1.codeImpactMode, codeIndexMode: args_1.codeIndexMode, codeQuerySql: args_1.codeQuerySql, codeReportMode: args_1.codeReportMode, codeSearchSymbol: args_1.codeSearchSymbol, codeStatusMode: args_1.codeStatusMode });
437
+ return (0, modes_1.isCodeEvidenceMode)();
1865
438
  }
1866
439
  function isCodeEvidenceModeFor(flags) {
1867
- return flags.codeIndexMode
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
  }