@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,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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test
3
- description: "Run change-driven E2E acceptance testing using Muggle AI — detect the user's recent code changes (local diff or a PR), map them to affected user flows, then run real-browser acceptance tests on localhost or a deployed preview/staging URL, publish results to the dashboard, and post a screenshot summary to the PR. Use this whenever the user wants to test, validate, check, or regression-test their own changes or work — 'test my changes', 'validate my changes', 'regression test my work', 'make sure I didn't break anything', 'did my recent commits/changes break anything or any user flows?', 'test before I push' — and especially as the acceptance-test gate before opening a pull request or merging (e.g. 'validate my changes before I open the PR' means run the acceptance suite, not just a completion checklist). The defining signal is change-driven validation of in-progress work tied to a commit, push, PR, or merge. For testing one specific named feature/flow use muggle-test-feature-local; this is not importing existing tests, configuring preferences, or replaying a single named script."
3
+ description: "Change-driven E2E acceptance testing with Muggle AI: detect the user's recent code changes (local diff or a PR), map them to affected user flows, run real-browser tests on localhost or a preview/staging URL, publish results, and post a screenshot summary to the PR. Use whenever the user wants to test, validate, or regression-test their own in-progress changes or work — \"make sure I didn't break anything\", \"did my recent commits break any user flows?\", \"test before I push\" — especially as the acceptance gate before opening or merging a PR. The defining signal is change-driven validation tied to a commit, push, PR, or merge. For one specific named feature/flow use muggle-test-feature-local; not for importing existing tests, configuring preferences, or replaying a single named script."
4
4
  ---
5
5
 
6
6
  # Muggle Test — Change-Driven E2E Acceptance Router
@@ -415,53 +415,15 @@ Use `AskUserQuestion`:
415
415
 
416
416
  This is a suggestion, not automatic invocation. Skip silently if every test passed cleanly.
417
417
 
