@muggleai/works 5.11.1 → 5.12.0-staging.74

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 (96) hide show
  1. package/config/runtime-targets.json +33 -0
  2. package/dist/{chunk-MSPMXBO5.js → chunk-DJ6W5PZA.js} +486 -351
  3. package/dist/{chunk-WUMAJOBT.js → chunk-VTSS6TGQ.js} +158 -56
  4. package/dist/cli.js +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/plugin/hooks/README.md +6 -1
  7. package/dist/plugin/hooks/hooks.json +24 -0
  8. package/dist/plugin/scripts/guardrail-capability-claim.sh +38 -0
  9. package/dist/plugin/scripts/guardrail-comment-reply-gate.sh +42 -0
  10. package/dist/plugin/scripts/guardrail-debug-path-gate.sh +1 -1
  11. package/dist/plugin/scripts/guardrail-e2e-gate.sh +1 -1
  12. package/dist/plugin/scripts/guardrail-record-comment-replies.sh +28 -0
  13. package/dist/plugin/scripts/guardrail-resolve-gate.sh +22 -0
  14. package/dist/plugin/scripts/guardrail-stage-gate.sh +1 -1
  15. package/dist/plugin/scripts/guardrail-terminal-gate.sh +1 -1
  16. package/dist/plugin/scripts/guardrail-walkthrough-gate.sh +1 -1
  17. package/dist/plugin/scripts/guardrail-watch-gate.sh +1 -1
  18. package/dist/plugin/scripts/guardrails.mjs +542 -34
  19. package/dist/plugin/scripts/pr-watch-loop.sh +9 -24
  20. package/dist/plugin/scripts/pr-watch-state.jq +52 -0
  21. package/dist/plugin/skills/_shared/identity-and-inbox.md +39 -0
  22. package/dist/plugin/skills/_shared/sync-branch-with-base.md +29 -0
  23. package/dist/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +3 -2
  24. package/dist/plugin/skills/_shared/vcs/github/unresolved-threads.md +2 -0
  25. package/dist/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +3 -1
  26. package/dist/plugin/skills/_shared/vcs/gitlab.md +0 -1
  27. package/dist/plugin/skills/do/address-reviews.md +5 -2
  28. package/dist/plugin/skills/do/build.md +9 -0
  29. package/dist/plugin/skills/do/e2e-acceptance.md +2 -0
  30. package/dist/plugin/skills/do/e2e-repair.md +98 -0
  31. package/dist/plugin/skills/do/open-prs/forward.md +17 -9
  32. package/dist/plugin/skills/do/open-prs/update.md +9 -2
  33. package/dist/plugin/skills/do/per-comment-replies.md +13 -1
  34. package/dist/plugin/skills/do/pre-flight.md +2 -2
  35. package/dist/plugin/skills/do/resolve-conflicts.md +59 -59
  36. package/dist/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  37. package/dist/plugin/skills/muggle-do/SKILL.md +6 -5
  38. package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +1 -1
  39. package/dist/plugin/skills/muggle-preferences/preference-gates/autoRebase.md +5 -5
  40. package/dist/plugin/skills/muggle-status/SKILL.md +15 -6
  41. package/dist/plugin/skills/muggle-test/SKILL.md +1 -0
  42. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +5 -3
  43. package/dist/plugin/skills/muggle-test-prepare/steps/rebase-check.md +1 -1
  44. package/dist/release-manifest.json +3 -3
  45. package/dist/src-FRYBTZ6J.js +1 -0
  46. package/package.json +22 -9
  47. package/plugin/hooks/README.md +6 -1
  48. package/plugin/hooks/hooks.json +24 -0
  49. package/plugin/scripts/guardrail-capability-claim.sh +38 -0
  50. package/plugin/scripts/guardrail-comment-reply-gate.sh +42 -0
  51. package/plugin/scripts/guardrail-debug-path-gate.sh +1 -1
  52. package/plugin/scripts/guardrail-e2e-gate.sh +1 -1
  53. package/plugin/scripts/guardrail-record-comment-replies.sh +28 -0
  54. package/plugin/scripts/guardrail-resolve-gate.sh +22 -0
  55. package/plugin/scripts/guardrail-stage-gate.sh +1 -1
  56. package/plugin/scripts/guardrail-terminal-gate.sh +1 -1
  57. package/plugin/scripts/guardrail-walkthrough-gate.sh +1 -1
  58. package/plugin/scripts/guardrail-watch-gate.sh +1 -1
  59. package/plugin/scripts/guardrails.mjs +542 -34
  60. package/plugin/scripts/pr-watch-loop.sh +9 -24
  61. package/plugin/scripts/pr-watch-state.jq +52 -0
  62. package/plugin/skills/_shared/identity-and-inbox.md +39 -0
  63. package/plugin/skills/_shared/sync-branch-with-base.md +29 -0
  64. package/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +3 -2
  65. package/plugin/skills/_shared/vcs/github/unresolved-threads.md +2 -0
  66. package/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +3 -1
  67. package/plugin/skills/_shared/vcs/gitlab.md +0 -1
  68. package/plugin/skills/do/address-reviews.md +5 -2
  69. package/plugin/skills/do/build.md +9 -0
  70. package/plugin/skills/do/e2e-acceptance.md +2 -0
  71. package/plugin/skills/do/e2e-repair.md +98 -0
  72. package/plugin/skills/do/open-prs/forward.md +17 -9
  73. package/plugin/skills/do/open-prs/update.md +9 -2
  74. package/plugin/skills/do/per-comment-replies.md +13 -1
  75. package/plugin/skills/do/pre-flight.md +2 -2
  76. package/plugin/skills/do/resolve-conflicts.md +59 -59
  77. package/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  78. package/plugin/skills/muggle-do/SKILL.md +6 -5
  79. package/plugin/skills/muggle-pr-followup/arm-watcher.md +1 -1
  80. package/plugin/skills/muggle-preferences/preference-gates/autoRebase.md +5 -5
  81. package/plugin/skills/muggle-status/SKILL.md +15 -6
  82. package/plugin/skills/muggle-test/SKILL.md +1 -0
  83. package/plugin/skills/muggle-test-feature-local/SKILL.md +5 -3
  84. package/plugin/skills/muggle-test-prepare/steps/rebase-check.md +1 -1
  85. package/scripts/postinstall.mjs +94 -6
  86. package/scripts/release-integrity/compareVersions.mjs +24 -0
  87. package/scripts/release-integrity/constants.mjs +8 -0
  88. package/scripts/release-integrity/index.d.mts +21 -0
  89. package/scripts/release-integrity/index.mjs +4 -0
  90. package/scripts/release-integrity/resolveIntegrityPolicy.mjs +37 -0
  91. package/scripts/release-integrity/verifyReleaseSignature.mjs +51 -0
  92. package/dist/plugin/skills/_shared/rebase-before-e2e.md +0 -21
  93. package/dist/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +0 -10
  94. package/dist/src-B5OTAZKM.js +0 -1
  95. package/plugin/skills/_shared/rebase-before-e2e.md +0 -21
  96. package/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +0 -10
