project-librarian 0.5.6 → 0.5.8

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 CHANGED
@@ -31,7 +31,7 @@ npx project-librarian@latest install --scope user --agents all
31
31
  npx project-librarian@latest install --scope project --agents all
32
32
  ```
33
33
 
34
- `install`은 재사용 가능한 실행 파일과 스킬 파일만 복사합니다. `AGENTS.md`, 에이전트 훅, `wiki/`, git 훅 파일, 진단, 선택적 코드 근거 지원을 만들거나 갱신하는 것은 에이전트가 실행하는 생명주기 명령입니다. `install-skill`은 호환성 별칭으로 계속 지원됩니다.
34
+ `install`은 재사용 가능한 실행 파일, 스킬 파일, 로컬 실행기에 필요한 필수 런타임 의존성을 복사합니다. `AGENTS.md`, 에이전트 훅, `wiki/`, git 훅 파일, 진단, 선택적 코드 근거 지원을 만들거나 갱신하는 것은 에이전트가 실행하는 생명주기 명령입니다. `install-skill`은 호환성 별칭으로 계속 지원됩니다.
35
35
 
36
36
  ## 업데이트
37
37
 
@@ -104,15 +104,30 @@ Project Librarian은 작은 첫 읽기와 더 깊은 정본으로 가는 경로
104
104
 
105
105
  여기 수치는 모든 상황에 대한 약속이 아니라 관리자 릴리스 근거입니다. 모든 값은 실제 Codex JSONL 사용량과 로컬 실행 시간이며, Project Librarian이 없는 `organic` 대조군과 격리된 환경에서 비교했습니다. 위키 라우팅 트랙과 코드 그래프 트랙은 분리해 측정하며, 한 트랙의 우위가 다른 트랙의 주장을 뒷받침하지 않습니다.
106
106
 
107
- 위키 트랙, 대조군 대비 비용 가중 토큰:
107
+ 최신 clean 합성 위키 라우팅 트랙 릴리스 근거는 2026-06-29에 `gpt-5.5`, `perf/small-repo-code-evidence-safeguards` 브랜치의 `ae79390` 커밋에서 측정했습니다. 42개 시나리오, 21개 with/without 쌍을 각 3회 측정하고 1회 예열했으며, 전체 주장 게이트는 통과했고 주장 ledger는 이 보고서를 release-claimable로 분류했습니다. repair 실행은 보존된 raw JSONL에서 claimable 측정 실행 125개를 재사용하고 남은 실패 슬롯 1개를 다시 측정했습니다. 이 주장은 합성 위키 라우팅 트랙과 표기된 작업 유형에 한정되며, 코드 그래프 동작, 실제 저장소, 모든 에이전트 표면, 모든 질문 형태에 대한 주장이 아닙니다.
108
108
 
109
- | 규모 | decision_lookup | aggregation | multi_session |
110
- | --- | --- | --- | --- |
111
- | 소형 | 14.4% 적음 | 81.0% 많음 | 22.0% 적음 |
112
- | 중형 | 52.0% 적음 | 19.0% 적음 | 54.1% 적음 |
113
- | 대형 | 71.1% 적음 | 29.0% 적음 | 71.8% 적음 |
109
+ 위키 트랙 집계, 대조군 대비 변화:
114
110
 
115
- 최신 합성 위키 라우팅 트랙 릴리스 후보는 2026-06-19에 `gpt-5.5`로 측정했으며, 42개 시나리오를 각 3회 측정하고 1회 예열했습니다. 전체 주장 게이트는 통과했습니다. 주장은 합성 위키 라우팅 트랙과 표기된 작업 유형에 한정되며, 코드 그래프 동작, 실제 저장소, 모든 에이전트 표면, 모든 질문 형태에 대한 주장이 아닙니다.
111
+ | 규모 | 비용 가중 토큰 | 전체 토큰 | 실행 시간 | 명령 | 도구 출력 |
112
+ | --- | ---: | ---: | ---: | ---: | ---: |
113
+ | 전체 | 51.39% 적음 | 48.67% 적음 | 19.83% 적음 | 18.40% 적음 | 85.88% 적음 |
114
+ | 소형 | 21.59% 적음 | 9.31% 적음 | 13.91% 적음 | 12.64% 적음 | 43.89% 적음 |
115
+ | 중형 | 45.95% 적음 | 37.42% 적음 | 11.88% 적음 | 4.30% 적음 | 69.12% 적음 |
116
+ | 대형 | 66.97% 적음 | 69.87% 적음 | 31.90% 적음 | 35.19% 적음 | 95.58% 적음 |
117
+
118
+ 위키 트랙 작업 유형별 비용 가중 토큰 변화:
119
+
120
+ | 작업 유형 | 변화 |
121
+ | --- | ---: |
122
+ | onboarding | 65.95% 적음 |
123
+ | decision_lookup | 48.46% 적음 |
124
+ | code_impact | 55.61% 적음 |
125
+ | release_policy | 58.22% 적음 |
126
+ | change_location | 29.59% 적음 |
127
+ | multi_session | 52.58% 적음 |
128
+ | aggregation | 42.53% 적음 |
129
+
130
+ 실행 시간과 명령 수의 한계도 있습니다. 이번 보고서에서 `code_impact`, `change_location`, `aggregation`은 토큰과 출력 바이트가 모두 개선됐지만, 실행 시간이나 명령 수에서는 일부 역전이 남아 있습니다.
116
131
 
117
132
  코드 그래프 트랙, 대조군 대비 비용 가중 토큰:
118
133
 
package/README.md CHANGED
@@ -31,7 +31,7 @@ The installed skill resolves the local runner and executes the right command fro
31
31
  npx project-librarian@latest install --scope project --agents all
32
32
  ```
33
33
 
34
- `install` copies reusable runner and skill files only. The agent-run lifecycle command creates or updates `AGENTS.md`, agent hooks, `wiki/`, git hook files, diagnostics, and optional code-evidence support. `install-skill` remains a compatibility alias.
34
+ `install` copies reusable runner and skill files plus required local-runner runtime dependencies. The agent-run lifecycle command creates or updates `AGENTS.md`, agent hooks, `wiki/`, git hook files, diagnostics, and optional code-evidence support. `install-skill` remains a compatibility alias.
35
35
 
36
36
  ## Update
37
37
 
@@ -104,15 +104,30 @@ See [Usage](docs/usage.md) for install scopes, runner paths, generated files, mi
104
104
 
105
105
  These numbers are maintainer release evidence, not a blanket promise. Every value is real Codex JSONL usage and local wall-clock time, measured hermetically against an `organic` control with no Project Librarian. The wiki-routing track and the code-graph track are measured separately; a win on one never backs a claim about the other.
106
106
 
107
- Wiki track, cost-weighted tokens vs control:
107
+ Latest clean synthetic wiki-routing track release evidence: 2026-06-29, `gpt-5.5`, branch `perf/small-repo-code-evidence-safeguards` at `ae79390`, 42 scenarios, 21 with/without pairs, 3 measured runs plus 1 warmup each. The overall claim gate **passed**, and the claim ledger classified the report as release-claimable. A repair run reused 125 claimable measured runs from retained raw JSONL and remeasured the remaining failed slot. This is bounded to the synthetic wiki-routing track and listed task families; it is not a claim about code-graph behavior, real repositories, every agent surface, or every question shape.
108
108
 
109
- | Scale | decision_lookup | aggregation | multi_session |
110
- | --- | --- | --- | --- |
111
- | Small | 14.4% less | 81.0% more | 22.0% less |
112
- | Medium | 52.0% less | 19.0% less | 54.1% less |
113
- | Large | 71.1% less | 29.0% less | 71.8% less |
109
+ Wiki track aggregate deltas vs control:
114
110
 
115
- Latest synthetic wiki-routing track release candidate: 2026-06-19, `gpt-5.5`, 42 scenarios, 3 measured runs plus 1 warmup each. The overall claim gate **passed**. This is bounded to the synthetic wiki-routing track and listed task families; it is not a claim about code-graph behavior, real repositories, every agent surface, or every question shape.
111
+ | Scale | Cost-weighted tokens | Total tokens | Wall time | Commands | Tool output |
112
+ | --- | ---: | ---: | ---: | ---: | ---: |
113
+ | All | 51.39% less | 48.67% less | 19.83% less | 18.40% less | 85.88% less |
114
+ | Small | 21.59% less | 9.31% less | 13.91% less | 12.64% less | 43.89% less |
115
+ | Medium | 45.95% less | 37.42% less | 11.88% less | 4.30% less | 69.12% less |
116
+ | Large | 66.97% less | 69.87% less | 31.90% less | 35.19% less | 95.58% less |
117
+
118
+ Wiki track task-family cost-weighted token deltas vs control:
119
+
120
+ | Task family | Delta |
121
+ | --- | ---: |
122
+ | onboarding | 65.95% less |
123
+ | decision_lookup | 48.46% less |
124
+ | code_impact | 55.61% less |
125
+ | release_policy | 58.22% less |
126
+ | change_location | 29.59% less |
127
+ | multi_session | 52.58% less |
128
+ | aggregation | 42.53% less |
129
+
130
+ Timing and command-count caveat: `code_impact`, `change_location`, and `aggregation` still had wall-time or command-count regressions in this report, even though token and output-byte metrics improved for every task family.
116
131
 
117
132
  Code-graph track, cost-weighted tokens vs control:
118
133
 
package/SKILL.md CHANGED
@@ -175,6 +175,13 @@ Require a compatible existing cache for changed-file-only updates with `--increm
175
175
  $PROJECT_LIBRARIAN --code-index --incremental --code-scope src
176
176
  ```
177
177
 
178
+ If the cache schema version differs from the current runner, inspect it and require explicit replacement approval before rebuilding:
179
+
180
+ ```bash
181
+ $PROJECT_LIBRARIAN --code-index-health
182
+ $PROJECT_LIBRARIAN --code-index --code-index-migrate --code-scope src
183
+ ```
184
+
178
185
  Run read-only SQL over the cache with `--code-query`:
179
186
 
180
187
  ```bash
package/dist/args.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.codeIndexMode = exports.acknowledgeSmallRepoMode = exports.noGitConfigMode = exports.reviewMigrationMode = exports.pruneCheckStrictMode = exports.pruneCheckMode = exports.captureInboxMode = exports.refreshIndexMode = exports.issueDraftMode = exports.issueCreateMode = exports.handoffVerification = exports.handoffStatusMode = exports.handoffState = exports.handoffShowMode = exports.handoffSaveMode = exports.handoffPromoteInboxMode = exports.handoffInjectionStatusMode = exports.handoffInjectionEnableMode = exports.handoffInjectionDisableMode = exports.handoffOpenQuestions = exports.handoffNextActions = exports.handoffLastSuccessCommand = exports.handoffLastFailureCommand = exports.handoffInputMode = exports.handoffGoal = exports.handoffDecisions = exports.handoffClearMode = exports.handoffBlocked = exports.glossaryMode = exports.fixMode = exports.doctorMode = exports.qualityCheckMode = exports.linkCheckMode = exports.migrationQualityCheckMode = exports.migrationLintMode = exports.migrationDoctorMode = exports.lintMode = exports.migrateMode = exports.invalidAgentTargets = exports.missingValueOptions = exports.unexpectedValueOptions = exports.unknownOptions = exports.args = exports.commandArgs = exports.command = exports.unknownCommand = exports.helpMode = exports.agentTargets = exports.parsedArgs = exports.rawArgs = void 0;
4
- exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = exports.codeIndexOutput = exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiVisualizeOutput = exports.wikiVisualizeMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexHealthMode = exports.codeIndexFullMode = exports.codeIndexIncrementalMode = exports.codeIndexEngineMode = exports.codeIndexEngine = void 0;
4
+ exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = exports.codeIndexOutput = exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiVisualizeOutput = exports.wikiVisualizeMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexHealthMode = exports.codeIndexFullMode = exports.codeIndexMigrateMode = exports.codeIndexIncrementalMode = exports.codeIndexEngineMode = exports.codeIndexEngine = void 0;
5
5
  exports.parseArgs = parseArgs;
6
6
  exports.argValue = argValue;
7
7
  exports.argValues = argValues;
@@ -22,6 +22,7 @@ const flagDefinitions = [
22
22
  { name: "--code-index-full", value: "none", aliases: ["--code-evidence-index-full"] },
23
23
  { name: "--code-index-health", value: "none" },
24
24
  { name: "--code-index-incremental", value: "none", aliases: ["--incremental", "--code-incremental", "--code-evidence-index-incremental"] },
25
+ { name: "--code-index-migrate", value: "none", aliases: ["--code-evidence-index-migrate"] },
25
26
  { name: "--code-index-out", value: "value", aliases: ["--code-evidence-out"] },
26
27
  { name: "--code-parser", value: "value", aliases: ["--code-evidence-parser"] },
27
28
  { name: "--code-query", value: "value", aliases: ["--code-evidence-query"] },
@@ -185,6 +186,7 @@ function parseArgs(argv) {
185
186
  codeIndexFullMode: hasAnyFlag("--code-index-full"),
186
187
  codeIndexHealthMode: args.has("--code-index-health"),
187
188
  codeIndexIncrementalMode: hasAnyFlag("--code-index-incremental"),
189
+ codeIndexMigrateMode: hasAnyFlag("--code-index-migrate"),
188
190
  codeIndexMode: hasAnyFlag("--code-index"),
189
191
  codeIndexOutput: argValueFromAny("--code-index-out") || ".project-wiki/code-evidence.sqlite",
190
192
  codeIndexScopes: argValuesFromAny("--code-scope"),
@@ -308,6 +310,7 @@ exports.codeIndexMode = exports.parsedArgs.codeIndexMode;
308
310
  exports.codeIndexEngine = exports.parsedArgs.codeIndexEngine;
309
311
  exports.codeIndexEngineMode = exports.parsedArgs.codeIndexEngineMode;
310
312
  exports.codeIndexIncrementalMode = exports.parsedArgs.codeIndexIncrementalMode;
313
+ exports.codeIndexMigrateMode = exports.parsedArgs.codeIndexMigrateMode;
311
314
  exports.codeIndexFullMode = exports.parsedArgs.codeIndexFullMode;
312
315
  exports.codeIndexHealthMode = exports.parsedArgs.codeIndexHealthMode;
313
316
  exports.codeReportMode = exports.parsedArgs.codeReportMode;
@@ -79,8 +79,8 @@ function readScopes(scopesJson, scopesText, fallback) {
79
79
  const scopes = scopesText.split(",").map((scope) => scope.trim()).filter(Boolean);
80
80
  return scopes.length > 0 ? scopes : fallback;
81
81
  }
82
- function rebuildCommand(indexableFiles, smallRepoThreshold) {
83
- const parts = ["project-librarian", "--code-index", "--code-index-full"];
82
+ function rebuildCommand(indexableFiles, smallRepoThreshold, options = {}) {
83
+ const parts = ["project-librarian", "--code-index", options.schemaMigration ? "--code-index-migrate" : "--code-index-full"];
84
84
  if (indexableFiles !== null && indexableFiles < smallRepoThreshold)
85
85
  parts.push("--acknowledge-small-repo");
86
86
  return parts.join(" ");
@@ -126,6 +126,7 @@ function inspectCodeIndexHealth(options) {
126
126
  const indexedFiles = indexedFilesText ? Number(indexedFilesText) : null;
127
127
  const indexableFiles = safeDiscoverCodeFiles(options.discoverCodeFiles, scopes.length > 0 ? scopes : options.defaultScopes);
128
128
  const compatible = foundSchemaVersion === options.expectedSchemaVersion;
129
+ const schemaMigration = Boolean(foundSchemaVersion) && !compatible;
129
130
  return {
130
131
  database_path: options.relativePath,
131
132
  expected_schema_version: options.expectedSchemaVersion,
@@ -136,7 +137,7 @@ function inspectCodeIndexHealth(options) {
136
137
  ? `code evidence index is compatible: schema ${foundSchemaVersion}`
137
138
  : `code evidence index schema version ${foundSchemaVersion || "(missing)"} is incompatible with ${options.expectedSchemaVersion}`,
138
139
  parser_mode: parserMode,
139
- recommended_rebuild_command: rebuildCommand(indexableFiles, options.smallRepoThreshold),
140
+ recommended_rebuild_command: rebuildCommand(indexableFiles, options.smallRepoThreshold, { schemaMigration }),
140
141
  scopes,
141
142
  status: compatible ? "compatible" : "incompatible_schema",
142
143
  tables,
@@ -71,6 +71,49 @@ function requireCompatibleDatabase(database, runtime) {
71
71
  ].join("\n"));
72
72
  }
73
73
  }
74
+ function schemaMigrationRequired(reason) {
75
+ return reason.startsWith("existing schema version ") && !reason.includes("(missing)");
76
+ }
77
+ function shellQuote(value) {
78
+ if (/^[A-Za-z0-9_./:=@+-]+$/.test(value))
79
+ return value;
80
+ return `'${value.replace(/'/g, "'\\''")}'`;
81
+ }
82
+ function schemaMigrationApprovalCommand(options) {
83
+ const parts = ["project-librarian", "--code-index", "--code-index-migrate"];
84
+ for (const scope of options.scopes) {
85
+ if (scope !== ".")
86
+ parts.push("--code-scope", scope);
87
+ }
88
+ if (args_1.codeIndexOutput !== ".project-wiki/code-evidence.sqlite")
89
+ parts.push("--code-index-out", args_1.codeIndexOutput);
90
+ if (args_1.acknowledgeSmallRepoMode)
91
+ parts.push("--acknowledge-small-repo");
92
+ if (options.requestedEngine !== "auto")
93
+ parts.push("--code-index-engine", options.requestedEngine);
94
+ if (options.parserMode !== "default")
95
+ parts.push("--code-parser", options.parserMode);
96
+ return parts.map(shellQuote).join(" ");
97
+ }
98
+ function schemaMigrationRequiredMessage(runtime, reason, options) {
99
+ const databasePath = runtime.codeEvidenceDatabasePath();
100
+ return [
101
+ `code evidence index schema migration required: ${reason}`,
102
+ "The existing disposable code evidence index must be replaced before this version can write it.",
103
+ `approve: ${schemaMigrationApprovalCommand(options)}`,
104
+ "inspect: project-librarian --code-index-health",
105
+ `database: ${databasePath.relativePath}`,
106
+ ].join("\n");
107
+ }
108
+ function unreadableIndexMessage(runtime) {
109
+ const health = runtime.codeIndexHealth();
110
+ return [
111
+ health.message,
112
+ "inspect: project-librarian --code-index-health",
113
+ `rebuild: ${health.recommended_rebuild_command}`,
114
+ `database: ${health.database_path}`,
115
+ ].join("\n");
116
+ }
74
117
  function elapsedMs(started) {
75
118
  return Number(process.hrtime.bigint() - started) / 1_000_000;
76
119
  }
@@ -88,6 +131,36 @@ function emitCodeIndexPhaseTimings(timings) {
88
131
  return;
89
132
  console.error(`code_index_phase_timings ${JSON.stringify(timings)}`);
90
133
  }
