@muggleai/works 5.0.1 → 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 (71) 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/scripts/guardrails.mjs +2 -1
  5. package/dist/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +1 -1
  6. package/dist/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +14 -6
  7. package/dist/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -2
  8. package/dist/plugin/skills/_shared/github-cli-recipes.md +2 -2
  9. package/dist/plugin/skills/_shared/pr-followup-helpers/classify.md +2 -2
  10. package/dist/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +6 -12
  11. package/dist/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +1 -1
  12. package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  13. package/dist/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +9 -5
  14. package/dist/plugin/skills/do/address-reviews.md +7 -7
  15. package/dist/plugin/skills/do/input-routing.md +1 -1
  16. package/dist/plugin/skills/do/open-prs/forward.md +1 -1
  17. package/dist/plugin/skills/do/resolve-conflicts.md +21 -19
  18. package/dist/plugin/skills/muggle-do/SKILL.md +1 -1
  19. package/dist/plugin/skills/muggle-feedback/SKILL.md +1 -1
  20. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  21. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +3 -3
  22. package/dist/plugin/skills/muggle-pr-followup/auto-track.md +1 -1
  23. package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +8 -6
  24. package/dist/plugin/skills/muggle-pr-followup/contract.md +30 -21
  25. package/dist/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +1 -1
  26. package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +1 -1
  27. package/dist/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +2 -2
  28. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +9 -9
  29. package/dist/plugin/skills/muggle-status/SKILL.md +1 -1
  30. package/dist/plugin/skills/muggle-test/SKILL.md +13 -51
  31. package/dist/plugin/skills/muggle-test/reference.md +35 -0
  32. package/dist/plugin/skills/muggle-test-import/SKILL.md +1 -10
  33. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +4 -1
  34. package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +13 -0
  35. package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
  36. package/dist/release-manifest.json +4 -4
  37. package/package.json +6 -6
  38. package/plugin/.claude-plugin/plugin.json +1 -1
  39. package/plugin/.cursor-plugin/plugin.json +1 -1
  40. package/plugin/scripts/guardrails.mjs +2 -1
  41. package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +1 -1
  42. package/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +14 -6
  43. package/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -2
  44. package/plugin/skills/_shared/github-cli-recipes.md +2 -2
  45. package/plugin/skills/_shared/pr-followup-helpers/classify.md +2 -2
  46. package/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +6 -12
  47. package/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +1 -1
  48. package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +4 -2
  49. package/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +9 -5
  50. package/plugin/skills/do/address-reviews.md +7 -7
  51. package/plugin/skills/do/input-routing.md +1 -1
  52. package/plugin/skills/do/open-prs/forward.md +1 -1
  53. package/plugin/skills/do/resolve-conflicts.md +21 -19
  54. package/plugin/skills/muggle-do/SKILL.md +1 -1
  55. package/plugin/skills/muggle-feedback/SKILL.md +1 -1
  56. package/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -1
  57. package/plugin/skills/muggle-pr-followup/SKILL.md +3 -3
  58. package/plugin/skills/muggle-pr-followup/auto-track.md +1 -1
  59. package/plugin/skills/muggle-pr-followup/bootstrap.md +8 -6
  60. package/plugin/skills/muggle-pr-followup/contract.md +30 -21
  61. package/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +1 -1
  62. package/plugin/skills/muggle-pr-followup/output-templates/help.md +1 -1
  63. package/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +2 -2
  64. package/plugin/skills/muggle-pr-followup/state-schemas.md +9 -9
  65. package/plugin/skills/muggle-status/SKILL.md +1 -1
  66. package/plugin/skills/muggle-test/SKILL.md +13 -51
  67. package/plugin/skills/muggle-test/reference.md +35 -0
  68. package/plugin/skills/muggle-test-import/SKILL.md +1 -10
  69. package/plugin/skills/muggle-test-prepare/SKILL.md +4 -1
  70. package/plugin/skills/muggle-test-prepare/steps/check-running.md +13 -0
  71. package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
@@ -1,16 +1,10 @@
1
- # Reply-echo skip
1
+ # Echo protection (intrinsic under thread-state)
2
2
 
3
- When `/muggle-do` posts a threaded reply to a review comment, GitHub surfaces that reply as a **new submitted review** under the same account. Left unchecked, the next watcher tick reads that review as fresh feedback and dispatches another cycle — which posts another reply, which becomes another review. The loop never converges.
3
+ When `/muggle-do` posts a threaded reply to a review comment, GitHub surfaces that reply as a **new submitted review** under the same account, and the reply becomes the newest comment in its thread. The watcher must never read that as fresh feedback, or it replies to itself forever.
4
4
 
