@lannguyensi/harness 0.44.0 → 0.45.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 (231) hide show
  1. package/CHANGELOG.md +1213 -0
  2. package/dist/cli/adopt/derive.d.ts +16 -7
  3. package/dist/cli/adopt/derive.js +19 -2
  4. package/dist/cli/adopt/derive.js.map +1 -1
  5. package/dist/cli/adopt/index.js +18 -3
  6. package/dist/cli/adopt/index.js.map +1 -1
  7. package/dist/cli/apply/apply.d.ts +1 -0
  8. package/dist/cli/apply/apply.js +62 -6
  9. package/dist/cli/apply/apply.js.map +1 -1
  10. package/dist/cli/apply/generate-codex-config.js +5 -6
  11. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  12. package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
  13. package/dist/cli/apply/generate-opencode-config.js +210 -0
  14. package/dist/cli/apply/generate-opencode-config.js.map +1 -0
  15. package/dist/cli/apply/generate-settings.d.ts +90 -32
  16. package/dist/cli/apply/generate-settings.js +133 -10
  17. package/dist/cli/apply/generate-settings.js.map +1 -1
  18. package/dist/cli/apply/index.d.ts +2 -1
  19. package/dist/cli/apply/index.js +2 -1
  20. package/dist/cli/apply/index.js.map +1 -1
  21. package/dist/cli/approve/stdin-report.d.ts +15 -0
  22. package/dist/cli/approve/stdin-report.js +1 -1
  23. package/dist/cli/approve/stdin-report.js.map +1 -1
  24. package/dist/cli/approve/understanding.d.ts +75 -0
  25. package/dist/cli/approve/understanding.js +150 -2
  26. package/dist/cli/approve/understanding.js.map +1 -1
  27. package/dist/cli/audit.d.ts +1 -1
  28. package/dist/cli/audit.js +3 -2
  29. package/dist/cli/audit.js.map +1 -1
  30. package/dist/cli/doctor/claude-mcp.d.ts +19 -1
  31. package/dist/cli/doctor/claude-mcp.js +36 -10
  32. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  33. package/dist/cli/doctor/codex.d.ts +3 -1
  34. package/dist/cli/doctor/codex.js +6 -9
  35. package/dist/cli/doctor/codex.js.map +1 -1
  36. package/dist/cli/doctor/format.js +94 -8
  37. package/dist/cli/doctor/format.js.map +1 -1
  38. package/dist/cli/doctor/index.d.ts +23 -0
  39. package/dist/cli/doctor/index.js +106 -2
  40. package/dist/cli/doctor/index.js.map +1 -1
  41. package/dist/cli/doctor/opencode.d.ts +24 -0
  42. package/dist/cli/doctor/opencode.js +107 -0
  43. package/dist/cli/doctor/opencode.js.map +1 -0
  44. package/dist/cli/doctor/target-checks.d.ts +11 -0
  45. package/dist/cli/doctor/target-checks.js +25 -0
  46. package/dist/cli/doctor/target-checks.js.map +1 -0
  47. package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
  48. package/dist/cli/doctor/toolchain-parity.js +154 -0
  49. package/dist/cli/doctor/toolchain-parity.js.map +1 -0
  50. package/dist/cli/doctor/types.d.ts +85 -1
  51. package/dist/cli/doctor/types.js +1 -1
  52. package/dist/cli/doctor/types.js.map +1 -1
  53. package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
  54. package/dist/cli/doctor/understanding-mode-env.js +83 -0
  55. package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
  56. package/dist/cli/dry-run.js +32 -17
  57. package/dist/cli/dry-run.js.map +1 -1
  58. package/dist/cli/explain.d.ts +1 -1
  59. package/dist/cli/explain.js +1 -1
  60. package/dist/cli/explain.js.map +1 -1
  61. package/dist/cli/index.js +58 -4
  62. package/dist/cli/index.js.map +1 -1
  63. package/dist/cli/init/composer.js +36 -13
  64. package/dist/cli/init/composer.js.map +1 -1
  65. package/dist/cli/init/interactive.d.ts +7 -4
  66. package/dist/cli/init/interactive.js +79 -27
  67. package/dist/cli/init/interactive.js.map +1 -1
  68. package/dist/cli/init/profiles.d.ts +2 -2
  69. package/dist/cli/init/profiles.js +32 -1
  70. package/dist/cli/init/profiles.js.map +1 -1
  71. package/dist/cli/init/templates.d.ts +2 -1
  72. package/dist/cli/init/templates.js +130 -20
  73. package/dist/cli/init/templates.js.map +1 -1
  74. package/dist/cli/pack/approve-escape.js +69 -8
  75. package/dist/cli/pack/approve-escape.js.map +1 -1
  76. package/dist/cli/pack/hook-branch-protection.js +1 -1
  77. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  78. package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
  79. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  80. package/dist/cli/pack/hook-post-merge-gate.js +1 -1
  81. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
  82. package/dist/cli/pack/hook-pre-tool-use.js +91 -4
  83. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  84. package/dist/cli/pack/hook-solution-acceptance.js +19 -2
  85. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  86. package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
  87. package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
  88. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  89. package/dist/cli/pause/index.js +1 -1
  90. package/dist/cli/pause/index.js.map +1 -1
  91. package/dist/cli/policy/intercept.d.ts +148 -0
  92. package/dist/cli/policy/intercept.js +379 -48
  93. package/dist/cli/policy/intercept.js.map +1 -1
  94. package/dist/cli/session-start/index.d.ts +12 -3
  95. package/dist/cli/session-start/index.js +67 -11
  96. package/dist/cli/session-start/index.js.map +1 -1
  97. package/dist/cli/session-start/stale-base-check.d.ts +71 -0
  98. package/dist/cli/session-start/stale-base-check.js +379 -0
  99. package/dist/cli/session-start/stale-base-check.js.map +1 -0
  100. package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
  101. package/dist/cli/session-start/toolchain-parity.js +244 -36
  102. package/dist/cli/session-start/toolchain-parity.js.map +1 -1
  103. package/dist/cli/smoke/assertions.d.ts +9 -2
  104. package/dist/cli/smoke/assertions.js +9 -2
  105. package/dist/cli/smoke/assertions.js.map +1 -1
  106. package/dist/cli/uninstall/index.js +18 -2
  107. package/dist/cli/uninstall/index.js.map +1 -1
  108. package/dist/cli/validate/checks.d.ts +3 -1
  109. package/dist/cli/validate/checks.js +298 -3
  110. package/dist/cli/validate/checks.js.map +1 -1
  111. package/dist/io/claude-mcp.d.ts +122 -1
  112. package/dist/io/claude-mcp.js +214 -12
  113. package/dist/io/claude-mcp.js.map +1 -1
  114. package/dist/io/duration.js.map +1 -0
  115. package/dist/io/expand-home.js.map +1 -0
  116. package/dist/io/extract.js.map +1 -0
  117. package/dist/io/harness-lock.js +1 -1
  118. package/dist/io/harness-lock.js.map +1 -1
  119. package/dist/{runtime → io}/ledger-record.d.ts +1 -1
  120. package/dist/io/ledger-record.js.map +1 -0
  121. package/dist/policies/index.d.ts +2 -2
  122. package/dist/policies/index.js +2 -2
  123. package/dist/policies/index.js.map +1 -1
  124. package/dist/policies/ledger-client.js +10 -6
  125. package/dist/policies/ledger-client.js.map +1 -1
  126. package/dist/policies/producers.js +1 -1
  127. package/dist/policies/producers.js.map +1 -1
  128. package/dist/policies/requires.js +2 -2
  129. package/dist/policies/requires.js.map +1 -1
  130. package/dist/policy-packs/builtin/branch-protection.js +28 -3
  131. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  132. package/dist/policy-packs/builtin/post-merge-gate.js +45 -9
  133. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
  134. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +139 -8
  135. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +246 -12
  136. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  137. package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
  138. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
  139. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
  140. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
  141. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
  142. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
  143. package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
  144. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
  145. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
  146. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
  147. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
  148. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
  149. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
  150. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
  151. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
  152. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
  153. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
  154. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
  155. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
  156. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
  157. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
  158. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
  159. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
  160. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
  161. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
  162. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
  163. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
  164. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
  165. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  166. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
  167. package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
  168. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  169. package/dist/policy-packs/registry.d.ts +11 -0
  170. package/dist/policy-packs/registry.js +1 -1
  171. package/dist/policy-packs/registry.js.map +1 -1
  172. package/dist/policy-packs/runtime.d.ts +1 -1
  173. package/dist/policy-packs/runtime.js +10 -1
  174. package/dist/policy-packs/runtime.js.map +1 -1
  175. package/dist/runtime/agent-facing.js +1 -1
  176. package/dist/runtime/agent-facing.js.map +1 -1
  177. package/dist/runtime/bash-match-facts.d.ts +9 -7
  178. package/dist/runtime/bash-match-facts.js +9 -7
  179. package/dist/runtime/bash-match-facts.js.map +1 -1
  180. package/dist/runtime/bash-match-registry.d.ts +75 -38
  181. package/dist/runtime/bash-match-registry.js +279 -232
  182. package/dist/runtime/bash-match-registry.js.map +1 -1
  183. package/dist/runtime/bash-prefix-parse.d.ts +7 -0
  184. package/dist/runtime/bash-prefix-parse.js +205 -17
  185. package/dist/runtime/bash-prefix-parse.js.map +1 -1
  186. package/dist/runtime/command-normalize.d.ts +112 -0
  187. package/dist/runtime/command-normalize.js +411 -38
  188. package/dist/runtime/command-normalize.js.map +1 -1
  189. package/dist/runtime/hermetic-spawn-guard.js +6 -0
  190. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  191. package/dist/runtime/index.d.ts +2 -2
  192. package/dist/runtime/index.js +2 -2
  193. package/dist/runtime/index.js.map +1 -1
  194. package/dist/runtime/intercept.d.ts +78 -3
  195. package/dist/runtime/intercept.js +151 -39
  196. package/dist/runtime/intercept.js.map +1 -1
  197. package/dist/runtime/ledger-add.js +1 -1
  198. package/dist/runtime/ledger-add.js.map +1 -1
  199. package/dist/runtime/read-only-bash.js +303 -24
  200. package/dist/runtime/read-only-bash.js.map +1 -1
  201. package/dist/schema/doctor.d.ts +25 -0
  202. package/dist/schema/doctor.js +23 -0
  203. package/dist/schema/doctor.js.map +1 -0
  204. package/dist/schema/extract.js +1 -1
  205. package/dist/schema/extract.js.map +1 -1
  206. package/dist/schema/index.d.ts +76 -0
  207. package/dist/schema/index.js +9 -0
  208. package/dist/schema/index.js.map +1 -1
  209. package/dist/schema/requires.js +2 -2
  210. package/dist/schema/requires.js.map +1 -1
  211. package/dist/schema/risk.d.ts +7 -0
  212. package/dist/schema/risk.js +23 -0
  213. package/dist/schema/risk.js.map +1 -1
  214. package/dist/schema/stale-base-check.d.ts +18 -0
  215. package/dist/schema/stale-base-check.js +37 -0
  216. package/dist/schema/stale-base-check.js.map +1 -0
  217. package/dist/schema/toolchain-parity.d.ts +3 -0
  218. package/dist/schema/toolchain-parity.js +13 -0
  219. package/dist/schema/toolchain-parity.js.map +1 -1
  220. package/package.json +3 -2
  221. package/dist/policies/duration.js.map +0 -1
  222. package/dist/policies/extract.js.map +0 -1
  223. package/dist/runtime/expand-home.js.map +0 -1
  224. package/dist/runtime/ledger-record.js.map +0 -1
  225. /package/dist/{policies → io}/duration.d.ts +0 -0
  226. /package/dist/{policies → io}/duration.js +0 -0
  227. /package/dist/{runtime → io}/expand-home.d.ts +0 -0
  228. /package/dist/{runtime → io}/expand-home.js +0 -0
  229. /package/dist/{policies → io}/extract.d.ts +0 -0
  230. /package/dist/{policies → io}/extract.js +0 -0
  231. /package/dist/{runtime → io}/ledger-record.js +0 -0
