@muggleai/works 5.0.2 → 5.0.3

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 (31) 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/github-cli-recipes/pr-metadata.md +1 -1
  5. package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  6. package/dist/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +5 -1
  7. package/dist/plugin/skills/do/input-routing.md +1 -1
  8. package/dist/plugin/skills/do/resolve-conflicts.md +21 -19
  9. package/dist/plugin/skills/muggle-do/SKILL.md +1 -1
  10. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  11. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  12. package/dist/plugin/skills/muggle-pr-followup/contract.md +17 -11
  13. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +2 -2
  14. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +3 -0
  15. package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +13 -0
  16. package/dist/release-manifest.json +4 -4
  17. package/package.json +6 -6
  18. package/plugin/.claude-plugin/plugin.json +1 -1
  19. package/plugin/.cursor-plugin/plugin.json +1 -1
  20. package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +1 -1
  21. package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  22. package/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +5 -1
  23. package/plugin/skills/do/input-routing.md +1 -1
  24. package/plugin/skills/do/resolve-conflicts.md +21 -19
  25. package/plugin/skills/muggle-do/SKILL.md +1 -1
  26. package/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  27. package/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
  28. package/plugin/skills/muggle-pr-followup/contract.md +17 -11
  29. package/plugin/skills/muggle-pr-followup/state-schemas.md +2 -2
  30. package/plugin/skills/muggle-test-prepare/SKILL.md +3 -0
  31. 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.3",
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.3",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -10,4 +10,4 @@ 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 current this tick). `mergeStateStatus` carries the finer state: `DIRTY` = conflicts with base, `BEHIND` = out of date with base (no conflict), `CLEAN`/`BLOCKED`/`UNSTABLE`/`HAS_HOOKS` = current. The watcher dispatches a rebase on `CONFLICTING`/`DIRTY` **or** `BEHIND` — keeping the branch current with its base, a merge-ready gap no review or CI signal would surface.
@@ -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 (`BEHIND`) or conflicting (`DIRTY`/`CONFLICTING`) with its base. `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.
@@ -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
 
@@ -77,22 +77,28 @@ 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. Read `mergeable` / `mergeStateStatus` from the Step 1 metadata; the branch needs a rebase when either:
83
+
84
+ - `mergeable == CONFLICTING` or `mergeStateStatus == DIRTY` — conflicts with the base, **or**
85
+ - `mergeStateStatus == BEHIND` — out of date with the base, no conflict. An unrebased branch never becomes merge-ready on its own, and is merge-blocked wherever the base requires up-to-date branches.
86
+
87
+ If a rebase is due **and** `conflict_resolve_attempts[head_sha] < 2` **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
83
88
 
84
89
  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):
90
+ 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.
91
+ 3. Dispatch `/muggle-do` with a *rebase* directive (PR URL + slug; no review ids, no check names):
87
92
  ```
88
- /muggle-do resolve conflicts on <pr-url> slug=<slug>
93
+ /muggle-do rebase on <pr-url> slug=<slug>
89
94
  ```
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.
95
+ 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.
96
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `rebase_needed: true`, `dispatched_rebase: true`.
97
+ 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
98
 
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.
99
+ Branch current with its base (`CLEAN` / `BLOCKED` / `UNSTABLE` / `HAS_HOOKS`), `mergeable == UNKNOWN` (GitHub still computing — treat as current this tick), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
94
100
 
95
- ### Step 6 — No actionable feedback, mergeable → poll CI for the head SHA
101
+ ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
96
102
 
97
103
  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
104
 
@@ -111,7 +117,7 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
111
117
 
112
118
  ### Step 7 — Idle
113
119
 
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`.
120
+ 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
121
 
116
122
  ## Output
117
123
 
@@ -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
 
@@ -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.3",
3
+ "buildId": "run-51-1",
4
+ "commitSha": "7285eaa7458b2a240299e20be18ba94590413f2e",
5
+ "buildTime": "2026-06-09T19:26:19Z",
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.3",
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.2",
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": "45c09f605d05fc72c60fae54847a689e1858aac9e2c4acca7f25c17a8cd1ad7b",
49
+ "darwin-x64": "b662a57106f1d90c646180ac506d2f567bbbcea33b3fe5ecb9e83b8166426400",
50
+ "linux-x64": "2f86a28e2dc9c7777710c31001686596ce36c560c93e93c53c9bcc4470c07be4",
51
+ "win32-x64": "42f3f28e58efb207a0f2b4993cbc3f2232c30e20960b29b56a45e4c387777346"
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.3",
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.3",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -10,4 +10,4 @@ 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 current this tick). `mergeStateStatus` carries the finer state: `DIRTY` = conflicts with base, `BEHIND` = out of date with base (no conflict), `CLEAN`/`BLOCKED`/`UNSTABLE`/`HAS_HOOKS` = current. The watcher dispatches a rebase on `CONFLICTING`/`DIRTY` **or** `BEHIND` — keeping the branch current with its base, a merge-ready gap no review or CI signal would surface.
@@ -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 (`BEHIND`) or conflicting (`DIRTY`/`CONFLICTING`) with its base. `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.
@@ -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
 
@@ -77,22 +77,28 @@ 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. Read `mergeable` / `mergeStateStatus` from the Step 1 metadata; the branch needs a rebase when either:
83
+
84
+ - `mergeable == CONFLICTING` or `mergeStateStatus == DIRTY` — conflicts with the base, **or**
85
+ - `mergeStateStatus == BEHIND` — out of date with the base, no conflict. An unrebased branch never becomes merge-ready on its own, and is merge-blocked wherever the base requires up-to-date branches.
86
+
87
+ If a rebase is due **and** `conflict_resolve_attempts[head_sha] < 2` **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
83
88
 
84
89
  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):
90
+ 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.
91
+ 3. Dispatch `/muggle-do` with a *rebase* directive (PR URL + slug; no review ids, no check names):
87
92
  ```
88
- /muggle-do resolve conflicts on <pr-url> slug=<slug>
93
+ /muggle-do rebase on <pr-url> slug=<slug>
89
94
  ```
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.
95
+ 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.
96
+ 4. Append a dispatching line to `followup.log`; emit a `tick` event with `rebase_needed: true`, `dispatched_rebase: true`.
97
+ 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
98
 
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.
99
+ Branch current with its base (`CLEAN` / `BLOCKED` / `UNSTABLE` / `HAS_HOOKS`), `mergeable == UNKNOWN` (GitHub still computing — treat as current this tick), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
94
100
 
95
- ### Step 6 — No actionable feedback, mergeable → poll CI for the head SHA
101
+ ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
96
102
 
97
103
  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
104
 
@@ -111,7 +117,7 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
111
117
 
112
118
  ### Step 7 — Idle
113
119
 
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`.
120
+ 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
121
 
116
122
  ## Output
117
123
 
@@ -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
 
@@ -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."