@ironbee-ai/cli 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/analytics/claude/emit.js +1 -1
  3. package/dist/analytics/claude/state.js +1 -1
  4. package/dist/analytics/codex/events-emit.js +2 -2
  5. package/dist/analytics/codex/subagent-transcripts.js +3 -3
  6. package/dist/clients/claude/agents/ironbee-scenario.md +4 -1
  7. package/dist/clients/claude/agents/ironbee-verifier.md +21 -3
  8. package/dist/clients/claude/hooks/activity-end.js +1 -1
  9. package/dist/clients/claude/hooks/activity-start.js +1 -1
  10. package/dist/clients/claude/hooks/clear-verdict.js +1 -1
  11. package/dist/clients/claude/hooks/require-verdict.js +2 -2
  12. package/dist/clients/claude/hooks/require-verification.js +4 -4
  13. package/dist/clients/claude/hooks/session-end.js +1 -1
  14. package/dist/clients/claude/hooks/session-start.js +4 -4
  15. package/dist/clients/claude/hooks/session-status.js +2 -2
  16. package/dist/clients/claude/hooks/subagent-start.js +1 -1
  17. package/dist/clients/claude/hooks/subagent-stop.js +1 -1
  18. package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
  19. package/dist/clients/claude/hooks/track-action.js +1 -1
  20. package/dist/clients/claude/hooks/verify-gate.js +4 -4
  21. package/dist/clients/claude/index.js +4 -4
  22. package/dist/clients/claude/platforms/scenario.android.md +1 -0
  23. package/dist/clients/claude/platforms/scenario.terminal.md +26 -0
  24. package/dist/clients/claude/platforms/skill.android.md +4 -0
  25. package/dist/clients/claude/platforms/skill.browser.md +1 -1
  26. package/dist/clients/claude/platforms/skill.terminal.md +62 -0
  27. package/dist/clients/claude/process-analytics.js +1 -1
  28. package/dist/clients/claude/statusline-toggle.js +2 -2
  29. package/dist/clients/codex/agents/ironbee-scenario.md +3 -0
  30. package/dist/clients/codex/agents/ironbee-verifier.md +20 -2
  31. package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +3 -0
  32. package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +3 -0
  33. package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +3 -0
  34. package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +3 -0
  35. package/dist/clients/codex/hooks/activity-end.js +1 -1
  36. package/dist/clients/codex/hooks/activity-start.js +1 -1
  37. package/dist/clients/codex/hooks/clear-verdict.js +3 -3
  38. package/dist/clients/codex/hooks/require-verdict.js +2 -2
  39. package/dist/clients/codex/hooks/require-verification.js +3 -3
  40. package/dist/clients/codex/hooks/session-start.js +3 -3
  41. package/dist/clients/codex/hooks/subagent-start.js +1 -1
  42. package/dist/clients/codex/hooks/subagent-stop.js +1 -1
  43. package/dist/clients/codex/hooks/track-action-monitor.js +1 -1
  44. package/dist/clients/codex/hooks/track-action-pre.js +1 -1
  45. package/dist/clients/codex/hooks/track-action.js +1 -1
  46. package/dist/clients/codex/hooks/verify-gate.js +1 -1
  47. package/dist/clients/codex/index.js +2 -2
  48. package/dist/clients/codex/platforms/command-verify.android.md +1 -0
  49. package/dist/clients/codex/platforms/command-verify.terminal.md +61 -0
  50. package/dist/clients/codex/platforms/rule.android.md +2 -1
  51. package/dist/clients/codex/platforms/rule.terminal.md +31 -0
  52. package/dist/clients/codex/platforms/scenario.android.md +1 -0
  53. package/dist/clients/codex/platforms/scenario.terminal.md +36 -0
  54. package/dist/clients/codex/platforms/skill.android.md +4 -0
  55. package/dist/clients/codex/platforms/skill.browser.md +1 -1
  56. package/dist/clients/codex/platforms/skill.terminal.md +57 -0
  57. package/dist/clients/codex/process-analytics.js +2 -2
  58. package/dist/clients/codex/rules/ironbee-verification.main.md +3 -0
  59. package/dist/clients/codex/skills/ironbee-verification.main.md +3 -0
  60. package/dist/clients/codex/thread-map.js +1 -1
  61. package/dist/clients/codex/util.js +44 -31
  62. package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +3 -0
  63. package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +3 -0
  64. package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +3 -0
  65. package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +3 -0
  66. package/dist/clients/cursor/hooks/activity-end.js +1 -1
  67. package/dist/clients/cursor/hooks/activity-start.js +1 -1
  68. package/dist/clients/cursor/hooks/clear-verdict.js +1 -1
  69. package/dist/clients/cursor/hooks/require-verdict.js +2 -2
  70. package/dist/clients/cursor/hooks/require-verification.js +3 -3
  71. package/dist/clients/cursor/hooks/session-end.js +1 -1
  72. package/dist/clients/cursor/hooks/session-start.js +4 -4
  73. package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
  74. package/dist/clients/cursor/hooks/track-action.js +1 -1
  75. package/dist/clients/cursor/hooks/verify-gate.js +1 -1
  76. package/dist/clients/cursor/index.js +1 -1
  77. package/dist/clients/cursor/platforms/command-verify.android.md +1 -0
  78. package/dist/clients/cursor/platforms/command-verify.terminal.md +61 -0
  79. package/dist/clients/cursor/platforms/rule.android.md +2 -1
  80. package/dist/clients/cursor/platforms/rule.terminal.md +31 -0
  81. package/dist/clients/cursor/platforms/scenario.android.md +1 -0
  82. package/dist/clients/cursor/platforms/scenario.terminal.md +29 -0
  83. package/dist/clients/cursor/platforms/skill.android.md +4 -0
  84. package/dist/clients/cursor/platforms/skill.browser.md +1 -1
  85. package/dist/clients/cursor/platforms/skill.terminal.md +54 -0
  86. package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
  87. package/dist/clients/cursor/skills/ironbee-verification.md +9 -0
  88. package/dist/commands/config.js +2 -2
  89. package/dist/commands/hook.js +10 -10
  90. package/dist/commands/import.js +3 -3
  91. package/dist/commands/install.js +1 -1
  92. package/dist/commands/process-job-file.js +1 -1
  93. package/dist/commands/queue.js +16 -16
  94. package/dist/commands/scenario.js +1 -1
  95. package/dist/commands/status.js +1 -1
  96. package/dist/commands/terminal.js +1 -0
  97. package/dist/commands/uninstall.js +1 -1
  98. package/dist/commands/verify.js +2 -2
  99. package/dist/hooks/core/actions.js +7 -7
  100. package/dist/hooks/core/session-state.js +1 -1
  101. package/dist/hooks/core/verification-context.js +19 -15
  102. package/dist/hooks/core/verify-gate.js +25 -20
  103. package/dist/import/claude/events/tool-call.js +1 -1
  104. package/dist/import/codex/events/tool-call.js +1 -1
  105. package/dist/import/marker.js +2 -2
  106. package/dist/import/skip.js +1 -1
  107. package/dist/index.js +1 -1
  108. package/dist/lib/config.js +1 -1
  109. package/dist/lib/install-version.js +1 -1
  110. package/dist/lib/platform-section.js +5 -4
  111. package/dist/lib/runtime-paths.js +1 -0
  112. package/dist/lib/scenario-staleness.js +1 -1
  113. package/dist/otel/claude/daemon/process.js +1 -1
  114. package/dist/otel/claude/daemon/reprocess.js +1 -1
  115. package/dist/otel/claude/daemon/response-usage.js +2 -2
  116. package/dist/queue/drain.js +1 -1
  117. package/dist/queue/flush.js +1 -1
  118. package/dist/queue/paths.js +1 -1
  119. package/dist/queue/process-file.js +2 -2
  120. package/dist/queue/spawn.js +1 -1
  121. package/dist/tui/config/schema.js +1 -1
  122. package/dist/tui/platforms/area.js +2 -2
  123. package/dist/tui/queue/read.js +4 -4
  124. package/dist/tui/sessions/read.js +2 -2
  125. package/package.json +1 -1
