@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/analytics/claude/pricing.js +1 -1
  3. package/dist/clients/claude/agents/ironbee-verifier.md +12 -2
  4. package/dist/clients/claude/commands/ironbee-verify.md +28 -15
  5. package/dist/clients/claude/hooks/activity-start.js +1 -1
  6. package/dist/clients/claude/hooks/require-verdict.js +2 -2
  7. package/dist/clients/claude/hooks/require-verification.js +7 -7
  8. package/dist/clients/claude/hooks/track-action.js +1 -1
  9. package/dist/clients/claude/index.js +4 -4
  10. package/dist/clients/claude/platforms/skill.android.md +65 -0
  11. package/dist/clients/codex/agents/ironbee-verifier.md +11 -1
  12. package/dist/clients/codex/commands/ironbee-verify/SKILL.md +24 -5
  13. package/dist/clients/codex/hooks/require-verification.js +7 -7
  14. package/dist/clients/codex/hooks/track-action.js +1 -1
  15. package/dist/clients/codex/index.js +2 -2
  16. package/dist/clients/codex/platforms/command-verify.android.md +61 -0
  17. package/dist/clients/codex/platforms/rule.android.md +32 -0
  18. package/dist/clients/codex/platforms/skill.android.md +55 -0
  19. package/dist/clients/codex/skills/ironbee-verification.md +3 -0
  20. package/dist/clients/codex/util.js +11 -11
  21. package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +21 -4
  22. package/dist/clients/cursor/hooks/require-verdict.js +1 -1
  23. package/dist/clients/cursor/hooks/require-verification.js +6 -6
  24. package/dist/clients/cursor/hooks/track-action.js +1 -1
  25. package/dist/clients/cursor/index.js +1 -1
  26. package/dist/clients/cursor/platforms/command-verify.android.md +61 -0
  27. package/dist/clients/cursor/platforms/rule.android.md +32 -0
  28. package/dist/clients/cursor/platforms/skill.android.md +55 -0
  29. package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
  30. package/dist/clients/cursor/skills/ironbee-verification.md +3 -0
  31. package/dist/commands/android.js +1 -0
  32. package/dist/commands/config.js +2 -2
  33. package/dist/commands/cycle-toggle.js +4 -4
  34. package/dist/commands/hook.js +16 -15
  35. package/dist/commands/install.js +1 -1
  36. package/dist/commands/mode-select.js +2 -0
  37. package/dist/hooks/core/actions.js +6 -5
  38. package/dist/hooks/core/session-state.js +1 -1
  39. package/dist/hooks/core/submit-verdict.js +4 -4
  40. package/dist/hooks/core/verification-lifecycle.js +1 -1
  41. package/dist/hooks/core/verify-gate.js +29 -23
  42. package/dist/import/claude/events/tool-call.js +1 -1
  43. package/dist/import/codex/events/tool-call.js +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/lib/config.js +1 -1
  46. package/dist/lib/install-version.js +1 -0
  47. package/dist/lib/platform-section.js +4 -3
  48. package/dist/lib/prompt.js +4 -4
  49. package/dist/lib/recording-tools.js +1 -0
  50. package/dist/lib/schema-sync.js +2 -0
  51. package/dist/lib/version.js +1 -1
  52. package/dist/scripts/postinstall.js +1 -1
  53. package/dist/tui/config/schema.js +1 -1
  54. package/dist/tui/platforms/area.js +2 -2
  55. package/dist/tui/projects/area.js +4 -4
  56. package/dist/tui/sessions/area.js +3 -3
  57. package/package.json +1 -1
@@ -1 +1 @@
1
- "use strict";var v=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var m=(e,t)=>v(e,"name",{value:t,configurable:!0});var q=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},G=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of P(t))!U.call(e,s)&&s!==n&&v(e,s,{get:()=>t[s],enumerable:!(o=L(t,s))||o.enumerable});return e};var H=e=>G(v({},"__esModule",{value:!0}),e);var Z={};q(Z,{run:()=>X});module.exports=H(Z);var T=require("../../../hooks/core/actions"),I=require("../../../import/ids"),r=require("../../../hooks/core/session-state"),z=require("../../../hooks/core/tool-use-stash"),D=require("../../../lib/config"),_=require("../../../lib/logger"),E=require("../../../lib/output"),J=require("../../../lib/stdin"),p=require("../../../queue"),i=require("../util");const V="bdt_content_start-recording",Q="bdt_content_stop-recording";function x(e){if(e==null)return 0;if(typeof e=="string")try{return Buffer.byteLength(e,"utf8")}catch{return 0}try{return Buffer.byteLength(JSON.stringify(e),"utf8")}catch{return 0}}m(x,"safeStringifyBytes");function Y(e){if(e==null)return{isError:!1,errorText:void 0};if(typeof e=="object"&&e!==null){const t=e;if(t.isError===!0||t.is_error===!0){const n=t.error??t.message??t.errorMessage;return{isError:!0,errorText:typeof n=="string"?n:JSON.stringify(t).slice(0,500)}}}if(typeof e=="string"){const t=e;if(/(?:^|\n)Process exited with code [1-9]/.test(t)||/^Exit code:\s*[1-9]/m.test(t)||/apply_patch verification failed/i.test(t)||/failed to find expected lines/i.test(t)||/^\s*Error\b/.test(t)||/(?:^|\n)\[Request interrupted by user\]/.test(t)||/modified since (?:last )?read|stale read/i.test(t)||/file (?:is )?too large|exceeds/i.test(t)||/file not found|No such file or directory|does not exist/i.test(t))return{isError:!0,errorText:t.slice(0,500)}}return{isError:!1,errorText:void 0}}m(Y,"detectFailure");function K(e){if(e===null||typeof e!="object")return;const t=e._metadata;if(t===null||typeof t!="object")return;const n=t.toolCallId;if(typeof n=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(n))return n}m(K,"extractMetadataToolCallId");function W(e,t){const n=(0,z.consumeToolUseData)(e,t);if(!n?.start_ns)return null;try{const o=process.hrtime.bigint()-BigInt(n.start_ns);return Number(o/1000000n)}catch(o){return _.logger.debug(`failed to derive duration from stash: ${o}`),null}}m(W,"deriveDurationMs");async function X(e){const t=(0,i.parseCodexHookStdin)((0,J.readStdin)()),n=t.session_id??"default",o=`${e}/.ironbee/sessions/${n}`,s=`${o}/actions.jsonl`;(0,_.setLogFile)(`${o}/session.log`);const f=t.tool_name??"",d=t.tool_use_id??"",u=t.tool_input,C=u&&typeof u=="object"?{...u,_metadata:void 0}:void 0,w=t.tool_response,b=(0,i.extractCodexMcpServer)(f),B=b==="browser-devtools"||b==="node-devtools"||b==="backend-devtools",k=W(n,d),g=Y(w),a=(0,i.classifyCodexTool)(f);if(B){const y=a.tool_name;y===V?(0,r.setRecordingActive)(o,!0):y===Q&&(0,r.setRecordingActive)(o,!1);const h=(0,r.getActiveActivityId)(o),c={...(0,T.baseFields)(s),type:"tool_call",timestamp:Date.now(),tool_type:a.tool_type,tool_name:a.tool_name,mcp_server:a.mcp_server??b,tool_input:C,tool_input_size:x(C),tool_response_size:g.isError?0:x(w),duration:k};h&&(c.activity_id=h);const N=K(u);N!==void 0?c.id=N:d.length>0&&(c.id=(0,I.deriveToolCallEventIdFromToolUseId)(n,d)),d&&(c.tool_use_id=d);const $=(0,r.getActiveVerificationId)(o);$&&(c.verification_id=$);const O=(0,r.getActiveTraceId)(o);O&&(c.trace_id=O),g.isError&&(c.error=g.errorText),await(0,T.appendAction)(s,c),(0,E.writeAndExit)(JSON.stringify({}),0);return}if(!(0,D.isJobQueueEnabled)(e)){(0,E.writeAndExit)(JSON.stringify({}),0);return}const S=(0,r.getActiveActivityId)(o),F=(0,i.extractCodexToolInput)(f,u),M=x(u),j=g.isError?0:x(w),l={...(0,T.baseFields)(s),type:"tool_call",timestamp:Date.now(),tool_type:a.tool_type,tool_name:a.tool_name||(0,i.normalizeCodexToolName)(f),mcp_server:a.mcp_server,tool_input:F,tool_input_size:M,tool_response_size:j,duration:k};S&&(l.activity_id=S),d.length>0&&(l.id=(0,I.deriveToolCallEventIdFromToolUseId)(n,d)),d&&(l.tool_use_id=d);const A=(0,r.getActiveVerificationId)(o);A&&(l.verification_id=A);const R=(0,r.getActiveTraceId)(o);R&&(l.trace_id=R),g.isError&&(l.error=g.errorText);try{(0,p.submit)(e,n,p.SEND_EVENT_TYPE,l)}catch(y){y instanceof p.JobTooLargeError?_.logger.debug(`track-action: wire event too large for tool_call ${f}; dropping`):_.logger.debug(`queue submit failed for tool_call ${f}: ${y}`)}(0,E.writeAndExit)(JSON.stringify({}),0)}m(X,"run");0&&(module.exports={run});
1
+ "use strict";var v=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var p=(t,e)=>v(t,"name",{value:e,configurable:!0});var V=(t,e)=>{for(var o in e)v(t,o,{get:e[o],enumerable:!0})},Q=(t,e,o,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of q(e))!H.call(t,s)&&s!==o&&v(t,s,{get:()=>e[s],enumerable:!(n=U(e,s))||n.enumerable});return t};var Y=t=>Q(v({},"__esModule",{value:!0}),t);var Z={};V(Z,{run:()=>X});module.exports=Y(Z);var b=require("../../../hooks/core/actions"),k=require("../../../import/ids"),r=require("../../../hooks/core/session-state"),F=require("../../../hooks/core/tool-use-stash"),D=require("../../../lib/config"),_=require("../../../lib/logger"),w=require("../../../lib/output"),M=require("../../../lib/recording-tools"),O=require("../../../lib/stdin"),y=require("../../../queue"),i=require("../util");function x(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}}p(x,"safeStringifyBytes");function G(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}}p(G,"detectFailure");function K(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}p(K,"extractMetadataToolCallId");function W(t,e){const o=(0,F.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 _.logger.debug(`failed to derive duration from stash: ${n}`),null}}p(W,"deriveDurationMs");async function X(t){const e=(0,i.parseCodexHookStdin)((0,O.readStdin)()),o=e.session_id??"default",n=`${t}/.ironbee/sessions/${o}`,s=`${n}/actions.jsonl`;(0,_.setLogFile)(`${n}/session.log`);const f=e.tool_name??"",d=e.tool_use_id??"",u=e.tool_input,I=u&&typeof u=="object"?{...u,_metadata:void 0}:void 0,C=e.tool_response,g=(0,i.extractCodexMcpServer)(f),j=g==="browser-devtools"||g==="node-devtools"||g==="backend-devtools"||g==="android-devtools",S=W(o,d),m=G(C),c=(0,i.classifyCodexTool)(f);if(j){const T=c.tool_name,E=(0,M.recordingToolsForServer)(g);E!==null&&(T===E.startTool?(0,r.setRecordingActive)(n,!0):T===E.stopTool&&(0,r.setRecordingActive)(n,!1));const R=(0,r.getActiveActivityId)(n),l={...(0,b.baseFields)(s),type:"tool_call",timestamp:Date.now(),tool_type:c.tool_type,tool_name:c.tool_name,mcp_server:c.mcp_server??g,tool_input:I,tool_input_size:x(I),tool_response_size:m.isError?0:x(C),duration:S};R&&(l.activity_id=R);const $=K(u);$!==void 0?l.id=$:d.length>0&&(l.id=(0,k.deriveToolCallEventIdFromToolUseId)(o,d)),d&&(l.tool_use_id=d);const z=(0,r.getActiveVerificationId)(n);z&&(l.verification_id=z);const J=(0,r.getActiveTraceId)(n);J&&(l.trace_id=J),m.isError&&(l.error=m.errorText),await(0,b.appendAction)(s,l),(0,w.writeAndExit)(JSON.stringify({}),0);return}if(!(0,D.isJobQueueEnabled)(t)){(0,w.writeAndExit)(JSON.stringify({}),0);return}const h=(0,r.getActiveActivityId)(n),B=(0,i.extractCodexToolInput)(f,u),L=x(u),P=m.isError?0:x(C),a={...(0,b.baseFields)(s),type:"tool_call",timestamp:Date.now(),tool_type:c.tool_type,tool_name:c.tool_name||(0,i.normalizeCodexToolName)(f),mcp_server:c.mcp_server,tool_input:B,tool_input_size:L,tool_response_size:P,duration:S};h&&(a.activity_id=h),d.length>0&&(a.id=(0,k.deriveToolCallEventIdFromToolUseId)(o,d)),d&&(a.tool_use_id=d);const A=(0,r.getActiveVerificationId)(n);A&&(a.verification_id=A);const N=(0,r.getActiveTraceId)(n);N&&(a.trace_id=N),m.isError&&(a.error=m.errorText);try{(0,y.submit)(t,o,y.SEND_EVENT_TYPE,a)}catch(T){T instanceof y.JobTooLargeError?_.logger.debug(`track-action: wire event too large for tool_call ${f}; dropping`):_.logger.debug(`queue submit failed for tool_call ${f}: ${T}`)}(0,w.writeAndExit)(JSON.stringify({}),0)}p(X,"run");0&&(module.exports={run});
@@ -1,3 +1,3 @@
1
- "use strict";var w=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var h=(u,o)=>w(u,"name",{value:o,configurable:!0});var z=(u,o)=>{for(var e in o)w(u,e,{get:o[e],enumerable:!0})},Q=(u,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of W(o))!Y.call(u,n)&&n!==e&&w(u,n,{get:()=>o[n],enumerable:!(r=X(o,n))||r.enumerable});return u};var Z=u=>Q(w({},"__esModule",{value:!0}),u);var to={};z(to,{CodexClient:()=>eo});module.exports=Z(to);var i=require("fs"),a=require("path"),M=require("../../lib/gitignore"),f=require("../../lib/logger"),l=require("../../lib/output"),P=require("../../lib/fs-prune"),g=require("../../lib/config"),$=require("../../lib/platform-section"),t=require("./util"),_=require("./thread-map"),B=require("./hooks/verify-gate"),N=require("./hooks/activity-end"),O=require("./hooks/session-start"),V=require("./hooks/activity-start"),G=require("./hooks/require-verification"),J=require("./hooks/require-verdict"),L=require("./hooks/clear-verdict"),F=require("./hooks/track-action"),K=require("./hooks/track-action-monitor"),U=require("./hooks/track-action-pre"),q=require("./hooks/subagent-start"),D=require("./hooks/subagent-stop");const E="browser-devtools",T="node-devtools",A="backend-devtools",j="ironbee",p="ironbee-verifier",R="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(u){return(0,a.join)(__dirname,"..",u,"platforms")}h(H,"platformsDirFor");function b(u){return l.pc.dim(u)}h(b,"codexColor");function I(u){return u.hooks.some(o=>o.command.includes(j))}h(I,"isIronBeeHookGroup");function oo(u){const o=Object.keys(u);return o.length===0?!0:o.length===1&&o[0]==="hooks"?Object.keys(u.hooks??{}).length===0:!1}h(oo,"isCodexHooksEmpty");class eo{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,e){const r=e??(0,g.loadConfig)(o),n=(0,g.getVerificationMode)(r),s=n!=="monitor";this.cleanupArtifacts(o);const c=(0,t.codexHooksJsonPath)(o);this.mergeHooksConfig(c,n),this.mergeConfigToml(o,r,s),s&&(n==="enforce"&&this.writeAgentsMdBlock(o,r),this.writeSkills(o,n==="enforce"),(0,$.syncPlatformSectionsToConfig)(o,H)),(0,M.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))}`),n==="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"))}`)):n==="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 e=(0,_.codexThreadMapPath)(o);if((0,i.existsSync)(e))try{(0,i.unlinkSync)(e)}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 e=(0,t.codexHooksJsonPath)(o);this.removeIronBeeHooks(e),this.maybeDeleteEmptyHooks(e),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 n=(0,a.join)(o,".agents","skills");this.removeDir((0,a.join)(n,"ironbee-verification")),this.removeDir((0,a.join)(n,"ironbee-verify")),(0,P.pruneEmptyDirs)((0,a.join)(o,".agents"))}async runVerifyGate(o){await(0,B.run)(o)}async runActivityEnd(o){await(0,N.run)(o)}async runSessionStart(o){await(0,O.run)(o)}async runActivityStart(o){await(0,V.run)(o)}async runRequireVerification(o,e){await(0,G.run)(o,e)}async runRequireVerdict(o,e){await(0,J.run)(o,e)}async runClearVerdict(o){await(0,L.run)(o)}async runTrackAction(o){await(0,F.run)(o)}async runTrackActionMonitor(o){await(0,K.run)(o)}async runTrackActionPre(o){await(0,U.run)(o)}async runSubagentStart(o){await(0,q.run)(o)}async runSubagentStop(o){await(0,D.run)(o)}resolveAgentSessionId(o,e){const r=process.env.CODEX_THREAD_ID;if(typeof r=="string"&&r.length>0&&e)return(0,_.lookupThreadSession)(e,r)}async runSessionEnd(o){f.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(o,e){const r=e!=="monitor",n=e==="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=>!I(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)[-_]devtools__.*",`ironbee hook require-verification --client codex${n}`),c("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${n}`),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",".*",e==="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 e=(0,i.readFileSync)(o,"utf-8"),r=JSON.parse(e);if(!r.hooks)return;let n=!1;for(const s of Object.keys(r.hooks)){const c=r.hooks[s].filter(d=>!I(d));c.length!==r.hooks[s].length&&(n=!0),c.length===0?delete r.hooks[s]:r.hooks[s]=c}n&&(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}catch(e){f.logger.debug(`failed to strip IronBee hooks from ${o}: ${e}`)}}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));oo(e)&&(0,i.unlinkSync)(o)}catch(e){f.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}mergeConfigToml(o,e,r){(0,i.mkdirSync)((0,a.join)(o,".codex"),{recursive:!0});let n=(0,t.readCodexConfigToml)(o);if(n=(0,t.ensureFeaturesHooksTrue)(n),n=(0,t.removeMcpServer)(n,E),n=(0,t.removeMcpServer)(n,T),n=(0,t.removeMcpServer)(n,A),r){const s=(0,g.getVerificationModel)(e,"codex"),c=(0,i.existsSync)((0,t.userCodexConfigTomlPath)())?(0,i.readFileSync)((0,t.userCodexConfigTomlPath)(),"utf-8"):"",d=(0,t.extractTomlTopLevelModel)(n)===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,e,s),n=(0,t.upsertAgentsTable)(n,p,[`description = ${JSON.stringify(R)}`,`config_file = ${JSON.stringify(`agents/${p}.toml`)}`])}else n=(0,t.removeAgentsTable)(n,p),this.removeVerifierAgentToml(o);(0,t.writeCodexConfigToml)(o,n)}writeVerifierAgentToml(o,e,r){const n=(0,a.join)(__dirname,"agents",`${p}.md`);let s;try{s=(0,i.readFileSync)(n,"utf-8")}catch(v){f.logger.debug(`failed to read verifier agent source ${n}: ${v}`);return}const c=H("codex");for(const v of g.ALL_CYCLES){const S=(0,g.isCycleEnabled)(e,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,`${p}.toml`)}const d=[];d.push(`name = ${JSON.stringify(p)}`),d.push(`description = ${JSON.stringify(R)}`),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 k=h((v,y,S)=>{v&&(d.push(""),d.push(`[mcp_servers.${y}]`),d.push(...no(S)),d.push("required = true"),d.push('default_tools_approval_mode = "approve"'))},"addCycle");k((0,g.isCycleEnabled)(e,"browser"),E,(0,g.getMcpServerEntry)(o)),k((0,g.isCycleEnabled)(e,"node"),T,(0,g.getNodeDevToolsMcpEntry)(o)),k((0,g.isCycleEnabled)(e,"backend"),A,(0,g.getBackendDevToolsMcpEntry)(o));const m=(0,t.codexAgentTomlPath)(o,p);(0,i.mkdirSync)((0,a.dirname)(m),{recursive:!0}),(0,i.writeFileSync)(m,d.join(`
1
+ "use strict";var E=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var h=(u,o)=>E(u,"name",{value:o,configurable:!0});var Q=(u,o)=>{for(var e in o)E(u,e,{get:o[e],enumerable:!0})},Z=(u,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of Y(o))!z.call(u,n)&&n!==e&&E(u,n,{get:()=>o[n],enumerable:!(r=W(o,n))||r.enumerable});return u};var j=u=>Z(E({},"__esModule",{value:!0}),u);var io={};Q(io,{CodexClient:()=>no});module.exports=j(io);var i=require("fs"),a=require("path"),B=require("../../lib/gitignore"),f=require("../../lib/logger"),l=require("../../lib/output"),P=require("../../lib/fs-prune"),d=require("../../lib/config"),$=require("../../lib/platform-section"),t=require("./util"),H=require("./thread-map"),N=require("./hooks/verify-gate"),O=require("./hooks/activity-end"),V=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 w="browser-devtools",T="node-devtools",A="backend-devtools",_="android-devtools",oo="ironbee",k="ironbee-verifier",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 R(u){return(0,a.join)(__dirname,"..",u,"platforms")}h(R,"platformsDirFor");function b(u){return l.pc.dim(u)}h(b,"codexColor");function M(u){return u.hooks.some(o=>o.command.includes(oo))}h(M,"isIronBeeHookGroup");function eo(u){const o=Object.keys(u);return o.length===0?!0:o.length===1&&o[0]==="hooks"?Object.keys(u.hooks??{}).length===0:!1}h(eo,"isCodexHooksEmpty");class no{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,e){const r=e??(0,d.loadConfig)(o),n=(0,d.getVerificationMode)(r),s=n!=="monitor";this.cleanupArtifacts(o);const c=(0,t.codexHooksJsonPath)(o);this.mergeHooksConfig(c,n),this.mergeConfigToml(o,r,s),s&&(n==="enforce"&&this.writeAgentsMdBlock(o,r),this.writeSkills(o,n==="enforce"),(0,$.syncPlatformSectionsToConfig)(o,R)),(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))}`),n==="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"))}`)):n==="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 e=(0,H.codexThreadMapPath)(o);if((0,i.existsSync)(e))try{(0,i.unlinkSync)(e)}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 e=(0,t.codexHooksJsonPath)(o);this.removeIronBeeHooks(e),this.maybeDeleteEmptyHooks(e),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 n=(0,a.join)(o,".agents","skills");this.removeDir((0,a.join)(n,"ironbee-verification")),this.removeDir((0,a.join)(n,"ironbee-verify")),(0,P.pruneEmptyDirs)((0,a.join)(o,".agents"))}async runVerifyGate(o){await(0,N.run)(o)}async runActivityEnd(o){await(0,O.run)(o)}async runSessionStart(o){await(0,V.run)(o)}async runActivityStart(o){await(0,G.run)(o)}async runRequireVerification(o,e){await(0,J.run)(o,e)}async runRequireVerdict(o,e){await(0,L.run)(o,e)}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,e){const r=process.env.CODEX_THREAD_ID;if(typeof r=="string"&&r.length>0&&e)return(0,H.lookupThreadSession)(e,r)}async runSessionEnd(o){f.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(o,e){const r=e!=="monitor",n=e==="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${n}`),c("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${n}`),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",".*",e==="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 e=(0,i.readFileSync)(o,"utf-8"),r=JSON.parse(e);if(!r.hooks)return;let n=!1;for(const s of Object.keys(r.hooks)){const c=r.hooks[s].filter(g=>!M(g));c.length!==r.hooks[s].length&&(n=!0),c.length===0?delete r.hooks[s]:r.hooks[s]=c}n&&(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}catch(e){f.logger.debug(`failed to strip IronBee hooks from ${o}: ${e}`)}}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));eo(e)&&(0,i.unlinkSync)(o)}catch(e){f.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}mergeConfigToml(o,e,r){(0,i.mkdirSync)((0,a.join)(o,".codex"),{recursive:!0});let n=(0,t.readCodexConfigToml)(o);if(n=(0,t.ensureFeaturesHooksTrue)(n),n=(0,t.removeMcpServer)(n,w),n=(0,t.removeMcpServer)(n,T),n=(0,t.removeMcpServer)(n,A),n=(0,t.removeMcpServer)(n,_),r){const s=(0,d.getVerificationModel)(e,"codex"),c=(0,i.existsSync)((0,t.userCodexConfigTomlPath)())?(0,i.readFileSync)((0,t.userCodexConfigTomlPath)(),"utf-8"):"",g=(0,t.extractTomlTopLevelModel)(n)===null&&(0,t.extractTomlTopLevelModel)(c)===null;s===void 0&&g&&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,e,s),n=(0,t.upsertAgentsTable)(n,k,[`description = ${JSON.stringify(I)}`,`config_file = ${JSON.stringify(`agents/${k}.toml`)}`])}else n=(0,t.removeAgentsTable)(n,k),this.removeVerifierAgentToml(o);(0,t.writeCodexConfigToml)(o,n)}writeVerifierAgentToml(o,e,r){const n=(0,a.join)(__dirname,"agents",`${k}.md`);let s;try{s=(0,i.readFileSync)(n,"utf-8")}catch(v){f.logger.debug(`failed to read verifier agent source ${n}: ${v}`);return}const c=R("codex");for(const v of d.ALL_CYCLES){const S=(0,d.isCycleEnabled)(e,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 g=[];g.push(`name = ${JSON.stringify(k)}`),g.push(`description = ${JSON.stringify(I)}`),g.push('sandbox_mode = "read-only"'),r&&g.push(`model = ${JSON.stringify(r)}`),g.push("developer_instructions = '''"),g.push(s.replace(/'''/g,"```").trimEnd()),g.push("'''");const p=h((v,y,S)=>{v&&(g.push(""),g.push(`[mcp_servers.${y}]`),g.push(...to(S)),g.push("required = true"),g.push('default_tools_approval_mode = "approve"'))},"addCycle");p((0,d.isCycleEnabled)(e,"browser"),w,(0,d.getMcpServerEntry)(o)),p((0,d.isCycleEnabled)(e,"node"),T,(0,d.getNodeDevToolsMcpEntry)(o)),p((0,d.isCycleEnabled)(e,"backend"),A,(0,d.getBackendDevToolsMcpEntry)(o)),p((0,d.isCycleEnabled)(e,"android"),_,(0,d.getAndroidDevToolsMcpEntry)(o));const m=(0,t.codexAgentTomlPath)(o,k);(0,i.mkdirSync)((0,a.dirname)(m),{recursive:!0}),(0,i.writeFileSync)(m,g.join(`
2
2
  `)+`
3
- `)}removeVerifierAgentToml(o){const e=(0,t.codexAgentTomlPath)(o,p);if((0,i.existsSync)(e))try{(0,i.unlinkSync)(e)}catch(r){f.logger.debug(`failed to remove verifier agent toml: ${r}`)}}removeIronBeeMcpServers(o){let e=(0,t.readCodexConfigToml)(o);e&&(e=(0,t.removeMcpServer)(e,E),e=(0,t.removeMcpServer)(e,T),e=(0,t.removeMcpServer)(e,A),e=(0,t.removeAgentsTable)(e,p),(0,t.writeCodexConfigToml)(o,e))}migrateAwayFromUserLevel(){const o=(0,t.userCodexHooksJsonPath)();this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o);const e=(0,t.userCodexConfigTomlPath)();if((0,i.existsSync)(e))try{let n=(0,i.readFileSync)(e,"utf-8");const s=n;n=(0,t.removeMcpServer)(n,E),n=(0,t.removeMcpServer)(n,T),n=(0,t.removeMcpServer)(n,A),n=(0,t.removeAgentsTable)(n,p),n!==s&&(0,i.writeFileSync)(e,n)}catch(n){f.logger.debug(`migrate: failed to clean user-level config.toml: ${n}`)}const r=(0,t.userCodexAgentTomlPath)(p);if((0,i.existsSync)(r))try{(0,i.unlinkSync)(r)}catch(n){f.logger.debug(`migrate: failed to remove user-level verifier toml: ${n}`)}}writeAgentsMdBlock(o,e){const r=(0,a.join)(o,"AGENTS.md"),n=(0,a.join)(__dirname,"rules","ironbee-verification.md");let s;try{s=(0,i.readFileSync)(n,"utf-8")}catch(m){f.logger.debug(`failed to read rule source ${n}: ${m}`);return}const c=H("codex");for(const m of g.ALL_CYCLES){const y=(0,g.isCycleEnabled)(e,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"):"",k=(0,t.upsertAgentsMdBlock)(d,s);(0,i.writeFileSync)(r,k)}writeSkills(o,e){const r=(0,a.join)(o,".agents","skills");if(e){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 k=(0,i.readFileSync)(d,"utf-8");(0,i.writeFileSync)((0,a.join)(c,"SKILL.md"),k)}catch(k){f.logger.debug(`failed to copy skill ${d}: ${k}`)}}const n=(0,a.join)(r,"ironbee-verify");(0,i.mkdirSync)(n,{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)(n,"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(e){f.logger.debug(`failed to remove ${o}: ${e}`)}}}function no(u){return(0,t.tomlBodyFromRecord)(u)}h(no,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
3
+ `)}removeVerifierAgentToml(o){const e=(0,t.codexAgentTomlPath)(o,k);if((0,i.existsSync)(e))try{(0,i.unlinkSync)(e)}catch(r){f.logger.debug(`failed to remove verifier agent toml: ${r}`)}}removeIronBeeMcpServers(o){let e=(0,t.readCodexConfigToml)(o);e&&(e=(0,t.removeMcpServer)(e,w),e=(0,t.removeMcpServer)(e,T),e=(0,t.removeMcpServer)(e,A),e=(0,t.removeMcpServer)(e,_),e=(0,t.removeAgentsTable)(e,k),(0,t.writeCodexConfigToml)(o,e))}migrateAwayFromUserLevel(){const o=(0,t.userCodexHooksJsonPath)();this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o);const e=(0,t.userCodexConfigTomlPath)();if((0,i.existsSync)(e))try{let n=(0,i.readFileSync)(e,"utf-8");const s=n;n=(0,t.removeMcpServer)(n,w),n=(0,t.removeMcpServer)(n,T),n=(0,t.removeMcpServer)(n,A),n=(0,t.removeMcpServer)(n,_),n=(0,t.removeAgentsTable)(n,k),n!==s&&(0,i.writeFileSync)(e,n)}catch(n){f.logger.debug(`migrate: failed to clean user-level config.toml: ${n}`)}const r=(0,t.userCodexAgentTomlPath)(k);if((0,i.existsSync)(r))try{(0,i.unlinkSync)(r)}catch(n){f.logger.debug(`migrate: failed to remove user-level verifier toml: ${n}`)}}writeAgentsMdBlock(o,e){const r=(0,a.join)(o,"AGENTS.md"),n=(0,a.join)(__dirname,"rules","ironbee-verification.md");let s;try{s=(0,i.readFileSync)(n,"utf-8")}catch(m){f.logger.debug(`failed to read rule source ${n}: ${m}`);return}const c=R("codex");for(const m of d.ALL_CYCLES){const y=(0,d.isCycleEnabled)(e,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 g=(0,i.existsSync)(r)?(0,i.readFileSync)(r,"utf-8"):"",p=(0,t.upsertAgentsMdBlock)(g,s);(0,i.writeFileSync)(r,p)}writeSkills(o,e){const r=(0,a.join)(o,".agents","skills");if(e){const c=(0,a.join)(r,"ironbee-verification");(0,i.mkdirSync)(c,{recursive:!0});const g=(0,a.join)(__dirname,"skills","ironbee-verification.md");try{const p=(0,i.readFileSync)(g,"utf-8");(0,i.writeFileSync)((0,a.join)(c,"SKILL.md"),p)}catch(p){f.logger.debug(`failed to copy skill ${g}: ${p}`)}}const n=(0,a.join)(r,"ironbee-verify");(0,i.mkdirSync)(n,{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)(n,"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(e){f.logger.debug(`failed to remove ${o}: ${e}`)}}}function to(u){return(0,t.tomlBodyFromRecord)(u)}h(to,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
@@ -0,0 +1,61 @@
1
+ <!-- Android mobile verification is ENABLED for this project. -->
2
+
3
+ ## Android Mode (when `android.verifyPatterns` matches an edited file)
4
+
5
+ > **Precondition: the project must actually target Android.** If there is no `android/` directory or `AndroidManifest.xml`, this section does NOT apply — `adt_*` tools require a connected Android device or emulator. Just do browser verification.
6
+
7
+ If the project has android verification enabled (`ironbee android enable` once at setup) and your edits touch matching paths, the Stop hook also enforces an Android cycle. The same `verification-start` covers both cycles; one platform-agnostic verdict covers both.
8
+
9
+ ### Mode behavior (android cycle)
10
+ - **default** (no arg or `default`): exercise only the UI flows / code paths your diff touched.
11
+ - **full**: exercise every Android code path reachable from files matching `android.verifyPatterns`.
12
+ - `visual` / `functional`: browser-only modes; android cycle behaves as `default` when they are passed.
13
+
14
+ ### Steps (run within step 3 of the Universal steps above)
15
+ 1. **If `recording.enable` is on, start a screen recording first**: `mcp__android-devtools__adt_content_start-recording` — the gate blocks every other android tool until it runs.
16
+ 2. **Connect to a running device or emulator**: `mcp__android-devtools__adt_device_connect`.
17
+ 3. **Launch the app** (or bring it to the relevant screen): `mcp__android-devtools__adt_device_launch-app`.
18
+ 4. **Pick an evidence path** for the changed code:
19
+ - **Device-evidence** (proves the change is visible / functional): drive UI (`mcp__android-devtools__adt_interaction_tap` / `mcp__android-devtools__adt_interaction_input-text` / `mcp__android-devtools__adt_interaction_swipe`) → screenshot (`mcp__android-devtools__adt_content_take-screenshot`) → UI snapshot (`mcp__android-devtools__adt_a11y_take-ui-snapshot`). **STOP and visually analyze the screenshot** — readability, layout, cut-off content, expected state rendered; the snapshot reports structure, the screenshot shows what the user actually sees. Both are MANDATORY on this path.
20
+ - **Log-evidence** (proves the changed code path executed): `mcp__android-devtools__adt_o11y_log-read` or `mcp__android-devtools__adt_o11y_log-follow`. Confirm expected log lines present AND no FATAL/crash from the app package.
21
+ 5. **If recording was started, stop it now** — `mcp__android-devtools__adt_content_stop-recording`. submit-verdict rejects with `"recording is still active"` when this step is skipped.
22
+ 6. **Submit verdict** — platform-agnostic, just status + checks (+ issues/fixes).
23
+
24
+ ### Verdict (platform-agnostic)
25
+ ```json
26
+ {
27
+ "session_id": "...",
28
+ "status": "pass",
29
+ "checks": ["LoginActivity renders after auth refactor", "no crash in Logcat"]
30
+ }
31
+ ```
32
+
33
+ For a multi-cycle pass, both browser and android pass criteria must hold.
34
+
35
+ ---
36
+
37
+ ## Default Mode (android cycle)
38
+
39
+ Focus on the UI flows or code paths your diff touched — not the entire app.
40
+
41
+ ### 1. Study the changes
42
+ 1. Run `git diff --name-only` and `git diff --name-only HEAD~1`
43
+ 2. **Ignore `.ironbee/`, `.claude/`, `.cursor/`** — tool config, not application code
44
+ 3. **Read the full diff** for every Android file in scope — note new UI elements, changed layouts, new logic branches, changed Logcat tags
45
+ 4. Before connecting, identify: which Activity / Fragment / Composable is affected? Which UI actions exercise it? Which Logcat tags emit on that path?
46
+
47
+ ### 2. Verify against the running device
48
+ - **Connect + launch the app** on the device/emulator
49
+ - **Drive the affected UI flow** — tap/type/swipe to reach the changed screen
50
+ - **Take a screenshot + a UI snapshot** — the screenshot must show the expected UI state after your change; the UI snapshot must show the expected view hierarchy / labels
51
+ - **Check Logcat** for the relevant tag(s) — expected log lines must appear; no FATAL or unhandled exception from the app package
52
+
53
+ ---
54
+
55
+ ## Full Mode (`$ironbee-verify full`, android cycle)
56
+
57
+ Verify every Android code path reachable from files matching `android.verifyPatterns`, not just the changed files. Do NOT run `git diff` or scope to recent changes.
58
+
59
+ - Launch every Activity / Fragment / screen in scope
60
+ - Drive at least one happy-path flow AND one error-path flow per screen
61
+ - Screenshot + UI snapshot each screen; no FATAL entries in Logcat
@@ -0,0 +1,32 @@
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
+ ## Android cycle
6
+
7
+ Android file changes IF the file matches `android.verifyPatterns` ALSO require verification through the **android-devtools** MCP server (prefix `adt_`). Android-cycle verification means connecting to a running device or emulator, driving the app UI to exercise the changed code and taking a screenshot — OR reading Logcat to confirm the changed code path executed without crashes.
8
+
9
+ Both cycles can be active simultaneously (e.g. you edit both a React component and a Kotlin activity in the same task). One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
10
+
11
+ ### ⚠️ `android-devtools` is ONLY for Android apps
12
+
13
+ `android-devtools` works with Android devices and emulators via ADB. It does NOT work for web-only, iOS-only, or desktop projects. If the project has no `android/` directory or `AndroidManifest.xml`, do NOT call `adt_*` tools.
14
+
15
+ **Misconfiguration recovery.** If you reach this state, the operator enabled the android cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` for the android cycle. Don't attempt the connection. Instead, stop and clearly report to the user: the project does not target Android; ask them to run `ironbee android disable` to unblock the gate.
16
+
17
+ ### Android-cycle additions to the main flow
18
+
19
+ These attach to the **Required steps** above — they don't replace any step. Numbering follows the main flow:
20
+
21
+ - **Within step 3 (run flow):** also run the android flow: connect (`adt_device_connect`) → launch app (`adt_device_launch-app`) → pick ONE evidence path:
22
+ - **Device-evidence**: drive UI (`adt_interaction_tap` / `adt_interaction_input-text` / `adt_interaction_swipe`) + screenshot (`adt_content_take-screenshot`) + UI snapshot (`adt_a11y_take-ui-snapshot`)
23
+ - **Log-evidence**: read Logcat (`adt_o11y_log-read` or `adt_o11y_log-follow`) confirming expected output and no crashes
24
+ - If `recording.enable` is on, the gate forces `adt_content_start-recording` BEFORE the android steps above and rejects the verdict if you don't call `adt_content_stop-recording` AFTER them. Always pair start/stop around the steps above.
25
+ - **Within step 6 (submit verdict):** submit one platform-agnostic verdict with `status` + `checks` (+ `issues`/`fixes` as needed). Android-cycle pass criteria: device connected AND (UI-interaction + screenshot + UI snapshot taken) OR (Logcat read with no crashes).
26
+
27
+ ### Additional BANNED for android cycle
28
+
29
+ - Calling `adt_*` tools without first opening a verification cycle (`ironbee hook verification-start`).
30
+ - **Calling `adt_*` tools when the project does NOT target Android.** Use the browser cycle only for web-only projects.
31
+ - Claiming `status: pass` for an android cycle when no evidence path was exercised.
32
+ - **Submitting a verdict while an android recording is still active** — always call `adt_content_stop-recording` BEFORE `ironbee hook submit-verdict`. The recording start/stop pair brackets the verification flow.
@@ -0,0 +1,55 @@
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
+ Both cycles can be active simultaneously. One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
@@ -67,6 +67,9 @@ If already running, skip start. If the build fails, fix it before proceeding.
67
67
  <!--IRONBEE:PLATFORM:backend-->
68
68
  <!--/IRONBEE:PLATFORM:backend-->
69
69
 
70
+ <!--IRONBEE:PLATFORM:android-->
71
+ <!--/IRONBEE:PLATFORM:android-->
72
+
70
73
  ## Important
71
74
  - **Always submit a verdict after every verification attempt** — both pass AND fail. Fail verdicts are tracked for analytics.
72
75
  - The Stop hook checks that the required tools were used for every active cycle and that the verdict carries non-empty `checks`.
@@ -1,27 +1,27 @@
1
- "use strict";var y=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var o=(n,t)=>y(n,"name",{value:t,configurable:!0});var W=(n,t)=>{for(var e in t)y(n,e,{get:t[e],enumerable:!0})},J=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of E(t))!P.call(n,r)&&r!==e&&y(n,r,{get:()=>t[r],enumerable:!(s=I(t,r))||s.enumerable});return n};var L=n=>J(y({},"__esModule",{value:!0}),n);var gn={};W(gn,{AGENTS_MD_END_MARKER:()=>h,AGENTS_MD_START_MARKER:()=>w,canonicalizeCodexServerName:()=>$,canonicalizeCodexToolName:()=>v,classifyCodexTool:()=>F,codexAgentTomlPath:()=>nn,codexConfigTomlPath:()=>R,codexHooksJsonPath:()=>cn,decodeJwtPayload:()=>A,ensureFeaturesHooksTrue:()=>Z,extractBashBinary:()=>j,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>D,extractTomlTopLevelModel:()=>tn,findTomlSection:()=>k,normalizeCodexToolName:()=>T,parseCodexHookStdin:()=>M,readCodexConfigToml:()=>on,removeAgentsTable:()=>N,removeMcpServer:()=>Q,resolveCodexUsage:()=>V,stripAgentsMdBlock:()=>sn,tomlBodyFromRecord:()=>en,upsertAgentsMdBlock:()=>rn,upsertAgentsTable:()=>Y,upsertMcpServer:()=>q,userCodexAgentTomlPath:()=>an,userCodexConfigTomlPath:()=>ln,userCodexHooksJsonPath:()=>dn,writeCodexConfigToml:()=>un});module.exports=L(gn);var m=require("fs"),x=require("os"),p=require("path"),b=require("../../lib/logger");function M(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(M,"parseCodexHookStdin");const _="mcp__",B={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools"},z=["bdt_","ndt_","bedt_"];function $(n){return B[n]??n}o($,"canonicalizeCodexServerName");function v(n){if(!z.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(v,"canonicalizeCodexToolName");const H=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"]];function S(n){if(!n)return null;if(n.startsWith(_)){const t=n.slice(_.length),e=t.indexOf("__");return e<0?null:$(t.slice(0,e))}for(const[t,e]of H)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function T(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(T,"normalizeCodexToolName");function F(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(_)){const s=n.slice(_.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),c=$(i),u=s.slice(r+2);return{tool_type:"mcp",tool_name:v(u),mcp_server:c}}}const t=S(n);if(t!==null&&!n.startsWith(_))return{tool_type:"mcp",tool_name:v(n),mcp_server:t};const e=T(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(F,"classifyCodexTool");function D(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(T(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?j(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,c=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof c=="boolean"?c:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,c=e.yield_time_ms,u=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof c=="number"?c:void 0,max_output_tokens:typeof u=="number"?u:void 0}}if(n.startsWith(_)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(D,"extractCodexToolInput");function j(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(j,"extractBashBinary");function A(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(A,"decodeJwtPayload");function K(n){if(typeof n=="string"){const t=A(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(K,"extractIdTokenFields");function V(n){const t=n??(0,p.join)((0,x.homedir)(),".codex","auth.json");if(!(0,m.existsSync)(t))return{};try{const e=JSON.parse((0,m.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=K(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(V,"resolveCodexUsage");function U(n,t){return n.trim()===`[${t}]`}o(U,"tableHeaderLineExact");function X(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(X,"isAnyTableHeader");function O(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(O,"tableHeaderName");function k(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(U(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(X(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(k,"findTomlSection");function G(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(G,"trimTrailingBlanks");function C(n,t){return n.length===0?t.join(`
1
+ "use strict";var y=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var o=(n,t)=>y(n,"name",{value:t,configurable:!0});var W=(n,t)=>{for(var e in t)y(n,e,{get:t[e],enumerable:!0})},J=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of E(t))!P.call(n,r)&&r!==e&&y(n,r,{get:()=>t[r],enumerable:!(s=I(t,r))||s.enumerable});return n};var L=n=>J(y({},"__esModule",{value:!0}),n);var gn={};W(gn,{AGENTS_MD_END_MARKER:()=>h,AGENTS_MD_START_MARKER:()=>w,canonicalizeCodexServerName:()=>$,canonicalizeCodexToolName:()=>v,classifyCodexTool:()=>F,codexAgentTomlPath:()=>nn,codexConfigTomlPath:()=>R,codexHooksJsonPath:()=>cn,decodeJwtPayload:()=>A,ensureFeaturesHooksTrue:()=>Z,extractBashBinary:()=>j,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>D,extractTomlTopLevelModel:()=>tn,findTomlSection:()=>k,normalizeCodexToolName:()=>T,parseCodexHookStdin:()=>M,readCodexConfigToml:()=>on,removeAgentsTable:()=>N,removeMcpServer:()=>Q,resolveCodexUsage:()=>V,stripAgentsMdBlock:()=>sn,tomlBodyFromRecord:()=>en,upsertAgentsMdBlock:()=>rn,upsertAgentsTable:()=>Y,upsertMcpServer:()=>q,userCodexAgentTomlPath:()=>an,userCodexConfigTomlPath:()=>dn,userCodexHooksJsonPath:()=>ln,writeCodexConfigToml:()=>un});module.exports=L(gn);var m=require("fs"),x=require("os"),p=require("path"),b=require("../../lib/logger");function M(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(M,"parseCodexHookStdin");const _="mcp__",B={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools",android_devtools:"android-devtools"},z=["bdt_","ndt_","bedt_","adt_"];function $(n){return B[n]??n}o($,"canonicalizeCodexServerName");function v(n){if(!z.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(v,"canonicalizeCodexToolName");const H=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"],["adt_","android-devtools"]];function S(n){if(!n)return null;if(n.startsWith(_)){const t=n.slice(_.length),e=t.indexOf("__");return e<0?null:$(t.slice(0,e))}for(const[t,e]of H)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function T(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(T,"normalizeCodexToolName");function F(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(_)){const s=n.slice(_.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),c=$(i),u=s.slice(r+2);return{tool_type:"mcp",tool_name:v(u),mcp_server:c}}}const t=S(n);if(t!==null&&!n.startsWith(_))return{tool_type:"mcp",tool_name:v(n),mcp_server:t};const e=T(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(F,"classifyCodexTool");function D(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(T(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?j(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,c=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof c=="boolean"?c:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,c=e.yield_time_ms,u=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof c=="number"?c:void 0,max_output_tokens:typeof u=="number"?u:void 0}}if(n.startsWith(_)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(D,"extractCodexToolInput");function j(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(j,"extractBashBinary");function A(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(A,"decodeJwtPayload");function K(n){if(typeof n=="string"){const t=A(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(K,"extractIdTokenFields");function V(n){const t=n??(0,p.join)((0,x.homedir)(),".codex","auth.json");if(!(0,m.existsSync)(t))return{};try{const e=JSON.parse((0,m.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=K(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(V,"resolveCodexUsage");function U(n,t){return n.trim()===`[${t}]`}o(U,"tableHeaderLineExact");function X(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(X,"isAnyTableHeader");function O(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(O,"tableHeaderName");function k(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(U(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(X(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(k,"findTomlSection");function G(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(G,"trimTrailingBlanks");function C(n,t){return n.length===0?t.join(`
2
2
  `)+`
3
3
  `:n.replace(/\n+$/,"")+`
4
4
 
5
5
  `+t.join(`
6
6
  `)+`
7
7
  `}o(C,"appendBlockWithSeparator");function Z(n){const t=n.split(`
8
- `),e=k(t,"features");if(e===null)return C(n,["[features]","hooks = true"]);const s=t.slice(e.startIdx+1,e.endIdx),r=/^\s*hooks\s*=/;let i=!1;for(let d=0;d<s.length;d+=1)if(r.test(s[d])){s[d]="hooks = true",i=!0;break}i||s.unshift("hooks = true");const c=G(s),a=[...t.slice(0,e.startIdx),t[e.startIdx],...c,...e.endIdx<t.length?[""]:[],...t.slice(e.endIdx)].join(`
8
+ `),e=k(t,"features");if(e===null)return C(n,["[features]","hooks = true"]);const s=t.slice(e.startIdx+1,e.endIdx),r=/^\s*hooks\s*=/;let i=!1;for(let l=0;l<s.length;l+=1)if(r.test(s[l])){s[l]="hooks = true",i=!0;break}i||s.unshift("hooks = true");const c=G(s),a=[...t.slice(0,e.startIdx),t[e.startIdx],...c,...e.endIdx<t.length?[""]:[],...t.slice(e.endIdx)].join(`
9
9
  `);return a.endsWith(`
10
10
  `)?a:a+`
11
11
  `}o(Z,"ensureFeaturesHooksTrue");function q(n,t,e){const s=`mcp_servers.${t}`,r=n.split(`
12
- `),i=k(r,s),u=[`[${s}]`,...e];if(i===null)return C(n,u);const a=r.slice(0,i.startIdx),d=r.slice(i.endIdx),l=[...a,...u,...d.length>0?[""]:[],...d].join(`
13
- `);return l.endsWith(`
14
- `)?l:l+`
12
+ `),i=k(r,s),u=[`[${s}]`,...e];if(i===null)return C(n,u);const a=r.slice(0,i.startIdx),l=r.slice(i.endIdx),d=[...a,...u,...l.length>0?[""]:[],...l].join(`
13
+ `);return d.endsWith(`
14
+ `)?d:d+`
15
15
  `}o(q,"upsertMcpServer");function Q(n,t){const e=`mcp_servers.${t}`,s=`${e}.`,r=n.split(`
16
- `),i=[];let c=!1,u=!1;for(const l of r){const g=O(l);if(g!==null&&(c=g===e||g.startsWith(s),c)){u=!0;continue}c||i.push(l)}if(!u)return n;const a=[];let d=!1;for(const l of i){const g=l.trim().length===0;g&&d||(a.push(l),d=g)}const f=a.join(`
16
+ `),i=[];let c=!1,u=!1;for(const d of r){const g=O(d);if(g!==null&&(c=g===e||g.startsWith(s),c)){u=!0;continue}c||i.push(d)}if(!u)return n;const a=[];let l=!1;for(const d of i){const g=d.trim().length===0;g&&l||(a.push(d),l=g)}const f=a.join(`
17
17
  `);return f.endsWith(`
18
18
  `)||f.length===0?f:f+`
19
19
  `}o(Q,"removeMcpServer");function Y(n,t,e){const s=`agents.${t}`,r=n.split(`
20
- `),i=k(r,s),u=[`[${s}]`,...e];if(i===null)return C(n,u);const a=r.slice(0,i.startIdx),d=r.slice(i.endIdx),l=[...a,...u,...d.length>0?[""]:[],...d].join(`
21
- `);return l.endsWith(`
22
- `)?l:l+`
20
+ `),i=k(r,s),u=[`[${s}]`,...e];if(i===null)return C(n,u);const a=r.slice(0,i.startIdx),l=r.slice(i.endIdx),d=[...a,...u,...l.length>0?[""]:[],...l].join(`
21
+ `);return d.endsWith(`
22
+ `)?d:d+`
23
23
  `}o(Y,"upsertAgentsTable");function N(n,t){const e=`agents.${t}`,s=`${e}.`,r=n.split(`
24
- `),i=[];let c=!1,u=!1;for(const l of r){const g=O(l);if(g!==null&&(c=g===e||g.startsWith(s),c)){u=!0;continue}c||i.push(l)}if(!u)return n;const a=[];let d=!1;for(const l of i){const g=l.trim().length===0;g&&d||(a.push(l),d=g)}const f=a.join(`
24
+ `),i=[];let c=!1,u=!1;for(const d of r){const g=O(d);if(g!==null&&(c=g===e||g.startsWith(s),c)){u=!0;continue}c||i.push(d)}if(!u)return n;const a=[];let l=!1;for(const d of i){const g=d.trim().length===0;g&&l||(a.push(d),l=g)}const f=a.join(`
25
25
  `);return f.endsWith(`
26
26
  `)||f.length===0?f:f+`
27
27
  `}o(N,"removeAgentsTable");function nn(n,t){return(0,p.join)(n,".codex","agents",`${t}.toml`)}o(nn,"codexAgentTomlPath");function tn(n){for(const t of n.split(`
@@ -35,4 +35,4 @@ ${h}`,s=n.indexOf(w),r=n.indexOf(h);if(s>=0&&r>s){const i=n.slice(0,s),c=n.slice
35
35
 
36
36
  `:"")+r;return i.trim().length===0?null:i.endsWith(`
37
37
  `)?i:i+`
38
- `}o(sn,"stripAgentsMdBlock");function on(n){const t=R(n);if(!(0,m.existsSync)(t))return"";try{return(0,m.readFileSync)(t,"utf-8")}catch(e){return b.logger.debug(`failed to read ${t}: ${e}`),""}}o(on,"readCodexConfigToml");function un(n,t){const e=R(n);try{(0,m.writeFileSync)(e,t)}catch(s){b.logger.debug(`failed to write ${e}: ${s}`)}}o(un,"writeCodexConfigToml");function R(n){return(0,p.join)(n,".codex","config.toml")}o(R,"codexConfigTomlPath");function cn(n){return(0,p.join)(n,".codex","hooks.json")}o(cn,"codexHooksJsonPath");function ln(){return(0,p.join)((0,x.homedir)(),".codex","config.toml")}o(ln,"userCodexConfigTomlPath");function dn(){return(0,p.join)((0,x.homedir)(),".codex","hooks.json")}o(dn,"userCodexHooksJsonPath");function an(n){return(0,p.join)((0,x.homedir)(),".codex","agents",`${n}.toml`)}o(an,"userCodexAgentTomlPath");0&&(module.exports={AGENTS_MD_END_MARKER,AGENTS_MD_START_MARKER,canonicalizeCodexServerName,canonicalizeCodexToolName,classifyCodexTool,codexAgentTomlPath,codexConfigTomlPath,codexHooksJsonPath,decodeJwtPayload,ensureFeaturesHooksTrue,extractBashBinary,extractCodexMcpServer,extractCodexToolInput,extractTomlTopLevelModel,findTomlSection,normalizeCodexToolName,parseCodexHookStdin,readCodexConfigToml,removeAgentsTable,removeMcpServer,resolveCodexUsage,stripAgentsMdBlock,tomlBodyFromRecord,upsertAgentsMdBlock,upsertAgentsTable,upsertMcpServer,userCodexAgentTomlPath,userCodexConfigTomlPath,userCodexHooksJsonPath,writeCodexConfigToml});
38
+ `}o(sn,"stripAgentsMdBlock");function on(n){const t=R(n);if(!(0,m.existsSync)(t))return"";try{return(0,m.readFileSync)(t,"utf-8")}catch(e){return b.logger.debug(`failed to read ${t}: ${e}`),""}}o(on,"readCodexConfigToml");function un(n,t){const e=R(n);try{(0,m.writeFileSync)(e,t)}catch(s){b.logger.debug(`failed to write ${e}: ${s}`)}}o(un,"writeCodexConfigToml");function R(n){return(0,p.join)(n,".codex","config.toml")}o(R,"codexConfigTomlPath");function cn(n){return(0,p.join)(n,".codex","hooks.json")}o(cn,"codexHooksJsonPath");function dn(){return(0,p.join)((0,x.homedir)(),".codex","config.toml")}o(dn,"userCodexConfigTomlPath");function ln(){return(0,p.join)((0,x.homedir)(),".codex","hooks.json")}o(ln,"userCodexHooksJsonPath");function an(n){return(0,p.join)((0,x.homedir)(),".codex","agents",`${n}.toml`)}o(an,"userCodexAgentTomlPath");0&&(module.exports={AGENTS_MD_END_MARKER,AGENTS_MD_START_MARKER,canonicalizeCodexServerName,canonicalizeCodexToolName,classifyCodexTool,codexAgentTomlPath,codexConfigTomlPath,codexHooksJsonPath,decodeJwtPayload,ensureFeaturesHooksTrue,extractBashBinary,extractCodexMcpServer,extractCodexToolInput,extractTomlTopLevelModel,findTomlSection,normalizeCodexToolName,parseCodexHookStdin,readCodexConfigToml,removeAgentsTable,removeMcpServer,resolveCodexUsage,stripAgentsMdBlock,tomlBodyFromRecord,upsertAgentsMdBlock,upsertAgentsTable,upsertMcpServer,userCodexAgentTomlPath,userCodexConfigTomlPath,userCodexHooksJsonPath,writeCodexConfigToml});
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ironbee-verify
3
- description: "Trigger verification of code changes — runs every active cycle wired up for this project (see the platform sections at the bottom of the file)."
3
+ description: "Trigger verification of code changes — runs every active cycle wired up for this project (see the platform sections at the bottom of the file). Default is verify-only (report the verdict and stop); a leading `fix` argument adds the fix-and-re-verify loop until pass."
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
@@ -8,9 +8,19 @@ disable-model-invocation: true
8
8
 
9
9
  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.
10
10
 
11
+ ## Mode
12
+
13
+ The FIRST whitespace-delimited token of whatever the user provided alongside this command selects the mode; everything after it is the scenario:
14
+
15
+ - `fix` → **verify-and-fix**: on a fail verdict, fix the reported issues, rebuild, and re-verify until the verdict passes.
16
+ - `report` → **verify-only** (the explicit form of the default).
17
+ - Anything else, or nothing → **verify-only** (default), and the WHOLE provided text is the scenario.
18
+
19
+ **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.
20
+
11
21
  ## Verification scenario
12
22
 
13
- 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.
23
+ 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**).
14
24
 
15
25
  - **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.
16
26
  - **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.
@@ -22,6 +32,8 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
22
32
  ## Universal steps
23
33
 
24
34
  1. **Start verification**: Run `echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start` via terminal.
35
+ **In fix mode**, add the intent flag so IronBee's completion gate enforces fix-until-pass:
36
+ `echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start --intent fix`
25
37
  2. **Build and start** the application if not already running.
26
38
  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.
27
39
  4. **Stop** the dev server when verification is complete (every cycle — including the final one).
@@ -29,7 +41,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
29
41
  6. **Submit your verdict** via terminal. One verdict covers every active cycle:
30
42
  - Pass: `echo '{"session_id":"...","status":"pass","checks":["..."]}' | ironbee hook submit-verdict`
31
43
  - Fail: `echo '{"session_id":"...","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict`
32
- 7. **If failed** → collect ALL issues first (finish testing every active cycle), submit one fail verdict with all issues, then fix everything, rebuild, and re-verify. Do not fix one issue at a time — batch fixes to avoid repeated build/restart cycles.
44
+ 7. **If failed** → collect ALL issues first (finish testing every active cycle) and submit ONE fail verdict with all issues. Then branch by mode:
45
+ - **Verify-only (default)**: report the issues to the user and stop — do not edit code. Suggest `/ironbee-verify fix` to repair them.
46
+ - **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.
33
47
  8. If pass after a previous fail, include `"fixes"` in the verdict describing what was fixed.
34
48
 
35
49
  ---
@@ -43,6 +57,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
43
57
  <!--IRONBEE:PLATFORM:backend-->
44
58
  <!--/IRONBEE:PLATFORM:backend-->
45
59
 
60
+ <!--IRONBEE:PLATFORM:android-->
61
+ <!--/IRONBEE:PLATFORM:android-->
62
+
46
63
  ---
47
64
 
48
65
  ## When to FAIL
@@ -51,7 +68,7 @@ If you observe ANY problem on any active cycle — wrong data, unexpected errors
51
68
 
52
69
  **Do NOT rationalize away problems.** If something looks wrong or behaves unexpectedly, it IS wrong.
53
70
 
54
- **After a fail verdict, you MUST fix the issues and re-verify.** Do not just report and stop.
71
+ **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.
55
72
 
56
73
  ## Verdict Quality
57
74
 
@@ -1,4 +1,4 @@
1
- "use strict";var l=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var _=(o,t)=>l(o,"name",{value:t,configurable:!0});var $=(o,t)=>{for(var i in t)l(o,i,{get:t[i],enumerable:!0})},x=(o,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of P(t))!U.call(o,s)&&s!==i&&l(o,s,{get:()=>t[s],enumerable:!(e=O(t,s))||e.enumerable});return o};var k=o=>x(l({},"__esModule",{value:!0}),o);var E={};$(E,{run:()=>I});module.exports=k(E);var c=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),d=require("../../../lib/config"),n=require("../../../lib/logger"),v=require("../../../lib/stdin");async function I(o,t){const i=t?.soft===!0;let e;try{e=JSON.parse((0,v.readStdin)())}catch(r){n.logger.debug(`failed to parse stdin: ${r}`);const u={permission:"allow"};process.stdout.write(JSON.stringify(u)),process.exit(0);return}const s=e.conversation_id??"default";(0,n.setLogFile)(`${o}/.ironbee/sessions/${s}/session.log`);const p=`${o}/.ironbee/sessions/${s}`,f=`${p}/actions.jsonl`;if(!i&&(0,b.hasToolCallsSinceLastVerdict)(f)){const r={permission:"deny",agent_message:`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
1
+ "use strict";var l=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var _=(o,t)=>l(o,"name",{value:t,configurable:!0});var $=(o,t)=>{for(var i in t)l(o,i,{get:t[i],enumerable:!0})},x=(o,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of P(t))!U.call(o,s)&&s!==i&&l(o,s,{get:()=>t[s],enumerable:!(e=O(t,s))||e.enumerable});return o};var k=o=>x(l({},"__esModule",{value:!0}),o);var E={};$(E,{run:()=>I});module.exports=k(E);var c=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),d=require("../../../lib/config"),n=require("../../../lib/logger"),v=require("../../../lib/stdin");async function I(o,t){const i=t?.soft===!0;let e;try{e=JSON.parse((0,v.readStdin)())}catch(r){n.logger.debug(`failed to parse stdin: ${r}`);const u={permission:"allow"};process.stdout.write(JSON.stringify(u)),process.exit(0);return}const s=e.conversation_id??"default";(0,n.setLogFile)(`${o}/.ironbee/sessions/${s}/session.log`);const p=`${o}/.ironbee/sessions/${s}`,f=`${p}/actions.jsonl`;if(!i&&(0,b.hasToolCallsSinceLastVerdict)(f)){const r={permission:"deny",agent_message:`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
@@ -1,15 +1,15 @@
1
- "use strict";var l=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var P=(t,o)=>l(t,"name",{value:o,configurable:!0});var B=(t,o)=>{for(var c in o)l(t,c,{get:o[c],enumerable:!0})},K=(t,o,c,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of V(o))!x.call(t,r)&&r!==c&&l(t,r,{get:()=>o[r],enumerable:!(s=N(o,r))||s.enumerable});return t};var j=t=>K(l({},"__esModule",{value:!0}),t);var L={};B(L,{run:()=>J});module.exports=j(L);var R=require("crypto"),e=require("../../../hooks/core/session-state"),I=require("../../../hooks/core/actions"),S=require("../../../hooks/core/activity"),h=require("../../../hooks/core/verification-lifecycle"),O=require("../../../lib/config"),f=require("../../../lib/logger"),U=require("../../../lib/stdin");const y={"MCP:bdt_":"browser-devtools","MCP:ndt_":"node-devtools","MCP:bedt_":"backend-devtools"},F="browser-devtools";async function J(t,o){const c=o?.soft===!0;let s;try{s=JSON.parse((0,U.readStdin)())}catch(n){f.logger.debug(`failed to parse stdin: ${n}`);const A={permission:"allow"};process.stdout.write(JSON.stringify(A)),process.exit(0);return}const r=s.conversation_id??"default",i=`${t}/.ironbee/sessions/${r}`;(0,f.setLogFile)(`${i}/session.log`);const m=`${i}/actions.jsonl`,v=(0,e.getActiveVerificationId)(i);if(!v&&!c){const n={permission:"deny",agent_message:`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools).
1
+ "use strict";var f=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var R=(t,o)=>f(t,"name",{value:o,configurable:!0});var F=(t,o)=>{for(var c in o)f(t,c,{get:o[c],enumerable:!0})},K=(t,o,c,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of x(o))!B.call(t,r)&&r!==c&&f(t,r,{get:()=>o[r],enumerable:!(s=V(o,r))||s.enumerable});return t};var j=t=>K(f({},"__esModule",{value:!0}),t);var W={};F(W,{run:()=>L});module.exports=j(W);var I=require("crypto"),e=require("../../../hooks/core/session-state"),h=require("../../../hooks/core/actions"),S=require("../../../hooks/core/activity"),O=require("../../../hooks/core/verification-lifecycle"),U=require("../../../lib/config"),E=require("../../../lib/recording-tools"),g=require("../../../lib/logger"),M=require("../../../lib/stdin");const T={"MCP:bdt_":"browser-devtools","MCP:ndt_":"node-devtools","MCP:bedt_":"backend-devtools","MCP:adt_":"android-devtools"},J="browser-devtools";async function L(t,o){const c=o?.soft===!0;let s;try{s=JSON.parse((0,M.readStdin)())}catch(n){g.logger.debug(`failed to parse stdin: ${n}`);const A={permission:"allow"};process.stdout.write(JSON.stringify(A)),process.exit(0);return}const r=s.conversation_id??"default",i=`${t}/.ironbee/sessions/${r}`;(0,g.setLogFile)(`${i}/session.log`);const C=`${i}/actions.jsonl`,P=(0,e.getActiveVerificationId)(i);if(!P&&!c){const n={permission:"deny",agent_message:`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":"${r}"}' | ironbee hook verification-start
5
5
 
6
- Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const g=s.tool_name??"",E=g.startsWith("MCP:bdt_"),k=g.endsWith("bdt_content_start-recording");if(!c&&E&&(0,e.isRecordingRequired)(i)&&!(0,e.isRecordingActive)(i)&&!k){const n={permission:"deny",agent_message:`BLOCKED: Recording is required but not started.
6
+ Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend, MCP:adt_* for android.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const p=s.tool_name??"",m=p.startsWith("MCP:")?p.slice(4):"",l=m?(0,E.recordingToolsForBareTool)(m):null;if(!c&&l!==null&&(0,e.isRecordingRequired)(i)&&!(0,e.isRecordingActive)(i)&&m!==l.startTool){const n={permission:"deny",agent_message:`BLOCKED: Recording is required but not started.
7
7
 
8
8
  1. Start recording NOW:
9
- Use MCP:bdt_content_start-recording
9
+ Use MCP:${l.startTool}
10
10
 
11
- 2. Run the verification (navigate, screenshot, aria, console, etc.)
11
+ 2. Run the verification flow for the active cycle(s)
12
12
 
13
13
  3. **Stop recording BEFORE submitting verdict:**
14
- Use MCP:bdt_content_stop-recording
15
- submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,S.startActivity)({sessionDir:i,actionsFile:m,source:"pre_tool_use"});let d=v;c&&!d&&(d=(await(0,h.startVerification)({sessionId:r,sessionDir:i,actionsFile:m,recordingEnabled:!1})).verificationId);const T=(0,e.getActiveTraceId)(i),p=(0,e.getActiveActivityId)(i),b=(0,I.resolveProjectName)(t),_=[`prj:${b}`,`sid:${r}`];p&&_.push(`aid:${p}`),d&&_.push(`vid:${d}`);const M=`ironbee=${_.join(";")}`,u=(0,O.loadConfig)(t),C={...s.tool_input??{}},a={projectName:b,sessionId:r,activityId:p,verificationId:d,traceId:T,traceState:M,toolCallId:(0,R.randomUUID)()};s.tool_use_id&&(a.toolUseId=s.tool_use_id),a.mcpServer=(()=>{for(const n of Object.keys(y))if(g.startsWith(n))return y[n];return F})();const w=(0,e.getUserEmail)(i);w&&(a.userEmail=w),u.collector?.url&&(a.collectorUrl=u.collector.url),u.collector?.apiKey&&(a.collectorApiKey=u.collector.apiKey),C._metadata=a;const $={permission:"allow",updated_input:C};process.stdout.write(JSON.stringify($)),process.exit(0)}P(J,"run");0&&(module.exports={run});
14
+ Use MCP:${l.stopTool}
15
+ submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,S.startActivity)({sessionDir:i,actionsFile:C,source:"pre_tool_use"});let a=P;c&&!a&&(a=(await(0,O.startVerification)({sessionId:r,sessionDir:i,actionsFile:C,recordingEnabled:!1})).verificationId);const k=(0,e.getActiveTraceId)(i),v=(0,e.getActiveActivityId)(i),b=(0,h.resolveProjectName)(t),_=[`prj:${b}`,`sid:${r}`];v&&_.push(`aid:${v}`),a&&_.push(`vid:${a}`);const $=`ironbee=${_.join(";")}`,u=(0,U.loadConfig)(t),y={...s.tool_input??{}},d={projectName:b,sessionId:r,activityId:v,verificationId:a,traceId:k,traceState:$,toolCallId:(0,I.randomUUID)()};s.tool_use_id&&(d.toolUseId=s.tool_use_id),d.mcpServer=(()=>{for(const n of Object.keys(T))if(p.startsWith(n))return T[n];return J})();const w=(0,e.getUserEmail)(i);w&&(d.userEmail=w),u.collector?.url&&(d.collectorUrl=u.collector.url),u.collector?.apiKey&&(d.collectorApiKey=u.collector.apiKey),y._metadata=d;const N={permission:"allow",updated_input:y};process.stdout.write(JSON.stringify(N)),process.exit(0)}R(L,"run");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var h=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var a=(n,t)=>h(n,"name",{value:t,configurable:!0});var G=(n,t)=>{for(var i in t)h(n,i,{get:t[i],enumerable:!0})},K=(n,t,i,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of z(t))!U.call(n,e)&&e!==i&&h(n,e,{get:()=>t[e],enumerable:!(o=X(t,e))||o.enumerable});return n};var j=n=>K(h({},"__esModule",{value:!0}),n);var st={};G(st,{run:()=>it});module.exports=j(st);var p=require("../../../hooks/core/actions"),c=require("../../../hooks/core/session-state"),B=require("../../../hooks/core/verification-context"),w=require("../../../lib/config"),l=require("../../../lib/logger"),L=require("../../../lib/output"),W=require("../../../lib/stdin"),m=require("../../../queue"),O=require("../util");const S=/callTool\(\s*['"]([^'"]+)['"]/g,k="mcp__browser-devtools__",y="bdt_",x="ndt_",$="bedt_",I="browser-devtools",Q="node-devtools",Y="backend-devtools",q=`${y}execute`,H=`${y}content_start-recording`,Z=`${y}content_stop-recording`;function tt(n){return n.startsWith($)?Y:n.startsWith(y)?I:n.startsWith(x)?Q:null}a(tt,"resolveServerByPrefix");function nt(n){return n.startsWith(k)?n.slice(k.length):n.startsWith("MCP:")?n.slice(4):n}a(nt,"stripBrowserDevtoolsPrefixes");function ot(n,t){if(n[t]!=="{")return;let i=0;for(let o=t;o<n.length;o++)if(n[o]==="{")i++;else if(n[o]==="}"&&(i--,i===0))return n.slice(t,o+1)}a(ot,"extractBalancedBraces");function et(n){const t=typeof n=="string"?n:JSON.stringify(n??""),i=[],o=new Set;let e=S.exec(t);for(;e!==null;){const u=nt(e[1]);if(!o.has(u)){o.add(u);let d;const T=e.index+e[0].length,E=t.slice(T).trimStart();if(E.startsWith(",")){const _=E.slice(1).trimStart();if(_.startsWith("{")){const f=ot(_,0);if(f)try{d=JSON.parse(f)}catch{d=f}}}i.push({name:u,args:d})}e=S.exec(t)}return i}a(et,"extractNestedToolCalls");async function it(n){let t;try{t=JSON.parse((0,W.readStdin)())}catch(s){l.logger.debug(`failed to parse stdin: ${s}`),process.stdout.write(JSON.stringify({})),process.exit(0);return}const i=t.conversation_id??"default",o=`${n}/.ironbee/sessions/${i}`,e=`${o}/actions.jsonl`;(0,l.setLogFile)(`${o}/session.log`);const u=t.tool_name??"unknown",d=Date.now(),T=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,E=(0,c.getActiveActivityId)(o),_=(0,c.getActiveVerificationId)(o),f=(0,c.getActiveTraceId)(o),r=(0,O.classifyTool)(u,t.tool_input),v=r.tool_type==="mcp"&&r.tool_name.startsWith(y),D=r.tool_type==="mcp"&&r.tool_name.startsWith(x),V=r.tool_type==="mcp"&&r.tool_name.startsWith($),C=v||D||V,F=r.tool_type==="mcp"?tt(r.tool_name)??r.mcp_server:r.mcp_server,M=C?T:(0,O.extractCursorToolInput)(u,T),R=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let g;if(R){const s=[];t.failure_type&&s.push(t.failure_type),t.is_interrupt&&s.push("interrupted"),g=`${s.length>0?`${s.join(",")}: `:""}${R}`}const A={...(0,p.baseFields)(e),type:"tool_call",timestamp:d,tool_name:r.tool_name,tool_type:r.tool_type,tool_use_id:t.tool_use_id,tool_input:M,tool_input_size:P(t.tool_input),tool_response:g?void 0:t.tool_output,tool_response_size:P(g?void 0:t.tool_output),activity_id:E,verification_id:_,trace_id:f,duration:typeof t.duration=="number"?t.duration:null,mcp_server:F,error:g};if(C?(await(0,p.appendAction)(e,A),v&&(r.tool_name===H?((0,c.setRecordingActive)(o,!0),l.logger.debug("track-action: recording started")):r.tool_name===Z&&((0,c.setRecordingActive)(o,!1),l.logger.debug("track-action: recording stopped")))):rt(n,i,A),l.logger.debug(`track-action: ${u}${g?" (failed)":""}`),v&&r.tool_name===q&&!g){const s=et(t.tool_input);for(const b of s){const J={...(0,p.baseFields)(e),type:"tool_call",timestamp:d,tool_name:b.name,tool_type:"mcp",tool_input:b.args,activity_id:E,verification_id:_,trace_id:f,duration:null,mcp_server:I};await(0,p.appendAction)(e,J),l.logger.debug(`track-action (nested): ${b.name}`)}}const N={};if(C)try{const s=(0,B.buildVerificationContextOnceForCycle)({projectDir:n,sessionId:i,sessionDir:o,activeVerificationId:_,config:(0,w.loadConfig)(n)});s.length>0&&(N.additional_context=s)}catch(s){l.logger.debug(`track-action: verification-context injection skipped: ${s instanceof Error?s.message:s}`)}(0,L.writeAndExit)(JSON.stringify(N),0)}a(it,"run");function rt(n,t,i){if(!(0,w.isJobQueueEnabled)(n))return;const o={...i};delete o.tool_response;try{(0,m.submit)(n,t,m.SEND_EVENT_TYPE,o)}catch(e){if(e instanceof m.JobTooLargeError){l.logger.debug(`track-action: wire event too large for ${i.tool_name}; dropping`);return}l.logger.debug(`track-action: failed to submit ${i.tool_name}: ${e instanceof Error?e.message:e}`)}}a(rt,"submitEvent");function P(n){if(n==null)return 0;try{const t=typeof n=="string"?n:JSON.stringify(n);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}a(P,"byteSize");0&&(module.exports={run});
1
+ "use strict";var b=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var a=(o,t)=>b(o,"name",{value:t,configurable:!0});var Y=(o,t)=>{for(var r in t)b(o,r,{get:t[r],enumerable:!0})},q=(o,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of j(t))!Q.call(o,e)&&e!==r&&b(o,e,{get:()=>t[e],enumerable:!(n=K(t,e))||n.enumerable});return o};var G=o=>q(b({},"__esModule",{value:!0}),o);var ct={};Y(ct,{run:()=>st});module.exports=G(ct);var p=require("../../../hooks/core/actions"),c=require("../../../hooks/core/session-state"),W=require("../../../hooks/core/verification-context"),h=require("../../../lib/config"),l=require("../../../lib/logger"),V=require("../../../lib/recording-tools"),B=require("../../../lib/output"),F=require("../../../lib/stdin"),m=require("../../../queue"),O=require("../util");const k=/callTool\(\s*['"]([^'"]+)['"]/g,x="mcp__browser-devtools__",w="bdt_",$="ndt_",I="bedt_",P="adt_",L="browser-devtools",H="node-devtools",Z="backend-devtools",tt="android-devtools",ot=`${w}execute`;function nt(o){return o.startsWith(I)?Z:o.startsWith(P)?tt:o.startsWith(w)?L:o.startsWith($)?H:null}a(nt,"resolveServerByPrefix");function et(o){return o.startsWith(x)?o.slice(x.length):o.startsWith("MCP:")?o.slice(4):o}a(et,"stripBrowserDevtoolsPrefixes");function it(o,t){if(o[t]!=="{")return;let r=0;for(let n=t;n<o.length;n++)if(o[n]==="{")r++;else if(o[n]==="}"&&(r--,r===0))return o.slice(t,n+1)}a(it,"extractBalancedBraces");function rt(o){const t=typeof o=="string"?o:JSON.stringify(o??""),r=[],n=new Set;let e=k.exec(t);for(;e!==null;){const u=et(e[1]);if(!n.has(u)){n.add(u);let d;const y=e.index+e[0].length,E=t.slice(y).trimStart();if(E.startsWith(",")){const _=E.slice(1).trimStart();if(_.startsWith("{")){const f=it(_,0);if(f)try{d=JSON.parse(f)}catch{d=f}}}r.push({name:u,args:d})}e=k.exec(t)}return r}a(rt,"extractNestedToolCalls");async function st(o){let t;try{t=JSON.parse((0,F.readStdin)())}catch(i){l.logger.debug(`failed to parse stdin: ${i}`),process.stdout.write(JSON.stringify({})),process.exit(0);return}const r=t.conversation_id??"default",n=`${o}/.ironbee/sessions/${r}`,e=`${n}/actions.jsonl`;(0,l.setLogFile)(`${n}/session.log`);const u=t.tool_name??"unknown",d=Date.now(),y=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,E=(0,c.getActiveActivityId)(n),_=(0,c.getActiveVerificationId)(n),f=(0,c.getActiveTraceId)(n),s=(0,O.classifyTool)(u,t.tool_input),C=s.tool_type==="mcp"&&s.tool_name.startsWith(w),M=s.tool_type==="mcp"&&s.tool_name.startsWith($),J=s.tool_type==="mcp"&&s.tool_name.startsWith(I),X=s.tool_type==="mcp"&&s.tool_name.startsWith(P),v=C||M||J||X,A=s.tool_type==="mcp"?nt(s.tool_name)??s.mcp_server:s.mcp_server,z=v?y:(0,O.extractCursorToolInput)(u,y),R=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let g;if(R){const i=[];t.failure_type&&i.push(t.failure_type),t.is_interrupt&&i.push("interrupted"),g=`${i.length>0?`${i.join(",")}: `:""}${R}`}const S={...(0,p.baseFields)(e),type:"tool_call",timestamp:d,tool_name:s.tool_name,tool_type:s.tool_type,tool_use_id:t.tool_use_id,tool_input:z,tool_input_size:D(t.tool_input),tool_response:g?void 0:t.tool_output,tool_response_size:D(g?void 0:t.tool_output),activity_id:E,verification_id:_,trace_id:f,duration:typeof t.duration=="number"?t.duration:null,mcp_server:A,error:g};if(v){await(0,p.appendAction)(e,S);const i=(0,V.recordingToolsForServer)(A);i!==null&&(s.tool_name===i.startTool?((0,c.setRecordingActive)(n,!0),l.logger.debug(`track-action: recording started (${i.cycle})`)):s.tool_name===i.stopTool&&((0,c.setRecordingActive)(n,!1),l.logger.debug(`track-action: recording stopped (${i.cycle})`)))}else lt(o,r,S);if(l.logger.debug(`track-action: ${u}${g?" (failed)":""}`),C&&s.tool_name===ot&&!g){const i=rt(t.tool_input);for(const T of i){const U={...(0,p.baseFields)(e),type:"tool_call",timestamp:d,tool_name:T.name,tool_type:"mcp",tool_input:T.args,activity_id:E,verification_id:_,trace_id:f,duration:null,mcp_server:L};await(0,p.appendAction)(e,U),l.logger.debug(`track-action (nested): ${T.name}`)}}const N={};if(v)try{const i=(0,W.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:r,sessionDir:n,activeVerificationId:_,config:(0,h.loadConfig)(o)});i.length>0&&(N.additional_context=i)}catch(i){l.logger.debug(`track-action: verification-context injection skipped: ${i instanceof Error?i.message:i}`)}(0,B.writeAndExit)(JSON.stringify(N),0)}a(st,"run");function lt(o,t,r){if(!(0,h.isJobQueueEnabled)(o))return;const n={...r};delete n.tool_response;try{(0,m.submit)(o,t,m.SEND_EVENT_TYPE,n)}catch(e){if(e instanceof m.JobTooLargeError){l.logger.debug(`track-action: wire event too large for ${r.tool_name}; dropping`);return}l.logger.debug(`track-action: failed to submit ${r.tool_name}: ${e instanceof Error?e.message:e}`)}}a(lt,"submitEvent");function D(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}a(D,"byteSize");0&&(module.exports={run});