@muggleai/works 4.15.1 → 5.0.1

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 (63) hide show
  1. package/dist/{chunk-YKR2TQ24.js → chunk-KDRSEHK5.js} +2 -1
  2. package/dist/{chunk-5G7WI7IY.js → chunk-XO5BQZM3.js} +2 -2
  3. package/dist/cli.js +2 -2
  4. package/dist/index.js +2 -2
  5. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  6. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  7. package/dist/plugin/hooks/README.md +26 -0
  8. package/dist/plugin/hooks/hooks.json +39 -0
  9. package/dist/plugin/scripts/ensure-electron-app.sh +1 -1
  10. package/dist/plugin/scripts/guardrail-build-router.sh +9 -0
  11. package/dist/plugin/scripts/guardrail-e2e-gate.sh +8 -0
  12. package/dist/plugin/scripts/guardrail-pr-opened.sh +10 -0
  13. package/dist/plugin/scripts/guardrail-record-tests.sh +9 -0
  14. package/dist/plugin/scripts/guardrails.mjs +143 -0
  15. package/dist/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +2 -1
  16. package/dist/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +16 -0
  17. package/dist/plugin/skills/_shared/pr-followup-helpers.md +1 -0
  18. package/dist/plugin/skills/do/address-reviews.md +1 -1
  19. package/dist/plugin/skills/do/build.md +4 -0
  20. package/dist/plugin/skills/do/cleanup.md +6 -5
  21. package/dist/plugin/skills/do/fix-ci.md +1 -1
  22. package/dist/plugin/skills/do/input-routing.md +7 -6
  23. package/dist/plugin/skills/do/next-step.md +18 -0
  24. package/dist/plugin/skills/do/open-prs/forward.md +2 -0
  25. package/dist/plugin/skills/do/resolve-conflicts.md +62 -0
  26. package/dist/plugin/skills/muggle-do/SKILL.md +2 -1
  27. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  28. package/dist/plugin/skills/muggle-pr-followup/contract.md +40 -16
  29. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +5 -1
  30. package/dist/plugin/skills/muggle-preferences/preference-gates/autoRouteBuildToMuggleDo.md +13 -0
  31. package/dist/plugin/skills/muggle-test/SKILL.md +7 -5
  32. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +7 -5
  33. package/dist/release-manifest.json +4 -4
  34. package/dist/{src-ECRJW2LY.js → src-2O5UH4FA.js} +1 -1
  35. package/package.json +6 -6
  36. package/plugin/.claude-plugin/plugin.json +1 -1
  37. package/plugin/.cursor-plugin/plugin.json +1 -1
  38. package/plugin/hooks/README.md +26 -0
  39. package/plugin/hooks/hooks.json +39 -0
  40. package/plugin/scripts/ensure-electron-app.sh +1 -1
  41. package/plugin/scripts/guardrail-build-router.sh +9 -0
  42. package/plugin/scripts/guardrail-e2e-gate.sh +8 -0
  43. package/plugin/scripts/guardrail-pr-opened.sh +10 -0
  44. package/plugin/scripts/guardrail-record-tests.sh +9 -0
  45. package/plugin/scripts/guardrails.mjs +143 -0
  46. package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +2 -1
  47. package/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +16 -0
  48. package/plugin/skills/_shared/pr-followup-helpers.md +1 -0
  49. package/plugin/skills/do/address-reviews.md +1 -1
  50. package/plugin/skills/do/build.md +4 -0
  51. package/plugin/skills/do/cleanup.md +6 -5
  52. package/plugin/skills/do/fix-ci.md +1 -1
  53. package/plugin/skills/do/input-routing.md +7 -6
  54. package/plugin/skills/do/next-step.md +18 -0
  55. package/plugin/skills/do/open-prs/forward.md +2 -0
  56. package/plugin/skills/do/resolve-conflicts.md +62 -0
  57. package/plugin/skills/muggle-do/SKILL.md +2 -1
  58. package/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  59. package/plugin/skills/muggle-pr-followup/contract.md +40 -16
  60. package/plugin/skills/muggle-pr-followup/state-schemas.md +5 -1
  61. package/plugin/skills/muggle-preferences/preference-gates/autoRouteBuildToMuggleDo.md +13 -0
  62. package/plugin/skills/muggle-test/SKILL.md +7 -5
  63. package/plugin/skills/muggle-test-feature-local/SKILL.md +7 -5
@@ -1,6 +1,6 @@
1
1
  # Watcher Per-Tick Contract
2
2
 
3
- The procedure for the **tick mode** of `muggle-pr-followup` — one polling iteration scoped to one PR. The watcher is a dumb pipe: it polls for new submitted reviews and CI checks, dispatches `/muggle-do` if there's review feedback or fixable red CI, and exits. It does not classify, fix, amend requirements, post replies, run cycles, or escalate.
3
+ The procedure for the **tick mode** of `muggle-pr-followup` — one polling iteration scoped to one PR. The watcher is a dumb pipe: it polls for new submitted reviews, CI checks, and merge-conflict state, dispatches `/muggle-do` if there's review feedback, fixable red CI, or an unmergeable branch, and exits. It does not classify, fix, resolve, amend requirements, post replies, run cycles, or escalate.
4
4
 
