@ironbee-ai/cli 0.21.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/assets/auth.html +79 -0
- package/dist/clients/claude/agents/ironbee-verifier.md +12 -2
- package/dist/clients/claude/commands/ironbee-verify.md +28 -15
- package/dist/clients/claude/hooks/activity-start.js +1 -1
- package/dist/clients/claude/hooks/require-verdict.js +2 -2
- package/dist/clients/claude/hooks/require-verification.js +7 -7
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/skill.android.md +65 -0
- package/dist/clients/codex/agents/ironbee-verifier.md +11 -1
- package/dist/clients/codex/commands/ironbee-verify/SKILL.md +24 -5
- package/dist/clients/codex/hooks/require-verification.js +7 -7
- package/dist/clients/codex/hooks/track-action.js +1 -1
- package/dist/clients/codex/index.js +2 -2
- package/dist/clients/codex/platforms/command-verify.android.md +61 -0
- package/dist/clients/codex/platforms/rule.android.md +32 -0
- package/dist/clients/codex/platforms/skill.android.md +55 -0
- package/dist/clients/codex/skills/ironbee-verification.md +3 -0
- package/dist/clients/codex/util.js +11 -11
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +21 -4
- package/dist/clients/cursor/hooks/require-verdict.js +1 -1
- package/dist/clients/cursor/hooks/require-verification.js +6 -6
- package/dist/clients/cursor/hooks/track-action.js +1 -1
- package/dist/clients/cursor/index.js +1 -1
- package/dist/clients/cursor/platforms/command-verify.android.md +61 -0
- package/dist/clients/cursor/platforms/rule.android.md +32 -0
- package/dist/clients/cursor/platforms/skill.android.md +55 -0
- package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
- package/dist/clients/cursor/skills/ironbee-verification.md +3 -0
- package/dist/commands/android.js +1 -0
- package/dist/commands/config.js +2 -2
- package/dist/commands/cycle-toggle.js +4 -4
- package/dist/commands/hook.js +16 -15
- package/dist/commands/import.js +4 -4
- package/dist/commands/install.js +1 -1
- package/dist/commands/login.js +2 -2
- package/dist/commands/mode-select.js +2 -0
- package/dist/hooks/core/actions.js +6 -5
- package/dist/hooks/core/session-state.js +1 -1
- package/dist/hooks/core/submit-verdict.js +4 -4
- package/dist/hooks/core/verification-lifecycle.js +1 -1
- package/dist/hooks/core/verify-gate.js +29 -23
- package/dist/import/claude/events/tool-call.js +1 -1
- package/dist/import/codex/events/tool-call.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/auth.js +5 -5
- package/dist/lib/collector.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/install-version.js +1 -0
- package/dist/lib/platform-section.js +4 -3
- package/dist/lib/prompt.js +4 -4
- package/dist/lib/recording-tools.js +1 -0
- package/dist/lib/schema-sync.js +2 -0
- package/dist/lib/version.js +1 -1
- package/dist/scripts/postinstall.js +1 -1
- package/dist/tui/config/schema.js +1 -1
- package/dist/tui/platforms/area.js +2 -2
- package/dist/tui/projects/area.js +4 -4
- package/dist/tui/sessions/area.js +3 -3
- package/package.json +1 -1
- package/dist/assets/login.html +0 -93
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.23.0 (2026-06-13)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **auth:** add OAuth support ([#23](https://github.com/ironbee-ai/ironbee-cli/issues/23)) ([20b650c](https://github.com/ironbee-ai/ironbee-cli/commit/20b650c75ca1b8c77cf7cc21dfa06a6b9a103532))
|
|
8
|
+
|
|
9
|
+
## 0.22.0 (2026-06-12)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* android platform support ([#25](https://github.com/ironbee-ai/ironbee-cli/issues/25)) ([f155488](https://github.com/ironbee-ai/ironbee-cli/commit/f155488bcb595895ce0b4b6383207a9e5466a41b))
|
|
14
|
+
|
|
3
15
|
## 0.21.2 (2026-06-09)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>{{TITLE}} — IronBee</title>
|
|
6
|
+
<style>
|
|
7
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8
|
+
body {
|
|
9
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
background: #0f0f11;
|
|
15
|
+
color: #e5e5e5;
|
|
16
|
+
}
|
|
17
|
+
.container {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 16px;
|
|
22
|
+
max-width: 840px;
|
|
23
|
+
padding: 2.5rem;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
.logo {
|
|
27
|
+
width: 288px;
|
|
28
|
+
height: 288px;
|
|
29
|
+
opacity: 0.95;
|
|
30
|
+
}
|
|
31
|
+
h1 {
|
|
32
|
+
font-size: 1.6rem;
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
color: #e5e5e5;
|
|
35
|
+
letter-spacing: -0.3px;
|
|
36
|
+
}
|
|
37
|
+
p {
|
|
38
|
+
color: #888;
|
|
39
|
+
font-size: 0.9rem;
|
|
40
|
+
line-height: 1.6;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="container">
|
|
46
|
+
<svg class="logo" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="IronBee">
|
|
47
|
+
<path d="M217.557 138.711C211.185 166.725 218.839 188.038 224.039 201.771L249.307 154.531L275.674 201.771C285.561 179.579 286.328 161.013 281.167 135.964C277.431 117.837 259.744 95.3889 249.307 84C241.91 92.6058 222.355 117.618 217.557 138.711Z" fill="url(#g0)"/>
|
|
48
|
+
<path d="M249.527 416C223.951 394.731 206.425 358.067 202.396 343.052L249.527 381.394L272.378 362.937L295.559 343.052C290.988 371.089 262.417 403.805 249.527 416Z" fill="url(#g1)"/>
|
|
49
|
+
<path d="M220.854 231.434L250.077 171.12L279.629 231.434L250.077 256.153L220.854 231.434Z" fill="url(#g2)"/>
|
|
50
|
+
<path d="M298.525 286.804C302.568 301.218 299.258 322.399 297.097 331.188L273.587 350.303L249.857 369.749L225.907 349.754L202.397 331.188C197.387 320.465 200.31 297.131 202.397 286.804L249.857 324.486L298.525 286.804Z" fill="url(#g3)"/>
|
|
51
|
+
<path d="M249.856 313.609L203.495 275.268C203.495 263.315 211.552 247.29 215.58 240.771L249.856 267.028L284.573 240.771C291.955 248.066 296.071 266.809 297.207 275.268L249.856 313.609Z" fill="url(#g4)"/>
|
|
52
|
+
<path d="M215.909 165.298C215.36 179.579 220.853 193.752 224.039 201.771L236.563 178.481L249.307 154.531L275.674 201.881C280.947 190.785 284.902 174.526 284.133 167.275C281.277 147.061 274.136 138.052 264.138 125.638C255.679 132.01 244.847 132.141 235.355 125.638C223.38 137.613 216.568 156.289 215.909 165.298Z" fill="url(#g5)" fill-opacity="0.7"/>
|
|
53
|
+
<path d="M249.856 347.996L202.396 286.804L249.856 324.486L298.525 286.804L249.856 347.996Z" fill="#333941" fill-opacity="0.64"/>
|
|
54
|
+
<path d="M249.527 292.296L215.58 240.771L249.857 267.028L284.573 240.771L249.527 292.296Z" fill="#333941" fill-opacity="0.35"/>
|
|
55
|
+
<path d="M249.419 404.685L202.396 343.053L249.419 381.614L295.559 343.053L249.419 404.685Z" fill="#0F0F0F" fill-opacity="0.1"/>
|
|
56
|
+
<path d="M285.482 209.139C297.06 183.136 300.205 166.929 294.24 132.4L346.304 224.989L319.38 218.541L350.314 284.253C319.826 267.918 305.852 251.479 285.482 209.139Z" fill="url(#g6)"/>
|
|
57
|
+
<path d="M214.785 209.139C203.206 183.136 200.062 166.929 206.027 132.4L153.963 224.989L180.887 218.541L149.953 284.253C180.44 267.918 194.414 251.479 214.785 209.139Z" fill="url(#g7)"/>
|
|
58
|
+
<path d="M458.055 281.287L306.25 134.086L375.851 242.958L344.516 235.599L359.745 289.281C390.46 298.849 431.254 294.285 458.055 281.287Z" fill="url(#g8)"/>
|
|
59
|
+
<path d="M42.2112 281.287L194.017 134.086L124.415 242.958L155.75 235.599L140.521 289.281C109.807 298.849 69.0124 294.285 42.2112 281.287Z" fill="url(#g9)"/>
|
|
60
|
+
<path d="M155.707 235.608C120.279 262.873 94.8569 272.188 42.5488 281.311C69.9036 294.823 110.553 298.339 140.546 289.221L155.707 235.608Z" fill="black" fill-opacity="0.16"/>
|
|
61
|
+
<path d="M344.558 235.608C380.054 262.765 405.526 272.042 457.935 281.129C430.527 294.588 389.799 298.089 359.748 289.007L344.558 235.608Z" fill="black" fill-opacity="0.16"/>
|
|
62
|
+
<defs>
|
|
63
|
+
<linearGradient id="g0" x1="248.978" y1="84" x2="248.978" y2="416" gradientUnits="userSpaceOnUse"><stop stop-color="#E5E5E5"/><stop offset="1" stop-color="#272F39"/></linearGradient>
|
|
64
|
+
<linearGradient id="g1" x1="248.978" y1="84" x2="248.978" y2="416" gradientUnits="userSpaceOnUse"><stop stop-color="#E5E5E5"/><stop offset="1" stop-color="#272F39"/></linearGradient>
|
|
65
|
+
<linearGradient id="g2" x1="250.032" y1="171.12" x2="250.032" y2="369.749" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#272F39"/></linearGradient>
|
|
66
|
+
<linearGradient id="g3" x1="250.032" y1="171.12" x2="250.032" y2="369.749" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#272F39"/></linearGradient>
|
|
67
|
+
<linearGradient id="g4" x1="250.351" y1="240.771" x2="250.351" y2="313.609" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#272F39"/></linearGradient>
|
|
68
|
+
<linearGradient id="g5" x1="250.05" y1="125.638" x2="250.05" y2="201.881" gradientUnits="userSpaceOnUse"><stop stop-color="#B1B1B2"/><stop offset="1" stop-color="#343B43"/></linearGradient>
|
|
69
|
+
<linearGradient id="g6" x1="315.879" y1="132.019" x2="318.569" y2="284.812" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#454B54"/></linearGradient>
|
|
70
|
+
<linearGradient id="g7" x1="184.387" y1="132.019" x2="181.698" y2="284.812" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#454B54"/></linearGradient>
|
|
71
|
+
<linearGradient id="g8" x1="380.834" y1="132.773" x2="383.683" y2="294.632" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#4A5159"/></linearGradient>
|
|
72
|
+
<linearGradient id="g9" x1="119.433" y1="132.773" x2="116.583" y2="294.632" gradientUnits="userSpaceOnUse"><stop stop-color="#D9D9D9"/><stop offset="1" stop-color="#4A5159"/></linearGradient>
|
|
73
|
+
</defs>
|
|
74
|
+
</svg>
|
|
75
|
+
<h1>{{TITLE}}</h1>
|
|
76
|
+
{{BODY}}
|
|
77
|
+
</div>
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
cycle out-of-band — it drives the devtools tools, judges the result, and records the
|
|
7
7
|
verdict in the shared session, then returns a short summary. It does NOT edit code: if it
|
|
8
8
|
finds problems it reports them as issues for the main agent to fix.
|
|
9
|
-
tools: Bash, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*
|
|
9
|
+
tools: Bash, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# IronBee Verifier (delegated verification)
|
|
@@ -42,6 +42,12 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
42
42
|
```
|
|
43
43
|
echo '{}' | ironbee hook verification-start
|
|
44
44
|
```
|
|
45
|
+
**If the delegating prompt contains a `Mode: fix` line**, pass the intent
|
|
46
|
+
along so IronBee's completion gate enforces fix-until-pass on the main agent:
|
|
47
|
+
```
|
|
48
|
+
echo '{}' | ironbee hook verification-start --intent fix
|
|
49
|
+
```
|
|
50
|
+
(No declared mode → plain form as above, no flag.)
|
|
45
51
|
2. Build and start the application **only if it isn't already running** (check
|
|
46
52
|
`docker compose ps` / process output / config — don't guess ports). **Track whether YOU
|
|
47
53
|
started it**: if it was already up, the user or main agent owns it — leave it alone.
|
|
@@ -53,7 +59,8 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
53
59
|
this verification*, stop it now before you return — kill the exact process/container you
|
|
54
60
|
launched (e.g. the backgrounded `npm run dev`, the `docker compose up` you ran). **Never
|
|
55
61
|
stop a server that was already running** (user/main-agent-owned). Also honor any
|
|
56
|
-
cycle-specific teardown (e.g.
|
|
62
|
+
cycle-specific teardown noted in the platform sections (e.g. stopping an active screen
|
|
63
|
+
recording) BEFORE submitting your verdict.
|
|
57
64
|
5. **Submit your verdict immediately** — do NOT wait:
|
|
58
65
|
```
|
|
59
66
|
echo '<verdict-json>' | ironbee hook submit-verdict
|
|
@@ -76,3 +83,6 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
76
83
|
|
|
77
84
|
<!--IRONBEE:PLATFORM:backend-->
|
|
78
85
|
<!--/IRONBEE:PLATFORM:backend-->
|
|
86
|
+
|
|
87
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
88
|
+
<!--/IRONBEE:PLATFORM:android-->
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
---
|
|
2
|
-
argument-hint: "[scenario text | path to scenario file]"
|
|
3
|
-
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent;
|
|
2
|
+
argument-hint: "[fix|report] [scenario text | path to scenario file]"
|
|
3
|
+
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent. Default is verify-only (report the verdict and stop); a leading `fix` argument adds the fix-and-re-verify loop until pass. Optionally pass a custom scenario (inline text or a file path) that defines what to verify.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# IronBee Verify
|
|
7
7
|
|
|
8
|
-
Verify the current code changes by **delegating to the `ironbee-verifier` sub-agent**. It drives the verification tools out-of-band in this **shared session** and returns a verdict summary — so the heavy devtools output (DOM, console, screenshots) stays in its context, not yours. **You do not run the verification tools yourself**: you resolve the scenario (below), spawn the verifier, and relay its result. The gate still runs every active cycle and all must pass for `status: pass`.
|
|
8
|
+
Verify the current code changes by **delegating to the `ironbee-verifier` sub-agent**. It drives the verification tools out-of-band in this **shared session** and returns a verdict summary — so the heavy devtools output (DOM, console, screenshots) stays in its context, not yours. **You do not run the verification tools yourself**: you resolve the mode and scenario (below), spawn the verifier, and relay its result. The gate still runs every active cycle and all must pass for `status: pass`.
|
|
9
|
+
|
|
10
|
+
## Mode
|
|
11
|
+
|
|
12
|
+
The FIRST whitespace-delimited token of the arguments selects the mode; everything after it is the scenario:
|
|
13
|
+
|
|
14
|
+
- `fix` → **verify-and-fix**: on a fail verdict, fix the reported issues and re-delegate until the verdict passes.
|
|
15
|
+
- `report` → **verify-only** (the explicit form of the default).
|
|
16
|
+
- Anything else, or no arguments → **verify-only** (default), and the WHOLE argument string is the scenario.
|
|
17
|
+
|
|
18
|
+
**Verify-only** means: relay the verdict and STOP — do **not** edit code, do **not** re-delegate on fail. The fail verdict is still submitted and recorded (that's the point — an honest status report). If the user wants the issues repaired, suggest `/ironbee-verify fix`. One caveat (enforce mode): if code was edited earlier in THIS turn, the Stop gate may still block on the fail verdict and demand fixes — follow the gate then; the mode token never overrides enforcement.
|
|
9
19
|
|
|
10
20
|
## Verification scenario
|
|
11
21
|
|
|
12
22
|
A custom verification scenario may be supplied when this command is invoked — either as **inline text** or as a **path to a file** (any location, any format; read at run time).
|
|
13
23
|
|
|
14
|
-
>
|
|
15
|
-
> *(
|
|
24
|
+
> Mode + scenario argument: `$ARGUMENTS`
|
|
25
|
+
> *(strip a leading `fix` / `report` mode token first — the remainder is the scenario; empty remainder → the verifier uses its default flow)*
|
|
16
26
|
|
|
17
27
|
- **If a scenario is supplied, it is authoritative**: the verifier must verify exactly what it describes, exercising precisely the flows/states/endpoints it names — this **replaces** the default "exercise the changed pages/endpoints" guidance.
|
|
18
28
|
- **If the scenario is (or points to) a file path**, read that file with your file-read tool yourself and pass its **contents** into the verifier's prompt (the verifier has no file-read tool). Do not assume a fixed location or format — read whatever path was given.
|
|
@@ -21,17 +31,20 @@ A custom verification scenario may be supplied when this command is invoked —
|
|
|
21
31
|
|
|
22
32
|
## Steps
|
|
23
33
|
|
|
24
|
-
1. **Resolve the scenario**: file path → read it now; inline text → use as-is; empty → none.
|
|
25
|
-
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task
|
|
34
|
+
1. **Resolve the mode and scenario**: strip a leading `fix` / `report` token (see **Mode**); then file path → read it now; inline text → use as-is; empty → none.
|
|
35
|
+
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task, the mode, and the resolved scenario, e.g.:
|
|
26
36
|
> Verify the current code changes.
|
|
37
|
+
> Mode: \<`fix` in fix mode — OMIT this line entirely in verify-only mode>
|
|
27
38
|
> Scenario: \<the resolved scenario text, or "none — exercise the changed pages/endpoints">
|
|
28
|
-
The verifier runs `verification-start` → drives every active cycle's tools → submits the single verdict, all in this shared session. It resolves the session id from the environment, so you don't pass one.
|
|
39
|
+
The verifier runs `verification-start` (relaying the fix intent to IronBee's completion gate, which then enforces fix-until-pass on you) → drives every active cycle's tools → submits the single verdict, all in this shared session. It resolves the session id from the environment, so you don't pass one.
|
|
29
40
|
3. **Relay the verifier's summary** — the verdict status and, on fail, the issues it found.
|
|
30
|
-
4. **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
4. **On a fail verdict, branch by mode**:
|
|
42
|
+
- **Verify-only (default)**: stop here. Report the issues clearly and suggest `/ironbee-verify fix` to repair them. Do not edit code.
|
|
43
|
+
- **Fix mode (`fix` token)**: fix the issues it reported. Optionally record what you fixed so the next pass verdict can describe it:
|
|
44
|
+
```
|
|
45
|
+
echo '{"fixes":["what you repaired"]}' | ironbee hook record-fix
|
|
46
|
+
```
|
|
47
|
+
Then re-run the verification by re-delegating (step 2) — repeat until the verdict passes. (If you skip `record-fix`, IronBee fills `fixes` from the files you changed since the fail.)
|
|
35
48
|
|
|
36
49
|
Do NOT verify inline — always delegate, so your context stays clean. The per-cycle "how to verify" detail (which tools to drive, the verdict expectations) lives in the `ironbee-verifier` sub-agent itself — you don't need it here to delegate.
|
|
37
50
|
|
|
@@ -43,6 +56,6 @@ Do NOT verify inline — always delegate, so your context stays clean. The per-c
|
|
|
43
56
|
- Its `checks` are specific observations (e.g. `"submitted valid credentials, redirected to /dashboard"`, `"console clean — 0 errors"`), not `"it works"`.
|
|
44
57
|
|
|
45
58
|
## Important
|
|
46
|
-
- The **verifier** produces the verdict; your job is to delegate, relay it, and fix on fail.
|
|
47
|
-
-
|
|
59
|
+
- The **verifier** produces the verdict; your job is to delegate, relay it, and — in fix mode — fix on fail.
|
|
60
|
+
- **Fix mode only**: a fail verdict means you must fix the issues and re-delegate until pass. In verify-only mode (the default) you report and stop — fixing without the `fix` token is overstepping.
|
|
48
61
|
- Never verify inline to "save a round trip" — delegation keeps your context clean and is the supported path.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var r=Object.defineProperty;var
|
|
1
|
+
"use strict";var r=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var a=(s,t)=>r(s,"name",{value:t,configurable:!0});var S=(s,t)=>{for(var e in t)r(s,e,{get:t[e],enumerable:!0})},$=(s,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!b.call(s,i)&&i!==e&&r(s,i,{get:()=>t[i],enumerable:!(o=f(t,i))||o.enumerable});return s};var w=s=>$(r({},"__esModule",{value:!0}),s);var A={};S(A,{run:()=>y});module.exports=w(A);var p=require("../../../hooks/core/actions"),m=require("../../../hooks/core/activity"),c=require("../../../hooks/core/session-state"),n=require("../../../lib/logger"),u=require("../../../lib/stdin"),d=require("../../../otel/claude/daemon/ensure");async function y(s){let t;try{t=JSON.parse((0,u.readStdin)())}catch(l){n.logger.debug(`failed to parse stdin: ${l}`),process.exit(0)}const e=t.session_id??"default",o=`${s}/.ironbee/sessions/${e}`;(0,n.setLogFile)(`${o}/session.log`);const i=`${o}/actions.jsonl`;await(0,c.reconcileAbandonedActivity)(o,i,p.appendAction),await(0,m.startActivity)({sessionDir:o,actionsFile:i,source:"user_prompt"}),await(0,d.ensureOTELCollector)(s),process.exit(0)}a(y,"run");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var _=(e,o)=>a(e,"name",{value:o,configurable:!0});var T=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},w=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of x(o))!y.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=$(o,s))||t.enumerable});return e};var I=e=>w(a({},"__esModule",{value:!0}),e);var E={};T(E,{run:()=>k});module.exports=I(E);var d=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),
|
|
1
|
+
"use strict";var a=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var _=(e,o)=>a(e,"name",{value:o,configurable:!0});var T=(e,o)=>{for(var i in o)a(e,i,{get:o[i],enumerable:!0})},w=(e,o,i,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of x(o))!y.call(e,s)&&s!==i&&a(e,s,{get:()=>o[s],enumerable:!(t=$(o,s))||t.enumerable});return e};var I=e=>w(a({},"__esModule",{value:!0}),e);var E={};T(E,{run:()=>k});module.exports=I(E);var d=require("fs"),b=require("../../../hooks/core/actions"),h=require("../../../hooks/core/activity"),v=require("../../../hooks/core/tool-use-stash"),l=require("../../../lib/config"),n=require("../../../lib/logger"),C=require("../../../lib/stdin");async function k(e,o){const i=o?.soft===!0;let t;try{t=JSON.parse((0,C.readStdin)())}catch(c){n.logger.debug(`failed to parse stdin: ${c}`),process.exit(0)}const s=t.session_id??"default";(0,n.setLogFile)(`${e}/.ironbee/sessions/${s}/session.log`);const u=`${e}/.ironbee/sessions/${s}`,f=`${u}/actions.jsonl`;!i&&(0,b.hasToolCallsSinceLastVerdict)(f)&&(process.stderr.write(`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
|
|
2
2
|
|
|
3
3
|
Submit your verdict first:
|
|
4
4
|
echo '{"session_id":"${s}","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict
|
|
5
5
|
|
|
6
6
|
Then you can edit code to fix the issues.
|
|
7
|
-
`),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const c=(0,l.loadConfig)(e),p=(0,l.getCaptureFileChangeset)(c),g=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&p){const m={file_existed:g};if(p&&g)try{m.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(S){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${S}`)}(0,
|
|
7
|
+
`),process.exit(2));const r=t.tool_input?.file_path;if(r&&t.tool_use_id){const c=(0,l.loadConfig)(e),p=(0,l.getCaptureFileChangeset)(c),g=(0,d.existsSync)(r);if(t.tool_name==="Write"||t.tool_name==="Edit"&&p){const m={file_existed:g};if(p&&g)try{m.prior_content=(0,d.readFileSync)(r,"utf-8")}catch(S){n.logger.debug(`failed to pre-read ${r} for changeset capture: ${S}`)}(0,v.stashToolUseData)(s,t.tool_use_id,m)}}await(0,h.startActivity)({sessionDir:u,actionsFile:f,source:"pre_tool_use"}),process.exit(0)}_(k,"run");0&&(module.exports={run});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var S=(o,t)=>u(o,"name",{value:t,configurable:!0});var L=(o,t)=>{for(var s in t)u(o,s,{get:t[s],enumerable:!0})},j=(o,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of F(t))!K.call(o,e)&&e!==s&&u(o,e,{get:()=>t[e],enumerable:!(n=V(t,e))||n.enumerable});return o};var B=o=>j(u({},"__esModule",{value:!0}),o);var q={};L(q,{run:()=>M});module.exports=B(q);var w=require("crypto"),i=require("../../../hooks/core/session-state"),R=require("../../../hooks/core/actions"),$=require("../../../hooks/core/activity"),O=require("../../../hooks/core/verification-lifecycle"),E=require("../../../hooks/core/verification-context"),U=require("../../../lib/config"),A=require("../../../lib/recording-tools"),f=require("../../../lib/logger"),v=require("../util"),x=require("../../../lib/stdin");const D="browser-devtools";async function M(o,t){const s=t?.soft===!0;let n;try{n=JSON.parse((0,x.readStdin)())}catch(y){f.logger.debug(`failed to parse stdin: ${y}`),process.exit(0)}const e=n.session_id??"default",r=`${o}/.ironbee/sessions/${e}`;(0,f.setLogFile)(`${r}/session.log`);const _=`${r}/actions.jsonl`,h=(0,i.getActiveVerificationId)(r);!h&&!s&&(process.stderr.write(`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools).
|
|
2
2
|
|
|
3
3
|
Start verification first:
|
|
4
4
|
echo '{"session_id":"${e}"}' | ironbee hook verification-start
|
|
5
5
|
|
|
6
|
-
Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend.
|
|
7
|
-
`),process.exit(2));const
|
|
6
|
+
Then use the verification tools for the active cycle(s) \u2014 bdt_* for browser, ndt_* for node, bedt_* for backend, adt_* for android.
|
|
7
|
+
`),process.exit(2));const b=n.tool_name??"",l=(0,A.recordingToolsForServer)((0,v.extractMcpServerName)(b));!s&&l!==null&&(0,i.isRecordingRequired)(r)&&!(0,i.isRecordingActive)(r)&&!b.endsWith(l.startTool)&&(process.stderr.write(`BLOCKED: Recording is required but not started.
|
|
8
8
|
|
|
9
9
|
1. Start recording NOW:
|
|
10
|
-
Use
|
|
10
|
+
Use mcp__${l.server}__${l.startTool}
|
|
11
11
|
|
|
12
|
-
2. Run the verification
|
|
12
|
+
2. Run the verification flow for the active cycle(s)
|
|
13
13
|
|
|
14
14
|
3. **Stop recording BEFORE submitting verdict:**
|
|
15
|
-
Use
|
|
15
|
+
Use mcp__${l.server}__${l.stopTool}
|
|
16
16
|
submit-verdict will reject with "recording is still active" if you skip this.
|
|
17
|
-
`),process.exit(2)),await(0
|
|
17
|
+
`),process.exit(2)),await(0,$.startActivity)({sessionDir:r,actionsFile:_,source:"pre_tool_use"});let d=h;s&&!d&&(d=(await(0,O.startVerification)({sessionId:e,sessionDir:r,actionsFile:_,recordingEnabled:!1})).verificationId);const N=(0,i.getActiveTraceId)(r),p=(0,i.getActiveActivityId)(r),C=(0,R.resolveProjectName)(o),g=[`prj:${C}`,`sid:${e}`];p&&g.push(`aid:${p}`),d&&g.push(`vid:${d}`);const P=`ironbee=${g.join(";")}`,c=(0,U.loadConfig)(o),I={...n.tool_input??{}},a={projectName:C,sessionId:e,activityId:p,verificationId:d,traceId:N,traceState:P,toolCallId:(0,w.randomUUID)()};n.tool_use_id&&(a.toolUseId=n.tool_use_id),a.mcpServer=(0,v.extractMcpServerName)(n.tool_name)??D;const T=(0,i.getUserEmail)(r);T&&(a.userEmail=T),c.collector?.url&&(a.collectorUrl=c.collector.url),c.collector?.oauthToken?a.collectorOAuthToken=c.collector.oauthToken:c.collector?.apiKey&&(a.collectorApiKey=c.collector.apiKey),I._metadata=a;const m={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",updatedInput:I}},k=(0,E.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:e,sessionDir:r,activeVerificationId:d,config:c});k.length>0&&m.hookSpecificOutput&&(m.hookSpecificOutput.additionalContext=k),process.stdout.write(JSON.stringify(m)),process.exit(0)}S(M,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var v=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var d=(t,o)=>v(t,"name",{value:o,configurable:!0});var z=(t,o)=>{for(var i in o)v(t,i,{get:o[i],enumerable:!0})},M=(t,o,i,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of J(o))!U.call(t,n)&&n!==i&&v(t,n,{get:()=>o[n],enumerable:!(e=B(o,n))||e.enumerable});return t};var W=t=>M(v({},"__esModule",{value:!0}),t);var eo={};z(eo,{run:()=>oo});module.exports=W(eo);var f=require("../../../hooks/core/actions"),c=require("../../../hooks/core/session-state"),$=require("../../../import/ids"),h=require("../../../lib/config"),s=require("../../../lib/logger"),I=require("../../../lib/recording-tools"),L=require("../../../lib/stdin"),g=require("../../../queue"),y=require("../util");const k=/callTool\(\s*['"]([^'"]+)['"]/g,A="mcp__browser-devtools__",X="bdt_",K="browser-devtools",Q="node-devtools",Y="backend-devtools",j="android-devtools",q=`${X}execute`;function G(t){return t.startsWith(A)?t:`${A}${t}`}d(G,"toFullName");function H(t,o){if(t[o]!=="{")return;let i=0;for(let e=o;e<t.length;e++)if(t[e]==="{")i++;else if(t[e]==="}"&&(i--,i===0))return t.slice(o,e+1)}d(H,"extractBalancedBraces");function Z(t){const o=typeof t=="string"?t:JSON.stringify(t??""),i=[],e=new Set;let n=k.exec(o);for(;n!==null;){const a=G(n[1]);if(!e.has(a)){e.add(a);let u;const p=n.index+n[0].length,m=o.slice(p).trimStart();if(m.startsWith(",")){const T=m.slice(1).trimStart();if(T.startsWith("{")){const _=H(T,0);if(_)try{u=JSON.parse(_)}catch{u=_}}}i.push({name:a,args:u})}n=k.exec(o)}return i}d(Z,"extractNestedToolCalls");async function oo(t){let o;try{o=JSON.parse((0,L.readStdin)())}catch(l){s.logger.debug(`failed to parse stdin: ${l}`),process.exit(0)}const i=o.session_id??"default",e=`${t}/.ironbee/sessions/${i}`,n=`${e}/actions.jsonl`;(0,s.setLogFile)(`${e}/session.log`);const a=o.tool_name??"unknown",u=Date.now(),p=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,m=(0,c.getActiveActivityId)(e),T=(0,c.getActiveVerificationId)(e),_=(0,c.getActiveTraceId)(e),r=(0,y.classifyTool)(a,o.tool_input),S=r.tool_type==="mcp"&&r.mcp_server===K,x=r.tool_type==="mcp"&&r.mcp_server===Q,D=r.tool_type==="mcp"&&r.mcp_server===Y,V=r.tool_type==="mcp"&&r.mcp_server===j,O=S||x||D||V,F=O?p:(0,y.extractClaudeToolInput)(a,p),w=typeof o.error=="string"&&o.error.length>0?o.error:void 0,E=w?o.is_interrupt?`interrupted: ${w}`:w:void 0,C={...(0,f.baseFields)(n),type:"tool_call",timestamp:u,tool_name:r.tool_name,tool_type:r.tool_type,tool_use_id:o.tool_use_id,tool_input:F,tool_input_size:N(p),tool_response:E?void 0:o.tool_response,tool_response_size:N(E?void 0:o.tool_response),activity_id:m,verification_id:T,trace_id:_,duration:typeof o.duration_ms=="number"?o.duration_ms:null,mcp_server:r.mcp_server,error:E};if(o.tool_use_id!==void 0&&o.tool_use_id.length>0&&(C.id=(0,$.deriveToolCallEventIdFromToolUseId)(i,o.tool_use_id)),O){await(0,f.appendAction)(n,C);const l=(0,I.recordingToolsForServer)(r.mcp_server);l!==null&&(r.tool_name===l.startTool?((0,c.setRecordingActive)(e,!0),s.logger.debug(`track-action: recording started (${l.cycle})`)):r.tool_name===l.stopTool&&((0,c.setRecordingActive)(e,!1),s.logger.debug(`track-action: recording stopped (${l.cycle})`)))}else to(t,i,C);if(s.logger.debug(`track-action: ${a}${E?" (failed)":""}`),S&&r.tool_name===q&&!E){const l=Z(o.tool_input);for(const b of l){const R=(0,y.classifyTool)(b.name,b.args),P={...(0,f.baseFields)(n),type:"tool_call",timestamp:u,tool_name:R.tool_name,tool_type:R.tool_type,tool_input:b.args,activity_id:m,verification_id:T,trace_id:_,duration:null,mcp_server:R.mcp_server};await(0,f.appendAction)(n,P),s.logger.debug(`track-action (nested): ${b.name}`)}}process.exit(0)}d(oo,"run");function to(t,o,i){if(!(0,h.isJobQueueEnabled)(t))return;const e={...i};delete e.tool_response;try{(0,g.submit)(t,o,g.SEND_EVENT_TYPE,e)}catch(n){if(n instanceof g.JobTooLargeError){s.logger.debug(`track-action: wire event too large for ${i.tool_name}; dropping`);return}s.logger.debug(`track-action: failed to submit ${i.tool_name}: ${n instanceof Error?n.message:n}`)}}d(to,"submitEvent");function N(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}d(N,"byteSize");0&&(module.exports={run});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}
|
|
1
|
+
"use strict";var ee=Object.create;var _=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var re=Object.getPrototypeOf,ie=Object.prototype.hasOwnProperty;var v=(t,e)=>_(t,"name",{value:e,configurable:!0});var se=(t,e)=>{for(var n in e)_(t,n,{get:e[n],enumerable:!0})},C=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of oe(e))!ie.call(t,r)&&r!==n&&_(t,r,{get:()=>e[r],enumerable:!(o=ne(e,r))||o.enumerable});return t};var te=(t,e,n)=>(n=t!=null?ee(re(t)):{},C(e||!t||!t.__esModule?_(n,"default",{value:t,enumerable:!0}):n,t)),ce=t=>C(_({},"__esModule",{value:!0}),t);var he={};se(he,{ClaudeClient:()=>ye,prepareIronBeeDir:()=>Se});module.exports=ce(he);var s=require("fs"),l=require("path"),k=require("../../lib/logger"),a=require("../../lib/output"),M=require("../../lib/gitignore"),P=require("../../lib/fs-prune"),N=require("./hooks/verify-gate"),I=require("./hooks/clear-verdict"),B=require("./hooks/track-action"),H=require("./hooks/track-action-monitor"),U=require("./hooks/session-start"),J=require("./hooks/require-verdict"),V=require("./hooks/require-verification"),j=require("./hooks/activity-start"),D=require("./hooks/activity-end"),F=require("./hooks/session-end"),u=require("../../lib/config"),X=require("../../hooks/core/actions"),x=require("../../lib/platform-section"),y=require("../../lib/install-snapshots"),$=require("./hooks/session-status");const S="browser-devtools",h="node-devtools",b="backend-devtools",E="android-devtools",ae="ironbee",le="ironbee hook session-status";function ue(t){return(0,l.join)(__dirname,"..",t,"platforms")}v(ue,"platformsDirFor");function w(t,e,n){return e?(t.includes(n)||t.push(n),t):t.filter(o=>o!==n)}v(w,"syncCyclePermission");function L(t){const e=Object.keys(t);if(e.length===0)return!0;if(e.length===1&&e[0]==="mcpServers"){const n=t.mcpServers;return n===void 0||Object.keys(n).length===0}return!1}v(L,"isMcpConfigEmpty");function de(t,e){const n=[` - ${t}:`];!("type"in e)&&"command"in e&&n.push(" type: stdio");for(const[o,r]of Object.entries(e))if(r!==void 0)if(r!==null&&typeof r=="object"&&!Array.isArray(r)){const i=Object.entries(r);if(i.length===0)n.push(` ${o}: {}`);else{n.push(` ${o}:`);for(const[c,m]of i)n.push(` ${c}: ${JSON.stringify(m)}`)}}else n.push(` ${o}: ${JSON.stringify(r)}`);return n}v(de,"renderInlineMcpServerYaml");function me(t,e){const n=[];if((0,u.isCycleEnabled)(e,"browser")&&n.push({key:S,entry:(0,u.getMcpServerEntry)(t)}),(0,u.isCycleEnabled)(e,"node")&&n.push({key:h,entry:(0,u.getNodeDevToolsMcpEntry)(t)}),(0,u.isCycleEnabled)(e,"backend")&&n.push({key:b,entry:(0,u.getBackendDevToolsMcpEntry)(t)}),(0,u.isCycleEnabled)(e,"android")&&n.push({key:E,entry:(0,u.getAndroidDevToolsMcpEntry)(t)}),n.length===0)return"";const o=["mcpServers:"];for(const{key:r,entry:i}of n)o.push(...de(r,i));return o.join(`
|
|
2
|
+
`)}v(me,"buildVerifierMcpServersBlock");function fe(t,e){if(e.length===0)return t;const n=t.split(`
|
|
3
3
|
`);if(n[0]!=="---")return t;let o=-1;for(let c=1;c<n.length;c++)if(n[c]==="---"){o=c;break}if(o<0)return t;const r=n.slice(0,o),i=n.slice(o);return[...r,...e.split(`
|
|
4
4
|
`),...i].join(`
|
|
5
|
-
`)}
|
|
5
|
+
`)}v(fe,"injectVerifierMcpServers");function ge(t,e){if(!e)return t;const n=t.split(`
|
|
6
6
|
`);if(n[0]!=="---")return t;let o=-1;for(let c=1;c<n.length;c++)if(n[c]==="---"){o=c;break}if(o<0)return t;const r=n.slice(0,o);if(r.some(c=>/^model\s*:/.test(c)))return t;const i=n.slice(o);return[...r,`model: ${e}`,...i].join(`
|
|
7
|
-
`)}k(fe,"injectVerifierModel");function ge(t){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(t))if(!e.has(n))return!1;if(t.hooks!==void 0&&Object.keys(t.hooks).length>0)return!1;if(t.permissions!==void 0){const n=t.permissions.allow??[],o=t.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}k(ge,"isClaudeSettingsEmpty");const pe=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function C(t){const e=t.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}k(C,"otelEnvOwnedByUs");function ke(t){return t.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}k(ke,"sanitizeResourceValue");class ve{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{k(this,"ClaudeClient")}detect(e){return(0,s.existsSync)((0,l.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>se(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,d.loadConfig)(e),r=(0,d.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const c=(0,l.join)(e,".claude"),f=(0,l.join)(c,"skills"),m=(0,l.join)(c,"rules"),g=(0,l.join)(c,"commands");(0,s.mkdirSync)(f,{recursive:!0}),(0,s.mkdirSync)(m,{recursive:!0}),(0,s.mkdirSync)(g,{recursive:!0});const u=(0,l.join)(c,"settings.json");if(this.mergeHooksConfig(u,r),this.writePermissions(u,i,e),(0,d.isOTELEnabled)(o)&&this.writeOTELEnv(u,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const K=(0,l.join)(f,"ironbee-verification.md"),W=(0,s.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(K,W);const Y=(0,l.join)(m,"ironbee-verification.md"),Q=(0,s.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(Y,Q)}const E=(0,l.join)(g,"ironbee-verify.md"),X=(0,s.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,s.writeFileSync)(E,X);const O=(0,l.join)(c,"agents");(0,s.mkdirSync)(O,{recursive:!0});const x=(0,l.join)(O,"ironbee-verifier.md"),G=(0,s.readFileSync)((0,l.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),z=me(G,de(e,o)),q=fe(z,(0,d.getVerificationModel)(o,"claude"));(0,s.writeFileSync)(x,q);const $=(0,l.join)(e,".mcp.json");this.writeMcpConfig($,e),(0,F.syncPlatformSectionsToConfig)(e,le),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(u)}`),r==="enforce"?(console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} skills ${a.pc.dim("\u2192")} ${a.pc.dim(f)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} rule ${a.pc.dim("\u2192")} ${a.pc.dim(m)}`)):console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} commands ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} agents ${a.pc.dim("\u2192")} ${a.pc.dim((0,l.join)(c,"agents"))}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} mcp ${a.pc.dim("\u2192")} ${a.pc.dim($)}`)}else console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(u)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,l.join)(e,".claude"),o=(0,l.join)(n,"skills","ironbee-verification.md"),r=(0,l.join)(n,"skills","ironbee-analyze.md"),i=(0,l.join)(n,"rules","ironbee-verification.md"),c=(0,l.join)(n,"commands","ironbee-analyze.md"),f=(0,l.join)(n,"commands","ironbee-verify.md"),m=(0,l.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(c),this.removeFile(f),this.removeFile(m);const g=(0,l.join)(n,"settings.json");this.removeIronBeeHooks(g),this.removePermission(g),this.removeOTELEnv(g),this.maybeDeleteEmptySettings(g);const u=(0,l.join)(e,".mcp.json");this.removeMcpServer(u),this.uninstallStatusLine(e),(0,A.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,d.isSessionStatusEnabled)(n))return;const o=(0,l.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,w.isIronbeeStatusLine)(r.command)&&(0,v.readStatusLineSnapshot)(e,"claude")===void 0&&(0,v.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:ae},c=(0,d.getStatusLineRefreshInterval)(n);c!==void 0&&(i.refreshInterval=c),this.writeStatusLineBlock(o,i),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} statusline ${a.pc.dim("\u2192")} ${a.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,l.join)(e,".claude","settings.local.json"),o=(0,v.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,v.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,w.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,c=o.refreshInterval,f={type:"command",command:r};return typeof i=="number"&&(f.padding=i),typeof c=="number"&&(f.refreshInterval=c),f}catch(n){p.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,s.existsSync)(e))try{const r=JSON.parse((0,s.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){p.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});o.statusLine=n,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,s.unlinkSync)(e):(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeOTELEnv(e,n,o){let r={};if((0,s.existsSync)(e))try{const u=JSON.parse((0,s.readFileSync)(e,"utf-8"));u!==null&&typeof u=="object"&&!Array.isArray(u)&&(r=u)}catch(u){p.logger.debug(`failed to read ${e} for otel env write: ${u}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});const i=r.env,c=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},f=c.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof f=="string"&&f.length>0&&!C(c)){console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const m=(0,d.getOTELPort)(o),g=ke((0,D.resolveProjectName)(n));c.CLAUDE_CODE_ENABLE_TELEMETRY="1",c.OTEL_LOGS_EXPORTER="otlp",c.OTEL_METRICS_EXPORTER="none",c.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",c.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${m}`,c.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",c.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${g}`,c.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=c,(0,s.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} otel env ${a.pc.dim("\u2192")} ${a.pc.dim(`${e} (127.0.0.1:${m})`)}`)}removeOTELEnv(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!C(i))return;for(const c of pe)delete i[c];Object.keys(i).length===0&&delete o.env,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){p.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));ge(n)&&(0,s.unlinkSync)(e)}catch(n){p.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,P.run)(e)}async runClearVerdict(e){await(0,M.run)(e)}async runTrackAction(e){await(0,N.run)(e)}async runSessionStart(e){await(0,B.run)(e)}async runRequireVerdict(e,n){await(0,H.run)(e,n)}async runRequireVerification(e,n){await(0,U.run)(e,n)}async runActivityStart(e){await(0,j.run)(e)}async runActivityEnd(e){await(0,J.run)(e)}async runTrackActionMonitor(e){await(0,I.run)(e)}async runSessionEnd(e){await(0,V.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(ce))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,s.existsSync)(e))try{i=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${e}: ${m}`),i={}}i.hooks||(i.hooks={});for(const m of Object.keys(i.hooks)){const g=i.hooks[m].filter(u=>!this.isIronBeeHook(u));g.length===0?delete i.hooks[m]:i.hooks[m]=g}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const c=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.Stop||(i.hooks.Stop=[]);const f=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:f}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,s.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[y]&&(delete n.mcpServers[y],o=!0),n.mcpServers&&n.mcpServers[S]&&(delete n.mcpServers[S],o=!0),n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),R(n)?(0,s.unlinkSync)(e):o&&(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){p.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8")),o=`mcp__${y}__*`,r=`mcp__${S}__*`,i=`mcp__${h}__*`,c="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(m=>m!==o&&m!==r&&m!==i&&m!==c&&m!==f),(0,s.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){p.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,s.existsSync)(e)&&(0,s.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,s.existsSync)(e))try{o=JSON.parse((0,s.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[y],delete o.mcpServers[S],delete o.mcpServers[h],R(o)){try{(0,s.rmSync)(e,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,s.existsSync)(e))try{r=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(u){p.logger.debug(`failed to parse ${e}: ${u}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${y}__*`,c=`mcp__${S}__*`,f=`mcp__${h}__*`,m="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const u=(0,d.loadConfig)(o);r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"browser"),i),r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"node"),c),r.permissions.allow=_(r.permissions.allow,(0,d.isCycleEnabled)(u,"backend"),f),r.permissions.allow=r.permissions.allow.filter(E=>E!==g),r.permissions.allow.includes(m)||r.permissions.allow.push(m)}else r.permissions.allow=r.permissions.allow.filter(u=>u!==i&&u!==c&&u!==f&&u!==m&&u!==g);(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}}function ye(t){(0,s.mkdirSync)((0,l.join)(t,".ironbee"),{recursive:!0}),(0,L.ensureIronBeeGitignored)(t)}k(ye,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
7
|
+
`)}v(ge,"injectVerifierModel");function pe(t){const e=new Set(["hooks","permissions"]);for(const n of Object.keys(t))if(!e.has(n))return!1;if(t.hooks!==void 0&&Object.keys(t.hooks).length>0)return!1;if(t.permissions!==void 0){const n=t.permissions.allow??[],o=t.permissions.deny??[];if(n.length>0||o.length>0)return!1}return!0}v(pe,"isClaudeSettingsEmpty");const ke=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_LOGS_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_LOG_RAW_API_BODIES","OTEL_RESOURCE_ATTRIBUTES","OTEL_LOGS_EXPORT_INTERVAL"];function A(t){const e=t.OTEL_RESOURCE_ATTRIBUTES;return typeof e=="string"&&e.includes("ironbee.project_name")}v(A,"otelEnvOwnedByUs");function ve(t){return t.replace(/[,=\s]+/g,"-").replace(/^-+|-+$/g,"")||"project"}v(ve,"sanitizeResourceValue");class ye{constructor(){this.name="claude";this.supportsVerifierModel=!0}static{v(this,"ClaudeClient")}detect(e){return(0,s.existsSync)((0,l.join)(e,".claude"))}resolveProjectDir(){return process.env.CLAUDE_PROJECT_DIR??process.cwd()}resolveAgentSessionId(e,n){const o=process.env.CLAUDE_CODE_SESSION_ID;return typeof o=="string"&&o.length>0?o:void 0}async runSessionStatus(){const{runSessionStatus:e}=await Promise.resolve().then(()=>te(require("./hooks/session-status")));await e()}install(e,n){const o=n??(0,u.loadConfig)(e),r=(0,u.getVerificationMode)(o),i=r!=="monitor";this.cleanupArtifacts(e);const c=(0,l.join)(e,".claude"),m=(0,l.join)(c,"skills"),f=(0,l.join)(c,"rules"),d=(0,l.join)(c,"commands");(0,s.mkdirSync)(m,{recursive:!0}),(0,s.mkdirSync)(f,{recursive:!0}),(0,s.mkdirSync)(d,{recursive:!0});const g=(0,l.join)(c,"settings.json");if(this.mergeHooksConfig(g,r),this.writePermissions(g,i,e),(0,u.isOTELEnabled)(o)&&this.writeOTELEnv(g,e,o),this.installStatusLine(e,o),i){if(r==="enforce"){const W=(0,l.join)(m,"ironbee-verification.md"),Y=(0,s.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(W,Y);const Q=(0,l.join)(f,"ironbee-verification.md"),Z=(0,s.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.md"),"utf-8");(0,s.writeFileSync)(Q,Z)}const p=(0,l.join)(d,"ironbee-verify.md"),O=(0,s.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify.md"),"utf-8");(0,s.writeFileSync)(p,O);const T=(0,l.join)(c,"agents");(0,s.mkdirSync)(T,{recursive:!0});const G=(0,l.join)(T,"ironbee-verifier.md"),z=(0,s.readFileSync)((0,l.join)(__dirname,"agents","ironbee-verifier.md"),"utf-8"),q=fe(z,me(e,o)),K=ge(q,(0,u.getVerificationModel)(o,"claude"));(0,s.writeFileSync)(G,K);const R=(0,l.join)(e,".mcp.json");this.writeMcpConfig(R,e),(0,x.syncPlatformSectionsToConfig)(e,ue),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`),r==="enforce"?(console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} skills ${a.pc.dim("\u2192")} ${a.pc.dim(m)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} rule ${a.pc.dim("\u2192")} ${a.pc.dim(f)}`)):console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} commands ${a.pc.dim("\u2192")} ${a.pc.dim(d)}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} agents ${a.pc.dim("\u2192")} ${a.pc.dim((0,l.join)(c,"agents"))}`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} mcp ${a.pc.dim("\u2192")} ${a.pc.dim(R)}`)}else console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} settings ${a.pc.dim("\u2192")} ${a.pc.dim(g)}`)}uninstall(e){this.cleanupArtifacts(e),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} removed hooks, skill, rule, command, MCP, and permissions`)}cleanupArtifacts(e){const n=(0,l.join)(e,".claude"),o=(0,l.join)(n,"skills","ironbee-verification.md"),r=(0,l.join)(n,"skills","ironbee-analyze.md"),i=(0,l.join)(n,"rules","ironbee-verification.md"),c=(0,l.join)(n,"commands","ironbee-analyze.md"),m=(0,l.join)(n,"commands","ironbee-verify.md"),f=(0,l.join)(n,"agents","ironbee-verifier.md");this.removeFile(o),this.removeFile(r),this.removeFile(i),this.removeFile(c),this.removeFile(m),this.removeFile(f);const d=(0,l.join)(n,"settings.json");this.removeIronBeeHooks(d),this.removePermission(d),this.removeOTELEnv(d),this.maybeDeleteEmptySettings(d);const g=(0,l.join)(e,".mcp.json");this.removeMcpServer(g),this.uninstallStatusLine(e),(0,P.pruneEmptyDirs)(n)}installStatusLine(e,n){if(!(0,u.isSessionStatusEnabled)(n))return;const o=(0,l.join)(e,".claude","settings.local.json"),r=this.readStatusLineBlock(o);r&&!(0,$.isIronbeeStatusLine)(r.command)&&(0,y.readStatusLineSnapshot)(e,"claude")===void 0&&(0,y.upsertStatusLineSnapshot)(e,"claude",r);const i={type:"command",command:le},c=(0,u.getStatusLineRefreshInterval)(n);c!==void 0&&(i.refreshInterval=c),this.writeStatusLineBlock(o,i),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} statusline ${a.pc.dim("\u2192")} ${a.pc.dim(o)}`)}uninstallStatusLine(e){const n=(0,l.join)(e,".claude","settings.local.json"),o=(0,y.readStatusLineSnapshot)(e,"claude");if(o){this.writeStatusLineBlock(n,o),(0,y.clearStatusLineSnapshot)(e,"claude");return}const r=this.readStatusLineBlock(n);r&&(0,$.isIronbeeStatusLine)(r.command)&&this.removeStatusLineBlock(n)}readStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object")return;const o=n.statusLine;if(o===null||typeof o!="object")return;const r=o.command;if(typeof r!="string"||r.length===0)return;const i=o.padding,c=o.refreshInterval,m={type:"command",command:r};return typeof i=="number"&&(m.padding=i),typeof c=="number"&&(m.refreshInterval=c),m}catch(n){k.logger.debug(`failed to read statusLine from ${e}: ${n}`);return}}writeStatusLineBlock(e,n){let o={};if((0,s.existsSync)(e))try{const r=JSON.parse((0,s.readFileSync)(e,"utf-8"));r!==null&&typeof r=="object"&&!Array.isArray(r)&&(o=r)}catch(r){k.logger.debug(`failed to read ${e} for statusLine write: ${r}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});o.statusLine=n,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}removeStatusLineBlock(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n;delete o.statusLine,Object.keys(o).length===0?(0,s.unlinkSync)(e):(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove statusLine from ${e}: ${n}`)}}writeOTELEnv(e,n,o){let r={};if((0,s.existsSync)(e))try{const g=JSON.parse((0,s.readFileSync)(e,"utf-8"));g!==null&&typeof g=="object"&&!Array.isArray(g)&&(r=g)}catch(g){k.logger.debug(`failed to read ${e} for otel env write: ${g}`)}else(0,s.mkdirSync)((0,l.join)(e,".."),{recursive:!0});const i=r.env,c=i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{},m=c.OTEL_EXPORTER_OTLP_ENDPOINT;if(typeof m=="string"&&m.length>0&&!A(c)){console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} ${a.pc.yellow("existing OTEL telemetry env detected \u2014 left untouched (session_context not wired for this project)")}`);return}const f=(0,u.getOTELPort)(o),d=ve((0,X.resolveProjectName)(n));c.CLAUDE_CODE_ENABLE_TELEMETRY="1",c.OTEL_LOGS_EXPORTER="otlp",c.OTEL_METRICS_EXPORTER="none",c.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",c.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${f}`,c.OTEL_LOG_RAW_API_BODIES="file:.ironbee/otel",c.OTEL_RESOURCE_ATTRIBUTES=`ironbee.project_name=${d}`,c.OTEL_LOGS_EXPORT_INTERVAL="5000",r.env=c,(0,s.writeFileSync)(e,JSON.stringify(r,null,2)),console.log(` ${a.pc.dim("\u2192")} ${(0,a.orange)("[claude]")} otel env ${a.pc.dim("\u2192")} ${a.pc.dim(`${e} (127.0.0.1:${f})`)}`)}removeOTELEnv(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(n===null||typeof n!="object"||Array.isArray(n))return;const o=n,r=o.env;if(r===null||typeof r!="object"||Array.isArray(r))return;const i=r;if(!A(i))return;for(const c of ke)delete i[c];Object.keys(i).length===0&&delete o.env,(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}catch(n){k.logger.debug(`failed to remove otel env from ${e}: ${n}`)}}maybeDeleteEmptySettings(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));pe(n)&&(0,s.unlinkSync)(e)}catch(n){k.logger.debug(`failed to inspect ${e} for emptiness: ${n}`)}}async runVerifyGate(e){await(0,N.run)(e)}async runClearVerdict(e){await(0,I.run)(e)}async runTrackAction(e){await(0,B.run)(e)}async runSessionStart(e){await(0,U.run)(e)}async runRequireVerdict(e,n){await(0,J.run)(e,n)}async runRequireVerification(e,n){await(0,V.run)(e,n)}async runActivityStart(e){await(0,j.run)(e)}async runActivityEnd(e){await(0,D.run)(e)}async runTrackActionMonitor(e){await(0,H.run)(e)}async runSessionEnd(e){await(0,F.run)(e)}async runTrackActionPre(e){}isIronBeeHook(e){return e.hooks.some(n=>n.command.includes(ae))}mergeHooksConfig(e,n){const o=n!=="monitor",r=n==="assist"?" --soft":"";let i={};if((0,s.existsSync)(e))try{i=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(f){k.logger.debug(`failed to parse ${e}: ${f}`),i={}}i.hooks||(i.hooks={});for(const f of Object.keys(i.hooks)){const d=i.hooks[f].filter(g=>!this.isIronBeeHook(g));d.length===0?delete i.hooks[f]:i.hooks[f]=d}i.hooks.SessionStart||(i.hooks.SessionStart=[]),i.hooks.SessionStart.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-start --client claude"}]}),i.hooks.UserPromptSubmit||(i.hooks.UserPromptSubmit=[]),i.hooks.UserPromptSubmit.push({matcher:"",hooks:[{type:"command",command:"ironbee hook activity-start --client claude"}]}),o&&(i.hooks.PreToolUse||(i.hooks.PreToolUse=[]),i.hooks.PreToolUse.push({matcher:"mcp__browser-devtools__.*|mcp__node-devtools__.*|mcp__backend-devtools__.*|mcp__android-devtools__.*",hooks:[{type:"command",command:`ironbee hook require-verification --client claude${r}`}]}),i.hooks.PreToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:`ironbee hook require-verdict --client claude${r}`}]}),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]),i.hooks.PostToolUse.push({matcher:"Write|Edit",hooks:[{type:"command",command:"ironbee hook clear-verdict --client claude"}]})),i.hooks.PostToolUse||(i.hooks.PostToolUse=[]);const c=o?"ironbee hook track-action --client claude":"ironbee hook track-action-monitor --client claude";i.hooks.PostToolUse.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.PostToolUseFailure||(i.hooks.PostToolUseFailure=[]),i.hooks.PostToolUseFailure.push({matcher:"",hooks:[{type:"command",command:c}]}),i.hooks.Stop||(i.hooks.Stop=[]);const m=n==="enforce"?"ironbee hook verify-gate --client claude":"ironbee hook activity-end --client claude";i.hooks.Stop.push({matcher:"",hooks:[{type:"command",command:m}]}),i.hooks.SessionEnd||(i.hooks.SessionEnd=[]),i.hooks.SessionEnd.push({matcher:"",hooks:[{type:"command",command:"ironbee hook session-end --client claude"}]}),(0,s.writeFileSync)(e,JSON.stringify(i,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));if(!n.hooks)return;for(const o of Object.keys(n.hooks)){const r=n.hooks[o].filter(i=>!this.isIronBeeHook(i));r.length===0?delete n.hooks[o]:n.hooks[o]=r}(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove hooks from ${e}: ${n}`)}}removeMcpServer(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8"));let o=!1;n.mcpServers&&n.mcpServers[S]&&(delete n.mcpServers[S],o=!0),n.mcpServers&&n.mcpServers[h]&&(delete n.mcpServers[h],o=!0),n.mcpServers&&n.mcpServers[b]&&(delete n.mcpServers[b],o=!0),n.mcpServers&&n.mcpServers[E]&&(delete n.mcpServers[E],o=!0),L(n)?(0,s.unlinkSync)(e):o&&(0,s.writeFileSync)(e,JSON.stringify(n,null,2))}catch(n){k.logger.debug(`failed to remove MCP server from ${e}: ${n}`)}}removePermission(e){if((0,s.existsSync)(e))try{const n=JSON.parse((0,s.readFileSync)(e,"utf-8")),o=`mcp__${S}__*`,r=`mcp__${h}__*`,i=`mcp__${b}__*`,c=`mcp__${E}__*`,m="Bash(ironbee *)",f="Bash(ironbee analyze)";n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(d=>d!==o&&d!==r&&d!==i&&d!==c&&d!==m&&d!==f),(0,s.writeFileSync)(e,JSON.stringify(n,null,2)))}catch(n){k.logger.debug(`failed to remove permission from ${e}: ${n}`)}}removeFile(e){(0,s.existsSync)(e)&&(0,s.unlinkSync)(e)}writeMcpConfig(e,n){let o={mcpServers:{}};if((0,s.existsSync)(e))try{o=JSON.parse((0,s.readFileSync)(e,"utf-8")),o.mcpServers||(o.mcpServers={})}catch(r){k.logger.debug(`failed to parse ${e}: ${r}`),o={mcpServers:{}}}if(delete o.mcpServers[S],delete o.mcpServers[h],delete o.mcpServers[b],delete o.mcpServers[E],L(o)){try{(0,s.rmSync)(e,{force:!0})}catch(r){k.logger.debug(`failed to remove empty ${e}: ${r}`)}return}(0,s.writeFileSync)(e,JSON.stringify(o,null,2))}writePermissions(e,n,o){let r={};if((0,s.existsSync)(e))try{r=JSON.parse((0,s.readFileSync)(e,"utf-8"))}catch(p){k.logger.debug(`failed to parse ${e}: ${p}`),r={}}r.permissions||(r.permissions={allow:[],deny:[]}),r.permissions.allow||(r.permissions.allow=[]);const i=`mcp__${S}__*`,c=`mcp__${h}__*`,m=`mcp__${b}__*`,f=`mcp__${E}__*`,d="Bash(ironbee *)",g="Bash(ironbee analyze)";if(n){const p=(0,u.loadConfig)(o);r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"browser"),i),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"node"),c),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"backend"),m),r.permissions.allow=w(r.permissions.allow,(0,u.isCycleEnabled)(p,"android"),f),r.permissions.allow=r.permissions.allow.filter(O=>O!==g),r.permissions.allow.includes(d)||r.permissions.allow.push(d)}else r.permissions.allow=r.permissions.allow.filter(p=>p!==i&&p!==c&&p!==m&&p!==f&&p!==d&&p!==g);(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}}function Se(t){(0,s.mkdirSync)((0,l.join)(t,".ironbee"),{recursive:!0}),(0,M.ensureIronBeeGitignored)(t)}v(Se,"prepareIronBeeDir");0&&(module.exports={ClaudeClient,prepareIronBeeDir});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!-- Android mobile verification is ENABLED for this project. The Stop hook
|
|
2
|
+
enforces an android cycle whenever an edited file matches
|
|
3
|
+
`android.verifyPatterns`. -->
|
|
4
|
+
|
|
5
|
+
## ⚠️ CRITICAL: when NOT to use android-devtools
|
|
6
|
+
|
|
7
|
+
**`android-devtools` is ONLY for Android apps** (native Kotlin/Java + React Native / Expo with an Android target). Do **NOT** call `adt_*` tools for projects that do not have an Android target.
|
|
8
|
+
|
|
9
|
+
**How to tell whether the project targets Android:**
|
|
10
|
+
- `android/` directory at the project root (React Native, Expo)
|
|
11
|
+
- `app/build.gradle` or `build.gradle.kts` with `com.android.application`
|
|
12
|
+
- `AndroidManifest.xml` present under `android/` or `app/src/main/`
|
|
13
|
+
|
|
14
|
+
If you see only `ios/`, `web/`, or no mobile directories — the project does NOT target Android. Do NOT call any `adt_*` tools.
|
|
15
|
+
|
|
16
|
+
**Misconfiguration recovery.** If this cycle activated but the project isn't an Android project, the operator enabled the android cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` until `maxRetries` is exhausted. Don't attempt a device connection. Stop and tell the user clearly: the project does not target Android; ask them to run `ironbee android disable` to unblock the gate.
|
|
17
|
+
|
|
18
|
+
## Android flow
|
|
19
|
+
|
|
20
|
+
> **Recording (only when `recording.enable` is on in config):** the gate blocks every other android tool until you first call `mcp__android-devtools__adt_content_start-recording`, and `submit-verdict` rejects with `"recording is still active"` unless you call `mcp__android-devtools__adt_content_stop-recording` after the steps below. **Treat start/stop as bookends around steps 1-3.** The recording ships to the collector as verification evidence.
|
|
21
|
+
|
|
22
|
+
1. **Connect to a running device or emulator**: `mcp__android-devtools__adt_device_connect` (list available targets first with `mcp__android-devtools__adt_device_list-targets` if needed).
|
|
23
|
+
- For an emulator, the device is usually `emulator-5554`.
|
|
24
|
+
- For a physical device with USB debugging on, it will appear in the target list.
|
|
25
|
+
2. **Ensure the app is running** on the target device: `mcp__android-devtools__adt_device_launch-app` with the package name.
|
|
26
|
+
3. **Pick an evidence path** per changed code area:
|
|
27
|
+
- **Device-evidence path** (UI interaction confirms the change is live):
|
|
28
|
+
- Drive the app UI to exercise the changed code: `mcp__android-devtools__adt_interaction_tap`, `mcp__android-devtools__adt_interaction_input-text`, `mcp__android-devtools__adt_interaction_swipe`, `mcp__android-devtools__adt_interaction_scroll` as needed (use `mcp__android-devtools__adt_a11y_find-element` to locate elements).
|
|
29
|
+
- Take a screenshot: `mcp__android-devtools__adt_content_take-screenshot` — then STOP and visually analyze it (readability, layout, cut-off content, expected state rendered). The screenshot tells you what the user actually sees.
|
|
30
|
+
- Take a UI snapshot: `mcp__android-devtools__adt_a11y_take-ui-snapshot` — verify the view hierarchy / labels / structure match the change.
|
|
31
|
+
- Screenshot AND UI snapshot are BOTH MANDATORY on this path (the Stop hook checks each) — visual + structural evidence, like the browser cycle's screenshot + aria-snapshot pair.
|
|
32
|
+
- **Log-evidence path** (device logs confirm the changed code path executed):
|
|
33
|
+
- Read Logcat output for the tag(s) relevant to the changed code: `mcp__android-devtools__adt_o11y_log-read` or `mcp__android-devtools__adt_o11y_log-follow` (drain a follow with `mcp__android-devtools__adt_o11y_log-get-followed`, stop it with `mcp__android-devtools__adt_o11y_log-stop-follow`).
|
|
34
|
+
- Confirm expected log lines appear AND no unexpected crashes (FATAL / E/ entries for the app package).
|
|
35
|
+
|
|
36
|
+
### Verdict fields
|
|
37
|
+
The verdict is platform-agnostic — submit only semantic judgment:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"session_id": "<sid>",
|
|
42
|
+
"status": "pass",
|
|
43
|
+
"checks": ["LoginActivity renders correctly after auth change", "no crash in Logcat"]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
On fail, include `issues`. On pass after a previous fail, include `fixes`.
|
|
48
|
+
|
|
49
|
+
Android-cycle pass criteria:
|
|
50
|
+
- **Device-evidence**: at least one UI interaction tool fired AND a screenshot was taken AND a UI snapshot was taken AND both show the expected UI state/structure.
|
|
51
|
+
- **Log-evidence**: Logcat was read AND the expected log lines are present AND no crash (FATAL / unhandled exception) from the app's package.
|
|
52
|
+
|
|
53
|
+
## Multi-cycle (browser + android simultaneously)
|
|
54
|
+
|
|
55
|
+
When you edit both a web frontend file (browser-cycle) and an Android source file (android-cycle) in the same task, both cycles activate. **Single** `verification-start`, **single** `verdict.json`, **single** retry counter cover both. One platform-agnostic verdict regardless of how many cycles ran:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"session_id": "<sid>",
|
|
60
|
+
"status": "pass",
|
|
61
|
+
"checks": ["web checkout form renders", "Android app shows order confirmation screen"]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For a multi-cycle `pass`, ALL active cycles' pass criteria must hold.
|
|
@@ -34,6 +34,12 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
34
34
|
```
|
|
35
35
|
echo '{}' | ironbee hook verification-start
|
|
36
36
|
```
|
|
37
|
+
**If the delegating prompt contains a `Mode: fix` line**, pass the intent
|
|
38
|
+
along so IronBee's completion gate enforces fix-until-pass on the main agent:
|
|
39
|
+
```
|
|
40
|
+
echo '{}' | ironbee hook verification-start --intent fix
|
|
41
|
+
```
|
|
42
|
+
(No declared mode → plain form as above, no flag.)
|
|
37
43
|
2. Build and start the application **only if it isn't already running** (check
|
|
38
44
|
`docker compose ps` / process output / config — don't guess ports). **Track whether YOU
|
|
39
45
|
started it**: if it was already up, the user or main agent owns it — leave it alone.
|
|
@@ -43,7 +49,8 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
43
49
|
4. **Teardown — shut down ONLY what you started, every run.** If in step 2 YOU started the
|
|
44
50
|
app / dev server *for this verification*, stop it now before you return. **Never stop a
|
|
45
51
|
server that was already running** (user/main-agent-owned). Honor any cycle-specific
|
|
46
|
-
teardown (e.g.
|
|
52
|
+
teardown noted in the platform sections (e.g. stopping an active screen recording)
|
|
53
|
+
BEFORE submitting your verdict.
|
|
47
54
|
5. **Submit your verdict immediately** — do NOT wait:
|
|
48
55
|
```
|
|
49
56
|
echo '<verdict-json>' | ironbee hook submit-verdict
|
|
@@ -65,3 +72,6 @@ echo '{"status":"pass","checks":["..."]}' | ironbee hook submit-verdict
|
|
|
65
72
|
|
|
66
73
|
<!--IRONBEE:PLATFORM:backend-->
|
|
67
74
|
<!--/IRONBEE:PLATFORM:backend-->
|
|
75
|
+
|
|
76
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
77
|
+
<!--/IRONBEE:PLATFORM:android-->
|