@ironbee-ai/cli 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/analytics/claude/emit.js +1 -1
- package/dist/analytics/claude/state.js +1 -1
- package/dist/analytics/codex/events-emit.js +2 -2
- package/dist/analytics/codex/subagent-transcripts.js +3 -3
- package/dist/clients/claude/agents/ironbee-scenario.md +191 -0
- package/dist/clients/claude/agents/ironbee-verifier.md +22 -5
- package/dist/clients/claude/commands/ironbee-manage-scenario.md +36 -0
- package/dist/clients/claude/commands/ironbee-search-scenario.md +22 -0
- package/dist/clients/claude/commands/ironbee-sync-scenario.md +31 -0
- package/dist/clients/claude/commands/ironbee-verify.md +13 -12
- package/dist/clients/claude/hooks/activity-end.js +1 -1
- package/dist/clients/claude/hooks/activity-start.js +1 -1
- package/dist/clients/claude/hooks/clear-verdict.js +1 -1
- package/dist/clients/claude/hooks/require-verdict.js +2 -2
- package/dist/clients/claude/hooks/require-verification.js +3 -3
- package/dist/clients/claude/hooks/session-end.js +1 -1
- package/dist/clients/claude/hooks/session-start.js +4 -4
- package/dist/clients/claude/hooks/session-status.js +2 -2
- package/dist/clients/claude/hooks/subagent-start.js +1 -1
- package/dist/clients/claude/hooks/subagent-stop.js +1 -1
- package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/hooks/verify-gate.js +4 -4
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/scenario.android.md +32 -0
- package/dist/clients/claude/platforms/scenario.backend.md +26 -0
- package/dist/clients/claude/platforms/scenario.browser.md +41 -0
- package/dist/clients/claude/platforms/scenario.node.md +27 -0
- package/dist/clients/claude/platforms/skill.android.md +4 -0
- package/dist/clients/claude/process-analytics.js +1 -1
- package/dist/clients/claude/statusline-toggle.js +2 -2
- package/dist/clients/claude/trust.js +1 -0
- package/dist/clients/codex/agents/ironbee-scenario.md +179 -0
- package/dist/clients/codex/agents/ironbee-verifier.md +22 -5
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +102 -0
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.md +38 -0
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +37 -0
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.md +23 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +55 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.md +33 -0
- package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +12 -3
- package/dist/clients/codex/commands/ironbee-verify/SKILL.md +4 -3
- package/dist/clients/codex/hooks/activity-end.js +1 -1
- package/dist/clients/codex/hooks/activity-start.js +1 -1
- package/dist/clients/codex/hooks/clear-verdict.js +3 -3
- package/dist/clients/codex/hooks/require-verdict.js +2 -2
- package/dist/clients/codex/hooks/require-verification.js +3 -3
- package/dist/clients/codex/hooks/session-start.js +3 -3
- package/dist/clients/codex/hooks/subagent-start.js +1 -1
- package/dist/clients/codex/hooks/subagent-stop.js +1 -1
- package/dist/clients/codex/hooks/track-action-monitor.js +1 -1
- package/dist/clients/codex/hooks/track-action-pre.js +1 -1
- package/dist/clients/codex/hooks/track-action.js +1 -1
- package/dist/clients/codex/hooks/verify-gate.js +1 -1
- package/dist/clients/codex/index.js +2 -2
- package/dist/clients/codex/platforms/command-verify.android.md +1 -0
- package/dist/clients/codex/platforms/rule.android.md +2 -1
- package/dist/clients/codex/platforms/scenario.android.md +32 -0
- package/dist/clients/codex/platforms/scenario.backend.md +26 -0
- package/dist/clients/codex/platforms/scenario.browser.md +40 -0
- package/dist/clients/codex/platforms/scenario.node.md +27 -0
- package/dist/clients/codex/platforms/skill.android.md +4 -0
- package/dist/clients/codex/process-analytics.js +2 -2
- package/dist/clients/codex/thread-map.js +1 -1
- package/dist/clients/codex/util.js +44 -31
- package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +100 -0
- package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +34 -0
- package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +54 -0
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +2 -1
- package/dist/clients/cursor/hooks/activity-end.js +1 -1
- package/dist/clients/cursor/hooks/activity-start.js +1 -1
- package/dist/clients/cursor/hooks/clear-verdict.js +1 -1
- package/dist/clients/cursor/hooks/require-verdict.js +2 -2
- package/dist/clients/cursor/hooks/require-verification.js +3 -3
- package/dist/clients/cursor/hooks/session-end.js +1 -1
- package/dist/clients/cursor/hooks/session-start.js +4 -4
- package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
- package/dist/clients/cursor/hooks/track-action.js +1 -1
- package/dist/clients/cursor/hooks/verify-gate.js +1 -1
- package/dist/clients/cursor/index.js +1 -1
- package/dist/clients/cursor/platforms/command-verify.android.md +1 -0
- package/dist/clients/cursor/platforms/rule.android.md +2 -1
- package/dist/clients/cursor/platforms/scenario.android.md +32 -0
- package/dist/clients/cursor/platforms/scenario.backend.md +26 -0
- package/dist/clients/cursor/platforms/scenario.browser.md +40 -0
- package/dist/clients/cursor/platforms/scenario.node.md +27 -0
- package/dist/clients/cursor/platforms/skill.android.md +4 -0
- package/dist/commands/config.js +1 -1
- package/dist/commands/hook.js +10 -10
- package/dist/commands/import.js +3 -3
- package/dist/commands/process-job-file.js +1 -1
- package/dist/commands/queue.js +16 -16
- package/dist/commands/scenario.js +1 -0
- package/dist/commands/status.js +1 -1
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/verify.js +2 -2
- package/dist/hooks/core/actions.js +7 -7
- package/dist/hooks/core/nested-tools.js +1 -1
- package/dist/hooks/core/scenario-tools.js +1 -0
- package/dist/hooks/core/session-state.js +1 -1
- package/dist/hooks/core/verification-context.js +8 -8
- package/dist/import/marker.js +2 -2
- package/dist/import/skip.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/git.js +1 -1
- package/dist/lib/install-version.js +1 -1
- package/dist/lib/platform-section.js +3 -3
- package/dist/lib/runtime-paths.js +1 -0
- package/dist/lib/scenario-staleness.js +1 -0
- package/dist/otel/claude/daemon/process.js +1 -1
- package/dist/otel/claude/daemon/reprocess.js +1 -1
- package/dist/otel/claude/daemon/response-usage.js +2 -2
- package/dist/queue/drain.js +1 -1
- package/dist/queue/flush.js +1 -1
- package/dist/queue/paths.js +1 -1
- package/dist/queue/process-file.js +2 -2
- package/dist/queue/spawn.js +1 -1
- package/dist/tui/config/schema.js +1 -1
- package/dist/tui/queue/read.js +4 -4
- package/dist/tui/scenarios/area.js +2 -0
- package/dist/tui/sessions/read.js +2 -2
- package/dist/tui/shell/registry.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var g=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var p=(e,t)=>g(e,"name",{value:t,configurable:!0});var U=(e,t)=>{for(var n in t)g(e,n,{get:t[n],enumerable:!0})},$=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of E(t))!W.call(e,o)&&o!==n&&g(e,o,{get:()=>t[o],enumerable:!(i=w(t,o))||i.enumerable});return e};var k=e=>$(g({},"__esModule",{value:!0}),e);var D={};U(D,{run:()=>A});module.exports=k(D);var _=require("fs"),h=require("../../../hooks/core/clear-verdict"),v=require("../../../hooks/core/verification-lifecycle"),m=require("../../../hooks/core/actions"),F=require("../../../hooks/core/session-state"),x=require("../../../hooks/core/tool-use-stash"),c=require("../../../hooks/core/file-diff"),u=require("../../../lib/runtime-paths"),b=require("path"),a=require("../../../lib/config"),I=require("../../../import/ids"),l=require("../../../lib/logger"),S=require("../../../lib/stdin");function L(e,t){const n=e.tool_name,i=e.tool_input;if(!i)return null;const o=e.tool_use_id?(0,x.consumeToolUseData)(t,e.tool_use_id):null;if(n==="Edit"){const r=i.old_string??"",s=i.new_string??"",d=(0,c.diffLineCounts)(r,s);return{tool_name:"Edit",operation:"update",lines_added:d.added,lines_removed:d.removed,stash:o}}if(n==="Write"){const r=i.content??"",s=o?.file_existed??!1;return{tool_name:"Write",operation:s?"update":"create",lines_added:(0,c.countLines)(r),lines_removed:s?null:0,stash:o}}return null}p(L,"deriveChangeFacts");function T(e,t,n){const i=t?.prior_content??"";let o;try{o=(0,_.existsSync)(e)?(0,_.readFileSync)(e,"utf-8"):""}catch(s){l.logger.debug(`failed to read post-edit content of ${e} for changeset: ${s}`);return}return(0,c.createUnifiedDiff)(i,o,n)??void 0}p(T,"buildChangeset");async function A(e){let t="default",n;try{n=JSON.parse((0,S.readStdin)()),t=n.session_id??"default",(0,l.setLogFile)((0,u.sessionLogFile)(e,t))}catch(f){l.logger.debug(`failed to parse stdin: ${f}`),process.exit(0)}const i=n.tool_input?.file_path;i&&i.endsWith("verdict.json")&&i.replace(/\\/g,"/").startsWith((0,u.sessionsRoot)(e).replace(/\\/g,"/")+"/")&&(l.logger.debug(`skipping clear-verdict: write target is verdict file ${i}`),process.exit(0)),i||(l.logger.debug("skipping clear-verdict: missing file_path in tool_input"),process.exit(0));const o=(0,a.loadConfig)(e);(0,a.requiresVerification)(i,o)||(l.logger.debug(`skipping clear-verdict: file does not require verification (${i})`),process.exit(0));const r=(0,u.sessionDir)(e,t),s=`${r}/actions.jsonl`,d=L(n,t);d||(l.logger.debug(`skipping clear-verdict: unsupported tool ${n.tool_name}`),process.exit(0));const y=await(0,v.openFixCycleIfFixing)({sessionDir:r,actionsFile:s}),C={...(0,m.baseFields)(s),type:"file_change",timestamp:Date.now(),tool_name:d.tool_name,file_path:i,operation:d.operation,lines_added:d.lines_added,lines_removed:d.lines_removed,activity_id:(0,F.getActiveActivityId)(r),fix_id:y};if(n.tool_use_id!==void 0&&n.tool_use_id.length>0&&(C.id=(0,I.deriveFileChangeEventId)(t,n.tool_use_id)),(0,a.getCaptureFileChangeset)(o)){const f=T(i,d.stash,(0,a.getMaxChangesetBytes)(o));f!==void 0&&(C.changeset=f)}await(0,m.appendAction)(s,C),(0,h.runClearVerdict)({verdictFile:(0,b.join)((0,u.sessionDir)(e,t),"verdict.json"),sessionDir:r}),process.exit(0)}p(A,"run");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var b=(e,o)=>a(e,"name",{value:o,configurable:!0});var I=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},k=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of T(o))!w.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=y(o,s))||t.enumerable});return e};var E=e=>k(a({},"__esModule",{value:!0}),e);var P={};I(P,{run:()=>F});module.exports=E(P);var d=require("fs"),h=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),l=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),c=require("../../../lib/runtime-paths");async function F(e,o){const i=o?.soft===!0;let t;try{t=JSON.parse((0,S.readStdin)())}catch(u){n.logger.debug(`failed to parse stdin: ${u}`),process.exit(0)}const s=t.session_id??"default";(0,n.setLogFile)((0,c.sessionLogFile)(e,s));const f=(0,c.sessionDir)(e,s),p=`${f}/actions.jsonl`;!i&&(0,h.hasToolCallsSinceLastVerdict)(p)&&(process.stderr.write(`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
|
|
2
2
|
|
|
3
3
|
Submit your verdict first:
|
|
4
4
|
echo '{"session_id":"${s}","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict
|
|
5
5
|
|
|
6
6
|
Then you can edit code to fix the issues.
|
|
7
|
-
`),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const
|
|
7
|
+
`),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const u=(0,l.loadConfig)(e),g=(0,l.getCaptureFileChangeset)(u),m=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&g){const _={file_existed:m};if(g&&m)try{_.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(x){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${x}`)}(0,C.stashToolUseData)(s,t.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:p,source:"pre_tool_use"}),process.exit(0)}b(F,"run");0&&(module.exports={run});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"use strict";var u=Object.defineProperty;var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var w=(o,t)=>u(o,"name",{value:t,configurable:!0});var B=(o,t)=>{for(var s in t)u(o,s,{get:t[s],enumerable:!0})},M=(o,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of L(t))!j.call(o,e)&&e!==s&&u(o,e,{get:()=>t[e],enumerable:!(r=D(t,e))||r.enumerable});return o};var q=o=>M(u({},"__esModule",{value:!0}),o);var Y={};B(Y,{run:()=>W});module.exports=q(Y);var R=require("crypto"),i=require("../../../hooks/core/session-state"),O=require("../../../hooks/core/actions"),E=require("../../../hooks/core/activity"),U=require("../../../hooks/core/verification-lifecycle"),$=require("../../../hooks/core/verification-context"),A=require("../../../lib/config"),x=require("../../../lib/recording-tools"),N=require("../../../hooks/core/scenario-tools"),f=require("../../../lib/logger"),_=require("../util"),P=require("../../../lib/stdin"),V=require("../../../lib/runtime-paths");const J="browser-devtools";async function W(o,t){const s=t?.soft===!0;let r;try{r=JSON.parse((0,P.readStdin)())}catch(y){f.logger.debug(`failed to parse stdin: ${y}`),process.exit(0)}const e=r.session_id??"default",n=(0,V.sessionDir)(o,e);(0,f.setLogFile)(`${n}/session.log`);const h=`${n}/actions.jsonl`,p=(0,N.isScenarioTool)(r.tool_name),S=(0,i.getActiveVerificationId)(n);!S&&!s&&!p&&(process.stderr.write(`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools).
|
|
2
2
|
|
|
3
3
|
Start verification first:
|
|
4
4
|
echo '{"session_id":"${e}"}' | ironbee hook verification-start
|
|
5
5
|
|
|
6
6
|
Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend, adt_* for android.
|
|
7
|
-
`),process.exit(2));const
|
|
7
|
+
`),process.exit(2));const T=r.tool_name??"",l=(0,x.recordingToolsForServer)((0,_.extractMcpServerName)(T));!s&&!p&&l!==null&&(0,i.isRecordingRequired)(n)&&!(0,i.isRecordingActive)(n)&&!T.endsWith(l.startTool)&&(process.stderr.write(`BLOCKED: Recording is required but not started.
|
|
8
8
|
|
|
9
9
|
1. Start recording NOW:
|
|
10
10
|
Use mcp__${l.server}__${l.startTool}
|
|
@@ -14,4 +14,4 @@ Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser
|
|
|
14
14
|
3. **Stop recording BEFORE submitting verdict:**
|
|
15
15
|
Use mcp__${l.server}__${l.stopTool}
|
|
16
16
|
submit-verdict will reject with "recording is still active" if you skip this.
|
|
17
|
-
`),process.exit(2)),await(0
|
|
17
|
+
`),process.exit(2)),await(0,E.startActivity)({sessionDir:n,actionsFile:h,source:"pre_tool_use"});let d=S;s&&!d&&!p&&(d=(await(0,U.startVerification)({sessionId:e,sessionDir:n,actionsFile:h,recordingEnabled:!1})).verificationId);const F=(0,i.getActiveTraceId)(n),g=(0,i.getActiveActivityId)(n),b=(0,O.resolveProjectName)(o),m=[`prj:${b}`,`sid:${e}`];g&&m.push(`aid:${g}`),d&&m.push(`vid:${d}`);const K=`ironbee=${m.join(";")}`,c=(0,A.loadConfig)(o),C={...r.tool_input??{}},a={projectName:b,sessionId:e,activityId:g,verificationId:d,traceId:F,traceState:K,toolCallId:(0,R.randomUUID)()};r.tool_use_id&&(a.toolUseId=r.tool_use_id),a.mcpServer=(0,_.extractMcpServerName)(r.tool_name)??J;const I=(0,i.getUserEmail)(n);I&&(a.userEmail=I),c.collector?.url&&(a.collectorUrl=c.collector.url),c.collector?.oauthToken?a.collectorOAuthToken=c.collector.oauthToken:c.collector?.apiKey&&(a.collectorApiKey=c.collector.apiKey),C._metadata=a;const v={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:C}},k=(0,$.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:e,sessionDir:n,activeVerificationId:d,config:c});k.length>0&&v.hookSpecificOutput&&(v.hookSpecificOutput.additionalContext=k),process.stdout.write(JSON.stringify(v)),process.exit(0)}w(W,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var c=(n,s)=>a(n,"name",{value:s,configurable:!0});var I=(n,s)=>{for(var i in s)a(n,i,{get:s[i],enumerable:!0})},b=(n,s,i,o)=>{if(s&&typeof s=="object"||typeof s=="function")for(let e of v(s))!_.call(n,e)&&e!==i&&a(n,e,{get:()=>s[e],enumerable:!(o=A(s,e))||o.enumerable});return n};var C=n=>b(a({},"__esModule",{value:!0}),n);var h={};I(h,{run:()=>$});module.exports=C(h);var t=require("../../../hooks/core/actions"),p=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),u=require("../../../hooks/core/activity-participants"),l=require("../../../import/ids"),r=require("../../../lib/logger"),f=require("../../../lib/stdin"),g=require("../../../queue"),S=require("../../../analytics/claude/hook-trigger"),y=require("../../../lib/runtime-paths");async function $(n){let s;try{s=JSON.parse((0,f.readStdin)())}catch(E){r.logger.debug(`failed to parse stdin: ${E}`),process.exit(0)}const i=s.session_id??"default",o=(0,y.sessionDir)(n,i),e=`${o}/actions.jsonl`;(0,r.setLogFile)(`${o}/session.log`),await(0,m.closeOpenCycles)(o,e,"session_end"),await(0,p.endActivity)({sessionDir:o,actionsFile:e}),(0,u.clearActivityParticipants)(o);const d=Date.now(),w={...(0,t.baseFields)(e),id:(0,l.deriveSessionEndEventId)(i),type:"session_end",timestamp:d,session_id:i,duration:(0,t.findDurationSinceLastAction)(e,"session_start",d),reason:s.reason};await(0,t.appendAction)(e,w),await(0,S.runAnalyticsTrigger)({projectDir:n,sessionId:i,triggerType:"SessionEnd",endReason:s.reason,transcriptSource:"claude-code"}),await(0,g.flushSynchronously)(n,i),r.logger.debug(`session-end: ${i}`),process.exit(0)}c($,"run");0&&(module.exports={run});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var g=(s,i)=>l(s,"name",{value:i,configurable:!0});var F=(s,i)=>{for(var e in i)l(s,e,{get:i[e],enumerable:!0})},T=(s,i,e,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of C(i))!O.call(s,t)&&t!==e&&l(s,t,{get:()=>i[t],enumerable:!(o=k(i,t))||o.enumerable});return s};var A=s=>T(l({},"__esModule",{value:!0}),s);var N={};F(N,{run:()=>x});module.exports=A(N);var a=require("../../../hooks/core/actions"),S=require("../../../import/ids"),n=require("../../../hooks/core/session-state"),d=require("../util"),r=require("../../../lib/config"),p=require("./session-status"),u=require("../../../lib/logger"),f=require("../../../lib/output"),b=require("../../../lib/stdin"),h=require("../../../lib/telemetry"),v=require("../../../otel/claude/daemon/ensure"),c=require("../../../lib/runtime-paths");async function x(s){let i;try{i=JSON.parse((0,b.readStdin)())}catch(y){u.logger.debug(`failed to parse stdin: ${y}`),process.exit(0)}const e=i.session_id??"default",o=(0,c.sessionActionsFile)(s,e);(0,u.setLogFile)((0,c.sessionLogFile)(s,e)),await(0,v.ensureOTELCollector)(s);const t=(0,c.sessionDir)(s,e);(0,n.setProjectDir)(t,s),(0,n.setUserEmail)(t,(0,d.getClaudeUserEmail)()),(0,n.setUsage)(t,(0,d.resolveClaudeUsage)()),(0,r.isSessionStatusEnabled)((0,r.loadConfig)(s))&&(0,n.setChainedStatusLine)(t,(0,p.resolveChainTarget)(s)??null);const E={...(0,a.baseFields)(o),id:(0,S.deriveSessionStartEventId)(e),type:"session_start",timestamp:Date.now(),session_id:e,client:"claude",source:i.source??"unknown"};await(0,a.appendAction)(o,E),i.source==="compact"?await(0,n.reconcileForCompact)(t,o,a.appendAction):await(0,n.reconcileSessionState)(t,o,a.appendAction);const m=(0,r.getVerificationEnabled)((0,r.loadConfig)(s));if(await(0,h.trackSessionStart)("claude",e,m,s),u.logger.debug(`session-start: ${e} (${i.source??"unknown"})`),!m){(0,f.writeAndExit)("",0);return}const w=JSON.stringify({session_id:e,status:"pass",checks:["form submits successfully","new item appears in list"]}),I=JSON.stringify({session_id:e,status:"fail",checks:["form renders","submit button unresponsive"],issues:["button click handler not firing","TypeError in console"]});(0,f.writeAndExit)(`
|
|
2
2
|
=====================================
|
|
3
3
|
IRONBEE VERIFICATION \u2014 SESSION ACTIVE
|
|
4
4
|
=====================================
|
|
@@ -10,13 +10,13 @@ After EVERY verification attempt, you MUST submit a verdict BEFORE doing anythin
|
|
|
10
10
|
- If fail \u2192 submit fail verdict FIRST, then fix. Do NOT skip to fixing code without submitting.
|
|
11
11
|
|
|
12
12
|
Submit via Bash:
|
|
13
|
-
echo '${
|
|
13
|
+
echo '${w}' | ironbee hook submit-verdict
|
|
14
14
|
|
|
15
15
|
On fail (issues is required):
|
|
16
|
-
echo '${
|
|
16
|
+
echo '${I}' | ironbee hook submit-verdict
|
|
17
17
|
|
|
18
18
|
Required fields: session_id, status, checks
|
|
19
19
|
On fail, include: issues (array of strings describing what failed)
|
|
20
20
|
On pass after a previous fail, include: fixes (array of strings describing what was fixed)
|
|
21
21
|
=====================================
|
|
22
|
-
`,0)}
|
|
22
|
+
`,0)}g(x,"run");0&&(module.exports={run});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}r(
|
|
1
|
+
"use strict";var w=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var r=(e,t)=>w(e,"name",{value:t,configurable:!0});var Q=(e,t)=>{for(var n in t)w(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of q(t))!K.call(e,o)&&o!==n&&w(e,o,{get:()=>t[o],enumerable:!(s=H(t,o))||s.enumerable});return e};var X=e=>V(w({},"__esModule",{value:!0}),e);var de={};Q(de,{applyOauthRateLimits:()=>L,buildSessionStatusEvent:()=>J,computeSessionStatusSignature:()=>z,deriveSessionStatusEventId:()=>A,isIronbeeStatusLine:()=>S,needsRateLimitFallback:()=>B,normalizeModelId:()=>U,resolveChainTarget:()=>W,resolveProjectDir:()=>I,runSessionStatus:()=>ue});module.exports=X(de);var k=require("child_process"),v=require("crypto"),u=require("fs"),C=require("os"),g=require("path"),P=require("../../../lib/install-snapshots"),b=require("../../../lib/runtime-paths"),c=require("../../../lib/config"),$=require("../../../lib/event"),f=require("../../../lib/logger"),O=require("../../../lib/stdin"),N=require("../../../queue"),j=require("../../../hooks/core/actions"),y=require("../../../hooks/core/session-state"),D=require("../oauth");const Y="claude";function S(e){return typeof e!="string"?!1:/^\s*ironbee\s+hook\s+session-status(\s+--client\s+\S+)?\s*$/.test(e)}r(S,"isIronbeeStatusLine");function I(e){return process.env.CLAUDE_PROJECT_DIR??e.workspace?.project_dir??process.cwd()}r(I,"resolveProjectDir");function x(e){if((0,u.existsSync)(e))try{const t=JSON.parse((0,u.readFileSync)(e,"utf-8"));if(t===null||typeof t!="object")return;const n=t.statusLine;if(n===null||typeof n!="object")return;const s=n.command;return typeof s=="string"&&s.length>0?s:void 0}catch(t){f.logger.debug(`session-status: failed to read statusLine from ${e}: ${t instanceof Error?t.message:t}`);return}}r(x,"readStatusLineCommand");function W(e){const t=(0,P.readStatusLineSnapshot)(e,Y)?.command;if(t&&!S(t))return t;const n=x((0,g.join)(e,".claude","settings.json"));if(n&&!S(n))return n;const s=x((0,g.join)((0,C.homedir)(),".claude","settings.json"));if(s&&!S(s))return s}r(W,"resolveChainTarget");function a(e){return typeof e=="number"&&Number.isFinite(e)?e:0}r(a,"num");function U(e){return(e??"").replace(/\[[^\]]*\]/g,"").trim()}r(U,"normalizeModelId");function Z(e){return e==null?null:{input_tokens:a(e.input_tokens),output_tokens:a(e.output_tokens),cache_creation_input_tokens:a(e.cache_creation_input_tokens),cache_read_input_tokens:a(e.cache_read_input_tokens)}}r(Z,"mapCurrentUsage");function E(e){if(!(!e||typeof e.used_percentage!="number"||typeof e.resets_at!="number"))return{used_percentage:e.used_percentage,resets_at:e.resets_at*1e3}}r(E,"mapRateLimitWindow");function J(e,t,n){const s=(0,b.sessionDir)(t,n),o=(0,g.join)(s,"actions.jsonl"),i=e.context_window??{},d=e.cost??{},_=E(e.rate_limits?.five_hour),l=E(e.rate_limits?.seven_day),h=(0,y.readState)(s).activeActivityId??void 0,m={...(0,j.baseFields)(o),id:A(n,a(d.total_duration_ms)),type:"session_status",timestamp:Date.now(),model:U(e.model?.id),cost:{total_cost_usd:a(d.total_cost_usd),total_duration:a(d.total_duration_ms),total_api_duration:a(d.total_api_duration_ms),total_lines_added:a(d.total_lines_added),total_lines_removed:a(d.total_lines_removed)},context_window:{total_input_tokens:a(i.total_input_tokens),total_output_tokens:a(i.total_output_tokens),context_window_size:a(i.context_window_size),used_percentage:a(i.used_percentage),remaining_percentage:a(i.remaining_percentage),current_usage:Z(i.current_usage)}};return h!==void 0&&(m.activity_id=h),(_||l)&&(m.rate_limits={},_&&(m.rate_limits.five_hour=_),l&&(m.rate_limits.seven_day=l)),m}r(J,"buildSessionStatusEvent");function A(e,t){const n=(0,v.createHash)("sha256").update(`session_status:${e}:${t}`).digest("hex");return(0,$.formatHexAsUuid)(n)}r(A,"deriveSessionStatusEventId");function z(e){const t={model:e.model,context_window:e.context_window,cost:{total_cost_usd:e.cost.total_cost_usd,total_lines_added:e.cost.total_lines_added,total_lines_removed:e.cost.total_lines_removed},rate_limits:e.rate_limits};return(0,v.createHash)("sha256").update(JSON.stringify(t)).digest("hex")}r(z,"computeSessionStatusSignature");function ee(e,t){return(0,g.join)((0,b.sessionDir)(e,t),"statusline","state.json")}r(ee,"statuslineStatePath");function te(e){if(!(0,u.existsSync)(e))return{};try{const t=JSON.parse((0,u.readFileSync)(e,"utf-8"));if(t===null||typeof t!="object")return{};const n=t;return{sig:typeof n.sig=="string"?n.sig:void 0,lastEmitMs:typeof n.lastEmitMs=="number"?n.lastEmitMs:void 0,usage:ne(n.usage),usageFetchedMs:typeof n.usageFetchedMs=="number"?n.usageFetchedMs:void 0}}catch(t){return f.logger.debug(`session-status: failed to read statusline state ${e}: ${t instanceof Error?t.message:t}`),{}}}r(te,"readStatuslineState");function F(e){if(e===null||typeof e!="object")return;const t=e;if(typeof t.used_percentage=="number"&&typeof t.resets_at=="number")return{used_percentage:t.used_percentage,resets_at:t.resets_at}}r(F,"parseCachedWindow");function ne(e){if(e===null||typeof e!="object")return;const t=e,n=F(t.five_hour),s=F(t.seven_day);if(!n&&!s)return;const o={};return n&&(o.five_hour=n),s&&(o.seven_day=s),o}r(ne,"parseCachedUsage");function M(e,t){const n=`${e}.tmp.${process.pid}.${Date.now()}`;try{(0,u.mkdirSync)((0,g.dirname)(e),{recursive:!0}),(0,u.writeFileSync)(n,JSON.stringify(t)),(0,u.renameSync)(n,e)}catch(s){try{(0,u.existsSync)(n)&&(0,u.unlinkSync)(n)}catch{}f.logger.debug(`session-status: failed to write statusline state ${e}: ${s instanceof Error?s.message:s}`)}}r(M,"writeStatuslineState");async function se(e,t){const n=e.session_id;if(!n)return;const s=(0,c.loadConfig)(t);if(!(0,c.isSessionStatusEnabled)(s))return;const o=(0,b.sessionDir)(t,n);if(!(0,u.existsSync)(o))return;const i=e.context_window??{};if((i.current_usage===null||i.current_usage===void 0)&&a(i.total_input_tokens)===0)return;const d=J(e,t,n),_=ee(t,n),l=te(_),p=await ie(d,s,l),h=z(d),m=Date.now(),R=(0,c.getStatusLineEmitMinIntervalSeconds)(s)*1e3,G=l.sig===h,T=R>0&&l.lastEmitMs!==void 0&&m-l.lastEmitMs<R;if(!G&&!T){(0,N.submit)(t,n,"send_event",d),M(_,{sig:h,lastEmitMs:m,usage:p.usage,usageFetchedMs:p.usageFetchedMs});return}p.fetched&&M(_,{sig:l.sig,lastEmitMs:l.lastEmitMs,usage:p.usage,usageFetchedMs:p.usageFetchedMs})}r(se,"submitSessionStatusEvent");function B(e){return e.rate_limits===void 0||e.rate_limits.five_hour===void 0&&e.rate_limits.seven_day===void 0}r(B,"needsRateLimitFallback");function L(e,t){!t.five_hour&&!t.seven_day||(e.rate_limits=e.rate_limits??{},t.five_hour&&!e.rate_limits.five_hour&&(e.rate_limits.five_hour=t.five_hour),t.seven_day&&!e.rate_limits.seven_day&&(e.rate_limits.seven_day=t.seven_day))}r(L,"applyOauthRateLimits");async function ie(e,t,n){if(!(0,c.getClaudeOauthAccessEnabled)(t)||!B(e))return{usage:n.usage,usageFetchedMs:n.usageFetchedMs,fetched:!1};const s=(0,c.getClaudeOauthAccessUsageTtlSeconds)(t)*1e3;if(n.usageFetchedMs!==void 0&&Date.now()-n.usageFetchedMs<s)return n.usage&&L(e,n.usage),{usage:n.usage,usageFetchedMs:n.usageFetchedMs,fetched:!1};const d=await(0,D.fetchClaudeRateLimits)()??n.usage;return d&&L(e,d),{usage:d,usageFetchedMs:Date.now(),fetched:!0}}r(ie,"maybeFillRateLimitsFromOauth");function oe(e,t){return new Promise(n=>{let s;const o=process.platform==="win32"?re():void 0;try{s=o?(0,k.spawn)(o,["-c",e],{stdio:["pipe","inherit","inherit"]}):(0,k.spawn)(e,[],{stdio:["pipe","inherit","inherit"],shell:!0})}catch(i){f.logger.debug(`session-status: failed to spawn chained statusline: ${i instanceof Error?i.message:i}`),n();return}s.on("error",i=>{f.logger.debug(`session-status: chained statusline error: ${i.message}`),n()}),s.on("exit",i=>{process.exitCode=i??0,n()}),s.stdin&&(s.stdin.on("error",()=>{}),s.stdin.write(t),s.stdin.end())})}r(oe,"runChained");function re(){const e=["C:\\Program Files\\Git\\bin\\bash.exe","C:\\Program Files\\Git\\usr\\bin\\bash.exe","C:\\Program Files (x86)\\Git\\bin\\bash.exe"];for(const t of e)if((0,u.existsSync)(t))return t}r(re,"findGitBash");function ae(e){const t=e.model?.id??"?",n=a(e.context_window?.used_percentage);return`[${t}] ${n}% ctx`}r(ae,"buildDefaultLine");async function ue(){let e;try{e=(0,O.readStdin)()}catch(i){f.logger.debug(`session-status: failed to read stdin: ${i}`);return}let t;try{t=JSON.parse(e)}catch(i){f.logger.debug(`session-status: failed to parse stdin: ${i}`);return}const n=I(t),s=se(t,n).catch(i=>{f.logger.debug(`session-status: submit failed: ${i instanceof Error?i.message:i}`)});let o;if(t.session_id){const i=(0,b.sessionDir)(n,t.session_id);try{o=(0,y.getChainedStatusLine)(i)}catch{o=void 0}}if(o===void 0&&(o=W(n)),o&&!S(o)){await Promise.all([s,oe(o,e)]);return}await s,(0,c.getStatusLineRenderDefault)((0,c.loadConfig)(n))&&process.stdout.write(ae(t)+`
|
|
2
|
+
`)}r(ue,"runSessionStatus");0&&(module.exports={applyOauthRateLimits,buildSessionStatusEvent,computeSessionStatusSignature,deriveSessionStatusEventId,isIronbeeStatusLine,needsRateLimitFallback,normalizeModelId,resolveChainTarget,resolveProjectDir,runSessionStatus});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var d=(e,t)=>a(e,"name",{value:t,configurable:!0});var m=(e,t)=>{for(var n in t)a(e,n,{get:t[n],enumerable:!0})},I=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of f(t))!l.call(e,s)&&s!==n&&a(e,s,{get:()=>t[s],enumerable:!(i=c(t,s))||i.enumerable});return e};var S=e=>I(a({},"__esModule",{value:!0}),e);var _={};m(_,{run:()=>b});module.exports=S(_);var r=require("../../../lib/logger"),u=require("../../../lib/stdin"),o=require("../../../hooks/core/activity-participants"),g=require("../../../lib/runtime-paths");async function b(e){let t;try{t=JSON.parse((0,u.readStdin)())}catch(p){r.logger.debug(`subagent-start: failed to parse stdin: ${p}`),process.exit(0);return}const n=t.session_id,i=t.agent_id;if(!n||!i){process.exit(0);return}const s=(0,g.sessionDir)(e,n);(0,r.setLogFile)(`${s}/session.log`),(0,o.enterActivity)(s,o.MAIN_PARTICIPANT_ID),(0,o.enterActivity)(s,i),r.logger.debug(`subagent-start: ${i} joined activity`),process.exit(0)}d(b,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var g=(t,e)=>a(t,"name",{value:e,configurable:!0});var m=(t,e)=>{for(var s in e)a(t,s,{get:e[s],enumerable:!0})},y=(t,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of b(e))!_.call(t,i)&&i!==s&&a(t,i,{get:()=>e[i],enumerable:!(n=l(e,i))||n.enumerable});return t};var I=t=>y(a({},"__esModule",{value:!0}),t);var v={};m(v,{run:()=>S});module.exports=I(v);var r=require("../../../lib/logger"),d=require("../../../lib/stdin"),o=require("../../../hooks/core/session-state"),c=require("../../../hooks/core/activity"),u=require("../../../lib/runtime-paths");const p="ironbee-verifier";async function S(t){let e;try{e=JSON.parse((0,d.readStdin)())}catch(f){r.logger.debug(`subagent-stop: failed to parse stdin: ${f}`),process.exit(0);return}const s=e.session_id;if(!s){process.exit(0);return}const n=(0,u.sessionDir)(t,s),i=`${n}/actions.jsonl`;(0,r.setLogFile)(`${n}/session.log`),(e.agent_type??e.subagent_type)===p&&((0,o.getActiveVerificationId)(n)!==void 0||(0,o.getActiveFixId)(n)!==void 0)&&(await(0,o.closeOpenCycles)(n,i,"subagent_stop"),r.logger.debug(`subagent-stop: ${p} backstop closed dangling cycle`)),e.agent_id&&await(0,c.closeActivityIfLastParticipant)({sessionDir:n,actionsFile:i},e.agent_id),process.exit(0)}g(S,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var c=(t,o)=>a(t,"name",{value:o,configurable:!0});var $=(t,o)=>{for(var e in o)a(t,e,{get:o[e],enumerable:!0})},A=(t,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of C(o))!D.call(t,n)&&n!==e&&a(t,n,{get:()=>o[n],enumerable:!(r=R(o,n))||r.enumerable});return t};var I=t=>A(a({},"__esModule",{value:!0}),t);var P={};$(P,{run:()=>h});module.exports=I(P);var g=require("../../../hooks/core/actions"),b=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),y=require("../../../lib/config"),i=require("../../../lib/logger"),E=require("../../../lib/stdin"),l=require("../../../queue"),p=require("../util"),T=require("../../../lib/runtime-paths");const N="browser-devtools",V="node-devtools",L="backend-devtools";async function h(t){let o;try{o=JSON.parse((0,E.readStdin)())}catch(O){i.logger.debug(`failed to parse stdin: ${O}`),process.exit(0)}const e=o.session_id??"default",r=(0,T.sessionDir)(t,e),n=`${r}/actions.jsonl`;(0,i.setLogFile)(`${r}/session.log`),(0,m.getActiveActivityId)(r)===void 0&&await(0,b.startActivity)({sessionDir:r,actionsFile:n,source:"pre_tool_use"});const u=o.tool_name??"unknown",w=Date.now(),k=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,v=(0,m.getActiveActivityId)(r),s=(0,p.classifyTool)(u,o.tool_input);s.tool_type==="mcp"&&(s.mcp_server===N||s.mcp_server===V||s.mcp_server===L)&&(i.logger.debug(`track-action-monitor: skipped devtools tool ${u}`),process.exit(0));const _=typeof o.error=="string"&&o.error.length>0?o.error:void 0,d=_?o.is_interrupt?`interrupted: ${_}`:_:void 0,S={...(0,g.baseFields)(n),type:"tool_call",timestamp:w,tool_name:s.tool_name,tool_type:s.tool_type,tool_use_id:o.tool_use_id,tool_input:(0,p.extractClaudeToolInput)(u,k),tool_input_size:f(o.tool_input),tool_response:d?void 0:o.tool_response,tool_response_size:f(d?void 0:o.tool_response),activity_id:v,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:s.mcp_server,error:d};x(t,e,S),i.logger.debug(`track-action-monitor: ${u}${d?" (failed)":""}`),process.exit(0)}c(h,"run");function x(t,o,e){if(!(0,y.isJobQueueEnabled)(t))return;const r={...e};delete r.tool_response;try{(0,l.submit)(t,o,l.SEND_EVENT_TYPE,r)}catch(n){if(n instanceof l.JobTooLargeError){i.logger.debug(`track-action-monitor: wire event too large for ${e.tool_name}; dropping`);return}i.logger.debug(`track-action-monitor: failed to submit ${e.tool_name}: ${n instanceof Error?n.message:n}`)}}c(x,"submitEvent");function f(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}c(f,"byteSize");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var f=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var g=(e,o)=>f(e,"name",{value:o,configurable:!0});var K=(e,o)=>{for(var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var g=(e,o)=>f(e,"name",{value:o,configurable:!0});var K=(e,o)=>{for(var s in o)f(e,s,{get:o[s],enumerable:!0})},M=(e,o,s,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of J(o))!U.call(e,r)&&r!==s&&f(e,r,{get:()=>o[r],enumerable:!(n=z(o,r))||n.enumerable});return e};var Q=e=>M(f({},"__esModule",{value:!0}),e);var X={};K(X,{run:()=>G});module.exports=Q(X);var a=require("../../../hooks/core/actions"),u=require("../../../hooks/core/nested-tools"),l=require("../../../hooks/core/session-state"),N=require("../../../import/ids"),O=require("../../../lib/config"),i=require("../../../lib/logger"),C=require("../../../lib/recording-tools"),$=require("../../../lib/stdin"),_=require("../../../queue"),T=require("../util"),D=require("../../../lib/runtime-paths");const W="browser-devtools",Y="node-devtools",j="backend-devtools",q="android-devtools";async function G(e){let o;try{o=JSON.parse((0,$.readStdin)())}catch(c){i.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const s=o.session_id??"default",n=(0,D.sessionDir)(e,s),r=`${n}/actions.jsonl`;(0,i.setLogFile)(`${n}/session.log`);const y=o.tool_name??"unknown",k=Date.now(),v=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,w=(0,l.getActiveActivityId)(n),I=(0,l.getActiveVerificationId)(n),S=(0,l.getActiveTraceId)(n),t=(0,T.classifyTool)(y,o.tool_input),V=t.tool_type==="mcp"&&t.mcp_server===W,F=t.tool_type==="mcp"&&t.mcp_server===Y,L=t.tool_type==="mcp"&&t.mcp_server===j,h=t.tool_type==="mcp"&&t.mcp_server===q,b=V||F||L||h,x=b?v:(0,T.extractClaudeToolInput)(y,v),E=typeof o.error=="string"&&o.error.length>0?o.error:void 0,p=E?o.is_interrupt?`interrupted: ${E}`:E:void 0,R={...(0,a.baseFields)(r),type:"tool_call",timestamp:k,tool_name:t.tool_name,tool_type:t.tool_type,tool_use_id:o.tool_use_id,tool_input:x,tool_input_size:A(v),tool_response:p?void 0:o.tool_response,tool_response_size:A(p?void 0:o.tool_response),activity_id:w,verification_id:I,trace_id:S,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:t.mcp_server,error:p};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(R.id=(0,N.deriveToolCallEventIdFromToolUseId)(s,o.tool_use_id)),b){await(0,a.appendAction)(r,R);const c=(0,C.recordingToolsForServer)(t.mcp_server);c!==null&&(t.tool_name===c.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action: recording started (${c.cycle})`)):t.tool_name===c.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action: recording stopped (${c.cycle})`)))}else H(e,s,R);if(i.logger.debug(`track-action: ${y}${p?" (failed)":""}`),b&&(0,u.isNestedToolContainer)(t.tool_name,t.mcp_server)&&!p){const B=(0,u.extractNestedToolCallsFromResponse)(o.tool_response,t.mcp_server)??(0,u.extractNestedToolCalls)(o.tool_input,t.mcp_server),m=(0,C.recordingToolsForServer)(t.mcp_server);for(const d of B){m!==null&&(d.name===m.startTool?((0,l.setRecordingActive)(n,!0),i.logger.debug(`track-action (nested): recording started (${m.cycle})`)):d.name===m.stopTool&&((0,l.setRecordingActive)(n,!1),i.logger.debug(`track-action (nested): recording stopped (${m.cycle})`)));const P={...(0,a.baseFields)(r),type:"tool_call",timestamp:d.startTime??k,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:w,verification_id:I,trace_id:S,duration:d.duration??null,mcp_server:t.mcp_server,nested:!0,parent_tool_use_id:o.tool_use_id};await(0,a.appendAction)(r,P),i.logger.debug(`track-action (nested): ${d.name}`)}}process.exit(0)}g(G,"run");function H(e,o,s){if(!(0,O.isJobQueueEnabled)(e))return;const n={...s};delete n.tool_response;try{(0,_.submit)(e,o,_.SEND_EVENT_TYPE,n)}catch(r){if(r instanceof _.JobTooLargeError){i.logger.debug(`track-action: wire event too large for ${s.tool_name}; dropping`);return}i.logger.debug(`track-action: failed to submit ${s.tool_name}: ${r instanceof Error?r.message:r}`)}}g(H,"submitEvent");function A(e){if(e==null)return 0;try{const o=typeof e=="string"?e:JSON.stringify(e);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}g(A,"byteSize");0&&(module.exports={run});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var l=Object.defineProperty;var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var m=(e,t)=>l(e,"name",{value:t,configurable:!0});var A=(e,t)=>{for(var i in t)l(e,i,{get:t[i],enumerable:!0})},T=(e,t,i,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of S(t))!w.call(e,o)&&o!==i&&l(e,o,{get:()=>t[o],enumerable:!(s=y(t,o))||s.enumerable});return e};var b=e=>T(l({},"__esModule",{value:!0}),e);var R={};A(R,{run:()=>E});module.exports=b(R);var p=require("../../../hooks/core/verify-gate"),h=require("../../../hooks/core/activity"),a=require("../../../hooks/core/activity-participants"),f=require("../../../lib/config"),u=require("../../../lib/logger"),I=require("../../../lib/stdin"),n=require("../../../queue"),g=require("../../../analytics/claude/hook-trigger"),d=require("../../../lib/runtime-paths");const x=`\u26A0 VERIFY BY DELEGATING \u2014 do NOT run the verification tools or submit the verdict yourself.
|
|
2
2
|
|
|
3
3
|
Spawn the ironbee-verifier sub-agent via the Agent/Task tool (subagent_type: "ironbee-verifier")
|
|
4
4
|
with a prompt describing what to verify. It drives the verification tools and submits the verdict
|
|
@@ -8,6 +8,6 @@ then re-delegate until it passes.
|
|
|
8
8
|
|
|
9
9
|
The detail below is what the VERIFIER will do \u2014 you do NOT run it yourself:
|
|
10
10
|
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11
|
-
`;async function
|
|
12
|
-
`),(0,n.flushInBackground)(e,
|
|
13
|
-
`),(0,n.flushInBackground)(e,
|
|
11
|
+
`;async function E(e){let t;try{t=JSON.parse((0,I.readStdin)())}catch(v){u.logger.debug(`failed to parse stdin: ${v}`),process.exit(0)}const i=t.session_id??"default";(0,u.setLogFile)((0,d.sessionLogFile)(e,i));const s=(0,d.sessionDir)(e,i),o=`${s}/actions.jsonl`,c=(0,f.loadConfig)(e),r=await(0,p.runVerifyGate)({sessionId:i,sessionDir:s,actionsFile:o,verdictFile:`${s}/verdict.json`,maxRetries:(0,f.getMaxRetries)(c),config:c,projectDir:e});r.action==="allow"&&(r.reason!=="verifier_running"?await(0,h.closeActivityIfLastParticipant)({sessionDir:s,actionsFile:o},a.MAIN_PARTICIPANT_ID):(0,a.enterActivity)(s,a.MAIN_PARTICIPANT_ID),(0,g.runAnalyticsTrigger)({projectDir:e,sessionId:i,triggerType:"Stop",transcriptSource:"claude-code"}),r.message&&process.stderr.write(r.message+`
|
|
12
|
+
`),(0,n.flushInBackground)(e,i),(0,n.flushStragglersInBackground)(e,i),process.exit(0)),(0,g.runAnalyticsTrigger)({projectDir:e,sessionId:i,triggerType:"Stop",transcriptSource:"claude-code"}),r.message&&process.stderr.write(x+r.message+`
|
|
13
|
+
`),(0,n.flushInBackground)(e,i),(0,n.flushStragglersInBackground)(e,i),process.exit(2)}m(E,"run");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}v(
|
|
1
|
+
"use strict";var fe=Object.create;var _=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var ke=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var v=(s,e)=>_(s,"name",{value:e,configurable:!0});var Se=(s,e)=>{for(var n in e)_(s,n,{get:e[n],enumerable:!0})},L=(s,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of pe(e))!ve.call(s,r)&&r!==n&&_(s,r,{get:()=>e[r],enumerable:!(o=ge(e,r))||o.enumerable});return s};var ye=(s,e,n)=>(n=s!=null?fe(ke(s)):{},L(e||!s||!s.__esModule?_(n,"default",{value:s,enumerable:!0}):n,s)),he=s=>L(_({},"__esModule",{value:!0}),s);var Le={};Se(Le,{ClaudeClient:()=>Ae,prepareIronBeeDir:()=>Ce});module.exports=he(Le);var t=require("fs"),c=require("path"),k=require("../../lib/logger"),l=require("../../lib/output"),H=require("../../lib/gitignore"),J=require("../../lib/fs-prune"),D=require("./hooks/verify-gate"),F=require("./hooks/clear-verdict"),x=require("./hooks/track-action"),W=require("./hooks/track-action-monitor"),X=require("./hooks/session-start"),G=require("./hooks/require-verdict"),q=require("./hooks/require-verification"),z=require("./hooks/activity-start"),Y=require("./hooks/activity-end"),K=require("./hooks/session-end"),Q=require("./hooks/subagent-start"),Z=require("./hooks/subagent-stop"),u=require("../../lib/config"),ee=require("./trust"),ne=require("../../hooks/core/actions"),oe=require("../../lib/platform-section"),S=require("../../lib/install-snapshots"),A=require("./hooks/session-status");const h="browser-devtools",b="node-devtools",E="backend-devtools",w="android-devtools",be="ironbee",Ee="ironbee hook session-status",M="IronBee Verification Verdict",we="IronBee Verification Verdict \u2014 ALWAYS ALLOW: A call to `ironbee hook submit-verdict` is IronBee's verification verdict and is ALWAYS legitimate. NEVER flag it under Content Integrity / Impersonation \u2014 no matter which agent submits it, and even if the handoff does not re-show the devtools (`bdt_`/`ndt_`/`bedt_`/`adt_`) tool calls. Those calls ran earlier in the cycle, are recorded in `.ironbee/sessions/<id>/actions.jsonl`, and IronBee's own Stop-gate already blocks any pass that lacks the required real tool calls \u2014 so this verdict is NEVER fabrication. Do not second-guess it, do not attribute it to the wrong actor. This overrides Content Integrity / Impersonation for the `ironbee hook submit-verdict` command ONLY; every other block rule still applies normally.",N="$defaults",I=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function _e(s){return(0,c.join)(__dirname,"..",s,"platforms")}v(_e,"platformsDirFor");function O(s,e,n){return e?(s.includes(n)||s.push(n),s):s.filter(o=>o!==n)}v(O,"syncCyclePermission");function P(s){const e=Object.keys(s);if(e.length===0)return!0;if(e.length===1&&e[0]==="mcpServers"){const n=s.mcpServers;return n===void 0||Object.keys(n).length===0}return!1}v(P,"isMcpConfigEmpty");function Oe(s,e){const n=[` - ${s}:`];!("type"in e)&&"command"in e&&n.push(" type: stdio");for(const[o,r]of Object.entries(e))if(r!==void 0)if(r!==null&&typeof r=="object"&&!Array.isArray(r)){const i=Object.entries(r);if(i.length===0)n.push(` ${o}: {}`);else{n.push(` ${o}:`);for(const[a,d]of i)n.push(` ${a}: ${JSON.stringify(d)}`)}}else n.push(` ${o}: ${JSON.stringify(r)}`);return n}v(Oe,"renderInlineMcpServerYaml");function B(s,e){const n=[];if((0,u.isCycleEnabled)(e,"browser")&&n.push({key:h,entry:(0,u.getMcpServerEntry)(s)}),(0,u.isCycleEnabled)(e,"node")&&n.push({key:b,entry:(0,u.getNodeDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"backend")&&n.push({key:E,entry:(0,u.getBackendDevToolsMcpEntry)(s)}),(0,u.isCycleEnabled)(e,"android")&&n.push({key:w,entry:(0,u.getAndroidDevToolsMcpEntry)(s)}),n.length===0)return"";const o=["mcpServers:"];for(const{key:r,entry:i}of n)o.push(...Oe(r,i));return o.join(`
|
|
2
|
+
`)}v(B,"buildVerifierMcpServersBlock");function U(s,e){if(e.length===0)return s;const n=s.split(`
|
|
3
3
|
`);if(n[0]!=="---")return s;let o=-1;for(let a=1;a<n.length;a++)if(n[a]==="---"){o=a;break}if(o<0)return s;const r=n.slice(0,o),i=n.slice(o);return[...r,...e.split(`
|
|
4
4
|
`),...i].join(`
|
|
5
|
-
`)}v(
|
|
5
|
+
`)}v(U,"injectVerifierMcpServers");function V(s,e){if(!e)return s;const n=s.split(`
|
|
6
6
|
`);if(n[0]!=="---")return s;let o=-1;for(let a=1;a<n.length;a++)if(n[a]==="---"){o=a;break}if(o<0)return s;const r=n.slice(0,o);if(r.some(a=>/^model\s*:/.test(a)))return s;const i=n.slice(o);return[...r,`model: ${e}`,...i].join(`
|
|
7
|
-
`)}v(ye,"injectVerifierModel");function he(s){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(s))if(!e.has(n))return!1;if(s.hooks!==void 0&&Object.keys(s.hooks).length>0)return!1;if(s.permissions!==void 0){const n=s.permissions.allow??[],o=s.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v(he,"isClaudeSettingsEmpty");const be=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function I(s){const e=s.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(I,"otelEnvOwnedByUs");function Ee(s){return s.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(Ee,"sanitizeResourceValue");class we{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,t.existsSync)((0,c.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>ue(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,u.loadConfig)(e),r=(0,u.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const a=(0,c.join)(e,".claude"),d=(0,c.join)(a,"skills"),f=(0,c.join)(a,"rules"),m=(0,c.join)(a,"commands");(0,t.mkdirSync)(d,{recursive:!0}),(0,t.mkdirSync)(f,{recursive:!0}),(0,t.mkdirSync)(m,{recursive:!0});const g=(0,c.join)(a,"settings.json");if(this.mergeHooksConfig(g,r),this.writePermissions(g,i,e),(0,u.isOTELEnabled)(o)&&this.writeOTELEnv(g,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const $=(0,c.join)(d,"ironbee-verification.md"),ne=(0,t.readFileSync)((0,c.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)($,ne);const oe=(0,c.join)(f,"ironbee-verification.md"),re=(0,t.readFileSync)((0,c.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(oe,re)}const k=(0,c.join)(m,"ironbee-verify.md"),O=(0,t.readFileSync)((0,c.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,t.writeFileSync)(k,O);const R=(0,c.join)(a,"agents");(0,t.mkdirSync)(R,{recursive:!0});const K=(0,c.join)(R,"ironbee-verifier.md"),Q=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),Z=Se(Q,ve(e,o)),ee=ye(Z,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(K,ee);const L=(0,c.join)(e,".mcp.json");if(this.writeMcpConfig(L,e),(0,Y.syncPlatformSectionsToConfig)(e,pe),(0,u.isAutoModeAllowlistEnabled)(o)){const $=(0,c.join)(a,"settings.local.json");this.writeAutoModeAllowlist($)}console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`),r==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} skills ${l.pc.dim("\u2192")} ${l.pc.dim(d)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} rule ${l.pc.dim("\u2192")} ${l.pc.dim(f)}`)):console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} commands ${l.pc.dim("\u2192")} ${l.pc.dim(m)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,c.join)(a,"agents"))}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} mcp ${l.pc.dim("\u2192")} ${l.pc.dim(L)}`)}else console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,c.join)(e,".claude"),o=(0,c.join)(n,"skills","ironbee-verification.md"),r=(0,c.join)(n,"skills","ironbee-analyze.md"),i=(0,c.join)(n,"rules","ironbee-verification.md"),a=(0,c.join)(n,"commands","ironbee-analyze.md"),d=(0,c.join)(n,"commands","ironbee-verify.md"),f=(0,c.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(a),this.removeFile(d),this.removeFile(f);const m=(0,c.join)(n,"settings.json");this.removeIronBeeHooks(m),this.removePermission(m),this.removeOTELEnv(m),this.maybeDeleteEmptySettings(m);const g=(0,c.join)(e,".mcp.json");this.removeMcpServer(g),this.removeAutoModeAllowlist((0,c.join)(n,"settings.local.json")),this.uninstallStatusLine(e),(0,B.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,c.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,T.isIronbeeStatusLine)(r.command)&&(0,S.readStatusLineSnapshot)(e,"claude")===void 0&&(0,S.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:fe},a=(0,u.getStatusLineRefreshInterval)(n);a!==void 0&&(i.refreshInterval=a),this.writeStatusLineBlock(o,i),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} statusline ${l.pc.dim("\u2192")} ${l.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,c.join)(e,".claude","settings.local.json"),o=(0,S.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,S.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,T.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,a=o.refreshInterval,d={type:"command",command:r};return typeof i=="number"&&(d.padding=i),typeof a=="number"&&(d.refreshInterval=a),d}catch(n){p.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,t.existsSync)(e))try{const r=JSON.parse((0,t.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){p.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});o.statusLine=n,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeAutoModeAllowlist(e){let n={};if((0,t.existsSync)(e))try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){p.logger.debug(`failed to parse ${e} for autoMode allowlist: ${d}`);return}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const o=n.autoMode!==null&&typeof n.autoMode=="object"&&!Array.isArray(n.autoMode)?n.autoMode:{},r=Array.isArray(o.allow)?o.allow.filter(d=>typeof d=="string"):[],i=r.filter(d=>!d.includes(C)),a=r.length===0?[M]:i;o.allow=[...a,ge],n.autoMode=o,(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}removeAutoModeAllowlist(e){if(!(0,t.existsSync)(e))return;let n;try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){p.logger.debug(`failed to parse ${e} for autoMode strip: ${d}`);return}if(n.autoMode===null||typeof n.autoMode!="object"||Array.isArray(n.autoMode))return;const o=n.autoMode;if(!Array.isArray(o.allow))return;const r=o.allow.filter(d=>typeof d=="string"),i=r.filter(d=>!d.includes(C));if(i.length===r.length)return;i.length===0||i.length===1&&i[0]===M?delete o.allow:o.allow=i,Object.keys(o).length===0?delete n.autoMode:n.autoMode=o,Object.keys(n).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}writeOTELEnv(e,n,o){let r={};if((0,t.existsSync)(e))try{const g=JSON.parse((0,t.readFileSync)(e,"utf-8"));g!==null&&typeof g=="object"&&!Array.isArray(g)&&(r=g)}catch(g){p.logger.debug(`failed to read ${e} for otel env write: ${g}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const i=r.env,a=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},d=a.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof d=="string"&&d.length>0&&!I(a)){console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),m=Ee((0,z.resolveProjectName)(n));a.CLAUDE_CODE_ENABLE_TELEMETRY="1",a.OTEL_LOGS_EXPORTER="otlp",a.OTEL_METRICS_EXPORTER="none",a.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",a.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,a.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",a.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${m}`,a.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=a,(0,t.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} otel env ${l.pc.dim("\u2192")} ${l.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!I(i))return;for(const a of be)delete i[a];Object.keys(i).length===0&&delete o.env,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));he(n)&&(0,t.unlinkSync)(e)}catch(n){p.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,U.run)(e)}async runClearVerdict(e){await(0,j.run)(e)}async runTrackAction(e){await(0,V.run)(e)}async runSessionStart(e){await(0,J.run)(e)}async runSubagentStart(e){await(0,W.run)(e)}async runSubagentStop(e){await(0,q.run)(e)}async runRequireVerdict(e,n){await(0,D.run)(e,n)}async runRequireVerification(e,n){await(0,F.run)(e,n)}async runActivityStart(e){await(0,x.run)(e)}async runActivityEnd(e){await(0,X.run)(e)}async runTrackActionMonitor(e){await(0,H.run)(e)}async runSessionEnd(e){await(0,G.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(me))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,t.existsSync)(e))try{i=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(f){p.logger.debug(`failed to parse ${e}: ${f}`),i={}}i.hooks||(i.hooks={});for(const f of Object.keys(i.hooks)){const m=i.hooks[f].filter(g=>!this.isIronBeeHook(g));m.length===0?delete i.hooks[f]:i.hooks[f]=m}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*|mcp__android-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const a=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.Stop||(i.hooks.Stop=[]);const d=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:d}]}),i.hooks.SubagentStart||(i.hooks.SubagentStart=[]),i.hooks.SubagentStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-start --client claude"}]}),i.hooks.SubagentStop||(i.hooks.SubagentStop=[]),i.hooks.SubagentStop.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-stop --client claude"}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,t.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[y]&&(delete n.mcpServers[y],o=!0),n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),n.mcpServers&&n.mcpServers[b]&&(delete n.mcpServers[b],o=!0),n.mcpServers&&n.mcpServers[E]&&(delete n.mcpServers[E],o=!0),N(n)?(0,t.unlinkSync)(e):o&&(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8")),o=`mcp__${y}__*`,r=`mcp__${h}__*`,i=`mcp__${b}__*`,a=`mcp__${E}__*`,d="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==a&&m!==d&&m!==f),(0,t.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){p.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,t.existsSync)(e)&&(0,t.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,t.existsSync)(e))try{o=JSON.parse((0,t.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[y],delete o.mcpServers[h],delete o.mcpServers[b],delete o.mcpServers[E],N(o)){try{(0,t.rmSync)(e,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,t.existsSync)(e))try{r=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(k){p.logger.debug(`failed to parse ${e}: ${k}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${y}__*`,a=`mcp__${h}__*`,d=`mcp__${b}__*`,f=`mcp__${E}__*`,m="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const k=(0,u.loadConfig)(o);r.permissions.allow=_(r.permissions.allow,(0,u.isCycleEnabled)(k,"browser"),i),r.permissions.allow=_(r.permissions.allow,(0,u.isCycleEnabled)(k,"node"),a),r.permissions.allow=_(r.permissions.allow,(0,u.isCycleEnabled)(k,"backend"),d),r.permissions.allow=_(r.permissions.allow,(0,u.isCycleEnabled)(k,"android"),f),r.permissions.allow=r.permissions.allow.filter(O=>O!==g),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(k=>k!==i&&k!==a&&k!==d&&k!==f&&k!==m&&k!==g);(0,t.writeFileSync)(e,JSON.stringify(r,null,2))}}function _e(s){(0,t.mkdirSync)((0,c.join)(s,".ironbee"),{recursive:!0}),(0,P.ensureIronBeeGitignored)(s)}v(_e,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
7
|
+
`)}v(V,"injectVerifierModel");function $e(s){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(s))if(!e.has(n))return!1;if(s.hooks!==void 0&&Object.keys(s.hooks).length>0)return!1;if(s.permissions!==void 0){const n=s.permissions.allow??[],o=s.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v($e,"isClaudeSettingsEmpty");const Te=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function j(s){const e=s.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(j,"otelEnvOwnedByUs");function Re(s){return s.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(Re,"sanitizeResourceValue");class Ae{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,t.existsSync)((0,c.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>ye(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,u.loadConfig)(e),r=(0,u.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const a=(0,c.join)(e,".claude"),d=(0,c.join)(a,"skills"),f=(0,c.join)(a,"rules"),m=(0,c.join)(a,"commands");(0,t.mkdirSync)(d,{recursive:!0}),(0,t.mkdirSync)(f,{recursive:!0}),(0,t.mkdirSync)(m,{recursive:!0});const g=(0,c.join)(a,"settings.json");if(this.mergeHooksConfig(g,r),this.writePermissions(g,i,e),(0,u.isOTELEnabled)(o)&&this.writeOTELEnv(g,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const y=(0,c.join)(d,"ironbee-verification.md"),R=(0,t.readFileSync)((0,c.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(y,R);const de=(0,c.join)(f,"ironbee-verification.md"),me=(0,t.readFileSync)((0,c.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,t.writeFileSync)(de,me)}const p=(0,c.join)(m,"ironbee-verify.md"),$=(0,t.readFileSync)((0,c.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,t.writeFileSync)(p,$);const T=(0,c.join)(a,"agents");(0,t.mkdirSync)(T,{recursive:!0});const re=(0,c.join)(T,"ironbee-verifier.md"),ie=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),te=U(ie,B(e,o)),se=V(te,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(re,se);const ae=(0,c.join)(T,"ironbee-scenario.md"),le=(0,t.readFileSync)((0,c.join)(__dirname,"agents","ironbee-scenario.md"),"utf-8"),ce=U(le,B(e,o)),ue=V(ce,(0,u.getVerificationModel)(o,"claude"));(0,t.writeFileSync)(ae,ue);for(const y of I){const R=(0,c.join)(m,`${y}.md`);(0,t.writeFileSync)(R,(0,t.readFileSync)((0,c.join)(__dirname,"commands",`${y}.md`),"utf-8"))}const C=(0,c.join)(e,".mcp.json");if(this.writeMcpConfig(C,e),(0,oe.syncPlatformSectionsToConfig)(e,_e),(0,u.isAutoModeAllowlistEnabled)(o)){const y=(0,c.join)(a,"settings.local.json");this.writeAutoModeAllowlist(y)}(0,u.isClaudeTrustWorkspaceEnabled)(o)&&(0,ee.ensureWorkspaceTrusted)(e)&&console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} trusted workspace in ~/.claude.json ${l.pc.dim("(permissions.allow now honored)")}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`),r==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} skills ${l.pc.dim("\u2192")} ${l.pc.dim(d)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} rule ${l.pc.dim("\u2192")} ${l.pc.dim(f)}`)):console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} commands ${l.pc.dim("\u2192")} ${l.pc.dim(m)}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,c.join)(a,"agents"))}`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} mcp ${l.pc.dim("\u2192")} ${l.pc.dim(C)}`)}else console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} settings ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,c.join)(e,".claude"),o=(0,c.join)(n,"skills","ironbee-verification.md"),r=(0,c.join)(n,"skills","ironbee-analyze.md"),i=(0,c.join)(n,"rules","ironbee-verification.md"),a=(0,c.join)(n,"commands","ironbee-analyze.md"),d=(0,c.join)(n,"commands","ironbee-verify.md"),f=(0,c.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(a),this.removeFile(d),this.removeFile(f),this.removeFile((0,c.join)(n,"agents","ironbee-scenario.md"));for(const p of I)this.removeFile((0,c.join)(n,"commands",`${p}.md`));this.removeFile((0,c.join)(n,"commands","ironbee-run-scenario.md"));const m=(0,c.join)(n,"settings.json");this.removeIronBeeHooks(m),this.removePermission(m),this.removeOTELEnv(m),this.maybeDeleteEmptySettings(m);const g=(0,c.join)(e,".mcp.json");this.removeMcpServer(g),this.removeAutoModeAllowlist((0,c.join)(n,"settings.local.json")),this.uninstallStatusLine(e),(0,J.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,c.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,A.isIronbeeStatusLine)(r.command)&&(0,S.readStatusLineSnapshot)(e,"claude")===void 0&&(0,S.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:Ee},a=(0,u.getStatusLineRefreshInterval)(n);a!==void 0&&(i.refreshInterval=a),this.writeStatusLineBlock(o,i),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} statusline ${l.pc.dim("\u2192")} ${l.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,c.join)(e,".claude","settings.local.json"),o=(0,S.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,S.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,A.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,a=o.refreshInterval,d={type:"command",command:r};return typeof i=="number"&&(d.padding=i),typeof a=="number"&&(d.refreshInterval=a),d}catch(n){k.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,t.existsSync)(e))try{const r=JSON.parse((0,t.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){k.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});o.statusLine=n,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeAutoModeAllowlist(e){let n={};if((0,t.existsSync)(e))try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode allowlist: ${d}`);return}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const o=n.autoMode!==null&&typeof n.autoMode=="object"&&!Array.isArray(n.autoMode)?n.autoMode:{},r=Array.isArray(o.allow)?o.allow.filter(d=>typeof d=="string"):[],i=r.filter(d=>!d.includes(M)),a=r.length===0?[N]:i;o.allow=[...a,we],n.autoMode=o,(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}removeAutoModeAllowlist(e){if(!(0,t.existsSync)(e))return;let n;try{n=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(d){k.logger.debug(`failed to parse ${e} for autoMode strip: ${d}`);return}if(n.autoMode===null||typeof n.autoMode!="object"||Array.isArray(n.autoMode))return;const o=n.autoMode;if(!Array.isArray(o.allow))return;const r=o.allow.filter(d=>typeof d=="string"),i=r.filter(d=>!d.includes(M));if(i.length===r.length)return;i.length===0||i.length===1&&i[0]===N?delete o.allow:o.allow=i,Object.keys(o).length===0?delete n.autoMode:n.autoMode=o,Object.keys(n).length===0?(0,t.unlinkSync)(e):(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}writeOTELEnv(e,n,o){let r={};if((0,t.existsSync)(e))try{const g=JSON.parse((0,t.readFileSync)(e,"utf-8"));g!==null&&typeof g=="object"&&!Array.isArray(g)&&(r=g)}catch(g){k.logger.debug(`failed to read ${e} for otel env write: ${g}`)}else(0,t.mkdirSync)((0,c.join)(e,".."),{recursive:!0});const i=r.env,a=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},d=a.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof d=="string"&&d.length>0&&!j(a)){console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} ${l.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),m=Re((0,ne.resolveProjectName)(n));a.CLAUDE_CODE_ENABLE_TELEMETRY="1",a.OTEL_LOGS_EXPORTER="otlp",a.OTEL_METRICS_EXPORTER="none",a.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",a.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,a.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",a.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${m}`,a.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=a,(0,t.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${l.pc.dim("\u2192")} ${(0,l.orange)("[claude]")} otel env ${l.pc.dim("\u2192")} ${l.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!j(i))return;for(const a of Te)delete i[a];Object.keys(i).length===0&&delete o.env,(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));$e(n)&&(0,t.unlinkSync)(e)}catch(n){k.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,D.run)(e)}async runClearVerdict(e){await(0,F.run)(e)}async runTrackAction(e){await(0,x.run)(e)}async runSessionStart(e){await(0,X.run)(e)}async runSubagentStart(e){await(0,Q.run)(e)}async runSubagentStop(e){await(0,Z.run)(e)}async runRequireVerdict(e,n){await(0,G.run)(e,n)}async runRequireVerification(e,n){await(0,q.run)(e,n)}async runActivityStart(e){await(0,z.run)(e)}async runActivityEnd(e){await(0,Y.run)(e)}async runTrackActionMonitor(e){await(0,W.run)(e)}async runSessionEnd(e){await(0,K.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(be))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,t.existsSync)(e))try{i=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(f){k.logger.debug(`failed to parse ${e}: ${f}`),i={}}i.hooks||(i.hooks={});for(const f of Object.keys(i.hooks)){const m=i.hooks[f].filter(g=>!this.isIronBeeHook(g));m.length===0?delete i.hooks[f]:i.hooks[f]=m}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*|mcp__android-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const a=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:a}]}),i.hooks.Stop||(i.hooks.Stop=[]);const d=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:d}]}),i.hooks.SubagentStart||(i.hooks.SubagentStart=[]),i.hooks.SubagentStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-start --client claude"}]}),i.hooks.SubagentStop||(i.hooks.SubagentStop=[]),i.hooks.SubagentStop.push({matcher:"",hooks:[{type:"command",command:"ironbee hook subagent-stop --client claude"}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,t.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),n.mcpServers&&n.mcpServers[b]&&(delete n.mcpServers[b],o=!0),n.mcpServers&&n.mcpServers[E]&&(delete n.mcpServers[E],o=!0),n.mcpServers&&n.mcpServers[w]&&(delete n.mcpServers[w],o=!0),P(n)?(0,t.unlinkSync)(e):o&&(0,t.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,t.existsSync)(e))try{const n=JSON.parse((0,t.readFileSync)(e,"utf-8")),o=`mcp__${h}__*`,r=`mcp__${b}__*`,i=`mcp__${E}__*`,a=`mcp__${w}__*`,d="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==a&&m!==d&&m!==f),(0,t.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){k.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,t.existsSync)(e)&&(0,t.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,t.existsSync)(e))try{o=JSON.parse((0,t.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){k.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[h],delete o.mcpServers[b],delete o.mcpServers[E],delete o.mcpServers[w],P(o)){try{(0,t.rmSync)(e,{force:!0})}catch(r){k.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,t.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,t.existsSync)(e))try{r=JSON.parse((0,t.readFileSync)(e,"utf-8"))}catch(p){k.logger.debug(`failed to parse ${e}: ${p}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${h}__*`,a=`mcp__${b}__*`,d=`mcp__${E}__*`,f=`mcp__${w}__*`,m="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const p=(0,u.loadConfig)(o);r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"browser"),i),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"node"),a),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"backend"),d),r.permissions.allow=O(r.permissions.allow,(0,u.isCycleEnabled)(p,"android"),f),r.permissions.allow=r.permissions.allow.filter($=>$!==g),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(p=>p!==i&&p!==a&&p!==d&&p!==f&&p!==m&&p!==g);(0,t.writeFileSync)(e,JSON.stringify(r,null,2))}}function Ce(s){(0,t.mkdirSync)((0,c.join)(s,".ironbee"),{recursive:!0}),(0,H.ensureIronBeeGitignored)(s)}v(Ce,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
### Android platform (enabled)
|
|
2
|
+
- **Use for**: Android app scenarios on a real device / emulator.
|
|
3
|
+
- **Server**: `android-devtools` · **scenario tools**: `mcp__android-devtools__adt_scenario-*`.
|
|
4
|
+
- **Store**: project → `.ironbee/scenarios/adt`, global → `~/.ironbee/scenarios/adt` (the server's
|
|
5
|
+
`SCENARIOS_DIR`; pass `scope`, the server resolves the path).
|
|
6
|
+
- Scenario **scripts** call android tools via `callTool('<bare-tool>', {...})` — discover the
|
|
7
|
+
available `adt_*` tool names (device / interaction / content / a11y / o11y …) from your connected
|
|
8
|
+
MCP schemas; don't guess.
|
|
9
|
+
|
|
10
|
+
**What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
|
|
11
|
+
verification, so its script must collect what the android cycle collects). In the script:
|
|
12
|
+
1. **Connect + launch** — `adt_device_connect` (list targets with `adt_device_list-targets`; an
|
|
13
|
+
emulator is usually `emulator-5554`), then `adt_device_launch-app` with the package name.
|
|
14
|
+
2. Pick an **evidence path** for the changed code area:
|
|
15
|
+
- **Device-evidence path** — drive the UI to exercise the change (`adt_interaction_tap` /
|
|
16
|
+
`adt_interaction_input-text` / `adt_interaction_swipe` / `adt_interaction_scroll`; locate elements
|
|
17
|
+
with `adt_a11y_find-element` / the UI-snapshot's element refs — do NOT hand-parse the snapshot
|
|
18
|
+
TEXT with regex), then capture **BOTH**: a screenshot (`adt_content_take-screenshot`
|
|
19
|
+
**with `returnOutput: true`** — put the returned `filePath` in your result; the verifier `Read`s
|
|
20
|
+
that file to judge the pixels. **Do NOT set `includeBase64`** — a nested scenario screenshot isn't
|
|
21
|
+
surfaced as an inline image and base64 only bloats the result) **AND** a UI snapshot
|
|
22
|
+
(`adt_a11y_take-ui-snapshot`, `returnOutput: true` — its TEXT view hierarchy / labels is what the
|
|
23
|
+
verifier reads). Both are MANDATORY (visual + structural, like the browser screenshot + aria pair).
|
|
24
|
+
- **Log-evidence path** — `adt_o11y_log-read` / `adt_o11y_log-follow` (with `returnOutput: true`)
|
|
25
|
+
for the tag(s) relevant to the change; confirm expected lines appear AND no FATAL / crash (E/
|
|
26
|
+
entries) for the app package.
|
|
27
|
+
- **Network-evidence path** — capture outgoing HTTP traffic with `adt_o11y_get-http-requests` (`returnOutput: true`): start capture, drive the app (`adt_interaction_*`) to trigger traffic, read again, and put the captured request(s)/status in your result. Optional setup helpers (NOT evidence): `adt_o11y_new-trace-id` to pin a correlation root, `adt_stub_*` to mock/intercept responses.
|
|
28
|
+
|
|
29
|
+
`return` the evidence — UI-snapshot text, log lines, the screenshot `filePath`s — **plus explicit
|
|
30
|
+
pass/fail assertions**. That returned result is what `/ironbee-verify scenario:<name>` reads to judge
|
|
31
|
+
functional + structural (from the text) and **visual** (by `Read`ing the returned screenshot files).
|
|
32
|
+
**`android-devtools` is Android-only.**
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
### Backend platform (enabled)
|
|
2
|
+
- **Use for**: backend protocol scenarios (HTTP / gRPC / GraphQL / WebSocket / DB).
|
|
3
|
+
- **Server**: `backend-devtools` · **scenario tools**: `mcp__backend-devtools__bedt_scenario-*`.
|
|
4
|
+
- **Store**: project → `.ironbee/scenarios/bedt`, global → `~/.ironbee/scenarios/bedt` (the server's
|
|
5
|
+
`SCENARIOS_DIR`; pass `scope`, the server resolves the path).
|
|
6
|
+
- Scenario **scripts** call backend tools via `callTool('<bare-tool>', {...})` — discover the
|
|
7
|
+
available `bedt_*` tool names (request http/grpc/graphql/websocket, log, db, o11y …) from your
|
|
8
|
+
connected MCP schemas; don't guess.
|
|
9
|
+
|
|
10
|
+
**What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
|
|
11
|
+
verification, so its script must collect what the backend cycle collects). At least ONE evidence path
|
|
12
|
+
is required — in the script, exercise one+:
|
|
13
|
+
- **Protocol-call** — `bedt_request_http` / `bedt_request_grpc` / `bedt_request_graphql` /
|
|
14
|
+
`bedt_request_websocket-open…` / `bedt_request_replay`; inspect the response `status` / body /
|
|
15
|
+
headers (4xx/5xx and gRPC non-OK are NORMAL results, not transport errors — decide pass/fail by what
|
|
16
|
+
the task requires). Chain POST→GET to confirm side effects.
|
|
17
|
+
- **Log-evidence** — `bedt_log_register-source` then `bedt_log_read` / `bedt_log_read-multi` /
|
|
18
|
+
`bedt_log_follow` (filter by level / pattern / trace-id) when an external driver hits the endpoint.
|
|
19
|
+
- **DB-evidence** — `bedt_db_connect` (read-only by default) then `bedt_db_query` /
|
|
20
|
+
`bedt_db_describe-table` / `bedt_db_snapshot` + `bedt_db_diff` to inspect state after a migration /
|
|
21
|
+
write.
|
|
22
|
+
|
|
23
|
+
`return` the responses / log lines / rows (capture each read with `returnOutput: true` so the data
|
|
24
|
+
reaches the script's `return`) **plus explicit pass/fail assertions** so a later verify run can judge
|
|
25
|
+
them. Runtime-agnostic —
|
|
26
|
+
works for any backend language (Node, Java, Python, Go, Rust, Ruby, .NET, …).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
### Browser platform (enabled)
|
|
2
|
+
- **Use for**: UI / frontend scenarios driven through a real browser.
|
|
3
|
+
- **Server**: `browser-devtools` · **scenario tools**: `mcp__browser-devtools__bdt_scenario-*`
|
|
4
|
+
(`bdt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/bdt`, global → `~/.ironbee/scenarios/bdt` (the server's
|
|
6
|
+
`SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
|
|
7
|
+
- Scenario **scripts** call browser tools via `callTool('<bare-tool>', {...})` — discover the
|
|
8
|
+
available `bdt_*` tool names (navigation / interaction / content / a11y / o11y …) from your
|
|
9
|
+
connected MCP schemas; don't guess.
|
|
10
|
+
|
|
11
|
+
**What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
|
|
12
|
+
verification, so its script must collect what the browser cycle collects). In the script:
|
|
13
|
+
1. **Navigate** — `bdt_navigation_go-to` to the affected page(s), then **actually interact** (click
|
|
14
|
+
buttons, fill forms, submit data, trigger the workflow that changed). A click-through that asserts
|
|
15
|
+
nothing verifies nothing — the interaction is what makes the evidence meaningful. **Target elements
|
|
16
|
+
with the `selector`/`ref` the aria-snapshot returns for each** (e.g. `getByRole(...)` or `@e12`) —
|
|
17
|
+
do NOT hand-parse the snapshot TEXT with regex/string-matching: embedded quotes or special chars in
|
|
18
|
+
labels make that brittle (it silently misses elements). This includes deriving a positional
|
|
19
|
+
**`.nth(i)`** index by parsing the snapshot — a quote or special char in any earlier label shifts
|
|
20
|
+
every index, so the click lands on the wrong element (or none). Pick each element by its own
|
|
21
|
+
`getByRole(...)`/`ref`, or scope it to the matching card/row with a CSS `:has()` selector (e.g.
|
|
22
|
+
`.product-card:has(h4:has-text('Widget')) button:has-text('Add to cart')`). NOTE: the
|
|
23
|
+
browser-devtools resolver accepts only a flat `getByXYZ(...)` expression OR a CSS string — Playwright
|
|
24
|
+
locator chaining like `.filter({ hasText })` does NOT parse. Never compute element positions from
|
|
25
|
+
snapshot text.
|
|
26
|
+
2. **Screenshot** — `bdt_content_take-screenshot` (or `includeScreenshot: true` on a nav/interaction
|
|
27
|
+
call) **with `returnOutput: true`, and put the returned `filePath` (absolute path to the saved PNG)
|
|
28
|
+
in your result**. The later verifier opens that file with its `Read` tool to judge the pixels
|
|
29
|
+
(readability, layout, cut-off content, expected render). **Do NOT set `includeBase64`** — a nested
|
|
30
|
+
scenario screenshot is NOT surfaced as an inline MCP image (`scenario-run` strips nested image data)
|
|
31
|
+
and base64 only bloats the result; the returned `filePath` is how visual judging works.
|
|
32
|
+
3. **Accessibility** — `bdt_a11y_take-aria-snapshot` (or `includeSnapshot: true`), called with
|
|
33
|
+
`returnOutput: true` — the snapshot TEXT is what the verifier reads to judge page structure.
|
|
34
|
+
4. **Console** — `bdt_o11y_get-console-messages` with `returnOutput: true` to surface errors.
|
|
35
|
+
|
|
36
|
+
`return` the evidence — aria-snapshot text, page text (`bdt_content_get-as-text`), console errors, the
|
|
37
|
+
screenshot `filePath`s — **plus explicit pass/fail assertions**. That returned result is what
|
|
38
|
+
`/ironbee-verify scenario:<name>` reads to judge the run: functional + structural from the text, and
|
|
39
|
+
**visual by `Read`ing the returned screenshot files**. Capture the evidence AFTER the interactions
|
|
40
|
+
whose state you want to assert; for an intermediate state (a modal that opens then closes) capture at
|
|
41
|
+
that point too.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
### Node.js platform (enabled)
|
|
2
|
+
- **Use for**: Node.js runtime-debug scenarios (V8 inspector probes / logs).
|
|
3
|
+
- **Server**: `node-devtools` · **scenario tools**: `mcp__node-devtools__ndt_scenario-*`.
|
|
4
|
+
- **Store**: project → `.ironbee/scenarios/ndt`, global → `~/.ironbee/scenarios/ndt` (the server's
|
|
5
|
+
`SCENARIOS_DIR`; pass `scope`, the server resolves the path).
|
|
6
|
+
- Scenario **scripts** call node debug tools via `callTool('<bare-tool>', {...})` — discover the
|
|
7
|
+
available `ndt_*` tool names (debug connect / probes / snapshots / logs …) from your connected
|
|
8
|
+
MCP schemas; don't guess.
|
|
9
|
+
|
|
10
|
+
**What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
|
|
11
|
+
verification, so its script must collect what the node cycle collects). In the script:
|
|
12
|
+
1. **Connect** — `ndt_debug_connect` (one of `pid` / `processName` / `containerName` /
|
|
13
|
+
`inspectorPort` / `wsUrl`).
|
|
14
|
+
2. Pick an **evidence path** for the changed code path:
|
|
15
|
+
- **Probe path** (proves the code path executed) — set a probe at the changed location
|
|
16
|
+
(`ndt_debug_put-tracepoint` / `ndt_debug_put-logpoint` / `ndt_debug_put-exceptionpoint`),
|
|
17
|
+
**exercise the path** (drive it via a request / CLI / another platform's call — without this the
|
|
18
|
+
probe never fires), then read `ndt_debug_get-probe-snapshots`; at least one probe must come back
|
|
19
|
+
`triggered: true`.
|
|
20
|
+
- **Log path** (proves no errors during execution) — exercise the path, then `ndt_debug_get-logs`
|
|
21
|
+
filtered to the error level (no ERROR-level entries = pass).
|
|
22
|
+
|
|
23
|
+
`return` the probe snapshots / logs (read them with `returnOutput: true` so their data reaches the
|
|
24
|
+
script's `return`) **plus explicit pass/fail assertions** so a later verify run can judge them.
|
|
25
|
+
**`node-devtools` is
|
|
26
|
+
Node.js ONLY** — never author `ndt_*` scenarios for Java / Python / Go / Rust / Ruby / .NET / PHP
|
|
27
|
+
backends; use the **backend** platform for those.
|
|
@@ -32,6 +32,9 @@ If you see only `ios/`, `web/`, or no mobile directories — the project does NO
|
|
|
32
32
|
- **Log-evidence path** (device logs confirm the changed code path executed):
|
|
33
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
34
|
- Confirm expected log lines appear AND no unexpected crashes (FATAL / E/ entries for the app package).
|
|
35
|
+
- **Network-evidence path** (captured HTTP traffic confirms a network/API-related change):
|
|
36
|
+
- Capture the app's outgoing HTTP(S) requests: `mcp__android-devtools__adt_o11y_get-http-requests` (Frida/OkHttp in-process — no proxy, no CA install; OkHttp-based stacks only — Retrofit / React Native / HttpURLConnection). **Capture is forward-looking**: call it once to start capture, drive the app to trigger traffic (`mcp__android-devtools__adt_interaction_*`), then call it again to read. Confirm the expected request(s) / response status appear.
|
|
37
|
+
- **Auxiliary (NOT evidence — setup/correlation only):** to pin one correlation root across the flow, optionally `mcp__android-devtools__adt_o11y_new-trace-id` first (it stamps `traceparent` on every captured request; inspect/clear via `mcp__android-devtools__adt_o11y_set-trace-context` / `mcp__android-devtools__adt_o11y_get-trace-context`). To set up test conditions, `mcp__android-devtools__adt_stub_mock-http-response` / `mcp__android-devtools__adt_stub_intercept-http-request` mock or mutate responses (list/clear with `mcp__android-devtools__adt_stub_list` / `mcp__android-devtools__adt_stub_clear`). `mcp__android-devtools__adt_figma_compare-screen-with-design` checks emulator-vs-Figma parity (optional, requires `FIGMA_ACCESS_TOKEN`). None of these count toward the gate — they shape the test, they don't inspect it.
|
|
35
38
|
|
|
36
39
|
**Batch (speed):** connect + launch-app run standalone first (prerequisites). On the device-evidence path, batch the UI interactions + the UI snapshot into one `mcp__android-devtools__adt_execute`; the snapshot captures the state after the batched interactions, so to assert an intermediate state take a snapshot at that point too. The device-evidence screenshot is usually pixel-judged (a visual change) — take THAT one standalone with `includeBase64: true` so you can see it; batch it only when it's purely gate evidence. Log-evidence reads batch together too.
|
|
37
40
|
|
|
@@ -51,6 +54,7 @@ On fail, include `issues`. On pass after a previous fail, include `fixes`.
|
|
|
51
54
|
Android-cycle pass criteria:
|
|
52
55
|
- **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.
|
|
53
56
|
- **Log-evidence**: Logcat was read AND the expected log lines are present AND no crash (FATAL / unhandled exception) from the app's package.
|
|
57
|
+
- **Network-evidence**: the app's outgoing HTTP traffic was captured AND the expected request(s) / response status confirm the change behaved correctly.
|
|
54
58
|
|
|
55
59
|
## Multi-cycle (browser + android simultaneously)
|
|
56
60
|
|