project-librarian 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CONTRIBUTING.md +36 -0
  2. package/README.ko.md +57 -360
  3. package/README.md +56 -359
  4. package/dist/args.js +6 -1
  5. package/dist/code-index/extractors/light-languages.js +285 -0
  6. package/dist/code-index/extractors/registry.js +12 -0
  7. package/dist/code-index/extractors/shared.js +18 -1
  8. package/dist/code-index/extractors/typescript.js +30 -16
  9. package/dist/code-index/modes.js +136 -32
  10. package/dist/code-index/native-helper-matrix.js +99 -0
  11. package/dist/code-index/native-helper.js +292 -0
  12. package/dist/code-index/ownership.js +8 -6
  13. package/dist/code-index/schema.js +72 -13
  14. package/dist/code-index/search.js +1 -1
  15. package/dist/code-index-db.js +20 -12
  16. package/dist/code-index-file-policy.js +17 -11
  17. package/dist/code-index.js +365 -13
  18. package/dist/hooks.js +5 -5
  19. package/dist/init-project-wiki.js +7 -1
  20. package/dist/install-skill.js +99 -6
  21. package/dist/mcp-server.js +4 -4
  22. package/dist/migration.js +27 -2
  23. package/dist/native/darwin-arm64/project-librarian-indexer +0 -0
  24. package/dist/native/darwin-x64/project-librarian-indexer +0 -0
  25. package/dist/native/linux-arm64/project-librarian-indexer +0 -0
  26. package/dist/native/linux-arm64-musl/project-librarian-indexer +0 -0
  27. package/dist/native/linux-x64/project-librarian-indexer +0 -0
  28. package/dist/native/linux-x64-musl/project-librarian-indexer +0 -0
  29. package/dist/native/project-librarian-indexer-manifest.json +70 -0
  30. package/dist/native/win32-arm64/project-librarian-indexer.exe +0 -0
  31. package/dist/native/win32-x64/project-librarian-indexer.exe +0 -0
  32. package/dist/templates.js +4 -3
  33. package/dist/workspace.js +137 -10
  34. package/docs/README.md +11 -0
  35. package/docs/benchmarks.md +64 -0
  36. package/docs/cli-reference.md +60 -0
  37. package/docs/code-evidence.md +87 -0
  38. package/docs/ko/README.md +13 -0
  39. package/docs/ko/benchmarks.md +64 -0
  40. package/docs/ko/cli-reference.md +60 -0
  41. package/docs/ko/code-evidence.md +87 -0
  42. package/docs/ko/maintainer.md +76 -0
  43. package/docs/ko/usage.md +167 -0
  44. package/docs/maintainer.md +76 -0
  45. package/docs/usage.md +175 -0
  46. package/package.json +13 -2
package/dist/migration.js CHANGED
@@ -57,6 +57,7 @@ exports.migrationCoverageStatusMap = migrationCoverageStatusMap;
57
57
  exports.semanticStatusForInboxStatus = semanticStatusForInboxStatus;
58
58
  exports.runReviewMigrationMode = runReviewMigrationMode;
59
59
  const fs = __importStar(require("node:fs"));
60
+ const path = __importStar(require("node:path"));
60
61
  const taxonomy_1 = require("./taxonomy");
61
62
  const workspace_1 = require("./workspace");
62
63
  const templates_1 = require("./templates");
@@ -317,9 +318,31 @@ function activeMigrationLegacyRoots() {
317
318
  const verifiedRoot = migrationVerificationLegacyRoot();
318
319
  return verifiedRoot ? [verifiedRoot] : legacyWikiRoots();
319
320
  }
321
+ function migrationDirectoryPath(relativePath) {
322
+ const resolved = path.resolve(workspace_1.root, relativePath);
323
+ const rootResolved = path.resolve(workspace_1.root);
324
+ if (resolved !== rootResolved && !resolved.startsWith(`${rootResolved}${path.sep}`)) {
325
+ throw new Error(`migration directory must stay inside the project root: ${relativePath}`);
326
+ }
327
+ const stat = fs.lstatSync(resolved);
328
+ if (stat.isSymbolicLink()) {
329
+ throw new Error(`migration refuses to follow symlinked wiki directory: ${relativePath}`);
330
+ }
331
+ if (!stat.isDirectory()) {
332
+ throw new Error(`migration path is not a directory: ${relativePath}`);
333
+ }
334
+ const realPath = fs.realpathSync(resolved);
335
+ if (realPath !== rootResolved && !realPath.startsWith(`${rootResolved}${path.sep}`)) {
336
+ throw new Error(`migration directory must resolve inside the project root: ${relativePath}`);
337
+ }
338
+ return resolved;
339
+ }
320
340
  function expectedMigrationUnits() {
321
341
  return activeMigrationLegacyRoots()
322
- .flatMap((legacyRoot) => (0, wiki_files_1.walkMarkdownFiles)((0, workspace_1.abs)(legacyRoot), [], (0, workspace_1.abs)(legacyRoot)))
342
+ .flatMap((legacyRoot) => {
343
+ const legacyPath = migrationDirectoryPath(legacyRoot);
344
+ return (0, wiki_files_1.walkMarkdownFiles)(legacyPath, [], legacyPath);
345
+ })
323
346
  .flatMap((file) => extractMigrationUnits(file.basePath, (0, workspace_1.read)(file.path)));
324
347
  }