@@ -0,0 +1,379 @@
1
+ // `harness session-start stale-base-check` — SessionStart hook
2
+ // entrypoint, fourth sibling of `harness session-start preflight` /
3
+ // `branch-check` / `toolchain-parity`.
4
+ //
5
+ // Incident (task ea8becf5): a task branch was cut from a local `master`
6
+ // that had not been fetched in 4 days. The v0.42.0 release had already
7
+ // landed on `origin/master` 6h before the branch point. The resulting PR
8
+ // went straight to CONFLICTING and its CI never ran. Nothing surfaced the
9
+ // gap at the time it was still cheap to fix (before work landed on the
10
+ // branch), because:
11
+ // - `harness preflight` checks the working TREE, never the remote.
12
+ // - `harness session-start branch-check` reads `.git/HEAD` and a
13
+ // protected-branch list; it has no notion of "is this branch's base
14
+ // current".
15
+ // - the post-merge-gate convention only ever fires AFTER a merge.
16
+ // And the obvious-looking fix is itself the trap this module exists to
17
+ // avoid: `git merge-base HEAD origin/master` LOOKS authoritative, but
18
+ // `origin/master` is a LOCAL, cached ref — exactly the ref that was 4
19
+ // days stale in the incident. Computing "how stale" from a stale ref
20
+ // answers a different question than the one that matters.
21
+ //
22
+ // --- WHERE this sits (task brief step 3) ---
23
+ // SessionStart companion, not a `preflight`-check addition and not a
24
+ // PreToolUse gate:
25
+ // - `harness policy intercept` (the PreToolUse hot path, evaluated on
26
+ // EVERY Bash/Edit/Write tool call — see runtime/intercept.ts) must
27
+ // never do network I/O; a `git fetch` there would add real latency to
28
+ // every single tool call for the life of the session. This module is
29
+ // wired ONLY under `harness session-start ...` (this file lives in
30
+ // src/cli/, which src/runtime/intercept.ts is structurally forbidden
31
+ // from importing — enforced by .dependency-cruiser.cjs's
32
+ // "runtime-no-upward-imports" rule, i.e. `npm run check:boundaries`
33
+ // fails the build if that ever changes), so the one network call this
34
+ // module makes can only ever happen once per SessionStart, never on
35
+ // the hot path.
36
+ // - `agent-preflight` (the external `preflight` binary
37
+ // `session-start/index.ts` wraps) is a SEPARATE npm package; teaching
38
+ // it to do remote checks is out of this task's scope (task brief
39
+ // "Out of scope").
40
+ // - A PreToolUse gate (`requires.ledger_tag`-style, like
41
+ // `preflight-before-push`) was considered and REJECTED: every such
42
+ // gate in this codebase fails CLOSED for a SECURITY invariant (an
43
+ // absent/expired tag blocks the action). A stale base is a
44
+ // time-loss problem, not a security problem — blocking on it would
45
+ // violate the task's hard constraint ("Keine Aussperrung: ein
46
+ // veralteter Base darf die Arbeit nicht verhindern") for zero
47
+ // security benefit. A SessionStart producer's own contract
48
+ // (`blocking:false`, always exit 0) makes the non-blocking posture
49
+ // structural rather than a policy authors could get wrong later.
50
+ //
51
+ // --- SCHÄRFE (severity) chosen: WARNUNG, not Hinweis, not Block ---
52
+ // - Block is impossible by construction here (see above) and would
53
+ // directly violate the hard constraint anyway.
54
+ // - A quiet Hinweis (a single low-key note easy to scroll past) risks
55
+ // reproducing the founding incident's own failure mode almost
56
+ // exactly: "nichts fiel auf" is the whole reason this task exists.
57
+ // - So: a loud stderr WARNING line — same visibility tier as the two
58
+ // existing loud-but-non-blocking SessionStart producers
59
+ // (`toolchain-parity`'s drift lines, `preflight`'s not-ready
60
+ // diagnostic) — naming the concrete commit count, how old the missing
61
+ // work is, and the exact recovery command. Purely advisory: no
62
+ // PreToolUse gate consumes the `stale-base:` ledger fact this writes
63
+ // (same posture as `toolchain-parity:`, audit-trail only for
64
+ // `harness audit`/forensics).
65
+ //
66
+ // --- Fetch cost / why opt-in (task brief: "inkl. Fetch-Kosten") ---
67
+ // `stale_base_check.enabled` defaults to `false` (schema:
68
+ // src/schema/stale-base-check.ts). An operator who has not asked for
69
+ // this pays ZERO extra session-start latency or network egress — the
70
+ // producer returns before touching `child_process` at all when
71
+ // unconfigured. Once enabled, the cost is bounded by
72
+ // `fetch_timeout_ms` (default 8000ms) per `git` subprocess call (at most
73
+ // three: fetch, rev-list, log — see `realCheckStaleBase` below), and the
74
+ // fetch itself is normally near-instant: `git fetch <remote>
75
+ // <defaultBranch>` only ever needs to transfer the commits this checkout
76
+ // doesn't already have. It always writes `FETCH_HEAD`; on a normal clone
77
+ // (whose configured `remote.<remote>.fetch` refspec matches the named
78
+ // branch) `git` ALSO refreshes the local `refs/remotes/<remote>/
79
+ // <defaultBranch>` tracking ref as a side effect — a welcome bonus (it
80
+ // self-heals the exact staleness the incident hit) but never something
81
+ // this module relies on or reads back: see below.
82
+ //
83
+ // --- The ref-staleness trap, closed ---
84
+ // The remote's default-branch NAME (e.g. "master") is resolved from the
85
+ // local `refs/remotes/origin/HEAD` file via `resolveOriginHeadBase` — that
86
+ // is safe to trust: a repo's default branch NAME essentially never
87
+ // changes after initial setup. What must NEVER be trusted is that same
88
+ // ref's SHA as a staleness signal — that is precisely what went stale in
89
+ // the incident. This module never READS a local `refs/remotes/<remote>/
90
+ // <defaultBranch>` sha for comparison (regardless of whether the fetch
91
+ // above happened to refresh it); the only sha it ever compares `branch`
92
+ // against is `FETCH_HEAD`, populated by a live `git fetch` performed
93
+ // THIS run.
94
+ //
95
+ // SessionStart contract, same as the three siblings: `blocking:false`.
96
+ // Every failure path (not configured, not a git repo, detached HEAD,
97
+ // default branch unresolved, `git` not on PATH, no remote configured, no
98
+ // credentials, offline/timeout, unparseable output, a ledger-write
99
+ // failure) logs one line to stderr and exits 0 — never blocking, never
100
+ // throwing (except the hermetic-spawn-guard violation under vitest
101
+ // without an injected `runCheck`, which — like the sibling producers —
102
+ // must propagate rather than be swallowed; see hermetic-spawn-guard.ts).
103
+ import { execFile } from "node:child_process";
104
+ import { findGitEntry, resolveCommonDir, resolveGitContext, resolveOriginHeadBase, } from "../../runtime/git-context.js";
105
+ import { assertNoRealSpawnInTests } from "../../runtime/hermetic-spawn-guard.js";
106
+ import { resolveManifestLedgerWriter } from "../../runtime/ledger-writer.js";
107
+ import { resolveReadSessionId, } from "../../runtime/session-id.js";
108
+ import { loadManifest } from "../loader.js";
109
+ import { formatSnapshotAge } from "./toolchain-parity.js";
110
+ const FALLBACK_SESSION = "default";
111
+ const LEDGER_SOURCE = "harness-session-start-stale-base-check";
112
+ const DEFAULT_REMOTE = "origin";
113
+ const DEFAULT_FETCH_TIMEOUT_MS = 8_000;
114
+ async function readStdin(stream) {
115
+ return new Promise((resolve, reject) => {
116
+ let data = "";
117
+ stream.setEncoding("utf8");
118
+ stream.on("data", (chunk) => {
119
+ data += chunk;
120
+ });
121
+ stream.on("end", () => resolve(data));
122
+ stream.on("error", reject);
123
+ });
124
+ }
125
+ /**
126
+ * Run one `git <args>` in `cwd`, classifying the common failure shapes
127
+ * (missing binary / timeout / non-zero exit) the same way the sibling
128
+ * producers' subprocess wrappers do (`spawnPreflight` in
129
+ * session-start/index.ts, `realNpmGlobalsSpawn` in toolchain-parity.ts).
130
+ * Never rejects.
131
+ */
132
+ function execGit(args, cwd, timeoutMs) {
133
+ return new Promise((resolve) => {
134
+ execFile("git", args, { cwd, timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, encoding: "utf8" }, (err, stdout, stderr) => {
135
+ const text = (stdout ?? "").toString().trim();
136
+ if (!err) {
137
+ resolve({ ok: true, stdout: text, reason: "" });
138
+ return;
139
+ }
140
+ const e = err;
141
+ if (e.code === "ENOENT") {
142
+ resolve({ ok: false, stdout: "", reason: "`git` not on PATH" });
143
+ return;
144
+ }
145
+ if (e.killed) {
146
+ resolve({
147
+ ok: false,
148
+ stdout: "",
149
+ reason: `\`git ${args.join(" ")}\` timed out after ${timeoutMs}ms (remote unreachable or very slow)`,
150
+ });
151
+ return;
152
+ }
153
+ const stderrText = (stderr ?? "").toString().trim().split("\n")[0] ?? "";
154
+ const capped = stderrText.length > 200 ? `${stderrText.slice(0, 199)}…` : stderrText;
155
+ resolve({
156
+ ok: false,
157
+ stdout: "",
158
+ reason: `\`git ${args.join(" ")}\` failed${capped ? `: ${capped}` : ` (exit ${e.code ?? "?"})`}`,
159
+ });
160
+ });
161
+ });
162
+ }
163
+ /**
164
+ * The real, network-touching implementation. Guarded by
165
+ * `assertNoRealSpawnInTests` (see that module's doc) so an un-injected
166
+ * test can never accidentally exercise a real `git fetch` — every test in
167
+ * this module's suite either injects `runCheck` or opts into the real
168
+ * path via the documented `HARNESS_ALLOW_REAL_SPAWN=1` escape hatch
169
+ * against a local fixture repo (never a real network host).
170
+ *
171
+ * Sequence: `git fetch --no-tags <remote> <defaultBranch>` (always writes
172
+ * FETCH_HEAD; may also refresh the local remote-tracking ref as a normal
173
+ * git side effect — see the module header), then `git rev-list
174
+ * --left-right --count <branch>...FETCH_HEAD` for the ahead/behind counts,
175
+ * then — only when behind > 0, since that is the only case the caller's
176
+ * message needs it — `git log -1 --format=%H%x09%cI FETCH_HEAD` for the
177
+ * live remote tip's sha + committer date. Comparison is always against
178
+ * `FETCH_HEAD` (this run's live result), never a cached tracking ref.
179
+ */
180
+ function realCheckStaleBase(args) {
181
+ assertNoRealSpawnInTests("git fetch (stale-base-check)", "Inject a fake `runCheck` (SessionStartStaleBaseCheckOptions.runCheck), or opt into the " +
182
+ "real path against a LOCAL fixture repo via HARNESS_ALLOW_REAL_SPAWN=1 for a dedicated " +
183
+ "real-git plumbing test — never exercise this against a real network host in a test.");
184
+ const { cwd, remote, defaultBranch, branch, timeoutMs } = args;
185
+ return (async () => {
186
+ const fetch = await execGit(["fetch", "--no-tags", "--", remote, defaultBranch], cwd, timeoutMs);
187
+ if (!fetch.ok)
188
+ return { ok: false, reason: fetch.reason };
189
+ const counts = await execGit(["rev-list", "--left-right", "--count", `${branch}...FETCH_HEAD`], cwd, timeoutMs);
190
+ if (!counts.ok)
191
+ return { ok: false, reason: counts.reason };
192
+ const parts = counts.stdout.split(/\s+/).filter((p) => p.length > 0);
193
+ const aheadCount = Number.parseInt(parts[0] ?? "", 10);
194
+ const behindCount = Number.parseInt(parts[1] ?? "", 10);
195
+ if (!Number.isFinite(aheadCount) || !Number.isFinite(behindCount)) {
196
+ return { ok: false, reason: `\`git rev-list --left-right --count\` produced unparseable output: "${counts.stdout}"` };
197
+ }
198
+ if (behindCount === 0)
199
+ return { ok: true, aheadCount, behindCount };
200
+ const log = await execGit(["log", "-1", "--format=%H%x09%cI", "FETCH_HEAD"], cwd, timeoutMs);
201
+ if (!log.ok) {
202
+ // The counts themselves are still good evidence — degrade the
203
+ // extra detail, not the whole result.
204
+ return { ok: true, aheadCount, behindCount };
205
+ }
206
+ const [remoteSha, latestRemoteCommitIso] = log.stdout.split("\t");
207
+ return {
208
+ ok: true,
209
+ aheadCount,
210
+ behindCount,
211
+ ...(remoteSha ? { remoteSha } : {}),
212
+ ...(latestRemoteCommitIso ? { latestRemoteCommitIso } : {}),
213
+ };
214
+ })();
215
+ }
216
+ export async function runSessionStartStaleBaseCheck(opts = {}) {
217
+ const stdin = opts.stdin ?? process.stdin;
218
+ const stderr = opts.stderr ?? process.stderr;
219
+ const now = opts.now ?? new Date();
220
+ const note = (msg) => {
221
+ stderr.write(`harness session-start stale-base-check: ${msg}\n`);
222
+ };
223
+ const done = (wrote, repo, branch, sessionId, sessionSource, reason, behindCount) => ({
224
+ exitCode: 0,
225
+ wrote,
226
+ repo,
227
+ branch,
228
+ sessionId,
229
+ sessionSource,
230
+ ...(behindCount !== undefined && { behindCount }),
231
+ ...(reason !== undefined && { reason }),
232
+ });
233
+ let event;
234
+ try {
235
+ event = JSON.parse((await readStdin(stdin)).trim() || "{}");
236
+ }
237
+ catch (err) {
238
+ const reason = `malformed event JSON: ${err.message}`;
239
+ note(reason);
240
+ return done(false, "", "", FALLBACK_SESSION, "default", reason);
241
+ }
242
+ const cwd = typeof opts.cwd === "string" && opts.cwd.length > 0
243
+ ? opts.cwd
244
+ : typeof event.cwd === "string" && event.cwd.length > 0
245
+ ? event.cwd
246
+ : process.cwd();
247
+ const explicit = typeof opts.session === "string" && opts.session.length > 0
248
+ ? opts.session
249
+ : typeof event.session_id === "string" && event.session_id.length > 0
250
+ ? event.session_id
251
+ : undefined;
252
+ const resolveSession = opts.resolveSession ?? resolveReadSessionId;
253
+ const sessionId = resolveSession(explicit, {});
254
+ const sessionSource = typeof opts.session === "string" && opts.session.length > 0
255
+ ? "flag"
256
+ : typeof event.session_id === "string" && event.session_id.length > 0
257
+ ? "stdin"
258
+ : sessionId === FALLBACK_SESSION
259
+ ? "default"
260
+ : (typeof process.env.CLAUDE_CODE_SESSION_ID === "string" &&
261
+ process.env.CLAUDE_CODE_SESSION_ID === sessionId) ||
262
+ (typeof process.env.CLAUDE_SESSION_ID === "string" &&
263
+ process.env.CLAUDE_SESSION_ID === sessionId)
264
+ ? "env"
265
+ : "transcript";
266
+ let manifest;
267
+ if (opts.manifest) {
268
+ manifest = opts.manifest;
269
+ }
270
+ else {
271
+ try {
272
+ manifest = loadManifest(opts).manifest;
273
+ }
274
+ catch (err) {
275
+ const reason = `manifest load failed: ${err.message}`;
276
+ note(reason);
277
+ return done(false, "", "", sessionId, sessionSource, reason);
278
+ }
279
+ }
280
+ const config = manifest.stale_base_check;
281
+ if (!config.enabled) {
282
+ const reason = "not configured (add `stale_base_check: { enabled: true }` to harness.yaml); skipping — no network touched";
283
+ note(reason);
284
+ return done(false, "", "", sessionId, sessionSource, reason);
285
+ }
286
+ const { repo, branch } = resolveGitContext(cwd);
287
+ if (repo === "") {
288
+ const reason = `cwd is not inside a git work tree (${cwd}); nothing to check`;
289
+ note(reason);
290
+ return done(false, "", "", sessionId, sessionSource, reason);
291
+ }
292
+ if (branch === "") {
293
+ const reason = "detached HEAD (or unreadable HEAD) — no branch to check a base for";
294
+ note(reason);
295
+ return done(false, repo, "", sessionId, sessionSource, reason);
296
+ }
297
+ const remote = config.remote ?? DEFAULT_REMOTE;
298
+ const timeoutMs = config.fetch_timeout_ms ?? DEFAULT_FETCH_TIMEOUT_MS;
299
+ let defaultBranch = config.default_branch;
300
+ if (!defaultBranch) {
301
+ // NOTE: `resolveOriginHeadBase` reads `refs/remotes/origin/HEAD`
302
+ // specifically (hardcoded "origin" — see runtime/git-context.ts), not
303
+ // `refs/remotes/<remote>/HEAD`. For the overwhelming common case
304
+ // (`remote` left at its "origin" default) this is exactly right. A
305
+ // manifest that configures a NON-"origin" `remote` without also
306
+ // setting `default_branch` explicitly will fail this auto-resolution
307
+ // and fall into the "could not be resolved" degrade path below —
308
+ // fail-open (skip, no fact written), never a wrong/misleading result,
309
+ // so this is a completeness gap, not a correctness risk.
310
+ const gitDir = findGitEntry(cwd)?.gitDir;
311
+ defaultBranch = gitDir ? (resolveOriginHeadBase(resolveCommonDir(gitDir)) ?? undefined) : undefined;
312
+ }
313
+ if (!defaultBranch) {
314
+ const reason = `default branch could not be resolved (checked \`stale_base_check.default_branch\` and ` +
315
+ `refs/remotes/${remote}/HEAD / packed-refs); skipping. Set \`stale_base_check.default_branch\` ` +
316
+ "explicitly to fix this.";
317
+ note(reason);
318
+ return done(false, repo, branch, sessionId, sessionSource, reason);
319
+ }
320
+ if (branch === defaultBranch) {
321
+ const reason = `on ${defaultBranch} itself; nothing to compare a base against`;
322
+ note(reason);
323
+ return done(false, repo, branch, sessionId, sessionSource, reason);
324
+ }
325
+ const runCheck = opts.runCheck ?? realCheckStaleBase;
326
+ const result = await runCheck({ cwd, remote, defaultBranch, branch, timeoutMs });
327
+ if (!result.ok) {
328
+ // Fail-open by construction (task hard constraint): every reason here
329
+ // — no remote configured, no credentials, offline, timeout, git
330
+ // missing — degrades to "no fact written", never a thrown error and
331
+ // never a block. A stale base costs time, not safety.
332
+ note(`${result.reason} — degrading cleanly (not blocking; the base-staleness check is advisory-only)`);
333
+ return done(false, repo, branch, sessionId, sessionSource, result.reason);
334
+ }
335
+ const remoteRef = `${remote}/${defaultBranch}`;
336
+ if (result.behindCount === 0) {
337
+ note(`base is current with ${remoteRef} (live check via \`git fetch\`)`);
338
+ }
339
+ else {
340
+ const ageSuffix = result.latestRemoteCommitIso
341
+ ? ` (newest missing commit landed ${formatSnapshotAge(Math.max(0, now.getTime() - Date.parse(result.latestRemoteCommitIso)))} ago, ${result.latestRemoteCommitIso})`
342
+ : "";
343
+ const aheadSuffix = result.aheadCount > 0 ? `; this branch itself is ${result.aheadCount} commit(s) ahead of that base` : "";
344
+ note(`WARNING: this branch's base is ${result.behindCount} commit(s) behind ${remoteRef}${ageSuffix}${aheadSuffix}. ` +
345
+ `This is a LIVE check (a real \`git fetch\`, not the local ${remoteRef} ref, which may itself be stale). ` +
346
+ `Recommended before continuing: \`git fetch ${remote} && git rebase ${remoteRef}\` (or merge) — ` +
347
+ "otherwise this PR risks landing CONFLICTING with CI never running (the incident this check exists to catch).");
348
+ }
349
+ const content = result.behindCount === 0
350
+ ? `stale-base:${repo}:${branch} ok base:${remoteRef}`
351
+ : `stale-base:${repo}:${branch} behind:${result.behindCount} ahead:${result.aheadCount} base:${remoteRef}` +
352
+ (result.remoteSha ? ` remote_sha:${result.remoteSha}` : "");
353
+ let writeLedger = opts.writeLedger;
354
+ if (!writeLedger) {
355
+ const resolved = resolveManifestLedgerWriter(manifest, {
356
+ ...(opts.ledgerTimeoutMs !== undefined ? { ledgerTimeoutMs: opts.ledgerTimeoutMs } : {}),
357
+ });
358
+ if (!resolved.ok) {
359
+ const reason = `${resolved.reason}; cannot record ${content}`;
360
+ note(reason);
361
+ return done(false, repo, branch, sessionId, sessionSource, reason, result.behindCount);
362
+ }
363
+ writeLedger = resolved.write;
364
+ }
365
+ const written = await writeLedger({ sessionId, content, source: LEDGER_SOURCE });
366
+ if (!written.ok) {
367
+ const reason = `ledger write failed: ${written.reason ?? "unknown error"}`;
368
+ note(reason);
369
+ return done(false, repo, branch, sessionId, sessionSource, reason, result.behindCount);
370
+ }
371
+ note(`recorded ${content} for session ${sessionId}`);
372
+ if (sessionSource === "default") {
373
+ note("WARNING: session resolved to the literal \"default\". This tag is audit-only (no gate consumes " +
374
+ "stale-base: yet), but pipe SessionStart event JSON on stdin, export $CLAUDE_SESSION_ID, or pass " +
375
+ "--session <id> for manual / scripted use to keep the audit trail useful.");
376
+ }
377
+ return done(true, repo, branch, sessionId, sessionSource, undefined, result.behindCount);
378
+ }
379
+ //# sourceMappingURL=stale-base-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale-base-check.js","sourceRoot":"","sources":["../../../src/cli/session-start/stale-base-check.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,15 @@
1
+ import { type LedgerWriteFn } from "../../runtime/ledger-writer.js";
1
2
  import { type ResolveReadSessionOptions } from "../../runtime/session-id.js";
