@holmes-lab/holmes-kit 0.19.0 → 0.19.3

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 (35) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/.build-id +1 -1
  3. package/dist/holmes/cli/agents.d.ts +22 -0
  4. package/dist/holmes/cli/agents.js +76 -1
  5. package/dist/holmes/cli/approve.js +6 -1
  6. package/dist/holmes/cli/doctor.d.ts +51 -1
  7. package/dist/holmes/cli/doctor.js +211 -36
  8. package/dist/holmes/cli/index.js +7 -1
  9. package/dist/holmes/cli/init.js +12 -0
  10. package/dist/holmes/cli/native-deps.d.ts +65 -0
  11. package/dist/holmes/cli/native-deps.js +131 -0
  12. package/dist/holmes/cpg/cycle-observation.d.ts +65 -0
  13. package/dist/holmes/cpg/cycle-observation.js +146 -0
  14. package/dist/holmes/governance/approval-queue.d.ts +23 -4
  15. package/dist/holmes/governance/approval-queue.js +44 -6
  16. package/dist/holmes/hooks/stop.d.ts +15 -0
  17. package/dist/holmes/hooks/stop.js +46 -3
  18. package/dist/holmes/mcp/handlers.d.ts +2 -0
  19. package/dist/holmes/mcp/handlers.js +29 -2
  20. package/dist/holmes/mcp/maintenance-analyze.d.ts +37 -0
  21. package/dist/holmes/mcp/maintenance-analyze.js +73 -1
  22. package/dist/holmes/mcp/maintenance-evidence.d.ts +41 -0
  23. package/dist/holmes/mcp/maintenance-evidence.js +71 -4
  24. package/dist/holmes/project/install-scripts-policy.d.ts +76 -0
  25. package/dist/holmes/project/install-scripts-policy.js +131 -0
  26. package/dist/holmes/project/npx-bin.d.ts +6 -0
  27. package/dist/holmes/project/npx-bin.js +10 -0
  28. package/dist/holmes/review/failed-test-names.d.ts +19 -0
  29. package/dist/holmes/review/failed-test-names.js +43 -0
  30. package/dist/holmes/review/run-replay.d.ts +23 -0
  31. package/dist/holmes/review/run-replay.js +30 -0
  32. package/dist/holmes/review/test-runner.d.ts +27 -0
  33. package/dist/holmes/review/test-runner.js +59 -3
  34. package/docs/install-guide.md +54 -5
  35. package/package.json +4 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,126 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  <!-- @implements A-SPEC-209 -->
