@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.
- package/CHANGELOG.md +8 -0
- package/dist/clients/base.js +1 -1
- package/dist/clients/claude/agents/ironbee-scenario.md +40 -11
- package/dist/clients/claude/agents/ironbee-verifier.md +40 -4
- package/dist/clients/claude/commands/ironbee-manage-scenario.md +2 -1
- package/dist/clients/claude/hooks/require-verdict.js +2 -2
- package/dist/clients/claude/hooks/require-verification.js +3 -3
- package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/scenario.terminal.md +26 -0
- package/dist/clients/claude/platforms/skill.browser.md +1 -1
- package/dist/clients/claude/platforms/skill.terminal.md +62 -0
- package/dist/clients/codex/agents/ironbee-scenario.md +39 -10
- package/dist/clients/codex/agents/ironbee-verifier.md +39 -3
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +21 -6
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.md +2 -1
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +3 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +4 -1
- package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +4 -0
- package/dist/clients/codex/hooks/require-verification.js +1 -1
- package/dist/clients/codex/hooks/track-action.js +1 -1
- package/dist/clients/codex/index.js +2 -2
- package/dist/clients/codex/platforms/command-verify.terminal.md +61 -0
- package/dist/clients/codex/platforms/rule.terminal.md +31 -0
- package/dist/clients/codex/platforms/scenario.terminal.md +36 -0
- package/dist/clients/codex/platforms/skill.browser.md +1 -1
- package/dist/clients/codex/platforms/skill.terminal.md +57 -0
- package/dist/clients/codex/rules/ironbee-verification.main.md +3 -0
- package/dist/clients/codex/skills/ironbee-verification.main.md +14 -0
- package/dist/clients/codex/util.js +1 -1
- package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +21 -6
- package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +3 -0
- package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +4 -1
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +4 -0
- package/dist/clients/cursor/hooks/require-verdict.js +2 -2
- package/dist/clients/cursor/hooks/require-verification.js +3 -3
- package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
- package/dist/clients/cursor/hooks/track-action.js +1 -1
- package/dist/clients/cursor/index.js +1 -1
- package/dist/clients/cursor/platforms/command-verify.terminal.md +61 -0
- package/dist/clients/cursor/platforms/rule.terminal.md +31 -0
- package/dist/clients/cursor/platforms/scenario.terminal.md +29 -0
- package/dist/clients/cursor/platforms/skill.browser.md +1 -1
- package/dist/clients/cursor/platforms/skill.terminal.md +54 -0
- package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
- package/dist/clients/cursor/skills/ironbee-verification.md +14 -0
- package/dist/clients/registry.js +1 -1
- package/dist/commands/config.js +2 -2
- package/dist/commands/hook.js +22 -19
- package/dist/commands/install.js +1 -1
- package/dist/commands/platform-suggest.js +2 -0
- package/dist/commands/scenario.js +1 -1
- package/dist/commands/terminal.js +1 -0
- package/dist/hooks/core/actions.js +9 -7
- package/dist/hooks/core/run-checks.js +7 -0
- package/dist/hooks/core/verification-context.js +19 -15
- package/dist/hooks/core/verify-gate.js +35 -21
- package/dist/import/claude/events/tool-call.js +1 -1
- package/dist/import/codex/events/tool-call.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/event.js +1 -1
- package/dist/lib/headless.js +1 -0
- package/dist/lib/install-version.js +1 -1
- package/dist/lib/platform-section.js +5 -4
- package/dist/lib/prompt.js +6 -5
- package/dist/lib/scenario-staleness.js +1 -1
- package/dist/tui/config/schema.js +1 -1
- package/dist/tui/platforms/area.js +2 -2
- package/dist/tui/projects/area.js +4 -4
- package/dist/tui/shell/session.js +5 -5
- 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
|
package/dist/clients/base.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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`
|
|
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
|
|
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
|
|
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
|
-
|
|
163
|
-
the `argsSchema` metadata. **Discover the available `callTool` tool names for a
|
|
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,
|
|
177
|
-
|
|
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
|
|
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
|
|
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),
|
|
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),
|
|
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
|
|
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
|
|
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
|
|
2
|
-
`)}v(
|
|
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(
|
|
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.
|