2
3
  import type { Manifest } from "../../schema/index.js";
3
4
  import { type LoaderOptions } from "../loader.js";
5
+ /**
6
+ * Profile names land in a filename; strip anything not filename-safe.
7
+ * Exported (task 13919613) so `harness doctor`'s toolchain-parity section
8
+ * can resolve the SAME own-profile filename this producer would, without
9
+ * re-implementing the sanitization rule. Pure export, no behaviour change
10
+ * to any call site in this module.
11
+ */
12
+ export declare function sanitizeProfileName(value: string): string;
4
13
  /**
5
14
  * The toolchain snapshot v1 content (task brief §"Snapshot-Inhalt v1").
6
15
  * Every collected field is optional: a collection failure (spawn missing/
@@ -58,6 +67,116 @@ export type CollectNpmGlobalsResult = {
58
67
  ok: false;
59
68
  reason: string;
60
69
  };
70
+ /**
71
+ * Hermetic guard (mirrors `realNpmExec` in src/cli/doctor/npm-bin-path.ts):
72
+ * asserts BEFORE touching `child_process` that we are not running under
73
+ * vitest without a test having injected `runNodeVersion`. This function has
74
+ * no try/catch around the assertion, and its only caller,
75
+ * `collectLocalSnapshot`, calls it (via the `runNodeVersion` param, defaulted
76
+ * to this function in `runSessionStartToolchainParity`) with no surrounding
77
+ * try/catch either — see src/runtime/hermetic-spawn-guard.ts for why that
78
+ * matters (a thrown `HermeticSpawnViolationError` must propagate, not be
79
+ * folded into an ordinary "collection failed" outcome).
80
+ *
81
+ * PATH-resolved deliberately: `execFile("node", ...)` does a PATH lookup for
82
+ * the literal string "node", NOT `process.execPath` — the entire point of
83
+ * this collector is capturing the PATH state of the *shell* the session
84
+ * runs in (the PATH-shim incident this task follows up on was exactly a
85
+ * PATH pointing at the wrong node), which `process.version` (the node
86
+ * running THIS harness process) would silently paper over.
87
+ *
88
+ * Exported (task 13919613) so `harness doctor`'s toolchain-parity section
89
+ * can default its own `runNodeVersion` injection knob to the SAME real
90
+ * collector this producer uses — the whole point of the reuse is that
91
+ * doctor never re-implements this spawn/parse logic. No behaviour change:
92
+ * this is the exact function every existing call site already used.
93
+ */
94
+ export declare function realNodeVersionSpawn(timeoutMs: number): Promise<CollectNodeVersionResult>;
95
+ /**
96
+ * Hermetic guard, same shape/rationale as {@link realNodeVersionSpawn} —
97
+ * see that function's doc. Tolerant of a non-zero exit the same way
98
+ * `spawnPreflight` (session-start/index.ts) is tolerant of `preflight run`'s
99
+ * exit code: `npm ls -g` can exit non-zero on an unrelated extraneous/
100
+ * invalid global entry while still emitting a perfectly parseable
101
+ * `dependencies` JSON block, so a parseable stdout wins over the exit code.
102
+ *
103
+ * Exported (task 13919613) for the same reuse reason as
104
+ * {@link realNodeVersionSpawn} — `harness doctor`'s toolchain-parity
105
+ * section defaults to this real collector rather than duplicating it.
106
+ */
107
+ export declare function realNpmGlobalsSpawn(timeoutMs: number): Promise<CollectNpmGlobalsResult>;
108
+ /**
109
+ * Read `<workspaceRoot>/.ai/workflow/manifest.json`'s `version` field
110
+ * (the orchestrator-workflow kit's own version marker). A missing file is
111
+ * the expected common case (most repos are not OW-kit-managed) and is
112
+ * NOT an error — it degrades silently to `{}`. Malformed JSON or a
113
+ * non-string `version` field on a manifest that DOES exist is noteworthy
114
+ * (something is there but broken), so that degrades to `{ error }`.
115
+ *
116
+ * Exported (task 13919613) for the same reuse reason as
117
+ * {@link realNodeVersionSpawn}.
118
+ */
119
+ export declare function realReadOwKitVersion(workspaceRoot: string): {
120
+ version?: string;
121
+ error?: string;
122
+ };
123
+ /**
124
+ * MCP server names from Claude Code's user-scope registry — a pure FILE
125
+ * READ (never a spawn), reusing the exact primitive `harness doctor`'s
126
+ * claude-mcp check ultimately compares against
127
+ * (`resolveClaudeUserRegistryPath` + `readTopLevelMcpServers` in
128
+ * src/io/claude-mcp.ts — the same functions `adopt`/`detect` use for the
129
+ * same "read the effective registration from outside io/claude-mcp.ts"
130
+ * purpose). Deliberately NOT `claude mcp list` (a spawn): the brief calls
131
+ * for a file-read source for the NAMES here, and this is it.
132
+ *
133
+ * Exported (task 13919613) for the same reuse reason as
134
+ * {@link realNodeVersionSpawn}.
135
+ */
136
+ export declare function realReadMcpServerNames(): {
137
+ names: string[];
138
+ error?: string;
139
+ };
140
+ /**
141
+ * Exported (task 13919613) so `harness doctor`'s toolchain-parity section
142
+ * can call {@link collectLocalSnapshot} directly instead of duplicating
143
+ * its orchestration of the four collectors.
144
+ */
145
+ export interface CollectLocalSnapshotOptions {
146
+ profile: string;
147
+ now: Date;
148
+ workspaceRoot: string;
149
+ nodeTimeoutMs: number;
150
+ npmTimeoutMs: number;
151
+ runNodeVersion: (timeoutMs: number) => Promise<CollectNodeVersionResult>;
152
+ runNpmGlobals: (timeoutMs: number) => Promise<CollectNpmGlobalsResult>;
153
+ readOwKitVersion: (workspaceRoot: string) => {
154
+ version?: string;
155
+ error?: string;
156
+ };
157
+ readMcpServerNames: () => {
158
+ names: string[];
159
+ error?: string;
160
+ };
161
+ }
162
+ /**
163
+ * Collect the local, LIVE toolchain snapshot. Never throws (every
164
+ * collector above degrades to `{ok:false,reason}` / `{error}` instead of
165
+ * rejecting/throwing) — a collection failure is reported both inline on
166
+ * the returned snapshot (the matching `*Error` field) AND as a `notes`
167
+ * line for the caller to log, but never stops the other collectors from
168
+ * running. node/npm run in PARALLEL (see the timeout constants' comment
169
+ * above for why).
170
+ *
171
+ * Exported (task 13919613): `harness doctor`'s toolchain-parity section
172
+ * imports this directly as its Collector core — see that module's header
173
+ * for the reuse rationale. No behaviour change to this function or any
174
+ * existing call site.
175
+ */
176
+ export declare function collectLocalSnapshot(opts: CollectLocalSnapshotOptions): Promise<{
177
+ snapshot: ToolchainSnapshot;
178
+ notes: string[];
179
+ }>;
61
180
  export type DriftKind = "node" | "ow_kit" | "npm_package_version" | "npm_package_missing_local" | "npm_package_missing_peer" | "mcp_missing_local" | "mcp_missing_peer";