5
- ## Rule
5
+ Under the thread-state dispatch trigger this is **intrinsic** — there is no "advance past the echo" step to get wrong:
6
6
 
7
- A submitted review is an **echo** when **every** comment in it carries the loop marker `<!-- muggle-do:bot -->` (see [`loop-signature.md`](loop-signature.md)). An echo is the loop's own reply wearing a review's clothing, never human intent.
7
+ - **Line-comment threads.** A thread is actionable only when its newest comment lacks the loop marker `<!-- muggle-do:bot -->` (see [`loop-signature.md`](loop-signature.md)). After the loop replies, the newest comment is the loop's own and carries the marker, so the thread drops out of the actionable set on its own.
8
+ - **Body-only reviews.** A loop reply always carries a line comment, so it is never body-only; the body-only check (no line comments, `id > lastBodyReviewId` — see [`../github-cli-recipes/submitted-reviews.md`](../github-cli-recipes/submitted-reviews.md)) excludes echoes structurally.
8
9
 
9
- On an echo review, the watcher must:
10
-
11
- 1. Advance `last_seen.reviewId` past the echo's id (so it is not seen again), and
12
- 2. **Skip it** — never dispatch `/muggle-do` for it.
13
-
14
- ## Detection
15
-
16
- Classify by the marker, never by `author.login` — under a shared account the loop posts as the PR author, so the login cannot tell echo from human. Fetch the review's comments; if the set is non-empty and every comment body contains `<!-- muggle-do:bot -->`, it is an echo. A review with at least one marker-less comment is human feedback and must be processed normally.
10
+ Classify by the marker, never by `author.login` — under a shared account the loop posts as the PR author, so the login cannot tell echo from human.
@@ -37,4 +37,4 @@ fix(ci): lint — remove unused import
37
37
 
38
38
  - Never post a top-level comment in reply to a line-level comment. It loses thread context.
39
39
  - Never `gh pr review --comment` for replies — that endpoint is for *new* reviews.
40
- - Never reply twice to the same comment. The cursor in `last_seen.json` is the only re-entry guard; advance it after every reply.
40
+ - Never reply twice to the same comment. The loop marker on each posted reply is the re-entry guard — a thread whose newest comment is loop-marked is no longer actionable, so the next round won't re-reply.
@@ -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.
@@ -9,8 +9,10 @@ One per watcher iteration (idle or not).
9
9
  "session_slug": "<slug>",
10
10
  "repo": "<owner>/<repo>",
11
11
  "pr_number": <int>,
12
- "reviews_seen": <int>,
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,
@@ -19,9 +21,11 @@ One per watcher iteration (idle or not).
19
21
  }
20
22
  ```
21
23
 
22
- - `reviews_seen`: count of new submitted reviews past the cursor, **after** filtering by the escalated set.
23
- - `dispatched_review_ids`: 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.
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.
25
+ - `dispatched_review_ids`: owning review ids handed to `/muggle-do`. Empty when idle.
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
- - `idle`: true when no reviews were dispatched this tick.
31
+ - `idle`: true when nothing was dispatched this tick.
@@ -13,16 +13,16 @@ The entry procedure for `/muggle-do`'s **address-reviews** mode — invoked by t
13
13
  `$ARGUMENTS` carries:
14
14
  - PR URL: `<owner>/<repo>#<n>` derivable from the URL.
15
15
  - Session slug: `<slug>`.
16
- - List of review ids: one or more integers.
16
+ - Owning review ids: one or more integers (the reviews whose actionable threads or body-only feedback the watcher flagged).
17
17
 
18
- Exact phrasing comes from the watcher's dispatch (see [`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md#step-5-if-one-or-more-new-reviews-dispatch)). Parse all three out of the directive text.
18
+ Exact phrasing comes from the watcher's dispatch (see [`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md)). Parse all three out of the directive text.
19
19
 
20
20
  ## Inputs from disk
21
21
 
22
22
  Read from `~/.muggle-ai/muggle-do/sessions/<slug>/`:
23
23
 
24
24
  - `prs.json` — to locate the PR's local checkout path (the `repo` field maps to a configured local repo) and capture `head_sha_before`.
25
- - `last_seen.json` — for `pushed_shas[]` (used by the resolve-reminder stage) and to update the cursor.
25
+ - `last_seen.json` — for `pushed_shas[]` (used by the resolve-reminder stage) and to update `lastBodyReviewId`.
26
26
  - `state.md` — for the cached `loop_user` login (used by resolve-reminder thread classification).
27
27
 
28
28
  ## Procedure
