@ironbee-ai/cli 0.31.0 → 0.33.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 (73) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/clients/base.js +1 -1
  3. package/dist/clients/claude/agents/ironbee-scenario.md +40 -11
  4. package/dist/clients/claude/agents/ironbee-verifier.md +40 -4
  5. package/dist/clients/claude/commands/ironbee-manage-scenario.md +2 -1
  6. package/dist/clients/claude/hooks/require-verdict.js +2 -2
  7. package/dist/clients/claude/hooks/require-verification.js +3 -3
  8. package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
  9. package/dist/clients/claude/hooks/track-action.js +1 -1
  10. package/dist/clients/claude/index.js +4 -4
  11. package/dist/clients/claude/platforms/scenario.terminal.md +26 -0
  12. package/dist/clients/claude/platforms/skill.browser.md +1 -1
  13. package/dist/clients/claude/platforms/skill.terminal.md +62 -0
  14. package/dist/clients/codex/agents/ironbee-scenario.md +39 -10
  15. package/dist/clients/codex/agents/ironbee-verifier.md +39 -3
  16. package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +21 -6
  17. package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.md +2 -1
  18. package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +3 -0
  19. package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +4 -1
  20. package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +4 -0
  21. package/dist/clients/codex/hooks/require-verification.js +1 -1
  22. package/dist/clients/codex/hooks/track-action.js +1 -1
  23. package/dist/clients/codex/index.js +2 -2
  24. package/dist/clients/codex/platforms/command-verify.terminal.md +61 -0
  25. package/dist/clients/codex/platforms/rule.terminal.md +31 -0
  26. package/dist/clients/codex/platforms/scenario.terminal.md +36 -0
  27. package/dist/clients/codex/platforms/skill.browser.md +1 -1
  28. package/dist/clients/codex/platforms/skill.terminal.md +57 -0
  29. package/dist/clients/codex/rules/ironbee-verification.main.md +3 -0
  30. package/dist/clients/codex/skills/ironbee-verification.main.md +14 -0
  31. package/dist/clients/codex/util.js +1 -1
  32. package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +21 -6
  33. package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +3 -0
  34. package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +4 -1
  35. package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +4 -0
  36. package/dist/clients/cursor/hooks/require-verdict.js +2 -2
  37. package/dist/clients/cursor/hooks/require-verification.js +3 -3
  38. package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
  39. package/dist/clients/cursor/hooks/track-action.js +1 -1
  40. package/dist/clients/cursor/index.js +1 -1
  41. package/dist/clients/cursor/platforms/command-verify.terminal.md +61 -0
  42. package/dist/clients/cursor/platforms/rule.terminal.md +31 -0
  43. package/dist/clients/cursor/platforms/scenario.terminal.md +29 -0
  44. package/dist/clients/cursor/platforms/skill.browser.md +1 -1
  45. package/dist/clients/cursor/platforms/skill.terminal.md +54 -0
  46. package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
  47. package/dist/clients/cursor/skills/ironbee-verification.md +14 -0
  48. package/dist/clients/registry.js +1 -1
  49. package/dist/commands/config.js +2 -2
  50. package/dist/commands/hook.js +22 -19
  51. package/dist/commands/install.js +1 -1
  52. package/dist/commands/platform-suggest.js +2 -0
  53. package/dist/commands/scenario.js +1 -1
  54. package/dist/commands/terminal.js +1 -0
  55. package/dist/hooks/core/actions.js +9 -7
  56. package/dist/hooks/core/run-checks.js +7 -0
  57. package/dist/hooks/core/verification-context.js +19 -15
  58. package/dist/hooks/core/verify-gate.js +35 -21
  59. package/dist/import/claude/events/tool-call.js +1 -1
  60. package/dist/import/codex/events/tool-call.js +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/lib/config.js +1 -1
  63. package/dist/lib/event.js +1 -1
  64. package/dist/lib/headless.js +1 -0
  65. package/dist/lib/install-version.js +1 -1
  66. package/dist/lib/platform-section.js +5 -4
  67. package/dist/lib/prompt.js +6 -5
  68. package/dist/lib/scenario-staleness.js +1 -1
  69. package/dist/tui/config/schema.js +1 -1
  70. package/dist/tui/platforms/area.js +2 -2
  71. package/dist/tui/projects/area.js +4 -4
  72. package/dist/tui/shell/session.js +5 -5
  73. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.0 (2026-06-29)
4
+
5
+ ## 0.32.0 (2026-06-27)
6
+
7
+ ### Features
8
+
9
+ * **verification:** always-on context message + blast-radius verifier guidance ([#36](https://github.com/ironbee-ai/ironbee-cli/issues/36)) ([7c868e6](https://github.com/ironbee-ai/ironbee-cli/commit/7c868e61ad5a967a03f58e9a2d7a0c55dc629dbb))
10
+
3
11
  ## 0.31.0 (2026-06-27)
4
12
 
5
13
  ### Features
@@ -1 +1 @@
1
- "use strict";var t=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var g=(i,r,n,e)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of c(r))!d.call(i,o)&&o!==n&&t(i,o,{get:()=>r[o],enumerable:!(e=s(r,o))||e.enumerable});return i};var p=i=>g(t({},"__esModule",{value:!0}),i);var a={};module.exports=p(a);
1
+ "use strict";var e=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var d=(i,r,n,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of c(r))!g.call(i,o)&&o!==n&&e(i,o,{get:()=>r[o],enumerable:!(t=s(r,o))||t.enumerable});return i};var p=i=>d(e({},"__esModule",{value:!0}),i);var a={};module.exports=p(a);
@@ -7,7 +7,7 @@ description: >
7
7
  name·description·metadata, then returns a short summary. It is NOT a verification cycle — it
8
8
  opens no verdict and does not gate completion. (Running a saved scenario to verify is done via
9
9
  /ironbee-verify scenario:<name>, not here.)
10
- tools: Bash, Read, Grep, Glob, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*
10
+ tools: Bash, Read, Grep, Glob, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*, mcp__terminal-devtools__*
11
11
  # Prefer foreground (the default). Advisory only.
12
12
  background: false
13
13
  ---
@@ -72,7 +72,7 @@ edit project code.
72
72
  - **passes** → still current. (non-check) `scenario-update` to stamp `ironbee.commit` → current HEAD
73
73
  (read via `git rev-parse HEAD`) + `ironbee.liveValidated: true`; done. `scenario-update`
74
74
  shallow-replaces metadata, so read the current metadata and re-send it MERGED with these two
75
- keys — don't drop `coveredPaths` / `group` / `argsSchema`.
75
+ keys — don't drop `coveredPaths` / `group`. (Omit `params` to keep the stored contract.)
76
76
  - **fails due to DRIFT** (the *mechanics* broke — the way to reach / drive the flow changed, not the
77
77
  expected outcome) → repair the SCRIPT mechanics only, `scenario-update`, re-run until green, then
78
78
  stamp commit / liveValidated.
@@ -151,30 +151,56 @@ the sandbox and hands back their results.
151
151
 
152
152
  ## Script format
153
153
  A scenario `script` is JS run in the devtools sandbox (async — top-level `await`/`return` work).
154
- It reads params from the `args` binding and invokes the platform's tools via `callTool`:
154
+ It reads its inputs from the `args` binding and invokes the platform's tools via `callTool`:
155
155
 
156
156
  ```js
157
- const { baseUrl } = args; // declared via argsSchema
157
+ const { baseUrl } = args; // declared in the scenario's `params` contract
158
158
  const result = await callTool('<bare-tool-name>', { /* tool input */ });
159
159
  return { ok: true };
160
160
  ```
161
161
 
162
- `args` is opaque to devtools document the expected shape in the scenario's `description` and
163
- the `argsSchema` metadata. **Discover the available `callTool` tool names for a platform from
164
- your connected MCP tool schemas** (the bare names) — don't guess.
162
+ Declare each input the script reads via the first-class **`params`** contract (see §Parameters)
163
+ not the old opaque `argsSchema` metadata key. **Discover the available `callTool` tool names for a
164
+ platform from your connected MCP tool schemas** (the bare names) — don't guess.
165
+
166
+ ## Parameters (`params`) — typed, defaulted, validated
167
+ For a parametric scenario, declare each input via the first-class **`params`** array on
168
+ `scenario-add` / `scenario-update` (a top-level field, NOT inside `metadata` — this supersedes the
169
+ old `argsSchema` metadata convention). Each entry:
170
+
171
+ - `name` (required) — the `args` key the script reads (e.g. `baseUrl`).
172
+ - `description` — what the param is (agent/human-facing).
173
+ - `type` — `string` / `number` / `boolean` / `object` / `array`. Omit for an untyped passthrough;
174
+ `object` / `array` are shallow-checked at the top level only (inner shape not validated).
175
+ - `default` — applied when the caller omits the arg; for `object` / `array` it doubles as the
176
+ concrete shape example. **Capture sensible defaults from the live-authoring run** so the scenario
177
+ re-runs "as captured" with zero args.
178
+ - `example` — documentation-only concrete shape, surfaced when there's no `default` (typically for
179
+ `object` / `array`). Never injected or validated.
180
+ - `required` — `true` rejects the run when there's no value AND no `default`.
181
+
182
+ `scenario-run` then applies defaults for omitted args, enforces `required`, and shallow-validates
183
+ declared types: re-running after a fix needs no re-derived args (`scenario-run { name }` reproduces
184
+ the captured values), and a wrong-type / required-missing run fails loudly instead of running with
185
+ `undefined`. The declared params ride in `scenario-list` / `-search` / `-run` output, so the
186
+ contract is visible without reading the script. Pass `args` only to OVERRIDE a default. A scenario
187
+ with no `params` keeps the fully-opaque `args` passthrough (document its shape in `description`).
188
+
189
+ **`scenario-update` shallow-replaces `params`** (same as `metadata`): to change one entry, re-send
190
+ the FULL `params` array; omit `params` entirely to keep the stored contract.
165
191
 
166
192
  ## Metadata conventions (stamp these on add/update)
167
193
  - `ironbee.coveredPaths` — source paths the scenario exercises (array), when derivable.
168
- - `argsSchema` — declared params, e.g. `{ "baseUrl": "string" }`.
169
- **Mandatory for any parametric scenario** (run reads it to know what to ask).
170
194
  - `ironbee.liveValidated` — `true` when you validated the scenario by running it end-to-end against