62
181
  export interface DriftItem {
63
182
  kind: DriftKind;
@@ -104,14 +223,7 @@ export interface SessionStartToolchainParityOptions extends LoaderOptions {
104
223
  /** Inject a manifest (tests). Bypasses loadManifest. */
105
224
  manifest?: Manifest;
106
225
  /** Inject the ledger writer (tests). */
107
- writeLedger?: (args: {
108
- sessionId: string;
109
- content: string;
110
- source: string;
111
- }) => Promise<{
112
- ok: boolean;
113
- reason?: string;
114
- }>;
226
+ writeLedger?: LedgerWriteFn;
115
227
  /** Inject the read-path session resolver (env + transcript discovery). Test seam. */
116
228
  resolveSession?: (explicit: string | undefined, opts: ResolveReadSessionOptions) => string;
117
229
  /** Inject the `node --version` collector (tests) — see realNodeVersionSpawn's doc. */
@@ -140,10 +252,32 @@ export interface SessionStartToolchainParityResult {
140
252
  peersCompared: number;
141
253
  /** Total drift items found across every compared peer. */
142
254
  driftCount: number;
255
+ /**
256
+ * Peer files present in the machine-state directory that failed to parse
257
+ * as valid JSON (readable but not JSON, or JSON that fails the minimal
258
+ * `profile`/`timestamp` shape check) and were therefore excluded from
259
+ * `driftCount`/`peersCompared`. A non-zero count here means `driftCount`
260
+ * reflects a PARTIAL comparison, not a full one — see the
261
+ * `:unparseable-peer:<n>` ledger-content suffix this drives.
262
+ *
263
+ * The contrapositive does NOT hold: peers whose file could not be READ
264
+ * at all (EACCES, EISDIR, ...) are skipped by the unreadable branch and
265
+ * are not counted here, so the absence of the suffix does not prove a
266
+ * complete comparison. That branch is scoped to the follow-up hardening
267
+ * round (task c1b5ade5).
268
+ */
269
+ unparseablePeerCount: number;
143
270
  /** Resolved session id. */
144
271
  sessionId: string;
145
272
  sessionSource: "flag" | "stdin" | "env" | "transcript" | "default";
146
273
  /** Human-readable explanation of a non-write outcome, for diagnostics. */
147
274
  reason?: string;
148
275
  }
276
+ /**
277
+ * Exported (task 13919613) so `harness doctor`'s toolchain-parity section
278
+ * resolves the SAME default machine-state directory this producer writes
279
+ * to when `toolchain_parity.machine_state_dir` is omitted from the
280
+ * manifest — reusing the constant instead of re-deriving it.
281
+ */
282
+ export declare function defaultMachineStateDir(): string;
149
283
  export declare function runSessionStartToolchainParity(opts?: SessionStartToolchainParityOptions): Promise<SessionStartToolchainParityResult>;