@muggleai/works 4.12.3 → 4.13.0

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 (59) hide show
  1. package/dist/{chunk-JNI7INIO.js → chunk-QUWM3JQY.js} +2 -2
  2. package/dist/{chunk-CPF6AR2I.js → chunk-TWILR37J.js} +37 -37
  3. package/dist/cli.js +2 -2
  4. package/dist/index.js +2 -2
  5. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  6. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  7. package/dist/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +1 -0
  8. package/dist/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -4
  9. package/dist/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
  10. package/dist/plugin/skills/_shared/pr-followup-helpers/classify.md +3 -4
  11. package/dist/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +22 -0
  12. package/dist/plugin/skills/_shared/pr-followup-helpers.md +1 -0
  13. package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +56 -0
  14. package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
  15. package/dist/plugin/skills/do/address-reviews.md +14 -8
  16. package/dist/plugin/skills/do/e2e-acceptance.md +7 -3
  17. package/dist/plugin/skills/do/per-comment-replies.md +8 -2
  18. package/dist/plugin/skills/do/resolve-reminder.md +16 -18
  19. package/dist/plugin/skills/muggle-feedback/ops/submit.md +1 -1
  20. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
  21. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +10 -3
  22. package/dist/plugin/skills/muggle-pr-followup/auto-track.md +91 -0
  23. package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +11 -4
  24. package/dist/plugin/skills/muggle-pr-followup/contract.md +3 -2
  25. package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +2 -1
  26. package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +7 -1
  27. package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +6 -3
  28. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +12 -0
  29. package/dist/plugin/skills/muggle-preferences/ops/configure.md +1 -1
  30. package/dist/plugin/skills/muggle-preferences/preference-gates/autoReuseValidationContext.md +11 -0
  31. package/dist/release-manifest.json +4 -4
  32. package/dist/{src-YR5UKLPC.js → src-BD5AM6OH.js} +1 -1
  33. package/package.json +6 -6
  34. package/plugin/.claude-plugin/plugin.json +1 -1
  35. package/plugin/.cursor-plugin/plugin.json +1 -1
  36. package/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +1 -0
  37. package/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -4
  38. package/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
  39. package/plugin/skills/_shared/pr-followup-helpers/classify.md +3 -4
  40. package/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +22 -0
  41. package/plugin/skills/_shared/pr-followup-helpers.md +1 -0
  42. package/plugin/skills/_shared/resolve-e2e-validation-context.md +56 -0
  43. package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
  44. package/plugin/skills/do/address-reviews.md +14 -8
  45. package/plugin/skills/do/e2e-acceptance.md +7 -3
  46. package/plugin/skills/do/per-comment-replies.md +8 -2
  47. package/plugin/skills/do/resolve-reminder.md +16 -18
  48. package/plugin/skills/muggle-feedback/ops/submit.md +1 -1
  49. package/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
  50. package/plugin/skills/muggle-pr-followup/SKILL.md +10 -3
  51. package/plugin/skills/muggle-pr-followup/auto-track.md +91 -0
  52. package/plugin/skills/muggle-pr-followup/bootstrap.md +11 -4
  53. package/plugin/skills/muggle-pr-followup/contract.md +3 -2
  54. package/plugin/skills/muggle-pr-followup/output-templates/help.md +2 -1
  55. package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +7 -1
  56. package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +6 -3
  57. package/plugin/skills/muggle-pr-followup/state-schemas.md +12 -0
  58. package/plugin/skills/muggle-preferences/ops/configure.md +1 -1
  59. package/plugin/skills/muggle-preferences/preference-gates/autoReuseValidationContext.md +11 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-pr-followup
3
- description: Watcher loop for PR review follow-ups. Polls one PR for new submitted reviews and dispatches `/muggle-do` (address-reviews mode) when there are any. A dumb pipe — no classification, no cycle execution, no replies. Use `/loop 1m /muggle:muggle-pr-followup <slug> <pr-number>` for ongoing polling, or `/muggle:muggle-pr-followup <pr-url>` to bootstrap a fresh watcher on an existing PR.
3
+ description: Watcher loop for PR review follow-ups. Polls one PR for new submitted reviews and dispatches `/muggle-do` (address-reviews mode) when there are any. A dumb pipe — no classification, no cycle execution, no replies. Run it with no args to auto-track every PR you pushed this session (poll-only watchers, any repo). Use `/loop 1m /muggle:muggle-pr-followup <slug> <pr-number>` for ongoing polling, or `/muggle:muggle-pr-followup <pr-url>` to bootstrap a fresh watcher on an existing PR (asks once for the E2E validation context, then runs unattended).
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
@@ -16,7 +16,7 @@ A watcher that babysits one open PR's review thread. Polls for new submitted rev
16
16
 
17
17
  ## Routing
18
18
 
19
- The skill recognizes two modes by inspecting `$ARGUMENTS` and falling back to on-disk state. It never runs procedure inline — it identifies the mode and routes to the appropriate procedure file.
19
+ The skill recognizes its mode by inspecting `$ARGUMENTS` and falling back to on-disk state. It never runs procedure inline — it identifies the mode and routes to the appropriate procedure file.
20
20
 