8
+ ## [0.19.3] - 2026-09-10
9
+
10
+ `doctor` told every third-party install its SQLite binding was missing while it sat on disk. No API change.
11
+
12
+ ### Fixed
13
+ - **A false FAIL on the default install layout** (REQ-583). `gatherNativeEvidence` located
14
+ `better-sqlite3` by joining `packageRoot/node_modules/better-sqlite3`, and npm HOISTS
15
+ dependencies to the project root — so for a normal install at
16
+ `<proj>/node_modules/@holmes-lab/holmes-kit` the joined path never existed. Measured against the
17
+ published 0.19.2: the binding was present, `require('better-sqlite3')` executed a `:memory:`
18
+ query, and `doctor` still reported `no binary`. Hoisting is npm's default, which made this the
19
+ default experience of a correct install. The package directory is now RESOLVED
20
+ (`require.resolve('<name>/package.json', { paths: [root] })`) — Node's own rule, so hoisted,
21
+ nested and linked layouts all answer correctly, and a genuinely absent dependency still fails.
22
+ Its sibling — the `node version` check reading `engines.node` — carried the same join and moved
23
+ with it; it had been reporting `engines.node=(unknown)` for the same reason.
24
+ - **The release gate ran the suite in the operator's environment** (REQ-582). `npm publish`
25
+ refused a green tree: `verify-release.js` called `npm test` with the shell's `HOLMES_APPROVAL`,
26
+ which the spawned-hook parity test (A-SPEC-338) legitimately reacts to — the spawned child
27
+ inherits the token while its in-process twin is handed `{approval: undefined}`. The gate now runs
28
+ under `cleanTestEnv`, the same scrub `test_run` uses (borrowed, never copied), and refuses to
29
+ proceed unscrubbed if it cannot load it. It also names the failing tests instead of saying only
30
+ "red" — finding out which test had failed previously cost two extra full-suite runs.
31
+
32
+ - **A live session could block its own publish** (REQ-583). The `APPEND_ONLY_GOVERNANCE` list that
33
+ tells the release gate which dirty files are governance churn rather than unshipped code held
34
+ only the approval queue and provenance — while REQ-564/566/569/578 added `session-context`,
35
+ `impact-advisories`, `anchor-density` and `cycle-observations`, and `test-outcomes` was promoted
36
+ to tracking. Measured: `npm publish` refused because one `session-context` line was appended
37
+ between two `git status` snapshots inside the tree-untouched assertion, and the same list guards
38
+ the dirty-tree check a user meets first. The list now follows the ledgers, and a test pins that
39
+ nothing which ships (`src/`, `dist/`, the manifest, specs) is ever excused.
40
+
41
+ ### Verified against the published package
42
+ - Installed `@holmes-lab/holmes-kit@0.19.2` from the registry into a clean project, applied this
43
+ fix, and re-diagnosed from an unrelated working directory: **0 FAIL**, three-harness handshakes
44
+ (claude / codex / antigravity) all PASS on the version-pinned npx wiring, `target wiring — 3
45
+ holmes hook(s) resolve to this install`, and `better-sqlite3 — loads and executes against
46
+ :memory:`.
47
+
48
+ ## [0.19.2] - 2026-09-10
49
+
50
+ Wiring diagnosis no longer depends on where you run it. No API change.
51
+
52
+ ### Fixed
53
+ - **`doctor` gave opposite verdicts for the same wiring depending on the directory it was run
54
+ from** (REQ-581). A relative executable path in a wiring file is resolved by Node against
55
+ `process.cwd()`, and a relative path is the CONVENTION here — `.mcp.json` has always carried
56
+ `bin/holmes-mcp.js`. Measured: the same project under the same `--target` read
57
+ `PASS codex wiring` from the repository root and `FAIL — points at a missing file` from `/tmp`,
58
+ with the fix line telling the operator to re-init a wiring that was never wrong. All four
59
+ affected checks (`mcp wiring spawn`, `codex wiring`, and the `wiringHandshakeChecks` that cover
60
+ all three harnesses) now resolve against the TARGET project, and the handshake launches the
61
+ server FROM that project — the way a harness actually starts it. Absolute wirings (antigravity's)
62
+ are returned untouched, and a path that is genuinely missing still fails. The message keeps the
63
+ string exactly as written in the wiring file: what an operator must fix is that string, not our
64
+ arithmetic.
65
+ - **The same wiring could read PASS from one check and FAIL from its sibling.** Measured on a
66
+ third-party tarball install: `claude wiring handshake` passed while `mcp wiring spawn` failed on
67
+ the identical `npx` command, because only one of the two spawn paths had been moved. Both now
68
+ launch from the target.
69
+
70
+ ### Verified on a third-party install (macOS, npm 11.17.0, Node 26.5.1)
71
+ - Tarball installed into a clean project: `better-sqlite3` binding present, `:memory:` query ok.
72
+ - With install scripts blocked (`--ignore-scripts`, the npm 12 condition): the CLI still starts,
73
+ `init` still wires, the MCP server still answers — and `doctor` names the cause and prints a
74
+ recovery command that works verbatim (`npm rebuild better-sqlite3 --foreground-scripts`).
75
+ - Diagnosed from an unrelated working directory with the consumer's own CLI: claude / codex /
76
+ antigravity handshakes 3/3 PASS, `target wiring — 3 holmes hook(s) resolve to this install`.
77
+
78
+ ### Corrected from 0.19.1's notes
79
+ - 0.19.1 recorded that `codexMcpBlock` "writes Windows backslash paths into `config.toml`
80
+ unescaped". Re-measured: it does not. `tomlStr` escapes `\` correctly and the write→read
81
+ round-trip returns `C:\Users\name\proj\bin\holmes-mcp.js` byte-identical (pure functions, so
82
+ the result is platform-independent). The `doctor-wiring-handshake` failure seen on a Windows
83
+ checkout is therefore unexplained by escaping — and is a candidate instance of the cwd defect
84
+ fixed above, which is a HYPOTHESIS for the next Windows run, not a conclusion.
85
+
86
+ ## [0.19.1] - 2026-09-10
87
+
88
+ Windows install hardening, measured on Windows 11 / npm 12.0.1 / Node 24.19.0. No API change.
89
+
90
+ ### Fixed
91
+ - **npm 12 skipped `better-sqlite3`'s install script and nothing said so** (REQ-579). npm ≥ 12
92
+ (and npm 11.19+) blocks dependency install scripts unless the root `package.json` approves
93
+ them; `npm ci` then succeeds with no `better_sqlite3.node`, and every SQLite-backed feature
94
+ fails on first use. `package.json` now declares `allowScripts: { "better-sqlite3@12.11.1": true }`
95
+ — the ONE dependency that needs its script, pinned to the lockfile version so a bump forces a
96
+ fresh review (a test goes red until the pin is updated). The 8 tree-sitter packages are
97
+ deliberately not approved: measured, they load from their shipped `prebuilds/` with the script
98
+ blocked. Older npm 11 ignores the field.
99
+ - **doctor misdiagnosed every missing SQLite binary as an ABI mismatch** (REQ-580). The
100
+ `better-sqlite3` check now judges from evidence — binary present or not, the load error's
101
+ wording, the npm major, the governing `allowScripts`, the install layout (repository / project
102
+ dependency / global / npx), and on Windows the toolchain and a spaced path — and names the
103
+ cause: `scripts-blocked`, `abi-mismatch`, `build-failed`, or `unknown` with the raw error. A
104
+ prebuilt-download failure is stated as unobservable, never asserted. The fix line is the exact
105
+ command for that layout; for a global install it targets the dependency
106
+ (`npm rebuild -g better-sqlite3 --foreground-scripts --allow-scripts=better-sqlite3`), because
107
+ `rebuild -g @holmes-lab/holmes-kit` dies `EEXIST` re-linking the bin under npm 12 (measured).
108
+ - **doctor's grammar check only resolved packages.** It now parses a source with all 8 grammars
109
+ (typescript, tsx, python, c-sharp, java, go, rust, cpp) in a fresh child process — in-process
110
+ parsing is unreliable after another module registry has loaded tree-sitter's native addon.
111
+ - **Windows recovery commands are runnable.** npm commands are emitted as `npm.cmd` on win32
112
+ (`npmBin`, the twin of `npxBin`), and when a `Restricted`/`AllSigned` PowerShell execution policy
113
+ is observed the verdict says the `.ps1` shims are blocked and the `.cmd` ones are not.
114
+
115
+ ### Docs
116
+ - `docs/install-guide.md`: npm 12 section, per-layout recovery table, PowerShell policy note,
117
+ build-failed evidence. `docs/windows-test-plan-2026-09.md`: items E/F/G updated with the
118
+ 2026-09-09/10 measurements and the known Windows-only baseline failures.
119
+
120
+ ### Known, not fixed (recorded honestly)
121
+ - macOS/Linux were not exercised on hardware in this release; their behaviour is covered by
122
+ platform-injected unit tests and the three-harness parity suite only.
123
+ - `codexMcpBlock` writes Windows backslash paths into `config.toml` unescaped
124
+ (`doctor-wiring-handshake` fails on a Windows checkout); doctor's `global prefix` check reports a
125
+ protected `C:\Program Files\nodejs` as writable. Both are pre-existing and tracked as follow-ups.
126
+ **(The escaping half of this was re-measured in 0.19.2 and does not reproduce — see that entry.)**
127
+
8
128
  ## [0.19.0] - 2026-09-08
9
129
 
10
130
  ### Added
package/dist/.build-id CHANGED
@@ -1 +1 @@
1
- 0043954-mtspy09e
1
+ fb773a8-mtug045o
@@ -62,6 +62,28 @@ export declare function antigravityHookCommand(scriptPath: string): string;
62
62
  /** win32-only, best-effort: a spaced path becomes its 8.3 short name (no spaces); anything else — or
63
63
  * any lookup failure — returns the input unchanged. */
64
64
  export declare function shortPathIfSpaced(p: string): string;
65
+ export declare const MANAGED_BEGIN = "<!-- holmes-kit:managed:begin -->";
66
+ export declare const MANAGED_END = "<!-- holmes-kit:managed:end -->";
67
+ /**
68
+ * @implements A-SPEC-576.2
69
+ * The regeneration that keeps what it did not write.
70
+ *
71
+ * Measured cause: `init` rewrote AGENTS.md from the template, deleting the ADR-018 parity rule a
72
+ * commit had added the day before — and said nothing, so the loss was found weeks later by grep.
73
+ * Generated text now lives inside a MANAGED BLOCK; everything outside it belongs to the person
74
+ * whose repository this is.
75
+ *
76
+ * PURE, and it takes the whole file rather than a path: the caller reads `before` during init's
77
+ * COMPUTE phase, so a dry-run predicts the same bytes the real run writes (A-SPEC-190 §9).
78
+ *
79
+ * `preserved` is the report, not a courtesy. A run that moved something and returned an empty list
80
+ * would be the same silent loss in a new costume.
81
+ */
82
+ export declare function mergeAgentsMd(existing: string | null, generated: string): {
83
+ content: string;
84
+ preserved: string[];
85
+ note: string | null;
86
+ };
65
87
  /**
66
88
  * 이 하네스에 써야 할 파일들. **쓰지는 않는다** — 무엇을 쓸지만 말한다.
67
89
  *
@@ -33,9 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CODEX_MARKETPLACE = exports.CODEX_PLUGIN_DIR = exports.HARNESS_ENFORCES = exports.AGENTS = void 0;
36
+ exports.MANAGED_END = exports.MANAGED_BEGIN = exports.CODEX_MARKETPLACE = exports.CODEX_PLUGIN_DIR = exports.HARNESS_ENFORCES = exports.AGENTS = void 0;
37
37
  exports.antigravityHookCommand = antigravityHookCommand;
38
38
  exports.shortPathIfSpaced = shortPathIfSpaced;
39
+ exports.mergeAgentsMd = mergeAgentsMd;
39
40
  exports.agentFiles = agentFiles;
40
41
  exports.agentLinks = agentLinks;
41
42
  // @implements A-SPEC-442
@@ -197,6 +198,80 @@ ${enforced
197
198
  ? `Gates are **enforced**. Tool calls that write un-anchored code or target unapproved specifications will be denied. Unresolved critical findings block completion.`
198
199
  : `This harness does not have measured **hook enforcement points**. Holmes-Kit provides tools and guidance here without active gate blocking.`}
199
200
  `;
201
+ exports.MANAGED_BEGIN = '<!-- holmes-kit:managed:begin -->';
202
+ exports.MANAGED_END = '<!-- holmes-kit:managed:end -->';
203
+ const PRESERVED_NOTE = '<!-- kept from this file by holmes-kit init — holmes-kit does not write below this line -->';
204
+ /**
205
+ * @implements A-SPEC-576.2
206
+ * The regeneration that keeps what it did not write.
207
+ *
208
+ * Measured cause: `init` rewrote AGENTS.md from the template, deleting the ADR-018 parity rule a
209
+ * commit had added the day before — and said nothing, so the loss was found weeks later by grep.
210
+ * Generated text now lives inside a MANAGED BLOCK; everything outside it belongs to the person
211
+ * whose repository this is.
212
+ *
213
+ * PURE, and it takes the whole file rather than a path: the caller reads `before` during init's
214
+ * COMPUTE phase, so a dry-run predicts the same bytes the real run writes (A-SPEC-190 §9).
215
+ *
216
+ * `preserved` is the report, not a courtesy. A run that moved something and returned an empty list
217
+ * would be the same silent loss in a new costume.
218
+ */
219
+ function mergeAgentsMd(existing, generated) {
220
+ const block = exports.MANAGED_BEGIN + '\n' + (generated.endsWith('\n') ? generated : generated + '\n') + exports.MANAGED_END + '\n';
221
+ if (existing === null || existing === '')
222
+ return { content: block, preserved: [], note: null };
223
+ const b = existing.indexOf(exports.MANAGED_BEGIN);
224
+ const e = existing.indexOf(exports.MANAGED_END);
225
+ if (b !== -1 && e > b) {
226
+ // The ordinary case once a file has been through here: swap the block, and do not touch a
227
+ // single byte on either side of it.
228
+ const body = generated.endsWith('\n') ? generated : generated + '\n';
229
+ return {
230
+ content: existing.slice(0, b) + exports.MANAGED_BEGIN + '\n' + body + exports.MANAGED_END + existing.slice(e + exports.MANAGED_END.length),
231
+ preserved: [],
232
+ note: null,
233
+ };
234
+ }
235
+ // LEGACY: a file written before the markers existed — every AGENTS.md in the field today. Its
236
+ // lines are compared to the generated ones, and whatever the generator never wrote is carried
237
+ // below the block. Line terminators are kept as read, so a CRLF checkout stays CRLF.
238
+ const known = new Set(generated.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== ''));
239
+ const lines = existing.split(/(?<=\n)/);
240
+ const runs = [];
241
+ let run = [];
242
+ let pendingBlanks = [];
243
+ for (const line of lines) {
244
+ const t = line.trim();
245
+ if (t === '') {
246
+ if (run.length > 0)
247
+ pendingBlanks.push(line);
248
+ continue;
249
+ }
250
+ if (known.has(t)) {
251
+ run = closeRun(runs, run);
252
+ pendingBlanks = [];
253
+ continue;
254
+ }
255
+ // A blank line INSIDE a run is part of it; blanks trailing a run are not.
256
+ run.push(...pendingBlanks, line);
257
+ pendingBlanks = [];
258
+ }
259
+ closeRun(runs, run);
260
+ if (runs.length === 0)
261
+ return { content: block, preserved: [], note: null };
262
+ const preserved = runs.map((r) => (r.find((l) => l.trim() !== '') ?? '').trim());
263
+ return {
264
+ content: block + '\n' + PRESERVED_NOTE + '\n\n' + runs.map((r) => r.join('')).join('\n'),
265
+ preserved,
266
+ note: `kept ${runs.length} section(s) this generator did not write`,
267
+ };
268
+ }
269
+ /** Close the current run into `runs` (dropping an empty one) and hand back a fresh one. */
270
+ function closeRun(runs, run) {
271
+ if (run.length > 0)
272
+ runs.push(run);
273
+ return [];
274
+ }
200
275
  /**
201
276
  * 이 하네스에 써야 할 파일들. **쓰지는 않는다** — 무엇을 쓸지만 말한다.
202
277
  *
@@ -78,7 +78,12 @@ const DEFAULT_TTL_MINUTES = 30;
78
78
  const MAX_TTL_MINUTES = 7 * 24 * 60;
79
79
  const DEFAULT_RATIONALE = 'approved via holmes-kit approve';
80
80
  const findPending = (root, id) => {
81
- const state = (0, approval_queue_1.readQueue)(root);
81
+ // @implements A-SPEC-576.1 RESOLUTION reads everything; the human's LIST reads decisions only.
82
+ // The inbox filter hides gate refusals from the screen, but `approve <id>` must still find one:
83
+ // the fallback below only holds entries written after the REQ-563 split, so anything refused
84
+ // before it lives in the queue alone. Filtering here would cut the out-of-band approval path the
85
+ // deny message itself promises.
86
+ const state = (0, approval_queue_1.readQueue)(root, { includeAllKinds: true });
82
87
  const pending = state.pending.find((p) => p.id === id);
83
88
  if (pending)
84
89
  return { entry: pending, state };
@@ -1,3 +1,4 @@
1
+ import { NativeEvidence } from './native-deps';
1
2
  import { type SemanticTier } from '../semantic/tier';
2
3
  /**
3
4
  * `holmes-kit doctor` — install diagnostics.
@@ -82,6 +83,43 @@ export declare function prefixVerdict(input: PrefixProbe): {
82
83
  detail: string;
83
84
  fix?: string;
84
85
  };
86
+ /**
87
+ * @implements A-SPEC-580
88
+ * Parse a trivial source with every grammar, in a child node whose module resolution starts at
89
+ * `packageRoot`. `parsed` counts typescript and tsx separately (8 for the 7 grammar packages).
90
+ */
91
+ export declare function grammarProbe(packageRoot: string): {
92
+ parsed: number;
93
+ failed: string[];
94
+ error?: string;
95
+ };
96
+ /**
97
+ * @implements A-SPEC-580
98
+ * Everything observable about the better-sqlite3 install, gathered for `nativeVerdict`. Each probe
99
+ * is independent and optional: a spawn that fails leaves its field `undefined` (not observed), and
100
+ * the verdict says so rather than treating silence as evidence.
101
+ *
102
+ * Which package.json holds the approval policy depends on the layout: this checkout's own for a
103
+ * repository run, the PROJECT's (three levels up from node_modules/@holmes-lab/holmes-kit) for a
104
+ * local dependency. A global or npx install has no project package.json that npm consults, so its
105
+ * coverage is `uncovered` by construction and the remedy is the per-command `--allow-scripts`.
106
+ */
107
+ /**
108
+ * Where an installed dependency actually lives, asked of NODE rather than guessed from a path.
109
+ *
110
+ * 0.19.2 shipped a false FAIL to every third-party consumer: it built the directory by joining
111
+ * `packageRoot/node_modules/<name>`, and npm HOISTS dependencies to the project root, so for an
112
+ * install at `<proj>/node_modules/@holmes-lab/holmes-kit` the joined path never existed — while the
113
+ * binding sat at `<proj>/node_modules/better-sqlite3` and loaded fine. Hoisting is npm's default,
114
+ * which made the false verdict the default path of a normal install.
115
+ *
116
+ * `package.json` is the resolution target because we want the DIRECTORY, not the entry point: a
117
+ * package whose `exports` hides its main still resolves its own manifest by convention. A failure
118
+ * to resolve returns null, and the caller reports "absent" exactly as before — the repair changes
119
+ * how we look, never whether a real miss is reported.
120
+ */
121
+ export declare function resolveDepDir(fromRoot: string, name: string): string | null;
122
+ export declare function gatherNativeEvidence(packageRoot: string): NativeEvidence;
85
123
  /** The parent environment minus the variables that legitimately change a gate decision. Pure. */