@@ -37,10 +37,10 @@ Two sources, combined into one batch (dedupe by comment id):
37
37
 
38
38
  **(a) The dispatched reviews.** For each review id in the input:
39
39
 
40
- - Fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md) (cursor 0; filter to the specific id).
40
+ - Fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md) (no watermark; filter to the specific id).
41
41
  - Fetch its line comments per [`../_shared/github-cli-recipes/line-comments-for-review.md`](../_shared/github-cli-recipes/line-comments-for-review.md).
42
42
 
43
- **(b) Unaddressed comments on every unresolved thread.** Fetch unresolved threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). For each thread classified **unaddressed human comment** — newest comment lacks the loop marker `<!-- muggle-do:bot -->` ([`loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)) and post-dates the loop's last marked reply — add it to the batch, even if its review predates the cursor. This is how a human thread follow-up (a marker-less reply) gets addressed. **Exclude** comments whose review id is in `last_seen.escalated_review_ids` — paused awaiting the user, not re-work.
43
+ **(b) Unaddressed comments on every unresolved thread.** Fetch unresolved threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). For each thread classified **unaddressed human comment** — newest comment lacks the loop marker `<!-- muggle-do:bot -->` ([`loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)) and post-dates the loop's last marked reply — add it to the batch — unresolved thread state, not any review-id watermark, is the authority here. This is how a human thread follow-up (a marker-less reply) gets addressed. **Exclude** comments whose review id is in `last_seen.escalated_review_ids` — paused awaiting the user, not re-work.
44
44
 
45
45
  Group (a) and (b) into one combined batch.
46
46
 
@@ -101,7 +101,7 @@ Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable re
101
101
 
102
102
  - `last_seen.cycles_completed` += 1
103
103
  - `last_seen.last_pushed_sha` = the new head SHA (update.md already wrote this; verify)
104
- - `last_seen.reviewId` = max(input review ids ∪ last_seen.reviewId)
104
+ - `last_seen.lastBodyReviewId` = max(body-only input review ids ∪ last_seen.lastBodyReviewId) — line-comment threads need no watermark; they fall out of the actionable set once the per-comment reply carries the loop marker.
105
105
 
106
106
  ### Step 5.5 — Resolve-reminder (runs every round)
107
107
 
@@ -145,5 +145,5 @@ Do **not** push, do **not** post replies, do **not** run resolve-reminder. The c
145
145
  ## Invariants
146
146
 
147
147
  - One `/muggle-do` invocation = at most one push and one resolve-reminder, regardless of how many reviews are in the batch.
148
- - Every input review id ends up in either the cursor (handled) or `escalated_review_ids` (skipped) — never both, never neither.
148
+ - Every input review id ends up either handled (its thread answered with a loop-marked reply, or — for a body-only review — folded into `lastBodyReviewId`) or in `escalated_review_ids` (skipped) — never both, never neither.
149
149
  - The watcher is respawned exactly when the PR is still open at the end of the cycle.
@@ -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`.
@@ -46,7 +46,7 @@ Write `~/.muggle-ai/muggle-do/sessions/<slug>/prs.json` per [`../../muggle-pr-fo
46
46
  [{ "repo": "owner/repo", "number": 142, "url": "...", "head_sha": "...", "state": "open" }]