@@ -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,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.
@@ -24,6 +24,7 @@ verification, so its script must collect what the android cycle collects). In th
24
24
  - **Log-evidence path** — `adt_o11y_log-read` / `adt_o11y_log-follow` (with `returnOutput: true`)
25
25
  for the tag(s) relevant to the change; confirm expected lines appear AND no FATAL / crash (E/
26
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.
27
28
 
28
29
  `return` the evidence — UI-snapshot text, log lines, the screenshot `filePath`s — **plus explicit
29
30
  pass/fail assertions**. That returned result is what `/ironbee-verify scenario:<name>` reads to judge
@@ -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).**
@@ -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
 
@@ -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
@@ -0,0 +1,54 @@
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 you run in a terminal** — CLIs, REPLs, shells, and full-screen TUIs. It spawns the program attached to a PTY (like tmux) so you can drive it and observe its output. Do **NOT** call `MCP:tdt_*` tools for changes with no terminal-observable behavior.
8
+
9
+ **How to tell whether the change is terminal-observable:**
10
+ - A CLI / command entrypoint (`bin/`, a `main()` that parses argv, a `package.json` `bin` field, a Cobra / Click / argparse / Commander command)
11
+ - A REPL or interactive shell the change affects
12
+ - A full-screen TUI (curses / blessed / ink / bubbletea / ratatui)
13
+
14
+ If the change is web-only UI with no terminal surface, that belongs to the **browser cycle** — do NOT call any `MCP:tdt_*` tools for it.
15
+
16
+ **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 fabricate a PTY session. Stop and tell the user clearly: the change has no terminal surface; ask them to run `ironbee terminal disable` to unblock the gate.
17
+
18
+ ## Terminal flow
19
+
20
+ 1. **Pick an evidence path** per changed code area:
21
+ - **Run-evidence path** (a one-shot command confirms the change works):
22
+ - Run the command in a PTY: `MCP:tdt_pty_run` with the command line — it returns the full output AND the exit code in one call.
23
+ - Confirm the output is what the change should produce AND the exit code is expected (`0` for success, or the specific non-zero code a flag/error path should return). The exit code is as important as the text — a command that prints the right thing but exits non-zero is a fail.
24
+ - **Interactive-evidence path** (driving a REPL / shell / full-screen TUI confirms the change is live):
25
+ - Spawn the program attached to a PTY: `MCP:tdt_pty_start` — it returns a `paneId` you use for the rest of the flow.
26
+ - Drive it with input: `MCP:tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`, …) for control keys / navigation, or `MCP:tdt_interaction_send-text` to type a literal string.
27
+ - **Synchronize before reading** — call `MCP:tdt_sync_wait-for` to block until the expected output appears, rather than guessing a delay.
28
+ - Capture the output: `MCP:tdt_content_capture` — use `mode: stream` for line-oriented programs (REPLs, shells; pass an incremental `since` cursor to read only new lines) and `mode: screen` for full-screen TUIs (the rendered screen buffer).
29
+ - Confirm the captured output shows the expected result.
30
+ - Stop the pane when done: `MCP:tdt_pty_stop`.
31
+ - **Auxiliary (NOT evidence — sync/setup/correlation only):** `MCP:tdt_sync_wait-for-idle` (wait until output stops changing), `MCP:tdt_content_get-cursor` (current cursor position), `MCP:tdt_pty_resize` / `MCP:tdt_pty_signal` / `MCP:tdt_pty_list` (manage panes). None of these count toward the gate — they help you drive the test, they don't inspect it.
32
+
33
+ **Batch (speed):** on the run-evidence path each `MCP:tdt_pty_run` is one round-trip already. On the interactive path, prefer `MCP:tdt_sync_wait-for` over fixed delays so you read exactly when the output is ready; capture incrementally with `mode: stream` + a `since` cursor instead of re-reading the whole buffer.
34
+
35
+ ### Verdict fields
36
+ The verdict is platform-agnostic — submit only semantic judgment:
37
+
38
+ ```json
39
+ {
40
+ "session_id": "<sid>",
41
+ "status": "pass",
42
+ "checks": ["`mycli --json` emits valid JSON and exits 0", "REPL `:help` lists the new command"]
43
+ }
44
+ ```
45
+
46
+ On fail, include `issues`. On pass after a previous fail, include `fixes`.
47
+
48
+ Terminal-cycle pass criteria:
49
+ - **Run-evidence**: the command was run via `MCP:tdt_pty_run` AND its output and exit code confirm the change behaved correctly.
50
+ - **Interactive-evidence**: a pane was spawned (`MCP:tdt_pty_start`) AND input was driven (`MCP:tdt_interaction_send-keys` / `MCP:tdt_interaction_send-text`) AND output was captured (`MCP:tdt_content_capture`) AND it shows the expected result.
51
+
52
+ ## Multi-cycle (browser + terminal simultaneously)
53
+
54
+ 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.
@@ -54,3 +54,6 @@ Every file edit automatically clears your verdict, requiring re-verification.
54
54
 