21
21
  | Input | On-disk check | Mode |
22
22
  | :---- | :------------ | :--- |
@@ -25,7 +25,8 @@ The skill recognizes two modes by inspecting `$ARGUMENTS` and falling back to on
25
25
  | `<slug> <pr-number>` | session dir missing | **error:** "no session at `<path>`; pass a PR URL to start one" |
26
26
  | `<pr-number>` alone | exactly one existing session contains it | **tick** for that PR |
27
27
  | `<pr-number>` alone | zero or multiple matches | **error:** ambiguous; list candidates and exit |
28
- | empty / `help` / `?` | — | **help:** list active loops per [`output-templates/help.md`](output-templates/help.md) |
28
+ | empty | — | **auto-track** → [`auto-track.md`](auto-track.md) |
29
+ | `help` / `?` | — | **help:** list active loops per [`output-templates/help.md`](output-templates/help.md) |
29
30
 
30
31
  Bootstrap accepts three optional trailing flags:
31
32
 
@@ -33,6 +34,12 @@ Bootstrap accepts three optional trailing flags:
33
34
  - `--resume` — opt in to reusing an existing session slot (default is refuse on conflict)
34
35
  - `--forward-only` — pin cursor past existing reviews (skip history). Default is cursor 0, which processes prior submitted reviews on the first tick.
35
36
 
37
+ ## Preferences
38
+
39
+ | Preference | Gate |
40
+ | :--------- | :--- |
41
+ | `autoReuseValidationContext` | Bootstrap reuses an existing validation context instead of re-asking — fired in the Step 6.5 gather per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md) |
42
+
36
43
  ## Folder TOC
37
44
 
38
45
  See [`CLAUDE.md`](CLAUDE.md) for the one-line index of every file in this folder.