47
47
  ```
48
48
 
49
- Seed `~/.muggle-ai/muggle-do/sessions/<slug>/last_seen.json` per [`../../muggle-pr-followup/state-schemas.md`](../../muggle-pr-followup/state-schemas.md#last_seenjson) — empty cursor shape with `pushed_shas: []`. Forward mode never has prior reviews to skip, so `reviewId: 0`.
49
+ Seed `~/.muggle-ai/muggle-do/sessions/<slug>/last_seen.json` per [`../../muggle-pr-followup/state-schemas.md`](../../muggle-pr-followup/state-schemas.md#last_seenjson) — empty-watermark shape with `pushed_shas: []`. Forward mode never has prior reviews to skip, so `lastBodyReviewId: 0`.
50
50
 
51
51
  **Do not** seed `cycle.json` or `requirements.md`. The watcher is a dumb pipe; `/muggle-do` reads reviews off GitHub.
52
52
 
@@ -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
  ---
2
2
  name: muggle-feedback
3
- description: Use when the user wants to flag that a generated Muggle Test action script — or one specific step in it — did the wrong thing, and have it captured so Muggle can analyze and regenerate affected scripts. Covers any report that a script or step is wrong, broken, or misbehaving (clicked the wrong element, bad summary, "step N didn't work", "broken at the submit step"), whether the user just ran a test locally or remotely or pastes a Muggle dashboard run/script URL and points at what failed. Also use to view, list, or delete previously submitted feedback. Triggers on '/muggle-feedback', 'give feedback on this run', 'the test was wrong', 'flag this run', 'show/list/delete my feedback'. Auto-detects run context from a recent local run, a remote run, or a dashboard URL in the prompt.
3
+ description: "Use when the user wants to flag that a generated Muggle Test action script — or one specific step in it — did the wrong thing, so Muggle can analyze and regenerate affected scripts. Covers any report that a script or step is wrong, broken, or misbehaving (clicked the wrong element, step N did not work, broken at the submit step), whether from a recent local/remote run or a pasted Muggle dashboard run/script URL. Also lists, views, or deletes previously submitted feedback. Triggers on /muggle-feedback, give feedback on this run, the test was wrong, flag this run."
4
4
  ---
5
5
 
6
6
  # Muggle Test Feedback
@@ -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
 
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: muggle-pr-followup
3
- description: Use this skill when the user wants a pull request's incoming review feedback handled for them — it watches one PR's review thread and, each time a reviewer submits new comments, dispatches the work to address them. Engage on PR-review-follow-up intent: "watch my PR and address review comments as they come in", "keep an eye on PR #123 and respond to reviewer feedback", "follow up on my PR's reviews", "babysit my PR's review thread", "auto-handle reviews on the PR I just opened", "I'm stepping away — handle my PR's reviews while I'm gone". Run with no args to track every PR you pushed this session (any repo); pass a PR URL to start watching a specific one. This is PR-review-specific automation: when the recurring thing the user wants handled is a PR's review comments, use this — not the generic `loop` skill. It only watches and dispatches; the actual edits and replies are `muggle-do`. Not for posting test results to a PR (use muggle-pr-visual-walkthrough).
3
+ description: "Use when the user wants a pull request's incoming review feedback handled for them — it watches one PR's review thread and, each time a reviewer submits new comments, dispatches the work to address them. Engage on PR-review-follow-up intent: \"watch my PR and address review comments as they come in\", \"keep an eye on PR #123 and respond to reviewer feedback\", \"babysit my PR's review thread\", \"I'm stepping away — handle my PR's reviews while I'm gone\". This is PR-review-specific automation — prefer it over the generic loop skill. It only watches and dispatches; the actual edits and replies are muggle-do. Not for posting test results to a PR (use muggle-pr-visual-walkthrough)."
4
4
  ---
5
5
 
6
6
  # muggle-pr-followup
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
 
@@ -34,7 +34,7 @@ Bootstrap accepts three optional trailing flags:
34
34
 
35
35
  - `--slug=<name>` — override the default `<repo>-pr<n>` slug
36
36
  - `--resume` — opt in to reusing an existing session slot (default is refuse on conflict)
37
- - `--forward-only` — pin cursor past existing reviews (skip history). Default is cursor 0, which processes prior submitted reviews on the first tick.
37
+ - `--forward-only` — pin `lastBodyReviewId` past existing **body-only** reviews (skip history on those). Line-comment threads are always picked up from live thread state, regardless of this flag.
38
38
 
39
39
  ## Preferences
40
40
 
@@ -51,7 +51,7 @@ For each PR URL in the track list, run the [`bootstrap.md`](bootstrap.md) proced
51
51
  - **Existing slot → skip silently** (never the slot-conflict abort); add it to the *skipped* list.
52
52
  - **`caller = "auto-track"`** in the bootstrap telemetry event.
53
53
 
54
- Everything else is unchanged: URL parse, metadata + terminal-PR abort, slug, cursor 0 (process prior reviews on the first tick), and the `prs.json`/`last_seen.json`/`state.md` writes minus the pre-flight block.
54
+ Everything else is unchanged: URL parse, metadata + terminal-PR abort, slug, `lastBodyReviewId` 0 (line-comment threads are picked up live from thread state; body-only reviews from id 0), and the `prs.json`/`last_seen.json`/`state.md` writes minus the pre-flight block.
55
55
 
56
56
  ### Step 5 — Print the summary
57
57
 
@@ -17,7 +17,7 @@ Bootstrap asks **one** questionnaire — the E2E validation context the loop wil
17
17
  - `<pr-url>` matches `https?://github\.com/[^/]+/[^/]+/pull/\d+` — required.
18
18
  - `--slug=<name>` overrides the default `<repo>-pr<n>` slug.
19
19
  - `--resume` opts into refreshing an existing slot instead of refusing on conflict.
