project-librarian 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +261 -93
- package/README.md +190 -76
- package/SKILL.md +16 -10
- package/dist/args.js +25 -2
- package/dist/code-index-file-policy.js +105 -2
- package/dist/code-index.js +338 -70
- package/dist/hooks.js +78 -0
- package/dist/init-project-wiki.js +265 -167
- package/dist/install-skill.js +2 -9
- package/dist/mcp-server.js +709 -0
- package/dist/migration.js +875 -60
- package/dist/modes.js +262 -62
- package/dist/retrieval-eval.js +68 -0
- package/dist/taxonomy.js +193 -0
- package/dist/templates.js +214 -36
- package/dist/wiki-concepts.js +49 -0
- package/dist/wiki-files.js +120 -27
- package/dist/wiki-graph.js +166 -0
- package/dist/wiki-visualizer.js +558 -0
- package/package.json +5 -4
- package/README.ja.md +0 -215
- package/README.zh.md +0 -215
|
@@ -33,20 +33,28 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.maxIndexedBytes = exports.ignoredDirectories = void 0;
|
|
36
|
+
exports.SMALL_REPO_FILE_THRESHOLD = exports.maxIndexedBytes = exports.ignoredDirectories = exports.codeEvidenceDirectory = void 0;
|
|
37
37
|
exports.fileLanguage = fileLanguage;
|
|
38
38
|
exports.isJavaScriptLike = isJavaScriptLike;
|
|
39
39
|
exports.shouldIndexFile = shouldIndexFile;
|
|
40
40
|
exports.isIgnoredCodePath = isIgnoredCodePath;
|
|
41
|
+
exports.discoverCodeFiles = discoverCodeFiles;
|
|
42
|
+
exports.smallRepoCodeIndexGate = smallRepoCodeIndexGate;
|
|
43
|
+
const childProcess = __importStar(require("node:child_process"));
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
41
45
|
const path = __importStar(require("node:path"));
|
|
42
46
|
const workspace_1 = require("./workspace");
|
|
47
|
+
// Single source of truth for the disposable code-evidence directory name. Both
|
|
48
|
+
// the heavy code-index module and the light bootstrap path (hooks.ts) key off
|
|
49
|
+
// this; keeping it here avoids loading typescript/node:sqlite during bootstrap.
|
|
50
|
+
exports.codeEvidenceDirectory = ".project-wiki";
|
|
43
51
|
exports.ignoredDirectories = new Set([
|
|
44
52
|
".git",
|
|
45
53
|
".codex",
|
|
46
54
|
".claude",
|
|
47
55
|
".cursor",
|
|
48
56
|
".gemini",
|
|
49
|
-
|
|
57
|
+
exports.codeEvidenceDirectory,
|
|
50
58
|
"node_modules",
|
|
51
59
|
".next",
|
|
52
60
|
"dist",
|
|
@@ -120,3 +128,98 @@ function isIgnoredCodePath(relativePath) {
|
|
|
120
128
|
.filter(Boolean)
|
|
121
129
|
.some((part) => exports.ignoredDirectories.has(part));
|
|
122
130
|
}
|
|
131
|
+
// Mirrors normalizeProjectRelative in code-index.ts for git ls-files output, but
|
|
132
|
+
// throws instead of exiting so the light bootstrap path can use discovery too.
|
|
133
|
+
// Git emits repo-relative paths, so the escape branch is unreachable for real
|
|
134
|
+
// output; it stays as a loud guard, not a recovery path.
|
|
135
|
+
function normalizeGitIndexedFile(input) {
|
|
136
|
+
const raw = input.trim() || ".";
|
|
137
|
+
const resolved = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(workspace_1.root, raw);
|
|
138
|
+
const rootResolved = path.resolve(workspace_1.root);
|
|
139
|
+
if (resolved !== rootResolved && !resolved.startsWith(`${rootResolved}${path.sep}`)) {
|
|
140
|
+
throw new Error(`git-indexed file must stay inside the project root: ${input}`);
|
|
141
|
+
}
|
|
142
|
+
return (0, workspace_1.normalizePath)(path.relative(rootResolved, resolved)) || ".";
|
|
143
|
+
}
|
|
144
|
+
function walkCodeFiles(relativePath, files = []) {
|
|
145
|
+
if (isIgnoredCodePath(relativePath))
|
|
146
|
+
return files.sort();
|
|
147
|
+
const target = (0, workspace_1.abs)(relativePath);
|
|
148
|
+
if (!fs.existsSync(target))
|
|
149
|
+
return files;
|
|
150
|
+
const stat = fs.statSync(target);
|
|
151
|
+
if (stat.isFile()) {
|
|
152
|
+
if (stat.size <= exports.maxIndexedBytes && shouldIndexFile(relativePath))
|
|
153
|
+
files.push(relativePath);
|
|
154
|
+
return files.sort();
|
|
155
|
+
}
|
|
156
|
+
for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
|
|
157
|
+
const child = (0, workspace_1.normalizePath)(path.join(relativePath, entry.name));
|
|
158
|
+
if (entry.isDirectory()) {
|
|
159
|
+
if (!exports.ignoredDirectories.has(entry.name))
|
|
160
|
+
walkCodeFiles(child, files);
|
|
161
|
+
}
|
|
162
|
+
else if (entry.isFile() && shouldIndexFile(child)) {
|
|
163
|
+
const childStat = fs.statSync((0, workspace_1.abs)(child));
|
|
164
|
+
if (childStat.size <= exports.maxIndexedBytes)
|
|
165
|
+
files.push(child);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return files.sort();
|
|
169
|
+
}
|
|
170
|
+
function gitTrackedAndUnignoredFiles(scopes) {
|
|
171
|
+
try {
|
|
172
|
+
const output = childProcess.execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", ...scopes], {
|
|
173
|
+
cwd: workspace_1.root,
|
|
174
|
+
encoding: "buffer",
|
|
175
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
176
|
+
});
|
|
177
|
+
return output.toString("utf8").split("\0").filter(Boolean).map((file) => normalizeGitIndexedFile(file));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function discoverCodeFiles(scopes) {
|
|
184
|
+
const gitFiles = gitTrackedAndUnignoredFiles(scopes);
|
|
185
|
+
const candidates = gitFiles ?? scopes.flatMap((scope) => walkCodeFiles(scope));
|
|
186
|
+
return Array.from(new Set(candidates))
|
|
187
|
+
.filter((file) => !isIgnoredCodePath(file))
|
|
188
|
+
.filter((file) => fs.existsSync((0, workspace_1.abs)(file)))
|
|
189
|
+
.filter((file) => fs.statSync((0, workspace_1.abs)(file)).isFile())
|
|
190
|
+
.filter((file) => shouldIndexFile(file))
|
|
191
|
+
.filter((file) => fs.statSync((0, workspace_1.abs)(file)).size <= exports.maxIndexedBytes)
|
|
192
|
+
.sort();
|
|
193
|
+
}
|
|
194
|
+
// --- Scale-aware code-evidence gate ------------------------------------------
|
|
195
|
+
//
|
|
196
|
+
// Threshold in indexable files (the discoverCodeFiles count) below which the
|
|
197
|
+
// code-evidence surfaces warn (--code-index) or skip by default (bootstrap MCP
|
|
198
|
+
// auto-registration). Evidence: stageR1 real-corpus run, 2026-06-12
|
|
199
|
+
// (benchmarks/reports/llm/stageR1-real.md) — the ~1.2k-file repo LOST every
|
|
200
|
+
// measured question (impact_trace +116.9%, workspace_graph +106.5% cost-weighted
|
|
201
|
+
// tokens vs direct reads) while the ~11.8k-file repo still lost the cheap
|
|
202
|
+
// ownership lookup (+99.0%) and won only the expensive traversal questions
|
|
203
|
+
// (impact_trace -27.7%, workspace_graph -2.6%); stage2d synthetic scales
|
|
204
|
+
// (benchmarks/reports/llm/stage2d-codegraph.md) lost across the board. 5000 sits
|
|
205
|
+
// between the two real measured points: an n=2 extrapolation (1.2k all-loss /
|
|
206
|
+
// 11.8k partial win), explicitly subject to revision as more scales are measured.
|
|
207
|
+
exports.SMALL_REPO_FILE_THRESHOLD = 5000;
|
|
208
|
+
// Decision for the --code-index scale gate: below the threshold the build halts
|
|
209
|
+
// with an evidence-citing warning unless the caller explicitly acknowledged the
|
|
210
|
+
// measured cost. Consent is honored, never refused (2026-06-12 decision: defaults
|
|
211
|
+
// follow the evidence, explicit requests get warning + consent).
|
|
212
|
+
function smallRepoCodeIndexGate(indexableFileCount, acknowledged) {
|
|
213
|
+
if (acknowledged || indexableFileCount >= exports.SMALL_REPO_FILE_THRESHOLD)
|
|
214
|
+
return { proceed: true, warning: "" };
|
|
215
|
+
return {
|
|
216
|
+
proceed: false,
|
|
217
|
+
warning: [
|
|
218
|
+
`--code-index halted: ${indexableFileCount} indexable files is below the ${exports.SMALL_REPO_FILE_THRESHOLD}-file scale threshold.`,
|
|
219
|
+
"Measured evidence (real-corpus stageR1): on a ~1.2k-file repo the code-evidence track cost MORE on every measured question (impact_trace +116.9%, workspace_graph +106.5% cost-weighted tokens vs direct reads), and even on a ~11.8k-file repo ownership-style cheap lookups lost (+99.0%); only expensive traversal questions won there (impact_trace -27.7%).",
|
|
220
|
+
"Reports: benchmarks/reports/llm/stageR1-real.md and benchmarks/reports/llm/stage2d-codegraph.md in the project-librarian repo (threshold is an n=2 extrapolation, subject to revision).",
|
|
221
|
+
"Not measured, so not disproven: human-facing report value (--code-report) and answer accuracy/grounding value. This gate reflects measured LLM token cost only.",
|
|
222
|
+
"To build the index anyway, re-run with --acknowledge-small-repo.",
|
|
223
|
+
].join("\n"),
|
|
224
|
+
};
|
|
225
|
+
}
|
package/dist/code-index.js
CHANGED
|
@@ -33,17 +33,32 @@ 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 = void 0;
|
|
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;
|
|
46
|
+
exports.searchSymbols = searchSymbols;
|
|
47
|
+
exports.codeImpact = codeImpact;
|
|
48
|
+
exports.codeContextPack = codeContextPack;
|
|
49
|
+
exports.codeIndexSnapshot = codeIndexSnapshot;
|
|
50
|
+
exports.openCodeEvidenceDatabaseForServing = openCodeEvidenceDatabaseForServing;
|
|
36
51
|
exports.runCodeIndexMode = runCodeIndexMode;
|
|
37
52
|
exports.runCodeQueryMode = runCodeQueryMode;
|
|
38
53
|
exports.runCodeReportMode = runCodeReportMode;
|
|
39
54
|
exports.runCodeStatusMode = runCodeStatusMode;
|
|
40
55
|
exports.runCodeFilesMode = runCodeFilesMode;
|
|
41
56
|
exports.runCodeImpactMode = runCodeImpactMode;
|
|
57
|
+
exports.runCodeContextPackMode = runCodeContextPackMode;
|
|
42
58
|
exports.runCodeSearchSymbolMode = runCodeSearchSymbolMode;
|
|
43
59
|
exports.isCodeEvidenceMode = isCodeEvidenceMode;
|
|
44
60
|
exports.isCodeEvidenceModeFor = isCodeEvidenceModeFor;
|
|
45
61
|
const crypto = __importStar(require("node:crypto"));
|
|
46
|
-
const childProcess = __importStar(require("node:child_process"));
|
|
47
62
|
const fs = __importStar(require("node:fs"));
|
|
48
63
|
const path = __importStar(require("node:path"));
|
|
49
64
|
const ts = __importStar(require("typescript"));
|
|
@@ -52,7 +67,8 @@ const code_index_db_1 = require("./code-index-db");
|
|
|
52
67
|
const code_index_file_policy_1 = require("./code-index-file-policy");
|
|
53
68
|
const code_index_sql_1 = require("./code-index-sql");
|
|
54
69
|
const workspace_1 = require("./workspace");
|
|
55
|
-
|
|
70
|
+
exports.codeContextPackCharCap = 4000;
|
|
71
|
+
exports.codeContextPackTruncationNotice = "[truncated - refine the query]";
|
|
56
72
|
const codeIndexSchemaVersion = "3";
|
|
57
73
|
const httpMethods = new Set(["all", "delete", "get", "patch", "post", "put"]);
|
|
58
74
|
const treeSitterGrammarPackages = {
|
|
@@ -117,11 +133,11 @@ function normalizeProjectRelative(input, label) {
|
|
|
117
133
|
return (0, workspace_1.normalizePath)(path.relative(rootResolved, resolved)) || ".";
|
|
118
134
|
}
|
|
119
135
|
function codeEvidenceDatabasePath() {
|
|
120
|
-
const raw = args_1.codeIndexOutput.trim() || `${codeEvidenceDirectory}/code-evidence.sqlite`;
|
|
136
|
+
const raw = args_1.codeIndexOutput.trim() || `${code_index_file_policy_1.codeEvidenceDirectory}/code-evidence.sqlite`;
|
|
121
137
|
const absolutePath = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(workspace_1.root, raw);
|
|
122
|
-
const evidenceRoot = path.resolve(workspace_1.root, codeEvidenceDirectory);
|
|
138
|
+
const evidenceRoot = path.resolve(workspace_1.root, code_index_file_policy_1.codeEvidenceDirectory);
|
|
123
139
|
if (absolutePath === evidenceRoot || !absolutePath.startsWith(`${evidenceRoot}${path.sep}`)) {
|
|
124
|
-
fail(`--code-index-out must stay inside ${codeEvidenceDirectory}/`);
|
|
140
|
+
fail(`--code-index-out must stay inside ${code_index_file_policy_1.codeEvidenceDirectory}/`);
|
|
125
141
|
}
|
|
126
142
|
return {
|
|
127
143
|
absolutePath,
|
|
@@ -182,56 +198,6 @@ function extractionProfile(relativePath, parserMode) {
|
|
|
182
198
|
return "config";
|
|
183
199
|
return "inventory-only";
|
|
184
200
|
}
|
|
185
|
-
function walkCodeFiles(relativePath, files = []) {
|
|
186
|
-
if ((0, code_index_file_policy_1.isIgnoredCodePath)(relativePath))
|
|
187
|
-
return files.sort();
|
|
188
|
-
const target = (0, workspace_1.abs)(relativePath);
|
|
189
|
-
if (!fs.existsSync(target))
|
|
190
|
-
return files;
|
|
191
|
-
const stat = fs.statSync(target);
|
|
192
|
-
if (stat.isFile()) {
|
|
193
|
-
if (stat.size <= code_index_file_policy_1.maxIndexedBytes && (0, code_index_file_policy_1.shouldIndexFile)(relativePath))
|
|
194
|
-
files.push(relativePath);
|
|
195
|
-
return files.sort();
|
|
196
|
-
}
|
|
197
|
-
for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
|
|
198
|
-
const child = (0, workspace_1.normalizePath)(path.join(relativePath, entry.name));
|
|
199
|
-
if (entry.isDirectory()) {
|
|
200
|
-
if (!code_index_file_policy_1.ignoredDirectories.has(entry.name))
|
|
201
|
-
walkCodeFiles(child, files);
|
|
202
|
-
}
|
|
203
|
-
else if (entry.isFile() && (0, code_index_file_policy_1.shouldIndexFile)(child)) {
|
|
204
|
-
const childStat = fs.statSync((0, workspace_1.abs)(child));
|
|
205
|
-
if (childStat.size <= code_index_file_policy_1.maxIndexedBytes)
|
|
206
|
-
files.push(child);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return files.sort();
|
|
210
|
-
}
|
|
211
|
-
function gitTrackedAndUnignoredFiles(scopes) {
|
|
212
|
-
try {
|
|
213
|
-
const output = childProcess.execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", ...scopes], {
|
|
214
|
-
cwd: workspace_1.root,
|
|
215
|
-
encoding: "buffer",
|
|
216
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
217
|
-
});
|
|
218
|
-
return output.toString("utf8").split("\0").filter(Boolean).map((file) => normalizeProjectRelative(file, "git-indexed file"));
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function discoverCodeFiles(scopes) {
|
|
225
|
-
const gitFiles = gitTrackedAndUnignoredFiles(scopes);
|
|
226
|
-
const candidates = gitFiles ?? scopes.flatMap((scope) => walkCodeFiles(scope));
|
|
227
|
-
return Array.from(new Set(candidates))
|
|
228
|
-
.filter((file) => !(0, code_index_file_policy_1.isIgnoredCodePath)(file))
|
|
229
|
-
.filter((file) => fs.existsSync((0, workspace_1.abs)(file)))
|
|
230
|
-
.filter((file) => fs.statSync((0, workspace_1.abs)(file)).isFile())
|
|
231
|
-
.filter((file) => (0, code_index_file_policy_1.shouldIndexFile)(file))
|
|
232
|
-
.filter((file) => fs.statSync((0, workspace_1.abs)(file)).size <= code_index_file_policy_1.maxIndexedBytes)
|
|
233
|
-
.sort();
|
|
234
|
-
}
|
|
235
201
|
function readCodeFile(relativePath, parserMode = "default") {
|
|
236
202
|
const text = fs.readFileSync((0, workspace_1.abs)(relativePath), "utf8");
|
|
237
203
|
return {
|
|
@@ -1163,7 +1129,7 @@ function removeDatabaseFiles(databasePath) {
|
|
|
1163
1129
|
function codeIndexStaleness(database) {
|
|
1164
1130
|
const scopes = indexedScopes(database);
|
|
1165
1131
|
const parserMode = indexedParserMode(database);
|
|
1166
|
-
const current = new Map(discoverCodeFiles(scopes.length > 0 ? scopes : ["."]).map((file) => {
|
|
1132
|
+
const current = new Map((0, code_index_file_policy_1.discoverCodeFiles)(scopes.length > 0 ? scopes : ["."]).map((file) => {
|
|
1167
1133
|
const codeFile = readCodeFile(file, parserMode);
|
|
1168
1134
|
return [codeFile.path, codeFile.hash];
|
|
1169
1135
|
}));
|
|
@@ -1311,6 +1277,13 @@ function matchingCodeowners(filePath, rules) {
|
|
|
1311
1277
|
const matches = rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
|
|
1312
1278
|
return matches[matches.length - 1]?.owners ?? [];
|
|
1313
1279
|
}
|
|
1280
|
+
// Return every CODEOWNERS rule that matches a path, in file order. The last entry
|
|
1281
|
+
// is the effective owner under last-match-wins; earlier entries are overridden.
|
|
1282
|
+
// Reuses the same matcher as matchingCodeowners so precedence answers stay
|
|
1283
|
+
// consistent with --code-report / --code-impact.
|
|
1284
|
+
function matchedCodeownerRules(filePath, rules) {
|
|
1285
|
+
return rules.filter((rule) => codeownerPatternMatches(rule.pattern, filePath));
|
|
1286
|
+
}
|
|
1314
1287
|
function ownershipContext() {
|
|
1315
1288
|
return {
|
|
1316
1289
|
codeownerRules: codeownerRules(),
|
|
@@ -1608,14 +1581,138 @@ function codeReportForRequestedSection(database) {
|
|
|
1608
1581
|
data: codeReportSectionData(database, section),
|
|
1609
1582
|
};
|
|
1610
1583
|
}
|
|
1584
|
+
function escapeLikeTerm(term) {
|
|
1585
|
+
return term.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
1586
|
+
}
|
|
1587
|
+
function containsLikePattern(term) {
|
|
1588
|
+
return `%${escapeLikeTerm(term)}%`;
|
|
1589
|
+
}
|
|
1590
|
+
function prefixLikePattern(term) {
|
|
1591
|
+
return `${escapeLikeTerm(term)}%`;
|
|
1592
|
+
}
|
|
1593
|
+
function ftsPrefixQuery(term) {
|
|
1594
|
+
const tokens = Array.from(new Set(term.match(/[\p{L}\p{N}_]+/gu) ?? []));
|
|
1595
|
+
return tokens.slice(0, 8).map((token) => `"${token.replace(/"/g, "\"\"")}"*`).join(" AND ");
|
|
1596
|
+
}
|
|
1597
|
+
function stringValue(row, key) {
|
|
1598
|
+
const value = row[key];
|
|
1599
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : "";
|
|
1600
|
+
}
|
|
1601
|
+
function addRankedRow(rowsByKey, row, key, score) {
|
|
1602
|
+
const current = rowsByKey.get(key);
|
|
1603
|
+
if (!current || score > current.score)
|
|
1604
|
+
rowsByKey.set(key, { row, score });
|
|
1605
|
+
}
|
|
1606
|
+
function rankedRows(rowsByKey, limit, stableKeys) {
|
|
1607
|
+
return Array.from(rowsByKey.values())
|
|
1608
|
+
.sort((left, right) => {
|
|
1609
|
+
const scoreDelta = right.score - left.score;
|
|
1610
|
+
if (scoreDelta !== 0)
|
|
1611
|
+
return scoreDelta;
|
|
1612
|
+
for (const key of stableKeys) {
|
|
1613
|
+
const compared = stringValue(left.row, key).localeCompare(stringValue(right.row, key));
|
|
1614
|
+
if (compared !== 0)
|
|
1615
|
+
return compared;
|
|
1616
|
+
}
|
|
1617
|
+
return 0;
|
|
1618
|
+
})
|
|
1619
|
+
.slice(0, limit)
|
|
1620
|
+
.map((ranked) => ranked.row);
|
|
1621
|
+
}
|
|
1622
|
+
function searchFiles(database, term, limit = 25) {
|
|
1623
|
+
const normalized = term.trim();
|
|
1624
|
+
if (!normalized)
|
|
1625
|
+
return [];
|
|
1626
|
+
const contains = containsLikePattern(normalized);
|
|
1627
|
+
const prefix = prefixLikePattern(normalized);
|
|
1628
|
+
const rowsByKey = new Map();
|
|
1629
|
+
const exactRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path = ? ORDER BY path LIMIT ?").all(normalized, limit);
|
|
1630
|
+
exactRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 900));
|
|
1631
|
+
const prefixRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(prefix, limit);
|
|
1632
|
+
prefixRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 750));
|
|
1633
|
+
const ftsQuery = ftsPrefixQuery(normalized);
|
|
1634
|
+
if (ftsQuery) {
|
|
1635
|
+
const ftsRows = database.prepare(`
|
|
1636
|
+
SELECT files.path, files.language, files.profile, files.lines, files.bytes
|
|
1637
|
+
FROM files_fts
|
|
1638
|
+
JOIN files ON files.path = files_fts.path
|
|
1639
|
+
WHERE files_fts MATCH ?
|
|
1640
|
+
ORDER BY bm25(files_fts, 8.0, 1.0, 1.0, 0.25), files.path
|
|
1641
|
+
LIMIT ?
|
|
1642
|
+
`).all(ftsQuery, limit);
|
|
1643
|
+
ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 650 - index));
|
|
1644
|
+
}
|
|
1645
|
+
const containsRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(contains, limit);
|
|
1646
|
+
containsRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 500));
|
|
1647
|
+
return rankedRows(rowsByKey, limit, ["path"]);
|
|
1648
|
+
}
|
|
1649
|
+
function symbolKey(row) {
|
|
1650
|
+
return [
|
|
1651
|
+
stringValue(row, "file_path"),
|
|
1652
|
+
stringValue(row, "line"),
|
|
1653
|
+
stringValue(row, "kind"),
|
|
1654
|
+
stringValue(row, "name"),
|
|
1655
|
+
stringValue(row, "signature"),
|
|
1656
|
+
].join("\u0000");
|
|
1657
|
+
}
|
|
1658
|
+
function searchSymbols(database, term, limit = 50) {
|
|
1659
|
+
const normalized = term.trim();
|
|
1660
|
+
if (!normalized)
|
|
1661
|
+
return [];
|
|
1662
|
+
const contains = containsLikePattern(normalized);
|
|
1663
|
+
const prefix = prefixLikePattern(normalized);
|
|
1664
|
+
const rowsByKey = new Map();
|
|
1665
|
+
const exactRows = database.prepare(`
|
|
1666
|
+
SELECT name, kind, file_path, line, signature
|
|
1667
|
+
FROM symbols
|
|
1668
|
+
WHERE name = ? OR signature = ?
|
|
1669
|
+
ORDER BY file_path, line
|
|
1670
|
+
LIMIT ?
|
|
1671
|
+
`).all(normalized, normalized, limit);
|
|
1672
|
+
exactRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 1000));
|
|
1673
|
+
const prefixRows = database.prepare(`
|
|
1674
|
+
SELECT name, kind, file_path, line, signature
|
|
1675
|
+
FROM symbols
|
|
1676
|
+
WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\'
|
|
1677
|
+
ORDER BY file_path, line
|
|
1678
|
+
LIMIT ?
|
|
1679
|
+
`).all(prefix, prefix, limit);
|
|
1680
|
+
prefixRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 850));
|
|
1681
|
+
const ftsQuery = ftsPrefixQuery(normalized);
|
|
1682
|
+
if (ftsQuery) {
|
|
1683
|
+
const ftsRows = database.prepare(`
|
|
1684
|
+
SELECT symbols.name, symbols.kind, symbols.file_path, symbols.line, symbols.signature
|
|
1685
|
+
FROM symbols_fts
|
|
1686
|
+
JOIN symbols
|
|
1687
|
+
ON symbols.name = symbols_fts.name
|
|
1688
|
+
AND symbols.kind = symbols_fts.kind
|
|
1689
|
+
AND symbols.file_path = symbols_fts.file_path
|
|
1690
|
+
AND symbols.signature = symbols_fts.signature
|
|
1691
|
+
WHERE symbols_fts MATCH ?
|
|
1692
|
+
ORDER BY bm25(symbols_fts, 8.0, 1.0, 4.0, 2.0), symbols.file_path, symbols.line
|
|
1693
|
+
LIMIT ?
|
|
1694
|
+
`).all(ftsQuery, limit);
|
|
1695
|
+
ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, symbolKey(row), 700 - index));
|
|
1696
|
+
}
|
|
1697
|
+
const containsRows = database.prepare(`
|
|
1698
|
+
SELECT name, kind, file_path, line, signature
|
|
1699
|
+
FROM symbols
|
|
1700
|
+
WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\'
|
|
1701
|
+
ORDER BY file_path, line
|
|
1702
|
+
LIMIT ?
|
|
1703
|
+
`).all(contains, contains, contains, limit);
|
|
1704
|
+
containsRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 500));
|
|
1705
|
+
return rankedRows(rowsByKey, limit, ["file_path", "line", "kind", "name", "signature"]);
|
|
1706
|
+
}
|
|
1611
1707
|
function codeImpact(database, target) {
|
|
1612
|
-
const
|
|
1613
|
-
const
|
|
1614
|
-
const
|
|
1615
|
-
const
|
|
1616
|
-
const
|
|
1617
|
-
const
|
|
1618
|
-
const
|
|
1708
|
+
const normalized = target.trim();
|
|
1709
|
+
const like = containsLikePattern(normalized);
|
|
1710
|
+
const fileMatches = searchFiles(database, normalized, 25);
|
|
1711
|
+
const symbolMatches = searchSymbols(database, normalized, 50);
|
|
1712
|
+
const routeMatches = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? ESCAPE '\\' OR handler LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 50").all(like, like, like);
|
|
1713
|
+
const importMatches = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? ESCAPE '\\' OR to_ref LIKE ? ESCAPE '\\' OR imported LIKE ? ESCAPE '\\' ORDER BY from_file, line LIMIT 75").all(like, like, like);
|
|
1714
|
+
const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? ESCAPE '\\' OR source LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 100").all(like, like);
|
|
1715
|
+
const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 100").all(like);
|
|
1619
1716
|
const routeTargets = routeMatches.map((row) => `${String(row.method)} ${String(row.route)}`);
|
|
1620
1717
|
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);
|
|
1621
1718
|
const relatedFilePaths = Array.from(new Set([
|
|
@@ -1670,16 +1767,172 @@ function codeImpact(database, target) {
|
|
|
1670
1767
|
})).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner)),
|
|
1671
1768
|
};
|
|
1672
1769
|
}
|
|
1770
|
+
function sampleLines(items, limit, render) {
|
|
1771
|
+
const lines = items.slice(0, limit).map(render);
|
|
1772
|
+
if (items.length > limit)
|
|
1773
|
+
lines.push(` ...+${items.length - limit} more`);
|
|
1774
|
+
return lines;
|
|
1775
|
+
}
|
|
1776
|
+
function pushBudgetedLine(lines, line) {
|
|
1777
|
+
const candidate = [...lines, line].join("\n");
|
|
1778
|
+
if (candidate.length > exports.codeContextPackCharCap)
|
|
1779
|
+
return false;
|
|
1780
|
+
lines.push(line);
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
function pushBudgetedSection(lines, title, items, limit, render) {
|
|
1784
|
+
if (items.length === 0)
|
|
1785
|
+
return;
|
|
1786
|
+
if (!pushBudgetedLine(lines, title))
|
|
1787
|
+
return;
|
|
1788
|
+
for (const line of sampleLines(items, limit, render)) {
|
|
1789
|
+
if (!pushBudgetedLine(lines, line)) {
|
|
1790
|
+
pushBudgetedLine(lines, " ...more omitted; refine the query");
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function finalizeCodeContextPack(body) {
|
|
1796
|
+
if (body.length <= exports.codeContextPackCharCap)
|
|
1797
|
+
return body;
|
|
1798
|
+
const budget = exports.codeContextPackCharCap - exports.codeContextPackTruncationNotice.length - 1;
|
|
1799
|
+
return `${body.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${exports.codeContextPackTruncationNotice}`;
|
|
1800
|
+
}
|
|
1801
|
+
function codeContextScaleLine(fileCount) {
|
|
1802
|
+
return fileCount < code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD
|
|
1803
|
+
? `scale small (${fileCount} indexed files < ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); direct reads are usually cheaper for simple lookups`
|
|
1804
|
+
: `scale large (${fileCount} indexed files >= ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); indexed traversal is useful for impact-style context`;
|
|
1805
|
+
}
|
|
1806
|
+
function structuralSignature(value) {
|
|
1807
|
+
const signature = oneLine(String(value ?? ""));
|
|
1808
|
+
const bodyStart = signature.indexOf("{");
|
|
1809
|
+
return bodyStart >= 0 ? signature.slice(0, bodyStart).trimEnd() : signature;
|
|
1810
|
+
}
|
|
1811
|
+
function sortedUnique(values) {
|
|
1812
|
+
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
1813
|
+
}
|
|
1814
|
+
function codeContextPack(database, query) {
|
|
1815
|
+
const normalized = query.trim();
|
|
1816
|
+
if (!normalized)
|
|
1817
|
+
return 'Code context pack: missing query; use --code-context-pack "path-or-symbol-or-route".';
|
|
1818
|
+
const like = containsLikePattern(normalized);
|
|
1819
|
+
const files = searchFiles(database, normalized, 12);
|
|
1820
|
+
const symbols = searchSymbols(database, normalized, 20);
|
|
1821
|
+
const routes = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? ESCAPE '\\' OR handler LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 20").all(like, like, like);
|
|
1822
|
+
const imports = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? ESCAPE '\\' OR to_ref LIKE ? ESCAPE '\\' OR imported LIKE ? ESCAPE '\\' ORDER BY from_file, line LIMIT 30").all(like, like, like);
|
|
1823
|
+
const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? ESCAPE '\\' OR source LIKE ? ESCAPE '\\' OR evidence LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 30").all(like, like, like);
|
|
1824
|
+
const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ESCAPE '\\' OR evidence LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 30").all(like, like);
|
|
1825
|
+
const relatedFilePaths = sortedUnique([
|
|
1826
|
+
...files.map((row) => String(row.path ?? "")),
|
|
1827
|
+
...symbols.map((row) => String(row.file_path ?? "")),
|
|
1828
|
+
...routes.map((row) => String(row.file_path ?? "")),
|
|
1829
|
+
...imports.map((row) => String(row.from_file ?? "")),
|
|
1830
|
+
...outgoingEdges.map((row) => String(row.file_path ?? "")),
|
|
1831
|
+
...incomingEdges.map((row) => String(row.file_path ?? "")),
|
|
1832
|
+
]);
|
|
1833
|
+
const ownership = ownershipContext();
|
|
1834
|
+
const ownerRows = new Map();
|
|
1835
|
+
for (const filePath of relatedFilePaths) {
|
|
1836
|
+
const info = ownershipInfo(filePath, ownership);
|
|
1837
|
+
const current = ownerRows.get(info.owner) ?? { files: 0, owner: info.owner, owner_source: info.owner_source, sample_files: [] };
|
|
1838
|
+
current.files += 1;
|
|
1839
|
+
if (current.sample_files.length < 4)
|
|
1840
|
+
current.sample_files.push(filePath);
|
|
1841
|
+
ownerRows.set(info.owner, current);
|
|
1842
|
+
}
|
|
1843
|
+
const owners = Array.from(ownerRows.values()).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner));
|
|
1844
|
+
const staleness = codeIndexStaleness(database);
|
|
1845
|
+
const coverage = evidenceCoverage(database);
|
|
1846
|
+
const staleLabel = staleness.stale
|
|
1847
|
+
? `STALE ${staleness.changed} changed, ${staleness.added} added, ${staleness.deleted} deleted`
|
|
1848
|
+
: "fresh";
|
|
1849
|
+
const lines = [
|
|
1850
|
+
`Code context pack "${normalized}": ${files.length} file matches, ${symbols.length} symbols, ${routes.length} routes, ${imports.length} imports, ${incomingEdges.length} incoming / ${outgoingEdges.length} outgoing edges; index ${staleLabel}; ${codeContextScaleLine(Number(coverage.files ?? 0))}.`,
|
|
1851
|
+
"Evidence is structural only: paths, lines, signatures, routes, imports, edges, and owners; no source snippets are included.",
|
|
1852
|
+
];
|
|
1853
|
+
pushBudgetedSection(lines, "Files:", files, 8, (row) => ` file-match ${String(row.path)} (${String(row.language)}, ${String(row.profile)}, ${Number(row.lines ?? 0)} lines)`);
|
|
1854
|
+
pushBudgetedSection(lines, "Symbols:", symbols, 12, (row) => ` symbol-match ${String(row.file_path)}:${String(row.line)} ${String(row.kind)} ${String(row.name)} - ${structuralSignature(row.signature)}`);
|
|
1855
|
+
pushBudgetedSection(lines, "Routes:", routes, 8, (row) => ` route-match ${String(row.method)} ${String(row.route)} -> ${String(row.handler)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1856
|
+
pushBudgetedSection(lines, "Imports:", imports, 8, (row) => ` import-match ${String(row.from_file)}:${String(row.line)} -> ${String(row.to_ref)}${row.imported ? ` (${String(row.imported)})` : ""}`);
|
|
1857
|
+
pushBudgetedSection(lines, "Incoming edges:", incomingEdges, 8, (row) => ` edge-in ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1858
|
+
pushBudgetedSection(lines, "Outgoing edges:", outgoingEdges, 8, (row) => ` edge-out ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1859
|
+
pushBudgetedSection(lines, "Owners:", owners, 6, (row) => ` owner ${row.owner} (${row.owner_source}, ${row.files} files): ${row.sample_files.join(", ")}`);
|
|
1860
|
+
return finalizeCodeContextPack(lines.join("\n"));
|
|
1861
|
+
}
|
|
1862
|
+
function snapshotRows(database, sql) {
|
|
1863
|
+
return database.prepare(sql).all().map((row) => {
|
|
1864
|
+
const normalized = {};
|
|
1865
|
+
for (const key of Object.keys(row).sort()) {
|
|
1866
|
+
const value = row[key];
|
|
1867
|
+
normalized[key] = typeof value === "string" || typeof value === "number" || value === null ? value : String(value);
|
|
1868
|
+
}
|
|
1869
|
+
return normalized;
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
function codeIndexSnapshot(database) {
|
|
1873
|
+
return {
|
|
1874
|
+
configs: snapshotRows(database, "SELECT file_path, line, key, value FROM configs ORDER BY file_path, line, key, value"),
|
|
1875
|
+
edges: snapshotRows(database, "SELECT file_path, line, kind, source_kind, source, target_kind, target, evidence FROM edges ORDER BY file_path, line, kind, source, target, evidence"),
|
|
1876
|
+
files: snapshotRows(database, "SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path"),
|
|
1877
|
+
imports: snapshotRows(database, "SELECT from_file, line, to_ref, imported, raw FROM imports ORDER BY from_file, line, to_ref, imported, raw"),
|
|
1878
|
+
routes: snapshotRows(database, "SELECT file_path, line, method, route, handler FROM routes ORDER BY file_path, line, method, route, handler"),
|
|
1879
|
+
symbols: snapshotRows(database, "SELECT file_path, line, kind, name, signature FROM symbols ORDER BY file_path, line, kind, name, signature"),
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
// Error thrown when the code-evidence index is missing or schema-incompatible.
|
|
1883
|
+
// The MCP server catches this to return an isError tool result (tools/list still
|
|
1884
|
+
// works); CLI modes keep their own process.exit path via requireExistingIndex.
|
|
1885
|
+
class CodeEvidenceIndexUnavailableError extends Error {
|
|
1886
|
+
constructor(message) {
|
|
1887
|
+
super(message);
|
|
1888
|
+
this.name = "CodeEvidenceIndexUnavailableError";
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
exports.CodeEvidenceIndexUnavailableError = CodeEvidenceIndexUnavailableError;
|
|
1892
|
+
// Open the existing .project-wiki code-evidence index READ-ONLY for serving:
|
|
1893
|
+
// validates existence and schema version, then pins PRAGMA query_only = ON. Uses
|
|
1894
|
+
// the same path resolution and schema constant as the indexer so the server and
|
|
1895
|
+
// the writer share one contract. Throws CodeEvidenceIndexUnavailableError (never
|
|
1896
|
+
// exits) so the MCP server can answer with guidance to run --code-index.
|
|
1897
|
+
function openCodeEvidenceDatabaseForServing() {
|
|
1898
|
+
const databasePath = codeEvidenceDatabasePath();
|
|
1899
|
+
if (!fs.existsSync(databasePath.absolutePath)) {
|
|
1900
|
+
throw new CodeEvidenceIndexUnavailableError(`missing code evidence index: ${databasePath.relativePath}; run \`project-librarian --code-index\` first`);
|
|
1901
|
+
}
|
|
1902
|
+
const database = openDatabase(databasePath.absolutePath);
|
|
1903
|
+
let schemaVersion = "";
|
|
1904
|
+
try {
|
|
1905
|
+
schemaVersion = readMetaValue(database, "schema_version");
|
|
1906
|
+
}
|
|
1907
|
+
catch (error) {
|
|
1908
|
+
database.close();
|
|
1909
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1910
|
+
throw new CodeEvidenceIndexUnavailableError(`code evidence index at ${databasePath.relativePath} is not readable; rebuild with \`project-librarian --code-index\`. Error: ${message}`);
|
|
1911
|
+
}
|
|
1912
|
+
if (schemaVersion !== codeIndexSchemaVersion) {
|
|
1913
|
+
database.close();
|
|
1914
|
+
throw new CodeEvidenceIndexUnavailableError(`code evidence index schema version ${schemaVersion || "(missing)"} is incompatible with ${codeIndexSchemaVersion}; rebuild with \`project-librarian --code-index\``);
|
|
1915
|
+
}
|
|
1916
|
+
database.exec("PRAGMA query_only = ON");
|
|
1917
|
+
return { database, relativePath: databasePath.relativePath };
|
|
1918
|
+
}
|
|
1673
1919
|
function prepareOutputPath() {
|
|
1674
1920
|
const databasePath = codeEvidenceDatabasePath();
|
|
1675
1921
|
(0, workspace_1.mkdirp)(path.dirname(databasePath.relativePath));
|
|
1676
|
-
(0, workspace_1.mkdirp)(codeEvidenceDirectory);
|
|
1677
|
-
fs.writeFileSync((0, workspace_1.abs)(`${codeEvidenceDirectory}/.gitignore`), "*\n!.gitignore\n");
|
|
1922
|
+
(0, workspace_1.mkdirp)(code_index_file_policy_1.codeEvidenceDirectory);
|
|
1923
|
+
fs.writeFileSync((0, workspace_1.abs)(`${code_index_file_policy_1.codeEvidenceDirectory}/.gitignore`), "*\n!.gitignore\n");
|
|
1678
1924
|
}
|
|
1679
1925
|
function runCodeIndexMode() {
|
|
1680
1926
|
const databasePath = codeEvidenceDatabasePath();
|
|
1681
1927
|
const scopes = codeScopes();
|
|
1682
1928
|
const parserMode = selectedCodeParserMode();
|
|
1929
|
+
// Scale gate before ANY write or database work: below the measured threshold
|
|
1930
|
+
// the build halts with the evidence-citing warning unless --acknowledge-small-repo
|
|
1931
|
+
// was passed (2026-06-12 scale-aware guidance decision).
|
|
1932
|
+
const discoveredFiles = (0, code_index_file_policy_1.discoverCodeFiles)(scopes);
|
|
1933
|
+
const scaleGate = (0, code_index_file_policy_1.smallRepoCodeIndexGate)(discoveredFiles.length, args_1.acknowledgeSmallRepoMode);
|
|
1934
|
+
if (!scaleGate.proceed)
|
|
1935
|
+
fail(scaleGate.warning);
|
|
1683
1936
|
const existingIndex = fs.existsSync(databasePath.absolutePath);
|
|
1684
1937
|
if (args_1.codeIndexIncrementalMode && !existingIndex) {
|
|
1685
1938
|
fail(`--incremental requires an existing compatible code evidence index: ${databasePath.relativePath}`);
|
|
@@ -1706,7 +1959,7 @@ function runCodeIndexMode() {
|
|
|
1706
1959
|
if (!incremental)
|
|
1707
1960
|
setupDatabase(database);
|
|
1708
1961
|
const statements = createIndexStatements(database);
|
|
1709
|
-
const currentFiles =
|
|
1962
|
+
const currentFiles = discoveredFiles.map((filePath) => readCodeFile(filePath, parserMode));
|
|
1710
1963
|
const currentByPath = new Map(currentFiles.map((file) => [file.path, file]));
|
|
1711
1964
|
const indexed = incremental ? new Map(database.prepare("SELECT path, hash FROM files").all().map((row) => [String(row.path), String(row.hash)])) : new Map();
|
|
1712
1965
|
const deletedPaths = incremental ? Array.from(indexed.keys()).filter((filePath) => !currentByPath.has(filePath)) : [];
|
|
@@ -1826,6 +2079,21 @@ function runCodeImpactMode() {
|
|
|
1826
2079
|
database.close();
|
|
1827
2080
|
}
|
|
1828
2081
|
}
|
|
2082
|
+
function runCodeContextPackMode() {
|
|
2083
|
+
if (!args_1.codeContextPackTarget.trim()) {
|
|
2084
|
+
console.error("missing context pack query: use --code-context-pack \"path-or-symbol-or-route\"");
|
|
2085
|
+
process.exit(1);
|
|
2086
|
+
}
|
|
2087
|
+
requireExistingIndex();
|
|
2088
|
+
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
2089
|
+
try {
|
|
2090
|
+
warnIfCodeIndexStale(database);
|
|
2091
|
+
console.log(codeContextPack(database, args_1.codeContextPackTarget.trim()));
|
|
2092
|
+
}
|
|
2093
|
+
finally {
|
|
2094
|
+
database.close();
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1829
2097
|
function runCodeSearchSymbolMode() {
|
|
1830
2098
|
if (!args_1.codeSearchSymbol.trim()) {
|
|
1831
2099
|
console.error("missing symbol search term: use --code-search-symbol \"term\"");
|
|
@@ -1835,18 +2103,18 @@ function runCodeSearchSymbolMode() {
|
|
|
1835
2103
|
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1836
2104
|
try {
|
|
1837
2105
|
warnIfCodeIndexStale(database);
|
|
1838
|
-
|
|
1839
|
-
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));
|
|
2106
|
+
printRows(searchSymbols(database, args_1.codeSearchSymbol.trim()));
|
|
1840
2107
|
}
|
|
1841
2108
|
finally {
|
|
1842
2109
|
database.close();
|
|
1843
2110
|
}
|
|
1844
2111
|
}
|
|
1845
2112
|
function isCodeEvidenceMode() {
|
|
1846
|
-
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 });
|
|
2113
|
+
return isCodeEvidenceModeFor({ codeContextPackTarget: args_1.codeContextPackTarget, 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 });
|
|
1847
2114
|
}
|
|
1848
2115
|
function isCodeEvidenceModeFor(flags) {
|
|
1849
|
-
return flags.
|
|
2116
|
+
return Boolean(flags.codeContextPackTarget)
|
|
2117
|
+
|| flags.codeIndexMode
|
|
1850
2118
|
|| Boolean(flags.codeQuerySql)
|
|
1851
2119
|
|| flags.codeReportMode
|
|
1852
2120
|
|| flags.codeStatusMode
|