@indigoai-us/hq-cli 5.98.3 → 5.99.1

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 (73) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +347 -0
  3. package/assets/scaffold/core/scripts/hook-lib.sh +557 -0
  4. package/assets/scaffold/core/scripts/hq-session.sh +251 -0
  5. package/assets/scaffold/core/scripts/lib/session-id.sh +96 -0
  6. package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +52 -0
  7. package/dist/commands/core-checkpoint.js +11 -3
  8. package/dist/commands/core.js +60 -5
  9. package/dist/commands/doctor.d.ts +97 -0
  10. package/dist/commands/doctor.js +228 -0
  11. package/dist/commands/scaffold-fast.d.ts +41 -0
  12. package/dist/commands/scaffold-fast.js +57 -0
  13. package/dist/fast-core.d.ts +16 -0
  14. package/dist/fast-core.js +47 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +10 -1
  17. package/dist/lib/core-utils/soft-timeout.d.ts +55 -0
  18. package/dist/lib/core-utils/soft-timeout.js +205 -0
  19. package/dist/lib/core-utils/timeout-guard.d.ts +62 -0
  20. package/dist/lib/core-utils/timeout-guard.js +207 -0
  21. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +194 -0
  22. package/dist/lib/doctor/__testing__/fake-hq-tree.js +357 -0
  23. package/dist/lib/doctor/allowed-divergence.d.ts +72 -0
  24. package/dist/lib/doctor/allowed-divergence.js +134 -0
  25. package/dist/lib/doctor/checks/claude-wiring.d.ts +55 -0
  26. package/dist/lib/doctor/checks/claude-wiring.js +524 -0
  27. package/dist/lib/doctor/checks/codex-wiring.d.ts +45 -0
  28. package/dist/lib/doctor/checks/codex-wiring.js +376 -0
  29. package/dist/lib/doctor/checks/grok-wiring.d.ts +35 -0
  30. package/dist/lib/doctor/checks/grok-wiring.js +186 -0
  31. package/dist/lib/doctor/checks/runtime-probe.d.ts +101 -0
  32. package/dist/lib/doctor/checks/runtime-probe.js +335 -0
  33. package/dist/lib/doctor/compat.d.ts +85 -0
  34. package/dist/lib/doctor/compat.js +102 -0
  35. package/dist/lib/doctor/deep/classify.d.ts +61 -0
  36. package/dist/lib/doctor/deep/classify.js +75 -0
  37. package/dist/lib/doctor/deep/effects.d.ts +107 -0
  38. package/dist/lib/doctor/deep/effects.js +229 -0
  39. package/dist/lib/doctor/deep/executor.d.ts +112 -0
  40. package/dist/lib/doctor/deep/executor.js +369 -0
  41. package/dist/lib/doctor/deep/parity.d.ts +129 -0
  42. package/dist/lib/doctor/deep/parity.js +355 -0
  43. package/dist/lib/doctor/deep/sandbox.d.ts +190 -0
  44. package/dist/lib/doctor/deep/sandbox.js +572 -0
  45. package/dist/lib/doctor/fix/apply.d.ts +119 -0
  46. package/dist/lib/doctor/fix/apply.js +352 -0
  47. package/dist/lib/doctor/fix/backup.d.ts +40 -0
  48. package/dist/lib/doctor/fix/backup.js +64 -0
  49. package/dist/lib/doctor/fix/remediation.d.ts +71 -0
  50. package/dist/lib/doctor/fix/remediation.js +103 -0
  51. package/dist/lib/doctor/fixtures/discover.d.ts +96 -0
  52. package/dist/lib/doctor/fixtures/discover.js +287 -0
  53. package/dist/lib/doctor/fixtures/schema.d.ts +171 -0
  54. package/dist/lib/doctor/fixtures/schema.js +248 -0
  55. package/dist/lib/doctor/hook-gate-profiles.d.ts +55 -0
  56. package/dist/lib/doctor/hook-gate-profiles.js +107 -0
  57. package/dist/lib/doctor/json-output.d.ts +90 -0
  58. package/dist/lib/doctor/json-output.js +76 -0
  59. package/dist/lib/doctor/payload-shapes.d.ts +170 -0
  60. package/dist/lib/doctor/payload-shapes.js +275 -0
  61. package/dist/lib/doctor/platform.d.ts +244 -0
  62. package/dist/lib/doctor/platform.js +490 -0
  63. package/dist/lib/doctor/registry.d.ts +49 -0
  64. package/dist/lib/doctor/registry.js +176 -0
  65. package/dist/lib/doctor/report.d.ts +87 -0
  66. package/dist/lib/doctor/report.js +164 -0
  67. package/dist/lib/doctor/types.d.ts +87 -0
  68. package/dist/lib/doctor/types.js +29 -0
  69. package/dist/main.js +6 -0
  70. package/dist/utils/version-check.js +2 -2
  71. package/dist/utils/version-gate.d.ts +1 -1
  72. package/dist/utils/version-gate.js +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Per-platform hook payload shapes, tool-name aliasing, and block protocols
