@lannguyensi/harness 0.41.0 → 0.42.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +9 -0
  3. package/dist/cli/adopt/derive.d.ts +15 -0
  4. package/dist/cli/adopt/derive.js +34 -7
  5. package/dist/cli/adopt/derive.js.map +1 -1
  6. package/dist/cli/adopt/index.d.ts +30 -0
  7. package/dist/cli/adopt/index.js +23 -3
  8. package/dist/cli/adopt/index.js.map +1 -1
  9. package/dist/cli/apply/generate-settings.d.ts +14 -1
  10. package/dist/cli/apply/generate-settings.js +7 -3
  11. package/dist/cli/apply/generate-settings.js.map +1 -1
  12. package/dist/cli/approve/stdin-report.js +2 -0
  13. package/dist/cli/approve/stdin-report.js.map +1 -1
  14. package/dist/cli/doctor/claude-mcp.d.ts +50 -0
  15. package/dist/cli/doctor/claude-mcp.js +170 -0
  16. package/dist/cli/doctor/claude-mcp.js.map +1 -0
  17. package/dist/cli/doctor/format.js +37 -0
  18. package/dist/cli/doctor/format.js.map +1 -1
  19. package/dist/cli/doctor/index.d.ts +10 -0
  20. package/dist/cli/doctor/index.js +24 -0
  21. package/dist/cli/doctor/index.js.map +1 -1
  22. package/dist/cli/doctor/npm-bin-path.js +42 -0
  23. package/dist/cli/doctor/npm-bin-path.js.map +1 -1
  24. package/dist/cli/doctor/types.d.ts +11 -0
  25. package/dist/cli/doctor/types.js.map +1 -1
  26. package/dist/cli/index.js +253 -5
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/init/agent-tasks-auth.js +32 -0
  29. package/dist/cli/init/agent-tasks-auth.js.map +1 -1
  30. package/dist/cli/init/composer.js +3 -9
  31. package/dist/cli/init/composer.js.map +1 -1
  32. package/dist/cli/init/dependencies.js +21 -0
  33. package/dist/cli/init/dependencies.js.map +1 -1
  34. package/dist/cli/init/detect.d.ts +20 -3
  35. package/dist/cli/init/detect.js +34 -36
  36. package/dist/cli/init/detect.js.map +1 -1
  37. package/dist/cli/init/interactive.d.ts +33 -0
  38. package/dist/cli/init/interactive.js +399 -5
  39. package/dist/cli/init/interactive.js.map +1 -1
  40. package/dist/cli/init/profiles.d.ts +1 -1
  41. package/dist/cli/init/profiles.js +1 -1
  42. package/dist/cli/init/templates.d.ts +1 -1
  43. package/dist/cli/init/templates.js +64 -5
  44. package/dist/cli/init/templates.js.map +1 -1
  45. package/dist/cli/main.js +10 -0
  46. package/dist/cli/main.js.map +1 -1
  47. package/dist/cli/pack/hook-post-merge-gate-record.d.ts +30 -0
  48. package/dist/cli/pack/hook-post-merge-gate-record.js +153 -0
  49. package/dist/cli/pack/hook-post-merge-gate-record.js.map +1 -0
  50. package/dist/cli/pack/hook-post-merge-gate.d.ts +28 -0
  51. package/dist/cli/pack/hook-post-merge-gate.js +244 -0
  52. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -0
  53. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +148 -2
  54. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +1 -1
  55. package/dist/cli/pack/understanding-report-schema-hint.js +1 -1
  56. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  57. package/dist/cli/record/index.d.ts +64 -0
  58. package/dist/cli/record/index.js +211 -0
  59. package/dist/cli/record/index.js.map +1 -0
  60. package/dist/cli/session-start/index.d.ts +7 -0
  61. package/dist/cli/session-start/index.js +133 -38
  62. package/dist/cli/session-start/index.js.map +1 -1
  63. package/dist/cli/session-start/toolchain-parity.d.ts +149 -0
  64. package/dist/cli/session-start/toolchain-parity.js +618 -0
  65. package/dist/cli/session-start/toolchain-parity.js.map +1 -0
  66. package/dist/cli/uninstall/index.d.ts +73 -1
  67. package/dist/cli/uninstall/index.js +134 -2
  68. package/dist/cli/uninstall/index.js.map +1 -1
  69. package/dist/io/claude-mcp.d.ts +258 -0
  70. package/dist/io/claude-mcp.js +544 -0
  71. package/dist/io/claude-mcp.js.map +1 -0
  72. package/dist/policies/ledger-client.d.ts +34 -1
  73. package/dist/policies/ledger-client.js +21 -16
  74. package/dist/policies/ledger-client.js.map +1 -1
  75. package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +195 -0
  76. package/dist/policy-packs/builtin/post-merge-gate-runtime.js +290 -0
  77. package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -0
  78. package/dist/policy-packs/builtin/post-merge-gate.d.ts +56 -0
  79. package/dist/policy-packs/builtin/post-merge-gate.js +291 -0
  80. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -0
  81. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +28 -13
  82. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  83. package/dist/policy-packs/registry.d.ts +1 -1
  84. package/dist/policy-packs/registry.js +12 -0
  85. package/dist/policy-packs/registry.js.map +1 -1
  86. package/dist/runtime/git-context.d.ts +50 -0
  87. package/dist/runtime/git-context.js +108 -1
  88. package/dist/runtime/git-context.js.map +1 -1
  89. package/dist/runtime/hermetic-spawn-guard.d.ts +25 -0
  90. package/dist/runtime/hermetic-spawn-guard.js +136 -0
  91. package/dist/runtime/hermetic-spawn-guard.js.map +1 -0
  92. package/dist/runtime/ledger-writer.d.ts +27 -0
  93. package/dist/runtime/ledger-writer.js +46 -0
  94. package/dist/runtime/ledger-writer.js.map +1 -0
  95. package/dist/runtime/read-only-bash.js +118 -0
  96. package/dist/runtime/read-only-bash.js.map +1 -1
  97. package/dist/schema/index.d.ts +41 -0
  98. package/dist/schema/index.js +5 -0
  99. package/dist/schema/index.js.map +1 -1
  100. package/dist/schema/toolchain-parity.d.ts +18 -0
  101. package/dist/schema/toolchain-parity.js +35 -0
  102. package/dist/schema/toolchain-parity.js.map +1 -0
  103. package/package.json +4 -2