5
5
  Routing into this mode is documented in [`SKILL.md`](SKILL.md#routing). The architectural rationale lives in the brain doc `architecture/2026-05-08-muggle-do-pr-comment-loop-design.md`.
6
6
 
@@ -25,35 +25,43 @@ If either file is missing or the PR is not in `prs.json`, the tick is a no-op. L
25
25
 
26
26
  ## Procedure
27
27
 
28
+ ### Step 0 — Stale-fire guard
29
+
30
+ If `prs.json[0].state` on disk is already `merged` or `closed`, this slot was finalized by a prior tick and this is a stale (queued) fire — per-minute cron fires enqueued while the session was busy still drain after the cron is cancelled. Defensively cancel any lingering cron for this slug (`CronList` → the job whose command ends with `/muggle:muggle-pr-followup <slug> <n>` → `CronDelete`; no-op if none), append a `stale-tick` line to `followup.log`, and exit. Do not re-fetch or re-finalize.
31
+
28
32
  ### Step 1 — Refresh PR state
29
33
 
30
- Per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). Update `prs.json[0].head_sha` and `prs.json[0].state` from the response.
34
+ Per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). Update `prs.json[0].head_sha` and `prs.json[0].state` from the response; keep `mergeable` / `mergeStateStatus` from the same call for Step 5.
31
35
 
32
36
  ### Step 2 — Termination check
33
37
 
34
38
  If `state` is `MERGED` or `CLOSED`:
35
39
 
36
40
  1. Finalize the slot per [`finalize.md`](finalize.md) — mark terminal, write `result.md`, log + telemetry, unschedule this watcher's cron.
37
- 2. **If `MERGED`**, hand off post-merge cleanup as the last action of the turn (skip on `CLOSED` — unmerged, leave the branch and any worktree intact):
41
+ 2. Hand off the terminal wrap-up as the last action of the turn — for both `MERGED` and `CLOSED`:
38
42
 
39
43
  ```
40
- /muggle-do post-merge cleanup slug=<slug>
44
+ /muggle-do post-merge cleanup slug=<slug> state=<merged|closed>
41
45
  ```
42
46
 
43
- `/muggle-do` owns the worktree/branch knowledge and honors the `autoCleanup` gate. This is a runtime dispatch, not a doc dependency on `/muggle-do` — see the one-way rule in [`../CLAUDE.md`](../CLAUDE.md).
47
+ `/muggle-do` owns the worktree/branch knowledge: it runs teardown only on `merged` (honoring the `autoCleanup` gate — `closed` is unmerged, so the branch and any worktree stay intact), then suggests the next step. This is a runtime dispatch, not a doc dependency on `/muggle-do` — see the one-way rule in [`../CLAUDE.md`](../CLAUDE.md).
44
48
  3. Exit. The watcher has unscheduled itself; no future ticks fire for this PR.
45
49
 
46
50
 
47
51
  ### Step 3 — Fetch new submitted reviews
48
52
 
49
- Per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md). **Also exclude review ids that appear in `last_seen.escalated_review_ids`** — those have already been escalated and the watcher must not re-dispatch them.
53
+ Per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md). Exclude two kinds of review id:
54
+
55
+ - ids in `last_seen.escalated_review_ids` — already escalated; the watcher must not re-dispatch them.
56
+ - **echo reviews** per [`../_shared/pr-followup-helpers/echo-skip.md`](../_shared/pr-followup-helpers/echo-skip.md) — a review whose every comment carries the loop marker is the loop's own reply, surfaced by GitHub as a new review. Advance `last_seen.reviewId` past it and skip; never dispatch, or the watcher replies to itself forever.
50
57
 
51
58
  ### Step 4 — If one or more new reviews → dispatch (reviews preempt CI)
52
59
 
53
60
  The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher's job is to hand over the list of new review ids and exit.
54
61
 
55
62
  1. Reset `last_seen.idle_tick_count` to 0.
56
- 2. Dispatch `/muggle-do` with an *address-reviews* directive carrying:
63
+ 2. **Stop this watcher (single-thread).** Cancel its cron so no tick fires while the dev cycle runs: `CronList`, find the job whose command ends with `/muggle:muggle-pr-followup <slug> <n>` (exact two-arg match), `CronDelete` it. `/muggle-do` respawns the watcher when the cycle finishes — exactly one cron ever, and no tick overlaps a running cycle.
64
+ 3. Dispatch `/muggle-do` with an *address-reviews* directive carrying:
57
65
  - PR URL (from `prs.json[0].url`)
58
66
  - Session slug (from the invocation arguments)
59
67
  - Every new review id from Step 3, as a space-separated list
@@ -62,11 +70,26 @@ The watcher does **not** classify. Classification, batching, replying, escalatio
62
70
  ```
63
71
  /muggle-do address reviews <id1> <id2> ... on <pr-url> slug=<slug>
64
72
  ```
