project-librarian 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hooks.js CHANGED
@@ -39,8 +39,15 @@ exports.upsertHookConfig = upsertHookConfig;
39
39
  exports.upsertClaudeHookConfig = upsertClaudeHookConfig;
40
40
  exports.upsertGeminiHookConfig = upsertGeminiHookConfig;
41
41
  exports.upsertCursorHookConfig = upsertCursorHookConfig;
42
+ exports.codeEvidenceIndexExists = codeEvidenceIndexExists;
43
+ exports.mcpRegistrationGate = mcpRegistrationGate;
44
+ exports.upsertClaudeMcpConfig = upsertClaudeMcpConfig;
45
+ exports.upsertCursorMcpConfig = upsertCursorMcpConfig;
46
+ exports.upsertGeminiMcpConfig = upsertGeminiMcpConfig;
42
47
  const childProcess = __importStar(require("node:child_process"));
48
+ const fs = __importStar(require("node:fs"));
43
49
  const args_1 = require("./args");
50
+ const code_index_file_policy_1 = require("./code-index-file-policy");
44
51
  const workspace_1 = require("./workspace");
45
52
  function upsertGitHooksPath() {
46
53
  if (args_1.noGitConfigMode)
@@ -136,6 +143,75 @@ function upsertCursorHookConfig() {
136
143
  (0, workspace_1.write)(relativePath, next);
137
144
  return previous === next ? "exists" : previous ? "updated" : "created";
138
145
  }
146
+ const mcpServerName = "project-librarian";
147
+ // Candidate local-runner paths, project root relative, in the recorded
148
+ // local-runner-first order (mirrors validationTrailers()). The first existing
149
+ // runner wins; absent any local install we register the published binary.
150
+ const localRunnerCandidates = [
151
+ "tools/project-librarian/dist/init-project-wiki.js",
152
+ ".codex/skills/project-librarian/dist/init-project-wiki.js",
153
+ ".claude/skills/project-librarian/dist/init-project-wiki.js",
154
+ ".cursor/skills/project-librarian/dist/init-project-wiki.js",
155
+ ".gemini/skills/project-librarian/dist/init-project-wiki.js",
156
+ ];
157
+ // Deterministic command policy for the registered MCP server: if the repo
158
+ // contains a local runner, register `node <runner> mcp`; otherwise register the
159
+ // installed binary `project-librarian mcp`. This mirrors the local-runner-first
160
+ // skill policy (run the installed local copy with node, not npx) so registration
161
+ // does not depend on network/registry access. The runner path is stored project
162
+ // relative so the registration stays portable across clones.
163
+ function mcpServerEntry() {
164
+ const runner = localRunnerCandidates.find((candidate) => fs.existsSync((0, workspace_1.abs)(candidate)));
165
+ if (runner)
166
+ return { command: "node", args: [runner, "mcp"] };
167
+ return { command: mcpServerName, args: ["mcp"] };
168
+ }
169
+ // Preservation-first, idempotent merge of the project-librarian MCP server into a
170
+ // JSON config file's `mcpServers` map. Unknown keys and other servers are never
171
+ // clobbered; only `mcpServers["project-librarian"]` is set. A second run with an
172
+ // unchanged entry returns "exists". Used for Claude `.mcp.json`, Cursor
173
+ // `.cursor/mcp.json`, and (via the same map) Gemini `.gemini/settings.json`.
174
+ //
175
+ // Codex boundary: `codex mcp` only manages USER-level config (~/.codex/config.toml
176
+ // via `codex mcp add`); there is no documented project-level MCP config file under
177
+ // `.codex/`. Per the no-user-level-writes rule we do not register Codex here; the
178
+ // README documents running `codex mcp add project-librarian -- node <runner> mcp`.
179
+ function upsertMcpServersFile(relativePath) {
180
+ const config = (0, workspace_1.parseJson)(relativePath, {});
181
+ if (!config.mcpServers || typeof config.mcpServers !== "object" || Array.isArray(config.mcpServers)) {
182
+ config.mcpServers = {};
183
+ }
184
+ config.mcpServers[mcpServerName] = mcpServerEntry();
185
+ const next = `${JSON.stringify(config, null, 2)}\n`;
186
+ const previous = (0, workspace_1.exists)(relativePath) ? (0, workspace_1.read)(relativePath) : "";
187
+ if (previous === next)
188
+ return "exists";
189
+ (0, workspace_1.write)(relativePath, next);
190
+ return previous ? "updated" : "created";
191
+ }
192
+ function codeEvidenceIndexExists() {
193
+ return (0, workspace_1.walkFilesUnder)(code_index_file_policy_1.codeEvidenceDirectory, (file) => file.endsWith(".sqlite")).length > 0;
194
+ }
195
+ function mcpRegistrationGate() {
196
+ if (codeEvidenceIndexExists())
197
+ return { register: true };
198
+ const indexableFileCount = (0, code_index_file_policy_1.discoverCodeFiles)(["."]).length;
199
+ if (indexableFileCount >= code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD)
200
+ return { register: true };
201
+ return {
202
+ register: false,
203
+ reason: `skipped-small-repo ${indexableFileCount} indexable files < ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD} (code-evidence tools measured costlier than direct reads at this scale: stageR1; opt in via --code-index --acknowledge-small-repo, then re-run bootstrap)`,
204
+ };
205
+ }
206
+ function upsertClaudeMcpConfig() {
207
+ return upsertMcpServersFile(".mcp.json");
208
+ }
209
+ function upsertCursorMcpConfig() {
210
+ return upsertMcpServersFile(".cursor/mcp.json");
211
+ }
212
+ function upsertGeminiMcpConfig() {
213
+ return upsertMcpServersFile(".gemini/settings.json");
214
+ }
139
215
  function buildStartupHookScript(output) {
140
216
  return `#!/usr/bin/env node
141
217
 
@@ -183,6 +259,8 @@ const sections = files
183
259
 
184
260
  const additionalContext = [
185
261
  "[Project wiki startup review]",
262
+ "Injected context: wiki/startup.md and wiki/index.md are ALREADY included below this line.",
263
+ "Do not re-read these two files this session; route any further reads through the index.",
186
264
  "Use ./wiki as the project-planning source of truth only. Start with compact routing context; read detailed project canonical, decision, or meta files on demand.",
187
265
  "Project canonical content language is selected from user/project context; do not assume a fixed default language.",
188
266
  "When project planning content is added, changed, or removed, update ./wiki in the same turn.",
@@ -7,18 +7,19 @@ const install_skill_1 = require("./install-skill");
7
7
  const modes_1 = require("./modes");
8
8
  const migration_1 = require("./migration");
9
9
  const templates_1 = require("./templates");
10
+ const wiki_visualizer_1 = require("./wiki-visualizer");
10
11
  const workspace_1 = require("./workspace");
11
- const wiki_files_1 = require("./wiki-files");
12
12
  function codeIndex() {
13
13
  return require("./code-index");
14
14
  }
15
15
  function printUsage() {
16
16
  console.log(`Usage:
17
- project-librarian [init] [options]
18
- project-librarian install-skill [--scope user|project] [--agents codex|claude|cursor|gemini|all|both]
17
+ project-librarian [init|update] [options]
18
+ project-librarian install-skill [--scope user|project] [--agents codex|claude|cursor|gemini|all]
19
+ project-librarian mcp
19
20
 
20
21
  Options:
21
- --migrate, --adopt-existing Preserve an existing wiki as wiki_legacy and create migration inboxes.
22
+ --migrate, --adopt-existing Preserve an existing wiki as wiki_legacy and create unit-level migration map, split plan, coverage ledger, review files, and inboxes.
22
23
  --lint Validate the generated project wiki setup without editing files.
23
24
  --link-check Report broken wiki links, duplicate routes, and orphan pages.
24
25
  --quality-check Report stale, conflicting, and low-quality wiki document signals.
@@ -31,14 +32,18 @@ Options:
31
32
  --issue-draft Print a problem/side-effect GitHub issue body draft.
32
33
  --issue-body-file <path> With --issue-create, use an existing Markdown body file.
33
34
  --issue-title <title> Override the generated issue draft title.
34
- --query <terms> Search wiki paths, metadata, titles, and bodies.
35
+ --query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
36
+ --wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
37
+ --wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
38
+ --wiki-visualize-out <path> With --wiki-visualize, write under a custom .project-wiki/ path.
35
39
  --refresh-index Update the managed auto-discovered wiki index block.
36
40
  --capture-inbox Append a candidate note with --title, --content, and optional --category.
37
41
  --glossary-init Create and route the optional glossary page.
38
42
  --prune-check Report active pages with stale or unresolved signals.
39
- --review-migration Sync migration inbox statuses into migration review files.
43
+ --review-migration Sync unit coverage and compatible inbox statuses into migration review files.
40
44
  --no-git-config Install hook files without changing git core.hooksPath.
41
45
  --code-index Build the disposable .project-wiki code evidence index.
46
+ --acknowledge-small-repo With --code-index, proceed below the small-repo scale gate after its cost warning.
42
47
  --incremental With --code-index, require an existing compatible index and update only changes.
43
48
  --code-index-full With --code-index, force a full rebuild even when incremental update is possible.
44
49
  --code-parser <mode> With --code-index, use parser mode default or tree-sitter.
@@ -47,9 +52,22 @@ Options:
47
52
  --code-report Print architecture and ownership summaries from the code evidence index.
48
53
  --code-report-section <section> With --code-report, print one section: coverage, ownership, languages, parsers, workspaces, workspace-graph, routes, hotspots, configs, or edges.
49
54
  --code-impact <term> Show file, symbol, route, import, and edge impact evidence for a term.
55
+ --code-context-pack <term> Print a budgeted first-pass code context pack for a path, symbol, route, or module term.
50
56
  --code-search-symbol <term> Search indexed symbols.
57
+
58
+ Commands:
59
+ update Run the idempotent wiki/setup update path; rejects migration flags.
60
+ mcp Run the stdio MCP server exposing answer-shaped code-evidence tools (code_context_pack, code_impact, code_ownership, code_workspace_graph, code_search, code_status) over the existing .project-wiki index.
61
+
51
62
  --help Show this help.`);
52
63
  }
64
+ // console.log queues asynchronously on pipes; an immediate process.exit() discards
65
+ // anything past the first ~64KB pipe chunk (observed truncating a large
66
+ // --code-report on an 11k-file repo). Exiting from a zero-length write callback
67
+ // guarantees everything queued before it has drained.
68
+ function exitAfterStdoutDrain(code) {
69
+ process.stdout.write("", () => process.exit(code));
70
+ }
53
71
  if (args_1.helpMode) {
54
72
  printUsage();
55
73
  process.exit(0);
@@ -74,6 +92,10 @@ if (args_1.missingValueOptions.length > 0) {
74
92
  printUsage();
75
93
  process.exit(1);
76
94
  }
95
+ if (args_1.command === "update" && args_1.migrateMode) {
96
+ console.error("update cannot be combined with --migrate or --adopt-existing; use project-librarian --migrate for migration.");
97
+ process.exit(1);
98
+ }
77
99
  if (args_1.fixMode && !args_1.doctorMode) {
78
100
  console.error("--fix is only supported with --doctor.");
79
101
  process.exit(1);
@@ -86,10 +108,18 @@ if (args_1.codeReportSection && !args_1.codeReportMode) {
86
108
  console.error("--code-report-section is only supported with --code-report.");
87
109
  process.exit(1);
88
110
  }
111
+ if (args_1.wikiVisualizeOutput && !args_1.wikiVisualizeMode) {
112
+ console.error("--wiki-visualize-out is only supported with --wiki-visualize.");
113
+ process.exit(1);
114
+ }
89
115
  if (args_1.codeIndexIncrementalMode && !args_1.codeIndexMode) {
90
116
  console.error("--incremental is only supported with --code-index.");
91
117
  process.exit(1);
92
118
  }
119
+ if (args_1.acknowledgeSmallRepoMode && !args_1.codeIndexMode) {
120
+ console.error("--acknowledge-small-repo is only supported with --code-index.");
121
+ process.exit(1);
122
+ }
93
123
  if (args_1.codeIndexFullMode && !args_1.codeIndexMode) {
94
124
  console.error("--code-index-full is only supported with --code-index.");
95
125
  process.exit(1);
@@ -106,165 +136,233 @@ if (args_1.command === "install-skill") {
106
136
  (0, install_skill_1.runInstallSkillMode)();
107
137
  process.exit(0);
108
138
  }
109
- const activeCodeModes = [args_1.codeQueryMode, args_1.codeReportMode, args_1.codeStatusMode, args_1.codeFilesMode, args_1.codeImpactMode, args_1.codeSearchSymbolMode, args_1.codeIndexMode].filter(Boolean).length;
110
- if (activeCodeModes > 1) {
111
- console.error("Use one code evidence mode at a time: --code-index, --code-query, --code-report, --code-status, --code-files, --code-impact, or --code-search-symbol.");
112
- process.exit(1);
113
- }
114
- if (args_1.codeQueryMode) {
115
- codeIndex().runCodeQueryMode();
116
- process.exit(0);
117
- }
118
- if (args_1.codeReportMode) {
119
- codeIndex().runCodeReportMode();
120
- process.exit(0);
121
- }
122
- if (args_1.codeStatusMode) {
123
- codeIndex().runCodeStatusMode();
124
- process.exit(0);
125
- }
126
- if (args_1.codeFilesMode) {
127
- codeIndex().runCodeFilesMode();
128
- process.exit(0);
129
- }
130
- if (args_1.codeImpactMode) {
131
- codeIndex().runCodeImpactMode();
132
- process.exit(0);
133
- }
134
- if (args_1.codeSearchSymbolMode) {
135
- codeIndex().runCodeSearchSymbolMode();
136
- process.exit(0);
137
- }
138
- if (args_1.codeIndexMode) {
139
- codeIndex().runCodeIndexMode();
140
- process.exit(0);
141
- }
142
- if (args_1.queryTerm) {
143
- (0, modes_1.runQueryMode)();
144
- process.exit(0);
145
- }
146
- if (args_1.issueCreateMode) {
147
- (0, modes_1.runIssueCreateMode)();
148
- process.exit(0);
149
- }
150
- if (args_1.issueDraftMode) {
151
- (0, modes_1.runIssueDraftMode)();
152
- process.exit(0);
153
- }
154
- if (args_1.pruneCheckMode) {
155
- (0, modes_1.runPruneCheckMode)();
156
- process.exit(0);
157
- }
158
- if (args_1.reviewMigrationMode) {
159
- (0, migration_1.runReviewMigrationMode)();
160
- process.exit(0);
161
- }
162
- if (args_1.migrationDoctorMode) {
163
- (0, modes_1.runMigrationDoctorMode)();
164
- process.exit(0);
165
- }
166
- if (args_1.migrationQualityCheckMode) {
167
- (0, modes_1.runMigrationQualityCheckMode)();
168
- process.exit(0);
169
- }
170
- if (args_1.migrationLintMode) {
171
- (0, modes_1.runMigrationLintMode)();
172
- process.exit(0);
173
- }
174
- if (args_1.doctorMode) {
175
- (0, modes_1.runDoctorMode)(args_1.fixMode);
176
- process.exit(0);
177
- }
178
- if (args_1.linkCheckMode) {
179
- (0, modes_1.runLinkCheckMode)();
180
- process.exit(0);
181
- }
182
- if (args_1.qualityCheckMode) {
183
- (0, modes_1.runQualityCheckMode)();
184
- process.exit(0);
185
- }
186
- if (args_1.lintMode) {
187
- (0, modes_1.runLintMode)();
188
- process.exit(0);
189
- }
190
- const migrationState = args_1.migrateMode ? (0, migration_1.prepareMigrationMode)() : null;
191
- const results = [];
192
- if (migrationState)
193
- results.push(["migration prepare", migrationState.note]);
194
- (0, workspace_1.mkdirp)("wiki/canonical");
195
- (0, workspace_1.mkdirp)("wiki/decisions");
196
- (0, workspace_1.mkdirp)("wiki/inbox");
197
- (0, workspace_1.mkdirp)("wiki/meta");
198
- (0, workspace_1.mkdirp)("wiki/sources");
199
- (0, workspace_1.mkdirp)(".codex/hooks");
200
- (0, workspace_1.mkdirp)(".claude/hooks");
201
- (0, workspace_1.mkdirp)(".cursor/hooks");
202
- (0, workspace_1.mkdirp)(".cursor/rules");
203
- (0, workspace_1.mkdirp)(".gemini/hooks");
204
- (0, workspace_1.mkdirp)(".githooks");
205
- results.push(["AGENTS.md", (0, workspace_1.upsertMarkedSection)("AGENTS.md", "<!-- PROJECT-WIKI-FIRST:START -->", "<!-- PROJECT-WIKI-FIRST:END -->", templates_1.agentsSection)]);
206
- results.push(["CLAUDE.md", (0, workspace_1.upsertMarkedSection)("CLAUDE.md", "<!-- PROJECT-WIKI-CLAUDE:START -->", "<!-- PROJECT-WIKI-CLAUDE:END -->", templates_1.claudeSection)]);
207
- results.push(["GEMINI.md", (0, workspace_1.upsertMarkedSection)("GEMINI.md", "<!-- PROJECT-WIKI-GEMINI:START -->", "<!-- PROJECT-WIKI-GEMINI:END -->", templates_1.geminiSection)]);
208
- results.push([".cursor/rules/project-librarian.mdc", (0, workspace_1.writeManaged)(".cursor/rules/project-librarian.mdc", templates_1.cursorRule)]);
209
- results.push(["wiki/AGENTS.md", (0, workspace_1.upsertMarkedSection)("wiki/AGENTS.md", "<!-- PROJECT-WIKI-INTERNAL:START -->", "<!-- PROJECT-WIKI-INTERNAL:END -->", templates_1.wikiAgentsSection)]);
210
- results.push([".githooks/prepare-commit-msg", (0, workspace_1.writeManaged)(".githooks/prepare-commit-msg", hooks_1.gitPrepareCommitMsgHook)]);
211
- (0, workspace_1.makeExecutable)(".githooks/prepare-commit-msg");
212
- results.push([".githooks/wiki-commit-trailers.js", (0, workspace_1.writeManaged)(".githooks/wiki-commit-trailers.js", hooks_1.gitWikiCommitTrailersScript)]);
213
- (0, workspace_1.makeExecutable)(".githooks/wiki-commit-trailers.js");
214
- results.push(["git core.hooksPath", (0, hooks_1.upsertGitHooksPath)()]);
215
- results.push([".codex/hooks.json", (0, hooks_1.upsertHookConfig)()]);
216
- results.push([".codex/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".codex/hooks/wiki-session-start.js", hooks_1.hookScript)]);
217
- results.push([".claude/settings.json", (0, hooks_1.upsertClaudeHookConfig)()]);
218
- results.push([".claude/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".claude/hooks/wiki-session-start.js", hooks_1.hookScript)]);
219
- results.push([".cursor/hooks.json", (0, hooks_1.upsertCursorHookConfig)()]);
220
- results.push([".cursor/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".cursor/hooks/wiki-session-start.js", hooks_1.cursorHookScript)]);
221
- results.push([".gemini/settings.json", (0, hooks_1.upsertGeminiHookConfig)()]);
222
- results.push([".gemini/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".gemini/hooks/wiki-session-start.js", hooks_1.hookScript)]);
223
- results.push(["wiki/startup.md", (0, workspace_1.writeManaged)("wiki/startup.md", (0, wiki_files_1.withPreservedMarkedSections)("wiki/startup.md", templates_1.startup, [["<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->"]]))]);
224
- results.push(["wiki/index.md", (0, workspace_1.writeManaged)("wiki/index.md", (0, wiki_files_1.withPreservedMarkedSections)("wiki/index.md", templates_1.index, [
225
- ["<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->"],
226
- ["<!-- PROJECT-WIKI-GLOSSARY:START -->", "<!-- PROJECT-WIKI-GLOSSARY:END -->"],
227
- ["<!-- PROJECT-WIKI-INBOX:START -->", "<!-- PROJECT-WIKI-INBOX:END -->"],
228
- ["<!-- PROJECT-WIKI-AUTO-INDEX:START -->", "<!-- PROJECT-WIKI-AUTO-INDEX:END -->"],
229
- ]))]);
230
- results.push(["wiki/meta/operating-model.md", (0, workspace_1.writeManaged)("wiki/meta/operating-model.md", templates_1.wikiOperatingModel)]);
231
- results.push(["wiki/meta/decision-policy.md", (0, workspace_1.writeManaged)("wiki/meta/decision-policy.md", templates_1.decisionPolicy)]);
232
- results.push(["wiki/canonical/wiki-operating-model.md", (0, workspace_1.deleteIfGenerated)("wiki/canonical/wiki-operating-model.md", ["# Wiki Operating Model"])]);
233
- results.push(["wiki/canonical/decision-policy.md", (0, workspace_1.deleteIfGenerated)("wiki/canonical/decision-policy.md", ["# Decision Policy"])]);
234
- results.push(["wiki/decisions/wiki-v1-decisions.md", (0, workspace_1.deleteIfGenerated)("wiki/decisions/wiki-v1-decisions.md", ["# Wiki v1 Decisions", "# Wiki Operations v1 Decisions"])]);
235
- for (const [relativePath, content] of Object.entries(templates_1.starterFiles)) {
236
- results.push([relativePath, (0, workspace_1.writeStarter)(relativePath, content)]);
237
- }
238
- results.push(["wiki/meta/wiki-ops-v1-decisions.md", (0, workspace_1.writeManaged)("wiki/meta/wiki-ops-v1-decisions.md", templates_1.starterFiles["wiki/meta/wiki-ops-v1-decisions.md"])]);
239
- if (args_1.glossaryMode) {
240
- results.push(["wiki/canonical/glossary.md", (0, workspace_1.writeStarter)("wiki/canonical/glossary.md", templates_1.glossary)]);
241
- results.push(["wiki/index.md glossary router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-GLOSSARY:START -->", "<!-- PROJECT-WIKI-GLOSSARY:END -->", templates_1.glossaryIndexBlock)]);
242
- }
243
- if (args_1.captureInboxMode) {
244
- results.push(["wiki/inbox/project-candidates.md", (0, modes_1.appendCaptureInbox)()]);
245
- results.push(["wiki/index.md inbox router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-INBOX:START -->", "<!-- PROJECT-WIKI-INBOX:END -->", templates_1.inboxIndexBlock)]);
246
- }
247
- if (args_1.refreshIndexMode) {
248
- results.push(["wiki/index.md auto-discovered pages", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-AUTO-INDEX:START -->", "<!-- PROJECT-WIKI-AUTO-INDEX:END -->", (0, modes_1.buildRefreshIndexBlock)())]);
249
- }
250
- if (args_1.migrateMode && migrationState) {
251
- const migration = (0, migration_1.runMigrationMode)(migrationState);
252
- for (const result of migration.results)
253
- results.push(result);
254
- results.push(["migration summary", `${migration.total} files from ${migration.legacyPath || "no legacy"}`]);
255
- }
256
- const modes = [];
257
- if (args_1.migrateMode)
258
- modes.push("migration");
259
- if (args_1.glossaryMode)
260
- modes.push("glossary");
261
- if (args_1.captureInboxMode)
262
- modes.push("capture-inbox");
263
- if (args_1.refreshIndexMode)
264
- modes.push("refresh-index");
265
- if (args_1.noGitConfigMode)
266
- modes.push("no-git-config");
267
- console.log(modes.length > 0 ? `Project Librarian + ${modes.join(" + ")} complete.` : "Project Librarian complete.");
268
- for (const [relativePath, status] of results) {
269
- console.log(`${String(status).padEnd(7)} ${relativePath}`);
139
+ if (args_1.command === "mcp") {
140
+ // Hand-rolled stdio MCP server over the existing code-evidence index. Lazy
141
+ // require keeps the server (and its node:sqlite dependency) out of the normal
142
+ // bootstrap path. The server roots at process.cwd() and runs until stdin ends,
143
+ // exiting from inside runMcpServerMode; the init flow below must not run.
144
+ require("./mcp-server").runMcpServerMode();
145
+ }
146
+ else {
147
+ runInitCommand();
148
+ }
149
+ function runInitCommand() {
150
+ const activeCodeModes = [args_1.codeQueryMode, args_1.codeReportMode, args_1.codeStatusMode, args_1.codeFilesMode, args_1.codeImpactMode, args_1.codeContextPackMode, args_1.codeSearchSymbolMode, args_1.codeIndexMode].filter(Boolean).length;
151
+ if (activeCodeModes > 1) {
152
+ console.error("Use one code evidence mode at a time: --code-index, --code-query, --code-report, --code-status, --code-files, --code-impact, --code-context-pack, or --code-search-symbol.");
153
+ process.exit(1);
154
+ }
155
+ if (args_1.codeQueryMode) {
156
+ codeIndex().runCodeQueryMode();
157
+ exitAfterStdoutDrain(0);
158
+ return;
159
+ }
160
+ if (args_1.codeReportMode) {
161
+ codeIndex().runCodeReportMode();
162
+ exitAfterStdoutDrain(0);
163
+ return;
164
+ }
165
+ if (args_1.codeStatusMode) {
166
+ codeIndex().runCodeStatusMode();
167
+ exitAfterStdoutDrain(0);
168
+ return;
169
+ }
170
+ if (args_1.codeFilesMode) {
171
+ codeIndex().runCodeFilesMode();
172
+ exitAfterStdoutDrain(0);
173
+ return;
174
+ }
175
+ if (args_1.codeImpactMode) {
176
+ codeIndex().runCodeImpactMode();
177
+ exitAfterStdoutDrain(0);
178
+ return;
179
+ }
180
+ if (args_1.codeContextPackMode) {
181
+ codeIndex().runCodeContextPackMode();
182
+ exitAfterStdoutDrain(0);
183
+ return;
184
+ }
185
+ if (args_1.codeSearchSymbolMode) {
186
+ codeIndex().runCodeSearchSymbolMode();
187
+ exitAfterStdoutDrain(0);
188
+ return;
189
+ }
190
+ if (args_1.codeIndexMode) {
191
+ codeIndex().runCodeIndexMode();
192
+ process.exit(0);
193
+ }
194
+ if (args_1.wikiImpactMode) {
195
+ (0, modes_1.runWikiImpactMode)();
196
+ exitAfterStdoutDrain(0);
197
+ return;
198
+ }
199
+ if (args_1.wikiVisualizeMode) {
200
+ try {
201
+ const output = (0, wiki_visualizer_1.writeWikiVisualizer)(args_1.wikiVisualizeOutput);
202
+ console.log(`Project wiki visualizer written: ${output}`);
203
+ exitAfterStdoutDrain(0);
204
+ }
205
+ catch (error) {
206
+ console.error(error instanceof Error ? error.message : String(error));
207
+ process.exit(1);
208
+ }
209
+ return;
210
+ }
211
+ if (args_1.queryTerm) {
212
+ (0, modes_1.runQueryMode)();
213
+ exitAfterStdoutDrain(0);
214
+ return;
215
+ }
216
+ if (args_1.issueCreateMode) {
217
+ (0, modes_1.runIssueCreateMode)();
218
+ process.exit(0);
219
+ }
220
+ if (args_1.issueDraftMode) {
221
+ (0, modes_1.runIssueDraftMode)();
222
+ exitAfterStdoutDrain(0);
223
+ return;
224
+ }
225
+ if (args_1.pruneCheckMode) {
226
+ (0, modes_1.runPruneCheckMode)();
227
+ process.exit(0);
228
+ }
229
+ if (args_1.reviewMigrationMode) {
230
+ (0, migration_1.runReviewMigrationMode)();
231
+ process.exit(0);
232
+ }
233
+ if (args_1.migrationDoctorMode) {
234
+ (0, modes_1.runMigrationDoctorMode)();
235
+ process.exit(0);
236
+ }
237
+ if (args_1.migrationQualityCheckMode) {
238
+ (0, modes_1.runMigrationQualityCheckMode)();
239
+ process.exit(0);
240
+ }
241
+ if (args_1.migrationLintMode) {
242
+ (0, modes_1.runMigrationLintMode)();
243
+ process.exit(0);
244
+ }
245
+ if (args_1.doctorMode) {
246
+ (0, modes_1.runDoctorMode)(args_1.fixMode);
247
+ process.exit(0);
248
+ }
249
+ if (args_1.linkCheckMode) {
250
+ (0, modes_1.runLinkCheckMode)();
251
+ process.exit(0);
252
+ }
253
+ if (args_1.qualityCheckMode) {
254
+ (0, modes_1.runQualityCheckMode)();
255
+ process.exit(0);
256
+ }
257
+ if (args_1.lintMode) {
258
+ (0, modes_1.runLintMode)();
259
+ process.exit(0);
260
+ }
261
+ const migrationState = args_1.migrateMode ? (0, migration_1.prepareMigrationMode)() : null;
262
+ const results = [];
263
+ if (migrationState)
264
+ results.push(["migration prepare", migrationState.note]);
265
+ (0, workspace_1.mkdirp)("wiki/canonical");
266
+ (0, workspace_1.mkdirp)("wiki/decisions");
267
+ (0, workspace_1.mkdirp)("wiki/inbox");
268
+ (0, workspace_1.mkdirp)("wiki/meta");
269
+ (0, workspace_1.mkdirp)("wiki/sources");
270
+ (0, workspace_1.mkdirp)(".codex/hooks");
271
+ (0, workspace_1.mkdirp)(".claude/hooks");
272
+ (0, workspace_1.mkdirp)(".cursor/hooks");
273
+ (0, workspace_1.mkdirp)(".cursor/rules");
274
+ (0, workspace_1.mkdirp)(".gemini/hooks");
275
+ (0, workspace_1.mkdirp)(".githooks");
276
+ // B1 fallback: sync the CURRENT startup.md TL;DR into the managed AGENTS.md block
277
+ // so non-interactive `codex exec` (which does not run SessionStart hooks) still
278
+ // gets compact startup context. Routers are starter files written later in this
279
+ // flow, so on a fresh bootstrap startup.md does not exist yet; fall back to the
280
+ // template TL;DR that bootstrap is about to write. A missing "## TL;DR" section in
281
+ // an existing startup.md fails loudly inside extractStartupTldr (no fallback).
282
+ const startupForSync = (0, workspace_1.exists)("wiki/startup.md") ? (0, workspace_1.read)("wiki/startup.md") : templates_1.startup;
283
+ const startupTldrForAgents = (0, templates_1.extractStartupTldr)(startupForSync);
284
+ results.push(["AGENTS.md", (0, workspace_1.upsertMarkedSection)("AGENTS.md", "<!-- PROJECT-WIKI-FIRST:START -->", "<!-- PROJECT-WIKI-FIRST:END -->", (0, templates_1.agentsSection)(startupTldrForAgents))]);
285
+ results.push(["CLAUDE.md", (0, workspace_1.upsertMarkedSection)("CLAUDE.md", "<!-- PROJECT-WIKI-CLAUDE:START -->", "<!-- PROJECT-WIKI-CLAUDE:END -->", templates_1.claudeSection)]);
286
+ results.push(["GEMINI.md", (0, workspace_1.upsertMarkedSection)("GEMINI.md", "<!-- PROJECT-WIKI-GEMINI:START -->", "<!-- PROJECT-WIKI-GEMINI:END -->", templates_1.geminiSection)]);
287
+ results.push([".cursor/rules/project-librarian.mdc", (0, workspace_1.writeManaged)(".cursor/rules/project-librarian.mdc", templates_1.cursorRule)]);
288
+ results.push(["wiki/AGENTS.md", (0, workspace_1.upsertMarkedSection)("wiki/AGENTS.md", "<!-- PROJECT-WIKI-INTERNAL:START -->", "<!-- PROJECT-WIKI-INTERNAL:END -->", templates_1.wikiAgentsSection)]);
289
+ results.push([".githooks/prepare-commit-msg", (0, workspace_1.writeManaged)(".githooks/prepare-commit-msg", hooks_1.gitPrepareCommitMsgHook)]);
290
+ (0, workspace_1.makeExecutable)(".githooks/prepare-commit-msg");
291
+ results.push([".githooks/wiki-commit-trailers.js", (0, workspace_1.writeManaged)(".githooks/wiki-commit-trailers.js", hooks_1.gitWikiCommitTrailersScript)]);
292
+ (0, workspace_1.makeExecutable)(".githooks/wiki-commit-trailers.js");
293
+ results.push(["git core.hooksPath", (0, hooks_1.upsertGitHooksPath)()]);
294
+ results.push([".codex/hooks.json", (0, hooks_1.upsertHookConfig)()]);
295
+ results.push([".codex/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".codex/hooks/wiki-session-start.js", hooks_1.hookScript)]);
296
+ results.push([".claude/settings.json", (0, hooks_1.upsertClaudeHookConfig)()]);
297
+ results.push([".claude/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".claude/hooks/wiki-session-start.js", hooks_1.hookScript)]);
298
+ results.push([".cursor/hooks.json", (0, hooks_1.upsertCursorHookConfig)()]);
299
+ results.push([".cursor/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".cursor/hooks/wiki-session-start.js", hooks_1.cursorHookScript)]);
300
+ results.push([".gemini/settings.json", (0, hooks_1.upsertGeminiHookConfig)()]);
301
+ results.push([".gemini/hooks/wiki-session-start.js", (0, workspace_1.writeManaged)(".gemini/hooks/wiki-session-start.js", hooks_1.hookScript)]);
302
+ // Bootstrap-managed MCP registration (preservation-first, idempotent). Claude
303
+ // Code reads `.mcp.json`, Cursor reads `.cursor/mcp.json`, and Gemini reads
304
+ // `mcpServers` inside `.gemini/settings.json`. Codex only supports user-level MCP
305
+ // config (`codex mcp add` -> ~/.codex/config.toml), so it is intentionally not
306
+ // registered at project level; the README documents the manual user-level step.
307
+ // Registration is scale-gated (2026-06-12 decision): below the measured
308
+ // file-count threshold with no existing .project-wiki index, the rows report the
309
+ // skip reason instead of writing config; an existing index registers regardless.
310
+ const mcpGate = (0, hooks_1.mcpRegistrationGate)();
311
+ if (mcpGate.register) {
312
+ results.push([".mcp.json", (0, hooks_1.upsertClaudeMcpConfig)()]);
313
+ results.push([".cursor/mcp.json", (0, hooks_1.upsertCursorMcpConfig)()]);
314
+ results.push([".gemini/settings.json mcpServers", (0, hooks_1.upsertGeminiMcpConfig)()]);
315
+ }
316
+ else {
317
+ results.push([".mcp.json", mcpGate.reason]);
318
+ results.push([".cursor/mcp.json", mcpGate.reason]);
319
+ results.push([".gemini/settings.json mcpServers", mcpGate.reason]);
320
+ }
321
+ // Routers accumulate user-maintained project state after bootstrap, so they are
322
+ // starter files: templates are written only when the file is absent, never rebuilt.
323
+ results.push(["wiki/startup.md", (0, workspace_1.writeStarter)("wiki/startup.md", templates_1.startup)]);
324
+ results.push(["wiki/index.md", (0, workspace_1.writeStarter)("wiki/index.md", templates_1.index)]);
325
+ results.push(["wiki/meta/operating-model.md", (0, workspace_1.writeManaged)("wiki/meta/operating-model.md", templates_1.wikiOperatingModel)]);
326
+ results.push(["wiki/meta/decision-policy.md", (0, workspace_1.writeManaged)("wiki/meta/decision-policy.md", templates_1.decisionPolicy)]);
327
+ results.push(["wiki/canonical/wiki-operating-model.md", (0, workspace_1.deleteIfGenerated)("wiki/canonical/wiki-operating-model.md", ["# Wiki Operating Model"])]);
328
+ results.push(["wiki/canonical/decision-policy.md", (0, workspace_1.deleteIfGenerated)("wiki/canonical/decision-policy.md", ["# Decision Policy"])]);
329
+ results.push(["wiki/decisions/wiki-v1-decisions.md", (0, workspace_1.deleteIfGenerated)("wiki/decisions/wiki-v1-decisions.md", ["# Wiki v1 Decisions", "# Wiki Operations v1 Decisions"])]);
330
+ for (const [relativePath, content] of Object.entries(templates_1.starterFiles)) {
331
+ if (!templates_1.defaultStarterFilePaths.has(relativePath))
332
+ continue;
333
+ results.push([relativePath, (0, workspace_1.writeStarter)(relativePath, content)]);
334
+ }
335
+ results.push(["wiki/meta/wiki-ops-v1-decisions.md", (0, workspace_1.writeManaged)("wiki/meta/wiki-ops-v1-decisions.md", templates_1.starterFiles["wiki/meta/wiki-ops-v1-decisions.md"])]);
336
+ if (args_1.glossaryMode) {
337
+ results.push(["wiki/canonical/glossary.md", (0, workspace_1.writeStarter)("wiki/canonical/glossary.md", templates_1.glossary)]);
338
+ results.push(["wiki/index.md glossary router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-GLOSSARY:START -->", "<!-- PROJECT-WIKI-GLOSSARY:END -->", templates_1.glossaryIndexBlock)]);
339
+ }
340
+ if (args_1.captureInboxMode) {
341
+ results.push(["wiki/inbox/project-candidates.md", (0, modes_1.appendCaptureInbox)()]);
342
+ results.push(["wiki/index.md inbox router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-INBOX:START -->", "<!-- PROJECT-WIKI-INBOX:END -->", templates_1.inboxIndexBlock)]);
343
+ }
344
+ if (args_1.refreshIndexMode) {
345
+ results.push(["wiki/index.md auto-discovered pages", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-AUTO-INDEX:START -->", "<!-- PROJECT-WIKI-AUTO-INDEX:END -->", (0, modes_1.buildRefreshIndexBlock)())]);
346
+ }
347
+ if (args_1.migrateMode && migrationState) {
348
+ const migration = (0, migration_1.runMigrationMode)(migrationState);
349
+ for (const result of migration.results)
350
+ results.push(result);
351
+ results.push(["migration summary", `${migration.total} files from ${migration.legacyPath || "no legacy"}`]);
352
+ }
353
+ const modes = [];
354
+ if (args_1.migrateMode)
355
+ modes.push("migration");
356
+ if (args_1.glossaryMode)
357
+ modes.push("glossary");
358
+ if (args_1.captureInboxMode)
359
+ modes.push("capture-inbox");
360
+ if (args_1.refreshIndexMode)
361
+ modes.push("refresh-index");
362
+ if (args_1.noGitConfigMode)
363
+ modes.push("no-git-config");
364
+ console.log(modes.length > 0 ? `Project Librarian + ${modes.join(" + ")} complete.` : "Project Librarian complete.");
365
+ for (const [relativePath, status] of results) {
366
+ console.log(`${String(status).padEnd(7)} ${relativePath}`);
367
+ }
270
368
  }