86
124
  export declare function probeEnv(parent: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
87
125
  /** Seam for the gate probe's child process. Default is a real `spawnSync`; tests inject a fake. */
@@ -94,6 +132,18 @@ export interface ProbeRunner {
94
132
  stderr?: string;
95
133
  };
96
134
  }
135
+ /**
136
+ * Where a wiring's executable actually is, judged from the PROJECT rather than from the caller.
137
+ *
138
+ * Measured 2026-09-10: the same `.codex/config.toml` under the same `--target` read PASS from the
139
+ * repository root and FAIL from `/tmp`, because a relative arg is resolved by Node against
140
+ * `process.cwd()`. A relative arg is the CONVENTION here — `.mcp.json` has always carried
141
+ * `bin/holmes-mcp.js` — so the verdict was decided by where the person diagnosing stood.
142
+ *
143
+ * An absolute arg is returned untouched: agy's wiring is absolute and must not move. An empty arg
144
+ * stays empty — resolving it would conjure the target directory itself into a "file that exists".
145
+ */
146
+ export declare function resolveWiringPath(target: string, arg: string): string;
97
147
  export declare function runDoctor(packageRoot: string, target?: string, opts?: DoctorOptions, extraChecks?: Check[]): Promise<Check[]>;
98
148
  /**
99
149
  * Drive a real MCP stdio handshake: initialize -> initialized -> tools/list, with a timeout.
@@ -101,7 +151,7 @@ export declare function runDoctor(packageRoot: string, target?: string, opts?: D
101
151
  * and closes stdin makes the server exit before answering (a false FAIL this check produced on its
102
152
  * very first run against a healthy server).
103
153
  */
104
- export declare function wiringSpawnCheck(command: string, args: string[], timeoutMs?: number, platform?: NodeJS.Platform): Promise<Check>;
154
+ export declare function wiringSpawnCheck(command: string, args: string[], timeoutMs?: number, platform?: NodeJS.Platform, cwd?: string): Promise<Check>;
105
155
  /**
106
156
  * Push-gate presence, diagnosed only where it applies: a repo that opted into governance (.ax)
107
157
  * AND has git. Absence or a hook without our signature is a WARN carrying the install command —