project-librarian 0.5.4 → 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.
- package/CONTRIBUTING.md +36 -0
- package/README.ko.md +57 -360
- package/README.md +56 -359
- package/dist/args.js +6 -1
- package/dist/code-index/extractors/light-languages.js +285 -0
- package/dist/code-index/extractors/registry.js +12 -0
- package/dist/code-index/extractors/shared.js +18 -1
- package/dist/code-index/extractors/typescript.js +30 -16
- package/dist/code-index/modes.js +136 -32
- package/dist/code-index/native-helper-matrix.js +99 -0
- package/dist/code-index/native-helper.js +292 -0
- package/dist/code-index/ownership.js +8 -6
- package/dist/code-index/schema.js +72 -13
- package/dist/code-index/search.js +1 -1
- package/dist/code-index-db.js +20 -12
- package/dist/code-index-file-policy.js +17 -11
- package/dist/code-index.js +365 -13
- package/dist/hooks.js +5 -5
- package/dist/init-project-wiki.js +7 -1
- package/dist/install-skill.js +99 -6
- package/dist/mcp-server.js +4 -4
- package/dist/migration.js +27 -2
- package/dist/native/darwin-arm64/project-librarian-indexer +0 -0
- package/dist/native/darwin-x64/project-librarian-indexer +0 -0
- package/dist/native/linux-arm64/project-librarian-indexer +0 -0
- package/dist/native/linux-arm64-musl/project-librarian-indexer +0 -0
- package/dist/native/linux-x64/project-librarian-indexer +0 -0
- package/dist/native/linux-x64-musl/project-librarian-indexer +0 -0
- package/dist/native/project-librarian-indexer-manifest.json +70 -0
- package/dist/native/win32-arm64/project-librarian-indexer.exe +0 -0
- package/dist/native/win32-x64/project-librarian-indexer.exe +0 -0
- package/dist/templates.js +4 -3
- package/dist/workspace.js +137 -10
- package/docs/README.md +11 -0
- package/docs/benchmarks.md +64 -0
- package/docs/cli-reference.md +60 -0
- package/docs/code-evidence.md +87 -0
- package/docs/ko/README.md +13 -0
- package/docs/ko/benchmarks.md +64 -0
- package/docs/ko/cli-reference.md +60 -0
- package/docs/ko/code-evidence.md +87 -0
- package/docs/ko/maintainer.md +76 -0
- package/docs/ko/usage.md +167 -0
- package/docs/maintainer.md +76 -0
- package/docs/usage.md +175 -0
- package/package.json +13 -2
package/dist/code-index.js
CHANGED
|
@@ -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,30 +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
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
}
|
|
181
|
+
const text = fs.readFileSync(absolutePath, "utf8");
|
|
127
182
|
const language = (0, code_index_file_policy_1.fileLanguage)(relativePath) || "config";
|
|
128
183
|
return {
|
|
129
|
-
bytes:
|
|
184
|
+
bytes: effectiveFingerprint.size,
|
|
130
185
|
hash: crypto.createHash("sha256").update(text).digest("hex"),
|
|
131
186
|
language,
|
|
132
|
-
lines: text
|
|
133
|
-
mtimeMs:
|
|
187
|
+
lines: lineCount(text),
|
|
188
|
+
mtimeMs: effectiveFingerprint.mtimeMs,
|
|
134
189
|
path: relativePath,
|
|
135
190
|
profile: (0, registry_1.extractionProfile)(relativePath, language, parserMode),
|
|
136
|
-
size:
|
|
191
|
+
size: effectiveFingerprint.size,
|
|
137
192
|
text,
|
|
138
193
|
};
|
|
139
194
|
}
|
|
@@ -142,8 +197,9 @@ function extractionBackendForProfile(profile) {
|
|
|
142
197
|
return extractionBackendRegistry.backendForProfile(profile);
|
|
143
198
|
}
|
|
144
199
|
function indexCodeFile(file, statements) {
|
|
145
|
-
|
|
146
|
-
statements.
|
|
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);
|
|
147
203
|
extractionBackendForProfile(file.profile).index(file, statements);
|
|
148
204
|
}
|
|
149
205
|
function printRows(rows) {
|
|
@@ -172,6 +228,295 @@ function removeDatabaseFiles(databasePath) {
|
|
|
172
228
|
fs.unlinkSync(filePath);
|
|
173
229
|
}
|
|
174
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
|
+
}
|
|
175
520
|
function codeIndexStaleness(database) {
|
|
176
521
|
const scopes = (0, schema_1.indexedScopes)(database);
|
|
177
522
|
const parserMode = (0, schema_1.indexedParserMode)(database);
|
|
@@ -193,7 +538,7 @@ function codeIndexStaleness(database) {
|
|
|
193
538
|
}
|
|
194
539
|
if (existing.mtimeMs === file.mtimeMs && existing.size === file.size)
|
|
195
540
|
continue;
|
|
196
|
-
if (readCodeFile(file.path, parserMode).hash !== existing.hash)
|
|
541
|
+
if (readCodeFile(file.path, parserMode, file).hash !== existing.hash)
|
|
197
542
|
changed += 1;
|
|
198
543
|
}
|
|
199
544
|
const deleted = indexedRows.filter((row) => !currentPaths.has(String(row.path))).length;
|
|
@@ -383,7 +728,7 @@ function prepareOutputPath() {
|
|
|
383
728
|
const databasePath = codeEvidenceDatabasePath();
|
|
384
729
|
(0, workspace_1.mkdirp)(path.dirname(databasePath.relativePath));
|
|
385
730
|
(0, workspace_1.mkdirp)(code_index_file_policy_1.codeEvidenceDirectory);
|
|
386
|
-
|
|
731
|
+
(0, workspace_1.write)(`${code_index_file_policy_1.codeEvidenceDirectory}/.gitignore`, "*\n!.gitignore\n");
|
|
387
732
|
}
|
|
388
733
|
function codeIndexModeRuntime() {
|
|
389
734
|
return {
|
|
@@ -396,13 +741,20 @@ function codeIndexModeRuntime() {
|
|
|
396
741
|
codeScopes,
|
|
397
742
|
fail,
|
|
398
743
|
indexCodeFile,
|
|
744
|
+
nativeCodeIndexAvailable,
|
|
745
|
+
nativeCodeIndexIncrementalEligible,
|
|
399
746
|
openDatabase,
|
|
400
747
|
prepareOutputPath,
|
|
401
748
|
readCodeFileFingerprint,
|
|
402
749
|
readCodeFile,
|
|
403
750
|
removeDatabaseFiles,
|
|
404
751
|
requireExistingIndex,
|
|
752
|
+
runNativeCodeIndexIncrementalMode,
|
|
753
|
+
runNativeCodeIndexMode,
|
|
754
|
+
selectedCodeIndexEngine,
|
|
405
755
|
selectedCodeParserMode,
|
|
756
|
+
codeIndexEngineSelectionContext,
|
|
757
|
+
shouldUseNativeCodeIndexAuto,
|
|
406
758
|
warnIfCodeIndexStale,
|
|
407
759
|
};
|
|
408
760
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -372,15 +372,15 @@ exports.gitPrepareCommitMsgHook = `#!/bin/sh
|
|
|
372
372
|
MSG_FILE="$1"
|
|
373
373
|
SOURCE="$2"
|
|
374
374
|
|
|
375
|
+
# Security boundary: prepare-commit-msg runs before the commit is created, so it
|
|
376
|
+
# must not execute scripts from the mutable worktree. Trailer generation remains
|
|
377
|
+
# available as an explicit maintainer action, but this hook is intentionally
|
|
378
|
+
# passive.
|
|
375
379
|
case "$SOURCE" in
|
|
376
|
-
merge|squash|commit)
|
|
380
|
+
merge|squash|commit|*)
|
|
377
381
|
exit 0
|
|
378
382
|
;;
|
|
379
383
|
esac
|
|
380
|
-
|
|
381
|
-
if command -v node >/dev/null 2>&1 && [ -f ".githooks/wiki-commit-trailers.js" ]; then
|
|
382
|
-
node .githooks/wiki-commit-trailers.js "$MSG_FILE"
|
|
383
|
-
fi
|
|
384
384
|
`;
|
|
385
385
|
exports.gitWikiCommitTrailersScript = `#!/usr/bin/env node
|
|
386
386
|
|
|
@@ -17,7 +17,7 @@ function codeIndex() {
|
|
|
17
17
|
function printUsage() {
|
|
18
18
|
console.log(`Usage:
|
|
19
19
|
project-librarian [init|update] [options]
|
|
20
|
-
project-librarian install [--scope user|project] [--agents codex|claude|cursor|gemini|all]
|
|
20
|
+
project-librarian install [--scope user|project] [--agents codex|claude|cursor|gemini|all] [--dry-run]
|
|
21
21
|
project-librarian mcp
|
|
22
22
|
|
|
23
23
|
Options:
|
|
@@ -34,6 +34,7 @@ Options:
|
|
|
34
34
|
--issue-draft Print a problem/side-effect GitHub issue body draft.
|
|
35
35
|
--issue-body-file <path> With --issue-create, use an existing Markdown body file.
|
|
36
36
|
--issue-title <title> Override the generated issue draft title.
|
|
37
|
+
--dry-run With install, preview copied skill files without writing them.
|
|
37
38
|
--query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
|
|
38
39
|
--wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
|
|
39
40
|
--wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
|
|
@@ -61,6 +62,7 @@ Options:
|
|
|
61
62
|
--acknowledge-small-repo With --code-index, proceed below the small-repo scale gate after its cost warning.
|
|
62
63
|
--incremental With --code-index, require an existing compatible index and update only changes.
|
|
63
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.
|
|
64
66
|
--code-parser <mode> With --code-index, use parser mode default or tree-sitter.
|
|
65
67
|
--code-query <sql> Run conservative read-only SQL over the code evidence index.
|
|
66
68
|
--code-status, --code-files Inspect the code evidence index.
|
|
@@ -164,6 +166,10 @@ if (args_1.codeIndexFullMode && !args_1.codeIndexMode) {
|
|
|
164
166
|
console.error("--code-index-full is only supported with --code-index.");
|
|
165
167
|
process.exit(1);
|
|
166
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
|
+
}
|
|
167
173
|
if (args_1.codeParserMode && !args_1.codeIndexMode) {
|
|
168
174
|
console.error("--code-parser is only supported with --code-index.");
|
|
169
175
|
process.exit(1);
|
package/dist/install-skill.js
CHANGED
|
@@ -112,26 +112,119 @@ function installTarget(agent, scope) {
|
|
|
112
112
|
const base = scope === "user" ? userAgentRoot(agent) : path.join(process.cwd(), projectAgentRoot(agent));
|
|
113
113
|
return path.join(base, "skills", skillName);
|
|
114
114
|
}
|
|
115
|
+
function assertNoTargetSymlink(targetRoot, target, includeLeaf) {
|
|
116
|
+
const rootResolved = path.resolve(targetRoot);
|
|
117
|
+
const targetResolved = path.resolve(target);
|
|
118
|
+
if (targetResolved !== rootResolved && !targetResolved.startsWith(`${rootResolved}${path.sep}`)) {
|
|
119
|
+
fail(`skill install target escaped target root: ${target}`);
|
|
120
|
+
}
|
|
121
|
+
if (fs.existsSync(rootResolved) && fs.lstatSync(rootResolved).isSymbolicLink()) {
|
|
122
|
+
fail("skill install refuses to follow destination symlink: .");
|
|
123
|
+
}
|
|
124
|
+
const relative = path.relative(rootResolved, targetResolved);
|
|
125
|
+
const parts = relative ? relative.split(path.sep).filter(Boolean) : [];
|
|
126
|
+
const checkedParts = includeLeaf ? parts : parts.slice(0, -1);
|
|
127
|
+
let current = rootResolved;
|
|
128
|
+
for (const part of checkedParts) {
|
|
129
|
+
current = path.join(current, part);
|
|
130
|
+
if (!fs.existsSync(current))
|
|
131
|
+
continue;
|
|
132
|
+
const stat = fs.lstatSync(current);
|
|
133
|
+
if (stat.isSymbolicLink()) {
|
|
134
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(rootResolved, current) || "."}`);
|
|
135
|
+
}
|
|
136
|
+
if (current !== targetResolved && !stat.isDirectory()) {
|
|
137
|
+
fail(`skill install target has a non-directory path component: ${path.relative(rootResolved, current)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function isInsidePath(base, target) {
|
|
142
|
+
const baseResolved = path.resolve(base);
|
|
143
|
+
const targetResolved = path.resolve(target);
|
|
144
|
+
return targetResolved === baseResolved || targetResolved.startsWith(`${baseResolved}${path.sep}`);
|
|
145
|
+
}
|
|
146
|
+
function mkdirFromBaseNoSymlink(base, target) {
|
|
147
|
+
const baseResolved = path.resolve(base);
|
|
148
|
+
const targetResolved = path.resolve(target);
|
|
149
|
+
if (!isInsidePath(baseResolved, targetResolved)) {
|
|
150
|
+
fail(`skill install target escaped checked base: ${target}`);
|
|
151
|
+
}
|
|
152
|
+
let current = baseResolved;
|
|
153
|
+
const parts = path.relative(baseResolved, targetResolved).split(path.sep).filter(Boolean);
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
current = path.join(current, part);
|
|
156
|
+
if (fs.existsSync(current)) {
|
|
157
|
+
const stat = fs.lstatSync(current);
|
|
158
|
+
if (stat.isSymbolicLink()) {
|
|
159
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(baseResolved, current)}`);
|
|
160
|
+
}
|
|
161
|
+
if (!stat.isDirectory()) {
|
|
162
|
+
fail(`skill install target has a non-directory path component: ${path.relative(baseResolved, current)}`);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
fs.mkdirSync(current);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function mkdirpNoTargetSymlink(targetRoot, target) {
|
|
170
|
+
const rootResolved = path.resolve(targetRoot);
|
|
171
|
+
const targetResolved = path.resolve(target);
|
|
172
|
+
const cwdResolved = path.resolve(process.cwd());
|
|
173
|
+
if (isInsidePath(cwdResolved, rootResolved)) {
|
|
174
|
+
mkdirFromBaseNoSymlink(cwdResolved, rootResolved);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
fs.mkdirSync(rootResolved, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
const relative = path.relative(rootResolved, targetResolved);
|
|
180
|
+
let current = rootResolved;
|
|
181
|
+
if (!relative) {
|
|
182
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
183
|
+
fail("skill install refuses to follow destination symlink: .");
|
|
184
|
+
}
|
|
185
|
+
if (!fs.existsSync(current))
|
|
186
|
+
fs.mkdirSync(current);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
190
|
+
current = path.join(current, part);
|
|
191
|
+
if (fs.existsSync(current)) {
|
|
192
|
+
const stat = fs.lstatSync(current);
|
|
193
|
+
if (stat.isSymbolicLink()) {
|
|
194
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(rootResolved, current)}`);
|
|
195
|
+
}
|
|
196
|
+
if (!stat.isDirectory()) {
|
|
197
|
+
fail(`skill install target has a non-directory path component: ${path.relative(rootResolved, current)}`);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
fs.mkdirSync(current);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
115
204
|
function sameFile(source, target) {
|
|
116
|
-
if (!fs.existsSync(target)
|
|
205
|
+
if (!fs.existsSync(target))
|
|
206
|
+
return false;
|
|
207
|
+
const stat = fs.lstatSync(target);
|
|
208
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
117
209
|
return false;
|
|
118
210
|
return fs.readFileSync(source).equals(fs.readFileSync(target));
|
|
119
211
|
}
|
|
120
|
-
function copyPath(source, target, dryRun) {
|
|
212
|
+
function copyPath(source, target, targetRoot, dryRun) {
|
|
121
213
|
if (!fs.existsSync(source))
|
|
122
214
|
fail(`missing package file: ${source}`);
|
|
123
215
|
const existed = fs.existsSync(target);
|
|
124
216
|
if (dryRun)
|
|
125
217
|
return "dry-run";
|
|
126
218
|
const sourceStat = fs.statSync(source);
|
|
219
|
+
assertNoTargetSymlink(targetRoot, target, true);
|
|
127
220
|
if (sourceStat.isDirectory()) {
|
|
128
|
-
|
|
221
|
+
mkdirpNoTargetSymlink(targetRoot, target);
|
|
129
222
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
130
|
-
copyPath(path.join(source, entry.name), path.join(target, entry.name), false);
|
|
223
|
+
copyPath(path.join(source, entry.name), path.join(target, entry.name), targetRoot, false);
|
|
131
224
|
}
|
|
132
225
|
return existed ? "updated" : "created";
|
|
133
226
|
}
|
|
134
|
-
|
|
227
|
+
mkdirpNoTargetSymlink(targetRoot, path.dirname(target));
|
|
135
228
|
if (sameFile(source, target))
|
|
136
229
|
return "exists";
|
|
137
230
|
fs.copyFileSync(source, target);
|
|
@@ -146,7 +239,7 @@ function copyPackageFiles(targetRoot, dryRun, labelRoot = targetRoot) {
|
|
|
146
239
|
return packageFiles.map((relativePath) => {
|
|
147
240
|
const source = path.join(root, relativePath);
|
|
148
241
|
const target = path.join(targetRoot, relativePath);
|
|
149
|
-
return [path.join(labelRoot, relativePath), copyPath(source, target, dryRun)];
|
|
242
|
+
return [path.join(labelRoot, relativePath), copyPath(source, target, targetRoot, dryRun)];
|
|
150
243
|
});
|
|
151
244
|
}
|
|
152
245
|
function syncProjectSkillInstall(agent) {
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Hand-rolled MCP stdio server for the code-evidence index.
|
|
3
3
|
//
|
|
4
|
-
// Why hand-rolled: the
|
|
5
|
-
// node:sqlite
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Why hand-rolled: the package keeps the MCP surface dependency-light. Code
|
|
5
|
+
// evidence already depends on node:sqlite, the package's TypeScript dependency,
|
|
6
|
+
// and optional tree-sitter grammars; adopting @modelcontextprotocol/sdk would add
|
|
7
|
+
// another hard runtime dependency for a small stdio slice.
|
|
8
8
|
//
|
|
9
9
|
// Transport: MCP stdio transport carries one JSON-RPC message per line on stdin
|
|
10
10
|
// and stdout (newline-delimited JSON, "ndjson"); stderr is free for logging. We
|