@ironbee-ai/cli 0.21.1 → 0.22.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 +12 -0
- package/dist/analytics/claude/pricing.js +1 -1
- package/dist/clients/claude/agents/ironbee-verifier.md +12 -2
- package/dist/clients/claude/commands/ironbee-verify.md +28 -15
- package/dist/clients/claude/hooks/activity-start.js +1 -1
- package/dist/clients/claude/hooks/require-verdict.js +2 -2
- package/dist/clients/claude/hooks/require-verification.js +7 -7
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/skill.android.md +65 -0
- package/dist/clients/codex/agents/ironbee-verifier.md +11 -1
- package/dist/clients/codex/commands/ironbee-verify/SKILL.md +24 -5
- package/dist/clients/codex/hooks/require-verification.js +7 -7
- 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.android.md +61 -0
- package/dist/clients/codex/platforms/rule.android.md +32 -0
- package/dist/clients/codex/platforms/skill.android.md +55 -0
- package/dist/clients/codex/skills/ironbee-verification.md +3 -0
- package/dist/clients/codex/util.js +11 -11
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +21 -4
- package/dist/clients/cursor/hooks/require-verdict.js +1 -1
- package/dist/clients/cursor/hooks/require-verification.js +6 -6
- 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.android.md +61 -0
- package/dist/clients/cursor/platforms/rule.android.md +32 -0
- package/dist/clients/cursor/platforms/skill.android.md +55 -0
- package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
- package/dist/clients/cursor/skills/ironbee-verification.md +3 -0
- package/dist/commands/android.js +1 -0
- package/dist/commands/config.js +2 -2
- package/dist/commands/cycle-toggle.js +4 -4
- package/dist/commands/hook.js +16 -15
- package/dist/commands/install.js +1 -1
- package/dist/commands/mode-select.js +2 -0
- package/dist/hooks/core/actions.js +6 -5
- package/dist/hooks/core/session-state.js +1 -1
- package/dist/hooks/core/submit-verdict.js +4 -4
- package/dist/hooks/core/verification-lifecycle.js +1 -1
- package/dist/hooks/core/verify-gate.js +29 -23
- 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/install-version.js +1 -0
- package/dist/lib/platform-section.js +4 -3
- package/dist/lib/prompt.js +4 -4
- package/dist/lib/recording-tools.js +1 -0
- package/dist/lib/schema-sync.js +2 -0
- package/dist/lib/version.js +1 -1
- package/dist/scripts/postinstall.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/sessions/area.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 (2026-06-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* android platform support ([#25](https://github.com/ironbee-ai/ironbee-cli/issues/25)) ([f155488](https://github.com/ironbee-ai/ironbee-cli/commit/f155488bcb595895ce0b4b6383207a9e5466a41b))
|
|
8
|
+
|
|
9
|
+
## 0.21.2 (2026-06-09)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **pricing:** add Claude Fable 5 / Mythos 5 model pricing ([3efc304](https://github.com/ironbee-ai/ironbee-cli/commit/3efc304d784f6637ab8c6e83a3f9c451cd0a16c8))
|
|
14
|
+
|
|
3
15
|
## 0.21.1 (2026-06-09)
|
|
4
16
|
|
|
5
17
|
## 0.21.0 (2026-06-09)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var u=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var o=(e,c)=>u(e,"name",{value:c,configurable:!0});var l=(e,c)=>{for(var r in c)u(e,r,{get:c[r],enumerable:!0})},b=(e,c,r,n)=>{if(c&&typeof c=="object"||typeof c=="function")for(let a of d(c))!p.call(e,a)&&a!==r&&u(e,a,{get:()=>c[a],enumerable:!(n=s(c,a))||n.enumerable});return e};var f=e=>b(u({},"__esModule",{value:!0}),e);var m={};l(m,{BYTES_PER_TOKEN:()=>i.BYTES_PER_TOKEN,OPUS_4_6_FAST_PRICING:()=>_,PRICING:()=>t,computeMessageCostUsd:()=>k,lookupPricing:()=>h,lookupPricingForUsage:()=>w});module.exports=f(m);var i=require("../shared/tokens");const t={"claude-fable-5":{input:10,output:50,cache_read:1,cache_creation:12.5,cache_creation_1h:20,web_search:.01},"claude-mythos-5":{input:10,output:50,cache_read:1,cache_creation:12.5,cache_creation_1h:20,web_search:.01},"claude-opus-4-8":{input:5,output:25,cache_read:.5,cache_creation:6.25,cache_creation_1h:10,web_search:.01},"claude-opus-4-7":{input:5,output:25,cache_read:.5,cache_creation:6.25,cache_creation_1h:10,web_search:.01},"claude-opus-4-6":{input:5,output:25,cache_read:.5,cache_creation:6.25,cache_creation_1h:10,web_search:.01},"claude-opus-4-5":{input:5,output:25,cache_read:.5,cache_creation:6.25,cache_creation_1h:10,web_search:.01},"claude-opus-4-1":{input:15,output:75,cache_read:1.5,cache_creation:18.75,cache_creation_1h:30,web_search:.01},"claude-opus-4":{input:15,output:75,cache_read:1.5,cache_creation:18.75,cache_creation_1h:30,web_search:.01},"claude-sonnet-4-6":{input:3,output:15,cache_read:.3,cache_creation:3.75,cache_creation_1h:6,web_search:.01},"claude-sonnet-4-5":{input:3,output:15,cache_read:.3,cache_creation:3.75,cache_creation_1h:6,web_search:.01},"claude-sonnet-4":{input:3,output:15,cache_read:.3,cache_creation:3.75,cache_creation_1h:6,web_search:.01},"claude-haiku-4-5":{input:1,output:5,cache_read:.1,cache_creation:1.25,cache_creation_1h:2,web_search:.01},"claude-haiku-4":{input:1,output:5,cache_read:.1,cache_creation:1.25,cache_creation_1h:2,web_search:.01}},_={input:30,output:150,cache_read:3,cache_creation:37.5,cache_creation_1h:60,web_search:.01};function h(e){const c=t[e];return c!==void 0?c:e.startsWith("claude-fable-")?t["claude-fable-5"]:e.startsWith("claude-mythos-")?t["claude-mythos-5"]:e.startsWith("claude-opus-")?t["claude-opus-4-8"]:e.startsWith("claude-sonnet-")?t["claude-sonnet-4-6"]:e.startsWith("claude-haiku-")?t["claude-haiku-4-5"]:null}o(h,"lookupPricing");function w(e,c){return c==="fast"&&e.includes("opus-4-6")?_:h(e)}o(w,"lookupPricingForUsage");function k(e,c){if(c===null)return 0;const r=e.cache_creation_tokens*c.cache_creation,n=e.web_search_requests??0,a=c.web_search??0;return(e.input_tokens*c.input+e.output_tokens*c.output+r+e.cache_read_tokens*c.cache_read)/1e6+n*a}o(k,"computeMessageCostUsd");0&&(module.exports={BYTES_PER_TOKEN,OPUS_4_6_FAST_PRICING,PRICING,computeMessageCostUsd,lookupPricing,lookupPricingForUsage});
|
|
@@ -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, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*
|
|
9
|
+
tools: Bash, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# IronBee Verifier (delegated verification)
|
|
@@ -42,6 +42,12 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
42
42
|
```
|
|
43
43
|
echo '{}' | ironbee hook verification-start
|
|
44
44
|
```
|
|
45
|
+
**If the delegating prompt contains a `Mode: fix` line**, pass the intent
|
|
46
|
+
along so IronBee's completion gate enforces fix-until-pass on the main agent:
|
|
47
|
+
```
|
|
48
|
+
echo '{}' | ironbee hook verification-start --intent fix
|
|
49
|
+
```
|
|
50
|
+
(No declared mode → plain form as above, no flag.)
|
|
45
51
|
2. Build and start the application **only if it isn't already running** (check
|
|
46
52
|
`docker compose ps` / process output / config — don't guess ports). **Track whether YOU
|
|
47
53
|
started it**: if it was already up, the user or main agent owns it — leave it alone.
|
|
@@ -53,7 +59,8 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
53
59
|
this verification*, stop it now before you return — kill the exact process/container you
|
|
54
60
|
launched (e.g. the backgrounded `npm run dev`, the `docker compose up` you ran). **Never
|
|
55
61
|
stop a server that was already running** (user/main-agent-owned). Also honor any
|
|
56
|
-
cycle-specific teardown (e.g.
|
|
62
|
+
cycle-specific teardown noted in the platform sections (e.g. stopping an active screen
|
|
63
|
+
recording) BEFORE submitting your verdict.
|
|
57
64
|
5. **Submit your verdict immediately** — do NOT wait:
|
|
58
65
|
```
|
|
59
66
|
echo '<verdict-json>' | ironbee hook submit-verdict
|
|
@@ -76,3 +83,6 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
76
83
|
|
|
77
84
|
<!--IRONBEE:PLATFORM:backend-->
|
|
78
85
|
<!--/IRONBEE:PLATFORM:backend-->
|
|
86
|
+
|
|
87
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
88
|
+
<!--/IRONBEE:PLATFORM:android-->
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
---
|
|
2
|
-
argument-hint: "[scenario text | path to scenario file]"
|
|
3
|
-
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent;
|
|
2
|
+
argument-hint: "[fix|report] [scenario text | path to scenario file]"
|
|
3
|
+
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent. Default is verify-only (report the verdict and stop); a leading `fix` argument adds the fix-and-re-verify loop until pass. Optionally pass a custom scenario (inline text or a file path) that defines what to verify.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# IronBee Verify
|
|
7
7
|
|
|
8
|
-
Verify the current code changes by **delegating to the `ironbee-verifier` sub-agent**. It drives the verification tools out-of-band in this **shared session** and returns a verdict summary — so the heavy devtools output (DOM, console, screenshots) stays in its context, not yours. **You do not run the verification tools yourself**: you resolve the scenario (below), spawn the verifier, and relay its result. The gate still runs every active cycle and all must pass for `status: pass`.
|
|
8
|
+
Verify the current code changes by **delegating to the `ironbee-verifier` sub-agent**. It drives the verification tools out-of-band in this **shared session** and returns a verdict summary — so the heavy devtools output (DOM, console, screenshots) stays in its context, not yours. **You do not run the verification tools yourself**: you resolve the mode and scenario (below), spawn the verifier, and relay its result. The gate still runs every active cycle and all must pass for `status: pass`.
|
|
9
|
+
|
|
10
|
+
## Mode
|
|
11
|
+
|
|
12
|
+
The FIRST whitespace-delimited token of the arguments selects the mode; everything after it is the scenario:
|
|
13
|
+
|
|
14
|
+
- `fix` → **verify-and-fix**: on a fail verdict, fix the reported issues and re-delegate until the verdict passes.
|
|
15
|
+
- `report` → **verify-only** (the explicit form of the default).
|
|
16
|
+
- Anything else, or no arguments → **verify-only** (default), and the WHOLE argument string is the scenario.
|
|
17
|
+
|
|
18
|
+
**Verify-only** means: relay the verdict and STOP — do **not** edit code, do **not** re-delegate on fail. The fail verdict is still submitted and recorded (that's the point — an honest status report). If the user wants the issues repaired, suggest `/ironbee-verify fix`. One caveat (enforce mode): if code was edited earlier in THIS turn, the Stop gate may still block on the fail verdict and demand fixes — follow the gate then; the mode token never overrides enforcement.
|
|
9
19
|
|
|
10
20
|
## Verification scenario
|
|
11
21
|
|
|
12
22
|
A custom verification scenario may be supplied when this command is invoked — either as **inline text** or as a **path to a file** (any location, any format; read at run time).
|
|
13
23
|
|
|
14
|
-
>
|
|
15
|
-
> *(
|
|
24
|
+
> Mode + scenario argument: `$ARGUMENTS`
|
|
25
|
+
> *(strip a leading `fix` / `report` mode token first — the remainder is the scenario; empty remainder → the verifier uses its default flow)*
|
|
16
26
|
|
|
17
27
|
- **If a scenario is supplied, it is authoritative**: the verifier must verify exactly what it describes, exercising precisely the flows/states/endpoints it names — this **replaces** the default "exercise the changed pages/endpoints" guidance.
|
|
18
28
|
- **If the scenario is (or points to) a file path**, read that file with your file-read tool yourself and pass its **contents** into the verifier's prompt (the verifier has no file-read tool). Do not assume a fixed location or format — read whatever path was given.
|
|
@@ -21,17 +31,20 @@ A custom verification scenario may be supplied when this command is invoked —
|
|
|
21
31
|
|
|
22
32
|
## Steps
|
|
23
33
|
|
|
24
|
-
1. **Resolve the scenario**: file path → read it now; inline text → use as-is; empty → none.
|
|
25
|
-
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task
|
|
34
|
+
1. **Resolve the mode and scenario**: strip a leading `fix` / `report` token (see **Mode**); then file path → read it now; inline text → use as-is; empty → none.
|
|
35
|
+
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task, the mode, and the resolved scenario, e.g.:
|
|
26
36
|
> Verify the current code changes.
|
|
37
|
+
> Mode: \<`fix` in fix mode — OMIT this line entirely in verify-only mode>
|
|
27
38
|
> Scenario: \<the resolved scenario text, or "none — exercise the changed pages/endpoints">
|
|
28
|
-
The verifier runs `verification-start` → drives every active cycle's tools → submits the single verdict, all in this shared session. It resolves the session id from the environment, so you don't pass one.
|
|
39
|
+
The verifier runs `verification-start` (relaying the fix intent to IronBee's completion gate, which then enforces fix-until-pass on you) → drives every active cycle's tools → submits the single verdict, all in this shared session. It resolves the session id from the environment, so you don't pass one.
|
|
29
40
|
3. **Relay the verifier's summary** — the verdict status and, on fail, the issues it found.
|
|
30
|
-
4. **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
4. **On a fail verdict, branch by mode**:
|
|
42
|
+
- **Verify-only (default)**: stop here. Report the issues clearly and suggest `/ironbee-verify fix` to repair them. Do not edit code.
|
|
43
|
+
- **Fix mode (`fix` token)**: fix the issues it reported. Optionally record what you fixed so the next pass verdict can describe it:
|
|
44
|
+
```
|
|
45
|
+
echo '{"fixes":["what you repaired"]}' | ironbee hook record-fix
|
|
46
|
+
```
|
|
47
|
+
Then re-run the verification by re-delegating (step 2) — repeat until the verdict passes. (If you skip `record-fix`, IronBee fills `fixes` from the files you changed since the fail.)
|
|
35
48
|
|
|
36
49
|
Do NOT verify inline — always delegate, so your context stays clean. The per-cycle "how to verify" detail (which tools to drive, the verdict expectations) lives in the `ironbee-verifier` sub-agent itself — you don't need it here to delegate.
|
|
37
50
|
|
|
@@ -43,6 +56,6 @@ Do NOT verify inline — always delegate, so your context stays clean. The per-c
|
|
|
43
56
|
- Its `checks` are specific observations (e.g. `"submitted valid credentials, redirected to /dashboard"`, `"console clean — 0 errors"`), not `"it works"`.
|
|
44
57
|
|
|
45
58
|
## Important
|
|
46
|
-
- The **verifier** produces the verdict; your job is to delegate, relay it, and fix on fail.
|
|
47
|
-
-
|
|
59
|
+
- The **verifier** produces the verdict; your job is to delegate, relay it, and — in fix mode — fix on fail.
|
|
60
|
+
- **Fix mode only**: a fail verdict means you must fix the issues and re-delegate until pass. In verify-only mode (the default) you report and stop — fixing without the `fix` token is overstepping.
|
|
48
61
|
- Never verify inline to "save a round trip" — delegation keeps your context clean and is the supported path.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var r=Object.defineProperty;var
|
|
1
|
+
"use strict";var r=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var a=(s,t)=>r(s,"name",{value:t,configurable:!0});var S=(s,t)=>{for(var e in t)r(s,e,{get:t[e],enumerable:!0})},$=(s,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!b.call(s,i)&&i!==e&&r(s,i,{get:()=>t[i],enumerable:!(o=f(t,i))||o.enumerable});return s};var w=s=>$(r({},"__esModule",{value:!0}),s);var A={};S(A,{run:()=>y});module.exports=w(A);var p=require("../../../hooks/core/actions"),m=require("../../../hooks/core/activity"),c=require("../../../hooks/core/session-state"),n=require("../../../lib/logger"),u=require("../../../lib/stdin"),d=require("../../../otel/claude/daemon/ensure");async function y(s){let t;try{t=JSON.parse((0,u.readStdin)())}catch(l){n.logger.debug(`failed to parse stdin: ${l}`),process.exit(0)}const e=t.session_id??"default",o=`${s}/.ironbee/sessions/${e}`;(0,n.setLogFile)(`${o}/session.log`);const i=`${o}/actions.jsonl`;await(0,c.reconcileAbandonedActivity)(o,i,p.appendAction),await(0,m.startActivity)({sessionDir:o,actionsFile:i,source:"user_prompt"}),await(0,d.ensureOTELCollector)(s),process.exit(0)}a(y,"run");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var _=(e,o)=>a(e,"name",{value:o,configurable:!0});var T=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},w=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of x(o))!y.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=$(o,s))||t.enumerable});return e};var I=e=>w(a({},"__esModule",{value:!0}),e);var E={};T(E,{run:()=>k});module.exports=I(E);var d=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),
|
|
1
|
+
"use strict";var a=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var _=(e,o)=>a(e,"name",{value:o,configurable:!0});var T=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},w=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of x(o))!y.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=$(o,s))||t.enumerable});return e};var I=e=>w(a({},"__esModule",{value:!0}),e);var E={};T(E,{run:()=>k});module.exports=I(E);var d=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),v=require("../../../hooks/core/tool-use-stash"),l=require("../../../lib/config"),n=require("../../../lib/logger"),C=require("../../../lib/stdin");async function k(e,o){const i=o?.soft===!0;let t;try{t=JSON.parse((0,C.readStdin)())}catch(c){n.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const s=t.session_id??"default";(0,n.setLogFile)(`${e}/.ironbee/sessions/${s}/session.log`);const u=`${e}/.ironbee/sessions/${s}`,f=`${u}/actions.jsonl`;!i&&(0,b.hasToolCallsSinceLastVerdict)(f)&&(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.
|
|
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 c=(0,l.loadConfig)(e),p=(0,l.getCaptureFileChangeset)(c),g=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&p){const m={file_existed:g};if(p&&g)try{m.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(S){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${S}`)}(0,
|
|
7
|
+
`),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const c=(0,l.loadConfig)(e),p=(0,l.getCaptureFileChangeset)(c),g=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&p){const m={file_existed:g};if(p&&g)try{m.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(S){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${S}`)}(0,v.stashToolUseData)(s,t.tool_use_id,m)}}await(0,h.startActivity)({sessionDir:u,actionsFile:f,source:"pre_tool_use"}),process.exit(0)}_(k,"run");0&&(module.exports={run});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var R=(o,t)=>u(o,"name",{value:t,configurable:!0});var L=(o,t)=>{for(var n in t)u(o,n,{get:t[n],enumerable:!0})},j=(o,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of F(t))!K.call(o,e)&&e!==n&&u(o,e,{get:()=>t[e],enumerable:!(s=V(t,e))||s.enumerable});return o};var B=o=>j(u({},"__esModule",{value:!0}),o);var q={};L(q,{run:()=>M});module.exports=B(q);var T=require("crypto"),i=require("../../../hooks/core/session-state"),k=require("../../../hooks/core/actions"),$=require("../../../hooks/core/activity"),E=require("../../../hooks/core/verification-lifecycle"),O=require("../../../hooks/core/verification-context"),U=require("../../../lib/config"),x=require("../../../lib/recording-tools"),f=require("../../../lib/logger"),v=require("../util"),A=require("../../../lib/stdin");const D="browser-devtools";async function M(o,t){const n=t?.soft===!0;let s;try{s=JSON.parse((0,A.readStdin)())}catch(w){f.logger.debug(`failed to parse stdin: ${w}`),process.exit(0)}const e=s.session_id??"default",r=`${o}/.ironbee/sessions/${e}`;(0,f.setLogFile)(`${r}/session.log`);const _=`${r}/actions.jsonl`,h=(0,i.getActiveVerificationId)(r);!h&&!n&&(process.stderr.write(`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-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.
|
|
7
|
-
`),process.exit(2));const
|
|
6
|
+
Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend, adt_* for android.
|
|
7
|
+
`),process.exit(2));const b=s.tool_name??"",c=(0,x.recordingToolsForServer)((0,v.extractMcpServerName)(b));!n&&c!==null&&(0,i.isRecordingRequired)(r)&&!(0,i.isRecordingActive)(r)&&!b.endsWith(c.startTool)&&(process.stderr.write(`BLOCKED: Recording is required but not started.
|
|
8
8
|
|
|
9
9
|
1. Start recording NOW:
|
|
10
|
-
Use
|
|
10
|
+
Use mcp__${c.server}__${c.startTool}
|
|
11
11
|
|
|
12
|
-
2. Run the verification
|
|
12
|
+
2. Run the verification flow for the active cycle(s)
|
|
13
13
|
|
|
14
14
|
3. **Stop recording BEFORE submitting verdict:**
|
|
15
|
-
Use
|
|
15
|
+
Use mcp__${c.server}__${c.stopTool}
|
|
16
16
|
submit-verdict will reject with "recording is still active" if you skip this.
|
|
17
|
-
`),process.exit(2)),await(0
|
|
17
|
+
`),process.exit(2)),await(0,$.startActivity)({sessionDir:r,actionsFile:_,source:"pre_tool_use"});let d=h;n&&!d&&(d=(await(0,E.startVerification)({sessionId:e,sessionDir:r,actionsFile:_,recordingEnabled:!1})).verificationId);const N=(0,i.getActiveTraceId)(r),p=(0,i.getActiveActivityId)(r),C=(0,k.resolveProjectName)(o),g=[`prj:${C}`,`sid:${e}`];p&&g.push(`aid:${p}`),d&&g.push(`vid:${d}`);const P=`ironbee=${g.join(";")}`,l=(0,U.loadConfig)(o),I={...s.tool_input??{}},a={projectName:C,sessionId:e,activityId:p,verificationId:d,traceId:N,traceState:P,toolCallId:(0,T.randomUUID)()};s.tool_use_id&&(a.toolUseId=s.tool_use_id),a.mcpServer=(0,v.extractMcpServerName)(s.tool_name)??D;const y=(0,i.getUserEmail)(r);y&&(a.userEmail=y),l.collector?.url&&(a.collectorUrl=l.collector.url),l.collector?.apiKey&&(a.collectorApiKey=l.collector.apiKey),I._metadata=a;const m={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:I}},S=(0,O.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:e,sessionDir:r,activeVerificationId:d,config:l});S.length>0&&m.hookSpecificOutput&&(m.hookSpecificOutput.additionalContext=S),process.stdout.write(JSON.stringify(m)),process.exit(0)}R(M,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var v=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var d=(t,o)=>v(t,"name",{value:o,configurable:!0});var z=(t,o)=>{for(var i in o)v(t,i,{get:o[i],enumerable:!0})},M=(t,o,i,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of J(o))!U.call(t,n)&&n!==i&&v(t,n,{get:()=>o[n],enumerable:!(e=B(o,n))||e.enumerable});return t};var W=t=>M(v({},"__esModule",{value:!0}),t);var eo={};z(eo,{run:()=>oo});module.exports=W(eo);var f=require("../../../hooks/core/actions"),c=require("../../../hooks/core/session-state"),$=require("../../../import/ids"),h=require("../../../lib/config"),s=require("../../../lib/logger"),I=require("../../../lib/recording-tools"),L=require("../../../lib/stdin"),g=require("../../../queue"),y=require("../util");const k=/callTool\(\s*['"]([^'"]+)['"]/g,A="mcp__browser-devtools__",X="bdt_",K="browser-devtools",Q="node-devtools",Y="backend-devtools",j="android-devtools",q=`${X}execute`;function G(t){return t.startsWith(A)?t:`${A}${t}`}d(G,"toFullName");function H(t,o){if(t[o]!=="{")return;let i=0;for(let e=o;e<t.length;e++)if(t[e]==="{")i++;else if(t[e]==="}"&&(i--,i===0))return t.slice(o,e+1)}d(H,"extractBalancedBraces");function Z(t){const o=typeof t=="string"?t:JSON.stringify(t??""),i=[],e=new Set;let n=k.exec(o);for(;n!==null;){const a=G(n[1]);if(!e.has(a)){e.add(a);let u;const p=n.index+n[0].length,m=o.slice(p).trimStart();if(m.startsWith(",")){const T=m.slice(1).trimStart();if(T.startsWith("{")){const _=H(T,0);if(_)try{u=JSON.parse(_)}catch{u=_}}}i.push({name:a,args:u})}n=k.exec(o)}return i}d(Z,"extractNestedToolCalls");async function oo(t){let o;try{o=JSON.parse((0,L.readStdin)())}catch(l){s.logger.debug(`failed to parse stdin: ${l}`),process.exit(0)}const i=o.session_id??"default",e=`${t}/.ironbee/sessions/${i}`,n=`${e}/actions.jsonl`;(0,s.setLogFile)(`${e}/session.log`);const a=o.tool_name??"unknown",u=Date.now(),p=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,m=(0,c.getActiveActivityId)(e),T=(0,c.getActiveVerificationId)(e),_=(0,c.getActiveTraceId)(e),r=(0,y.classifyTool)(a,o.tool_input),S=r.tool_type==="mcp"&&r.mcp_server===K,x=r.tool_type==="mcp"&&r.mcp_server===Q,D=r.tool_type==="mcp"&&r.mcp_server===Y,V=r.tool_type==="mcp"&&r.mcp_server===j,O=S||x||D||V,F=O?p:(0,y.extractClaudeToolInput)(a,p),w=typeof o.error=="string"&&o.error.length>0?o.error:void 0,E=w?o.is_interrupt?`interrupted: ${w}`:w:void 0,C={...(0,f.baseFields)(n),type:"tool_call",timestamp:u,tool_name:r.tool_name,tool_type:r.tool_type,tool_use_id:o.tool_use_id,tool_input:F,tool_input_size:N(p),tool_response:E?void 0:o.tool_response,tool_response_size:N(E?void 0:o.tool_response),activity_id:m,verification_id:T,trace_id:_,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:r.mcp_server,error:E};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(C.id=(0,$.deriveToolCallEventIdFromToolUseId)(i,o.tool_use_id)),O){await(0,f.appendAction)(n,C);const l=(0,I.recordingToolsForServer)(r.mcp_server);l!==null&&(r.tool_name===l.startTool?((0,c.setRecordingActive)(e,!0),s.logger.debug(`track-action: recording started (${l.cycle})`)):r.tool_name===l.stopTool&&((0,c.setRecordingActive)(e,!1),s.logger.debug(`track-action: recording stopped (${l.cycle})`)))}else to(t,i,C);if(s.logger.debug(`track-action: ${a}${E?" (failed)":""}`),S&&r.tool_name===q&&!E){const l=Z(o.tool_input);for(const b of l){const R=(0,y.classifyTool)(b.name,b.args),P={...(0,f.baseFields)(n),type:"tool_call",timestamp:u,tool_name:R.tool_name,tool_type:R.tool_type,tool_input:b.args,activity_id:m,verification_id:T,trace_id:_,duration:null,mcp_server:R.mcp_server};await(0,f.appendAction)(n,P),s.logger.debug(`track-action (nested): ${b.name}`)}}process.exit(0)}d(oo,"run");function to(t,o,i){if(!(0,h.isJobQueueEnabled)(t))return;const e={...i};delete e.tool_response;try{(0,g.submit)(t,o,g.SEND_EVENT_TYPE,e)}catch(n){if(n instanceof g.JobTooLargeError){s.logger.debug(`track-action: wire event too large for ${i.tool_name}; dropping`);return}s.logger.debug(`track-action: failed to submit ${i.tool_name}: ${n instanceof Error?n.message:n}`)}}d(to,"submitEvent");function N(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}}d(N,"byteSize");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}
|
|
1
|
+
"use strict";var ee=Object.create;var _=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var re=Object.getPrototypeOf,ie=Object.prototype.hasOwnProperty;var v=(t,e)=>_(t,"name",{value:e,configurable:!0});var se=(t,e)=>{for(var n in e)_(t,n,{get:e[n],enumerable:!0})},C=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of oe(e))!ie.call(t,r)&&r!==n&&_(t,r,{get:()=>e[r],enumerable:!(o=ne(e,r))||o.enumerable});return t};var te=(t,e,n)=>(n=t!=null?ee(re(t)):{},C(e||!t||!t.__esModule?_(n,"default",{value:t,enumerable:!0}):n,t)),ce=t=>C(_({},"__esModule",{value:!0}),t);var he={};se(he,{ClaudeClient:()=>ye,prepareIronBeeDir:()=>Se});module.exports=ce(he);var s=require("fs"),l=require("path"),k=require("../../lib/logger"),a=require("../../lib/output"),M=require("../../lib/gitignore"),P=require("../../lib/fs-prune"),N=require("./hooks/verify-gate"),I=require("./hooks/clear-verdict"),B=require("./hooks/track-action"),H=require("./hooks/track-action-monitor"),U=require("./hooks/session-start"),J=require("./hooks/require-verdict"),V=require("./hooks/require-verification"),j=require("./hooks/activity-start"),D=require("./hooks/activity-end"),F=require("./hooks/session-end"),u=require("../../lib/config"),X=require("../../hooks/core/actions"),x=require("../../lib/platform-section"),y=require("../../lib/install-snapshots"),$=require("./hooks/session-status");const S="browser-devtools",h="node-devtools",b="backend-devtools",E="android-devtools",ae="ironbee",le="ironbee hook session-status";function ue(t){return(0,l.join)(__dirname,"..",t,"platforms")}v(ue,"platformsDirFor");function w(t,e,n){return e?(t.includes(n)||t.push(n),t):t.filter(o=>o!==n)}v(w,"syncCyclePermission");function L(t){const e=Object.keys(t);if(e.length===0)return!0;if(e.length===1&&e[0]==="mcpServers"){const n=t.mcpServers;return n===void 0||Object.keys(n).length===0}return!1}v(L,"isMcpConfigEmpty");function de(t,e){const n=[` - ${t}:`];!("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[c,m]of i)n.push(` ${c}: ${JSON.stringify(m)}`)}}else n.push(` ${o}: ${JSON.stringify(r)}`);return n}v(de,"renderInlineMcpServerYaml");function me(t,e){const n=[];if((0,u.isCycleEnabled)(e,"browser")&&n.push({key:S,entry:(0,u.getMcpServerEntry)(t)}),(0,u.isCycleEnabled)(e,"node")&&n.push({key:h,entry:(0,u.getNodeDevToolsMcpEntry)(t)}),(0,u.isCycleEnabled)(e,"backend")&&n.push({key:b,entry:(0,u.getBackendDevToolsMcpEntry)(t)}),(0,u.isCycleEnabled)(e,"android")&&n.push({key:E,entry:(0,u.getAndroidDevToolsMcpEntry)(t)}),n.length===0)return"";const o=["mcpServers:"];for(const{key:r,entry:i}of n)o.push(...de(r,i));return o.join(`
|
|
2
|
+
`)}v(me,"buildVerifierMcpServersBlock");function fe(t,e){if(e.length===0)return t;const n=t.split(`
|
|
3
3
|
`);if(n[0]!=="---")return t;let o=-1;for(let c=1;c<n.length;c++)if(n[c]==="---"){o=c;break}if(o<0)return t;const r=n.slice(0,o),i=n.slice(o);return[...r,...e.split(`
|
|
4
4
|
`),...i].join(`
|
|
5
|
-
`)}
|
|
5
|
+
`)}v(fe,"injectVerifierMcpServers");function ge(t,e){if(!e)return t;const n=t.split(`
|
|
6
6
|
`);if(n[0]!=="---")return t;let o=-1;for(let c=1;c<n.length;c++)if(n[c]==="---"){o=c;break}if(o<0)return t;const r=n.slice(0,o);if(r.some(c=>/^model\s*:/.test(c)))return t;const i=n.slice(o);return[...r,`model: ${e}`,...i].join(`
|
|
7
|
-
`)}k(fe,"injectVerifierModel");function ge(t){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(t))if(!e.has(n))return!1;if(t.hooks!==void 0&&Object.keys(t.hooks).length>0)return!1;if(t.permissions!==void 0){const n=t.permissions.allow??[],o=t.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}k(ge,"isClaudeSettingsEmpty");const pe=["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 C(t){const e=t.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}k(C,"otelEnvOwnedByUs");function ke(t){return t.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}k(ke,"sanitizeResourceValue");class ve{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{k(this,"ClaudeClient")}detect(e){return(0,s.existsSync)((0,l.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(()=>se(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,d.loadConfig)(e),r=(0,d.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const c=(0,l.join)(e,".claude"),f=(0,l.join)(c,"skills"),m=(0,l.join)(c,"rules"),g=(0,l.join)(c,"commands");(0,s.mkdirSync)(f,{recursive:!0}),(0,s.mkdirSync)(m,{recursive:!0}),(0,s.mkdirSync)(g,{recursive:!0});const u=(0,l.join)(c,"settings.json");if(this.mergeHooksConfig(u,r),this.writePermissions(u,i,e),(0,d.isOTELEnabled)(o)&&this.writeOTELEnv(u,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const K=(0,l.join)(f,"ironbee-verification.md"),W=(0,s.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(K,W);const Y=(0,l.join)(m,"ironbee-verification.md"),Q=(0,s.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(Y,Q)}const E=(0,l.join)(g,"ironbee-verify.md"),X=(0,s.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,s.writeFileSync)(E,X);const O=(0,l.join)(c,"agents");(0,s.mkdirSync)(O,{recursive:!0});const x=(0,l.join)(O,"ironbee-verifier.md"),G=(0,s.readFileSync)((0,l.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),z=me(G,de(e,o)),q=fe(z,(0,d.getVerificationModel)(o,"claude"));(0,s.writeFileSync)(x,q);const $=(0,l.join)(e,".mcp.json");this.writeMcpConfig($,e),(0,F.syncPlatformSectionsToConfig)(e,le),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(u)}`),r==="enforce"?(console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} skills ${a.pc.dim("\u2192")} ${a.pc.dim(f)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} rule ${a.pc.dim("\u2192")} ${a.pc.dim(m)}`)):console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} commands ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} agents ${a.pc.dim("\u2192")} ${a.pc.dim((0,l.join)(c,"agents"))}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} mcp ${a.pc.dim("\u2192")} ${a.pc.dim($)}`)}else console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(u)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,l.join)(e,".claude"),o=(0,l.join)(n,"skills","ironbee-verification.md"),r=(0,l.join)(n,"skills","ironbee-analyze.md"),i=(0,l.join)(n,"rules","ironbee-verification.md"),c=(0,l.join)(n,"commands","ironbee-analyze.md"),f=(0,l.join)(n,"commands","ironbee-verify.md"),m=(0,l.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(c),this.removeFile(f),this.removeFile(m);const g=(0,l.join)(n,"settings.json");this.removeIronBeeHooks(g),this.removePermission(g),this.removeOTELEnv(g),this.maybeDeleteEmptySettings(g);const u=(0,l.join)(e,".mcp.json");this.removeMcpServer(u),this.uninstallStatusLine(e),(0,A.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,d.isSessionStatusEnabled)(n))return;const o=(0,l.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,w.isIronbeeStatusLine)(r.command)&&(0,v.readStatusLineSnapshot)(e,"claude")===void 0&&(0,v.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:ae},c=(0,d.getStatusLineRefreshInterval)(n);c!==void 0&&(i.refreshInterval=c),this.writeStatusLineBlock(o,i),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} statusline ${a.pc.dim("\u2192")} ${a.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,l.join)(e,".claude","settings.local.json"),o=(0,v.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,v.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,w.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,c=o.refreshInterval,f={type:"command",command:r};return typeof i=="number"&&(f.padding=i),typeof c=="number"&&(f.refreshInterval=c),f}catch(n){p.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,s.existsSync)(e))try{const r=JSON.parse((0,s.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){p.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});o.statusLine=n,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,s.unlinkSync)(e):(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeOTELEnv(e,n,o){let r={};if((0,s.existsSync)(e))try{const u=JSON.parse((0,s.readFileSync)(e,"utf-8"));u!==null&&typeof u=="object"&&!Array.isArray(u)&&(r=u)}catch(u){p.logger.debug(`failed to read ${e} for otel env write: ${u}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});const i=r.env,c=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},f=c.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof f=="string"&&f.length>0&&!C(c)){console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const m=(0,d.getOTELPort)(o),g=ke((0,D.resolveProjectName)(n));c.CLAUDE_CODE_ENABLE_TELEMETRY="1",c.OTEL_LOGS_EXPORTER="otlp",c.OTEL_METRICS_EXPORTER="none",c.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",c.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${m}`,c.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",c.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${g}`,c.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=c,(0,s.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} otel env ${a.pc.dim("\u2192")} ${a.pc.dim(`${e} (127.0.0.1:${m})`)}`)}removeOTELEnv(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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(!C(i))return;for(const c of pe)delete i[c];Object.keys(i).length===0&&delete o.env,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));ge(n)&&(0,s.unlinkSync)(e)}catch(n){p.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,P.run)(e)}async runClearVerdict(e){await(0,M.run)(e)}async runTrackAction(e){await(0,N.run)(e)}async runSessionStart(e){await(0,B.run)(e)}async runRequireVerdict(e,n){await(0,H.run)(e,n)}async runRequireVerification(e,n){await(0,U.run)(e,n)}async runActivityStart(e){await(0,j.run)(e)}async runActivityEnd(e){await(0,J.run)(e)}async runTrackActionMonitor(e){await(0,I.run)(e)}async runSessionEnd(e){await(0,V.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(ce))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,s.existsSync)(e))try{i=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${e}: ${m}`),i={}}i.hooks||(i.hooks={});for(const m of Object.keys(i.hooks)){const g=i.hooks[m].filter(u=>!this.isIronBeeHook(u));g.length===0?delete i.hooks[m]:i.hooks[m]=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__.*",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 c=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.Stop||(i.hooks.Stop=[]);const f=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:f}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,s.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[y]&&(delete n.mcpServers[y],o=!0),n.mcpServers&&n.mcpServers[S]&&(delete n.mcpServers[S],o=!0),n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),R(n)?(0,s.unlinkSync)(e):o&&(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8")),o=`mcp__${y}__*`,r=`mcp__${S}__*`,i=`mcp__${h}__*`,c="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==c&&m!==f),(0,s.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){p.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,s.existsSync)(e)&&(0,s.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,s.existsSync)(e))try{o=JSON.parse((0,s.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[y],delete o.mcpServers[S],delete o.mcpServers[h],R(o)){try{(0,s.rmSync)(e,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,s.existsSync)(e))try{r=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(u){p.logger.debug(`failed to parse ${e}: ${u}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${y}__*`,c=`mcp__${S}__*`,f=`mcp__${h}__*`,m="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const u=(0,d.loadConfig)(o);r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"browser"),i),r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"node"),c),r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"backend"),f),r.permissions.allow=r.permissions.allow.filter(E=>E!==g),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(u=>u!==i&&u!==c&&u!==f&&u!==m&&u!==g);(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}}function ye(t){(0,s.mkdirSync)((0,l.join)(t,".ironbee"),{recursive:!0}),(0,L.ensureIronBeeGitignored)(t)}k(ye,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
7
|
+
`)}v(ge,"injectVerifierModel");function pe(t){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(t))if(!e.has(n))return!1;if(t.hooks!==void 0&&Object.keys(t.hooks).length>0)return!1;if(t.permissions!==void 0){const n=t.permissions.allow??[],o=t.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v(pe,"isClaudeSettingsEmpty");const ke=["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 A(t){const e=t.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(A,"otelEnvOwnedByUs");function ve(t){return t.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(ve,"sanitizeResourceValue");class ye{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,s.existsSync)((0,l.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(()=>te(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 c=(0,l.join)(e,".claude"),m=(0,l.join)(c,"skills"),f=(0,l.join)(c,"rules"),d=(0,l.join)(c,"commands");(0,s.mkdirSync)(m,{recursive:!0}),(0,s.mkdirSync)(f,{recursive:!0}),(0,s.mkdirSync)(d,{recursive:!0});const g=(0,l.join)(c,"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 W=(0,l.join)(m,"ironbee-verification.md"),Y=(0,s.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(W,Y);const Q=(0,l.join)(f,"ironbee-verification.md"),Z=(0,s.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(Q,Z)}const p=(0,l.join)(d,"ironbee-verify.md"),O=(0,s.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,s.writeFileSync)(p,O);const T=(0,l.join)(c,"agents");(0,s.mkdirSync)(T,{recursive:!0});const G=(0,l.join)(T,"ironbee-verifier.md"),z=(0,s.readFileSync)((0,l.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),q=fe(z,me(e,o)),K=ge(q,(0,u.getVerificationModel)(o,"claude"));(0,s.writeFileSync)(G,K);const R=(0,l.join)(e,".mcp.json");this.writeMcpConfig(R,e),(0,x.syncPlatformSectionsToConfig)(e,ue),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`),r==="enforce"?(console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} skills ${a.pc.dim("\u2192")} ${a.pc.dim(m)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} rule ${a.pc.dim("\u2192")} ${a.pc.dim(f)}`)):console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} commands ${a.pc.dim("\u2192")} ${a.pc.dim(d)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} agents ${a.pc.dim("\u2192")} ${a.pc.dim((0,l.join)(c,"agents"))}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} mcp ${a.pc.dim("\u2192")} ${a.pc.dim(R)}`)}else console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,l.join)(e,".claude"),o=(0,l.join)(n,"skills","ironbee-verification.md"),r=(0,l.join)(n,"skills","ironbee-analyze.md"),i=(0,l.join)(n,"rules","ironbee-verification.md"),c=(0,l.join)(n,"commands","ironbee-analyze.md"),m=(0,l.join)(n,"commands","ironbee-verify.md"),f=(0,l.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(c),this.removeFile(m),this.removeFile(f);const d=(0,l.join)(n,"settings.json");this.removeIronBeeHooks(d),this.removePermission(d),this.removeOTELEnv(d),this.maybeDeleteEmptySettings(d);const g=(0,l.join)(e,".mcp.json");this.removeMcpServer(g),this.uninstallStatusLine(e),(0,P.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,l.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,$.isIronbeeStatusLine)(r.command)&&(0,y.readStatusLineSnapshot)(e,"claude")===void 0&&(0,y.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:le},c=(0,u.getStatusLineRefreshInterval)(n);c!==void 0&&(i.refreshInterval=c),this.writeStatusLineBlock(o,i),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} statusline ${a.pc.dim("\u2192")} ${a.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,l.join)(e,".claude","settings.local.json"),o=(0,y.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,y.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,$.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,c=o.refreshInterval,m={type:"command",command:r};return typeof i=="number"&&(m.padding=i),typeof c=="number"&&(m.refreshInterval=c),m}catch(n){k.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,s.existsSync)(e))try{const r=JSON.parse((0,s.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,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});o.statusLine=n,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,s.unlinkSync)(e):(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeOTELEnv(e,n,o){let r={};if((0,s.existsSync)(e))try{const g=JSON.parse((0,s.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,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});const i=r.env,c=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},m=c.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof m=="string"&&m.length>0&&!A(c)){console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),d=ve((0,X.resolveProjectName)(n));c.CLAUDE_CODE_ENABLE_TELEMETRY="1",c.OTEL_LOGS_EXPORTER="otlp",c.OTEL_METRICS_EXPORTER="none",c.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",c.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,c.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",c.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${d}`,c.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=c,(0,s.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} otel env ${a.pc.dim("\u2192")} ${a.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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(!A(i))return;for(const c of ke)delete i[c];Object.keys(i).length===0&&delete o.env,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));pe(n)&&(0,s.unlinkSync)(e)}catch(n){k.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,N.run)(e)}async runClearVerdict(e){await(0,I.run)(e)}async runTrackAction(e){await(0,B.run)(e)}async runSessionStart(e){await(0,U.run)(e)}async runRequireVerdict(e,n){await(0,J.run)(e,n)}async runRequireVerification(e,n){await(0,V.run)(e,n)}async runActivityStart(e){await(0,j.run)(e)}async runActivityEnd(e){await(0,D.run)(e)}async runTrackActionMonitor(e){await(0,H.run)(e)}async runSessionEnd(e){await(0,F.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(ae))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,s.existsSync)(e))try{i=JSON.parse((0,s.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 d=i.hooks[f].filter(g=>!this.isIronBeeHook(g));d.length===0?delete i.hooks[f]:i.hooks[f]=d}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 c=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.Stop||(i.hooks.Stop=[]);const m=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:m}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,s.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.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,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[S]&&(delete n.mcpServers[S],o=!0),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),L(n)?(0,s.unlinkSync)(e):o&&(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8")),o=`mcp__${S}__*`,r=`mcp__${h}__*`,i=`mcp__${b}__*`,c=`mcp__${E}__*`,m="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(d=>d!==o&&d!==r&&d!==i&&d!==c&&d!==m&&d!==f),(0,s.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){k.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,s.existsSync)(e)&&(0,s.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,s.existsSync)(e))try{o=JSON.parse((0,s.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){k.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[S],delete o.mcpServers[h],delete o.mcpServers[b],delete o.mcpServers[E],L(o)){try{(0,s.rmSync)(e,{force:!0})}catch(r){k.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,s.existsSync)(e))try{r=JSON.parse((0,s.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__${S}__*`,c=`mcp__${h}__*`,m=`mcp__${b}__*`,f=`mcp__${E}__*`,d="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const p=(0,u.loadConfig)(o);r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"browser"),i),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"node"),c),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"backend"),m),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"android"),f),r.permissions.allow=r.permissions.allow.filter(O=>O!==g),r.permissions.allow.includes(d)||r.permissions.allow.push(d)}else r.permissions.allow=r.permissions.allow.filter(p=>p!==i&&p!==c&&p!==m&&p!==f&&p!==d&&p!==g);(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}}function Se(t){(0,s.mkdirSync)((0,l.join)(t,".ironbee"),{recursive:!0}),(0,M.ensureIronBeeGitignored)(t)}v(Se,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!-- Android mobile verification is ENABLED for this project. The Stop hook
|
|
2
|
+
enforces an android cycle whenever an edited file matches
|
|
3
|
+
`android.verifyPatterns`. -->
|
|
4
|
+
|
|
5
|
+
## ⚠️ CRITICAL: when NOT to use android-devtools
|
|
6
|
+
|
|
7
|
+
**`android-devtools` is ONLY for Android apps** (native Kotlin/Java + React Native / Expo with an Android target). Do **NOT** call `adt_*` tools for projects that do not have an Android target.
|
|
8
|
+
|
|
9
|
+
**How to tell whether the project targets Android:**
|
|
10
|
+
- `android/` directory at the project root (React Native, Expo)
|
|
11
|
+
- `app/build.gradle` or `build.gradle.kts` with `com.android.application`
|
|
12
|
+
- `AndroidManifest.xml` present under `android/` or `app/src/main/`
|
|
13
|
+
|
|
14
|
+
If you see only `ios/`, `web/`, or no mobile directories — the project does NOT target Android. Do NOT call any `adt_*` tools.
|
|
15
|
+
|
|
16
|
+
**Misconfiguration recovery.** If this cycle activated but the project isn't an Android project, the operator enabled the android cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` until `maxRetries` is exhausted. Don't attempt a device connection. Stop and tell the user clearly: the project does not target Android; ask them to run `ironbee android disable` to unblock the gate.
|
|
17
|
+
|
|
18
|
+
## Android flow
|
|
19
|
+
|
|
20
|
+
> **Recording (only when `recording.enable` is on in config):** the gate blocks every other android tool until you first call `mcp__android-devtools__adt_content_start-recording`, and `submit-verdict` rejects with `"recording is still active"` unless you call `mcp__android-devtools__adt_content_stop-recording` after the steps below. **Treat start/stop as bookends around steps 1-3.** The recording ships to the collector as verification evidence.
|
|
21
|
+
|
|
22
|
+
1. **Connect to a running device or emulator**: `mcp__android-devtools__adt_device_connect` (list available targets first with `mcp__android-devtools__adt_device_list-targets` if needed).
|
|
23
|
+
- For an emulator, the device is usually `emulator-5554`.
|
|
24
|
+
- For a physical device with USB debugging on, it will appear in the target list.
|
|
25
|
+
2. **Ensure the app is running** on the target device: `mcp__android-devtools__adt_device_launch-app` with the package name.
|
|
26
|
+
3. **Pick an evidence path** per changed code area:
|
|
27
|
+
- **Device-evidence path** (UI interaction confirms the change is live):
|
|
28
|
+
- Drive the app UI to exercise the changed code: `mcp__android-devtools__adt_interaction_tap`, `mcp__android-devtools__adt_interaction_input-text`, `mcp__android-devtools__adt_interaction_swipe`, `mcp__android-devtools__adt_interaction_scroll` as needed (use `mcp__android-devtools__adt_a11y_find-element` to locate elements).
|
|
29
|
+
- Take a screenshot: `mcp__android-devtools__adt_content_take-screenshot` — then STOP and visually analyze it (readability, layout, cut-off content, expected state rendered). The screenshot tells you what the user actually sees.
|
|
30
|
+
- Take a UI snapshot: `mcp__android-devtools__adt_a11y_take-ui-snapshot` — verify the view hierarchy / labels / structure match the change.
|
|
31
|
+
- Screenshot AND UI snapshot are BOTH MANDATORY on this path (the Stop hook checks each) — visual + structural evidence, like the browser cycle's screenshot + aria-snapshot pair.
|
|
32
|
+
- **Log-evidence path** (device logs confirm the changed code path executed):
|
|
33
|
+
- Read Logcat output for the tag(s) relevant to the changed code: `mcp__android-devtools__adt_o11y_log-read` or `mcp__android-devtools__adt_o11y_log-follow` (drain a follow with `mcp__android-devtools__adt_o11y_log-get-followed`, stop it with `mcp__android-devtools__adt_o11y_log-stop-follow`).
|
|
34
|
+
- Confirm expected log lines appear AND no unexpected crashes (FATAL / E/ entries for the app package).
|
|
35
|
+
|
|
36
|
+
### Verdict fields
|
|
37
|
+
The verdict is platform-agnostic — submit only semantic judgment:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"session_id": "<sid>",
|
|
42
|
+
"status": "pass",
|
|
43
|
+
"checks": ["LoginActivity renders correctly after auth change", "no crash in Logcat"]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
On fail, include `issues`. On pass after a previous fail, include `fixes`.
|
|
48
|
+
|
|
49
|
+
Android-cycle pass criteria:
|
|
50
|
+
- **Device-evidence**: at least one UI interaction tool fired AND a screenshot was taken AND a UI snapshot was taken AND both show the expected UI state/structure.
|
|
51
|
+
- **Log-evidence**: Logcat was read AND the expected log lines are present AND no crash (FATAL / unhandled exception) from the app's package.
|
|
52
|
+
|
|
53
|
+
## Multi-cycle (browser + android simultaneously)
|
|
54
|
+
|
|
55
|
+
When you edit both a web frontend file (browser-cycle) and an Android source file (android-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:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"session_id": "<sid>",
|
|
60
|
+
"status": "pass",
|
|
61
|
+
"checks": ["web checkout form renders", "Android app shows order confirmation screen"]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For a multi-cycle `pass`, ALL active cycles' pass criteria must hold.
|
|
@@ -34,6 +34,12 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
34
34
|
```
|
|
35
35
|
echo '{}' | ironbee hook verification-start
|
|
36
36
|
```
|
|
37
|
+
**If the delegating prompt contains a `Mode: fix` line**, pass the intent
|
|
38
|
+
along so IronBee's completion gate enforces fix-until-pass on the main agent:
|
|
39
|
+
```
|
|
40
|
+
echo '{}' | ironbee hook verification-start --intent fix
|
|
41
|
+
```
|
|
42
|
+
(No declared mode → plain form as above, no flag.)
|
|
37
43
|
2. Build and start the application **only if it isn't already running** (check
|
|
38
44
|
`docker compose ps` / process output / config — don't guess ports). **Track whether YOU
|
|
39
45
|
started it**: if it was already up, the user or main agent owns it — leave it alone.
|
|
@@ -43,7 +49,8 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
43
49
|
4. **Teardown — shut down ONLY what you started, every run.** If in step 2 YOU started the
|
|
44
50
|
app / dev server *for this verification*, stop it now before you return. **Never stop a
|
|
45
51
|
server that was already running** (user/main-agent-owned). Honor any cycle-specific
|
|
46
|
-
teardown (e.g.
|
|
52
|
+
teardown noted in the platform sections (e.g. stopping an active screen recording)
|
|
53
|
+
BEFORE submitting your verdict.
|
|
47
54
|
5. **Submit your verdict immediately** — do NOT wait:
|
|
48
55
|
```
|
|
49
56
|
echo '<verdict-json>' | ironbee hook submit-verdict
|
|
@@ -65,3 +72,6 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
65
72
|
|
|
66
73
|
<!--IRONBEE:PLATFORM:backend-->
|
|
67
74
|
<!--/IRONBEE:PLATFORM:backend-->
|
|
75
|
+
|
|
76
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
77
|
+
<!--/IRONBEE:PLATFORM:android-->
|
|
@@ -5,17 +5,29 @@ description: >
|
|
|
5
5
|
Use when the user types `$ironbee-verify` to explicitly run the verify flow,
|
|
6
6
|
or when you need to verify ad-hoc outside the normal post-edit gate. Runs the
|
|
7
7
|
full multi-cycle verification (browser/node/backend as configured) and
|
|
8
|
-
submits a verdict.
|
|
9
|
-
|
|
8
|
+
submits a verdict. Default is verify-only (report the verdict and stop); a
|
|
9
|
+
leading `fix` argument adds the fix-and-re-verify loop until pass. A custom
|
|
10
|
+
scenario may ride along with the invocation — inline text or a path to a
|
|
11
|
+
scenario file — defining exactly what to verify.
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
# IronBee Verify
|
|
13
15
|
|
|
14
16
|
Verify the current code changes through real tools. The gate runs every cycle that has been wired up for this project, and all active cycles must be satisfied within a single verification cycle for `status: pass`. Each cycle has its own tools and flow — **see the platform sections near the bottom of this file** for which cycles apply and what to call. The verdict shape itself is platform-agnostic (`status`, `checks`, `issues?`, `fixes?`); the gate enforces that you called each cycle's required tools and that `checks` is non-empty.
|
|
15
17
|
|
|
18
|
+
## Mode
|
|
19
|
+
|
|
20
|
+
The FIRST whitespace-delimited token of whatever the user provided alongside `$ironbee-verify` selects the mode; everything after it is the scenario:
|
|
21
|
+
|
|
22
|
+
- `fix` → **verify-and-fix**: on a fail verdict, fix the reported issues, rebuild, and re-verify until the verdict passes.
|
|
23
|
+
- `report` → **verify-only** (the explicit form of the default).
|
|
24
|
+
- Anything else, or nothing → **verify-only** (default), and the WHOLE provided text is the scenario.
|
|
25
|
+
|
|
26
|
+
**Verify-only** means: submit the (possibly fail) verdict, report the issues to the user, and STOP — do **not** edit code, do **not** re-verify. The fail verdict is still recorded (that's the point — an honest status report). If the user wants the issues repaired, suggest `$ironbee-verify fix`. The mode token never overrides the gate: if enforcement blocks completion because of this turn's edits, follow the gate.
|
|
27
|
+
|
|
16
28
|
## Verification scenario
|
|
17
29
|
|
|
18
|
-
A custom verification scenario may be supplied when this command is invoked — either as **inline text** or as a **path to a file** (any location, any format; it is read at run time). The scenario is whatever the user provided alongside invoking this command.
|
|
30
|
+
A custom verification scenario may be supplied when this command is invoked — either as **inline text** or as a **path to a file** (any location, any format; it is read at run time). The scenario is whatever the user provided alongside invoking this command, after stripping a leading `fix` / `report` mode token (see **Mode**).
|
|
19
31
|
|
|
20
32
|
- **If a scenario is supplied, it is authoritative**: verify exactly what it describes. Drive each active cycle's tools to exercise precisely the flows, states, and endpoints it names — this **replaces** the default "exercise the changed pages/endpoints" guidance.
|
|
21
33
|
- **If the scenario is (or points to) a file path**, read that file with your file-read tool and treat its contents as the scenario. Do not assume a fixed location or format — read whatever path was given.
|
|
@@ -27,6 +39,8 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
|
|
|
27
39
|
## Universal steps
|
|
28
40
|
|
|
29
41
|
1. **Start verification**: Run `echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start` via Bash (substitute the actual session ID printed by the SessionStart hook).
|
|
42
|
+
**In fix mode**, add the intent flag so IronBee's completion gate enforces fix-until-pass:
|
|
43
|
+
`echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start --intent fix`
|
|
30
44
|
2. **Build and start** the application if not already running.
|
|
31
45
|
3. **For every active cycle, run its flow** — driven by the **Verification scenario** above when one was supplied, otherwise as described in the platform sections near the bottom of this file. All active cycles must be exercised within this same verification cycle.
|
|
32
46
|
4. **Stop** the dev server when verification is complete (every cycle — including the final one).
|
|
@@ -34,7 +48,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
|
|
|
34
48
|
6. **Submit your verdict** via Bash. One verdict covers every active cycle:
|
|
35
49
|
- Pass: `echo '{"session_id":"...","status":"pass","checks":["..."]}' | ironbee hook submit-verdict`
|
|
36
50
|
- Fail: `echo '{"session_id":"...","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict`
|
|
37
|
-
7. **If failed** → collect ALL issues first (finish testing every active cycle)
|
|
51
|
+
7. **If failed** → collect ALL issues first (finish testing every active cycle) and submit ONE fail verdict with all issues. Then branch by mode:
|
|
52
|
+
- **Verify-only (default)**: report the issues to the user and stop — do not edit code. Suggest `$ironbee-verify fix` to repair them.
|
|
53
|
+
- **Fix mode (`fix` token)**: fix everything, rebuild, and re-verify until pass. Do not fix one issue at a time — batch fixes to avoid repeated build/restart cycles.
|
|
38
54
|
8. If pass after a previous fail, include `"fixes"` in the verdict describing what was fixed.
|
|
39
55
|
|
|
40
56
|
---
|
|
@@ -48,6 +64,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
|
|
|
48
64
|
<!--IRONBEE:PLATFORM:backend-->
|
|
49
65
|
<!--/IRONBEE:PLATFORM:backend-->
|
|
50
66
|
|
|
67
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
68
|
+
<!--/IRONBEE:PLATFORM:android-->
|
|
69
|
+
|
|
51
70
|
---
|
|
52
71
|
|
|
53
72
|
## When to FAIL
|
|
@@ -56,7 +75,7 @@ If you observe ANY problem on any active cycle — wrong data, unexpected errors
|
|
|
56
75
|
|
|
57
76
|
**Do NOT rationalize away problems.** If something looks wrong or behaves unexpectedly, it IS wrong.
|
|
58
77
|
|
|
59
|
-
**After a fail verdict, you MUST fix the issues and re-verify
|
|
78
|
+
**After a fail verdict in fix mode, you MUST fix the issues and re-verify** — do not just report and stop. In verify-only mode (the default) the opposite holds: report and stop; fixing without the `fix` token is overstepping.
|
|
60
79
|
|
|
61
80
|
## Verdict Quality
|
|
62
81
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var C=(o,e)=>u(o,"name",{value:e,configurable:!0});var q=(o,e)=>{for(var s in e)u(o,s,{get:e[s],enumerable:!0})},B=(o,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of F(e))!K.call(o,t)&&t!==s&&u(o,t,{get:()=>e[t],enumerable:!(n=j(e,t))||n.enumerable});return o};var J=o=>B(u({},"__esModule",{value:!0}),o);var H={};q(H,{run:()=>L});module.exports=J(H);var T=require("crypto"),$=require("../../../hooks/core/activity"),i=require("../../../hooks/core/session-state"),x=require("../../../hooks/core/actions"),N=require("../../../hooks/core/verification-lifecycle"),E=require("../../../hooks/core/verification-context"),O=require("../../../lib/config"),m=require("../../../lib/logger"),U=require("../../../lib/recording-tools"),A=require("../../../lib/stdin"),l=require("../util");async function L(o,e){const s=e?.soft===!0,n=(0,l.parseCodexHookStdin)((0,A.readStdin)()),t=n.session_id??"default",r=`${o}/.ironbee/sessions/${t}`,y=`${r}/actions.jsonl`;(0,m.setLogFile)(`${r}/session.log`);const b=(0,i.getActiveVerificationId)(r);if(!b&&!s){const p=`BLOCKED: You must start a verification cycle before using devtools tools.
|
|
2
2
|
|
|
3
3
|
Start verification first:
|
|
4
|
-
echo '{"session_id":"${
|
|
4
|
+
echo '{"session_id":"${t}"}' | ironbee hook verification-start
|
|
5
5
|
|
|
6
|
-
Then use the verification tools for the active cycle(s) \u2014 mcp__browser-devtools__bdt_* for browser, mcp__node-devtools__ndt_* for node, mcp__backend-devtools__bedt_* for backend.`;process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:
|
|
6
|
+
Then use the verification tools for the active cycle(s) \u2014 mcp__browser-devtools__bdt_* for browser, mcp__node-devtools__ndt_* for node, mcp__backend-devtools__bedt_* for backend, mcp__android-devtools__adt_* for android.`;process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:p}})),process.exit(0);return}const g=n.tool_name??"",h=(0,l.extractCodexMcpServer)(g),c=(0,U.recordingToolsForServer)(h),D=c!==null?(0,l.canonicalizeCodexToolName)(g.split("__").pop()??""):"";if(!s&&c!==null&&(0,i.isRecordingRequired)(r)&&!(0,i.isRecordingActive)(r)&&D!==c.startTool){const p=`BLOCKED: Recording is required but not started.
|
|
7
7
|
|
|
8
8
|
1. Start recording NOW:
|
|
9
|
-
Use
|
|
9
|
+
Use mcp__${c.server}__${c.startTool}
|
|
10
10
|
|
|
11
|
-
2. Run the verification
|
|
11
|
+
2. Run the verification flow for the active cycle(s)
|
|
12
12
|
|
|
13
13
|
3. **Stop recording BEFORE submitting verdict:**
|
|
14
|
-
Use
|
|
15
|
-
submit-verdict will reject with "recording is still active" if you skip this
|
|
14
|
+
Use mcp__${c.server}__${c.stopTool}
|
|
15
|
+
submit-verdict will reject with "recording is still active" if you skip this.`;process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:p}})),process.exit(0);return}await(0,$.startActivity)({sessionDir:r,actionsFile:y,source:"pre_tool_use"});let a=b;s&&!a&&(a=(await(0,N.startVerification)({sessionId:t,sessionDir:r,actionsFile:y,recordingEnabled:!1})).verificationId);const V=(0,i.getActiveTraceId)(r),_=(0,i.getActiveActivityId)(r),S=(0,x.resolveProjectName)(o),v=[`prj:${S}`,`sid:${t}`];_&&v.push(`aid:${_}`),a&&v.push(`vid:${a}`);const P=`ironbee=${v.join(";")}`,f=(0,O.loadConfig)(o),w={...n.tool_input&&typeof n.tool_input=="object"?n.tool_input:{}},d={projectName:S,sessionId:t,activityId:_,verificationId:a,traceId:V,traceState:P,toolCallId:(0,T.randomUUID)()};n.tool_use_id&&(d.toolUseId=n.tool_use_id),d.mcpServer=h??"browser-devtools";const I=(0,i.getUserEmail)(r);I&&(d.userEmail=I),f.collector?.url&&(d.collectorUrl=f.collector.url),f.collector?.apiKey&&(d.collectorApiKey=f.collector.apiKey),w._metadata=d;const R={hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:w},k=(0,E.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:t,sessionDir:r,activeVerificationId:a,config:f});k.length>0&&(R.additionalContext=k),process.stdout.write(JSON.stringify({hookSpecificOutput:R})),m.logger.debug(`require-verification: allowed ${g} with _metadata`),process.exit(0)}C(L,"run");0&&(module.exports={run});
|