20
- - `--forward-only` pins the cursor past existing reviews (skip history). Default is cursor 0 — the watcher will pick up prior submitted reviews on its first tick.
20
+ - `--forward-only` pins `lastBodyReviewId` past existing **body-only** reviews (skip history on those). It does **not** affect line-comment threads — those are always picked up from live thread state. Default is `0`.
21
21
 
22
22
  ## Procedure
23
23
 
@@ -46,12 +46,14 @@ Default: `<repo>-pr<n>` (e.g. `muggle-ai-works-pr154`). Override: `--slug=<name>
46
46
  If `~/.muggle-ai/muggle-do/sessions/<slug>/` exists (including a slot just migrated above):
47
47
 
48
48
  - Without `--resume` → exit with the slot-conflict abort. Both remedies (delete + re-run, or pass `--resume`) are spelled out in the message.
49
- - With `--resume` → refresh `prs.json[0].head_sha` to the current `headRefOid` from Step 2; leave `last_seen.json` and the cursor untouched. If `state.md` already has a `## Pre-flight answers` block, skip to Step 8; if not (older session), run Step 6.5 to backfill it, then skip to Step 8.
49
+ - With `--resume` → refresh `prs.json[0].head_sha` to the current `headRefOid` from Step 2; leave `last_seen.json` untouched. If `state.md` already has a `## Pre-flight answers` block, skip to Step 8; if not (older session), run Step 6.5 to backfill it, then skip to Step 8.
50
50
 
51
- ### Step 6 — Resolve the initial cursor
51
+ ### Step 6 — Resolve the body-only watermark
52
52
 
53
- - **Default (no `--forward-only`):** cursor is `0`. The watcher will pick up every existing submitted review on its first tick. This matches the common case where the user opened the PR, left review comments they want addressed, and is now running bootstrap.
54
- - **With `--forward-only`:** fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md), then take `max(id)`. The watcher only acts on later submissions. Use when bootstrapping a PR with stale/already-handled prior reviews you don't want re-processed.
53
+ Line-comment threads need no seeding — the watcher derives them from live thread state on every tick, so existing unresolved threads are picked up on the first tick regardless of this step. This step only sets `lastBodyReviewId`, the narrow watermark for body-only reviews (a submitted review with no line comments).
54
+
55
+ - **Default (no `--forward-only`):** `lastBodyReviewId = 0`. The watcher picks up every existing body-only review on its first tick. Matches the common case — the user opened the PR, left feedback they want addressed, and is now bootstrapping.
56
+ - **With `--forward-only`:** fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md), then take `max(id)`. Body-only reviews at or below that id are treated as already-handled. This no longer hides existing line-comment threads — those are always picked up from thread state.
55
57
 
56
58
  ### Step 6.5 — Resolve E2E validation context
57
59
 
@@ -67,7 +69,7 @@ Write under `~/.muggle-ai/muggle-do/sessions/<slug>/`:
67
69
 
68
70
  **`prs.json`** — see [`state-schemas.md`](state-schemas.md#prsjson). One entry, `state` = `"open"`, `head_sha` from Step 2's `headRefOid`.
69
71
 
70
- **`last_seen.json`** — see [`state-schemas.md`](state-schemas.md#last_seenjson). One key (`"<owner>/<repo>#<n>"`), `reviewId` from Step 6, `last_pushed_sha: null`, `idle_tick_count: 0`, `cycles_completed: 0`, `escalated_review_ids: []`, `pushed_shas: []`.
72
+ **`last_seen.json`** — see [`state-schemas.md`](state-schemas.md#last_seenjson). One key (`"<owner>/<repo>#<n>"`), `lastBodyReviewId` from Step 6, `last_pushed_sha: null`, `idle_tick_count: 0`, `cycles_completed: 0`, `escalated_review_ids: []`, `pushed_shas: []`.
71
73
 
72
74
  **`state.md`** — see [`state-schemas.md`](state-schemas.md#statemd). `Bootstrapped from URL: yes`. Cache the loop-user login. Append the `## Pre-flight answers` block with the fields resolved in Step 6.5, per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md#persisted-fields).
73
75
 
@@ -1,8 +1,8 @@
1
1
  # Watcher Per-Tick Contract
2
2
 
3
- The procedure for the **tick mode** of `muggle-pr-followup` — one polling iteration scoped to one PR. The watcher is a dumb pipe: it polls for new submitted reviews, CI checks, and merge-conflict state, dispatches `/muggle-do` if there's review feedback, fixable red CI, or an unmergeable branch, and exits. It does not classify, fix, resolve, amend requirements, post replies, run cycles, or escalate.
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
- Routing into this mode is documented in [`SKILL.md`](SKILL.md#routing). The architectural rationale lives in the brain doc `architecture/2026-05-08-muggle-do-pr-comment-loop-design.md`.
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
 
7
7
  ## Turn preamble
8
8
 
@@ -48,48 +48,57 @@ If `state` is `MERGED` or `CLOSED`:
48
48
  3. Exit. The watcher has unscheduled itself; no future ticks fire for this PR.
49
49
 
50
50
 
51
- ### Step 3 — Fetch new submitted reviews
51
+ ### Step 3 — Compute the actionable set from live thread state
52
52
 
53
- Per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md). Exclude two kinds of review id:
53
+ The watcher's dispatch trigger is **derived from current GitHub state**, not a stored review-id cursor — see the [thread-state baseline design](../../../../muggle-ai-brain/architecture/2026-06-06-pr-followup-thread-state-baseline-design.md). Two sources, unioned:
54
54
 
55
- - ids in `last_seen.escalated_review_ids` — already escalated; the watcher must not re-dispatch them.
56
- - **echo reviews** per [`../_shared/pr-followup-helpers/echo-skip.md`](../_shared/pr-followup-helpers/echo-skip.md) — a review whose every comment carries the loop marker is the loop's own reply, surfaced by GitHub as a new review. Advance `last_seen.reviewId` past it and skip; never dispatch, or the watcher replies to itself forever.
55
+ **(a) Actionable threads.** Fetch unresolved review threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). A thread is **actionable** when `isResolved == false` **and** `isOutdated == false` **and** its newest comment lacks the loop marker `<!-- muggle-do:bot -->` — classify by the marker, never `author.login` (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)). The marker rule makes echo intrinsic: once the loop has replied, the thread's newest comment is the loop's own, so the thread is no longer actionable — no cursor to advance, no self-recursion (see [`../_shared/pr-followup-helpers/echo-skip.md`](../_shared/pr-followup-helpers/echo-skip.md)).
57
56
 
58
- ### Step 4 — If one or more new reviews → dispatch (reviews preempt CI)
57
+ **(b) Actionable body-only reviews.** A body-only review — a submitted `CHANGES_REQUESTED`/`COMMENTED` review with no line comments — has no thread to derive state from, so it keeps a narrow watermark. Fetch submitted reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md); a body-only review is actionable when `id > last_seen.lastBodyReviewId` **and** `id ∉ last_seen.escalated_review_ids`.
59
58
 
60
- The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher's job is to hand over the list of new review ids and exit.
59
+ Collect the **owning review ids** for dispatch: for each actionable thread, the owning review of its newest comment (`pullRequestReview.databaseId` from the query); plus every actionable body-only review id. The dedup'd union is the dispatch list.
60
+
61
+ ### Step 4 — If the actionable set is non-empty → dispatch (reviews preempt CI)
62
+
63
+ The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher hands over the owning review ids and exits — `/muggle-do`'s address-reviews re-derives the unresolved threads itself (its authority), so the watcher only needs to decide *that* there is work, not enumerate it exhaustively.
61
64
 
62
65
  1. Reset `last_seen.idle_tick_count` to 0.
63
66
  2. **Stop this watcher (single-thread).** Cancel its cron so no tick fires while the dev cycle runs: `CronList`, find the job whose command ends with `/muggle:muggle-pr-followup <slug> <n>` (exact two-arg match), `CronDelete` it. `/muggle-do` respawns the watcher when the cycle finishes — exactly one cron ever, and no tick overlaps a running cycle.
64
67
  3. Dispatch `/muggle-do` with an *address-reviews* directive carrying:
65
68
  - PR URL (from `prs.json[0].url`)
66
69
  - Session slug (from the invocation arguments)
67
- - Every new review id from Step 3, as a space-separated list
70
+ - The owning review ids from Step 3, as a space-separated list
68
71
 
69
72
  Exact phrasing belongs to `/muggle-do`'s intent-routing. A reasonable shape is:
70
73
  ```
71
74
  /muggle-do address reviews <id1> <id2> ... on <pr-url> slug=<slug>
72
75
  ```
73
76
  4. Append a dispatching line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
74
- 5. Emit a `tick` event with `reviews_seen: <count>`, `dispatched_review_ids: [<id>, ...]`.
75
- 6. Exit. **Reviews preempt CI** — when reviews land, this tick dispatches address-reviews and never polls CI. The watcher is now stopped; the dev cycle owns the PR and restarts the watcher when it finishes. (The watcher also self-unschedules in Step 2, terminal.)
77
+ 5. Emit a `tick` event with `actionable_threads: <count>`, `dispatched_review_ids: [<id>, ...]`.
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
+
80
+ ### Step 5 — No actionable feedback → keep the branch rebased on its base
81
+
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:
76
83
 
77
- ### Step 5 — No new reviews → check mergeability
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.
78
86
 
79
- Read `mergeable` / `mergeStateStatus` from the Step 1 metadata. If `mergeable == CONFLICTING` (or `mergeStateStatus == DIRTY`), **and** `conflict_resolve_attempts[head_sha] < 2`, **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
87
+ If a rebase is due **and** `conflict_resolve_attempts[head_sha] < 2` **and** `head_sha` ∉ `conflict_escalated_shas` → dispatch and exit:
80
88
 
81
89
  1. Reset `last_seen.idle_tick_count` to 0.
82
- 2. **Stop this watcher (single-thread):** cancel its cron exactly as in Step 4 — `/muggle-do`'s resolve-conflicts respawns it when the cycle is done.
83
- 3. Dispatch `/muggle-do` with a *resolve-conflicts* directive (PR URL + slug; no review ids, no check names):
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):
84
92
  ```
85
- /muggle-do resolve conflicts on <pr-url> slug=<slug>
93
+ /muggle-do rebase on <pr-url> slug=<slug>
86
94
  ```