55
55
  <!--IRONBEE:PLATFORM:android-->
56
56
  <!--/IRONBEE:PLATFORM:android-->
57
+
58
+ <!--IRONBEE:PLATFORM:terminal-->
59
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -35,6 +35,12 @@ If already running, skip start. If the build fails, fix it before proceeding.
35
35
 
36
36
  **Don't guess ports.** After starting, check the actual port via `docker compose ps`, process output, or config files.
37
37
 
38
+ ## Verify end-to-end — trace the blast radius (don't stop at the edited file)
39
+
40
+ A change's defect most often surfaces not on the edited file's own surface but in a **downstream consumer** of what the change produces — wherever its output is read back, stored, rendered, or acted on. Before driving tools, spend ONE quick pass reading/searching the code to map the blast radius: identify what the change produces and which other surfaces consume it, then exercise the FULL flow from where the change is produced through to where its effect is observable — not only the surface the edited file owns. A feature that works at its source but breaks in a downstream consumer is a **FAIL**.
41
+
42
+ This holds even when the consumer was not itself edited: the place you should have updated but didn't never appears in the changed-files list, so don't let that list bound your verification — **follow the data, not the diff.** Keep the mapping quick (a focused scan, not a full audit) so it doesn't eat the speed budget.
43
+
38
44
  ## Universal flow
39
45
 
40
46
  1. Implement your changes (write/edit code).
@@ -92,6 +98,9 @@ Each tool call is a separate LLM round-trip, and that round-trip — not the too
92
98
  <!--IRONBEE:PLATFORM:android-->
93
99
  <!--/IRONBEE:PLATFORM:android-->
94
100
 
101
+ <!--IRONBEE:PLATFORM:terminal-->
102
+ <!--/IRONBEE:PLATFORM:terminal-->
103
+
95
104
  ## Important
96
105
  - **Always submit a verdict after every verification attempt** — both pass AND fail. Fail verdicts are tracked for analytics.
97
106
  - The stop hook checks that the required tools were used for every active cycle and that the verdict carries non-empty `checks`.
