@guilz-dev/belay 0.4.0 → 0.7.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 (240) hide show
  1. package/README.md +27 -2
  2. package/dist/adapters/claude/runtime-entry.js +54 -25
  3. package/dist/adapters/layouts/scope.js +3 -2
  4. package/dist/adapters/shared/gate-runtime.d.ts +8 -2
  5. package/dist/adapters/shared/gate-runtime.js +579 -181
  6. package/dist/bundle/claude-runtime.mjs +13574 -6334
  7. package/dist/bundle/codex-runtime.mjs +13108 -5903
  8. package/dist/bundle/cursor-runtime.mjs +12676 -5476
  9. package/dist/cli.js +80 -2
  10. package/dist/commands/approve.js +34 -10
  11. package/dist/commands/classify-for-report.js +1 -1
  12. package/dist/commands/config.d.ts +8 -0
  13. package/dist/commands/config.js +134 -11
  14. package/dist/commands/doctor.js +87 -1
  15. package/dist/commands/explain.js +11 -1
  16. package/dist/commands/judge.js +3 -2
  17. package/dist/commands/recovery-checkpoints.d.ts +164 -0
  18. package/dist/commands/recovery-checkpoints.js +347 -0
  19. package/dist/commands/revoke.js +19 -7
  20. package/dist/commands/session.d.ts +20 -0
  21. package/dist/commands/session.js +51 -0
  22. package/dist/commands/status.js +7 -0
  23. package/dist/config-io.js +91 -23
  24. package/dist/conformance/guarantee-posture.d.ts +21 -0
  25. package/dist/conformance/guarantee-posture.js +46 -0
  26. package/dist/conformance/guarantee-table.d.ts +1 -0
  27. package/dist/conformance/guarantee-table.js +20 -9
  28. package/dist/conformance/types.d.ts +1 -0
  29. package/dist/core/approval-replay.d.ts +1 -1
  30. package/dist/core/approval-replay.js +4 -1
  31. package/dist/core/approval-service.d.ts +6 -1
  32. package/dist/core/approval-service.js +87 -24
  33. package/dist/core/approval.d.ts +4 -0
  34. package/dist/core/approval.js +19 -5
  35. package/dist/core/audit-types.d.ts +13 -0
  36. package/dist/core/capability/approval-state-mutation.d.ts +32 -0
  37. package/dist/core/capability/approval-state-mutation.js +165 -0
  38. package/dist/core/capability/approval-v3.d.ts +32 -0
  39. package/dist/core/capability/approval-v3.js +114 -0
  40. package/dist/core/capability/attestation.d.ts +16 -0
  41. package/dist/core/capability/attestation.js +62 -0
  42. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  43. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  44. package/dist/core/capability/boundary-driver-container.d.ts +19 -0
  45. package/dist/core/capability/boundary-driver-container.js +189 -0
  46. package/dist/core/capability/boundary-driver.d.ts +29 -0
  47. package/dist/core/capability/boundary-driver.js +44 -0
  48. package/dist/core/capability/boundary-egress.d.ts +21 -0
  49. package/dist/core/capability/boundary-egress.js +109 -0
  50. package/dist/core/capability/boundary-grant-materialize.d.ts +14 -0
  51. package/dist/core/capability/boundary-grant-materialize.js +93 -0
  52. package/dist/core/capability/boundary-profile.d.ts +15 -0
  53. package/dist/core/capability/boundary-profile.js +25 -0
  54. package/dist/core/capability/boundary-run.d.ts +48 -0
  55. package/dist/core/capability/boundary-run.js +51 -0
  56. package/dist/core/capability/boundary-session.d.ts +61 -0
  57. package/dist/core/capability/boundary-session.js +138 -0
  58. package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
  59. package/dist/core/capability/boundary-workspace-mount.js +68 -0
  60. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  61. package/dist/core/capability/capability-request-hash.js +8 -0
  62. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  63. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  64. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  65. package/dist/core/capability/gate-policy-shadow.js +130 -0
  66. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  67. package/dist/core/capability/gate-shadow-audit.js +1 -0
  68. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  69. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  70. package/dist/core/capability/grant-consumption.d.ts +4 -0
  71. package/dist/core/capability/grant-consumption.js +11 -0
  72. package/dist/core/capability/grant-lease.d.ts +44 -0
  73. package/dist/core/capability/grant-lease.js +243 -0
  74. package/dist/core/capability/grant-loader.d.ts +12 -0
  75. package/dist/core/capability/grant-loader.js +23 -0
  76. package/dist/core/capability/grant-match.d.ts +6 -0
  77. package/dist/core/capability/grant-match.js +96 -0
  78. package/dist/core/capability/grant.d.ts +16 -0
  79. package/dist/core/capability/grant.js +16 -0
  80. package/dist/core/capability/index.d.ts +8 -0
  81. package/dist/core/capability/index.js +7 -0
  82. package/dist/core/capability/limits.d.ts +7 -0
  83. package/dist/core/capability/limits.js +55 -0
  84. package/dist/core/capability/policy-bridge.d.ts +18 -0
  85. package/dist/core/capability/policy-bridge.js +51 -0
  86. package/dist/core/capability/policy-engine.d.ts +77 -0
  87. package/dist/core/capability/policy-engine.js +663 -0
  88. package/dist/core/capability/policy-types.d.ts +20 -0
  89. package/dist/core/capability/policy-types.js +1 -0
  90. package/dist/core/capability/request.d.ts +48 -0
  91. package/dist/core/capability/request.js +1 -0
  92. package/dist/core/capability/resolver.d.ts +4 -0
  93. package/dist/core/capability/resolver.js +4 -0
  94. package/dist/core/classify-subagent.d.ts +2 -1
  95. package/dist/core/classify-subagent.js +38 -2
  96. package/dist/core/classify-tool.js +126 -62
  97. package/dist/core/config.d.ts +34 -3
  98. package/dist/core/config.js +118 -3
  99. package/dist/core/effect-ir/audit.d.ts +9 -0
  100. package/dist/core/effect-ir/audit.js +86 -0
  101. package/dist/core/effect-ir/build.d.ts +31 -0
  102. package/dist/core/effect-ir/build.js +277 -0
  103. package/dist/core/effect-ir/index.d.ts +8 -0
  104. package/dist/core/effect-ir/index.js +7 -0
  105. package/dist/core/effect-ir/normalize.d.ts +4 -0
  106. package/dist/core/effect-ir/normalize.js +104 -0
  107. package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
  108. package/dist/core/effect-ir/package-exec-eval.js +152 -0
  109. package/dist/core/effect-ir/package-exec.d.ts +29 -0
  110. package/dist/core/effect-ir/package-exec.js +292 -0
  111. package/dist/core/effect-ir/policy.d.ts +13 -0
  112. package/dist/core/effect-ir/policy.js +77 -0
  113. package/dist/core/effect-ir/types.d.ts +55 -0
  114. package/dist/core/effect-ir/types.js +1 -0
  115. package/dist/core/egress/allowlist.d.ts +1 -0
  116. package/dist/core/egress/allowlist.js +8 -0
  117. package/dist/core/egress-approval.js +45 -51
  118. package/dist/core/gate-contract.d.ts +5 -0
  119. package/dist/core/gate-contract.js +5 -0
  120. package/dist/core/gate-engine.js +30 -8
  121. package/dist/core/index.d.ts +3 -1
  122. package/dist/core/index.js +2 -1
  123. package/dist/core/judge-cloud-consent.js +29 -15
  124. package/dist/core/judge-doctor.js +39 -14
  125. package/dist/core/judge-fallback-hints.d.ts +13 -0
  126. package/dist/core/judge-fallback-hints.js +108 -0
  127. package/dist/core/network-endpoint.d.ts +10 -0
  128. package/dist/core/network-endpoint.js +51 -0
  129. package/dist/core/path-utils.d.ts +1 -0
  130. package/dist/core/path-utils.js +5 -2
  131. package/dist/core/process-runner.d.ts +10 -0
  132. package/dist/core/process-runner.js +124 -0
  133. package/dist/core/recover-advice.js +2 -1
  134. package/dist/core/recovery/artifact-store.d.ts +29 -0
  135. package/dist/core/recovery/artifact-store.js +336 -0
  136. package/dist/core/recovery/capability.d.ts +3 -0
  137. package/dist/core/recovery/capability.js +22 -0
  138. package/dist/core/recovery/checkpoint.d.ts +25 -0
  139. package/dist/core/recovery/checkpoint.js +260 -0
  140. package/dist/core/recovery/fail-closed.d.ts +6 -0
  141. package/dist/core/recovery/fail-closed.js +25 -0
  142. package/dist/core/recovery/index.d.ts +5 -0
  143. package/dist/core/recovery/index.js +4 -0
  144. package/dist/core/recovery/operator-guidance.d.ts +7 -0
  145. package/dist/core/recovery/operator-guidance.js +39 -0
  146. package/dist/core/recovery/reconcile.d.ts +3 -0
  147. package/dist/core/recovery/reconcile.js +48 -0
  148. package/dist/core/recovery/resource-identity.d.ts +4 -0
  149. package/dist/core/recovery/resource-identity.js +34 -0
  150. package/dist/core/recovery/restore.d.ts +13 -0
  151. package/dist/core/recovery/restore.js +114 -0
  152. package/dist/core/recovery/snapshot-node.d.ts +27 -0
  153. package/dist/core/recovery/snapshot-node.js +261 -0
  154. package/dist/core/recovery/types.d.ts +104 -0
  155. package/dist/core/recovery/types.js +1 -0
  156. package/dist/core/standing-allow.js +3 -0
  157. package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
  158. package/dist/core/transactional/apply-observed-changes.js +275 -0
  159. package/dist/core/transactional/backend-selector.d.ts +8 -0
  160. package/dist/core/transactional/backend-selector.js +118 -0
  161. package/dist/core/transactional/backend.d.ts +55 -0
  162. package/dist/core/transactional/backend.js +1 -0
  163. package/dist/core/transactional/diff-evaluator.js +4 -1
  164. package/dist/core/transactional/eligibility.js +4 -0
  165. package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
  166. package/dist/core/transactional/file-checkpoint-backend.js +278 -0
  167. package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
  168. package/dist/core/transactional/file-checkpoint-git.js +217 -0
  169. package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
  170. package/dist/core/transactional/file-checkpoint-staging.js +54 -0
  171. package/dist/core/transactional/file-clone.d.ts +15 -0
  172. package/dist/core/transactional/file-clone.js +139 -0
  173. package/dist/core/transactional/file-tree-path.d.ts +7 -0
  174. package/dist/core/transactional/file-tree-path.js +50 -0
  175. package/dist/core/transactional/file-tree.d.ts +41 -0
  176. package/dist/core/transactional/file-tree.js +186 -0
  177. package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
  178. package/dist/core/transactional/git-worktree-backend.js +50 -0
  179. package/dist/core/transactional/git-worktree.d.ts +14 -9
  180. package/dist/core/transactional/git-worktree.js +62 -87
  181. package/dist/core/transactional/index.d.ts +11 -0
  182. package/dist/core/transactional/index.js +10 -0
  183. package/dist/core/transactional/reasons.d.ts +1 -0
  184. package/dist/core/transactional/reasons.js +5 -0
  185. package/dist/core/transactional/runner.js +171 -23
  186. package/dist/core/transactional/snapshot-node.d.ts +26 -0
  187. package/dist/core/transactional/snapshot-node.js +87 -0
  188. package/dist/core/transactional/types.d.ts +21 -1
  189. package/dist/core/types.d.ts +30 -3
  190. package/dist/core/verdict/adapter.d.ts +3 -4
  191. package/dist/core/verdict/adapter.js +24 -12
  192. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  193. package/dist/core/verdict/cursor-acp-client.js +345 -0
  194. package/dist/core/verdict/egress-classify.d.ts +2 -2
  195. package/dist/core/verdict/egress-classify.js +2 -2
  196. package/dist/core/verdict/git-classifier.d.ts +26 -0
  197. package/dist/core/verdict/git-classifier.js +533 -0
  198. package/dist/core/verdict/judge-audit.js +1 -0
  199. package/dist/core/verdict/judge-baseline.d.ts +1 -1
  200. package/dist/core/verdict/judge-baseline.js +1 -0
  201. package/dist/core/verdict/judge-broker-service.js +12 -0
  202. package/dist/core/verdict/judge-catalog.js +1 -0
  203. package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
  204. package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
  205. package/dist/core/verdict/judge-cli.js +5 -3
  206. package/dist/core/verdict/judge-provider-matrix.js +1 -1
  207. package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
  208. package/dist/core/verdict/judge-runtime-config.js +10 -6
  209. package/dist/core/verdict/judge-session-broker.d.ts +1 -1
  210. package/dist/core/verdict/judge-session-broker.js +10 -5
  211. package/dist/core/verdict/judge-transport.d.ts +5 -1
  212. package/dist/core/verdict/judge-transport.js +24 -14
  213. package/dist/core/verdict/judge.d.ts +2 -15
  214. package/dist/core/verdict/judge.js +5 -68
  215. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  216. package/dist/core/verdict/launcher-resolve.js +14 -16
  217. package/dist/core/verdict/prescan.d.ts +15 -0
  218. package/dist/core/verdict/prescan.js +68 -0
  219. package/dist/core/verdict/shell-policy.d.ts +26 -0
  220. package/dist/core/verdict/shell-policy.js +40 -0
  221. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  222. package/dist/core/verdict/shell-semantics.js +225 -0
  223. package/dist/core/verdict/types.d.ts +18 -1
  224. package/dist/core/verdict/verdict.js +313 -178
  225. package/dist/corpus/evaluate.js +3 -7
  226. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  227. package/dist/corpus/gate-latency-budget.js +33 -0
  228. package/dist/corpus/mutators.d.ts +1 -1
  229. package/dist/corpus/mutators.js +11 -0
  230. package/dist/corpus/runtime-match.js +1 -3
  231. package/dist/corpus/standing-allow-catalog.generated.js +29 -9
  232. package/dist/judge-broker-daemon.js +15 -1
  233. package/dist/services/sandbox-service.d.ts +4 -0
  234. package/dist/services/sandbox-service.js +28 -2
  235. package/dist/types.d.ts +13 -2
  236. package/dist/version.d.ts +1 -1
  237. package/dist/version.js +1 -1
  238. package/package.json +1 -1
  239. package/skills/belay/SKILL.md +8 -3
  240. package/skills/belay/belay-approve.md +10 -7
package/README.md CHANGED
@@ -109,6 +109,16 @@ When an action is denied, approve the **next matching action once** by sending:
109
109
  /belay-approve <approval-id>
110
110
  ```
111
111
 
112
+ With the default `approval.flow: one_step`, an editor approval immediately replays the exact denied
113
+ shell action; no follow-up prompt is required. Tool and subagent approvals still require retrying
114
+ the original action unchanged. The one-shot grant is claimed before shell replay, so a failed or
115
+ timed-out replay requires a fresh approval. Replay runs through the configured `BoundaryDriver`.
116
+ After replay succeeds, the approval prompt continues the current host turn so the agent can report
117
+ the result and resume its workflow without another user message.
118
+ You may put a follow-up instruction on the lines after `/belay-approve <id>`; Belay replays the
119
+ approved shell action first, then lets the remaining prompt continue. CLI replay remains explicit:
120
+ `belay approve <approval-id> --replay`.
121
+
112
122
  Approvals are one-shot and expire after 15 minutes by default. Every decision is
113
123
  written to `.cursor/belay/audit.ndjson`, `.claude/belay/audit.ndjson`, or
114
124
  `.codex/belay/audit.ndjson` (depending on the adapter).
@@ -239,12 +249,23 @@ Notable settings:
239
249
  current OS user. `upgrade` migrates repo-local approvals in; disabling merges
240
250
  them back. File-mutation tools and shell redirects cannot write control-plane
241
251
  paths while it is enabled.
252
+ - **`policy.transactional.checkpoint.enabled: true`** — after enabling the
253
+ transactional git-worktree runner, persist repo-local pre-images and expose them through
254
+ `belay recover list`. Restore is conflict-checked and always requires a signed
255
+ out-of-band, exact one-shot approval. Network, remote Git, databases, processes, and
256
+ repo-external effects are outside this guarantee.
242
257
  - **Cloud judge** — configure with `belay config` (interactive) or `belay config set judge.providerId <id>`.
243
258
  Providers: `ollama`, `codex`, `claude`, `cursor`. **Provider** is the vendor/service
244
259
  (`judge.providerId`); **driver** is the API compatibility layer (`judge.provider`:
245
260
  `ollama`, `openai-compatible`, or `anthropic`); **host** is where hooks install
246
261
  (`config.adapter`: `cursor`, `claude`, `codex`). Set `judge.endpoint` when needed;
247
- credentials via `belay config credential mode project|apiKey` or env vars. Record egress
262
+ credentials via `belay config credential mode project|apiKey` or env vars. **`project`**
263
+ reads API keys from the shell environment at runtime (`BELAY_JUDGE_API_KEY`, then provider
264
+ vars such as `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `CURSOR_API_KEY`); Belay does **not**
265
+ load `.env` files for Judge. When the native CLI is available, `project` can also use the
266
+ host login session without a stored key. **`apiKey`** stores a key in Belay's credential store
267
+ (`credentials.json`, mode `0600`; under `~/.config/belay/` when control plane is enabled).
268
+ Record egress
248
269
  consent during `belay config` or via `belay judge consent` → `belay approve` →
249
270
  `belay judge use … --cloud-consent-approval-id`. Cloud providers can use native CLI
250
271
  transport without `judge.endpoint` when the host CLI is available (`codex-cli`,
@@ -275,7 +296,11 @@ belay doctor [--fix] # check (and repair) floor health
275
296
  belay status # show install scope / skill-only state
276
297
  belay metrics # would-block / verdict summary
277
298
  belay report # audit log report
278
- belay recover [--command "rm important.ts"] # find recovery candidates
299
+ belay recover [advice] [--command "rm important.ts"] # advisory candidates only
300
+ belay recover status # checkpoint backend and state counts
301
+ belay recover list # proven repo-local recovery points
302
+ belay recover show <checkpoint-id>
303
+ belay recover apply <checkpoint-id> # signed OOB exact one-shot approval required
279
304
  belay explain -- <shell-command> # inspect a verdict
280
305
  belay explain --kind subagent -- "deploy to production"
281
306
  belay explain --kind tool --tool Write -- .env
@@ -1,4 +1,7 @@
1
1
  import process from 'node:process';
2
+ import { effectPlanAuditFields } from '../../core/effect-ir/audit.js';
3
+ import { buildCapabilityEffectPlan } from '../../core/effect-ir/build.js';
4
+ import { hashValue, toolFingerprint } from '../../core/fingerprint.js';
2
5
  import { unnormalizedGateVerdict } from '../../core/gate-contract.js';
3
6
  import { claudeLayout } from '../layouts/claude.js';
4
7
  import { appendObservedAudit, createDefaultGateRuntimeDeps, evaluateGatedAction, gateVerdictToClaudePreToolUseResponse, gateVerdictToClaudeUserPromptResponse, processApprovalPrompt, resolveGateConfig, } from '../shared/gate-runtime.js';
@@ -22,6 +25,23 @@ async function readStdinJson() {
22
25
  function jsonResponse(value) {
23
26
  process.stdout.write(`${JSON.stringify(value)}\n`);
24
27
  }
28
+ function claudeFallbackToolVerdict(params) {
29
+ return {
30
+ ...unnormalizedGateVerdict({
31
+ reason: params.reason,
32
+ mode: params.mode,
33
+ user_message: params.userMessage,
34
+ agent_message: params.agentMessage,
35
+ }),
36
+ effectPlan: buildCapabilityEffectPlan({
37
+ actionKind: 'tool',
38
+ summary: params.toolName,
39
+ inputFingerprint: hashValue(`claude-fallback:${params.reason}:${toolFingerprint(params.toolName, params.payload, params.repoRoot)}`),
40
+ requests: [],
41
+ effectFree: false,
42
+ }),
43
+ };
44
+ }
25
45
  async function loadRuntimeContext(cwd) {
26
46
  const repoRoot = findRepoRoot(cwd, claudeLayout);
27
47
  const configPath = claudeLayout.configPath(repoRoot);
@@ -120,13 +140,10 @@ export async function runBeforeSubmitPromptHook() {
120
140
  jsonResponse(gateVerdictToClaudeUserPromptResponse(result));
121
141
  }
122
142
  catch {
123
- jsonResponse({
124
- hookSpecificOutput: {
125
- hookEventName: 'UserPromptSubmit',
126
- continue: false,
127
- user_message: 'belay failed while processing approval state. Run belay doctor, then retry.',
128
- },
129
- });
143
+ jsonResponse(gateVerdictToClaudeUserPromptResponse({
144
+ continue: false,
145
+ user_message: 'belay failed while processing approval state. Run belay doctor, then retry.',
146
+ }));
130
147
  }
131
148
  }
132
149
  export async function runShellGateHook() {
@@ -167,6 +184,15 @@ export async function runToolGateHook(_eventName) {
167
184
  const toolName = String(payload.tool_name ?? '');
168
185
  const mappedKind = mapClaudeToolName(toolName);
169
186
  if (mappedKind === 'mcp') {
187
+ const verdict = claudeFallbackToolVerdict({
188
+ toolName,
189
+ payload,
190
+ repoRoot: ctx.repoRoot,
191
+ reason: 'unsupported_mcp_tool',
192
+ mode: ctx.config.mode,
193
+ userMessage: 'belay blocked this MCP tool because Claude MCP payloads are not normalized safely yet.',
194
+ agentMessage: 'Belay denied this MCP tool because its payload shape is unsupported.',
195
+ });
170
196
  await deps.appendAudit(ctx, {
171
197
  event: 'preToolUse',
172
198
  kind: 'tool',
@@ -176,17 +202,21 @@ export async function runToolGateHook(_eventName) {
176
202
  wouldBlock: true,
177
203
  permission: 'deny',
178
204
  summary: toolName,
179
- });
180
- const verdict = unnormalizedGateVerdict({
181
- reason: 'unsupported_mcp_tool',
182
- mode: ctx.config.mode,
183
- user_message: 'belay blocked this MCP tool because Claude MCP payloads are not normalized safely yet.',
184
- agent_message: 'Belay denied this MCP tool because its payload shape is unsupported.',
205
+ ...effectPlanAuditFields(verdict.effectPlan),
185
206
  });
186
207
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict));
187
208
  return;
188
209
  }
189
210
  if (!mappedKind) {
211
+ const verdict = claudeFallbackToolVerdict({
212
+ toolName,
213
+ payload,
214
+ repoRoot: ctx.repoRoot,
215
+ reason: 'unmapped_tool',
216
+ mode: ctx.config.mode,
217
+ userMessage: 'belay does not recognize this tool action. Run belay doctor, then retry.',
218
+ agentMessage: 'Belay denied this action because the tool could not be normalized.',
219
+ });
190
220
  await deps.appendAudit(ctx, {
191
221
  event: 'preToolUse',
192
222
  kind: 'tool',
@@ -196,18 +226,22 @@ export async function runToolGateHook(_eventName) {
196
226
  wouldBlock: true,
197
227
  permission: 'deny',
198
228
  summary: toolName,
199
- });
200
- const verdict = unnormalizedGateVerdict({
201
- reason: 'unmapped_tool',
202
- mode: ctx.config.mode,
203
- user_message: 'belay does not recognize this tool action. Run belay doctor, then retry.',
204
- agent_message: 'Belay denied this action because the tool could not be normalized.',
229
+ ...effectPlanAuditFields(verdict.effectPlan),
205
230
  });
206
231
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict));
207
232
  return;
208
233
  }
209
234
  const normalizedPayload = normalizeClaudeToolPayload(toolName, payload);
210
235
  if (!normalizedPayload) {
236
+ const verdict = claudeFallbackToolVerdict({
237
+ toolName,
238
+ payload,
239
+ repoRoot: ctx.repoRoot,
240
+ reason: 'normalization_failed',
241
+ mode: ctx.config.mode,
242
+ userMessage: 'belay could not normalize this Claude tool payload. Run belay doctor, then retry.',
243
+ agentMessage: 'Belay denied this action because the Claude tool payload could not be normalized.',
244
+ });
211
245
  await deps.appendAudit(ctx, {
212
246
  event: 'preToolUse',
213
247
  kind: 'tool',
@@ -217,12 +251,7 @@ export async function runToolGateHook(_eventName) {
217
251
  wouldBlock: true,
218
252
  permission: 'deny',
219
253
  summary: toolName,
220
- });
221
- const verdict = unnormalizedGateVerdict({
222
- reason: 'normalization_failed',
223
- mode: ctx.config.mode,
224
- user_message: 'belay could not normalize this Claude tool payload. Run belay doctor, then retry.',
225
- agent_message: 'Belay denied this action because the Claude tool payload could not be normalized.',
254
+ ...effectPlanAuditFields(verdict.effectPlan),
226
255
  });
227
256
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict));
228
257
  return;
@@ -1,5 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
+ import { isPathOutsideRoot } from '../../core/path-utils.js';
3
4
  function agentHomeDir(adapter) {
4
5
  const home = os.homedir();
5
6
  if (adapter === 'cursor') {
@@ -23,13 +24,13 @@ export function isPathInside(child, parent) {
23
24
  const resolvedChild = path.resolve(child);
24
25
  const resolvedParent = path.resolve(parent);
25
26
  const relative = path.relative(resolvedParent, resolvedChild);
26
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
27
+ return relative === '' || (!isPathOutsideRoot(relative) && !path.isAbsolute(relative));
27
28
  }
28
29
  export function buildRunnerInvocation(platform, hooksDir, repoRoot, hookScript, ...args) {
29
30
  const runnerFile = platform === 'win32' ? 'belay-runner.cmd' : 'belay-runner';
30
31
  const runnerAbs = path.resolve(hooksDir, runnerFile);
31
32
  const relative = path.relative(path.resolve(repoRoot), runnerAbs);
32
- const useRelative = relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative);
33
+ const useRelative = relative.length > 0 && !isPathOutsideRoot(relative) && !path.isAbsolute(relative);
33
34
  const runnerRef = useRelative
34
35
  ? platform === 'win32'
35
36
  ? `.\\${relative.split(path.sep).join('\\')}`
@@ -1,6 +1,5 @@
1
1
  import { type ApprovalReplayHint } from '../../core/approval-replay.js';
2
- import type { GatedActionKind } from '../../core/gate-contract.js';
3
- import { type GatePermissionResponse, type GateVerdict } from '../../core/gate-contract.js';
2
+ import { type GatedActionKind, type GatePermissionResponse, type GateVerdict } from '../../core/gate-contract.js';
4
3
  import { GateNormalizationError } from '../../core/gate-engine.js';
5
4
  import { type BelayConfigV3 } from '../../core/index.js';
6
5
  import type { ApprovalStateFile, ClassifierOptions } from '../../core/types.js';
@@ -24,6 +23,13 @@ export interface GateRuntimeDeps {
24
23
  state: ApprovalStateFile;
25
24
  }>;
26
25
  writeApprovals: (filePath: string, state: ApprovalStateFile) => Promise<void>;
26
+ replayApprovedShell: (ctx: GateRuntimeContext, command: string, cwd: string, timeoutMs: number) => Promise<{
27
+ exitCode: number | null;
28
+ timedOut: boolean;
29
+ signal: string | null;
30
+ stdout?: string;
31
+ stderr?: string;
32
+ }>;
27
33
  }
28
34
  export declare function createDefaultGateRuntimeDeps(): GateRuntimeDeps;
29
35
  export declare function resolveGateConfig(ctx: Pick<GateRuntimeContext, 'layout' | 'repoRoot' | 'configPath'>, deps: GateRuntimeDeps): Promise<BelayConfigV3>;