@@ -1,59 +1,59 @@
1
- # Rebase (watcher-dispatched)
2
-
3
- Rebase a PR's branch onto its base — whether it's merely **behind** (out of date, no conflict) or actually **conflicting** — behind a verify-or-rollback gate, then force-push, so a PR doesn't sit stale or un-mergeable forever. A dumb-pipe dispatch like fix-ci: the watcher detects the branch is behind or conflicting and hands off; the executor owns the rebase (and any conflict resolution), never the decision to dispatch.
4
-
5
- ## Turn preamble
6
-
7
- ```
8
- **/muggle-do rebase** — rebasing <owner>/<repo>#<n> onto <base> to bring the branch up to date.
9
- ```
10
-
11
- ## Input
12
-
13
- `$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and a `rebase` directive (no review ids, no failing check names). Parse all three.
14
-
15
- ## Inputs from disk
16
-
17
- From `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR + branch + `head_sha`), `last_seen.json` (`conflict_resolve_attempts`, `conflict_escalated_keys`, `pushed_shas`), `state.md` (worktree path, validation strategy, base branch).
18
-
19
- ## Procedure
20
-
21
- ### Step 1 — Re-attach
22
-
23
- Materialize the PR branch in its worktree per [`../_shared/pr-branch-worktree.md`](../_shared/pr-branch-worktree.md) (or use `state.md`'s `worktreePath`). Capture `rebase_sha = prs.json[0].head_sha` and the base branch (`baseRefName` from [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md)). Capture the base tip too — `.base_commit.sha` from that recipe's `compare` call — and form `rebase_key = "<rebase_sha>..<base_tip_sha>"`. Both budget fields below are keyed on that pair, matching the watcher's Step 5; writing a bare SHA instead leaves an entry the watcher ignores, and the rebase re-dispatches forever.
24
-
25
- ### Step 2 — Rebase onto base (resolve conflicts if any)
26
-
27
- Run the rebase from [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) against the base branch, taking its `always` path unconditionally — this programmatic mode never asks, so skip the `autoRebase` prompt (the watcher already decided a rebase is due).
28
-
29
- - **Clean replay** — a behind-only branch (and any rebase that hits no conflicts) replays without intervention. Proceed to Step 3.
30
- - **Conflicts** — handle per [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
31
- - `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub.
32
- - `always` → resolve via [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md); Steps 3 and 5 below are this mode's instance of the [`../_shared/verify-or-rollback-gate.md`](../_shared/verify-or-rollback-gate.md).
33
-
34
- ### Step 3 — Verify the resolution
35
-
36
- Build (typecheck + lint on the changed surface) + unit suite must pass. Run E2E per [`e2e-acceptance.md`](e2e-acceptance.md) when app logic changed and the session carries validation context. A rebase that does not verify is rolled back → escalate per Step 5. **Never push an unverified rebase.**
37
-
38
- ### Step 4 — Force-push + respawn
39
-
40
- Force-push per [`../_shared/vcs/common/push-to-branch.md`](../_shared/vcs/common/push-to-branch.md) (tool-agnostic instruction; handles the signing gate for rebased commits and directs to provider-specific force-push paths). Use `git push --force-with-lease` for the force operation. Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_key]` — both whole-file rewrites (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md), never the Edit tool. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md). Its next tick re-checks the branch against its base on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
41
-
42
- ### Step 5 — Escalate (can't resolve / budget spent)
43
-
44
- When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[rebase_key]` has reached 2:
45
-
46
- 1. Add `rebase_key` to `last_seen.conflict_escalated_keys` so the watcher does not re-dispatch this head against this base. If the base later moves, the key changes and the branch re-arms on its own — a conflict the user resolved upstream stops being this watcher's dead end.
47
- 2. Emit one terminal escalation naming the PR and the conflicting files (or the failing verification, for a behind-only rebase that didn't verify).
48
- 3. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md) — it keeps polling for the user's manual resolution or any new reviews.
49
-
50
- ### Step 6 — Telemetry
51
-
52
- Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.md`](../_shared/telemetry-events/muggle-do-cycle.md)) with `outcome: "rebased"` (a verified rebase pushed — behind-only or conflicts resolved) or `"rebase-escalated"`.
53
-
54
- ## Guardrails
55
-
56
- - Max 2 rebase attempts per SHA; then escalate rather than churn.
57
- - Never push an unverified rebase — verify-or-rollback always.
58
- - Never push unsigned commits — every push or force-push goes through the signing gate in [`../_shared/vcs/common/push-to-branch.md`](../_shared/vcs/common/push-to-branch.md).
59
- - Resolve `autoResolveConflicts` from the configured preference (per the gate contract — don't assume a default): `always` resolves conflicts behind the verify-or-rollback gate, `never` escalates to the user. A clean behind-only rebase needs neither.
1
+ # Rebase (watcher-dispatched)
2
+
3
+ Rebase a PR's branch onto its base — whether it's merely **behind** (out of date, no conflict) or actually **conflicting** — behind a verify-or-rollback gate, then force-push, so a PR doesn't sit stale or un-mergeable forever. A dumb-pipe dispatch like fix-ci: the watcher detects the branch is behind or conflicting and hands off; the executor owns the rebase (and any conflict resolution), never the decision to dispatch.
4
+
5
+ ## Turn preamble
6
+
7
+ ```
8
+ **/muggle-do rebase** — rebasing <owner>/<repo>#<n> onto <base> to bring the branch up to date.
9
+ ```
10
+
11
+ ## Input
12
+
13
+ `$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and a `rebase` directive (no review ids, no failing check names). Parse all three.
14
+
15
+ ## Inputs from disk
16
+
17
+ From `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR + branch + `head_sha`), `last_seen.json` (`conflict_resolve_attempts`, `conflict_escalated_keys`, `pushed_shas`), `state.md` (worktree path, validation strategy, base branch).
18
+
19
+ ## Procedure
20
+
21
+ ### Step 1 — Re-attach
22
+
23
+ Materialize the PR branch in its worktree per [`../_shared/pr-branch-worktree.md`](../_shared/pr-branch-worktree.md) (or use `state.md`'s `worktreePath`). Capture `rebase_sha = prs.json[0].head_sha` and the base branch (`baseRefName` from [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md)). Capture the base tip too — `.base_commit.sha` from that recipe's `compare` call — and form `rebase_key = "<rebase_sha>..<base_tip_sha>"`. Both budget fields below are keyed on that pair, matching the watcher's Step 5; writing a bare SHA instead leaves an entry the watcher ignores, and the rebase re-dispatches forever.
24
+
25
+ ### Step 2 — Rebase onto base (resolve conflicts if any)
26
+
27
+ Run the rebase from [`../_shared/sync-branch-with-base.md`](../_shared/sync-branch-with-base.md) against the base branch, taking its `always` path unconditionally — this programmatic mode never asks, so skip the `autoRebase` prompt (the watcher already decided a rebase is due).
28
+
29
+ - **Clean replay** — a behind-only branch (and any rebase that hits no conflicts) replays without intervention. Proceed to Step 3.
30
+ - **Conflicts** — handle per [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
31
+ - `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub.
32
+ - `always` → resolve via [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md); Steps 3 and 5 below are this mode's instance of the [`../_shared/verify-or-rollback-gate.md`](../_shared/verify-or-rollback-gate.md).
33
+
34
+ ### Step 3 — Verify the resolution
35
+
36
+ Build (typecheck + lint on the changed surface) + unit suite must pass. Run E2E per [`e2e-acceptance.md`](e2e-acceptance.md) when app logic changed and the session carries validation context. A rebase that does not verify is rolled back → escalate per Step 5. **Never push an unverified rebase.**
37
+
38
+ ### Step 4 — Force-push + respawn
39
+
40
+ Force-push per [`../_shared/vcs/common/push-to-branch.md`](../_shared/vcs/common/push-to-branch.md) (tool-agnostic instruction; handles the signing gate for rebased commits and directs to provider-specific force-push paths). Use `git push --force-with-lease` for the force operation. Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_key]` — both whole-file rewrites (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md), never the Edit tool. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md). Its next tick re-checks the branch against its base on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
41
+
42
+ ### Step 5 — Escalate (can't resolve / budget spent)
43
+
44
+ When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[rebase_key]` has reached 2:
45
+
46
+ 1. Add `rebase_key` to `last_seen.conflict_escalated_keys` so the watcher does not re-dispatch this head against this base. If the base later moves, the key changes and the branch re-arms on its own — a conflict the user resolved upstream stops being this watcher's dead end.
47
+ 2. Emit one terminal escalation naming the PR and the conflicting files (or the failing verification, for a behind-only rebase that didn't verify).
48
+ 3. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md) — it keeps polling for the user's manual resolution or any new reviews.
49
+
50
+ ### Step 6 — Telemetry
51
+
52
+ Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.md`](../_shared/telemetry-events/muggle-do-cycle.md)) with `outcome: "rebased"` (a verified rebase pushed — behind-only or conflicts resolved) or `"rebase-escalated"`.
53
+
54
+ ## Guardrails
55
+
56
+ - Max 2 rebase attempts per SHA; then escalate rather than churn.
57
+ - Never push an unverified rebase — verify-or-rollback always.
58
+ - Never push unsigned commits — every push or force-push goes through the signing gate in [`../_shared/vcs/common/push-to-branch.md`](../_shared/vcs/common/push-to-branch.md).
59
+ - Resolve `autoResolveConflicts` from the configured preference (per the gate contract — don't assume a default): `always` resolves conflicts behind the verify-or-rollback gate, `never` escalates to the user. A clean behind-only rebase needs neither.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-browser-task
3
- description: "Perform a real action on a website or web app from a plain-English instruction — log in and do the thing: submit a form, create or update a record, send a message, place an order, refund a charge, file a ticket, publish a post, complete a multi-step flow. Muggle drives a real browser through the site's own UI and supplies the session — managed login profiles and stored credentials — so a named service (Slack, Jira, Stripe, WordPress, a marketplace, a booking site, an admin panel) is a signal to reach for this skill, not to hunt for its API or connector; \"no logged-in session\" is not a reason to decline. Use whenever the user wants something done in a web UI ('log into X and …', 'send the message', 'create the ticket'), rather than declining or doing it by hand; ask for missing specifics inside the flow, not instead of starting it. Boundary: this performs the action (operating a site's own UI), not migrating personal data between consumer apps; to verify a flow works instead, use muggle-test-feature-local."
3
+ description: "Perform a real action on a website or web app from a plain-English instruction — log in and do the thing: submit a form, create or update a record, send a message, place an order, refund a charge, file a ticket, publish a post, complete a multi-step flow. Muggle drives a real browser through the site's own UI and supplies the session — managed login profiles, stored credentials, and a live inbox per profile that reads magic links, emailed OTPs, and email 2FA — so a named service (Slack, Jira, Stripe, WordPress, a marketplace, a booking site, an admin panel) is a signal to reach for this skill, not to hunt for its API or connector; \"no logged-in session\" and \"the code goes to an email\" are not reasons to decline. Use whenever the user wants something done in a web UI ('log into X and …', 'send the message', 'create the ticket'), rather than declining or doing it by hand; ask for missing specifics inside the flow, not instead of starting it. Boundary: this performs the action (operating a site's own UI), not migrating personal data between consumer apps; to verify a flow works instead, use muggle-test-feature-local."
4
4
  ---
5
5
 
6
6
  # Muggle Test Task Runner
@@ -20,9 +20,10 @@ Runs an autonomous dev cycle from requirements to PR. **Fire and review:** user
20
20
  | 5 | Unit tests | [`../do/unit-tests.md`](../do/unit-tests.md) |
21
21
  | 6 | E2E acceptance | [`../do/e2e-acceptance.md`](../do/e2e-acceptance.md) |
22
22
  | 7 | Create or update PR | [`../do/open-prs.md`](../do/open-prs.md) |
23
+ | 7.5 | E2E repair loop | [`../do/e2e-repair.md`](../do/e2e-repair.md) |
23
24
  | 8 | Hand off to watcher | [`../muggle-pr-followup/SKILL.md`](../muggle-pr-followup/SKILL.md) |
24
25
 
25
- Stage 7 dispatches one watcher per opened PR as its last action.
26
+ Stage 7 dispatches one watcher per opened PR as its last action, gated on Stage 7.5's clearance — a cycle with unrepaired E2E failures never reaches a watcher.
26
27
 
27
28
  ## Execution protocol (non-negotiable)
28
29
 
@@ -31,12 +32,12 @@ The pipeline table lists **pointers, not summaries**. Open each stage's file and
31
32
  **Bootstrap before any code, in order:**
32
33
  1. Emit telemetry — [`../_shared/telemetry-emit.md`](../_shared/telemetry-emit.md), `skillName: "muggle-do"`.
33
34
  2. Create `~/.muggle-ai/muggle-do/sessions/<slug>/` with `state.md` + `iterations/001.md` (pre-flight owns this; do it even when running unattended).
34
- 3. `TodoWrite` one item per stage 1–8 — these stages are the checklist; never swap in your own decomposition.
35
+ 3. `TodoWrite` one item per stage 1–8, Stage 7.5 included — these stages are the checklist; never swap in your own decomposition.
35
36
 
36
37
  **Per stage:** read the file → execute it → append a marker to `iterations/<NNN>.md` citing the evidence that file requires (jest exit code, E2E verdict + `runId`, screenshot path). A stage is done only when its evidence is written, never on recollection.
37
38
 
38
39
  ### "Autonomous" / "without my intervention" collapses exactly one thing
39
- Best-effort the Stage-1 questionnaire and don't ask. It does **not** license skipping telemetry, session artifacts, requirements, unit tests, E2E (`autoE2ETest` defaults to `always`), browser verification, the gate below, or the watcher hand-off. Run the whole pipeline silently — never a shortcut.
40
+ Best-effort the Stage-1 questionnaire and don't ask. It does **not** license skipping telemetry, session artifacts, requirements, unit tests, E2E (`autoE2ETest` defaults to `always`), browser verification, the gate below, the Stage-7.5 repair loop, or the watcher hand-off. Stage 7.5 investigates and repairs unattended too; only its user decision collapses, into an automatic waive. Run the whole pipeline silently — never a shortcut.
40
41
 
41
42
  ### Definition of Done — gate before Stage 7
42
43
  Do not create or update a PR until each line holds, or is waived by a one-line reason written into `state.md` (silence is not a waiver):
@@ -81,7 +82,7 @@ When invoked with the directive (PR URL + slug + review ids), routes to [`../do/
81
82
 
82
83
  ## Guardrails
83
84
 
84
- - Stage 1 is the only user-facing forward stage. Stages 2–7 don't ask mid-cycle; blocker → pre-flight bug.
85
+ - Stage 1 and Stage 7.5's user decision are the only user-facing forward stages. Stages 2–7 don't ask mid-cycle; blocker → pre-flight bug.
85
86
  - Same stage failing 3× → escalate.
86
- - 3 cycle iterations reach E2E with failures → ship with `[E2E FAILING]`.
87
+ - 3 repair iterations reach E2E with failures → waive, ship with `[E2E FAILING]`, and hand to the watcher.
87
88
  - Address-reviews escalation (ambiguous or design-adjustment) does not block the watcher; user resolves on GitHub.
@@ -14,7 +14,7 @@ How an orchestrating session starts the watch on one PR. Every arming point runs
14
14
  bash "<abs>/scripts/pr-watch-loop.sh" --slot "<slot>" --repo "<owner>/<repo>" --pr <n> --base <base-branch>
15
15
  ```
16
16
 
17
- **Never author a per-slot `watch.sh`.** The loop ships as [`../../scripts/pr-watch-loop.sh`](../../scripts/pr-watch-loop.sh), with its wake conditions in [`../../scripts/pr-watch-events.sh`](../../scripts/pr-watch-events.sh); arming runs it and passes arguments. Writing the loop from this prose was how it drifted — each arm produced an independent derivation, and a derivation that quietly dropped a wake still ran, still heartbeat, still logged, and simply never fired for the signal it lost. Two slots on one machine ended up without the behind-base wake, which left their PRs unmergeable under watchers that looked healthy. The prose below says *why* each wake exists; the shipped files are the only definition of *what* fires. A slot holding a legacy generated `watch.sh` keeps it until re-armed, at which point the supersede guard retires the old loop.
17
+ **Never author a per-slot `watch.sh`.** The loop ships as [`../../scripts/pr-watch-loop.sh`](../../scripts/pr-watch-loop.sh), with its wake conditions in [`../../scripts/pr-watch-events.sh`](../../scripts/pr-watch-events.sh) and its state projection in [`../../scripts/pr-watch-state.jq`](../../scripts/pr-watch-state.jq); arming runs it and passes arguments. Writing the loop from this prose was how it drifted — each arm produced an independent derivation, and a derivation that quietly dropped a wake still ran, still heartbeat, still logged, and simply never fired for the signal it lost. Two slots on one machine ended up without the behind-base wake, which left their PRs unmergeable under watchers that looked healthy. The prose below says *why* each wake exists; the shipped files are the only definition of *what* fires. A slot holding a legacy generated `watch.sh` keeps it until re-armed, at which point the supersede guard retires the old loop.
18
18
 
19
19
  The label is `PR #<n> — <title>`. Label and command both matter: some task surfaces show one, some the other, and a slot-bearing command keeps the watch identifiable everywhere a raw script blob would not. One monitor per PR, alive from arm to terminal: it is the watch's visible handle, showing as a running task the entire time the PR is polled. Its loop checks about every 60 seconds, re-reading the watermark and touching the slot's `watch-heartbeat` file each iteration — the liveness beacon that tells [`reconcile.md`](reconcile.md) a quiet watch is still alive; on a newer submitted review, a newer thread comment (`gitlab`: a newer discussion note), a thread newly unresolved (`gitlab`: discussion), **the head SHA's checks settling red** (no check pending and one or more in the `fail` bucket per [`../_shared/vcs/common/ci-rollup.md`](../_shared/vcs/common/ci-rollup.md)), **the branch falling behind or conflicting with its base** (`behind_by > 0` or the conflict signal per [`../_shared/vcs/common/branch-standing.md`](../_shared/vcs/common/branch-standing.md)), or — **only while the watch is blocked** (`BLOCKED_CIDIGEST` non-empty) — **the head's CI digest changing in any way** (not just to red) — it prints one line and **keeps watching**, advancing its in-memory floor so each event fires the tick exactly once. The review and thread floors are monotonic ids; the other three are not. The CI-red floor is the **head SHA**, because the check rollup is non-monotonic — it flips green↔red and resets on every push — so recording the red head SHA fires CI once per red head, and a later push re-arms it on the new SHA. The rebase floor (`REBASED`) is the **`rebase_key`** — `<head_sha>..<base_tip_sha>` — because staleness is a function of both sides: keying on the pair fires once per newly-due pair and re-arms when either the head or the base moves, where a head-only key would wedge permanently the first time the base advances (the head cannot change while nobody pushes). A head whose checks are still **pending** is never a red wake, and a branch with `behind_by == 0` and `mergeable == UNKNOWN` is never a rebase wake: pending checks may yet go green and conflict state is still computing, and the tick would idle on either (Steps 5–6) regardless. The blocked-CI signal is different in kind — a **resume** probe, live only while the watch is blocked: it wakes on any move of the head's CI digest (the same bucket-plus-sorted-name/conclusion signature the blocked fingerprint records — [`blocked-tick.md`](blocked-tick.md)) away from `BLOCKED_CIDIGEST`, so a block waiting on a green pass, a rerun, or an external deploy check resumes as promptly as one waiting on red. Quiet iterations print nothing and cost nothing — no model tokens are spent while the watch is quiet.
20
20
 
@@ -1,11 +1,11 @@
1
1
  # `autoRebase`
2
2
 
3
- Rebase onto `origin/{default}` before dev servers / E2E, or run as-is. Substitute `{behind}` and `{default}`.
3
+ Sync onto `origin/{base}` before dev servers / E2E and before a push, or run as-is. Substitute `{behind}` and `{base}`.
4
4
 
5
- **Picker 1** — header `Rebase first`, question `"Branch is {behind} commits behind origin/{default} — rebase first?"`
6
- - `Rebase first` — `Pull in default-branch changes so the run reflects the merged main line.` → `always`
5
+ **Picker 1** — header `Rebase first`, question `"Branch is {behind} commits behind origin/{base} — rebase first?"`
6
+ - `Rebase first` — `Pull in the base branch's changes so the run reflects the merged main line.` → `always`
7
7
  - `Run as-is` — `Skip the rebase.` → `never`
8
8
 
9
9
  **Silent action**
10
- - `always` → `Rebasing onto origin/{default} ({behind} behind)`
11
- - `never` → `Running as-is ({behind} behind origin/{default})`
10
+ - `always` → `Rebasing onto origin/{base} ({behind} behind)`
11
+ - `never` → `Running as-is ({behind} behind origin/{base})`
@@ -16,17 +16,25 @@ Gates run per `preference-gates/README.md`.
16
16
 
17
17
  | Preference | Step | Decision it gates |
18
18
  |------------|------|-------------------|
19
- | `checkForUpdates` | Check 4 | Check for newer Muggle Test version |
19
+ | `checkForUpdates` | Check 5 | Check for newer Muggle Test version |
20
20
 
21
21
  ## Checks
22
22
 
23
- 1. **Electron app** — read `~/.muggle-ai/electron-app/` to find the installed version directory. Read `.install-metadata.json` to get version and checksum. Verify the binary exists at the expected path. On macOS, check code signing with `spctl --assess --verbose`.
23
+ 1. **Release ring** — run `muggle status` and read its `Runtime target:` and `Backend:` lines. Report the ring and the backend it resolves to.
24
24
 
25
- 2. **MCP server** — call `muggle-local-check-status` to verify the server is responsive. Report auth state (authenticated, email, token expiry).
25
+ This is the first check because it reframes every other one: an install on a non-production ring talks to a different backend, authenticates against a different tenant, and runs a different studio binary, so "is it healthy" cannot be answered without it.
26
26
 
27
- 3. **Authentication** — call `muggle-remote-auth-status`. Report whether credentials are valid and when they expire.
27
+ - `production` → render as `[pass]`.
28
+ - Any other ring → render as `[note]`, not a failure. A staging or dev install is a deliberate state, not a fault, but it must be visible: it is the explanation for auth and backend behaviour that would otherwise look broken.
29
+ - If `MUGGLE_MCP_PROMPT_SERVICE_TARGET` is set in the environment, say so and name its value. It overrides the ring baked in at publish time, it is easy to leave set from an earlier shell, and it explains a ring that disagrees with the installed package.
28
30
 
29
- 4. **CLI version** — gate `checkForUpdates` (per `preference-gates/README.md`):
31
+ 2. **Electron app** — read `~/.muggle-ai/electron-app/` to find the installed version directory. Non-production rings install to a ring-suffixed directory (`<version>-staging`), so match the directory for the ring reported in Check 1 rather than assuming the bare version — the streams publish the same version deliberately, and the bare directory belongs to production. Read `.install-metadata.json` to get version and checksum. Verify the binary exists at the expected path. On macOS, check code signing with `spctl --assess --verbose`.
32
+
33
+ 3. **MCP server** — call `muggle-local-check-status` to verify the server is responsive. Report auth state (authenticated, email, token expiry).
34
+
35
+ 4. **Authentication** — call `muggle-remote-auth-status`. Report whether credentials are valid and when they expire.
36
+
37
+ 5. **CLI version** — gate `checkForUpdates` (per `preference-gates/README.md`):
30
38
  - `always` → run the check below.
31
39
  - `never` → render the row as `[skip] check disabled by preference`.
32
40
  - `ask` → run Picker 1 from `preference-gates/checkForUpdates.md` via `AskUserQuestion`; map the answer back to one of the actions above.
@@ -38,6 +46,7 @@ Gates run per `preference-gates/README.md`.
38
46
  ```
39
47
  Muggle AI — Status
40
48
 
49
+ Release ring [pass/note] ring, backend URL
41
50
  Electron app [pass/fail] version, binary status
42
51
  MCP server [pass/fail] responsive, auth state
43
52
  Authentication [pass/fail] user, expiry
@@ -46,4 +55,4 @@ CLI version [pass/warn] installed → latest
46
55
  [All systems operational / Issues found — run /muggle:muggle-repair to fix.]
47
56
  ```
48
57
 
49
- Use pass/fail indicators for each check. If any check fails, tell the user to run `/muggle:muggle-repair`. If the CLI version check warns (installed < latest), tell the user to run `/muggle:muggle-upgrade`.
58
+ Use pass/fail indicators for each check; the release ring uses `[pass]` on production and `[note]` elsewhere, never `[fail]`. When the ring is not production, state it in the closing line too, so it is not lost in a table the reader skims. If any check fails, tell the user to run `/muggle:muggle-repair`. If the CLI version check warns (installed < latest), tell the user to run `/muggle:muggle-upgrade`.
@@ -4,6 +4,7 @@ description: "Change-driven E2E acceptance testing: detects the user's recent ch
4
4
  mandatoryStages:
5
5
  - ../_shared/failure-mode-handling.md
6
6
  - ../_shared/debug-failed-run.md
7
+ - ../_shared/identity-and-inbox.md
7
8
  ---
8
9
 
9
10
  # Muggle Test — Change-Driven E2E Acceptance Router
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: muggle-test-feature-local
3
- description: Run a real-browser end-to-end (E2E) acceptance test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. Use this skill whenever the user asks to test, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'E2E' explicitly.
3
+ description: Run a real-browser end-to-end (E2E) acceptance test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. The browser runs under a real identity: each managed login profile owns a live inbox, so magic-link and sign-in-link logins, emailed OTPs, email-based 2FA, verification mail, and password resets are all testable without a mail stub or a test-only backdoor — an email-gated flow is never a reason to call something untestable. Use this skill whenever the user asks to test, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'E2E' explicitly.
4
+ mandatoryStages:
5
+ - ../_shared/identity-and-inbox.md
4
6
  ---
5
7
 
6
8
  # Muggle Test Feature Local
@@ -22,7 +24,7 @@ The local URL only changes where the browser opens; it does not change the remot
22
24
  Three gates apply, each per the standard procedure in [`preference-gates/README.md`](../muggle-preferences/preference-gates/README.md):
23
25
 
24
26
  - `autoUseWorktree` at pre-flight (see [`_shared/use-worktrees.md`](../_shared/use-worktrees.md)).
25
- - `autoRebase` before Step 7 (Execute) when `behind > 0` (see [`_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md)).
27
+ - `autoRebase` before Step 7 (Execute) when `behind > 0` (see [`_shared/sync-branch-with-base.md`](../_shared/sync-branch-with-base.md)).
26
28
  - `autoCleanup` after the PR is merged (see [`_shared/post-merge-cleanup.md`](../_shared/post-merge-cleanup.md)).
27
29
 
28
30
  ## Local environment prerequisites
@@ -48,7 +50,7 @@ Gates run per `preference-gates/README.md`.
48
50
  | `autoSelectProject` | 2 | Reuse last-used Muggle Test project for this repo |
49
51
  | `autoSelectLocalHost` | 4 | Reuse last-used local dev server URL for this repo |
50
52
  | `autoUseWorktree` | 0 (pre-flight) | Isolate dev work in a worktree (see [`_shared/use-worktrees.md`](../_shared/use-worktrees.md)) |
51
- | `autoRebase` | 0 (pre-flight) | Rebase onto `origin/<default>` before Step 7 (Execute) (see [`_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md)) |
53
+ | `autoRebase` | 0 (pre-flight) | Rebase onto `origin/<default>` before Step 7 (Execute) (see [`_shared/sync-branch-with-base.md`](../_shared/sync-branch-with-base.md)) |
52
54
  | `showElectronBrowser` | 7 | Show Electron browser window during local E2E tests |
53
55
  | `openTestResultsAfterRun` | 8 | Open results page on Muggle Test dashboard after run |
54
56
  | `postPRVisualWalkthrough` | 10 | Post visual walkthrough to PR after results |
@@ -1,3 +1,3 @@
1
1
  # Rebase check
2
2
 
3
- Gated by `autoRebase`. Fire per [`../../_shared/rebase-before-e2e.md`](../../_shared/rebase-before-e2e.md) when `behind > 0`. Otherwise skip silently.
3
+ Gated by `autoRebase`. Fire per [`../../_shared/sync-branch-with-base.md`](../../_shared/sync-branch-with-base.md) when `behind > 0`. Otherwise skip silently.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.11.0",
3
- "buildId": "run-72-1",
4
- "commitSha": "e24d1a3b4178fa964462ba6bc77d8fee86791cdc",
5
- "buildTime": "2026-08-16T07:54:34Z",
3
+ "buildId": "run-74-1",
4
+ "commitSha": "d2d28543763361dcae7b5bef1c31ca4ae75df64c",
5
+ "buildTime": "2026-08-26T20:14:09Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -0,0 +1 @@
1
+ export { DEFAULT_PREFERENCES, ElectronAppReleaseStream, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RuntimeTarget, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, reconcileProjectPreferences, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveElectronAppPathOrNull, resolvePreferences, resolveRuntimeTarget, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-DJ6W5PZA.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@muggleai/works",
3
3
  "mcpName": "io.github.multiplex-ai/muggle",
4
- "version": "5.11.1",
4
+ "version": "5.12.0-staging.74",
5
5
  "description": "Ship quality products with AI-powered E2E acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -12,7 +12,9 @@
12
12
  "dist",
13
13
  "plugin",
14
14
  "bin/muggle.js",
15
- "scripts/postinstall.mjs"
15
+ "scripts/postinstall.mjs",
16
+ "scripts/release-integrity",
17
+ "config/runtime-targets.json"
16
18
  ],
17
19
  "scripts": {
18
20
  "clean": "rimraf dist",
@@ -50,13 +52,23 @@
50
52
  "muggleConfig": {
51
53
  "electronAppVersion": "1.9.0",
52
54
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
53
- "runtimeTargetDefault": "production",
54
- "checksums": {
55
- "darwin-arm64": "1baa38bde71b74984705bcac3e5a42a7a158a7b0574dab496f3e240846c0ac65",
56
- "darwin-x64": "098cc2db6f32c7857bd933512320524d213804b378dc8bf0ade791396f058041",
57
- "linux-x64": "d434a88a087c7410785a443cfe03102322fa7a295b77600f5ed92e2113e271c2",
58
- "win32-x64": "0aecbc01010ab0a268137ed31bcaaa05fe9215daa9f4c0e4871b07554d28c0c8"
59
- }
55
+ "runtimeTargetDefault": "staging",
56
+ "checksumsByStream": {
57
+ "production": {
58
+ "darwin-arm64": "1baa38bde71b74984705bcac3e5a42a7a158a7b0574dab496f3e240846c0ac65",
59
+ "darwin-x64": "098cc2db6f32c7857bd933512320524d213804b378dc8bf0ade791396f058041",
60
+ "linux-x64": "d434a88a087c7410785a443cfe03102322fa7a295b77600f5ed92e2113e271c2",
61
+ "win32-x64": "0aecbc01010ab0a268137ed31bcaaa05fe9215daa9f4c0e4871b07554d28c0c8"
62
+ },
63
+ "staging": {
64
+ "darwin-x64": "754bbdaa9ccb0f10bfd92c0f5efd7877c4635708b8a7dde8bbf76bc6c74598b1",
65
+ "darwin-arm64": "8f8854788769971af4ad5c83531d595951f3c0306d3ddcc9b6b8cb5a5ccd375d",
66
+ "linux-x64": "929396f628a05f001782d48524cd9f76fdbb59216658982a5a3909dfe413cbea",
67
+ "win32-x64": "a8b5b46b97ed788030be84982a3e8d463abbfa7f3e8bc795692ccbffc82bfa92"
68
+ }
69
+ },
70
+ "electronAppSignedFromVersion": "1.10.0",
71
+ "signerIdentityUri": "https://github.com/multiplex-ai/muggle-ai-teaching-service/.github/workflows/release-electron-app-reusable.yml@refs/heads/master"
60
72
  },
61
73
  "dependencies": {
62
74
  "@modelcontextprotocol/sdk": "^1.25.3",
@@ -64,6 +76,7 @@
64
76
  "axios": "^1.7.9",
65
77
  "commander": "^14.0.3",
66
78
  "open": "^11.0.0",
79
+ "sigstore": "^5.0.0",
67
80
  "ulid": "^3.0.2",
68
81
  "uuid": "^14.0.0",
69
82
  "winston": "^3.17.0",
@@ -24,9 +24,11 @@ Enforcement is reserved for the handoffs that were being skipped: the E2E accept
24
24
 
25
25
  ## Mechanism
26
26
 
27
+ A recorder that *clears* an obligation — a walkthrough posted, a failure diagnosed, a test case classified — records only when the call did not visibly fail (`callOutcome.ts`). Recording from the request alone let a rejected `gh pr comment` mark the walkthrough posted, so the gate went quiet on a PR that never received it.
28
+
27
29
  Each guardrail is a thin bash wrapper in `../scripts/` registered in `hooks.json`. The wrapper pipes the event payload (stdin JSON) to the bundled `../scripts/guardrails.mjs <subcommand>`, which holds the decision logic (built from `src/guardrails/`, vitest-covered). Per-session state in `~/.muggle-ai/guardrails/<session_id>.json` tracks what fired. Any *failure* degrades to `{}` (allow) — a gate blocks only by an explicit, tested decision, never by accident.
28
30
 
29
- Each wrapper short-circuits in shell first, so the common case never pays Node cold-start. That pre-filter is a second, looser copy of what `guardrails.mjs` matches, and it is the one place a guardrail can fail *silently*: a payload it drops — a skip marker, a reopen line, a comment edit — reaches no recorder, and the gate keeps demanding an action the user already took. Over-matching is free; under-matching is a dead escape hatch. `src/test/guardrails/hook-prefilter.test.ts` pins every payload each subcommand acts on against the wrapper guarding it, and derives the skip-marker tokens from source so a new marker is covered the moment it exists.
31
+ Each wrapper short-circuits in shell first, so the common case never pays Node cold-start. A gate that has spent its block budget stamps a `<gate>Released` flag, which its wrapper then pre-filters on — without it a released gate keeps cold-starting Node on every remaining turn end to answer `{}`, and the walkthrough gate keeps making provider calls to do it. That pre-filter is a second, looser copy of what `guardrails.mjs` matches, and it is the one place a guardrail can fail *silently*: a payload it drops — a skip marker, a reopen line, a comment edit — reaches no recorder, and the gate keeps demanding an action the user already took. Over-matching is free; under-matching is a dead escape hatch. `src/test/guardrails/hook-prefilter.test.ts` pins every payload each subcommand acts on against the wrapper guarding it, and derives the skip-marker tokens from source so a new marker is covered the moment it exists.
30
32
 
31
33
  ## Guardrails
32
34
 
@@ -39,6 +41,7 @@ Each wrapper short-circuits in shell first, so the common case never pays Node c
39
41
  | `PostToolUse` (Skill + muggle telemetry-skill-emit) | `guardrail-skill-stages.sh` | advise | a skill this plugin ships was invoked; its `SKILL.md` frontmatter may declare `mandatoryStages`. Registered on the skill's own MCP announcement too, so the declaration still lands if the Skill payload ever stops carrying a name | — | record `lastInvokedSkillName` + `mandatoryStages`, and name the declared files as required reading at the moment of use |
40
42
  | `PostToolUse` (Read) | `guardrail-record-stage-read.sh` | record | a markdown file under a `skills/` directory was opened | — | append to `stagesRead` — how the stage gate tells a stage that was read from one that was skipped |
41
43
  | `PostToolUse` (Bash + muggle telemetry-event-emit / user-feedback-create) | `guardrail-record-stage-signals.sh` | record | a Step 6f `pre-execution-classification` emit, debug-path evidence for a failed run (a `*-failure-classified\|resolved` emit or feedback naming it), or a `MUGGLE_STAGE_SKIP` / `MUGGLE_CLASSIFY_SKIP` / `MUGGLE_DEBUG_SKIP` marker | — | set `classifiedTestCaseIds` / `debuggedRuns` / the matching skip flag |
44
+ | `PostToolUse` (Bash) | `guardrail-record-comment-replies.sh` | record | an unresolved-thread fetch named review threads still awaiting an answer (classified by the `<!-- muggle-do:bot -->` marker, never the author login), a threaded reply the provider **confirmed** answered comments in one, or a `MUGGLE_REPLY_SKIP` marker deferred them | — | claim each thread and cover the comments answered, in the per-PR ledger beside the muggle-do slot |
42
45
  | `PreToolUse` (Bash) | `guardrail-report-format.sh` | **enforce** | a `gh pr comment\|create\|edit` body — or a `gh api … issues/comments/<id>` PATCH editing one — reads like an E2E report but lacks the `build-pr-section` sentinel | — | **deny** — render via `muggle build-pr-section` instead |
43
46
  | `PreToolUse` (muggle local execute/replay) | `guardrail-classify-gate.sh` | **enforce** | `muggle-test` is the skill in play and the target test case has no `pre-execution-classification` this session | — | **deny** — run Step 6f first (it calls `muggle-remote-test-script-list`, which is where the run learns the case has never passed) or record a legitimate skip via `MUGGLE_CLASSIFY_SKIP`. Scoped to `muggle-test`, so the single-target skills that legitimately skip classification are untouched; fails open when the test case can't be resolved |
44
47
  | `Stop` | `guardrail-e2e-gate.sh` | **enforce** | unit tests passed this session, no E2E ran yet, and no skip was recorded | `autoE2ETest` | **block** the turn until E2E runs via `muggle-test` or a `MUGGLE_E2E_SKIP` marker records a legitimate skip (full message once, one-line reminders after; releases after 3 blocks) |
@@ -47,6 +50,8 @@ Each wrapper short-circuits in shell first, so the common case never pays Node c
47
50
  | `Stop` | `guardrail-walkthrough-gate.sh` | **enforce** | an E2E acceptance run happened this session and a PR in play — opened this session or on the working branch — carries no walkthrough in its body or comments | `postPRVisualWalkthrough` | **block** the turn until the walkthrough is posted via `muggle-pr-visual-walkthrough` or a `MUGGLE_WALKTHROUGH_SKIP` marker records a legitimate skip (releases after 3 blocks; an unreachable PR fails open to not-owed) |
48
51
  | `Stop` | `guardrail-stage-gate.sh` | **enforce** | a skill invoked this session declared `mandatoryStages` and one of those files was never opened | — | **block** the turn until the stages are read or a `MUGGLE_STAGE_SKIP` marker records a legitimate skip (releases after 3 blocks) |
49
52
  | `Stop` | `guardrail-debug-path-gate.sh` | **enforce** | a local execution returned a non-passing run this session and nothing routed it through `_shared/debug-failed-run.md` | — | **block** the turn until the run is diagnosed (its `*-failure-classified` emit or feedback naming it) or a `MUGGLE_DEBUG_SKIP: <runId> <reason>` marker clears that run (releases after 3 blocks) |
53
+ | `Stop` | `guardrail-comment-reply-gate.sh` | **enforce** | this session claimed a review thread and left one of its comments uncovered | — | **block** the turn until each comment gets its threaded reply per `do/per-comment-replies.md` or a `MUGGLE_REPLY_SKIP: <comment-id> <reason>` marker defers it (releases after 3 blocks). The claim is the signal, not a push, so a round that answers a question with no code change is caught too; a thread another session claimed never blocks this one |
54
+ | `Stop` | `guardrail-capability-claim.sh` | **enforce** | the closing turn tells the user an email- or login-gated flow can't be tested, reached, or verified | — | **block** once, citing `_shared/identity-and-inbox.md`: the managed profile's live inbox, stored credentials, and CAPTCHA solver clear exactly that blocker. Reads the claim out of the transcript, since a Stop payload carries no message text. Per-sentence matching, and a sentence naming SMS/phone OTP or authenticator TOTP never fires — those limits are real. Nudges once per session, then stays quiet, so a misread costs one turn |
50
55
  | `UserPromptSubmit` | `guardrail-build-router.sh` | advise | a build/implement/fix request (first one this session) | `autoRouteBuildToMuggleDo` | route the work through `muggle-do` (build delegated to superpowers) |
51
56
 
52
57
  ## Session-start reconcile nudge
@@ -34,6 +34,12 @@
34
34
  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-report-format.sh\"",
35
35
  "async": false,
36
36
  "timeout": 10
37
+ },
38
+ {
39
+ "type": "command",
40
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-resolve-gate.sh\"",
41
+ "async": false,
42
+ "timeout": 10
37
43
  }
38
44
  ]
39
45
  },
@@ -76,6 +82,12 @@
76
82
  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-stage-signals.sh\"",
77
83
  "async": false,
78
84
  "timeout": 10
85
+ },
86
+ {
87
+ "type": "command",
88
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-comment-replies.sh\"",
89
+ "async": false,
90
+ "timeout": 10
79
91
  }
80
92
  ]
81
93
  },
@@ -184,6 +196,12 @@
184
196
  "async": false,
185
197
  "timeout": 15
186
198
  },
199
+ {
200
+ "type": "command",
201
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-comment-reply-gate.sh\"",
202
+ "async": false,
203
+ "timeout": 10
204
+ },
187
205
  {
188
206
  "type": "command",
189
207
  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-stage-gate.sh\"",
@@ -195,6 +213,12 @@
195
213
  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-debug-path-gate.sh\"",
196
214
  "async": false,
197
215
  "timeout": 10
216
+ },
217
+ {
218
+ "type": "command",
219
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-capability-claim.sh\"",
220
+ "async": false,
221
+ "timeout": 10
198
222
  }
199
223
  ]
200
224
  }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ # capability-claim gate (Stop). Catches the turn that tells the user an email- or
5
+ # login-gated flow can't be tested — the one class of blocker a managed login
6
+ # profile clears, with its live inbox, stored credentials, and CAPTCHA solver.
7
+ # Nudges once per session, then stays quiet.
8
+ #
9
+ # This must stay synchronous (only a sync Stop hook can block the turn end) and
10
+ # it fires on EVERY turn end, so the pre-filter has to be cheap. A Stop payload
11
+ # carries no message text, only a transcript path, and the claim lives in the
12
+ # assistant's prose — so the filter greps the transcript tail for an
13
+ # impossibility word in-shell and spawns Node only on a hit. Node then applies
14
+ # the real per-sentence detector, which is what keeps a genuine SMS/TOTP limit
15
+ # from being "corrected". Over-matching here costs a needless spawn; it can
16
+ # never emit a spurious nudge. Degrades to {} so it never blocks on its own
17
+ # failure.
18
+ payload="$(cat)"
19
+
20
+ transcript="$(printf '%s' "$payload" \
21
+ | grep -oE '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' \
22
+ | head -1 \
23
+ | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/' \
24
+ | sed 's/\\\\/\//g')"
25
+
26
+ if [ -z "$transcript" ] || [ ! -f "$transcript" ]; then
27
+ printf '{}'
28
+ exit 0
29
+ fi
30
+
31
+ if ! tail -c 20000 "$transcript" \
32
+ | grep -Eiq 'can.t|cannot|unable to|no way to|untestable|unverifiable|impossible|infeasible'; then
33
+ printf '{}'
34
+ exit 0
35
+ fi
36
+
37
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
38
+ printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" capability-claim-gate 2>/dev/null || printf '{}'
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ # claimed-review-thread → threaded-reply gate (Stop). When a round claimed a
5
+ # review thread and left it unanswered, block the turn end until it replies or
6
+ # the deferral is recorded.
7
+ #
8
+ # This must stay synchronous (only a sync Stop hook can block the turn end), and
9
+ # it fires on EVERY turn end. The obligation lives in the per-PR ledger rather
10
+ # than the session state file, so the pre-filter keys on a ledger existing at
11
+ # all: with no ledger anywhere there is nothing this gate could owe, and we
12
+ # return {} in-shell without paying Node cold-start. Degrades to {}.
13
+ payload="$(cat)"
14
+
15
+ raw_sid="$(printf '%s' "$payload" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/')"
16
+ [ -n "$raw_sid" ] || raw_sid="unknown"
17
+ sid="$(printf '%s' "$raw_sid" | sed 's/[^A-Za-z0-9_-]/_/g')"
18
+
19
+ # Resolve the same home dir Node's os.homedir() uses. HOME is correct on
20
+ # macOS/Linux and on most Git Bash setups; fall back to converting USERPROFILE
21
+ # when HOME doesn't hold the state dir (some Windows shells point HOME elsewhere).
22
+ home="${HOME:-}"
23
+ if [ ! -d "$home/.muggle-ai" ] && command -v cygpath >/dev/null 2>&1 && [ -n "${USERPROFILE:-}" ]; then
24
+ home="$(cygpath -u "$USERPROFILE" 2>/dev/null || printf '%s' "$home")"
25
+ fi
26
+
27
+ state_file="$home/.muggle-ai/guardrails/$sid.json"
28
+ if ! ls "$home"/.muggle-ai/muggle-do/sessions/*/comment-ledger.json >/dev/null 2>&1; then
29
+ printf '{}'
30
+ exit 0
31
+ fi
32
+ if [ -f "$state_file" ] && grep -q '"commentReplySkipped": true' "$state_file"; then
33
+ printf '{}'
34
+ exit 0
35
+ fi
36
+ if [ -f "$state_file" ] && grep -q '"commentReplyReleased": true' "$state_file"; then
37
+ printf '{}'
38
+ exit 0
39
+ fi
40
+
41
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
42
+ printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" comment-reply-gate 2>/dev/null || printf '{}'
@@ -32,7 +32,7 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
32
32
  if [ ! -f "$state_file" ] \
33
33
  || ! grep -q '"failedRuns"' "$state_file" \
34
34
  || grep -q '"failedRuns": \[\]' "$state_file" \
35
- || grep -q '"debugSkipped": true' "$state_file"; then
35
+ || grep -q '"debugReleased": true' "$state_file" \n || grep -q '"debugSkipped": true' "$state_file"; then
36
36
  printf '{}'
37
37
  exit 0
38
38
  fi
@@ -29,7 +29,7 @@ fi
29
29
  state_file="$home/.muggle-ai/guardrails/$sid.json"
30
30
  if [ ! -f "$state_file" ] \
31
31
  || ! grep -q '"unitTestsGreen": true' "$state_file" \
32
- || grep -q '"e2eRun": true' "$state_file"; then
32
+ || grep -q '"e2eReleased": true' "$state_file" \n || grep -q '"e2eRun": true' "$state_file"; then
33
33
  printf '{}'
34
34
  exit 0
35
35
  fi
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ # comment-reply observer (PostToolUse/Bash). Records the two things the reply
5
+ # gate settles from: the unresolved-thread fetch a review round works from,
6
+ # which claims each thread it names, and the threaded-reply POSTs that cover the
7
+ # comments in them. Emits no directive — the Stop gate reads the ledger.
8
+ #
9
+ # Fires after every Bash call, so a keyword pre-filter keeps Node off the hot
10
+ # path: only a thread fetch, a reply POST, or a skip marker reaches
11
+ # guardrails.mjs, which then parses the provider response and updates the ledger.
12
+ # The claim is what marks a thread as taken, so a push is no longer a signal and
13
+ # spawning Node on one would be pure waste.
14
+ #
15
+ # The marker arm matches the MUGGLE_<GATE>_SKIP shape, never one token, for the
16
+ # same reason the other observers do: a gate whose marker is missing from a
17
+ # hand-listed set instructs the user to run an echo that can never register,
18
+ # then blocks the turn anyway. Over-matching here only costs a needless spawn.
19
+ # Degrades to {}.
20
+ payload="$(cat)"
21
+
22
+ if ! grep -Eiq 'reviewThreads|merge_requests/[0-9]+/discussions|comments/[0-9]+/replies|discussions/[A-Za-z0-9_-]+/notes|MUGGLE_[A-Z0-9_]+_SKIP' <<<"$payload"; then
23
+ printf '{}'
24
+ exit 0
25
+ fi
26
+
27
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
28
+ printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" record-comment-replies 2>/dev/null || printf '{}'