@@ -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)+`
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});
1
+ "use strict";var m=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var l=(e,o)=>m(e,"name",{value:o,configurable:!0});var q=(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 K(o))!L.call(e,i)&&i!==n&&m(e,i,{get:()=>o[i],enumerable:!(r=V(o,i))||r.enumerable});return e};var Y=e=>M(m({},"__esModule",{value:!0}),e);var le={};q(le,{configCommand:()=>u,runGet:()=>B,runList:()=>U,runPath:()=>W,runSet:()=>F,runUnset:()=>_});module.exports=Y(le);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","terminal","browserDevTools","nodeDevTools","backendDevTools","androidDevTools","terminalDevTools","telemetry","privacy","statusLine","otel","codex","runtime"]);function S(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.json")}l(S,"projectConfigPath");function C(e){return(0,d.join)((0,d.resolve)(e),".ironbee","config.local.json")}l(C,"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
+ `)}l(j,"writeConfigFile");function H(e){const o=e.indexOf(".");return o===-1?e:e.slice(0,o)}l(H,"topKey");const X=["verification.context"];function v(e){for(const o of X)if(e===o||e.startsWith(o+"."))return!1;return z.has(H(e))}l(v,"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 Z(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(Z,"setAtPath");function ee(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(ee,"unsetAtPath");function oe(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(oe,"parseValue");function $(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:C(o),label:"local",useGlobal:!1}:{path:S(o),label:"project",useGlobal:!1}}l($,"resolveWriteTarget");function E(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:S(n)}:e.local===!0?{label:"local",path:C(n)}:{label:"merged",path:n}}l(E,"resolveReadTarget");function A(e){return e.label==="merged"?(0,b.loadConfig)(e.path):w(e.path)}l(A,"readForGet");function ne(e){return e===void 0?"(unset)":typeof e=="string"?e:JSON.stringify(e,null,2)}l(ne,"formatValue");function te(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(te,"rerenderArtifacts");function P(e,o,n,r){const i=(0,g.existsSync)(e)?(0,g.readFileSync)(e,"utf-8"):null;j(e,o);try{return te(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(P,"writeAndRerender");const R=10;function re(e){const o=(0,f.listActiveProjects)(),n=(0,J.canonicalizePath)(e);return o.filter(r=>r.path!==n)}l(re,"listOtherProjects");function ie(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(ie,"printOtherProjectsBanner");async function T(e,o,n){if(!o)return;const r=re(e);if(r.length===0)return;ie(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=$(n),i=oe(o,n.json===!0),a=w(r.path),s=JSON.parse(JSON.stringify(a));Z(s,e,i);const c=n.projectDir??process.cwd(),p=n.rerender!==!1&&v(e);let y=[];p?y=P(r.path,s,c,n.client):j(r.path,s),console.log(`${t.pc.green("\u2713")} Set ${t.pc.cyan(e)} = ${t.pc.bold(ne(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&&v(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 _(e,o){if(e.length===0)throw new Error("Key is required (dotted path, e.g. `collector.url`).");const n=$(o),r=w(n.path),i=JSON.parse(JSON.stringify(r));if(!ee(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&&v(e);let p=[];c?p=P(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(_,"runUnset");function B(e,o){const n=E(o),r=A(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(B,"runGet");function U(e){const o=E(e),n=A(o);console.log(JSON.stringify(n,null,2))}l(U,"runList");function W(e){const o=$(e);console.log(o.path)}l(W,"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{B(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 _(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{U(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{W(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});
@@ -1,24 +1,24 @@
1
- "use strict";var p=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var b=(i,e)=>p(i,"name",{value:e,configurable:!0});var P=(i,e)=>{for(var t in e)p(i,t,{get:e[t],enumerable:!0})},x=(i,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of N(e))!C.call(i,n)&&n!==t&&p(i,n,{get:()=>e[n],enumerable:!(o=j(e,n))||o.enumerable});return i};var D=i=>x(p({},"__esModule",{value:!0}),i);var _={};P(_,{hookCommand:()=>r});module.exports=D(_);var I=require("commander"),E=require("../clients/registry"),S=require("../hooks/core/submit-verdict"),h=require("../hooks/core/verification-lifecycle"),u=require("../lib/config"),l=require("../lib/logger"),d=require("../lib/output"),m=require("../lib/stdin"),k=require("../hooks/core/session-state"),v=require("../clients/session-id"),f=require("../clients/agent-project-dir");function g(i,e){return(0,u.getVerificationEnabled)((0,u.loadConfig)(i))?!1:(l.logger.debug(`${e}: verification is disabled \u2014 silent no-op`),!0)}b(g,"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,E.findClient)(e);return t||(process.stderr.write(`Error: unknown client "${e}". Run \`ironbee install\` to set up.
3
- `),process.exit(1)),t}b(s,"resolveClient");const r=new I.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,E.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(g(e,"verification-start")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
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=`${e}/.ironbee/sessions/${n}`;(0,l.setLogFile)(`${c}/session.log`);const a=(0,u.isRecordingEnabled)(e),w=await(0,h.startVerification)({sessionId:n,sessionDir:c,actionsFile:`${c}/actions.jsonl`,recordingEnabled:a,intent:t}),y={verification_id:w.verificationId,trace_id:w.traceId};a&&(y.recording_required=!0,y.message="Recording is required. Call bdt_content_start-recording BEFORE using any other browser tools."),(0,d.writeAndExit)(JSON.stringify(y)+`
8
- `,0)}),r.command("verification-end").description("End a verification cycle (called by agent via Bash)").action(async()=>{const i=(0,f.resolveAgentProjectDir)();if(g(i,"verification-end")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
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=`${i}/.ironbee/sessions/${t}`;(0,l.setLogFile)(`${o}/session.log`);const n=await(0,h.endVerification)({sessionId:t,sessionDir:o,actionsFile:`${o}/actions.jsonl`});n.success?(0,d.writeAndExit)(JSON.stringify({verification_id:n.verificationId,trace_id:n.traceId})+`
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(g(e,"submit-verdict")){(0,d.writeAndExit)(`verification is disabled in this project; verdict ignored
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=`${e}/.ironbee/sessions/${n}`;(0,l.setLogFile)(`${c}/session.log`);const a=await(0,S.runSubmitVerdict)({sessionId:n,sessionDir:c,verdictFile:`${c}/verdict.json`,actionsFile:`${c}/actions.jsonl`,verdictJson:t,projectDir:e});a.success?(0,d.writeAndExit)(a.message+`
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(g(i,"record-fix")){(0,d.writeAndExit)(JSON.stringify({message:"verification is disabled in this project; ignoring"})+`
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=`${i}/.ironbee/sessions/${t}`;(0,l.setLogFile)(`${c}/session.log`),(0,k.addPendingFixes)(c,n),l.logger.debug(`record-fix: session=${t} recorded ${n.length} fix note(s)`),(0,d.writeAndExit)(JSON.stringify({recorded:n.length})+`
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});
@@ -1,7 +1,7 @@
1
- "use strict";var x=Object.create;var g=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var l=(e,o)=>g(e,"name",{value:o,configurable:!0});var P=(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 j(o))!N.call(e,r)&&r!==n&&g(e,r,{get:()=>o[r],enumerable:!(s=_(o,r))||s.enumerable});return e};var O=(e,o,n)=>(n=e!=null?x(k(e)):{},I(o||!e||!e.__esModule?g(n,"default",{value:e,enumerable:!0}):n,e)),F=e=>I(g({},"__esModule",{value:!0}),e);var K={};P(K,{importCommand:()=>U,parseFlags:()=>$,promptYesNo:()=>R});module.exports=F(K);var y=require("commander"),w=require("fs"),h=require("path"),M=O(require("readline")),t=require("../lib/output"),S=require("../lib/config"),C=require("../lib/collector"),E=require("../lib/logger"),i=require("../import");const B=4,D=1,z=32,U=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=$(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(` {
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});q(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 R("Proceed?")||(t.log.info("Aborted."),process.exit(0));const m=r.filter(c=>!Y(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,T)=>{a.onSessionComplete(c,T)},"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 $(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=A(e.concurrency),m=L(e.batchSize);return{scope:n,timeRange:s,dryRun:e.dryRun===!0,yes:e.yes===!0,force:e.force===!0,concurrency:d,batchSize:m}}l($,"parseFlags");function L(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(L,"resolveBatchSize");function A(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(A,"resolveConcurrency");function v(e){return Math.min(z,Math.max(D,Math.floor(e)))}l(v,"clamp");function Y(e,o){if(o)return!1;const n=`${e.projectDir}/.ironbee/sessions/${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(Y,"shouldSkipForRunner");function q(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(G(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(q,"renderSummary");function u(e){return e.toLocaleString("en-US")}l(u,"formatNum");function G(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}l(G,"formatBytes");async function R(e,o){const n=o?.createInterface??M.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(R,"promptYesNo");0&&(module.exports={importCommand,parseFlags,promptYesNo});
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 S=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var g=(n,e)=>S(n,"name",{value:e,configurable:!0});var V=(n,e)=>{for(var r in e)S(n,r,{get:e[r],enumerable:!0})},_=(n,e,r,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let d of A(e))!R.call(n,d)&&d!==r&&S(n,d,{get:()=>e[d],enumerable:!(c=N(e,d))||c.enumerable});return n};var B=n=>_(S({},"__esModule",{value:!0}),n);var G={};V(G,{CYCLE_HINTS:()=>L,installCommand:()=>q,runInstallAll:()=>j,syncSchemaIfChanged:()=>F});module.exports=B(G);var P=require("commander"),T=require("fs"),D=require("path"),f=require("../clients/registry"),M=require("../clients/claude"),p=require("../lib/config"),t=require("../lib/output"),k=require("../lib/projects-registry"),m=require("../lib/prompt"),v=require("../lib/telemetry"),C=require("../lib/install-version"),h=require("../lib/schema-sync"),I=require("./cycle-toggle"),o=require("./mode-select");const L={browser:"web UI \xB7 DOM \xB7 console \xB7 a11y \xB7 screenshots \xB7 recording",node:"Node.js runtime \xB7 tracepoints \xB7 logpoints \xB7 exceptions \xB7 variables \xB7 logs",backend:"HTTP \xB7 gRPC \xB7 GraphQL \xB7 WebSocket \xB7 DB \xB7 logs"};async function O(n,e,r){if(e!==void 0)return r&&e!=="enforce"&&t.log.warn(`--strict ignored: it only applies to --mode enforce (strict is inert in ${e}).`),{mode:e,strict:e==="enforce"&&r?!0:void 0};if(r&&t.log.warn("--strict ignored: pass it together with --mode enforce."),!(0,m.isInteractive)())return;const c=Math.max(0,o.ALL_MODES.indexOf((0,o.resolveInstallDefaultMode)(n))),d=o.ALL_MODES.map(u=>({label:o.MODE_LABELS[u],hint:o.MODE_HINTS[u]})),i=(0,o.resolveInstallDefaultStrict)(n)?1:0,a=o.STRICT_OPTIONS.map(u=>({label:o.STRICT_LABELS[u?"true":"false"],hint:o.STRICT_HINTS[u?"true":"false"]}));for(;;){const u=await(0,m.promptSelect)("Which verification mode?",d,c),l=o.ALL_MODES[u];if(l!=="enforce")return{mode:l};const s=await(0,m.promptSelect)(`How strict should verification be? (${t.pc.cyan("Esc")} = back)`,a,i,{allowBack:!0});if(s!==m.PROMPT_BACK)return{mode:l,strict:o.STRICT_OPTIONS[s]}}}g(O,"resolveModeSelection");function $(n,e){if(e===void 0)return;const{mode:r,strict:c}=e,i=(0,o.applyModeToLayer)(n,"project",r,c)?"":t.pc.dim(" (unchanged)"),a=r==="enforce"&&c!==void 0?t.pc.dim(` \xB7 ${o.STRICT_LABELS[c?"true":"false"]}`):"";t.log.info(`Mode: ${t.pc.bold(o.MODE_LABELS[r])}${a}${i}`)}g($,"applyModeSelection");async function H(n,e,r){if(e!==void 0)return e;const c=(0,p.loadConfig)(n);if((r??(0,p.getVerificationMode)(c))==="monitor"||!(0,m.isInteractive)())return;const i=p.ALL_CYCLES.map(l=>({label:l,hint:L[l]})),a=p.ALL_CYCLES.map((l,s)=>(0,p.isCyclePatternsActive)(c,l)?s:-1).filter(l=>l>=0);return(await(0,m.promptMultiSelect)("Which platforms should require verification?",i,a)).map(l=>p.ALL_CYCLES[l])}g(H,"resolvePlatformSelection");function E(n,e){if(e===void 0)return;const r=(0,I.reconcileCyclesInLayer)(n,"project",e,!0),c=e.length>0?t.pc.bold(e.join(", ")):t.pc.dim("none (monitoring tools only)"),d=r.length>0?t.pc.dim(` \u2014 updated: ${r.join(", ")}`):"";t.log.info(`Platforms: ${c}${d}`)}g(E,"applyPlatformSelection");function W(n,e){return e!==void 0?(0,f.resolveTargetClients)(n.path,e):(0,f.detectClients)(n.path)}g(W,"clientsForBatchEntry");async function j(n){const e=(0,f.listActiveProjects)();if(e.length===0)return t.log.info("No registered projects to install. Run `ironbee install` somewhere first."),{failures:0,total:0};t.log.info(`Installing across ${t.pc.bold(String(e.length))} registered project(s)\u2026`),t.log.blank();let r=0;const c=[];for(const i of e)try{const a=W(i,n.client);if(a.length===0){t.log.warn(`Skipping ${i.path} \u2014 no resolvable clients`);continue}const u=a.map(l=>l.name);t.log.label("project",t.pc.dim(i.path)),t.log.step(`installing ${t.pc.bold(u.join(", "))}`),(0,M.prepareIronBeeDir)(i.path),$(i.path,n.mode!==void 0?{mode:n.mode,strict:n.mode==="enforce"&&n.strict?!0:void 0}:void 0),E(i.path,n.platforms);for(const l of a)l.install(i.path);(0,k.upsertProject)(i.path),c.push(...u)}catch(a){r++,t.log.error(` ${i.path}: ${a instanceof Error?a.message:String(a)}`)}t.log.blank(),r===0?t.log.success(`Installed across ${e.length} project(s). ${t.pc.dim("Restart your AI coding client(s) to apply.")}`):t.log.warn(`Completed with ${r} failure(s) \u2014 see above.`);const d=Array.from(new Set(c));return d.length>0&&await(0,v.trackInstall)(d),{failures:r,total:e.length}}g(j,"runInstallAll");async function F(){try{if((0,h.isAutoRerenderDisabled)()||(0,h.readSyncedSchemaVersion)()>=C.INSTALL_SCHEMA_VERSION)return!1;const e=(0,f.listActiveProjects)();if(e.length===0)return(0,h.writeSyncedSchemaVersion)(C.INSTALL_SCHEMA_VERSION),!1;if(!(0,m.isInteractive)())return!1;t.log.blank(),t.log.warn(`IronBee's installed setup structure changed in this version \u2014 re-rendering ${t.pc.bold(String(e.length))} registered project(s):`);for(const r of e.slice(0,10))t.log.label("project",t.pc.dim(r.path));return e.length>10&&t.log.info(t.pc.dim(` \u2026and ${e.length-10} more`)),await(0,m.promptAcknowledge)(t.pc.dim(" Press Enter to update them now\u2026 ")),await j({}),(0,h.writeSyncedSchemaVersion)(C.INSTALL_SCHEMA_VERSION),!0}catch(n){return t.log.blank(),t.log.warn(`IronBee schema sync skipped: ${n instanceof Error?n.message:String(n)}`),!1}}g(F,"syncSchemaIfChanged");const q=new P.Command("install").description("Install IronBee hooks and guidance files into a project. Use --all to install across every registered project (e.g. after a global config change).").argument("[project-dir]","target project directory",".").option("--client <name>",`client to install for (${(0,f.clientNames)()}), or "all"`).option("--all","Install across every project in the user-home inventory (~/.ironbee/projects.json) instead of just one. The [project-dir] argument is ignored when --all is set.").option("--platforms <list>",`comma-separated platforms to enable (${p.ALL_CYCLES.join(", ")}); skips the interactive picker. Empty ("") disables all. Default: interactive picker (pre-checked from current config), browser-only on a fresh non-interactive install.`).option("--mode <mode>",`verification mode (${o.ALL_MODES.join(", ")}); skips the interactive picker. enforce = full enforcement, assist = tools installed but not enforced (default), monitor = monitoring-only. Default: interactive picker (pre-selecting the current mode), untouched on a non-interactive install.`).option("--strict","with --mode enforce: always make the agent actually try every change in the app \u2014 it can't skip any. Enforce-only (ignored for assist/monitor or with no --mode). Default: the agent may skip changes with nothing to try (refactors, type-only edits, docs). Interactively, picking enforce opens this as a sub-choice.").action(async(n,e)=>{let r;if(e.platforms!==void 0)try{r=(0,I.parsePlatformsFlag)(e.platforms)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}let c;if(e.mode!==void 0)try{c=(0,o.parseModeFlag)(e.mode)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}const d=e.strict===!0;if(e.all===!0){(await j({client:e.client,platforms:r,mode:c,strict:d})).failures>0&&process.exit(1);return}const i=(0,D.resolve)(n);(0,T.existsSync)(i)||(t.log.error(`Directory not found: ${i}`),process.exit(1));let a;if(e.client!==void 0)try{a=(0,f.resolveTargetClients)(i,e.client)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}else{const s=(0,f.detectClients)(i);if(s.length>0){a=s;const b=s.map(w=>w.name).join(", ");t.log.info(`Detected client(s): ${t.pc.bold(b)}`)}else if((0,m.isInteractive)()){const b=[...f.REGISTERED_CLIENTS.map(y=>y.name),"all"],w=b.map(y=>({label:y,hint:y==="all"?"every registered client":void 0}));t.log.warn(`No client detected in ${t.pc.dim(i)}.`);const x=await(0,m.promptSelect)("Which client(s) to install for?",w,0);a=(0,f.resolveTargetClients)(i,b[x])}else t.log.warn(`No client detected. Defaulting to: ${f.REGISTERED_CLIENTS[0].name}`),a=[f.REGISTERED_CLIENTS[0]]}const u=await O(i,c,d),l=await H(i,r,u?.mode);t.log.blank(),(0,M.prepareIronBeeDir)(i),$(i,u),E(i,l);for(const s of a)s.install(i);(0,k.upsertProject)(i),t.log.blank(),t.log.label("project",t.pc.dim(i)),t.log.blank(),t.log.success(`IronBee installed. ${t.pc.dim("Restart your AI coding client to activate the hooks.")}`),await(0,v.trackInstall)(a.map(s=>s.name),i)});0&&(module.exports={CYCLE_HINTS,installCommand,runInstallAll,syncSchemaIfChanged});
1
+ "use strict";var S=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var g=(n,e)=>S(n,"name",{value:e,configurable:!0});var V=(n,e)=>{for(var r in e)S(n,r,{get:e[r],enumerable:!0})},_=(n,e,r,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let d of N(e))!R.call(n,d)&&d!==r&&S(n,d,{get:()=>e[d],enumerable:!(c=A(e,d))||c.enumerable});return n};var B=n=>_(S({},"__esModule",{value:!0}),n);var Y={};V(Y,{CYCLE_HINTS:()=>D,installCommand:()=>F,runInstallAll:()=>j,syncSchemaIfChanged:()=>W});module.exports=B(Y);var P=require("commander"),T=require("fs"),L=require("path"),f=require("../clients/registry"),M=require("../clients/claude"),p=require("../lib/config"),t=require("../lib/output"),k=require("../lib/projects-registry"),m=require("../lib/prompt"),v=require("../lib/telemetry"),I=require("../lib/install-version"),h=require("../lib/schema-sync"),C=require("./cycle-toggle"),o=require("./mode-select");const D={browser:"web UI \xB7 DOM \xB7 console \xB7 a11y \xB7 screenshots \xB7 recording",node:"Node.js runtime \xB7 tracepoints \xB7 logpoints \xB7 exceptions \xB7 variables \xB7 logs",backend:"HTTP \xB7 gRPC \xB7 GraphQL \xB7 WebSocket \xB7 DB \xB7 logs",android:"Android device / emulator \xB7 UI interactions \xB7 screenshots \xB7 Logcat \xB7 HTTP capture",terminal:"CLI \xB7 REPL \xB7 TUI \xB7 PTY drive \xB7 output capture"};async function O(n,e,r){if(e!==void 0)return r&&e!=="enforce"&&t.log.warn(`--strict ignored: it only applies to --mode enforce (strict is inert in ${e}).`),{mode:e,strict:e==="enforce"&&r?!0:void 0};if(r&&t.log.warn("--strict ignored: pass it together with --mode enforce."),!(0,m.isInteractive)())return;const c=Math.max(0,o.ALL_MODES.indexOf((0,o.resolveInstallDefaultMode)(n))),d=o.ALL_MODES.map(u=>({label:o.MODE_LABELS[u],hint:o.MODE_HINTS[u]})),i=(0,o.resolveInstallDefaultStrict)(n)?1:0,a=o.STRICT_OPTIONS.map(u=>({label:o.STRICT_LABELS[u?"true":"false"],hint:o.STRICT_HINTS[u?"true":"false"]}));for(;;){const u=await(0,m.promptSelect)("Which verification mode?",d,c),l=o.ALL_MODES[u];if(l!=="enforce")return{mode:l};const s=await(0,m.promptSelect)(`How strict should verification be? (${t.pc.cyan("Esc")} = back)`,a,i,{allowBack:!0});if(s!==m.PROMPT_BACK)return{mode:l,strict:o.STRICT_OPTIONS[s]}}}g(O,"resolveModeSelection");function E(n,e){if(e===void 0)return;const{mode:r,strict:c}=e,i=(0,o.applyModeToLayer)(n,"project",r,c)?"":t.pc.dim(" (unchanged)"),a=r==="enforce"&&c!==void 0?t.pc.dim(` \xB7 ${o.STRICT_LABELS[c?"true":"false"]}`):"";t.log.info(`Mode: ${t.pc.bold(o.MODE_LABELS[r])}${a}${i}`)}g(E,"applyModeSelection");async function H(n,e,r){if(e!==void 0)return e;const c=(0,p.loadConfig)(n);if((r??(0,p.getVerificationMode)(c))==="monitor"||!(0,m.isInteractive)())return;const i=p.ALL_CYCLES.map(l=>({label:l,hint:D[l]})),a=p.ALL_CYCLES.map((l,s)=>(0,p.isCyclePatternsActive)(c,l)?s:-1).filter(l=>l>=0);return(await(0,m.promptMultiSelect)("Which platforms should require verification?",i,a)).map(l=>p.ALL_CYCLES[l])}g(H,"resolvePlatformSelection");function $(n,e){if(e===void 0)return;const r=(0,C.reconcileCyclesInLayer)(n,"project",e,!0),c=e.length>0?t.pc.bold(e.join(", ")):t.pc.dim("none (monitoring tools only)"),d=r.length>0?t.pc.dim(` \u2014 updated: ${r.join(", ")}`):"";t.log.info(`Platforms: ${c}${d}`)}g($,"applyPlatformSelection");function U(n,e){return e!==void 0?(0,f.resolveTargetClients)(n.path,e):(0,f.detectClients)(n.path)}g(U,"clientsForBatchEntry");async function j(n){const e=(0,f.listActiveProjects)();if(e.length===0)return t.log.info("No registered projects to install. Run `ironbee install` somewhere first."),{failures:0,total:0};t.log.info(`Installing across ${t.pc.bold(String(e.length))} registered project(s)\u2026`),t.log.blank();let r=0;const c=[];for(const i of e)try{const a=U(i,n.client);if(a.length===0){t.log.warn(`Skipping ${i.path} \u2014 no resolvable clients`);continue}const u=a.map(l=>l.name);t.log.label("project",t.pc.dim(i.path)),t.log.step(`installing ${t.pc.bold(u.join(", "))}`),(0,M.prepareIronBeeDir)(i.path),E(i.path,n.mode!==void 0?{mode:n.mode,strict:n.mode==="enforce"&&n.strict?!0:void 0}:void 0),$(i.path,n.platforms);for(const l of a)l.install(i.path);(0,k.upsertProject)(i.path),c.push(...u)}catch(a){r++,t.log.error(` ${i.path}: ${a instanceof Error?a.message:String(a)}`)}t.log.blank(),r===0?t.log.success(`Installed across ${e.length} project(s). ${t.pc.dim("Restart your AI coding client(s) to apply.")}`):t.log.warn(`Completed with ${r} failure(s) \u2014 see above.`);const d=Array.from(new Set(c));return d.length>0&&await(0,v.trackInstall)(d),{failures:r,total:e.length}}g(j,"runInstallAll");async function W(){try{if((0,h.isAutoRerenderDisabled)()||(0,h.readSyncedSchemaVersion)()>=I.INSTALL_SCHEMA_VERSION)return!1;const e=(0,f.listActiveProjects)();if(e.length===0)return(0,h.writeSyncedSchemaVersion)(I.INSTALL_SCHEMA_VERSION),!1;if(!(0,m.isInteractive)())return!1;t.log.blank(),t.log.warn(`IronBee's installed setup structure changed in this version \u2014 re-rendering ${t.pc.bold(String(e.length))} registered project(s):`);for(const r of e.slice(0,10))t.log.label("project",t.pc.dim(r.path));return e.length>10&&t.log.info(t.pc.dim(` \u2026and ${e.length-10} more`)),await(0,m.promptAcknowledge)(t.pc.dim(" Press Enter to update them now\u2026 ")),await j({}),(0,h.writeSyncedSchemaVersion)(I.INSTALL_SCHEMA_VERSION),!0}catch(n){return t.log.blank(),t.log.warn(`IronBee schema sync skipped: ${n instanceof Error?n.message:String(n)}`),!1}}g(W,"syncSchemaIfChanged");const F=new P.Command("install").description("Install IronBee hooks and guidance files into a project. Use --all to install across every registered project (e.g. after a global config change).").argument("[project-dir]","target project directory",".").option("--client <name>",`client to install for (${(0,f.clientNames)()}), or "all"`).option("--all","Install across every project in the user-home inventory (~/.ironbee/projects.json) instead of just one. The [project-dir] argument is ignored when --all is set.").option("--platforms <list>",`comma-separated platforms to enable (${p.ALL_CYCLES.join(", ")}); skips the interactive picker. Empty ("") disables all. Default: interactive picker (pre-checked from current config), browser-only on a fresh non-interactive install.`).option("--mode <mode>",`verification mode (${o.ALL_MODES.join(", ")}); skips the interactive picker. enforce = full enforcement, assist = tools installed but not enforced (default), monitor = monitoring-only. Default: interactive picker (pre-selecting the current mode), untouched on a non-interactive install.`).option("--strict","with --mode enforce: always make the agent actually try every change in the app \u2014 it can't skip any. Enforce-only (ignored for assist/monitor or with no --mode). Default: the agent may skip changes with nothing to try (refactors, type-only edits, docs). Interactively, picking enforce opens this as a sub-choice.").action(async(n,e)=>{let r;if(e.platforms!==void 0)try{r=(0,C.parsePlatformsFlag)(e.platforms)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}let c;if(e.mode!==void 0)try{c=(0,o.parseModeFlag)(e.mode)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}const d=e.strict===!0;if(e.all===!0){(await j({client:e.client,platforms:r,mode:c,strict:d})).failures>0&&process.exit(1);return}const i=(0,L.resolve)(n);(0,T.existsSync)(i)||(t.log.error(`Directory not found: ${i}`),process.exit(1));let a;if(e.client!==void 0)try{a=(0,f.resolveTargetClients)(i,e.client)}catch(s){t.log.error(s instanceof Error?s.message:String(s)),process.exit(1)}else{const s=(0,f.detectClients)(i);if(s.length>0){a=s;const b=s.map(w=>w.name).join(", ");t.log.info(`Detected client(s): ${t.pc.bold(b)}`)}else if((0,m.isInteractive)()){const b=[...f.REGISTERED_CLIENTS.map(y=>y.name),"all"],w=b.map(y=>({label:y,hint:y==="all"?"every registered client":void 0}));t.log.warn(`No client detected in ${t.pc.dim(i)}.`);const x=await(0,m.promptSelect)("Which client(s) to install for?",w,0);a=(0,f.resolveTargetClients)(i,b[x])}else t.log.warn(`No client detected. Defaulting to: ${f.REGISTERED_CLIENTS[0].name}`),a=[f.REGISTERED_CLIENTS[0]]}const u=await O(i,c,d),l=await H(i,r,u?.mode);t.log.blank(),(0,M.prepareIronBeeDir)(i),E(i,u),$(i,l);for(const s of a)s.install(i);(0,k.upsertProject)(i),t.log.blank(),t.log.label("project",t.pc.dim(i)),t.log.blank(),t.log.success(`IronBee installed. ${t.pc.dim("Restart your AI coding client to activate the hooks.")}`),await(0,v.trackInstall)(a.map(s=>s.name),i)});0&&(module.exports={CYCLE_HINTS,installCommand,runInstallAll,syncSchemaIfChanged});
@@ -1 +1 @@
1
- "use strict";var r=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var l=(o,s)=>{for(var n in s)r(o,n,{get:s[n],enumerable:!0})},d=(o,s,n,t)=>{if(s&&typeof s=="object"||typeof s=="function")for(let e of p(s))!c.call(o,e)&&e!==n&&r(o,e,{get:()=>s[e],enumerable:!(t=m(s,e))||t.enumerable});return o};var f=o=>d(r({},"__esModule",{value:!0}),o);var h={};l(h,{processJobFileCommand:()=>b});module.exports=f(h);var a=require("commander"),i=require("../queue");const b=new a.Command("process-job-file").description("Internal worker \u2014 process one snapshot file").argument("<path>","absolute path to a snapshot file (jobs-<UUID>.jsonl)").action(async o=>{await(0,i.processFile)(o)});0&&(module.exports={processJobFileCommand});
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});
@@ -1,37 +1,37 @@
1
- "use strict";var b=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var f=(e,t)=>b(e,"name",{value:t,configurable:!0});var B=(e,t)=>{for(var r in t)b(e,r,{get:t[r],enumerable:!0})},C=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of _(t))!q.call(e,c)&&c!==r&&b(e,c,{get:()=>t[c],enumerable:!(i=x(t,c))||i.enumerable});return e};var M=e=>C(b({},"__esModule",{value:!0}),e);var N={};B(N,{queueCommand:()=>v});module.exports=M(N);var E=require("commander"),a=require("fs"),h=require("path"),R=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,h.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,h.join)(r,i))}catch{return[]}}f(k,"listSnapshotsFor");function A(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(A,"collectStatus");function $(e){try{return(0,a.statSync)(e).size}catch{return 0}}f($,"fileSize");function T(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(T,"parseDuration");const v=new E.Command("queue").description("Inspect and manage the file-backed job queue");v.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=A(t,s);if(i+=d.liveLines,c+=d.snapshotCount,n+=d.deadLetterLines,process.stdout.write(`session=${s}
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
- `)}),v.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.
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))}),v.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>.
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()-T(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,h.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,h.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
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 w=new E.Command("dead-letter").description("Inspect and act on dead-letter entries");w.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}
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
- `)}),w.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.
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
- `)}),w.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,R.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 y of(0,o.readDeadLetterEntries)(r,u))if("original"in y&&y.original.id===e){c=u,n=y;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,h.join)(r,".ironbee","sessions",s);if(!(0,a.existsSync)(p)){process.stderr.write(`Error: target session "${s}" does not exist.
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 I=l.split(`
30
- `),j=[];let S=!1;for(const u of I)if(u.length!==0){if(!S)try{const y=JSON.parse(u);if("original"in y&&y.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(`
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)}}),w.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,h.join)(d,g)),c++}catch{}}}e.all?process.stdout.write(`Cleared dead-letter for ${i} session(s); removed ${c} rotated archive(s).
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
- `)}),v.addCommand(w);0&&(module.exports={queueCommand});
37
+ `)}),w.addCommand(v);0&&(module.exports={queueCommand});