@@ -0,0 +1,91 @@
1
+ # Auto-Track Procedure
2
+
3
+ The procedure for the **auto-track mode** of `muggle-pr-followup` — invoked when the skill is dispatched with **no arguments**. Routing is in [`SKILL.md`](SKILL.md#routing).
4
+
5
+ Auto-track discovers the PRs you pushed or opened during this Claude Code session — across **any repo** — and starts one **poll-only watcher** per PR. The watcher only watches: it seeds nothing about E2E. Running and posting E2E is `/muggle-do`'s job; a watcher with no validation context yields a `SKIPPED` E2E verdict when `/muggle-do` runs — see [`../do/e2e-acceptance.md`](../do/e2e-acceptance.md) Step 0.
6
+
7
+ ## Turn preamble
8
+
9
+ ```
10
+ **muggle-pr-followup auto-track** — tracking PRs from this session.
11
+ ```
12
+
13
+ ## Input
14
+
15
+ `$ARGUMENTS` is empty.
16
+
17
+ ## Procedure
18
+
19
+ ### Step 1 — Discover candidate PRs from session context
20
+
21
+ A PR counts as **pushed this session** if, earlier in this conversation, you:
22
+
23
+ - opened it (`gh pr create`), or
24
+ - pushed commits to a branch that has an open PR, or
25
+ - acted on it as the active PR (its URL appeared and you worked it).
26
+
27
+ Collect each candidate's canonical URL (`https://github.com/<owner>/<repo>/pull/<n>`); candidates may span repos.
28
+
29
+ ### Step 2 — Decide confident vs. uncertain
30
+
31
+ - **Confident** — context clearly identifies one or more PRs pushed this session. Use that set; go to Step 4. **Do not prompt.**
32
+ - **Uncertain** — nothing clearly attributable, or several candidates are plausible. Go to Step 3.
33
+
34
+ ### Step 3 — Picker (uncertain only)
35
+
36
+ Build a candidate list from the Step 1 URLs, plus — if the current working directory is a git repo — `gh pr list --author @me --state open --json number,title,url,headRefName` for that repo. Present an `AskUserQuestion` **multi-select** picker of the candidates (PR number + title + repo).
37
+
38
+ - Empty selection → exit with a one-line note (`No PRs selected; nothing to track.`). Write nothing.
39
+ - One or more selected → use them as the track list and go to Step 4.
40
+
41
+ ### Step 4 — Seed one poll-only watcher per PR
42
+
43
+ For each PR URL in the track list, run the [`bootstrap.md`](bootstrap.md) procedure with these auto-track overrides:
44
+
45
+ - **Skip Step 3 (verify working tree).** The PR's checkout need not be the current tree. If you know which directory it was pushed from, record `Working tree: <path>` in `state.md`; else omit it — `/muggle-do` resolves the tree when it runs.
46
+ - **Skip Step 6.5 (E2E validation context).** The watcher owns no E2E concern; do **not** write a `## Pre-flight answers` block.
47
+ - **Existing slot → skip silently** (never the slot-conflict abort); add it to the *skipped* list.
48
+ - **`caller = "auto-track"`** in the bootstrap telemetry event.
49
+
50
+ 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.
51
+
52
+ ### Step 5 — Print the summary
53
+
54
+ ```
55
+ muggle-pr-followup auto-track
56
+
57
+ Tracked (new):
58
+ <slug> → <owner>/<repo>#<n>
59
+ ...
60
+ (or "none")
61
+ Already tracking (skipped):
62
+ <slug> → <owner>/<repo>#<n>
63
+ ...
64
+ (or "none")
65
+
66
+ Dispatching:
67
+ /loop 1m /muggle:muggle-pr-followup <slug> <n>
68
+ ...
69
+ ```
70
+
71
+ Print the summary **before** the `/loop` dispatches so it stays visible.
72
+
73
+ ### Step 6 — Dispatch the watchers
74
+
75
+ As the last action of the turn, emit one `/loop` line per **newly tracked** PR (not the skipped ones):
76
+
77
+ ```
78
+ /loop 1m /muggle:muggle-pr-followup <slug> <n>
79
+ ```
80
+
81
+ Each registers an independent cron — the N-independent-watchers model from [`SKILL.md`](SKILL.md).
82
+
83
+ ### Step 7 — Emit telemetry
84
+
85
+ One bootstrap event per **newly tracked** PR per [`../_shared/telemetry-events/pr-followup-bootstrap.md`](../_shared/telemetry-events/pr-followup-bootstrap.md), with `caller = "auto-track"`. Fire-and-forget per [`../_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Skipped PRs emit nothing.
86
+
87
+ ## Invariants
88
+
89
+ - **Tracking depends only on the PR.** No E2E context is gathered, prompted for, or required.
90
+ - **Idempotent.** Existing slots are skipped; re-running never double-tracks.
91
+ - **No-op is silent.** Empty discovery followed by an empty picker writes nothing and dispatches nothing.
@@ -2,7 +2,7 @@
2
2
 
3
3
  The procedure for the **bootstrap mode** of `muggle-pr-followup` — invoked when a user dispatches the skill with a GitHub PR URL. Routing into this mode is documented in [`SKILL.md`](SKILL.md#routing).
4
4
 
5
- Bootstrap is **non-interactive**: it runs straight through, prompts the user for nothing, and ends with the first watcher dispatched. The first review the watcher sees triggers `/muggle-do`, where working-tree validation surfaces (via the existing E2E stage / muggle-test).
5
+ Bootstrap asks **one** questionnaire — the E2E validation context the loop will reuse — then runs through to the first watcher dispatch. The user is present at launch, so this is the only place to gather it; every later tick reads it from `state.md`. Without it, a URL-bootstrapped watcher has no `localUrl`/`projectId` and Stage 6 hard-halts instead of running E2E.
6
6
 
7
7
  ## Turn preamble
8
8
 
@@ -44,13 +44,19 @@ Default: `<repo>-pr<n>` (e.g. `muggle-ai-works-pr154`). Override: `--slug=<name>
44
44
  If `.muggle-do/sessions/<slug>/` exists:
45
45
 
46
46
  - Without `--resume` → exit with the slot-conflict abort. Both remedies (delete + re-run, or pass `--resume`) are spelled out in the message.
47
- - With `--resume` → refresh `prs.json[0].head_sha` to the current `headRefOid` from Step 2. Leave `last_seen.json`, `state.md`, and everything else untouched. Skip to Step 8 (no need to re-seed; no need to refetch the cursor).
47
+ - 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.
48
48
 
49
49
  ### Step 6 — Resolve the initial cursor
50
50
 
51
51
  - **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.
52
52
  - **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
53
 
54
+ ### Step 6.5 — Resolve E2E validation context
55
+
56
+ The only step that may prompt the user. Run the gather in [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md): reuse an existing context if one is found (gated by `autoReuseValidationContext`), else silent detection + one `AskUserQuestion` (strategy, local URL, backend, project, credentials, re-auth). Record Step 3's verified working tree as `Working tree`.
57
+
58
+ Capture the fields for Step 7. Do **not** run E2E now — the first watcher tick that dispatches `/muggle-do` does that.
59
+
54
60
  ### Step 7 — Seed state files
55
61
 
56
62
  Identify the loop user once per [`../_shared/github-cli-recipes/loop-user-identity.md`](../_shared/github-cli-recipes/loop-user-identity.md); cache in `state.md`.
@@ -61,7 +67,7 @@ Write under `.muggle-do/sessions/<slug>/`:
61
67
 
62
68
  **`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: []`.
63
69
 
64
- **`state.md`** — see [`state-schemas.md`](state-schemas.md#statemd). `Bootstrapped from URL: yes`. Cache the loop-user login.
70
+ **`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).
65
71
 
66
72
  Do **not** write `cycle.json` or `requirements.md` — those files are no longer part of the session slot.
67
73
 
@@ -85,6 +91,7 @@ Emit one event per [`../_shared/telemetry-events/pr-followup-bootstrap.md`](../_
85
91
 
86
92
  ## Invariants
87
93
 
88
- - All state writes happen in Step 7 — earlier aborts leave nothing on disk.
94
+ - Step 6.5 is the **only** user prompt. If the user cancels it, abort leaving nothing on disk.
95
+ - All state writes happen in Step 7 — earlier aborts (including a cancelled Step 6.5) leave nothing on disk.
89
96
  - If Step 7 fails mid-write, surface the OS error and tell the user to `rm -rf <slot>` and re-run; do not dispatch the watcher.
90
97
  - Bootstrap never retries.
@@ -37,7 +37,8 @@ If `state` is `MERGED` or `CLOSED`:
37
37
  2. Write `result.md` per [`state-schemas.md`](state-schemas.md#resultmd).
38
38
  3. Append a terminal line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
39
39
  4. Emit a `tick` event with `terminal: true` per [`../_shared/telemetry-events/pr-followup-tick.md`](../_shared/telemetry-events/pr-followup-tick.md).
40
- 5. Exit. **Do not schedule another tick.** The `/loop` framework stops invoking this skill once it sees no follow-up dispatch.
40
+ 5. **Cancel the cron schedule that fires this watcher.** `/loop 1m ...` from bootstrap was registered via `CronCreate`; a fixed-interval cron keeps firing regardless of whether the skill re-dispatches. Call `CronList`, find any job whose command ends with `/muggle:muggle-pr-followup <slug> <pr-number>` (exact two-arg match), and `CronDelete` it. No-op if none matches — the tick may have been invoked manually rather than via `/loop`.
41
+ 6. Exit. The watcher has now unscheduled itself; no future ticks will fire for this PR.
41
42
 
42
43
  ### Step 3 — Fetch new submitted reviews
43
44
 
@@ -66,7 +67,7 @@ The watcher does **not** classify. Classification, batching, replying, escalatio
66
67
  ```
67
68
  3. Append a dispatching line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
68
69
  4. Emit a `tick` event with `reviews_seen: <count>`, `dispatched_review_ids: [<id>, ...]`.
69
- 5. Exit. **Do not schedule another tick.** `/muggle-do` will respawn the watcher at the end of its cycle.
70
+ 5. Exit. The cron schedule from bootstrap keeps firing the watcher every minute, so the next tick still arrives even though this turn dispatched `/muggle-do`. The watcher only self-unschedules in Step 2 (terminal).
70
71
 
71
72
  ## Output
72
73
 
@@ -1,6 +1,6 @@
1
1
  # Help output
2
2
 
3
- For `/muggle:muggle-pr-followup` with no args, or with `help` / `?`:
3
+ For `/muggle:muggle-pr-followup help` or `?` (no args runs **auto-track** instead — see [`../auto-track.md`](../auto-track.md)):
4
4
 
5
5
  ```
6
6
  muggle-pr-followup — watcher loop for PR review follow-ups
@@ -11,6 +11,7 @@ Active loops:
11
11
  (or "no active loops")
12
12
 
13
13
  Usage:
14
+ /muggle:muggle-pr-followup → auto-track every PR you pushed this session
14
15
  /muggle:muggle-pr-followup <pr-url> → bootstrap a new loop
15
16
  /muggle:muggle-pr-followup <slug> <pr-number> → run one tick (called by /loop)
16
17
  /muggle:muggle-pr-followup <pr-number> → run one tick (slug inferred from on-disk state)
@@ -4,9 +4,12 @@ Posted via `gh api .../comments/<comment-id>/replies` per cycle, one per line co
4
4
 
5
5
  ```
6
6
  Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
7
+
8
+ <!-- muggle-do:bot -->
9
+ 🤖 _Automated reply from muggle-do._
7
10
  ```
8
11
 
9
- `<short-sha>` is the first 7 chars of the new SHA. The body must contain that substring — the resolve-reminder stage greps for it to classify threads as addressed-by-loop.
12
+ `<short-sha>` is the first 7 chars of the new SHA; the body must contain that substring so the resolve-reminder stage knows *which push* addressed the thread. The trailing signature block — defined in [`../../_shared/pr-followup-helpers/loop-signature.md`](../../_shared/pr-followup-helpers/loop-signature.md) — is mandatory; its `<!-- muggle-do:bot -->` marker is what identifies the comment as loop-authored.
10
13
 
11
14
  ## Top-level fallback (review-body-only)
12
15
 
@@ -14,4 +17,7 @@ When an actionable review has a non-empty body but zero line comments, GitHub ha
14
17
 
15
18
  ```
16
19
  Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
20
+
21
+ <!-- muggle-do:bot -->
22
+ 🤖 _Automated reply from muggle-do._
17
23
  ```
@@ -1,12 +1,15 @@
1
1
  # Resolve-reminder top-level PR comment
2
2
 
3
- Posted via `gh pr comment` after the resolve-reminder stage scans threads. Only when at least one addressed-by-loop thread exists.
3
+ Posted via `gh pr comment` once per review round, when at least one unresolved thread is addressed-by-loop with no newer human reply.
4
4
 
5
5
  ```
6
- I addressed these threads in <short-sha> — mark them resolved when satisfied:
6
+ These threads are addressed and still open — mark them resolved if satisfied, or reply if more is needed:
7
7
  - #<thread-id-1>
8
8
  - #<thread-id-2>
9
9
  - ...
10
+
11
+ <!-- muggle-do:bot -->
12
+ 🤖 _Automated reply from muggle-do._
10
13
  ```
11
14
 
12
- If `addressed_by_loop == 0`, no comment is posted (silent).
15
+ If no such thread exists, no comment is posted (silent). The trailing signature block ([`loop-signature.md`](../../_shared/pr-followup-helpers/loop-signature.md)) keeps the loop from later mistaking its own reminder for a human comment.
@@ -60,9 +60,21 @@ Free-form markdown. No required schema beyond a few well-known fields the caller
60
60
  **Created:** <ISO-8601>
61
61
  **Bootstrapped from URL:** <yes | no>
62
62
 
63
+ ## Pre-flight answers
64
+
65
+ - Validation: <local-e2e | staging-replay | unit-only | skip>
66
+ - Local URL: <url | N/A>
67
+ - Backend status: <up | down | N/A>
68
+ - Muggle Test project: <name> (<uuid>)
69
+ - Test credentials: <existing | new | skip>
70
+ - Auth status: <ok | re-authed | N/A>
71
+ - Working tree: <path>
72
+
63
73
  ...free-form notes added by /muggle-do and bootstrap...
64
74
  ```
65
75
 
76
+ The `## Pre-flight answers` block is the **E2E validation context** consumed by `do/e2e-acceptance.md` Step 0 — seeded by bootstrap (Step 6.5) or by pre-flight's output block. Fields: [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md#persisted-fields).
77
+
66
78
  The watcher does **not** read or write `state.md`. Only bootstrap, `/muggle-do`, and the caller's stages touch it.
67
79
 
68
80
  ## `followup.log`
@@ -26,7 +26,7 @@ you can tell me which (if any) should instead be set to `never`.
26
26
  For each option: label = key name, description = first paragraph of `preference-gates/<key>.md`. Multi-select question text = `Which of these should auto-proceed (set to "always")?`; selected = `always`.
27
27
 
28
28
  - `multiSelect: true`, `header: "Auth & session"` — `autoLogin`, `autoSelectProject`, `checkForUpdates`, `verboseOutput`
29
- - `multiSelect: true`, `header: "Test setup"` — `autoSelectLocalHost`, `autoDetectChanges`
29
+ - `multiSelect: true`, `header: "Test setup"` — `autoSelectLocalHost`, `autoDetectChanges`, `autoReuseValidationContext`
30
30
  - `multiSelect: true`, `header: "Test run"` — `showElectronBrowser`, `openTestResultsAfterRun`, `autoPublishLocalResults`
31
31
  - `multiSelect: true`, `header: "Suggestions & PR"` — `suggestRelatedUseCases`, `suggestRelatedTestCases`, `postPRVisualWalkthrough`, `autoCreatePR`
32
32
  - `multiSelect: true`, `header: "Branch hygiene"` — `autoUseWorktree`, `autoRebase`, `autoCleanup`
@@ -0,0 +1,11 @@
1
+ # `autoReuseValidationContext`
2
+
3
+ Reuse an existing E2E validation context (a prior session's `## Pre-flight answers` block for this working tree) instead of asking the validation questions again. Substitute `{contextSource}` (the slug + age of the block being offered). Fires only when such a block exists; with none, the calling skill runs the full gather and never reaches this gate.
4
+
5
+ **Picker 1** — header `Validation context`, question `"Reuse the validation context from {contextSource}?"`
6
+ - `Reuse it` — `Same local URL, project, strategy, and credentials as {contextSource}.` → copy the block into this session
7
+ - `Re-gather` — `Ask the validation questions fresh.` → run the full gather
8
+
9
+ **Silent action**
10
+ - `always` → `Reusing validation context from {contextSource}`
11
+ - `never` → no footer; the gather is the visible step.
@@ -1,7 +1,7 @@
1
1
  {
2
- "release": "4.12.3",
3
- "buildId": "run-41-1",
4
- "commitSha": "9f6c0d27cf0e83976386db90321cf9cc35ac5328",
5
- "buildTime": "2026-05-22T06:22:26Z",
2
+ "release": "4.13.0",
3
+ "buildId": "run-43-1",
4
+ "commitSha": "a3ec4b55712543c6d821193998ef49ee93ad42bb",
5
+ "buildTime": "2026-05-27T22:40:42Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -1 +1 @@
1
- export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-CPF6AR2I.js';
1
+ export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-TWILR37J.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@muggleai/works",
3
3
  "mcpName": "io.github.multiplex-ai/muggle",
4
- "version": "4.12.3",
4
+ "version": "4.13.0",
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.94",
44
+ "electronAppVersion": "1.0.99",
45
45
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
46
46
  "runtimeTargetDefault": "production",
47
47
  "checksums": {
48
- "darwin-arm64": "22731d8fac9b3ce7dc0490932a099fad0b7bab901e4efe3c2f8e44b4dd165bdb",
49
- "darwin-x64": "0965f1a3f7b0db6a976650e26de024f0baea58102cf9acc93e8c69c6dfd8709f",
50
- "linux-x64": "5280ae8ac73215f1c4f92d3b280eee0918341179485993493bd04f3f5a95b5b5",
51
- "win32-x64": "a02bf08ba6f5463b1dad354824c6369738248e86ce5ef02644d2185fdc2d047f"
48
+ "darwin-arm64": "6bfad4f1db8ecabdf78c73094b6c10e1554b540a964ad3a545d7f13706aaa0ec",
49
+ "darwin-x64": "2f8450becd36482842afa5059254e04229533999693feed99f77e4c87d01f2b1",
50
+ "linux-x64": "302fd730e2ff3ef2f1d70ffbb86f0861ea3b4b1c1fdd81887c86c5172b20d9b6",
51
+ "win32-x64": "c62660fd08eed2337ead48e60ff365a785e40fe36f8695bcc342aa83b1fc267c"
52
52
  }
53
53
  },
54
54
  "dependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
4
- "version": "4.12.3",
4
+ "version": "4.13.0",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
5
- "version": "4.12.3",
5
+ "version": "4.13.0",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -13,3 +13,4 @@ Filter client-side:
13
13
  - `id` not in `last_seen.escalated_review_ids`
14
14
  - `user.login` in the resolved allow-list
15
15
  - `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.
@@ -26,8 +26,10 @@ query($owner: String!, $name: String!, $number: Int!) {
26
26
  }'
27
27
  ```
28
28
 
29
- Filter client-side to `isResolved == false`. Classify each thread by inspecting its comments:
29
+ 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
30
 
31
- - **Addressed by loop** — at least one comment authored by the loop user citing a SHA in `last_seen.pushed_shas[]`.
32
- - **Addressed by human** — at least one comment authored by a non-loop user after the original, and no addressed-by-loop signal.
33
- - **Not addressed** — otherwise.
31
+ - **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.
33
+ - **Not addressed** — indeterminate (e.g. no comments).
34
+
35
+ A loop comment also cites a `<short-sha>` from `last_seen.pushed_shas[]` in its body, which tells *which* push addressed the thread.
@@ -2,7 +2,7 @@
2
2
 
3
3
  The address-reviews flow only acts on reviews submitted by users in the **allow-list** = (requested reviewers ∪ CODEOWNERS ∪ {PR author}) − bots. Re-resolve every invocation — never cache across cycles.
4
4
 
5
- The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. The agent itself never appears in the submitted-reviews list (it pushes commits and posts inline replies; it does not submit GitHub reviews), so there's no self-loop risk from including the author.
5
+ The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. Self-loop is prevented at the watcher's filter layer rather than here: `POST /pulls/<n>/comments/<id>/replies` does create an implicit review under the loop user's identity, and the watcher drops it via the loop-echo clause in [`../github-cli-recipes/submitted-reviews.md`](../github-cli-recipes/submitted-reviews.md) — matched by the loop signature ([`loop-signature.md`](loop-signature.md)), so the author's *genuine* reviews and thread replies still get through. Including the PR author in this allow-list is therefore safe.
6
6
 
7
7
  ## Step 1: requested reviewers
8
8
 
@@ -4,12 +4,11 @@ Classify the **review as a unit** — but reply per line comment (threaded), not
4
4
 
5
5
  ## Pre-check: self-loop filter
6
6
 
7
- GitHub auto-creates a synthetic review every time the agent posts `POST /comments/<id>/replies`. That review has the loop user as author, an empty body, and contains only the agent's own reply comments (`in_reply_to_id != null`). It carries no reviewer intent and must not trigger another cycle.
7
+ GitHub auto-creates a synthetic review every time the agent posts `POST /comments/<id>/replies`. In single-account workflows the loop posts under the PR author's own identity, so that synthetic review is indistinguishable from a human's thread reply by author or structure alone — both are reply-only wrappers. The **loop signature** is what separates them (see [`loop-signature.md`](loop-signature.md)).
8
8
 
9
- A review is a **self-loop** iff:
9
+ A review is a **self-loop** iff **every** line comment under it is a reply (`in_reply_to_id != null`) **and** carries the loop marker `<!-- muggle-do:bot -->`.
10
10
 
11
- - `body` is empty, AND
12
- - every line comment under it has `in_reply_to_id != null`
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).
13
12
 
14
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"`.
15
14
 
@@ -0,0 +1,22 @@
1
+ # Loop comment signature
2
+
3
+ Every GitHub comment `/muggle-do` posts — inline thread replies, top-level reference comments, resolve-reminders — **must** end with the signature block below. It is the only reliable way to tell loop-authored comments from human comments: in single-account workflows the loop posts under the PR author's own identity, so `author.login` cannot distinguish them. Echo-protection and addressed-by-loop classification both depend on this marker.
4
+
5
+ ## The signature
6
+
7
+ Append these two lines as the end of every loop-posted comment body:
8
+
9
+ ```
10
+ <!-- muggle-do:bot -->
11
+ 🤖 _Automated reply from muggle-do._
12
+ ```
13
+
14
+ - `<!-- muggle-do:bot -->` — hidden HTML marker; GitHub renders it invisibly and humans never type it. This is the **detection token**.
15
+ - The visible line makes the automation clear to a reader.
16
+
17
+ ## Detection
18
+
19
+ - **Loop-authored** — the comment body contains the literal `<!-- muggle-do:bot -->`.
20
+ - **Human** — the body does not contain the marker.
21
+
22
+ Classify by the marker, never by `author.login` alone — the login is ambiguous under a shared account. A comment that carries the marker is the loop's own and must never re-trigger a cycle; a comment without it is human intent to act on.
@@ -11,3 +11,4 @@ Each section is its own file — load only what the current step needs.
11
11
  | [`allow-list`](pr-followup-helpers/allow-list.md) | Resolve who counts as a reviewer (requested reviewers ∪ CODEOWNERS − bots − author). |
12
12
  | [`reply-routing`](pr-followup-helpers/reply-routing.md) | Pick the right reply endpoint per comment type (line, body-only, CI failure). |
13
13
  | [`classify`](pr-followup-helpers/classify.md) | Per-review binary label: actionable vs ambiguous, with worked examples + borderline rule. |
14
+ | [`loop-signature`](pr-followup-helpers/loop-signature.md) | The signature every loop-posted comment carries, and how to detect loop vs human comments by it. |
@@ -0,0 +1,56 @@
1
+ # Resolving the E2E Validation Context
2
+
3
+ **How-to procedure.** Followed by any seeder that prepares a session for unattended Stage 6 (E2E acceptance) runs.
4
+
5
+ **Goal:** resolve — once, while the user is present — everything Stage 6 needs to run without further prompts (validation strategy, local URL, backend status, Muggle Test project, test credentials, auth), and persist it to `state.md` so every later non-interactive run reads it instead of asking.
6
+
7
+ The sections below are the steps in order: reuse an existing context if one is on disk, else detect what's resolvable silently, ask the rest in one question, and write the result. The final [`## Persisted fields`](#persisted-fields) section doubles as the schema the cycle reads back.
8
+
9
+ ## Reuse an existing context
10
+
11
+ If a `## Pre-flight answers` block already exists for this working tree — the current session slot, or the most recent sibling session under `.muggle-do/sessions/*` — fire the [`autoReuseValidationContext`](../muggle-preferences/preference-gates/autoReuseValidationContext.md) gate before gathering anything:
12
+
13
+ - `always` → copy the existing block into this session; skip the questionnaire.
14
+ - `never` → ignore it; run the full gather below.
15
+ - `ask` → prompt reuse-vs-re-gather.
16
+
17
+ Run the gather only when no block exists or the user chose to re-gather.
18
+
19
+ ## Silent detection
20
+
21
+ Resolve without prompting; use as questionnaire defaults:
22
+
23
+ 1. Dev server + backend health — per [`dev-server-readiness.md`](dev-server-readiness.md).
24
+ 2. Muggle Test MCP auth — `muggle-remote-auth-status`.
25
+ 3. Candidate projects — `muggle-remote-project-list`, ranked against the repo's dev URL and the PR title.
26
+ 4. Existing test-user secrets — `muggle-remote-secret-list` per candidate project (`managed_profile_email` / `managed_profile_password`).
27
+ 5. Auth0 tenant for local dev — grep the repo env file for `*AUTH0_DOMAIN*`.
28
+
29
+ ## Questions
30
+
31
+ One `AskUserQuestion` for the validation subset, detected values as defaults. Canonical wording lives in [`../do/pre-flight.md`](../do/pre-flight.md) — reference, don't fork:
32
+
33
+ - Validation strategy — pre-flight Q4
34
+ - Local URL — pre-flight Q5 (defers to [`autoSelectLocalHost`](../muggle-preferences/preference-gates/autoSelectLocalHost.md))
35
+ - Backend reachable — pre-flight Q6
36
+ - Muggle Test project — pre-flight Q7 (defers to [`autoSelectProject`](../muggle-preferences/preference-gates/autoSelectProject.md))
37
+ - Test-user credentials — pre-flight Q8
38
+ - Re-auth Muggle Test MCP — pre-flight Q10
39
+
40
+ The chosen **validation strategy is the standing decision for every cycle** — no per-tick re-prompt. It subsumes the [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md) gate (pre-flight Q13), meaningless in a loop: `local-e2e` runs Stage 6 each cycle; `unit-only` / `skip` stands down. The gate's `always` default makes `local-e2e` the default when a dev server is detected.
41
+
42
+ Skip the forward-only questions (task scope, repo, branch, PR target, worktree, rebase) — the targeted repo and head branch are already checked out.
43
+
44
+ ## Persisted fields
45
+
46
+ Write to `state.md` under a `## Pre-flight answers` block:
47
+
48
+ - `Validation: <local-e2e | staging-replay | unit-only | skip>`
49
+ - `Local URL: <url | N/A>`
50
+ - `Backend status: <up | down | N/A>`
51
+ - `Muggle Test project: <name> (<uuid>)`
52
+ - `Test credentials: <existing | new | skip>`
53
+ - `Auth status: <ok | re-authed | N/A>`
54
+ - `Working tree: <path>` — the verified checkout the cycle runs against
55
+
56
+ Missing any required field is a seeding bug: escalate with the session path and halt. Never silently skip E2E.
@@ -23,4 +23,4 @@ One per address-reviews invocation, regardless of outcome.
23
23
  - `"escalated"` — all reviews were ambiguous; no push.
24
24
  - `"mixed"` — both branches happened in the same invocation.
25
25
  - `"no-op"` — every input id was already in the escalated set; no work.
26
- - `"self-loop-skip"` — review was a synthetic wrapper around the agent's own reply (empty body + all line comments are replies). Cursor advanced silently; no work, no escalation.
26
+ - `"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.
@@ -27,14 +27,18 @@ Read from `.muggle-do/sessions/<slug>/`:
27
27
 
28
28
  ## Procedure
29
29
 
30
- ### Step 1 — Read every listed review off GitHub
30
+ ### Step 1 — Assemble the work set
31
31
 
32
- For each review id in the input:
32
+ Two sources, combined into one batch (dedupe by comment id):
33
+
34
+ **(a) The dispatched reviews.** For each review id in the input:
33
35
 
34
36
  - Fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md) (cursor 0; filter to the specific id).
35
37
  - Fetch its line comments per [`../_shared/github-cli-recipes/line-comments-for-review.md`](../_shared/github-cli-recipes/line-comments-for-review.md).
36
38
 
37
- Group into one combined batch.
39
+ **(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.
40
+
41
+ Group (a) and (b) into one combined batch.
38
42
 
39
43
  ### Step 2 — Classify each review
40
44
 
@@ -57,7 +61,7 @@ The user clarifies on GitHub by submitting a new review. The next watcher tick p
57
61
 
58
62
  ### Step 4 — Handle actionables (if any)
59
63
 
60
- If `actionable_review_ids` is empty, skip Steps 4 and 5; proceed to Step 6 (cursor + respawn). Otherwise:
64
+ If `actionable_review_ids` is empty, skip the rest of Step 4 and Step 5; proceed to Step 5.5 (resolve-reminder) then Step 6. Otherwise:
61
65
 
62
66
  #### 4a. Flatten the work
63
67
 
@@ -75,7 +79,7 @@ Invoke [`unit-tests.md`](unit-tests.md). Cover the surface that just changed; re
75
79
 
76
80
  #### 4d. Run ONE E2E acceptance pass
77
81
 
78
- Invoke [`e2e-acceptance.md`](e2e-acceptance.md). One pass covering all related test cases for this PR, not one pass per comment. Use the `autoE2ETest` gate per its usual contract.
82
+ Invoke [`e2e-acceptance.md`](e2e-acceptance.md). One pass covering all related test cases for this PR, not one per comment. The stage reads the persisted validation context (seeded by pre-flight or by bootstrap Step 6.5); a poll-only session with no context (e.g. auto-track) is reported `SKIPPED`. The persisted `Validation` strategy is the standing decision — no per-tick `autoE2ETest` prompt. See [`e2e-acceptance.md`](e2e-acceptance.md) Step 0 and [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md).
79
83
 
80
84
  #### 4e. Create or update the PR
81
85
 
@@ -85,9 +89,7 @@ Invoke [`open-prs/update.md`](open-prs/update.md) (pass the PR URL + slug + exis
85
89
 
86
90
  Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable reviews and the new SHA. One reply per comment, in its own thread, describing what was done for it.
87
91
 
88
- #### 4g. Run the resolve-reminder stage
89
-
90
- Invoke [`resolve-reminder.md`](resolve-reminder.md). Scans unresolved threads, classifies, and posts a top-level PR comment listing addressed-by-loop thread ids.
92
+ (The resolve-reminder runs once per round in Step 5.5 below — not only after a push — so a round that pushed nothing still nudges addressed-but-unresolved threads.)
91
93
 
92
94
  ### Step 5 — Update session state
93
95
 
@@ -95,6 +97,10 @@ Invoke [`resolve-reminder.md`](resolve-reminder.md). Scans unresolved threads, c
95
97
  - `last_seen.last_pushed_sha` = the new head SHA (update.md already wrote this; verify)
96
98
  - `last_seen.reviewId` = max(input review ids ∪ last_seen.reviewId)
97
99
 
100
+ ### Step 5.5 — Resolve-reminder (runs every round)
101
+
102
+ Invoke [`resolve-reminder.md`](resolve-reminder.md) once, regardless of whether this round pushed. It scans unresolved threads, finds those whose newest comment is loop-marked (addressed, awaiting resolve), and posts one top-level PR comment nudging the reviewer to resolve them — or stays silent if there are none. Threads with a newer human comment were already pulled into this round's work set in Step 1(b).
103
+
98
104
  ### Step 6 — Respawn the watcher
99
105
 
100
106
  Refresh PR state per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). If the PR is now merged or closed: