project-librarian 0.5.5 → 0.5.6

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.
Files changed (38) hide show
  1. package/CONTRIBUTING.md +36 -0
  2. package/README.ko.md +57 -364
  3. package/README.md +56 -363
  4. package/dist/args.js +6 -1
  5. package/dist/code-index/extractors/light-languages.js +285 -0
  6. package/dist/code-index/extractors/registry.js +12 -0
  7. package/dist/code-index/extractors/shared.js +18 -1
  8. package/dist/code-index/extractors/typescript.js +30 -16
  9. package/dist/code-index/modes.js +136 -32
  10. package/dist/code-index/native-helper-matrix.js +99 -0
  11. package/dist/code-index/native-helper.js +292 -0
  12. package/dist/code-index/schema.js +72 -13
  13. package/dist/code-index/search.js +1 -1
  14. package/dist/code-index-file-policy.js +9 -1
  15. package/dist/code-index.js +363 -12
  16. package/dist/init-project-wiki.js +5 -0
  17. package/dist/native/darwin-arm64/project-librarian-indexer +0 -0
  18. package/dist/native/darwin-x64/project-librarian-indexer +0 -0
  19. package/dist/native/linux-arm64/project-librarian-indexer +0 -0
  20. package/dist/native/linux-arm64-musl/project-librarian-indexer +0 -0
  21. package/dist/native/linux-x64/project-librarian-indexer +0 -0
  22. package/dist/native/linux-x64-musl/project-librarian-indexer +0 -0
  23. package/dist/native/project-librarian-indexer-manifest.json +70 -0
  24. package/dist/native/win32-arm64/project-librarian-indexer.exe +0 -0
  25. package/dist/native/win32-x64/project-librarian-indexer.exe +0 -0
  26. package/docs/README.md +11 -0
  27. package/docs/benchmarks.md +64 -0
  28. package/docs/cli-reference.md +60 -0
  29. package/docs/code-evidence.md +87 -0
  30. package/docs/ko/README.md +13 -0
  31. package/docs/ko/benchmarks.md +64 -0
  32. package/docs/ko/cli-reference.md +60 -0
  33. package/docs/ko/code-evidence.md +87 -0
  34. package/docs/ko/maintainer.md +76 -0
  35. package/docs/ko/usage.md +167 -0
  36. package/docs/maintainer.md +76 -0
  37. package/docs/usage.md +175 -0
  38. package/package.json +11 -1
@@ -1,6 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.codeIndexSchemaVersion = void 0;
37
+ exports.fileFtsRowid = fileFtsRowid;
38
+ exports.createSecondaryIndexes = createSecondaryIndexes;
4
39
  exports.setupDatabase = setupDatabase;
5
40
  exports.createIndexStatements = createIndexStatements;
6
41
  exports.removeIndexedFile = removeIndexedFile;
@@ -10,14 +45,44 @@ exports.indexedScopes = indexedScopes;
10
45
  exports.indexedParserMode = indexedParserMode;
11
46
  exports.incrementalCompatibility = incrementalCompatibility;
12
47
  exports.codeIndexSnapshot = codeIndexSnapshot;
48
+ const crypto = __importStar(require("node:crypto"));
13
49
  const workspace_1 = require("../workspace");
