@lannguyensi/harness 0.64.0 → 1.0.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 (710) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +46 -42
  3. package/dist/cli/add/index.js +17 -21
  4. package/dist/cli/add/index.js.map +1 -1
  5. package/dist/cli/adopt/derive.d.ts +2 -2
  6. package/dist/cli/adopt/derive.js +2 -19
  7. package/dist/cli/adopt/derive.js.map +1 -1
  8. package/dist/cli/adopt/index.d.ts +1 -1
  9. package/dist/cli/adopt/index.js +1 -12
  10. package/dist/cli/adopt/index.js.map +1 -1
  11. package/dist/cli/apply/apply.d.ts +20 -35
  12. package/dist/cli/apply/apply.js +61 -136
  13. package/dist/cli/apply/apply.js.map +1 -1
  14. package/dist/cli/apply/generate-codex-config.js +10 -58
  15. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  16. package/dist/cli/apply/generate-memory-index.js +1 -2
  17. package/dist/cli/apply/generate-memory-index.js.map +1 -1
  18. package/dist/cli/apply/generate-opencode-config.d.ts +2 -10
  19. package/dist/cli/apply/generate-opencode-config.js +74 -113
  20. package/dist/cli/apply/generate-opencode-config.js.map +1 -1
  21. package/dist/cli/apply/generate-settings.d.ts +11 -64
  22. package/dist/cli/apply/generate-settings.js +27 -132
  23. package/dist/cli/apply/generate-settings.js.map +1 -1
  24. package/dist/cli/apply/index.d.ts +1 -1
  25. package/dist/cli/apply/index.js +1 -1
  26. package/dist/cli/apply/index.js.map +1 -1
  27. package/dist/cli/apply/install-codex-config.d.ts +153 -1
  28. package/dist/cli/apply/install-codex-config.js +445 -70
  29. package/dist/cli/apply/install-codex-config.js.map +1 -1
  30. package/dist/cli/apply/next-steps.d.ts +7 -0
  31. package/dist/cli/apply/next-steps.js +13 -1
  32. package/dist/cli/apply/next-steps.js.map +1 -1
  33. package/dist/cli/bounded-stdin.d.ts +0 -23
  34. package/dist/cli/bounded-stdin.js +2 -49
  35. package/dist/cli/bounded-stdin.js.map +1 -1
  36. package/dist/cli/describe.d.ts +1 -1
  37. package/dist/cli/describe.js +0 -3
  38. package/dist/cli/describe.js.map +1 -1
  39. package/dist/cli/diff/engine.d.ts +0 -11
  40. package/dist/cli/diff/engine.js +1 -17
  41. package/dist/cli/diff/engine.js.map +1 -1
  42. package/dist/cli/diff/index.js +14 -25
  43. package/dist/cli/diff/index.js.map +1 -1
  44. package/dist/cli/doctor/claude-mcp.d.ts +4 -19
  45. package/dist/cli/doctor/claude-mcp.js +11 -22
  46. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  47. package/dist/cli/doctor/codex.d.ts +12 -14
  48. package/dist/cli/doctor/codex.js +31 -144
  49. package/dist/cli/doctor/codex.js.map +1 -1
  50. package/dist/cli/doctor/format.js +23 -345
  51. package/dist/cli/doctor/format.js.map +1 -1
  52. package/dist/cli/doctor/hook-liveness.d.ts +56 -0
  53. package/dist/cli/doctor/hook-liveness.js +455 -0
  54. package/dist/cli/doctor/hook-liveness.js.map +1 -0
  55. package/dist/cli/doctor/index.d.ts +13 -87
  56. package/dist/cli/doctor/index.js +57 -713
  57. package/dist/cli/doctor/index.js.map +1 -1
  58. package/dist/cli/doctor/opencode.d.ts +2 -1
  59. package/dist/cli/doctor/opencode.js.map +1 -1
  60. package/dist/cli/doctor/types.d.ts +21 -387
  61. package/dist/cli/doctor/types.js.map +1 -1
  62. package/dist/cli/drain-stdio.d.ts +31 -0
  63. package/dist/cli/drain-stdio.js +76 -0
  64. package/dist/cli/drain-stdio.js.map +1 -0
  65. package/dist/cli/export.d.ts +3 -1
  66. package/dist/cli/export.js +7 -16
  67. package/dist/cli/export.js.map +1 -1
  68. package/dist/cli/gate/codex-disable.d.ts +29 -0
  69. package/dist/cli/gate/codex-disable.js +195 -0
  70. package/dist/cli/gate/codex-disable.js.map +1 -0
  71. package/dist/cli/gate/codex-enable.d.ts +41 -0
  72. package/dist/cli/gate/codex-enable.js +127 -0
  73. package/dist/cli/gate/codex-enable.js.map +1 -0
  74. package/dist/cli/gate/codex-snapshot.d.ts +208 -0
  75. package/dist/cli/gate/codex-snapshot.js +402 -0
  76. package/dist/cli/gate/codex-snapshot.js.map +1 -0
  77. package/dist/cli/gate/disable.d.ts +9 -0
  78. package/dist/cli/gate/disable.js +263 -28
  79. package/dist/cli/gate/disable.js.map +1 -1
  80. package/dist/cli/gate/enable.d.ts +26 -6
  81. package/dist/cli/gate/enable.js +152 -80
  82. package/dist/cli/gate/enable.js.map +1 -1
  83. package/dist/cli/gate/link-chain.d.ts +53 -0
  84. package/dist/cli/gate/link-chain.js +122 -0
  85. package/dist/cli/gate/link-chain.js.map +1 -0
  86. package/dist/cli/gate/snapshot.d.ts +289 -12
  87. package/dist/cli/gate/snapshot.js +481 -22
  88. package/dist/cli/gate/snapshot.js.map +1 -1
  89. package/dist/cli/index.d.ts +0 -9
  90. package/dist/cli/index.js +9 -2131
  91. package/dist/cli/index.js.map +1 -1
  92. package/dist/cli/init/composer.d.ts +3 -6
  93. package/dist/cli/init/composer.js +6 -370
  94. package/dist/cli/init/composer.js.map +1 -1
  95. package/dist/cli/init/dependencies.d.ts +1 -2
  96. package/dist/cli/init/dependencies.js +4 -80
  97. package/dist/cli/init/dependencies.js.map +1 -1
  98. package/dist/cli/init/detect.js +5 -4
  99. package/dist/cli/init/detect.js.map +1 -1
  100. package/dist/cli/init/interactive.d.ts +1 -23
  101. package/dist/cli/init/interactive.js +33 -247
  102. package/dist/cli/init/interactive.js.map +1 -1
  103. package/dist/cli/init/profiles.d.ts +2 -2
  104. package/dist/cli/init/profiles.js +51 -245
  105. package/dist/cli/init/profiles.js.map +1 -1
  106. package/dist/cli/init/templates.d.ts +2 -37
  107. package/dist/cli/init/templates.js +25 -1237
  108. package/dist/cli/init/templates.js.map +1 -1
  109. package/dist/cli/list.d.ts +1 -1
  110. package/dist/cli/list.js +1 -38
  111. package/dist/cli/list.js.map +1 -1
  112. package/dist/cli/loader.d.ts +15 -1
  113. package/dist/cli/loader.js +56 -39
  114. package/dist/cli/loader.js.map +1 -1
  115. package/dist/cli/main.js +7 -2
  116. package/dist/cli/main.js.map +1 -1
  117. package/dist/cli/migrate-home/index.js +2 -2
  118. package/dist/cli/migrate-home/index.js.map +1 -1
  119. package/dist/cli/pack/add.js +1 -1
  120. package/dist/cli/pack/add.js.map +1 -1
  121. package/dist/cli/pack/hook-bootstrap.d.ts +5 -164
  122. package/dist/cli/pack/hook-bootstrap.js +15 -291
  123. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  124. package/dist/cli/pack/hook-branch-protection.d.ts +28 -21
  125. package/dist/cli/pack/hook-branch-protection.js +365 -340
  126. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  127. package/dist/cli/pack/index.d.ts +0 -1
  128. package/dist/cli/pack/index.js +0 -1
  129. package/dist/cli/pack/index.js.map +1 -1
  130. package/dist/cli/pack/list.js +1 -6
  131. package/dist/cli/pack/list.js.map +1 -1
  132. package/dist/cli/pack/mutate.d.ts +7 -9
  133. package/dist/cli/pack/mutate.js +6 -9
  134. package/dist/cli/pack/mutate.js.map +1 -1
  135. package/dist/cli/pack/reseed.d.ts +2 -2
  136. package/dist/cli/pack/reseed.js +16 -22
  137. package/dist/cli/pack/reseed.js.map +1 -1
  138. package/dist/cli/{delegate/register.d.ts → register-gate-group.d.ts} +1 -1
  139. package/dist/cli/register-gate-group.js +348 -0
  140. package/dist/cli/register-gate-group.js.map +1 -0
  141. package/dist/cli/register-inspect-group.d.ts +6 -0
  142. package/dist/cli/register-inspect-group.js +189 -0
  143. package/dist/cli/register-inspect-group.js.map +1 -0
  144. package/dist/cli/register-operator-lifecycle.d.ts +1 -1
  145. package/dist/cli/register-operator-lifecycle.js +3 -100
  146. package/dist/cli/register-operator-lifecycle.js.map +1 -1
  147. package/dist/cli/register-pack-group.d.ts +5 -0
  148. package/dist/cli/register-pack-group.js +134 -0
  149. package/dist/cli/register-pack-group.js.map +1 -0
  150. package/dist/cli/{register-audit-group.d.ts → register-setup-group.d.ts} +1 -1
  151. package/dist/cli/register-setup-group.js +495 -0
  152. package/dist/cli/register-setup-group.js.map +1 -0
  153. package/dist/cli/remove/index.d.ts +0 -14
  154. package/dist/cli/remove/index.js +2 -115
  155. package/dist/cli/remove/index.js.map +1 -1
  156. package/dist/cli/remove/mutate.d.ts +0 -2
  157. package/dist/cli/remove/mutate.js +1 -19
  158. package/dist/cli/remove/mutate.js.map +1 -1
  159. package/dist/cli/remove/register.js +2 -19
  160. package/dist/cli/remove/register.js.map +1 -1
  161. package/dist/cli/shell-quote.d.ts +7 -0
  162. package/dist/cli/shell-quote.js +17 -0
  163. package/dist/cli/shell-quote.js.map +1 -0
  164. package/dist/cli/uninstall/index.d.ts +4 -2
  165. package/dist/cli/uninstall/index.js +15 -7
  166. package/dist/cli/uninstall/index.js.map +1 -1
  167. package/dist/cli/uninstall/register.js +6 -4
  168. package/dist/cli/uninstall/register.js.map +1 -1
  169. package/dist/cli/uninstall/snapshot.d.ts +15 -0
  170. package/dist/cli/uninstall/snapshot.js +38 -0
  171. package/dist/cli/uninstall/snapshot.js.map +1 -1
  172. package/dist/cli/validate/checks.d.ts +0 -77
  173. package/dist/cli/validate/checks.js +3 -1049
  174. package/dist/cli/validate/checks.js.map +1 -1
  175. package/dist/cli/validate/index.js +19 -15
  176. package/dist/cli/validate/index.js.map +1 -1
  177. package/dist/index.d.ts +0 -3
  178. package/dist/index.js +0 -3
  179. package/dist/index.js.map +1 -1
  180. package/dist/io/claude-mcp.d.ts +1 -1
  181. package/dist/io/claude-mcp.js +27 -14
  182. package/dist/io/claude-mcp.js.map +1 -1
  183. package/dist/io/harness-lock.js +2 -2
  184. package/dist/io/harness-lock.js.map +1 -1
  185. package/dist/io/lock.d.ts +0 -26
  186. package/dist/io/lock.js +10 -27
  187. package/dist/io/lock.js.map +1 -1
  188. package/dist/io/merge-settings.js +6 -4
  189. package/dist/io/merge-settings.js.map +1 -1
  190. package/dist/io/project-name.d.ts +22 -39
  191. package/dist/io/project-name.js +25 -42
  192. package/dist/io/project-name.js.map +1 -1
  193. package/dist/io/read-regular-file.d.ts +103 -40
  194. package/dist/io/read-regular-file.js +196 -35
  195. package/dist/io/read-regular-file.js.map +1 -1
  196. package/dist/io/restart-hints.js +3 -6
  197. package/dist/io/restart-hints.js.map +1 -1
  198. package/dist/io/version-compare.d.ts +1 -3
  199. package/dist/io/version-compare.js +1 -3
  200. package/dist/io/version-compare.js.map +1 -1
  201. package/dist/io/write-regular-file.d.ts +30 -0
  202. package/dist/io/write-regular-file.js +85 -0
  203. package/dist/io/write-regular-file.js.map +1 -0
  204. package/dist/overrides/index.d.ts +1 -0
  205. package/dist/overrides/index.js +1 -0
  206. package/dist/overrides/index.js.map +1 -1
  207. package/dist/overrides/merge-layers.d.ts +43 -0
  208. package/dist/overrides/merge-layers.js +76 -0
  209. package/dist/overrides/merge-layers.js.map +1 -0
  210. package/dist/overrides/merge.d.ts +16 -0
  211. package/dist/overrides/merge.js +21 -8
  212. package/dist/overrides/merge.js.map +1 -1
  213. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -69
  214. package/dist/policy-packs/builtin/branch-protection-runtime.js +12 -84
  215. package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
  216. package/dist/policy-packs/builtin/branch-protection.d.ts +7 -30
  217. package/dist/policy-packs/builtin/branch-protection.js +56 -89
  218. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  219. package/dist/policy-packs/config-check.d.ts +7 -5
  220. package/dist/policy-packs/config-check.js +2 -2
  221. package/dist/policy-packs/config-check.js.map +1 -1
  222. package/dist/policy-packs/expand.d.ts +1 -3
  223. package/dist/policy-packs/expand.js +5 -42
  224. package/dist/policy-packs/expand.js.map +1 -1
  225. package/dist/policy-packs/index.d.ts +3 -3
  226. package/dist/policy-packs/index.js +2 -2
  227. package/dist/policy-packs/index.js.map +1 -1
  228. package/dist/policy-packs/registry.d.ts +9 -38
  229. package/dist/policy-packs/registry.js +5 -65
  230. package/dist/policy-packs/registry.js.map +1 -1
  231. package/dist/policy-packs/runtime.d.ts +0 -4
  232. package/dist/policy-packs/runtime.js +9 -22
  233. package/dist/policy-packs/runtime.js.map +1 -1
  234. package/dist/policy-packs/types.d.ts +0 -19
  235. package/dist/policy-packs/ux-compare.d.ts +1 -11
  236. package/dist/policy-packs/ux-compare.js +6 -42
  237. package/dist/policy-packs/ux-compare.js.map +1 -1
  238. package/dist/policy-packs/ux-drift-check.d.ts +5 -5
  239. package/dist/policy-packs/ux-drift-check.js +17 -22
  240. package/dist/policy-packs/ux-drift-check.js.map +1 -1
  241. package/dist/policy-packs/version-check.d.ts +5 -26
  242. package/dist/policy-packs/version-check.js +19 -74
  243. package/dist/policy-packs/version-check.js.map +1 -1
  244. package/dist/probes/memory.d.ts +1 -1
  245. package/dist/probes/memory.js +1 -1
  246. package/dist/probes/memory.js.map +1 -1
  247. package/dist/runtime/agent-facing.d.ts +9 -0
  248. package/dist/runtime/agent-facing.js +28 -14
  249. package/dist/runtime/agent-facing.js.map +1 -1
  250. package/dist/runtime/bounded-text.d.ts +15 -0
  251. package/dist/runtime/bounded-text.js +27 -0
  252. package/dist/runtime/bounded-text.js.map +1 -0
  253. package/dist/runtime/git-branch.d.ts +125 -0
  254. package/dist/runtime/git-branch.js +381 -0
  255. package/dist/runtime/git-branch.js.map +1 -0
  256. package/dist/runtime/hermetic-spawn-guard.d.ts +2 -2
  257. package/dist/runtime/hermetic-spawn-guard.js +24 -34
  258. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  259. package/dist/runtime/home-dir.js +3 -2
  260. package/dist/runtime/home-dir.js.map +1 -1
  261. package/dist/runtime/tool-name-aliases.d.ts +0 -6
  262. package/dist/runtime/tool-name-aliases.js +5 -18
  263. package/dist/runtime/tool-name-aliases.js.map +1 -1
  264. package/dist/schema/grounding.d.ts +7 -54
  265. package/dist/schema/grounding.js +10 -12
  266. package/dist/schema/grounding.js.map +1 -1
  267. package/dist/schema/hooks.d.ts +39 -136
  268. package/dist/schema/hooks.js +3 -6
  269. package/dist/schema/hooks.js.map +1 -1
  270. package/dist/schema/index.d.ts +114 -3828
  271. package/dist/schema/index.js +41 -80
  272. package/dist/schema/index.js.map +1 -1
  273. package/dist/schema/memory.d.ts +54 -131
  274. package/dist/schema/memory.js +2 -2
  275. package/dist/schema/memory.js.map +1 -1
  276. package/dist/schema/policy-packs.d.ts +3 -45
  277. package/dist/schema/policy-packs.js +7 -13
  278. package/dist/schema/policy-packs.js.map +1 -1
  279. package/dist/schema/policy-ux.d.ts +7 -0
  280. package/dist/schema/policy-ux.js +18 -0
  281. package/dist/schema/policy-ux.js.map +1 -0
  282. package/dist/schema/removed-keys.d.ts +185 -0
  283. package/dist/schema/removed-keys.js +531 -0
  284. package/dist/schema/removed-keys.js.map +1 -0
  285. package/dist/schema/tools.d.ts +30 -268
  286. package/dist/schema/tools.js +4 -4
  287. package/dist/schema/tools.js.map +1 -1
  288. package/package.json +5 -16
  289. package/dist/cli/approve/branch-protection.d.ts +0 -69
  290. package/dist/cli/approve/branch-protection.js +0 -144
  291. package/dist/cli/approve/branch-protection.js.map +0 -1
  292. package/dist/cli/approve/risk.d.ts +0 -112
  293. package/dist/cli/approve/risk.js +0 -197
  294. package/dist/cli/approve/risk.js.map +0 -1
  295. package/dist/cli/approve/stdin-report.d.ts +0 -57
  296. package/dist/cli/approve/stdin-report.js +0 -145
  297. package/dist/cli/approve/stdin-report.js.map +0 -1
  298. package/dist/cli/approve/understanding.d.ts +0 -410
  299. package/dist/cli/approve/understanding.js +0 -996
  300. package/dist/cli/approve/understanding.js.map +0 -1
  301. package/dist/cli/audit.d.ts +0 -80
  302. package/dist/cli/audit.js +0 -252
  303. package/dist/cli/audit.js.map +0 -1
  304. package/dist/cli/delegate/index.d.ts +0 -76
  305. package/dist/cli/delegate/index.js +0 -444
  306. package/dist/cli/delegate/index.js.map +0 -1
  307. package/dist/cli/delegate/register.js +0 -90
  308. package/dist/cli/delegate/register.js.map +0 -1
  309. package/dist/cli/doctor/auto-approve-mode.d.ts +0 -37
  310. package/dist/cli/doctor/auto-approve-mode.js +0 -80
  311. package/dist/cli/doctor/auto-approve-mode.js.map +0 -1
  312. package/dist/cli/doctor/bypass-without-auto-approve.d.ts +0 -38
  313. package/dist/cli/doctor/bypass-without-auto-approve.js +0 -95
  314. package/dist/cli/doctor/bypass-without-auto-approve.js.map +0 -1
  315. package/dist/cli/doctor/codex-config-drift.d.ts +0 -28
  316. package/dist/cli/doctor/codex-config-drift.js +0 -240
  317. package/dist/cli/doctor/codex-config-drift.js.map +0 -1
  318. package/dist/cli/doctor/expire-on-tool-match.d.ts +0 -15
  319. package/dist/cli/doctor/expire-on-tool-match.js +0 -58
  320. package/dist/cli/doctor/expire-on-tool-match.js.map +0 -1
  321. package/dist/cli/doctor/rogue-ledger.d.ts +0 -53
  322. package/dist/cli/doctor/rogue-ledger.js +0 -153
  323. package/dist/cli/doctor/rogue-ledger.js.map +0 -1
  324. package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +0 -83
  325. package/dist/cli/doctor/session-start-preflight-setup-version.js +0 -114
  326. package/dist/cli/doctor/session-start-preflight-setup-version.js.map +0 -1
  327. package/dist/cli/doctor/settings-drift.d.ts +0 -25
  328. package/dist/cli/doctor/settings-drift.js +0 -213
  329. package/dist/cli/doctor/settings-drift.js.map +0 -1
  330. package/dist/cli/doctor/toolchain-parity.d.ts +0 -63
  331. package/dist/cli/doctor/toolchain-parity.js +0 -154
  332. package/dist/cli/doctor/toolchain-parity.js.map +0 -1
  333. package/dist/cli/doctor/ug-auto-approvals.d.ts +0 -40
  334. package/dist/cli/doctor/ug-auto-approvals.js +0 -91
  335. package/dist/cli/doctor/ug-auto-approvals.js.map +0 -1
  336. package/dist/cli/doctor/ug-delegations.d.ts +0 -26
  337. package/dist/cli/doctor/ug-delegations.js +0 -121
  338. package/dist/cli/doctor/ug-delegations.js.map +0 -1
  339. package/dist/cli/doctor/ug-inflight.d.ts +0 -35
  340. package/dist/cli/doctor/ug-inflight.js +0 -57
  341. package/dist/cli/doctor/ug-inflight.js.map +0 -1
  342. package/dist/cli/doctor/understanding-mode-env.d.ts +0 -51
  343. package/dist/cli/doctor/understanding-mode-env.js +0 -98
  344. package/dist/cli/doctor/understanding-mode-env.js.map +0 -1
  345. package/dist/cli/dry-run.d.ts +0 -58
  346. package/dist/cli/dry-run.js +0 -283
  347. package/dist/cli/dry-run.js.map +0 -1
  348. package/dist/cli/enriched-event.d.ts +0 -35
  349. package/dist/cli/enriched-event.js +0 -60
  350. package/dist/cli/enriched-event.js.map +0 -1
  351. package/dist/cli/event-input.d.ts +0 -28
  352. package/dist/cli/event-input.js +0 -73
  353. package/dist/cli/event-input.js.map +0 -1
  354. package/dist/cli/explain-action.d.ts +0 -25
  355. package/dist/cli/explain-action.js +0 -57
  356. package/dist/cli/explain-action.js.map +0 -1
  357. package/dist/cli/explain-policy.d.ts +0 -147
  358. package/dist/cli/explain-policy.js +0 -248
  359. package/dist/cli/explain-policy.js.map +0 -1
  360. package/dist/cli/explain.d.ts +0 -31
  361. package/dist/cli/explain.js +0 -159
  362. package/dist/cli/explain.js.map +0 -1
  363. package/dist/cli/gc/index.d.ts +0 -63
  364. package/dist/cli/gc/index.js +0 -566
  365. package/dist/cli/gc/index.js.map +0 -1
  366. package/dist/cli/pack/approve-escape.d.ts +0 -19
  367. package/dist/cli/pack/approve-escape.js +0 -190
  368. package/dist/cli/pack/approve-escape.js.map +0 -1
  369. package/dist/cli/pack/auto-approve-path.d.ts +0 -146
  370. package/dist/cli/pack/auto-approve-path.js +0 -462
  371. package/dist/cli/pack/auto-approve-path.js.map +0 -1
  372. package/dist/cli/pack/hook-codex-post-tool-use.d.ts +0 -25
  373. package/dist/cli/pack/hook-codex-post-tool-use.js +0 -160
  374. package/dist/cli/pack/hook-codex-post-tool-use.js.map +0 -1
  375. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +0 -48
  376. package/dist/cli/pack/hook-codex-pre-tool-use.js +0 -474
  377. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +0 -1
  378. package/dist/cli/pack/hook-codex-stop.d.ts +0 -35
  379. package/dist/cli/pack/hook-codex-stop.js +0 -329
  380. package/dist/cli/pack/hook-codex-stop.js.map +0 -1
  381. package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -24
  382. package/dist/cli/pack/hook-codex-user-prompt-submit.js +0 -93
  383. package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +0 -1
  384. package/dist/cli/pack/hook-post-merge-gate-record.d.ts +0 -37
  385. package/dist/cli/pack/hook-post-merge-gate-record.js +0 -190
  386. package/dist/cli/pack/hook-post-merge-gate-record.js.map +0 -1
  387. package/dist/cli/pack/hook-post-merge-gate.d.ts +0 -34
  388. package/dist/cli/pack/hook-post-merge-gate.js +0 -277
  389. package/dist/cli/pack/hook-post-merge-gate.js.map +0 -1
  390. package/dist/cli/pack/hook-post-tool-use.d.ts +0 -45
  391. package/dist/cli/pack/hook-post-tool-use.js +0 -137
  392. package/dist/cli/pack/hook-post-tool-use.js.map +0 -1
  393. package/dist/cli/pack/hook-pre-tool-use.d.ts +0 -92
  394. package/dist/cli/pack/hook-pre-tool-use.js +0 -1328
  395. package/dist/cli/pack/hook-pre-tool-use.js.map +0 -1
  396. package/dist/cli/pack/hook-runtime-reality.d.ts +0 -63
  397. package/dist/cli/pack/hook-runtime-reality.js +0 -210
  398. package/dist/cli/pack/hook-runtime-reality.js.map +0 -1
  399. package/dist/cli/pack/hook-solution-acceptance-writeguard.d.ts +0 -32
  400. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +0 -438
  401. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +0 -1
  402. package/dist/cli/pack/hook-solution-acceptance.d.ts +0 -157
  403. package/dist/cli/pack/hook-solution-acceptance.js +0 -530
  404. package/dist/cli/pack/hook-solution-acceptance.js.map +0 -1
  405. package/dist/cli/pack/hook-stay-in-scope.d.ts +0 -37
  406. package/dist/cli/pack/hook-stay-in-scope.js +0 -128
  407. package/dist/cli/pack/hook-stay-in-scope.js.map +0 -1
  408. package/dist/cli/pack/hook-subagent-start.d.ts +0 -23
  409. package/dist/cli/pack/hook-subagent-start.js +0 -88
  410. package/dist/cli/pack/hook-subagent-start.js.map +0 -1
  411. package/dist/cli/pack/hook-subagent-stop.d.ts +0 -19
  412. package/dist/cli/pack/hook-subagent-stop.js +0 -41
  413. package/dist/cli/pack/hook-subagent-stop.js.map +0 -1
  414. package/dist/cli/pack/hook-track-active-claim.d.ts +0 -27
  415. package/dist/cli/pack/hook-track-active-claim.js +0 -275
  416. package/dist/cli/pack/hook-track-active-claim.js.map +0 -1
  417. package/dist/cli/pack/transcript-report-scan.d.ts +0 -71
  418. package/dist/cli/pack/transcript-report-scan.js +0 -359
  419. package/dist/cli/pack/transcript-report-scan.js.map +0 -1
  420. package/dist/cli/pack/understanding-report-schema-hint.d.ts +0 -26
  421. package/dist/cli/pack/understanding-report-schema-hint.js +0 -120
  422. package/dist/cli/pack/understanding-report-schema-hint.js.map +0 -1
  423. package/dist/cli/pack/upgrade.d.ts +0 -32
  424. package/dist/cli/pack/upgrade.js +0 -273
  425. package/dist/cli/pack/upgrade.js.map +0 -1
  426. package/dist/cli/pause/index.d.ts +0 -113
  427. package/dist/cli/pause/index.js +0 -251
  428. package/dist/cli/pause/index.js.map +0 -1
  429. package/dist/cli/pause-check.d.ts +0 -22
  430. package/dist/cli/pause-check.js +0 -54
  431. package/dist/cli/pause-check.js.map +0 -1
  432. package/dist/cli/policy/intercept.d.ts +0 -233
  433. package/dist/cli/policy/intercept.js +0 -1022
  434. package/dist/cli/policy/intercept.js.map +0 -1
  435. package/dist/cli/policy/risk-envelope-enrichment.d.ts +0 -40
  436. package/dist/cli/policy/risk-envelope-enrichment.js +0 -110
  437. package/dist/cli/policy/risk-envelope-enrichment.js.map +0 -1
  438. package/dist/cli/record/index.d.ts +0 -81
  439. package/dist/cli/record/index.js +0 -348
  440. package/dist/cli/record/index.js.map +0 -1
  441. package/dist/cli/register-audit-group.js +0 -91
  442. package/dist/cli/register-audit-group.js.map +0 -1
  443. package/dist/cli/resolve-env.d.ts +0 -32
  444. package/dist/cli/resolve-env.js +0 -46
  445. package/dist/cli/resolve-env.js.map +0 -1
  446. package/dist/cli/session-export/index.d.ts +0 -46
  447. package/dist/cli/session-export/index.js +0 -169
  448. package/dist/cli/session-export/index.js.map +0 -1
  449. package/dist/cli/session-export/redact.d.ts +0 -22
  450. package/dist/cli/session-export/redact.js +0 -47
  451. package/dist/cli/session-export/redact.js.map +0 -1
  452. package/dist/cli/session-export/transcript.d.ts +0 -24
  453. package/dist/cli/session-export/transcript.js +0 -164
  454. package/dist/cli/session-export/transcript.js.map +0 -1
  455. package/dist/cli/session-start/branch-check.d.ts +0 -18
  456. package/dist/cli/session-start/branch-check.js +0 -140
  457. package/dist/cli/session-start/branch-check.js.map +0 -1
  458. package/dist/cli/session-start/index.d.ts +0 -121
  459. package/dist/cli/session-start/index.js +0 -591
  460. package/dist/cli/session-start/index.js.map +0 -1
  461. package/dist/cli/session-start/shared-options.d.ts +0 -103
  462. package/dist/cli/session-start/shared-options.js +0 -107
  463. package/dist/cli/session-start/shared-options.js.map +0 -1
  464. package/dist/cli/session-start/stale-base-check.d.ts +0 -46
  465. package/dist/cli/session-start/stale-base-check.js +0 -352
  466. package/dist/cli/session-start/stale-base-check.js.map +0 -1
  467. package/dist/cli/session-start/toolchain-parity.d.ts +0 -267
  468. package/dist/cli/session-start/toolchain-parity.js +0 -797
  469. package/dist/cli/session-start/toolchain-parity.js.map +0 -1
  470. package/dist/cli/smoke/assertions.d.ts +0 -46
  471. package/dist/cli/smoke/assertions.js +0 -170
  472. package/dist/cli/smoke/assertions.js.map +0 -1
  473. package/dist/cli/smoke/index.d.ts +0 -76
  474. package/dist/cli/smoke/index.js +0 -245
  475. package/dist/cli/smoke/index.js.map +0 -1
  476. package/dist/cli/smoke/runner.d.ts +0 -55
  477. package/dist/cli/smoke/runner.js +0 -134
  478. package/dist/cli/smoke/runner.js.map +0 -1
  479. package/dist/cli/smoke/stream-parser.d.ts +0 -65
  480. package/dist/cli/smoke/stream-parser.js +0 -115
  481. package/dist/cli/smoke/stream-parser.js.map +0 -1
  482. package/dist/cli/test-risk.d.ts +0 -27
  483. package/dist/cli/test-risk.js +0 -38
  484. package/dist/cli/test-risk.js.map +0 -1
  485. package/dist/io/display-path.d.ts +0 -11
  486. package/dist/io/display-path.js +0 -29
  487. package/dist/io/display-path.js.map +0 -1
  488. package/dist/io/duration.d.ts +0 -5
  489. package/dist/io/duration.js +0 -50
  490. package/dist/io/duration.js.map +0 -1
  491. package/dist/io/extract.d.ts +0 -79
  492. package/dist/io/extract.js +0 -226
  493. package/dist/io/extract.js.map +0 -1
  494. package/dist/io/ledger-record.d.ts +0 -83
  495. package/dist/io/ledger-record.js +0 -301
  496. package/dist/io/ledger-record.js.map +0 -1
  497. package/dist/io/read-json-dir-entries.d.ts +0 -36
  498. package/dist/io/read-json-dir-entries.js +0 -48
  499. package/dist/io/read-json-dir-entries.js.map +0 -1
  500. package/dist/io/safe-json-parse.d.ts +0 -5
  501. package/dist/io/safe-json-parse.js +0 -21
  502. package/dist/io/safe-json-parse.js.map +0 -1
  503. package/dist/policies/index.d.ts +0 -5
  504. package/dist/policies/index.js +0 -6
  505. package/dist/policies/index.js.map +0 -1
  506. package/dist/policies/ledger-client.d.ts +0 -113
  507. package/dist/policies/ledger-client.js +0 -500
  508. package/dist/policies/ledger-client.js.map +0 -1
  509. package/dist/policies/producers.d.ts +0 -12
  510. package/dist/policies/producers.js +0 -61
  511. package/dist/policies/producers.js.map +0 -1
  512. package/dist/policies/requires.d.ts +0 -75
  513. package/dist/policies/requires.js +0 -240
  514. package/dist/policies/requires.js.map +0 -1
  515. package/dist/policies/timestamp.d.ts +0 -14
  516. package/dist/policies/timestamp.js +0 -36
  517. package/dist/policies/timestamp.js.map +0 -1
  518. package/dist/policy-packs/builtin/permission-profiles.d.ts +0 -11
  519. package/dist/policy-packs/builtin/permission-profiles.js +0 -74
  520. package/dist/policy-packs/builtin/permission-profiles.js.map +0 -1
  521. package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +0 -286
  522. package/dist/policy-packs/builtin/post-merge-gate-runtime.js +0 -397
  523. package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +0 -1
  524. package/dist/policy-packs/builtin/post-merge-gate.d.ts +0 -56
  525. package/dist/policy-packs/builtin/post-merge-gate.js +0 -347
  526. package/dist/policy-packs/builtin/post-merge-gate.js.map +0 -1
  527. package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +0 -50
  528. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +0 -151
  529. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +0 -1
  530. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +0 -323
  531. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +0 -672
  532. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +0 -1
  533. package/dist/policy-packs/builtin/solution-acceptance.d.ts +0 -45
  534. package/dist/policy-packs/builtin/solution-acceptance.js +0 -226
  535. package/dist/policy-packs/builtin/solution-acceptance.js.map +0 -1
  536. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +0 -25
  537. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +0 -94
  538. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +0 -1
  539. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +0 -35
  540. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +0 -86
  541. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +0 -1
  542. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +0 -210
  543. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +0 -361
  544. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +0 -1
  545. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +0 -304
  546. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +0 -734
  547. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +0 -1
  548. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.d.ts +0 -36
  549. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js +0 -73
  550. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js.map +0 -1
  551. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +0 -19
  552. package/dist/policy-packs/builtin/understanding-before-execution/index.js +0 -83
  553. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +0 -1
  554. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.d.ts +0 -135
  555. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +0 -643
  556. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +0 -1
  557. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +0 -25
  558. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +0 -56
  559. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +0 -1
  560. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +0 -20
  561. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +0 -106
  562. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +0 -1
  563. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +0 -126
  564. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +0 -245
  565. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +0 -1
  566. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +0 -29
  567. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +0 -82
  568. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +0 -1
  569. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +0 -71
  570. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +0 -175
  571. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +0 -1
  572. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +0 -625
  573. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +0 -1076
  574. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +0 -1
  575. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +0 -128
  576. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +0 -213
  577. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +0 -1
  578. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +0 -129
  579. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +0 -166
  580. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +0 -1
  581. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +0 -164
  582. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +0 -275
  583. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +0 -1
  584. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +0 -1
  585. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +0 -10
  586. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +0 -1
  587. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +0 -526
  588. package/dist/policy-packs/builtin/understanding-before-execution.js +0 -1078
  589. package/dist/policy-packs/builtin/understanding-before-execution.js.map +0 -1
  590. package/dist/policy-packs/permission-translator.d.ts +0 -9
  591. package/dist/policy-packs/permission-translator.js +0 -76
  592. package/dist/policy-packs/permission-translator.js.map +0 -1
  593. package/dist/runtime/action-envelope.d.ts +0 -64
  594. package/dist/runtime/action-envelope.js +0 -46
  595. package/dist/runtime/action-envelope.js.map +0 -1
  596. package/dist/runtime/approval-signing.d.ts +0 -111
  597. package/dist/runtime/approval-signing.js +0 -384
  598. package/dist/runtime/approval-signing.js.map +0 -1
  599. package/dist/runtime/bash-match-facts.d.ts +0 -60
  600. package/dist/runtime/bash-match-facts.js +0 -196
  601. package/dist/runtime/bash-match-facts.js.map +0 -1
  602. package/dist/runtime/bash-match-registry.d.ts +0 -112
  603. package/dist/runtime/bash-match-registry.js +0 -406
  604. package/dist/runtime/bash-match-registry.js.map +0 -1
  605. package/dist/runtime/bash-prefix-parse.d.ts +0 -31
  606. package/dist/runtime/bash-prefix-parse.js +0 -576
  607. package/dist/runtime/bash-prefix-parse.js.map +0 -1
  608. package/dist/runtime/command-normalize.d.ts +0 -658
  609. package/dist/runtime/command-normalize.js +0 -2410
  610. package/dist/runtime/command-normalize.js.map +0 -1
  611. package/dist/runtime/deletion-target-resolve.d.ts +0 -28
  612. package/dist/runtime/deletion-target-resolve.js +0 -1078
  613. package/dist/runtime/deletion-target-resolve.js.map +0 -1
  614. package/dist/runtime/destructive-shell-floor.d.ts +0 -19
  615. package/dist/runtime/destructive-shell-floor.js +0 -611
  616. package/dist/runtime/destructive-shell-floor.js.map +0 -1
  617. package/dist/runtime/environment-resolver.d.ts +0 -36
  618. package/dist/runtime/environment-resolver.js +0 -138
  619. package/dist/runtime/environment-resolver.js.map +0 -1
  620. package/dist/runtime/git-context.d.ts +0 -201
  621. package/dist/runtime/git-context.js +0 -422
  622. package/dist/runtime/git-context.js.map +0 -1
  623. package/dist/runtime/index.d.ts +0 -12
  624. package/dist/runtime/index.js +0 -13
  625. package/dist/runtime/index.js.map +0 -1
  626. package/dist/runtime/intercept.d.ts +0 -623
  627. package/dist/runtime/intercept.js +0 -1384
  628. package/dist/runtime/intercept.js.map +0 -1
  629. package/dist/runtime/kube-context.d.ts +0 -16
  630. package/dist/runtime/kube-context.js +0 -63
  631. package/dist/runtime/kube-context.js.map +0 -1
  632. package/dist/runtime/kubectl-target-parse.d.ts +0 -31
  633. package/dist/runtime/kubectl-target-parse.js +0 -249
  634. package/dist/runtime/kubectl-target-parse.js.map +0 -1
  635. package/dist/runtime/ledger-add.d.ts +0 -16
  636. package/dist/runtime/ledger-add.js +0 -150
  637. package/dist/runtime/ledger-add.js.map +0 -1
  638. package/dist/runtime/ledger-writer.d.ts +0 -27
  639. package/dist/runtime/ledger-writer.js +0 -46
  640. package/dist/runtime/ledger-writer.js.map +0 -1
  641. package/dist/runtime/pause-sentinel.d.ts +0 -60
  642. package/dist/runtime/pause-sentinel.js +0 -156
  643. package/dist/runtime/pause-sentinel.js.map +0 -1
  644. package/dist/runtime/pending-approval.d.ts +0 -23
  645. package/dist/runtime/pending-approval.js +0 -67
  646. package/dist/runtime/pending-approval.js.map +0 -1
  647. package/dist/runtime/read-only-bash.d.ts +0 -141
  648. package/dist/runtime/read-only-bash.js +0 -2204
  649. package/dist/runtime/read-only-bash.js.map +0 -1
  650. package/dist/runtime/recovery-git-commit.d.ts +0 -22
  651. package/dist/runtime/recovery-git-commit.js +0 -284
  652. package/dist/runtime/recovery-git-commit.js.map +0 -1
  653. package/dist/runtime/reject-malformed-session-id.d.ts +0 -8
  654. package/dist/runtime/reject-malformed-session-id.js +0 -16
  655. package/dist/runtime/reject-malformed-session-id.js.map +0 -1
  656. package/dist/runtime/risk-classifier.d.ts +0 -38
  657. package/dist/runtime/risk-classifier.js +0 -311
  658. package/dist/runtime/risk-classifier.js.map +0 -1
  659. package/dist/runtime/session-id.d.ts +0 -113
  660. package/dist/runtime/session-id.js +0 -193
  661. package/dist/runtime/session-id.js.map +0 -1
  662. package/dist/runtime/shell-word.d.ts +0 -62
  663. package/dist/runtime/shell-word.js +0 -410
  664. package/dist/runtime/shell-word.js.map +0 -1
  665. package/dist/runtime/task-providers/agent-tasks.d.ts +0 -105
  666. package/dist/runtime/task-providers/agent-tasks.js +0 -247
  667. package/dist/runtime/task-providers/agent-tasks.js.map +0 -1
  668. package/dist/runtime/when-eval.d.ts +0 -62
  669. package/dist/runtime/when-eval.js +0 -212
  670. package/dist/runtime/when-eval.js.map +0 -1
  671. package/dist/runtime/workflow-policies.d.ts +0 -207
  672. package/dist/runtime/workflow-policies.js +0 -596
  673. package/dist/runtime/workflow-policies.js.map +0 -1
  674. package/dist/schema/audit.d.ts +0 -71
  675. package/dist/schema/audit.js +0 -32
  676. package/dist/schema/audit.js.map +0 -1
  677. package/dist/schema/doctor.d.ts +0 -25
  678. package/dist/schema/doctor.js +0 -23
  679. package/dist/schema/doctor.js.map +0 -1
  680. package/dist/schema/environments.d.ts +0 -215
  681. package/dist/schema/environments.js +0 -101
  682. package/dist/schema/environments.js.map +0 -1
  683. package/dist/schema/extract.d.ts +0 -9
  684. package/dist/schema/extract.js +0 -86
  685. package/dist/schema/extract.js.map +0 -1
  686. package/dist/schema/permission-profiles.d.ts +0 -2292
  687. package/dist/schema/permission-profiles.js +0 -60
  688. package/dist/schema/permission-profiles.js.map +0 -1
  689. package/dist/schema/policies.d.ts +0 -974
  690. package/dist/schema/policies.js +0 -298
  691. package/dist/schema/policies.js.map +0 -1
  692. package/dist/schema/requires.d.ts +0 -131
  693. package/dist/schema/requires.js +0 -88
  694. package/dist/schema/requires.js.map +0 -1
  695. package/dist/schema/risk.d.ts +0 -144
  696. package/dist/schema/risk.js +0 -194
  697. package/dist/schema/risk.js.map +0 -1
  698. package/dist/schema/session-start-preflight.d.ts +0 -10
  699. package/dist/schema/session-start-preflight.js +0 -106
  700. package/dist/schema/session-start-preflight.js.map +0 -1
  701. package/dist/schema/stale-base-check.d.ts +0 -18
  702. package/dist/schema/stale-base-check.js +0 -37
  703. package/dist/schema/stale-base-check.js.map +0 -1
  704. package/dist/schema/toolchain-parity.d.ts +0 -21
  705. package/dist/schema/toolchain-parity.js +0 -48
  706. package/dist/schema/toolchain-parity.js.map +0 -1
  707. package/dist/schema/workflows.d.ts +0 -519
  708. package/dist/schema/workflows.js +0 -81
  709. package/dist/schema/workflows.js.map +0 -1
  710. package/scripts/runtime-reality-docker-probe.mjs +0 -71
