@ironbee-ai/cli 0.27.0 → 0.29.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.29.0 (2026-06-22)
4
+
5
+ ### Features
6
+
7
+ * **verification:** read nested devtools calls from tool_response ([#32](https://github.com/ironbee-ai/ironbee-cli/issues/32)) ([5de693b](https://github.com/ironbee-ai/ironbee-cli/commit/5de693ba11d507dc306ddada46bcc3235f5a9ef0))
8
+
9
+ ## 0.28.0 (2026-06-20)
10
+
11
+ ### Features
12
+
13
+ * **verification:** default to assist mode instead of enforce ([#31](https://github.com/ironbee-ai/ironbee-cli/issues/31)) ([fd284f8](https://github.com/ironbee-ai/ironbee-cli/commit/fd284f88e1feee8efde7b534a943b348fad4a95e))
14
+
3
15
  ## 0.27.0 (2026-06-19)
4
16
 
5
17
  ## 0.26.0 (2026-06-18)
@@ -1 +1 @@
1
- "use strict";var m=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var f=(e,o)=>m(e,"name",{value:o,configurable:!0});var J=(e,o)=>{for(var i in o)m(e,i,{get:o[i],enumerable:!0})},U=(e,o,i,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of P(o))!z.call(e,r)&&r!==i&&m(e,r,{get:()=>o[r],enumerable:!(n=F(o,r))||n.enumerable});return e};var K=e=>U(m({},"__esModule",{value:!0}),e);var G={};J(G,{run:()=>j});module.exports=K(G);var d=require("../../../hooks/core/actions"),g=require("../../../hooks/core/nested-tools"),l=require("../../../hooks/core/session-state"),S=require("../../../import/ids"),O=require("../../../lib/config"),s=require("../../../lib/logger"),w=require("../../../lib/recording-tools"),N=require("../../../lib/stdin"),a=require("../../../queue"),T=require("../util");const M="browser-devtools",Q="node-devtools",W="backend-devtools",Y="android-devtools";async function j(e){let o;try{o=JSON.parse((0,N.readStdin)())}catch(c){s.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const i=o.session_id??"default",n=`${e}/.ironbee/sessions/${i}`,r=`${n}/actions.jsonl`;(0,s.setLogFile)(`${n}/session.log`);const y=o.tool_name??"unknown",R=Date.now(),b=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,C=(0,l.getActiveActivityId)(n),I=(0,l.getActiveVerificationId)(n),$=(0,l.getActiveTraceId)(n),t=(0,T.classifyTool)(y,o.tool_input),D=t.tool_type==="mcp"&&t.mcp_server===M,V=t.tool_type==="mcp"&&t.mcp_server===Q,L=t.tool_type==="mcp"&&t.mcp_server===W,h=t.tool_type==="mcp"&&t.mcp_server===Y,v=D||V||L||h,x=v?b:(0,T.extractClaudeToolInput)(y,b),E=typeof o.error=="string"&&o.error.length>0?o.error:void 0,u=E?o.is_interrupt?`interrupted: ${E}`:E:void 0,k={...(0,d.baseFields)(r),type:"tool_call",timestamp:R,tool_name:t.tool_name,tool_type:t.tool_type,tool_use_id:o.tool_use_id,tool_input:x,tool_input_size:A(b),tool_response:u?void 0:o.tool_response,tool_response_size:A(u?void 0:o.tool_response),activity_id:C,verification_id:I,trace_id:$,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:t.mcp_server,error:u};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(k.id=(0,S.deriveToolCallEventIdFromToolUseId)(i,o.tool_use_id)),v){await(0,d.appendAction)(r,k);const c=(0,w.recordingToolsForServer)(t.mcp_server);c!==null&&(t.tool_name===c.startTool?((0,l.setRecordingActive)(n,!0),s.logger.debug(`track-action: recording started (${c.cycle})`)):t.tool_name===c.stopTool&&((0,l.setRecordingActive)(n,!1),s.logger.debug(`track-action: recording stopped (${c.cycle})`)))}else q(e,i,k);if(s.logger.debug(`track-action: ${y}${u?" (failed)":""}`),v&&t.tool_name===(0,g.executeToolBareName)(t.mcp_server)&&!u){const c=(0,g.extractNestedToolCalls)(o.tool_input,t.mcp_server),_=(0,w.recordingToolsForServer)(t.mcp_server);for(const p of c){_!==null&&(p.name===_.startTool?((0,l.setRecordingActive)(n,!0),s.logger.debug(`track-action (nested): recording started (${_.cycle})`)):p.name===_.stopTool&&((0,l.setRecordingActive)(n,!1),s.logger.debug(`track-action (nested): recording stopped (${_.cycle})`)));const B={...(0,d.baseFields)(r),type:"tool_call",timestamp:R,tool_name:p.name,tool_type:"mcp",tool_input:p.args,activity_id:C,verification_id:I,trace_id:$,duration:null,mcp_server:t.mcp_server};await(0,d.appendAction)(r,B),s.logger.debug(`track-action (nested): ${p.name}`)}}process.exit(0)}f(j,"run");function q(e,o,i){if(!(0,O.isJobQueueEnabled)(e))return;const n={...i};delete n.tool_response;try{(0,a.submit)(e,o,a.SEND_EVENT_TYPE,n)}catch(r){if(r instanceof a.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}: ${r instanceof Error?r.message:r}`)}}f(q,"submitEvent");function A(e){if(e==null)return 0;try{const o=typeof e=="string"?e:JSON.stringify(e);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}f(A,"byteSize");0&&(module.exports={run});
1
+ "use strict";var f=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var g=(e,o)=>f(e,"name",{value:o,configurable:!0});var K=(e,o)=>{for(var r in o)f(e,r,{get:o[r],enumerable:!0})},M=(e,o,r,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of J(o))!U.call(e,s)&&s!==r&&f(e,s,{get:()=>o[s],enumerable:!(n=z(o,s))||n.enumerable});return e};var Q=e=>M(f({},"__esModule",{value:!0}),e);var X={};K(X,{run:()=>G});module.exports=Q(X);var a=require("../../../hooks/core/actions"),u=require("../../../hooks/core/nested-tools"),l=require("../../../hooks/core/session-state"),A=require("../../../import/ids"),O=require("../../../lib/config"),i=require("../../../lib/logger"),w=require("../../../lib/recording-tools"),D=require("../../../lib/stdin"),_=require("../../../queue"),T=require("../util");const W="browser-devtools",Y="node-devtools",j="backend-devtools",q="android-devtools";async function G(e){let o;try{o=JSON.parse((0,D.readStdin)())}catch(c){i.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const r=o.session_id??"default",n=`${e}/.ironbee/sessions/${r}`,s=`${n}/actions.jsonl`;(0,i.setLogFile)(`${n}/session.log`);const y=o.tool_name??"unknown",C=Date.now(),b=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,k=(0,l.getActiveActivityId)(n),S=(0,l.getActiveVerificationId)(n),I=(0,l.getActiveTraceId)(n),t=(0,T.classifyTool)(y,o.tool_input),V=t.tool_type==="mcp"&&t.mcp_server===W,h=t.tool_type==="mcp"&&t.mcp_server===Y,F=t.tool_type==="mcp"&&t.mcp_server===j,L=t.tool_type==="mcp"&&t.mcp_server===q,v=V||h||F||L,x=v?b:(0,T.extractClaudeToolInput)(y,b),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)(s),type:"tool_call",timestamp:C,tool_name:t.tool_name,tool_type:t.tool_type,tool_use_id:o.tool_use_id,tool_input:x,tool_input_size:$(b),tool_response:p?void 0:o.tool_response,tool_response_size:$(p?void 0:o.tool_response),activity_id:k,verification_id:S,trace_id:I,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:t.mcp_server,error:p};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(R.id=(0,A.deriveToolCallEventIdFromToolUseId)(r,o.tool_use_id)),v){await(0,a.appendAction)(s,R);const c=(0,w.recordingToolsForServer)(t.mcp_server);c!==null&&(t.tool_name===c.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action: recording started (${c.cycle})`)):t.tool_name===c.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action: recording stopped (${c.cycle})`)))}else H(e,r,R);if(i.logger.debug(`track-action: ${y}${p?" (failed)":""}`),v&&(0,u.isNestedToolContainer)(t.tool_name,t.mcp_server)&&!p){const B=(0,u.extractNestedToolCallsFromResponse)(o.tool_response,t.mcp_server)??(0,u.extractNestedToolCalls)(o.tool_input,t.mcp_server),m=(0,w.recordingToolsForServer)(t.mcp_server),N=new Set;for(const d of B){if(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})`))),N.has(d.name))continue;N.add(d.name);const P={...(0,a.baseFields)(s),type:"tool_call",timestamp:C,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:k,verification_id:S,trace_id:I,duration:null,mcp_server:t.mcp_server};await(0,a.appendAction)(s,P),i.logger.debug(`track-action (nested): ${d.name}`)}}process.exit(0)}g(G,"run");function H(e,o,r){if(!(0,O.isJobQueueEnabled)(e))return;const n={...r};delete n.tool_response;try{(0,_.submit)(e,o,_.SEND_EVENT_TYPE,n)}catch(s){if(s instanceof _.JobTooLargeError){i.logger.debug(`track-action: wire event too large for ${r.tool_name}; dropping`);return}i.logger.debug(`track-action: failed to submit ${r.tool_name}: ${s instanceof Error?s.message:s}`)}}g(H,"submitEvent");function $(e){if(e==null)return 0;try{const o=typeof e=="string"?e:JSON.stringify(e);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}g($,"byteSize");0&&(module.exports={run});
@@ -8,7 +8,7 @@ The Stop gate blocks completion until a verdict exists for your changes — dele
8
8
 
9
9
  ## BANNED
10
10
 
11
- - Running the verification tools (`bdt_*` / `ndt_*` / `bedt_*`) or `ironbee hook verification-start` / `submit-verdict` yourself — those are the verifier's job. Delegate.
11
+ - Running the verification tools (`bdt_*` / `ndt_*` / `bedt_*`) or `ironbee hook verification-start` / `submit-verdict` yourself — those are the verifier's job. Delegate. The verifier already submitted the single verdict in this shared session; you only RELAY it in text. Re-running `submit-verdict` yourself is REJECTED ("no active verification cycle" — the cycle already closed) and records nothing — a duplicate, not a verdict.
12
12
  - Reporting a task complete without delegating verification of your changes.
13
13
  - Submitting a verdict based on assumptions, code reading, or prior knowledge — the verifier verifies through real tools.
14
14
  - Writing `verdict.json` directly.
@@ -1 +1 @@
1
- "use strict";var c=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var s=Object.prototype.hasOwnProperty;var t=(m,o)=>{for(var e in o)c(m,e,{get:o[e],enumerable:!0})},p=(m,o,e,i)=>{if(o&&typeof o=="object"||typeof o=="function")for(let d of r(o))!s.call(m,d)&&d!==e&&c(m,d,{get:()=>o[d],enumerable:!(i=C(o,d))||i.enumerable});return m};var x=m=>p(c({},"__esModule",{value:!0}),m);var l={};t(l,{codexCommand:()=>f});module.exports=x(l);var n=require("commander"),a=require("./process-analytics");const f=new n.Command("codex").description("Codex-specific capabilities (Codex CLI only).").addCommand(a.codexProcessAnalyticsCommand);0&&(module.exports={codexCommand});
1
+ "use strict";var i=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var t=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var s=(m,o)=>{for(var e in o)i(m,e,{get:o[e],enumerable:!0})},x=(m,o,e,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let d of t(o))!p.call(m,d)&&d!==e&&i(m,d,{get:()=>o[d],enumerable:!(a=C(o,d))||a.enumerable});return m};var f=m=>x(i({},"__esModule",{value:!0}),m);var y={};s(y,{codexCommand:()=>l});module.exports=f(y);var n=require("commander"),r=require("./process-analytics"),c=require("./verifier");const l=new n.Command("codex").description("Codex-specific capabilities (Codex CLI only).").addCommand(c.verifierCommand).addCommand(r.codexProcessAnalyticsCommand);0&&(module.exports={codexCommand});
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: ironbee-verify
3
+ description: >
4
+ Verify the current code changes by driving the IronBee verification tools yourself — runs every
5
+ active cycle wired up for this project (see the platform sections at the bottom). Use when the
6
+ user types `$ironbee-verify`. Default is verify-only (report the verdict and stop); a leading
7
+ `fix` argument adds the fix-and-re-verify loop until pass. Optionally pass a custom scenario
8
+ (inline text or a file path) that defines what to verify.
9
+ ---
10
+
11
+ # IronBee Verify
12
+
13
+ Verify the current code changes through real tools. This project runs IronBee in **main-agent**
14
+ mode — the devtools tools (`mcp__browser-devtools__*` / `mcp__node-devtools__*` /
15
+ `mcp__backend-devtools__*` / `mcp__android-devtools__*`) are wired into THIS session, so **you**
16
+ drive them (there is no verifier sub-agent). The gate runs every active cycle and all must be
17
+ satisfied within a single verification cycle for `status: pass`. The verdict shape is
18
+ platform-agnostic (`status`, `checks`, `issues?`, `fixes?`); the gate enforces that you called
19
+ each active cycle's required tools and that `checks` is non-empty.
20
+
21
+ Your **Session ID** is injected into your context by the SessionStart hook (a `Session ID: <sid>`
22
+ line); author it in every `ironbee hook` command's JSON.
23
+
24
+ ## Mode
25
+
26
+ The FIRST whitespace-delimited token of whatever the user provided alongside this command selects
27
+ the mode; everything after it is the scenario:
28
+
29
+ - `fix` → **verify-and-fix**: on a fail verdict, fix the reported issues, rebuild, and re-verify
30
+ until the verdict passes.
31
+ - `report` → **verify-only** (the explicit form of the default).
32
+ - Anything else, or nothing → **verify-only** (default), and the WHOLE provided text is the scenario.
33
+
34
+ **Verify-only** means: submit the (possibly fail) verdict, report the issues, and STOP — do **not**
35
+ edit code, do **not** re-verify. The fail verdict is still recorded (an honest status report). If
36
+ the user wants the issues repaired, suggest `$ironbee-verify fix`. The mode token never overrides
37
+ the gate: if enforcement blocks completion because of this turn's edits, follow the gate.
38
+
39
+ ## Verification scenario
40
+
41
+ A custom verification scenario may be supplied — either **inline text** or a **path to a file**
42
+ (read at run time). The scenario is whatever the user provided alongside the command, after
43
+ stripping a leading `fix` / `report` mode token.
44
+
45
+ - **If a scenario is supplied, it is authoritative**: verify exactly what it describes, exercising
46
+ precisely the flows/states/endpoints it names — this **replaces** the default "exercise the
47
+ changed pages/endpoints" guidance.
48
+ - **If the scenario is (or points to) a file path**, read that file and treat its contents as the
49
+ scenario. Do not assume a fixed location or format.
50
+ - **If the path does not resolve**, stop and report `scenario file not found: <path>`, then ask how
51
+ to proceed — do not verify the literal path string.
52
+ - **If no scenario is supplied**, fall back to exercising the changed pages/endpoints per the active
53
+ platform sections below.
54
+
55
+ ## Universal steps
56
+
57
+ 1. **Start verification — run this ALONE first** (Codex runs shell and MCP tools in separate lanes,
58
+ so the open must land before any devtools call):
59
+ ```
60
+ echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start
61
+ ```
62
+ **In fix mode**, add the intent flag so IronBee's completion gate enforces fix-until-pass:
63
+ ```
64
+ echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start --intent fix
65
+ ```
66
+ 2. **Build and start** the application if not already running (don't guess ports). Track what YOU started.
67
+ 3. **For every active cycle, run its flow** — driven by the scenario above when supplied, otherwise
68
+ per the platform sections near the bottom of this file. All active cycles must be exercised within
69
+ this same verification cycle.
70
+ 4. **Stop** only what you started (dev server / container), then honor any cycle-specific teardown
71
+ (e.g. recording stop) BEFORE the verdict.
72
+ 5. **Submit your verdict** via terminal. One verdict covers every active cycle:
73
+ - Pass: `echo '{"session_id":"...","status":"pass","checks":["..."]}' | ironbee hook submit-verdict`
74
+ - Fail: `echo '{"session_id":"...","status":"fail","checks":["..."],"issues":["..."]}' | ironbee hook submit-verdict`
75
+ 6. **If failed** → collect ALL issues first (finish every active cycle) and submit ONE fail verdict.
76
+ Then branch by mode:
77
+ - **Verify-only (default)**: report the issues and stop — do not edit code. Suggest `$ironbee-verify fix`.
78
+ - **Fix mode (`fix` token)**: fix everything, rebuild, and re-verify until pass. Batch fixes to
79
+ avoid repeated build/restart cycles.
80
+ 7. If pass after a previous fail, include `"fixes"` in the verdict describing what was fixed.
81
+
82
+ ---
83
+
84
+ <!--IRONBEE:PLATFORM:browser-->
85
+ <!--/IRONBEE:PLATFORM:browser-->
86
+
87
+ <!--IRONBEE:PLATFORM:node-->
88
+ <!--/IRONBEE:PLATFORM:node-->
89
+
90
+ <!--IRONBEE:PLATFORM:backend-->
91
+ <!--/IRONBEE:PLATFORM:backend-->
92
+
93
+ <!--IRONBEE:PLATFORM:android-->
94
+ <!--/IRONBEE:PLATFORM:android-->
95
+
96
+ ---
97
+
98
+ ## When to FAIL
99
+
100
+ If you observe ANY problem on any active cycle — wrong data, unexpected errors, broken
101
+ interactions, missing evidence — you MUST submit a **fail** verdict. Do NOT rationalize problems
102
+ away. A FALSE failure (a valid, in-scope operation that should succeed but errors) is a FAIL, not
103
+ "verified failure handling".
104
+
105
+ ## Verdict Quality
106
+
107
+ Your `checks` array must list **specific observations**, not generic statements:
108
+ - GOOD: `["login form renders with email and password fields", "submitted valid credentials, redirected to /dashboard", "console clean — 0 errors"]`
109
+ - BAD: `["it works", "looks good", "feature implemented"]`
110
+
111
+ ## Important
112
+ - ALWAYS submit a verdict after every verification attempt — both pass AND fail.
113
+ - Do NOT edit code before submitting a fail verdict.
114
+ - Every `apply_patch` edit clears your verdict — re-verify after edits.
@@ -1 +1 @@
1
- "use strict";var I=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var b=(t,e)=>I(t,"name",{value:e,configurable:!0});var K=(t,e)=>{for(var o in e)I(t,o,{get:e[o],enumerable:!0})},W=(t,e,o,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Y(e))!G.call(t,i)&&i!==o&&I(t,i,{get:()=>e[i],enumerable:!(n=Q(e,i))||n.enumerable});return t};var X=t=>W(I({},"__esModule",{value:!0}),t);var ne={};K(ne,{run:()=>oe});module.exports=X(ne);var _=require("../../../hooks/core/actions"),A=require("../../../hooks/core/nested-tools"),R=require("../../../import/ids"),r=require("../../../hooks/core/session-state"),M=require("../../../hooks/core/tool-use-stash"),O=require("../../../lib/config"),a=require("../../../lib/logger"),N=require("../../../lib/output"),j=require("../../../lib/recording-tools"),L=require("../../../lib/stdin"),v=require("../../../queue"),s=require("../util");function S(t){if(t==null)return 0;if(typeof t=="string")try{return Buffer.byteLength(t,"utf8")}catch{return 0}try{return Buffer.byteLength(JSON.stringify(t),"utf8")}catch{return 0}}b(S,"safeStringifyBytes");function Z(t){if(t==null)return{isError:!1,errorText:void 0};if(typeof t=="object"&&t!==null){const e=t;if(e.isError===!0||e.is_error===!0){const o=e.error??e.message??e.errorMessage;return{isError:!0,errorText:typeof o=="string"?o:JSON.stringify(e).slice(0,500)}}}if(typeof t=="string"){const e=t;if(/(?:^|\n)Process exited with code [1-9]/.test(e)||/^Exit code:\s*[1-9]/m.test(e)||/apply_patch verification failed/i.test(e)||/failed to find expected lines/i.test(e)||/^\s*Error\b/.test(e)||/(?:^|\n)\[Request interrupted by user\]/.test(e)||/modified since (?:last )?read|stale read/i.test(e)||/file (?:is )?too large|exceeds/i.test(e)||/file not found|No such file or directory|does not exist/i.test(e))return{isError:!0,errorText:e.slice(0,500)}}return{isError:!1,errorText:void 0}}b(Z,"detectFailure");function ee(t){if(t===null||typeof t!="object")return;const e=t._metadata;if(e===null||typeof e!="object")return;const o=e.toolCallId;if(typeof o=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(o))return o}b(ee,"extractMetadataToolCallId");function te(t,e){const o=(0,M.consumeToolUseData)(t,e);if(!o?.start_ns)return null;try{const n=process.hrtime.bigint()-BigInt(o.start_ns);return Number(n/1000000n)}catch(n){return a.logger.debug(`failed to derive duration from stash: ${n}`),null}}b(te,"deriveDurationMs");async function oe(t){const e=(0,s.parseCodexHookStdin)((0,L.readStdin)()),o=e.session_id??"default",n=`${t}/.ironbee/sessions/${o}`,i=`${n}/actions.jsonl`;(0,a.setLogFile)(`${n}/session.log`);const y=e.tool_name??"",l=e.tool_use_id??"",u=e.tool_input,h=u&&typeof u=="object"?{...u,_metadata:void 0}:void 0,$=e.tool_response,d=(0,s.extractCodexMcpServer)(y),P=d==="browser-devtools"||d==="node-devtools"||d==="backend-devtools"||d==="android-devtools",z=te(o,l),m=Z($),g=(0,s.classifyCodexTool)(y);if(P){const T=g.tool_name,c=(0,j.recordingToolsForServer)(d);c!==null&&(T===c.startTool?(0,r.setRecordingActive)(n,!0):T===c.stopTool&&(0,r.setRecordingActive)(n,!1));const w=(0,r.getActiveActivityId)(n),f={...(0,_.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_type:g.tool_type,tool_name:g.tool_name,mcp_server:g.mcp_server??d,tool_input:h,tool_input_size:S(h),tool_response_size:m.isError?0:S($),duration:z};w&&(f.activity_id=w);const B=ee(u);B!==void 0?f.id=B:l.length>0&&(f.id=(0,R.deriveToolCallEventIdFromToolUseId)(o,l)),l&&(f.tool_use_id=l);const C=(0,r.getActiveVerificationId)(n);C&&(f.verification_id=C);const k=(0,r.getActiveTraceId)(n);if(k&&(f.trace_id=k),m.isError&&(f.error=m.errorText),await(0,_.appendAction)(i,f),T===(0,A.executeToolBareName)(d)&&!m.isError){const V=(0,A.extractNestedToolCalls)(h??u,d);for(const x of V){c!==null&&(x.name===c.startTool?((0,r.setRecordingActive)(n,!0),a.logger.debug(`track-action (nested): recording started (${c.cycle})`)):x.name===c.stopTool&&((0,r.setRecordingActive)(n,!1),a.logger.debug(`track-action (nested): recording stopped (${c.cycle})`)));const E={...(0,_.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_name:x.name,tool_type:"mcp",tool_input:x.args,duration:null,mcp_server:d};w&&(E.activity_id=w),C&&(E.verification_id=C),k&&(E.trace_id=k),await(0,_.appendAction)(i,E),a.logger.debug(`track-action (nested): ${x.name}`)}}(0,N.writeAndExit)(JSON.stringify({}),0);return}if(!(0,O.isJobQueueEnabled)(t)){(0,N.writeAndExit)(JSON.stringify({}),0);return}const J=(0,r.getActiveActivityId)(n),U=(0,s.extractCodexToolInput)(y,u),q=S(u),H=m.isError?0:S($),p={...(0,_.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_type:g.tool_type,tool_name:g.tool_name||(0,s.normalizeCodexToolName)(y),mcp_server:g.mcp_server,tool_input:U,tool_input_size:q,tool_response_size:H,duration:z};J&&(p.activity_id=J),l.length>0&&(p.id=(0,R.deriveToolCallEventIdFromToolUseId)(o,l)),l&&(p.tool_use_id=l);const D=(0,r.getActiveVerificationId)(n);D&&(p.verification_id=D);const F=(0,r.getActiveTraceId)(n);F&&(p.trace_id=F),m.isError&&(p.error=m.errorText);try{(0,v.submit)(t,o,v.SEND_EVENT_TYPE,p)}catch(T){T instanceof v.JobTooLargeError?a.logger.debug(`track-action: wire event too large for tool_call ${y}; dropping`):a.logger.debug(`queue submit failed for tool_call ${y}: ${T}`)}(0,N.writeAndExit)(JSON.stringify({}),0)}b(oe,"run");0&&(module.exports={run});
1
+ "use strict";var N=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var b=(t,e)=>N(t,"name",{value:e,configurable:!0});var Z=(t,e)=>{for(var o in e)N(t,o,{get:e[o],enumerable:!0})},ee=(t,e,o,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of W(e))!X.call(t,i)&&i!==o&&N(t,i,{get:()=>e[i],enumerable:!(n=K(e,i))||n.enumerable});return t};var te=t=>ee(N({},"__esModule",{value:!0}),t);var se={};Z(se,{run:()=>ie});module.exports=te(se);var T=require("../../../hooks/core/actions"),v=require("../../../hooks/core/nested-tools"),R=require("../../../import/ids"),r=require("../../../hooks/core/session-state"),P=require("../../../hooks/core/tool-use-stash"),U=require("../../../lib/config"),a=require("../../../lib/logger"),A=require("../../../lib/output"),q=require("../../../lib/recording-tools"),H=require("../../../lib/stdin"),x=require("../../../queue"),l=require("../util");function h(t){if(t==null)return 0;if(typeof t=="string")try{return Buffer.byteLength(t,"utf8")}catch{return 0}try{return Buffer.byteLength(JSON.stringify(t),"utf8")}catch{return 0}}b(h,"safeStringifyBytes");function oe(t){if(t==null)return{isError:!1,errorText:void 0};if(typeof t=="object"&&t!==null){const e=t;if(e.isError===!0||e.is_error===!0){const o=e.error??e.message??e.errorMessage;return{isError:!0,errorText:typeof o=="string"?o:JSON.stringify(e).slice(0,500)}}}if(typeof t=="string"){const e=t;if(/(?:^|\n)Process exited with code [1-9]/.test(e)||/^Exit code:\s*[1-9]/m.test(e)||/apply_patch verification failed/i.test(e)||/failed to find expected lines/i.test(e)||/^\s*Error\b/.test(e)||/(?:^|\n)\[Request interrupted by user\]/.test(e)||/modified since (?:last )?read|stale read/i.test(e)||/file (?:is )?too large|exceeds/i.test(e)||/file not found|No such file or directory|does not exist/i.test(e))return{isError:!0,errorText:e.slice(0,500)}}return{isError:!1,errorText:void 0}}b(oe,"detectFailure");function ne(t){if(t===null||typeof t!="object")return;const e=t._metadata;if(e===null||typeof e!="object")return;const o=e.toolCallId;if(typeof o=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(o))return o}b(ne,"extractMetadataToolCallId");function re(t,e){const o=(0,P.consumeToolUseData)(t,e);if(!o?.start_ns)return null;try{const n=process.hrtime.bigint()-BigInt(o.start_ns);return Number(n/1000000n)}catch(n){return a.logger.debug(`failed to derive duration from stash: ${n}`),null}}b(re,"deriveDurationMs");async function ie(t){const e=(0,l.parseCodexHookStdin)((0,H.readStdin)()),o=e.session_id??"default",n=`${t}/.ironbee/sessions/${o}`,i=`${n}/actions.jsonl`;(0,a.setLogFile)(`${n}/session.log`);const y=e.tool_name??"",d=e.tool_use_id??"",m=e.tool_input,$=m&&typeof m=="object"?{...m,_metadata:void 0}:void 0,w=e.tool_response,s=(0,l.extractCodexMcpServer)(y),z=s==="browser-devtools"||s==="node-devtools"||s==="backend-devtools"||s==="android-devtools",F=re(o,d),c=(0,l.classifyCodexTool)(y),J=z&&(0,v.isNestedToolContainer)(c.tool_name,s),D=J?(0,v.extractNestedToolCallsFromResponse)(w,s):null,g=D!==null?{isError:!1,errorText:void 0}:oe(w);if(z){const C=c.tool_name,f=(0,q.recordingToolsForServer)(s);f!==null&&(C===f.startTool?(0,r.setRecordingActive)(n,!0):C===f.stopTool&&(0,r.setRecordingActive)(n,!1));const E=(0,r.getActiveActivityId)(n),u={...(0,T.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_type:c.tool_type,tool_name:c.tool_name,mcp_server:c.mcp_server??s,tool_input:$,tool_input_size:h($),tool_response_size:g.isError?0:h(w),duration:F};E&&(u.activity_id=E);const B=ne(m);B!==void 0?u.id=B:d.length>0&&(u.id=(0,R.deriveToolCallEventIdFromToolUseId)(o,d)),d&&(u.tool_use_id=d);const k=(0,r.getActiveVerificationId)(n);k&&(u.verification_id=k);const S=(0,r.getActiveTraceId)(n);if(S&&(u.trace_id=S),g.isError&&(u.error=g.errorText),await(0,T.appendAction)(i,u),J&&!g.isError){const G=D??(0,v.extractNestedToolCalls)($??m,s),L=new Set;for(const _ of G){if(f!==null&&(_.name===f.startTool?((0,r.setRecordingActive)(n,!0),a.logger.debug(`track-action (nested): recording started (${f.cycle})`)):_.name===f.stopTool&&((0,r.setRecordingActive)(n,!1),a.logger.debug(`track-action (nested): recording stopped (${f.cycle})`))),L.has(_.name))continue;L.add(_.name);const I={...(0,T.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_name:_.name,tool_type:"mcp",tool_input:_.args,duration:null,mcp_server:s};E&&(I.activity_id=E),k&&(I.verification_id=k),S&&(I.trace_id=S),await(0,T.appendAction)(i,I),a.logger.debug(`track-action (nested): ${_.name}`)}}(0,A.writeAndExit)(JSON.stringify({}),0);return}if(!(0,U.isJobQueueEnabled)(t)){(0,A.writeAndExit)(JSON.stringify({}),0);return}const M=(0,r.getActiveActivityId)(n),V=(0,l.extractCodexToolInput)(y,m),Q=h(m),Y=g.isError?0:h(w),p={...(0,T.baseFields)(i),type:"tool_call",timestamp:Date.now(),tool_type:c.tool_type,tool_name:c.tool_name||(0,l.normalizeCodexToolName)(y),mcp_server:c.mcp_server,tool_input:V,tool_input_size:Q,tool_response_size:Y,duration:F};M&&(p.activity_id=M),d.length>0&&(p.id=(0,R.deriveToolCallEventIdFromToolUseId)(o,d)),d&&(p.tool_use_id=d);const O=(0,r.getActiveVerificationId)(n);O&&(p.verification_id=O);const j=(0,r.getActiveTraceId)(n);j&&(p.trace_id=j),g.isError&&(p.error=g.errorText);try{(0,x.submit)(t,o,x.SEND_EVENT_TYPE,p)}catch(C){C instanceof x.JobTooLargeError?a.logger.debug(`track-action: wire event too large for tool_call ${y}; dropping`):a.logger.debug(`queue submit failed for tool_call ${y}: ${C}`)}(0,A.writeAndExit)(JSON.stringify({}),0)}b(ie,"run");0&&(module.exports={run});
@@ -1,3 +1,3 @@
1
- "use strict";var E=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var h=(g,o)=>E(g,"name",{value:o,configurable:!0});var Q=(g,o)=>{for(var n in o)E(g,n,{get:o[n],enumerable:!0})},Z=(g,o,n,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let e of Y(o))!z.call(g,e)&&e!==n&&E(g,e,{get:()=>o[e],enumerable:!(r=W(o,e))||r.enumerable});return g};var j=g=>Z(E({},"__esModule",{value:!0}),g);var ro={};Q(ro,{CodexClient:()=>to});module.exports=j(ro);var i=require("fs"),a=require("path"),B=require("../../lib/gitignore"),f=require("../../lib/logger"),l=require("../../lib/output"),P=require("../../lib/fs-prune"),u=require("../../lib/config"),$=require("../../lib/platform-section"),t=require("./util"),R=require("./thread-map"),N=require("./hooks/verify-gate"),V=require("./hooks/activity-end"),O=require("./hooks/session-start"),G=require("./hooks/activity-start"),J=require("./hooks/require-verification"),L=require("./hooks/require-verdict"),F=require("./hooks/clear-verdict"),K=require("./hooks/track-action"),U=require("./hooks/track-action-monitor"),q=require("./hooks/track-action-pre"),D=require("./hooks/subagent-start"),X=require("./hooks/subagent-stop");const T="browser-devtools",w="node-devtools",A="backend-devtools",_="android-devtools",oo="ironbee",k="ironbee-verifier",eo=30,I="Verifies recent code changes through real browser/runtime/backend tools and submits the IronBee verdict. Spawn this custom agent (by agent_type) after editing code to run the verification cycle out-of-band \u2014 it drives the devtools tools, judges the result, and records the verdict in the shared session. It does NOT edit code.";function H(g){return(0,a.join)(__dirname,"..",g,"platforms")}h(H,"platformsDirFor");function b(g){return l.pc.dim(g)}h(b,"codexColor");function M(g){return g.hooks.some(o=>o.command.includes(oo))}h(M,"isIronBeeHookGroup");function no(g){const o=Object.keys(g);return o.length===0?!0:o.length===1&&o[0]==="hooks"?Object.keys(g.hooks??{}).length===0:!1}h(no,"isCodexHooksEmpty");class to{constructor(){this.name="codex";this.supportsVerifierModel=!0}static{h(this,"CodexClient")}detect(o){return(0,i.existsSync)((0,a.join)(o,".agents","skills","ironbee-verify"))}resolveProjectDir(){return process.env.CODEX_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(o,n){const r=n??(0,u.loadConfig)(o),e=(0,u.getVerificationMode)(r),s=e!=="monitor";this.cleanupArtifacts(o);const c=(0,t.codexHooksJsonPath)(o);this.mergeHooksConfig(c,e),this.mergeConfigToml(o,r,s),s&&(e==="enforce"&&this.writeAgentsMdBlock(o,r),this.writeSkills(o,e==="enforce"),(0,$.syncPlatformSectionsToConfig)(o,H)),(0,B.ensureIronBeeGitignored)(o),console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} hooks ${l.pc.dim("\u2192")} ${l.pc.dim(c)}`),console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} config ${l.pc.dim("\u2192")} ${l.pc.dim((0,t.codexConfigTomlPath)(o))}`),e==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,a.join)(o,"AGENTS.md"))}`),console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} skill ${l.pc.dim("\u2192")} ${l.pc.dim((0,a.join)(o,".agents","skills","ironbee-verification","SKILL.md"))}`),console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} command ${l.pc.dim("\u2192")} ${l.pc.dim((0,a.join)(o,".agents","skills","ironbee-verify","SKILL.md"))}`)):e==="assist"?(console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual $ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} command ${l.pc.dim("\u2192")} ${l.pc.dim((0,a.join)(o,".agents","skills","ironbee-verify","SKILL.md"))}`)):console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(),console.log(` ${l.pc.yellow("\u26A0")} ${l.pc.yellow("Codex requires one-time TUI setup:")}`),console.log(` ${l.pc.yellow("1.")} Run ${l.pc.bold("/hooks")} in a fresh Codex session to review and trust IronBee hooks`),console.log(` ${l.pc.yellow("2.")} Restart any open Codex sessions to pick up new hook config`)}uninstall(o){this.cleanupArtifacts(o),(0,P.pruneEmptyDirs)((0,a.join)(o,".codex"));const n=(0,R.codexThreadMapPath)(o);if((0,i.existsSync)(n))try{(0,i.unlinkSync)(n)}catch(r){f.logger.debug(`failed to remove codex thread map: ${r}`)}console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} removed hooks, MCP entries, AGENTS.md block, and skills`)}cleanupArtifacts(o){this.migrateAwayFromUserLevel();const n=(0,t.codexHooksJsonPath)(o);this.removeIronBeeHooks(n),this.maybeDeleteEmptyHooks(n),this.removeIronBeeMcpServers(o),this.removeVerifierAgentToml(o);const r=(0,a.join)(o,"AGENTS.md");if((0,i.existsSync)(r))try{const s=(0,i.readFileSync)(r,"utf-8"),c=(0,t.stripAgentsMdBlock)(s);c===null?(0,i.unlinkSync)(r):c!==s&&(0,i.writeFileSync)(r,c)}catch(s){f.logger.debug(`failed to strip AGENTS.md block: ${s}`)}const e=(0,a.join)(o,".agents","skills");this.removeDir((0,a.join)(e,"ironbee-verification")),this.removeDir((0,a.join)(e,"ironbee-verify")),(0,P.pruneEmptyDirs)((0,a.join)(o,".agents"))}async runVerifyGate(o){await(0,N.run)(o)}async runActivityEnd(o){await(0,V.run)(o)}async runSessionStart(o){await(0,O.run)(o)}async runActivityStart(o){await(0,G.run)(o)}async runRequireVerification(o,n){await(0,J.run)(o,n)}async runRequireVerdict(o,n){await(0,L.run)(o,n)}async runClearVerdict(o){await(0,F.run)(o)}async runTrackAction(o){await(0,K.run)(o)}async runTrackActionMonitor(o){await(0,U.run)(o)}async runTrackActionPre(o){await(0,q.run)(o)}async runSubagentStart(o){await(0,D.run)(o)}async runSubagentStop(o){await(0,X.run)(o)}resolveAgentSessionId(o,n){const r=process.env.CODEX_THREAD_ID;if(typeof r=="string"&&r.length>0&&n)return(0,R.lookupThreadSession)(n,r)}async runSessionEnd(o){f.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(o,n){const r=n!=="monitor",e=n==="assist"?" --soft":"";(0,i.mkdirSync)((0,a.dirname)(o),{recursive:!0});let s={hooks:{}};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8")),s.hooks||(s.hooks={})}catch(m){f.logger.debug(`failed to parse ${o}: ${m}`),s={hooks:{}}}for(const m of Object.keys(s.hooks)){const v=s.hooks[m].filter(y=>!M(y));v.length===0?delete s.hooks[m]:s.hooks[m]=v}const c=h((m,v,y)=>{s.hooks[m]||(s.hooks[m]=[]),s.hooks[m].push({matcher:v,hooks:[{type:"command",command:y}]})},"addGroup");c("SessionStart",".*","ironbee hook session-start --client codex"),c("UserPromptSubmit",".*","ironbee hook activity-start --client codex"),c("PreToolUse",".*","ironbee hook track-action-pre --client codex"),r&&(c("PreToolUse","^mcp__(browser|node|backend|android)[-_]devtools__.*",`ironbee hook require-verification --client codex${e}`),c("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${e}`),c("PostToolUse","^apply_patch$","ironbee hook clear-verdict --client codex"),c("SubagentStart",".*","ironbee hook subagent-start --client codex")),c("SubagentStop",".*","ironbee hook subagent-stop --client codex"),c("PostToolUse",".*",r?"ironbee hook track-action --client codex":"ironbee hook track-action-monitor --client codex"),c("Stop",".*",n==="enforce"?"ironbee hook verify-gate --client codex":"ironbee hook activity-end --client codex"),(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}removeIronBeeHooks(o){if((0,i.existsSync)(o))try{const n=(0,i.readFileSync)(o,"utf-8"),r=JSON.parse(n);if(!r.hooks)return;let e=!1;for(const s of Object.keys(r.hooks)){const c=r.hooks[s].filter(d=>!M(d));c.length!==r.hooks[s].length&&(e=!0),c.length===0?delete r.hooks[s]:r.hooks[s]=c}e&&(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}catch(n){f.logger.debug(`failed to strip IronBee hooks from ${o}: ${n}`)}}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const n=JSON.parse((0,i.readFileSync)(o,"utf-8"));no(n)&&(0,i.unlinkSync)(o)}catch(n){f.logger.debug(`failed to inspect ${o} for emptiness: ${n}`)}}mergeConfigToml(o,n,r){(0,i.mkdirSync)((0,a.join)(o,".codex"),{recursive:!0});let e=(0,t.readCodexConfigToml)(o);if(e=(0,t.ensureFeaturesHooksTrue)(e),e=(0,t.removeMcpServer)(e,T),e=(0,t.removeMcpServer)(e,w),e=(0,t.removeMcpServer)(e,A),e=(0,t.removeMcpServer)(e,_),r){const s=(0,u.getVerificationModel)(n,"codex"),c=(0,i.existsSync)((0,t.userCodexConfigTomlPath)())?(0,i.readFileSync)((0,t.userCodexConfigTomlPath)(),"utf-8"):"",d=(0,t.extractTomlTopLevelModel)(e)===null&&(0,t.extractTomlTopLevelModel)(c)===null;s===void 0&&d&&console.log(` ${l.pc.dim("\u2192")} ${b("[codex]")} ${l.pc.yellow("\u26A0 no model for the verifier")} \u2014 the ${l.pc.bold("ironbee-verifier")} sub-agent inherits the session model, but neither this project's .codex/config.toml nor ~/.codex/config.toml has a top-level ${l.pc.bold("model")}, so it may fail to spawn ("could not resolve the child model"). Fix: set ${l.pc.bold("model")} in ~/.codex/config.toml, or set ${l.pc.bold("verification.model")} in your ironbee config.`),this.writeVerifierAgentToml(o,n,s),e=(0,t.upsertAgentsTable)(e,k,[`description = ${JSON.stringify(I)}`,`config_file = ${JSON.stringify(`agents/${k}.toml`)}`]),e=(0,t.ensureMultiAgentV2SpawnMetadataExposed)(e)}else e=(0,t.removeAgentsTable)(e,k),e=(0,t.removeMultiAgentV2SpawnMetadata)(e),this.removeVerifierAgentToml(o);(0,t.writeCodexConfigToml)(o,e)}writeVerifierAgentToml(o,n,r){const e=(0,a.join)(__dirname,"agents",`${k}.md`);let s;try{s=(0,i.readFileSync)(e,"utf-8")}catch(v){f.logger.debug(`failed to read verifier agent source ${e}: ${v}`);return}const c=H("codex");for(const v of u.ALL_CYCLES){const S=(0,u.isCycleEnabled)(n,v)?C=>{const x=(0,a.join)(c,(0,$.fragmentFilename)("skill",v,C));return(0,i.existsSync)(x)?(0,i.readFileSync)(x,"utf-8").trimEnd():null}:null;s=(0,$.applyPlatformSection)(s,v,S,`${k}.toml`)}const d=[];d.push(`name = ${JSON.stringify(k)}`),d.push(`description = ${JSON.stringify(I)}`),d.push('sandbox_mode = "read-only"'),r&&d.push(`model = ${JSON.stringify(r)}`),d.push("developer_instructions = '''"),d.push(s.replace(/'''/g,"```").trimEnd()),d.push("'''");const p=h((v,y,S)=>{v&&(d.push(""),d.push(`[mcp_servers.${y}]`),d.push(...io(S)),d.push(`startup_timeout_sec = ${eo}`),d.push("required = true"),d.push('default_tools_approval_mode = "approve"'))},"addCycle");p((0,u.isCycleEnabled)(n,"browser"),T,(0,u.getMcpServerEntry)(o)),p((0,u.isCycleEnabled)(n,"node"),w,(0,u.getNodeDevToolsMcpEntry)(o)),p((0,u.isCycleEnabled)(n,"backend"),A,(0,u.getBackendDevToolsMcpEntry)(o)),p((0,u.isCycleEnabled)(n,"android"),_,(0,u.getAndroidDevToolsMcpEntry)(o));const m=(0,t.codexAgentTomlPath)(o,k);(0,i.mkdirSync)((0,a.dirname)(m),{recursive:!0}),(0,i.writeFileSync)(m,d.join(`
1
+ "use strict";var _=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var y=(f,e)=>_(f,"name",{value:e,configurable:!0});var oe=(f,e)=>{for(var o in e)_(f,o,{get:e[o],enumerable:!0})},ne=(f,e,o,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!ee.call(f,i)&&i!==o&&_(f,i,{get:()=>e[i],enumerable:!(s=Z(e,i))||s.enumerable});return f};var te=f=>ne(_({},"__esModule",{value:!0}),f);var le={};oe(le,{CodexClient:()=>se});module.exports=te(le);var r=require("fs"),g=require("path"),O=require("../../lib/gitignore"),b=require("../../lib/logger"),d=require("../../lib/output"),P=require("../../lib/fs-prune"),c=require("../../lib/config"),C=require("../../lib/platform-section"),n=require("./util"),M=require("./thread-map"),L=require("./hooks/verify-gate"),F=require("./hooks/activity-end"),G=require("./hooks/session-start"),J=require("./hooks/activity-start"),K=require("./hooks/require-verification"),U=require("./hooks/require-verdict"),q=require("./hooks/clear-verdict"),D=require("./hooks/track-action"),X=require("./hooks/track-action-monitor"),W=require("./hooks/track-action-pre"),Y=require("./hooks/subagent-start"),z=require("./hooks/subagent-stop");const x="browser-devtools",w="node-devtools",E="backend-devtools",T="android-devtools",ie="ironbee",k="ironbee-verifier",R=30,V="Verifies recent code changes through real browser/runtime/backend tools and submits the IronBee verdict. Spawn this custom agent (by agent_type) after editing code to run the verification cycle out-of-band \u2014 it drives the devtools tools, judges the result, and records the verdict in the shared session. It does NOT edit code.";function A(f){return(0,g.join)(__dirname,"..",f,"platforms")}y(A,"platformsDirFor");function h(f){return d.pc.dim(f)}y(h,"codexColor");function B(f){return f.hooks.some(e=>e.command.includes(ie))}y(B,"isIronBeeHookGroup");function re(f){const e=Object.keys(f);return e.length===0?!0:e.length===1&&e[0]==="hooks"?Object.keys(f.hooks??{}).length===0:!1}y(re,"isCodexHooksEmpty");class se{constructor(){this.name="codex";this.supportsVerifierModel=!0}static{y(this,"CodexClient")}detect(e){return(0,r.existsSync)((0,g.join)(e,".agents","skills","ironbee-verify"))}resolveProjectDir(){return process.env.CODEX_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(e,o){const s=o??(0,c.loadConfig)(e),i=(0,c.getVerificationMode)(s),t=i!=="monitor",a=(0,c.getCodexVerifierMode)(s);this.cleanupArtifacts(e);const l=(0,n.codexHooksJsonPath)(e);if(this.mergeHooksConfig(l,i,a),this.mergeConfigToml(e,s,t,a),t&&(i==="enforce"&&this.writeAgentsMdBlock(e,s,a),this.writeSkills(e,i==="enforce",s,a),(0,C.syncPlatformSectionsToConfig)(e,A)),(0,O.ensureIronBeeGitignored)(e),console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} hooks ${d.pc.dim("\u2192")} ${d.pc.dim(l)}`),console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} config ${d.pc.dim("\u2192")} ${d.pc.dim((0,n.codexConfigTomlPath)(e))}`),t){const p=a==="main-agent"?`${d.pc.yellow("main-agent")} (the main agent drives the devtools tools directly)`:`${d.pc.bold("sub-agent")} (delegated to the ironbee-verifier custom agent)`;console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} verify ${d.pc.dim("\u2192")} ${p}`)}i==="enforce"?(console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} agents ${d.pc.dim("\u2192")} ${d.pc.dim((0,g.join)(e,"AGENTS.md"))}`),console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} skill ${d.pc.dim("\u2192")} ${d.pc.dim((0,g.join)(e,".agents","skills","ironbee-verification","SKILL.md"))}`),console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} command ${d.pc.dim("\u2192")} ${d.pc.dim((0,g.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):i==="assist"?(console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} ${d.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual $ironbee-verify only, no enforcement`),console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} command ${d.pc.dim("\u2192")} ${d.pc.dim((0,g.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} ${d.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(),console.log(` ${d.pc.yellow("\u26A0")} ${d.pc.yellow("Codex requires one-time TUI setup:")}`),console.log(` ${d.pc.yellow("1.")} Run ${d.pc.bold("/hooks")} in a fresh Codex session to review and trust IronBee hooks`),console.log(` ${d.pc.yellow("2.")} Restart any open Codex sessions to pick up new hook config`)}uninstall(e){this.cleanupArtifacts(e),(0,r.existsSync)((0,n.codexHooksJsonPath)(e))||this.removeFeaturesHooksFlag(e),(0,P.pruneEmptyDirs)((0,g.join)(e,".codex"));const o=(0,M.codexThreadMapPath)(e);if((0,r.existsSync)(o))try{(0,r.unlinkSync)(o)}catch(s){b.logger.debug(`failed to remove codex thread map: ${s}`)}console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} removed hooks, MCP entries, AGENTS.md block, and skills`)}removeFeaturesHooksFlag(e){const o=(0,n.codexConfigTomlPath)(e);if((0,r.existsSync)(o))try{const s=(0,r.readFileSync)(o,"utf-8"),i=(0,n.removeFeaturesHooks)(s);i.trim().length===0?(0,r.unlinkSync)(o):i!==s&&(0,r.writeFileSync)(o,i)}catch(s){b.logger.debug(`failed to strip [features] hooks from config.toml: ${s}`)}}cleanupArtifacts(e){this.migrateAwayFromUserLevel();const o=(0,n.codexHooksJsonPath)(e);this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o),this.removeIronBeeMcpServers(e),this.removeVerifierAgentToml(e);const s=(0,g.join)(e,"AGENTS.md");if((0,r.existsSync)(s))try{const t=(0,r.readFileSync)(s,"utf-8"),a=(0,n.stripAgentsMdBlock)(t);a===null?(0,r.unlinkSync)(s):a!==t&&(0,r.writeFileSync)(s,a)}catch(t){b.logger.debug(`failed to strip AGENTS.md block: ${t}`)}const i=(0,g.join)(e,".agents","skills");this.removeDir((0,g.join)(i,"ironbee-verification")),this.removeDir((0,g.join)(i,"ironbee-verify")),(0,P.pruneEmptyDirs)((0,g.join)(e,".agents"))}async runVerifyGate(e){await(0,L.run)(e)}async runActivityEnd(e){await(0,F.run)(e)}async runSessionStart(e){await(0,G.run)(e)}async runActivityStart(e){await(0,J.run)(e)}async runRequireVerification(e,o){await(0,K.run)(e,o)}async runRequireVerdict(e,o){await(0,U.run)(e,o)}async runClearVerdict(e){await(0,q.run)(e)}async runTrackAction(e){await(0,D.run)(e)}async runTrackActionMonitor(e){await(0,X.run)(e)}async runTrackActionPre(e){await(0,W.run)(e)}async runSubagentStart(e){await(0,Y.run)(e)}async runSubagentStop(e){await(0,z.run)(e)}resolveAgentSessionId(e,o){const s=process.env.CODEX_THREAD_ID;if(typeof s=="string"&&s.length>0&&o)return(0,M.lookupThreadSession)(o,s)}async runSessionEnd(e){b.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(e,o,s){const i=o!=="monitor",t=o==="assist"?" --soft":"";(0,r.mkdirSync)((0,g.dirname)(e),{recursive:!0});let a={hooks:{}};if((0,r.existsSync)(e))try{a=JSON.parse((0,r.readFileSync)(e,"utf-8")),a.hooks||(a.hooks={})}catch(u){b.logger.debug(`failed to parse ${e}: ${u}`),a={hooks:{}}}for(const u of Object.keys(a.hooks)){const m=a.hooks[u].filter(S=>!B(S));m.length===0?delete a.hooks[u]:a.hooks[u]=m}const l=y((u,m,S)=>{a.hooks[u]||(a.hooks[u]=[]),a.hooks[u].push({matcher:m,hooks:[{type:"command",command:S}]})},"addGroup");l("SessionStart",".*","ironbee hook session-start --client codex"),l("UserPromptSubmit",".*","ironbee hook activity-start --client codex"),l("PreToolUse",".*","ironbee hook track-action-pre --client codex"),i&&(l("PreToolUse","^mcp__(browser|node|backend|android)[-_]devtools__.*",`ironbee hook require-verification --client codex${t}`),l("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${t}`),l("PostToolUse","^apply_patch$","ironbee hook clear-verdict --client codex"),s==="sub-agent"&&l("SubagentStart",".*","ironbee hook subagent-start --client codex")),l("SubagentStop",".*","ironbee hook subagent-stop --client codex"),l("PostToolUse",".*",i?"ironbee hook track-action --client codex":"ironbee hook track-action-monitor --client codex"),l("Stop",".*",o==="enforce"?"ironbee hook verify-gate --client codex":"ironbee hook activity-end --client codex"),(0,r.writeFileSync)(e,JSON.stringify(a,null,2))}removeIronBeeHooks(e){if((0,r.existsSync)(e))try{const o=(0,r.readFileSync)(e,"utf-8"),s=JSON.parse(o);if(!s.hooks)return;let i=!1;for(const t of Object.keys(s.hooks)){const a=s.hooks[t].filter(l=>!B(l));a.length!==s.hooks[t].length&&(i=!0),a.length===0?delete s.hooks[t]:s.hooks[t]=a}i&&(0,r.writeFileSync)(e,JSON.stringify(s,null,2))}catch(o){b.logger.debug(`failed to strip IronBee hooks from ${e}: ${o}`)}}maybeDeleteEmptyHooks(e){if((0,r.existsSync)(e))try{const o=JSON.parse((0,r.readFileSync)(e,"utf-8"));re(o)&&(0,r.unlinkSync)(e)}catch(o){b.logger.debug(`failed to inspect ${e} for emptiness: ${o}`)}}mergeConfigToml(e,o,s,i){(0,r.mkdirSync)((0,g.join)(e,".codex"),{recursive:!0});let t=(0,n.readCodexConfigToml)(e);if(t=(0,n.ensureFeaturesHooksTrue)(t),t=(0,n.removeMcpServer)(t,x),t=(0,n.removeMcpServer)(t,w),t=(0,n.removeMcpServer)(t,E),t=(0,n.removeMcpServer)(t,T),s&&i==="main-agent"){t=this.upsertSessionMcpServers(t,e,o),t=(0,n.removeAgentsTable)(t,k),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e),(0,n.writeCodexConfigToml)(e,t);return}if(s){const a=(0,c.getVerificationModel)(o,"codex"),l=(0,r.existsSync)((0,n.userCodexConfigTomlPath)())?(0,r.readFileSync)((0,n.userCodexConfigTomlPath)(),"utf-8"):"",p=(0,n.extractTomlTopLevelModel)(t)===null&&(0,n.extractTomlTopLevelModel)(l)===null;a===void 0&&p&&console.log(` ${d.pc.dim("\u2192")} ${h("[codex]")} ${d.pc.yellow("\u26A0 no model for the verifier")} \u2014 the ${d.pc.bold("ironbee-verifier")} sub-agent inherits the session model, but neither this project's .codex/config.toml nor ~/.codex/config.toml has a top-level ${d.pc.bold("model")}, so it may fail to spawn ("could not resolve the child model"). Fix: set ${d.pc.bold("model")} in ~/.codex/config.toml, or set ${d.pc.bold("verification.model")} in your ironbee config.`),this.writeVerifierAgentToml(e,o,a),t=(0,n.upsertAgentsTable)(t,k,[`description = ${JSON.stringify(V)}`,`config_file = ${JSON.stringify(`agents/${k}.toml`)}`]),t=(0,n.ensureMultiAgentV2SpawnMetadataExposed)(t)}else t=(0,n.removeAgentsTable)(t,k),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e);(0,n.writeCodexConfigToml)(e,t)}writeVerifierAgentToml(e,o,s){const i=(0,g.join)(__dirname,"agents",`${k}.md`);let t;try{t=(0,r.readFileSync)(i,"utf-8")}catch(u){b.logger.debug(`failed to read verifier agent source ${i}: ${u}`);return}const a=A("codex");for(const u of c.ALL_CYCLES){const S=(0,c.isCycleEnabled)(o,u)?H=>{const $=(0,g.join)(a,(0,C.fragmentFilename)("skill",u,H));return(0,r.existsSync)($)?(0,r.readFileSync)($,"utf-8").trimEnd():null}:null;t=(0,C.applyPlatformSection)(t,u,S,`${k}.toml`)}const l=[];l.push(`name = ${JSON.stringify(k)}`),l.push(`description = ${JSON.stringify(V)}`),l.push('sandbox_mode = "read-only"'),s&&l.push(`model = ${JSON.stringify(s)}`),l.push("developer_instructions = '''"),l.push(t.replace(/'''/g,"```").trimEnd()),l.push("'''");const p=y((u,m,S)=>{u&&(l.push(""),l.push(`[mcp_servers.${m}]`),l.push(...N(S)),l.push(`startup_timeout_sec = ${R}`),l.push("required = true"),l.push('default_tools_approval_mode = "approve"'))},"addCycle");p((0,c.isCycleEnabled)(o,"browser"),x,(0,c.getMcpServerEntry)(e)),p((0,c.isCycleEnabled)(o,"node"),w,(0,c.getNodeDevToolsMcpEntry)(e)),p((0,c.isCycleEnabled)(o,"backend"),E,(0,c.getBackendDevToolsMcpEntry)(e)),p((0,c.isCycleEnabled)(o,"android"),T,(0,c.getAndroidDevToolsMcpEntry)(e));const v=(0,n.codexAgentTomlPath)(e,k);(0,r.mkdirSync)((0,g.dirname)(v),{recursive:!0}),(0,r.writeFileSync)(v,l.join(`
2
2
  `)+`
3
- `)}removeVerifierAgentToml(o){const n=(0,t.codexAgentTomlPath)(o,k);if((0,i.existsSync)(n))try{(0,i.unlinkSync)(n)}catch(r){f.logger.debug(`failed to remove verifier agent toml: ${r}`)}}removeIronBeeMcpServers(o){let n=(0,t.readCodexConfigToml)(o);n&&(n=(0,t.removeMcpServer)(n,T),n=(0,t.removeMcpServer)(n,w),n=(0,t.removeMcpServer)(n,A),n=(0,t.removeMcpServer)(n,_),n=(0,t.removeAgentsTable)(n,k),n=(0,t.removeMultiAgentV2SpawnMetadata)(n),(0,t.writeCodexConfigToml)(o,n))}migrateAwayFromUserLevel(){const o=(0,t.userCodexHooksJsonPath)();this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o);const n=(0,t.userCodexConfigTomlPath)();if((0,i.existsSync)(n))try{let e=(0,i.readFileSync)(n,"utf-8");const s=e;e=(0,t.removeMcpServer)(e,T),e=(0,t.removeMcpServer)(e,w),e=(0,t.removeMcpServer)(e,A),e=(0,t.removeMcpServer)(e,_),e=(0,t.removeAgentsTable)(e,k),e=(0,t.removeMultiAgentV2SpawnMetadata)(e),e!==s&&(0,i.writeFileSync)(n,e)}catch(e){f.logger.debug(`migrate: failed to clean user-level config.toml: ${e}`)}const r=(0,t.userCodexAgentTomlPath)(k);if((0,i.existsSync)(r))try{(0,i.unlinkSync)(r)}catch(e){f.logger.debug(`migrate: failed to remove user-level verifier toml: ${e}`)}}writeAgentsMdBlock(o,n){const r=(0,a.join)(o,"AGENTS.md"),e=(0,a.join)(__dirname,"rules","ironbee-verification.md");let s;try{s=(0,i.readFileSync)(e,"utf-8")}catch(m){f.logger.debug(`failed to read rule source ${e}: ${m}`);return}const c=H("codex");for(const m of u.ALL_CYCLES){const y=(0,u.isCycleEnabled)(n,m)?S=>{const C=(0,a.join)(c,(0,$.fragmentFilename)("rule",m,S));if(!(0,i.existsSync)(C)){const x=S.length>0?`${m}:${S}`:m;return f.logger.debug(`AGENTS.md platform-section ${x}: missing fragment ${C}, using placeholder`),null}return(0,i.readFileSync)(C,"utf-8").trimEnd()}:null;s=(0,$.applyPlatformSection)(s,m,y,"AGENTS.md")}const d=(0,i.existsSync)(r)?(0,i.readFileSync)(r,"utf-8"):"",p=(0,t.upsertAgentsMdBlock)(d,s);(0,i.writeFileSync)(r,p)}writeSkills(o,n){const r=(0,a.join)(o,".agents","skills");if(n){const c=(0,a.join)(r,"ironbee-verification");(0,i.mkdirSync)(c,{recursive:!0});const d=(0,a.join)(__dirname,"skills","ironbee-verification.md");try{const p=(0,i.readFileSync)(d,"utf-8");(0,i.writeFileSync)((0,a.join)(c,"SKILL.md"),p)}catch(p){f.logger.debug(`failed to copy skill ${d}: ${p}`)}}const e=(0,a.join)(r,"ironbee-verify");(0,i.mkdirSync)(e,{recursive:!0});const s=(0,a.join)(__dirname,"commands","ironbee-verify","SKILL.md");try{const c=(0,i.readFileSync)(s,"utf-8");(0,i.writeFileSync)((0,a.join)(e,"SKILL.md"),c)}catch(c){f.logger.debug(`failed to copy verify command ${s}: ${c}`)}}removeDir(o){if((0,i.existsSync)(o))try{(0,i.rmSync)(o,{recursive:!0,force:!0})}catch(n){f.logger.debug(`failed to remove ${o}: ${n}`)}}}function io(g){return(0,t.tomlBodyFromRecord)(g)}h(io,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
3
+ `)}upsertSessionMcpServers(e,o,s){let i=e;const t=y((a,l,p)=>{if(!a)return;const v=[...N(p),`startup_timeout_sec = ${R}`,'default_tools_approval_mode = "approve"'];i=(0,n.upsertMcpServer)(i,l,v)},"addCycle");return t((0,c.isCycleEnabled)(s,"browser"),x,(0,c.getMcpServerEntry)(o)),t((0,c.isCycleEnabled)(s,"node"),w,(0,c.getNodeDevToolsMcpEntry)(o)),t((0,c.isCycleEnabled)(s,"backend"),E,(0,c.getBackendDevToolsMcpEntry)(o)),t((0,c.isCycleEnabled)(s,"android"),T,(0,c.getAndroidDevToolsMcpEntry)(o)),i}removeVerifierAgentToml(e){const o=(0,n.codexAgentTomlPath)(e,k);if((0,r.existsSync)(o))try{(0,r.unlinkSync)(o)}catch(s){b.logger.debug(`failed to remove verifier agent toml: ${s}`)}}removeIronBeeMcpServers(e){let o=(0,n.readCodexConfigToml)(e);o&&(o=(0,n.removeMcpServer)(o,x),o=(0,n.removeMcpServer)(o,w),o=(0,n.removeMcpServer)(o,E),o=(0,n.removeMcpServer)(o,T),o=(0,n.removeAgentsTable)(o,k),o=(0,n.removeMultiAgentV2SpawnMetadata)(o),(0,n.writeCodexConfigToml)(e,o))}migrateAwayFromUserLevel(){const e=(0,n.userCodexHooksJsonPath)();this.removeIronBeeHooks(e),this.maybeDeleteEmptyHooks(e);const o=(0,n.userCodexConfigTomlPath)();if((0,r.existsSync)(o))try{let i=(0,r.readFileSync)(o,"utf-8");const t=i;i=(0,n.removeMcpServer)(i,x),i=(0,n.removeMcpServer)(i,w),i=(0,n.removeMcpServer)(i,E),i=(0,n.removeMcpServer)(i,T),i=(0,n.removeAgentsTable)(i,k),i=(0,n.removeMultiAgentV2SpawnMetadata)(i),i!==t&&(0,r.writeFileSync)(o,i)}catch(i){b.logger.debug(`migrate: failed to clean user-level config.toml: ${i}`)}const s=(0,n.userCodexAgentTomlPath)(k);if((0,r.existsSync)(s))try{(0,r.unlinkSync)(s)}catch(i){b.logger.debug(`migrate: failed to remove user-level verifier toml: ${i}`)}}writeAgentsMdBlock(e,o,s){const i=(0,g.join)(e,"AGENTS.md"),t=s==="main-agent"?"ironbee-verification.main.md":"ironbee-verification.md",a=(0,g.join)(__dirname,"rules",t);let l;try{l=(0,r.readFileSync)(a,"utf-8")}catch(m){b.logger.debug(`failed to read rule source ${a}: ${m}`);return}const p=A("codex");for(const m of c.ALL_CYCLES){const H=(0,c.isCycleEnabled)(o,m)?$=>{const I=(0,g.join)(p,(0,C.fragmentFilename)("rule",m,$));if(!(0,r.existsSync)(I)){const Q=$.length>0?`${m}:${$}`:m;return b.logger.debug(`AGENTS.md platform-section ${Q}: missing fragment ${I}, using placeholder`),null}return(0,r.readFileSync)(I,"utf-8").trimEnd()}:null;l=(0,C.applyPlatformSection)(l,m,H,"AGENTS.md")}const v=(0,r.existsSync)(i)?(0,r.readFileSync)(i,"utf-8"):"",u=(0,n.upsertAgentsMdBlock)(v,l);(0,r.writeFileSync)(i,u)}writeSkills(e,o,s,i){const t=(0,g.join)(e,".agents","skills"),a=i==="main-agent";if(o){const v=(0,g.join)(t,"ironbee-verification");(0,r.mkdirSync)(v,{recursive:!0});const u=(0,g.join)(__dirname,"skills",a?"ironbee-verification.main.md":"ironbee-verification.md");try{let m=(0,r.readFileSync)(u,"utf-8");a&&(m=this.spliceCycleFragments(m,"skill",s,"ironbee-verification/SKILL.md")),(0,r.writeFileSync)((0,g.join)(v,"SKILL.md"),m)}catch(m){b.logger.debug(`failed to copy skill ${u}: ${m}`)}}const l=(0,g.join)(t,"ironbee-verify");(0,r.mkdirSync)(l,{recursive:!0});const p=(0,g.join)(__dirname,"commands","ironbee-verify",a?"SKILL.main.md":"SKILL.md");try{let v=(0,r.readFileSync)(p,"utf-8");a&&(v=this.spliceCycleFragments(v,"command-verify",s,"ironbee-verify/SKILL.md")),(0,r.writeFileSync)((0,g.join)(l,"SKILL.md"),v)}catch(v){b.logger.debug(`failed to copy verify command ${p}: ${v}`)}}spliceCycleFragments(e,o,s,i){const t=A("codex");let a=e;for(const l of c.ALL_CYCLES){const v=(0,c.isCycleEnabled)(s,l)?u=>{const m=(0,g.join)(t,(0,C.fragmentFilename)(o,l,u));return(0,r.existsSync)(m)?(0,r.readFileSync)(m,"utf-8").trimEnd():null}:null;a=(0,C.applyPlatformSection)(a,l,v,i)}return a}removeDir(e){if((0,r.existsSync)(e))try{(0,r.rmSync)(e,{recursive:!0,force:!0})}catch(o){b.logger.debug(`failed to remove ${e}: ${o}`)}}}function N(f){return(0,n.tomlBodyFromRecord)(f)}y(N,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
@@ -0,0 +1,39 @@
1
+ You MUST verify all code changes before completing any task — by driving the IronBee verification tools yourself. This project runs IronBee in **main-agent** mode: the devtools tools (`mcp__browser-devtools__*` / `mcp__node-devtools__*` / `mcp__backend-devtools__*` / `mcp__android-devtools__*`) are wired into THIS session. There is no verifier sub-agent — you verify inline.
2
+
3
+ Your **Session ID** is injected into your context by the SessionStart hook (a `Session ID: <sid>` line); author it in every `ironbee hook` command's JSON.
4
+
5
+ After editing code (`apply_patch`), before reporting completion:
6
+
7
+ 1. **Start verification — run this ALONE first** (Codex runs shell and MCP tools in separate lanes, so the open must land before any devtools call):
8
+ ```
9
+ echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start
10
+ ```
11
+ 2. Build and start the app if it isn't already running (don't guess ports). Track what YOU started.
12
+ 3. **Drive every active cycle's tools** (browser / runtime / backend / android, as wired up for this project — see the platform sections below) to exercise what changed. Reading code is NOT verification.
13
+ 4. Tear down only what you started, then **submit one verdict**:
14
+ ```
15
+ echo '{"session_id":"...","status":"pass","checks":["..."]}' | ironbee hook submit-verdict
16
+ ```
17
+ On fail add `"issues":[...]`; on pass-after-fail add `"fixes":[...]`. Nothing to verify → N/A (`"status":"not_applicable","reason":[...]`, or per-platform `"not_applicable_cycles":[...]`).
18
+ 5. If verification FAILS: submit the fail verdict first, then fix the issues, then re-verify (back to step 1) until it passes.
19
+
20
+ Every `apply_patch` clears the verdict, requiring re-verification. The Stop gate blocks completion until a valid verdict exists for your changes and the required tools were called for every active cycle.
21
+
22
+ <!--IRONBEE:PLATFORM:browser-->
23
+ <!--/IRONBEE:PLATFORM:browser-->
24
+
25
+ <!--IRONBEE:PLATFORM:node-->
26
+ <!--/IRONBEE:PLATFORM:node-->
27
+
28
+ <!--IRONBEE:PLATFORM:backend-->
29
+ <!--/IRONBEE:PLATFORM:backend-->
30
+
31
+ <!--IRONBEE:PLATFORM:android-->
32
+ <!--/IRONBEE:PLATFORM:android-->
33
+
34
+ ## BANNED
35
+
36
+ - Reporting a task complete without verifying your changes through the real tools.
37
+ - Submitting a verdict based on assumptions, code reading, or prior knowledge — verify through real tools.
38
+ - Writing `verdict.json` directly (always use `ironbee hook submit-verdict`).
39
+ - Submitting `pass` when your own evidence shows a legitimate, in-scope operation breaking — that is a FALSE pass; fail it.
@@ -8,7 +8,7 @@ The Stop gate blocks completion until a verdict exists for your changes — dele
8
8
 
9
9
  ## BANNED
10
10
 
11
- - Running the verification tools (`bdt_*` / `ndt_*` / `bedt_*`) or `ironbee hook verification-start` / `submit-verdict` yourself — those are the verifier's job. Delegate.
11
+ - Running the verification tools (`bdt_*` / `ndt_*` / `bedt_*`) or `ironbee hook verification-start` / `submit-verdict` yourself — those are the verifier's job. Delegate. The verifier already submitted the single verdict in this shared session; you only RELAY it in text. Re-running `submit-verdict` yourself is REJECTED ("no active verification cycle" — the cycle already closed) and records nothing — a duplicate, not a verdict.
12
12
  - Using the generic `spawn_agent` tool / a plain fork to "be" the verifier — that spawns a DEFAULT agent without the devtools. Spawn the `ironbee-verifier` custom agent by its `agent_type`.
13
13
  - Reporting a task complete without delegating verification of your changes.
14
14
  - Submitting a verdict based on assumptions, code reading, or prior knowledge — the verifier verifies through real tools.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: ironbee-verification
3
+ description: >
4
+ MANDATORY verification after code changes. Activates when implementing features, fixing
5
+ bugs, modifying UI components, API endpoints, styles, refactoring, or any task that changes
6
+ application behavior. After editing code you MUST verify the affected cycle(s) through real
7
+ tools and submit a single verdict (pass or fail) before reporting task completion. You drive
8
+ the verification tools yourself — they are wired into this session.
9
+ ---
10
+
11
+ # IronBee Verification
12
+
13
+ > **You verify INLINE.** This project runs IronBee in **main-agent** mode: the devtools tools
14
+ > (`mcp__browser-devtools__*` / `mcp__node-devtools__*` / `mcp__backend-devtools__*` /
15
+ > `mcp__android-devtools__*`) are wired into THIS session — you drive them yourself. There is no
16
+ > verifier sub-agent to delegate to.
17
+
18
+ ## Rule
19
+ No task is complete until your changes are verified through **real tools**, not by reading code or
20
+ inferring behavior. After verifying, you MUST submit a verdict (pass or fail) before doing
21
+ anything else. If verification fails, submit the fail verdict first, then fix and re-verify.
22
+
23
+ ## Session id
24
+ IronBee's SessionStart hook injects your **Session ID** into your context at the top of the
25
+ session (a line `Session ID: <sid>`). Author it in every `ironbee hook` command's JSON.
26
+
27
+ ## Cycles
28
+ IronBee runs verification in **cycles**. A single Stop hook can drive multiple cycles in
29
+ parallel — every active cycle must pass for your task to complete. You don't choose which cycle
30
+ runs — the edited file's path decides (pattern match). **See the platform sections near the
31
+ bottom of this file** for which cycles are active for this project, the tools they expose, and
32
+ the per-cycle flow.
33
+
34
+ ## Universal flow
35
+
36
+ 1. Finish your code edits (`apply_patch`).
37
+ 2. **Start verification — run this ALONE first**, before any devtools call:
38
+ ```
39
+ echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start
40
+ ```
41
+ Devtools tools are blocked until this opens the cycle. **Codex runs shell commands and MCP
42
+ tools in separate lanes**, so a same-message ordering of this shell command before a devtools
43
+ call is NOT guaranteed — a devtools call that lands first is blocked. Once the cycle is open,
44
+ independent MCP calls can ride one message.
45
+ 3. Build and start the application **only if it isn't already running** (check `docker compose ps`
46
+ / process output / config — don't guess ports). Track whether YOU started it.
47
+ 4. **Run the per-cycle flows for every active cycle** (see the platform sections below). All
48
+ active cycles must be exercised within this one verification cycle.
49
+ 5. **Teardown** — stop ONLY what you started (the dev server / container you launched for this
50
+ verification); never stop a server that was already running. Honor any cycle-specific teardown
51
+ (e.g. stop an active screen recording) BEFORE the verdict.
52
+ 6. **Submit your verdict immediately** — do NOT edit any code first:
53
+ ```
54
+ echo '<verdict-json>' | ironbee hook submit-verdict
55
+ ```
56
+ - Platform-agnostic shape: `session_id`, `status`, `checks`, optionally `issues` / `fixes`.
57
+ One verdict regardless of how many cycles ran.
58
+ - Pass → `{ "session_id": "...", "status": "pass", "checks": [...] }`
59
+ - Fail → add `"issues": [...]` describing what failed.
60
+ - Pass after a previous fail → add `"fixes": [...]` describing what was repaired.
61
+ - **A FALSE failure is a FAIL — not "verified failure handling".** Separate an EXPECTED
62
+ negative test (you deliberately fed invalid input and it correctly failed → supports a
63
+ `pass`) from a FALSE failure (a VALID, in-scope operation that SHOULD succeed but errors out
64
+ → a DEFECT → `status: "fail"`).
65
+ - **Nothing to verify? Use N/A — never fake evidence.** When the change has no runtime surface
66
+ (type-only edit, behavior-neutral refactor, config/docs that still tripped a cycle): global
67
+ `{ "session_id": "...", "status": "not_applicable", "reason": ["why"] }` (no `checks`), or
68
+ per-platform on a pass/fail verdict `"not_applicable_cycles": ["browser"], "reason": ["..."]`
69
+ to exempt some cycles while verifying others. `reason` is REQUIRED. Strict mode rejects N/A.
70
+ Base "nothing to verify" on the FULL change set (the change is often already COMMITTED) —
71
+ check `git diff HEAD~1 HEAD --stat`, not just a clean `git status`.
72
+ 7. If failed → fix → rebuild → go back to step 2 → repeat until pass (up to the retry cap).
73
+
74
+ ## Speed — batch your tool calls (fewer LLM round-trips)
75
+
76
+ Each tool call is a separate LLM round-trip, and that round-trip — not the tool's execution — is
77
+ the dominant cost. Drive the tools in as few turns as you can:
78
+
79
+ - **Batch a scope's work into ONE `*_execute` call.** Each cycle exposes a batch tool
80
+ (`mcp__browser-devtools__bdt_execute` / `mcp__node-devtools__ndt_execute` /
81
+ `mcp__backend-devtools__bedt_execute` / `mcp__android-devtools__adt_execute`) that runs many
82
+ steps in one turn — nest each as a `callTool('<tool>', { … })`. A batch nests only that cycle's
83
+ own tools. It's a JS sandbox, so a later step can reuse a value an earlier `callTool` returned;
84
+ `*_execute` STOPS at the first failing nested call. Authoring the batch is not the work — read
85
+ each result and confirm real evidence came back.
86
+ - **Discovery stays standalone** — the step that reveals what to do (navigate / connect / snapshot)
87
+ runs first and on its own; THEN batch the actions it told you to take.
88
+
89
+ <!--IRONBEE:PLATFORM:browser-->
90
+ <!--/IRONBEE:PLATFORM:browser-->
91
+
92
+ <!--IRONBEE:PLATFORM:node-->
93
+ <!--/IRONBEE:PLATFORM:node-->
94
+
95
+ <!--IRONBEE:PLATFORM:backend-->
96
+ <!--/IRONBEE:PLATFORM:backend-->
97
+
98
+ <!--IRONBEE:PLATFORM:android-->
99
+ <!--/IRONBEE:PLATFORM:android-->
100
+
101
+ ## Important
102
+ - **Always submit a verdict after every verification attempt** — both pass AND fail.
103
+ - Submit verdicts via `ironbee hook submit-verdict`, never write `verdict.json` directly.
104
+ - Every `apply_patch` edit automatically clears your session's verdict — re-verify after edits.
105
+ - After the retry cap, you may complete but must report unresolved issues.
106
+
107
+ ## BANNED
108
+ - Reporting a task complete without verifying your changes through the real tools.
109
+ - Claiming verification passed based on code reading, assumptions, or prior knowledge.
110
+ - Submitting `pass` when your own evidence shows a legitimate operation breaking.