14
- exports.codeIndexSchemaVersion = "4";
15
- function setupDatabase(database) {
50
+ exports.codeIndexSchemaVersion = "5";
51
+ function stableFtsRowid(parts) {
52
+ const hash = crypto.createHash("sha256");
53
+ for (const part of parts) {
54
+ hash.update(part);
55
+ hash.update("\0");
56
+ }
57
+ const digest = hash.digest();
58
+ let value = 0;
59
+ for (let index = 0; index < 6; index += 1)
60
+ value = value * 256 + digest[index];
61
+ return value + 1;
62
+ }
63
+ function fileFtsRowid(filePath) {
64
+ return stableFtsRowid(["file", filePath]);
65
+ }
66
+ const secondaryIndexSql = `
67
+ CREATE INDEX idx_symbols_file ON symbols(file_path);
68
+ CREATE INDEX idx_symbols_name ON symbols(name);
69
+ CREATE INDEX idx_imports_from ON imports(from_file);
70
+ CREATE INDEX idx_routes_path ON routes(route);
71
+ CREATE INDEX idx_configs_file ON configs(file_path);
72
+ CREATE INDEX idx_edges_source ON edges(source_kind, source);
73
+ CREATE INDEX idx_edges_target ON edges(target_kind, target);
74
+ CREATE INDEX idx_edges_kind ON edges(kind);
75
+ `;
76
+ function createSecondaryIndexes(database) {
77
+ database.exec(secondaryIndexSql);
78
+ }
79
+ function setupDatabase(database, options = {}) {
16
80
  database.exec(`
17
81
  PRAGMA journal_mode = WAL;
18
82
  CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
19
83
  CREATE TABLE files (
20
84
  path TEXT PRIMARY KEY,
85
+ fts_rowid INTEGER NOT NULL UNIQUE,
21
86
  language TEXT NOT NULL,
22
87
  profile TEXT NOT NULL,
23
88
  kind TEXT NOT NULL,
@@ -71,30 +136,24 @@ function setupDatabase(database) {
71
136
  );
72
137
  CREATE VIRTUAL TABLE files_fts USING fts5(path, language, profile, content);
73
138
  CREATE VIRTUAL TABLE symbols_fts USING fts5(name, kind, file_path, signature);
74
- CREATE INDEX idx_symbols_file ON symbols(file_path);
75
- CREATE INDEX idx_symbols_name ON symbols(name);
76
- CREATE INDEX idx_imports_from ON imports(from_file);
77
- CREATE INDEX idx_routes_path ON routes(route);
78
- CREATE INDEX idx_configs_file ON configs(file_path);
79
- CREATE INDEX idx_edges_source ON edges(source_kind, source);
80
- CREATE INDEX idx_edges_target ON edges(target_kind, target);
81
- CREATE INDEX idx_edges_kind ON edges(kind);
82
139
  `);
140
+ if (options.secondaryIndexes ?? true)
141
+ createSecondaryIndexes(database);
83
142
  }
84
143
  function createIndexStatements(database) {
85
144
  return {
86
145
  deleteConfig: database.prepare("DELETE FROM configs WHERE file_path = ?"),
87
146
  deleteEdge: database.prepare("DELETE FROM edges WHERE file_path = ?"),
88
147
  deleteFile: database.prepare("DELETE FROM files WHERE path = ?"),
89
- deleteFileFts: database.prepare("DELETE FROM files_fts WHERE path = ?"),
148
+ deleteFileFts: database.prepare("DELETE FROM files_fts WHERE rowid = (SELECT fts_rowid FROM files WHERE path = ?)"),
90
149
  deleteImport: database.prepare("DELETE FROM imports WHERE from_file = ?"),
91
150
  deleteRoute: database.prepare("DELETE FROM routes WHERE file_path = ?"),
92
151
  deleteSymbol: database.prepare("DELETE FROM symbols WHERE file_path = ?"),
93
152
  deleteSymbolFts: database.prepare("DELETE FROM symbols_fts WHERE file_path = ?"),
94
153
  insertConfig: database.prepare("INSERT INTO configs (key, value, file_path, line) VALUES (?, ?, ?, ?)"),
95
154
  insertEdge: database.prepare("INSERT INTO edges (kind, source_kind, source, target_kind, target, file_path, line, evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"),
96
- insertFile: database.prepare("INSERT INTO files (path, language, profile, kind, bytes, lines, hash, mtime_ms, size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
97
- insertFileFts: database.prepare("INSERT INTO files_fts (path, language, profile, content) VALUES (?, ?, ?, ?)"),
155
+ insertFile: database.prepare("INSERT INTO files (path, fts_rowid, language, profile, kind, bytes, lines, hash, mtime_ms, size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
156
+ insertFileFts: database.prepare("INSERT INTO files_fts (rowid, path, language, profile, content) VALUES (?, ?, ?, ?, ?)"),
98
157
  insertImport: database.prepare("INSERT INTO imports (from_file, to_ref, imported, line, raw) VALUES (?, ?, ?, ?, ?)"),
99
158
  insertMeta: database.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)"),
100
159
  insertRoute: database.prepare("INSERT INTO routes (method, route, file_path, line, handler) VALUES (?, ?, ?, ?, ?)"),
@@ -82,7 +82,7 @@ function searchFiles(database, term, limit = 25) {
82
82
  const ftsRows = database.prepare(`
83
83
  SELECT files.path, files.language, files.profile, files.lines, files.bytes
84
84
  FROM files_fts
85
- JOIN files ON files.path = files_fts.path
85
+ JOIN files ON files.fts_rowid = files_fts.rowid
86
86
  WHERE files_fts MATCH ?
87
87
  ORDER BY bm25(files_fts, 8.0, 1.0, 1.0, 0.25), files.path
88
88
  LIMIT ?
@@ -38,6 +38,7 @@ exports.fileLanguage = fileLanguage;
38
38
  exports.isJavaScriptLike = isJavaScriptLike;
39
39
  exports.shouldIndexFile = shouldIndexFile;
40
40
  exports.isIgnoredCodePath = isIgnoredCodePath;
41
+ exports.cachedDiscoveredCodeFileStat = cachedDiscoveredCodeFileStat;
41
42
  exports.discoverCodeFiles = discoverCodeFiles;
42
43
  exports.smallRepoCodeIndexGate = smallRepoCodeIndexGate;
43
44
  const childProcess = __importStar(require("node:child_process"));
@@ -76,6 +77,7 @@ const languageByExtension = {
76
77
  };
77
78
  const configExtensions = new Set([".json", ".yaml", ".yml", ".toml"]);
78
79
  exports.maxIndexedBytes = 1024 * 1024;
80
+ const discoveredCodeFileStats = new Map();
79
81
  function fileLanguage(relativePath) {
80
82
  if (path.basename(relativePath) === ".env.example")
81
83
  return "config";
@@ -170,7 +172,11 @@ function gitTrackedAndUnignoredFiles(scopes) {
170
172
  function indexableFileStat(file) {
171
173
  return (0, workspace_1.containedProjectFileStat)(file);
172
174
  }
175
+ function cachedDiscoveredCodeFileStat(relativePath) {
176
+ return discoveredCodeFileStats.get(relativePath);
177
+ }
173
178
  function discoverCodeFiles(scopes) {
179
+ discoveredCodeFileStats.clear();
174
180
  const gitFiles = gitTrackedAndUnignoredFiles(scopes);
175
181
  const candidates = gitFiles ?? scopes.flatMap((scope) => walkCodeFiles(scope));
176
182
  const files = [];
@@ -178,8 +184,10 @@ function discoverCodeFiles(scopes) {
178
184
  if (isIgnoredCodePath(file) || !shouldIndexFile(file))
179
185
  continue;
180
186
  const stat = indexableFileStat(file);
181
- if (stat && stat.size <= exports.maxIndexedBytes)
187
+ if (stat && stat.size <= exports.maxIndexedBytes) {
188
+ discoveredCodeFileStats.set(file, { absolutePath: (0, workspace_1.abs)(file), stat });
182
189
  files.push(file);
190
+ }
183
191
  }
184
192
  return files.sort();
185
193
  }
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
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;
36
+ exports.CodeEvidenceIndexUnavailableError = exports.nativeCodeIndexAutoFileThreshold = 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
38
  exports.codeIndexHealth = codeIndexHealth;
39
39
  exports.codeImpact = codeImpact;
@@ -61,6 +61,7 @@ const registry_1 = require("./code-index/extractors/registry");
61
61
  const shared_1 = require("./code-index/extractors/shared");
62
62
  const index_health_1 = require("./code-index/index-health");
63
63
  const modes_1 = require("./code-index/modes");
64
+ const native_helper_1 = require("./code-index/native-helper");
64
65
  const ownership_1 = require("./code-index/ownership");
65
66
  Object.defineProperty(exports, "codeownerRules", { enumerable: true, get: function () { return ownership_1.codeownerRules; } });
66
67
  Object.defineProperty(exports, "matchedCodeownerRules", { enumerable: true, get: function () { return ownership_1.matchedCodeownerRules; } });
@@ -77,6 +78,8 @@ Object.defineProperty(exports, "searchSymbols", { enumerable: true, get: functio
77
78
  const workspace_1 = require("./workspace");
78
79
  exports.codeContextPackCharCap = 4000;
79
80
  exports.codeContextPackTruncationNotice = "[truncated - refine the query]";
81
+ exports.nativeCodeIndexAutoFileThreshold = 1;
82
+ const codeFileFingerprintPaths = new Map();
80
83
  function fail(message) {
81
84
  console.error(message);
82
85
  process.exit(1);
@@ -110,31 +113,82 @@ function selectedCodeParserMode() {
110
113
  return "tree-sitter";
111
114
  fail(`invalid --code-parser: ${args_1.codeParser}; expected one of: default, tree-sitter`);
112
115
  }
116
+ function selectedCodeIndexEngine() {
117
+ const requested = args_1.codeIndexEngine.trim().toLowerCase();
118
+ if (!requested || requested === "auto")
119
+ return "auto";
120
+ if (requested === "typescript")
121
+ return "typescript";
122
+ if (requested === "native-rust")
123
+ return "native-rust";
124
+ fail(`invalid --code-index-engine: ${args_1.codeIndexEngine}; expected one of: auto, typescript, native-rust`);
125
+ }
126
+ function codeIndexEngineSelectionContext(discoveredFiles, parserMode) {
127
+ let nativeEligibleFileCount = 0;
128
+ for (const filePath of discoveredFiles) {
129
+ const language = (0, code_index_file_policy_1.fileLanguage)(filePath) || "config";
130
+ if (nativeAutoEligibleProfile((0, registry_1.extractionProfile)(filePath, language, parserMode)))
131
+ nativeEligibleFileCount += 1;
132
+ }
133
+ return {
134
+ discoveredFileCount: discoveredFiles.length,
135
+ nativeEligibleFileCount,
136
+ nativeIneligibleFileCount: discoveredFiles.length - nativeEligibleFileCount,
137
+ };
138
+ }
139
+ function shouldUseNativeCodeIndexAuto(context) {
140
+ return context.nativeEligibleFileCount >= exports.nativeCodeIndexAutoFileThreshold
141
+ && (0, native_helper_1.nativeCodeIndexHelperAvailable)();
142
+ }
143
+ function nativeCodeIndexAvailable() {
144
+ return (0, native_helper_1.nativeCodeIndexHelperAvailable)();
145
+ }
113
146
  function normalizedMtimeMs(stat) {
114
147
  return Number(stat.mtimeMs.toFixed(3));
115
148
  }
116
149
  function readCodeFileFingerprint(relativePath) {
117
- const { stat } = (0, workspace_1.requireContainedProjectFile)(relativePath, "code-index file");
150
+ const discovered = (0, code_index_file_policy_1.cachedDiscoveredCodeFileStat)(relativePath);
151
+ const { absolutePath, stat } = discovered ?? (0, workspace_1.requireContainedProjectFile)(relativePath, "code-index file");
152
+ codeFileFingerprintPaths.set(relativePath, absolutePath);
118
153
  return {
119
154
  mtimeMs: normalizedMtimeMs(stat),
120
155
  path: relativePath,
121
156
  size: stat.size,
122
157
  };
123
158
  }
124
- function readCodeFile(relativePath, parserMode = "default") {
125
- const { absolutePath } = (0, workspace_1.requireContainedProjectFile)(relativePath, "code-index file");
159
+ function lineCount(text) {
160
+ if (text.length === 0)
161
+ return 0;
162
+ let lines = 1;
163
+ for (let index = 0; index < text.length; index += 1) {
164
+ if (text.charCodeAt(index) === 10)
165
+ lines += 1;
166
+ }
167
+ return lines;
168
+ }
169
+ function readCodeFile(relativePath, parserMode = "default", fingerprint) {
170
+ let effectiveFingerprint = fingerprint;
171
+ let absolutePath = effectiveFingerprint ? codeFileFingerprintPaths.get(relativePath) : undefined;
172
+ if (!absolutePath || !effectiveFingerprint) {
173
+ const contained = (0, workspace_1.requireContainedProjectFile)(relativePath, "code-index file");
174
+ absolutePath = contained.absolutePath;
175
+ effectiveFingerprint ??= {
176
+ mtimeMs: normalizedMtimeMs(contained.stat),
177
+ path: relativePath,
178
+ size: contained.stat.size,
179
+ };
180
+ }
126
181
  const text = fs.readFileSync(absolutePath, "utf8");
127
- const fingerprint = readCodeFileFingerprint(relativePath);
128
182
  const language = (0, code_index_file_policy_1.fileLanguage)(relativePath) || "config";
129
183
  return {
130
- bytes: fingerprint.size,
184
+ bytes: effectiveFingerprint.size,
131
185
  hash: crypto.createHash("sha256").update(text).digest("hex"),
132
186
  language,
133
- lines: text.length === 0 ? 0 : text.split(/\r?\n/).length,
134
- mtimeMs: fingerprint.mtimeMs,
187
+ lines: lineCount(text),
188
+ mtimeMs: effectiveFingerprint.mtimeMs,
135
189
  path: relativePath,
136
190
  profile: (0, registry_1.extractionProfile)(relativePath, language, parserMode),
137
- size: fingerprint.size,
191
+ size: effectiveFingerprint.size,
138
192
  text,
139
193
  };
140
194
  }
@@ -143,8 +197,9 @@ function extractionBackendForProfile(profile) {
143
197
  return extractionBackendRegistry.backendForProfile(profile);
144
198
  }
145
199
  function indexCodeFile(file, statements) {
146
- statements.insertFile.run(file.path, file.language, file.profile, file.language === "config" ? "config" : "source", file.bytes, file.lines, file.hash, file.mtimeMs, file.size);
147
- statements.insertFileFts.run(file.path, file.language, file.profile, file.text);
200
+ const ftsRowid = (0, schema_1.fileFtsRowid)(file.path);
201
+ statements.insertFile.run(file.path, ftsRowid, file.language, file.profile, file.language === "config" ? "config" : "source", file.bytes, file.lines, file.hash, file.mtimeMs, file.size);
202
+ statements.insertFileFts.run(ftsRowid, file.path, file.language, file.profile, file.text);
148
203
  extractionBackendForProfile(file.profile).index(file, statements);
149
204
  }
150
205
  function printRows(rows) {
@@ -173,6 +228,295 @@ function removeDatabaseFiles(databasePath) {
173
228
  fs.unlinkSync(filePath);
174
229
  }
175
230
  }
231
+ function moveDatabaseFiles(sourcePath, targetPath) {
232
+ const backupPath = `${targetPath}.backup-${process.pid}-${Date.now()}`;
233
+ removeDatabaseFiles(backupPath);
234
+ for (const suffix of ["", "-wal", "-shm"]) {
235
+ const target = `${targetPath}${suffix}`;
236
+ if (fs.existsSync(target))
237
+ fs.renameSync(target, `${backupPath}${suffix}`);
238
+ }
239
+ try {
240
+ for (const suffix of ["", "-wal", "-shm"]) {
241
+ const source = `${sourcePath}${suffix}`;
242
+ if (fs.existsSync(source))
243
+ fs.renameSync(source, `${targetPath}${suffix}`);
244
+ }
245
+ removeDatabaseFiles(backupPath);
246
+ }
247
+ catch (error) {
248
+ removeDatabaseFiles(targetPath);
249
+ for (const suffix of ["", "-wal", "-shm"]) {
250
+ const backup = `${backupPath}${suffix}`;
251
+ if (fs.existsSync(backup))
252
+ fs.renameSync(backup, `${targetPath}${suffix}`);
253
+ }
254
+ throw error;
255
+ }
256
+ }
257
+ function removeTemporaryDatabaseFiles(databasePath) {
258
+ for (const suffix of ["", "-wal", "-shm"]) {
259
+ const filePath = `${databasePath}${suffix}`;
260
+ if (fs.existsSync(filePath))
261
+ fs.unlinkSync(filePath);
262
+ }
263
+ }
264
+ function nativeCodeIndexFileFor(filePath, parserMode) {
265
+ const fingerprint = readCodeFileFingerprint(filePath);
266
+ const language = (0, code_index_file_policy_1.fileLanguage)(filePath) || "config";
267
+ return {
268
+ ...fingerprint,
269
+ language,
270
+ profile: (0, registry_1.extractionProfile)(filePath, language, parserMode),
271
+ };
272
+ }
273
+ function nativeCodeIndexFileFromCodeFile(file) {
274
+ return {
275
+ language: file.language,
276
+ mtimeMs: file.mtimeMs,
277
+ path: file.path,
278
+ profile: file.profile,
279
+ size: file.size,
280
+ };
281
+ }
282
+ function nativeCodeIndexFileFromFingerprint(file, parserMode) {
283
+ const language = (0, code_index_file_policy_1.fileLanguage)(file.path) || "config";
284
+ return {
285
+ language,
286
+ mtimeMs: file.mtimeMs,
287
+ path: file.path,
288
+ profile: (0, registry_1.extractionProfile)(file.path, language, parserMode),
289
+ size: file.size,
290
+ };
291
+ }
292
+ function nativeEligibleProfile(profile) {
293
+ return nativeAutoEligibleProfile(profile) || profile === "config" || profile === "inventory-only";
294
+ }
295
+ function nativeCodeIndexIncrementalEligible(files, parserMode) {
296
+ return files.every((file) => nativeEligibleProfile(nativeCodeIndexFileFromFingerprint(file, parserMode).profile));
297
+ }
298
+ function nativeAutoEligibleProfile(profile) {
299
+ return [
300
+ "typescript-ast",
301
+ "python-light",
302
+ "go-light",
303
+ "c-light",
304
+ "cpp-light",
305
+ "csharp-light",
306
+ "java-light",
307
+ "kotlin-light",
308
+ "php-light",
309
+ "rust-light",
310
+ "swift-light",
311
+ ].includes(profile);
312
+ }
313
+ function nativeCodeIndexOutputMode() {
314
+ const requested = (process.env.PROJECT_LIBRARIAN_NATIVE_INDEXER_STRATEGY ?? "sqlite-direct").trim();
315
+ if (requested === "row-stream" || requested === "sqlite-bridge" || requested === "sqlite-direct")
316
+ return requested;
317
+ fail(`invalid PROJECT_LIBRARIAN_NATIVE_INDEXER_STRATEGY: ${requested}; expected row-stream, sqlite-bridge, or sqlite-direct`);
318
+ }
319
+ function appendTypeScriptPartitionToNativeDatabase(databasePath, files, parserMode) {
320
+ if (files.length === 0)
321
+ return 0;
322
+ const database = openDatabase(databasePath);
323
+ try {
324
+ const statements = (0, schema_1.createIndexStatements)(database);
325
+ database.exec("BEGIN");
326
+ for (const file of files)
327
+ indexCodeFile(readCodeFile(file.path, parserMode, file), statements);
328
+ database.exec("COMMIT");
329
+ return files.length;
330
+ }
331
+ catch (error) {
332
+ try {
333
+ database.exec("ROLLBACK");
334
+ }
335
+ catch {
336
+ // Ignore rollback failures after helper-created database errors.
337
+ }
338
+ throw error;
339
+ }
340
+ finally {
341
+ database.close();
342
+ }
343
+ }
344
+ function writeNativeRowsToDatabase(databasePath, rows, scopes, parserMode) {
345
+ removeDatabaseFiles(databasePath);
346
+ const database = openDatabase(databasePath);
347
+ try {
348
+ (0, schema_1.setupDatabase)(database, { secondaryIndexes: false });
349
+ const statements = (0, schema_1.createIndexStatements)(database);
350
+ database.exec("BEGIN");
351
+ statements.insertMeta.run("created_at", new Date().toISOString());
352
+ (0, schema_1.writeIndexMetadata)(scopes, parserMode, statements);
353
+ for (const file of rows.files) {
354
+ const ftsRowid = (0, schema_1.fileFtsRowid)(file.path);
355
+ statements.insertFile.run(file.path, ftsRowid, file.language, file.profile, file.kind, file.bytes, file.lines, file.hash, file.mtime_ms, file.size);
356
+ statements.insertFileFts.run(ftsRowid, file.path, file.language, file.profile, file.content);
357
+ }
358
+ for (const symbol of rows.symbols) {
359
+ statements.insertSymbol.run(symbol.name, symbol.kind, symbol.file_path, symbol.line, symbol.signature);
360
+ statements.insertSymbolFts.run(symbol.name, symbol.kind, symbol.file_path, symbol.signature);
361
+ }
362
+ for (const imported of rows.imports) {
363
+ statements.insertImport.run(imported.from_file, imported.to_ref, imported.imported, imported.line, imported.raw);
364
+ }
365
+ for (const route of rows.routes) {
366
+ statements.insertRoute.run(route.method, route.route, route.file_path, route.line, route.handler);
367
+ }
368
+ for (const config of rows.configs) {
369
+ statements.insertConfig.run(config.key, config.value, config.file_path, config.line);
370
+ }
371
+ for (const edge of rows.edges) {
372
+ statements.insertEdge.run(edge.kind, edge.source_kind, edge.source, edge.target_kind, edge.target, edge.file_path, edge.line, edge.evidence);
373
+ }
374
+ (0, schema_1.createSecondaryIndexes)(database);
375
+ database.exec("COMMIT");
376
+ }
377
+ catch (error) {
378
+ try {
379
+ database.exec("ROLLBACK");
380
+ }
381
+ catch {
382
+ // Ignore rollback failures after row-stream setup errors.
383
+ }
384
+ throw error;
385
+ }
386
+ finally {
387
+ database.close();
388
+ }
389
+ }
390
+ function runNativeCodeIndexMode(request) {
391
+ let helperPath = "";
392
+ try {
393
+ helperPath = (0, native_helper_1.requireNativeCodeIndexHelperPath)();
394
+ }
395
+ catch (error) {
396
+ fail(error instanceof Error ? error.message : String(error));
397
+ }
398
+ prepareOutputPath();
399
+ const tempDatabasePath = `${request.databasePath.absolutePath}.native-${process.pid}-${Date.now()}.tmp`;
400
+ removeDatabaseFiles(tempDatabasePath);
401
+ const manifestFiles = [];
402
+ const nativeFiles = [];
403
+ const typescriptFiles = [];
404
+ for (const filePath of request.discoveredFiles) {
405
+ const file = nativeCodeIndexFileFor(filePath, request.parserMode);
406
+ manifestFiles.push(file);
407
+ if (nativeEligibleProfile(file.profile))
408
+ nativeFiles.push(file);
409
+ else
410
+ typescriptFiles.push(file);
411
+ }
412
+ const typescriptProfiles = [...new Set(typescriptFiles.map((file) => file.profile))].sort();
413
+ const outputMode = nativeCodeIndexOutputMode();
414
+ const rowsPath = `${tempDatabasePath}.rows.json`;
415
+ const job = (0, native_helper_1.buildNativeCodeIndexJob)({
416
+ database_path: tempDatabasePath,
417
+ files: nativeFiles,
418
+ output_mode: outputMode,
419
+ parser_mode: request.parserMode,
420
+ schema_version: schema_1.codeIndexSchemaVersion,
421
+ scopes: request.scopes,
422
+ ...(outputMode === "row-stream" ? { rows_path: rowsPath } : {}),
423
+ });
424
+ let summary;
425
+ let typescriptIndexedFiles = 0;
426
+ try {
427
+ if (outputMode === "row-stream") {
428
+ const result = (0, native_helper_1.runNativeCodeIndexRowsHelper)(job, { helperPath });
429
+ summary = result.summary;
430
+ writeNativeRowsToDatabase(tempDatabasePath, result.rows, request.scopes, request.parserMode);
431
+ }
432
+ else {
433
+ summary = (0, native_helper_1.runNativeCodeIndexHelper)(job, { helperPath });
434
+ }
435
+ if (!fs.existsSync(tempDatabasePath)) {
436
+ fail(`native code index helper did not create database: ${tempDatabasePath}`);
437
+ }
438
+ typescriptIndexedFiles = appendTypeScriptPartitionToNativeDatabase(tempDatabasePath, typescriptFiles, request.parserMode);
439
+ moveDatabaseFiles(tempDatabasePath, request.databasePath.absolutePath);
440
+ }
441
+ catch (error) {
442
+ removeTemporaryDatabaseFiles(tempDatabasePath);
443
+ fail(error instanceof Error ? error.message : String(error));
444
+ }
445
+ finally {
446
+ if (fs.existsSync(rowsPath))
447
+ fs.unlinkSync(rowsPath);
448
+ }
449
+ console.log("Project wiki code evidence index complete.");
450
+ console.log(`database: ${request.databasePath.relativePath}`);
451
+ console.log("mode: full");
452
+ console.log(`parser_mode: ${request.parserMode}`);
453
+ console.log(`engine: ${typescriptIndexedFiles > 0 ? "mixed-native-rust" : "native-rust"}`);
454
+ if (request.requestedEngine === "auto")
455
+ console.log("engine_selection: auto");
456
+ console.log(`native_strategy: ${outputMode}`);
457
+ console.log(`scopes: ${request.scopes.join(", ")}`);
458
+ console.log(`files: ${manifestFiles.length}`);
459
+ console.log(`native_files: ${nativeFiles.length}`);
460
+ console.log(`typescript_files: ${typescriptIndexedFiles}`);
461
+ if (typescriptProfiles.length > 0)
462
+ console.log(`typescript_profiles: ${typescriptProfiles.join(", ")}`);
463
+ console.log(`reindexed_files: ${manifestFiles.length}`);
464
+ console.log(`deleted_files: ${summary.deleted_files ?? 0}`);
465
+ console.log(`unchanged_files: ${summary.unchanged_files ?? 0}`);
466
+ }
467
+ function runNativeCodeIndexIncrementalMode(request) {
468
+ let helperPath = "";
469
+ try {
470
+ helperPath = (0, native_helper_1.requireNativeCodeIndexHelperPath)();
471
+ }
472
+ catch (error) {
473
+ fail(error instanceof Error ? error.message : String(error));
474
+ }
475
+ const nativeFiles = request.reindexedFiles.map((file) => nativeCodeIndexFileFromFingerprint(file, request.parserMode));
476
+ const ineligibleProfiles = [...new Set(nativeFiles
477
+ .filter((file) => !nativeEligibleProfile(file.profile))
478
+ .map((file) => file.profile))].sort();
479
+ if (ineligibleProfiles.length > 0) {
480
+ fail(`native incremental writer does not support parser profiles: ${ineligibleProfiles.join(", ")}`);
481
+ }
482
+ prepareOutputPath();
483
+ const outputMode = "sqlite-direct";
484
+ const job = (0, native_helper_1.buildNativeCodeIndexJob)({
485
+ database_path: request.databasePath.absolutePath,
486
+ deleted_paths: request.deletedPaths,
487
+ files: nativeFiles,
488
+ mode: "incremental",
489
+ output_mode: outputMode,
490
+ parser_mode: request.parserMode,
491
+ schema_version: schema_1.codeIndexSchemaVersion,
492
+ scopes: request.scopes,
493
+ });
494
+ let summary;
495
+ try {
496
+ summary = (0, native_helper_1.runNativeCodeIndexHelper)(job, { helperPath });
497
+ if (!fs.existsSync(request.databasePath.absolutePath)) {
498
+ fail(`native incremental writer did not preserve database: ${request.databasePath.absolutePath}`);
499
+ }
500
+ }
501
+ catch (error) {
502
+ fail(error instanceof Error ? error.message : String(error));
503
+ }
504
+ console.log("Project wiki code evidence index complete.");
505
+ console.log(`database: ${request.databasePath.relativePath}`);
506
+ console.log("mode: incremental");
507
+ console.log(`parser_mode: ${request.parserMode}`);
508
+ console.log("engine: native-rust");
509
+ if (request.requestedEngine === "auto")
510
+ console.log("engine_selection: auto");
511
+ console.log(`native_strategy: ${outputMode}`);
512
+ console.log(`scopes: ${request.scopes.join(", ")}`);
513
+ console.log(`files: ${request.discoveredFiles.length}`);
514
+ console.log(`native_files: ${nativeFiles.length}`);
515
+ console.log("typescript_files: 0");
516
+ console.log(`reindexed_files: ${summary.reindexed_files ?? nativeFiles.length}`);
517
+ console.log(`deleted_files: ${summary.deleted_files ?? request.deletedPaths.length}`);
518
+ console.log(`unchanged_files: ${request.unchangedFiles}`);
519
+ }
176
520
  function codeIndexStaleness(database) {
177
521
  const scopes = (0, schema_1.indexedScopes)(database);
178
522
  const parserMode = (0, schema_1.indexedParserMode)(database);
@@ -194,7 +538,7 @@ function codeIndexStaleness(database) {
194
538
  }
195
539
  if (existing.mtimeMs === file.mtimeMs && existing.size === file.size)
196
540
  continue;
197
- if (readCodeFile(file.path, parserMode).hash !== existing.hash)
541
+ if (readCodeFile(file.path, parserMode, file).hash !== existing.hash)
198
542
  changed += 1;
199
543
  }
200
544
  const deleted = indexedRows.filter((row) => !currentPaths.has(String(row.path))).length;
@@ -397,13 +741,20 @@ function codeIndexModeRuntime() {
397
741
  codeScopes,
398
742
  fail,
399
743
  indexCodeFile,
744
+ nativeCodeIndexAvailable,
745
+ nativeCodeIndexIncrementalEligible,
400
746
  openDatabase,
401
747
  prepareOutputPath,
402
748
  readCodeFileFingerprint,
403
749
  readCodeFile,
404
750
  removeDatabaseFiles,
405
751
  requireExistingIndex,
752
+ runNativeCodeIndexIncrementalMode,
753
+ runNativeCodeIndexMode,
754
+ selectedCodeIndexEngine,
406
755
  selectedCodeParserMode,
756
+ codeIndexEngineSelectionContext,
757
+ shouldUseNativeCodeIndexAuto,
407
758
  warnIfCodeIndexStale,
408
759
  };
409
760
  }
@@ -62,6 +62,7 @@ Options:
62
62
  --acknowledge-small-repo With --code-index, proceed below the small-repo scale gate after its cost warning.
63
63
  --incremental With --code-index, require an existing compatible index and update only changes.
64
64
  --code-index-full With --code-index, force a full rebuild even when incremental update is possible.
65
+ --code-index-engine <engine> With --code-index, override default auto engine: typescript or native-rust.
65
66
  --code-parser <mode> With --code-index, use parser mode default or tree-sitter.
66
67
  --code-query <sql> Run conservative read-only SQL over the code evidence index.
67
68
  --code-status, --code-files Inspect the code evidence index.
@@ -165,6 +166,10 @@ if (args_1.codeIndexFullMode && !args_1.codeIndexMode) {
165
166
  console.error("--code-index-full is only supported with --code-index.");
166
167
  process.exit(1);
167
168
  }
169
+ if (args_1.codeIndexEngineMode && !args_1.codeIndexMode) {
170
+ console.error("--code-index-engine is only supported with --code-index.");
171
+ process.exit(1);
172
+ }
168
173
  if (args_1.codeParserMode && !args_1.codeIndexMode) {
169
174
  console.error("--code-parser is only supported with --code-index.");
170
175
  process.exit(1);