@@ -1,1328 +0,0 @@
1
- // Phase 6 #4 — `harness pack hook pre-tool-use [--pack <name>]` runtime verb.
2
- //
3
- // PreToolUse blocker for pack-driven gates. Wired by the
4
- // understanding-before-execution pack's hook contribution; receives the
5
- // Claude Code event JSON on stdin, consults the signed operator approval
6
- // marker (the only approval authority; the persisted JSON report and the
7
- // evidence ledger are audit evidence, task 7402301d), emits a
8
- // `{decision: "block"}` JSON to stdout when no valid marker approves.
9
- //
10
- // Why a new CLI verb (vs reusing `harness policy intercept`): the
11
- // existing intercept layer evaluates `policies[]` against `requires`,
12
- // which is purely ledger-based, while the gate has a pack-specific
13
- // signed-marker source the generic requires evaluator does not model
14
- // (task 7402301d); bolting it in would leak pack-specific semantics
15
- // into the generic policy layer. This verb lives next to the pack instead.
16
- //
17
- // Failure mode: any error in load / parse / ledger / report scan
18
- // resolves to ALLOW (exit 0, silent). The Understanding Gate is opt-in;
19
- // turning a bug in this code into a session-wide tool block would be
20
- // hostile. The npm package's own standalone blocker still runs as a
21
- // secondary safety net for solo users, and `harness explain --trace`
22
- // (Phase 4 #6) surfaces the runtime audit trail when configured.
23
- //
24
- // SLICE 3 (agent-tasks 37ad0b05) adds the delegation-path capture of the
25
- // child's own report from its session transcript. Two rules of that path
26
- // live here rather than in the modules it calls:
27
- //
28
- // - ONE TRANSCRIPT ENTRY IS ADOPTED AT MOST ONCE PER SESSION. Every
29
- // adopted entry's id is appended to
30
- // `<generatedDir>/.delegation-adoptions/<sid>` BEFORE the capture is
31
- // persisted, and the scan is given that set. Without it the same
32
- // report would be re-captured and re-minted every time the auto-marker
33
- // expired (or a task boundary cleared it), so the delegation's TTL
34
- // would silently replace the marker's.
35
- // - A CAPTURE THAT DOES NOT PARSE writes a parse-error log, once per
36
- // failed capture, through the same `persistStdinReport` the operator
37
- // path uses. That log is what the block envelope's malformed-sections
38
- // notice is rendered from, so the per-capture write is deliberate.
39
- //
40
- // FOLLOW-UP (agent-tasks 49d1ee41) adds the second report channel the
41
- // ADR's "Report capture under `-p`" fallback describes: a report-bound
42
- // delegation (one `harness delegate --report <path>` minted) is verified
43
- // against the CONVENTIONAL launcher-report path
44
- // (`delegationReportPathFor`, a plain function of the child session id,
45
- // never a value read from the payload), and on success that file's
46
- // content is persisted the same way a transcript hit is: adopted once
47
- // (this time keyed by the report's CONTENT hash, in the same
48
- // `.delegation-adoptions/` ledger, so the same launcher report cannot
49
- // mint a second marker once the first one expires), persisted `pending`
50
- // via `persistStdinReport`, and left for the auto path below to consume.
51
- // The transcript scan never runs for a report-bound delegation: the two
52
- // channels are mutually exclusive per delegation, decided by whether
53
- // `verifyDelegation` returns a `reportPathHash`.
54
- import { appendFileSync, existsSync, lstatSync, mkdirSync } from "node:fs";
55
- import * as path from "node:path";
56
- import { readRegularFileRejectingSymlink } from "../../io/read-regular-file.js";
57
- import { queryLedgerByTag, } from "../../policies/index.js";
58
- import { renderProducers } from "../../policies/producers.js";
59
- import { ADOPTION_LEDGER_DIRNAME, CLAUDE_CODE_HARNESS, approvalExpiryNotice, checkOperatorApprovalMarkers, checkPersistedReport, defaultReportsDir, delegationMarkerPathFor, delegationReportPathFor, describeMarkerTtlExpiry, harnessAllowed, listPersistedReportsBoundedWithSkips, MAX_HOOK_LISTING_ENTRIES, matchLedgerEntries, noApprovalMarkerReason, parseAutoApprove, readActiveClaim, recordPermissionModeObservation, sanitizeForDisplay, selectNewestStrictSessionReport, verifyDelegation, verifyInflightRecord, verifyMatchedMarkerReport, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
60
- import { resolveMode, toPackageMode, } from "../../policy-packs/builtin/understanding-before-execution.js";
61
- import { sha256Hex, signingKeyExists } from "../../runtime/approval-signing.js";
62
- import { findLatestParseError, renderMalformedSectionsNotice } from "../approve/understanding.js";
63
- import { persistStdinReport } from "../approve/stdin-report.js";
64
- import { attemptAutoApproval, AUTO_APPROVE_LEDGER_SOURCE } from "./auto-approve-path.js";
65
- import { scanTranscriptForReport } from "./transcript-report-scan.js";
66
- import { resolveGeneratedDir, writePendingApproval, } from "../../runtime/pending-approval.js";
67
- import { resolveManifestLedgerWriter } from "../../runtime/ledger-writer.js";
68
- import { isReadOnlyBashPipeline } from "../../runtime/read-only-bash.js";
69
- import { isRecoveryGitCommit } from "../../runtime/recovery-git-commit.js";
70
- import { ProducerSchema, } from "../../schema/index.js";
71
- import { renderAgentFacing } from "../../runtime/agent-facing.js";
72
- import { z } from "zod";
73
- import { isEscapeCommand } from "./approve-escape.js";
74
- import { checkHookPause, loadManifestOrInjected, parseConfigUx, readStdin, runGateWithStdinRefusal, } from "./hook-bootstrap.js";
75
- import { stdinTimeoutBlockJson } from "../bounded-stdin.js";
76
- import { renderReportSchemaHint } from "./understanding-report-schema-hint.js";
77
- const PACK_NAME = "understanding-before-execution";
78
- /**
79
- * The sentence appended to the standard block reason when a valid
80
- * delegation was present but the child's own report had not reached the
81
- * transcript within `auto_approve.report_scan.max_wait` (ADR "Report
82
- * capture under `-p`": past the bound the hook blocks with an instruction
83
- * that asks for REPEATED retries, because a text asking for a single
84
- * retry was measured to leave the outcome to run-to-run variation).
85
- *
86
- * Deliberately does NOT pin a heading level. The same block envelope
87
- * already carries `renderReportSchemaHint`'s "any heading level (#, ##,
88
- * ###)" rule, and naming one level here would give the agent two
89
- * different heading rules in one deny text.
90
- */
91
- export const DELEGATION_REPORT_RETRY_INSTRUCTION = "Emit or re-emit your Understanding Report as an `Understanding Report` section (any heading level), then retry this command; if it is denied again, retry again.";
92
- function findGroundingMcp(manifest) {
93
- return manifest.tools.mcp.find((m) => m.name === "grounding-mcp") ?? null;
94
- }
95
- // `ADOPTION_LEDGER_DIRNAME` (the once-per-session ADOPTED-ENTRY ledger
96
- // dirname, `.delegation-adoptions/`, a SIBLING of `.delegations/`, never
97
- // inside it or `.approvals/`) now lives in delegation-markers.ts, next to
98
- // `DELEGATION_MARKER_DIRNAME`: `harness gc`'s delegation sweep
99
- // (task 3ece079d) needs the same constant to find orphaned ledgers, and a
100
- // reader that never writes one has no business importing this CLI verb
101
- // module for it. One id per line; ids come from the scan (`entryId`),
102
- // which guarantees they carry no line break.
103
- function adoptedEntriesPathFor(generatedDir, childSessionId) {
104
- return path.join(generatedDir, ADOPTION_LEDGER_DIRNAME, childSessionId);
105
- }
106
- /**
107
- * The entry ids this session has already spent. An absent file is the
108
- * ordinary first-call case and reads as the empty set; every OTHER read
109
- * failure fails closed with a result the caller declines on, because a
110
- * ledger we cannot read cannot prove that the next transcript hit has not
111
- * already been adopted. Goes through the same symlink-rejecting reader
112
- * every other gate marker uses (`readRegularFileRejectingSymlink`):
113
- * without it a symlink planted at this path would have the ledger read
114
- * follow it to an arbitrary target the agent controls, and an empty or
115
- * missing target there would read as "nothing adopted yet", the same
116
- * open the delegation and approval marker reads already close.
117
- */
118
- function readAdoptedEntries(generatedDir, childSessionId) {
119
- const ledgerDir = path.join(generatedDir, ADOPTION_LEDGER_DIRNAME);
120
- let ledgerDirStat;
121
- try {
122
- ledgerDirStat = lstatSync(ledgerDir);
123
- }
124
- catch {
125
- return { ok: true, ids: new Set() };
126
- }
127
- if (!ledgerDirStat.isDirectory()) {
128
- return { ok: false, detail: `containment refusal: ${ledgerDir} is not a plain directory` };
129
- }
130
- const filePath = adoptedEntriesPathFor(generatedDir, childSessionId);
131
- const read = readRegularFileRejectingSymlink(filePath);
132
- if (read.kind === "missing")
133
- return { ok: true, ids: new Set() };
134
- if (read.kind === "symlink") {
135
- return { ok: false, detail: `${filePath} is a symlink, refusing for safety` };
136
- }
137
- if (read.kind === "not-regular") {
138
- return { ok: false, detail: `${filePath} is not a regular file` };
139
- }
140
- if (read.kind !== "ok") {
141
- return { ok: false, detail: `${filePath} exists but could not be read (I/O error)` };
142
- }
143
- const ids = new Set();
144
- for (const line of read.content.split("\n")) {
145
- const id = line.trim();
146
- if (id.length > 0)
147
- ids.add(id);
148
- }
149
- return { ok: true, ids };
150
- }
151
- /**
152
- * Append one adopted entry id, creating `.delegation-adoptions/` on first
153
- * use with the same default directory mode `atomicWriteFile` gives
154
- * `.delegations/` itself (the ledger FILE is 0600, like every marker).
155
- * `appendFileSync` opens with `O_APPEND`, so a single short write lands
156
- * whole even if two hooks race on the same session; no read-modify-write,
157
- * therefore nothing to lose. `lstatSync` (NOT `existsSync`/`statSync`)
158
- * gates the append the same way `readRegularFileRejectingSymlink` gates
159
- * the read: a symlink planted at this path would otherwise have
160
- * `appendFileSync` follow it and write the adoption record through to an
161
- * arbitrary target, same class of defense as the read side above.
162
- */
163
- function recordAdoptedEntry(generatedDir, childSessionId, entryId) {
164
- try {
165
- const ledgerDir = path.join(generatedDir, ADOPTION_LEDGER_DIRNAME);
166
- let ledgerDirStat;
167
- try {
168
- ledgerDirStat = lstatSync(ledgerDir);
169
- }
170
- catch {
171
- mkdirSync(ledgerDir, { recursive: true });
172
- ledgerDirStat = lstatSync(ledgerDir);
173
- }
174
- if (!ledgerDirStat.isDirectory()) {
175
- return {
176
- ok: false,
177
- detail: `containment refusal: ${ledgerDir} is not a plain directory`,
178
- };
179
- }
180
- const filePath = adoptedEntriesPathFor(generatedDir, childSessionId);
181
- let priorStat;
182
- try {
183
- priorStat = lstatSync(filePath);
184
- }
185
- catch {
186
- priorStat = undefined;
187
- }
188
- if (priorStat !== undefined && !priorStat.isFile()) {
189
- return {
190
- ok: false,
191
- detail: `${filePath} exists and is not a regular file, refusing to append through it`,
192
- };
193
- }
194
- appendFileSync(filePath, `${entryId}\n`, { mode: 0o600 });
195
- return { ok: true };
196
- }
197
- catch (err) {
198
- return { ok: false, detail: err.message };
199
- }
200
- }
201
- // The Claude Code "block" envelope. Mirrors the runtime/intercept.ts
202
- // shape (PR #81): `decision: "block"` keeps legacy 2.0.x CLIs blocking,
203
- // `hookSpecificOutput.permissionDecision: "deny"` is the Claude Code
204
- // 2.1+ documented contract for PreToolUse. This hook is always wired to
205
- // PreToolUse (the pack contributes only a PreToolUse hook), so the
206
- // envelope is unconditional here — no event-kind branch like
207
- // runtime/intercept.ts needs.
208
- // Producers list from the pack's config (agent-tasks/25bced52). Same
209
- // shape as the policy engine's `producers:` field, surfaced through the
210
- // understanding-gate's separate deny path. The constraint differs from
211
- // the policy engine: here we require at-least-one `ask` (the canonical
212
- // unblock surface) rather than at-least-one `mcp`, because post-v0.14.0
213
- // the gate signal is a filesystem marker and the mcp ledger_add path no
214
- // longer satisfies the gate. Only the operator-approval (`ask`) or a
215
- // shell from an un-hooked terminal can write the marker.
216
- const ProducersConfigSchema = z
217
- .array(ProducerSchema)
218
- .min(1)
219
- .refine((arr) => arr.some((p) => p.kind === "ask"), "understanding-gate config.producers must include at least one kind:ask entry (the canonical unblock surface)");
220
- function parseConfigProducers(raw, stderr) {
221
- if (raw === undefined)
222
- return undefined;
223
- const result = ProducersConfigSchema.safeParse(raw);
224
- if (!result.success) {
225
- stderr.write(`harness pack hook: config.producers ignored (${result.error.issues
226
- .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
227
- .join("; ")})\n`);
228
- return undefined;
229
- }
230
- return result.data;
231
- }
232
- // Agent-facing `ux:` block on the pack config (agent-tasks/e48e3b45):
233
- // parsed via the shared `parseConfigUx` (hook-bootstrap.ts). When present,
234
- // the deny envelope the agent sees becomes the plain-language
235
- // `{ cannot, required, run }` shape and the legacy "Understanding Gate:
236
- // no approved..." + schemaHint + producers vocabulary is suppressed.
237
- function blockJson(toolName, reason, producers, ux, sessionId, escapeHint, malformedSections, retryInstruction, expiryNotice) {
238
- // When the pack config declares `ux:`, the agent-facing surface
239
- // becomes the plain-language `{ cannot, required, run }` shape, and
240
- // the legacy schemaHint + producers block is suppressed (the ux
241
- // entries are now the canonical surface; mixing both would split
242
- // the agent's attention). Internal stuff (the `reason` argument's
243
- // engine vocabulary) still lands in stderr via the BLOCK diagnostic
244
- // for operator audit.
245
- let reasonText;
246
- if (ux) {
247
- reasonText = renderAgentFacing(ux, {
248
- SESSION_ID: sessionId,
249
- TOOL_NAME: toolName,
250
- });
251
- }
252
- else {
253
- // Legacy suffix kept unchanged so existing operators / docs that quote
254
- // the old surface still find the recognizable string. The producers
255
- // block (when configured) appends AFTER, so a reader's eye lands on
256
- // the structured recipe last. The schema-hint paragraph sits between
257
- // them: the agent reads the call-to-action first, then learns what
258
- // shape the report needs to take (without this, freeform prose
259
- // satisfies the marker write but silently fails the parser).
260
- const suffix = `Run \`harness approve understanding\` once you have produced and confirmed an Understanding Report.`;
261
- const schemaHint = renderReportSchemaHint();
262
- const producersBlock = renderProducers(producers, { SESSION_ID: sessionId });
263
- reasonText = `Understanding Gate: ${reason}. Tool: ${toolName}. ${suffix}\n${schemaHint}${producersBlock}`;
264
- }
265
- // Say why the approval lapsed (max_age elapsed, or the PostToolUse event
266
- // that expired it), after the ux/legacy envelope for the same reason the
267
- // malformed-sections notice below is appended there: both envelopes omit
268
- // the engine vocabulary, so without this the agent would not see it.
269
- if (expiryNotice) {
270
- reasonText = `${reasonText}\n\n${expiryNotice}`;
271
- }
272
- // Name the malformed sections from the session's own latest parse-error
273
- // log, when it carries any (task 823837fd, follow-up to 7e29e5d7): a
274
- // (list) heading that WAS present but whose body was prose instead of
275
- // markdown list items. Without this an agent whose report almost
276
- // parsed sees the same generic "no approved report" block as one that
277
- // never attempted a report at all. Appended after the ux/legacy
278
- // envelope (both are the "what to do" recipe; this is "what went wrong
279
- // last time") and before the escape hint (the most immediate
280
- // remediation, kept last). Sentence itself is shared with the Codex
281
- // hook via `renderMalformedSectionsNotice` (approve/understanding.ts)
282
- // so the two runtimes cannot drift apart byte-for-byte.
283
- const malformedNotice = renderMalformedSectionsNotice(malformedSections ?? []);
284
- if (malformedNotice) {
285
- reasonText = `${reasonText}\n\n${malformedNotice}`;
286
- }
287
- // A targeted remediation hint for an approve-like command that tripped the
288
- // escape matcher's metachar guard. Appended last so it reads after the
289
- // structured recipe regardless of which envelope (ux vs legacy) rendered.
290
- if (escapeHint) {
291
- reasonText = `${reasonText}\n\n${escapeHint}`;
292
- }
293
- // Slice 3's repeated-retry instruction, appended only when a valid
294
- // delegation was present and the child's report simply had not landed
295
- // yet. It is the most immediate remediation for that specific case (and
296
- // the only one the child can act on alone), so it reads last. The
297
- // standard reason above is kept intact rather than replaced: an
298
- // operator reading the audit trail still sees the same block cause.
299
- if (retryInstruction) {
300
- reasonText = `${reasonText}\n\n${retryInstruction}`;
301
- }
302
- return JSON.stringify({
303
- decision: "block",
304
- reason: reasonText,
305
- hookSpecificOutput: {
306
- hookEventName: "PreToolUse",
307
- permissionDecision: "deny",
308
- permissionDecisionReason: reasonText,
309
- },
310
- });
311
- }
312
- // Codepoints in the STRUCTURALLY relevant part of the command (the whole
313
- // command when there is no heredoc, otherwise only the intro line up to
314
- // the first newline) that JS's generic `\s` class matches but bash does
315
- // NOT treat as a blank separator here (task 623640a5 review, anti-lockout
316
- // finding): every one of them glues onto the adjacent token instead of
317
- // being stripped as insignificant whitespace, which is exactly what let a
318
- // report heredoc's real delimiter (as bash reads it) diverge from the
319
- // word isEscapeCommand extracts. Deliberately scoped to the intro line,
320
- // not the heredoc BODY (review 2026-08-08 round 2): the body is free-form
321
- // Understanding Report markdown, where an NBSP or em-space is ordinary,
322
- // legitimate prose whitespace, not a matcher-defeating character, so
323
- // naming one there would misattribute an independent block's cause to
324
- // inert body text an agent never needs to touch. Scans the RAW intro line
325
- // (not a `.trim()`'d copy, since trimming would silently eat a leading or
326
- // trailing occurrence before it could be named). Returns the codepoints
327
- // in `U+XXXX` form, ascending, deduplicated; empty when the intro line
328
- // carries none.
329
- function findNonBashBlankWhitespace(command) {
330
- const nl = command.indexOf("\n");
331
- const introLine = nl === -1 ? command : command.slice(0, nl);
332
- const codepoints = new Set();
333
- for (const ch of introLine) {
334
- if (ch === " " || ch === "\t")
335
- continue;
336
- if (/^\s$/.test(ch))
337
- codepoints.add(ch.codePointAt(0));
338
- }
339
- return [...codepoints]
340
- .sort((a, b) => a - b)
341
- .map((cp) => `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`);
342
- }
343
- // When a blocked Bash command clearly INTENDS to be the operator-approval
344
- // escape (`harness approve ...`) but trips the deliberately strict
345
- // isEscapeCommand matcher because it carries shell metacharacters (a pipe,
346
- // chaining, redirection, or substitution) or a malformed report heredoc,
347
- // it lands in the generic hard block with no clue that the command itself
348
- // was almost the way out. The strictness is intentional (chaining could
349
- // smuggle other work past the gate), so the fix is discoverability, not
350
- // relaxation: surface a targeted hint naming the two accepted shapes.
351
- // Returns null when the command is not approve-like or already qualifies
352
- // as a clean escape.
353
- function approveEscapeHint(toolName, command) {
354
- if (toolName !== "Bash")
355
- return null;
356
- const trimmed = command.trim();
357
- // This `\s` is deliberately broader than isEscapeCommand's `[ \t]`-only
358
- // bash-blank matcher (task 623640a5 review): it only gates whether to
359
- // SHOW the discoverability hint, not whether to accept the command as
360
- // the escape, so being permissive here is safe. Do not narrow it to
361
- // `[ \t]` to "match" the matcher.
362
- if (!/^harness\s+approve\b/.test(trimmed))
363
- return null;
364
- if (isEscapeCommand(trimmed))
365
- return null;
366
- let hint = "This looks like a `harness approve` command, but it was blocked because it carries shell " +
367
- "metacharacters (a pipe, `;`/`&&`/`||` chaining, `<`/`>` redirection, or command substitution) " +
368
- "in the executable part, or a malformed report heredoc. Two shapes are accepted: " +
369
- "(1) bare: `harness approve understanding` with nothing else; " +
370
- "(2) with the Understanding Report attached for capture: " +
371
- "`harness approve understanding <<'UNDERSTANDING_REPORT'` followed by the report markdown and a " +
372
- "final line containing exactly `UNDERSTANDING_REPORT`, with nothing after it. " +
373
- "Re-run in one of those shapes, then approve the prompt.";
374
- // Anti-lockout (task 623640a5 review): an agent that emitted an
375
- // invisible or non-ASCII whitespace codepoint (e.g. a non-breaking
376
- // space) sees a command that reads as clean, so the generic hint above
377
- // gives it no signal that whitespace is the actual cause — it just
378
- // resends the same bytes and stays blocked. Name the offending
379
- // codepoint(s) explicitly whenever the command carries one.
380
- const invisible = findNonBashBlankWhitespace(command);
381
- if (invisible.length > 0) {
382
- const plural = invisible.length > 1;
383
- hint +=
384
- ` It also contains ${plural ? "invisible or non-ASCII whitespace characters" : "an invisible or non-ASCII whitespace character"} ` +
385
- `(${invisible.join(", ")}) where an ordinary space or tab was expected; bash does not treat ` +
386
- `${plural ? "them" : "it"} as a separator here. Replace ${plural ? "them" : "it"} with a real space or tab and re-run.`;
387
- }
388
- return hint;
389
- }
390
- // The Claude Code PreToolUse "ask" envelope: surface the normal interactive
391
- // permission prompt. Per the hooks contract `permissionDecision: "ask"` is
392
- // PreToolUse-only, and the legacy top-level `decision` field is omitted on
393
- // purpose: a `decision: "block"` would hard-block legacy 2.0.x CLIs and
394
- // defeat the ask.
395
- function askJson() {
396
- const reason = "Understanding Gate: no approved Understanding Report yet. This is a " +
397
- "`harness approve` command (the operator-approval path). Approve this " +
398
- "prompt to record your go.";
399
- return JSON.stringify({
400
- hookSpecificOutput: {
401
- hookEventName: "PreToolUse",
402
- permissionDecision: "ask",
403
- permissionDecisionReason: reason,
404
- },
405
- });
406
- }
407
- async function checkLedger(manifest, sessionId, opts) {
408
- if (opts.ledgerQuery) {
409
- const result = await opts.ledgerQuery(sessionId);
410
- if ("degraded" in result) {
411
- return { matched: false, detail: `ledger degraded (${result.degraded})` };
412
- }
413
- return matchLedgerEntries(result, sessionId);
414
- }
415
- const server = findGroundingMcp(manifest);
416
- if (!server) {
417
- return { matched: false, detail: "grounding-mcp not declared in manifest" };
418
- }
419
- const command = Array.isArray(server.command)
420
- ? server.command
421
- : server.command.trim().split(/\s+/);
422
- const env = server.env ?? undefined;
423
- const timeoutMs = opts.ledgerTimeoutMs ?? server.health?.timeout_ms ?? 5_000;
424
- const result = await queryLedgerByTag({
425
- mcpCommand: command,
426
- ...(env && { mcpEnv: env }),
427
- sessionId,
428
- timeoutMs,
429
- });
430
- if (result.kind === "degraded") {
431
- return { matched: false, detail: `ledger degraded (${result.reason})` };
432
- }
433
- return matchLedgerEntries(result.entries, sessionId);
434
- }
435
- async function runPackHookPreToolUseCliInner(opts = {}) {
436
- const stdin = opts.stdin ?? process.stdin;
437
- const stdout = opts.stdout ?? process.stdout;
438
- const stderr = opts.stderr ?? process.stderr;
439
- const packName = opts.pack ?? PACK_NAME;
440
- // Read stdin defensively. Bad JSON falls through to allow (matches
441
- // policy intercept's failure mode) but emits a stderr diagnostic so
442
- // the degradation is loud — a silently-allowing gate manufactures
443
- // false confidence, which is the worst direction for a governance
444
- // hook to fail in.
445
- const raw = await readStdin(stdin);
446
- let event = {};
447
- try {
448
- event = JSON.parse(raw.trim() || "{}");
449
- }
450
- catch (err) {
451
- stderr.write(`harness pack hook: malformed event JSON on stdin (${err.message}), allowing.\n`);
452
- }
453
- const sessionId = (typeof event.session_id === "string" ? event.session_id : undefined) ??
454
- process.env.CLAUDE_CODE_SESSION_ID ??
455
- process.env.CLAUDE_SESSION_ID ??
456
- "";
457
- const toolName = typeof event.tool_name === "string" ? event.tool_name : "(unknown)";
458
- const rawCommand = event.tool_input && typeof event.tool_input === "object"
459
- ? event.tool_input.command
460
- : undefined;
461
- const commandStr = typeof rawCommand === "string" ? rawCommand : "";
462
- // Pause sentinel — operator-only kill switch. Honoured BEFORE manifest
463
- // load so the lockout-recovery flow (where the manifest is exactly
464
- // what's broken) still respects an active pause.
465
- if (checkHookPause("pre-tool-use", stderr, opts, opts.generatedDir, opts.now).paused) {
466
- const diagnostic = "harness paused; pre-tool-use allowing without evaluating.";
467
- return {
468
- exitCode: 0,
469
- blocked: false,
470
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
471
- diagnostic,
472
- };
473
- }
474
- // Load manifest (or use injection). Bail to allow on any failure so a
475
- // missing harness install never bricks the session. The resolved
476
- // manifest path feeds the harness.generated/ lookup below; an injected
477
- // manifest has no path, so the staging write is skipped in that case
478
- // (tests inject `generatedDir` directly instead).
479
- let manifest;
480
- let manifestPath;
481
- try {
482
- ({ manifest, manifestPath } = loadManifestOrInjected(opts, opts.manifest));
483
- }
484
- catch (err) {
485
- const diagnostic = `harness pack hook: manifest load failed (${err.message}), allowing.`;
486
- stderr.write(`${diagnostic}\n`);
487
- return {
488
- exitCode: 0,
489
- blocked: false,
490
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
491
- diagnostic,
492
- };
493
- }
494
- // Confirm the pack is enabled. A pack that isn't even declared in the
495
- // manifest means the operator wired this hook directly into
496
- // settings.json without `harness apply` — odd but harmless; allow.
497
- const declared = manifest.policy_packs.find((p) => p.name === packName);
498
- if (!declared) {
499
- const diagnostic = `harness pack hook: pack "${packName}" not declared in manifest, allowing.`;
500
- stderr.write(`${diagnostic}\n`);
501
- return {
502
- exitCode: 0,
503
- blocked: false,
504
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
505
- diagnostic,
506
- };
507
- }
508
- if (!declared.enabled) {
509
- const diagnostic = `harness pack hook: pack "${packName}" is enabled:false, allowing.`;
510
- stderr.write(`${diagnostic}\n`);
511
- return {
512
- exitCode: 0,
513
- blocked: false,
514
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
515
- diagnostic,
516
- };
517
- }
518
- if (sessionId === "") {
519
- const diagnostic = 'harness pack hook: no session_id resolvable from input or $CLAUDE_CODE_SESSION_ID/$CLAUDE_SESSION_ID, allowing.';
520
- stderr.write(`${diagnostic}\n`);
521
- return {
522
- exitCode: 0,
523
- blocked: false,
524
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
525
- diagnostic,
526
- };
527
- }
528
- // Resolve generatedDir up-front: marker check and pending-approval
529
- // staging both depend on it.
530
- const generatedDir = opts.generatedDir ??
531
- (manifestPath !== undefined
532
- ? resolveGeneratedDir({
533
- ...(opts.homeDir !== undefined ? { homeDir: opts.homeDir } : {}),
534
- manifestPath,
535
- })
536
- : undefined);
537
- // Source 1: filesystem marker (agent-tasks/88ca4bb3). Canonical for
538
- // harnessed sessions. The ledger check is no longer authoritative
539
- // because the agent has direct MCP access to the same ledger and
540
- // could self-approve; the marker file lives in harness.generated/
541
- // which Edit / Write / Bash are all gated from writing to. Bail to
542
- // ledger-as-audit only when generatedDir is unresolvable (injected
543
- // manifest without a resolved path: only happens in tests).
544
- //
545
- // `markerExpired` is hoisted out of the block below so the
546
- // recovery-git-commit exception (task 6e888423) further down can see
547
- // it: it is true only when a REAL marker existed for this
548
- // session/task and aged past `approval_lifecycle.max_age` — as
549
- // opposed to no marker ever existing, or one cleared by a
550
- // task-completion boundary tool. See understanding-before-execution-
551
- // runtime.ts's `OperatorMarkerApproval.expired` doc for the full
552
- // distinction.
553
- let markerExpired = false;
554
- // The max_age sentence for the block reason (wording only, never read by a
555
- // gate decision); set when the marker check reported a TTL expiry.
556
- let markerTtlExpiry;
557
- // True when checkOperatorApprovalMarkers found a marker FILE that failed
558
- // signature verification (harness/f9485cc7) — missing/invalid signature,
559
- // wrong alg, or tampered payload — for either the task-scoped or
560
- // session-scoped check. Surfaced in the block reason below with a
561
- // distinct phrase from "no approval marker" so an operator/auditor can
562
- // tell a forgery attempt apart from the routine "never approved" case.
563
- let markerForged = false;
564
- // `markers.detail` itself, hoisted alongside `markerForged` so the
565
- // in-flight consult below can still surface it: a forged SESSION/TASK
566
- // marker sitting next to a genuinely valid in-flight record (ADR "TTL,
567
- // cwd, and subagents") is a forgery attempt the operator should see
568
- // even though the in-flight record is what ends up opening the gate.
569
- let markerForgedDetail;
570
- // Set when the session marker was refused only because of its task
571
- // binding (task 5018c0c4): it was granted for another task, or predates
572
- // the binding. Blocks exactly like a missing marker; only the reason
573
- // text below differs, so the operator sees why an approval that exists
574
- // on disk does not open the gate.
575
- let sessionBindingRefusedDetail;
576
- // Set when a marker matched but no persisted report file hashes to the
577
- // content that marker was signed for any more (task fa423e9b).
578
- // The marker is then treated as not matching: the call falls through to the
579
- // ordinary block path (read-only Bash and the operator-approval command
580
- // keep their exemptions) with this as the reason, and the in-flight
581
- // subagent record below is not consulted, because it would otherwise
582
- // re-open the gate for a subagent under the very approval just refused.
583
- let reportHashMismatchDetail;
584
- const reportsDir = opts.reportsDir ?? defaultReportsDir();
585
- if (generatedDir !== undefined) {
586
- // Source 1a/1b: task-scoped marker for the currently-claimed task
587
- // (harness/1ee26e77 + PR #198 correctness fix), then the
588
- // session-scoped marker (legacy / fallback), both under the
589
- // `approval_lifecycle` TTL. The resolution is shared with the Codex
590
- // hook (`checkOperatorApprovalMarkers`, task e7c2ec3c) so the two
591
- // runtimes cannot drift on lifecycle semantics again.
592
- const markers = checkOperatorApprovalMarkers(generatedDir, sessionId, declared.config, stderr);
593
- markerExpired = markers.expired;
594
- markerTtlExpiry = describeMarkerTtlExpiry(markers);
595
- markerForged = markers.forged;
596
- if (markers.sessionBindingRefused)
597
- sessionBindingRefusedDetail = markers.detail;
598
- if (markerForged) {
599
- // `markers.detail` is only ever the SESSION marker's detail on the
600
- // unmatched path (see OperatorMarkerApproval.detail); when it is
601
- // the TASK marker that failed verification, that fact is buried
602
- // inside `taskCheckDetail`'s wrapping instead. Detect which one
603
- // actually carries the forgery phrase so this line names the
604
- // marker that was actually tampered with, not always the session
605
- // one (review T-003 R3 L1: a forged task marker next to an intact
606
- // session marker used to report the session's unrelated "no
607
- // approval marker" detail here).
608
- const taskMarkerIsForged = /forged\/unsigned marker rejected/.test(markers.taskCheckDetail);
609
- const forgedMarkerDetail = taskMarkerIsForged ? markers.taskCheckDetail : markers.detail;
610
- markerForgedDetail = `harness pack hook: forged/unsigned marker rejected for session ${sessionId}; ${forgedMarkerDetail}`;
611
- }
612
- if (markers.source !== "task") {
613
- // Trace the task-marker miss to stderr so an operator chasing
614
- // "why isn't my approval working?" sees the active-claim vs marker
615
- // mismatch, not just the eventual generic session-marker miss.
616
- stderr.write(`harness pack hook: task-scoped check: ${markers.taskCheckDetail}\n`);
617
- }
618
- if (markers.matched) {
619
- // Cross-check the matched marker's signed report hash against the
620
- // persisted reports (task fa423e9b): the task marker first, the
621
- // session marker as fallback, so the verdict never depends on which
622
- // marker the check order picked.
623
- const reportHash = verifyMatchedMarkerReport(reportsDir, markers);
624
- if (reportHash.ok) {
625
- const diagnostic = `harness pack hook: ${reportHash.detail}, allowing.`;
626
- stderr.write(`${diagnostic}\n`);
627
- return {
628
- exitCode: 0,
629
- blocked: false,
630
- approvalCheck: { approved: true, source: "marker", detail: reportHash.detail },
631
- diagnostic,
632
- };
633
- }
634
- reportHashMismatchDetail = reportHash.detail;
635
- }
636
- }
637
- // Source 1c: in-flight subagent record (ADR "TTL, cwd, and subagents").
638
- // An Agent-tool subagent shares its parent's
639
- // `session_id`, so the marker check above cannot tell a subagent's call
640
- // apart from the parent's own — the payload's `agent_id` is what does.
641
- // Consulted ONLY here, ONLY when the marker check just missed AND the
642
- // payload names a non-empty `agent_id`: a main-line call (no
643
- // `agent_id`) never reaches `verifyInflightRecord` at all, so no record
644
- // on disk for any agent can ever open the gate for a main-line call.
645
- const agentId = typeof event.agent_id === "string" && event.agent_id.length > 0
646
- ? event.agent_id
647
- : undefined;
648
- // Sanitized copy for every surface a human or the agent itself reads
649
- // (the block suffix, the forged phrase, this allow diagnostic): the raw
650
- // `agentId` above is what verification and path-building use, but an
651
- // attacker-controlled `agent_id` is otherwise interpolated verbatim
652
- // into stderr and the agent-facing deny text (review finding: newline
653
- // injection, unbounded length).
654
- const displayAgentId = agentId !== undefined ? sanitizeForDisplay(agentId) : undefined;
655
- // True SPECIFICALLY when a record FILE existed but failed verification
656
- // (bad signature, or its body disagreed with its own path) — the same
657
- // distinction `markerForged` draws for the operator marker. Folded into
658
- // the auto-approval gating below so a forged record is declined the
659
- // same way a forged marker is, without duplicating that enforcement.
660
- let inflightForged = false;
661
- // True SPECIFICALLY when the record verified and agreed with its own
662
- // path but aged past the staleness window — distinct from `forged` and
663
- // from a record simply never existing, so the subagent sentence below
664
- // can name the actual cause instead of always saying "no record".
665
- let inflightStale = false;
666
- if (generatedDir !== undefined &&
667
- agentId !== undefined &&
668
- reportHashMismatchDetail === undefined) {
669
- const record = verifyInflightRecord(generatedDir, sessionId, agentId, opts.now !== undefined ? { now: opts.now } : {});
670
- inflightForged = record.forged;
671
- inflightStale = record.stale;
672
- if (record.matched) {
673
- // A forged SESSION/TASK marker sitting next to this valid record is
674
- // a distinct forgery attempt: surfaced to stderr BEFORE the allow
675
- // diagnostic, but never gating it — the in-flight record is its
676
- // own, independently-verified approval authority (ADR "TTL, cwd,
677
- // and subagents"), so a forged copy of a DIFFERENT authority must
678
- // not turn this allow into a block.
679
- if (markerForgedDetail !== undefined) {
680
- stderr.write(`${markerForgedDetail}\n`);
681
- }
682
- const diagnostic = `harness pack hook: in-flight subagent record for agent ${displayAgentId} (${record.detail}), allowing.`;
683
- stderr.write(`${diagnostic}\n`);
684
- return {
685
- exitCode: 0,
686
- blocked: false,
687
- approvalCheck: { approved: true, source: "inflight", detail: record.detail },
688
- diagnostic,
689
- };
690
- }
691
- }
692
- // Persisted-report EVIDENCE probe (task 7402301d). This used to be
693
- // "Source 2", an equal approval authority consulted right after a forged
694
- // marker was rejected, and it was unsigned: under the threat model that
695
- // motivated marker signing (harness/f9485cc7: a write primitive the
696
- // `Edit|Write|Bash` matcher does not cover) one unsigned JSON write
697
- // into the reports directory forged an approval, no session id or key
698
- // read needed. The report now contributes only to the block diagnostic
699
- // (`report.detail`, which carries the distinct `unsigned
700
- // persisted-report approval rejected` phrase when the on-disk status
701
- // says approved) and to the parse-error lookup gate below
702
- // (`report.report === null`). `PersistedReportEvidence` has no
703
- // `approved` field on purpose; the signed marker is the only APPROVAL
704
- // source that opens the gate. The carve-outs below (read-only Bash,
705
- // recovery git-commit, escape-ask) are separate, independently-argued
706
- // exemptions, not a second approval source.
707
- const report = checkPersistedReport(reportsDir, sessionId);
708
- // Audit-only ledger probe: the ledger row is still recorded by
709
- // `harness approve understanding`, and we surface its presence in
710
- // the diagnostic so an operator chasing a flapping gate can see the
711
- // historic trail. The result intentionally does NOT influence the
712
- // allow/block decision.
713
- const ledger = await checkLedger(manifest, sessionId, opts);
714
- // A subagent call (non-empty `agent_id`) that reached this point had no
715
- // matching in-flight record — missing, for a different agent, forged,
716
- // stale, or naming a malformed id (`verifyInflightRecord` never throws
717
- // on any of those; it just reports `matched: false`). Branched on the
718
- // actual verification outcome rather than always naming "no record": a
719
- // forged or stale record is a materially different signal than one
720
- // that never existed — the parent DID hold a valid approval at some
721
- // point — and conflating them misdirects the agent's next action.
722
- // Gated on `generatedDir !== undefined`: when it
723
- // is not resolvable (test/injection path), `verifyInflightRecord` was
724
- // never consulted at all (see `inflightForged`/`inflightStale` staying
725
- // at their `false` initializers above), so there is no verification
726
- // outcome to report. Named explicitly so an agent reading the deny
727
- // text knows to stop rather than retry: unlike the operator-approval
728
- // case, nothing this session can do mints a new record — only the
729
- // parent's NEXT `subagent-start` call can.
730
- const subagentRecordSentence = generatedDir !== undefined && displayAgentId !== undefined
731
- ? inflightForged
732
- ? ` subagent ${displayAgentId}: the in-flight record for this subagent failed verification; stop cleanly and report to the orchestrator instead of retrying.`
733
- : inflightStale
734
- ? ` subagent ${displayAgentId}: the in-flight record for this subagent is older than the staleness window; stop cleanly and report to the orchestrator instead of retrying.`
735
- : ` subagent ${displayAgentId}: no in-flight approval record (the parent session held no valid approval when this subagent started, or the record was removed); stop cleanly and report to the orchestrator instead of retrying.`
736
- : "";
737
- // Neither operator source approved. When a marker FILE existed but
738
- // failed signature verification, use a distinct reason phrase
739
- // ("forged/unsigned marker rejected") instead of the routine "no
740
- // approval marker" — audit/operator surfaces can then tell an active
741
- // forgery attempt (or a pre-signing legacy marker) apart from a session
742
- // that simply never approved (harness/f9485cc7). A forged in-flight
743
- // record gets its own distinct phrase for the same reason, checked
744
- // second so a forged SESSION/TASK marker (the higher-authority forgery)
745
- // is never masked by a merely-forged copy of it.
746
- const unapprovedReason = generatedDir !== undefined
747
- ? markerForged
748
- ? `forged/unsigned marker rejected for session ${sessionId}; ${report.detail}; ${ledger.detail}`
749
- : inflightForged
750
- ? `forged/unsigned in-flight record for agent ${displayAgentId} rejected for session ${sessionId}; ${report.detail}; ${ledger.detail}`
751
- : sessionBindingRefusedDetail !== undefined
752
- ? `${sessionBindingRefusedDetail}; ${report.detail}; ${ledger.detail}${subagentRecordSentence}`
753
- : noApprovalMarkerReason(sessionId, markerTtlExpiry, report.detail, ledger.detail, subagentRecordSentence)
754
- : `generatedDir not resolvable (test/injection path); ${report.detail}; ${ledger.detail}`;
755
- // A marker that matched but whose signed report content is gone (the
756
- // approved report was edited or removed after approval, task fa423e9b)
757
- // gets its own reason, naming the marker kind and the one-command fix; no
758
- // "no approval marker" wording applies to it.
759
- const reason = reportHashMismatchDetail !== undefined
760
- ? `${reportHashMismatchDetail}; ${ledger.detail}`
761
- : unapprovedReason;
762
- // Stage the session id so `harness approve`, run from the operator's
763
- // shell where $CLAUDE_CODE_SESSION_ID / $CLAUDE_SESSION_ID is unset, can resolve it without
764
- // guessing from transcript filenames. Covers both the ask and the
765
- // block branches below. Best-effort: a staging-write failure must not
766
- // escalate a gate block into a hook error.
767
- if (generatedDir !== undefined) {
768
- try {
769
- writePendingApproval(generatedDir, sessionId);
770
- }
771
- catch {
772
- /* best-effort; the ask / block below proceeds regardless */
773
- }
774
- }
775
- // Exception: read-only Bash commands. The pack hook matcher
776
- // necessarily covers `Bash` as a whole, but commands like
777
- // `git status`, `gh pr view`, `ls`, `cat` mutate nothing. Blocking
778
- // them behind a full Understanding Report cycle trains the agent
779
- // and operator to experience the gate as noise, which erodes its
780
- // credibility on the writes that actually matter. Pass the
781
- // classifier on Bash commands only; Edit and Write stay
782
- // hard-blocked regardless (the matcher's other arms reach the
783
- // same final block path below). Unclassifiable Bash falls through
784
- // to the block (fail-closed).
785
- if (toolName === "Bash" && isReadOnlyBashPipeline(commandStr)) {
786
- const diagnostic = `harness pack hook: read-only Bash command, allowing without an approved report (\`${commandStr.trim()}\`)`;
787
- stderr.write(`${diagnostic}\n`);
788
- return {
789
- exitCode: 0,
790
- blocked: false,
791
- approvalCheck: { approved: true, source: "none", detail: diagnostic },
792
- diagnostic,
793
- };
794
- }
795
- // Exception: the narrow recovery-git-commit shape (task 6e888423,
796
- // agent-grounding frictions #2/#9/#58/#71). `approval_lifecycle.max_age`
797
- // can age out the marker mid-task — e.g. during a long reviewer-
798
- // amendment loop — leaving the recovery `git commit` that consolidates
799
- // ALREADY-approved Edit/Write output into a new HEAD hard-blocked
800
- // behind an operator. Gated on BOTH conditions so this cannot become a
801
- // generic bypass: `markerExpired` proves a real operator approval
802
- // existed for this exact session/task and merely aged out (a marker
803
- // that is simply absent, or one a task-completion boundary tool just
804
- // cleared for a NEW task, leaves this false and the block below still
805
- // applies), and `isRecoveryGitCommit` proves the command is a bare,
806
- // unchained `git commit` that cannot smuggle other work or introduce
807
- // new file content — see src/runtime/recovery-git-commit.ts for the
808
- // full safety argument. Edit / Write / every other Bash shape remain
809
- // hard-gated regardless.
810
- if (toolName === "Bash" && markerExpired && isRecoveryGitCommit(commandStr)) {
811
- const diagnostic = `harness pack hook: recovery-commit exemption — approval for session ${sessionId} had expired, but this session/task WAS previously approved; allowing the bare \`git commit\` to record already-approved work (\`${commandStr.trim()}\`). A fresh Understanding Report is still required for any new Edit/Write/Bash.`;
812
- stderr.write(`${diagnostic}\n`);
813
- return {
814
- exitCode: 0,
815
- blocked: false,
816
- approvalCheck: { approved: true, source: "recovery-commit", detail: diagnostic },
817
- diagnostic,
818
- };
819
- }
820
- // Exception: the operator-approval command itself. Hard-denying
821
- // `harness approve understanding` is a catch-22: it is the very command
822
- // that records the operator's go, and a Bash `deny` gives no prompt to
823
- // approve. Defer it to the interactive permission prompt instead, so the
824
- // operator's go on that prompt IS the approval, and `harness approve
825
- // understanding` then writes the ledger tag that unblocks the session.
826
- if (toolName === "Bash" && isEscapeCommand(commandStr)) {
827
- const diagnostic = `harness pack hook: ASK: operator-approval command, deferring to the interactive permission prompt`;
828
- stderr.write(`${diagnostic}\n`);
829
- stdout.write(`${askJson()}\n`);
830
- return {
831
- exitCode: 0,
832
- blocked: false,
833
- asked: true,
834
- approvalCheck: { approved: false, source: "none", detail: reason },
835
- diagnostic,
836
- };
837
- }
838
- // Step 9: the operator-opt-in auto-approval attempt (agent-tasks/
839
- // 74b4b17d, ADR docs/decisions/2026-08-27-ug-auto-mode-approval.md,
840
- // Option A). Deliberately LAST, on a call that the marker check and
841
- // every exemption above has already declined — i.e. exactly a call
842
- // that would otherwise reach the final block. It writes no marker for
843
- // anything the gate was already going to open, and it never returns an
844
- // allow of its own: the allow below is produced by the auto path's own
845
- // re-run of `checkOperatorApprovalMarkers`, the same authority step 3
846
- // consults. Any failure falls through to the block with step 3's
847
- // `markerExpired` / `markerForged` intact.
848
- //
849
- // The ledger writer is resolved lazily: `attemptAutoApproval` calls
850
- // this thunk only after its own opt-in check and `when` allowlist
851
- // check both pass, so a call that never opted in (or opted in but
852
- // runs in an unlisted `permission_mode`, the ordinary case for every
853
- // other gated call reaching step 9) never resolves a manifest-level
854
- // ledger writer at all. A CLI-injected `opts.writeLedger` (test
855
- // harness path) is used as-is when present, still only once the
856
- // thunk actually runs; otherwise `resolveManifestLedgerWriter` looks
857
- // up `grounding-mcp` in the manifest on demand. Stays audit-only
858
- // either way: a missing `grounding-mcp` entry costs one stderr line
859
- // inside the auto path, never an approval.
860
- // Step 9's key-one-by-delegation branch (slice 3, agent-tasks/37ad0b05,
861
- // ADR "Decision: two-key design"). Placed immediately before the
862
- // `attemptAutoApproval` call and therefore, like it, only on a call
863
- // every branch above has already declined. What happens here is
864
- // narrow: verify the delegation, and if it holds, obtain the child's
865
- // OWN report through the bounded transcript poll and persist it
866
- // `pending`. It writes no marker and returns no allow of its own; the
867
- // auto path below still applies every condition it applies to a
868
- // `when`-matched call, the `pending`-only report rule included, and the
869
- // marker re-check inside it is still the only thing that allows.
870
- //
871
- // Fail-closed at every step, each with its own stderr line: no opt-in,
872
- // an unlisted harness, no payload `session_id`, no delegation file, an
873
- // absent signing key, any refusal from `verifyDelegation`, an
874
- // unparseable or absent transcript report. Each one falls through with
875
- // `delegation` unset, and the block below is what the call then
876
- // reaches.
877
- let delegation;
878
- // Set only by the bound elapsing with no report in the transcript: the
879
- // measured case where the child DID what it was asked and the write is
880
- // simply still in flight. That is the one case where the deny text asks
881
- // for a retry, so it is the one case that sets this flag.
882
- let reportScanTimedOut = false;
883
- // `!inflightForged` added alongside `!markerForged`: a forged in-flight
884
- // record is the same posture as a forged marker (ADR condition 6, "a
885
- // forgery detected is never laundered into an approval") — no point
886
- // spending a delegation resolution on a call already known to end in a
887
- // decline.
888
- if (generatedDir !== undefined && !markerForged && !inflightForged) {
889
- // `null` stderr on purpose: `attemptAutoApproval` parses the same
890
- // block a few lines below and writes the malformed-config line
891
- // itself, so passing this hook's stderr here would double it.
892
- const autoCfg = parseAutoApprove(declared.config["auto_approve"], null);
893
- const payloadSid = typeof event.session_id === "string" && event.session_id.length > 0
894
- ? event.session_id
895
- : null;
896
- let delegationPath = null;
897
- if (autoCfg !== null && harnessAllowed(autoCfg, CLAUDE_CODE_HARNESS) && payloadSid !== null) {
898
- try {
899
- delegationPath = delegationMarkerPathFor(generatedDir, payloadSid);
900
- }
901
- catch {
902
- // A session id that cannot name a file has no delegation, by
903
- // construction (`verifyDelegation` draws the same conclusion).
904
- delegationPath = null;
905
- }
906
- }
907
- if (autoCfg !== null && delegationPath !== null && existsSync(delegationPath)) {
908
- const childSessionId = payloadSid;
909
- if (!signingKeyExists(generatedDir)) {
910
- // Never verify through a path that would MINT the key
911
- // (`verifyMarkerSignature` -> `getOrCreateSigningKey`). The
912
- // verifier prechecks this too; this line names the condition at
913
- // the hook, where the operator is reading.
914
- stderr.write("harness pack hook: delegation present but signing key absent; not verified\n");
915
- }
916
- else {
917
- // The conventional launcher-report path (a plain function of
918
- // `childSessionId`, never a value read from the payload or from
919
- // the delegation file itself): offered unconditionally, exactly
920
- // as `verifyDelegation` expects. It only affects the outcome when
921
- // the delegation actually binds a `report=` segment; for every
922
- // other delegation it is read and then never consulted (see the
923
- // `reportPathHash !== undefined` guard inside `verifyDelegation`).
924
- const launcherReportPath = delegationReportPathFor(generatedDir, childSessionId);
925
- const verified = verifyDelegation({
926
- generatedDir,
927
- childSessionId,
928
- // The payload's own cwd, held against the hashed binding the
929
- // parent signed. Absent means the delegation's cwd binding
930
- // cannot be satisfied, which the verifier reports as a
931
- // `cwd_mismatch` rather than waving through.
932
- cwd: typeof event.cwd === "string" && event.cwd.length > 0 ? event.cwd : null,
933
- // The same active-claim id the task-scoped marker check resolves
934
- // above, so a delegation issued for one task cannot authorize a
935
- // child that has claimed another. Read a SECOND time here on
936
- // purpose: `checkOperatorApprovalMarkers` resolves the claim
937
- // inside itself and returns only a detail string, never the id,
938
- // so there is no resolved value to reuse without widening that
939
- // shared runtime's return shape for one caller.
940
- taskId: readActiveClaim(generatedDir),
941
- launcherReportPath,
942
- });
943
- if (!verified.ok) {
944
- // An absent, moved, or content-modified launcher report surfaces
945
- // here through the verifier's own distinct reasons
946
- // (`report_missing` / `report_path_mismatch` /
947
- // `report_content_mismatch`), same as every other refusal: no
948
- // special-casing needed now that this hook actually checks the
949
- // fallback shape against a file. `verified.detail` names the
950
- // expected path for the three file-state cases this hook can
951
- // actually reach (absent / wrong path / wrong bytes). Two of
952
- // `verifyDelegation`'s binding-shape branches are unreachable
953
- // from here for different reasons, so their details are worth
954
- // spelling out separately rather than lumping them together:
955
- // "no report path offered" never fires from this call site,
956
- // since this hook always offers the conventional path
957
- // (`launcherReportPath` above is unconditional); "no
958
- // reportContentHash to check against" IS reachable here, just
959
- // not from a delegation this hook itself minted, since
960
- // `writeDelegationMarker` refuses to produce that half-binding
961
- // shape (a hand-signed marker can still carry it, as the
962
- // half-binding test in
963
- // understanding-before-execution-delegation.test.ts shows), and
964
- // its detail names the delegation marker's own path, not the
965
- // report path, since there is no report path yet to name.
966
- stderr.write(`harness pack hook: delegation for ${childSessionId} refused: ${verified.reason}: ${verified.detail}\n`);
967
- }
968
- else {
969
- delegation = { parentSessionId: verified.parentSessionId };
970
- // Key two: the child's own report. Already on disk and pending
971
- // (the child was denied once and its report has since been
972
- // captured) means there is nothing to scan for, so neither
973
- // channel below is read at all.
974
- // `childSessionId`, not the outer `sessionId`: this whole block
975
- // decides the PAYLOAD's session, and the two only coincide
976
- // because the env fallback never wins on a path that carries a
977
- // payload session_id. Spelling it out keeps the report this
978
- // branch looks for bound to the same session the delegation,
979
- // the scan and the adoption ledger are all keyed by.
980
- // Bounded by entry count and bytes too (planted entries would run
981
- // this hook past its budget, which the runtime treats as an allow):
982
- // past either bound no report is returned and the capture below is
983
- // skipped, since a listing that cannot see every entry cannot tell
984
- // whether the child already has a report.
985
- const delegationListing = listPersistedReportsBoundedWithSkips(reportsDir, {
986
- maxEntries: MAX_HOOK_LISTING_ENTRIES,
987
- });
988
- const existing = selectNewestStrictSessionReport(delegationListing.reports, // bounded: agent-writable dir; a skipped entry is no report
989
- childSessionId);
990
- // A report-bound delegation (the `--report` fallback shape,
991
- // `verified.reportPathHash !== undefined`) never falls through
992
- // to the transcript scan: the launcher already supplied and
993
- // proved (via the verify call above) the report the child would
994
- // otherwise have to emit into its own transcript, so scanning
995
- // for a second copy would be pointless and would let a
996
- // report-bound delegation ALSO be satisfied by whatever the
997
- // child happens to write, which defeats the point of the
998
- // launcher fixing the content.
999
- if (delegationListing.truncated) {
1000
- stderr.write(`harness pack hook: the reports directory ${delegationListing.truncatedDetail}; the report for session ${childSessionId} was not captured\n`);
1001
- }
1002
- else if (existing === null || existing.approvalStatus !== "pending") {
1003
- if (verified.reportPathHash !== undefined) {
1004
- // The bytes `verifyDelegation` already proved present,
1005
- // path-matched, and content-matched against the bound
1006
- // hash, returned verbatim on its ok shape. There is NO
1007
- // second read of `launcherReportPath` here on purpose: a
1008
- // second read would be a second trust decision, reopening
1009
- // the exact race (a rewrite between verification and
1010
- // persist) that returning the verified bytes closes
1011
- // structurally instead of by re-checking a hash.
1012
- const reportContent = verified.reportContent;
1013
- if (reportContent === undefined) {
1014
- // Cannot happen given the invariant `reportContent` is
1015
- // set whenever `reportPathHash` is (see the type's own
1016
- // doc comment); fail closed rather than persist nothing
1017
- // we can vouch for.
1018
- stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} at ${launcherReportPath} was verified but no content was returned; nothing was persisted\n`);
1019
- }
1020
- else {
1021
- const contentHash = sha256Hex(reportContent);
1022
- // Adoption keyed by the report's CONTENT hash, in the
1023
- // SAME once-per-session ledger the transcript scan uses,
1024
- // namespaced with a `report:` prefix so the two id shapes
1025
- // (`uuid:...` / `digest:...` for a transcript entry,
1026
- // `report:...` here) can never collide. Without this a
1027
- // launcher report could mint a fresh marker every time an
1028
- // earlier one expired, for as long as the delegation
1029
- // itself stayed valid: the delegation's TTL would
1030
- // silently become the report's own re-mint budget instead
1031
- // of a one-shot authorization.
1032
- const adoptedPath = adoptedEntriesPathFor(generatedDir, childSessionId);
1033
- const adopted = readAdoptedEntries(generatedDir, childSessionId);
1034
- const entryId = `report:${contentHash}`;
1035
- if (!adopted.ok) {
1036
- // Fail closed: without the ledger we cannot tell a
1037
- // fresh report from one this session already spent.
1038
- stderr.write(`harness pack hook: the adopted-entry ledger at ${adoptedPath} could not be read (${adopted.detail}); refusing to adopt a launcher-supplied report that may already have been adopted for session ${childSessionId}\n`);
1039
- }
1040
- else if (adopted.ids.has(entryId)) {
1041
- stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} was already adopted by an earlier mint; refusing to re-mint from the same report (issue a fresh delegation with a new report to authorize another mint)\n`);
1042
- }
1043
- else {
1044
- // RECORD THE ADOPTION FIRST, then persist, same ordering
1045
- // and same reasoning as the transcript-scan path below:
1046
- // a failed ledger write costs one blocked call and
1047
- // leaves the report re-adoptable, while the reverse
1048
- // order would leave a persisted, mintable report behind
1049
- // an unrecorded adoption.
1050
- const recorded = recordAdoptedEntry(generatedDir, childSessionId, entryId);
1051
- if (!recorded.ok) {
1052
- stderr.write(`harness pack hook: could not record the launcher-supplied report as adopted for session ${childSessionId} (${recorded.detail}); nothing was persisted\n`);
1053
- }
1054
- else {
1055
- const persisted = persistStdinReport({
1056
- markdown: reportContent,
1057
- reportsDir,
1058
- sessionId: childSessionId,
1059
- now: new Date(),
1060
- mode: toPackageMode(resolveMode(declared).mode),
1061
- });
1062
- if (persisted.ok) {
1063
- stderr.write(`harness pack hook: adopted the launcher-supplied report for session ${childSessionId} from ${launcherReportPath} and persisted it pending at ${persisted.filePath}\n`);
1064
- }
1065
- else {
1066
- // The entry stays adopted: the launcher's report is
1067
- // fixed content, so re-reading it would fail to
1068
- // parse identically forever. Unlike the transcript
1069
- // path this is not recoverable by a child retry
1070
- // (the child does not control the launcher's file),
1071
- // so no retry instruction is set here.
1072
- stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} did not parse (${persisted.reason}); nothing was persisted\n`);
1073
- }
1074
- }
1075
- }
1076
- }
1077
- // The transcript scan below never runs for a report-bound
1078
- // delegation: this `if` branch is its own complete
1079
- // report-capture attempt.
1080
- }
1081
- else {
1082
- const transcriptPath = typeof event.transcript_path === "string" && event.transcript_path.length > 0
1083
- ? event.transcript_path
1084
- : null;
1085
- // The once-per-session adoption ledger, read only on the path
1086
- // that can actually capture something.
1087
- const adoptedPath = adoptedEntriesPathFor(generatedDir, childSessionId);
1088
- const adopted = transcriptPath === null
1089
- ? { ok: true, ids: new Set() }
1090
- : readAdoptedEntries(generatedDir, childSessionId);
1091
- if (transcriptPath === null) {
1092
- stderr.write(`harness pack hook: delegation for ${childSessionId} is valid but the payload carries no transcript_path; the child's report cannot be captured\n`);
1093
- }
1094
- else if (!adopted.ok) {
1095
- // Fail closed: without the ledger we cannot tell a fresh
1096
- // report from one this session already spent.
1097
- stderr.write(`harness pack hook: the adopted-entry ledger at ${adoptedPath} could not be read (${adopted.detail}); refusing to capture a transcript entry that may already have been adopted for session ${childSessionId}\n`);
1098
- }
1099
- else {
1100
- const scan = await scanTranscriptForReport({
1101
- transcriptPath,
1102
- sessionId: childSessionId,
1103
- maxWaitMs: autoCfg.reportScan.maxWaitMs,
1104
- adopted: adopted.ids,
1105
- ...(opts.reportScanClock?.now !== undefined
1106
- ? { now: opts.reportScanClock.now }
1107
- : {}),
1108
- ...(opts.reportScanClock?.sleep !== undefined
1109
- ? { sleep: opts.reportScanClock.sleep }
1110
- : {}),
1111
- ...(opts.reportScanClock?.pollMs !== undefined
1112
- ? { pollMs: opts.reportScanClock.pollMs }
1113
- : {}),
1114
- });
1115
- if (scan.found) {
1116
- // RECORD THE ADOPTION FIRST, then persist. In this order a
1117
- // failed ledger write costs one blocked call and leaves the
1118
- // entry re-scannable, while the reverse order would leave a
1119
- // persisted, mintable report behind an unrecorded adoption:
1120
- // exactly the replay this ledger exists to stop.
1121
- const recorded = recordAdoptedEntry(generatedDir, childSessionId, scan.entryId);
1122
- if (!recorded.ok) {
1123
- stderr.write(`harness pack hook: could not record transcript entry ${scan.entryId} as adopted for session ${childSessionId} (${recorded.detail}); nothing was persisted\n`);
1124
- }
1125
- else {
1126
- // Reuses the SAME persister `harness approve
1127
- // understanding` uses for a heredoc-attached report, so
1128
- // the capture is session-bound, `pending`, and validated
1129
- // by one parser rather than a second one written here.
1130
- // A real `new Date()` on purpose: the persisted report
1131
- // must sort as the newest strict-session report against
1132
- // whatever is already on disk, which a caller-injected
1133
- // clock could not guarantee.
1134
- const persisted = persistStdinReport({
1135
- markdown: scan.markdown,
1136
- reportsDir,
1137
- sessionId: childSessionId,
1138
- now: new Date(),
1139
- mode: toPackageMode(resolveMode(declared).mode),
1140
- });
1141
- if (persisted.ok) {
1142
- stderr.write(`harness pack hook: captured the Understanding Report for session ${childSessionId} from its own transcript (line ${scan.lineIndex}, after ${scan.waitedMs}ms) and persisted it pending at ${persisted.filePath}\n`);
1143
- }
1144
- else {
1145
- // The entry stays adopted: re-reading a report that
1146
- // does not parse would fail identically forever, so
1147
- // the child needs a NEW one, which is what the block's
1148
- // retry instruction asks for. Set the same flag the
1149
- // timeout branch sets, for that reason: the retry
1150
- // sentence reads correctly for this case too, and
1151
- // without it the block below would ask for a retry
1152
- // implicitly while never saying so.
1153
- reportScanTimedOut = true;
1154
- stderr.write(`harness pack hook: the transcript report for session ${childSessionId} did not parse (${persisted.reason}); nothing was persisted\n`);
1155
- }
1156
- }
1157
- }
1158
- else if (scan.reason === "timeout") {
1159
- reportScanTimedOut = true;
1160
- stderr.write(scan.adoptedOnly === true
1161
- ? `harness pack hook: the newest Understanding Report entry in the transcript for session ${childSessionId} was already adopted; emit a fresh report\n`
1162
- : `harness pack hook: no Understanding Report for session ${childSessionId} reached its transcript within ${autoCfg.reportScan.maxWaitMs}ms (waited ${scan.waitedMs}ms); blocking and asking the child to re-emit and retry\n`);
1163
- }
1164
- else {
1165
- stderr.write(`harness pack hook: the transcript at ${transcriptPath} exists but could not be read; the report for session ${childSessionId} cannot be captured\n`);
1166
- }
1167
- }
1168
- }
1169
- // Closes the `if (verified.reportPathHash !== undefined) { ... }
1170
- // else { <transcript scan> }` branch opened above.
1171
- }
1172
- }
1173
- }
1174
- }
1175
- }
1176
- // Task 8f637efd: best-effort observation of this call's
1177
- // `permission_mode`, independent of whether `auto_approve` is even
1178
- // configured: this call site is deliberately the SAME one
1179
- // `attemptAutoApproval` runs at just below (see
1180
- // permission-mode-observations.ts's module header for why every call
1181
- // that reaches this point already needed approval, and for the
1182
- // coverage gap this accepts). Feeds `harness doctor`'s
1183
- // missing-`auto_approve` finding; never a gate input.
1184
- if (generatedDir !== undefined) {
1185
- recordPermissionModeObservation(generatedDir, sessionId, event.permission_mode, stderr);
1186
- }
1187
- const resolveAutoLedger = () => {
1188
- if (opts.writeLedger)
1189
- return { write: opts.writeLedger };
1190
- const resolved = resolveManifestLedgerWriter(manifest, {
1191
- ...(opts.ledgerTimeoutMs !== undefined ? { ledgerTimeoutMs: opts.ledgerTimeoutMs } : {}),
1192
- });
1193
- return resolved.ok ? { write: resolved.write } : { write: null, reason: resolved.reason };
1194
- };
1195
- const auto = await attemptAutoApproval({
1196
- generatedDir,
1197
- sessionId,
1198
- payloadSessionId: event.session_id,
1199
- permissionMode: event.permission_mode,
1200
- // Slice 2 (agent-tasks/57058364) moved these two out of the shared
1201
- // body and onto the call sites: the Codex hook calls the SAME
1202
- // `attemptAutoApproval` with `CODEX_HARNESS` and its own
1203
- // session-consistency evidence. Claude Code's evidence is the hook
1204
- // process's `$CLAUDE_CODE_SESSION_ID`, exactly what the body used
1205
- // to read directly, so the wording of every diagnostic on this path
1206
- // is unchanged.
1207
- harness: CLAUDE_CODE_HARNESS,
1208
- // This hook's own verb on the audit-only ledger fact, and its own
1209
- // stderr prefix by omission (`label` defaults to "harness pack
1210
- // hook"): both keep this path's output byte-identical to slice 1's.
1211
- ledgerSource: AUTO_APPROVE_LEDGER_SOURCE,
1212
- sessionConsistency: { kind: "env", variable: "CLAUDE_CODE_SESSION_ID" },
1213
- packConfig: declared.config,
1214
- reportsDir,
1215
- // Folded with `inflightForged`: `attemptAutoApproval`'s own guard on
1216
- // this flag is the ACTUAL enforcement point for "a forgery is never
1217
- // laundered into an approval" (the `!inflightForged` added to the
1218
- // block above only skips a doomed delegation resolution; it does not
1219
- // by itself stop this call).
1220
- markerForged: markerForged || inflightForged,
1221
- // Names which of the two the flag above actually folds together, so
1222
- // the decline diagnostic says the right artifact. A real marker
1223
- // forgery keeps the plain "marker" wording even when both happen to
1224
- // be true, matching the higher-authority precedence `reason` above
1225
- // already gives it.
1226
- forgedKind: markerForged ? "marker" : inflightForged ? "inflight" : undefined,
1227
- // Slice 3: present only when the branch above verified a delegation
1228
- // for this child session. It supplies key ONE in place of a
1229
- // `when`-listed `permission_mode`; every other condition, key two
1230
- // included, is unchanged.
1231
- ...(delegation !== undefined ? { delegation } : {}),
1232
- stderr,
1233
- resolveLedger: resolveAutoLedger,
1234
- });
1235
- if (auto.approved) {
1236
- const autoDiagnostic = `harness pack hook: ${auto.detail}, allowing.`;
1237
- stderr.write(`${autoDiagnostic}\n`);
1238
- return {
1239
- exitCode: 0,
1240
- blocked: false,
1241
- approvalCheck: { approved: true, source: "marker", detail: auto.detail },
1242
- diagnostic: autoDiagnostic,
1243
- };
1244
- }
1245
- const diagnostic = `harness pack hook: BLOCK — ${reason}`;
1246
- stderr.write(`${diagnostic}\n`);
1247
- const configProducers = parseConfigProducers(declared.config["producers"], stderr);
1248
- const configUx = parseConfigUx(declared.config["ux"], stderr, "harness pack hook");
1249
- const escapeHint = approveEscapeHint(toolName, commandStr);
1250
- // Best-effort lookup of the session's latest parse-error log (task
1251
- // 823837fd, follow-up gate task 823837fd review): the standalone Stop
1252
- // hook / `harness approve understanding` stdin-report path writes one
1253
- // to `<reports-parent>/parse-errors/` when the agent's report failed to
1254
- // parse. Reused here (findLatestParseError already swallows I/O
1255
- // errors, same contract as the approve-time lookup) so a blocked agent
1256
- // whose report almost parsed can be told WHICH sections were
1257
- // malformed, not just "no approved report". Gated on `report.report
1258
- // === null` (no persisted report at all for this session): a report
1259
- // that WAS persisted but is merely pending approval must not surface a
1260
- // stale parse-error from an earlier, already-fixed attempt; mirrors
1261
- // the CLI's own gate (`if (!latest)`, approve/understanding.ts). Moved
1262
- // here, right before the block render and after every exemption
1263
- // early-return above, so the lookup only runs on the path that
1264
- // actually renders it (pure code motion from its previous location
1265
- // right after `checkPersistedReport`).
1266
- const latestParseError = report.report === null
1267
- ? findLatestParseError(path.join(path.dirname(reportsDir), "parse-errors"), sessionId, {
1268
- maxEntries: MAX_HOOK_LISTING_ENTRIES,
1269
- })
1270
- : null;
1271
- // The expiry notice belongs to the routine "no approval marker" reason
1272
- // only: a forged marker, a forged in-flight record or a refused task
1273
- // binding keep their own reasons and never carry it.
1274
- const expiryNotice = generatedDir !== undefined &&
1275
- !markerForged &&
1276
- !inflightForged &&
1277
- sessionBindingRefusedDetail === undefined
1278
- ? approvalExpiryNotice(markerTtlExpiry, report.report)
1279
- : undefined;
1280
- // The subagent record sentence, delivered to the AGENT through the
1281
- // same channel the slice-3 retry instruction uses (appended after both
1282
- // the `ux:` and legacy envelopes, so it survives either one) rather
1283
- // than staying stderr/audit-only, reaching only `reason` /
1284
- // `approvalCheck.detail` and never the agent-facing stdout JSON. A
1285
- // main-line block (`displayAgentId` undefined) never carries it.
1286
- //
1287
- // The retry instruction takes PRECEDENCE when both would apply: nothing
1288
- // stops a delegated session (ADR "Report capture under `-p`") from also
1289
- // being the parent of an in-flight subagent, but the two instructions
1290
- // disagree on what the agent should do next — the subagent sentence
1291
- // says stop, the retry instruction says retry — and the delegation
1292
- // path can still succeed on the very next retry regardless of the
1293
- // subagent record's own state, so telling the agent to stop here would
1294
- // be wrong.
1295
- const agentInstruction = reportScanTimedOut
1296
- ? DELEGATION_REPORT_RETRY_INSTRUCTION
1297
- : displayAgentId !== undefined && subagentRecordSentence.length > 0
1298
- ? subagentRecordSentence.trim()
1299
- : null;
1300
- stdout.write(`${blockJson(toolName, "no approved Understanding Report for this session", configProducers, configUx, sessionId, escapeHint, latestParseError?.malformedSections, agentInstruction, expiryNotice)}\n`);
1301
- return {
1302
- exitCode: 0,
1303
- blocked: true,
1304
- approvalCheck: { approved: false, source: "none", detail: reason },
1305
- diagnostic,
1306
- };
1307
- }
1308
- /**
1309
- * `harness pack hook pre-tool-use`. A stdin read that times out (the idle
1310
- * bound fired before stdin closed) is refused here instead of being handed to
1311
- * the gate as an empty or truncated event it would allow: the event never
1312
- * finished arriving, so the gate cannot judge the tool call (task 7dfdcaaf).
1313
- * The operator pause still wins; every other input runs the gate unchanged.
1314
- */
1315
- export function runPackHookPreToolUseCli(opts = {}) {
1316
- return runGateWithStdinRefusal(opts, (stderr) => checkHookPause("pre-tool-use", stderr, opts, opts.generatedDir, opts.now).paused, (reason, stdout, stderr) => {
1317
- const diagnostic = `harness pack hook: BLOCK: ${reason}`;
1318
- stderr.write(`${diagnostic}\n`);
1319
- stdout.write(`${stdinTimeoutBlockJson(reason)}\n`);
1320
- return {
1321
- exitCode: 0,
1322
- blocked: true,
1323
- approvalCheck: { approved: false, source: "none", detail: reason },
1324
- diagnostic,
1325
- };
1326
- }, runPackHookPreToolUseCliInner);
1327
- }
1328
- //# sourceMappingURL=hook-pre-tool-use.js.map