65
- 3. Append a dispatching line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
66
- 4. Emit a `tick` event with `reviews_seen: <count>`, `dispatched_review_ids: [<id>, ...]`.
67
- 5. Exit. **Reviews preempt CI** — when reviews land, this tick dispatches address-reviews and never polls CI. The cron keeps firing; the next tick still arrives. The watcher only self-unschedules in Step 2 (terminal).
73
+ 4. Append a dispatching line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
74
+ 5. Emit a `tick` event with `reviews_seen: <count>`, `dispatched_review_ids: [<id>, ...]`.
75
+ 6. Exit. **Reviews preempt CI** — when reviews land, this tick dispatches address-reviews and never polls CI. The watcher is now stopped; the dev cycle owns the PR and restarts the watcher when it finishes. (The watcher also self-unschedules in Step 2, terminal.)
76
+
77
+ ### Step 5 — No new reviews → check mergeability
78
+
79
+ Read `mergeable` / `mergeStateStatus` from the Step 1 metadata. If `mergeable == CONFLICTING` (or `mergeStateStatus == DIRTY`), **and** `conflict_resolve_attempts[head_sha] < 2`, **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
80
+
81
+ 1. Reset `last_seen.idle_tick_count` to 0.
82
+ 2. **Stop this watcher (single-thread):** cancel its cron exactly as in Step 4 — `/muggle-do`'s resolve-conflicts respawns it when the cycle is done.
83
+ 3. Dispatch `/muggle-do` with a *resolve-conflicts* directive (PR URL + slug; no review ids, no check names):
84
+ ```
85
+ /muggle-do resolve conflicts on <pr-url> slug=<slug>
86
+ ```
87
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `conflicting: true`, `dispatched_resolve_conflicts: true`.
88
+ 5. Exit. The dev cycle owns the PR; its respawn restarts the watcher, whose next tick re-checks mergeability on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
89
+
90
+ `mergeable == MERGEABLE` / `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
68
91
 
69
- ### Step 5 — No new reviews → poll CI for the head SHA
92
+ ### Step 6 — No new reviews, mergeable → poll CI for the head SHA
70
93
 
71
94
  Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cli-recipes/pr-checks.md`](../_shared/github-cli-recipes/pr-checks.md), then:
72
95
 
@@ -74,17 +97,18 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
74
97
  - **All checks green / skipped, or no checks** → idle (green path).
75
98
  - **One or more checks red** (`bucket == "fail"`), **and** `ci_fix_attempts[head_sha] < 3`, **and** `head_sha` ∉ `ci_escalated_shas` → dispatch and exit:
76
99
  1. Reset `last_seen.idle_tick_count` to 0.
77
- 2. Dispatch `/muggle-do` with a *fix-ci* directive carrying the PR URL, slug, and the red check names (no review ids):
100
+ 2. **Stop this watcher (single-thread):** cancel its cron exactly as in Step 4 — `/muggle-do`'s fix-ci respawns it when the cycle is done.
101
+ 3. Dispatch `/muggle-do` with a *fix-ci* directive carrying the PR URL, slug, and the red check names (no review ids):
78
102
  ```
79
103
  /muggle-do fix ci <check-1> <check-2> ... on <pr-url> slug=<slug>
80
104
  ```
81
- 3. Append a dispatching line to `followup.log`; emit a `tick` event with `checks_red: <count>`, `dispatched_ci_fix: true`.
82
- 4. Exit. The next tick re-checks CI on the new head SHA — CI itself is the verify loop.
105
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `checks_red: <count>`, `dispatched_ci_fix: true`.
106
+ 5. Exit. The dev cycle owns the PR; its respawn restarts the watcher, whose next tick re-checks CI on the new head SHA — CI itself is the verify loop.
83
107
  - **One or more red, but `ci_fix_attempts[head_sha] >= 3` or `head_sha` ∈ `ci_escalated_shas`** → idle. The fix budget is spent; `/muggle-do`'s fix-ci stage already recorded the escalation. The watcher does not re-dispatch.
84
108
 
85
- ### Step 6 — Idle
109
+ ### Step 7 — Idle
86
110
 
87
- Any idle branch (Steps 4–5 that did not dispatch): increment `last_seen.idle_tick_count`, append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md), emit a `tick` event with `idle: true`, `reviews_seen: 0`, `dispatched_review_ids: []`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
111
+ Any idle branch (Steps 4–6 that did not dispatch): increment `last_seen.idle_tick_count`, append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md), emit a `tick` event with `idle: true`, `reviews_seen: 0`, `dispatched_review_ids: []`, `conflicting: <bool>`, `dispatched_resolve_conflicts: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
88
112
 
89
113
  ## Output
90
114
 
@@ -41,7 +41,9 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
41
41
  "escalated_review_ids": [<int>, ...],
42
42
  "pushed_shas": ["<sha>", ...],
43
43
  "ci_fix_attempts": { "<sha>": <int> },
44
- "ci_escalated_shas": ["<sha>", ...]
44
+ "ci_escalated_shas": ["<sha>", ...],
45
+ "conflict_resolve_attempts": { "<sha>": <int> },
46
+ "conflict_escalated_shas": ["<sha>", ...]
45
47
  }
46
48
  }
47
49
  ```
@@ -54,6 +56,8 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
54
56
  - `pushed_shas`: every SHA `/muggle-do` has pushed for this PR. Append-only. Used by the resolve-reminder stage to recognize threads addressed by the loop.