134
+ function finishCodeEvidencePhaseTimings(started, timings) {
135
+ timings.total_ms = Number(elapsedMs(started).toFixed(3));
136
+ emitCodeIndexPhaseTimings(timings);
137
+ }
138
+ function runWithCodeEvidenceDatabase(runtime, operation, options = {}) {
139
+ const totalStarted = process.hrtime.bigint();
140
+ const phaseTimings = {};
141
+ let database;
142
+ try {
143
+ measurePhase(phaseTimings, "require_existing_index_ms", () => runtime.requireExistingIndex());
144
+ options.beforeOpen?.();
145
+ database = measurePhase(phaseTimings, "open_database_ms", () => runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath));
146
+ operation(database, phaseTimings);
147
+ }
148
+ finally {
149
+ if (database) {
150
+ const openedDatabase = database;
151
+ measurePhase(phaseTimings, "close_database_ms", () => openedDatabase.close());
152
+ }
153
+ finishCodeEvidencePhaseTimings(totalStarted, phaseTimings);
154
+ }
155
+ }
156
+ function requireCompatibleDatabaseTimed(database, runtime, timings) {
157
+ measurePhase(timings, "compatibility_ms", () => requireCompatibleDatabase(database, runtime));
158
+ }
159
+ function checkedCodeIndexStaleness(database, runtime, timings) {
160
+ const staleness = measurePhase(timings, "staleness_ms", () => runtime.codeIndexStaleness(database));
161
+ runtime.warnIfCodeIndexStale(database, staleness);
162
+ return staleness;
163
+ }
91
164
  function configureBulkWriteConnection(database) {
92
165
  database.exec(`
93
166
  PRAGMA synchronous = OFF;
@@ -125,38 +198,49 @@ function runCodeIndexMode(runtime) {
125
198
  runtime.fail(scaleGate.warning);
126
199
  const engineSelectionContext = runtime.codeIndexEngineSelectionContext(discoveredFiles, parserMode);
127
200
  const engine = resolveCodeIndexEngine(requestedEngine, engineSelectionContext, runtime.shouldUseNativeCodeIndexAuto);
201
+ const existingIndex = fs.existsSync(databasePath.absolutePath);
202
+ if (args_1.codeIndexIncrementalMode && !existingIndex) {
203
+ runtime.fail(`--incremental requires an existing compatible code evidence index: ${databasePath.relativePath}`);
204
+ }
205
+ let compatibility = { compatible: false, reason: "compatibility was not checked" };
206
+ let checkedCompatibility = false;
207
+ if (existingIndex) {
208
+ try {
209
+ measurePhase(phaseTimings, "compatibility_ms", () => {
210
+ const existingDatabase = runtime.openDatabase(databasePath.absolutePath);
211
+ try {
212
+ compatibility = (0, schema_1.incrementalCompatibility)(existingDatabase, scopes, parserMode);
213
+ checkedCompatibility = true;
214
+ }
215
+ finally {
216
+ existingDatabase.close();
217
+ }
218
+ });
219
+ }
220
+ catch {
221
+ if (!args_1.codeIndexFullMode && !args_1.codeIndexMigrateMode)
222
+ runtime.fail(unreadableIndexMessage(runtime));
223
+ }
224
+ if (!compatibility.compatible && schemaMigrationRequired(compatibility.reason) && !args_1.codeIndexMigrateMode) {
225
+ runtime.fail(schemaMigrationRequiredMessage(runtime, compatibility.reason, { parserMode, requestedEngine, scopes }));
226
+ }
227
+ }
128
228
  if (engine === "native-rust") {
129
229
  if (!args_1.codeIndexIncrementalMode) {
130
230
  try {
131
231
  measurePhase(phaseTimings, "native_helper_ms", () => runtime.runNativeCodeIndexMode({ databasePath, discoveredFiles, parserMode, requestedEngine, scopes }));
132
- phaseTimings.total_ms = Number(elapsedMs(totalStarted).toFixed(3));
133
- emitCodeIndexPhaseTimings(phaseTimings);
232
+ finishCodeEvidencePhaseTimings(totalStarted, phaseTimings);
134
233
  return;
135
234
  }
136
235
  catch (error) {
137
- phaseTimings.total_ms = Number(elapsedMs(totalStarted).toFixed(3));
138
- emitCodeIndexPhaseTimings(phaseTimings);
236
+ finishCodeEvidencePhaseTimings(totalStarted, phaseTimings);
139
237
  throw error;
140
238
  }
141
239
  }
142
240
  }
143
- const existingIndex = fs.existsSync(databasePath.absolutePath);
144
- if (args_1.codeIndexIncrementalMode && !existingIndex) {
145
- runtime.fail(`--incremental requires an existing compatible code evidence index: ${databasePath.relativePath}`);
146
- }
147
241
  let incremental = false;
148
- if (existingIndex && !args_1.codeIndexFullMode) {
149
- let compatibility = { compatible: false, reason: "compatibility was not checked" };
150
- measurePhase(phaseTimings, "compatibility_ms", () => {
151
- const existingDatabase = runtime.openDatabase(databasePath.absolutePath);
152
- try {
153
- compatibility = (0, schema_1.incrementalCompatibility)(existingDatabase, scopes, parserMode);
154
- }
155
- finally {
156
- existingDatabase.close();
157
- }
158
- });
159
- incremental = !args_1.codeIndexFullMode && compatibility.compatible;
242
+ if (existingIndex && checkedCompatibility && !args_1.codeIndexFullMode && !args_1.codeIndexMigrateMode) {
243
+ incremental = compatibility.compatible;
160
244
  if (args_1.codeIndexIncrementalMode && !compatibility.compatible)
161
245
  runtime.fail(`--incremental cannot update ${databasePath.relativePath}: ${compatibility.reason}`);
162
246
  }
@@ -222,8 +306,7 @@ function runCodeIndexMode(runtime) {
222
306
  scopes,
223
307
  unchangedFiles,
224
308
  }));
225
- phaseTimings.total_ms = Number(elapsedMs(totalStarted).toFixed(3));
226
- emitCodeIndexPhaseTimings(phaseTimings);
309
+ finishCodeEvidencePhaseTimings(totalStarted, phaseTimings);
227
310
  return;
228
311
  }
229
312
  }
@@ -247,7 +330,6 @@ function runCodeIndexMode(runtime) {
247
330
  (0, schema_1.createSecondaryIndexes)(activeDatabase);
248
331
  activeDatabase.exec("COMMIT");
249
332
  });
250
- phaseTimings.total_ms = Number(elapsedMs(totalStarted).toFixed(3));
251
333
  console.log("Project wiki code evidence index complete.");
252
334
  console.log(`database: ${databasePath.relativePath}`);
253
335
  console.log(`mode: ${incremental ? "incremental" : "full"}`);
@@ -260,7 +342,7 @@ function runCodeIndexMode(runtime) {
260
342
  console.log(`reindexed_files: ${reindexedFiles.length}`);
261
343
  console.log(`deleted_files: ${deletedPaths.length}`);
262
344
  console.log(`unchanged_files: ${unchangedFiles}`);
263
- emitCodeIndexPhaseTimings(phaseTimings);
345
+ finishCodeEvidencePhaseTimings(totalStarted, phaseTimings);
264
346
  }
265
347
  catch (error) {
266
348
  try {
@@ -280,123 +362,93 @@ function runCodeQueryMode(runtime) {
280
362
  console.error("missing SQL: use --code-query \"select ...\"");
281
363
  process.exit(1);
282
364
  }
283
- runtime.requireExistingIndex();
284
- if (!(0, code_index_sql_1.isReadOnlySql)(args_1.codeQuerySql)) {
285
- console.error("code queries must be read-only SQL starting with SELECT or WITH");
286
- process.exit(1);
287
- }
288
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
289
- try {
365
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
290
366
  database.exec("PRAGMA query_only = ON");
291
- requireCompatibleDatabase(database, runtime);
292
- runtime.warnIfCodeIndexStale(database);
293
- printRows(database.prepare(args_1.codeQuerySql).all());
294
- }
295
- finally {
296
- database.close();
297
- }
367
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
368
+ checkedCodeIndexStaleness(database, runtime, phaseTimings);
369
+ const rows = measurePhase(phaseTimings, "query_ms", () => database.prepare(args_1.codeQuerySql).all());
370
+ measurePhase(phaseTimings, "render_ms", () => printRows(rows));
371
+ }, {
372
+ beforeOpen() {
373
+ if (!(0, code_index_sql_1.isReadOnlySql)(args_1.codeQuerySql)) {
374
+ console.error("code queries must be read-only SQL starting with SELECT or WITH");
375
+ process.exit(1);
376
+ }
377
+ },
378
+ });
298
379
  }
299
380
  function runCodeReportMode(runtime) {
300
- runtime.requireExistingIndex();
301
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
302
- try {
303
- requireCompatibleDatabase(database, runtime);
304
- const staleness = runtime.codeIndexStaleness(database);
305
- runtime.warnIfCodeIndexStale(database, staleness);
306
- const report = runtime.codeReportForRequestedSection(database, args_1.codeReportSection, { staleness });
381
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
382
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
383
+ const staleness = checkedCodeIndexStaleness(database, runtime, phaseTimings);
384
+ const report = measurePhase(phaseTimings, "query_ms", () => runtime.codeReportForRequestedSection(database, args_1.codeReportSection, { staleness }));
307
385
  if (!report)
308
386
  runtime.fail((0, reports_1.invalidCodeReportSectionMessage)(args_1.codeReportSection));
309
- printJson(report);
310
- }
311
- finally {
312
- database.close();
313
- }
387
+ measurePhase(phaseTimings, "render_ms", () => printJson(report));
388
+ });
314
389
  }
315
390
  function runCodeStatusMode(runtime) {
316
- runtime.requireExistingIndex();
317
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
318
- try {
319
- requireCompatibleDatabase(database, runtime);
320
- const rows = database.prepare(`
391
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
392
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
393
+ const rows = measurePhase(phaseTimings, "query_ms", () => database.prepare(`
321
394
  SELECT 'files' AS metric, count(*) AS value FROM files
322
395
  UNION ALL SELECT 'symbols', count(*) FROM symbols
323
396
  UNION ALL SELECT 'imports', count(*) FROM imports
324
397
  UNION ALL SELECT 'routes', count(*) FROM routes
325
398
  UNION ALL SELECT 'edges', count(*) FROM edges
326
399
  UNION ALL SELECT 'configs', count(*) FROM configs
327
- `).all();
328
- const staleness = runtime.codeIndexStaleness(database);
400
+ `).all());
401
+ const staleness = measurePhase(phaseTimings, "staleness_ms", () => runtime.codeIndexStaleness(database));
329
402
  rows.push({ metric: "stale_files", value: staleness.added + staleness.changed + staleness.deleted }, { metric: "stale_changed_files", value: staleness.changed }, { metric: "stale_added_files", value: staleness.added }, { metric: "stale_deleted_files", value: staleness.deleted });
330
- printRows(rows);
331
- }
332
- finally {
333
- database.close();
334
- }
403
+ measurePhase(phaseTimings, "render_ms", () => printRows(rows));
404
+ });
335
405
  }
336
406
  function runCodeIndexHealthMode(runtime) {
337
407
  printJson(runtime.codeIndexHealth());
338
408
  }
339
409
  function runCodeFilesMode(runtime) {
340
- runtime.requireExistingIndex();
341
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
342
- try {
343
- requireCompatibleDatabase(database, runtime);
344
- runtime.warnIfCodeIndexStale(database);
345
- printRows(database.prepare("SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path").all());
346
- }
347
- finally {
348
- database.close();
349
- }
410
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
411
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
412
+ checkedCodeIndexStaleness(database, runtime, phaseTimings);
413
+ const rows = measurePhase(phaseTimings, "query_ms", () => database.prepare("SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path").all());
414
+ measurePhase(phaseTimings, "render_ms", () => printRows(rows));
415
+ });
350
416
  }
351
417
  function runCodeImpactMode(runtime) {
352
418
  if (!args_1.codeImpactTarget.trim()) {
353
419
  console.error("missing impact target: use --code-impact \"path-or-symbol-or-module\"");
354
420
  process.exit(1);
355
421
  }
356
- runtime.requireExistingIndex();
357
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
358
- try {
359
- requireCompatibleDatabase(database, runtime);
360
- const staleness = runtime.codeIndexStaleness(database);
361
- runtime.warnIfCodeIndexStale(database, staleness);
362
- printJson(runtime.codeImpact(database, args_1.codeImpactTarget.trim(), { staleness }));
363
- }
364
- finally {
365
- database.close();
366
- }
422
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
423
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
424
+ const staleness = checkedCodeIndexStaleness(database, runtime, phaseTimings);
425
+ const impact = measurePhase(phaseTimings, "query_ms", () => runtime.codeImpact(database, args_1.codeImpactTarget.trim(), { staleness }));
426
+ measurePhase(phaseTimings, "render_ms", () => printJson(impact));
427
+ });
367
428
  }
368
429
  function runCodeContextPackMode(runtime) {
369
430
  if (!args_1.codeContextPackTarget.trim()) {
370
431
  console.error("missing context pack query: use --code-context-pack \"path-or-symbol-or-route\"");
371
432
  process.exit(1);
372
433
  }
373
- runtime.requireExistingIndex();
374
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
375
- try {
376
- requireCompatibleDatabase(database, runtime);
377
- const staleness = runtime.codeIndexStaleness(database);
378
- runtime.warnIfCodeIndexStale(database, staleness);
379
- console.log(runtime.codeContextPack(database, args_1.codeContextPackTarget.trim(), { staleness }));
380
- }
381
- finally {
382
- database.close();
383
- }
434
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
435
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
436
+ const staleness = checkedCodeIndexStaleness(database, runtime, phaseTimings);
437
+ const pack = measurePhase(phaseTimings, "query_ms", () => runtime.codeContextPack(database, args_1.codeContextPackTarget.trim(), { staleness }));
438
+ measurePhase(phaseTimings, "render_ms", () => console.log(pack));
439
+ });
384
440
  }
385
441
  function runCodeSearchSymbolMode(runtime) {
386
442
  if (!args_1.codeSearchSymbol.trim()) {
387
443
  console.error("missing symbol search term: use --code-search-symbol \"term\"");
388
444
  process.exit(1);
389
445
  }
390
- runtime.requireExistingIndex();
391
- const database = runtime.openDatabase(runtime.codeEvidenceDatabasePath().absolutePath);
392
- try {
393
- requireCompatibleDatabase(database, runtime);
394
- runtime.warnIfCodeIndexStale(database);
395
- printRows((0, search_1.searchSymbols)(database, args_1.codeSearchSymbol.trim()));
396
- }
397
- finally {
398
- database.close();
399
- }
446
+ runWithCodeEvidenceDatabase(runtime, (database, phaseTimings) => {
447
+ requireCompatibleDatabaseTimed(database, runtime, phaseTimings);
448
+ checkedCodeIndexStaleness(database, runtime, phaseTimings);
449
+ const rows = measurePhase(phaseTimings, "query_ms", () => (0, search_1.searchSymbols)(database, args_1.codeSearchSymbol.trim()));
450
+ measurePhase(phaseTimings, "render_ms", () => printRows(rows));
451
+ });
400
452
  }
401
453
  function isCodeEvidenceModeFor(flags) {
402
454
  return Boolean(flags.codeContextPackTarget)
@@ -65,15 +65,126 @@ function readJsonObject(relativePath) {
65
65
  function workspacePatternsFromRootPackage() {
66
66
  const rootPackage = readJsonObject("package.json");
67
67
  const workspaces = rootPackage?.workspaces;
68
- if (Array.isArray(workspaces))
69
- return workspaces.filter((value) => typeof value === "string");
68
+ if (Array.isArray(workspaces)) {
69
+ return workspaces
70
+ .filter((value) => typeof value === "string")
71
+ .map((pattern) => ({ pattern, source: "package.json workspaces" }));
72
+ }
70
73
  if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
71
74
  const packages = workspaces.packages;
72
- if (Array.isArray(packages))
73
- return packages.filter((value) => typeof value === "string");
75
+ if (Array.isArray(packages)) {
76
+ return packages
77
+ .filter((value) => typeof value === "string")
78
+ .map((pattern) => ({ pattern, source: "package.json workspaces" }));
79
+ }
74
80
  }
75
81
  return [];
76
82
  }
83
+ function trimYamlComment(value) {
84
+ let quote = "";
85
+ for (let index = 0; index < value.length; index += 1) {
86
+ const character = value[index] ?? "";
87
+ if (quote) {
88
+ if (character === quote)
89
+ quote = "";
90
+ continue;
91
+ }
92
+ if (character === "\"" || character === "'") {
93
+ quote = character;
94
+ continue;
95
+ }
96
+ if (character === "#" && (index === 0 || /\s/.test(value[index - 1] ?? "")))
97
+ return value.slice(0, index).trimEnd();
98
+ }
99
+ return value.trimEnd();
100
+ }
101
+ function parseYamlStringScalar(value) {
102
+ const trimmed = trimYamlComment(value).trim();
103
+ if (!trimmed)
104
+ return null;
105
+ if (trimmed.startsWith("\"")) {
106
+ const match = trimmed.match(/^"((?:\\.|[^"\\])*)"/);
107
+ if (!match)
108
+ return null;
109
+ return match[1]?.replace(/\\"/g, "\"") ?? "";
110
+ }
111
+ if (trimmed.startsWith("'")) {
112
+ const match = trimmed.match(/^'((?:''|[^'])*)'/);
113
+ if (!match)
114
+ return null;
115
+ return match[1]?.replace(/''/g, "'") ?? "";
116
+ }
117
+ return trimmed;
118
+ }
119
+ function splitYamlFlowArray(value) {
120
+ const trimmed = trimYamlComment(value).trim();
121
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
122
+ return null;
123
+ const entries = [];
124
+ let quote = "";
125
+ let current = "";
126
+ for (const character of trimmed.slice(1, -1)) {
127
+ if (quote) {
128
+ current += character;
129
+ if (character === quote)
130
+ quote = "";
131
+ continue;
132
+ }
133
+ if (character === "\"" || character === "'") {
134
+ quote = character;
135
+ current += character;
136
+ continue;
137
+ }
138
+ if (character === ",") {
139
+ entries.push(current);
140
+ current = "";
141
+ continue;
142
+ }
143
+ current += character;
144
+ }
145
+ entries.push(current);
146
+ return entries;
147
+ }
148
+ function workspacePatternsFromPnpmWorkspace() {
149
+ const filePath = "pnpm-workspace.yaml";
150
+ if (!(0, workspace_1.containedProjectFileStat)(filePath))
151
+ return [];
152
+ const lines = (0, workspace_1.read)(filePath).split(/\r?\n/);
153
+ const patterns = [];
154
+ let packagesIndent = null;
155
+ for (const line of lines) {
156
+ if (!line.trim() || line.trimStart().startsWith("#"))
157
+ continue;
158
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
159
+ const packagesMatch = line.match(/^(\s*)packages\s*:\s*(.*)$/);
160
+ if (packagesMatch) {
161
+ packagesIndent = packagesMatch[1]?.length ?? 0;
162
+ const inlineArray = splitYamlFlowArray(packagesMatch[2] ?? "");
163
+ if (inlineArray) {
164
+ for (const entry of inlineArray) {
165
+ const pattern = parseYamlStringScalar(entry);
166
+ if (pattern && !pattern.startsWith("!"))
167
+ patterns.push({ pattern, source: "pnpm-workspace.yaml packages" });
168
+ }
169
+ }
170
+ continue;
171
+ }
172
+ if (packagesIndent === null)
173
+ continue;
174
+ if (indent <= packagesIndent)
175
+ break;
176
+ const item = line.slice(indent).match(/^-\s+(.+)$/);
177
+ if (!item)
178
+ continue;
179
+ const pattern = parseYamlStringScalar(item[1] ?? "");
180
+ if (pattern && !pattern.startsWith("!"))
181
+ patterns.push({ pattern, source: "pnpm-workspace.yaml packages" });
182
+ }
183
+ return patterns;
184
+ }
185
+ function workspacePatterns() {
186
+ return [...workspacePatternsFromRootPackage(), ...workspacePatternsFromPnpmWorkspace()];
187
+ }
77
188
  function workspacePatternCandidates(pattern) {
78
189
  const normalized = (0, workspace_1.normalizePath)(pattern).replace(/^\/+/, "").replace(/\/+$/, "");
79
190
  if (!normalized || normalized.includes(".."))
@@ -94,8 +205,8 @@ function workspacePatternCandidates(pattern) {
94
205
  }
95
206
  function workspacePackages() {
96
207
  const packages = new Map();
97
- for (const pattern of workspacePatternsFromRootPackage()) {
98
- for (const candidate of workspacePatternCandidates(pattern)) {
208
+ for (const workspacePattern of workspacePatterns()) {
209
+ for (const candidate of workspacePatternCandidates(workspacePattern.pattern)) {
99
210
  const packageJsonPath = (0, workspace_1.normalizePath)(path.join(candidate, "package.json"));
100
211
  if (!(0, workspace_1.containedProjectFileStat)(packageJsonPath))
101
212
  continue;
@@ -104,8 +215,8 @@ function workspacePackages() {
104
215
  packages.set(candidate, {
105
216
  name: packageName,
106
217
  root: candidate,
107
- source: "package.json workspaces",
108
- workspace_pattern: pattern,
218
+ source: workspacePattern.source,
219
+ workspace_pattern: workspacePattern.pattern,
109
220
  });
110
221
  }
111
222
  }
@@ -47,7 +47,7 @@ exports.incrementalCompatibility = incrementalCompatibility;
47
47
  exports.codeIndexSnapshot = codeIndexSnapshot;
48
48
  const crypto = __importStar(require("node:crypto"));
49
49
  const workspace_1 = require("../workspace");
50
- exports.codeIndexSchemaVersion = "5";
50
+ exports.codeIndexSchemaVersion = "6";
51
51
  function stableFtsRowid(parts) {
52
52
  const hash = crypto.createHash("sha256");
53
53
  for (const part of parts) {
@@ -158,7 +158,7 @@ function createIndexStatements(database) {
158
158
  insertMeta: database.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)"),
159
159
  insertRoute: database.prepare("INSERT INTO routes (method, route, file_path, line, handler) VALUES (?, ?, ?, ?, ?)"),
160
160
  insertSymbol: database.prepare("INSERT INTO symbols (name, kind, file_path, line, signature) VALUES (?, ?, ?, ?, ?)"),
161
- insertSymbolFts: database.prepare("INSERT INTO symbols_fts (name, kind, file_path, signature) VALUES (?, ?, ?, ?)"),
161
+ insertSymbolFts: database.prepare("INSERT INTO symbols_fts (rowid, name, kind, file_path, signature) VALUES (last_insert_rowid(), ?, ?, ?, ?)"),
162
162
  };
163
163
  }
164
164
  function removeIndexedFile(filePath, statements) {
@@ -130,11 +130,7 @@ function searchSymbols(database, term, limit = 50) {
130
130
  const ftsRows = database.prepare(`
131
131
  SELECT symbols.name, symbols.kind, symbols.file_path, symbols.line, symbols.signature
132
132
  FROM symbols_fts
133
- JOIN symbols
134
- ON symbols.name = symbols_fts.name
135
- AND symbols.kind = symbols_fts.kind
136
- AND symbols.file_path = symbols_fts.file_path
137
- AND symbols.signature = symbols_fts.signature
133
+ JOIN symbols ON symbols.id = symbols_fts.rowid
138
134
  WHERE symbols_fts MATCH ?
139
135
  ORDER BY bm25(symbols_fts, 8.0, 1.0, 4.0, 2.0), symbols.file_path, symbols.line
140
136
  LIMIT ?
@@ -648,16 +648,22 @@ function codeContextScaleLine(fileCount) {
648
648
  ? `scale small (${fileCount} indexed files < ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); direct reads are usually cheaper for simple lookups`
649
649
  : `scale large (${fileCount} indexed files >= ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); indexed traversal is useful for impact-style context`;
650
650
  }
651
- function structuralSignature(value) {
652
- const signature = (0, shared_1.oneLine)(String(value ?? ""));
653
- const bodyStart = signature.indexOf("{");
654
- return bodyStart >= 0 ? signature.slice(0, bodyStart).trimEnd() : signature;
655
- }
656
- function codeContextPack(database, query, options = {}) {
657
- const normalized = query.trim();
658
- if (!normalized)
659
- return 'Code context pack: missing query; use --code-context-pack "path-or-symbol-or-route".';
660
- const evidence = (0, evidence_1.collectCodeEvidence)(database, normalized, {
651
+ function codeContextCollectorOptions(fileCount) {
652
+ if (fileCount < code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD) {
653
+ return {
654
+ edgeLimit: 8,
655
+ fileLimit: 8,
656
+ includeEdgeEvidenceMatches: false,
657
+ includeOwnerCodeowners: false,
658
+ includeRouteEdges: false,
659
+ importLimit: 12,
660
+ ownerSampleLimit: 3,
661
+ routeEdgeLimit: 0,
662
+ routeLimit: 8,
663
+ symbolLimit: 12,
664
+ };
665
+ }
666
+ return {
661
667
  edgeLimit: 30,
662
668
  fileLimit: 12,
663
669
  includeEdgeEvidenceMatches: true,
@@ -668,14 +674,26 @@ function codeContextPack(database, query, options = {}) {
668
674
  routeEdgeLimit: 0,
669
675
  routeLimit: 20,
670
676
  symbolLimit: 20,
671
- });
672
- const staleness = options.staleness ?? codeIndexStaleness(database);
677
+ };
678
+ }
679
+ function structuralSignature(value) {
680
+ const signature = (0, shared_1.oneLine)(String(value ?? ""));
681
+ const bodyStart = signature.indexOf("{");
682
+ return bodyStart >= 0 ? signature.slice(0, bodyStart).trimEnd() : signature;
683
+ }
684
+ function codeContextPack(database, query, options = {}) {
685
+ const normalized = query.trim();
686
+ if (!normalized)
687
+ return 'Code context pack: missing query; use --code-context-pack "path-or-symbol-or-route".';
673
688
  const coverage = (0, reports_1.evidenceCoverage)(database);
689
+ const indexedFileCount = Number(coverage.files ?? 0);
690
+ const evidence = (0, evidence_1.collectCodeEvidence)(database, normalized, codeContextCollectorOptions(indexedFileCount));
691
+ const staleness = options.staleness ?? codeIndexStaleness(database);
674
692
  const staleLabel = staleness.stale
675
693
  ? `STALE ${staleness.changed} changed, ${staleness.added} added, ${staleness.deleted} deleted`
676
694
  : "fresh";
677
695
  const lines = [
678
- `Code context pack "${normalized}": ${evidence.files.length} file matches, ${evidence.symbols.length} symbols, ${evidence.routes.length} routes, ${evidence.imports.length} imports, ${evidence.incomingEdges.length} incoming / ${evidence.outgoingEdges.length} outgoing edges; index ${staleLabel}; ${codeContextScaleLine(Number(coverage.files ?? 0))}.`,
696
+ `Code context pack "${normalized}": ${evidence.files.length} file matches, ${evidence.symbols.length} symbols, ${evidence.routes.length} routes, ${evidence.imports.length} imports, ${evidence.incomingEdges.length} incoming / ${evidence.outgoingEdges.length} outgoing edges; index ${staleLabel}; ${codeContextScaleLine(indexedFileCount)}.`,
679
697
  "Evidence is structural only: paths, lines, signatures, routes, imports, edges, and owners; no source snippets are included.",
680
698
  ];
681
699
  pushBudgetedSection(lines, "Files:", evidence.files, 8, (row) => ` file-match ${String(row.path)} (${String(row.language)}, ${String(row.profile)}, ${Number(row.lines ?? 0)} lines)`);
@@ -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-migrate With --code-index, approve replacing an incompatible schema-version index.
65
66
  --code-index-engine <engine> With --code-index, override default auto engine: typescript or native-rust.
66
67
  --code-parser <mode> With --code-index, use parser mode default or tree-sitter.
67
68
  --code-query <sql> Run conservative read-only SQL over the code evidence index.
@@ -166,6 +167,10 @@ if (args_1.codeIndexFullMode && !args_1.codeIndexMode) {
166
167
  console.error("--code-index-full is only supported with --code-index.");
167
168
  process.exit(1);
168
169
  }
170
+ if (args_1.codeIndexMigrateMode && !args_1.codeIndexMode) {
171
+ console.error("--code-index-migrate is only supported with --code-index.");
172
+ process.exit(1);
173
+ }
169
174
  if (args_1.codeIndexEngineMode && !args_1.codeIndexMode) {
170
175
  console.error("--code-index-engine is only supported with --code-index.");
171
176
  process.exit(1);
@@ -178,6 +183,10 @@ if (args_1.codeIndexIncrementalMode && args_1.codeIndexFullMode) {
178
183
  console.error("Use one code index update mode at a time: --incremental or --code-index-full.");
179
184
  process.exit(1);
180
185
  }
186
+ if (args_1.codeIndexIncrementalMode && args_1.codeIndexMigrateMode) {
187
+ console.error("Use one code index update mode at a time: --incremental or --code-index-migrate.");
188
+ process.exit(1);
189
+ }
181
190
  const activeHandoffModes = [
182
191
  args_1.handoffSaveMode ? "--handoff-save" : "",
183
192
  args_1.handoffShowMode ? "--handoff-show" : "",
@@ -52,6 +52,7 @@ const packageFiles = [
52
52
  "package.json",
53
53
  "agents",
54
54
  ];
55
+ const runtimeDependencyPackages = ["typescript"];
55
56
  function fail(message) {
56
57
  console.error(message);
57
58
  process.exit(1);
@@ -83,6 +84,18 @@ function installAgents() {
83
84
  function packageRoot() {
84
85
  return path.resolve(__dirname, "..");
85
86
  }
87
+ function runtimeDependencySource(packageName) {
88
+ try {
89
+ return path.dirname(require.resolve(`${packageName}/package.json`, { paths: [packageRoot()] }));
90
+ }
91
+ catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ return fail(`missing runtime dependency ${packageName}: run npm install before installing the Project Librarian skill. Error: ${message}`);
94
+ }
95
+ }
96
+ function runtimeDependencyTarget(packageName) {
97
+ return path.join("node_modules", ...packageName.split("/"));
98
+ }
86
99
  function userAgentRoot(agent) {
87
100
  const home = os.homedir();
88
101
  if (agent === "codex")
@@ -236,11 +249,18 @@ function installedProjectSkillSurfaces() {
236
249
  }
237
250
  function copyPackageFiles(targetRoot, dryRun, labelRoot = targetRoot) {
238
251
  const root = packageRoot();
239
- return packageFiles.map((relativePath) => {
252
+ const packageRows = packageFiles.map((relativePath) => {
240
253
  const source = path.join(root, relativePath);
241
254
  const target = path.join(targetRoot, relativePath);
242
255
  return [path.join(labelRoot, relativePath), copyPath(source, target, targetRoot, dryRun)];
243
256
  });
257
+ const dependencyRows = runtimeDependencyPackages.map((packageName) => {
258
+ const relativePath = runtimeDependencyTarget(packageName);
259
+ const source = runtimeDependencySource(packageName);
260
+ const target = path.join(targetRoot, relativePath);
261
+ return [path.join(labelRoot, relativePath), copyPath(source, target, targetRoot, dryRun)];
262
+ });
263
+ return [...packageRows, ...dependencyRows];
244
264
  }
245
265
  function syncProjectSkillInstall(agent) {
246
266
  return copyPackageFiles(projectSkillTarget(agent), false, projectSkillRelativeRoot(agent)).map(([label, status]) => {
@@ -261,7 +281,7 @@ function runInstallSkillMode() {
261
281
  console.log(`Project Librarian skill ${dryRun ? "install dry-run" : "install"} complete.`);
262
282
  console.log(`scope: ${scope}`);
263
283
  console.log(`agents: ${agents.join(", ")}`);
264
- console.log("note: install only installs the reusable skill files; it does not create or update AGENTS.md, CLAUDE.md, GEMINI.md, wiki/, .cursor/rules/, .cursor/hooks.json, .gemini/settings.json, .codex/hooks.json, or .claude/settings.json.");
284
+ console.log("note: install only installs the reusable skill files and required local-runner runtime dependencies; it does not create or update AGENTS.md, CLAUDE.md, GEMINI.md, wiki/, .cursor/rules/, .cursor/hooks.json, .gemini/settings.json, .codex/hooks.json, or .claude/settings.json.");
265
285
  console.log("compatibility: install-skill remains supported as an alias for install.");
266
286
  console.log("next: ask your agent to use Project Librarian from the target project root; the installed skill resolves the local runner.");
267
287
  for (const [label, status] of rows) {
@@ -5,7 +5,7 @@
5
5
  "architecture": "arm64",
6
6
  "format": "mach-o",
7
7
  "path": "dist/native/darwin-arm64/project-librarian-indexer",
8
- "sha256": "f5c69130f6c0441044141c761f1ccdc7e6ee4d06e327ce0f50d43a7aed76cc95",
8
+ "sha256": "848c29daf166e55672add5febf22fcd2ba261619a37969d9bc87cff06815787c",
9
9
  "size": 2697520,
10
10
  "triple": "darwin-arm64"
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "architecture": "x64",
14
14
  "format": "mach-o",
15
15
  "path": "dist/native/darwin-x64/project-librarian-indexer",
16
- "sha256": "25041398ba2de3ed749c14103d358ed4a7934a05459eb63c6cf5f6ae58c7fb03",
16
+ "sha256": "b419d82b7560defe1d43fc4d99d96b10222f1ead5bf6d7a6acabbae425ee7545",
17
17
  "size": 2770592,
18
18
  "triple": "darwin-x64"
19
19
  },
@@ -21,7 +21,7 @@
21
21
  "architecture": "arm64",
22
22
  "format": "elf",
23
23
  "path": "dist/native/linux-arm64/project-librarian-indexer",
24
- "sha256": "01bf4437fd62446958438ac58e9ca8bef811bdc6d70a63fc1a0ca1b627521e67",
24
+ "sha256": "f3cc884acca4f5863fb6a6545a4a9f9a37a67872bfe780f4b14f0c4a8a667f16",
25
25
  "size": 3065520,
26
26
  "triple": "linux-arm64"
27
27
  },
@@ -29,31 +29,31 @@
29
29
  "architecture": "arm64",
30
30
  "format": "elf",
31
31
  "path": "dist/native/linux-arm64-musl/project-librarian-indexer",
32
- "sha256": "b7287995bea678a6f55bd03761e5b0985d483e875d7594395b81bdb1e90a8dd9",
33
- "size": 3100840,
32
+ "sha256": "80f7440c4d115d05805b63b8118a172ddeb5a374b7154593e5b1d0b804a938ae",
33
+ "size": 3100816,
34
34
  "triple": "linux-arm64-musl"
35
35
  },
36
36
  {
37
37
  "architecture": "x64",
38
38
  "format": "elf",
39
39
  "path": "dist/native/linux-x64/project-librarian-indexer",
40
- "sha256": "5d93bb7576afbdde0acbde7983a874f2666f0d59227524c1eb42cc75b457c5ed",
41
- "size": 3110696,
40
+ "sha256": "3d93408db344567d7de6f477b58fdaf6aba19c943336fb7ab9720e3e9fa68c31",
41
+ "size": 3110832,
42
42
  "triple": "linux-x64"
43
43
  },
44
44
  {
45
45
  "architecture": "x64",
46
46
  "format": "elf",
47
47
  "path": "dist/native/linux-x64-musl/project-librarian-indexer",
48
- "sha256": "518aea0fc53ed7731751db4bb436a0be98f8583659d51aeff769bedb7a7cb8ab",
49
- "size": 3247384,
48
+ "sha256": "0055ec9fefa385d6d637fa1e457308c38f892e0fbdb389db3fcbdf546f0daca8",
49
+ "size": 3247400,
50
50
  "triple": "linux-x64-musl"
51
51
  },
52
52
  {
53
53
  "architecture": "arm64",
54
54
  "format": "pe",
55
55
  "path": "dist/native/win32-arm64/project-librarian-indexer.exe",
56
- "sha256": "392157f4d3591c48a66ee674b99a8a34cda3d38cc76df6343276becb23a7055c",
56
+ "sha256": "8c357be6b1e0c3c13337abccdc0edefc628267b272c8aca30be50c679bffb4af",
57
57
  "size": 2098688,
58
58
  "triple": "win32-arm64"
59
59
  },
@@ -61,7 +61,7 @@
61
61
  "architecture": "x64",
62
62
  "format": "pe",
63
63
  "path": "dist/native/win32-x64/project-librarian-indexer.exe",
64
- "sha256": "fabe69234daa9a6a738d0b7160c84573853d7a9e3be14c19ec6a27ca171e3f8a",
64
+ "sha256": "f4f9f6424fef446ece0684760afd8a404135acc007794c3e0f3effccdc8b4801",
65
65
  "size": 2375680,
66
66
  "triple": "win32-x64"
67
67
  }
package/dist/templates.js CHANGED
@@ -199,7 +199,7 @@ exports.startup = `${(0, exports.metadata)("startup-router", "short", "wiki/meta
199
199
  - This project is in an initial planning state unless the canonical wiki says otherwise.
200
200
  - Project truth lives in \`wiki/canonical/\`, future work in \`wiki/roadmaps/\` and \`wiki/plans/\`, project decisions in \`wiki/decisions/\`, and sources in \`wiki/sources/\`.
201
201
  - Wiki operating rules and wiki operating decisions live in \`wiki/meta/\`.
202
- - At session start, read only this file and \`wiki/index.md\` first; read detailed files on demand.
202
+ - At session start, read only this file and \`wiki/index.md\` first; use the index as a route table, open matching detail files directly, and avoid broad repo/wiki search unless no route matches.
203
203
  - Project canonical content language is not fixed by this bootstrap. The LLM should choose the language that best matches the user and project context.
204
204
  - Completed roadmaps/plans are removed after truth/rationale/evidence capture.
205
205
  - Update the wiki in the same turn when project-planning content changes.
@@ -232,7 +232,7 @@ exports.startup = `${(0, exports.metadata)("startup-router", "short", "wiki/meta
232
232
  ## Token Discipline
233
233
 
234
234
  - Codex, Claude Code, Cursor, and Gemini CLI session-start hooks inject only this file and \`wiki/index.md\`.
235
- - Detailed files are selected by the "read when" rules in \`wiki/index.md\`.
235
+ - Detailed files are selected by \`wiki/index.md\`; use broad wiki search only when no route matches or evidence conflicts.
236
236
  - Long decision history is not injected wholesale; read only relevant Decision Packs or ADRs.
237
237
  `;
238
238
  exports.index = `${(0, exports.metadata)("wiki-router", "short", "wiki/meta/wiki-ops-v1-decisions.md", "wiki page added, moved, removed, or routing changes")}
@@ -240,7 +240,7 @@ exports.index = `${(0, exports.metadata)("wiki-router", "short", "wiki/meta/wiki
240
240
 
241
241
  ## How To Use This Index
242
242
 
243
- This file is a router, not a file to expand into every answer. Read only the files that are relevant to the current question.
243
+ This is a route table, not a page inventory. Open the matching route first; use broad wiki search or file listing only when no route matches or evidence conflicts.
244
244
 
245
245
  ## Language Policy
246
246
 
@@ -9,13 +9,13 @@ node .codex/skills/project-librarian/dist/init-project-wiki.js install [--scope
9
9
 
10
10
  `install-skill` remains a compatibility alias for `install`.
11
11
 
12
- `update` is the explicit existing-project update command. It rejects `--migrate` and `--adopt-existing`; use top-level `--migrate` when legacy docs or wiki content should be preserved into `wiki_legacy*` and reviewed. When project-scoped Project Librarian skill installs already exist for the selected agent surfaces, `update` copies the current package's reusable skill files into those project skill directories before refreshing the managed setup.
12
+ `update` is the explicit existing-project update command. It rejects `--migrate` and `--adopt-existing`; use top-level `--migrate` when legacy docs or wiki content should be preserved into `wiki_legacy*` and reviewed. When project-scoped Project Librarian skill installs already exist for the selected agent surfaces, `update` copies the current package's reusable skill files and required local-runner runtime dependencies into those project skill directories before refreshing the managed setup.
13
13
 
14
14
  ### Important Options
15
15
 
16
16
  | Option | Purpose |
17
17
  | --- | --- |
18
- | `install --scope user|project --agents <list> --dry-run` | Install reusable skill files globally or into the current repository; `--dry-run` previews copied files for install only. |
18
+ | `install --scope user|project --agents <list> --dry-run` | Install reusable skill files and required local-runner runtime dependencies globally or into the current repository; `--dry-run` previews copied files for install only. |
19
19
  | `update --agents <list>` | Refresh an existing setup and existing project-scoped skill copies; selected surfaces can be `codex`, `claude`, `cursor`, `gemini`, or `all`. |
20
20
  | `--migrate`, `--adopt-existing` | Preserve an existing wiki as `wiki_legacy*`, create migration inboxes, and generate unit-map/split-plan/coverage review files. |
21
21
  | `--lint` | Validate generated setup without editing files. |
@@ -48,6 +48,7 @@ node .codex/skills/project-librarian/dist/init-project-wiki.js install [--scope
48
48
  | `--code-index-out <path>` | Use a custom SQLite output path under `.project-wiki/`; applies to index and read modes. |
49
49
  | `--acknowledge-small-repo` | With `--code-index`, proceed below the ~5k-file scale gate after the cost warning. |
50
50
  | `--incremental`, `--code-index-incremental`, `--code-index-full` | With `--code-index`, require an incremental update or force a full rebuild. |
51
+ | `--code-index-migrate` | With `--code-index`, explicitly approve replacing an existing index whose schema version differs from the current package. |
51
52
  | `--code-parser <mode>` | With `--code-index`, select `default` or optional `tree-sitter` extraction. |
52
53
  | `--code-index-health` | Inspect code evidence cache compatibility and print rebuild guidance without writing. |
53
54
  | `--code-index-engine <engine>` | Override the default `auto` index engine with `typescript` or `native-rust`. |
@@ -78,6 +78,12 @@ The matrix lists languages with implemented symbol/import extraction. Other reco
78
78
 
79
79
  Recognized but inventory-only extensions include `.rb`, `.vue`, and `.css`. Config files (`.json`, `.yaml`, `.yml`, `.toml`, `.env.example`, `package.json`, `tsconfig.json`, `Dockerfile`, and `Makefile`) are indexed as configuration or inventory evidence.
80
80
 
81
+ ## Schema Migration
82
+
83
+ The code evidence index is disposable, but Project Librarian still treats schema-version changes as an explicit migration boundary. If an existing `.project-wiki/code-evidence.sqlite` has a different schema version, `--code-index` stops before replacing the database and prints a migration-required message with the approval command.
84
+
85
+ Run `project-librarian --code-index-health` to inspect the current index. To approve replacing an incompatible-schema index, rerun the build with `--code-index --code-index-migrate` plus the same scope/parser options you want for the new index. `--incremental` cannot migrate schema versions.
86
+
81
87
  ## Native Helper Policy
82
88
 
83
89
  Experimental `--code-index-engine native-rust` runs the native helper for `typescript-ast`, `config`, the listed `*-light` profiles, and inventory-only source files. Omitted `--code-index-engine` means `auto`; full-index auto uses the native helper when a helper is available and at least one structurally extracted native profile is present, while config-only or inventory-only repositories stay on TypeScript. Compatible incremental auto uses the Rust direct-writer when a helper is available and the changed files are native-eligible.
@@ -9,13 +9,13 @@ node .codex/skills/project-librarian/dist/init-project-wiki.js install [--scope
9
9
 
10
10
  `install-skill`은 `install`의 호환성 별칭으로 계속 지원됩니다.
11
11
 
12
- `update`는 기존 프로젝트 갱신을 명시하는 명령입니다. `--migrate`와 `--adopt-existing`를 거부합니다. 기존 문서나 위키 내용을 `wiki_legacy*`로 보존하고 검토해야 한다면 top-level `--migrate`를 사용합니다. 선택된 에이전트 표면에 프로젝트 범위 Project Librarian 스킬 설치가 이미 있으면 `update`는 현재 패키지의 재사용 가능한 스킬 파일을 해당 프로젝트 스킬 디렉터리에 복사한 뒤 관리 설정을 갱신합니다.
12
+ `update`는 기존 프로젝트 갱신을 명시하는 명령입니다. `--migrate`와 `--adopt-existing`를 거부합니다. 기존 문서나 위키 내용을 `wiki_legacy*`로 보존하고 검토해야 한다면 top-level `--migrate`를 사용합니다. 선택된 에이전트 표면에 프로젝트 범위 Project Librarian 스킬 설치가 이미 있으면 `update`는 현재 패키지의 재사용 가능한 스킬 파일과 로컬 실행기에 필요한 필수 런타임 의존성을 해당 프로젝트 스킬 디렉터리에 복사한 뒤 관리 설정을 갱신합니다.
13
13
 
14
14
  ### 주요 옵션
15
15
 
16
16
  | 옵션 | 목적 |
17
17
  | --- | --- |
18
- | `install --scope user|project --agents <list> --dry-run` | 재사용 가능한 스킬 파일을 전역 또는 현재 저장소에 설치합니다. `--dry-run`은 install에서 복사될 파일을 미리 보여줍니다. |
18
+ | `install --scope user|project --agents <list> --dry-run` | 재사용 가능한 스킬 파일과 로컬 실행기에 필요한 필수 런타임 의존성을 전역 또는 현재 저장소에 설치합니다. `--dry-run`은 install에서 복사될 파일을 미리 보여줍니다. |
19
19
  | `update --agents <list>` | 기존 설정과 기존 프로젝트 범위 스킬 복사본을 갱신합니다. 표면은 `codex`, `claude`, `cursor`, `gemini`, `all` 중 하나입니다. |
20
20
  | `--migrate`, `--adopt-existing` | 기존 위키를 `wiki_legacy*`로 보존하고 migration inbox, unit-map, split-plan, coverage 검토 파일을 만듭니다. |
21
21
  | `--lint` | 파일을 수정하지 않고 생성된 설정을 검증합니다. |
@@ -48,6 +48,7 @@ node .codex/skills/project-librarian/dist/init-project-wiki.js install [--scope
48
48
  | `--code-index-out <path>` | `.project-wiki/` 아래 사용자 지정 SQLite 출력 경로를 사용합니다. index와 read mode에 모두 적용됩니다. |
49
49
  | `--acknowledge-small-repo` | `--code-index`와 함께 사용해 약 5k 파일 미만 규모 경고 후에도 진행합니다. |
50
50
  | `--incremental`, `--code-index-incremental`, `--code-index-full` | `--code-index`와 함께 사용해 증분 갱신을 요구하거나 전체 재생성을 강제합니다. |
51
+ | `--code-index-migrate` | `--code-index`와 함께 사용해 기존 인덱스의 스키마 버전이 현재 패키지와 다를 때 교체를 명시적으로 승인합니다. |
51
52
  | `--code-parser <mode>` | `--code-index`와 함께 사용해 `default` 또는 선택적 `tree-sitter` 추출을 선택합니다. |
52
53
  | `--code-index-health` | 파일을 쓰지 않고 코드 근거 캐시 호환성과 재빌드 안내를 출력합니다. |
53
54
  | `--code-index-engine <engine>` | 기본 `auto` 인덱스 엔진을 `typescript` 또는 `native-rust`로 override합니다. |
@@ -78,6 +78,12 @@ codex mcp add project-librarian -- node .codex/skills/project-librarian/dist/ini
78
78
 
79
79
  `.rb`, `.vue`, `.css`는 인식하지만 inventory-only입니다. `.json`, `.yaml`, `.yml`, `.toml`, `.env.example`, `package.json`, `tsconfig.json`, `Dockerfile`, `Makefile`은 설정 또는 inventory 근거로 인덱싱됩니다.
80
80
 
81
+ ## 스키마 마이그레이션
82
+
83
+ 코드 근거 인덱스는 폐기 가능한 캐시지만, Project Librarian은 스키마 버전 변경을 명시적 마이그레이션 경계로 취급합니다. 기존 `.project-wiki/code-evidence.sqlite`의 스키마 버전이 현재 패키지와 다르면 `--code-index`는 데이터베이스를 교체하기 전에 멈추고 마이그레이션 필요 메시지와 승인 명령을 출력합니다.
84
+
85
+ 현재 인덱스 상태는 `project-librarian --code-index-health`로 확인합니다. 호환되지 않는 스키마의 인덱스 교체를 승인하려면 새 인덱스에 적용할 scope/parser 옵션과 함께 `--code-index --code-index-migrate`를 다시 실행합니다. `--incremental`은 스키마 버전을 마이그레이션할 수 없습니다.
86
+
81
87
  ## 네이티브 헬퍼 정책
82
88
 
83
89
  실험적 `--code-index-engine native-rust`는 `typescript-ast`, `config`, 표의 `*-light` 프로파일, inventory-only 소스 파일을 네이티브 헬퍼로 처리합니다. `--code-index-engine`을 생략하면 `auto`입니다. full-index auto는 헬퍼를 사용할 수 있고 구조적으로 추출되는 네이티브 프로파일이 하나 이상 있을 때 네이티브 헬퍼를 사용하며, config-only 또는 inventory-only 저장소는 TypeScript 경로에 남깁니다. 호환되는 incremental auto는 헬퍼를 사용할 수 있고 변경 파일이 native-eligible이면 Rust direct-writer를 사용합니다.
package/docs/ko/usage.md CHANGED
@@ -16,7 +16,7 @@ npx project-librarian@latest install --scope user --agents all
16
16
  npx project-librarian@latest install --scope project --agents all
17
17
  ```
18
18
 
19
- `install`은 재사용 가능한 스킬 파일만 복사합니다. `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `wiki/`, `.cursor/rules/`, `.cursor/hooks.json`, `.gemini/settings.json`, `.codex/hooks.json`, `.claude/settings.json`은 만들거나 갱신하지 않습니다. `install-skill`은 호환성 별칭으로 계속 지원됩니다.
19
+ `install`은 재사용 가능한 스킬 파일과 로컬 실행기에 필요한 필수 런타임 의존성을 복사합니다. `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `wiki/`, `.cursor/rules/`, `.cursor/hooks.json`, `.gemini/settings.json`, `.codex/hooks.json`, `.claude/settings.json`은 만들거나 갱신하지 않습니다. `install-skill`은 호환성 별칭으로 계속 지원됩니다.
20
20
 
21
21
  | 상황 | 명령 |
22
22
  | --- | --- |
@@ -34,7 +34,7 @@ npx project-librarian@latest install --scope project --agents all
34
34
 
35
35
  마이그레이션 없는 기존 설정 갱신은 저장소에 이미 있는 에이전트 표면만 보존해서 갱신합니다. 따라서 Codex와 Claude 파일만 있던 저장소는 일반 갱신만으로 Cursor나 Gemini 파일이 새로 생기지 않습니다. 새 표면을 의도적으로 추가하려면 `project-librarian update --agents cursor` 또는 `project-librarian update --agents all`처럼 명시합니다. 목록에 없는 표면을 삭제하지는 않습니다.
36
36
 
37
- `project-librarian update`는 선택된 표면에 이미 프로젝트 범위 Project Librarian 스킬 설치가 있으면 현재 실행 중인 패키지의 재사용 가능한 스킬 파일을 그 프로젝트 스킬 디렉터리로 동기화합니다. 기본적으로 새 프로젝트 범위 스킬 설치를 만들지는 않고, 사용자 범위 스킬 설치도 갱신하지 않습니다. 사용자 범위 스킬은 `install --scope user`로 명시적으로 갱신합니다.
37
+ `project-librarian update`는 선택된 표면에 이미 프로젝트 범위 Project Librarian 스킬 설치가 있으면 현재 실행 중인 패키지의 재사용 가능한 스킬 파일과 프로젝트 로컬 실행기에 필요한 런타임 의존성을 그 프로젝트 스킬 디렉터리로 동기화합니다. 기본적으로 새 프로젝트 범위 스킬 설치를 만들지는 않고, 사용자 범위 스킬 설치도 갱신하지 않습니다. 사용자 범위 스킬은 `install --scope user`로 명시적으로 갱신합니다.
38
38
 
39
39
  ## 실행 경로
40
40
 
@@ -87,6 +87,7 @@ npx project-librarian@latest install --scope project --agents all
87
87
  | 여러 범위 빌드 | "`src`와 `packages/api`에 대해 Project Librarian 코드 근거를 만들어줘." | `--code-index --code-scope src --code-scope packages/api` |
88
88
  | 증분 갱신 요구 | "Project Librarian 코드 근거 인덱스를 증분 갱신해줘." | `--code-index --incremental` |
89
89
  | 전체 재생성 강제 | "Project Librarian 코드 근거 인덱스를 전체 재생성해줘." | `--code-index --code-index-full` |
90
+ | 스키마 마이그레이션 승인 | "Project Librarian 코드 근거 인덱스 스키마를 마이그레이션해줘." | `--code-index --code-index-migrate` |
90
91
  | 선택적 Tree-sitter 백엔드 사용 | "Tree-sitter 파서로 Project Librarian 코드 근거를 만들어줘." | `--code-index --code-parser tree-sitter` |
91
92
  | 캐시 호환성 진단 | "Project Librarian 코드 근거 캐시 상태와 호환성을 진단해줘." | `--code-index-health` |
92
93
  | 캐시 상태 확인 | "Project Librarian 코드 근거 상태를 보여줘." | `--code-status` |
@@ -98,7 +99,7 @@ npx project-librarian@latest install --scope project --agents all
98
99
  | 심볼 검색 | "`Auth` 심볼을 Project Librarian 코드 근거에서 찾아줘." | `--code-search-symbol Auth` |
99
100
  | 보수적 읽기 전용 SQL | "파일 경로를 위한 읽기 전용 Project Librarian 코드 근거 쿼리를 실행해줘." | `--code-query "select path from files order by path"` |
100
101
 
101
- 한 번에 하나의 코드 근거 모드만 실행할 수 있습니다. `--incremental`, `--code-index-full`, `--code-parser`는 `--code-index`와 함께 사용할 때만 유효합니다.
102
+ 한 번에 하나의 코드 근거 모드만 실행할 수 있습니다. `--incremental`, `--code-index-full`, `--code-index-migrate`, `--code-parser`는 `--code-index`와 함께 사용할 때만 유효합니다. `--code-index-migrate`는 기존 폐기형 인덱스의 스키마 버전이 현재 패키지와 다를 때 교체를 승인한다는 명시적 표시입니다.
102
103
 
103
104
  ## 설치되는 파일
104
105
 
package/docs/usage.md CHANGED
@@ -16,7 +16,7 @@ Install into the current repository instead:
16
16
  npx project-librarian@latest install --scope project --agents all
17
17
  ```
18
18
 
19
- `install` copies reusable skill files only. It does not create or update `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `wiki/`, `.cursor/rules/`, `.cursor/hooks.json`, `.gemini/settings.json`, `.codex/hooks.json`, or `.claude/settings.json`. `install-skill` remains supported as a compatibility alias.
19
+ `install` copies reusable skill files and required local-runner runtime dependencies. It does not create or update `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `wiki/`, `.cursor/rules/`, `.cursor/hooks.json`, `.gemini/settings.json`, `.codex/hooks.json`, or `.claude/settings.json`. `install-skill` remains supported as a compatibility alias.
20
20
 
21
21
  | Situation | Command |
22
22
  | --- | --- |
@@ -32,7 +32,7 @@ npx project-librarian@latest install --scope project --agents all
32
32
 
33
33
  The project setup/update runner also accepts `--agents`. Fresh setup defaults to all supported agent surfaces only when no project-scoped Project Librarian skill install is present. If the repository already has project-scoped skills such as `.codex/skills/project-librarian/` and `.claude/skills/project-librarian/`, the first setup uses that installed agent set by default. Existing non-migration updates preserve the agent surfaces already present in the repository, so a repo that has only Codex and Claude files will not gain Cursor or Gemini files on a plain update. Use `project-librarian update --agents cursor` or `project-librarian update --agents all` when you intentionally want to add newly supported surfaces; unlisted surfaces are not deleted.
34
34
 
35
- `project-librarian update` also syncs any project-scoped Project Librarian skill installs that already exist for the selected surfaces from the currently running package. This means `npx project-librarian@latest update` can refresh the repository's managed setup, hooks, wiki meta files, and existing project-scoped skill copies without migration. It does not create new project-scoped skill installs by default and does not update user-scoped skill installs; use `install --scope user` for that.
35
+ `project-librarian update` also syncs any project-scoped Project Librarian skill installs that already exist for the selected surfaces from the currently running package, including the runtime dependencies needed by the project-local runner. This means `npx project-librarian@latest update` can refresh the repository's managed setup, hooks, wiki meta files, and existing project-scoped skill copies without migration. It does not create new project-scoped skill installs by default and does not update user-scoped skill installs; use `install --scope user` for that.
36
36
 
37
37
  ## Runner Paths
38
38
 
@@ -85,6 +85,7 @@ Code evidence:
85
85
  | Build multiple scopes | "Build Project Librarian code evidence for `src` and `packages/api`." | `--code-index --code-scope src --code-scope packages/api` |
86
86
  | Require incremental update | "Update the Project Librarian code evidence index incrementally." | `--code-index --incremental` |
87
87
  | Force a full rebuild | "Fully rebuild the Project Librarian code evidence index." | `--code-index --code-index-full` |
88
+ | Approve a schema migration | "Migrate the Project Librarian code evidence index schema." | `--code-index --code-index-migrate` |
88
89
  | Use optional Tree-sitter backend | "Build Project Librarian code evidence with the Tree-sitter parser." | `--code-index --code-parser tree-sitter` |
89
90
  | Inspect cache compatibility | "Inspect Project Librarian code evidence cache health." | `--code-index-health` |
90
91
  | Show cache status | "Show Project Librarian code evidence status." | `--code-status` |
@@ -96,7 +97,7 @@ Code evidence:
96
97
  | Search indexed symbols | "Search Project Librarian code evidence for symbol `Auth`." | `--code-search-symbol Auth` |
97
98
  | Run conservative read-only SQL | "Run a read-only Project Librarian code evidence query for file paths." | `--code-query "select path from files order by path"` |
98
99
 
99
- Only one code evidence mode can run at a time. `--incremental`, `--code-index-full`, and `--code-parser` are valid only with `--code-index`.
100
+ Only one code evidence mode can run at a time. `--incremental`, `--code-index-full`, `--code-index-migrate`, and `--code-parser` are valid only with `--code-index`. `--code-index-migrate` is explicit approval to replace an existing disposable index when its schema version differs from the current package.
100
101
 
101
102
  ## What Gets Installed
102
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-librarian",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "Create and maintain compact project context for humans and LLM coding agents.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",