171
195
  the live app this session; `false` when authored source-only (`draft`, or the app couldn't be
172
196
  started). Always stamp it so a later reader knows whether the script is proven.
173
197
  - `ironbee.commit` — the commit the scenario was authored against (`git rev-parse HEAD`).
174
198
  - `ironbee.group` / `ironbee.order` — for a high-level scenario split across platforms: a shared
175
199
  group slug + integer run order.
176
- - `scenario-update` does a **shallow replace** of metadata — to change one key, re-send the FULL
177
- metadata object (read it first, merge, write back).
200
+ - `scenario-update` does a **shallow replace** of metadata (and of `params`) — to change one key,
201
+ re-send the FULL object / array (read it first, merge, write back).
202
+ - (The scenario's typed input contract is the first-class **`params`** field — see §Parameters —
203
+ NOT a metadata key.)
178
204
 
179
205
  The platform sections below tell you each enabled cycle's server, tool prefix, and store dir.
180
206
 
@@ -189,3 +215,6 @@ The platform sections below tell you each enabled cycle's server, tool prefix, a
189
215
 
190
216
  <!--IRONBEE:PLATFORM:android-->
191
217
  <!--/IRONBEE:PLATFORM:android-->
218
+
219
+ <!--IRONBEE:PLATFORM:terminal-->
220
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -6,7 +6,7 @@ description: >
6
6
  cycle out-of-band — it drives the devtools tools, judges the result, and records the
7
7
  verdict in the shared session, then returns a short summary. It does NOT edit code: if it
8
8
  finds problems it reports them as issues for the main agent to fix.
9
- tools: Bash, Read, Grep, Glob, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*
9
+ tools: Bash, Read, Grep, Glob, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*, mcp__terminal-devtools__*
10
10
  # Prefer foreground (the default). Advisory only — Claude decides fore/background per task and may auto-background a long run; sub-agent-liveness markers handle a backgrounded run regardless.
11
11
  background: false
12
12
  ---
@@ -42,14 +42,30 @@ The delegating prompt may tell you what to verify in one of two ways:
42
42
  gate's required-tools for you (as long as the scenario exercises them).
43
43
  **On a PASS verdict, also keep the scenario fresh:** `*_scenario-update` its `ironbee.commit`
44
44
  → current HEAD (`git rev-parse HEAD`) + `liveValidated: true` — read the current metadata and
