@muggleai/works 5.0.2 → 5.0.4

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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  3. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  4. package/dist/plugin/skills/_shared/debug-failed-run.md +53 -0
  5. package/dist/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +14 -2
  6. package/dist/plugin/skills/_shared/github-cli-recipes.md +1 -1
  7. package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  8. package/dist/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +5 -1
  9. package/dist/plugin/skills/do/e2e-acceptance.md +2 -0
  10. package/dist/plugin/skills/do/input-routing.md +1 -1
  11. package/dist/plugin/skills/do/resolve-conflicts.md +21 -19
  12. package/dist/plugin/skills/muggle-do/SKILL.md +1 -1
  13. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  14. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  15. package/dist/plugin/skills/muggle-pr-followup/contract.md +20 -12
  16. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +2 -2
  17. package/dist/plugin/skills/muggle-test/SKILL.md +10 -20
  18. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +4 -13
  19. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +3 -0
  20. package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +13 -0
  21. package/dist/release-manifest.json +4 -4
  22. package/package.json +6 -6
  23. package/plugin/.claude-plugin/plugin.json +1 -1
  24. package/plugin/.cursor-plugin/plugin.json +1 -1
  25. package/plugin/skills/_shared/debug-failed-run.md +53 -0
  26. package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +14 -2
  27. package/plugin/skills/_shared/github-cli-recipes.md +1 -1
  28. package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  29. package/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +5 -1
  30. package/plugin/skills/do/e2e-acceptance.md +2 -0
  31. package/plugin/skills/do/input-routing.md +1 -1
  32. package/plugin/skills/do/resolve-conflicts.md +21 -19
  33. package/plugin/skills/muggle-do/SKILL.md +1 -1
  34. package/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  35. package/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  36. package/plugin/skills/muggle-pr-followup/contract.md +20 -12
  37. package/plugin/skills/muggle-pr-followup/state-schemas.md +2 -2
  38. package/plugin/skills/muggle-test/SKILL.md +10 -20
  39. package/plugin/skills/muggle-test-feature-local/SKILL.md +4 -13
  40. package/plugin/skills/muggle-test-prepare/SKILL.md +3 -0
  41. package/plugin/skills/muggle-test-prepare/steps/check-running.md +13 -0
package/README.md CHANGED
@@ -19,7 +19,7 @@ Your AI assistant writes code fast. But does the feature actually work? Does the
19
19
 
20
20
  muggle-ai-works closes the gap between "code complete" and "actually works."
21
21
 
22
- - **Catch UX regressions before your users do** — `/muggle:muggle-test` AI drives a real browser against your localhost across desktop and mobile resolutions, clicks through flows like a user would, and reports failures with step-by-step screenshots. No Playwright scripts to maintain.
22
+ - **You create, we verify** — `/muggle:muggle-test` AI drives a real browser against your localhost across desktop and mobile resolutions, clicks through flows like a user would, and reports failures with step-by-step screenshots. No Playwright scripts to maintain.
23
23
  - **Go from requirement to merged PR in one command** — `/muggle:muggle-do` handles the full cycle: code the feature, run unit tests, run E2E acceptance tests against the app in a real browser at multiple viewports, triage failures, and open a PR with evidence attached.
24
24
  - **70+ MCP tools for custom workflows** — manage projects, generate test cases from plain English, replay test scripts, batch-run regressions, and publish results to your team. Works in Claude Code, Cursor, and any MCP client.
25
25
 
@@ -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": "5.0.2",
4
+ "version": "5.0.4",
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": "5.0.2",
5
+ "version": "5.0.4",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -0,0 +1,53 @@
1
+ # Debug a Failed Run — Shared Reference
2
+
3
+ > Presentation layer for a single failed run. Turns one non-passing terminal run — local or remote — into a debug path: **evidence → diagnosis → a guaranteed user choice that always offers "give feedback & rerun".** Used by `muggle-test` (Step 7C), `muggle-test-feature-local`, and the `muggle-do` e2e stage. Depends on [`failure-mode-handling.md`](./failure-mode-handling.md) for the bucket taxonomy and telemetry schema — never the reverse. Don't restate its tables; read them.
4
+
5
+ ## The guarantee
6
+
7
+ Every non-passing terminal run MUST route through this doc before it is reported. A failure is never summarized-and-dropped: the user is always shown *what happened* and is always presented a selection in which **"give feedback & rerun" is a first-class option**. "Skip — just report" stays selectable but is never the default.
8
+
9
+ The guarantee binds **interactive** callers. An autonomous caller with no user to prompt (the `acceptance-tester` agent) runs Steps 1–2 (evidence + diagnosis into its structured report) and skips Step 3's interactive offer.
10
+
11
+ ## Inputs (the caller passes these in)
12
+
13
+ - `runId` (local) or workflow runtime id (remote).
14
+ - `mode` — `replay` or `regen`, the mode that failed.
15
+ - `testCaseId`, `projectId`.
16
+ - A re-execute handle — the loop/tool the caller used to run this case, so a rerun re-enters the same execution path.
17
+
18
+ ## Step 1 — Gather evidence
19
+
20
+ Read **structured run fields**, never the `execute` stdout tail (see [`dev-loop/failures.md`](./dev-loop/failures.md)). Assemble:
21
+
22
+ - **Attempted steps + reasoning** — local: the attempted steps + `summaryStep` halt reason from `action-script.json` in `artifactsDir`; remote: the per-step list + `summaryStep` from `muggle-remote-wf-get-ts-gen-latest-run` / `muggle-remote-wf-get-ts-replay-latest-run`.
23
+ - **Visual evidence** — a failed run already preserves the full step-by-step on disk: every per-step frame under `<artifactsDir>/electron-runtime/screenshot/` (with per-step label data under `.../dataset/`), alongside the step script at `<artifactsDir>/action-script.json`. `run-result-get` returns `artifactsDir` — read the whole set there; don't trust a step's `screenshotLocalPath`, which points at the original runtime dir. Once published — failed runs are published too, see [`dev-loop/publish.md`](./dev-loop/publish.md) — the same frames are cloud-hosted per step as `screenshotUrl`, the form remote runs expose directly.
24
+ - **Verdict** — `Status` + `Error`.
25
+
26
+ ## Step 2 — Diagnose
27
+
28
+ Classify into the failure bucket per [`failure-mode-handling.md`](./failure-mode-handling.md) — §B for a replay failure, §C for a regen failure. That bucket **is** the initial diagnosis. Phrase it for a human ("Looks like a **stale script** — the selectors moved; the product itself probably still works"), not as a telemetry label. Emit the `replay-failure-classified` / `regen-failure-classified` event now, before presenting anything.
29
+
30
+ ## Step 3 — Present the debug card, then the guaranteed offer
31
+
32
+ Show the **debug card** first: attempted steps + reasoning, the failing step's screenshot (or a one-line note if a path is genuinely absent), and the one-line diagnosis.
33
+
34
+ Then present one `AskUserQuestion` whose options are:
35
+
36
+ 1. **Give feedback & rerun** — always present. Invoke the `muggle-feedback` skill with this run's anchor (`runId` local / `testScriptId` remote) so the user says what should have happened, then re-execute per Step 4.
37
+ 2. **The bucket's recommended action** from §B/§C (regenerate, report bug, share defect, retry…). Label it `(Recommended)` — it is the classifier's pick.
38
+ 3. **Retry as-is**.
39
+ 4. **Skip — just report** — last, never the default.
40
+
41
+ The bucket's recommended action and its alternatives live in `failure-mode-handling.md` §B/§C — read them there, don't restate them.
42
+
43
+ **Feedback anchor by lane.** Feedback attaches to a cloud action-script id. `muggle-feedback` owns resolving it: for a **local** run it publishes/uploads first when the run isn't already published; for a **remote** run it uses the existing script. Pass `runId` (local) or `testScriptId` (remote) and let it resolve the anchor — don't resolve or upload here.
44
+
45
+ ## Step 4 — Rerun (always regen)
46
+
47
+ On "give feedback & rerun": once `muggle-feedback` returns, re-execute the test case in **regen** mode — feedback adjusts the agent's course, so the prior script is discarded; replay is never the rerun path. Local: the regen branch of [`dev-loop/run.md`](./dev-loop/run.md). Remote: `muggle-remote-workflow-start-test-script-generation`. Read the new run's result back through this doc (Step 1) so a second failure is debugged too.
48
+
49
+ ## Step 5 — Resolve
50
+
51
+ Emit the `replay-failure-resolved` / `regen-failure-resolved` event with `userAction` set to the pick (`feedback-rerun`, the bucket action, `retry`, or `skip`) — one `*-classified` and one `*-resolved` per failure, per [`failure-mode-handling.md`](./failure-mode-handling.md) §D.
52
+
53
+ Process failures one at a time so the user isn't drowning in pickers.
@@ -1,6 +1,6 @@
1
1
  # PR metadata snapshot
2
2
 
3
- Fetch the fields the watcher and bootstrap need in one call.
3
+ Fetch the fields the watcher and bootstrap need.
4
4
 