55
57
  - `ci_fix_attempts`: per-SHA count of fix-ci cycles `/muggle-do` has run. The watcher stops dispatching fix-ci for a SHA once its count reaches 3. Keyed by head SHA.
56
58
  - `ci_escalated_shas`: head SHAs whose CI the fix-ci stage gave up on (attempts exhausted or only out-of-scope checks). The watcher excludes these from CI dispatch so a hopeless SHA is never re-fixed.
59
+ - `conflict_resolve_attempts`: per-SHA count of resolve-conflicts cycles `/muggle-do` has run. The watcher stops dispatching resolve-conflicts for a SHA once its count reaches 2. Keyed by head SHA.
60
+ - `conflict_escalated_shas`: head SHAs whose merge conflict resolve-conflicts gave up on (attempts exhausted, or `autoResolveConflicts=never`). The watcher excludes these from conflict dispatch so an unresolvable SHA is never re-attempted.
57
61
 
58
62
  ## `state.md`
59
63
 
@@ -0,0 +1,13 @@
1
+ # `autoRouteBuildToMuggleDo`
2
+
3
+ When the user asks to build, implement, or fix something, controls whether the front-door guardrail routes the work through `/muggle-do` — the orchestrator that runs requirements → build (delegated to superpowers' design→plan→review) → impact → unit tests → E2E → PR → watcher — or lets the request proceed however the model would otherwise handle it. Fires once per session, on the first build-intent prompt (UserPromptSubmit guardrail).
4
+
5
+ **Picker 1** — header `Route to muggle-do?`, question `"This looks like a build request — run it through /muggle-do (E2E + PR + watcher, build delegated to superpowers)?"`
6
+ - `Route it` — `Enter the /muggle-do pipeline.` → `always`
7
+ - `Ask me next time` — `Decide per request.` → `ask`
8
+ - `No — proceed normally` — `Handle it without /muggle-do.` → `never`
9
+
10
+ **Silent action**
11
+ - `always` → `Routing build requests through /muggle-do`
12
+ - `ask` → `Asking about routing to muggle-do`
13
+ - `never` → `Not routing to muggle-do`
@@ -113,13 +113,15 @@ If no changes detected (clean tree), tell the user and ask what they want to tes
113
113
 
114
114
  ## Step 3: Authenticate
115
115
 
116
- 1. Call `muggle-remote-auth-status`
117
- 2. If **authenticated and not expired** → gate `autoLogin` (per `preference-gates/README.md`):
118
- - `always` → reuse saved session.
116
+ 1. Call `muggle-remote-auth-status`. Three states: **valid** (`authenticated: true`), **expired** (`authenticated: false` + `isExpired: true`, `email` still present), **absent** (`authenticated: false`, no `email`).
117
+ 2. **Valid OR expired** (any stored identity) → gate `autoLogin` (per `preference-gates/README.md`). An expired token is NOT a reason to silently re-login the same account — surface the switch choice:
118
+ - `always` → reuse if valid; if expired, re-login the **same** account (`muggle-remote-auth-login`, then `muggle-remote-auth-poll`).
119
119
  - `never` → `muggle-remote-auth-login` with `forceNewSession: true`, then `muggle-remote-auth-poll`.
120
120
  - `ask` → run Picker 1 from `preference-gates/autoLogin.md` via `AskUserQuestion`; map the answer back to one of the actions above.
121
- 3. If **not authenticated or expired** → call `muggle-remote-auth-login`
122
- 4. If login pending → call `muggle-remote-auth-poll`
121
+ 3. **Absent** (no stored identity) → `muggle-remote-auth-login` directly, then `muggle-remote-auth-poll`.
122
+ 4. If login pending → call `muggle-remote-auth-poll`.
123
+
124
+ **Account-switch caveat (`never` / "Switch account").** The device flow has no `prompt=select_account`; switching relies on `forceNewSession` first clearing the Auth0 session via `/v2/logout?returnTo=<device-activation URL>`. That redirect only works if the activation URL is in the app's Auth0 *Allowed Logout URLs* — otherwise the browser shows an Auth0 error page and the session is silently reused. If that happens, tell the user to complete login in a **fresh incognito window** (no live SSO session) so Auth0 presents an account login.
123
125
 
124
126
  If auth fails repeatedly, suggest: `muggle logout && muggle login` from terminal.
125
127
 
@@ -147,14 +147,16 @@ Run the shared loop in [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md
147
147
 
148
148
  Caller glue: `mode` is the path chosen in §5; `localUrl` from §4; `cwd` = the repo root, or the prepared worktree when one is in use.
149
149
 
150
- ### 7. Execute (no approval prompt; `showUi` gated by `showElectronBrowser`)
150
+ ### 7. Execute (`showUi` gated by `showElectronBrowser`)
151
151
 
152
- Call `muggle-local-execute-test-generation` or `muggle-local-execute-replay` directly. **Do not** ask the user to re-approve the Electron launch — the user choosing this skill in the first place is the approval.
152
+ Resolve the `showElectronBrowser` gate **first**, then call `muggle-local-execute-test-generation` or `muggle-local-execute-replay`. **Do not** ask the user to re-approve the Electron launch itself — choosing this skill is the approval. That run-approval suppression does **not** extend to the gate below: when `showElectronBrowser=ask` you must still fire its picker.
153
153
 
154
- Gate `showElectronBrowser` (per `preference-gates/README.md`). Reuse choice within a session.
155
- - `always` → omit `showUi`.
154
+ Gate `showElectronBrowser` (per `preference-gates/README.md`). Reuse the choice within a session.
155
+ - `always` → omit `showUi` (the browser shows by default).
156
156
  - `never` → pass `showUi: false`.
157
- - `ask` → run Picker 1 from `preference-gates/showElectronBrowser.md` via `AskUserQuestion`; map the answer back to one of the actions above.
157
+ - `ask` → you **must** call `AskUserQuestion` (Picker 1 from `preference-gates/showElectronBrowser.md`) **before** the execute call, then map the answer to the `always`/`never` action above. Do not decide for the user.
158
+
159
+ `showUi` is only ever omitted or `false` — never pass `showUi: true`.
158
160
 
159
161
  ### 8. Upload run to cloud (every completed run; open `viewUrl` gated by `openTestResultsAfterRun`)
160
162
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "release": "4.15.1",
3
- "buildId": "run-47-1",
4
- "commitSha": "4bac13c8822d5c6b6989a3d800dc983f63556ab1",
5
- "buildTime": "2026-06-02T22:17:47Z",
2
+ "release": "5.0.1",
3
+ "buildId": "run-49-1",
4
+ "commitSha": "a12a3252381af4fb34b79598208ba62f9764154d",
5
+ "buildTime": "2026-06-04T17:35:40Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -1 +1 @@
1
- export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-YKR2TQ24.js';
1
+ export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-KDRSEHK5.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": "4.15.1",
4
+ "version": "5.0.1",
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",
@@ -41,14 +41,14 @@
41
41
  "test:watch": "vitest"
42
42
  },
43
43
  "muggleConfig": {
44
- "electronAppVersion": "1.0.109",
44
+ "electronAppVersion": "1.0.116",
45
45
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
46
46
  "runtimeTargetDefault": "production",
47
47
  "checksums": {
48
- "darwin-arm64": "9c2fa21b4e090c8b6f1acedf1bdbd824f0032a5d91d6f9abd3ca25cebee7f417",
49
- "darwin-x64": "3369988c627be5d362d5d9c8312f9902063a98a53d308695b40873211960501c",
50
- "linux-x64": "a986757132b45a4299a44c3dd1f3945ba6814d299e677380e2061f18da44b511",
51
- "win32-x64": "29ab5400d7dca049e4a3e75b76bc3e06398b88743f0a49d38fa6ae531fc980fe"
48
+ "darwin-arm64": "382466a6eefe49cddc5662556cb6b744c60c2eed9984e520df9f62726bc468a0",
49
+ "darwin-x64": "9c57b10b1ed03bcd6629cb36abd5c0e0b5d095a27a38305ad4272c77cd6334af",
50
+ "linux-x64": "d6b10a829f99027747349ab37521b765041343676f59e59792afcec1de024080",
51
+ "win32-x64": "77981c9d77dd49a786cfca7156ce326f56a75716cf911adad270521230435f75"
52
52
  }
53
53
  },
54
54
  "dependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
4
- "version": "4.15.1",
4
+ "version": "5.0.1",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
5
- "version": "4.15.1",
5
+ "version": "5.0.1",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -0,0 +1,26 @@
1
+ # Guardrail hooks
2
+
3
+ Condition-triggered hooks that make Muggle Test's high-value handoffs fire path-independently — no matter whether a change was built via muggle-do, superpowers, or ad-hoc edits.
4
+
5
+ ## Two layers
6
+
7
+ "Harness" spans two layers, and the distinction is load-bearing:
8
+
9
+ - **Claude Code layer** — the agent runtime that fires these hooks. A guardrail is a Claude-Code-layer trigger, nothing more.
10
+ - **Muggle Test layer** — the product (muggle-do, muggle-test, the watcher). This is what a guardrail *invokes*.
11
+
12
+ A guardrail injects an advisory directive (`additionalContext`); the model then runs the Muggle Test flow. The guardrail never reimplements the flow.
13
+
14
+ Design rationale: `muggle-ai-brain/architecture/2026-06-02-harness-pipeline-integration-design.md`.
15
+
16
+ ## Mechanism
17
+
18
+ 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` makes each guardrail fire once. Any failure degrades to `{}` — a guardrail must never block a turn.
19
+
20
+ ## Guardrails
21
+
22
+ | Hook event | Wrapper | Condition | Preference | Flow invoked |
23
+ | :--------- | :------ | :-------- | :--------- | :----------- |
24
+ | `PostToolUse` (Bash) | `guardrail-pr-opened.sh` | a `gh pr create`/`gh pr ready` just succeeded | `autoWatchPR` | start a `muggle-pr-followup` watcher on the new PR |
25
+ | `Stop` | `guardrail-e2e-gate.sh` | unit tests passed this session and no E2E ran yet (recorded by `guardrail-record-tests.sh`) | `autoE2ETest` | run change-driven E2E via `muggle-test` before finishing |
26
+ | `UserPromptSubmit` | `guardrail-build-router.sh` | a build/implement/fix request (first one this session) | `autoRouteBuildToMuggleDo` | route the work through `muggle-do` (build delegated to superpowers) |
@@ -11,6 +11,45 @@
11
11
  }
12
12
  ]
13
13
  }
14
+ ],
15
+ "PostToolUse": [
16
+ {
17
+ "matcher": "Bash",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-pr-opened.sh\"",
22
+ "async": false
23
+ },
24
+ {
25
+ "type": "command",
26
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
27
+ "async": false
28
+ }
29
+ ]
30
+ }
31
+ ],
32
+ "Stop": [
33
+ {
34
+ "hooks": [
35
+ {
36
+ "type": "command",
37
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-e2e-gate.sh\"",
38
+ "async": false
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "UserPromptSubmit": [
44
+ {
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-build-router.sh\"",
49
+ "async": false
50
+ }
51
+ ]
52
+ }
14
53
  ]
15
54
  }
16
55
  }
@@ -109,7 +109,7 @@ if [ -f "$prefs_global_file" ]; then
109
109
  postPRVisualWalkthrough:'ask', autoCreatePR:'ask',
110
110
  checkForUpdates:'ask', verboseOutput:'ask',
111
111
  autoUseWorktree:'ask', autoRebase:'ask', autoCleanup:'ask',
112
- autoE2ETest:'always'
112
+ autoE2ETest:'always', autoRouteBuildToMuggleDo:'ask'
113
113
  };
114
114
  const cwd = process.env.CLAUDE_CWD || process.env.CURSOR_CWD || process.cwd();
115
115
  const pPath = require('path').join(cwd, '.muggle-ai', 'preferences.json');
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Front-door router (UserPromptSubmit). On the first build/implement/fix prompt
5
+ # of a session, offers to route the work through /muggle-do (build delegated to
6
+ # superpowers), gated by autoRouteBuildToMuggleDo. Fires once per session.
7
+ # Degrades to {} so it never blocks a turn.
8
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
9
+ node "${root}/scripts/guardrails.mjs" build-router 2>/dev/null || printf '{}'
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # tests-green → E2E gate (Stop). When unit tests passed this session and no E2E
5
+ # acceptance run has happened, offer to run change-driven E2E (gated by
6
+ # autoE2ETest). Fires once per session. Degrades to {} so it never blocks a turn.
7
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
8
+ node "${root}/scripts/guardrails.mjs" e2e-gate 2>/dev/null || printf '{}'
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # PR-opened guardrail (PostToolUse/Bash). When a `gh pr create`/`gh pr ready`
5
+ # just succeeded, offer to start a muggle-pr-followup watcher on the new PR
6
+ # (gated by autoWatchPR, deduped per session). Decision logic lives in the
7
+ # bundled guardrails.mjs; this wrapper just pipes the event payload through and
8
+ # degrades to {} so a guardrail can never block a turn.
9
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
10
+ node "${root}/scripts/guardrails.mjs" pr-opened 2>/dev/null || printf '{}'
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # tests-green observer (PostToolUse/Bash). Records in per-session state when a
5
+ # unit-test command passed (and when a muggle E2E run happened). Emits no
6
+ # directive — the Stop gate (guardrail-e2e-gate.sh) reads the state. Degrades
7
+ # to {} so it never blocks a turn.
8
+ root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
9
+ node "${root}/scripts/guardrails.mjs" record-tests 2>/dev/null || printf '{}'
@@ -0,0 +1,143 @@
1
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+
5
+ // src/guardrails/cli.ts
6
+ var baseDir = (override) => override ?? join(homedir(), ".muggle-ai", "guardrails");
7
+ var fileFor = (sessionId2, override) => join(baseDir(override), `${sessionId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
8
+ function readState(sessionId2, dirOverride) {
9
+ const f = fileFor(sessionId2, dirOverride);
10
+ if (!existsSync(f)) return { sessionId: sessionId2, prsHandled: [] };
11
+ try {
12
+ const raw = JSON.parse(readFileSync(f, "utf-8"));
13
+ return { ...raw, sessionId: sessionId2, prsHandled: raw.prsHandled ?? [] };
14
+ } catch {
15
+ return { sessionId: sessionId2, prsHandled: [] };
16
+ }
17
+ }
18
+ function writeState(state, dirOverride) {
19
+ mkdirSync(baseDir(dirOverride), { recursive: true });
20
+ writeFileSync(fileFor(state.sessionId, dirOverride), JSON.stringify(state, null, 2));
21
+ }
22
+ function markPrHandled(sessionId2, prUrl, dirOverride) {
23
+ const state = readState(sessionId2, dirOverride);
24
+ if (!state.prsHandled.includes(prUrl)) state.prsHandled.push(prUrl);
25
+ writeState(state, dirOverride);
26
+ }
27
+
28
+ // src/guardrails/prOpened.ts
29
+ var PR_URL = /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+/;
30
+ var CREATE_CMD = /\bgh\s+pr\s+(create|ready)\b/;
31
+ function detectPrOpened(input2) {
32
+ if (input2.tool_name !== "Bash") return null;
33
+ const cmd = input2.tool_input?.command ?? "";
34
+ if (!CREATE_CMD.test(cmd)) return null;
35
+ const out = `${input2.tool_response?.stdout ?? ""}
36
+ ${input2.tool_response?.output ?? ""}`;
37
+ const m = out.match(PR_URL);
38
+ return m ? m[0] : null;
39
+ }
40
+
41
+ // src/guardrails/testsGreen.ts
42
+ var TEST_CMD = /\b(pnpm|npm|yarn)\s+(run\s+)?test\b|\b(jest|vitest|pytest)\b|\bgo\s+test\b|\bcargo\s+test\b/;
43
+ var FAIL = /\b\d+\s+failed\b|\bFAIL\b|✗/;
44
+ var E2E_RUN = /\bmuggle\b[^\n]*\b(execute|test)\b/i;
45
+ function isTestCommand(cmd) {
46
+ return TEST_CMD.test(cmd);
47
+ }
48
+ function testsPassed(input2) {
49
+ const out = `${input2.tool_response?.stdout ?? ""}
50
+ ${input2.tool_response?.stderr ?? ""}`;
51
+ if (!out.trim()) return false;
52
+ return !FAIL.test(out);
53
+ }
54
+ function isE2ERun(input2) {
55
+ const cmd = input2.tool_input?.command ?? "";
56
+ const tool = input2.tool_name ?? "";
57
+ return E2E_RUN.test(cmd) || /muggle.*(execute|test-generation|replay)/i.test(tool);
58
+ }
59
+
60
+ // src/guardrails/shouldRunE2E.ts
61
+ function shouldRunE2E(state) {
62
+ return state.unitTestsGreen === true && state.e2eRun !== true;
63
+ }
64
+
65
+ // src/guardrails/detectBuildIntent.ts
66
+ var BUILD = /\b(implement|build|add|create|write|fix|refactor|wire up|hook up|make (a|the|it)|change the)\b/i;
67
+ var QUESTION = /^\s*(why|what|how|when|where|who|is|are|does|do|can you (explain|tell)|explain)\b/i;
68
+ function detectBuildIntent(prompt) {
69
+ const p = (prompt ?? "").trim();
70
+ if (!p || p.startsWith("/")) return false;
71
+ if (QUESTION.test(p)) return false;
72
+ return BUILD.test(p);
73
+ }
74
+
75
+ // src/guardrails/emit.ts
76
+ function envelope(eventName, context, host2) {
77
+ if (!context) return "{}";
78
+ if (host2 === "cursor") return JSON.stringify({ additional_context: context });
79
+ return JSON.stringify({
80
+ hookSpecificOutput: { hookEventName: eventName, additionalContext: context }
81
+ });
82
+ }
83
+
84
+ // src/guardrails/cli.ts
85
+ function readStdin() {
86
+ try {
87
+ return JSON.parse(readFileSync(0, "utf-8"));
88
+ } catch {
89
+ return {};
90
+ }
91
+ }
92
+ var host = process.env.CURSOR_PLUGIN_ROOT ? "cursor" : "claude";
93
+ var sub = process.argv[2];
94
+ var input = readStdin();
95
+ var sessionId = input.session_id ?? "unknown";
96
+ function prOpened() {
97
+ const url = detectPrOpened(input);
98
+ if (!url) return "{}";
99
+ if (readState(sessionId).prsHandled.includes(url)) return "{}";
100
+ markPrHandled(sessionId, url);
101
+ const ctx = `A pull request was just opened: ${url}
102
+ Per the autoWatchPR preference, a muggle-pr-followup watcher should handle its incoming reviews. If autoWatchPR=always, start it now by invoking /muggle:muggle-pr-followup with the PR URL; if =ask, offer it to the user; if =never, do nothing.`;
103
+ return envelope("PostToolUse", ctx, host);
104
+ }
105
+ function recordTests() {
106
+ const cmd = input.tool_input?.command ?? "";
107
+ const state = readState(sessionId);
108
+ let changed = false;
109
+ if (isTestCommand(cmd) && testsPassed(input)) {
110
+ state.unitTestsGreen = true;
111
+ changed = true;
112
+ }
113
+ if (isE2ERun(input)) {
114
+ state.e2eRun = true;
115
+ changed = true;
116
+ }
117
+ if (changed) writeState(state);
118
+ return "{}";
119
+ }
120
+ function e2eGate() {
121
+ const state = readState(sessionId);
122
+ if (!shouldRunE2E(state)) return "{}";
123
+ state.e2eRun = true;
124
+ writeState(state);
125
+ const ctx = `Unit tests passed this session and no E2E acceptance run has happened yet. Per the autoE2ETest preference (default: always), run change-driven E2E now via /muggle:muggle-test before finishing. If autoE2ETest=never, skip.`;
126
+ return envelope("Stop", ctx, host);
127
+ }
128
+ function buildRouter() {
129
+ if (!detectBuildIntent(input.prompt ?? "")) return "{}";
130
+ const state = readState(sessionId);
131
+ if (state.buildIntentRouted) return "{}";
132
+ state.buildIntentRouted = true;
133
+ writeState(state);
134
+ const ctx = `This looks like a build/implement/fix request. Per the autoRouteBuildToMuggleDo preference, route it through /muggle-do \u2014 which runs requirements \u2192 build (delegated to superpowers' design\u2192plan\u2192review) \u2192 impact \u2192 unit tests \u2192 E2E \u2192 PR \u2192 watcher. If autoRouteBuildToMuggleDo=always, enter that flow; if =ask, offer it; if =never, proceed normally.`;
135
+ return envelope("UserPromptSubmit", ctx, host);
136
+ }
137
+ var handlers = {
138
+ "pr-opened": prOpened,
139
+ "record-tests": recordTests,
140
+ "e2e-gate": e2eGate,
141
+ "build-router": buildRouter
142
+ };
143
+ process.stdout.write((handlers[sub] ?? (() => "{}"))());
@@ -4,9 +4,10 @@ Fetch the fields the watcher and bootstrap need in one call.
4
4
 
5
5
  ```bash
6
6
  gh pr view <pr-number> --repo <owner>/<repo> \
7
- --json url,number,headRefOid,headRefName,baseRefName,state,mergedAt,closedAt,body,title,author
7
+ --json url,number,headRefOid,headRefName,baseRefName,state,mergeable,mergeStateStatus,mergedAt,closedAt,body,title,author
8
8
  ```
9
9
 
10
10
  - `state` is one of `OPEN`, `MERGED`, `CLOSED`.
11
11
  - `headRefOid` is the current head SHA — store as `head_sha` in `prs.json`.
12
12
  - `headRefName` is the branch — must match the working tree's branch in bootstrap.
13
+ - `mergeable` is `MERGEABLE`, `CONFLICTING`, or `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick). `mergeStateStatus` (`DIRTY` = conflicts, `BEHIND`, `CLEAN`, …) corroborates it. The watcher uses these to detect a merge-conflict that no review or CI signal would surface.
@@ -0,0 +1,16 @@
1
+ # Reply-echo skip
2
+
3
+ When `/muggle-do` posts a threaded reply to a review comment, GitHub surfaces that reply as a **new submitted review** under the same account. Left unchecked, the next watcher tick reads that review as fresh feedback and dispatches another cycle — which posts another reply, which becomes another review. The loop never converges.
4
+
5
+ ## Rule
6
+
7
+ A submitted review is an **echo** when **every** comment in it carries the loop marker `<!-- muggle-do:bot -->` (see [`loop-signature.md`](loop-signature.md)). An echo is the loop's own reply wearing a review's clothing, never human intent.
8
+
9
+ On an echo review, the watcher must:
10
+
11
+ 1. Advance `last_seen.reviewId` past the echo's id (so it is not seen again), and
12
+ 2. **Skip it** — never dispatch `/muggle-do` for it.
13
+
14
+ ## Detection
15
+
16
+ Classify by the marker, never by `author.login` — under a shared account the loop posts as the PR author, so the login cannot tell echo from human. Fetch the review's comments; if the set is non-empty and every comment body contains `<!-- muggle-do:bot -->`, it is an echo. A review with at least one marker-less comment is human feedback and must be processed normally.
@@ -12,3 +12,4 @@ Each section is its own file — load only what the current step needs.
12
12
  | [`reply-routing`](pr-followup-helpers/reply-routing.md) | Pick the right reply endpoint per comment type (line, body-only, CI failure). |
13
13
  | [`classify`](pr-followup-helpers/classify.md) | Per-review binary label: actionable vs ambiguous, with worked examples + borderline rule. |
14
14
  | [`loop-signature`](pr-followup-helpers/loop-signature.md) | The signature every loop-posted comment carries, and how to detect loop vs human comments by it. |
15
+ | [`echo-skip`](pr-followup-helpers/echo-skip.md) | Skip reviews that are the loop's own replies (every comment loop-marked), so the watcher never re-triggers on itself. |
@@ -114,7 +114,7 @@ Refresh PR state per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared
114
114
  1. Write `result.md` per [`../muggle-pr-followup/state-schemas.md`](../muggle-pr-followup/state-schemas.md#resultmd).
115
115
  2. Do **not** respawn the watcher.
116
116
 
117
- Otherwise, dispatch the next watcher as the last action of this turn:
117
+ Otherwise, dispatch the next watcher as the last action of this turn. The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 4), so this restart is the single live watcher — never a duplicate:
118
118
 
119
119
  ```
120
120
  /loop 1m /muggle:muggle-pr-followup <slug> <n>
@@ -29,6 +29,10 @@ For each affected repo:
29
29
 
30
30
  The body explains *why* when the why is non-obvious. The diff already says *what*.
31
31
 
32
+ ## Delegation
33
+
34
+ For a non-trivial change — multiple files, real design surface, or anything you would otherwise brainstorm before coding — run the implementation through superpowers' design → plan → subagent-driven build, then return to this stage's Output. That is a runtime hand-off (an action), not a doc dependency; do not encode superpowers' internals here. Routing a build request into this pipeline (the `autoRouteBuildToMuggleDo` front-door guardrail) exists to combine superpowers' design rigor with this pipeline's impact analysis, E2E, PR, and watcher — neither delivers both alone.
35
+
32
36
  ## Output
33
37
 
34
38
  Per repo: