@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 e=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var p=(o,t)=>e(o,"name",{value:t,configurable:!0});var I=(o,t)=>{for(var s in t)e(o,s,{get:t[s],enumerable:!0})},_=(o,t,s,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of y(t))!C.call(o,n)&&n!==s&&e(o,n,{get:()=>t[n],enumerable:!(i=w(t,n))||i.enumerable});return o};var H=o=>_(e({},"__esModule",{value:!0}),o);var h={};I(h,{run:()=>O});module.exports=H(h);var d=require("../../../hooks/core/verify-gate"),k=require("../../../hooks/core/activity"),a=require("../../../lib/config"),u=require("../../../lib/logger"),l=require("../../../lib/output"),S=require("../../../lib/stdin"),g=require("../../../queue"),f=require("../../../lib/runtime-paths");async function O(o){let t;try{t=JSON.parse((0,S.readStdin)())}catch(v){u.logger.debug(`failed to parse stdin: ${v}`),(0,l.writeAndExit)(JSON.stringify({}),0);return}const s=t.conversation_id??"default";(0,u.setLogFile)((0,f.sessionLogFile)(o,s));const i=(0,f.sessionDir)(o,s),n=`${i}/actions.jsonl`,m=(0,a.loadConfig)(o),r=await(0,d.runVerifyGate)({sessionId:s,sessionDir:i,actionsFile:n,verdictFile:`${i}/verdict.json`,maxRetries:(0,a.getMaxRetries)(m),config:m,projectDir:o}),c={};r.action==="allow"&&await(0,k.endActivity)({sessionDir:i,actionsFile:n}),r.action==="block"&&r.message&&(c.followup_message=r.message),(0,g.flushInBackground)(o,s),(0,g.flushStragglersInBackground)(o,s),(0,l.writeAndExit)(JSON.stringify(c),0)}p(O,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var S=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var k=(l,o)=>S(l,"name",{value:o,configurable:!0});var Q=(l,o)=>{for(var e in o)S(l,e,{get:o[e],enumerable:!0})},X=(l,o,e,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of x(o))!z.call(l,t)&&t!==e&&S(l,t,{get:()=>o[t],enumerable:!(s=W(o,t))||s.enumerable});return l};var Y=l=>X(S({},"__esModule",{value:!0}),l);var io={};Q(io,{CursorClient:()=>so});module.exports=Y(io);var i=require("fs"),c=require("path"),p=require("../../lib/logger"),n=require("../../lib/output"),_=require("../../lib/fs-prune"),T=require("./hooks/verify-gate"),P=require("./hooks/clear-verdict"),I=require("./hooks/track-action"),N=require("./hooks/track-action-monitor"),B=require("./hooks/session-start"),V=require("./hooks/require-verdict"),J=require("./hooks/require-verification"),U=require("./hooks/activity-start"),D=require("./hooks/activity-end"),L=require("./hooks/session-end"),a=require("../../lib/config"),F=require("../../lib/platform-section"),K=require("../../lib/gitignore");const v="browser-devtools",y="node-devtools",h="backend-devtools",b="android-devtools",Z="ironbee";function oo(l){return(0,c.join)(__dirname,"..",l,"platforms")}k(oo,"platformsDirFor");function H(l){const o=Object.keys(l);if(o.length===0)return!0;if(o.length===1&&o[0]==="mcpServers"){const e=l.mcpServers;return e===void 0||Object.keys(e).length===0}return!1}k(H,"isMcpConfigEmpty");const w="ironbee",O=[`${v}:*`,`${y}:*`,`${h}:*`,`${b}:*`];function eo(l){const o=new Set(["mcpAllowlist","terminalAllowlist"]);for(const t of Object.keys(l))if(!o.has(t))return!1;const e=l.mcpAllowlist??[],s=l.terminalAllowlist??[];return e.length===0&&s.length===0}k(eo,"isPermissionsEmpty");function ro(l){const o=new Set(["version","hooks"]);for(const e of Object.keys(l))if(!o.has(e))return!1;return Object.keys(l.hooks??{}).length===0}k(ro,"isCursorHooksEmpty");function d(l){return n.pc.dim(l)}k(d,"cursorColor");class so{constructor(){this.name="cursor"}static{k(this,"CursorClient")}detect(o){return(0,i.existsSync)((0,c.join)(o,".cursor"))}resolveProjectDir(){return process.env.CURSOR_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(o,e){const s=e??(0,a.loadConfig)(o),t=(0,a.getVerificationMode)(s),r=t!=="monitor";this.cleanupArtifacts(o),(0,K.ensureIronBeeGitignored)(o);const u=(0,c.join)(o,".cursor"),g=(0,c.join)(u,"rules"),f=(0,c.join)(u,"skills");(0,i.mkdirSync)(g,{recursive:!0}),(0,i.mkdirSync)(f,{recursive:!0});const m=(0,c.join)(u,"hooks.json");if(this.mergeHooksConfig(m,t),r){if(t==="enforce"){const M=(0,c.join)(f,"ironbee-verification.md"),j=(0,i.readFileSync)((0,c.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,i.writeFileSync)(M,j);const R=(0,c.join)(g,"ironbee-verification.mdc"),G=(0,i.readFileSync)((0,c.join)(__dirname,"rules","ironbee-verification.mdc"),"utf-8");(0,i.writeFileSync)(R,G),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} skill ${n.pc.dim("\u2192")} ${n.pc.dim(M)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} rule ${n.pc.dim("\u2192")} ${n.pc.dim(R)}`)}const C=(0,c.join)(f,"ironbee-verify");(0,i.mkdirSync)(C,{recursive:!0});const $=(0,c.join)(C,"SKILL.md"),q=(0,i.readFileSync)((0,c.join)(__dirname,"commands","ironbee-verify","SKILL.md"),"utf-8");(0,i.writeFileSync)($,q);const E=(0,c.join)(u,"mcp.json");this.writeMcpConfig(E,o);const A=(0,c.join)(u,"permissions.json");this.writePermissionsConfig(A,o),(0,F.syncPlatformSectionsToConfig)(o,oo),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),t==="assist"&&console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} command ${n.pc.dim("\u2192")} ${n.pc.dim($)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} mcp ${n.pc.dim("\u2192")} ${n.pc.dim(E)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} perms ${n.pc.dim("\u2192")} ${n.pc.dim(A)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} ${n.pc.yellow("Cursor requires manual steps:")}`),console.log(` ${n.pc.yellow("1.")} Restart Cursor to load the new hooks and MCP config`),console.log(` ${n.pc.yellow("2.")} Go to ${n.pc.bold("Settings \u2192 Tools & MCP")} and verify ${n.pc.bold("browser-devtools")} is enabled`),console.log(` ${n.pc.yellow("3.")} If the server shows as enabled but tools are unavailable, toggle it off and on`)}else console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to load the new hook config`)}uninstall(o){this.cleanupArtifacts(o),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} removed hooks, skill, rule, command, and MCP`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to apply changes`)}cleanupArtifacts(o){const e=(0,c.join)(o,".cursor"),s=(0,c.join)(e,"skills","ironbee-verification.md"),t=(0,c.join)(e,"rules","ironbee-verification.mdc"),r=(0,c.join)(e,"skills","ironbee-analyze","SKILL.md"),u=(0,c.join)(e,"skills","ironbee-verify","SKILL.md");this.removeFile(s),this.removeFile(t),this.removeFile(r),this.removeFile(u);const g=(0,c.join)(e,"hooks.json");this.removeIronBeeHooks(g),this.maybeDeleteEmptyHooks(g);const f=(0,c.join)(e,"mcp.json");this.removeMcpServer(f);const m=(0,c.join)(e,"permissions.json");this.removePermissionsEntries(m),(0,_.pruneEmptyDirs)(e)}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));ro(e)&&(0,i.unlinkSync)(o)}catch(e){p.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}async runVerifyGate(o){await(0,T.run)(o)}async runClearVerdict(o){await(0,P.run)(o)}async runTrackAction(o){await(0,I.run)(o)}async runSessionStart(o){await(0,B.run)(o)}async runRequireVerdict(o,e){await(0,V.run)(o,e)}async runRequireVerification(o,e){await(0,J.run)(o,e)}async runActivityStart(o){await(0,U.run)(o)}async runActivityEnd(o){await(0,D.run)(o)}async runTrackActionMonitor(o){await(0,N.run)(o)}async runSessionEnd(o){await(0,L.run)(o)}async runTrackActionPre(o){}isIronBeeHook(o){return o.command.includes(Z)}mergeHooksConfig(o,e){const s=e!=="monitor",t=e==="assist"?" --soft":"";let r={version:1,hooks:{}};if((0,i.existsSync)(o))try{r=JSON.parse((0,i.readFileSync)(o,"utf-8")),r.hooks||(r.hooks={})}catch(f){p.logger.debug(`failed to parse ${o}: ${f}`),r={version:1,hooks:{}}}for(const f of Object.keys(r.hooks)){const m=r.hooks[f].filter(C=>!this.isIronBeeHook(C));m.length===0?delete r.hooks[f]:r.hooks[f]=m}r.hooks.sessionStart||(r.hooks.sessionStart=[]),r.hooks.sessionStart.push({command:"ironbee hook session-start --client cursor"}),r.hooks.beforeSubmitPrompt||(r.hooks.beforeSubmitPrompt=[]),r.hooks.beforeSubmitPrompt.push({command:"ironbee hook activity-start --client cursor"}),s&&(r.hooks.preToolUse||(r.hooks.preToolUse=[]),r.hooks.preToolUse.push({command:`ironbee hook require-verification --client cursor${t}`,matcher:"MCP:(bdt|ndt|bedt|adt)_.*",failClosed:e==="enforce"}),r.hooks.preToolUse.push({command:`ironbee hook require-verdict --client cursor${t}`,matcher:"Write|StrReplace|Delete",failClosed:e==="enforce"}),r.hooks.postToolUse||(r.hooks.postToolUse=[]),r.hooks.postToolUse.push({command:"ironbee hook clear-verdict --client cursor",matcher:"Write|StrReplace|Delete"})),r.hooks.postToolUse||(r.hooks.postToolUse=[]);const u=s?"ironbee hook track-action --client cursor":"ironbee hook track-action-monitor --client cursor";r.hooks.postToolUse.push({command:u}),r.hooks.postToolUseFailure||(r.hooks.postToolUseFailure=[]),r.hooks.postToolUseFailure.push({command:u}),r.hooks.stop||(r.hooks.stop=[]);const g=e==="enforce"?"ironbee hook verify-gate --client cursor":"ironbee hook activity-end --client cursor";r.hooks.stop.push({command:g}),r.hooks.sessionEnd||(r.hooks.sessionEnd=[]),r.hooks.sessionEnd.push({command:"ironbee hook session-end --client cursor"}),r.version=1,(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}removeIronBeeHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));if(!e.hooks)return;for(const s of Object.keys(e.hooks)){const t=e.hooks[s].filter(r=>!this.isIronBeeHook(r));t.length===0?delete e.hooks[s]:e.hooks[s]=t}(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove hooks from ${o}: ${e}`)}}removeMcpServer(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));let s=!1;e.mcpServers&&e.mcpServers[v]&&(delete e.mcpServers[v],s=!0),e.mcpServers&&e.mcpServers[y]&&(delete e.mcpServers[y],s=!0),e.mcpServers&&e.mcpServers[h]&&(delete e.mcpServers[h],s=!0),e.mcpServers&&e.mcpServers[b]&&(delete e.mcpServers[b],s=!0),H(e)?(0,i.unlinkSync)(o):s&&(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove MCP server from ${o}: ${e}`)}}removeFile(o){(0,i.existsSync)(o)&&(0,i.unlinkSync)(o)}writeMcpConfig(o,e){let s={mcpServers:{}};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8")),s.mcpServers||(s.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${o}: ${r}`),s={mcpServers:{}}}const t=(0,a.loadConfig)(e);if((0,a.isCycleEnabled)(t,"browser")?s.mcpServers[v]=(0,a.getMcpServerEntry)(e):delete s.mcpServers[v],(0,a.isCycleEnabled)(t,"node")?s.mcpServers[y]=(0,a.getNodeDevToolsMcpEntry)(e):delete s.mcpServers[y],(0,a.isCycleEnabled)(t,"backend")?s.mcpServers[h]=(0,a.getBackendDevToolsMcpEntry)(e):delete s.mcpServers[h],(0,a.isCycleEnabled)(t,"android")?s.mcpServers[b]=(0,a.getAndroidDevToolsMcpEntry)(e):delete s.mcpServers[b],H(s)){try{(0,i.rmSync)(o,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${o}: ${r}`)}return}(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}writePermissionsConfig(o,e){let s={};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${o}: ${m}`),s={}}const t=(0,a.loadConfig)(e),r=[];(0,a.isCycleEnabled)(t,"browser")&&r.push(`${v}:*`),(0,a.isCycleEnabled)(t,"node")&&r.push(`${y}:*`),(0,a.isCycleEnabled)(t,"backend")&&r.push(`${h}:*`),(0,a.isCycleEnabled)(t,"android")&&r.push(`${b}:*`);const u=new Set(O),g=(s.mcpAllowlist??[]).filter(m=>!u.has(m));for(const m of r)g.includes(m)||g.push(m);g.length>0?s.mcpAllowlist=g:delete s.mcpAllowlist;const f=(s.terminalAllowlist??[]).filter(m=>m!==w);f.push(w),s.terminalAllowlist=f,(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}removePermissionsEntries(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8")),s=new Set(O);Array.isArray(e.mcpAllowlist)&&(e.mcpAllowlist=e.mcpAllowlist.filter(t=>!s.has(t)),e.mcpAllowlist.length===0&&delete e.mcpAllowlist),Array.isArray(e.terminalAllowlist)&&(e.terminalAllowlist=e.terminalAllowlist.filter(t=>t!==w),e.terminalAllowlist.length===0&&delete e.terminalAllowlist),eo(e)?(0,i.unlinkSync)(o):(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove permissions from ${o}: ${e}`)}}}0&&(module.exports={CursorClient});
|
|
1
|
+
"use strict";var $=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var v=(c,o)=>$(c,"name",{value:o,configurable:!0});var X=(c,o)=>{for(var e in o)$(c,e,{get:o[e],enumerable:!0})},Y=(c,o,e,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of z(o))!Q.call(c,t)&&t!==e&&$(c,t,{get:()=>o[t],enumerable:!(s=x(o,t))||s.enumerable});return c};var Z=c=>Y($({},"__esModule",{value:!0}),c);var no={};X(no,{CursorClient:()=>io});module.exports=Z(no);var i=require("fs"),l=require("path"),p=require("../../lib/logger"),n=require("../../lib/output"),T=require("../../lib/fs-prune"),N=require("./hooks/verify-gate"),P=require("./hooks/clear-verdict"),L=require("./hooks/track-action"),B=require("./hooks/track-action-monitor"),V=require("./hooks/session-start"),J=require("./hooks/require-verdict"),D=require("./hooks/require-verification"),F=require("./hooks/activity-start"),U=require("./hooks/activity-end"),K=require("./hooks/session-end"),a=require("../../lib/config"),q=require("../../lib/platform-section"),j=require("../../lib/gitignore");const y="browser-devtools",b="node-devtools",h="backend-devtools",S="android-devtools",oo="ironbee",O=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function eo(c){return(0,l.join)(__dirname,"..",c,"platforms")}v(eo,"platformsDirFor");function _(c){const o=Object.keys(c);if(o.length===0)return!0;if(o.length===1&&o[0]==="mcpServers"){const e=c.mcpServers;return e===void 0||Object.keys(e).length===0}return!1}v(_,"isMcpConfigEmpty");const E="ironbee",I=[`${y}:*`,`${b}:*`,`${h}:*`,`${S}:*`];function ro(c){const o=new Set(["mcpAllowlist","terminalAllowlist"]);for(const t of Object.keys(c))if(!o.has(t))return!1;const e=c.mcpAllowlist??[],s=c.terminalAllowlist??[];return e.length===0&&s.length===0}v(ro,"isPermissionsEmpty");function so(c){const o=new Set(["version","hooks"]);for(const e of Object.keys(c))if(!o.has(e))return!1;return Object.keys(c.hooks??{}).length===0}v(so,"isCursorHooksEmpty");function d(c){return n.pc.dim(c)}v(d,"cursorColor");class io{constructor(){this.name="cursor"}static{v(this,"CursorClient")}detect(o){return(0,i.existsSync)((0,l.join)(o,".cursor"))}resolveProjectDir(){return process.env.CURSOR_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(o,e){const s=e??(0,a.loadConfig)(o),t=(0,a.getVerificationMode)(s),r=t!=="monitor";this.cleanupArtifacts(o),(0,j.ensureIronBeeGitignored)(o);const u=(0,l.join)(o,".cursor"),g=(0,l.join)(u,"rules"),f=(0,l.join)(u,"skills");(0,i.mkdirSync)(g,{recursive:!0}),(0,i.mkdirSync)(f,{recursive:!0});const m=(0,l.join)(u,"hooks.json");if(this.mergeHooksConfig(m,t),r){if(t==="enforce"){const C=(0,l.join)(f,"ironbee-verification.md"),w=(0,i.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,i.writeFileSync)(C,w);const H=(0,l.join)(g,"ironbee-verification.mdc"),W=(0,i.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.mdc"),"utf-8");(0,i.writeFileSync)(H,W),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} skill ${n.pc.dim("\u2192")} ${n.pc.dim(C)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} rule ${n.pc.dim("\u2192")} ${n.pc.dim(H)}`)}const k=(0,l.join)(f,"ironbee-verify");(0,i.mkdirSync)(k,{recursive:!0});const A=(0,l.join)(k,"SKILL.md"),G=(0,i.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify","SKILL.md"),"utf-8");(0,i.writeFileSync)(A,G);for(const C of O){const w=(0,l.join)(f,C);(0,i.mkdirSync)(w,{recursive:!0}),(0,i.writeFileSync)((0,l.join)(w,"SKILL.md"),(0,i.readFileSync)((0,l.join)(__dirname,"commands",C,"SKILL.md"),"utf-8"))}const M=(0,l.join)(u,"mcp.json");this.writeMcpConfig(M,o);const R=(0,l.join)(u,"permissions.json");this.writePermissionsConfig(R,o),(0,q.syncPlatformSectionsToConfig)(o,eo),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),t==="assist"&&console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} command ${n.pc.dim("\u2192")} ${n.pc.dim(A)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} mcp ${n.pc.dim("\u2192")} ${n.pc.dim(M)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} perms ${n.pc.dim("\u2192")} ${n.pc.dim(R)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} ${n.pc.yellow("Cursor requires manual steps:")}`),console.log(` ${n.pc.yellow("1.")} Restart Cursor to load the new hooks and MCP config`),console.log(` ${n.pc.yellow("2.")} Go to ${n.pc.bold("Settings \u2192 Tools & MCP")} and verify ${n.pc.bold("browser-devtools")} is enabled`),console.log(` ${n.pc.yellow("3.")} If the server shows as enabled but tools are unavailable, toggle it off and on`)}else console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to load the new hook config`)}uninstall(o){this.cleanupArtifacts(o),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} removed hooks, skill, rule, command, and MCP`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to apply changes`)}cleanupArtifacts(o){const e=(0,l.join)(o,".cursor"),s=(0,l.join)(e,"skills","ironbee-verification.md"),t=(0,l.join)(e,"rules","ironbee-verification.mdc"),r=(0,l.join)(e,"skills","ironbee-analyze","SKILL.md"),u=(0,l.join)(e,"skills","ironbee-verify","SKILL.md");this.removeFile(s),this.removeFile(t),this.removeFile(r),this.removeFile(u);for(const k of O)this.removeFile((0,l.join)(e,"skills",k,"SKILL.md"));this.removeFile((0,l.join)(e,"skills","ironbee-run-scenario","SKILL.md"));const g=(0,l.join)(e,"hooks.json");this.removeIronBeeHooks(g),this.maybeDeleteEmptyHooks(g);const f=(0,l.join)(e,"mcp.json");this.removeMcpServer(f);const m=(0,l.join)(e,"permissions.json");this.removePermissionsEntries(m),(0,T.pruneEmptyDirs)(e)}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));so(e)&&(0,i.unlinkSync)(o)}catch(e){p.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}async runVerifyGate(o){await(0,N.run)(o)}async runClearVerdict(o){await(0,P.run)(o)}async runTrackAction(o){await(0,L.run)(o)}async runSessionStart(o){await(0,V.run)(o)}async runRequireVerdict(o,e){await(0,J.run)(o,e)}async runRequireVerification(o,e){await(0,D.run)(o,e)}async runActivityStart(o){await(0,F.run)(o)}async runActivityEnd(o){await(0,U.run)(o)}async runTrackActionMonitor(o){await(0,B.run)(o)}async runSessionEnd(o){await(0,K.run)(o)}async runTrackActionPre(o){}isIronBeeHook(o){return o.command.includes(oo)}mergeHooksConfig(o,e){const s=e!=="monitor",t=e==="assist"?" --soft":"";let r={version:1,hooks:{}};if((0,i.existsSync)(o))try{r=JSON.parse((0,i.readFileSync)(o,"utf-8")),r.hooks||(r.hooks={})}catch(f){p.logger.debug(`failed to parse ${o}: ${f}`),r={version:1,hooks:{}}}for(const f of Object.keys(r.hooks)){const m=r.hooks[f].filter(k=>!this.isIronBeeHook(k));m.length===0?delete r.hooks[f]:r.hooks[f]=m}r.hooks.sessionStart||(r.hooks.sessionStart=[]),r.hooks.sessionStart.push({command:"ironbee hook session-start --client cursor"}),r.hooks.beforeSubmitPrompt||(r.hooks.beforeSubmitPrompt=[]),r.hooks.beforeSubmitPrompt.push({command:"ironbee hook activity-start --client cursor"}),s&&(r.hooks.preToolUse||(r.hooks.preToolUse=[]),r.hooks.preToolUse.push({command:`ironbee hook require-verification --client cursor${t}`,matcher:"MCP:(bdt|ndt|bedt|adt)_.*",failClosed:e==="enforce"}),r.hooks.preToolUse.push({command:`ironbee hook require-verdict --client cursor${t}`,matcher:"Write|StrReplace|Delete",failClosed:e==="enforce"}),r.hooks.postToolUse||(r.hooks.postToolUse=[]),r.hooks.postToolUse.push({command:"ironbee hook clear-verdict --client cursor",matcher:"Write|StrReplace|Delete"})),r.hooks.postToolUse||(r.hooks.postToolUse=[]);const u=s?"ironbee hook track-action --client cursor":"ironbee hook track-action-monitor --client cursor";r.hooks.postToolUse.push({command:u}),r.hooks.postToolUseFailure||(r.hooks.postToolUseFailure=[]),r.hooks.postToolUseFailure.push({command:u}),r.hooks.stop||(r.hooks.stop=[]);const g=e==="enforce"?"ironbee hook verify-gate --client cursor":"ironbee hook activity-end --client cursor";r.hooks.stop.push({command:g}),r.hooks.sessionEnd||(r.hooks.sessionEnd=[]),r.hooks.sessionEnd.push({command:"ironbee hook session-end --client cursor"}),r.version=1,(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}removeIronBeeHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));if(!e.hooks)return;for(const s of Object.keys(e.hooks)){const t=e.hooks[s].filter(r=>!this.isIronBeeHook(r));t.length===0?delete e.hooks[s]:e.hooks[s]=t}(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove hooks from ${o}: ${e}`)}}removeMcpServer(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));let s=!1;e.mcpServers&&e.mcpServers[y]&&(delete e.mcpServers[y],s=!0),e.mcpServers&&e.mcpServers[b]&&(delete e.mcpServers[b],s=!0),e.mcpServers&&e.mcpServers[h]&&(delete e.mcpServers[h],s=!0),e.mcpServers&&e.mcpServers[S]&&(delete e.mcpServers[S],s=!0),_(e)?(0,i.unlinkSync)(o):s&&(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove MCP server from ${o}: ${e}`)}}removeFile(o){(0,i.existsSync)(o)&&(0,i.unlinkSync)(o)}writeMcpConfig(o,e){let s={mcpServers:{}};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8")),s.mcpServers||(s.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${o}: ${r}`),s={mcpServers:{}}}const t=(0,a.loadConfig)(e);if((0,a.isCycleEnabled)(t,"browser")?s.mcpServers[y]=(0,a.getMcpServerEntry)(e):delete s.mcpServers[y],(0,a.isCycleEnabled)(t,"node")?s.mcpServers[b]=(0,a.getNodeDevToolsMcpEntry)(e):delete s.mcpServers[b],(0,a.isCycleEnabled)(t,"backend")?s.mcpServers[h]=(0,a.getBackendDevToolsMcpEntry)(e):delete s.mcpServers[h],(0,a.isCycleEnabled)(t,"android")?s.mcpServers[S]=(0,a.getAndroidDevToolsMcpEntry)(e):delete s.mcpServers[S],_(s)){try{(0,i.rmSync)(o,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${o}: ${r}`)}return}(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}writePermissionsConfig(o,e){let s={};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${o}: ${m}`),s={}}const t=(0,a.loadConfig)(e),r=[];(0,a.isCycleEnabled)(t,"browser")&&r.push(`${y}:*`),(0,a.isCycleEnabled)(t,"node")&&r.push(`${b}:*`),(0,a.isCycleEnabled)(t,"backend")&&r.push(`${h}:*`),(0,a.isCycleEnabled)(t,"android")&&r.push(`${S}:*`);const u=new Set(I),g=(s.mcpAllowlist??[]).filter(m=>!u.has(m));for(const m of r)g.includes(m)||g.push(m);g.length>0?s.mcpAllowlist=g:delete s.mcpAllowlist;const f=(s.terminalAllowlist??[]).filter(m=>m!==E);f.push(E),s.terminalAllowlist=f,(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}removePermissionsEntries(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8")),s=new Set(I);Array.isArray(e.mcpAllowlist)&&(e.mcpAllowlist=e.mcpAllowlist.filter(t=>!s.has(t)),e.mcpAllowlist.length===0&&delete e.mcpAllowlist),Array.isArray(e.terminalAllowlist)&&(e.terminalAllowlist=e.terminalAllowlist.filter(t=>t!==E),e.terminalAllowlist.length===0&&delete e.terminalAllowlist),ro(e)?(0,i.unlinkSync)(o):(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove permissions from ${o}: ${e}`)}}}0&&(module.exports={CursorClient});
|
|
@@ -18,6 +18,7 @@ If the project has android verification enabled (`ironbee android enable` once a
|
|
|
18
18
|
4. **Pick an evidence path** for the changed code:
|
|
19
19
|
- **Device-evidence** (proves the change is visible / functional): drive UI (`MCP:adt_interaction_tap` / `MCP:adt_interaction_input-text` / `MCP:adt_interaction_swipe`) → screenshot (`MCP:adt_content_take-screenshot`) → UI snapshot (`MCP:adt_a11y_take-ui-snapshot`). **STOP and visually analyze the screenshot** — readability, layout, cut-off content, expected state rendered; the snapshot reports structure, the screenshot shows what the user actually sees. Both are MANDATORY on this path.
|
|
20
20
|
- **Log-evidence** (proves the changed code path executed): `MCP:adt_o11y_log-read` or `MCP:adt_o11y_log-follow`. Confirm expected log lines present AND no FATAL/crash from the app package.
|
|
21
|
+
- **Network-evidence** (proves a network/API change behaved correctly): `MCP:adt_o11y_get-http-requests` — forward-looking, so start capture, drive the app to trigger traffic, then read again; confirm the expected request(s)/status. Auxiliary (NOT gate evidence): `MCP:adt_o11y_new-trace-id` pins a correlation root; `MCP:adt_stub_*` mocks/intercepts responses for setup.
|
|
21
22
|
5. **If recording was started, stop it now** — `MCP:adt_content_stop-recording`. submit-verdict rejects with `"recording is still active"` when this step is skipped.
|
|
22
23
|
6. **Submit verdict** — platform-agnostic, just status + checks (+ issues/fixes).
|
|
23
24
|
|
|
@@ -21,8 +21,9 @@ These attach to the **Required steps** above — they don't replace any step. Nu
|
|
|
21
21
|
- **Within step 3 (run flow):** also run the android flow: connect (`MCP:adt_device_connect`) → launch app (`MCP:adt_device_launch-app`) → pick ONE evidence path:
|
|
22
22
|
- **Device-evidence**: drive UI (`MCP:adt_interaction_tap` / `MCP:adt_interaction_input-text` / `MCP:adt_interaction_swipe`) + screenshot (`MCP:adt_content_take-screenshot`) + UI snapshot (`MCP:adt_a11y_take-ui-snapshot`)
|
|
23
23
|
- **Log-evidence**: read Logcat (`MCP:adt_o11y_log-read` or `MCP:adt_o11y_log-follow`) confirming expected output and no crashes
|
|
24
|
+
- **Network-evidence**: capture outgoing HTTP traffic (`MCP:adt_o11y_get-http-requests` — forward-looking: start capture, drive the app, read again) confirming the expected request(s)/status. Auxiliary setup only (NOT evidence): `MCP:adt_o11y_new-trace-id` (pin correlation), `MCP:adt_stub_*` (mock/intercept).
|
|
24
25
|
- If `recording.enable` is on, the gate forces `MCP:adt_content_start-recording` BEFORE the android steps above and rejects the verdict if you don't call `MCP:adt_content_stop-recording` AFTER them. Always pair start/stop around the steps above.
|
|
25
|
-
- **Within step 6 (submit verdict):** submit one platform-agnostic verdict with `status` + `checks` (+ `issues`/`fixes` as needed). Android-cycle pass criteria: device connected AND (UI-interaction + screenshot + UI snapshot taken) OR (Logcat read with no crashes).
|
|
26
|
+
- **Within step 6 (submit verdict):** submit one platform-agnostic verdict with `status` + `checks` (+ `issues`/`fixes` as needed). Android-cycle pass criteria: device connected AND (UI-interaction + screenshot + UI snapshot taken) OR (Logcat read with no crashes) OR (outgoing HTTP traffic captured and confirmed).
|
|
26
27
|
|
|
27
28
|
### Additional BANNED for android cycle
|
|
28
29
|
|
|
@@ -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**: the `adt_scenario-*` tools
|
|
4
|
+
(`adt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/adt`, global → `~/.ironbee/scenarios/adt` (the
|
|
6
|
+
server's `SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
|
|
7
|
+
- Scenario **scripts** call this platform's tools via `callTool('<bare-tool>', {...})` — discover
|
|
8
|
+
the available `adt_*` tool names from your connected MCP tool 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**: the `bedt_scenario-*` tools
|
|
4
|
+
(`bedt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/bedt`, global → `~/.ironbee/scenarios/bedt` (the
|
|
6
|
+
server's `SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
|
|
7
|
+
- Scenario **scripts** call this platform's tools via `callTool('<bare-tool>', {...})` — discover
|
|
8
|
+
the available `bedt_*` tool names from your connected MCP tool 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,40 @@
|
|
|
1
|
+
### browser platform (enabled)
|
|
2
|
+
- **Use for**: UI / frontend scenarios driven through a real browser.
|
|
3
|
+
- **Server**: `browser-devtools` · **scenario tools**: the `bdt_scenario-*` tools
|
|
4
|
+
(`bdt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/bdt`, global → `~/.ironbee/scenarios/bdt` (the
|
|
6
|
+
server's `SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
|
|
7
|
+
- Scenario **scripts** call this platform's tools via `callTool('<bare-tool>', {...})` — discover
|
|
8
|
+
the available `bdt_*` tool names from your connected MCP tool 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 browser cycle collects). In the script:
|
|
12
|
+
1. **Navigate** — `bdt_navigation_go-to` to the affected page(s), then **actually interact** (click
|
|
13
|
+
buttons, fill forms, submit data, trigger the workflow that changed). A click-through that asserts
|
|
14
|
+
nothing verifies nothing — the interaction is what makes the evidence meaningful. **Target elements
|
|
15
|
+
with the `selector`/`ref` the aria-snapshot returns for each** (e.g. `getByRole(...)` or `@e12`) —
|
|
16
|
+
do NOT hand-parse the snapshot TEXT with regex/string-matching: embedded quotes or special chars in
|
|
17
|
+
labels make that brittle (it silently misses elements). This includes deriving a positional
|
|
18
|
+
**`.nth(i)`** index by parsing the snapshot — a quote or special char in any earlier label shifts
|
|
19
|
+
every index, so the click lands on the wrong element (or none). Pick each element by its own
|
|
20
|
+
`getByRole(...)`/`ref`, or scope it to the matching card/row with a CSS `:has()` selector (e.g.
|
|
21
|
+
`.product-card:has(h4:has-text('Widget')) button:has-text('Add to cart')`). NOTE: the
|
|
22
|
+
browser-devtools resolver accepts only a flat `getByXYZ(...)` expression OR a CSS string — Playwright
|
|
23
|
+
locator chaining like `.filter({ hasText })` does NOT parse. Never compute element positions from
|
|
24
|
+
snapshot text.
|
|
25
|
+
2. **Screenshot** — `bdt_content_take-screenshot` (or `includeScreenshot: true` on a nav/interaction
|
|
26
|
+
call) **with `returnOutput: true`, and put the returned `filePath` (absolute path to the saved PNG)
|
|
27
|
+
in your result**. The later verifier opens that file with its `Read` tool to judge the pixels
|
|
28
|
+
(readability, layout, cut-off content, expected render). **Do NOT set `includeBase64`** — a nested
|
|
29
|
+
scenario screenshot is NOT surfaced as an inline MCP image (`scenario-run` strips nested image data)
|
|
30
|
+
and base64 only bloats the result; the returned `filePath` is how visual judging works.
|
|
31
|
+
3. **Accessibility** — `bdt_a11y_take-aria-snapshot` (or `includeSnapshot: true`), called with
|
|
32
|
+
`returnOutput: true` — the snapshot TEXT is what the verifier reads to judge page structure.
|
|
33
|
+
4. **Console** — `bdt_o11y_get-console-messages` with `returnOutput: true` to surface errors.
|
|
34
|
+
|
|
35
|
+
`return` the evidence — aria-snapshot text, page text (`bdt_content_get-as-text`), console errors, the
|
|
36
|
+
screenshot `filePath`s — **plus explicit pass/fail assertions**. That returned result is what
|
|
37
|
+
`/ironbee-verify scenario:<name>` reads to judge the run: functional + structural from the text, and
|
|
38
|
+
**visual by `Read`ing the returned screenshot files**. Capture the evidence AFTER the interactions
|
|
39
|
+
whose state you want to assert; for an intermediate state (a modal that opens then closes) capture at
|
|
40
|
+
that point too.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
### node platform (enabled)
|
|
2
|
+
- **Use for**: Node.js runtime-debug scenarios (V8 inspector probes / logs).
|
|
3
|
+
- **Server**: `node-devtools` · **scenario tools**: the `ndt_scenario-*` tools
|
|
4
|
+
(`ndt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/ndt`, global → `~/.ironbee/scenarios/ndt` (the
|
|
6
|
+
server's `SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
|
|
7
|
+
- Scenario **scripts** call this platform's tools via `callTool('<bare-tool>', {...})` — discover
|
|
8
|
+
the available `ndt_*` tool names from your connected MCP tool 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:adt_o11y_log-read` or `MCP:adt_o11y_log-follow` (drain a follow with `MCP:adt_o11y_log-get-followed`, stop it with `MCP: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: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: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:adt_o11y_new-trace-id` first (it stamps `traceparent` on every captured request; inspect/clear via `MCP:adt_o11y_set-trace-context` / `MCP:adt_o11y_get-trace-context`). To set up test conditions, `MCP:adt_stub_mock-http-response` / `MCP:adt_stub_intercept-http-request` mock or mutate responses (list/clear with `MCP:adt_stub_list` / `MCP:adt_stub_clear`). `MCP: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: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
|
|
package/dist/commands/config.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var m=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var l=(e,o)=>m(e,"name",{value:o,configurable:!0});var L=(e,o)=>{for(var n in o)m(e,n,{get:o[n],enumerable:!0})},M=(e,o,n,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of V(o))!q.call(e,i)&&i!==n&&m(e,i,{get:()=>o[i],enumerable:!(r=K(o,i))||r.enumerable});return e};var Y=e=>M(m({},"__esModule",{value:!0}),e);var ie={};L(ie,{configCommand:()=>u,runGet:()=>U,runList:()=>W,runPath:()=>_,runSet:()=>F,runUnset:()=>B});module.exports=Y(ie);var D=require("commander"),g=require("fs"),N=require("os"),d=require("path"),f=require("../clients/registry"),I=require("./install"),b=require("../lib/config"),G=require("../lib/gitignore"),k=require("../lib/logger"),t=require("../lib/output"),h=require("../lib/prompt"),J=require("../lib/projects-registry");const z=new Set(["verification","collector","browser","node","backend","android","browserDevTools","nodeDevTools","backendDevTools","androidDevTools","telemetry","privacy","statusLine","otel","codex"]);function C(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.json")}l(C,"projectConfigPath");function S(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.local.json")}l(S,"projectLocalConfigPath");function O(){return(0,d.join)((0,N.homedir)(),".ironbee","config.json")}l(O,"globalConfigPath");function w(e){if(!(0,g.existsSync)(e))return{};try{const o=(0,g.readFileSync)(e,"utf-8");return o.trim().length===0?{}:JSON.parse(o)}catch(o){throw k.logger.debug(`failed to read ${e}: ${o}`),new Error(`Config at ${e} is not valid JSON: ${o instanceof Error?o.message:o}`)}}l(w,"readConfigFile");function j(e,o){(0,g.mkdirSync)((0,d.join)(e,".."),{recursive:!0}),(0,g.writeFileSync)(e,JSON.stringify(o,null,2)+`
|
|
1
|
+
"use strict";var m=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var l=(e,o)=>m(e,"name",{value:o,configurable:!0});var L=(e,o)=>{for(var n in o)m(e,n,{get:o[n],enumerable:!0})},M=(e,o,n,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of V(o))!q.call(e,i)&&i!==n&&m(e,i,{get:()=>o[i],enumerable:!(r=K(o,i))||r.enumerable});return e};var Y=e=>M(m({},"__esModule",{value:!0}),e);var ie={};L(ie,{configCommand:()=>u,runGet:()=>U,runList:()=>W,runPath:()=>_,runSet:()=>F,runUnset:()=>B});module.exports=Y(ie);var D=require("commander"),g=require("fs"),N=require("os"),d=require("path"),f=require("../clients/registry"),I=require("./install"),b=require("../lib/config"),G=require("../lib/gitignore"),k=require("../lib/logger"),t=require("../lib/output"),h=require("../lib/prompt"),J=require("../lib/projects-registry");const z=new Set(["verification","collector","browser","node","backend","android","browserDevTools","nodeDevTools","backendDevTools","androidDevTools","telemetry","privacy","statusLine","otel","codex","runtime"]);function C(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.json")}l(C,"projectConfigPath");function S(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.local.json")}l(S,"projectLocalConfigPath");function O(){return(0,d.join)((0,N.homedir)(),".ironbee","config.json")}l(O,"globalConfigPath");function w(e){if(!(0,g.existsSync)(e))return{};try{const o=(0,g.readFileSync)(e,"utf-8");return o.trim().length===0?{}:JSON.parse(o)}catch(o){throw k.logger.debug(`failed to read ${e}: ${o}`),new Error(`Config at ${e} is not valid JSON: ${o instanceof Error?o.message:o}`)}}l(w,"readConfigFile");function j(e,o){(0,g.mkdirSync)((0,d.join)(e,".."),{recursive:!0}),(0,g.writeFileSync)(e,JSON.stringify(o,null,2)+`
|
|
2
2
|
`)}l(j,"writeConfigFile");function H(e){const o=e.indexOf(".");return o===-1?e:e.slice(0,o)}l(H,"topKey");function $(e){return z.has(H(e))}l($,"affectsArtifacts");function Q(e,o){if(o.length===0)return e;const n=o.split(".");let r=e;for(const i of n){if(r==null||typeof r!="object"||Array.isArray(r))return;r=r[i]}return r}l(Q,"getAtPath");function X(e,o,n){if(o.length===0)throw new Error("Cannot set the root config \u2014 pass a dotted key (e.g. `collector.url`).");const r=o.split(".");let i=e;for(let a=0;a<r.length-1;a++){const s=r[a],c=i[s];(c==null||typeof c!="object"||Array.isArray(c))&&(i[s]={}),i=i[s]}i[r[r.length-1]]=n}l(X,"setAtPath");function Z(e,o){if(o.length===0)return!1;const n=o.split(".");let r=e;for(let a=0;a<n.length-1;a++){const s=n[a],c=r[s];if(c==null||typeof c!="object"||Array.isArray(c))return!1;r=c}const i=n[n.length-1];return Object.prototype.hasOwnProperty.call(r,i)?(delete r[i],!0):!1}l(Z,"unsetAtPath");function ee(e,o){if(o)try{return JSON.parse(e)}catch(n){throw new Error(`--json was set but value is not valid JSON: ${n instanceof Error?n.message:n}`)}try{return JSON.parse(e)}catch{return e}}l(ee,"parseValue");function v(e){if(e.global===!0&&e.local===!0)throw new Error("Pass at most one of --global / --local.");if(e.global===!0)return{path:O(),label:"global",useGlobal:!0};const o=e.projectDir??process.cwd();return e.local===!0?{path:S(o),label:"local",useGlobal:!1}:{path:C(o),label:"project",useGlobal:!1}}l(v,"resolveWriteTarget");function A(e){if((e.global===!0?1:0)+(e.project===!0?1:0)+(e.local===!0?1:0)>1)throw new Error("Pass at most one of --global / --project / --local.");if(e.global===!0)return{label:"global",path:O()};const n=e.projectDir??process.cwd();return e.project===!0?{label:"project",path:C(n)}:e.local===!0?{label:"local",path:S(n)}:{label:"merged",path:n}}l(A,"resolveReadTarget");function P(e){return e.label==="merged"?(0,b.loadConfig)(e.path):w(e.path)}l(P,"readForGet");function oe(e){return e===void 0?"(unset)":typeof e=="string"?e:JSON.stringify(e,null,2)}l(oe,"formatValue");function ne(e,o){const n=(0,f.detectClients)(e);if(o===void 0&&n.length===0)return console.log(` ${t.pc.dim("\xB7")} ${t.pc.dim("no clients detected in")} ${t.pc.dim(e)} ${t.pc.dim("\u2014 skipping artifact rerender")}`),[];const r=(0,b.loadConfig)(e),i=(0,f.resolveTargetClients)(e,o);for(const a of i)a.install(e,r);return(0,G.ensureIronBeeGitignored)(e),i.map(a=>a.name)}l(ne,"rerenderArtifacts");function E(e,o,n,r){const i=(0,g.existsSync)(e)?(0,g.readFileSync)(e,"utf-8"):null;j(e,o);try{return ne(n,r)}catch(a){try{i===null?(0,g.existsSync)(e)&&(0,g.unlinkSync)(e):(0,g.writeFileSync)(e,i)}catch(s){k.logger.debug(`config rollback failed: ${s}`)}throw a}}l(E,"writeAndRerender");const R=10;function te(e){const o=(0,f.listActiveProjects)(),n=(0,J.canonicalizePath)(e);return o.filter(r=>r.path!==n)}l(te,"listOtherProjects");function re(e){const o=e.length===1?"other project":"other projects";console.log(` ${t.pc.yellow("\u26A0")} ${t.pc.bold(String(e.length))} ${o} registered in the inventory still on the prior state:`);const n=e.slice(0,R);for(const r of n)console.log(` ${t.pc.dim("\xB7")} ${t.pc.dim(r.path)}`);e.length>R&&console.log(` ${t.pc.dim("\u2026and")} ${t.pc.bold(String(e.length-R))} ${t.pc.dim("more")}`)}l(re,"printOtherProjectsBanner");async function T(e,o,n){if(!o)return;const r=te(e);if(r.length===0)return;re(r);let i;if(n===!0)i=!0;else if(n===!1){console.log(` ${t.pc.dim("Run")} ${t.pc.cyan("ironbee install --all")} ${t.pc.dim("later to apply, or")} ${t.pc.cyan("ironbee install")} ${t.pc.dim("in selected projects.")}`);return}else if((0,h.isInteractive)()){const a=r.length===1?"this project":`these ${r.length} projects`;i=await(0,h.promptYesNo)(` Apply this change to ${a} now?`,!0)}else{console.log(` ${t.pc.dim("Run")} ${t.pc.cyan("ironbee install --all")} ${t.pc.dim("to apply everywhere, or pass")} ${t.pc.cyan("--apply-all")} ${t.pc.dim("to skip this prompt.")}`);return}if(!i){console.log(` ${t.pc.dim("Skipped. Run")} ${t.pc.cyan("ironbee install --all")} ${t.pc.dim("later to apply.")}`);return}console.log(),await(0,I.runInstallAll)({})}l(T,"maybeApplyToOtherProjects");async function F(e,o,n){if(e.length===0)throw new Error("Key is required (dotted path, e.g. `collector.url`).");const r=v(n),i=ee(o,n.json===!0),a=w(r.path),s=JSON.parse(JSON.stringify(a));X(s,e,i);const c=n.projectDir??process.cwd(),p=n.rerender!==!1&&$(e);let y=[];p?y=E(r.path,s,c,n.client):j(r.path,s),console.log(`${t.pc.green("\u2713")} Set ${t.pc.cyan(e)} = ${t.pc.bold(oe(i))} in ${r.label} config (${t.pc.dim(r.path)}).`),p?y.length>0&&(console.log(` ${t.pc.dim("Re-rendered artifacts for:")} ${t.pc.bold(y.join(", "))}`),console.log(` ${t.pc.yellow("\u26A0")} Restart your editor / agent session for the change to take effect.`)):n.rerender===!1&&$(e)&&console.log(` ${t.pc.yellow("\u26A0")} --no-rerender set: artifacts may now be out of sync with config. Run ${t.pc.cyan("ironbee install")} to resync.`),x(e),r.useGlobal&&await T(c,p,n.applyAll)}l(F,"runSet");function x(e){const o=(0,b.findActiveEnvOverride)(e);o!==void 0&&console.log(` ${t.pc.yellow("\u26A0")} ${t.pc.cyan(o.envVar)} is set in this shell \u2014 env overrides file config, so ${t.pc.cyan(e)} reads will return the env value until ${t.pc.cyan(o.envVar)} is unset.`)}l(x,"warnIfEnvShadowed");async function B(e,o){if(e.length===0)throw new Error("Key is required (dotted path, e.g. `collector.url`).");const n=v(o),r=w(n.path),i=JSON.parse(JSON.stringify(r));if(!Z(i,e)){console.log(`${t.pc.dim("\xB7")} ${t.pc.cyan(e)} not present in ${n.label} config (${t.pc.dim(n.path)}). No-op.`);return}const s=o.projectDir??process.cwd(),c=o.rerender!==!1&&$(e);let p=[];c?p=E(n.path,i,s,o.client):j(n.path,i),console.log(`${t.pc.green("\u2713")} Unset ${t.pc.cyan(e)} in ${n.label} config (${t.pc.dim(n.path)}).`),c&&p.length>0&&(console.log(` ${t.pc.dim("Re-rendered artifacts for:")} ${t.pc.bold(p.join(", "))}`),console.log(` ${t.pc.yellow("\u26A0")} Restart your editor / agent session for the change to take effect.`)),x(e),n.useGlobal&&await T(s,c,o.applyAll)}l(B,"runUnset");function U(e,o){const n=A(o),r=P(n),i=Q(r,e);i===void 0&&(console.error(`${t.pc.dim("\xB7")} ${t.pc.cyan(e)} ${t.pc.dim("not set in")} ${n.label} ${t.pc.dim("config")}`),process.exit(1)),console.log(typeof i=="string"?i:JSON.stringify(i,null,2))}l(U,"runGet");function W(e){const o=A(e),n=P(o);console.log(JSON.stringify(n,null,2))}l(W,"runList");function _(e){const o=v(e);console.log(o.path)}l(_,"runPath");const u=new D.Command("config").description("Read or write IronBee configuration values (project or global). Smart re-render: artifact-affecting keys auto-update installed client files.");u.command("get <key>").description("Print the value at a dotted path. Default: merged effective value (global + project + local). --global / --project / --local narrow to one source.").option("-p, --project-dir <dir>","Project directory (default: cwd).").option("-g, --global","Read from global config only (~/.ironbee/config.json).").option("--project","Read from project config only (<project>/.ironbee/config.json).").option("--local","Read from project-local config only (<project>/.ironbee/config.local.json \u2014 gitignored).").action((e,o)=>{try{U(e,o)}catch(n){console.error(`${t.pc.red("\u2717")} ${n instanceof Error?n.message:n}`),process.exit(1)}}),u.command("set <key> <value>").description("Set a config value. Type-coerces (true/42/[\u2026]/{\u2026}) unless --json forces strict parsing. Re-renders client artifacts when the top-level key affects them. After a global write (`-g`) on an artifact-affecting key, prompts whether to apply the change to every other registered project. Use --local to write to the gitignored personal-override layer instead of the committed project config.").option("-p, --project-dir <dir>","Project directory (default: cwd).").option("-g, --global","Write to global config (~/.ironbee/config.json).").option("--local","Write to the gitignored project-local override (<project>/.ironbee/config.local.json) instead of the committed project config. Mutually exclusive with --global.").option("--client <name>",`Filter clients for artifact rerender (${(0,f.clientNames)()}), or "all". Default: detected clients.`).option("--no-rerender","Skip artifact rerender even when the key normally triggers it.").option("--json","Require value to parse as strict JSON (no string fallback).").option("--apply-all","After a global write on an artifact-affecting key, apply the change to every registered project without prompting.").option("--no-apply-all","After a global write on an artifact-affecting key, do NOT apply to other registered projects (suppress the prompt).").action(async(e,o,n)=>{try{await F(e,o,n)}catch(r){console.error(`${t.pc.red("\u2717")} ${r instanceof Error?r.message:r}`),process.exit(1)}}),u.command("unset <key>").description("Remove a config key. Idempotent \u2014 no-op when the key is absent. Re-renders client artifacts when the top-level key affects them. After a global unset (`-g`) on an artifact-affecting key, prompts whether to apply the change to every other registered project. Use --local to remove from the gitignored personal-override layer instead of the committed project config.").option("-p, --project-dir <dir>","Project directory (default: cwd).").option("-g, --global","Write to global config (~/.ironbee/config.json).").option("--local","Write to the gitignored project-local override (<project>/.ironbee/config.local.json) instead of the committed project config. Mutually exclusive with --global.").option("--client <name>",`Filter clients for artifact rerender (${(0,f.clientNames)()}), or "all". Default: detected clients.`).option("--no-rerender","Skip artifact rerender even when the key normally triggers it.").option("--apply-all","After a global unset on an artifact-affecting key, apply the change to every registered project without prompting.").option("--no-apply-all","After a global unset on an artifact-affecting key, do NOT apply to other registered projects (suppress the prompt).").action(async(e,o)=>{try{await B(e,o)}catch(n){console.error(`${t.pc.red("\u2717")} ${n instanceof Error?n.message:n}`),process.exit(1)}}),u.command("list").description("Print the entire config. Default: merged effective config; --global / --project / --local narrow to one source.").option("-p, --project-dir <dir>","Project directory (default: cwd).").option("-g, --global","Read from global config only.").option("--project","Read from project config only.").option("--local","Read from project-local config only (<project>/.ironbee/config.local.json \u2014 gitignored).").action(e=>{try{W(e)}catch(o){console.error(`${t.pc.red("\u2717")} ${o instanceof Error?o.message:o}`),process.exit(1)}}),u.command("path").description("Print the on-disk path of the targeted config file (project by default; --global for global, --local for the gitignored personal-override layer).").option("-p, --project-dir <dir>","Project directory (default: cwd).").option("-g, --global","Print global config path (~/.ironbee/config.json).").option("--local","Print project-local config path (<project>/.ironbee/config.local.json).").action(e=>{try{_(e)}catch(o){console.error(`${t.pc.red("\u2717")} ${o instanceof Error?o.message:o}`),process.exit(1)}});0&&(module.exports={configCommand,runGet,runList,runPath,runSet,runUnset});
|
package/dist/commands/hook.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`),process.exit(1));const t=(0,
|
|
3
|
-
`),process.exit(1)),t}
|
|
1
|
+
"use strict";var g=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var E=(i,e)=>g(i,"name",{value:e,configurable:!0});var x=(i,e)=>{for(var t in e)g(i,t,{get:e[t],enumerable:!0})},D=(i,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of C(e))!P.call(i,n)&&n!==t&&g(i,n,{get:()=>e[n],enumerable:!(o=N(e,n))||o.enumerable});return i};var _=i=>D(g({},"__esModule",{value:!0}),i);var O={};x(O,{hookCommand:()=>r});module.exports=_(O);var S=require("commander"),w=require("../clients/registry"),k=require("../hooks/core/submit-verdict"),y=require("../hooks/core/verification-lifecycle"),u=require("../lib/config"),l=require("../lib/logger"),d=require("../lib/output"),m=require("../lib/stdin"),j=require("../hooks/core/session-state"),v=require("../clients/session-id"),f=require("../clients/agent-project-dir"),p=require("../lib/runtime-paths");function h(i,e){return(0,u.getVerificationEnabled)((0,u.loadConfig)(i))?!1:(l.logger.debug(`${e}: verification is disabled \u2014 silent no-op`),!0)}E(h,"isVerificationDisabled");function s(i){const e=i??process.env.IRONBEE_CLIENT;e||(process.stderr.write(`Error: client not specified. Use --client <name> or set IRONBEE_CLIENT env var.
|
|
2
|
+
`),process.exit(1));const t=(0,w.findClient)(e);return t||(process.stderr.write(`Error: unknown client "${e}". Run \`ironbee install\` to set up.
|
|
3
|
+
`),process.exit(1)),t}E(s,"resolveClient");const r=new S.Command("hook").description("Internal hook runners (invoked by the AI coding client)");r.command("verify-gate").description("Stop hook \u2014 gates task completion until browser verification passes").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runVerifyGate(t)}),r.command("clear-verdict").description("PostToolUse hook \u2014 clears stale verdict after code edits").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runClearVerdict(t)}),r.command("track-action").description("PostToolUse hook \u2014 tracks browser-devtools tool calls in actions.jsonl").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runTrackAction(t)}),r.command("track-action-monitor").description("PostToolUse hook (monitoring-only mode) \u2014 submits send_event jobs for non-devtools tools and falls back to starting an activity if needed").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runTrackActionMonitor(t)}),r.command("track-action-pre").description("Codex PreToolUse hook \u2014 stashes a hrtime timestamp keyed by tool_use_id so the matching PostToolUse can derive tool_call.duration (Codex hook stdin does not carry duration_ms). No-op on Claude/Cursor \u2014 their hosts provide duration natively.").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runTrackActionPre(t)}),r.command("subagent-start").description("SubagentStart hook \u2014 dispatched per client via IClient.runSubagentStart. Codex: writes the agent_id \u2192 parent session_id bridge (codex-threads.json). Claude: joins the sub-agent as an activity participant so the activity closes only when the last participant (main + all sub-agents) leaves.").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runSubagentStart?.(t)}),r.command("subagent-stop").description("SubagentStop hook \u2014 dispatched per client via IClient.runSubagentStop. Codex: prune the thread map + record the sub-agent's agent_transcript_path for the analytics fold. Claude: backstop that closes a verifier-owned activity/cycle the sub-agent left open without a verdict.").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runSubagentStop?.(t)}),r.command("activity-end").description("Stop hook (monitoring-only mode) \u2014 closes the active activity and triggers a background queue flush").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runActivityEnd(t)}),r.command("session-start").description("SessionStart hook \u2014 records session start in actions.jsonl").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runSessionStart(t)}),r.command("session-status").description("Statusline command (Claude) \u2014 emits a session_status event and chains the user's original statusline").action(async()=>{await(0,w.findClient)("claude")?.runSessionStatus?.()}),r.command("require-verdict").description("PreToolUse hook \u2014 blocks file edits until verdict is submitted after browser tool usage").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").option("--soft","non-blocking assist-mode variant: stash file_change state but never block the edit").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runRequireVerdict(t,{soft:i.soft===!0})}),r.command("session-end").description("SessionEnd hook \u2014 records session end in actions.jsonl").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runSessionEnd(t)}),r.command("activity-start").description("UserPromptSubmit/beforeSubmitPrompt hook \u2014 starts activity tracking on each agent turn").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runActivityStart(t)}),r.command("require-verification").description("PreToolUse hook \u2014 blocks browser tools until verification-start is called").option("--client <name>","client name (overrides IRONBEE_CLIENT env var)").option("--soft","non-blocking assist-mode variant: inject _metadata but never block the devtools call").action(async i=>{const e=s(i.client);if(!e)return;const t=e.resolveProjectDir();await e.runRequireVerification(t,{soft:i.soft===!0})}),r.command("verification-start").description(`Start a verification cycle (called by agent via Bash). Optional --intent flag: "fix" arms the verify-gate's fix-until-pass backstop (a fail verdict keeps blocking even in a zero-edit window); "report" or omitted = verify-only run (clears any stale intent \u2014 "report" is a tolerated alias of omitting the flag, mirroring the command's mode token).`).option("--intent <mode>",'declared intent of this verification run: "fix" or "report"').action(async i=>{const e=(0,f.resolveAgentProjectDir)();if(h(e,"verification-start")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
|
|
4
4
|
`,0);return}i.intent!==void 0&&i.intent!=="fix"&&i.intent!=="report"&&(process.stderr.write(`Error: --intent must be "fix" or "report".
|
|
5
5
|
`),process.exit(1));const t=i.intent;let o;try{o=JSON.parse((0,m.readStdin)())}catch{process.stderr.write(`Error: no JSON provided via stdin.
|
|
6
6
|
`),process.exit(1)}const n=(0,v.resolveAgentSessionId)(o,e);n||(process.stderr.write(`Error: JSON must include a "session_id" field, or run as a delegated sub-agent (your client resolves the session automatically).
|
|
7
|
-
`),process.exit(1));const c
|
|
8
|
-
`,0)}),r.command("verification-end").description("End a verification cycle (called by agent via Bash)").action(async()=>{const i=(0,f.resolveAgentProjectDir)();if(
|
|
7
|
+
`),process.exit(1));const c=(0,p.sessionDir)(e,n);(0,l.setLogFile)(`${c}/session.log`);const a=(0,u.isRecordingEnabled)(e),I=await(0,y.startVerification)({sessionId:n,sessionDir:c,actionsFile:`${c}/actions.jsonl`,recordingEnabled:a,intent:t}),b={verification_id:I.verificationId,trace_id:I.traceId};a&&(b.recording_required=!0,b.message="Recording is required. Call bdt_content_start-recording BEFORE using any other browser tools."),(0,d.writeAndExit)(JSON.stringify(b)+`
|
|
8
|
+
`,0)}),r.command("verification-end").description("End a verification cycle (called by agent via Bash)").action(async()=>{const i=(0,f.resolveAgentProjectDir)();if(h(i,"verification-end")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
|
|
9
9
|
`,0);return}let e;try{e=JSON.parse((0,m.readStdin)())}catch{process.stderr.write(`Error: no JSON provided via stdin.
|
|
10
10
|
`),process.exit(1)}const t=(0,v.resolveAgentSessionId)(e,i);t||(process.stderr.write(`Error: JSON must include a "session_id" field, or run as a delegated sub-agent (your client resolves the session automatically).
|
|
11
|
-
`),process.exit(1));const o
|
|
11
|
+
`),process.exit(1));const o=(0,p.sessionDir)(i,t);(0,l.setLogFile)(`${o}/session.log`);const n=await(0,y.endVerification)({sessionId:t,sessionDir:o,actionsFile:`${o}/actions.jsonl`});n.success?(0,d.writeAndExit)(JSON.stringify({verification_id:n.verificationId,trace_id:n.traceId})+`
|
|
12
12
|
`,0):(process.stderr.write(n.message+`
|
|
13
|
-
`),process.exit(1))}),r.command("submit-verdict").description("Submit verification verdict (called by agent via Bash)").option("--project-dir <dir>","project directory (overrides env vars)").action(async i=>{const e=(0,f.resolveAgentProjectDir)(i.projectDir);if(
|
|
13
|
+
`),process.exit(1))}),r.command("submit-verdict").description("Submit verification verdict (called by agent via Bash)").option("--project-dir <dir>","project directory (overrides env vars)").action(async i=>{const e=(0,f.resolveAgentProjectDir)(i.projectDir);if(h(e,"submit-verdict")){(0,d.writeAndExit)(`verification is disabled in this project; verdict ignored
|
|
14
14
|
`,0);return}let t;try{t=(0,m.readStdin)()}catch{process.stderr.write(`Error: no verdict JSON provided via stdin.
|
|
15
15
|
`),process.exit(1)}let o;try{o=JSON.parse(t)}catch{process.stderr.write(`Error: verdict is not valid JSON.
|
|
16
16
|
`),process.exit(1)}const n=(0,v.resolveAgentSessionId)(o,e);n||(process.stderr.write(`Error: verdict JSON must include a "session_id" field, or run as a delegated sub-agent (your client resolves the session automatically).
|
|
17
|
-
`),process.exit(1));const c
|
|
17
|
+
`),process.exit(1));const c=(0,p.sessionDir)(e,n);(0,l.setLogFile)(`${c}/session.log`);const a=await(0,k.runSubmitVerdict)({sessionId:n,sessionDir:c,verdictFile:`${c}/verdict.json`,actionsFile:`${c}/actions.jsonl`,verdictJson:t,projectDir:e});a.success?(0,d.writeAndExit)(a.message+`
|
|
18
18
|
`,0):(process.stderr.write(a.message+`
|
|
19
|
-
`),process.exit(1))}),r.command("record-fix").description("Record what was fixed after a fail verdict (called by agent via Bash). Stashed locally in state.json; merged into the next pass verdict's fixes. Emits no collector event.").action(async()=>{const i=(0,f.resolveAgentProjectDir)();if(
|
|
19
|
+
`),process.exit(1))}),r.command("record-fix").description("Record what was fixed after a fail verdict (called by agent via Bash). Stashed locally in state.json; merged into the next pass verdict's fixes. Emits no collector event.").action(async()=>{const i=(0,f.resolveAgentProjectDir)();if(h(i,"record-fix")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
|
|
20
20
|
`,0);return}let e;try{e=JSON.parse((0,m.readStdin)())}catch{process.stderr.write(`Error: no JSON provided via stdin.
|
|
21
21
|
`),process.exit(1)}const t=(0,v.resolveAgentSessionId)(e,i);t||(process.stderr.write(`Error: JSON must include a "session_id" field, or run as a delegated sub-agent (your client resolves the session automatically).
|
|
22
22
|
`),process.exit(1));const o=e.fixes,n=Array.isArray(o)?o.filter(a=>typeof a=="string"&&a.length>0):[];n.length===0&&(process.stderr.write(`Error: JSON must include a non-empty "fixes" array of strings.
|
|
23
|
-
`),process.exit(1));const c
|
|
23
|
+
`),process.exit(1));const c=(0,p.sessionDir)(i,t);(0,l.setLogFile)(`${c}/session.log`),(0,j.addPendingFixes)(c,n),l.logger.debug(`record-fix: session=${t} recorded ${n.length} fix note(s)`),(0,d.writeAndExit)(JSON.stringify({recorded:n.length})+`
|
|
24
24
|
`,0)});0&&(module.exports={hookCommand});
|
package/dist/commands/import.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var _=Object.create;var g=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var N=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var l=(e,o)=>g(e,"name",{value:o,configurable:!0});var O=(e,o)=>{for(var n in o)g(e,n,{get:o[n],enumerable:!0})},I=(e,o,n,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of k(o))!P.call(e,r)&&r!==n&&g(e,r,{get:()=>o[r],enumerable:!(s=j(o,r))||s.enumerable});return e};var F=(e,o,n)=>(n=e!=null?_(N(e)):{},I(o||!e||!e.__esModule?g(n,"default",{value:e,enumerable:!0}):n,e)),M=e=>I(g({},"__esModule",{value:!0}),e);var W={};O(W,{importCommand:()=>L,parseFlags:()=>R,promptYesNo:()=>T});module.exports=M(W);var y=require("commander"),w=require("fs"),h=require("path"),D=F(require("readline")),t=require("../lib/output"),S=require("../lib/config"),C=require("../lib/collector"),E=require("../lib/logger"),$=require("../lib/runtime-paths"),i=require("../import");const B=4,z=1,U=32,L=new y.Command("import").description("Import historical Claude Code + Codex sessions to the IronBee Collector").option("--since <duration>","import sessions started within the given duration (e.g. 30d, 2w, 6m, 12h)").option("--from <iso-date>","explicit start date (e.g. 2025-04-01)").option("--to <iso-date>","explicit end date (defaults to now)").option("--transcript <path>","import a single transcript .jsonl file (Claude or Codex \u2014 detected from path)").option("--projects <paths>","comma-separated absolute project paths").option("--all-projects","import sessions from every project under ~/.claude/projects/ and ~/.codex/sessions/").option("--dry-run","print the summary and exit without sending events").option("--yes","skip the interactive confirm prompt").option("--force","do not skip sessions with existing .ironbee/sessions/<id>/ directories").option("--concurrency <n>","number of sessions to import in parallel").option("--batch-size <n>","events per collector POST (default: 100). Set to 1 to isolate which event a 400 rejects.").action(async e=>{let o;try{o=R(e)}catch(c){t.log.error(c instanceof Error?c.message:String(c)),process.exit(1)}(0,C.getCollectorTarget)(process.cwd())===null&&(t.log.error("Collector is not configured. ironbee import has no destination to send events to."),t.log.blank(),t.log.dim("To configure, add a collector section to ~/.ironbee/config.json (or your project's .ironbee/config.json):"),t.log.blank(),console.log(t.pc.dim(` {
|
|
2
2
|
"collector": {
|
|
3
3
|
"url": "https://your-collector.example.com",
|
|
4
4
|
"oauthToken": "<from \`ironbee login\`>"
|
|
5
5
|
}
|
|
6
|
-
}`)),t.log.blank(),t.log.dim("Required: `url` (non-empty), `enable !== false`, and AT LEAST ONE of `oauthToken` (preferred \u2014 `ironbee login` writes this) or `apiKey` (shared-key path for CI / deployment tooling). The IRONBEE_COLLECTOR=false env override also disables."),process.exit(1));const s=(0,i.findTranscripts)(o.scope),r=(0,i.applyTimeRange)(s,o.timeRange);r.length===0&&(t.log.warn("No transcripts found matching the given scope and time range."),process.exit(0)),t.log.info(`Scanning ${r.length} transcript(s)...`);const d=(0,i.estimateImport)({targets:r,timeRange:o.timeRange,concurrency:o.concurrency,force:o.force});
|
|
7
|
-
Received SIGINT \u2014 finishing in-flight sessions then exiting...`),p=!0});const b=await(0,i.runConcurrentPool)({items:m,concurrency:o.concurrency,shouldCancel:l(()=>p,"shouldCancel"),onStart:l((c,f)=>{a.onSessionStart(c.sessionId,f)},"onStart"),onComplete:l((c,f,
|
|
6
|
+
}`)),t.log.blank(),t.log.dim("Required: `url` (non-empty), `enable !== false`, and AT LEAST ONE of `oauthToken` (preferred \u2014 `ironbee login` writes this) or `apiKey` (shared-key path for CI / deployment tooling). The IRONBEE_COLLECTOR=false env override also disables."),process.exit(1));const s=(0,i.findTranscripts)(o.scope),r=(0,i.applyTimeRange)(s,o.timeRange);r.length===0&&(t.log.warn("No transcripts found matching the given scope and time range."),process.exit(0)),t.log.info(`Scanning ${r.length} transcript(s)...`);const d=(0,i.estimateImport)({targets:r,timeRange:o.timeRange,concurrency:o.concurrency,force:o.force});G(d,o.dryRun),o.dryRun&&process.exit(0),d.toImportCount===0&&(t.log.warn("Nothing to import (all sessions already tracked). Use --force to re-import."),process.exit(0)),o.yes||await T("Proceed?")||(t.log.info("Aborted."),process.exit(0));const m=r.filter(c=>!q(c,o.force)),a=new i.ProgressReporter(m.length);let p=!1;process.on("SIGINT",()=>{t.log.warn(`
|
|
7
|
+
Received SIGINT \u2014 finishing in-flight sessions then exiting...`),p=!0});const b=await(0,i.runConcurrentPool)({items:m,concurrency:o.concurrency,shouldCancel:l(()=>p,"shouldCancel"),onStart:l((c,f)=>{a.onSessionStart(c.sessionId,f)},"onStart"),onComplete:l((c,f,x)=>{a.onSessionComplete(c,x)},"onComplete"),process:l(async c=>{try{return await(0,i.importSession)({target:c,force:o.force,batchSize:o.batchSize})}catch(f){return{sessionId:c.sessionId,status:"failed",eventsSent:(0,i.emptyEventCounts)(),reason:f instanceof Error?f.message:String(f)}}},"process")});a.finalSummary(),b.filter(c=>c.status==="failed").length>0&&process.exit(1)});function R(e){if([e.transcript!==void 0?1:0,e.projects!==void 0?1:0,e.allProjects===!0?1:0].reduce((a,p)=>a+p,0)>1)throw new Error("--transcript, --projects, and --all-projects are mutually exclusive");let n;if(e.transcript!==void 0)n={kind:"transcript",path:(0,h.resolve)(e.transcript)};else if(e.projects!==void 0){const a=e.projects.split(",").map(p=>p.trim()).filter(p=>p.length>0).map(p=>(0,h.resolve)(p));if(a.length===0)throw new Error("--projects must list at least one path");n={kind:"projects",paths:a}}else e.allProjects===!0?n={kind:"all-projects"}:n={kind:"current-project"};if(e.since!==void 0&&e.from!==void 0)throw new Error("--since and --from are mutually exclusive");let s=null;const r=Date.now();if(e.since!==void 0){const a=(0,i.parseSinceDuration)(e.since);if(a===null)throw new Error(`Invalid --since value: "${e.since}". Expected forms: 30d, 2w, 6m, 12h.`);s=(0,i.buildTimeRange)({sinceMs:a,nowMs:r})}else if(e.from!==void 0){const a=(0,i.parseIsoDate)(e.from);if(a===null)throw new Error(`Invalid --from value: "${e.from}". Expected ISO date (e.g. 2025-04-01).`);let p;if(e.to!==void 0){const b=(0,i.parseIsoDate)(e.to);if(b===null)throw new Error(`Invalid --to value: "${e.to}". Expected ISO date.`);p=b}s=(0,i.buildTimeRange)({fromMs:a,toMs:p,nowMs:r})}const d=Y(e.concurrency),m=A(e.batchSize);return{scope:n,timeRange:s,dryRun:e.dryRun===!0,yes:e.yes===!0,force:e.force===!0,concurrency:d,batchSize:m}}l(R,"parseFlags");function A(e){if(e===void 0)return null;const o=parseInt(e,10);if(!Number.isFinite(o)||o<1)throw new Error(`Invalid --batch-size value: "${e}". Must be an integer >= 1.`);return o}l(A,"resolveBatchSize");function Y(e){if(e!==void 0){const s=parseInt(e,10);if(!Number.isFinite(s))throw new Error(`Invalid --concurrency value: "${e}". Must be an integer.`);return v(s)}const n=(0,S.loadConfig)(process.cwd()).import?.concurrency;return typeof n=="number"&&Number.isFinite(n)?v(n):B}l(Y,"resolveConcurrency");function v(e){return Math.min(U,Math.max(z,Math.floor(e)))}l(v,"clamp");function q(e,o){if(o)return!1;const n=(0,$.sessionDir)(e.projectDir,e.sessionId);try{return(0,w.existsSync)(n)}catch(s){return E.logger.debug(`import skip pre-check failed for ${n}: ${s instanceof Error?s.message:s}`),!1}}l(q,"shouldSkipForRunner");function G(e,o){const n=o?"ironbee import \u2014 dry-run preview":"ironbee import \u2014 preview";t.log.blank(),console.log(t.pc.bold(n)),t.log.blank();const s=e.projects.reduce((r,d)=>r+d.found,0);console.log(`Scope: ${t.pc.bold(String(e.projects.length))} project(s), ${t.pc.bold(String(s))} session(s) found`);for(const r of e.projects)console.log(` ${t.pc.dim(r.projectDir.padEnd(50))} ${String(r.found).padStart(3)} sessions ${t.pc.dim(`${r.skipped} skipped`)}`);if(t.log.blank(),e.timeRange!==null){const r=new Date(e.timeRange.fromMs).toISOString().slice(0,10),d=new Date(e.timeRange.toMs).toISOString().slice(0,10);console.log(`Time range: ${t.pc.bold(r)} .. ${t.pc.bold(d)}`)}else console.log(`Time range: ${t.pc.dim("(no filter \u2014 every session)")}`);t.log.blank(),console.log(`To import: ${t.pc.bold(String(e.toImportCount))} session(s)`),console.log(" Estimated events:"),console.log(` session_start / session_end ${u(e.estimatedEvents.session_start+e.estimatedEvents.session_end)}`),console.log(` activity_start / activity_end ${u(e.estimatedEvents.activity_start+e.estimatedEvents.activity_end)}`),console.log(` tool_call ${u(e.estimatedEvents.tool_call)}`),console.log(` file_change ${u(e.estimatedEvents.file_change)}`),console.log(` api_request ${u(e.estimatedEvents.api_request)}`),console.log(` session_analytics ${u(e.estimatedEvents.session_analytics)}`),console.log(` session_turn_analytics ${u(e.estimatedEvents.session_turn_analytics)}`),console.log(` session_turn_step_analytics ${u(e.estimatedEvents.session_turn_step_analytics)}`),t.log.blank(),console.log(` Estimated total cost surfaced: ${t.pc.bold(`$${e.estimatedCostUsd.toFixed(2)}`)}`),console.log(` Estimated wire bytes: ${t.pc.dim(K(e.estimatedWireBytes))}`),console.log(` Collector endpoint: ${e.collectorUrl!==null?t.pc.bold(e.collectorUrl):t.pc.red("(not configured)")}`),console.log(` Concurrency: ${t.pc.bold(String(e.concurrency))} sessions in flight`),t.log.blank()}l(G,"renderSummary");function u(e){return e.toLocaleString("en-US")}l(u,"formatNum");function K(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}l(K,"formatBytes");async function T(e,o){const n=o?.createInterface??D.createInterface;return new Promise(s=>{const r=n({input:process.stdin,output:process.stdout});r.question(`${e} [Y/n] `,d=>{r.close();const m=d.trim().toLowerCase();if(m==="n"||m==="no"){s(!1);return}s(!0)})})}l(T,"promptYesNo");0&&(module.exports={importCommand,parseFlags,promptYesNo});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var n=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var m=(e,o)=>{for(var t in o)n(e,t,{get:o[t],enumerable:!0})},d=(e,o,t,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of c(o))!l.call(e,s)&&s!==t&&n(e,s,{get:()=>o[s],enumerable:!(r=p(o,s))||r.enumerable});return e};var h=e=>d(n({},"__esModule",{value:!0}),e);var b={};m(b,{processJobFileCommand:()=>j});module.exports=h(b);var i=require("commander"),a=require("../queue");const j=new i.Command("process-job-file").description("Internal worker \u2014 process one snapshot file").argument("<path>","absolute path to a snapshot file (jobs-<UUID>.jsonl)").option("--project <dir>","project dir the snapshot belongs to (required for the external runtime layout \u2014 the path alone can't recover it)").option("--session <id>","session id the snapshot belongs to").action(async(e,o)=>{await(0,a.processFile)(e,{projectDir:o.project,sessionId:o.session})});0&&(module.exports={processJobFileCommand});
|
package/dist/commands/queue.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
"use strict";var b=Object.defineProperty;var
|
|
2
|
-
`).filter(r=>r.length>0).length}catch{return 0}}f(L,"countLines");function k(e,t){const r=(0,o.queueDir)(e,t);if(!(0,a.existsSync)(r))return[];try{return(0,a.readdirSync)(r).filter(i=>o.SNAPSHOT_FILENAME_REGEX.test(i)).map(i=>(0,
|
|
3
|
-
`);return}let i=0,c=0,n=0;for(const s of r){const d=
|
|
1
|
+
"use strict";var b=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var f=(e,t)=>b(e,"name",{value:t,configurable:!0});var C=(e,t)=>{for(var r in t)b(e,r,{get:t[r],enumerable:!0})},M=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of q(t))!B.call(e,c)&&c!==r&&b(e,c,{get:()=>t[c],enumerable:!(i=_(t,c))||i.enumerable});return e};var A=e=>M(b({},"__esModule",{value:!0}),e);var P={};C(P,{queueCommand:()=>w});module.exports=A(P);var E=require("commander"),R=require("../lib/runtime-paths"),a=require("fs"),y=require("path"),I=require("../lib/config"),o=require("../queue");function m(e){const t=(0,o.sessionsRoot)(e);if(!(0,a.existsSync)(t))return[];try{return(0,a.readdirSync)(t).filter(r=>{try{return(0,a.statSync)((0,y.join)(t,r)).isDirectory()}catch{return!1}})}catch{return[]}}f(m,"listSessionIds");function L(e){try{const t=(0,a.readFileSync)(e,"utf-8");return t.length===0?0:t.split(`
|
|
2
|
+
`).filter(r=>r.length>0).length}catch{return 0}}f(L,"countLines");function k(e,t){const r=(0,o.queueDir)(e,t);if(!(0,a.existsSync)(r))return[];try{return(0,a.readdirSync)(r).filter(i=>o.SNAPSHOT_FILENAME_REGEX.test(i)).map(i=>(0,y.join)(r,i))}catch{return[]}}f(k,"listSnapshotsFor");function T(e,t){const r=(0,o.liveQueueFile)(e,t),i=(0,o.deadLetterFile)(e,t),c=k(e,t);let n=0;for(const s of c)try{n+=(0,a.statSync)(s).size}catch{}return{sessionId:t,liveLines:L(r),liveBytes:$(r),snapshotCount:c.length,snapshotBytes:n,deadLetterLines:L(i),deadLetterBytes:$(i)}}f(T,"collectStatus");function $(e){try{return(0,a.statSync)(e).size}catch{return 0}}f($,"fileSize");function N(e){const t=e.match(/^(\d+)\s*(d|h|m|s)$/);if(!t)throw new Error(`invalid duration "${e}" \u2014 use e.g. 14d, 2h, 30m, 60s`);return parseInt(t[1],10)*{s:1e3,m:6e4,h:36e5,d:864e5}[t[2]]}f(N,"parseDuration");const w=new E.Command("queue").description("Inspect and manage the file-backed job queue");w.command("status").description("Show queue counts per session (aggregated across the project)").option("--session <id>","limit to one session").option("--project-dir <dir>","project directory (overrides env/cwd)").action(e=>{const t=(0,o.resolveProjectDir)(e.projectDir),r=e.session?[e.session]:m(t);if(r.length===0){process.stdout.write(`No sessions found.
|
|
3
|
+
`);return}let i=0,c=0,n=0;for(const s of r){const d=T(t,s);if(i+=d.liveLines,c+=d.snapshotCount,n+=d.deadLetterLines,process.stdout.write(`session=${s}
|
|
4
4
|
`),process.stdout.write(` live: ${d.liveLines} lines (${d.liveBytes} B)
|
|
5
5
|
`),process.stdout.write(` snapshots: ${d.snapshotCount} files (${d.snapshotBytes} B)
|
|
6
6
|
`),process.stdout.write(` dead-letter: ${d.deadLetterLines} lines (${d.deadLetterBytes} B)
|
|
7
7
|
`),d.deadLetterLines>0){const g=(0,o.readDeadLetterEntries)(t,s).slice(-3);for(const l of g)process.stdout.write(` [${l.received_at}] ${l.category}
|
|
8
8
|
`)}}process.stdout.write(`
|
|
9
9
|
Totals: live=${i} snapshots=${c} dead=${n}
|
|
10
|
-
`)}),
|
|
10
|
+
`)}),w.command("drain").description("Synchronously drain pending snapshots (per-session or all in the project)").option("--session <id>","drain one session").option("--project-dir <dir>","project directory (overrides env/cwd)").option("--skip-recent-ms <ms>","skip snapshots modified within the last N ms (race-avoidance for detached drainers spawned alongside active workers; default 0 = drain everything)").action(async e=>{const t=(0,o.resolveProjectDir)(e.projectDir),r=e.session?[e.session]:m(t),i=e.skipRecentMs?Number.parseInt(e.skipRecentMs,10):0;if(r.length===0){process.stdout.write(`Nothing to drain.
|
|
11
11
|
`);return}let c=0;for(const n of r){const s=await(0,o.drain)(t,n,{skipRecentMs:i});process.stdout.write(`session=${n} processed=${s.snapshotsProcessed} remaining=${s.snapshotsRemaining} cleaned=${s.cleanedQueueDir}
|
|
12
12
|
`),c+=s.snapshotsRemaining}c>0&&(process.stdout.write(`
|
|
13
13
|
${c} snapshot(s) remain \u2014 investigate worker.log.
|
|
14
|
-
`),process.exit(1))}),
|
|
14
|
+
`),process.exit(1))}),w.command("purge").description("Destructive cleanup of queue state").option("--snapshots","delete all snapshot files in all sessions without processing").option("--sessions <spec>","remove old queue/ subdirs; use `older-than=<duration>`, e.g. `older-than=14d`").option("--project-dir <dir>","project directory (overrides env/cwd)").action(e=>{const t=(0,o.resolveProjectDir)(e.projectDir);if(!e.snapshots&&!e.sessions&&(process.stderr.write(`Error: pass --snapshots or --sessions older-than=<dur>.
|
|
15
15
|
`),process.exit(1)),e.snapshots){let r=0;for(const i of m(t))for(const c of k(t,i))try{(0,a.unlinkSync)(c),r++}catch{}process.stdout.write(`Removed ${r} snapshot file(s).
|
|
16
|
-
`)}if(e.sessions){const r=e.sessions.match(/^older-than=(.+)$/);r||(process.stderr.write("Error: --sessions must be of the form `older-than=<duration>` (e.g. older-than=14d).\n"),process.exit(1));let i;try{i=Date.now()-
|
|
17
|
-
`),process.exit(1);return}let c=0;for(const n of m(t)){const s=(0,o.queueDir)(t,n);if(!(0,a.existsSync)(s))continue;let d;try{d=(0,a.statSync)(s)}catch{continue}if(d.mtimeMs>i)continue;let p=!1;try{const g=(0,a.readdirSync)(s);for(const l of g)if(l==="jobs.jsonl"&&$((0,
|
|
16
|
+
`)}if(e.sessions){const r=e.sessions.match(/^older-than=(.+)$/);r||(process.stderr.write("Error: --sessions must be of the form `older-than=<duration>` (e.g. older-than=14d).\n"),process.exit(1));let i;try{i=Date.now()-N(r[1])}catch(n){process.stderr.write(`Error: ${n instanceof Error?n.message:String(n)}
|
|
17
|
+
`),process.exit(1);return}let c=0;for(const n of m(t)){const s=(0,o.queueDir)(t,n);if(!(0,a.existsSync)(s))continue;let d;try{d=(0,a.statSync)(s)}catch{continue}if(d.mtimeMs>i)continue;let p=!1;try{const g=(0,a.readdirSync)(s);for(const l of g)if(l==="jobs.jsonl"&&$((0,y.join)(s,l))>0&&(p=!0),o.SNAPSHOT_FILENAME_REGEX.test(l)&&(p=!0),(l==="dead-letter.jsonl"||o.DEAD_LETTER_ROTATED_REGEX.test(l))&&$((0,y.join)(s,l))>0&&(p=!0),p)break}catch{continue}if(p){process.stdout.write(`skip session=${n} \u2014 has undelivered work or triage records
|
|
18
18
|
`);continue}try{(0,a.rmSync)(s,{recursive:!0,force:!0}),c++,process.stdout.write(`removed queue/ for session=${n}
|
|
19
19
|
`)}catch{}}process.stdout.write(`Removed ${c} queue/ subdir(s).
|
|
20
|
-
`)}});const
|
|
20
|
+
`)}});const v=new E.Command("dead-letter").description("Inspect and act on dead-letter entries");v.command("list").description("List dead-letter entries").option("--session <id>","scope to one session").option("--limit <n>","limit output count",e=>parseInt(e,10),50).option("--project-dir <dir>","project directory (overrides env/cwd)").action(e=>{const t=(0,o.resolveProjectDir)(e.projectDir),r=e.session?[e.session]:m(t),i=[];for(const s of r)for(const d of(0,o.readDeadLetterEntries)(t,s))i.push({sessionId:s,entry:d});i.sort((s,d)=>s.entry.received_at.localeCompare(d.entry.received_at));const c=e.limit??50,n=i.slice(-c);for(const s of n){const d="original"in s.entry?s.entry.original.id:"(parse-error)";process.stdout.write(`[${s.entry.received_at}] session=${s.sessionId} id=${s.entry.id} original=${d} category=${s.entry.category}
|
|
21
21
|
`)}process.stdout.write(`
|
|
22
22
|
Total matching: ${i.length} (showing last ${n.length})
|
|
23
|
-
`)}),
|
|
23
|
+
`)}),v.command("stats").description("Histogram of dead-letter categories").option("--session <id>","scope to one session").option("--project-dir <dir>","project directory (overrides env/cwd)").action(e=>{const t=(0,o.resolveProjectDir)(e.projectDir),r=e.session?[e.session]:m(t),i=new Map;for(const n of r)for(const s of(0,o.readDeadLetterEntries)(t,n)){const d=i.get(s.category);i.set(s.category,(d??0)+1)}const c=[...i.entries()].sort((n,s)=>s[1]-n[1]);if(c.length===0){process.stdout.write(`No dead-letter entries.
|
|
24
24
|
`);return}for(const[n,s]of c)process.stdout.write(`${String(s).padStart(6," ")} ${n}
|
|
25
|
-
`)}),
|
|
26
|
-
`),process.exit(1);return}const i=m(r);let c=null,n=null;for(const u of i){for(const
|
|
27
|
-
`),process.exit(1);return}const s=t.session??c,d=(0,o.queueDir)(r,s),p=(0,
|
|
25
|
+
`)}),v.command("retry <job_id>").description("Re-queue a dead-letter entry by its original job id. Atomic read-modify-write.").option("--session <id>","force re-queue into a specific session (default: the owning session)").option("--project-dir <dir>","project directory (overrides env/cwd)").action((e,t)=>{const r=(0,o.resolveProjectDir)(t.projectDir);if(!(0,I.isJobQueueEnabled)(r)){process.stderr.write(`Error: jobQueue is disabled. Add a "jobQueue": {} section to config.json (or remove "enable": false) to retry.
|
|
26
|
+
`),process.exit(1);return}const i=m(r);let c=null,n=null;for(const u of i){for(const h of(0,o.readDeadLetterEntries)(r,u))if("original"in h&&h.original.id===e){c=u,n=h;break}if(n)break}if(!n||!c){process.stderr.write(`Error: no dead-letter entry found with original id "${e}".
|
|
27
|
+
`),process.exit(1);return}const s=t.session??c,d=(0,o.queueDir)(r,s),p=(0,R.sessionDir)(r,s);if(!(0,a.existsSync)(p)){process.stderr.write(`Error: target session "${s}" does not exist.
|
|
28
28
|
`),process.exit(1);return}(0,a.mkdirSync)(d,{recursive:!0,mode:448});const g=(0,o.deadLetterFile)(r,c);let l;try{l=(0,a.readFileSync)(g,"utf-8")}catch(u){process.stderr.write(`Error reading dead-letter file: ${u instanceof Error?u.message:String(u)}
|
|
29
|
-
`),process.exit(1);return}const
|
|
30
|
-
`),j=[];let S=!1;for(const u of
|
|
29
|
+
`),process.exit(1);return}const x=l.split(`
|
|
30
|
+
`),j=[];let S=!1;for(const u of x)if(u.length!==0){if(!S)try{const h=JSON.parse(u);if("original"in h&&h.original.id===e){S=!0;continue}}catch{}j.push(u)}const D=`${g}.tmp-${process.pid}-${Date.now()}`;try{(0,a.writeFileSync)(D,j.length>0?j.join(`
|
|
31
31
|
`)+`
|
|
32
32
|
`:"",{mode:384}),(0,a.renameSync)(D,g)}catch(u){try{(0,a.unlinkSync)(D)}catch{}process.stderr.write(`Error rewriting dead-letter file: ${u instanceof Error?u.message:String(u)}
|
|
33
33
|
`),process.exit(1);return}try{const u=(0,o.submit)(r,s,n.original.type,n.original.data,{id:n.original.id});process.stdout.write(`Re-queued job id=${u} type=${n.original.type} session=${s}
|
|
34
34
|
`)}catch(u){process.stderr.write(`Error re-submitting: ${u instanceof Error?u.message:String(u)}
|
|
35
|
-
`),process.exit(1)}}),
|
|
35
|
+
`),process.exit(1)}}),v.command("clear").description("Empty the live dead-letter file; pass --all to also remove rotated archives").option("--session <id>","scope to one session").option("--all","also unlink rotated dead-letter-<UUID>.jsonl archives").option("--project-dir <dir>","project directory (overrides env/cwd)").action(e=>{const t=(0,o.resolveProjectDir)(e.projectDir),r=e.session?[e.session]:m(t);let i=0,c=0;for(const n of r){const s=(0,o.deadLetterFile)(t,n);if((0,a.existsSync)(s))try{(0,a.writeFileSync)(s,"",{mode:384}),i++}catch{}if(e.all){const d=(0,o.queueDir)(t,n);let p;try{p=(0,a.readdirSync)(d)}catch{continue}for(const g of p)if(o.DEAD_LETTER_ROTATED_REGEX.test(g))try{(0,a.unlinkSync)((0,y.join)(d,g)),c++}catch{}}}e.all?process.stdout.write(`Cleared dead-letter for ${i} session(s); removed ${c} rotated archive(s).
|
|
36
36
|
`):process.stdout.write(`Cleared dead-letter for ${i} session(s). Use --all to also remove rotated archives.
|
|
37
|
-
`)}),
|
|
37
|
+
`)}),w.addCommand(v);0&&(module.exports={queueCommand});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var S=Object.create;var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var y=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var h=(e,t)=>g(e,"name",{value:t,configurable:!0});var R=(e,t)=>{for(var r in t)g(e,r,{get:t[r],enumerable:!0})},p=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of $(t))!j.call(e,i)&&i!==r&&g(e,i,{get:()=>t[i],enumerable:!(s=C(t,i))||s.enumerable});return e};var N=(e,t,r)=>(r=e!=null?S(y(e)):{},p(t||!e||!e.__esModule?g(r,"default",{value:e,enumerable:!0}):r,e)),O=e=>p(g({},"__esModule",{value:!0}),e);var x={};R(x,{scenarioCommand:()=>m});module.exports=O(x);var b=require("commander"),u=require("path"),n=N(require("picocolors")),d=require("../lib/scenario-staleness"),w=require("../lib/git"),c=require("../lib/config");function F(e){return e==="fresh"?n.default.green("fresh "):e==="stale"?n.default.yellow("stale "):n.default.dim("unknown")}h(F,"stateLabel");function k(e){const t=(0,u.resolve)(e.project??process.cwd()),r=(0,d.checkScenarioFreshness)(t);if(e.json===!0){const o=e.stale===!0?r.filter(l=>l.state==="stale"):r;console.log(JSON.stringify(o,null,2)),e.stale===!0&&o.length>0&&(process.exitCode=1);return}if(r.length===0){console.log(n.default.dim("No saved scenarios found (.ironbee/scenarios/)."));return}const s=r.filter(o=>o.state==="stale");if(e.stale===!0){if(s.length===0){console.log(n.default.green("\u2713 No stale scenarios."));return}for(const o of s)v(o);console.log(""),console.log(n.default.yellow(`${s.length} stale scenario(s) \u2014 re-validate with /ironbee-sync-scenario or /ironbee-verify scenario:<name>.`)),process.exitCode=1;return}for(const o of r)v(o);const i=r.filter(o=>o.state==="fresh").length,f=r.filter(o=>o.state==="unknown").length;console.log(""),console.log(`${r.length} scenario(s): ${n.default.green(`${i} fresh`)}, ${n.default.yellow(`${s.length} stale`)}, ${n.default.dim(`${f} unknown`)}.`),s.length>0&&console.log(n.default.dim("Re-validate stale ones with /ironbee-sync-scenario or /ironbee-verify scenario:<name>."))}h(k,"runStatus");function v(e){if(console.log(`${F(e.state)} ${n.default.bold(e.name)} ${n.default.dim(`[${e.platform}]`)} \u2014 ${e.reason}`),e.changedCoveredPaths.length>0){const t=e.changedCoveredPaths.slice(0,5);for(const r of t)console.log(` ${n.default.dim("\xB7")} ${r}`);e.changedCoveredPaths.length>t.length&&console.log(` ${n.default.dim(`\xB7 +${e.changedCoveredPaths.length-t.length} more`)}`)}}h(v,"printRow");function D(e){const t=(0,u.resolve)(e.project??process.cwd()),r=(0,c.loadConfig)(t);let s=(0,c.getVerificationContextCommitDepth)(r);if(e.commitDepth!==void 0){const a=Number.parseInt(e.commitDepth,10);Number.isFinite(a)&&a>=0&&(s=a)}const i=(0,w.getChangedPathsRelative)(t,s);if(i===null){if(e.json===!0){console.log(JSON.stringify({available:!1,reason:"not a git repository",scenarioCount:0,covered:[],gaps:[],changed:[]},null,2));return}console.log(n.default.dim("Not a git repository \u2014 cannot compute the changed set."));return}const f=i.filter(a=>(0,c.requiresVerification)(a,r)),o=(0,d.coverageGaps)(t,f);if(e.json===!0){console.log(JSON.stringify(o,null,2));return}if(o.changed.length===0){console.log(n.default.dim("No verification-relevant changed files in the current window (working tree + recent commits)."));return}if(o.scenarioCount===0){console.log(n.default.yellow(`No saved scenarios \u2014 all ${o.changed.length} verification-relevant changed file(s) are uncovered.`)),console.log(n.default.dim("Author one with /ironbee-manage-scenario."));return}if(o.gaps.length===0){console.log(n.default.green(`\u2713 All ${o.changed.length} verification-relevant changed file(s) are covered by a saved scenario.`));return}console.log(n.default.yellow(`${o.gaps.length} of ${o.changed.length} verification-relevant changed file(s) covered by NO scenario:`));const l=o.gaps.slice(0,30);for(const a of l)console.log(` ${n.default.dim("\xB7")} ${a}`);o.gaps.length>l.length&&console.log(` ${n.default.dim(`\xB7 +${o.gaps.length-l.length} more`)}`),console.log(""),console.log(n.default.dim("Author a scenario for an uncovered area with /ironbee-manage-scenario. (Advisory \u2014 coveredPaths is author-declared, so a gap can be a false positive.)"))}h(D,"runCoverage");const m=new b.Command("scenario").description("Inspect saved verification scenarios (.ironbee/scenarios/)");m.command("status").description("Show freshness (fresh / stale / unknown) of saved scenarios vs the current code").option("-p, --project <dir>","project directory (default: cwd)").option("--stale","show only stale scenarios; exit non-zero when any exist (CI gate)").option("--json","machine-readable JSON output").action(e=>{k(e)}),m.command("coverage").description("Show changed files (working tree + recent commits) covered by NO saved scenario (the inverse of status)").option("-p, --project <dir>","project directory (default: cwd)").option("--commit-depth <n>","commits back to include beyond the working tree (default: verificationContext.commitDepth)").option("--json","machine-readable JSON output").action(e=>{D(e)});0&&(module.exports={scenarioCommand});
|