5
5
  ```bash
6
6
  gh pr view <pr-number> --repo <owner>/<repo> \
@@ -10,4 +10,16 @@ gh pr view <pr-number> --repo <owner>/<repo> \
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.
13
+ - `mergeable` is `MERGEABLE`, `CONFLICTING`, or `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick). The watcher's **conflict** signal is `mergeable == CONFLICTING` (corroborated by `mergeStateStatus == DIRTY`).
14
+
15
+ ## Behind-by (out-of-date detection)
16
+
17
+ `mergeStateStatus == BEHIND` is **not** a reliable out-of-date signal. GitHub collapses merge state into one value with precedence — `DIRTY` (conflict) and `BLOCKED` (missing required review, pending/failing required check) outrank `BEHIND` and mask it, and `BEHIND` surfaces *at all* only when the base enforces "require branches up to date." So a PR that is genuinely behind **and** awaiting review reports `BLOCKED`; `BEHIND` never shows, and its staleness goes unseen.
18
+
19
+ Detect out-of-date straight from commit ancestry instead — independent of merge-state precedence, review state, and branch protection:
20
+
21
+ ```bash
22
+ gh api repos/<owner>/<repo>/compare/<baseRefName>...<head_sha> --jq '.behind_by'
23
+ ```
24
+
25
+ `behind_by > 0` ⇒ the head is missing that many base commits ⇒ out of date. `0` ⇒ current with base. (`ahead_by` counts the head's own commits — ignore it.) This is the watcher's out-of-date trigger; it is exact even while `mergeable == UNKNOWN`.
@@ -8,7 +8,7 @@ Skills assume a working `gh auth status`. Auth errors surface verbatim from `gh`
8
8
 
9
9
  | Recipe | Use case |
10
10
  | :----- | :------- |
11
- | [`pr-metadata`](github-cli-recipes/pr-metadata.md) | Snapshot PR state, head SHA, branch — watcher + bootstrap. |
11
+ | [`pr-metadata`](github-cli-recipes/pr-metadata.md) | Snapshot PR state, head SHA, branch, conflict + `behind_by` out-of-date detection — watcher + bootstrap. |
12
12
  | [`submitted-reviews`](github-cli-recipes/submitted-reviews.md) | Fetch a review by id / watcher's body-only-review check. |
13
13
  | [`pr-checks`](github-cli-recipes/pr-checks.md) | Check-run rollup for the head SHA — watcher's CI poll. |
14
14
  | [`line-comments-for-review`](github-cli-recipes/line-comments-for-review.md) | Pull a review's line comments — `/muggle-do` per-comment routing. |
@@ -17,7 +17,7 @@ One per address-reviews invocation, regardless of outcome.
17
17
  "ci_checks_escalated": ["<check-name>", ...],
18
18
  "head_sha_before": "<sha-or-null>",
19
19
  "head_sha_after": "<sha-or-null>",
20
- "outcome": "pushed" | "escalated" | "mixed" | "no-op" | "self-loop-skip" | "ci-fixed" | "ci-escalated"
20
+ "outcome": "pushed" | "escalated" | "mixed" | "no-op" | "self-loop-skip" | "ci-fixed" | "ci-escalated" | "rebased" | "rebase-escalated"
21
21
  }
22
22
  ```
23
23
 
@@ -29,5 +29,7 @@ One per address-reviews invocation, regardless of outcome.
29
29
  - `"self-loop-skip"` — review was a synthetic wrapper around the agent's own reply (every line comment is a reply carrying the loop marker `<!-- muggle-do:bot -->`). Cursor advanced silently; no work, no escalation.
30
30
  - `"ci-fixed"` — a watcher-dispatched fix-ci cycle pushed a fix for one or more red checks.
31
31
  - `"ci-escalated"` — fix-ci exhausted its 3 attempts for the SHA or the failing checks were out of scope; the SHA was added to `ci_escalated_shas`. No further auto-fix on it.
32
+ - `"rebased"` — a watcher-dispatched rebase cycle rebased the branch onto its base (behind-only or conflicts resolved), verified, and force-pushed.
33
+ - `"rebase-escalated"` — the rebase couldn't be completed (a conflict under `autoResolveConflicts=never`, verification failed, or the 2-attempt budget for the SHA was spent); the SHA was added to `conflict_escalated_shas`. No further auto-rebase on it.
32
34
 
33
- For fix-ci cycles (`ci-fixed` / `ci-escalated`) the `review_ids_*` arrays are empty and the `ci_checks_*` arrays carry the data: `ci_checks_in` (red checks dispatched), `ci_checks_fixed` (made green and pushed), `ci_checks_escalated` (out-of-scope or unresolved).
35
+ For fix-ci cycles (`ci-fixed` / `ci-escalated`) the `review_ids_*` arrays are empty and the `ci_checks_*` arrays carry the data: `ci_checks_in` (red checks dispatched), `ci_checks_fixed` (made green and pushed), `ci_checks_escalated` (out-of-scope or unresolved). For rebase cycles (`rebased` / `rebase-escalated`) all the `review_ids_*` and `ci_checks_*` arrays are empty; the SHA fields carry the before/after of the rebase.
@@ -11,6 +11,8 @@ One per watcher iteration (idle or not).
11
11
  "pr_number": <int>,
12
12
  "actionable_threads": <int>,
13
13
  "dispatched_review_ids": [<int>, ...],
14
+ "rebase_needed": true | false,
15
+ "dispatched_rebase": true | false,
14
16
  "checks_red": <int>,
15
17
  "dispatched_ci_fix": true | false,
16
18
  "terminal": true | false,
@@ -21,7 +23,9 @@ One per watcher iteration (idle or not).
21
23
 
22
24
  - `actionable_threads`: count of actionable items this tick — unresolved, non-outdated threads whose newest comment is unmarked, plus body-only reviews past `lastBodyReviewId` — **after** filtering by the escalated set.
23
25
  - `dispatched_review_ids`: owning review ids handed to `/muggle-do`. Empty when idle.
24
- - `checks_red`: count of failing checks on the head SHA. `0` when reviews were dispatched (reviews preempt the CI poll) or CI was green/pending.
26
+ - `rebase_needed`: true when the branch is behind its base (`behind_by > 0`) or conflicting (`mergeable == CONFLICTING`). `false` when reviews were dispatched (reviews preempt the mergeability check).
27
+ - `dispatched_rebase`: true when this tick dispatched `/muggle-do` with a rebase directive.
28
+ - `checks_red`: count of failing checks on the head SHA. `0` when reviews or a rebase were dispatched (both preempt the CI poll) or CI was green/pending.
25
29
  - `dispatched_ci_fix`: true when this tick dispatched `/muggle-do` with a fix-ci directive.
26
30
  - `terminal`: true when this tick observed the PR merged or closed and wrote `result.md`.
27
31
  - `idle`: true when nothing was dispatched this tick.
@@ -145,6 +145,8 @@ For each test case:
145
145
 
146
146
  **Overall:** PASS | FAIL | PARTIAL | INCONCLUSIVE | BLOCKED | SKIPPED — see [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) section F for the canonical taxonomy.
147
147
 
148
+ Failed runs use the same evidence + diagnosis assembly as the interactive debug path ([`../_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) Steps 1–2) — write that evidence into the **Failed** block above. This stage is autonomous, so it skips Step 3's interactive offer.
149
+
148
150
  ## Hard constraints
149
151
 
150
152
  - **Do NOT shut down the dev server.** The caller manages dev-server lifecycle.
@@ -4,7 +4,7 @@ How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–4 are programmatic
4
4
 
5
5
  1. **Address-reviews** — a `github.com/.../pull/<n>` URL **and** one or more review ids (integers ≥ 100000000) → [`address-reviews.md`](address-reviews.md).
6
6
  2. **Fix-CI** — a `github.com/.../pull/<n>` URL **and** a `fix ci` / `fix-ci` directive with failing check names (no review ids) → [`fix-ci.md`](fix-ci.md).
7
- 3. **Resolve-conflicts** — a `github.com/.../pull/<n>` URL **and** a `resolve conflicts` / `resolve-conflicts` directive (no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md).
7
+ 3. **Rebase** — a `github.com/.../pull/<n>` URL **and** a `rebase` directive (or legacy `resolve conflicts` / `resolve-conflicts`; no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md). Rebases the branch onto its base whether it's merely behind or actually conflicting.
8
8
  4. **Post-merge cleanup** — a `cleanup` token and `slug=<slug>` (no PR URL, no review ids), optionally `state=<merged|closed>` (default `merged`) → [`cleanup.md`](cleanup.md).
9
9
  5. **Empty / `help` / `menu` / `?`** → menu + session selector.
10
10
  6. **Task automation** (perform an action on a website) → `muggle:muggle-browser-task`.
@@ -1,16 +1,16 @@
1
- # Resolve-Conflicts (watcher-dispatched)
1
+ # Rebase (watcher-dispatched)
2
2
 
3
- Rebase a PR whose branch conflicts with its base, resolve the conflicts behind a verify-or-rollback gate, and force-push — so a mergeable-blocked PR doesn't sit idle forever. A dumb-pipe dispatch like fix-ci: the watcher detects `mergeable == CONFLICTING` and hands off; the executor owns the rebase + resolution, never the decision to dispatch.
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
4
 
5
5
  ## Turn preamble
6
6
 
7
7
  ```
8
- **/muggle-do resolve-conflicts** — rebasing <owner>/<repo>#<n> onto <base> to clear merge conflicts.
8
+ **/muggle-do rebase** — rebasing <owner>/<repo>#<n> onto <base> to bring the branch up to date.
9
9
  ```
10
10
 
11
11
  ## Input
12
12
 
13
- `$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and a `resolve conflicts` directive (no review ids, no failing check names). Parse all three.
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
14
 
15
15
  ## Inputs from disk
16
16
 
@@ -20,43 +20,45 @@ From `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR + branch + `head_
20
20
 
21
21
  ### Step 1 — Re-attach
22
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 `conflict_sha = prs.json[0].head_sha` and the base branch (`baseRefName` from [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md)).
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/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md)).
24
24
 
25
- ### Step 2 — Rebase onto base + resolve
25
+ ### Step 2 — Rebase onto base (resolve conflicts if any)
26
26
 
27
- Run [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) against the base branch (it fires because a conflicting PR is behind). Conflict handling follows [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
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
28
 
29
- - default `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub, or opts into `autoResolveConflicts=always`.
30
- - `always` → resolve behind the verify-or-rollback gate in [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md).
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
+ - default `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub, or opts into `autoResolveConflicts=always`.
32
+ - `always` → resolve behind the verify-or-rollback gate in [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md).
31
33
 
32
34
  ### Step 3 — Verify the resolution
33
35
 
34
- 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 resolution that does not verify is rolled back → escalate per Step 5. **Never push an unverified merge.**
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.**
35
37
 
36
38
  ### Step 4 — Force-push + respawn
37
39
 
38
- Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[conflict_sha]`. Respawn the watcher as the last action:
40
+ Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_sha]`. Respawn the watcher as the last action:
39
41
 
40
42
  ```
41
43
  /loop 1m /muggle:muggle-pr-followup <slug> <n>
42
44
  ```
43
45
 
44
- The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5b), so this restart is the single live watcher. Its next tick re-checks mergeability on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
46
+ The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5b), so this restart is the single live watcher. 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.
45
47
 
46
48
  ### Step 5 — Escalate (can't resolve / budget spent)
47
49
 
48
- When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[conflict_sha]` has reached 2:
50
+ When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[rebase_sha]` has reached 2:
49
51
 
50
- 1. Add `conflict_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
51
- 2. Emit one terminal escalation naming the PR and the conflicting files.
52
+ 1. Add `rebase_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
53
+ 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).
52
54
  3. Respawn the watcher (last action) — it keeps polling for the user's manual resolution or any new reviews.
53
55
 
54
56
  ### Step 6 — Telemetry
55
57
 
56
- Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.md`](../_shared/telemetry-events/muggle-do-cycle.md)) with `outcome: "conflicts-resolved"` (a verified rebase pushed) or `"conflicts-escalated"`.
58
+ 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"`.
57
59
 
58
60
  ## Guardrails
59
61
 
60
- - Max 2 resolve attempts per SHA; then escalate rather than churn.
61
- - Never push an unverified merge — verify-or-rollback always.
62
- - The default `autoResolveConflicts=never` escalates to the user rather than guessing a merge. Auto-resolution is strictly opt-in.
62
+ - Max 2 rebase attempts per SHA; then escalate rather than churn.
63
+ - Never push an unverified rebase — verify-or-rollback always.
64
+ - The default `autoResolveConflicts=never` escalates to the user rather than guessing a conflict resolution. Auto-resolution of conflicts is strictly opt-in; a clean behind-only rebase needs no opt-in.
@@ -55,7 +55,7 @@ When invoked with the directive (PR URL + slug + review ids), routes to [`../do/
55
55
 
56
56
  ## Input routing
57
57
 
58
- `/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and four programmatic modes the watcher dispatches (address-reviews, fix-ci, resolve-conflicts, post-merge cleanup). Resolve `$ARGUMENTS` to a mode per [`../do/input-routing.md`](../do/input-routing.md) before doing anything else.
58
+ `/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and four programmatic modes the watcher dispatches (address-reviews, fix-ci, rebase, post-merge cleanup). Resolve `$ARGUMENTS` to a mode per [`../do/input-routing.md`](../do/input-routing.md) before doing anything else.
59
59
 
60
60
  ## Preferences
61
61
 
@@ -1,6 +1,6 @@
1
1
  # muggle-pr-followup — folder TOC
2
2
 
3
- This folder holds the watcher loop for PR review follow-ups. The watcher is a **dumb pipe**: it polls for new submitted reviews and CI checks and dispatches `/muggle-do` when there's review feedback or fixable red CI. Cycle execution, classification, replies, and escalation all live in `/muggle-do`'s address-reviews mode — see [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the architectural rationale.
3
+ This folder holds the watcher loop that drives one PR toward merge-ready. The watcher is a **dumb pipe**: it polls for actionable review threads, CI checks, and the branch's standing against its base, and dispatches `/muggle-do` when there's review feedback, fixable red CI, or a branch behind or conflicting with its base. Cycle execution, classification, replies, rebases, and escalation all live in `/muggle-do` — see [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the architectural rationale.
4
4
 
5
5
  ## Files in this folder
6
6
 
@@ -7,7 +7,7 @@ description: "Use when the user wants a pull request's incoming review feedback
7
7
 
8
8
  > Telemetry first step: see [`../_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-pr-followup"`.
9
9
 
10
- A watcher that babysits one open PR's review thread, CI, and merge-conflict state. Polls for new submitted reviews, check-run state, and mergeability; when review feedback lands, CI goes red, or the branch conflicts with its base, hands the work to `/muggle-do` and exits. On merge or close, it hands the terminal wrap-up to `/muggle-do` the same way — teardown when merged, then a next-step suggestion. `/muggle-do` is the executor — it classifies the reviews, fixes the failing checks, or rebases-and-resolves the conflict, pushes, replies per comment, and respawns the watcher.
10
+ A watcher that babysits one open PR toward **merge-ready** — review threads addressed, CI green, and the branch rebased on its base. Polls for actionable feedback, check-run state, and the branch's standing against its base; when feedback lands, CI goes red, or the branch falls behind or conflicts with its base, hands the work to `/muggle-do` and exits. On merge or close, it hands the terminal wrap-up to `/muggle-do` the same way — teardown when merged, then a next-step suggestion. `/muggle-do` is the executor — it classifies the reviews, fixes the failing checks, or rebases onto the base (resolving any conflicts), pushes, replies per comment, and respawns the watcher.
11
11
 
12
12
  **The watcher is a dumb pipe.** It does not classify reviews, iterate cycles, post replies, or escalate. All of that lives in `/muggle-do`. See [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the rationale.
13
13
 
@@ -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 actionable review threads, CI checks, and merge-conflict state, dispatches `/muggle-do` if there's unaddressed 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.
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 actionable review threads, CI checks, and the branch's standing against its base, dispatches `/muggle-do` if there's unaddressed review feedback, fixable red CI, or a branch that's behind or conflicting with its base, and exits. It does not classify, fix, resolve, rebase, 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 docs `architecture/2026-05-08-muggle-do-pr-comment-loop-design.md` (the overall loop) and `architecture/2026-06-06-pr-followup-thread-state-baseline-design.md` (the thread-state dispatch trigger).
6
6
 
@@ -31,7 +31,7 @@ If `prs.json[0].state` on disk is already `merged` or `closed`, this slot was fi
31
31
 
32
32
  ### Step 1 — Refresh PR state
33
33
 
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.
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` (conflict signal) for Step 5, and run the recipe's `compare` call to capture `behind_by` (out-of-date signal) for Step 5.
35
35
 
36
36
  ### Step 2 — Termination check
37
37
 
@@ -77,22 +77,30 @@ The watcher does **not** classify. Classification, batching, replying, escalatio
77
77
  5. Emit a `tick` event with `actionable_threads: <count>`, `dispatched_review_ids: [<id>, ...]`.
78
78
  6. Exit. **Reviews preempt CI** — when there is actionable feedback, 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.)
79
79
 
80
- ### Step 5 — No actionable feedback → check mergeability
80
+ ### Step 5 — No actionable feedback → keep the branch rebased on its base
81
81
 
82
- 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:
82
+ A merge-ready branch is **current with its base** — neither conflicting nor behind. From the Step 1 metadata, the branch needs a rebase when either:
83
+
84
+ - `mergeable == CONFLICTING` (corroborated by `mergeStateStatus == DIRTY`) — conflicts with the base, **or**
85
+ - `behind_by > 0` — out of date with the base. Read this from the `compare` call (commit ancestry), **never** from `mergeStateStatus == BEHIND`: GitHub masks `BEHIND` behind `DIRTY`/`BLOCKED` and only surfaces it under "require branches up to date" protection, so a stale PR that is also awaiting review or has a red required check reports `BLOCKED` — and its staleness would go unseen. See [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md#behind-by-out-of-date-detection).
86
+
87
+ This trigger is **independent of approval and CI state**: an out-of-date branch is rebased whether or not it has been reviewed, approved, or has green checks. The watcher acts on staleness directly — it never waits for an approval to surface it.
88
+
89
+ If a rebase is due **and** `conflict_resolve_attempts[head_sha] < 2` **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
83
90
 
84
91
  1. Reset `last_seen.idle_tick_count` to 0.
85
- 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.
86
- 3. Dispatch `/muggle-do` with a *resolve-conflicts* directive (PR URL + slug; no review ids, no check names):
92
+ 2. **Stop this watcher (single-thread):** cancel its cron exactly as in Step 4 — `/muggle-do`'s rebase respawns it when the cycle is done.
93
+ 3. Dispatch `/muggle-do` with a *rebase* directive (PR URL + slug; no review ids, no check names):
87
94
  ```
88
- /muggle-do resolve conflicts on <pr-url> slug=<slug>
95
+ /muggle-do rebase on <pr-url> slug=<slug>
89
96
  ```
90
- 4. Append a dispatching line to `followup.log`; emit a `tick` event with `conflicting: true`, `dispatched_resolve_conflicts: true`.
91
- 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.
97
+ The executor rebases onto the base: a behind-only branch replays cleanly and force-pushes; a conflicting branch resolves behind the `autoResolveConflicts` gate. Both paths are `/muggle-do`'s — the watcher only decides *that* a rebase is due, never how.
98
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `rebase_needed: true`, `dispatched_rebase: true`.
99
+ 5. Exit. The dev cycle owns the PR; its respawn restarts the watcher, whose 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.
92
100
 
93
- `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.
101
+ Otherwise — `behind_by == 0` and not conflicting (`mergeable == UNKNOWN` is fine here: `behind_by` is exact while GitHub is still computing conflict state, so a stale branch still triggers), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
94
102
 
95
- ### Step 6 — No actionable feedback, mergeable → poll CI for the head SHA
103
+ ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
96
104
 
97
105
  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:
98
106
 
@@ -111,7 +119,7 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
111
119
 
112
120
  ### Step 7 — Idle
113
121
 
114
- 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`, `actionable_threads: 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`.
122
+ 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`, `actionable_threads: 0`, `dispatched_review_ids: []`, `rebase_needed: <bool>`, `dispatched_rebase: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
115
123
 
116
124
  ## Output
117
125
 
@@ -56,8 +56,8 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
56
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.
57
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.
58
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.
59
+ - `conflict_resolve_attempts`: per-SHA count of rebase cycles `/muggle-do` has run for this SHA (behind-only or conflicting — both rebase onto the base). The watcher stops dispatching a rebase for a SHA once its count reaches 2. Keyed by head SHA. A clean behind-only rebase produces a new SHA, so the cap only bites a SHA that keeps failing to rebase-and-verify.
60
+ - `conflict_escalated_shas`: head SHAs whose rebase `/muggle-do` gave up on (attempts exhausted, or a conflict under `autoResolveConflicts=never`). The watcher excludes these from rebase dispatch so a hopeless SHA is never re-attempted.
61
61
 
62
62
  ## `state.md`
63
63
 
@@ -297,7 +297,7 @@ Checkout flow FAILED 15.7s 12 https://www.muggle-ai.co
297
297
  Total: 3 tests | 2 passed | 1 failed | 37.1s
298
298
  ```
299
299
 
300
- For failures: show which step failed, the local screenshot path, and a suggestion.
300
+ For failures, don't hand-write a verdict in the summary — route each through the debug path (Step 7C).
301
301
 
302
302
  ## Step 7B: Execute — Remote Mode
303
303
 
@@ -341,20 +341,11 @@ Login with invalid creds COMPLETED rt-def456
341
341
  Checkout flow QUEUED rt-ghi789
342
342
  ```
343
343
 
344
- ## Step 7C: Route failures through the failure-mode handler
344
+ ## Step 7C: Route every failed run through the debug path
345
345
 
346
- For every run with `status: "failed"` (or any non-passing terminal state) from 7A or 7B, follow [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md):
346
+ For every run with `status: "failed"` (or any non-passing terminal state) from 7A or 7B, route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md). This is **mandatory** — a failure is never reported without it. The debug path gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always an option and "skip" is never the default.
347
347
 
348
- - **Replay-mode failures** — section B (buckets: `infra` / `stale-script` / `product-defect`).
349
- - **Regen-mode failures** — section C (buckets: `transient` / `infra` / `agent-course` / `product-uxux`).
350
-
351
- For each failed run:
352
- 1. Read the run with `muggle-local-run-result-get` (local) or `muggle-remote-wf-get-ts-gen-latest-run` / `muggle-remote-wf-get-ts-replay-latest-run` (remote) and extract signals per the heuristics in the shared doc.
353
- 2. Emit `replay-failure-classified` or `regen-failure-classified` via `muggle-local-telemetry-event-emit` **before** asking the user.
354
- 3. Present the recommended action via `AskUserQuestion` along with the alternatives the shared doc lists for that bucket.
355
- 4. After the user picks, emit the matching `*-resolved` event with `userAction` set to what they chose.
356
-
357
- Process failures one at a time so the user isn't drowning in pickers — but emit telemetry per failure regardless.
348
+ Pass it per failed run: the `runId` (local) or workflow runtime id (remote), the `mode` that failed, `testCaseId`, `projectId`, and the execution handle (local: the dev loop from "Run the dev loop"; remote: 7B's workflow-start) so a rerun re-enters the same path. Process failures one at a time so the user isn't drowning in pickers.
358
349
 
359
350
  ## Step 8: Open Results in Browser
360
351
 
@@ -403,23 +394,22 @@ Once a PR exists for this work, offer to keep watching its review thread.
403
394
 
404
395
  The `/mprfollowup` shortcut starts the same watcher manually at any time.
405
396
 
406
- ## Step 10: Offer feedback on failures
407
-
408
- After the report is complete, if **any** test in the run had a `failed` or unexpected status (or the user verbally flags something looked off), suggest the feedback skill:
397
+ ## Step 10: Offer feedback on a clean pass
409
398
 
410
- > "Looks like `<N>` test(s) didn't go as expected. Want to leave feedback on what should've happened? It triggers regeneration on the affected scripts."
399
+ Failures already got a guaranteed feedback-&-rerun offer in Step 7C's debug path — don't re-ask for them here.
411
400
 
412
- Use `AskUserQuestion`:
413
- - **Yes — give feedback** → invoke the `muggle-feedback` skill via the `Skill` tool. Pass the failed run's `runId` (local) or `testScriptId` (remote) as anchor context so the submit flow opens with the correct script already loaded.
401
+ This step is only for a run that **passed** but the user flags as off (a misclick, wrong element, a summary that doesn't match intent). When that happens, use `AskUserQuestion`:
402
+ - **Yes — give feedback** → invoke the `muggle-feedback` skill via the `Skill` tool, passing the run's `runId` (local) or `testScriptId` (remote) as anchor context so the submit flow opens with the correct script already loaded.
414
403
  - **No — skip**
415
404
 
416
- This is a suggestion, not automatic invocation. Skip silently if every test passed cleanly.
405
+ Skip silently if nothing looked off.
417
406
 
418
407
  ## Non-negotiables
419
408
 
420
409
  Each rule below is covered in-step above; these are the ones this skill most often violates, kept here as reinforcement:
421
410
 
422
411
  - **Test-case shape** — never skip the generate→review cycle, never consolidate the generator's micro-tests, one atomic behavior per test case. Creating test cases directly or merging the generator's output is the single most frequent mistake.
412
+ - **Every failure routes through the debug path** — no failed run is summarized-and-dropped. Step 7C → [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) is mandatory, "give feedback & rerun" is always offered, and "skip" is never the default.
423
413
  - **Confirm intent before acting** — local vs remote; never guess the localhost/preview URL.
424
414
  - **PR URLs run in a dedicated worktree** — never switch the user's main checkout; pass that worktree as `cwd`.
425
415
  - **Every selection uses `AskUserQuestion`** — never ask the user to type a number; the user picks the project (never auto-select).
@@ -174,22 +174,13 @@ Read the run record per [`../_shared/dev-loop/failures.md`](../_shared/dev-loop/
174
174
 
175
175
  - Include in the report: status, duration, pass/fail summary, per-step summary (passed runs), artifact paths, errors if failed, and script view URL when publishing ran.
176
176
 
177
- ### 9a. Route failures through the failure-mode handler
177
+ ### 9a. Route a failed run through the debug path
178
178
 
179
- If the run's status is `failed` or any non-passing terminal state, follow [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md):
179
+ If the run's status is `failed` or any non-passing terminal state, route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) — **mandatory**; a failure is never reported without it. It gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always offered and "skip" is never the default.
180
180
 
181
- - **Replay-mode run failed** (the user picked an existing script in Step 5) → section B (buckets: `infra` / `stale-script` / `product-defect`).
182
- - **Regen-mode run failed** (the user picked "Generate new script" or no script existed) → section C (buckets: `transient` / `infra` / `agent-course` / `product-uxux`).
181
+ Pass it: the `runId`, the `mode` that failed (replay if the user picked an existing script in Step 5, else regen), `testCaseId`, `projectId`, and the local execution handle so a rerun re-enters the same path.
183
182
 
184
- Steps:
185
- 1. Read the run via `muggle-local-run-result-get` and extract signals per the heuristics in the shared doc.
186
- 2. Emit `replay-failure-classified` or `regen-failure-classified` via `muggle-local-telemetry-event-emit` **before** asking the user.
187
- 3. Present the recommended action via `AskUserQuestion` with the alternatives the shared doc lists for that bucket.
188
- 4. After the user picks, emit the matching `*-resolved` event with `userAction`.
189
-
190
- If the user picks `muggle-feedback` from any bucket's options, invoke the `muggle-feedback` skill via the `Skill` tool, passing the just-finished `runId` so the submit flow opens with this run preloaded.
191
-
192
- Skip silently when the run passed cleanly — failure-mode events are by definition about failures.
183
+ Skip only when the run passed cleanly — the debug path is by definition about failures.
193
184
 
194
185
  ### 9b. Remind the user to guide the agent (every Electron invocation)
195
186
 
@@ -62,6 +62,7 @@ Gates run per [`preference-gates/README.md`](../muggle-preferences/preference-ga
62
62
  |------------|-------|
63
63
  | `autoRebase` | [rebase-check](./steps/rebase-check.md) — rebase onto `origin/<default>` before starting dev servers |
64
64
  | `reusePreparePlan` | [reuse-plan](./steps/reuse-plan.md) — reuse the saved prepare plan for this stack, or rediscover |
65
+ | `autoSelectLocalHost` | [check-running](./steps/check-running.md) — reuse the recorded dev-server URL silently, or confirm it each run |
65
66
 
66
67
  ## Workflow
67
68
 
@@ -111,6 +112,8 @@ After a test run, the caller can re-invoke for cleanup or leave services running
111
112
 
112
113
  ## Guardrails
113
114
 
115
+ - **Never invent or default a host/port** — the dev-server URL is a recorded value, not a guess. Resolve it from `<repo>/.muggle-ai/last-host.json` (the [`autoSelectLocalHost`](../muggle-preferences/preference-gates/autoSelectLocalHost.md) cache) before probing ports; a framework default like `:3000` is never a fallback. See [check-running](./steps/check-running.md).
116
+ - **No silent auto-selection without a gate** — when no preference authorizes a silent choice (host, restart, kill), confirm with the user. A gate set to `always` is the only license to skip the question; absent that, ask.
114
117
  - **Verify first, offer to start second** — check what's already running before proposing to start anything.
115
118
  - **The user may prefer to start services themselves** — always offer that option.
116
119
  - **Never start a process the user didn't approve.**
@@ -1,5 +1,18 @@
1
1
  # Check what's already running
2
2
 
3
+ ## Resolve the target host first
4
+
5
+ The dev-server URL the tests will hit is a **recorded value, not a guess** — resolve it before probing anything.
6
+
7
+ 1. Read the cached host with `muggle-local-last-host-get`. It reads `<cwd>/.muggle-ai/last-host.json`; a worktree usually has **no cache of its own**, so when the worktree returns nothing, pass the **main** working-tree root as `cwd` — `git rev-parse --git-common-dir`, then its parent directory.
8
+ 2. Apply the [`autoSelectLocalHost`](../../muggle-preferences/preference-gates/autoSelectLocalHost.md) gate (read its value from the `Muggle Test Preferences` session-context line; absent → `ask`):
9
+ - `always` **and** a cache exists → use it silently: `Using saved local URL {lastHost}`.
10
+ - otherwise (`ask` / `never`, or no cache) → **confirm before using any host.** Run the gate's Picker 1 with `{lastHost}` (cached URL, omitted when absent) and `{suggestedHost}` (a port you actually detect listening). Never auto-pick, and never fall back to a framework default like `:3000`; if nothing is cached or detected, ask the user to type the URL.
11
+
12
+ The resolved host fixes the **expected** dev-server port for the detection below — probe that port; don't infer the target from whichever port happens to be listening.
13
+
14
+ ## Detect listening services
15
+
3
16
  Run port detection and (when an app declares a backend URL) backend-health probe per [`../../_shared/dev-server-readiness.md`](../../_shared/dev-server-readiness.md). Cross-reference hits against selected service directories.
4
17
 
5
18
  > "**backend-api** is already listening on port 3001 (PID 54321) — looks good."
@@ -1,7 +1,7 @@
1
1
  {
2
- "release": "5.0.2",
3
- "buildId": "run-50-1",
4
- "commitSha": "9893215c2e48b50b132e223dbfa952531b7d2442",
5
- "buildTime": "2026-06-08T07:17:59Z",
2
+ "release": "5.0.4",
3
+ "buildId": "run-52-1",
4
+ "commitSha": "d27f03da1295aa4e58c8e2ba6a26a0e76c7700c9",
5
+ "buildTime": "2026-06-09T21:46:24Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
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.0.2",
4
+ "version": "5.0.4",
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.1.0",
44
+ "electronAppVersion": "1.2.3",
45
45
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
46
46
  "runtimeTargetDefault": "production",
47
47
  "checksums": {
48
- "darwin-arm64": "1df66af13de18e90b53a0dfad7b329358265a7380bc8f61b6bfcfaaefdce2825",
49
- "darwin-x64": "3d350547f398c09e485d6b33bb3493d50698ab19a63890792a7a03aeee4a93f2",
50
- "linux-x64": "93bed227dee0c52cff3cdc6e2a346c86d1902ce0ca93bced5632d4d8e7ae290e",
51
- "win32-x64": "c2971e824fea4637ce520b3ee8a2fd8bd8562f1f826ac3674a80931791007088"
48
+ "darwin-arm64": "f46cfc5f052021f13de7d7721276d58863b958ec717631e98b97b7cd3d0fe4fa",
49
+ "darwin-x64": "e2254373a0bb3fffa46b236629c5d54a611ab05e138104a98f6f60a5d1280805",
50
+ "linux-x64": "ff5698fdbc1d65f436b75d860ebe50ee7171c7726565fa0ab2149a6f1cde4d13",
51
+ "win32-x64": "35ffa7af1767e802c7670c85205ea11961cb5583aa5dc30947d142eb7c734ff5"
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": "5.0.2",
4
+ "version": "5.0.4",
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": "5.0.2",
5
+ "version": "5.0.4",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -0,0 +1,53 @@
1
+ # Debug a Failed Run — Shared Reference
2
+
3
+ > Presentation layer for a single failed run. Turns one non-passing terminal run — local or remote — into a debug path: **evidence → diagnosis → a guaranteed user choice that always offers "give feedback & rerun".** Used by `muggle-test` (Step 7C), `muggle-test-feature-local`, and the `muggle-do` e2e stage. Depends on [`failure-mode-handling.md`](./failure-mode-handling.md) for the bucket taxonomy and telemetry schema — never the reverse. Don't restate its tables; read them.
4
+
5
+ ## The guarantee
6
+
7
+ Every non-passing terminal run MUST route through this doc before it is reported. A failure is never summarized-and-dropped: the user is always shown *what happened* and is always presented a selection in which **"give feedback & rerun" is a first-class option**. "Skip — just report" stays selectable but is never the default.
8
+
9
+ The guarantee binds **interactive** callers. An autonomous caller with no user to prompt (the `acceptance-tester` agent) runs Steps 1–2 (evidence + diagnosis into its structured report) and skips Step 3's interactive offer.
10
+
11
+ ## Inputs (the caller passes these in)
12
+
13
+ - `runId` (local) or workflow runtime id (remote).
14
+ - `mode` — `replay` or `regen`, the mode that failed.
15
+ - `testCaseId`, `projectId`.
16
+ - A re-execute handle — the loop/tool the caller used to run this case, so a rerun re-enters the same execution path.
17
+
18
+ ## Step 1 — Gather evidence
19
+
20
+ Read **structured run fields**, never the `execute` stdout tail (see [`dev-loop/failures.md`](./dev-loop/failures.md)). Assemble:
21
+
22
+ - **Attempted steps + reasoning** — local: the attempted steps + `summaryStep` halt reason from `action-script.json` in `artifactsDir`; remote: the per-step list + `summaryStep` from `muggle-remote-wf-get-ts-gen-latest-run` / `muggle-remote-wf-get-ts-replay-latest-run`.
23
+ - **Visual evidence** — a failed run already preserves the full step-by-step on disk: every per-step frame under `<artifactsDir>/electron-runtime/screenshot/` (with per-step label data under `.../dataset/`), alongside the step script at `<artifactsDir>/action-script.json`. `run-result-get` returns `artifactsDir` — read the whole set there; don't trust a step's `screenshotLocalPath`, which points at the original runtime dir. Once published — failed runs are published too, see [`dev-loop/publish.md`](./dev-loop/publish.md) — the same frames are cloud-hosted per step as `screenshotUrl`, the form remote runs expose directly.
24
+ - **Verdict** — `Status` + `Error`.
25
+
26
+ ## Step 2 — Diagnose
27
+
28
+ Classify into the failure bucket per [`failure-mode-handling.md`](./failure-mode-handling.md) — §B for a replay failure, §C for a regen failure. That bucket **is** the initial diagnosis. Phrase it for a human ("Looks like a **stale script** — the selectors moved; the product itself probably still works"), not as a telemetry label. Emit the `replay-failure-classified` / `regen-failure-classified` event now, before presenting anything.
29
+
30
+ ## Step 3 — Present the debug card, then the guaranteed offer
31
+
32
+ Show the **debug card** first: attempted steps + reasoning, the failing step's screenshot (or a one-line note if a path is genuinely absent), and the one-line diagnosis.
33
+
34
+ Then present one `AskUserQuestion` whose options are:
35
+
36
+ 1. **Give feedback & rerun** — always present. Invoke the `muggle-feedback` skill with this run's anchor (`runId` local / `testScriptId` remote) so the user says what should have happened, then re-execute per Step 4.
37
+ 2. **The bucket's recommended action** from §B/§C (regenerate, report bug, share defect, retry…). Label it `(Recommended)` — it is the classifier's pick.
38
+ 3. **Retry as-is**.
39
+ 4. **Skip — just report** — last, never the default.
40
+
41
+ The bucket's recommended action and its alternatives live in `failure-mode-handling.md` §B/§C — read them there, don't restate them.
42
+
43
+ **Feedback anchor by lane.** Feedback attaches to a cloud action-script id. `muggle-feedback` owns resolving it: for a **local** run it publishes/uploads first when the run isn't already published; for a **remote** run it uses the existing script. Pass `runId` (local) or `testScriptId` (remote) and let it resolve the anchor — don't resolve or upload here.
44
+
45
+ ## Step 4 — Rerun (always regen)
46
+
47
+ On "give feedback & rerun": once `muggle-feedback` returns, re-execute the test case in **regen** mode — feedback adjusts the agent's course, so the prior script is discarded; replay is never the rerun path. Local: the regen branch of [`dev-loop/run.md`](./dev-loop/run.md). Remote: `muggle-remote-workflow-start-test-script-generation`. Read the new run's result back through this doc (Step 1) so a second failure is debugged too.
48
+
49
+ ## Step 5 — Resolve
50
+
51
+ Emit the `replay-failure-resolved` / `regen-failure-resolved` event with `userAction` set to the pick (`feedback-rerun`, the bucket action, `retry`, or `skip`) — one `*-classified` and one `*-resolved` per failure, per [`failure-mode-handling.md`](./failure-mode-handling.md) §D.
52
+
53
+ Process failures one at a time so the user isn't drowning in pickers.
@@ -1,6 +1,6 @@
1
1
  # PR metadata snapshot
2
2
 
3
- Fetch the fields the watcher and bootstrap need in one call.
3
+ Fetch the fields the watcher and bootstrap need.
4
4
 
5
5
  ```bash
6
6
  gh pr view <pr-number> --repo <owner>/<repo> \
@@ -10,4 +10,16 @@ gh pr view <pr-number> --repo <owner>/<repo> \
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.
13
+ - `mergeable` is `MERGEABLE`, `CONFLICTING`, or `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick). The watcher's **conflict** signal is `mergeable == CONFLICTING` (corroborated by `mergeStateStatus == DIRTY`).
14
+
15
+ ## Behind-by (out-of-date detection)
16
+
17
+ `mergeStateStatus == BEHIND` is **not** a reliable out-of-date signal. GitHub collapses merge state into one value with precedence — `DIRTY` (conflict) and `BLOCKED` (missing required review, pending/failing required check) outrank `BEHIND` and mask it, and `BEHIND` surfaces *at all* only when the base enforces "require branches up to date." So a PR that is genuinely behind **and** awaiting review reports `BLOCKED`; `BEHIND` never shows, and its staleness goes unseen.
18
+
19
+ Detect out-of-date straight from commit ancestry instead — independent of merge-state precedence, review state, and branch protection:
20
+
21
+ ```bash
22
+ gh api repos/<owner>/<repo>/compare/<baseRefName>...<head_sha> --jq '.behind_by'
23
+ ```
24
+
25
+ `behind_by > 0` ⇒ the head is missing that many base commits ⇒ out of date. `0` ⇒ current with base. (`ahead_by` counts the head's own commits — ignore it.) This is the watcher's out-of-date trigger; it is exact even while `mergeable == UNKNOWN`.
@@ -8,7 +8,7 @@ Skills assume a working `gh auth status`. Auth errors surface verbatim from `gh`
8
8
 
9
9
  | Recipe | Use case |
10
10
  | :----- | :------- |
11
- | [`pr-metadata`](github-cli-recipes/pr-metadata.md) | Snapshot PR state, head SHA, branch — watcher + bootstrap. |
11
+ | [`pr-metadata`](github-cli-recipes/pr-metadata.md) | Snapshot PR state, head SHA, branch, conflict + `behind_by` out-of-date detection — watcher + bootstrap. |
12
12
  | [`submitted-reviews`](github-cli-recipes/submitted-reviews.md) | Fetch a review by id / watcher's body-only-review check. |
13
13
  | [`pr-checks`](github-cli-recipes/pr-checks.md) | Check-run rollup for the head SHA — watcher's CI poll. |
14
14
  | [`line-comments-for-review`](github-cli-recipes/line-comments-for-review.md) | Pull a review's line comments — `/muggle-do` per-comment routing. |
@@ -17,7 +17,7 @@ One per address-reviews invocation, regardless of outcome.
17
17
  "ci_checks_escalated": ["<check-name>", ...],
18
18
  "head_sha_before": "<sha-or-null>",
19
19
  "head_sha_after": "<sha-or-null>",
20
- "outcome": "pushed" | "escalated" | "mixed" | "no-op" | "self-loop-skip" | "ci-fixed" | "ci-escalated"
20
+ "outcome": "pushed" | "escalated" | "mixed" | "no-op" | "self-loop-skip" | "ci-fixed" | "ci-escalated" | "rebased" | "rebase-escalated"
21
21
  }
22
22
  ```
23
23
 
@@ -29,5 +29,7 @@ One per address-reviews invocation, regardless of outcome.
29
29
  - `"self-loop-skip"` — review was a synthetic wrapper around the agent's own reply (every line comment is a reply carrying the loop marker `<!-- muggle-do:bot -->`). Cursor advanced silently; no work, no escalation.
30
30
  - `"ci-fixed"` — a watcher-dispatched fix-ci cycle pushed a fix for one or more red checks.
31
31
  - `"ci-escalated"` — fix-ci exhausted its 3 attempts for the SHA or the failing checks were out of scope; the SHA was added to `ci_escalated_shas`. No further auto-fix on it.
32
+ - `"rebased"` — a watcher-dispatched rebase cycle rebased the branch onto its base (behind-only or conflicts resolved), verified, and force-pushed.
33
+ - `"rebase-escalated"` — the rebase couldn't be completed (a conflict under `autoResolveConflicts=never`, verification failed, or the 2-attempt budget for the SHA was spent); the SHA was added to `conflict_escalated_shas`. No further auto-rebase on it.
32
34
 
33
- For fix-ci cycles (`ci-fixed` / `ci-escalated`) the `review_ids_*` arrays are empty and the `ci_checks_*` arrays carry the data: `ci_checks_in` (red checks dispatched), `ci_checks_fixed` (made green and pushed), `ci_checks_escalated` (out-of-scope or unresolved).
35
+ For fix-ci cycles (`ci-fixed` / `ci-escalated`) the `review_ids_*` arrays are empty and the `ci_checks_*` arrays carry the data: `ci_checks_in` (red checks dispatched), `ci_checks_fixed` (made green and pushed), `ci_checks_escalated` (out-of-scope or unresolved). For rebase cycles (`rebased` / `rebase-escalated`) all the `review_ids_*` and `ci_checks_*` arrays are empty; the SHA fields carry the before/after of the rebase.
@@ -11,6 +11,8 @@ One per watcher iteration (idle or not).
11
11
  "pr_number": <int>,
12
12
  "actionable_threads": <int>,
13
13
  "dispatched_review_ids": [<int>, ...],
14
+ "rebase_needed": true | false,
15
+ "dispatched_rebase": true | false,
14
16
  "checks_red": <int>,
15
17
  "dispatched_ci_fix": true | false,
16
18
  "terminal": true | false,
@@ -21,7 +23,9 @@ One per watcher iteration (idle or not).
21
23
 
22
24
  - `actionable_threads`: count of actionable items this tick — unresolved, non-outdated threads whose newest comment is unmarked, plus body-only reviews past `lastBodyReviewId` — **after** filtering by the escalated set.
23
25
  - `dispatched_review_ids`: owning review ids handed to `/muggle-do`. Empty when idle.
24
- - `checks_red`: count of failing checks on the head SHA. `0` when reviews were dispatched (reviews preempt the CI poll) or CI was green/pending.
26
+ - `rebase_needed`: true when the branch is behind its base (`behind_by > 0`) or conflicting (`mergeable == CONFLICTING`). `false` when reviews were dispatched (reviews preempt the mergeability check).
27
+ - `dispatched_rebase`: true when this tick dispatched `/muggle-do` with a rebase directive.
28
+ - `checks_red`: count of failing checks on the head SHA. `0` when reviews or a rebase were dispatched (both preempt the CI poll) or CI was green/pending.
25
29
  - `dispatched_ci_fix`: true when this tick dispatched `/muggle-do` with a fix-ci directive.
26
30
  - `terminal`: true when this tick observed the PR merged or closed and wrote `result.md`.
27
31
  - `idle`: true when nothing was dispatched this tick.
@@ -145,6 +145,8 @@ For each test case:
145
145
 
146
146
  **Overall:** PASS | FAIL | PARTIAL | INCONCLUSIVE | BLOCKED | SKIPPED — see [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) section F for the canonical taxonomy.
147
147
 
148
+ Failed runs use the same evidence + diagnosis assembly as the interactive debug path ([`../_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) Steps 1–2) — write that evidence into the **Failed** block above. This stage is autonomous, so it skips Step 3's interactive offer.
149
+
148
150
  ## Hard constraints
149
151
 
150
152
  - **Do NOT shut down the dev server.** The caller manages dev-server lifecycle.
@@ -4,7 +4,7 @@ How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–4 are programmatic
4
4
 
5
5
  1. **Address-reviews** — a `github.com/.../pull/<n>` URL **and** one or more review ids (integers ≥ 100000000) → [`address-reviews.md`](address-reviews.md).
6
6
  2. **Fix-CI** — a `github.com/.../pull/<n>` URL **and** a `fix ci` / `fix-ci` directive with failing check names (no review ids) → [`fix-ci.md`](fix-ci.md).
7
- 3. **Resolve-conflicts** — a `github.com/.../pull/<n>` URL **and** a `resolve conflicts` / `resolve-conflicts` directive (no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md).
7
+ 3. **Rebase** — a `github.com/.../pull/<n>` URL **and** a `rebase` directive (or legacy `resolve conflicts` / `resolve-conflicts`; no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md). Rebases the branch onto its base whether it's merely behind or actually conflicting.
8
8
  4. **Post-merge cleanup** — a `cleanup` token and `slug=<slug>` (no PR URL, no review ids), optionally `state=<merged|closed>` (default `merged`) → [`cleanup.md`](cleanup.md).
9
9
  5. **Empty / `help` / `menu` / `?`** → menu + session selector.
10
10
  6. **Task automation** (perform an action on a website) → `muggle:muggle-browser-task`.
@@ -1,16 +1,16 @@
1
- # Resolve-Conflicts (watcher-dispatched)
1
+ # Rebase (watcher-dispatched)
2
2
 
3
- Rebase a PR whose branch conflicts with its base, resolve the conflicts behind a verify-or-rollback gate, and force-push — so a mergeable-blocked PR doesn't sit idle forever. A dumb-pipe dispatch like fix-ci: the watcher detects `mergeable == CONFLICTING` and hands off; the executor owns the rebase + resolution, never the decision to dispatch.
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
4
 
5
5
  ## Turn preamble
6
6
 
7
7
  ```
8
- **/muggle-do resolve-conflicts** — rebasing <owner>/<repo>#<n> onto <base> to clear merge conflicts.
8
+ **/muggle-do rebase** — rebasing <owner>/<repo>#<n> onto <base> to bring the branch up to date.
9
9
  ```
10
10
 
11
11
  ## Input
12
12
 
13
- `$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and a `resolve conflicts` directive (no review ids, no failing check names). Parse all three.
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
14
 
15
15
  ## Inputs from disk
16
16
 
@@ -20,43 +20,45 @@ From `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR + branch + `head_
20
20
 
21
21
  ### Step 1 — Re-attach
22
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 `conflict_sha = prs.json[0].head_sha` and the base branch (`baseRefName` from [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md)).
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/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md)).
24
24
 
25
- ### Step 2 — Rebase onto base + resolve
25
+ ### Step 2 — Rebase onto base (resolve conflicts if any)
26
26
 
27
- Run [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) against the base branch (it fires because a conflicting PR is behind). Conflict handling follows [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
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
28
 
29
- - default `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub, or opts into `autoResolveConflicts=always`.
30
- - `always` → resolve behind the verify-or-rollback gate in [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md).
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
+ - default `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub, or opts into `autoResolveConflicts=always`.
32
+ - `always` → resolve behind the verify-or-rollback gate in [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md).
31
33
 
32
34
  ### Step 3 — Verify the resolution
33
35
 
34
- 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 resolution that does not verify is rolled back → escalate per Step 5. **Never push an unverified merge.**
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.**
35
37
 
36
38
  ### Step 4 — Force-push + respawn
37
39
 
38
- Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[conflict_sha]`. Respawn the watcher as the last action:
40
+ Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_sha]`. Respawn the watcher as the last action:
39
41
 
40
42
  ```
41
43
  /loop 1m /muggle:muggle-pr-followup <slug> <n>
42
44
  ```
43
45
 
44
- The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5b), so this restart is the single live watcher. Its next tick re-checks mergeability on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
46
+ The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5b), so this restart is the single live watcher. 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.
45
47
 
46
48
  ### Step 5 — Escalate (can't resolve / budget spent)
47
49
 
48
- When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[conflict_sha]` has reached 2:
50
+ When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[rebase_sha]` has reached 2:
49
51
 
50
- 1. Add `conflict_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
51
- 2. Emit one terminal escalation naming the PR and the conflicting files.
52
+ 1. Add `rebase_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
53
+ 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).
52
54
  3. Respawn the watcher (last action) — it keeps polling for the user's manual resolution or any new reviews.
