@muggleai/works 4.10.1 → 4.11.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 (40) hide show
  1. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  2. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  3. package/dist/plugin/skills/_shared/pr-followup-helpers.md +150 -0
  4. package/dist/plugin/skills/do/build.md +51 -0
  5. package/dist/plugin/skills/do/e2e-acceptance.md +2 -2
  6. package/dist/plugin/skills/do/impact-analysis.md +2 -2
  7. package/dist/plugin/skills/do/open-prs.md +67 -78
  8. package/dist/plugin/skills/do/pre-flight.md +3 -3
  9. package/dist/plugin/skills/do/requirements.md +2 -2
  10. package/dist/plugin/skills/do/unit-tests.md +2 -2
  11. package/dist/plugin/skills/muggle-do/SKILL.md +34 -77
  12. package/dist/plugin/skills/muggle-feedback/SKILL.md +2 -1
  13. package/dist/plugin/skills/muggle-feedback/ops/submit.md +16 -3
  14. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +59 -0
  15. package/dist/plugin/skills/muggle-pr-followup/contract.md +191 -0
  16. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +14 -0
  17. package/dist/release-manifest.json +4 -4
  18. package/package.json +6 -6
  19. package/plugin/.claude-plugin/plugin.json +1 -1
  20. package/plugin/.cursor-plugin/plugin.json +1 -1
  21. package/plugin/skills/_shared/pr-followup-helpers.md +150 -0
  22. package/plugin/skills/do/build.md +51 -0
  23. package/plugin/skills/do/e2e-acceptance.md +2 -2
  24. package/plugin/skills/do/impact-analysis.md +2 -2
  25. package/plugin/skills/do/open-prs.md +67 -78
  26. package/plugin/skills/do/pre-flight.md +3 -3
  27. package/plugin/skills/do/requirements.md +2 -2
  28. package/plugin/skills/do/unit-tests.md +2 -2
  29. package/plugin/skills/muggle-do/SKILL.md +34 -77
  30. package/plugin/skills/muggle-feedback/SKILL.md +2 -1
  31. package/plugin/skills/muggle-feedback/ops/submit.md +16 -3
  32. package/plugin/skills/muggle-pr-followup/SKILL.md +59 -0
  33. package/plugin/skills/muggle-pr-followup/contract.md +191 -0
  34. package/plugin/skills/muggle-test-feature-local/SKILL.md +14 -0
  35. package/dist/plugin/skills/do/pr-followup.md +0 -225
  36. package/dist/plugin/skills/do/validate-code.md +0 -38
  37. package/dist/plugin/skills/muggle-do-pr-followup/SKILL.md +0 -37
  38. package/plugin/skills/do/pr-followup.md +0 -225
  39. package/plugin/skills/do/validate-code.md +0 -38
  40. package/plugin/skills/muggle-do-pr-followup/SKILL.md +0 -37
@@ -32,7 +32,8 @@ If intent is ambiguous, use `AskUserQuestion` once with options **Submit / List
32
32
 
33
33
  ## Non-negotiables (all ops)
34
34
 
35
- - Use `AskUserQuestion` for every selection (project, test case, run, target type, confirm). Never ask the user to "reply with a number" in plain text.
35
+ - Use `AskUserQuestion` for every selection (project, test case, run, target type, which step(s), confirm). Step selection must be a clickable picker built from the rendered steps (see [`ops/submit.md`](ops/submit.md) §3b) — never ask the user to type a number.
36
+ - Always render the run's steps and summary (§2) **before** collecting feedback — users can only point at what they can see.
36
37
  - Convert step numbers between 1-based (rendered to user) and 0-based (wire format) at the boundary. Never expose 0-based indices to the user.
37
38
  - One MCP submit/delete call per feedback piece — never batch into a single call.
38
39
  - Surface the `feedbackAnalysisWorkflowRuntimeId` returned by submit so the user knows regeneration is running. Do not poll it from this skill.
@@ -55,6 +55,8 @@ Print:
55
55
 
56
56
  ## 3. Collect feedback (batch)
57
57
 
58
+ ### 3a. Pick the scope (entity type)
59
+
58
60
  Use `AskUserQuestion` to scope which targets:
59
61
 
60
62
  > "Where is the problem?"
@@ -63,10 +65,21 @@ Use `AskUserQuestion` to scope which targets:
63
65
  > - The whole script's outcome / summary
64
66
  > - Multiple steps **and** the whole outcome
65
67
 