87
- 4. Append a dispatching line to `followup.log`; emit a `tick` event with `conflicting: true`, `dispatched_resolve_conflicts: true`.
88
- 5. Exit. The dev cycle owns the PR; its respawn restarts the watcher, whose next tick re-checks mergeability on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
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.
89
98
 
90
- `mergeable == MERGEABLE` / `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick), or budget spent (`conflict_resolve_attempts[head_sha] >= 2` or `head_sha` ∈ `conflict_escalated_shas`) → fall through to CI.
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.
91
100
 
92
- ### Step 6 — No new reviews, mergeable → poll CI for the head SHA
101
+ ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
93
102
 
94
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:
95
104
 
@@ -108,7 +117,7 @@ Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cl
108
117
 
109
118
  ### Step 7 — Idle
110
119
 
111
- Any idle branch (Steps 4–6 that did not dispatch): increment `last_seen.idle_tick_count`, append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md), emit a `tick` event with `idle: true`, `reviews_seen: 0`, `dispatched_review_ids: []`, `conflicting: <bool>`, `dispatched_resolve_conflicts: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
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`.
112
121
 
113
122
  ## Output
114
123
 
@@ -5,7 +5,7 @@
5
5
  ```
6
6
  Bootstrapped PR follow-up for <owner>/<repo>#<n>
7
7
  Slug: <slug>
8
- Cursor: 0 (will process <N> existing review(s) on first tick) | review #<id> (forward-only) | empty (no prior reviews)
8
+ Baseline: thread-state (unresolved threads picked up live); lastBodyReviewId=0 | =<id> (forward-only)
9
9
  Working tree: <toplevel>
10
10
  Dispatching: /loop 1m /muggle:muggle-pr-followup <slug> <n>
11
11
  ```
@@ -6,7 +6,7 @@ For `/muggle:muggle-pr-followup help` or `?` (no args runs **auto-track** instea
6
6
  muggle-pr-followup — watcher loop for PR review follow-ups
7
7
 
8
8
  Active loops:
9
- <slug> → <owner>/<repo>#<n> (cursor @ review #<id>, <N> cycles)
9
+ <slug> → <owner>/<repo>#<n> (<N> cycles)
10
10
  ...
11
11
  (or "no active loops")
12
12
 
@@ -5,13 +5,13 @@ The watcher does **not** print to the user during normal operation. It only appe
5
5
  ## Idle tick
6
6
 
7
7
  ```
8
- <ISO-8601> tick pr=<n> reviews_seen=0 idle
8
+ <ISO-8601> tick pr=<n> threads=0 idle
9
9
  ```
10
10
 
11
11
  ## Dispatching tick
12
12
 
13
13
  ```
14
- <ISO-8601> tick pr=<n> reviews_seen=<count> dispatched=<id1>,<id2>,...
14
+ <ISO-8601> tick pr=<n> threads=<count> dispatched=<id1>,<id2>,...
15
15
  ```
16
16
 
17
17
  ## Terminal tick
@@ -34,7 +34,7 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
34
34
  ```json
35
35
  {
36
36
  "<owner>/<repo>#<n>": {
37
- "reviewId": <int>,
37
+ "lastBodyReviewId": <int>,
38
38
  "last_pushed_sha": "<sha-or-null>",
39
39
  "idle_tick_count": <int>,
40
40
  "cycles_completed": <int>,
@@ -48,16 +48,16 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
48
48
  }
49
49
  ```
50
50
 
51
- - `reviewId`: the cursor. The watcher fetches reviews with `id > reviewId`. Bootstrap pins this to the highest existing submitted review id (or `0` if none).
51
+ - `lastBodyReviewId`: narrow watermark for **body-only** reviews (a submitted review carrying no line comments). The watcher dispatches a body-only review only when `id > lastBodyReviewId`. Line-comment threads do **not** use it — they are dispatched from live thread state (unresolved + not outdated + newest comment unmarked by the loop), so there is no cursor that can pin past them. Bootstrap sets it to the highest existing submitted review id with `--forward-only`, else `0`.
52
52
  - `last_pushed_sha`: most recent SHA `/muggle-do` pushed in this PR's life; `null` until the first push.
53
- - `idle_tick_count`: incremented each tick that sees zero new reviews. Reset to 0 on any tick that dispatches `/muggle-do`. Diagnostic only — does not gate behavior.
53
+ - `idle_tick_count`: incremented each tick whose actionable set is empty. Reset to 0 on any tick that dispatches `/muggle-do`. Diagnostic only — does not gate behavior.
54
54
  - `cycles_completed`: incremented each time `/muggle-do` completes an address-reviews invocation (regardless of actionable/ambiguous/mixed).
55
- - `escalated_review_ids`: review ids classified as ambiguous by `/muggle-do`. The watcher excludes these from future review fetches so the same ambiguous review is never re-dispatched.
55
+ - `escalated_review_ids`: review ids classified as ambiguous by `/muggle-do`. The watcher excludes these from the actionable set (both body-only reviews and the threads they own) so the same ambiguous review is never re-dispatched.
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
 
@@ -94,8 +94,8 @@ The watcher does **not** read or write `state.md`. Only bootstrap, `/muggle-do`,
94
94
  Append-only line-per-tick log. One line per watcher tick, plus one line per `/muggle-do` invocation. Format is loose, but each line starts with an ISO-8601 timestamp:
95
95
 
96
96
  ```
97
- 2026-05-20T12:34:56Z tick pr=154 reviews_seen=0 idle
98
- 2026-05-20T12:35:56Z tick pr=154 reviews_seen=1 dispatched=4295962800
97
+ 2026-05-20T12:34:56Z tick pr=154 threads=0 idle
98
+ 2026-05-20T12:35:56Z tick pr=154 threads=1 dispatched=4295962800
99
99
  2026-05-20T12:36:14Z muggle-do cycle review_ids=[4295962800] outcome=pushed head_sha=abc1234
100
100
  ```
101
101
 
@@ -116,7 +116,7 @@ Written exactly once when the PR's watcher exits terminally (PR merged or closed
116
116
 
117
117
  ## Timeline
118
118
 
119
- - <ISO-8601> bootstrap (cursor pinned at <reviewId>)
119
+ - <ISO-8601> bootstrap (lastBodyReviewId <id>; line-comment threads state-derived)
120
120
  - <ISO-8601> review <id> from <login> — actionable, pushed <sha>
121
121
  - <ISO-8601> review <id> from <login> — ambiguous, escalated
122
122
  - ...
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-status
3
- description: Use this skill to check the health of the user's Muggle AI installation and diagnose why it's misbehaving — MCP server connectivity, tool loading, login/auth validity, and overall setup. Engage on an explicit "muggle status", but also on any diagnostic question about Muggle itself: "is muggle working / healthy / set up right?", "why does muggle keep failing / timing out / saying it can't connect?", "are the muggle MCP tools actually loading?", "is my muggle login/auth still valid?", "muggle's been acting up — take a look / what's wrong?", "muggle commands fail silently — is the install unhealthy?". This is diagnosis and reporting: prefer it over answering from memory whenever the user is unsure Muggle itself is functioning. Boundary: checking/diagnosing is muggle-status; actually fixing a broken install is muggle-repair (a clear "fix it" goes there). Not for the health of the user's own app, CI, or infrastructure.
3
+ description: "Check the health of the user's Muggle AI installation and diagnose why it's misbehaving — MCP server connectivity, tool loading, login/auth validity, overall setup. Engage on muggle status and on any diagnostic question about Muggle itself: is muggle working or healthy, why does muggle keep failing or timing out, are the muggle MCP tools loading, is my muggle login still valid. Prefer this over answering from memory whenever the user is unsure Muggle itself is functioning. Boundary: diagnosing is muggle-status; fixing a broken install is muggle-repair. Not for the health of the user's own app, CI, or infra."
4
4
  ---
5
5
 
6
6
  # Muggle Test Status