53
55
 
54
56
  ### Step 6 — Telemetry
55
57
 
56
- Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.md`](../_shared/telemetry-events/muggle-do-cycle.md)) with `outcome: "conflicts-resolved"` (a verified rebase pushed) or `"conflicts-escalated"`.
58
+ 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"`.
57
59
 
58
60
  ## Guardrails
59
61
 
60
- - Max 2 resolve attempts per SHA; then escalate rather than churn.
61
- - Never push an unverified merge — verify-or-rollback always.
62
- - The default `autoResolveConflicts=never` escalates to the user rather than guessing a merge. Auto-resolution is strictly opt-in.
62
+ - Max 2 rebase attempts per SHA; then escalate rather than churn.
63
+ - Never push an unverified rebase — verify-or-rollback always.
64
+ - The default `autoResolveConflicts=never` escalates to the user rather than guessing a conflict resolution. Auto-resolution of conflicts is strictly opt-in; a clean behind-only rebase needs no opt-in.
@@ -55,7 +55,7 @@ When invoked with the directive (PR URL + slug + review ids), routes to [`../do/
55
55
 
56
56
  ## Input routing
57
57
 
58
- `/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and four programmatic modes the watcher dispatches (address-reviews, fix-ci, resolve-conflicts, post-merge cleanup). Resolve `$ARGUMENTS` to a mode per [`../do/input-routing.md`](../do/input-routing.md) before doing anything else.
58
+ `/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and four programmatic modes the watcher dispatches (address-reviews, fix-ci, rebase, post-merge cleanup). Resolve `$ARGUMENTS` to a mode per [`../do/input-routing.md`](../do/input-routing.md) before doing anything else.
59
59
 
60
60
  ## Preferences
61
61
 
@@ -1,6 +1,6 @@
1
1
  # muggle-pr-followup — folder TOC
2
2
 
3
- This folder holds the watcher loop for PR review follow-ups. The watcher is a **dumb pipe**: it polls for new submitted reviews and CI checks and dispatches `/muggle-do` when there's review feedback or fixable red CI. Cycle execution, classification, replies, and escalation all live in `/muggle-do`'s address-reviews mode — see [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the architectural rationale.
3
+ This folder holds the watcher loop that drives one PR toward merge-ready. The watcher is a **dumb pipe**: it polls for actionable review threads, CI checks, and the branch's standing against its base, and dispatches `/muggle-do` when there's review feedback, fixable red CI, or a branch behind or conflicting with its base. Cycle execution, classification, replies, rebases, and escalation all live in `/muggle-do` — see [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the architectural rationale.
4
4
 
5
5
  ## Files in this folder
6
6
 
@@ -7,7 +7,7 @@ description: "Use when the user wants a pull request's incoming review feedback
7
7
 
8
8
  > Telemetry first step: see [`../_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-pr-followup"`.
9
9
 
10
- A watcher that babysits one open PR's review thread, CI, and merge-conflict state. Polls for new submitted reviews, check-run state, and mergeability; when review feedback lands, CI goes red, or the branch conflicts with its base, hands the work to `/muggle-do` and exits. On merge or close, it hands the terminal wrap-up to `/muggle-do` the same way — teardown when merged, then a next-step suggestion. `/muggle-do` is the executor — it classifies the reviews, fixes the failing checks, or rebases-and-resolves the conflict, pushes, replies per comment, and respawns the watcher.
10
+ A watcher that babysits one open PR toward **merge-ready** — review threads addressed, CI green, and the branch rebased on its base. Polls for actionable feedback, check-run state, and the branch's standing against its base; when feedback lands, CI goes red, or the branch falls behind or conflicts with its base, hands the work to `/muggle-do` and exits. On merge or close, it hands the terminal wrap-up to `/muggle-do` the same way — teardown when merged, then a next-step suggestion. `/muggle-do` is the executor — it classifies the reviews, fixes the failing checks, or rebases onto the base (resolving any conflicts), pushes, replies per comment, and respawns the watcher.
11
11
 
12
12
  **The watcher is a dumb pipe.** It does not classify reviews, iterate cycles, post replies, or escalate. All of that lives in `/muggle-do`. See [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the rationale.
13
13
 
@@ -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 actionable review threads, CI checks, and merge-conflict state, dispatches `/muggle-do` if there's unaddressed 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.
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 actionable review threads, CI checks, and the branch's standing against its base, dispatches `/muggle-do` if there's unaddressed review feedback, fixable red CI, or a branch that's behind or conflicting with its base, and exits. It does not classify, fix, resolve, rebase, 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 docs `architecture/2026-05-08-muggle-do-pr-comment-loop-design.md` (the overall loop) and `architecture/2026-06-06-pr-followup-thread-state-baseline-design.md` (the thread-state dispatch trigger).
6
6
 
@@ -31,7 +31,7 @@ If `prs.json[0].state` on disk is already `merged` or `closed`, this slot was fi
31
31
 
32
32
  ### Step 1 — Refresh PR state
33
33
 
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.
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` (conflict signal) for Step 5, and run the recipe's `compare` call to capture `behind_by` (out-of-date signal) for Step 5.
35
35
 
36
36
  ### Step 2 — Termination check
37
37
 
@@ -77,22 +77,30 @@ The watcher does **not** classify. Classification, batching, replying, escalatio
77
77
  5. Emit a `tick` event with `actionable_threads: <count>`, `dispatched_review_ids: [<id>, ...]`.
78
78
  6. Exit. **Reviews preempt CI** — when there is actionable feedback, 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.)
79
79
 
80
- ### Step 5 — No actionable feedback → check mergeability
80
+ ### Step 5 — No actionable feedback → keep the branch rebased on its base
81
81
 
82
- 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:
82
+ A merge-ready branch is **current with its base** — neither conflicting nor behind. From the Step 1 metadata, the branch needs a rebase when either:
83
+
84
+ - `mergeable == CONFLICTING` (corroborated by `mergeStateStatus == DIRTY`) — conflicts with the base, **or**
85
+ - `behind_by > 0` — out of date with the base. Read this from the `compare` call (commit ancestry), **never** from `mergeStateStatus == BEHIND`: GitHub masks `BEHIND` behind `DIRTY`/`BLOCKED` and only surfaces it under "require branches up to date" protection, so a stale PR that is also awaiting review or has a red required check reports `BLOCKED` — and its staleness would go unseen. See [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md#behind-by-out-of-date-detection).
86
+
87
+ This trigger is **independent of approval and CI state**: an out-of-date branch is rebased whether or not it has been reviewed, approved, or has green checks. The watcher acts on staleness directly — it never waits for an approval to surface it.
88
+
89
+ If a rebase is due **and** `conflict_resolve_attempts[head_sha] < 2` **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
83
90
 
84
91
  1. Reset `last_seen.idle_tick_count` to 0.
85
- 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.
86
- 3. Dispatch `/muggle-do` with a *resolve-conflicts* directive (PR URL + slug; no review ids, no check names):
92
+ 2. **Stop this watcher (single-thread):** cancel its cron exactly as in Step 4 — `/muggle-do`'s rebase respawns it when the cycle is done.
93
+ 3. Dispatch `/muggle-do` with a *rebase* directive (PR URL + slug; no review ids, no check names):
87
94
  ```
88
- /muggle-do resolve conflicts on <pr-url> slug=<slug>
95
+ /muggle-do rebase on <pr-url> slug=<slug>
89
96
  ```
90
- 4. Append a dispatching line to `followup.log`; emit a `tick` event with `conflicting: true`, `dispatched_resolve_conflicts: true`.
91
- 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.
97
+ The executor rebases onto the base: a behind-only branch replays cleanly and force-pushes; a conflicting branch resolves behind the `autoResolveConflicts` gate. Both paths are `/muggle-do`'s — the watcher only decides *that* a rebase is due, never how.
98
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `rebase_needed: true`, `dispatched_rebase: true`.
99
+ 5. Exit. The dev cycle owns the PR; its respawn restarts the watcher, whose 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.
92
100
 
93
- `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.
101
+ Otherwise — `behind_by == 0` and not conflicting (`mergeable == UNKNOWN` is fine here: `behind_by` is exact while GitHub is still computing conflict state, so a stale branch still triggers), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
94
102
 
95
- ### Step 6 — No actionable feedback, mergeable → poll CI for the head SHA
103
+ ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
96
104
 
97
105
  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:
98
106
 
@@ -111,7 +119,7 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
111
119
 
112
120
  ### Step 7 — Idle
113
121
 
114
- 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`, `actionable_threads: 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`.
122
+ 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`, `actionable_threads: 0`, `dispatched_review_ids: []`, `rebase_needed: <bool>`, `dispatched_rebase: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
115
123
 
116
124
  ## Output
117
125
 
@@ -56,8 +56,8 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
56
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.
57
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.
58
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.
59
+ - `conflict_resolve_attempts`: per-SHA count of rebase cycles `/muggle-do` has run for this SHA (behind-only or conflicting — both rebase onto the base). The watcher stops dispatching a rebase for a SHA once its count reaches 2. Keyed by head SHA. A clean behind-only rebase produces a new SHA, so the cap only bites a SHA that keeps failing to rebase-and-verify.
60
+ - `conflict_escalated_shas`: head SHAs whose rebase `/muggle-do` gave up on (attempts exhausted, or a conflict under `autoResolveConflicts=never`). The watcher excludes these from rebase dispatch so a hopeless SHA is never re-attempted.
61
61
 
62
62
  ## `state.md`
63
63
 
@@ -297,7 +297,7 @@ Checkout flow FAILED 15.7s 12 https://www.muggle-ai.co
297
297
  Total: 3 tests | 2 passed | 1 failed | 37.1s
298
298
  ```
299
299
 
300
- For failures: show which step failed, the local screenshot path, and a suggestion.
300
+ For failures, don't hand-write a verdict in the summary — route each through the debug path (Step 7C).
301
301
 
302
302
  ## Step 7B: Execute — Remote Mode
303
303
 
@@ -341,20 +341,11 @@ Login with invalid creds COMPLETED rt-def456
341
341
  Checkout flow QUEUED rt-ghi789
342
342
  ```
343
343
 
344
- ## Step 7C: Route failures through the failure-mode handler
344
+ ## Step 7C: Route every failed run through the debug path
345
345
 
346
- For every run with `status: "failed"` (or any non-passing terminal state) from 7A or 7B, follow [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md):
346
+ For every run with `status: "failed"` (or any non-passing terminal state) from 7A or 7B, route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md). This is **mandatory** — a failure is never reported without it. The debug path gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always an option and "skip" is never the default.
347
347
 
348
- - **Replay-mode failures** — section B (buckets: `infra` / `stale-script` / `product-defect`).
349
- - **Regen-mode failures** — section C (buckets: `transient` / `infra` / `agent-course` / `product-uxux`).
350
-
351
- For each failed run:
352
- 1. Read the run with `muggle-local-run-result-get` (local) or `muggle-remote-wf-get-ts-gen-latest-run` / `muggle-remote-wf-get-ts-replay-latest-run` (remote) and extract signals per the heuristics in the shared doc.
353
- 2. Emit `replay-failure-classified` or `regen-failure-classified` via `muggle-local-telemetry-event-emit` **before** asking the user.
354
- 3. Present the recommended action via `AskUserQuestion` along with the alternatives the shared doc lists for that bucket.
355
- 4. After the user picks, emit the matching `*-resolved` event with `userAction` set to what they chose.
356
-
357
- Process failures one at a time so the user isn't drowning in pickers — but emit telemetry per failure regardless.
348
+ Pass it per failed run: the `runId` (local) or workflow runtime id (remote), the `mode` that failed, `testCaseId`, `projectId`, and the execution handle (local: the dev loop from "Run the dev loop"; remote: 7B's workflow-start) so a rerun re-enters the same path. Process failures one at a time so the user isn't drowning in pickers.
358
349
 
359
350
  ## Step 8: Open Results in Browser
360
351
 
@@ -403,23 +394,22 @@ Once a PR exists for this work, offer to keep watching its review thread.
403
394
 
404
395
  The `/mprfollowup` shortcut starts the same watcher manually at any time.
405
396
 
406
- ## Step 10: Offer feedback on failures
407
-
408
- After the report is complete, if **any** test in the run had a `failed` or unexpected status (or the user verbally flags something looked off), suggest the feedback skill:
397
+ ## Step 10: Offer feedback on a clean pass
409
398
 
410
- > "Looks like `<N>` test(s) didn't go as expected. Want to leave feedback on what should've happened? It triggers regeneration on the affected scripts."
399
+ Failures already got a guaranteed feedback-&-rerun offer in Step 7C's debug path — don't re-ask for them here.
411
400
 
412
- Use `AskUserQuestion`:
413
- - **Yes — give feedback** → invoke the `muggle-feedback` skill via the `Skill` tool. Pass the failed run's `runId` (local) or `testScriptId` (remote) as anchor context so the submit flow opens with the correct script already loaded.
401
+ This step is only for a run that **passed** but the user flags as off (a misclick, wrong element, a summary that doesn't match intent). When that happens, use `AskUserQuestion`:
402
+ - **Yes — give feedback** → invoke the `muggle-feedback` skill via the `Skill` tool, passing the run's `runId` (local) or `testScriptId` (remote) as anchor context so the submit flow opens with the correct script already loaded.
414
403
  - **No — skip**
415
404
 
416
- This is a suggestion, not automatic invocation. Skip silently if every test passed cleanly.
405
+ Skip silently if nothing looked off.
417
406
 
418
407
  ## Non-negotiables
419
408
 
420
409
  Each rule below is covered in-step above; these are the ones this skill most often violates, kept here as reinforcement:
421
410
 
422
411
  - **Test-case shape** — never skip the generate→review cycle, never consolidate the generator's micro-tests, one atomic behavior per test case. Creating test cases directly or merging the generator's output is the single most frequent mistake.
412
+ - **Every failure routes through the debug path** — no failed run is summarized-and-dropped. Step 7C → [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) is mandatory, "give feedback & rerun" is always offered, and "skip" is never the default.
423
413
  - **Confirm intent before acting** — local vs remote; never guess the localhost/preview URL.
424
414
  - **PR URLs run in a dedicated worktree** — never switch the user's main checkout; pass that worktree as `cwd`.
425
415
  - **Every selection uses `AskUserQuestion`** — never ask the user to type a number; the user picks the project (never auto-select).
@@ -174,22 +174,13 @@ Read the run record per [`../_shared/dev-loop/failures.md`](../_shared/dev-loop/
174
174
 
175
175
  - Include in the report: status, duration, pass/fail summary, per-step summary (passed runs), artifact paths, errors if failed, and script view URL when publishing ran.
176
176
 
177
- ### 9a. Route failures through the failure-mode handler
177
+ ### 9a. Route a failed run through the debug path
178
178
 
179
- If the run's status is `failed` or any non-passing terminal state, follow [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md):
179
+ If the run's status is `failed` or any non-passing terminal state, route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) — **mandatory**; a failure is never reported without it. It gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always offered and "skip" is never the default.
180
180
 
181
- - **Replay-mode run failed** (the user picked an existing script in Step 5) → section B (buckets: `infra` / `stale-script` / `product-defect`).
182
- - **Regen-mode run failed** (the user picked "Generate new script" or no script existed) → section C (buckets: `transient` / `infra` / `agent-course` / `product-uxux`).
181
+ Pass it: the `runId`, the `mode` that failed (replay if the user picked an existing script in Step 5, else regen), `testCaseId`, `projectId`, and the local execution handle so a rerun re-enters the same path.
183
182
 
184
- Steps:
185
- 1. Read the run via `muggle-local-run-result-get` and extract signals per the heuristics in the shared doc.
186
- 2. Emit `replay-failure-classified` or `regen-failure-classified` via `muggle-local-telemetry-event-emit` **before** asking the user.
187
- 3. Present the recommended action via `AskUserQuestion` with the alternatives the shared doc lists for that bucket.
188
- 4. After the user picks, emit the matching `*-resolved` event with `userAction`.
189
-
190
- If the user picks `muggle-feedback` from any bucket's options, invoke the `muggle-feedback` skill via the `Skill` tool, passing the just-finished `runId` so the submit flow opens with this run preloaded.
191
-
192
- Skip silently when the run passed cleanly — failure-mode events are by definition about failures.
183
+ Skip only when the run passed cleanly — the debug path is by definition about failures.
193
184
 
194
185
  ### 9b. Remind the user to guide the agent (every Electron invocation)
195
186
 
@@ -62,6 +62,7 @@ Gates run per [`preference-gates/README.md`](../muggle-preferences/preference-ga
62
62
  |------------|-------|
63
63
  | `autoRebase` | [rebase-check](./steps/rebase-check.md) — rebase onto `origin/<default>` before starting dev servers |
64
64
  | `reusePreparePlan` | [reuse-plan](./steps/reuse-plan.md) — reuse the saved prepare plan for this stack, or rediscover |
65
+ | `autoSelectLocalHost` | [check-running](./steps/check-running.md) — reuse the recorded dev-server URL silently, or confirm it each run |
65
66
 
66
67
  ## Workflow
67
68
 
@@ -111,6 +112,8 @@ After a test run, the caller can re-invoke for cleanup or leave services running
111
112
 
112
113
  ## Guardrails
113
114
 
115
+ - **Never invent or default a host/port** — the dev-server URL is a recorded value, not a guess. Resolve it from `<repo>/.muggle-ai/last-host.json` (the [`autoSelectLocalHost`](../muggle-preferences/preference-gates/autoSelectLocalHost.md) cache) before probing ports; a framework default like `:3000` is never a fallback. See [check-running](./steps/check-running.md).
116
+ - **No silent auto-selection without a gate** — when no preference authorizes a silent choice (host, restart, kill), confirm with the user. A gate set to `always` is the only license to skip the question; absent that, ask.
114
117
  - **Verify first, offer to start second** — check what's already running before proposing to start anything.
115
118
  - **The user may prefer to start services themselves** — always offer that option.
116
119
  - **Never start a process the user didn't approve.**
@@ -1,5 +1,18 @@
1
1
  # Check what's already running
2
2
 
3
+ ## Resolve the target host first
4
+
5
+ The dev-server URL the tests will hit is a **recorded value, not a guess** — resolve it before probing anything.
6
+
7
+ 1. Read the cached host with `muggle-local-last-host-get`. It reads `<cwd>/.muggle-ai/last-host.json`; a worktree usually has **no cache of its own**, so when the worktree returns nothing, pass the **main** working-tree root as `cwd` — `git rev-parse --git-common-dir`, then its parent directory.
8
+ 2. Apply the [`autoSelectLocalHost`](../../muggle-preferences/preference-gates/autoSelectLocalHost.md) gate (read its value from the `Muggle Test Preferences` session-context line; absent → `ask`):
9
+ - `always` **and** a cache exists → use it silently: `Using saved local URL {lastHost}`.
10
+ - otherwise (`ask` / `never`, or no cache) → **confirm before using any host.** Run the gate's Picker 1 with `{lastHost}` (cached URL, omitted when absent) and `{suggestedHost}` (a port you actually detect listening). Never auto-pick, and never fall back to a framework default like `:3000`; if nothing is cached or detected, ask the user to type the URL.
11
+
12
+ The resolved host fixes the **expected** dev-server port for the detection below — probe that port; don't infer the target from whichever port happens to be listening.
13
+
14
+ ## Detect listening services
15
+
3
16
  Run port detection and (when an app declares a backend URL) backend-health probe per [`../../_shared/dev-server-readiness.md`](../../_shared/dev-server-readiness.md). Cross-reference hits against selected service directories.
4
17
 
5
18
  > "**backend-api** is already listening on port 3001 (PID 54321) — looks good."