3
+ * (US-010).
4
+ *
5
+ * `hq doctor --deep-test` replays one fixture case through all three platform
6
+ * adapters and compares their verdicts (parity.ts). The three platforms do not
7
+ * speak the same wire language, and this module owns every difference so the
8
+ * replay orchestration can stay platform-agnostic:
9
+ *
10
+ * 1. Payload shape. Claude and Codex hooks read a snake_case document
11
+ * (`hook_event_name` / `tool_name` / `tool_input`). Grok speaks camelCase
12
+ * (`hookEventName` / `toolName` / `toolInput`) and its own tool names
13
+ * (`Shell`, `StrReplace`, …). {@link toClaudePayload}, {@link toCodexPayload},
14
+ * and {@link toGrokPayload} normalise one case into each shape.
15
+ *
16
+ * 2. Tool-name aliases. Grok surfaces the same underlying tool under several
17
+ * names — `Shell` / `run_terminal_command` for Bash, `StrReplace` /
18
+ * `search_replace` for Edit, `write` for Write, and so on. The alias tables
19
+ * here mirror the case statement in `.grok/hooks/hq-grok-hook-adapter.sh`
20
+ * exactly, so a payload we emit is one the real adapter recognises, and a
21
+ * Grok name the adapter would receive maps back to the canonical Claude
22
+ * tool the fixtures are written against.
23
+ *
24
+ * 3. Block protocol. Claude and Codex signal a block with a non-zero exit.
25
+ * Grok's PreToolUse block is a stdout `{"decision":"deny"}` document AND/OR
26
+ * a non-zero exit — {@link interpretGrokBlock} recognises either, so a
27
+ * correct Grok block that exits 0 with a deny decision is not misread as an
28
+ * allow. Grok's passive events (everything but PreToolUse) run side-effect
29
+ * hooks but cannot inject model-facing context or emit a decision at all;
30
+ * {@link grokCanBlock} names that limitation for the replay to report NA.
31
+ *
32
+ * Everything here is pure and exhaustively unit-tested; parity.ts wires these to
33
+ * the real adapters and to the verdict comparison.
34
+ */
35
+ /** The three platforms a fixture case is replayed through. */
36
+ export type PlatformId = "claude" | "codex" | "grok";
37
+ /** Canonical platform order for side-by-side reporting. */
38
+ export declare const PARITY_PLATFORMS: readonly PlatformId[];
39
+ /**
40
+ * The minimal slice of a fixture case the payload builders need: the lifecycle
41
+ * event, the tool name (canonical Claude name, as fixtures are authored), and
42
+ * the opaque tool input. A {@link import("./fixtures/schema.js").FixtureCase} is
43
+ * assignable to this.
44
+ */
45
+ export interface CaseShape {
46
+ /** Lifecycle event, e.g. `PreToolUse`. */
47
+ event: string;
48
+ /** Canonical Claude tool name, e.g. `Bash`, `Edit`, `Read`. */
49
+ tool: string;
50
+ /** The tool input payload, opaque to normalisation. */
51
+ input: unknown;
52
+ }
53
+ /** Common fields stamped onto every payload so session-scoped hooks behave. */
54
+ export interface PayloadContext {
55
+ /** Working directory the hook should see (the sandbox root). */
56
+ cwd: string;
57
+ /** Session id, so per-session dedupe does not collapse into a shared ledger. */
58
+ sessionId: string;
59
+ }
60
+ /** The snake_case document Claude and Codex hooks read from stdin. */
61
+ export interface SnakeCasePayload {
62
+ hook_event_name: string;
63
+ tool_name: string;
64
+ tool_input: unknown;
65
+ cwd: string;
66
+ session_id: string;
67
+ }
68
+ /** The camelCase document the Grok adapter reads from stdin. */
69
+ export interface GrokPayload {
70
+ hookEventName: string;
71
+ toolName: string;
72
+ toolInput: unknown;
73
+ cwd: string;
74
+ sessionId: string;
75
+ }
76
+ /**
77
+ * The canonical Grok tool name to emit for each canonical Claude tool. These are
78
+ * names the real adapter's case statement recognises; an unlisted tool is passed
79
+ * through unchanged (the adapter's `*` arm does the same). The AC requires at
80
+ * minimum `Shell` (Bash), `StrReplace` (Edit), `Read`, and `Write`.
81
+ */
82
+ export declare const CLAUDE_TO_GROK_TOOL: Readonly<Record<string, string>>;
83
+ /**
84
+ * Every Grok tool alias mapped back to its canonical Claude tool, mirroring the
85
+ * `case "$GTOOL" in …` block of `.grok/hooks/hq-grok-hook-adapter.sh`. Includes
86
+ * the snake_case aliases the AC calls out — `run_terminal_command`,
87
+ * `search_replace`, `write` — alongside the CamelCase forms.
88
+ */
89
+ export declare const GROK_TO_CLAUDE_TOOL: Readonly<Record<string, string>>;
90
+ /** The Grok tool name to emit for a canonical Claude tool (identity if unknown). */
91
+ export declare function toGrokToolName(claudeTool: string): string;
92
+ /**
93
+ * The canonical Claude tool a Grok tool name maps to, mirroring the adapter.
94
+ * An unrecognised name is returned unchanged (the adapter's `*` arm keeps it).
95
+ */
96
+ export declare function resolveGrokTool(grokTool: string): string;
97
+ /**
98
+ * Normalise a case into the Claude payload shape: snake_case keys, the canonical
99
+ * tool name, and the input under `tool_input`. This is exactly what
100
+ * `.claude/hooks/hook-gate.sh` delegates to a hook on stdin.
101
+ */
102
+ export declare function toClaudePayload(c: CaseShape, ctx: PayloadContext): SnakeCasePayload;
103
+ /**
104
+ * Normalise a case into the Codex payload shape. Codex hooks read the same
105
+ * snake_case document Claude does (the Codex adapter normalises its own edit
106
+ * events into this shape before the gate sees them), so this is structurally a
107
+ * Claude payload — kept a distinct function so a future Codex-specific quirk
108
+ * (e.g. an `apply_patch` representation) has one obvious seam to grow at.
109
+ */
110
+ export declare function toCodexPayload(c: CaseShape, ctx: PayloadContext): SnakeCasePayload;
111
+ /**
112
+ * Normalise a case into the Grok payload shape: camelCase keys and the Grok
113
+ * tool alias, matching what the real Grok adapter reads. The inner `toolInput`
114
+ * field names stay as the case authored them (the adapter reads
115
+ * `.toolInput.command` / `.toolInput.file_path` / …), so only the outer envelope
116
+ * is renamed.
117
+ */
118
+ export declare function toGrokPayload(c: CaseShape, ctx: PayloadContext): GrokPayload;
119
+ /**
120
+ * Convert a Grok camelCase payload back into the Claude-shaped snake_case
121
+ * document the gate delegates on stdin — exactly what
122
+ * `.grok/hooks/hq-grok-hook-adapter.sh` does internally before it pipes to
123
+ * `hook-gate.sh`: it reads the camelCase envelope, maps the Grok tool alias back
124
+ * to the canonical Claude tool, and builds `CLAUDE_JSON`. Reproducing that
125
+ * normalisation lets the replay route one Grok case through the same canonical
126
+ * `.claude/` gate + script the adapter ultimately execs, isolating a single hook.
127
+ */
128
+ export declare function grokToSnakePayload(payload: GrokPayload): SnakeCasePayload;
129
+ /**
130
+ * Grok's passive lifecycle events: they run side-effect hooks (autocommit,
131
+ * checkpoints, policy evaluation) but, unlike Claude/Codex, cannot inject
132
+ * model-facing context or emit a block decision. Only `PreToolUse` can deny.
133
+ * The set mirrors the adapter's event handlers.
134
+ */
135
+ export declare const GROK_PASSIVE_EVENTS: readonly string[];
136
+ /**
137
+ * Normalise the event-name spellings the Grok adapter accepts (`pre_tool_use`
138
+ * and `PreToolUse` both occur in Grok docs) to the canonical Claude spelling.
139
+ */
140
+ export declare function normalizeGrokEvent(event: string): string;
141
+ /**
142
+ * Whether Grok can produce a comparable block/allow verdict for this event.
143
+ * Only `PreToolUse` can — every other event is passive under Grok and its
144
+ * verdict must be reported NA rather than compared (AC5), never PASS or FAIL.
145
+ */
146
+ export declare function grokCanBlock(event: string): boolean;
147
+ /**
148
+ * Claude/Codex block protocol: a hook (through the gate) signals a block with a
149
+ * non-zero exit. Timeouts are handled upstream, so a `null` exit (killed) is not
150
+ * expected here; treated conservatively as non-zero.
151
+ */
152
+ export declare function interpretExitBlock(exitCode: number | null): boolean;
153
+ /**
154
+ * The decision a Grok adapter stdout carries, or null when none. Scans the
155
+ * output line by line for a JSON object with a string `decision` field —
156
+ * diagnostics on other lines (or on stderr) are ignored — and returns the last
157
+ * such decision seen, so a trailing `deny` is not masked by an earlier `allow`.
158
+ */
159
+ export declare function parseGrokDecision(stdout: string): "deny" | "allow" | null;
160
+ /**
161
+ * Grok block protocol: a block is either a non-zero exit OR a stdout `deny`
162
+ * decision — the adapter's `deny()` emits both, but a deny with exit 0 is still
163
+ * a block (AC3), so both signals are honoured. A `null` exit (timeout/kill) is
164
+ * handled upstream; here it counts as non-zero and therefore a block.
165
+ */
166
+ export declare function interpretGrokBlock(output: {
167
+ exitCode: number | null;
168
+ stdout: string;
169
+ }): boolean;
170
+ //# sourceMappingURL=payload-shapes.d.ts.map
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Per-platform hook payload shapes, tool-name aliasing, and block protocols
3
+ * (US-010).
4
+ *
5
+ * `hq doctor --deep-test` replays one fixture case through all three platform
6
+ * adapters and compares their verdicts (parity.ts). The three platforms do not
7
+ * speak the same wire language, and this module owns every difference so the
8
+ * replay orchestration can stay platform-agnostic:
9
+ *
10
+ * 1. Payload shape. Claude and Codex hooks read a snake_case document
11
+ * (`hook_event_name` / `tool_name` / `tool_input`). Grok speaks camelCase
12
+ * (`hookEventName` / `toolName` / `toolInput`) and its own tool names
13
+ * (`Shell`, `StrReplace`, …). {@link toClaudePayload}, {@link toCodexPayload},
14
+ * and {@link toGrokPayload} normalise one case into each shape.
15
+ *
16
+ * 2. Tool-name aliases. Grok surfaces the same underlying tool under several
17
+ * names — `Shell` / `run_terminal_command` for Bash, `StrReplace` /
18
+ * `search_replace` for Edit, `write` for Write, and so on. The alias tables
19
+ * here mirror the case statement in `.grok/hooks/hq-grok-hook-adapter.sh`
20
+ * exactly, so a payload we emit is one the real adapter recognises, and a
21
+ * Grok name the adapter would receive maps back to the canonical Claude
22
+ * tool the fixtures are written against.
23
+ *
24
+ * 3. Block protocol. Claude and Codex signal a block with a non-zero exit.
25
+ * Grok's PreToolUse block is a stdout `{"decision":"deny"}` document AND/OR
26
+ * a non-zero exit — {@link interpretGrokBlock} recognises either, so a
27
+ * correct Grok block that exits 0 with a deny decision is not misread as an
28
+ * allow. Grok's passive events (everything but PreToolUse) run side-effect
29
+ * hooks but cannot inject model-facing context or emit a decision at all;
30
+ * {@link grokCanBlock} names that limitation for the replay to report NA.
31
+ *
32
+ * Everything here is pure and exhaustively unit-tested; parity.ts wires these to
33
+ * the real adapters and to the verdict comparison.
34
+ */
35
+ /** Canonical platform order for side-by-side reporting. */
36
+ export const PARITY_PLATFORMS = [
37
+ "claude",
38
+ "codex",
39
+ "grok",
40
+ ];
41
+ // --- tool-name aliases ---------------------------------------------------------
42
+ /**
43
+ * The canonical Grok tool name to emit for each canonical Claude tool. These are
44
+ * names the real adapter's case statement recognises; an unlisted tool is passed
45
+ * through unchanged (the adapter's `*` arm does the same). The AC requires at
46
+ * minimum `Shell` (Bash), `StrReplace` (Edit), `Read`, and `Write`.
47
+ */
48
+ export const CLAUDE_TO_GROK_TOOL = {
49
+ Bash: "Shell",
50
+ Edit: "StrReplace",
51
+ Write: "Write",
52
+ Read: "Read",
53
+ Grep: "Grep",
54
+ Glob: "ListDir",
55
+ WebSearch: "WebSearch",
56
+ Task: "spawn_subagent",
57
+ };
58
+ /**
59
+ * Every Grok tool alias mapped back to its canonical Claude tool, mirroring the
60
+ * `case "$GTOOL" in …` block of `.grok/hooks/hq-grok-hook-adapter.sh`. Includes
61
+ * the snake_case aliases the AC calls out — `run_terminal_command`,
62
+ * `search_replace`, `write` — alongside the CamelCase forms.
63
+ */
64
+ export const GROK_TO_CLAUDE_TOOL = {
65
+ // Bash
66
+ run_terminal_command: "Bash",
67
+ Shell: "Bash",
68
+ Bash: "Bash",
69
+ bash: "Bash",
70
+ // Edit
71
+ search_replace: "Edit",
72
+ StrReplace: "Edit",
73
+ Edit: "Edit",
74
+ MultiEdit: "Edit",
75
+ apply_patch: "Edit",
76
+ // Write
77
+ write: "Write",
78
+ Write: "Write",
79
+ // Read
80
+ read_file: "Read",
81
+ Read: "Read",
82
+ // Grep
83
+ grep: "Grep",
84
+ Grep: "Grep",
85
+ // Glob
86
+ list_dir: "Glob",
87
+ Glob: "Glob",
88
+ ListDir: "Glob",
89
+ // WebSearch
90
+ web_search: "WebSearch",
91
+ WebSearch: "WebSearch",
92
+ // Task
93
+ spawn_subagent: "Task",
94
+ Task: "Task",
95
+ };
96
+ /** The Grok tool name to emit for a canonical Claude tool (identity if unknown). */
97
+ export function toGrokToolName(claudeTool) {
98
+ return CLAUDE_TO_GROK_TOOL[claudeTool] ?? claudeTool;
99
+ }
100
+ /**
101
+ * The canonical Claude tool a Grok tool name maps to, mirroring the adapter.
102
+ * An unrecognised name is returned unchanged (the adapter's `*` arm keeps it).
103
+ */
104
+ export function resolveGrokTool(grokTool) {
105
+ return GROK_TO_CLAUDE_TOOL[grokTool] ?? grokTool;
106
+ }
107
+ // --- payload builders ----------------------------------------------------------
108
+ /**
109
+ * Normalise a case into the Claude payload shape: snake_case keys, the canonical
110
+ * tool name, and the input under `tool_input`. This is exactly what
111
+ * `.claude/hooks/hook-gate.sh` delegates to a hook on stdin.
112
+ */
113
+ export function toClaudePayload(c, ctx) {
114
+ return {
115
+ hook_event_name: c.event,
116
+ tool_name: c.tool,
117
+ tool_input: c.input,
118
+ cwd: ctx.cwd,
119
+ session_id: ctx.sessionId,
120
+ };
121
+ }
122
+ /**
123
+ * Normalise a case into the Codex payload shape. Codex hooks read the same
124
+ * snake_case document Claude does (the Codex adapter normalises its own edit
125
+ * events into this shape before the gate sees them), so this is structurally a
126
+ * Claude payload — kept a distinct function so a future Codex-specific quirk
127
+ * (e.g. an `apply_patch` representation) has one obvious seam to grow at.
128
+ */
129
+ export function toCodexPayload(c, ctx) {
130
+ return {
131
+ hook_event_name: c.event,
132
+ tool_name: c.tool,
133
+ tool_input: c.input,
134
+ cwd: ctx.cwd,
135
+ session_id: ctx.sessionId,
136
+ };
137
+ }
138
+ /**
139
+ * Normalise a case into the Grok payload shape: camelCase keys and the Grok
140
+ * tool alias, matching what the real Grok adapter reads. The inner `toolInput`
141
+ * field names stay as the case authored them (the adapter reads
142
+ * `.toolInput.command` / `.toolInput.file_path` / …), so only the outer envelope
143
+ * is renamed.
144
+ */
145
+ export function toGrokPayload(c, ctx) {
146
+ return {
147
+ hookEventName: c.event,
148
+ toolName: toGrokToolName(c.tool),
149
+ toolInput: c.input,
150
+ cwd: ctx.cwd,
151
+ sessionId: ctx.sessionId,
152
+ };
153
+ }
154
+ /**
155
+ * Convert a Grok camelCase payload back into the Claude-shaped snake_case
156
+ * document the gate delegates on stdin — exactly what
157
+ * `.grok/hooks/hq-grok-hook-adapter.sh` does internally before it pipes to
158
+ * `hook-gate.sh`: it reads the camelCase envelope, maps the Grok tool alias back
159
+ * to the canonical Claude tool, and builds `CLAUDE_JSON`. Reproducing that
160
+ * normalisation lets the replay route one Grok case through the same canonical
161
+ * `.claude/` gate + script the adapter ultimately execs, isolating a single hook.
162
+ */
163
+ export function grokToSnakePayload(payload) {
164
+ return {
165
+ hook_event_name: payload.hookEventName,
166
+ tool_name: resolveGrokTool(payload.toolName),
167
+ tool_input: payload.toolInput,
168
+ cwd: payload.cwd,
169
+ session_id: payload.sessionId,
170
+ };
171
+ }
172
+ // --- Grok events ---------------------------------------------------------------
173
+ /**
174
+ * Grok's passive lifecycle events: they run side-effect hooks (autocommit,
175
+ * checkpoints, policy evaluation) but, unlike Claude/Codex, cannot inject
176
+ * model-facing context or emit a block decision. Only `PreToolUse` can deny.
177
+ * The set mirrors the adapter's event handlers.
178
+ */
179
+ export const GROK_PASSIVE_EVENTS = [
180
+ "SessionStart",
181
+ "UserPromptSubmit",
182
+ "PostToolUse",
183
+ "Stop",
184
+ "PreCompact",
185
+ "SessionEnd",
186
+ ];
187
+ /**
188
+ * Normalise the event-name spellings the Grok adapter accepts (`pre_tool_use`
189
+ * and `PreToolUse` both occur in Grok docs) to the canonical Claude spelling.
190
+ */
191
+ export function normalizeGrokEvent(event) {
192
+ switch (event) {
193
+ case "pre_tool_use":
194
+ case "PreToolUse":
195
+ return "PreToolUse";
196
+ case "post_tool_use":
197
+ case "PostToolUse":
198
+ return "PostToolUse";
199
+ case "session_start":
200
+ case "SessionStart":
201
+ return "SessionStart";
202
+ case "user_prompt_submit":
203
+ case "UserPromptSubmit":
204
+ return "UserPromptSubmit";
205
+ case "pre_compact":
206
+ case "PreCompact":
207
+ return "PreCompact";
208
+ case "stop":
209
+ case "Stop":
210
+ return "Stop";
211
+ case "session_end":
212
+ case "SessionEnd":
213
+ return "SessionEnd";
214
+ default:
215
+ return event;
216
+ }
217
+ }
218
+ /**
219
+ * Whether Grok can produce a comparable block/allow verdict for this event.
220
+ * Only `PreToolUse` can — every other event is passive under Grok and its
221
+ * verdict must be reported NA rather than compared (AC5), never PASS or FAIL.
222
+ */
223
+ export function grokCanBlock(event) {
224
+ return normalizeGrokEvent(event) === "PreToolUse";
225
+ }
226
+ // --- block protocols -----------------------------------------------------------
227
+ /**
228
+ * Claude/Codex block protocol: a hook (through the gate) signals a block with a
229
+ * non-zero exit. Timeouts are handled upstream, so a `null` exit (killed) is not
230
+ * expected here; treated conservatively as non-zero.
231
+ */
232
+ export function interpretExitBlock(exitCode) {
233
+ return exitCode !== 0;
234
+ }
235
+ /**
236
+ * The decision a Grok adapter stdout carries, or null when none. Scans the
237
+ * output line by line for a JSON object with a string `decision` field —
238
+ * diagnostics on other lines (or on stderr) are ignored — and returns the last
239
+ * such decision seen, so a trailing `deny` is not masked by an earlier `allow`.
240
+ */
241
+ export function parseGrokDecision(stdout) {
242
+ let decision = null;
243
+ for (const rawLine of stdout.split("\n")) {
244
+ const line = rawLine.trim();
245
+ if (!line.startsWith("{"))
246
+ continue;
247
+ let parsed;
248
+ try {
249
+ parsed = JSON.parse(line);
250
+ }
251
+ catch {
252
+ continue;
253
+ }
254
+ if (!parsed || typeof parsed !== "object")
255
+ continue;
256
+ const value = parsed.decision;
257
+ if (value === "deny" || value === "block")
258
+ decision = "deny";
259
+ else if (value === "allow")
260
+ decision = "allow";
261
+ }
262
+ return decision;
263
+ }
264
+ /**
265
+ * Grok block protocol: a block is either a non-zero exit OR a stdout `deny`
266
+ * decision — the adapter's `deny()` emits both, but a deny with exit 0 is still
267
+ * a block (AC3), so both signals are honoured. A `null` exit (timeout/kill) is
268
+ * handled upstream; here it counts as non-zero and therefore a block.
269
+ */
270
+ export function interpretGrokBlock(output) {
271
+ if (output.exitCode !== 0)
272
+ return true;
273
+ return parseGrokDecision(output.stdout) === "deny";
274
+ }
275
+ //# sourceMappingURL=payload-shapes.js.map