@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,258 @@
1
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
2
+ export interface ClaudeMcpExecResult {
3
+ code: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ /** True iff the failure was a spawn-time ENOENT (the `claude` binary is not resolvable on PATH). */
7
+ enoent: boolean;
8
+ /** True iff the call was killed after exceeding the timeout. */
9
+ timedOut: boolean;
10
+ }
11
+ export interface ClaudeMcpExec {
12
+ (args: string[], timeoutMs: number): Promise<ClaudeMcpExecResult>;
13
+ }
14
+ export interface ClaudeMcpCallOptions {
15
+ /** Override the `claude` spawn. Tests inject a fake; default is the real CLI. */
16
+ exec?: ClaudeMcpExec;
17
+ /** Per-call timeout in ms. Default {@link DEFAULT_TIMEOUT_MS}. */
18
+ timeoutMs?: number;
19
+ }
20
+ /** Claude Code's `mcpServers` entry shape: command + optional args/env (matches SettingsMcpServer in generate-settings.ts). */
21
+ export interface ClaudeMcpServerSpec {
22
+ command: string;
23
+ args?: string[];
24
+ env?: Record<string, string>;
25
+ }
26
+ export type AddJsonStatus = "added" | "already-exists" | "invalid-config" | "cli-missing" | "timeout" | "error";
27
+ export interface AddJsonResult {
28
+ status: AddJsonStatus;
29
+ message: string;
30
+ code: number;
31
+ }
32
+ /**
33
+ * Wrap a value as a single POSIX shell single-quoted token, escaping any
34
+ * embedded single quote as `'\''`. Used to build the copy-pasteable
35
+ * `claude mcp add-json ... <json>` fallback commands harness prints when the
36
+ * CLI is missing or a server is unregistered, so the operator can paste them
37
+ * verbatim even when a value (e.g. a home path containing an apostrophe, as
38
+ * projected into grounding-mcp's EVIDENCE_LEDGER_DB) has a single quote.
39
+ * Harness never executes these strings itself.
40
+ */
41
+ export declare function posixSingleQuote(value: string): string;
42
+ export declare function addJsonMcpServer(name: string, spec: ClaudeMcpServerSpec, opts?: ClaudeMcpCallOptions): Promise<AddJsonResult>;
43
+ export type RemoveStatus = "removed" | "not-found" | "cli-missing" | "timeout" | "error";
44
+ export interface RemoveResult {
45
+ status: RemoveStatus;
46
+ message: string;
47
+ code: number;
48
+ }
49
+ export declare function removeMcpServer(name: string, opts?: ClaudeMcpCallOptions): Promise<RemoveResult>;
50
+ /**
51
+ * One `claude mcp remove --scope user <name>` line per name, sorted.
52
+ * Sibling of `manualAddJsonLines` in `cli/init/interactive.ts` — the
53
+ * copy-pasteable fallback GC (task 363a6de0) prints when it can't reach
54
+ * the `claude` CLI (or hits some other non-"removed"/"not-found" outcome)
55
+ * to deregister a stale, harness-owned server itself. Uses
56
+ * {@link posixSingleQuote} on the name for the same reason
57
+ * `manualAddJsonLines` quotes its JSON payload: a server name is an
58
+ * ordinary token today, but quoting it costs nothing and keeps the line
59
+ * safe to paste verbatim regardless. Harness never executes these strings
60
+ * itself.
61
+ */
62
+ export declare function manualRemoveLines(names: readonly string[]): string[];
63
+ export type GetStatus = "found" | "not-found" | "cli-missing" | "timeout";
64
+ export interface GetResult {
65
+ status: GetStatus;
66
+ raw: string;
67
+ code: number;
68
+ }
69
+ export declare function getMcpServer(name: string, opts?: ClaudeMcpCallOptions): Promise<GetResult>;
70
+ export type ClaudeMcpConnectionStatus = "connected" | "failed" | "needs-authentication" | "unknown";
71
+ export interface ClaudeMcpListEntry {
72
+ name: string;
73
+ command: string;
74
+ args: string[];
75
+ status: ClaudeMcpConnectionStatus;
76
+ statusText: string;
77
+ }
78
+ /**
79
+ * Parse `claude mcp list` stdout. Lines that don't match the expected
80
+ * shape (blank lines, any future banner/preamble) are skipped rather
81
+ * than throwing — the exit code alone tells us the command succeeded.
82
+ *
83
+ * Caveat (same one `buildMcpServers` in generate-settings.ts documents
84
+ * for the inverse direction): splitting the command+args segment on
85
+ * whitespace mis-splits a command path with embedded spaces. Foreign
86
+ * entries whose "command" is a URL (e.g. `claude.ai Gmail: https://... -
87
+ * ! Needs authentication`) still parse correctly since URLs have no
88
+ * embedded whitespace.
89
+ */
90
+ export declare function parseClaudeMcpListOutput(stdout: string): ClaudeMcpListEntry[];
91
+ export type ListStatus = "ok" | "cli-missing" | "timeout" | "error";
92
+ export interface ListResult {
93
+ status: ListStatus;
94
+ servers: ClaudeMcpListEntry[];
95
+ message?: string;
96
+ }
97
+ export declare function listMcpServers(opts?: ClaudeMcpCallOptions): Promise<ListResult>;
98
+ export interface ResolveRegistryPathOptions {
99
+ /** Override for Claude Code's own config home. Defaults to `path.join(os.homedir(), ".claude")`. */
100
+ homeDir?: string;
101
+ /** Override for process.env (CLAUDE_CONFIG_DIR lookup). Defaults to `process.env`. */
102
+ env?: NodeJS.ProcessEnv;
103
+ }
104
+ /**
105
+ * Resolve the path to the user-scope registry file the `claude` CLI
106
+ * itself reads/writes — the same precedence the CLI applies: an explicit
107
+ * `CLAUDE_CONFIG_DIR` wins (`$CLAUDE_CONFIG_DIR/.claude.json`); otherwise
108
+ * it's `~/.claude.json`, derived here as `path.join(path.dirname(homeDir),
109
+ * ".claude.json")` to match the existing resolution in
110
+ * `src/cli/uninstall/index.ts` (`probeProjectLocalClaudeJson`).
111
+ */
112
+ export declare function resolveClaudeUserRegistryPath(opts?: ResolveRegistryPathOptions): string;
113
+ export interface RegistryReadResult {
114
+ servers: Record<string, unknown>;
115
+ error: string | null;
116
+ }
117
+ /**
118
+ * Read strictly the top-level `mcpServers` key of the registry file. Never
119
+ * reads/interprets `projects.<path>.mcpServers` — that's project-local
120
+ * Claude Code state, out of scope here (see `probeProjectLocalClaudeJson`
121
+ * in uninstall for the existing, separate, read-only handling of it).
122
+ *
123
+ * Exported (task 83d8d03a) as the single allowed primitive for reading the
124
+ * effective Claude Code user-scope MCP registration from outside this
125
+ * module — `adopt` (MCP-drift comparison) and `detect` (team/full
126
+ * recognition) both call this directly instead of duplicating a registry
127
+ * reader or spawning `claude mcp list`. Read-only; never writes.
128
+ */
129
+ export declare function readTopLevelMcpServers(registryPath: string): RegistryReadResult;
130
+ export type EnsureAction = "noop" | "add" | "replace" | "skipped";
131
+ export interface EnsureServerResult {
132
+ name: string;
133
+ action: EnsureAction;
134
+ /** Present when action === "replace". */
135
+ remove?: RemoveResult;
136
+ /** Present when action === "add" or a "replace" whose remove step succeeded. */
137
+ add?: AddJsonResult;
138
+ /** Present when action === "skipped": the registry file could not be read safely, so drift cannot be determined. */
139
+ reason?: string;
140
+ }
141
+ export interface EnsureMcpServersGcOptions {
142
+ /**
143
+ * Names this harness install is allowed to garbage-collect from the
144
+ * registry (task 363a6de0, D-107 ownership union: the current
145
+ * manifest's `tools.mcp[]` names, union the `.last-apply` manifest
146
+ * snapshot's `tools.mcp[]` names — the snapshot as of BEFORE the
147
+ * caller's own apply ran, so a combined edit that both drops an entry
148
+ * and re-stamps `.last-apply` doesn't erase the provenance). Building
149
+ * this set is entirely the caller's responsibility — see `wireClaudeMcp`
150
+ * in `cli/init/interactive.ts` for the reference construction.
151
+ * Deliberately NOT included: the uninstall module's
152
+ * `DEFAULT_OWNED_MCP_SERVERS` (D-107 supersedes the original D-103,
153
+ * reviewer HIGH finding) — a server sharing one of those default names
154
+ * that the operator registered themselves, outside/before any harness
155
+ * manifest, must never be GC'd just because it's absent from `desired`.
156
+ * A registry entry outside this set is NEVER touched by GC, no matter
157
+ * how it drifts from `desired`.
158
+ */
159
+ ownedNames: string[];
160
+ }
161
+ export type GcAction = "removed" | "skipped";
162
+ export interface GcServerResult {
163
+ name: string;
164
+ action: GcAction;
165
+ /** The underlying `claude mcp remove` outcome. */
166
+ remove: RemoveResult;
167
+ /**
168
+ * Present when action === "skipped": `remove.status` was something
169
+ * other than "removed"/"not-found" (cli-missing, timeout, or a genuine
170
+ * error) — restated here as a human-readable reason so callers don't
171
+ * have to re-derive it from `remove`.
172
+ */
173
+ reason?: string;
174
+ }
175
+ export interface GcResult {
176
+ /**
177
+ * One entry per GC candidate: an owned name (per `opts.gc.ownedNames`)
178
+ * that is present in the registry and absent from `desired`. Empty when
179
+ * there were no candidates, or when `registryReadError` is set.
180
+ */
181
+ results: GcServerResult[];
182
+ /**
183
+ * Present when the registry could not be read safely — the same
184
+ * condition the main loop reports per-name via `skipped`/`reason`. No
185
+ * GC candidate could be determined, so no removal was attempted for any
186
+ * owned name; `results` stays empty rather than guessing.
187
+ */
188
+ registryReadError?: string;
189
+ }
190
+ export interface EnsureMcpServersOptions {
191
+ /** Desired state: server name -> spec. */
192
+ desired: Record<string, ClaudeMcpServerSpec>;
193
+ exec?: ClaudeMcpExec;
194
+ timeoutMs?: number;
195
+ /** Explicit override for the registry file read for drift comparison. Takes precedence over homeDir/env. */
196
+ registryPath?: string;
197
+ /** Base for the default registry path when `registryPath` is not given. */
198
+ homeDir?: string;
199
+ /** Override for process.env (CLAUDE_CONFIG_DIR lookup) when `registryPath` is not given. */
200
+ env?: NodeJS.ProcessEnv;
201
+ /**
202
+ * Opt-in garbage collection (task 363a6de0): after reconciling
203
+ * `desired`, additionally `claude mcp remove` any name in
204
+ * `gc.ownedNames` that's registered but no longer in `desired` (i.e.
205
+ * removed or disabled in the manifest since it was last registered).
206
+ * Omitted entirely: behavior is exactly the pre-GC contract — every
207
+ * registry entry outside `desired` is left untouched, and
208
+ * `EnsureMcpServersResult.gc` stays undefined.
209
+ */
210
+ gc?: EnsureMcpServersGcOptions;
211
+ }
212
+ export interface EnsureMcpServersResult {
213
+ registryPath: string;
214
+ results: EnsureServerResult[];
215
+ /** Present iff `opts.gc` was given. */
216
+ gc?: GcResult;
217
+ }
218
+ /**
219
+ * Reconcile `desired` against the live user-scope registry:
220
+ * - name absent from the registry → `add-json`
221
+ * - name present, spec identical → no-op, NO exec call at all
222
+ * - name present, spec differs (drift) → `remove` then `add-json`
223
+ * The registry is only ever read (for the comparison); every write goes
224
+ * through the CLI wrapper. If the registry file can't be read safely
225
+ * (malformed JSON, `mcpServers` not an object, non-ENOENT read error),
226
+ * every desired server is reported `skipped` with a reason instead of
227
+ * guessing — a missing file (ENOENT, i.e. no registry yet) is NOT an
228
+ * error and is treated as an empty registry.
229
+ *
230
+ * `opts.gc` (task 363a6de0) opts into a second pass over the SAME
231
+ * registry snapshot: every name in `opts.gc.ownedNames` that is currently
232
+ * registered but absent from `desired` gets `claude mcp remove`d — this is
233
+ * how a manifest edit that deletes or disables a `tools.mcp[]` entry
234
+ * propagates into a deregistration on the next ensure run. A name that's
235
+ * owned but ALSO still in `desired` (e.g. an operator hand-registered a
236
+ * drifted copy of a still-active entry) is never a GC candidate — the
237
+ * `desired` membership check excludes it before ownership is even
238
+ * considered, so it's protected automatically, not via special-cased
239
+ * content comparison. Without `opts.gc`, behavior is exactly the pre-GC
240
+ * contract: every registry entry outside `desired` is left alone. If the
241
+ * registry couldn't be read safely, GC is skipped too, mirroring the main
242
+ * loop (`gc.registryReadError` set, `gc.results` empty) — same "never
243
+ * guess" rule.
244
+ */
245
+ export declare function ensureMcpServers(opts: EnsureMcpServersOptions): Promise<EnsureMcpServersResult>;
246
+ export interface StripOwnedMcpServersResult {
247
+ settings: Record<string, unknown>;
248
+ /** Names actually removed (subset of ownedNames that were present), sorted. */
249
+ removedNames: string[];
250
+ }
251
+ /**
252
+ * Remove exactly the given owned names from `settings.mcpServers`,
253
+ * preserve every foreign entry, drop the `mcpServers` key entirely when
254
+ * it becomes empty, and leave every other top-level key byte-identical
255
+ * (same values, same insertion order). Pure — no file I/O; callers own
256
+ * reading/writing settings.json.
257
+ */
258
+ export declare function stripOwnedMcpServers(settings: Record<string, unknown>, ownedNames: readonly string[]): StripOwnedMcpServersResult;