325
348
  function loadMigrationUnitContext() {
@@ -962,6 +985,7 @@ function nextLegacyPath() {
962
985
  }
963
986
  function prepareMigrationMode() {
964
987
  if ((0, workspace_1.exists)("wiki")) {
988
+ migrationDirectoryPath("wiki");
965
989
  const legacyPath = nextLegacyPath();
966
990
  fs.renameSync((0, workspace_1.abs)("wiki"), (0, workspace_1.abs)(legacyPath));
967
991
  return { legacyPath, note: `moved wiki to ${legacyPath}` };
@@ -981,7 +1005,8 @@ function migrationTargetForKind(kind) {
981
1005
  }
982
1006
  function runMigrationMode(migrationState) {
983
1007
  const legacyPath = migrationState.legacyPath;
984
- const markdownFiles = legacyPath && (0, workspace_1.exists)(legacyPath) ? (0, wiki_files_1.walkMarkdownFiles)((0, workspace_1.abs)(legacyPath), [], (0, workspace_1.abs)(legacyPath)) : [];
1008
+ const legacyRootPath = legacyPath && (0, workspace_1.exists)(legacyPath) ? migrationDirectoryPath(legacyPath) : "";
1009
+ const markdownFiles = legacyRootPath ? (0, wiki_files_1.walkMarkdownFiles)(legacyRootPath, [], legacyRootPath) : [];
985
1010
  const fileRecords = markdownFiles.map((file) => {
986
1011
  const text = (0, workspace_1.read)(file.path);
987
1012
  return { file, text, formOnlyReason: formOnlyMigrationDocumentReason(file.basePath, text) };
@@ -0,0 +1,70 @@
1
+ {
2
+ "artifact": "project-librarian-indexer",
3
+ "helpers": [
4
+ {
5
+ "architecture": "arm64",
6
+ "format": "mach-o",
7
+ "path": "dist/native/darwin-arm64/project-librarian-indexer",
8
+ "sha256": "f5c69130f6c0441044141c761f1ccdc7e6ee4d06e327ce0f50d43a7aed76cc95",
9
+ "size": 2697520,
10
+ "triple": "darwin-arm64"
11
+ },
12
+ {
13
+ "architecture": "x64",
14
+ "format": "mach-o",
15
+ "path": "dist/native/darwin-x64/project-librarian-indexer",
16
+ "sha256": "25041398ba2de3ed749c14103d358ed4a7934a05459eb63c6cf5f6ae58c7fb03",
17
+ "size": 2770592,
18
+ "triple": "darwin-x64"
19
+ },
20
+ {
21
+ "architecture": "arm64",
22
+ "format": "elf",
23
+ "path": "dist/native/linux-arm64/project-librarian-indexer",
24
+ "sha256": "01bf4437fd62446958438ac58e9ca8bef811bdc6d70a63fc1a0ca1b627521e67",
25
+ "size": 3065520,
26
+ "triple": "linux-arm64"
27
+ },
28
+ {
29
+ "architecture": "arm64",
30
+ "format": "elf",
31
+ "path": "dist/native/linux-arm64-musl/project-librarian-indexer",
32
+ "sha256": "b7287995bea678a6f55bd03761e5b0985d483e875d7594395b81bdb1e90a8dd9",
33
+ "size": 3100840,
34
+ "triple": "linux-arm64-musl"
35
+ },
36
+ {
37
+ "architecture": "x64",
38
+ "format": "elf",
39
+ "path": "dist/native/linux-x64/project-librarian-indexer",
40
+ "sha256": "5d93bb7576afbdde0acbde7983a874f2666f0d59227524c1eb42cc75b457c5ed",
41
+ "size": 3110696,
42
+ "triple": "linux-x64"
43
+ },
44
+ {
45
+ "architecture": "x64",
46
+ "format": "elf",
47
+ "path": "dist/native/linux-x64-musl/project-librarian-indexer",
48
+ "sha256": "518aea0fc53ed7731751db4bb436a0be98f8583659d51aeff769bedb7a7cb8ab",
49
+ "size": 3247384,
50
+ "triple": "linux-x64-musl"
51
+ },
52
+ {
53
+ "architecture": "arm64",
54
+ "format": "pe",
55
+ "path": "dist/native/win32-arm64/project-librarian-indexer.exe",
56
+ "sha256": "392157f4d3591c48a66ee674b99a8a34cda3d38cc76df6343276becb23a7055c",
57
+ "size": 2098688,
58
+ "triple": "win32-arm64"
59
+ },
60
+ {
61
+ "architecture": "x64",
62
+ "format": "pe",
63
+ "path": "dist/native/win32-x64/project-librarian-indexer.exe",
64
+ "sha256": "fabe69234daa9a6a738d0b7160c84573853d7a9e3be14c19ec6a27ca171e3f8a",
65
+ "size": 2375680,
66
+ "triple": "win32-x64"
67
+ }
68
+ ],
69
+ "schema_version": 1
70
+ }
package/dist/templates.js CHANGED
@@ -87,7 +87,7 @@ During conversation:
87
87
  - Do not store non-project LLM memory, assistant preferences, collaboration reminders, or workflow instructions in project wiki canonical or decision docs.
88
88
  - Follow \`wiki/AGENTS.md\` for detailed rules when editing files under \`wiki/\`.
89
89
  - Treat broad maintenance/improvement automation requests that do not name a concrete command (for example "improve this project", "start improvement automation", or "개선 자동화 시작해") as analyze-first project work, not as a plain bootstrap/update. Inspect repo, wiki, CI, test, release, dependency, and code-structure evidence; produce a ranked backlog with evidence and verification paths; persist the plan in \`wiki/plans/\` when project-planning content changes; then execute safe high-priority items with tests.
90
- - Let \`.githooks/prepare-commit-msg\` append wiki trailers automatically for staged wiki, hook, AGENTS, or project-librarian files.
90
+ - Do not execute worktree-controlled commit hooks for wiki trailers; add trailers explicitly when needed.
91
91
  - ${exports.wikiTrustContract}
92
92
  - ${exports.codeEvidenceTrustContract}
93
93
  - ${exports.guidanceClaimEvidenceContract}
@@ -177,9 +177,9 @@ Update rules:
177
177
  Commit rules:
178
178
 
179
179
  - Follow the repository's commit-message policy when one exists.
180
- - Let \`.githooks/prepare-commit-msg\` append wiki trailers automatically when git hooks are enabled.
180
+ - Do not execute worktree-controlled commit hooks for wiki trailers; add trailers explicitly when needed.
181
181
  - If bootstrap was run with \`--no-git-config\`, hook files are installed but \`core.hooksPath\` is not changed.
182
- - Do not hand-write wiki trailers unless the hook is unavailable or a trailer needs correction.
182
+ - Hand-write wiki trailers when project policy requires them; keep them accurate and evidence-backed.
183
183
  <!-- PROJECT-WIKI-INTERNAL:END -->`;
184
184
  const metadata = (scope, budget, decisionRef, trigger, status = "active") => `---
185
185
  status: ${status}
@@ -415,6 +415,7 @@ node dist/init-project-wiki.js --query "search terms"
415
415
  ## Git Hook Setup
416
416
 
417
417
  - The script installs \`.githooks/prepare-commit-msg\` and \`.githooks/wiki-commit-trailers.js\`.
418
+ - \`.githooks/prepare-commit-msg\` is intentionally passive and must not execute worktree-controlled scripts.
418
419
  - By default, git repositories with an unset \`core.hooksPath\` are configured with \`git config core.hooksPath .githooks\`.
419
420
  - Existing \`core.hooksPath\` values are preserved so an existing hook chain is not replaced.
420
421
  - Run bootstrap with \`--no-git-config\` to install hook files without changing git config.
package/dist/workspace.js CHANGED
@@ -51,31 +51,105 @@ exports.normalizePath = normalizePath;
51
51
  exports.commandOk = commandOk;
52
52
  exports.isGitRepository = isGitRepository;
53
53
  exports.makeExecutable = makeExecutable;
54
+ exports.containedProjectFileStat = containedProjectFileStat;
55
+ exports.containedProjectDirectoryStat = containedProjectDirectoryStat;
56
+ exports.requireContainedProjectFile = requireContainedProjectFile;
54
57
  exports.walkFilesUnder = walkFilesUnder;
55
58
  const fs = __importStar(require("node:fs"));
56
59
  const path = __importStar(require("node:path"));
57
60
  const childProcess = __importStar(require("node:child_process"));
58
61
  exports.root = process.cwd();
59
62
  exports.today = new Date().toISOString().slice(0, 10);
63
+ const projectRoot = path.resolve(exports.root);
60
64
  function abs(relativePath) {
61
65
  return path.join(exports.root, relativePath);
62
66
  }
67
+ function isInsideProject(absolutePath) {
68
+ const resolved = path.resolve(absolutePath);
69
+ return resolved === projectRoot || resolved.startsWith(`${projectRoot}${path.sep}`);
70
+ }
71
+ function resolveProjectPath(relativePath, label = "path") {
72
+ const resolved = path.isAbsolute(relativePath) ? path.resolve(relativePath) : path.resolve(exports.root, relativePath);
73
+ if (!isInsideProject(resolved)) {
74
+ throw new Error(`${label} must stay inside the project root: ${relativePath}`);
75
+ }
76
+ return resolved;
77
+ }
78
+ function assertNoSymlinkInProjectPath(relativePath, includeLeaf, label = "path") {
79
+ const target = resolveProjectPath(relativePath, label);
80
+ const relative = path.relative(projectRoot, target);
81
+ if (!relative)
82
+ return target;
83
+ const parts = relative.split(path.sep).filter(Boolean);
84
+ const checkedParts = includeLeaf ? parts : parts.slice(0, -1);
85
+ let current = projectRoot;
86
+ for (const part of checkedParts) {
87
+ current = path.join(current, part);
88
+ if (!fs.existsSync(current))
89
+ continue;
90
+ const stat = fs.lstatSync(current);
91
+ if (stat.isSymbolicLink()) {
92
+ throw new Error(`${label} refuses to follow symlink: ${normalizePath(path.relative(projectRoot, current))}`);
93
+ }
94
+ if (current !== target && !stat.isDirectory()) {
95
+ throw new Error(`${label} has a non-directory path component: ${normalizePath(path.relative(projectRoot, current))}`);
96
+ }
97
+ }
98
+ return target;
99
+ }
100
+ function mkdirpAbsolute(target, label = "path") {
101
+ if (!isInsideProject(target)) {
102
+ throw new Error(`${label} must stay inside the project root: ${target}`);
103
+ }
104
+ const relative = path.relative(projectRoot, target);
105
+ if (!relative)
106
+ return;
107
+ let current = projectRoot;
108
+ for (const part of relative.split(path.sep).filter(Boolean)) {
109
+ current = path.join(current, part);
110
+ if (fs.existsSync(current)) {
111
+ const stat = fs.lstatSync(current);
112
+ if (stat.isSymbolicLink()) {
113
+ throw new Error(`${label} refuses to follow symlink: ${normalizePath(path.relative(projectRoot, current))}`);
114
+ }
115
+ if (!stat.isDirectory()) {
116
+ throw new Error(`${label} has a non-directory path component: ${normalizePath(path.relative(projectRoot, current))}`);
117
+ }
118
+ continue;
119
+ }
120
+ fs.mkdirSync(current);
121
+ }
122
+ }
123
+ function writeFileNoFollow(filePath, content) {
124
+ const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
125
+ const fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | noFollow, 0o666);
126
+ try {
127
+ fs.writeFileSync(fd, content);
128
+ }
129
+ finally {
130
+ fs.closeSync(fd);
131
+ }
132
+ }
63
133
  function exists(relativePath) {
64
134
  return fs.existsSync(abs(relativePath));
65
135
  }
66
136
  function read(relativePath) {
67
- return fs.readFileSync(abs(relativePath), "utf8");
137
+ const filePath = assertNoSymlinkInProjectPath(relativePath, true, "managed read");
138
+ return fs.readFileSync(filePath, "utf8");
68
139
  }
69
140
  function write(relativePath, content) {
70
- const filePath = abs(relativePath);
71
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
72
- fs.writeFileSync(filePath, content);
141
+ const filePath = assertNoSymlinkInProjectPath(relativePath, true, "managed write");
142
+ mkdirpAbsolute(path.dirname(filePath), "managed write");
143
+ writeFileNoFollow(filePath, content);
73
144
  }
74
145
  function mkdirp(relativePath) {
75
- fs.mkdirSync(abs(relativePath), { recursive: true });
146
+ const dirPath = assertNoSymlinkInProjectPath(relativePath, true, "managed directory");
147
+ mkdirpAbsolute(dirPath, "managed directory");
76
148
  }
77
149
  function writeManaged(relativePath, content) {
78
- const previous = exists(relativePath) ? read(relativePath) : "";
150
+ const previous = exists(relativePath)
151
+ ? (assertNoSymlinkInProjectPath(relativePath, true, "managed read"), read(relativePath))
152
+ : "";
79
153
  if (previous === content)
80
154
  return "exists";
81
155
  write(relativePath, content);
@@ -86,6 +160,7 @@ function writeStarter(relativePath, content) {
86
160
  write(relativePath, content);
87
161
  return "created";
88
162
  }
163
+ assertNoSymlinkInProjectPath(relativePath, true, "managed read");
89
164
  const current = read(relativePath);
90
165
  if (current === content)
91
166
  return "exists";
@@ -111,6 +186,7 @@ function upsertMarkedSection(relativePath, startMarker, endMarker, section) {
111
186
  write(relativePath, `${section.trim()}\n`);
112
187
  return "created";
113
188
  }
189
+ assertNoSymlinkInProjectPath(relativePath, true, "managed read");
114
190
  const current = read(relativePath);
115
191
  const start = current.indexOf(startMarker);
116
192
  const end = current.indexOf(endMarker);
@@ -133,15 +209,17 @@ function upsertMarkedSection(relativePath, startMarker, endMarker, section) {
133
209
  function deleteIfGenerated(relativePath, sentinels) {
134
210
  if (!exists(relativePath))
135
211
  return "absent";
212
+ const filePath = assertNoSymlinkInProjectPath(relativePath, true, "managed delete");
136
213
  const current = read(relativePath);
137
214
  if (!sentinels.some((sentinel) => current.includes(sentinel)))
138
215
  return "manual-review";
139
- fs.unlinkSync(abs(relativePath));
216
+ fs.unlinkSync(filePath);
140
217
  return "removed";
141
218
  }
142
219
  function parseJson(relativePath, fallback) {
143
220
  if (!exists(relativePath))
144
221
  return fallback;
222
+ assertNoSymlinkInProjectPath(relativePath, true, "managed read");
145
223
  try {
146
224
  return JSON.parse(read(relativePath));
147
225
  }
@@ -191,11 +269,60 @@ function isGitRepository() {
191
269
  function makeExecutable(relativePath) {
192
270
  if (!exists(relativePath))
193
271
  return;
194
- const currentMode = fs.statSync(abs(relativePath)).mode;
195
- fs.chmodSync(abs(relativePath), currentMode | 0o755);
272
+ const filePath = assertNoSymlinkInProjectPath(relativePath, true, "managed chmod");
273
+ const currentMode = fs.statSync(filePath).mode;
274
+ fs.chmodSync(filePath, currentMode | 0o755);
275
+ }
276
+ function containedProjectFileStat(relativePath) {
277
+ const filePath = resolveProjectPath(relativePath, "project file");
278
+ let stat;
279
+ try {
280
+ stat = fs.lstatSync(filePath);
281
+ }
282
+ catch {
283
+ return null;
284
+ }
285
+ if (stat.isSymbolicLink() || !stat.isFile())
286
+ return null;
287
+ let realPath = "";
288
+ try {
289
+ realPath = fs.realpathSync(filePath);
290
+ }
291
+ catch {
292
+ return null;
293
+ }
294
+ return isInsideProject(realPath) ? stat : null;
295
+ }
296
+ function containedProjectDirectoryStat(relativePath) {
297
+ const dirPath = resolveProjectPath(relativePath, "project directory");
298
+ let stat;
299
+ try {
300
+ stat = fs.lstatSync(dirPath);
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ if (stat.isSymbolicLink() || !stat.isDirectory())
306
+ return null;
307
+ let realPath = "";
308
+ try {
309
+ realPath = fs.realpathSync(dirPath);
310
+ }
311
+ catch {
312
+ return null;
313
+ }
314
+ return isInsideProject(realPath) ? stat : null;
315
+ }
316
+ function requireContainedProjectFile(relativePath, label = "project file") {
317
+ const filePath = resolveProjectPath(relativePath, label);
318
+ const stat = containedProjectFileStat(relativePath);
319
+ if (!stat) {
320
+ throw new Error(`${label} must be a regular file inside the project root and must not be a symlink: ${relativePath}`);
321
+ }
322
+ return { absolutePath: filePath, stat };
196
323
  }
197
324
  function walkFilesUnder(relativePath, predicate, acc = []) {
198
- const dirPath = abs(relativePath);
325
+ const dirPath = assertNoSymlinkInProjectPath(relativePath, true, "managed walk");
199
326
  if (!fs.existsSync(dirPath))
200
327
  return acc;
201
328
  for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
package/docs/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Project Librarian Docs
2
+
3
+ Start from the main [README](../README.md) when evaluating the project. These pages hold the details that are useful after the first-screen overview.
4
+
5
+ | Document | Contents |
6
+ | --- | --- |
7
+ | [Usage](usage.md) | Install scopes, runner paths, generated files, migration behavior, and common agent requests. |
8
+ | [Code Evidence](code-evidence.md) | Code-evidence index, MCP server, freshness contract, scale gate, language support, and native helper policy. |
9
+ | [CLI Reference](cli-reference.md) | Complete command and option reference. |
10
+ | [Benchmark Evidence](benchmarks.md) | Published benchmark claims, limits, task-family definitions, and reproduction commands. |
11
+ | [Maintainer Guide](maintainer.md) | Local development, release readiness, trusted publishing, and benchmark operations. |
@@ -0,0 +1,64 @@
1
+ # Benchmark Evidence
2
+
3
+ These numbers are maintainer release evidence, not a blanket promise. Every value is real Codex JSONL usage and local wall-clock time (ChatGPT/Codex auth, `gpt-5.5`), measured hermetically: isolated Codex home, allowlist-only environment, clean tree, post-run fixture validation, with 3 measured runs plus 1 warmup per scenario against an `organic` control that has no Project Librarian.
4
+
5
+ The wiki-routing track and the code-graph track are measured and reported separately. A win on one never backs a claim about the other.
6
+
7
+ ## Reproduce A Release Candidate
8
+
9
+ ```bash
10
+ npm run benchmark:release:preview
11
+ npm run benchmark:release -- --allow-codex-run
12
+ ```
13
+
14
+ Measured release runs stream `[benchmark:progress]` lines to stderr for the scenario count, expected Codex exec total, current exec ordinal, phase, prompt id, exit status, elapsed time, and raw JSONL path. stdout stays reserved for the final JSON result.
15
+
16
+ Generated benchmark reports under `benchmarks/reports/llm/` are ignored by default. Maintainers should commit deliberate release baselines only when they are meant to support a public claim.
17
+
18
+ ## Wiki Track
19
+
20
+ Cost-weighted tokens, Project Librarian vs control:
21
+
22
+ | Scale | decision_lookup | aggregation | multi_session (2nd session) |
23
+ | --- | --- | --- | --- |
24
+ | Small | 14.4% less | 81.0% more | 22.0% less |
25
+ | Medium | 52.0% less | 19.0% less | 54.1% less |
26
+ | Large | 71.1% less | 29.0% less | 71.8% less |
27
+
28
+ Latest synthetic wiki-track release candidate: 2026-06-19, `gpt-5.5`, 42 scenarios, 3 measured runs plus 1 warmup each. The overall claim gate **passed**: 42/42 scenarios passed correctness, all 42 scenarios were claimable, and every corpus gate met the 3-run minimum.
29
+
30
+ The release claim is bounded to the synthetic wiki-routing track and the listed task families. It is not a claim about code-graph behavior, real repositories, every agent surface, or every question shape. Published boundaries remain visible: small `aggregation` still costs 81.0% more with the wiki, small `release_policy` costs 9.4% more in the full report, and `aggregation` stays slower at every scale even when token cost drops.
31
+
32
+ ## Code-Graph Track
33
+
34
+ Measured on two SHA-pinned open-source repositories with hand-authored answer keys and the answer-shaped MCP tools injected into the hermetic Codex home. The claim gate passed with 30/30 runs correct after two evaluator false positives were fixed and the report was re-scored from raw JSONL; recompute-from-raw is the standing audit policy.
35
+
36
+ Cost-weighted tokens, Project Librarian vs control:
37
+
38
+ | Question | excalidraw (~1.2k files) | backstage (~11.8k files) |
39
+ | --- | --- | --- |
40
+ | impact_trace | 117% more | **27.7% less** |
41
+ | workspace_graph | 106% more | 2.6% less |
42
+ | ownership_lookup | - | 99% more |
43
+
44
+ The claim is a scale crossover, and the losses are published next to the win. On the 11.8k-file repository the tool wins the expensive traversal question (`impact_trace` 27.7% fewer cost-weighted tokens, 24.5% fewer scan bytes) and breaks even on the workspace graph, but everything loses on the small repository and cheap lookups (CODEOWNERS ownership) lose at every measured scale.
45
+
46
+ ## Benchmark Names
47
+
48
+ Repositories under test:
49
+
50
+ - **excalidraw** - a real open-source whiteboard/diagramming app (~1.2k files); the small-repo data point.
51
+ - **backstage** - Spotify's open-source developer-portal platform (~11.8k files); the large-repo data point.
52
+
53
+ Question types:
54
+
55
+ - **decision_lookup** - find the latest project decision and its date from the wiki.
56
+ - **aggregation** - answer a question whose facts are scattered across several pages and must be synthesized.
57
+ - **multi_session** - a second session on the same project, measuring whether the durable wiki helps the next session, not just the first.
58
+ - **impact_trace** - trace the full set of direct and indirect importers for a changing module.
59
+ - **ownership_lookup** - resolve the owner by CODEOWNERS last-match precedence.
60
+ - **workspace_graph** - resolve the workspace/package dependency graph.
61
+
62
+ ## Maintainer Commands
63
+
64
+ Maintainer benchmark commands also live in [benchmarks/README.md](../benchmarks/README.md). They are for release evidence and public claim validation, not normal end-user setup.
@@ -0,0 +1,60 @@
1
+ # CLI Reference
2
+
3
+ Use the resolved local runner for automation or direct CLI execution:
4
+
5
+ ```bash
6
+ node .codex/skills/project-librarian/dist/init-project-wiki.js [init|update] [options]
7
+ node .codex/skills/project-librarian/dist/init-project-wiki.js install [--scope user|project] [--agents codex|claude|cursor|gemini|all]
8
+ ```
9
+
10
+ `install-skill` remains a compatibility alias for `install`.
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.
13
+
14
+ ### Important Options
15
+
16
+ | Option | Purpose |
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. |
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
+ | `--migrate`, `--adopt-existing` | Preserve an existing wiki as `wiki_legacy*`, create migration inboxes, and generate unit-map/split-plan/coverage review files. |
21
+ | `--lint` | Validate generated setup without editing files. |
22
+ | `--link-check` | Report broken wiki links, duplicate routes, orphan pages, and pages the startup router cannot reach within the depth budget. |
23
+ | `--quality-check` | Report stale, conflicting, and low-quality wiki document signals. |
24
+ | `--doctor` | Run lint, link-check, and quality-check together. |
25
+ | `--doctor --fix` | Safely refresh generated index routing before diagnostics. `--fix` is only a modifier for `--doctor`. |
26
+ | `--migration-lint` | Validate migration coverage, unit-map, split-plan, and review scaffolding separately from normal lint. |
27
+ | `--migration-quality-check` | Report migration policy/structure signals separately from normal quality-check. |
28
+ | `--migration-doctor` | Run migration-lint and migration-quality-check together. |
29
+ | `--query <terms>` | Search wiki paths, metadata, titles, and bodies; answer-first output with per-page TL;DR lines under a hard size cap. |
30
+ | `--wiki-impact <page-or-term>` | Show wiki backlinks, `decision_ref` citations, outgoing links, and router depth for matching pages. |
31
+ | `--wiki-visualize` | Write a self-contained static wiki graph visualizer to `.project-wiki/wiki-graph.html`. |
32
+ | `--wiki-visualize-out <path>` | With `--wiki-visualize`, write to a custom repository-relative path under `.project-wiki/`. |
33
+ | `--refresh-index` | Update generated auto-discovered wiki routing. |
34
+ | `--capture-inbox --title <title> --content <content> --category <category>` | Append a candidate note to the wiki inbox; category defaults to `project-candidate`. |
35
+ | `--handoff-save --goal <goal> --state <state> --next <action>` | Save generated local session handoff state under `.project-wiki/session/`. Repeat `--next`, `--decision`, `--blocked`, `--open-question`, `--verification`, `--last-success-command`, and `--last-failure-command` as needed. |
36
+ | `--handoff-show`, `--handoff-status`, `--handoff-clear` | Print, inspect, or remove generated session handoff state. Startup hooks mention the handoff when it exists but do not inject the full file by default. |
37
+ | `--handoff-promote-inbox` | Append selected generated handoff facts to `wiki/inbox/project-candidates.md` as a pending candidate. It does not write canonical, plan, or decision pages. |
38
+ | `--handoff-injection-enable`, `--handoff-injection-disable`, `--handoff-injection-status` | Opt in, opt out, or inspect the capped full handoff injection experiment. Default startup behavior remains pointer-only. |
39
+ | `--issue-draft --issue-title <title>` | Print a read-only GitHub issue body draft for problems or side effects. |
40
+ | `--issue-create --issue-title <title> --issue-body-file <path>` | Create a GitHub issue through `gh` after explicit user approval; `--issue-body-file` reuses an existing Markdown body. |
41
+ | `--glossary-init` | Create and route the optional glossary page. |
42
+ | `--prune-check` | Report active pages with stale or unresolved lifecycle signals. |
43
+ | `--prune-check --prune-check-strict` | Omit pages selected only because their `updated` date is older than today. |
44
+ | `--review-migration`, `--semantic-migrate` | Sync migration coverage and inbox statuses into migration review files. |
45
+ | `--no-git-config` | Install hook files without changing `git core.hooksPath`. |
46
+ | `--code-index` | Build the disposable code evidence index. |
47
+ | `--code-scope <path>` | With `--code-index`, restrict indexing to one or more project-relative files or directories. |
48
+ | `--code-index-out <path>` | Use a custom SQLite output path under `.project-wiki/`; applies to index and read modes. |
49
+ | `--acknowledge-small-repo` | With `--code-index`, proceed below the ~5k-file scale gate after the cost warning. |
50
+ | `--incremental`, `--code-index-incremental`, `--code-index-full` | With `--code-index`, require an incremental update or force a full rebuild. |
51
+ | `--code-parser <mode>` | With `--code-index`, select `default` or optional `tree-sitter` extraction. |
52
+ | `--code-index-health` | Inspect code evidence cache compatibility and print rebuild guidance without writing. |
53
+ | `--code-index-engine <engine>` | Override the default `auto` index engine with `typescript` or `native-rust`. |
54
+ | `--code-status`, `--code-files` | Inspect cache freshness or list indexed files. |
55
+ | `--code-report` | Print architecture and ownership summaries from the evidence index. |
56
+ | `--code-report-section <section>` | Print one section: `coverage`, `ownership`, `languages`, `parsers`, `workspaces`, `workspace-graph`, `routes`, `hotspots`, `configs`, or `edges`. |
57
+ | `--code-impact <term>` | Show file, symbol, route, import, edge, and owner impact evidence. |
58
+ | `--code-context-pack <term>` | Print a budgeted first-pass context pack with structural file, symbol, route, import, edge, and ownership evidence. |
59
+ | `--code-search-symbol <term>` | Search indexed symbols. |
60
+ | `--code-query <sql>` | Run conservative read-only SQL over the evidence index. |
@@ -0,0 +1,87 @@
1
+ # Code Evidence
2
+
3
+ Project Librarian can build a disposable SQLite index under `.project-wiki/` and serve it through read-only CLI and MCP surfaces. This is optional; the planning wiki works without it.
4
+
5
+ ## Freshness Contract
6
+
7
+ Before citing `--code-report`, `--code-impact`, `--code-context-pack`, or MCP tool output as current code-structure evidence, run:
8
+
9
+ ```bash
10
+ project-librarian --code-status
11
+ ```
12
+
13
+ or MCP `code_status`, and require `stale_files: 0`. Stale reports are pointers for rebuild, not authoritative project truth.
14
+
15
+ ## Scale Gate
16
+
17
+ The code-evidence index is measured as a scale crossover, not a universal win. Below ~5k indexable files, `--code-index` halts unless `--acknowledge-small-repo` is passed. Bootstrap skips MCP auto-registration unless an existing `.project-wiki` SQLite index shows the user already opted in.
18
+
19
+ Measured release evidence:
20
+
21
+ | Question | excalidraw (~1.2k files) | backstage (~11.8k files) |
22
+ | --- | --- | --- |
23
+ | impact_trace | 117% more | **27.7% less** |
24
+ | workspace_graph | 106% more | 2.6% less |
25
+ | ownership_lookup | - | 99% more |
26
+
27
+ The index pays off only on genuinely large repositories for expensive traversal questions. Cheap lookups can lose even at larger scale.
28
+
29
+ ## MCP Server
30
+
31
+ `project-librarian mcp` runs a hand-rolled stdio MCP server (JSON-RPC 2.0 over newline-delimited JSON, no MCP SDK dependency) that serves the existing `.project-wiki` code-evidence index read-only. The package's hard runtime dependency is `typescript`; code evidence also uses Node's `node:sqlite`, with Tree-sitter grammars remaining optional.
32
+
33
+ The server exposes answer-shaped tools:
34
+
35
+ - `code_context_pack`
36
+ - `code_impact`
37
+ - `code_ownership`
38
+ - `code_workspace_graph`
39
+ - `code_search`
40
+ - `code_status`
41
+
42
+ Responses lead with a one-line answer, follow with compact path/symbol/signature evidence, cap each reply, and prepend a warning when `code_status` reports the index is stale.
43
+
44
+ The server also exposes fixed resources:
45
+
46
+ - `project-librarian://wiki/startup`
47
+ - `project-librarian://wiki/index`
48
+ - `project-librarian://code/status`
49
+
50
+ It includes prompt templates for wiki taxonomy updates, code impact traces, maintenance improvement reviews, and retrieval quality reviews. Resource reads come from a fixed URI registry rather than arbitrary filesystem paths.
51
+
52
+ Bootstrap registers the server for Claude Code (`.mcp.json`), Cursor (`.cursor/mcp.json`), and Gemini CLI (`mcpServers` in `.gemini/settings.json`), preserving any existing servers and keys and reporting `exists` on a re-run. When the repository contains a local runner the registration uses `node <runner> mcp`; otherwise it uses the installed `project-librarian mcp` binary.
53
+
54
+ Codex registers MCP servers at the user level only (`codex mcp add`), so bootstrap does not write a project-level Codex MCP config. To use the server with Codex, run it once per machine:
55
+
56
+ ```bash
57
+ codex mcp add project-librarian -- node .codex/skills/project-librarian/dist/init-project-wiki.js mcp
58
+ ```
59
+
60
+ ## Language Support Matrix
61
+
62
+ The matrix lists languages with implemented symbol/import extraction. Other recognized extensions are inventory-only. Default mode uses `typescript-ast`, `*-light` extraction for the listed non-JS languages, config extraction, and inventory rows. `--code-parser tree-sitter` switches supported source files to `tree-sitter-*` profiles.
63
+
64
+ | Language | Extensions | Default extraction | Tree-sitter extraction | Indexed evidence |
65
+ | --- | --- | --- | --- | --- |
66
+ | TypeScript | `.ts`, `.tsx`, `.cts`, `.mts` | `typescript-ast` | `tree-sitter-typescript`, `tree-sitter-tsx` | functions, classes, methods, variables, interfaces, types, enums, imports, exports, calls, common HTTP routes |
67
+ | JavaScript | `.js`, `.jsx`, `.cjs`, `.mjs` | `typescript-ast` | `tree-sitter-javascript` | functions, classes, methods, variables, imports, exports, `require()` calls, calls, common HTTP routes |
68
+ | Python | `.py` | `python-light` | `tree-sitter-python` | functions, classes, `import`, `from ... import` |
69
+ | Go | `.go` | `go-light` | `tree-sitter-go` | functions, methods, types, consts, vars, single imports, import blocks |
70
+ | Rust | `.rs` | `rust-light` | `tree-sitter-rust` | functions, structs, enums, traits, impls, `use` imports |
71
+ | Java | `.java` | `java-light` | `tree-sitter-java` | classes, interfaces, enums, methods, imports |
72
+ | PHP | `.php` | `php-light` | `tree-sitter-php` | functions, classes, interfaces, traits, methods, namespace uses |
73
+ | Kotlin | `.kt`, `.kts` | `kotlin-light` | `tree-sitter-kotlin` | functions, classes, objects, imports |
74
+ | Swift | `.swift` | `swift-light` | `tree-sitter-swift` | functions, classes, structs, protocols, enums, imports |
75
+ | C | `.c`, `.h` | `c-light` | `tree-sitter-c` | functions, structs, enums, includes |
76
+ | C++ | `.cc`, `.cpp`, `.cxx`, `.hpp`, `.hh`, `.hxx` | `cpp-light` | `tree-sitter-cpp` | functions, classes/structs, namespaces, enums, includes/usings |
77
+ | C# | `.cs` | `csharp-light` | `tree-sitter-csharp` | classes, interfaces, structs, enums, methods, usings |
78
+
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
+
81
+ ## Native Helper Policy
82
+
83
+ 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.
84
+
85
+ Helper resolution checks `PROJECT_LIBRARIAN_NATIVE_INDEXER` first, then `dist/native/<platform>-<arch>/project-librarian-indexer` or `.exe`; Linux musl installs resolve to `dist/native/linux-<arch>-musl/`.
86
+
87
+ Public releases must not ship only one staged helper. `release:check` accepts either no packaged native helper or a complete supported matrix (`darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-arm64-musl`, `linux-x64`, `linux-x64-musl`, `win32-arm64`, `win32-x64`), checks helper executable bits, Mach-O/ELF/PE platform headers, and the packaged-helper SHA-256 manifest. The GitHub publish workflow builds the supported helper matrix, runs `npm run native:package-manifest`, verifies `npm run native:package-audit:matrix`, and publishes only after the final helper-including package passes `npm run release:check`.
@@ -0,0 +1,13 @@
1
+ # Project Librarian 문서
2
+
3
+ 프로젝트를 처음 평가할 때는 먼저 [한국어 README](../../README.ko.md)를 읽으세요. 이 디렉터리는 첫 화면에 모두 담기에는 긴 세부 정보를 한국어로 정리합니다.
4
+
5
+ 이 한국어 문서는 GitHub 저장소용입니다. npm 패키지에는 영어 상세 문서만 포함되며, `docs/ko/`는 포함하지 않습니다.
6
+
7
+ | 문서 | 내용 |
8
+ | --- | --- |
9
+ | [사용 가이드](usage.md) | 설치 범위, 실행 경로, 생성 파일, 마이그레이션 동작, 일반 에이전트 요청. |
10
+ | [코드 근거](code-evidence.md) | 코드 근거 인덱스, MCP 서버, 최신성 계약, 규모 게이트, 언어 지원, 네이티브 헬퍼 정책. |
11
+ | [CLI 참조](cli-reference.md) | 전체 명령과 옵션 참조. |
12
+ | [벤치마크 근거](benchmarks.md) | 공개 벤치마크 주장, 한계, 작업 유형, 재현 명령. |
13
+ | [관리자 가이드](maintainer.md) | 로컬 개발, 릴리스 준비, 신뢰 배포, 벤치마크 운영. |