@@ -0,0 +1,544 @@
1
+ // All interaction with Claude Code's user-scope MCP registration lives here.
2
+ // harness never writes `~/.claude.json` (or `$CLAUDE_CONFIG_DIR/.claude.json`)
3
+ // itself — mutations go exclusively through the `claude mcp` CLI, which is
4
+ // the only thing Claude Code actually reads at runtime for user-scope
5
+ // servers (unlike the `mcpServers` block in `~/.claude/settings.json`,
6
+ // which Claude Code does not consume — see
7
+ // .ai/runs/2026-07-18-init-mcp-wiring-claude-code/01-plan.md).
8
+ //
9
+ // Three layers, task T-001 scope only (no init/doctor wiring — that's
10
+ // T-002/T-003):
11
+ // 1. A thin, injectable-exec wrapper around `claude mcp get/add-json/
12
+ // remove/list`, ENOENT-tolerant and timeout-bounded, that turns raw
13
+ // exit codes/stderr into structured result objects (never throws for
14
+ // "CLI missing" or "timed out" — those are just another outcome).
15
+ // 2. `ensureMcpServers`: reconciles a desired per-server state against
16
+ // the live registry (add/replace-on-drift/no-op), and — opt-in via
17
+ // `opts.gc` (task 363a6de0, MCP-removal GC) — also `claude mcp
18
+ // remove`s any caller-designated "owned" name that's registered but
19
+ // no longer in `desired` (a manifest entry the operator removed or
20
+ // disabled). Ownership is entirely the caller's call: this module
21
+ // never guesses which names harness owns, so a foreign/manually
22
+ // registered server is never touched, no matter how it drifts from
23
+ // `desired`. The ONLY read of the registry file this module performs
24
+ // serves both the drift comparison and the GC candidate scan, and it
25
+ // reads strictly the top-level `mcpServers` key — never
26
+ // `projects.<path>.mcpServers` (that's project-local Claude Code
27
+ // state; `uninstall`'s `probeProjectLocalClaudeJson` already surfaces
28
+ // it separately, read-only). All writes still go through the CLI
29
+ // wrapper above.
30
+ // 3. `stripOwnedMcpServers`: a pure function that removes owned names
31
+ // from a parsed settings.json object (the dead `mcpServers` block
32
+ // the old write path left behind). No file I/O — callers own reading
33
+ // and writing the file.
34
+ //
35
+ // Verified CLI contract (empirical probe, 2026-07-18, disposable
36
+ // CLAUDE_CONFIG_DIR):
37
+ // `claude mcp add-json --scope user <name> <json>`
38
+ // new name → exit 0, stdout "Added stdio MCP server <name> to user config"
39
+ // name exists → exit 1, stderr "MCP server <name> already exists in user config" (no overwrite, no prompt)
40
+ // malformed → exit 1, stderr "Invalid configuration"
41
+ // `claude mcp remove --scope user <name>`
42
+ // present → exit 0
43
+ // absent → exit 1, stderr 'No MCP server named "<name>" in user scope'
44
+ // `claude mcp get <name>`
45
+ // present → exit 0; absent → exit 1
46
+ // `claude mcp list`
47
+ // exit 0 even with dead servers. Line format:
48
+ // `<name>: <command> <args> - <marker> <statusText>` with marker one
49
+ // of "✔ Connected" | "✘ Failed to connect" | "! Needs authentication".
50
+ // Respects CLAUDE_CONFIG_DIR: the user-scope registry then lives at
51
+ // $CLAUDE_CONFIG_DIR/.claude.json instead of ~/.claude.json.
52
+ import { spawn } from "node:child_process";
53
+ import * as fs from "node:fs";
54
+ import * as os from "node:os";
55
+ import * as path from "node:path";
56
+ import { assertNoRealSpawnInTests } from "../runtime/hermetic-spawn-guard.js";
57
+ export const DEFAULT_TIMEOUT_MS = 30_000;
58
+ /**
59
+ * Hermetic guard (task 54739002 primitive, applied here per task
60
+ * 0d80e969): asserts BEFORE touching `child_process` that we are not
61
+ * running under vitest without a test having injected `opts.exec`. This
62
+ * is the ONLY spawn point behind every `claude mcp <verb>` call in this
63
+ * module (add-json/remove/get/list all default to `opts.exec ??
64
+ * realClaudeMcpExec`). An accidental real spawn here talks to the
65
+ * OPERATOR'S REAL Claude Code user-scope MCP registry (`~/.claude.json`
66
+ * or `$CLAUDE_CONFIG_DIR/.claude.json`) — `list`/`get` only read it, but
67
+ * `add-json`/`remove` actually mutate it. See
68
+ * src/runtime/hermetic-spawn-guard.ts for why and the env signal used.
69
+ *
70
+ * `realClaudeMcpExec` has no try/catch around this call, so the thrown
71
+ * `HermeticSpawnViolationError` propagates directly to whichever
72
+ * exported wrapper (addJsonMcpServer/removeMcpServer/getMcpServer/
73
+ * listMcpServers) called it, and from there to every one of THEIR
74
+ * callers (including `ensureMcpServers`). Local "no try/catch here" is
75
+ * not the actual guarantee, though: the OW guard
76
+ * (src/cli/init/interactive.ts) proved a local absence-of-catch
77
+ * argument isn't enough on its own — that violation had to survive a
78
+ * catch further up the call chain. The backstops verified for THIS
79
+ * module's call sites:
80
+ * - init's wire-now path: `wireRuntime`'s own catch
81
+ * (src/cli/init/interactive.ts, the "Failed to wire ..." handler)
82
+ * re-throws a `HermeticSpawnViolationError` before its normal
83
+ * degrade-to-warning-and-retry handling, and runInteractive's outer
84
+ * catch (src/cli/init/interactive.ts, the top-level handler that
85
+ * otherwise treats a caught error as either an `isAbortError`
86
+ * Ctrl-C or a rethrow) re-throws it again past every remaining
87
+ * intermediate handler.
88
+ * - doctor's `buildClaudeMcpRegistration` (src/cli/doctor/claude-mcp.ts)
89
+ * and `doctor()` (src/cli/doctor/index.ts) have no try/catch at all
90
+ * around the `listMcpServers` call, so it propagates unmodified.
91
+ * - uninstall's `removeRegisteredMcpServers` (src/cli/uninstall/
92
+ * index.ts) and `uninstall()` likewise have no try/catch around the
93
+ * `removeMcpServer` call.
94
+ */
95
+ function realClaudeMcpExec(args, timeoutMs) {
96
+ // Every caller in this module passes >=2 elements (["mcp", <verb>, ...]),
97
+ // so the fallback below is unreached today — kept anyway so a future
98
+ // call site that passes fewer never degrades to a blank/truncated
99
+ // "claude " label in the guard's own error message.
100
+ const verb = args.slice(0, 2).join(" ") || "(no args)";
101
+ assertNoRealSpawnInTests(`claude ${verb}`, "Inject a fake `exec` (ClaudeMcpExec) via opts.exec instead of exercising the real spawn path.");
102
+ return new Promise((resolve) => {
103
+ let child;
104
+ try {
105
+ child = spawn("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
106
+ }
107
+ catch (err) {
108
+ const e = err;
109
+ resolve({
110
+ code: 127,
111
+ stdout: "",
112
+ stderr: `spawn failed: ${e.message}`,
113
+ enoent: e.code === "ENOENT",
114
+ timedOut: false,
115
+ });
116
+ return;
117
+ }
118
+ let stdout = "";
119
+ let stderr = "";
120
+ let settled = false;
121
+ const timer = setTimeout(() => {
122
+ if (settled)
123
+ return;
124
+ settled = true;
125
+ child.kill("SIGTERM");
126
+ resolve({ code: -1, stdout, stderr, enoent: false, timedOut: true });
127
+ }, timeoutMs);
128
+ child.stdout?.on("data", (c) => {
129
+ stdout += c.toString("utf8");
130
+ });
131
+ child.stderr?.on("data", (c) => {
132
+ stderr += c.toString("utf8");
133
+ });
134
+ child.on("error", (err) => {
135
+ if (settled)
136
+ return;
137
+ settled = true;
138
+ clearTimeout(timer);
139
+ const e = err;
140
+ resolve({ code: 127, stdout, stderr, enoent: e.code === "ENOENT", timedOut: false });
141
+ });
142
+ child.on("exit", (code) => {
143
+ if (settled)
144
+ return;
145
+ settled = true;
146
+ clearTimeout(timer);
147
+ resolve({ code: code ?? 1, stdout, stderr, enoent: false, timedOut: false });
148
+ });
149
+ });
150
+ }
151
+ // Omit empty args/env so the JSON handed to `add-json` stays minimal
152
+ // (mirrors buildMcpServers in generate-settings.ts).
153
+ function compactSpec(spec) {
154
+ const out = { command: spec.command };
155
+ if (spec.args && spec.args.length > 0)
156
+ out.args = spec.args;
157
+ if (spec.env && Object.keys(spec.env).length > 0)
158
+ out.env = spec.env;
159
+ return out;
160
+ }
161
+ /**
162
+ * Wrap a value as a single POSIX shell single-quoted token, escaping any
163
+ * embedded single quote as `'\''`. Used to build the copy-pasteable
164
+ * `claude mcp add-json ... <json>` fallback commands harness prints when the
165
+ * CLI is missing or a server is unregistered, so the operator can paste them
166
+ * verbatim even when a value (e.g. a home path containing an apostrophe, as
167
+ * projected into grounding-mcp's EVIDENCE_LEDGER_DB) has a single quote.
168
+ * Harness never executes these strings itself.
169
+ */
170
+ export function posixSingleQuote(value) {
171
+ return `'${value.replace(/'/g, "'\\''")}'`;
172
+ }
173
+ export async function addJsonMcpServer(name, spec, opts = {}) {
174
+ const exec = opts.exec ?? realClaudeMcpExec;
175
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
176
+ const json = JSON.stringify(compactSpec(spec));
177
+ const r = await exec(["mcp", "add-json", "--scope", "user", name, json], timeoutMs);
178
+ if (r.timedOut) {
179
+ return { status: "timeout", message: `claude mcp add-json timed out after ${timeoutMs}ms`, code: r.code };
180
+ }
181
+ if (r.enoent) {
182
+ return { status: "cli-missing", message: "claude CLI not found on PATH", code: r.code };
183
+ }
184
+ if (r.code === 0) {
185
+ return { status: "added", message: r.stdout.trim(), code: r.code };
186
+ }
187
+ const stderr = r.stderr.trim();
188
+ if (/already exists/i.test(stderr)) {
189
+ return { status: "already-exists", message: stderr, code: r.code };
190
+ }
191
+ if (/invalid configuration/i.test(stderr)) {
192
+ return { status: "invalid-config", message: stderr, code: r.code };
193
+ }
194
+ return { status: "error", message: stderr || `claude mcp add-json exited ${r.code}`, code: r.code };
195
+ }
196
+ export async function removeMcpServer(name, opts = {}) {
197
+ const exec = opts.exec ?? realClaudeMcpExec;
198
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
199
+ const r = await exec(["mcp", "remove", "--scope", "user", name], timeoutMs);
200
+ if (r.timedOut) {
201
+ return { status: "timeout", message: `claude mcp remove timed out after ${timeoutMs}ms`, code: r.code };
202
+ }
203
+ if (r.enoent) {
204
+ return { status: "cli-missing", message: "claude CLI not found on PATH", code: r.code };
205
+ }
206
+ if (r.code === 0) {
207
+ return { status: "removed", message: r.stdout.trim(), code: r.code };
208
+ }
209
+ const stderr = r.stderr.trim();
210
+ if (/No MCP server named/i.test(stderr)) {
211
+ return { status: "not-found", message: stderr, code: r.code };
212
+ }
213
+ return { status: "error", message: stderr || `claude mcp remove exited ${r.code}`, code: r.code };
214
+ }
215
+ /**
216
+ * One `claude mcp remove --scope user <name>` line per name, sorted.
217
+ * Sibling of `manualAddJsonLines` in `cli/init/interactive.ts` — the
218
+ * copy-pasteable fallback GC (task 363a6de0) prints when it can't reach
219
+ * the `claude` CLI (or hits some other non-"removed"/"not-found" outcome)
220
+ * to deregister a stale, harness-owned server itself. Uses
221
+ * {@link posixSingleQuote} on the name for the same reason
222
+ * `manualAddJsonLines` quotes its JSON payload: a server name is an
223
+ * ordinary token today, but quoting it costs nothing and keeps the line
224
+ * safe to paste verbatim regardless. Harness never executes these strings
225
+ * itself.
226
+ */
227
+ export function manualRemoveLines(names) {
228
+ return [...names].sort().map((name) => `claude mcp remove --scope user ${posixSingleQuote(name)}`);
229
+ }
230
+ export async function getMcpServer(name, opts = {}) {
231
+ const exec = opts.exec ?? realClaudeMcpExec;
232
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
233
+ const r = await exec(["mcp", "get", name], timeoutMs);
234
+ if (r.timedOut) {
235
+ return { status: "timeout", raw: "", code: r.code };
236
+ }
237
+ if (r.enoent) {
238
+ return { status: "cli-missing", raw: "", code: r.code };
239
+ }
240
+ if (r.code === 0) {
241
+ return { status: "found", raw: r.stdout, code: r.code };
242
+ }
243
+ // Per the verified contract `get` only ever exits 0 (found) or 1
244
+ // (absent); any other non-zero, non-enoent exit still reads as
245
+ // "not-found" rather than inventing an "error" bucket the contract
246
+ // never demonstrated.
247
+ return { status: "not-found", raw: r.stderr.trim(), code: r.code };
248
+ }
249
+ function markerToStatus(marker) {
250
+ switch (marker) {
251
+ case "✔":
252
+ return "connected";
253
+ case "✘":
254
+ return "failed";
255
+ case "!":
256
+ return "needs-authentication";
257
+ default:
258
+ return "unknown";
259
+ }
260
+ }
261
+ // `<name>: <command> <args...> - <marker> <statusText>`. Greedy `.+` for
262
+ // the command segment finds the RIGHTMOST " - <marker> " separator,
263
+ // which is what we want since the command/args portion (e.g. a URL like
264
+ // `https://...`) may itself contain " - ". The name segment stops at the
265
+ // first ": " (not just ":") so URLs like "https://..." — colon with no
266
+ // following space — never get mistaken for the name/command boundary.
267
+ const LIST_LINE_RE = /^(.+?): (.+) - (✔|✘|!) (.+)$/;
268
+ /**
269
+ * Parse `claude mcp list` stdout. Lines that don't match the expected
270
+ * shape (blank lines, any future banner/preamble) are skipped rather
271
+ * than throwing — the exit code alone tells us the command succeeded.
272
+ *
273
+ * Caveat (same one `buildMcpServers` in generate-settings.ts documents
274
+ * for the inverse direction): splitting the command+args segment on
275
+ * whitespace mis-splits a command path with embedded spaces. Foreign
276
+ * entries whose "command" is a URL (e.g. `claude.ai Gmail: https://... -
277
+ * ! Needs authentication`) still parse correctly since URLs have no
278
+ * embedded whitespace.
279
+ */
280
+ export function parseClaudeMcpListOutput(stdout) {
281
+ const entries = [];
282
+ for (const rawLine of stdout.split("\n")) {
283
+ const line = rawLine.trim();
284
+ if (line === "")
285
+ continue;
286
+ const m = LIST_LINE_RE.exec(line);
287
+ if (!m)
288
+ continue;
289
+ const name = (m[1] ?? "").trim();
290
+ const commandLine = (m[2] ?? "").trim();
291
+ const marker = m[3] ?? "";
292
+ const statusText = (m[4] ?? "").trim();
293
+ const tokens = commandLine.split(/\s+/).filter((t) => t.length > 0);
294
+ entries.push({
295
+ name,
296
+ command: tokens[0] ?? "",
297
+ args: tokens.slice(1),
298
+ status: markerToStatus(marker),
299
+ statusText,
300
+ });
301
+ }
302
+ return entries;
303
+ }
304
+ export async function listMcpServers(opts = {}) {
305
+ const exec = opts.exec ?? realClaudeMcpExec;
306
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
307
+ const r = await exec(["mcp", "list"], timeoutMs);
308
+ if (r.timedOut) {
309
+ return { status: "timeout", servers: [], message: `claude mcp list timed out after ${timeoutMs}ms` };
310
+ }
311
+ if (r.enoent) {
312
+ return { status: "cli-missing", servers: [], message: "claude CLI not found on PATH" };
313
+ }
314
+ if (r.code !== 0) {
315
+ return { status: "error", servers: [], message: r.stderr.trim() || `claude mcp list exited ${r.code}` };
316
+ }
317
+ return { status: "ok", servers: parseClaudeMcpListOutput(r.stdout) };
318
+ }
319
+ // ---------------------------------------------------------------------
320
+ // Layer 2: ensure routine (desired-state reconciliation + opt-in GC)
321
+ // ---------------------------------------------------------------------
322
+ function isRecord(x) {
323
+ return typeof x === "object" && x !== null && !Array.isArray(x);
324
+ }
325
+ /**
326
+ * Resolve the path to the user-scope registry file the `claude` CLI
327
+ * itself reads/writes — the same precedence the CLI applies: an explicit
328
+ * `CLAUDE_CONFIG_DIR` wins (`$CLAUDE_CONFIG_DIR/.claude.json`); otherwise
329
+ * it's `~/.claude.json`, derived here as `path.join(path.dirname(homeDir),
330
+ * ".claude.json")` to match the existing resolution in
331
+ * `src/cli/uninstall/index.ts` (`probeProjectLocalClaudeJson`).
332
+ */
333
+ export function resolveClaudeUserRegistryPath(opts = {}) {
334
+ const env = opts.env ?? process.env;
335
+ const configDir = env["CLAUDE_CONFIG_DIR"];
336
+ if (typeof configDir === "string" && configDir.length > 0) {
337
+ return path.join(configDir, ".claude.json");
338
+ }
339
+ const homeDir = opts.homeDir ?? path.join(os.homedir(), ".claude");
340
+ return path.join(path.dirname(homeDir), ".claude.json");
341
+ }
342
+ /**
343
+ * Read strictly the top-level `mcpServers` key of the registry file. Never
344
+ * reads/interprets `projects.<path>.mcpServers` — that's project-local
345
+ * Claude Code state, out of scope here (see `probeProjectLocalClaudeJson`
346
+ * in uninstall for the existing, separate, read-only handling of it).
347
+ *
348
+ * Exported (task 83d8d03a) as the single allowed primitive for reading the
349
+ * effective Claude Code user-scope MCP registration from outside this
350
+ * module — `adopt` (MCP-drift comparison) and `detect` (team/full
351
+ * recognition) both call this directly instead of duplicating a registry
352
+ * reader or spawning `claude mcp list`. Read-only; never writes.
353
+ */
354
+ export function readTopLevelMcpServers(registryPath) {
355
+ let raw;
356
+ try {
357
+ raw = fs.readFileSync(registryPath, "utf8");
358
+ }
359
+ catch (err) {
360
+ const e = err;
361
+ if (e.code === "ENOENT")
362
+ return { servers: {}, error: null };
363
+ return { servers: {}, error: `cannot read ${registryPath}: ${e.message}` };
364
+ }
365
+ let parsed;
366
+ try {
367
+ parsed = JSON.parse(raw);
368
+ }
369
+ catch (err) {
370
+ return { servers: {}, error: `${registryPath} is not valid JSON: ${err.message}` };
371
+ }
372
+ if (!isRecord(parsed)) {
373
+ return { servers: {}, error: `${registryPath} is not a JSON object` };
374
+ }
375
+ const mcp = parsed["mcpServers"];
376
+ if (mcp === undefined)
377
+ return { servers: {}, error: null };
378
+ if (!isRecord(mcp)) {
379
+ return { servers: {}, error: `${registryPath} \`mcpServers\` is not a JSON object` };
380
+ }
381
+ return { servers: mcp, error: null };
382
+ }
383
+ // Equality tolerates a missing `args` on either side as `[]` and a
384
+ // missing `env` on either side as `{}` (constraint: "args-Default []
385
+ // tolerieren"; env gets the same treatment for consistency).
386
+ function specsEqual(existing, desired) {
387
+ if (!isRecord(existing))
388
+ return false;
389
+ if (existing["command"] !== desired.command)
390
+ return false;
391
+ const existingArgs = Array.isArray(existing["args"]) ? existing["args"] : [];
392
+ const desiredArgs = desired.args ?? [];
393
+ if (existingArgs.length !== desiredArgs.length)
394
+ return false;
395
+ for (let i = 0; i < existingArgs.length; i++) {
396
+ if (existingArgs[i] !== desiredArgs[i])
397
+ return false;
398
+ }
399
+ const existingEnv = isRecord(existing["env"]) ? existing["env"] : {};
400
+ const desiredEnv = desired.env ?? {};
401
+ const existingEnvKeys = Object.keys(existingEnv).sort();
402
+ const desiredEnvKeys = Object.keys(desiredEnv).sort();
403
+ if (existingEnvKeys.length !== desiredEnvKeys.length)
404
+ return false;
405
+ for (let i = 0; i < existingEnvKeys.length; i++) {
406
+ const k = existingEnvKeys[i];
407
+ if (k !== desiredEnvKeys[i])
408
+ return false;
409
+ if (existingEnv[k] !== desiredEnv[k])
410
+ return false;
411
+ }
412
+ return true;
413
+ }
414
+ /**
415
+ * Reconcile `desired` against the live user-scope registry:
416
+ * - name absent from the registry → `add-json`
417
+ * - name present, spec identical → no-op, NO exec call at all
418
+ * - name present, spec differs (drift) → `remove` then `add-json`
419
+ * The registry is only ever read (for the comparison); every write goes
420
+ * through the CLI wrapper. If the registry file can't be read safely
421
+ * (malformed JSON, `mcpServers` not an object, non-ENOENT read error),
422
+ * every desired server is reported `skipped` with a reason instead of
423
+ * guessing — a missing file (ENOENT, i.e. no registry yet) is NOT an
424
+ * error and is treated as an empty registry.
425
+ *
426
+ * `opts.gc` (task 363a6de0) opts into a second pass over the SAME
427
+ * registry snapshot: every name in `opts.gc.ownedNames` that is currently
428
+ * registered but absent from `desired` gets `claude mcp remove`d — this is
429
+ * how a manifest edit that deletes or disables a `tools.mcp[]` entry
430
+ * propagates into a deregistration on the next ensure run. A name that's
431
+ * owned but ALSO still in `desired` (e.g. an operator hand-registered a
432
+ * drifted copy of a still-active entry) is never a GC candidate — the
433
+ * `desired` membership check excludes it before ownership is even
434
+ * considered, so it's protected automatically, not via special-cased
435
+ * content comparison. Without `opts.gc`, behavior is exactly the pre-GC
436
+ * contract: every registry entry outside `desired` is left alone. If the
437
+ * registry couldn't be read safely, GC is skipped too, mirroring the main
438
+ * loop (`gc.registryReadError` set, `gc.results` empty) — same "never
439
+ * guess" rule.
440
+ */
441
+ export async function ensureMcpServers(opts) {
442
+ const registryPath = opts.registryPath ?? resolveClaudeUserRegistryPath({ homeDir: opts.homeDir, env: opts.env });
443
+ const { servers: existing, error: readError } = readTopLevelMcpServers(registryPath);
444
+ const callOpts = { exec: opts.exec, timeoutMs: opts.timeoutMs };
445
+ const results = [];
446
+ for (const name of Object.keys(opts.desired).sort()) {
447
+ const spec = opts.desired[name];
448
+ if (readError !== null) {
449
+ results.push({ name, action: "skipped", reason: readError });
450
+ continue;
451
+ }
452
+ const current = existing[name];
453
+ if (current === undefined) {
454
+ const add = await addJsonMcpServer(name, spec, callOpts);
455
+ results.push({ name, action: "add", add });
456
+ continue;
457
+ }
458
+ if (specsEqual(current, spec)) {
459
+ results.push({ name, action: "noop" });
460
+ continue;
461
+ }
462
+ const remove = await removeMcpServer(name, callOpts);
463
+ if (remove.status !== "removed" && remove.status !== "not-found") {
464
+ // remove failed for a reason unrelated to "already gone" (cli-missing,
465
+ // timeout, or a genuine error) — a follow-up add-json would just fail
466
+ // the same way (or worse, collide), so stop here and report it.
467
+ results.push({ name, action: "replace", remove });
468
+ continue;
469
+ }
470
+ const add = await addJsonMcpServer(name, spec, callOpts);
471
+ results.push({ name, action: "replace", remove, add });
472
+ }
473
+ let gc;
474
+ if (opts.gc) {
475
+ if (readError !== null) {
476
+ gc = { results: [], registryReadError: readError };
477
+ }
478
+ else {
479
+ const desiredNames = new Set(Object.keys(opts.desired));
480
+ const owned = new Set(opts.gc.ownedNames);
481
+ // Owned ∧ registered ∧ NOT desired (D-103/D-107 — this module is
482
+ // agnostic to how the caller built `ownedNames`). `desired`
483
+ // membership is checked first, so a still-active manifest entry is
484
+ // protected regardless of whether its registered content has
485
+ // drifted.
486
+ const candidates = Object.keys(existing)
487
+ .filter((name) => owned.has(name) && !desiredNames.has(name))
488
+ .sort();
489
+ const gcResults = [];
490
+ for (const name of candidates) {
491
+ const remove = await removeMcpServer(name, callOpts);
492
+ if (remove.status === "removed" || remove.status === "not-found") {
493
+ gcResults.push({ name, action: "removed", remove });
494
+ }
495
+ else {
496
+ gcResults.push({ name, action: "skipped", remove, reason: remove.message });
497
+ }
498
+ }
499
+ gc = { results: gcResults };
500
+ }
501
+ }
502
+ return { registryPath, results, ...(gc !== undefined ? { gc } : {}) };
503
+ }
504
+ /**
505
+ * Remove exactly the given owned names from `settings.mcpServers`,
506
+ * preserve every foreign entry, drop the `mcpServers` key entirely when
507
+ * it becomes empty, and leave every other top-level key byte-identical
508
+ * (same values, same insertion order). Pure — no file I/O; callers own
509
+ * reading/writing settings.json.
510
+ */
511
+ export function stripOwnedMcpServers(settings, ownedNames) {
512
+ const mcpServers = settings["mcpServers"];
513
+ if (!isRecord(mcpServers)) {
514
+ // Absent or malformed `mcpServers`: nothing this function owns to
515
+ // strip. Returning the same reference keeps the "byte-identical"
516
+ // guarantee trivially true.
517
+ return { settings, removedNames: [] };
518
+ }
519
+ const owned = new Set(ownedNames);
520
+ const kept = {};
521
+ const removed = [];
522
+ for (const [name, value] of Object.entries(mcpServers)) {
523
+ if (owned.has(name)) {
524
+ removed.push(name);
525
+ }
526
+ else {
527
+ kept[name] = value;
528
+ }
529
+ }
530
+ if (removed.length === 0) {
531
+ return { settings, removedNames: [] };
532
+ }
533
+ // Spread preserves existing key order; reassigning `mcpServers` in
534
+ // place keeps its original position instead of moving it to the end.
535
+ const out = { ...settings };
536
+ if (Object.keys(kept).length === 0) {
537
+ delete out["mcpServers"];
538
+ }
539
+ else {
540
+ out["mcpServers"] = kept;
541
+ }
542
+ return { settings: out, removedNames: removed.sort() };
543
+ }
544
+ //# sourceMappingURL=claude-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-mcp.js","sourceRoot":"","sources":["../../src/io/claude-mcp.ts"],"names":[],"mappings":"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"}
@@ -42,6 +42,19 @@ export interface LedgerSessionQuery {
42
42
  sessionId: string;
43
43
  sinceIso?: string;
44
44
  contentPrefix?: string;
45
+ /**
46
+ * Optional per-call override of the session's `timeoutMs` (task
47
+ * 2026-07-18 subprocess-test-deflake, T-003). Applies only to this
48
+ * call's `ledger_summary` round-trip (the `tools/list` capability
49
+ * probe, only run when `sinceIso`/`contentPrefix` is set, still uses
50
+ * the session default). Omitted, behaviour is byte-for-byte identical
51
+ * to before this option existed: the session's own `timeoutMs` is
52
+ * used. A timeout on an overridden call still sets the session-wide
53
+ * latch exactly like a timeout on the session default would — see
54
+ * `LedgerSession.callTool`'s `options.timeoutMs` doc for the shared
55
+ * latch contract and the enforcement-path cost-bound caveat.
56
+ */
57
+ timeoutMs?: number;
45
58
  }
46
59
  /**
47
60
  * Result of a generic `LedgerSession.callTool` round-trip. Three-way so a
@@ -73,7 +86,27 @@ export type LedgerSessionCallResult = {
73
86
  */
74
87
  export interface LedgerSession {
75
88
  querySummary(query: LedgerSessionQuery): Promise<LedgerQueryResult>;
76
- callTool(name: string, args: Record<string, unknown>): Promise<LedgerSessionCallResult>;
89
+ /**
90
+ * `options.timeoutMs` (task 2026-07-18 subprocess-test-deflake, T-003)
91
+ * is an optional per-call override of the session's `timeoutMs` for
92
+ * THIS call only; every other call on the session keeps using the
93
+ * session default. Omitted, behaviour is byte-for-byte identical to
94
+ * before this option existed. Fail-open/degrade semantics and the
95
+ * session-wide timeout latch are unchanged either way: a timeout on an
96
+ * overridden call latches the session exactly like a timeout on the
97
+ * session default does — every subsequent call (on this session, with
98
+ * or without its own override) short-circuits to `degraded` without a
99
+ * new round-trip.
100
+ *
101
+ * Cost-bound caveat (OKF gate-fail-posture-matrix): the latch's promise
102
+ * that a dead ledger costs a session at most ~1× its `timeoutMs` holds
103
+ * only while per-call overrides on the enforcement critical path stay
104
+ * ≤ the session default. The override exists for tests and non-critical
105
+ * callers; do not pass a LARGER override on the PreToolUse gate path.
106
+ */
107
+ callTool(name: string, args: Record<string, unknown>, options?: {
108
+ timeoutMs?: number;
109
+ }): Promise<LedgerSessionCallResult>;
77
110
  dispose(): void;
78
111
  }
79
112
  export declare function openLedgerSession(opts: LedgerClientOptions): LedgerSession;