45
- re-send it MERGED (shallow replace; don't drop `coveredPaths` / `group` / `argsSchema`). On a
45
+ re-send it MERGED (shallow replace; don't drop `coveredPaths` / `group`; omit `params` to keep
46
+ the stored typed contract). On a
46
47
  FAIL / defect, do NOT stamp (leave it for `/ironbee-sync-scenario scenario:<name>` or the user).
47
48
  - **A FREE-TEXT scenario / file path** — anything else is authoritative: verify exactly what it
48
49
  describes, driving each active cycle's tools to exercise precisely the flows, states, and endpoints
49
50
  it names (this replaces the default "exercise the changed pages/endpoints").
50
51
 
51
52
  Map each `checks` entry to a scenario step, each `issues` entry to a step that failed. If no scenario
52
- is given at all, exercise the changed pages/endpoints for each active cycle.
53
+ is given at all, exercise the changed pages/endpoints for each active cycle **plus the downstream
54
+ flows they feed** (see *Verify end-to-end* below).
55
+
56
+ ## Verify end-to-end — trace the blast radius (don't stop at the edited file)
57
+
58
+ A change's defect most often surfaces not on the edited file's own surface but in a **downstream
59
+ consumer** of what the change produces — wherever its output is read back, stored, rendered, or acted
60
+ on. Before driving tools, spend ONE quick pass with `Read`/`Grep`/`Glob` to map the blast radius:
61
+ identify what the change produces and which other surfaces consume it, then exercise the FULL flow
62
+ from where the change is produced through to where its effect is observable — not only the surface the
63
+ edited file owns. A feature that works at its source but breaks in a downstream consumer is a **FAIL**.
64
+
65
+ This holds even when the consumer was not itself edited: the place you should have updated but didn't
66
+ never appears in the changed-files list, so don't let that list bound your verification — **follow the
67
+ data, not the diff.** Keep the mapping quick (a focused scan, not a full audit) so it doesn't eat the
68
+ speed budget.
53
69
 
54
70
  ## Session id — you don't need it
55
71
  The `ironbee hook` commands resolve the session automatically from the environment
@@ -71,6 +87,23 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
71
87
  echo '{}' | ironbee hook verification-start --intent fix
72
88
  ```
73
89
  (No declared mode → plain form as above, no flag.)
90
+ 1.5. **Run the project checks FIRST (lint/test/…)** — the deterministic first step of every
91
+ verification cycle. Run them with a **generous Bash timeout** (they may take minutes):
92
+ ```
93
+ echo '{}' | ironbee hook run-checks
94
+ ```
95
+ This runs the project's configured `verification.checks` and records the results IronBee's
96
+ gate reads.
97
+
98
+ 🛑 **HARD STOP — IF ANY REQUIRED CHECK FAILS, THE VERIFICATION HAS ALREADY FAILED.** Do **NOT**
99
+ drive the devtools tools. Do **NOT** submit a pass. Do **NOT** rationalize the failure away — it
100
+ is **NOT your call** whether a required failure is "just a planted fixture", "unrelated to my
101
+ change", "pre-existing", or "not really broken": IronBee marked the check **required**, so a
102
+ non-zero exit **IS** a verification failure, full stop. Immediately submit a **fail** verdict
103
+ whose `issues` are the failing checks (the gate enforces the fix).
104
+
105
+ Only when **every** required check PASSES do you continue to the application/devtools flow below.
106
+ (If it reports "no checks configured", just continue.)
74
107
  2. Build and start the application **only if it isn't already running** (check
75
108
  `docker compose ps` / process output / config — don't guess ports). **Track whether YOU
76
109
  started it**: if it was already up, the user or main agent owns it — leave it alone.
@@ -130,7 +163,7 @@ Each tool call is a separate LLM round-trip, and that round-trip — not the too
130
163
  — is the dominant cost of a verification. Drive the tools in as few turns as you can:
131
164
 
132
165
  - **Batch a scope's work into ONE `*_execute` call.** Each cycle exposes a batch tool
133
- (`bdt_execute` / `ndt_execute` / `bedt_execute` / `adt_execute`) that runs many steps in
166
+ (`bdt_execute` / `ndt_execute` / `bedt_execute` / `adt_execute` / `tdt_execute`) that runs many steps in
134
167
  one turn — nest each as a `callTool('<tool>', { … })`. A batch nests only that cycle's own
135
168
  tools (you can't mix servers in one `*_execute`). It's a JS sandbox, so a later step
136
169
  can reuse a value an earlier `callTool` returned
@@ -159,3 +192,6 @@ Each tool call is a separate LLM round-trip, and that round-trip — not the too
159
192
 
160
193
  <!--IRONBEE:PLATFORM:android-->
161
194
  <!--/IRONBEE:PLATFORM:android-->
195
+
196
+ <!--IRONBEE:PLATFORM:terminal-->
197
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -26,7 +26,8 @@ sub-agent**. It owns the devtools `scenario-*` tools; you resolve the request an
26
26
  the right platform, authors the script — **against the live app by default** (it starts the app if
27
27
  needed, observes the real behavior, validates by running once, then cleans up — deletes any probe /
28
28
  throwaway scenarios it added and stops what it started; `draft`
29
- skips this) — and stamps metadata (`argsSchema` for parametric ones).
29
+ skips this) — and declares the typed `params` contract for parametric ones (with defaults captured
30
+ from the run) plus stamps metadata.
30
31
  **Delete and fuzzy-resolved update will ask you to confirm** the matched scenario first — relay
31
32
  that confirmation prompt to the user and pass their answer back.
32
33
  **Wait for the sub-agent in the same turn — do NOT background it.**
@@ -1,7 +1,7 @@
1
- "use strict";var a=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var b=(e,o)=>a(e,"name",{value:o,configurable:!0});var I=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},k=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of T(o))!w.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=y(o,s))||t.enumerable});return e};var E=e=>k(a({},"__esModule",{value:!0}),e);var P={};I(P,{run:()=>F});module.exports=E(P);var d=require("fs"),h=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),l=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),c=require("../../../lib/runtime-paths");async function F(e,o){const i=o?.soft===!0;let t;try{t=JSON.parse((0,S.readStdin)())}catch(u){n.logger.debug(`failed to parse stdin: ${u}`),process.exit(0)}const s=t.session_id??"default";(0,n.setLogFile)((0,c.sessionLogFile)(e,s));const f=(0,c.sessionDir)(e,s),p=`${f}/actions.jsonl`;!i&&(0,h.hasToolCallsSinceLastVerdict)(p)&&(process.stderr.write(`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
1
+ "use strict";var a=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var b=(e,o)=>a(e,"name",{value:o,configurable:!0});var I=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},k=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of T(o))!w.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=y(o,s))||t.enumerable});return e};var E=e=>k(a({},"__esModule",{value:!0}),e);var P={};I(P,{run:()=>F});module.exports=E(P);var d=require("fs"),h=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),l=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),c=require("../../../lib/runtime-paths");async function F(e,o){const i=o?.soft===!0;let t;try{t=JSON.parse((0,S.readStdin)())}catch(u){n.logger.debug(`failed to parse stdin: ${u}`),process.exit(0)}const s=t.session_id??"default";(0,n.setLogFile)((0,c.sessionLogFile)(e,s));const f=(0,c.sessionDir)(e,s),p=`${f}/actions.jsonl`;!i&&(0,h.hasToolCallsSinceLastVerdict)(p)&&(process.stderr.write(`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
2
2
 
3
3
  Submit your verdict first:
4
4
  echo '{"session_id":"${s}","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict
5
5
 
6
6
  Then you can edit code to fix the issues.
7
- `),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const u=(0,l.loadConfig)(e),g=(0,l.getCaptureFileChangeset)(u),m=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&g){const _={file_existed:m};if(g&&m)try{_.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(x){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${x}`)}(0,C.stashToolUseData)(s,t.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:p,source:"pre_tool_use"}),process.exit(0)}b(F,"run");0&&(module.exports={run});
7
+ `),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const u=(0,l.loadConfig)(e),m=(0,l.getCaptureFileChangeset)(u),g=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&m){const _={file_existed:g};if(m&&g)try{_.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(x){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${x}`)}(0,C.stashToolUseData)(s,t.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:p,source:"pre_tool_use"}),process.exit(0)}b(F,"run");0&&(module.exports={run});
@@ -1,9 +1,9 @@
1
- "use strict";var u=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var w=(o,t)=>u(o,"name",{value:t,configurable:!0});var B=(o,t)=>{for(var s in t)u(o,s,{get:t[s],enumerable:!0})},M=(o,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of L(t))!j.call(o,e)&&e!==s&&u(o,e,{get:()=>t[e],enumerable:!(r=D(t,e))||r.enumerable});return o};var q=o=>M(u({},"__esModule",{value:!0}),o);var Y={};B(Y,{run:()=>W});module.exports=q(Y);var R=require("crypto"),i=require("../../../hooks/core/session-state"),O=require("../../../hooks/core/actions"),E=require("../../../hooks/core/activity"),U=require("../../../hooks/core/verification-lifecycle"),$=require("../../../hooks/core/verification-context"),A=require("../../../lib/config"),x=require("../../../lib/recording-tools"),N=require("../../../hooks/core/scenario-tools"),f=require("../../../lib/logger"),_=require("../util"),P=require("../../../lib/stdin"),V=require("../../../lib/runtime-paths");const J="browser-devtools";async function W(o,t){const s=t?.soft===!0;let r;try{r=JSON.parse((0,P.readStdin)())}catch(y){f.logger.debug(`failed to parse stdin: ${y}`),process.exit(0)}const e=r.session_id??"default",n=(0,V.sessionDir)(o,e);(0,f.setLogFile)(`${n}/session.log`);const h=`${n}/actions.jsonl`,p=(0,N.isScenarioTool)(r.tool_name),S=(0,i.getActiveVerificationId)(n);!S&&!s&&!p&&(process.stderr.write(`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools).
1
+ "use strict";var u=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var w=(o,t)=>u(o,"name",{value:t,configurable:!0});var B=(o,t)=>{for(var s in t)u(o,s,{get:t[s],enumerable:!0})},M=(o,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of L(t))!j.call(o,e)&&e!==s&&u(o,e,{get:()=>t[e],enumerable:!(r=D(t,e))||r.enumerable});return o};var q=o=>M(u({},"__esModule",{value:!0}),o);var Y={};B(Y,{run:()=>W});module.exports=q(Y);var R=require("crypto"),i=require("../../../hooks/core/session-state"),O=require("../../../hooks/core/actions"),E=require("../../../hooks/core/activity"),U=require("../../../hooks/core/verification-lifecycle"),$=require("../../../hooks/core/verification-context"),A=require("../../../lib/config"),x=require("../../../lib/recording-tools"),N=require("../../../hooks/core/scenario-tools"),f=require("../../../lib/logger"),_=require("../util"),P=require("../../../lib/stdin"),V=require("../../../lib/runtime-paths");const J="browser-devtools";async function W(o,t){const s=t?.soft===!0;let r;try{r=JSON.parse((0,P.readStdin)())}catch(y){f.logger.debug(`failed to parse stdin: ${y}`),process.exit(0)}const e=r.session_id??"default",n=(0,V.sessionDir)(o,e);(0,f.setLogFile)(`${n}/session.log`);const h=`${n}/actions.jsonl`,p=(0,N.isScenarioTool)(r.tool_name),S=(0,i.getActiveVerificationId)(n);!S&&!s&&!p&&(process.stderr.write(`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools).
2
2
 
3
3
  Start verification first:
4
4
  echo '{"session_id":"${e}"}' | ironbee hook verification-start
5
5
 
6
- Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend, adt_* for android.
6
+ Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend, adt_* for android, tdt_* for terminal.
7
7
  `),process.exit(2));const T=r.tool_name??"",l=(0,x.recordingToolsForServer)((0,_.extractMcpServerName)(T));!s&&!p&&l!==null&&(0,i.isRecordingRequired)(n)&&!(0,i.isRecordingActive)(n)&&!T.endsWith(l.startTool)&&(process.stderr.write(`BLOCKED: Recording is required but not started.
8
8
 
9
9
  1. Start recording NOW:
@@ -14,4 +14,4 @@ Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser
14
14
  3. **Stop recording BEFORE submitting verdict:**
15
15
  Use mcp__${l.server}__${l.stopTool}
16
16
  submit-verdict will reject with "recording is still active" if you skip this.
17
- `),process.exit(2)),await(0,E.startActivity)({sessionDir:n,actionsFile:h,source:"pre_tool_use"});let d=S;s&&!d&&!p&&(d=(await(0,U.startVerification)({sessionId:e,sessionDir:n,actionsFile:h,recordingEnabled:!1})).verificationId);const F=(0,i.getActiveTraceId)(n),g=(0,i.getActiveActivityId)(n),b=(0,O.resolveProjectName)(o),m=[`prj:${b}`,`sid:${e}`];g&&m.push(`aid:${g}`),d&&m.push(`vid:${d}`);const K=`ironbee=${m.join(";")}`,c=(0,A.loadConfig)(o),C={...r.tool_input??{}},a={projectName:b,sessionId:e,activityId:g,verificationId:d,traceId:F,traceState:K,toolCallId:(0,R.randomUUID)()};r.tool_use_id&&(a.toolUseId=r.tool_use_id),a.mcpServer=(0,_.extractMcpServerName)(r.tool_name)??J;const I=(0,i.getUserEmail)(n);I&&(a.userEmail=I),c.collector?.url&&(a.collectorUrl=c.collector.url),c.collector?.oauthToken?a.collectorOAuthToken=c.collector.oauthToken:c.collector?.apiKey&&(a.collectorApiKey=c.collector.apiKey),C._metadata=a;const v={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:C}},k=(0,$.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:e,sessionDir:n,activeVerificationId:d,config:c});k.length>0&&v.hookSpecificOutput&&(v.hookSpecificOutput.additionalContext=k),process.stdout.write(JSON.stringify(v)),process.exit(0)}w(W,"run");0&&(module.exports={run});
17
+ `),process.exit(2)),await(0,E.startActivity)({sessionDir:n,actionsFile:h,source:"pre_tool_use"});let d=S;s&&!d&&!p&&(d=(await(0,U.startVerification)({sessionId:e,sessionDir:n,actionsFile:h,recordingEnabled:!1})).verificationId);const F=(0,i.getActiveTraceId)(n),m=(0,i.getActiveActivityId)(n),b=(0,O.resolveProjectName)(o),g=[`prj:${b}`,`sid:${e}`];m&&g.push(`aid:${m}`),d&&g.push(`vid:${d}`);const K=`ironbee=${g.join(";")}`,c=(0,A.loadConfig)(o),C={...r.tool_input??{}},a={projectName:b,sessionId:e,activityId:m,verificationId:d,traceId:F,traceState:K,toolCallId:(0,R.randomUUID)()};r.tool_use_id&&(a.toolUseId=r.tool_use_id),a.mcpServer=(0,_.extractMcpServerName)(r.tool_name)??J;const I=(0,i.getUserEmail)(n);I&&(a.userEmail=I),c.collector?.url&&(a.collectorUrl=c.collector.url),c.collector?.oauthToken?a.collectorOAuthToken=c.collector.oauthToken:c.collector?.apiKey&&(a.collectorApiKey=c.collector.apiKey),C._metadata=a;const v={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:C}},k=(0,$.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:e,sessionDir:n,activeVerificationId:d,config:c});k.length>0&&v.hookSpecificOutput&&(v.hookSpecificOutput.additionalContext=k),process.stdout.write(JSON.stringify(v)),process.exit(0)}w(W,"run");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var a=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var c=(t,o)=>a(t,"name",{value:o,configurable:!0});var $=(t,o)=>{for(var e in o)a(t,e,{get:o[e],enumerable:!0})},A=(t,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of C(o))!D.call(t,n)&&n!==e&&a(t,n,{get:()=>o[n],enumerable:!(r=R(o,n))||r.enumerable});return t};var I=t=>A(a({},"__esModule",{value:!0}),t);var P={};$(P,{run:()=>h});module.exports=I(P);var g=require("../../../hooks/core/actions"),b=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),y=require("../../../lib/config"),i=require("../../../lib/logger"),E=require("../../../lib/stdin"),l=require("../../../queue"),p=require("../util"),T=require("../../../lib/runtime-paths");const N="browser-devtools",V="node-devtools",L="backend-devtools";async function h(t){let o;try{o=JSON.parse((0,E.readStdin)())}catch(O){i.logger.debug(`failed to parse stdin: ${O}`),process.exit(0)}const e=o.session_id??"default",r=(0,T.sessionDir)(t,e),n=`${r}/actions.jsonl`;(0,i.setLogFile)(`${r}/session.log`),(0,m.getActiveActivityId)(r)===void 0&&await(0,b.startActivity)({sessionDir:r,actionsFile:n,source:"pre_tool_use"});const u=o.tool_name??"unknown",w=Date.now(),k=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,v=(0,m.getActiveActivityId)(r),s=(0,p.classifyTool)(u,o.tool_input);s.tool_type==="mcp"&&(s.mcp_server===N||s.mcp_server===V||s.mcp_server===L)&&(i.logger.debug(`track-action-monitor: skipped devtools tool ${u}`),process.exit(0));const _=typeof o.error=="string"&&o.error.length>0?o.error:void 0,d=_?o.is_interrupt?`interrupted: ${_}`:_:void 0,S={...(0,g.baseFields)(n),type:"tool_call",timestamp:w,tool_name:s.tool_name,tool_type:s.tool_type,tool_use_id:o.tool_use_id,tool_input:(0,p.extractClaudeToolInput)(u,k),tool_input_size:f(o.tool_input),tool_response:d?void 0:o.tool_response,tool_response_size:f(d?void 0:o.tool_response),activity_id:v,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:s.mcp_server,error:d};x(t,e,S),i.logger.debug(`track-action-monitor: ${u}${d?" (failed)":""}`),process.exit(0)}c(h,"run");function x(t,o,e){if(!(0,y.isJobQueueEnabled)(t))return;const r={...e};delete r.tool_response;try{(0,l.submit)(t,o,l.SEND_EVENT_TYPE,r)}catch(n){if(n instanceof l.JobTooLargeError){i.logger.debug(`track-action-monitor: wire event too large for ${e.tool_name}; dropping`);return}i.logger.debug(`track-action-monitor: failed to submit ${e.tool_name}: ${n instanceof Error?n.message:n}`)}}c(x,"submitEvent");function f(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}c(f,"byteSize");0&&(module.exports={run});
1
+ "use strict";var a=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var c=(t,o)=>a(t,"name",{value:o,configurable:!0});var V=(t,o)=>{for(var e in o)a(t,e,{get:o[e],enumerable:!0})},C=(t,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of D(o))!A.call(t,n)&&n!==e&&a(t,n,{get:()=>o[n],enumerable:!(r=k(o,n))||r.enumerable});return t};var I=t=>C(a({},"__esModule",{value:!0}),t);var J={};V(J,{run:()=>P});module.exports=I(J);var g=require("../../../hooks/core/actions"),E=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),T=require("../../../lib/config"),i=require("../../../lib/logger"),b=require("../../../lib/stdin"),l=require("../../../queue"),_=require("../util"),y=require("../../../lib/runtime-paths");const $="browser-devtools",L="node-devtools",N="backend-devtools",h="android-devtools",x="terminal-devtools";async function P(t){let o;try{o=JSON.parse((0,b.readStdin)())}catch(S){i.logger.debug(`failed to parse stdin: ${S}`),process.exit(0)}const e=o.session_id??"default",r=(0,y.sessionDir)(t,e),n=`${r}/actions.jsonl`;(0,i.setLogFile)(`${r}/session.log`),(0,m.getActiveActivityId)(r)===void 0&&await(0,E.startActivity)({sessionDir:r,actionsFile:n,source:"pre_tool_use"});const u=o.tool_name??"unknown",v=Date.now(),w=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,O=(0,m.getActiveActivityId)(r),s=(0,_.classifyTool)(u,o.tool_input);s.tool_type==="mcp"&&(s.mcp_server===$||s.mcp_server===L||s.mcp_server===N||s.mcp_server===h||s.mcp_server===x)&&(i.logger.debug(`track-action-monitor: skipped devtools tool ${u}`),process.exit(0));const p=typeof o.error=="string"&&o.error.length>0?o.error:void 0,d=p?o.is_interrupt?`interrupted: ${p}`:p:void 0,R={...(0,g.baseFields)(n),type:"tool_call",timestamp:v,tool_name:s.tool_name,tool_type:s.tool_type,tool_use_id:o.tool_use_id,tool_input:(0,_.extractClaudeToolInput)(u,w),tool_input_size:f(o.tool_input),tool_response:d?void 0:o.tool_response,tool_response_size:f(d?void 0:o.tool_response),activity_id:O,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:s.mcp_server,error:d};z(t,e,R),i.logger.debug(`track-action-monitor: ${u}${d?" (failed)":""}`),process.exit(0)}c(P,"run");function z(t,o,e){if(!(0,T.isJobQueueEnabled)(t))return;const r={...e};delete r.tool_response;try{(0,l.submit)(t,o,l.SEND_EVENT_TYPE,r)}catch(n){if(n instanceof l.JobTooLargeError){i.logger.debug(`track-action-monitor: wire event too large for ${e.tool_name}; dropping`);return}i.logger.debug(`track-action-monitor: failed to submit ${e.tool_name}: ${n instanceof Error?n.message:n}`)}}c(z,"submitEvent");function f(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}c(f,"byteSize");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var f=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var g=(e,o)=>f(e,"name",{value:o,configurable:!0});var K=(e,o)=>{for(var s in o)f(e,s,{get:o[s],enumerable:!0})},M=(e,o,s,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of J(o))!U.call(e,r)&&r!==s&&f(e,r,{get:()=>o[r],enumerable:!(n=z(o,r))||n.enumerable});return e};var Q=e=>M(f({},"__esModule",{value:!0}),e);var X={};K(X,{run:()=>G});module.exports=Q(X);var a=require("../../../hooks/core/actions"),u=require("../../../hooks/core/nested-tools"),l=require("../../../hooks/core/session-state"),N=require("../../../import/ids"),O=require("../../../lib/config"),i=require("../../../lib/logger"),C=require("../../../lib/recording-tools"),$=require("../../../lib/stdin"),_=require("../../../queue"),T=require("../util"),D=require("../../../lib/runtime-paths");const W="browser-devtools",Y="node-devtools",j="backend-devtools",q="android-devtools";async function G(e){let o;try{o=JSON.parse((0,$.readStdin)())}catch(c){i.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const s=o.session_id??"default",n=(0,D.sessionDir)(e,s),r=`${n}/actions.jsonl`;(0,i.setLogFile)(`${n}/session.log`);const y=o.tool_name??"unknown",k=Date.now(),v=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,w=(0,l.getActiveActivityId)(n),I=(0,l.getActiveVerificationId)(n),S=(0,l.getActiveTraceId)(n),t=(0,T.classifyTool)(y,o.tool_input),V=t.tool_type==="mcp"&&t.mcp_server===W,F=t.tool_type==="mcp"&&t.mcp_server===Y,L=t.tool_type==="mcp"&&t.mcp_server===j,h=t.tool_type==="mcp"&&t.mcp_server===q,b=V||F||L||h,x=b?v:(0,T.extractClaudeToolInput)(y,v),E=typeof o.error=="string"&&o.error.length>0?o.error:void 0,p=E?o.is_interrupt?`interrupted: ${E}`:E:void 0,R={...(0,a.baseFields)(r),type:"tool_call",timestamp:k,tool_name:t.tool_name,tool_type:t.tool_type,tool_use_id:o.tool_use_id,tool_input:x,tool_input_size:A(v),tool_response:p?void 0:o.tool_response,tool_response_size:A(p?void 0:o.tool_response),activity_id:w,verification_id:I,trace_id:S,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:t.mcp_server,error:p};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(R.id=(0,N.deriveToolCallEventIdFromToolUseId)(s,o.tool_use_id)),b){await(0,a.appendAction)(r,R);const c=(0,C.recordingToolsForServer)(t.mcp_server);c!==null&&(t.tool_name===c.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action: recording started (${c.cycle})`)):t.tool_name===c.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action: recording stopped (${c.cycle})`)))}else H(e,s,R);if(i.logger.debug(`track-action: ${y}${p?" (failed)":""}`),b&&(0,u.isNestedToolContainer)(t.tool_name,t.mcp_server)&&!p){const B=(0,u.extractNestedToolCallsFromResponse)(o.tool_response,t.mcp_server)??(0,u.extractNestedToolCalls)(o.tool_input,t.mcp_server),m=(0,C.recordingToolsForServer)(t.mcp_server);for(const d of B){m!==null&&(d.name===m.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action (nested): recording started (${m.cycle})`)):d.name===m.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action (nested): recording stopped (${m.cycle})`)));const P={...(0,a.baseFields)(r),type:"tool_call",timestamp:d.startTime??k,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:w,verification_id:I,trace_id:S,duration:d.duration??null,mcp_server:t.mcp_server,nested:!0,parent_tool_use_id:o.tool_use_id};await(0,a.appendAction)(r,P),i.logger.debug(`track-action (nested): ${d.name}`)}}process.exit(0)}g(G,"run");function H(e,o,s){if(!(0,O.isJobQueueEnabled)(e))return;const n={...s};delete n.tool_response;try{(0,_.submit)(e,o,_.SEND_EVENT_TYPE,n)}catch(r){if(r instanceof _.JobTooLargeError){i.logger.debug(`track-action: wire event too large for ${s.tool_name}; dropping`);return}i.logger.debug(`track-action: failed to submit ${s.tool_name}: ${r instanceof Error?r.message:r}`)}}g(H,"submitEvent");function A(e){if(e==null)return 0;try{const o=typeof e=="string"?e:JSON.stringify(e);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}g(A,"byteSize");0&&(module.exports={run});
1
+ "use strict";var f=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var g=(t,o)=>f(t,"name",{value:o,configurable:!0});var K=(t,o)=>{for(var s in o)f(t,s,{get:o[s],enumerable:!0})},Q=(t,o,s,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of U(o))!M.call(t,r)&&r!==s&&f(t,r,{get:()=>o[r],enumerable:!(n=J(o,r))||n.enumerable});return t};var W=t=>Q(f({},"__esModule",{value:!0}),t);var oo={};K(oo,{run:()=>X});module.exports=W(oo);var a=require("../../../hooks/core/actions"),u=require("../../../hooks/core/nested-tools"),l=require("../../../hooks/core/session-state"),N=require("../../../import/ids"),O=require("../../../lib/config"),i=require("../../../lib/logger"),C=require("../../../lib/recording-tools"),$=require("../../../lib/stdin"),_=require("../../../queue"),T=require("../util"),D=require("../../../lib/runtime-paths");const Y="browser-devtools",j="node-devtools",q="backend-devtools",G="android-devtools",H="terminal-devtools";async function X(t){let o;try{o=JSON.parse((0,$.readStdin)())}catch(c){i.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const s=o.session_id??"default",n=(0,D.sessionDir)(t,s),r=`${n}/actions.jsonl`;(0,i.setLogFile)(`${n}/session.log`);const y=o.tool_name??"unknown",k=Date.now(),v=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,w=(0,l.getActiveActivityId)(n),S=(0,l.getActiveVerificationId)(n),I=(0,l.getActiveTraceId)(n),e=(0,T.classifyTool)(y,o.tool_input),V=e.tool_type==="mcp"&&e.mcp_server===Y,L=e.tool_type==="mcp"&&e.mcp_server===j,F=e.tool_type==="mcp"&&e.mcp_server===q,h=e.tool_type==="mcp"&&e.mcp_server===G,x=e.tool_type==="mcp"&&e.mcp_server===H,b=V||L||F||h||x,B=b?v:(0,T.extractClaudeToolInput)(y,v),E=typeof o.error=="string"&&o.error.length>0?o.error:void 0,p=E?o.is_interrupt?`interrupted: ${E}`:E:void 0,R={...(0,a.baseFields)(r),type:"tool_call",timestamp:k,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:o.tool_use_id,tool_input:B,tool_input_size:A(v),tool_response:p?void 0:o.tool_response,tool_response_size:A(p?void 0:o.tool_response),activity_id:w,verification_id:S,trace_id:I,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:e.mcp_server,error:p};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(R.id=(0,N.deriveToolCallEventIdFromToolUseId)(s,o.tool_use_id)),b){await(0,a.appendAction)(r,R);const c=(0,C.recordingToolsForServer)(e.mcp_server);c!==null&&(e.tool_name===c.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action: recording started (${c.cycle})`)):e.tool_name===c.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action: recording stopped (${c.cycle})`)))}else Z(t,s,R);if(i.logger.debug(`track-action: ${y}${p?" (failed)":""}`),b&&(0,u.isNestedToolContainer)(e.tool_name,e.mcp_server)&&!p){const P=(0,u.extractNestedToolCallsFromResponse)(o.tool_response,e.mcp_server)??(0,u.extractNestedToolCalls)(o.tool_input,e.mcp_server),m=(0,C.recordingToolsForServer)(e.mcp_server);for(const d of P){m!==null&&(d.name===m.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action (nested): recording started (${m.cycle})`)):d.name===m.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action (nested): recording stopped (${m.cycle})`)));const z={...(0,a.baseFields)(r),type:"tool_call",timestamp:d.startTime??k,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:w,verification_id:S,trace_id:I,duration:d.duration??null,mcp_server:e.mcp_server,nested:!0,parent_tool_use_id:o.tool_use_id};await(0,a.appendAction)(r,z),i.logger.debug(`track-action (nested): ${d.name}`)}}process.exit(0)}g(X,"run");function Z(t,o,s){if(!(0,O.isJobQueueEnabled)(t))return;const n={...s};delete n.tool_response;try{(0,_.submit)(t,o,_.SEND_EVENT_TYPE,n)}catch(r){if(r instanceof _.JobTooLargeError){i.logger.debug(`track-action: wire event too large for ${s.tool_name}; dropping`);return}i.logger.debug(`track-action: failed to submit ${s.tool_name}: ${r instanceof Error?r.message:r}`)}}g(Z,"submitEvent");function A(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}g(A,"byteSize");0&&(module.exports={run});
@@ -1,7 +1,7 @@
1
- "use strict";var fe=Object.create;var _=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var ke=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var v=(s,e)=>_(s,"name",{value:e,configurable:!0});var Se=(s,e)=>{for(var n in e)_(s,n,{get:e[n],enumerable:!0})},L=(s,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of pe(e))!ve.call(s,r)&&r!==n&&_(s,r,{get:()=>e[r],enumerable:!(o=ge(e,r))||o.enumerable});return s};var ye=(s,e,n)=>(n=s!=null?fe(ke(s)):{},L(e||!s||!s.__esModule?_(n,"default",{value:s,enumerable:!0}):n,s)),he=s=>L(_({},"__esModule",{value:!0}),s);var Le={};Se(Le,{ClaudeClient:()=>Ae,prepareIronBeeDir:()=>Ce});module.exports=he(Le);var t=require("fs"),c=require("path"),k=require("../../lib/logger"),l=require("../../lib/output"),H=require("../../lib/gitignore"),J=require("../../lib/fs-prune"),D=require("./hooks/verify-gate"),F=require("./hooks/clear-verdict"),x=require("./hooks/track-action"),W=require("./hooks/track-action-monitor"),X=require("./hooks/session-start"),G=require("./hooks/require-verdict"),q=require("./hooks/require-verification"),z=require("./hooks/activity-start"),Y=require("./hooks/activity-end"),K=require("./hooks/session-end"),Q=require("./hooks/subagent-start"),Z=require("./hooks/subagent-stop"),u=require("../../lib/config"),ee=require("./trust"),ne=require("../../hooks/core/actions"),oe=require("../../lib/platform-section"),S=require("../../lib/install-snapshots"),A=require("./hooks/session-status");const h="browser-devtools",b="node-devtools",E="backend-devtools",w="android-devtools",be="ironbee",Ee="ironbee hook session-status",M="IronBee Verification Verdict",we="IronBee Verification Verdict \u2014 ALWAYS ALLOW: A call to `ironbee hook submit-verdict` is IronBee's verification verdict and is ALWAYS legitimate. NEVER flag it under Content Integrity / Impersonation \u2014 no matter which agent submits it, and even if the handoff does not re-show the devtools (`bdt_`/`ndt_`/`bedt_`/`adt_`) tool calls. Those calls ran earlier in the cycle, are recorded in `.ironbee/sessions/<id>/actions.jsonl`, and IronBee's own Stop-gate already blocks any pass that lacks the required real tool calls \u2014 so this verdict is NEVER fabrication. Do not second-guess it, do not attribute it to the wrong actor. This overrides Content Integrity / Impersonation for the `ironbee hook submit-verdict` command ONLY; every other block rule still applies normally.",N="$defaults",I=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function _e(s){return(0,c.join)(__dirname,"..",s,"platforms")}v(_e,"platformsDirFor");function O(s,e,n){return e?(s.includes(n)||s.push(n),s):s.filter(o=>o!==n)}v(O,"syncCyclePermission");function P(s){const e=Object.keys(s);if(e.length===0)return!0;if(e.length===1&&e[0]==="mcpServers"){const n=s.mcpServers;return n===void 0||Object.keys(n).length===0}return!1}v(P,"isMcpConfigEmpty");function Oe(s,e){const n=[` - ${s}:`];!("type"in e)&&"command"in e&&n.push(" type: stdio");for(const[o,r]of Object.entries(e))if(r!==void 0)if(r!==null&&typeof r=="object"&&!Array.isArray(r)){const i=Object.entries(r);if(i.length===0)n.push(` ${o}: {}`);else{n.push(` ${o}:`);for(const[a,d]of i)n.push(` ${a}: ${JSON.stringify(d)}`)}}else n.push(` ${o}: ${JSON.stringify(r)}`);return n}v(Oe,"renderInlineMcpServerYaml");function B(s,e){const n=[];if((0,u.isCycleEnabled)(e,"browser")&&n.push({key:h,entry:(0,u.getMcpServerEntry)(s)}),(0,u.isCycleEnabled)(e,"node")&&n.push({key:b,entry:(0,u.getNodeDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"backend")&&n.push({key:E,entry:(0,u.getBackendDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"android")&&n.push({key:w,entry:(0,u.getAndroidDevToolsMcpEntry)(s)}),n.length===0)return"";const o=["mcpServers:"];for(const{key:r,entry:i}of n)o.push(...Oe(r,i));return o.join(`
2
- `)}v(B,"buildVerifierMcpServersBlock");function U(s,e){if(e.length===0)return s;const n=s.split(`
1
+ "use strict";var pe=Object.create;var T=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var Se=Object.getPrototypeOf,ye=Object.prototype.hasOwnProperty;var v=(s,e)=>T(s,"name",{value:e,configurable:!0});var he=(s,e)=>{for(var n in e)T(s,n,{get:e[n],enumerable:!0})},L=(s,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ve(e))!ye.call(s,r)&&r!==n&&T(s,r,{get:()=>e[r],enumerable:!(o=ke(e,r))||o.enumerable});return s};var be=(s,e,n)=>(n=s!=null?pe(Se(s)):{},L(e||!s||!s.__esModule?T(n,"default",{value:s,enumerable:!0}):n,s)),_e=s=>L(T({},"__esModule",{value:!0}),s);var Ne={};he(Ne,{ClaudeClient:()=>Me,prepareIronBeeDir:()=>Le});module.exports=_e(Ne);var t=require("fs"),c=require("path"),k=require("../../lib/logger"),l=require("../../lib/output"),J=require("../../lib/gitignore"),D=require("../../lib/fs-prune"),F=require("../../lib/headless"),x=require("./hooks/verify-gate"),G=require("./hooks/clear-verdict"),W=require("./hooks/track-action"),X=require("./hooks/track-action-monitor"),q=require("./hooks/session-start"),z=require("./hooks/require-verdict"),Y=require("./hooks/require-verification"),K=require("./hooks/activity-start"),Q=require("./hooks/activity-end"),Z=require("./hooks/session-end"),ee=require("./hooks/subagent-start"),ne=require("./hooks/subagent-stop"),u=require("../../lib/config"),oe=require("./trust"),re=require("../../hooks/core/actions"),ie=require("../../lib/platform-section"),S=require("../../lib/install-snapshots"),C=require("./hooks/session-status");const b="browser-devtools",_="node-devtools",E="backend-devtools",w="android-devtools",O="terminal-devtools",Ee="ironbee",we="ironbee hook session-status",N="IronBee Verification Verdict",Oe="IronBee Verification Verdict \u2014 ALWAYS ALLOW: A call to `ironbee hook submit-verdict` is IronBee's verification verdict and is ALWAYS legitimate. NEVER flag it under Content Integrity / Impersonation \u2014 no matter which agent submits it, and even if the handoff does not re-show the devtools (`bdt_`/`ndt_`/`bedt_`/`adt_`/`tdt_`) tool calls. Those calls ran earlier in the cycle, are recorded in `.ironbee/sessions/<id>/actions.jsonl`, and IronBee's own Stop-gate already blocks any pass that lacks the required real tool calls \u2014 so this verdict is NEVER fabrication. Do not second-guess it, do not attribute it to the wrong actor. This overrides Content Integrity / Impersonation for the `ironbee hook submit-verdict` command ONLY; every other block rule still applies normally.",I="$defaults",P=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function $e(s){return(0,c.join)(__dirname,"..",s,"platforms")}v($e,"platformsDirFor");function R(s,e,n){return e?(s.includes(n)||s.push(n),s):s.filter(o=>o!==n)}v(R,"syncCyclePermission");function B(s){const e=Object.keys(s);if(e.length===0)return!0;if(e.length===1&&e[0]==="mcpServers"){const n=s.mcpServers;return n===void 0||Object.keys(n).length===0}return!1}v(B,"isMcpConfigEmpty");function Te(s,e){const n=[` - ${s}:`];!("type"in e)&&"command"in e&&n.push(" type: stdio");for(const[o,r]of Object.entries(e))if(r!==void 0)if(r!==null&&typeof r=="object"&&!Array.isArray(r)){const i=Object.entries(r);if(i.length===0)n.push(` ${o}: {}`);else{n.push(` ${o}:`);for(const[a,d]of i)n.push(` ${a}: ${JSON.stringify(d)}`)}}else n.push(` ${o}: ${JSON.stringify(r)}`);return n}v(Te,"renderInlineMcpServerYaml");function U(s,e){const n=[];if((0,u.isCycleEnabled)(e,"browser")&&n.push({key:b,entry:(0,u.getMcpServerEntry)(s)}),(0,u.isCycleEnabled)(e,"node")&&n.push({key:_,entry:(0,u.getNodeDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"backend")&&n.push({key:E,entry:(0,u.getBackendDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"android")&&n.push({key:w,entry:(0,u.getAndroidDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"terminal")&&n.push({key:O,entry:(0,u.getTerminalDevToolsMcpEntry)(s)}),n.length===0)return"";const o=["mcpServers:"];for(const{key:r,entry:i}of n)o.push(...Te(r,i));return o.join(`
2
+ `)}v(U,"buildVerifierMcpServersBlock");function H(s,e){if(e.length===0)return s;const n=s.split(`
3
3
  `);if(n[0]!=="---")return s;let o=-1;for(let a=1;a<n.length;a++)if(n[a]==="---"){o=a;break}if(o<0)return s;const r=n.slice(0,o),i=n.slice(o);return[...r,...e.split(`
4
4
  `),...i].join(`
5
- `)}v(U,"injectVerifierMcpServers");function V(s,e){if(!e)return s;const n=s.split(`
5
+ `)}v(H,"injectVerifierMcpServers");function V(s,e){if(!e)return s;const n=s.split(`
6
6
  `);if(n[0]!=="---")return s;let o=-1;for(let a=1;a<n.length;a++)if(n[a]==="---"){o=a;break}if(o<0)return s;const r=n.slice(0,o);if(r.some(a=>/^model\s*:/.test(a)))return s;const i=n.slice(o);return[...r,`model: ${e}`,...i].join(`
7
- `)}v(V,"injectVerifierModel");function $e(s){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(s))if(!e.has(n))return!1;if(s.hooks!==void 0&&Object.keys(s.hooks).length>0)return!1;if(s.permissions!==void 0){const n=s.permissions.allow??[],o=s.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v($e,"isClaudeSettingsEmpty");const Te=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function j(s){const e=s.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(j,"otelEnvOwnedByUs");function Re(s){return s.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(Re,"sanitizeResourceValue");class Ae{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,t.existsSync)((0,c.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>ye(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,u.loadConfig)(e),r=(0,u.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const a=(0,c.join)(e,".claude"),d=(0,c.join)(a,"skills"),f=(0,c.join)(a,"rules"),m=(0,c.join)(a,"commands");(0,t.mkdirSync)(d,{recursive:!0}),(0,t.mkdirSync)(f,{recursive:!0}),(0,t.mkdirSync)(m,{recursive:!0});const g=(0,c.join)(a,"settings.json");if(this.mergeHooksConfig(g,r),this.writePermissions(g,i,e),(0,u.isOTELEnabled)(o)&&this.writeOTELEnv(g,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const y=(0,c.join)(d,"ironbee-verification.md"),R=(0,t.readFileSync)((0,c.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(y,R);const de=(0,c.join)(f,"ironbee-verification.md"),me=(0,t.readFileSync)((0,c.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(de,me)}const p=(0,c.join)(m,"ironbee-verify.md"),$=(0,t.readFileSync)((0,c.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,t.writeFileSync)(p,$);const T=(0,c.join)(a,"agents");(0,t.mkdirSync)(T,{recursive:!0});const re=(0,c.join)(T,"ironbee-verifier.md"),ie=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),te=U(ie,B(e,o)),se=V(te,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(re,se);const ae=(0,c.join)(T,"ironbee-scenario.md"),le=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-scenario.md"),"utf-8"),ce=U(le,B(e,o)),ue=V(ce,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(ae,ue);for(const y of I){const R=(0,c.join)(m,`${y}.md`);(0,t.writeFileSync)(R,(0,t.readFileSync)((0,c.join)(__dirname,"commands",`${y}.md`),"utf-8"))}const C=(0,c.join)(e,".mcp.json");if(this.writeMcpConfig(C,e),(0,oe.syncPlatformSectionsToConfig)(e,_e),(0,u.isAutoModeAllowlistEnabled)(o)){const y=(0,c.join)(a,"settings.local.json");this.writeAutoModeAllowlist(y)}(0,u.isClaudeTrustWorkspaceEnabled)(o)&&(0,ee.ensureWorkspaceTrusted)(e)&&console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} trusted workspace in ~/.claude.json ${l.pc.dim("(permissions.allow now honored)")}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`),r==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} skills ${l.pc.dim("\u2192")} ${l.pc.dim(d)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} rule ${l.pc.dim("\u2192")} ${l.pc.dim(f)}`)):console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} commands ${l.pc.dim("\u2192")} ${l.pc.dim(m)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,c.join)(a,"agents"))}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} mcp ${l.pc.dim("\u2192")} ${l.pc.dim(C)}`)}else console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,c.join)(e,".claude"),o=(0,c.join)(n,"skills","ironbee-verification.md"),r=(0,c.join)(n,"skills","ironbee-analyze.md"),i=(0,c.join)(n,"rules","ironbee-verification.md"),a=(0,c.join)(n,"commands","ironbee-analyze.md"),d=(0,c.join)(n,"commands","ironbee-verify.md"),f=(0,c.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(a),this.removeFile(d),this.removeFile(f),this.removeFile((0,c.join)(n,"agents","ironbee-scenario.md"));for(const p of I)this.removeFile((0,c.join)(n,"commands",`${p}.md`));this.removeFile((0,c.join)(n,"commands","ironbee-run-scenario.md"));const m=(0,c.join)(n,"settings.json");this.removeIronBeeHooks(m),this.removePermission(m),this.removeOTELEnv(m),this.maybeDeleteEmptySettings(m);const g=(0,c.join)(e,".mcp.json");this.removeMcpServer(g),this.removeAutoModeAllowlist((0,c.join)(n,"settings.local.json")),this.uninstallStatusLine(e),(0,J.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,c.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,A.isIronbeeStatusLine)(r.command)&&(0,S.readStatusLineSnapshot)(e,"claude")===void 0&&(0,S.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:Ee},a=(0,u.getStatusLineRefreshInterval)(n);a!==void 0&&(i.refreshInterval=a),this.writeStatusLineBlock(o,i),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} statusline ${l.pc.dim("\u2192")} ${l.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,c.join)(e,".claude","settings.local.json"),o=(0,S.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,S.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,A.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,a=o.refreshInterval,d={type:"command",command:r};return typeof i=="number"&&(d.padding=i),typeof a=="number"&&(d.refreshInterval=a),d}catch(n){k.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,t.existsSync)(e))try{const r=JSON.parse((0,t.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){k.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});o.statusLine=n,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeAutoModeAllowlist(e){let n={};if((0,t.existsSync)(e))try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode allowlist: ${d}`);return}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const o=n.autoMode!==null&&typeof n.autoMode=="object"&&!Array.isArray(n.autoMode)?n.autoMode:{},r=Array.isArray(o.allow)?o.allow.filter(d=>typeof d=="string"):[],i=r.filter(d=>!d.includes(M)),a=r.length===0?[N]:i;o.allow=[...a,we],n.autoMode=o,(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}removeAutoModeAllowlist(e){if(!(0,t.existsSync)(e))return;let n;try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode strip: ${d}`);return}if(n.autoMode===null||typeof n.autoMode!="object"||Array.isArray(n.autoMode))return;const o=n.autoMode;if(!Array.isArray(o.allow))return;const r=o.allow.filter(d=>typeof d=="string"),i=r.filter(d=>!d.includes(M));if(i.length===r.length)return;i.length===0||i.length===1&&i[0]===N?delete o.allow:o.allow=i,Object.keys(o).length===0?delete n.autoMode:n.autoMode=o,Object.keys(n).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}writeOTELEnv(e,n,o){let r={};if((0,t.existsSync)(e))try{const g=JSON.parse((0,t.readFileSync)(e,"utf-8"));g!==null&&typeof g=="object"&&!Array.isArray(g)&&(r=g)}catch(g){k.logger.debug(`failed to read ${e} for otel env write: ${g}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const i=r.env,a=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},d=a.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof d=="string"&&d.length>0&&!j(a)){console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),m=Re((0,ne.resolveProjectName)(n));a.CLAUDE_CODE_ENABLE_TELEMETRY="1",a.OTEL_LOGS_EXPORTER="otlp",a.OTEL_METRICS_EXPORTER="none",a.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",a.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,a.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",a.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${m}`,a.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=a,(0,t.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} otel env ${l.pc.dim("\u2192")} ${l.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!j(i))return;for(const a of Te)delete i[a];Object.keys(i).length===0&&delete o.env,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));$e(n)&&(0,t.unlinkSync)(e)}catch(n){k.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,D.run)(e)}async runClearVerdict(e){await(0,F.run)(e)}async runTrackAction(e){await(0,x.run)(e)}async runSessionStart(e){await(0,X.run)(e)}async runSubagentStart(e){await(0,Q.run)(e)}async runSubagentStop(e){await(0,Z.run)(e)}async runRequireVerdict(e,n){await(0,G.run)(e,n)}async runRequireVerification(e,n){await(0,q.run)(e,n)}async runActivityStart(e){await(0,z.run)(e)}async runActivityEnd(e){await(0,Y.run)(e)}async runTrackActionMonitor(e){await(0,W.run)(e)}async runSessionEnd(e){await(0,K.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(be))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,t.existsSync)(e))try{i=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(f){k.logger.debug(`failed to parse ${e}: ${f}`),i={}}i.hooks||(i.hooks={});for(const f of Object.keys(i.hooks)){const m=i.hooks[f].filter(g=>!this.isIronBeeHook(g));m.length===0?delete i.hooks[f]:i.hooks[f]=m}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*|mcp__android-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const a=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.Stop||(i.hooks.Stop=[]);const d=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:d}]}),i.hooks.SubagentStart||(i.hooks.SubagentStart=[]),i.hooks.SubagentStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-start --client claude"}]}),i.hooks.SubagentStop||(i.hooks.SubagentStop=[]),i.hooks.SubagentStop.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-stop --client claude"}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,t.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),n.mcpServers&&n.mcpServers[b]&&(delete n.mcpServers[b],o=!0),n.mcpServers&&n.mcpServers[E]&&(delete n.mcpServers[E],o=!0),n.mcpServers&&n.mcpServers[w]&&(delete n.mcpServers[w],o=!0),P(n)?(0,t.unlinkSync)(e):o&&(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8")),o=`mcp__${h}__*`,r=`mcp__${b}__*`,i=`mcp__${E}__*`,a=`mcp__${w}__*`,d="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==a&&m!==d&&m!==f),(0,t.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){k.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,t.existsSync)(e)&&(0,t.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,t.existsSync)(e))try{o=JSON.parse((0,t.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){k.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[h],delete o.mcpServers[b],delete o.mcpServers[E],delete o.mcpServers[w],P(o)){try{(0,t.rmSync)(e,{force:!0})}catch(r){k.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,t.existsSync)(e))try{r=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(p){k.logger.debug(`failed to parse ${e}: ${p}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${h}__*`,a=`mcp__${b}__*`,d=`mcp__${E}__*`,f=`mcp__${w}__*`,m="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const p=(0,u.loadConfig)(o);r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"browser"),i),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"node"),a),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"backend"),d),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"android"),f),r.permissions.allow=r.permissions.allow.filter($=>$!==g),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(p=>p!==i&&p!==a&&p!==d&&p!==f&&p!==m&&p!==g);(0,t.writeFileSync)(e,JSON.stringify(r,null,2))}}function Ce(s){(0,t.mkdirSync)((0,c.join)(s,".ironbee"),{recursive:!0}),(0,H.ensureIronBeeGitignored)(s)}v(Ce,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
7
+ `)}v(V,"injectVerifierModel");function Re(s){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(s))if(!e.has(n))return!1;if(s.hooks!==void 0&&Object.keys(s.hooks).length>0)return!1;if(s.permissions!==void 0){const n=s.permissions.allow??[],o=s.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v(Re,"isClaudeSettingsEmpty");const Ae=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function j(s){const e=s.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(j,"otelEnvOwnedByUs");function Ce(s){return s.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(Ce,"sanitizeResourceValue");class Me{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,t.existsSync)((0,c.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>be(require("./hooks/session-status")));await e()}async runHeadlessPrompt(e,n){return(0,F.runHeadlessCommand)("claude",["-p",e,"--output-format","text","--allowedTools","Read","Glob","Grep"],{cwd:n.projectDir,timeoutMs:n.timeoutMs,signal:n.signal})}install(e,n){const o=n??(0,u.loadConfig)(e),r=(0,u.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const a=(0,c.join)(e,".claude"),d=(0,c.join)(a,"skills"),f=(0,c.join)(a,"rules"),g=(0,c.join)(a,"commands");(0,t.mkdirSync)(d,{recursive:!0}),(0,t.mkdirSync)(f,{recursive:!0}),(0,t.mkdirSync)(g,{recursive:!0});const m=(0,c.join)(a,"settings.json");if(this.mergeHooksConfig(m,r),this.writePermissions(m,i,e),(0,u.isOTELEnabled)(o)&&this.writeOTELEnv(m,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const h=(0,c.join)(d,"ironbee-verification.md"),A=(0,t.readFileSync)((0,c.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(h,A);const fe=(0,c.join)(f,"ironbee-verification.md"),ge=(0,t.readFileSync)((0,c.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(fe,ge)}const y=(0,c.join)(g,"ironbee-verify.md"),p=(0,t.readFileSync)((0,c.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,t.writeFileSync)(y,p);const $=(0,c.join)(a,"agents");(0,t.mkdirSync)($,{recursive:!0});const te=(0,c.join)($,"ironbee-verifier.md"),se=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),ae=H(se,U(e,o)),le=V(ae,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(te,le);const ce=(0,c.join)($,"ironbee-scenario.md"),ue=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-scenario.md"),"utf-8"),de=H(ue,U(e,o)),me=V(de,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(ce,me);for(const h of P){const A=(0,c.join)(g,`${h}.md`);(0,t.writeFileSync)(A,(0,t.readFileSync)((0,c.join)(__dirname,"commands",`${h}.md`),"utf-8"))}const M=(0,c.join)(e,".mcp.json");if(this.writeMcpConfig(M,e),(0,ie.syncPlatformSectionsToConfig)(e,$e),(0,u.isAutoModeAllowlistEnabled)(o)){const h=(0,c.join)(a,"settings.local.json");this.writeAutoModeAllowlist(h)}(0,u.isClaudeTrustWorkspaceEnabled)(o)&&(0,oe.ensureWorkspaceTrusted)(e)&&console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} trusted workspace in ~/.claude.json ${l.pc.dim("(permissions.allow now honored)")}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(m)}`),r==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} skills ${l.pc.dim("\u2192")} ${l.pc.dim(d)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} rule ${l.pc.dim("\u2192")} ${l.pc.dim(f)}`)):console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} commands ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,c.join)(a,"agents"))}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} mcp ${l.pc.dim("\u2192")} ${l.pc.dim(M)}`)}else console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(m)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,c.join)(e,".claude"),o=(0,c.join)(n,"skills","ironbee-verification.md"),r=(0,c.join)(n,"skills","ironbee-analyze.md"),i=(0,c.join)(n,"rules","ironbee-verification.md"),a=(0,c.join)(n,"commands","ironbee-analyze.md"),d=(0,c.join)(n,"commands","ironbee-verify.md"),f=(0,c.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(a),this.removeFile(d),this.removeFile(f),this.removeFile((0,c.join)(n,"agents","ironbee-scenario.md"));for(const y of P)this.removeFile((0,c.join)(n,"commands",`${y}.md`));this.removeFile((0,c.join)(n,"commands","ironbee-run-scenario.md"));const g=(0,c.join)(n,"settings.json");this.removeIronBeeHooks(g),this.removePermission(g),this.removeOTELEnv(g),this.maybeDeleteEmptySettings(g);const m=(0,c.join)(e,".mcp.json");this.removeMcpServer(m),this.removeAutoModeAllowlist((0,c.join)(n,"settings.local.json")),this.uninstallStatusLine(e),(0,D.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,c.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,C.isIronbeeStatusLine)(r.command)&&(0,S.readStatusLineSnapshot)(e,"claude")===void 0&&(0,S.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:we},a=(0,u.getStatusLineRefreshInterval)(n);a!==void 0&&(i.refreshInterval=a),this.writeStatusLineBlock(o,i),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} statusline ${l.pc.dim("\u2192")} ${l.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,c.join)(e,".claude","settings.local.json"),o=(0,S.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,S.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,C.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,a=o.refreshInterval,d={type:"command",command:r};return typeof i=="number"&&(d.padding=i),typeof a=="number"&&(d.refreshInterval=a),d}catch(n){k.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,t.existsSync)(e))try{const r=JSON.parse((0,t.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){k.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});o.statusLine=n,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeAutoModeAllowlist(e){let n={};if((0,t.existsSync)(e))try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode allowlist: ${d}`);return}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const o=n.autoMode!==null&&typeof n.autoMode=="object"&&!Array.isArray(n.autoMode)?n.autoMode:{},r=Array.isArray(o.allow)?o.allow.filter(d=>typeof d=="string"):[],i=r.filter(d=>!d.includes(N)),a=r.length===0?[I]:i;o.allow=[...a,Oe],n.autoMode=o,(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}removeAutoModeAllowlist(e){if(!(0,t.existsSync)(e))return;let n;try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode strip: ${d}`);return}if(n.autoMode===null||typeof n.autoMode!="object"||Array.isArray(n.autoMode))return;const o=n.autoMode;if(!Array.isArray(o.allow))return;const r=o.allow.filter(d=>typeof d=="string"),i=r.filter(d=>!d.includes(N));if(i.length===r.length)return;i.length===0||i.length===1&&i[0]===I?delete o.allow:o.allow=i,Object.keys(o).length===0?delete n.autoMode:n.autoMode=o,Object.keys(n).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}writeOTELEnv(e,n,o){let r={};if((0,t.existsSync)(e))try{const m=JSON.parse((0,t.readFileSync)(e,"utf-8"));m!==null&&typeof m=="object"&&!Array.isArray(m)&&(r=m)}catch(m){k.logger.debug(`failed to read ${e} for otel env write: ${m}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const i=r.env,a=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},d=a.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof d=="string"&&d.length>0&&!j(a)){console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),g=Ce((0,re.resolveProjectName)(n));a.CLAUDE_CODE_ENABLE_TELEMETRY="1",a.OTEL_LOGS_EXPORTER="otlp",a.OTEL_METRICS_EXPORTER="none",a.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",a.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,a.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",a.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${g}`,a.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=a,(0,t.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} otel env ${l.pc.dim("\u2192")} ${l.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!j(i))return;for(const a of Ae)delete i[a];Object.keys(i).length===0&&delete o.env,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));Re(n)&&(0,t.unlinkSync)(e)}catch(n){k.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,x.run)(e)}async runClearVerdict(e){await(0,G.run)(e)}async runTrackAction(e){await(0,W.run)(e)}async runSessionStart(e){await(0,q.run)(e)}async runSubagentStart(e){await(0,ee.run)(e)}async runSubagentStop(e){await(0,ne.run)(e)}async runRequireVerdict(e,n){await(0,z.run)(e,n)}async runRequireVerification(e,n){await(0,Y.run)(e,n)}async runActivityStart(e){await(0,K.run)(e)}async runActivityEnd(e){await(0,Q.run)(e)}async runTrackActionMonitor(e){await(0,X.run)(e)}async runSessionEnd(e){await(0,Z.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(Ee))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,t.existsSync)(e))try{i=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(f){k.logger.debug(`failed to parse ${e}: ${f}`),i={}}i.hooks||(i.hooks={});for(const f of Object.keys(i.hooks)){const g=i.hooks[f].filter(m=>!this.isIronBeeHook(m));g.length===0?delete i.hooks[f]:i.hooks[f]=g}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*|mcp__android-devtools__.*|mcp__terminal-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const a=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.Stop||(i.hooks.Stop=[]);const d=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:d}]}),i.hooks.SubagentStart||(i.hooks.SubagentStart=[]),i.hooks.SubagentStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-start --client claude"}]}),i.hooks.SubagentStop||(i.hooks.SubagentStop=[]),i.hooks.SubagentStop.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-stop --client claude"}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,t.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[b]&&(delete n.mcpServers[b],o=!0),n.mcpServers&&n.mcpServers[_]&&(delete n.mcpServers[_],o=!0),n.mcpServers&&n.mcpServers[E]&&(delete n.mcpServers[E],o=!0),n.mcpServers&&n.mcpServers[w]&&(delete n.mcpServers[w],o=!0),n.mcpServers&&n.mcpServers[O]&&(delete n.mcpServers[O],o=!0),B(n)?(0,t.unlinkSync)(e):o&&(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8")),o=`mcp__${b}__*`,r=`mcp__${_}__*`,i=`mcp__${E}__*`,a=`mcp__${w}__*`,d=`mcp__${O}__*`,f="Bash(ironbee *)",g="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==a&&m!==d&&m!==f&&m!==g),(0,t.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){k.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,t.existsSync)(e)&&(0,t.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,t.existsSync)(e))try{o=JSON.parse((0,t.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){k.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[b],delete o.mcpServers[_],delete o.mcpServers[E],delete o.mcpServers[w],delete o.mcpServers[O],B(o)){try{(0,t.rmSync)(e,{force:!0})}catch(r){k.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,t.existsSync)(e))try{r=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(p){k.logger.debug(`failed to parse ${e}: ${p}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${b}__*`,a=`mcp__${_}__*`,d=`mcp__${E}__*`,f=`mcp__${w}__*`,g=`mcp__${O}__*`,m="Bash(ironbee *)",y="Bash(ironbee analyze)";if(n){const p=(0,u.loadConfig)(o);r.permissions.allow=R(r.permissions.allow,(0,u.isCycleEnabled)(p,"browser"),i),r.permissions.allow=R(r.permissions.allow,(0,u.isCycleEnabled)(p,"node"),a),r.permissions.allow=R(r.permissions.allow,(0,u.isCycleEnabled)(p,"backend"),d),r.permissions.allow=R(r.permissions.allow,(0,u.isCycleEnabled)(p,"android"),f),r.permissions.allow=R(r.permissions.allow,(0,u.isCycleEnabled)(p,"terminal"),g),r.permissions.allow=r.permissions.allow.filter($=>$!==y),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(p=>p!==i&&p!==a&&p!==d&&p!==f&&p!==g&&p!==m&&p!==y);(0,t.writeFileSync)(e,JSON.stringify(r,null,2))}}function Le(s){(0,t.mkdirSync)((0,c.join)(s,".ironbee"),{recursive:!0}),(0,J.ensureIronBeeGitignored)(s)}v(Le,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
@@ -0,0 +1,26 @@
1
+ ### Terminal platform (enabled)
2
+ - **Use for**: CLI / REPL / TUI scenarios driven in a PTY.
3
+ - **Server**: `terminal-devtools` · **scenario tools**: `mcp__terminal-devtools__tdt_scenario-*`.
4
+ - **Store**: project → `.ironbee/scenarios/tdt`, global → `~/.ironbee/scenarios/tdt` (the server's
5
+ `SCENARIOS_DIR`; pass `scope`, the server resolves the path).
6
+ - Scenario **scripts** call terminal tools via `callTool('<bare-tool>', {...})` — discover the
7
+ available `tdt_*` tool names (pty / interaction / content / sync …) from your connected
8
+ MCP schemas; don't guess.
9
+
10
+ **What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
11
+ verification, so its script must collect what the terminal cycle collects). In the script, pick an
12
+ **evidence path** for the changed code area:
13
+ 1. **Run-evidence path** — run the command with `callTool('tdt_pty_run', { ... })` **with
14
+ `returnOutput: true`** so the full output + exit code come back in the result; put that captured
15
+ output and exit code in your result. Best for non-interactive CLIs (a subcommand, a script).
16
+ 2. **Interactive-evidence path** — `callTool('tdt_pty_start', {...})` to spawn (returns a `paneId`),
17
+ then drive input with `callTool('tdt_interaction_send-keys', {...})` (tmux syntax: `Enter` / `C-c` /
18
+ `Up` / `Tab`) or `callTool('tdt_interaction_send-text', {...})`, synchronize with
19
+ `callTool('tdt_sync_wait-for', {...})` (prefer over fixed delays), and read output with
20
+ `callTool('tdt_content_capture', { returnOutput: true, ... })` (`mode: stream` for REPLs / shells,
21
+ `mode: screen` for a full-screen TUI) — put the captured output in your result. Stop the pane with
22
+ `callTool('tdt_pty_stop', {...})` when done.
23
+
24
+ `return` the evidence — captured output / exit code — **plus explicit pass/fail assertions**. That
25
+ returned result is what `/ironbee-verify scenario:<name>` reads to judge whether the change behaved
26
+ correctly. **`terminal-devtools` drives CLIs/REPLs/TUIs in a PTY.**
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **Recording (only when `recording.enable` is on in config):** the gate blocks every other browser tool until you first call `mcp__browser-devtools__bdt_content_start-recording`, and `submit-verdict` rejects with `"recording is still active"` unless you call `mcp__browser-devtools__bdt_content_stop-recording` after the steps below. **Treat start/stop as bookends around steps 1-5.** The same is enforced as step 6 of the Universal flow.
8
8
 
9
- 1. **Navigate**: `mcp__browser-devtools__bdt_navigation_go-to` — go to the affected page(s)
9
+ 1. **Navigate**: `mcp__browser-devtools__bdt_navigation_go-to` — go to the affected page(s) **AND any downstream page that renders or consumes what the change produces** — verify the change's effect where it's observed, not only the page the edited file owns
10
10
  2. **Interact**: actually exercise what changed — click buttons, fill forms, submit data, trigger workflows. Don't just look at the page.
11
11
  3. **Screenshot**: `mcp__browser-devtools__bdt_content_take-screenshot` — capture the final visual state
12
12
  4. **Accessibility**: `mcp__browser-devtools__bdt_a11y_take-aria-snapshot` — verify page structure
@@ -0,0 +1,62 @@
1
+ <!-- Terminal verification is ENABLED for this project. The Stop hook
2
+ enforces a terminal cycle whenever an edited file matches
3
+ `terminal.verifyPatterns`. -->
4
+
5
+ ## ⚠️ CRITICAL: when NOT to use terminal-devtools
6
+
7
+ **`terminal-devtools` is ONLY for programs that speak through a terminal** — CLIs, REPLs, and full-screen TUIs (the tools drive them by spawning them attached to a PTY, like tmux). Do **NOT** call `tdt_*` tools for changes with no terminal-observable behavior.
8
+
9
+ **How to tell whether the change has a terminal surface:**
10
+ - A CLI subcommand / script / binary that produces output and an exit code
11
+ - A REPL or interactive prompt the user types into
12
+ - A full-screen terminal UI (a TUI rendered on the terminal grid)
13
+
14
+ If the change is **pure library internals with no runnable entry point**, or a **web-only UI change** (that's the browser cycle) — there is nothing terminal to drive. Do NOT call any `tdt_*` tools.
15
+
16
+ **Misconfiguration recovery.** If this cycle activated but the project has nothing terminal to drive, the operator enabled the terminal cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` until `maxRetries` is exhausted. Don't attempt to spawn a PTY. Stop and tell the user clearly: the project has no terminal-observable surface; ask them to run `ironbee terminal disable` to unblock the gate.
17
+
18
+ ## Terminal flow
19
+
20
+ Pick **ONE evidence path** per changed code area:
21
+
22
+ - **Run-evidence path** (one-shot CLI command — best for non-interactive CLIs: a subcommand, a script):
23
+ - Run the command with `mcp__terminal-devtools__tdt_pty_run` — it spawns, runs, and returns the full output + exit code in one call.
24
+ - Confirm the output AND exit code reflect the change (expected text present, `exitCode: 0` unless a failure is the expected result).
25
+ - **Interactive-evidence path** (REPL / TUI / multi-step flows):
26
+ - Spawn the program: `mcp__terminal-devtools__tdt_pty_start` (returns a `paneId`).
27
+ - Drive input: `mcp__terminal-devtools__tdt_interaction_send-keys` (keystrokes, tmux syntax: `Enter` / `C-c` / `Up` / `Tab`) or `mcp__terminal-devtools__tdt_interaction_send-text` (literal text).
28
+ - Synchronize: `mcp__terminal-devtools__tdt_sync_wait-for` to block until the expected output appears — **prefer this over fixed delays**.
29
+ - Read output: `mcp__terminal-devtools__tdt_content_capture` — `mode: stream` for line-oriented programs / REPLs / shells (incremental cursor reads via `since`); `mode: screen` for the rendered grid of a full-screen TUI.
30
+ - Tear down when done: `mcp__terminal-devtools__tdt_pty_stop`.
31
+ - **Auxiliary (NOT evidence — synchronization / process-control helpers only):** `mcp__terminal-devtools__tdt_sync_wait-for-idle` (wait until output goes quiet when there's no clean marker), `mcp__terminal-devtools__tdt_content_get-cursor`, `mcp__terminal-devtools__tdt_pty_resize`, `mcp__terminal-devtools__tdt_pty_signal`, `mcp__terminal-devtools__tdt_pty_list`. These shape / control the run; they don't inspect it, so they don't count toward the gate.
32
+
33
+ ### Verdict fields
34
+ The verdict is platform-agnostic — submit only semantic judgment:
35
+
36
+ ```json
37
+ {
38
+ "session_id": "<sid>",
39
+ "status": "pass",
40
+ "checks": ["`mycli deploy` exits 0 with expected summary", "no stack trace in output"]
41
+ }
42
+ ```
43
+
44
+ On fail, include `issues`. On pass after a previous fail, include `fixes`.
45
+
46
+ Terminal-cycle pass criteria:
47
+ - **Run-evidence**: a command ran via `mcp__terminal-devtools__tdt_pty_run` AND its output + exit code confirm the change.
48
+ - **Interactive-evidence**: a pane was spawned AND input was driven AND output was captured AND it shows the expected result.
49
+
50
+ ## Multi-cycle (browser + terminal simultaneously)
51
+
52
+ When you edit both a web frontend file (browser-cycle) and a CLI source file (terminal-cycle) in the same task, both cycles activate. **Single** `verification-start`, **single** `verdict.json`, **single** retry counter cover both. One platform-agnostic verdict regardless of how many cycles ran:
53
+
54
+ ```json
55
+ {
56
+ "session_id": "<sid>",
57
+ "status": "pass",
58
+ "checks": ["web dashboard renders", "`mycli sync` exits 0 with expected summary"]
59
+ }
60
+ ```
61
+
62
+ For a multi-cycle `pass`, ALL active cycles' pass criteria must hold.