418
- ## Tool Reference
419
-
420
- | Phase | Tool | Mode |
421
- |:------|:-----|:-----|
422
- | Auth | `muggle-remote-auth-status` | Both |
423
- | Auth | `muggle-remote-auth-login` | Both |
424
- | Auth | `muggle-remote-auth-poll` | Both |
425
- | Project | `muggle-remote-project-list` | Both |
426
- | Project | `muggle-remote-project-create` | Both |
427
- | Use Case | `muggle-remote-use-case-list` | Both |
428
- | Use Case | `muggle-remote-use-case-create-from-prompts` | Both |
429
- | Test Case | `muggle-remote-test-case-list-by-use-case` | Both |
430
- | Test Case | `muggle-remote-test-case-generate-from-prompt` | Both |
431
- | Test Case | `muggle-remote-test-case-create` | Both |
432
- | Test Case | `muggle-remote-test-case-get` | Both |
433
- | Execute (regen) | `muggle-local-execute-test-generation` | Local |
434
- | Execute (replay) | `muggle-local-execute-replay` | Local |
435
- | Replay action script fetch | `muggle-remote-test-script-get`, `muggle-remote-action-script-get` | Local replay |
436
- | Execute (regen) | `muggle-remote-workflow-start-test-script-generation` | Remote |
437
- | Execute (replay) | `muggle-remote-workflow-start-test-script-replay` | Remote |
438
- | Failure-mode telemetry | `muggle-local-telemetry-event-emit` | Both |
439
- | Results | `muggle-local-run-result-get` | Local |
440
- | Results | `muggle-remote-wf-get-ts-gen-latest-run`, `muggle-remote-wf-get-ts-replay-latest-run` | Remote |
441
- | Publish | `muggle-local-publish-test-script` | Local |
442
- | Per-step screenshots (for walkthrough) | `muggle-remote-test-script-get` | Both |
443
- | Browser | `open` (shell command) | Both |
444
- | PR walkthrough | `muggle-pr-visual-walkthrough` (shared skill) | Both |
445
-
446
- ## Guardrails
447
-
448
- - **Always confirm intent first** — never assume local vs remote without asking
449
- - **PR URLs always run in a dedicated worktree** — never switch the user's main checkout. Materialize per [`_shared/pr-branch-worktree.md`](../_shared/pr-branch-worktree.md) and pass that path as `cwd` to local execute tools; the cross-worktree single-flight lock relies on it to serialize concurrent runs from different branches.
450
- - **User MUST select project** — present clickable options via `AskUserQuestion`, wait for explicit choice, never auto-select
451
- - **Best-effort shortlist use cases** — use the change summary to narrow the list to the most relevant 1–5 use cases and pre-check them; never dump every use case in the project on the user. Always leave an escape hatch to reveal the full list.
452
- - **Best-effort shortlist test cases** — same idea: pre-check the test cases most relevant to the change summary; never enumerate every test case attached to a use case. Always leave an escape hatch to reveal the full list.
453
- - **Use `AskUserQuestion` for every selection** — never ask the user to type a number; always present clickable options
454
- - **Auto-detect localhost URL when possible**; only fall back to free-text when nothing is listening on a common port
455
- - **Parallelize independent cloud jobs** — when creating N use cases, generating/creating N test cases, fetching N test case details, starting N remote workflows, polling N workflow runtimes, publishing N local runs, or fetching N per-step test scripts, issue all N calls in a single message so they fan out in parallel. The only tolerated sequential loop is local Electron execution (one browser, one test at a time). For use case creation specifically, use the native batch form of `muggle-remote-use-case-create-from-prompts` (all descriptions in one `instructions` array) instead of parallel calls.
456
- - **One atomic behavior per test case** — every test case verifies exactly one user-observable behavior. Never bundle signup/login/navigation/bootstrap/teardown into a test case body. Ordering and dependencies are Muggle Test's service responsibility, not the skill's.
457
- - **Never consolidate the generator's output** — if `muggle-remote-test-case-generate-from-prompt` returns N micro-tests, accept all N; never merge them into fewer test cases, even if "the plan" says 4 UC / 4 TC.
458
- - **Never skip the generate→review cycle** — always present generated test cases to the user before calling `muggle-remote-test-case-create`, even when you're confident. "I'll skip the review and create directly" is always wrong.
459
- - **Never silently drop test cases** — log failures and continue, then report them
460
- - **Never guess the URL** — always ask the user for localhost or preview URL
461
- - **Always publish before opening browser** — the dashboard needs the published data to show results
462
- - **Delegate PR posting to `muggle-pr-visual-walkthrough`** — never inline the walkthrough markdown or call `gh pr comment` directly from this skill; ask the user and hand off
463
- - **Can be invoked at any state** — if the user already has a project or use cases set up, skip to the relevant step rather than re-doing everything
464
-
465
- ## Agent Dispatch
466
-
467
- When used in a multi-agent team (e.g., muggle-ai-teams), this skill is available through the **acceptance-tester** agent at `plugin/agents/acceptance-tester.md`. Orchestrators can dispatch it via `Agent()` instead of invoking this skill directly. The agent wraps this skill and four others (muggle-test-import, muggle-preferences, muggle-repair, muggle-status) and returns structured test results with blocking issues and suggested fixes for coding agents to act on.
418
+ ## Non-negotiables
419
+
420
+ Each rule below is covered in-step above; these are the ones this skill most often violates, kept here as reinforcement:
421
+
422
+ - **Test-case shape** — never skip the generate→review cycle, never consolidate the generator's micro-tests, one atomic behavior per test case. Creating test cases directly or merging the generator's output is the single most frequent mistake.
423
+ - **Confirm intent before acting** — local vs remote; never guess the localhost/preview URL.
424
+ - **PR URLs run in a dedicated worktree** — never switch the user's main checkout; pass that worktree as `cwd`.
425
+ - **Every selection uses `AskUserQuestion`** — never ask the user to type a number; the user picks the project (never auto-select).
426
+ - **Parallelize independent cloud jobs**; the only sequential loop is local Electron execution (one browser).
427
+ - **Publish before opening the browser**, and delegate PR posting to `muggle-pr-visual-walkthrough` — never inline the walkthrough or call `gh pr comment` here.
428
+
429
+ Phase→tool map and multi-agent (acceptance-tester) dispatch: [`reference.md`](reference.md).
@@ -0,0 +1,35 @@
1
+ # muggle-test reference
2
+
3
+ Lookup material pulled out of `SKILL.md` to keep the always-loaded body lean. Every tool here is also named inline in the step that uses it — consult this only when you want the full phase→tool map in one place.
4
+
5
+ ## Tool Reference
6
+
7
+ | Phase | Tool | Mode |
8
+ |:------|:-----|:-----|
9
+ | Auth | `muggle-remote-auth-status` | Both |
10
+ | Auth | `muggle-remote-auth-login` | Both |
11
+ | Auth | `muggle-remote-auth-poll` | Both |
12
+ | Project | `muggle-remote-project-list` | Both |
13
+ | Project | `muggle-remote-project-create` | Both |
14
+ | Use Case | `muggle-remote-use-case-list` | Both |
15
+ | Use Case | `muggle-remote-use-case-create-from-prompts` | Both |
16
+ | Test Case | `muggle-remote-test-case-list-by-use-case` | Both |
17
+ | Test Case | `muggle-remote-test-case-generate-from-prompt` | Both |
18
+ | Test Case | `muggle-remote-test-case-create` | Both |
19
+ | Test Case | `muggle-remote-test-case-get` | Both |
20
+ | Execute (regen) | `muggle-local-execute-test-generation` | Local |
21
+ | Execute (replay) | `muggle-local-execute-replay` | Local |
22
+ | Replay action script fetch | `muggle-remote-test-script-get`, `muggle-remote-action-script-get` | Local replay |
23
+ | Execute (regen) | `muggle-remote-workflow-start-test-script-generation` | Remote |
24
+ | Execute (replay) | `muggle-remote-workflow-start-test-script-replay` | Remote |
25
+ | Failure-mode telemetry | `muggle-local-telemetry-event-emit` | Both |
26
+ | Results | `muggle-local-run-result-get` | Local |
27
+ | Results | `muggle-remote-wf-get-ts-gen-latest-run`, `muggle-remote-wf-get-ts-replay-latest-run` | Remote |
28
+ | Publish | `muggle-local-publish-test-script` | Local |
29
+ | Per-step screenshots (for walkthrough) | `muggle-remote-test-script-get` | Both |
30
+ | Browser | `open` (shell command) | Both |
31
+ | PR walkthrough | `muggle-pr-visual-walkthrough` (shared skill) | Both |
32
+
33
+ ## Agent Dispatch
34
+
35
+ In a multi-agent team (e.g. muggle-ai-teams), this skill is reachable through the **acceptance-tester** agent at `plugin/agents/acceptance-tester.md`. Orchestrators dispatch it via `Agent()` instead of invoking the skill directly. The agent wraps this skill and four others (muggle-test-import, muggle-preferences, muggle-repair, muggle-status) and returns structured test results with blocking issues and suggested fixes for coding agents to act on.
@@ -1,15 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-import
3
- description: >
4
- Bring existing tests and test artifacts INTO Muggle Test — from Playwright, Cypress, PRDs,
5
- Gherkin feature files, test plan docs, Notion exports, or any source.
6
- TRIGGER when: user wants to import/migrate/load/upload/add/convert existing test files or
7
- test docs into Muggle Test — e.g. "import my playwright tests", "migrate from cypress to muggle",
8
- "upload my PRD to muggle", "add my e2e specs to our muggle project", "load these test cases
9
- into muggle", "turn this feature file into muggle test cases", "create muggle test cases from
10
- my PRD", "track my specs in muggle", or any .spec.ts/.cy.js/.feature/.md file + muggle.
11
- DO NOT TRIGGER when: user wants to run/replay Muggle Test scripts, scan a site, generate new
12
- tests from scratch, or check existing test results.
3
+ description: "Bring existing tests and test artifacts INTO Muggle Test — from Playwright, Cypress, PRDs, Gherkin feature files, test-plan docs, Notion exports, or any source. TRIGGER when the user wants to import, migrate, load, or convert existing test files or docs into Muggle Test — import my playwright tests, migrate from cypress to muggle, upload my PRD to muggle, turn this feature file into muggle test cases, or any .spec.ts/.cy.js/.feature/.md file plus muggle. DO NOT trigger for running or replaying scripts, scanning a site, generating new tests from scratch, or checking results."
13
4
  ---
14
5
 
15
6
  # Muggle Test Import
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-prepare
3
- description: "Use this skill to get a user's local environment ready before running E2E acceptance tests — verifying that the dev servers, APIs, and sibling services they need are actually up and responding, and offering to start whatever is missing (with approval at each step). Trigger whenever the user wants to confirm that specific ports or localhost URLs are listening/up before testing (e.g. 'check if localhost:3000 and the api on 8080 are listening', 'are my services up?'), make sure required services are running, spin up or prepare their local dev stack, or verify their setup — and whenever another muggle skill (muggle-test, muggle-do, muggle-test-feature-local) needs services running but they're not listening on the expected ports. This is environment readiness and service startup, not running the tests themselves."
3
+ description: "Get a user's local environment ready before running E2E acceptance tests — verify the dev servers, APIs, and sibling services they need are up and responding, and offer to start whatever is missing (with approval per step). Trigger when the user wants to confirm specific ports or localhost URLs are listening before testing (check if localhost:3000 and the api on 8080 are up, are my services running), spin up their local dev stack, or verify their setup — and whenever another muggle skill (muggle-test, muggle-do, muggle-test-feature-local) needs services running but they're not. This is environment readiness and service startup, not running the tests."
4
4
  ---
5
5
 
6
6
  # Muggle Test Prepare
@@ -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,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-test-regenerate-missing
3
- description: "Bulk-regenerate test scripts for every test case in a Muggle AI project that doesn't currently have an active script. Scans the project, finds test cases stuck in DRAFT or GENERATION_PENDING (no usable script attached), shows the user the list, and on approval kicks off bulk remote test script generation via the Muggle Test cloud. Use this skill whenever the user asks to 'regenerate missing scripts', 'fill in missing test scripts', 'generate scripts for test cases without one', 'regen all the test cases that don't have scripts', 'rebuild scripts for stale test cases', 'fix test cases with no script', 'bulk regenerate', or any phrasing that means 'kick off script generation across a project for the cases that need it'. Triggers on: 'regenerate missing test scripts', 'generate scripts for all empty test cases', 'fill the gaps in my test scripts', 'bulk test script regen', 'all my test cases without active scripts'. This is the go-to skill for project-wide script catch-up — it handles discovery, filtering, confirmation, and remote workflow dispatch end-to-end."
3
+ description: "Bulk-regenerate test scripts for every test case in a Muggle AI project that has no active script. Scans the project, finds test cases stuck in DRAFT or GENERATION_PENDING, shows the user the list, and on approval kicks off bulk remote script generation via the Muggle Test cloud. Use when the user wants to regenerate missing scripts, fill in missing test scripts, generate scripts for test cases without one, rebuild scripts for stale test cases, or bulk regenerate across a project. Handles discovery, filtering, confirmation, and remote dispatch end-to-end."
4
4
  ---
5
5
 
6
6
  # Muggle Test — Regenerate Missing Test Scripts
@@ -1,7 +1,7 @@
1
1
  {
2
- "release": "5.0.1",
3
- "buildId": "run-49-1",
4
- "commitSha": "a12a3252381af4fb34b79598208ba62f9764154d",
5
- "buildTime": "2026-06-04T17:35:40Z",
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.1",
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.0.116",
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": "382466a6eefe49cddc5662556cb6b744c60c2eed9984e520df9f62726bc468a0",
49
- "darwin-x64": "9c57b10b1ed03bcd6629cb36abd5c0e0b5d095a27a38305ad4272c77cd6334af",
50
- "linux-x64": "d6b10a829f99027747349ab37521b765041343676f59e59792afcec1de024080",
51
- "win32-x64": "77981c9d77dd49a786cfca7156ce326f56a75716cf911adad270521230435f75"
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.1",
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.1",
5
+ "version": "5.0.3",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -64,12 +64,13 @@ function shouldRunE2E(state) {
64
64
 
65
65
  // src/guardrails/detectBuildIntent.ts
66
66
  var BUILD = /\b(implement|build|add|create|write|fix|refactor|wire up|hook up|make (a|the|it)|change the)\b/i;
67
+ var DEVCYCLE = /\bresolve\b[^.?!]{0,40}\bconflicts?\b|\bget\b[^.?!]{0,40}\bpr\b[^.?!]{0,40}\b(green|merged?|passing)\b/i;
67
68
  var QUESTION = /^\s*(why|what|how|when|where|who|is|are|does|do|can you (explain|tell)|explain)\b/i;
68
69
  function detectBuildIntent(prompt) {
69
70
  const p = (prompt ?? "").trim();
70
71
  if (!p || p.startsWith("/")) return false;
71
72
  if (QUESTION.test(p)) return false;
72
- return BUILD.test(p);
73
+ return BUILD.test(p) || DEVCYCLE.test(p);
73
74
  }
74
75
 
75
76
  // src/guardrails/emit.ts
@@ -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.
@@ -1,16 +1,24 @@
1
- # Submitted reviews past a cursor
1
+ # Submitted reviews
2
2
 
3
- For the watcher's poll and the address-reviews fetch.
3
+ Two consumers (this recipe links to neither — it is a shared primitive):
4
+
5
+ - the watcher's **body-only-review** check — a submitted review carrying a summary body but **no** line comments. Line-comment threads are dispatched from live thread state, not here.
6
+ - the address-reviews fetch of a **specific** review id.
4
7
 
5
8
  ```bash
6
9
  gh api repos/<owner>/<repo>/pulls/<n>/reviews --paginate
7
10
  ```
8
11
 
9
- Filter client-side:
12
+ Common filter:
10
13
 
11
14
  - `submitted_at != null` (skip PENDING drafts)
12
- - `id > last_seen.reviewId`
13
- - `id` not in `last_seen.escalated_review_ids`
14
15
  - `user.login` in the resolved allow-list
15
16
  - `state` in `{CHANGES_REQUESTED, COMMENTED}`, OR `APPROVED` with a non-empty body or at least one line comment
16
- - **Not a loop echo.** `POST /pulls/<n>/comments/<id>/replies` creates an implicit review whose comments all have `in_reply_to_id` set. Fetch each candidate review's comments via `gh api repos/<owner>/<repo>/pulls/<n>/reviews/<id>/comments` and drop the review **only if every comment is a reply (`in_reply_to_id != null`) and carries the loop marker `<!-- muggle-do:bot -->`** (see [`../pr-followup-helpers/loop-signature.md`](../pr-followup-helpers/loop-signature.md)). A reply-only wrapper with any comment **lacking** the marker is a human follow-up — keep it; the round addresses it. Matching the marker, not structure, is what skips the loop's own replies (posted under the author's identity in single-account workflows) without dropping genuine follow-ups.
17
+
18
+ The **watcher's body-only check** adds:
19
+
20
+ - the review has **no line comments** — `gh api repos/<owner>/<repo>/pulls/<n>/reviews/<id>/comments` returns `[]`. A review with line comments is dispatched from thread state, not here.
21
+ - `id > last_seen.lastBodyReviewId`
22
+ - `id` not in `last_seen.escalated_review_ids`
23
+
24
+ A reply posted by the loop surfaces as an implicit review, but it always carries the reply as a line comment, so it can never be body-only — the body-only filter excludes it structurally, no marker check needed. Thread-level echo protection is intrinsic to the marker rule in [`unresolved-threads.md`](unresolved-threads.md).
@@ -1,6 +1,6 @@
1
1
  # Unresolved comment threads
2
2
 
3
- For the resolve-reminder stage. GraphQL only — REST does not expose `isResolved`.
3
+ For the watcher's dispatch trigger and the resolve-reminder stage. GraphQL only — REST does not expose `isResolved`/`isOutdated`.
4
4
 
5
5
  ```bash
6
6
  gh api graphql -F owner=<owner> -F name=<repo> -F number=<n> -f query='
@@ -11,9 +11,11 @@ query($owner: String!, $name: String!, $number: Int!) {
11
11
  nodes {
12
12
  id
13
13
  isResolved
14
+ isOutdated
14
15
  comments(first: 100) {
15
16
  nodes {
16
17
  databaseId
18
+ pullRequestReview { databaseId }
17
19
  author { login }
18
20
  body
19
21
  createdAt
@@ -29,7 +31,9 @@ query($owner: String!, $name: String!, $number: Int!) {
29
31
  Filter client-side to `isResolved == false`. Walk each thread's comments in `createdAt` order and classify by the loop marker (see [`../pr-followup-helpers/loop-signature.md`](../pr-followup-helpers/loop-signature.md)), not by `author.login` — the login is ambiguous under a shared account:
30
32
 
31
33
  - **Addressed, awaiting resolve** — the **newest** comment carries the loop marker `<!-- muggle-do:bot -->`. The loop has replied and nothing newer is waiting. → resolve-reminder.
32
- - **Unaddressed human comment** — the newest comment lacks the marker and is newer than the thread's newest loop-marked comment (or the thread has no loop comment yet). → actionable: the round should address it.
34
+ - **Unaddressed human comment** — the newest comment lacks the marker and is newer than the thread's newest loop-marked comment (or the thread has no loop comment yet). → actionable: the round should address it. The **watcher's dispatch trigger** additionally requires `isOutdated == false` — a thread whose anchored line has since changed is skipped, since re-surfacing a stale anchor risks answering a concern the change already mooted. The resolve-reminder stage ignores `isOutdated`.
33
35
  - **Not addressed** — indeterminate (e.g. no comments).
34
36
 
37
+ Each comment exposes its owning review as `pullRequestReview.databaseId` — the watcher collects this from an actionable thread's newest comment to build its dispatch list.
38
+
35
39
  A loop comment also cites a `<short-sha>` from `last_seen.pushed_shas[]` in its body, which tells *which* push addressed the thread.
@@ -9,10 +9,10 @@ Skills assume a working `gh auth status`. Auth errors surface verbatim from `gh`
9
9
  | Recipe | Use case |
10
10
  | :----- | :------- |
11
11
  | [`pr-metadata`](github-cli-recipes/pr-metadata.md) | Snapshot PR state, head SHA, branch — watcher + bootstrap. |
12
- | [`submitted-reviews`](github-cli-recipes/submitted-reviews.md) | Fetch reviews past a cursor — watcher's poll. |
12
+ | [`submitted-reviews`](github-cli-recipes/submitted-reviews.md) | Fetch a review by id / watcher's body-only-review check. |
13
13
  | [`pr-checks`](github-cli-recipes/pr-checks.md) | Check-run rollup for the head SHA — watcher's CI poll. |
14
14
  | [`line-comments-for-review`](github-cli-recipes/line-comments-for-review.md) | Pull a review's line comments — `/muggle-do` per-comment routing. |
15
- | [`unresolved-threads`](github-cli-recipes/unresolved-threads.md) | GraphQL fetch of unresolved comment threads — resolve-reminder. |
15
+ | [`unresolved-threads`](github-cli-recipes/unresolved-threads.md) | GraphQL unresolved-thread state — watcher's dispatch trigger + resolve-reminder. |
16
16
  | [`reply-line-comment`](github-cli-recipes/reply-line-comment.md) | POST a threaded reply on a line comment. |
17
17
  | [`top-level-comment`](github-cli-recipes/top-level-comment.md) | POST a top-level PR comment — resolve-reminder + overflow. |
18
18
  | [`push-to-branch`](github-cli-recipes/push-to-branch.md) | Push + capture new SHA after address-reviews work. |
@@ -10,7 +10,7 @@ A review is a **self-loop** iff **every** line comment under it is a reply (`in_
10
10
 
11
11
  If any comment in a reply-only wrapper **lacks** the marker, it is a **human follow-up** on an existing thread — not a self-loop. It carries reviewer intent; treat it as actionable and address it in this round (the caller's unresolved-thread sweep picks up the thread context).
12
12
 
13
- Self-loops bypass the actionable/ambiguous decision entirely. Action: advance the cursor silently. No push, no reply, no resolve-reminder, no escalation, no entry in `escalated_review_ids`. Telemetry: emit one `cycle` event with `outcome: "self-loop-skip"`.
13
+ Self-loops bypass the actionable/ambiguous decision entirely. Action: skip silently — a body-only echo folds its id into `lastBodyReviewId`; a line-comment echo already drops out of the actionable set via the marker. No push, no reply, no resolve-reminder, no escalation, no entry in `escalated_review_ids`. Telemetry: emit one `cycle` event with `outcome: "self-loop-skip"`.
14
14
 
15
15
  Only reviews that survive the self-loop check proceed to classify below.
16
16
 
@@ -51,7 +51,7 @@ Reply shape (all replies for one review reference the same SHA):
51
51
  | 1 comment: "won't this break the prod migration we did last week?" | Implicit change request gated on knowledge the loop can't access |
52
52
  | Mixed: 2 concrete directives + 1 "rethink the whole approach" | The "rethink" subverts the others; escalate to confirm scope |
53
53
 
54
- Escalate per the caller's procedure (add the review id to the cursor's escalated set, emit one terminal message, pause the PR).
54
+ Escalate per the caller's procedure (add the review id to `escalated_review_ids`, emit one terminal message, pause the PR).
55
55
 
56
56
  ## Borderline rule
57
57