66
- For each chosen target, prompt for the feedback paragraph in plain text. Keep prompts specific:
68
+ ### 3b. Pick the step(s) — clickable picker
69
+
70
+ When 3a includes any step-level scope, present the rendered steps as a **clickable `AskUserQuestion` picker** — never as a typed number.
71
+
72
+ - **Option format** — Label: `Step <n>: <action label> on <element text or id>` (≤80 chars). Description: the step's `briefExplanation` (≤120 chars).
73
+ - **Multi-select** — `multiSelect: true` for "Multiple steps" or "Multiple steps and the whole outcome"; `multiSelect: false` for "One specific step".
74
+ - **Long scripts (>10 steps)** — rank by keyword overlap with the user's prompt (label or briefExplanation); fall back to the first 10 if no match. Append **"Show all steps"** as a final option that re-asks with the full list — never silently truncate.
75
+
76
+ For each selected step, prompt in plain text:
77
+
78
+ > "What should step `<n>` have done instead?"
79
+
80
+ For the whole-script scope, prompt:
67
81
 
68
- - For a step: "What should step `<n>` have done instead?"
69
- - For the whole script: "What's wrong with the overall outcome?"
82
+ > "What's wrong with the overall outcome?"
70
83
 
71
84
  Validate each paragraph is non-empty (re-prompt if blank). Build an in-memory list of feedback pieces:
72
85
 
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: muggle-pr-followup
3
+ description: Generic per-PR follow-up loop. One polling tick per dispatch. Watches one PR for new submitted reviews; when an actionable review lands, invokes the caller's implementation cycle (build/test/walkthrough/push handler the caller declares), then resumes polling. Use as `/loop 1m /muggle:muggle-pr-followup <session-slug> <pr-number>`. Caller-agnostic — `muggle-do` is the first caller but not the last.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Muggle PR Follow-up
8
+
9
+ > Telemetry first step: see [`_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-pr-followup"`.
10
+
11
+ A generic loop that babysits one open PR until it's merged or closed. Polls for submitted reviews; on an actionable review, dispatches the caller's implementation cycle to re-build, re-test, refresh the walkthrough, and push.
12
+
13
+ **Per-PR isolation.** One dispatch per PR, not per session. Multi-PR work runs N independent loops.
14
+
15
+ ## Input
16
+
17
+ `$ARGUMENTS = <session-slug> <pr-number>`. The session-slug is the directory basename under `.muggle-<caller>/sessions/` (e.g. `.muggle-do/sessions/`) that this loop reads its state from. The pr-number identifies which PR in that session's manifest this loop watches.
18
+
19
+ - Empty / `help` / `?` → list active `(slug, pr-number)` pairs across known callers, exit.
20
+ - One argument → ambiguous; list PRs under that slug, exit.
21
+ - Two arguments → resolve and run one tick.
22
+
23
+ ## Contract
24
+
25
+ The per-tick contract — termination check, allow-list resolution, review polling, classify, cycle dispatch, escalation, telemetry — lives in [`contract.md`](contract.md). Read it before driving a tick.
26
+
27
+ ## Caller-supplied implementation cycle
28
+
29
+ The skill itself is caller-agnostic. When an actionable review lands, it invokes the **implementation cycle** declared by the caller in the session's `cycle.json`:
30
+
31
+ ```json
32
+ {
33
+ "cycleName": "muggle-do dev cycle",
34
+ "steps": [
35
+ { "stage": 3, "file": "../do/build.md" },
36
+ { "stage": 4, "file": "../do/impact-analysis.md" },
37
+ { "stage": 5, "file": "../do/unit-tests.md" },
38
+ { "stage": 6, "file": "../do/e2e-acceptance.md" },
39
+ { "name": "post-walkthrough", "skill": "muggle-pr-visual-walkthrough", "mode": "A" }
40
+ ],
41
+ "pushHandler": "git push origin <branch>"
42
+ }
43
+ ```
44
+
45
+ The cycle's job: read the amended `requirements.md`, run each declared step, push to the existing branch. The cycle returns one of `pushed | escalated | failed`. The skill doesn't care what's inside the steps — it just iterates them, captures the outcome, and writes a reply summary referencing the new SHA.
46
+
47
+ Callers without a `cycle.json` are rejected with `no cycle declared — caller must seed cycle.json at dispatch time`.
48
+
49
+ ## Subagent dispatch (optional)
50
+
51
+ A caller may declare `"useSubagent": true` in `cycle.json`. When set, the implementation cycle runs as a separate Claude subagent rather than inline in the loop's tick — gives stronger isolation per PR but adds overhead per cycle. Default is inline (which is itself isolated per-tick via `/loop`).
52
+
53
+ ## Design adjustment escalation
54
+
55
+ When the implementation cycle reports back that the review requires not just code change but a **design adjustment** beyond what the current requirements support (e.g. the build stage discovers the requested change conflicts with a load-bearing assumption), the cycle returns `failed: design-adjustment`. The loop treats this like an ambiguous escalation but with a different terminal message that points at the design conflict and asks the user to decide.
56
+
57
+ ## Why a separate skill (and not e.g. inside the caller's own skill folder)
58
+
59
+ `/loop` dispatches via slash command; slash commands resolve to top-level skills under `plugin/skills/`. A per-tick loop driven by `/loop` therefore has to live as a top-level skill. Putting the loop's content directly inside a caller-specific folder would make `/loop`-dispatch impossible (or fragile via shell-only entry points). Keeping the loop generic and caller-agnostic is what lets `/loop` dispatch into it cleanly.
@@ -0,0 +1,191 @@
1
+ # PR follow-up per-tick contract
2
+
3
+ Caller-agnostic per-tick contract for the [muggle-pr-followup](SKILL.md) skill. One tick = one polling iteration scoped to one PR.
4
+
5
+ ## Turn preamble
6
+
7
+ ```
8
+ **PR follow-up** — polling <repo>#<pr-number>, tick #<K>.
9
+ ```
10
+
11
+ Resolve `<K>` from `idle_tick_count + cycles_completed` in this PR's state slot.
12
+
13
+ ## When this contract may break the caller's "no questions" rule
14
+
15
+ Most callers' forward pipelines never ask the user mid-cycle. This loop, however, may emit a single escalation message when a submitted review is ambiguous (see [classify](#step-6-classify-the-review) below). By the time the loop is polling, the user has walked away from the forward pipeline; forcing a guess on an ambiguous review is worse than pausing.
16
+
17
+ Escalation is the **only** user-facing path. Otherwise the cycle runs silently end to end.
18
+
19
+ ## Inputs
20
+
21
+ Read these from `.muggle-<caller>/sessions/<slug>/` (the caller's session dir):
22
+
23
+ - `state.md` — session metadata (for context when classifying reviews).
24
+ - `prs.json` — list of `{repo, number, url, head_sha, state, escalated?, cycling?}`. This loop touches only the entry whose `number` matches the dispatched PR number.
25
+ - `last_seen.json` — keyed by `"<owner>/<repo>#<n>"`. For this PR: `{reviewId, last_pushed_sha, idle_tick_count, cycles_completed, escalated_review_ids[]}`.
26
+ - `cycle.json` — caller's declared implementation cycle (see [SKILL.md](SKILL.md#caller-supplied-implementation-cycle)).
27
+
28
+ If any of these don't exist or the PR isn't in `prs.json`, the tick is a no-op — log an error to `followup.log` and exit.
29
+
30
+ ## Per-tick contract
31
+
32
+ ### Step 1: Refresh this PR's state
33
+
34
+ ```bash
35
+ gh pr view <number> --repo <repo> --json state,mergedAt,closedAt,headRefOid
36
+ ```
37
+
38
+ If `state` is `MERGED` or `CLOSED`, mark this entry terminal in `prs.json`. Update `head_sha` if it changed.
39
+
40
+ ### Step 2: Termination check
41
+
42
+ If this PR is terminal: write a per-PR section into `result.md` (URL, final state, `cycles_completed`, count escalated, final SHA), emit final telemetry, **do not schedule another tick**. Other PRs in the session have their own loops; they terminate independently.
43
+
44
+ ### Step 3: Resolve the reviewer allow-list (every tick)
45
+
46
+ ```bash
47
+ gh pr view <number> --repo <repo> --json reviewRequests,author
48
+ ```
49
+
50
+ Add requested reviewers. Add CODEOWNERS by parsing `.github/CODEOWNERS` (or `CODEOWNERS` / `docs/CODEOWNERS`) from the PR's head branch. Remove the PR author and any bot accounts (logins ending in `[bot]`, plus the standard list: `dependabot`, `github-actions`, `renovate`, `mergify`).
51
+
52
+ ### Step 4: Pull new submitted reviews
53
+
54
+ ```bash
55
+ gh api repos/<owner>/<repo>/pulls/<number>/reviews --paginate
56
+ ```
57
+
58
+ Filter to reviews where:
59
+
60
+ - `submitted_at` is non-null (skip drafts — `PENDING` reviews are still being composed).
61
+ - `id > last_seen.reviewId`.
62
+ - `user.login` is in the allow-list.
63
+ - `id` is not in `escalated_review_ids`.
64
+ - `state` is `CHANGES_REQUESTED` or `COMMENTED`, OR `APPROVED` with at least one line comment or a non-empty body.
65
+
66
+ ### Step 5: Pick the oldest new review
67
+
68
+ If no new review past the cursor: increment `idle_tick_count`, append a heartbeat line to `followup.log`, exit. Next tick fires in 1 min via `/loop`.
69
+
70
+ If one or more: take the oldest by `submitted_at`. Fetch its associated line comments (`gh api repos/<owner>/<repo>/pulls/<n>/comments` filtered by `pull_request_review_id`). Reset `idle_tick_count` to 0.
71
+
72
+ **At most one review per tick.** If two reviews land between ticks, the second waits.
73
+
74
+ ### Step 6: Classify the review
75
+
76
+ Apply the classify rule in [`../_shared/pr-followup-helpers.md`](../_shared/pr-followup-helpers.md). The rule applies to the **review as a unit**. Two outcomes:
77
+
78
+ - **Actionable** → continue to Step 7.
79
+ - **Ambiguous** → continue to Step 8.
80
+
81
+ ### Step 7: Dispatch the implementation cycle
82
+
83
+ When the review is actionable:
84
+
85
+ 1. **Pause polling** for this PR (set `cycling: true` on this PR's entry in `prs.json`).
86
+ 2. **Amend `requirements.md`** in the session dir with a new `## Amendment — review <review_id> by <login> (<timestamp>)` section pasting the review body and each comment (with `<file>:<line>` context).
87
+ 3. **Invoke the implementation cycle** declared in the caller's `cycle.json`. Iterate the `steps[]` in order. Each step is either a markdown file to follow, a skill to invoke, or a shell command (per the `cycle.json` schema in SKILL.md). When a step fails, the cycle returns `failed: <step-name>`; the loop escalates per Step 8 with the failure as the reason.
88
+ 4. **Push** via `cycle.json`'s `pushHandler`. Set `last_seen.last_pushed_sha` to the new HEAD.
89
+ 5. **Reply** with one summary via `gh pr comment <n>`:
90
+ ```
91
+ Addressed review <review_id> in <sha> — cycle ran clean (or: with <N> failures, see walkthrough).
92
+ ```
93
+ 6. **Resume polling**: clear `cycling: true`, increment `cycles_completed`, advance `last_seen.reviewId` past this review.
94
+ 7. Emit per-cycle telemetry.
95
+
96
+ If the cycle returns `failed: design-adjustment` (the cycle discovered the review can't be implemented without rethinking the design itself, not just the code), escalate per Step 8 with a `design-adjustment` reason — the terminal message asks the user to confirm the design intent before retrying.
97
+
98
+ ### Step 8: Escalate
99
+
100
+ When the review is ambiguous, or the cycle failed:
101
+
102
+ 1. Add the review id to `last_seen.escalated_review_ids`.
103
+ 2. Append a `followup.log` entry describing the review and the reason.
104
+ 3. Pause this PR's loop by writing `escalated: true` against this PR's entry in `prs.json`.
105
+ 4. End the turn with a **single terminal message** to the user:
106
+
107
+ ```
108
+ **PR follow-up escalation — <repo>#<number> — review <review_id>**
109
+
110
+ <reviewer-login> submitted a review I can't act on coherently:
111
+
112
+ > <quoted review body, or "(no body)" if empty>
113
+
114
+ Comments:
115
+ - <file>:<line> — <quoted comment body>
116
+
117
+ [For ambiguous]
118
+ Best two interpretations:
119
+ 1. <one-line interpretation A>
120
+ 2. <one-line interpretation B>
121
+
122
+ [For cycle failure]
123
+ The implementation cycle failed at <step-name>: <reason>.
124
+
125
+ Reply on the review yourself, leave a follow-up comment, or tell me which way to go.
126
+ ```
127
+
128
+ The user clears the escalation by replying on GitHub (next tick sees a new submitted review past the cursor) or by giving a directive in this terminal session.
129
+
130
+ ### Step 9: Emit tick-summary telemetry and exit
131
+
132
+ Emit one tick event per `muggle-local-telemetry-skill-emit`. Exit the turn.
133
+
134
+ ## Reply routing
135
+
136
+ - **Summary reply on a review**: `gh pr comment <number> --body "..."` referencing the review id and the new SHA. There's no "reply to a review" endpoint.
137
+ - **Reply to a specific line comment** (optional): `POST /repos/{owner}/{repo}/pulls/{n}/comments/{comment_id}/replies`.
138
+ - **Never post the same summary twice** — `last_seen.reviewId` is the only re-entry guard.
139
+
140
+ ## Telemetry
141
+
142
+ **Per-cycle** (one event per actionable review handled):
143
+
144
+ ```json
145
+ {
146
+ "skill": "muggle-pr-followup",
147
+ "event": "cycle",
148
+ "caller": "<caller>",
149
+ "session_slug": "<slug>",
150
+ "repo": "<repo>",
151
+ "pr_number": <n>,
152
+ "review_id": <id>,
153
+ "outcome": "pushed|escalated|failed:<step>",
154
+ "comment_count": <count>,
155
+ "head_sha_before": "<sha>",
156
+ "head_sha_after": "<sha-or-null>"
157
+ }
158
+ ```
159
+
160
+ **Per-tick summary** (always one, even idle):
161
+
162
+ ```json
163
+ {
164
+ "skill": "muggle-pr-followup",
165
+ "event": "tick",
166
+ "session_slug": "<slug>",
167
+ "repo": "<repo>",
168
+ "pr_number": <n>,
169
+ "reviews_seen": <count>,
170
+ "review_picked": true|false,
171
+ "cycle_dispatched": true|false,
172
+ "tick_duration_ms": <ms>
173
+ }
174
+ ```
175
+
176
+ ## Output
177
+
178
+ This stage produces no console output beyond:
179
+ - The turn preamble (always).
180
+ - An escalation terminal message (only when escalating).
181
+ - The final `result.md` summary section for this PR (only on the terminating tick — written to disk, not printed).
182
+
183
+ ## Self-check before ending the turn
184
+
185
+ - [ ] `last_seen.json` advanced for any review handled.
186
+ - [ ] `prs.json` reflects current state (terminal marked; `escalated`/`cycling` flags consistent).
187
+ - [ ] `followup.log` has at minimum a heartbeat or per-review line for this tick.
188
+ - [ ] Telemetry events emitted (per-cycle when applicable + per-tick).
189
+ - [ ] If pushed, `last_pushed_sha` is set and `cycles_completed` incremented.
190
+ - [ ] If escalated, `escalated_review_ids` contains the review id.
191
+ - [ ] If terminal, the loop is NOT continued.
@@ -215,6 +215,19 @@ If the user picks `muggle-feedback` from any bucket's options, invoke the `muggl
215
215
 
216
216
  Skip silently when the run passed cleanly — failure-mode events are by definition about failures.
217
217
 
218
+ ### 9b. Remind the user to guide the agent (every Electron invocation)
219
+
220
+ Fires after **every** Electron run, pass or fail. A run can technically pass while still containing steps the user would correct — a misclick, wrong element, or a summary that doesn't match intent. This is the user's chance to flag it before regeneration picks up elsewhere.
221
+
222
+ **Skip if 9a already routed the user into `muggle-feedback` for this run.** Otherwise ask via `AskUserQuestion`:
223
+
224
+ > "Did the agent miss or do anything wrong on this run? Your feedback regenerates the affected scripts."
225
+
226
+ - **Yes — give feedback** → invoke `muggle-feedback` via the `Skill` tool, passing the just-finished `runId` so the submit flow opens on this run's steps and summary.
227
+ - **No — looks good** → continue to Step 10.
228
+
229
+ Non-blocking — one click to dismiss. Do not re-ask for the same `runId` within a session.
230
+
218
231
  ### 10. Offer to post a visual walkthrough to the PR
219
232
 
220
233
  After reporting results:
@@ -231,6 +244,7 @@ After reporting results:
231
244
  - **Never prompt for Electron launch approval** before execution — invoking this skill is the approval. Just run.
232
245
  - If replayable scripts exist, do not default to generation without user choice.
233
246
  - No hiding failures: surface errors and artifact paths.
247
+ - **Always offer the agent-guidance reminder after every Electron run** (Step 9b) — pass or fail — unless 9a already routed the user into `muggle-feedback`. Never silently end a run without giving the user a one-click path to flag what was wrong.
234
248
  - Replay: never hand-built or simplified `actionScript` — only from `muggle-remote-action-script-get`.
235
249
  - Use `AskUserQuestion` for every selection — project, use case, test case, script. Never ask the user to type a number.
236
250
  - Project, use case, and test case selection lists must always include "Create new ...". Include "Show full list" whenever the API returned at least one row for that step; omit "Show full list" when the list is empty (offer "Create new ..." only). For creates, use preview tools (`muggle-remote-use-case-prompt-preview`, `muggle-remote-test-case-generate-from-prompt`) before persisting.
@@ -1,225 +0,0 @@
1
- # PR follow-up agent (Stage 8/8)
2
-
3
- You are babysitting one or more open pull requests opened by stage 7. Each invocation of this stage is **one polling tick** dispatched by `/loop 5m /muggle:muggle-do-pr-followup <slug>`. The tick is short, idempotent, and addresses **at most one item per PR**.
4
-
5
- The loop ends when every PR in the session is merged or closed.
6
-
7
- ## Turn preamble
8
-
9
- Start the turn with:
10
-
11
- ```
12
- **Stage 8/8 — PR follow-up** — polling <N> PR(s), tick #<K>.
13
- ```
14
-
15
- Resolve `<N>` from `prs.json` (non-terminal entries only) and `<K>` from the tick counter in `state.md`.
16
-
17
- ## Stage-8 exception to the no-mid-cycle-questions rule
18
-
19
- Stages 2–7 never ask the user mid-cycle. **Stage 8 may escalate** when a reviewer comment is ambiguous (see [Decision rule: classify](#decision-rule-classify) below). This is deliberate — the user has already walked away by the time stage 8 starts, and forcing a guess on an ambiguous design comment is worse than pausing.
20
-
21
- Escalation is the **only** user-facing path in stage 8. Anything else — directives, questions, CI failures, retries — runs silently.
22
-
23
- ## Inputs
24
-
25
- Read these from `.muggle-do/sessions/<slug>/`:
26
-
27
- - `state.md` — current tick counter, session metadata, the pre-flight answers (for context when classifying comments).
28
- - `prs.json` — list of `{repo, number, url, head_sha, state}`. Entries with `state: "merged"` or `state: "closed"` are skipped.
29
- - `last_seen.json` — per-PR cursor: `{commentId, reviewId, checkRunCompletedAt, last_pushed_sha, idle_tick_count, escalated_comment_ids[]}`.
30
-
31
- If any of these don't exist, the tick is a no-op — log an error to `followup.log` and exit.
32
-
33
- ## Per-tick contract
34
-
35
- Do these steps in order. **Do not batch — at most one actionable item per PR.**
36
-
37
- ### Step 1: Refresh PR states
38
-
39
- For each PR in `prs.json` not already terminal:
40
-
41
- ```bash
42
- gh pr view <number> --repo <repo> --json state,mergedAt,closedAt,headRefOid
43
- ```
44
-
45
- If `state` is `MERGED` or `CLOSED`, mark the entry terminal in `prs.json`. Update `head_sha` if it changed.
46
-
47
- ### Step 2: Termination check
48
-
49
- If every entry in `prs.json` is now terminal:
50
-
51
- 1. Write `result.md` with one section per PR (URL, final state, count of items addressed, count escalated, final commit SHA).
52
- 2. **Do not schedule the next tick.** End the turn with no `ScheduleWakeup`-equivalent — `/loop` ends naturally.
53
- 3. Emit the final tick-summary telemetry event (see [Telemetry](#telemetry)) with `prs_terminal == pr_count`.
54
-
55
- ### Step 3: Resolve the reviewer allow-list (every tick)
56
-
57
- For each non-terminal PR, compute the set of GitHub logins allowed to drive changes:
58
-
59
- ```bash
60
- gh pr view <number> --repo <repo> --json reviewRequests,author
61
- ```
62
-
63
- Add requested reviewers. Add CODEOWNERS by parsing `.github/CODEOWNERS` (or `CODEOWNERS` / `docs/CODEOWNERS`) from the PR's head branch. Remove the PR author and any bot accounts (logins ending in `[bot]`, plus the standard list: `dependabot`, `github-actions`, `renovate`).
64
-
65
- This is per-tick by design (decision 9): reviewers added or removed after the PR opened take effect on the next poll.
66
-
67
- ### Step 4: Pull new actionable items
68
-
69
- For each non-terminal PR, fetch items newer than the cursor in `last_seen.json`:
70
-
71
- - **Line-level review comments**:
72
- ```bash
73
- gh api repos/<owner>/<repo>/pulls/<number>/comments --paginate
74
- ```
75
- Filter to comments with `id > last_seen.commentId` AND `user.login` in the allow-list AND not already in `escalated_comment_ids` (avoid re-escalating).
76
-
77
- - **CHANGES_REQUESTED review bodies** (only when the review has a non-empty body and no associated line comments):
78
- ```bash
79
- gh api repos/<owner>/<repo>/pulls/<number>/reviews --paginate
80
- ```
81
- Filter to reviews with `id > last_seen.reviewId` AND `state == "CHANGES_REQUESTED"` AND `user.login` in the allow-list AND `body` is non-empty.
82
-
83
- - **Failing CI checks** (apply the `head_sha` guard, decision 11):
84
- ```bash
85
- gh pr checks <number> --repo <repo> --json name,state,completedAt,detailsUrl,workflow
86
- ```
87
- Filter to checks where `state == "FAILURE"` AND `completedAt > last_seen.checkRunCompletedAt`. **Skip any check whose target SHA equals `last_pushed_sha`** — CI is still digesting our last push, addressing it again would double-handle.
88
-
89
- ### Step 5: Pick one item per PR
90
-
91
- If a PR has zero actionable items, increment `idle_tick_count` for that PR. Otherwise sort the PR's items by timestamp ascending and take the **oldest one**. Reset `idle_tick_count` to 0 for that PR.
92
-
93
- If every PR has zero actionable items this tick:
94
- - Append a one-line heartbeat to `followup.log`: `<ts> tick #<K> idle (PRs: #A, #B, ...)`.
95
- - If `idle_tick_count >= 12` for any PR, also rewrite `state.md` with `idle since <ts>, last poll <ts>` for that PR (decision 12).
96
- - Emit tick-summary telemetry, exit the turn (next tick fires in 5 min via `/loop`).
97
-
98
- ### Step 6: Classify and route
99
-
100
- For each picked item, classify it:
101
-
102
- #### Decision rule: classify
103
-
104
- | Class | Signals | Action |
105
- | :---- | :------ | :----- |
106
- | **directive** | Imperative verb on a concrete target: "rename X to Y", "extract this", "add a null check", "remove this branch", "use `const` here", "this should be `async`", "delete this comment". Includes review-body summaries that read as a list of changes. | Fix → commit → push → reply `Done in <sha> — <one-line>`. |
107
- | **question** | Ends with `?` and is not a rhetorical disguise. "Why this approach?", "Is this called from X?", "Does this need to handle Z?". | Reply inline with the answer. No code change. No push. |
108
- | **CI failure** | Source is a failing check, not a comment. | Read the failing job log, fix, commit, push. No reply. |
109
- | **ambiguous** (default) | Proposes an alternative without instructing ("I think we should use Z instead", "Have you considered Y?"), conflicts with a deliberate choice in the PR description or design doc, or is a multi-part comment mixing question and change request. | **Escalate.** See [Step 7: Escalate](#step-7-escalate). |
110
-
111
- When the comment matches neither **directive** nor **question** cleanly, default to **ambiguous**. Do not guess. The cost of escalating a directive that could have been auto-handled is small; the cost of pushing a wrong change because we guessed is large.
112
-
113
- #### Decision rule: reply text (adaptive, decision 6)
114
-
115
- - **directive**: short, one-line. `Done in <sha> — renamed \`fooBar\` to \`foo_bar\` per request.` Use the [reply-routing helper](#reply-routing) to hit the correct endpoint.
116
- - **question**: answer inline. Pull surrounding-code context if needed. Reply length matches the question's complexity — don't write three paragraphs to answer a yes/no.
117
- - **CI failure**: no comment to reply to. The fix commit is the response. The commit message should reference the failing check by name (e.g. `fix(ci): typecheck — narrow type of foo`).
118
- - **ambiguous**: no reply written by the bot. The escalation goes to the user, who replies to the comment themselves.
119
-
120
- ### Step 7: Escalate
121
-
122
- When the picked item is **ambiguous**:
123
-
124
- 1. Add the comment id to `last_seen.escalated_comment_ids` so it isn't re-picked next tick.
125
- 2. Append an entry to `followup.log` describing the comment and why it was classified ambiguous.
126
- 3. Pause this PR's loop by writing `escalated: true` against the PR's entry in `prs.json`. Subsequent ticks skip this PR until the user clears the escalation.
127
- 4. End the turn with a **single terminal message** to the user:
128
-
129
- ```
130
- **Stage 8 escalation — <repo>#<number>**
131
-
132
- <reviewer-login> left an ambiguous comment on <file>:<line>:
133
-
134
- > <quoted comment body>
135
-
136
- Classifying it as a directive would mean: <one-line interpretation>
137
- Classifying it as a question would mean: <one-line alternative>
138
-
139
- Reply to that GitHub comment yourself, or tell me which way to go. I'll resume polling once the comment is either resolved or has a follow-up from you.
140
- ```
141
-
142
- The user clears the escalation by either resolving the GitHub thread (the next tick sees it resolved and removes it from `escalated_comment_ids`) or by replying in this terminal session with a directive that the next tick will pick up.
143
-
144
- ### Step 8: Update cursors and push
145
-
146
- After addressing a non-escalated item:
147
-
148
- - Advance `last_seen.commentId` / `last_seen.reviewId` / `last_seen.checkRunCompletedAt` past the addressed item.
149
- - If a push happened, set `last_seen.last_pushed_sha` to the new HEAD SHA. This arms the `head_sha` guard for the next tick (decision 11).
150
- - Emit per-item telemetry (see [Telemetry](#telemetry)).
151
-
152
- ### Step 9: Emit tick-summary telemetry and exit
153
-
154
- Emit one `muggle-local-telemetry-skill-emit` event per tick (see [Telemetry](#telemetry)). Exit the turn. Next tick fires in 5 min via `/loop`.
155
-
156
- ## Reply routing
157
-
158
- GitHub's PR comment APIs are not uniform. Route by parent type:
159
-
160
- - **Reply to a line-level review comment** (most common): `POST /repos/{owner}/{repo}/pulls/{n}/comments/{comment_id}/replies` with `{"body": "..."}`. The reply lands in the same review thread.
161
- - **Reply to a CHANGES_REQUESTED review body** (no inline comment to reply to): post a fresh top-level PR comment via `gh pr comment <number> --body "..."` referencing the review. There is no "reply to review body" endpoint.
162
- - **Failing CI**: no reply. The fix commit is the response.
163
-
164
- Never post the same reply twice — the cursor in `last_seen.json` is the only re-entry guard.
165
-
166
- ## Telemetry
167
-
168
- Two telemetry shapes per tick (decision 14):
169
-
170
- **Per-item** (one event per addressed/escalated item):
171
-
172
- ```json
173
- {
174
- "skill": "muggle-do-pr-followup",
175
- "event": "item",
176
- "session_slug": "<slug>",
177
- "repo": "<repo>",
178
- "pr_number": <n>,
179
- "item_type": "directive|question|ci_failure|ambiguous",
180
- "outcome": "fixed_and_pushed|replied|escalated",
181
- "comment_id": <id-or-null>,
182
- "head_sha": "<sha-or-null>"
183
- }
184
- ```
185
-
186
- **Per-tick summary** (always one, even on idle ticks):
187
-
188
- ```json
189
- {
190
- "skill": "muggle-do-pr-followup",
191
- "event": "tick",
192
- "session_slug": "<slug>",
193
- "tick": <K>,
194
- "pr_count": <total>,
195
- "prs_terminal": <count>,
196
- "items_seen": <count>,
197
- "items_addressed": <count>,
198
- "items_escalated": <count>,
199
- "pushed": true|false,
200
- "tick_duration_ms": <ms>
201
- }
202
- ```
203
-
204
- ## Output
205
-
206
- This stage produces no console output beyond:
207
- - The turn preamble (always).
208
- - An escalation terminal message (only when an item is classified ambiguous).
209
- - The final `result.md` summary (only on the terminating tick — written to disk, not printed).
210
-
211
- Everything else lives in `followup.log` and `last_seen.json`.
212
-
213
- ## Self-check before ending the turn
214
-
215
- Before exiting, confirm:
216
-
217
- - [ ] `last_seen.json` was advanced for every item handled.
218
- - [ ] `prs.json` reflects current PR states (terminal entries marked).
219
- - [ ] `followup.log` has at minimum a heartbeat or per-item line for this tick.
220
- - [ ] Telemetry events were emitted (per-item + per-tick).
221
- - [ ] If pushed, `last_pushed_sha` is set.
222
- - [ ] If escalated, `escalated_comment_ids` contains the comment id.
223
- - [ ] If terminal, the loop is NOT continued (do not schedule another tick).
224
-
225
- If any are missing, fix before exit — a dropped cursor causes double-handling next tick.
@@ -1,38 +0,0 @@
1
- # Code Validation Agent (Stage 4/7)
2
-
3
- You are validating that each repository's git state is ready for the dev cycle pipeline.
4
-
5
- ## Turn preamble
6
-
7
- Start the turn with:
8
-
9
- ```
10
- **Stage 4/7 — Validate code** — checking branch and commit state for each repo with changes.
11
- ```
12
-
13
- ## Input
14
-
15
- You receive:
16
- - A list of repos with changes (from impact analysis), including their paths and branch names
17
-
18
- ## Your Job
19
-
20
- For each repo:
21
-
22
- 1. **Verify the branch is a feature branch** (not main/master/the default branch). This should already be validated by impact analysis, but double-check.
23
- 2. **Check for uncommitted changes:** Run `git status --porcelain` in the repo. If there are uncommitted changes, warn the user — uncommitted changes won't be included in PRs.
24
- 3. **Get the branch diff:** Run `git diff <default-branch>...HEAD --stat` for a summary of changes.
25
- 4. **Verify commits exist on the branch:** Run `git log <default-branch>..HEAD --oneline` to confirm there are commits to push.
26
-
27
- ## Output
28
-
29
- Per repo:
30
-
31
- **Repo: (name)**
32
- - Branch: (name)
33
- - Commits on branch: (count and one-line summaries)
34
- - Uncommitted changes: yes/no (with warning if yes)
35
- - Diff stat: (file change summary)
36
- - Status: READY | WARNING | ERROR
37
-
38
- **Overall:** READY to proceed / BLOCKED (with reasons)
@@ -1,37 +0,0 @@
1
- ---
2
- name: muggle-do-pr-followup
3
- description: One polling tick of /muggle-do stage 8 — addresses reviewer comments and CI failures on PRs opened by an earlier /muggle-do session. Dispatched by `/loop 5m /muggle:muggle-do-pr-followup <slug>` after stage 7; also re-runnable by hand on any session slug.
4
- disable-model-invocation: true
5
- ---
6
-
7
- # Muggle Test Do — PR follow-up
8
-
9
- > Telemetry first step: see [`_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-do-pr-followup"`.
10
-
11
- This skill is the **dispatch entry** for stage 8 of /muggle-do. It is intentionally thin — the actual per-tick logic lives in [`../do/pr-followup.md`](../do/pr-followup.md), the single source of truth.
12
-
13
- ## Input routing
14
-
15
- `$ARGUMENTS` is the **session slug** (the directory name under `.muggle-do/sessions/`).
16
-
17
- - Empty / `help` / `?` → list available session slugs from `.muggle-do/sessions/` that have a non-empty `prs.json` with at least one non-terminal entry, then exit. Do not start a poll.
18
- - Otherwise → resolve `.muggle-do/sessions/<slug>/`. If the directory or its `prs.json` is missing, log the error to `followup.log` (creating it if needed) and exit. Do **not** ask the user.
19
-
20
- ## Run one tick
21
-
22
- With a valid session slug, follow [`../do/pr-followup.md`](../do/pr-followup.md) exactly. That file owns:
23
-
24
- - The turn preamble.
25
- - The 9-step per-tick contract.
26
- - The classify rule (directive / question / CI failure / ambiguous → escalate).
27
- - Reply routing.
28
- - Telemetry shape.
29
- - The self-check before exit.
30
-
31
- This skill file adds nothing beyond the slug resolution above.
32
-
33
- ## Why a separate entry exists
34
-
35
- - `/loop 5m /muggle:muggle-do-pr-followup <slug>` (dispatched by stage 7 of /muggle-do) needs a slash-addressable target.
36
- - Manual re-attach: if `/loop` was killed and the user wants to resume, they can run `/muggle:muggle-do-pr-followup <slug>` once to drive a single tick, or re-dispatch the `/loop` themselves.
37
- - Isolated debugging: a single tick is reproducible against a real session dir.