@ironbee-ai/cli 0.31.0 → 0.33.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 +8 -0
- package/dist/clients/base.js +1 -1
- package/dist/clients/claude/agents/ironbee-scenario.md +40 -11
- package/dist/clients/claude/agents/ironbee-verifier.md +40 -4
- package/dist/clients/claude/commands/ironbee-manage-scenario.md +2 -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/track-action-monitor.js +1 -1
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/scenario.terminal.md +26 -0
- package/dist/clients/claude/platforms/skill.browser.md +1 -1
- package/dist/clients/claude/platforms/skill.terminal.md +62 -0
- package/dist/clients/codex/agents/ironbee-scenario.md +39 -10
- package/dist/clients/codex/agents/ironbee-verifier.md +39 -3
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +21 -6
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.md +2 -1
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +3 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +4 -1
- package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +4 -0
- package/dist/clients/codex/hooks/require-verification.js +1 -1
- package/dist/clients/codex/hooks/track-action.js +1 -1
- package/dist/clients/codex/index.js +2 -2
- package/dist/clients/codex/platforms/command-verify.terminal.md +61 -0
- package/dist/clients/codex/platforms/rule.terminal.md +31 -0
- package/dist/clients/codex/platforms/scenario.terminal.md +36 -0
- package/dist/clients/codex/platforms/skill.browser.md +1 -1
- package/dist/clients/codex/platforms/skill.terminal.md +57 -0
- package/dist/clients/codex/rules/ironbee-verification.main.md +3 -0
- package/dist/clients/codex/skills/ironbee-verification.main.md +14 -0
- package/dist/clients/codex/util.js +1 -1
- package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +21 -6
- package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +3 -0
- package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +4 -1
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +4 -0
- 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/track-action-monitor.js +1 -1
- package/dist/clients/cursor/hooks/track-action.js +1 -1
- package/dist/clients/cursor/index.js +1 -1
- package/dist/clients/cursor/platforms/command-verify.terminal.md +61 -0
- package/dist/clients/cursor/platforms/rule.terminal.md +31 -0
- package/dist/clients/cursor/platforms/scenario.terminal.md +29 -0
- package/dist/clients/cursor/platforms/skill.browser.md +1 -1
- package/dist/clients/cursor/platforms/skill.terminal.md +54 -0
- package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
- package/dist/clients/cursor/skills/ironbee-verification.md +14 -0
- package/dist/clients/registry.js +1 -1
- package/dist/commands/config.js +2 -2
- package/dist/commands/hook.js +22 -19
- package/dist/commands/install.js +1 -1
- package/dist/commands/platform-suggest.js +2 -0
- package/dist/commands/scenario.js +1 -1
- package/dist/commands/terminal.js +1 -0
- package/dist/hooks/core/actions.js +9 -7
- package/dist/hooks/core/run-checks.js +7 -0
- package/dist/hooks/core/verification-context.js +19 -15
- package/dist/hooks/core/verify-gate.js +35 -21
- package/dist/import/claude/events/tool-call.js +1 -1
- package/dist/import/codex/events/tool-call.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/event.js +1 -1
- package/dist/lib/headless.js +1 -0
- package/dist/lib/install-version.js +1 -1
- package/dist/lib/platform-section.js +5 -4
- package/dist/lib/prompt.js +6 -5
- package/dist/lib/scenario-staleness.js +1 -1
- package/dist/tui/config/schema.js +1 -1
- package/dist/tui/platforms/area.js +2 -2
- package/dist/tui/projects/area.js +4 -4
- package/dist/tui/shell/session.js +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!-- Terminal verification is ENABLED for this project. The Stop hook
|
|
2
|
+
enforces a terminal cycle whenever an edited file matches
|
|
3
|
+
`terminal.verifyPatterns`. -->
|
|
4
|
+
|
|
5
|
+
## ⚠️ CRITICAL: when NOT to use terminal-devtools
|
|
6
|
+
|
|
7
|
+
**`terminal-devtools` is ONLY for programs with terminal-observable behavior** — CLIs, REPLs, shells, and full-screen TUIs that you can drive by spawning them attached to a PTY. Do **NOT** call `tdt_*` tools for changes that produce no terminal-observable behavior.
|
|
8
|
+
|
|
9
|
+
**How to tell whether the change has terminal-observable behavior:**
|
|
10
|
+
- A CLI entrypoint (`bin/` script, `package.json` `"bin"` field, a `main()` that parses argv, a `cobra`/`commander`/`click`/`clap` command tree)
|
|
11
|
+
- A REPL / interactive prompt the user types into
|
|
12
|
+
- A full-screen TUI (`blessed`, `ink`, `ratatui`, `bubbletea`, `ncurses`, …)
|
|
13
|
+
- A script / build target / shell command whose stdout, stderr, or exit code changed
|
|
14
|
+
|
|
15
|
+
If the change is a web-only UI with no command-line or terminal surface — that's the **browser cycle**, not this one. Do NOT call any `tdt_*` tools.
|
|
16
|
+
|
|
17
|
+
**Misconfiguration recovery.** If this cycle activated but the change has no terminal-observable behavior, the operator enabled the terminal cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` until `maxRetries` is exhausted. Don't attempt to spawn a PTY. Stop and tell the user clearly: this change has no terminal-observable behavior; ask them to run `ironbee terminal disable` to unblock the gate.
|
|
18
|
+
|
|
19
|
+
## Terminal flow
|
|
20
|
+
|
|
21
|
+
The terminal cycle drives CLIs / REPLs / TUIs by spawning them attached to a PTY (like tmux). Pick the evidence path that fits the changed code area:
|
|
22
|
+
|
|
23
|
+
1. **Pick an evidence path** per changed code area:
|
|
24
|
+
- **Run-evidence path** (one-shot command confirms the change works):
|
|
25
|
+
- Run the affected command end-to-end: `mcp__terminal-devtools__tdt_pty_run` — it spawns the command attached to a PTY, runs it to completion, and returns the FULL output plus the exit code in one call. Best for non-interactive CLIs, build targets, scripts, and test runs.
|
|
26
|
+
- Confirm the returned output shows the expected result AND the exit code matches expectation (`0` for success; the expected non-zero code when failure is the change under test).
|
|
27
|
+
- **Interactive-evidence path** (driving a live session confirms an interactive change):
|
|
28
|
+
- Spawn the program attached to a PTY: `mcp__terminal-devtools__tdt_pty_start` — returns a `paneId` you reference for the rest of the flow. Best for REPLs, shells, and full-screen TUIs.
|
|
29
|
+
- Drive input to exercise the changed code: `mcp__terminal-devtools__tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`, …) for control keys / navigation, `mcp__terminal-devtools__tdt_interaction_send-text` for literal text.
|
|
30
|
+
- **Synchronize before reading** — block until the expected output appears with `mcp__terminal-devtools__tdt_sync_wait-for` (prefer this over fixed delays).
|
|
31
|
+
- Capture the output: `mcp__terminal-devtools__tdt_content_capture` — use `mode: stream` for line-oriented programs (REPLs, shells; supports an incremental `since` cursor so you read only new lines), and `mode: screen` for full-screen TUIs (snapshots the rendered screen).
|
|
32
|
+
- Confirm the capture shows the expected result for the change.
|
|
33
|
+
- Stop the pane when done: `mcp__terminal-devtools__tdt_pty_stop`.
|
|
34
|
+
- **Auxiliary (NOT evidence — synchronization / housekeeping only):** `mcp__terminal-devtools__tdt_sync_wait-for-idle` (block until output settles), `mcp__terminal-devtools__tdt_content_get-cursor` (read the stream cursor position), `mcp__terminal-devtools__tdt_pty_resize` (resize the PTY), `mcp__terminal-devtools__tdt_pty_signal` (send a signal), `mcp__terminal-devtools__tdt_pty_list` (list active panes). None of these count toward the gate — they help you drive the session, they don't inspect the change.
|
|
35
|
+
|
|
36
|
+
**Batch (speed):** on the run-evidence path, a single `mcp__terminal-devtools__tdt_pty_run` already does the whole thing (spawn + run + capture + exit code), so there's nothing to batch. On the interactive-evidence path, `tdt_pty_start` runs standalone first (prerequisite); then batch a coherent sequence of `tdt_interaction_send-*` + `tdt_sync_wait-for` + `tdt_content_capture` into one `mcp__terminal-devtools__tdt_execute` — the capture reads the state after the batched input, so to assert an intermediate state add a `tdt_sync_wait-for` + capture at that point too.
|
|
37
|
+
|
|
38
|
+
### Verdict fields
|
|
39
|
+
The verdict is platform-agnostic — submit only semantic judgment:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"session_id": "<sid>",
|
|
44
|
+
"status": "pass",
|
|
45
|
+
"checks": ["`mycli build` exits 0 and prints the new summary line", "REPL `:help` lists the new command"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
On fail, include `issues`. On pass after a previous fail, include `fixes`.
|
|
50
|
+
|
|
51
|
+
Terminal-cycle pass criteria:
|
|
52
|
+
- **Run-evidence**: the affected command was run via `tdt_pty_run` AND its output AND exit code confirm the change behaved correctly.
|
|
53
|
+
- **Interactive-evidence**: a pane was spawned (`tdt_pty_start`) AND input was driven (`tdt_interaction_send-keys` / `tdt_interaction_send-text`) AND output was captured (`tdt_content_capture`) AND it shows the expected result.
|
|
54
|
+
|
|
55
|
+
## Multi-cycle (browser + terminal simultaneously)
|
|
56
|
+
|
|
57
|
+
Both cycles can be active simultaneously. One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
|
|
@@ -31,6 +31,9 @@ Every `apply_patch` clears the verdict, requiring re-verification. The Stop gate
|
|
|
31
31
|
<!--IRONBEE:PLATFORM:android-->
|
|
32
32
|
<!--/IRONBEE:PLATFORM:android-->
|
|
33
33
|
|
|
34
|
+
<!--IRONBEE:PLATFORM:terminal-->
|
|
35
|
+
<!--/IRONBEE:PLATFORM:terminal-->
|
|
36
|
+
|
|
34
37
|
## BANNED
|
|
35
38
|
|
|
36
39
|
- Reporting a task complete without verifying your changes through the real tools.
|
|
@@ -42,6 +42,17 @@ the per-cycle flow.
|
|
|
42
42
|
tools in separate lanes**, so a same-message ordering of this shell command before a devtools
|
|
43
43
|
call is NOT guaranteed — a devtools call that lands first is blocked. Once the cycle is open,
|
|
44
44
|
independent MCP calls can ride one message.
|
|
45
|
+
2.5. **Run the project checks FIRST (lint/test/…)** — the deterministic first step of verification:
|
|
46
|
+
```
|
|
47
|
+
echo '{"session_id":"<your-session-id>"}' | ironbee hook run-checks
|
|
48
|
+
```
|
|
49
|
+
Use a generous shell timeout (they may take minutes). Runs the configured `verification.checks`
|
|
50
|
+
and records the results the gate reads. 🛑 **IF ANY REQUIRED CHECK FAILS, THE VERIFICATION HAS
|
|
51
|
+
ALREADY FAILED — STOP.** It is **NOT your call** whether the failure is "just a fixture",
|
|
52
|
+
"unrelated", or "pre-existing" — a required non-zero exit **IS** a failure. Do **NOT** touch the
|
|
53
|
+
devtools tools, do **NOT** submit a pass; submit a **fail** verdict whose `issues` are the
|
|
54
|
+
failing checks (the gate enforces the fix). Only when **every** required check PASSES do you
|
|
55
|
+
continue. ("no checks configured" → continue.)
|
|
45
56
|
3. Build and start the application **only if it isn't already running** (check `docker compose ps`
|
|
46
57
|
/ process output / config — don't guess ports). Track whether YOU started it.
|
|
47
58
|
4. **Run the per-cycle flows for every active cycle** (see the platform sections below). All
|
|
@@ -98,6 +109,9 @@ the dominant cost. Drive the tools in as few turns as you can:
|
|
|
98
109
|
<!--IRONBEE:PLATFORM:android-->
|
|
99
110
|
<!--/IRONBEE:PLATFORM:android-->
|
|
100
111
|
|
|
112
|
+
<!--IRONBEE:PLATFORM:terminal-->
|
|
113
|
+
<!--/IRONBEE:PLATFORM:terminal-->
|
|
114
|
+
|
|
101
115
|
## Important
|
|
102
116
|
- **Always submit a verdict after every verification attempt** — both pass AND fail.
|
|
103
117
|
- Submit verdicts via `ironbee hook submit-verdict`, never write `verdict.json` directly.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var $=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var o=(n,t)=>$(n,"name",{value:t,configurable:!0});var J=(n,t)=>{for(var e in t)$(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of W(t))!M.call(n,r)&&r!==e&&$(n,r,{get:()=>t[r],enumerable:!(s=L(t,r))||s.enumerable});return n};var B=n=>P($({},"__esModule",{value:!0}),n);var hn={};J(hn,{AGENTS_MD_END_MARKER:()=>y,AGENTS_MD_START_MARKER:()=>v,canonicalizeCodexServerName:()=>C,canonicalizeCodexToolName:()=>R,classifyCodexTool:()=>D,codexAgentTomlPath:()=>ln,codexConfigTomlPath:()=>O,codexHooksJsonPath:()=>pn,decodeJwtPayload:()=>E,ensureFeaturesHooksTrue:()=>Z,ensureMultiAgentV2SpawnMetadataExposed:()=>Y,ensureSandboxWritableRoot:()=>sn,extractBashBinary:()=>A,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>K,extractTomlTopLevelModel:()=>un,findTomlSection:()=>p,normalizeCodexToolName:()=>j,parseCodexHookStdin:()=>z,readCodexConfigToml:()=>gn,removeAgentsTable:()=>rn,removeFeaturesHooks:()=>q,removeMcpServer:()=>tn,removeMultiAgentV2SpawnMetadata:()=>N,removeSandboxWritableRoot:()=>on,resolveCodexUsage:()=>X,stripAgentsMdBlock:()=>cn,tomlBodyFromRecord:()=>an,upsertAgentsMdBlock:()=>dn,upsertAgentsTable:()=>en,upsertMcpServer:()=>nn,userCodexAgentTomlPath:()=>_n,userCodexConfigTomlPath:()=>mn,userCodexHooksJsonPath:()=>xn,writeCodexConfigToml:()=>fn});module.exports=B(hn);var x=require("fs"),I=require("os"),m=require("path"),b=require("../../lib/logger");function z(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(z,"parseCodexHookStdin");const h="mcp__",H={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools",android_devtools:"android-devtools"},F=["bdt_","ndt_","bedt_","adt_"];function C(n){return H[n]??n}o(C,"canonicalizeCodexServerName");function R(n){if(!F.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length>=3&&t[1]==="scenario"?`${t[0]}_scenario-${t.slice(2).join("-")}`:t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(R,"canonicalizeCodexToolName");const V=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"],["adt_","android-devtools"]];function S(n){if(!n)return null;if(n.startsWith(h)){const t=n.slice(h.length),e=t.indexOf("__");return e<0?null:C(t.slice(0,e))}for(const[t,e]of V)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function j(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(j,"normalizeCodexToolName");function D(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(h)){const s=n.slice(h.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),u=C(i),l=s.slice(r+2);return{tool_type:"mcp",tool_name:R(l),mcp_server:u}}}const t=S(n);if(t!==null&&!n.startsWith(h))return{tool_type:"mcp",tool_name:R(n),mcp_server:t};const e=j(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(D,"classifyCodexTool");function K(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(j(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?A(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,u=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof u=="boolean"?u:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,u=e.yield_time_ms,l=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof u=="number"?u:void 0,max_output_tokens:typeof l=="number"?l:void 0}}if(n.startsWith(h)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(K,"extractCodexToolInput");function A(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(A,"extractBashBinary");function E(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(E,"decodeJwtPayload");function U(n){if(typeof n=="string"){const t=E(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(U,"extractIdTokenFields");function X(n){const t=n??(0,m.join)((0,I.homedir)(),".codex","auth.json");if(!(0,x.existsSync)(t))return{};try{const e=JSON.parse((0,x.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=U(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(X,"resolveCodexUsage");function G(n,t){return n.trim()===`[${t}]`}o(G,"tableHeaderLineExact");function Q(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(Q,"isAnyTableHeader");function T(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(T,"tableHeaderName");function p(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(G(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(Q(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(p,"findTomlSection");function k(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(k,"trimTrailingBlanks");function w(n,t){return n.length===0?t.join(`
|
|
1
|
+
"use strict";var $=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var o=(n,t)=>$(n,"name",{value:t,configurable:!0});var J=(n,t)=>{for(var e in t)$(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of W(t))!M.call(n,r)&&r!==e&&$(n,r,{get:()=>t[r],enumerable:!(s=L(t,r))||s.enumerable});return n};var B=n=>P($({},"__esModule",{value:!0}),n);var hn={};J(hn,{AGENTS_MD_END_MARKER:()=>y,AGENTS_MD_START_MARKER:()=>v,canonicalizeCodexServerName:()=>C,canonicalizeCodexToolName:()=>R,classifyCodexTool:()=>D,codexAgentTomlPath:()=>ln,codexConfigTomlPath:()=>O,codexHooksJsonPath:()=>pn,decodeJwtPayload:()=>E,ensureFeaturesHooksTrue:()=>Z,ensureMultiAgentV2SpawnMetadataExposed:()=>Y,ensureSandboxWritableRoot:()=>sn,extractBashBinary:()=>A,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>K,extractTomlTopLevelModel:()=>un,findTomlSection:()=>p,normalizeCodexToolName:()=>j,parseCodexHookStdin:()=>z,readCodexConfigToml:()=>gn,removeAgentsTable:()=>rn,removeFeaturesHooks:()=>q,removeMcpServer:()=>tn,removeMultiAgentV2SpawnMetadata:()=>N,removeSandboxWritableRoot:()=>on,resolveCodexUsage:()=>X,stripAgentsMdBlock:()=>cn,tomlBodyFromRecord:()=>an,upsertAgentsMdBlock:()=>dn,upsertAgentsTable:()=>en,upsertMcpServer:()=>nn,userCodexAgentTomlPath:()=>_n,userCodexConfigTomlPath:()=>mn,userCodexHooksJsonPath:()=>xn,writeCodexConfigToml:()=>fn});module.exports=B(hn);var x=require("fs"),I=require("os"),m=require("path"),b=require("../../lib/logger");function z(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(z,"parseCodexHookStdin");const h="mcp__",H={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools",android_devtools:"android-devtools",terminal_devtools:"terminal-devtools"},F=["bdt_","ndt_","bedt_","adt_","tdt_"];function C(n){return H[n]??n}o(C,"canonicalizeCodexServerName");function R(n){if(!F.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length>=3&&t[1]==="scenario"?`${t[0]}_scenario-${t.slice(2).join("-")}`:t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(R,"canonicalizeCodexToolName");const V=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"],["adt_","android-devtools"],["tdt_","terminal-devtools"]];function S(n){if(!n)return null;if(n.startsWith(h)){const t=n.slice(h.length),e=t.indexOf("__");return e<0?null:C(t.slice(0,e))}for(const[t,e]of V)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function j(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(j,"normalizeCodexToolName");function D(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(h)){const s=n.slice(h.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),u=C(i),l=s.slice(r+2);return{tool_type:"mcp",tool_name:R(l),mcp_server:u}}}const t=S(n);if(t!==null&&!n.startsWith(h))return{tool_type:"mcp",tool_name:R(n),mcp_server:t};const e=j(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(D,"classifyCodexTool");function K(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(j(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?A(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,u=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof u=="boolean"?u:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,u=e.yield_time_ms,l=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof u=="number"?u:void 0,max_output_tokens:typeof l=="number"?l:void 0}}if(n.startsWith(h)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(K,"extractCodexToolInput");function A(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(A,"extractBashBinary");function E(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(E,"decodeJwtPayload");function U(n){if(typeof n=="string"){const t=E(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(U,"extractIdTokenFields");function X(n){const t=n??(0,m.join)((0,I.homedir)(),".codex","auth.json");if(!(0,x.existsSync)(t))return{};try{const e=JSON.parse((0,x.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=U(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(X,"resolveCodexUsage");function G(n,t){return n.trim()===`[${t}]`}o(G,"tableHeaderLineExact");function Q(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(Q,"isAnyTableHeader");function T(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(T,"tableHeaderName");function p(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(G(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(Q(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(p,"findTomlSection");function k(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(k,"trimTrailingBlanks");function w(n,t){return n.length===0?t.join(`
|
|
2
2
|
`)+`
|
|
3
3
|
`:n.replace(/\n+$/,"")+`
|
|
4
4
|
|
|
@@ -67,23 +67,35 @@ tools directly: that keeps it gate-orthogonal — no `verification_id`, can't fa
|
|
|
67
67
|
> passes" means fixing the SCRIPT, never working around the app.)
|
|
68
68
|
|
|
69
69
|
## Script format
|
|
70
|
-
JS run in the devtools sandbox (async — top-level `await`/`return` work); reads
|
|
70
|
+
JS run in the devtools sandbox (async — top-level `await`/`return` work); reads its inputs from `args`:
|
|
71
71
|
|
|
72
72
|
```js
|
|
73
|
-
const { baseUrl } = args; // declared
|
|
73
|
+
const { baseUrl } = args; // declared in the scenario's `params` contract
|
|
74
74
|
const result = await callTool('<bare-tool-name>', { /* tool input */ });
|
|
75
75
|
return { ok: true };
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Discover the available `callTool` tool names for a platform from your connected MCP schemas — don't
|
|
79
|
-
guess.
|
|
79
|
+
guess. Declare each input via the first-class **`params`** contract (§Parameters), not `argsSchema`.
|
|
80
|
+
|
|
81
|
+
## Parameters (`params`) — typed, defaulted, validated
|
|
82
|
+
Declare a parametric scenario's inputs via the first-class **`params`** array on
|
|
83
|
+
`scenario-add` / `scenario-update` (top-level field, NOT metadata — supersedes the old `argsSchema`
|
|
84
|
+
convention). Each entry: `name` (required — the `args` key the script reads), `description`, `type`
|
|
85
|
+
(`string`/`number`/`boolean`/`object`/`array`; `object`/`array` shallow-checked at the top level),
|
|
86
|
+
`default` (applied when the arg is omitted — **capture it from the live-authoring run** so the
|
|
87
|
+
scenario re-runs "as captured" with zero args), `example` (doc-only shape when there's no `default`),
|
|
88
|
+
`required` (reject the run when there's no value AND no `default`). `scenario-run` applies defaults,
|
|
89
|
+
enforces `required`, shallow-validates declared types, and surfaces `params` in list/search/run
|
|
90
|
+
output. Pass `args` only to OVERRIDE a default. `scenario-update` shallow-replaces `params` (re-send
|
|
91
|
+
the full array; omit to keep the stored contract).
|
|
80
92
|
|
|
81
93
|
## Metadata conventions (stamp on add/update)
|
|
82
|
-
- `argsSchema` — declared params, e.g. `{ "baseUrl": "string" }`. **Mandatory for parametric scenarios.**
|
|
83
94
|
- `ironbee.coveredPaths` — source paths exercised (array), when derivable.
|
|
84
95
|
- `ironbee.group` / `ironbee.order` — for a cross-platform split.
|
|
85
|
-
- `*_scenario-update` does a **shallow replace** of metadata — to change one key,
|
|
86
|
-
|
|
96
|
+
- `*_scenario-update` does a **shallow replace** of metadata (and of `params`) — to change one key,
|
|
97
|
+
re-send the FULL object / array (read it first, merge, write back). The typed input contract is the
|
|
98
|
+
first-class `params` field (§Parameters), not a metadata key.
|
|
87
99
|
|
|
88
100
|
The platform sections below list each enabled cycle's server, tool prefix, and store dir.
|
|
89
101
|
|
|
@@ -98,3 +110,6 @@ The platform sections below list each enabled cycle's server, tool prefix, and s
|
|
|
98
110
|
|
|
99
111
|
<!--IRONBEE:PLATFORM:android-->
|
|
100
112
|
<!--/IRONBEE:PLATFORM:android-->
|
|
113
|
+
|
|
114
|
+
<!--IRONBEE:PLATFORM:terminal-->
|
|
115
|
+
<!--/IRONBEE:PLATFORM:terminal-->
|
|
@@ -22,7 +22,7 @@ directly. This is NOT a verification cycle — no verdict, no gate.
|
|
|
22
22
|
- **passes** → still current; (non-check) `*_scenario-update` to stamp `ironbee.commit` → HEAD
|
|
23
23
|
(read via `git rev-parse HEAD`) + `ironbee.liveValidated: true`. `*_scenario-update`
|
|
24
24
|
shallow-replaces metadata — read current metadata and re-send it MERGED with these two keys
|
|
25
|
-
(don't drop `coveredPaths` / `group
|
|
25
|
+
(don't drop `coveredPaths` / `group`; omit `params` to keep the stored typed contract).
|
|
26
26
|
- **mechanical DRIFT** (the way to reach / drive the flow changed, not the expected outcome) →
|
|
27
27
|
repair the SCRIPT mechanics only, `*_scenario-update`, re-run until green, then stamp.
|
|
28
28
|
- **real DEFECT** (the expected outcome is unreachable — the app broke) → **STOP, report, do NOT
|
|
@@ -52,3 +52,6 @@ running anything, use `ironbee scenario status`.)
|
|
|
52
52
|
|
|
53
53
|
<!--IRONBEE:PLATFORM:android-->
|
|
54
54
|
<!--/IRONBEE:PLATFORM:android-->
|
|
55
|
+
|
|
56
|
+
<!--IRONBEE:PLATFORM:terminal-->
|
|
57
|
+
<!--/IRONBEE:PLATFORM:terminal-->
|
|
@@ -35,6 +35,7 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
|
|
|
35
35
|
1. **Start verification**: Run `echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start` via terminal.
|
|
36
36
|
**In fix mode**, add the intent flag so IronBee's completion gate enforces fix-until-pass:
|
|
37
37
|
`echo '{"session_id":"<your-session-id>"}' | ironbee hook verification-start --intent fix`
|
|
38
|
+
1.5. **Run the project checks FIRST (lint/test/…)**: `echo '{"session_id":"<your-session-id>"}' | ironbee hook run-checks` (generous shell timeout — they may take minutes). Runs the configured `verification.checks` and records the results the gate reads. 🛑 **IF ANY REQUIRED CHECK FAILS, THE VERIFICATION HAS ALREADY FAILED — STOP.** It is **NOT your call** whether the failure is "just a fixture", "unrelated", or "pre-existing" — a required non-zero exit **IS** a failure. Do **NOT** touch the devtools tools or submit a pass; submit a **fail** verdict whose `issues` are the failing checks (the gate enforces the fix). Only when **every** required check PASSES do you continue. ("no checks configured" → just continue.)
|
|
38
39
|
2. **Build and start** the application if not already running.
|
|
39
40
|
3. **For every active cycle, run its flow** — driven by the **Verification scenario** above when one was supplied, otherwise as described in the platform sections near the bottom of this file. All active cycles must be exercised within this same verification cycle.
|
|
40
41
|
4. **Stop** the dev server when verification is complete (every cycle — including the final one).
|
|
@@ -61,6 +62,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
|
|
|
61
62
|
<!--IRONBEE:PLATFORM:android-->
|
|
62
63
|
<!--/IRONBEE:PLATFORM:android-->
|
|
63
64
|
|
|
65
|
+
<!--IRONBEE:PLATFORM:terminal-->
|
|
66
|
+
<!--/IRONBEE:PLATFORM:terminal-->
|
|
67
|
+
|
|
64
68
|
---
|
|
65
69
|
|
|
66
70
|
## When to FAIL
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var h=(o,t)=>u(o,"name",{value:t,configurable:!0});var k=(o,t)=>{for(var i in t)u(o,i,{get:t[i],enumerable:!0})},I=(o,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!x.call(o,s)&&s!==i&&u(o,s,{get:()=>t[s],enumerable:!(e=P(t,s))||e.enumerable});return o};var E=o=>I(u({},"__esModule",{value:!0}),o);var $={};k($,{run:()=>F});module.exports=E($);var c=require("fs"),b=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),d=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),p=require("../../../lib/runtime-paths");async function F(o,t){const i=t?.soft===!0;let e;try{e=JSON.parse((0,S.readStdin)())}catch(r){n.logger.debug(`failed to parse stdin: ${r}`);const l={permission:"allow"};process.stdout.write(JSON.stringify(l)),process.exit(0);return}const s=e.conversation_id??"default";(0,n.setLogFile)((0,p.sessionLogFile)(o,s));const f=(0,p.sessionDir)(o,s),g=`${f}/actions.jsonl`;if(!i&&(0,b.hasToolCallsSinceLastVerdict)(g)){const r={permission:"deny",agent_message:`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
|
|
2
2
|
|
|
3
3
|
Submit your verdict first:
|
|
4
4
|
echo '{"session_id":"${s}","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict
|
|
5
5
|
|
|
6
|
-
Then you can edit code to fix the issues.`};process.stdout.write(JSON.stringify(r)),process.exit(2);return}const a=e.tool_input?.file_path??e.tool_input?.path;if(a&&e.tool_use_id){const r=(0,d.loadConfig)(o),
|
|
6
|
+
Then you can edit code to fix the issues.`};process.stdout.write(JSON.stringify(r)),process.exit(2);return}const a=e.tool_input?.file_path??e.tool_input?.path;if(a&&e.tool_use_id){const r=(0,d.loadConfig)(o),l=(0,d.getCaptureFileChangeset)(r),m=(0,c.existsSync)(a),w=e.tool_name==="Write",T=e.tool_name==="StrReplace"||e.tool_name==="Delete";if(w||T&&l){const _={file_existed:m};if(l&&m)try{_.prior_content=(0,c.readFileSync)(a,"utf-8")}catch(O){n.logger.debug(`failed to pre-read ${a} for changeset capture: ${O}`)}(0,C.stashToolUseData)(s,e.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:g,source:"pre_tool_use"});const y={permission:"allow"};process.stdout.write(JSON.stringify(y)),process.exit(0)}h(F,"run");0&&(module.exports={run});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var f=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var R=(o,t)=>f(o,"name",{value:t,configurable:!0});var J=(o,t)=>{for(var c in t)f(o,c,{get:t[c],enumerable:!0})},L=(o,t,c,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!j.call(o,r)&&r!==c&&f(o,r,{get:()=>t[r],enumerable:!(i=F(t,r))||i.enumerable});return o};var D=o=>L(f({},"__esModule",{value:!0}),o);var Y={};J(Y,{run:()=>q});module.exports=D(Y);var S=require("crypto"),e=require("../../../hooks/core/session-state"),I=require("../../../hooks/core/actions"),k=require("../../../hooks/core/activity"),
|
|
1
|
+
"use strict";var f=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var R=(o,t)=>f(o,"name",{value:t,configurable:!0});var J=(o,t)=>{for(var c in t)f(o,c,{get:t[c],enumerable:!0})},L=(o,t,c,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!j.call(o,r)&&r!==c&&f(o,r,{get:()=>t[r],enumerable:!(i=F(t,r))||i.enumerable});return o};var D=o=>L(f({},"__esModule",{value:!0}),o);var Y={};J(Y,{run:()=>q});module.exports=D(Y);var S=require("crypto"),e=require("../../../hooks/core/session-state"),I=require("../../../hooks/core/actions"),k=require("../../../hooks/core/activity"),M=require("../../../hooks/core/verification-lifecycle"),O=require("../../../lib/config"),U=require("../../../lib/recording-tools"),E=require("../../../hooks/core/scenario-tools"),p=require("../../../lib/logger"),A=require("../../../lib/stdin"),N=require("../../../lib/runtime-paths");const h={"MCP:bdt_":"browser-devtools","MCP:ndt_":"node-devtools","MCP:bedt_":"backend-devtools","MCP:adt_":"android-devtools","MCP:tdt_":"terminal-devtools"},W="browser-devtools";async function q(o,t){const c=t?.soft===!0;let i;try{i=JSON.parse((0,A.readStdin)())}catch(n){p.logger.debug(`failed to parse stdin: ${n}`);const B={permission:"allow"};process.stdout.write(JSON.stringify(B)),process.exit(0);return}const r=i.conversation_id??"default",s=(0,N.sessionDir)(o,r);(0,p.setLogFile)(`${s}/session.log`);const P=`${s}/actions.jsonl`,g=(0,E.isScenarioTool)(i.tool_name),b=(0,e.getActiveVerificationId)(s);if(!b&&!c&&!g){const n={permission:"deny",agent_message:`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools).
|
|
2
2
|
|
|
3
3
|
Start verification first:
|
|
4
4
|
echo '{"session_id":"${r}"}' | ironbee hook verification-start
|
|
5
5
|
|
|
6
|
-
Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend, MCP:adt_* for android.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const m=i.tool_name??"",v=m.startsWith("MCP:")?m.slice(4):"",u=v?(0,
|
|
6
|
+
Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend, MCP:adt_* for android, MCP:tdt_* for terminal.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const m=i.tool_name??"",v=m.startsWith("MCP:")?m.slice(4):"",u=v?(0,U.recordingToolsForBareTool)(v):null;if(!c&&!g&&u!==null&&(0,e.isRecordingRequired)(s)&&!(0,e.isRecordingActive)(s)&&v!==u.startTool){const n={permission:"deny",agent_message:`BLOCKED: Recording is required but not started.
|
|
7
7
|
|
|
8
8
|
1. Start recording NOW:
|
|
9
9
|
Use MCP:${u.startTool}
|
|
@@ -12,4 +12,4 @@ Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for bro
|
|
|
12
12
|
|
|
13
13
|
3. **Stop recording BEFORE submitting verdict:**
|
|
14
14
|
Use MCP:${u.stopTool}
|
|
15
|
-
submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,k.startActivity)({sessionDir:s,actionsFile:P,source:"pre_tool_use"});let d=b;c&&!d&&!g&&(d=(await(0,
|
|
15
|
+
submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,k.startActivity)({sessionDir:s,actionsFile:P,source:"pre_tool_use"});let d=b;c&&!d&&!g&&(d=(await(0,M.startVerification)({sessionId:r,sessionDir:s,actionsFile:P,recordingEnabled:!1})).verificationId);const $=(0,e.getActiveTraceId)(s),_=(0,e.getActiveActivityId)(s),y=(0,I.resolveProjectName)(o),C=[`prj:${y}`,`sid:${r}`];_&&C.push(`aid:${_}`),d&&C.push(`vid:${d}`);const V=`ironbee=${C.join(";")}`,a=(0,O.loadConfig)(o),T={...i.tool_input??{}},l={projectName:y,sessionId:r,activityId:_,verificationId:d,traceId:$,traceState:V,toolCallId:(0,S.randomUUID)()};i.tool_use_id&&(l.toolUseId=i.tool_use_id),l.mcpServer=(()=>{for(const n of Object.keys(h))if(m.startsWith(n))return h[n];return W})();const w=(0,e.getUserEmail)(s);w&&(l.userEmail=w),a.collector?.url&&(l.collectorUrl=a.collector.url),a.collector?.oauthToken?l.collectorOAuthToken=a.collector.oauthToken:a.collector?.apiKey&&(l.collectorApiKey=a.collector.apiKey),T._metadata=l;const x={permission:"allow",updated_input:T};process.stdout.write(JSON.stringify(x)),process.exit(0)}R(q,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var d=Object.defineProperty;var
|
|
1
|
+
"use strict";var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var c=(o,t)=>d(o,"name",{value:t,configurable:!0});var C=(o,t)=>{for(var r in t)d(o,r,{get:t[r],enumerable:!0})},P=(o,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $(t))!v.call(o,n)&&n!==r&&d(o,n,{get:()=>t[n],enumerable:!(i=k(t,n))||i.enumerable});return o};var R=o=>P(d({},"__esModule",{value:!0}),o);var W={};C(W,{run:()=>M});module.exports=R(W);var T=require("../../../hooks/core/actions"),b=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),E=require("../../../lib/config"),s=require("../../../lib/logger"),p=require("../../../lib/output"),O=require("../../../lib/stdin"),a=require("../../../queue"),f=require("../util"),A=require("../../../lib/runtime-paths");const D="bdt_",L="ndt_",S="bedt_",F="adt_",J="tdt_";async function M(o){let t;try{t=JSON.parse((0,O.readStdin)())}catch(u){s.logger.debug(`failed to parse stdin: ${u}`),(0,p.writeAndExit)(JSON.stringify({}),0);return}const r=t.conversation_id??"default",i=(0,A.sessionDir)(o,r),n=`${i}/actions.jsonl`;(0,s.setLogFile)(`${i}/session.log`),(0,m.getActiveActivityId)(i)===void 0&&await(0,b.startActivity)({sessionDir:i,actionsFile:n,source:"pre_tool_use"});const _=t.tool_name??"unknown",N=Date.now(),w=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,I=(0,m.getActiveActivityId)(i),e=(0,f.classifyTool)(_,t.tool_input);if(e.tool_type==="mcp"&&(e.tool_name.startsWith(D)||e.tool_name.startsWith(L)||e.tool_name.startsWith(S)||e.tool_name.startsWith(F)||e.tool_name.startsWith(J))){s.logger.debug(`track-action-monitor: skipped devtools tool ${_}`),(0,p.writeAndExit)(JSON.stringify({}),0);return}const g=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let l;if(g){const u=[];t.failure_type&&u.push(t.failure_type),t.is_interrupt&&u.push("interrupted"),l=`${u.length>0?`${u.join(",")}: `:""}${g}`}const h={...(0,T.baseFields)(n),type:"tool_call",timestamp:N,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:t.tool_use_id,tool_input:(0,f.extractCursorToolInput)(_,w),tool_input_size:y(t.tool_input),tool_response:l?void 0:t.tool_output,tool_response_size:y(l?void 0:t.tool_output),activity_id:I,duration:typeof t.duration=="number"?t.duration:null,mcp_server:e.mcp_server,error:l};x(o,r,h),s.logger.debug(`track-action-monitor: ${_}${l?" (failed)":""}`),(0,p.writeAndExit)(JSON.stringify({}),0)}c(M,"run");function x(o,t,r){if(!(0,E.isJobQueueEnabled)(o))return;const i={...r};delete i.tool_response;try{(0,a.submit)(o,t,a.SEND_EVENT_TYPE,i)}catch(n){if(n instanceof a.JobTooLargeError){s.logger.debug(`track-action-monitor: wire event too large for ${r.tool_name}; dropping`);return}s.logger.debug(`track-action-monitor: failed to submit ${r.tool_name}: ${n instanceof Error?n.message:n}`)}}c(x,"submitEvent");function y(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}c(y,"byteSize");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var T=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var g=(o,t)=>T(o,"name",{value:t,configurable:!0});var G=(o,t)=>{for(var l in t)T(o,l,{get:t[l],enumerable:!0})},H=(o,t,l,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Y(t))!q.call(o,r)&&r!==l&&T(o,r,{get:()=>t[r],enumerable:!(i=Q(t,r))||i.enumerable});return o};var Z=o=>H(T({},"__esModule",{value:!0}),o);var at={};G(at,{run:()=>st});module.exports=Z(at);var p=require("../../../hooks/core/actions"),y=require("../../../hooks/core/nested-tools"),a=require("../../../hooks/core/session-state"),F=require("../../../hooks/core/verification-context"),E=require("../../../lib/config"),s=require("../../../lib/logger"),R=require("../../../lib/recording-tools"),P=require("../../../lib/output"),W=require("../../../lib/stdin"),f=require("../../../queue"),b=require("../util"),M=require("../../../lib/runtime-paths");const N="bdt_",$="ndt_",D="bedt_",L="adt_",x="tdt_",tt="browser-devtools",ot="node-devtools",nt="backend-devtools",et="android-devtools",it="terminal-devtools";function rt(o){return o.startsWith(D)?nt:o.startsWith(L)?et:o.startsWith(x)?it:o.startsWith(N)?tt:o.startsWith($)?ot:null}g(rt,"resolveServerByPrefix");async function st(o){let t;try{t=JSON.parse((0,W.readStdin)())}catch(n){s.logger.debug(`failed to parse stdin: ${n}`),process.stdout.write(JSON.stringify({})),process.exit(0);return}const l=t.conversation_id??"default",i=(0,M.sessionDir)(o,l),r=`${i}/actions.jsonl`;(0,s.setLogFile)(`${i}/session.log`);const v=t.tool_name??"unknown",A=Date.now(),k=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,w=(0,a.getActiveActivityId)(i),O=(0,a.getActiveVerificationId)(i),I=(0,a.getActiveTraceId)(i),e=(0,b.classifyTool)(v,t.tool_input),B=e.tool_type==="mcp"&&e.tool_name.startsWith(N),J=e.tool_type==="mcp"&&e.tool_name.startsWith($),X=e.tool_type==="mcp"&&e.tool_name.startsWith(D),z=e.tool_type==="mcp"&&e.tool_name.startsWith(L),U=e.tool_type==="mcp"&&e.tool_name.startsWith(x),m=B||J||X||z||U,c=e.tool_type==="mcp"?rt(e.tool_name)??e.mcp_server:e.mcp_server,K=m?k:(0,b.extractCursorToolInput)(v,k),C=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let u;if(C){const n=[];t.failure_type&&n.push(t.failure_type),t.is_interrupt&&n.push("interrupted"),u=`${n.length>0?`${n.join(",")}: `:""}${C}`}const S={...(0,p.baseFields)(r),type:"tool_call",timestamp:A,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:t.tool_use_id,tool_input:K,tool_input_size:V(t.tool_input),tool_response:u?void 0:t.tool_output,tool_response_size:V(u?void 0:t.tool_output),activity_id:w,verification_id:O,trace_id:I,duration:typeof t.duration=="number"?t.duration:null,mcp_server:c,error:u};if(m){await(0,p.appendAction)(r,S);const n=(0,R.recordingToolsForServer)(c);n!==null&&(e.tool_name===n.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action: recording started (${n.cycle})`)):e.tool_name===n.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action: recording stopped (${n.cycle})`)))}else lt(o,l,S);if(s.logger.debug(`track-action: ${v}${u?" (failed)":""}`),m&&c!==null&&e.tool_name===(0,y.executeToolBareName)(c)&&!u){const n=(0,y.extractNestedToolCalls)(t.tool_input,c),_=(0,R.recordingToolsForServer)(c);for(const d of n){_!==null&&(d.name===_.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action (nested): recording started (${_.cycle})`)):d.name===_.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action (nested): recording stopped (${_.cycle})`)));const j={...(0,p.baseFields)(r),type:"tool_call",timestamp:d.startTime??A,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:w,verification_id:O,trace_id:I,duration:d.duration??null,mcp_server:c,nested:!0,parent_tool_use_id:t.tool_use_id};await(0,p.appendAction)(r,j),s.logger.debug(`track-action (nested): ${d.name}`)}}const h={};if(m)try{const n=(0,F.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:l,sessionDir:i,activeVerificationId:O,config:(0,E.loadConfig)(o)});n.length>0&&(h.additional_context=n)}catch(n){s.logger.debug(`track-action: verification-context injection skipped: ${n instanceof Error?n.message:n}`)}(0,P.writeAndExit)(JSON.stringify(h),0)}g(st,"run");function lt(o,t,l){if(!(0,E.isJobQueueEnabled)(o))return;const i={...l};delete i.tool_response;try{(0,f.submit)(o,t,f.SEND_EVENT_TYPE,i)}catch(r){if(r instanceof f.JobTooLargeError){s.logger.debug(`track-action: wire event too large for ${l.tool_name}; dropping`);return}s.logger.debug(`track-action: failed to submit ${l.tool_name}: ${r instanceof Error?r.message:r}`)}}g(lt,"submitEvent");function V(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}g(V,"byteSize");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
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});
|
|
1
|
+
"use strict";var E=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var v=(a,o)=>E(a,"name",{value:o,configurable:!0});var Y=(a,o)=>{for(var e in o)E(a,e,{get:o[e],enumerable:!0})},Z=(a,o,e,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of Q(o))!X.call(a,t)&&t!==e&&E(a,t,{get:()=>o[t],enumerable:!(s=z(o,t))||s.enumerable});return a};var oo=a=>Z(E({},"__esModule",{value:!0}),a);var to={};Y(to,{CursorClient:()=>no});module.exports=oo(to);var i=require("fs"),l=require("path"),p=require("../../lib/logger"),n=require("../../lib/output"),N=require("../../lib/fs-prune"),P=require("./hooks/verify-gate"),L=require("./hooks/clear-verdict"),B=require("./hooks/track-action"),V=require("./hooks/track-action-monitor"),J=require("./hooks/session-start"),D=require("./hooks/require-verdict"),F=require("./hooks/require-verification"),U=require("./hooks/activity-start"),K=require("./hooks/activity-end"),q=require("./hooks/session-end"),c=require("../../lib/config"),j=require("../../lib/platform-section"),G=require("../../lib/gitignore");const y="browser-devtools",h="node-devtools",S="backend-devtools",b="android-devtools",C="terminal-devtools",eo="ironbee",O=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function ro(a){return(0,l.join)(__dirname,"..",a,"platforms")}v(ro,"platformsDirFor");function I(a){const o=Object.keys(a);if(o.length===0)return!0;if(o.length===1&&o[0]==="mcpServers"){const e=a.mcpServers;return e===void 0||Object.keys(e).length===0}return!1}v(I,"isMcpConfigEmpty");const A="ironbee",T=[`${y}:*`,`${h}:*`,`${S}:*`,`${b}:*`,`${C}:*`];function so(a){const o=new Set(["mcpAllowlist","terminalAllowlist"]);for(const t of Object.keys(a))if(!o.has(t))return!1;const e=a.mcpAllowlist??[],s=a.terminalAllowlist??[];return e.length===0&&s.length===0}v(so,"isPermissionsEmpty");function io(a){const o=new Set(["version","hooks"]);for(const e of Object.keys(a))if(!o.has(e))return!1;return Object.keys(a.hooks??{}).length===0}v(io,"isCursorHooksEmpty");function d(a){return n.pc.dim(a)}v(d,"cursorColor");class no{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,c.loadConfig)(o),t=(0,c.getVerificationMode)(s),r=t!=="monitor";this.cleanupArtifacts(o),(0,G.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 w=(0,l.join)(f,"ironbee-verification.md"),$=(0,i.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,i.writeFileSync)(w,$);const H=(0,l.join)(g,"ironbee-verification.mdc"),x=(0,i.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.mdc"),"utf-8");(0,i.writeFileSync)(H,x),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} skill ${n.pc.dim("\u2192")} ${n.pc.dim(w)}`),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 M=(0,l.join)(k,"SKILL.md"),W=(0,i.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify","SKILL.md"),"utf-8");(0,i.writeFileSync)(M,W);for(const w of O){const $=(0,l.join)(f,w);(0,i.mkdirSync)($,{recursive:!0}),(0,i.writeFileSync)((0,l.join)($,"SKILL.md"),(0,i.readFileSync)((0,l.join)(__dirname,"commands",w,"SKILL.md"),"utf-8"))}const R=(0,l.join)(u,"mcp.json");this.writeMcpConfig(R,o);const _=(0,l.join)(u,"permissions.json");this.writePermissionsConfig(_,o),(0,j.syncPlatformSectionsToConfig)(o,ro),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(M)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} mcp ${n.pc.dim("\u2192")} ${n.pc.dim(R)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} perms ${n.pc.dim("\u2192")} ${n.pc.dim(_)}`),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,N.pruneEmptyDirs)(e)}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));io(e)&&(0,i.unlinkSync)(o)}catch(e){p.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}async runVerifyGate(o){await(0,P.run)(o)}async runClearVerdict(o){await(0,L.run)(o)}async runTrackAction(o){await(0,B.run)(o)}async runSessionStart(o){await(0,J.run)(o)}async runRequireVerdict(o,e){await(0,D.run)(o,e)}async runRequireVerification(o,e){await(0,F.run)(o,e)}async runActivityStart(o){await(0,U.run)(o)}async runActivityEnd(o){await(0,K.run)(o)}async runTrackActionMonitor(o){await(0,V.run)(o)}async runSessionEnd(o){await(0,q.run)(o)}async runTrackActionPre(o){}isIronBeeHook(o){return o.command.includes(eo)}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|tdt)_.*",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[h]&&(delete e.mcpServers[h],s=!0),e.mcpServers&&e.mcpServers[S]&&(delete e.mcpServers[S],s=!0),e.mcpServers&&e.mcpServers[b]&&(delete e.mcpServers[b],s=!0),e.mcpServers&&e.mcpServers[C]&&(delete e.mcpServers[C],s=!0),I(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,c.loadConfig)(e);if((0,c.isCycleEnabled)(t,"browser")?s.mcpServers[y]=(0,c.getMcpServerEntry)(e):delete s.mcpServers[y],(0,c.isCycleEnabled)(t,"node")?s.mcpServers[h]=(0,c.getNodeDevToolsMcpEntry)(e):delete s.mcpServers[h],(0,c.isCycleEnabled)(t,"backend")?s.mcpServers[S]=(0,c.getBackendDevToolsMcpEntry)(e):delete s.mcpServers[S],(0,c.isCycleEnabled)(t,"android")?s.mcpServers[b]=(0,c.getAndroidDevToolsMcpEntry)(e):delete s.mcpServers[b],(0,c.isCycleEnabled)(t,"terminal")?s.mcpServers[C]=(0,c.getTerminalDevToolsMcpEntry)(e):delete s.mcpServers[C],I(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,c.loadConfig)(e),r=[];(0,c.isCycleEnabled)(t,"browser")&&r.push(`${y}:*`),(0,c.isCycleEnabled)(t,"node")&&r.push(`${h}:*`),(0,c.isCycleEnabled)(t,"backend")&&r.push(`${S}:*`),(0,c.isCycleEnabled)(t,"android")&&r.push(`${b}:*`),(0,c.isCycleEnabled)(t,"terminal")&&r.push(`${C}:*`);const u=new Set(T),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!==A);f.push(A),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(T);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!==A),e.terminalAllowlist.length===0&&delete e.terminalAllowlist),so(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});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!-- Terminal verification is ENABLED for this project. -->
|
|
2
|
+
|
|
3
|
+
## Terminal Mode (when `terminal.verifyPatterns` matches an edited file)
|
|
4
|
+
|
|
5
|
+
> **Precondition: the change must actually have terminal-observable behavior.** If the edited code is not reachable from a CLI / REPL / shell / full-screen TUI, this section does NOT apply — `MCP:tdt_*` tools drive a program through a PTY. Just do browser verification.
|
|
6
|
+
|
|
7
|
+
If the project has terminal verification enabled (`ironbee terminal enable` once at setup) and your edits touch matching paths, the stop hook also enforces a terminal cycle. The same `verification-start` covers both cycles; one platform-agnostic verdict covers both.
|
|
8
|
+
|
|
9
|
+
### Mode behavior (terminal cycle)
|
|
10
|
+
- **default** (no arg or `default`): exercise only the CLI flows / commands your diff touched.
|
|
11
|
+
- **full**: exercise every CLI command / flow reachable from files matching `terminal.verifyPatterns`.
|
|
12
|
+
- `visual` / `functional`: browser-only modes; terminal cycle behaves as `default` when they are passed.
|
|
13
|
+
|
|
14
|
+
### Steps (run within step 3 of the Universal steps above)
|
|
15
|
+
1. **Pick an evidence path** for the changed code:
|
|
16
|
+
- **Run-evidence** (a one-shot command proves the change works): `MCP:tdt_pty_run` with the command line — it returns the full output AND the exit code in one call. Confirm the output is what the change should produce AND the exit code is expected (`0` for success, or the specific non-zero code an error/flag path returns). A command that prints the right text but exits non-zero is a fail.
|
|
17
|
+
- **Interactive-evidence** (driving a REPL / shell / full-screen TUI proves the change is live):
|
|
18
|
+
- Spawn the program: `MCP:tdt_pty_start` → returns a `paneId`.
|
|
19
|
+
- Drive input: `MCP:tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`) or `MCP:tdt_interaction_send-text` (literal text).
|
|
20
|
+
- **Synchronize, don't guess delays**: `MCP:tdt_sync_wait-for` blocks until the expected output appears.
|
|
21
|
+
- Capture: `MCP:tdt_content_capture` — `mode: stream` for line-oriented programs (REPLs, shells; pass an incremental `since` cursor to read only new lines), `mode: screen` for full-screen TUIs (rendered screen buffer). Confirm it shows the expected result.
|
|
22
|
+
- Stop the pane: `MCP:tdt_pty_stop`.
|
|
23
|
+
- Auxiliary (NOT gate evidence): `MCP:tdt_sync_wait-for-idle` waits until output stops changing; `MCP:tdt_content_get-cursor` reports cursor position; `MCP:tdt_pty_resize` / `MCP:tdt_pty_signal` / `MCP:tdt_pty_list` manage panes.
|
|
24
|
+
2. **Submit verdict** — platform-agnostic, just status + checks (+ issues/fixes).
|
|
25
|
+
|
|
26
|
+
### Verdict (platform-agnostic)
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"session_id": "...",
|
|
30
|
+
"status": "pass",
|
|
31
|
+
"checks": ["`mycli --json` emits valid JSON and exits 0", "REPL `:help` lists the new command"]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For a multi-cycle pass, both browser and terminal pass criteria must hold.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Default Mode (terminal cycle)
|
|
40
|
+
|
|
41
|
+
Focus on the CLI flows or commands your diff touched — not the entire program.
|
|
42
|
+
|
|
43
|
+
### 1. Study the changes
|
|
44
|
+
1. Run `git diff --name-only` and `git diff --name-only HEAD~1`
|
|
45
|
+
2. **Ignore `.ironbee/`, `.claude/`, `.cursor/`** — tool config, not application code
|
|
46
|
+
3. **Read the full diff** for every terminal-facing file in scope — note new commands, changed flags / args, new output formats, changed exit codes, new REPL / TUI states
|
|
47
|
+
4. Before running, identify: which command / subcommand / REPL state is affected? Which invocation exercises it? What output and exit code should it now produce?
|
|
48
|
+
|
|
49
|
+
### 2. Verify in a PTY
|
|
50
|
+
- **Run-evidence**: run the affected command via `MCP:tdt_pty_run` — confirm the output matches the change AND the exit code is correct
|
|
51
|
+
- **Interactive-evidence**: spawn the program, drive the affected flow with `send-keys` / `send-text`, `wait-for` the expected output, then capture it — the captured output must show the expected result after your change
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Full Mode (`/ironbee-verify full`, terminal cycle)
|
|
56
|
+
|
|
57
|
+
Verify every CLI command / flow reachable from files matching `terminal.verifyPatterns`, not just the changed files. Do NOT run `git diff` or scope to recent changes.
|
|
58
|
+
|
|
59
|
+
- Run every command / subcommand in scope
|
|
60
|
+
- Exercise at least one happy-path invocation AND one error-path invocation (bad flag / missing arg) per command, confirming both output and exit code
|
|
61
|
+
- For REPL / TUI surfaces, drive each affected state and capture the rendered output
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- Terminal verification is ENABLED for this project. The stop hook
|
|
2
|
+
enforces a terminal cycle whenever an edited file matches
|
|
3
|
+
`terminal.verifyPatterns`. -->
|
|
4
|
+
|
|
5
|
+
## Terminal cycle
|
|
6
|
+
|
|
7
|
+
Terminal file changes IF the file matches `terminal.verifyPatterns` ALSO require verification through the **terminal-devtools** MCP server (prefix `MCP:tdt_*`). Terminal-cycle verification means spawning the program attached to a PTY (like tmux) and either running a one-shot command to confirm its output and exit code — OR driving a REPL / shell / full-screen TUI with input and capturing the output to confirm the changed code path behaves correctly.
|
|
8
|
+
|
|
9
|
+
Both cycles can be active simultaneously (e.g. you edit both a React component and a CLI command in the same task). One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
|
|
10
|
+
|
|
11
|
+
### ⚠️ `terminal-devtools` is ONLY for terminal programs
|
|
12
|
+
|
|
13
|
+
`terminal-devtools` drives CLIs, REPLs, shells, and full-screen TUIs through a PTY. It does NOT apply to web-only UI, or to changes with no terminal-observable behavior. If the change has no command / REPL / TUI surface, do NOT call `MCP:tdt_*` tools — a web-only change belongs to the browser cycle.
|
|
14
|
+
|
|
15
|
+
**Misconfiguration recovery.** If this cycle activated but the change has no terminal-observable behavior, the operator enabled the terminal cycle by mistake. The stop hook will keep blocking with `incomplete_tools` for the terminal cycle. Don't fabricate a PTY session. Instead, stop and clearly report to the user: the change has no terminal surface; ask them to run `ironbee terminal disable` to unblock the gate.
|
|
16
|
+
|
|
17
|
+
### Terminal-cycle additions to the main flow
|
|
18
|
+
|
|
19
|
+
These attach to the **Required steps** above — they don't replace any step. Numbering follows the main flow:
|
|
20
|
+
|
|
21
|
+
- **Within step 3 (run flow):** also run the terminal flow — pick ONE evidence path:
|
|
22
|
+
- **Run-evidence**: run a one-shot command (`MCP:tdt_pty_run`) and confirm its output AND exit code are what the change should produce.
|
|
23
|
+
- **Interactive-evidence**: spawn a pane (`MCP:tdt_pty_start`) → drive input (`MCP:tdt_interaction_send-keys` / `MCP:tdt_interaction_send-text`) → synchronize with `MCP:tdt_sync_wait-for` → capture output (`MCP:tdt_content_capture`, `mode: stream` for REPLs/shells with an incremental `since` cursor, `mode: screen` for full-screen TUIs) → stop the pane (`MCP:tdt_pty_stop`). Auxiliary only (NOT evidence): `MCP:tdt_sync_wait-for-idle`, `MCP:tdt_content_get-cursor`, `MCP:tdt_pty_resize` / `MCP:tdt_pty_signal` / `MCP:tdt_pty_list`.
|
|
24
|
+
- **Within step 6 (submit verdict):** submit one platform-agnostic verdict with `status` + `checks` (+ `issues`/`fixes` as needed). Terminal-cycle pass criteria: (a one-shot command was run via `tdt_pty_run` and its output/exit code confirm the change) OR (a pane was spawned AND input was driven AND output was captured and shows the expected result).
|
|
25
|
+
|
|
26
|
+
### Additional BANNED for terminal cycle
|
|
27
|
+
|
|
28
|
+
- Calling `MCP:tdt_*` tools without first opening a verification cycle (`ironbee hook verification-start`).
|
|
29
|
+
- **Calling `MCP:tdt_*` tools when the change has no terminal-observable behavior.** Use the browser cycle for web-only changes.
|
|
30
|
+
- Claiming `status: pass` for a terminal cycle when no evidence path was exercised.
|
|
31
|
+
- Claiming `status: pass` on the run-evidence path while ignoring a non-zero exit code — the exit code is part of the evidence.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### terminal platform (enabled)
|
|
2
|
+
- **Use for**: CLI / REPL / shell / full-screen TUI scenarios driven through a PTY.
|
|
3
|
+
- **Server**: `terminal-devtools` · **scenario tools**: the `tdt_scenario-*` tools
|
|
4
|
+
(`tdt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
|
|
5
|
+
- **Store**: project → `.ironbee/scenarios/tdt`, global → `~/.ironbee/scenarios/tdt` (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 `tdt_*` 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 terminal cycle collects). In the script:
|
|
12
|
+
1. Pick an **evidence path** for the changed code area:
|
|
13
|
+
- **Run-evidence path** — run the one-shot command with `tdt_pty_run` (`returnOutput: true`); put the
|
|
14
|
+
returned output AND exit code in your result. Confirm the output is what the change should produce
|
|
15
|
+
AND the exit code is expected (`0` for success, or the specific non-zero code an error/flag path
|
|
16
|
+
returns) — a command that prints the right text but exits non-zero is a fail.
|
|
17
|
+
- **Interactive-evidence path** — spawn the program with `tdt_pty_start` (returns a `paneId`), drive it
|
|
18
|
+
with `tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`) and/or
|
|
19
|
+
`tdt_interaction_send-text` (literal text), **synchronize with `tdt_sync_wait-for`** (block until the
|
|
20
|
+
expected output appears — prefer this over fixed delays), then capture with `tdt_content_capture`
|
|
21
|
+
(`returnOutput: true`; `mode: stream` for line-oriented REPLs/shells with an incremental `since`
|
|
22
|
+
cursor, `mode: screen` for full-screen TUIs) and put the captured output in your result. Stop the
|
|
23
|
+
pane with `tdt_pty_stop` when done. Auxiliary helpers (NOT evidence): `tdt_sync_wait-for-idle`,
|
|
24
|
+
`tdt_content_get-cursor`, `tdt_pty_resize` / `tdt_pty_signal` / `tdt_pty_list`.
|
|
25
|
+
|
|
26
|
+
`return` the evidence — the command output + exit code, or the captured REPL / TUI output — **plus
|
|
27
|
+
explicit pass/fail assertions**. That returned result is what `/ironbee-verify scenario:<name>` reads to
|
|
28
|
+
judge whether the terminal flow behaved correctly.
|
|
29
|
+
**`terminal-devtools` is for terminal programs only (CLI / REPL / shell / TUI).**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
> **Recording (only when `recording.enable` is on in config):** the gate blocks every other browser tool until you first call `MCP:bdt_content_start-recording`, and `submit-verdict` rejects with `"recording is still active"` unless you call `MCP:bdt_content_stop-recording` after the steps below. **Treat start/stop as bookends around steps 1-5.** The same is enforced as step 6 of the Universal flow.
|
|
8
8
|
|
|
9
|
-
1. **Navigate**: `MCP:bdt_navigation_go-to` — go to the affected page(s)
|
|
9
|
+
1. **Navigate**: `MCP:bdt_navigation_go-to` — go to the affected page(s) **AND any downstream page that renders or consumes what the change produces** — verify the change's effect where it's observed, not only the page the edited file owns
|
|
10
10
|
2. **Interact**: actually exercise what changed — click buttons, fill forms, submit data, trigger workflows. Don't just look at the page.
|
|
11
11
|
3. **Screenshot**: `MCP:bdt_content_take-screenshot` — capture the final visual state
|
|
12
12
|
4. **Accessibility**: `MCP:bdt_a11y_take-aria-snapshot` — verify page structure
|