@muggleai/works 5.31.0 → 5.32.0-staging.283
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-mcp.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/.mcp.json +1 -1
- package/dist/plugin/agents/acceptance-tester.md +1 -1
- package/dist/plugin/skills/CLAUDE.md +4 -4
- package/dist/plugin/skills/_shared/deliverable-categories/email.md +38 -0
- package/dist/plugin/skills/_shared/deliverable-categories.md +3 -2
- package/dist/plugin/skills/do/impact-analysis.md +1 -1
- package/dist/plugin/skills/do/pre-flight.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/adopt.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +2 -0
- package/dist/plugin/skills/muggle-pr-followup/blocked-tick.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/contract.md +8 -2
- package/dist/plugin/skills/muggle-pr-followup/stop.md +1 -1
- package/dist/release-manifest.json +3 -3
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-mcp.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/agents/acceptance-tester.md +1 -1
- package/plugin/skills/CLAUDE.md +4 -4
- package/plugin/skills/_shared/deliverable-categories/email.md +38 -0
- package/plugin/skills/_shared/deliverable-categories.md +3 -2
- package/plugin/skills/do/impact-analysis.md +1 -1
- package/plugin/skills/do/pre-flight.md +1 -1
- package/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
- package/plugin/skills/muggle-pr-followup/adopt.md +1 -1
- package/plugin/skills/muggle-pr-followup/arm-watcher.md +2 -0
- package/plugin/skills/muggle-pr-followup/blocked-tick.md +1 -1
- package/plugin/skills/muggle-pr-followup/contract.md +8 -2
- package/plugin/skills/muggle-pr-followup/stop.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +0 -310
- package/plugin/skills/muggle-pr-followup/evals/evals.json +0 -310
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "muggleai",
|
|
10
10
|
"source": "./plugin",
|
|
11
11
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
12
|
-
"version": "5.
|
|
12
|
+
"version": "5.32.0-staging.283"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.32.0-staging.283",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.32.0-staging.283",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.32.0-staging.283",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/.mcp.json
CHANGED
|
@@ -22,7 +22,7 @@ You operate through skills — never call raw MCP tools directly.
|
|
|
22
22
|
|
|
23
23
|
Select the skill based on what the orchestrator asks you to do. If the task doesn't clearly map to one skill, ask for clarification.
|
|
24
24
|
|
|
25
|
-
A criterion about a video, image, audio, data, or
|
|
25
|
+
A criterion about a video, image, audio, data, research, or email artifact is verified by a deliverable check rather than a browser run, per [`../skills/_shared/deliverable-categories.md`](../skills/_shared/deliverable-categories.md). Its results are reported in the **Deliverables** block and never as a Muggle run.
|
|
26
26
|
|
|
27
27
|
The run loop these skills execute (replay/regen, timeouts, publish, screenshots) lives in [`../skills/_shared/dev-loop/run.md`](../skills/_shared/dev-loop/run.md) — you reach it through `muggle-test`, never by calling MCP tools yourself.
|
|
28
28
|
|
|
@@ -46,17 +46,17 @@ When a skill comes to depend on another Claude Code-only tool, add its row there
|
|
|
46
46
|
|
|
47
47
|
## Model tiers
|
|
48
48
|
|
|
49
|
-
Each skill sets a `model:` in its `SKILL.md` frontmatter sized to its cognitive load. `model:` is a native Claude Code field — the override applies while the skill is active and reverts to the session model when it exits. Cheaper, faster models run the mechanical skills; the default (Opus) is reserved for the ones that actually reason. Cost and latency scale with the model, and these skills run often
|
|
49
|
+
Each skill sets a `model:` in its `SKILL.md` frontmatter sized to its cognitive load. `model:` is a native Claude Code field — the override applies while the skill is active and reverts to the session model when it exits. Cheaper, faster models run the mechanical skills; the default (Opus) is reserved for the ones that actually reason. Cost and latency scale with the model, and these skills run often, so the tier is a real lever, not cosmetics.
|
|
50
50
|
|
|
51
51
|
| Model | Skills | Why this tier |
|
|
52
52
|
|-------|--------|---------------|
|
|
53
|
-
| `haiku` | `muggle`, `muggle-status`, `muggle-repair`, `muggle-upgrade`, `muggle-preferences`, `muggle-feedback
|
|
54
|
-
| `sonnet` | `muggle-pr-visual-walkthrough` (executes via the `visual-walkthrough-builder` agent), `muggle-test-regenerate-missing` | Multi-step orchestration with light judgment, short of deep reasoning: assemble run data and build a PR section with fit-vs-overflow handling; scan, filter, bulk-dispatch, and classify per-item failures into buckets. More moving parts than a router, but each step is well-defined. |
|
|
53
|
+
| `haiku` | `muggle`, `muggle-status`, `muggle-repair`, `muggle-upgrade`, `muggle-preferences`, `muggle-feedback` | Routers and dumb pipes. They follow an explicit procedure with no open-ended reasoning: route intent to a downstream skill, run a fixed CLI sequence, CRUD a config file, or format a status report. |
|
|
54
|
+
| `sonnet` | `muggle-pr-visual-walkthrough` (executes via the `visual-walkthrough-builder` agent), `muggle-test-regenerate-missing`, `muggle-pr-followup` | Multi-step orchestration with light judgment, short of deep reasoning: assemble run data and build a PR section with fit-vs-overflow handling; scan, filter, bulk-dispatch, and classify per-item failures into buckets; run a watcher tick across a dozen procedure files and fire the one `/muggle-do` directive its verdict calls for. More moving parts than a router, but each step is well-defined. The watcher moved up from `haiku`: its tick eval passed 3–4 of 23 scenarios there, describing the procedure instead of running it, and a tick now runs only when its token-free watch loop exits with something to act on. |
|
|
55
55
|
| `opus` (explicit pin) | `muggle-test-prepare` (executes via the `test-prepare-runner` agent) | Pinned for **reliability**, not raw reasoning load: it's flaky on smaller models, and since other skills gate on the environment it readies, a wrong call is expensive. Pin explicitly rather than leaving `model:` unset so it stays on Opus even when the user's session runs a cheaper model. |
|
|
56
56
|
|
|
57
57
|
| default (Opus) — no `model:` set | `muggle-do`, `muggle-test`, `muggle-test-feature-local`, `muggle-browser-task`, `muggle-test-import` | Reasoning-heavy. Authoring code to a PR, mapping a code diff to affected user flows and interpreting E2E results, reasoning about an arbitrary website's flow to drive a browser, translating Playwright/Cypress/PRD artifacts into Muggle test cases. Leave `model:` unset so the skill inherits the session model. |
|
|
58
58
|
|
|
59
|
-
**A `SKILL.md` `model:` only bites when the skill starts a fresh session; invoked mid-session the session model keeps running.** A skill whose pin must hold regardless resolves its user interaction in the `SKILL.md`, then dispatches a `plugin/agents/*` agent that carries the pin — the harness applies an agent's `model:` on every dispatch. That is why `muggle-pr-visual-walkthrough
|
|
59
|
+
**A `SKILL.md` `model:` only bites when the skill starts a fresh session; invoked mid-session the session model keeps running.** A skill whose pin must hold regardless resolves its user interaction in the `SKILL.md`, then dispatches a `plugin/agents/*` agent that carries the pin — the harness applies an agent's `model:` on every dispatch. That is why `muggle-pr-visual-walkthrough` and `muggle-test-prepare` execute through agents. Agents have no user channel; an unresolved decision comes back as `needs-input:` for the skill to resolve.
|
|
60
60
|
|
|
61
61
|
**Choosing a tier for a new skill.** Ask what the skill actually does. Pure routing / fixed procedure / CRUD / reporting → `haiku`. Several well-defined steps with some judgment or classification → `sonnet`. Open-ended reasoning, code authoring, or interpreting ambiguous real-world state → leave `model:` unset (Opus). When unsure between two tiers, pick the cheaper one and watch for misbehavior — the likeliest to need a bump is anything doing AI-based classification. If a skill proves flaky on its tier and reliability matters more than cost (other skills depend on it, or a wrong call is expensive), pin it up explicitly — `model: opus` — rather than leaving it unset, so the floor holds regardless of the user's session model.
|
|
62
62
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Email deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for an `[email]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A rendered email message — transactional, notification, or campaign: the template rendered to HTML (and its plain-text part) by the repo's own render or preview command, with the repo's fixture or preview data. A committed static HTML email is checked as committed.
|
|
10
|
+
|
|
11
|
+
An email a running flow sends and a user receives is a `[web]` criterion, read from the managed profile's inbox by a test case; this category covers the message itself.
|
|
12
|
+
|
|
13
|
+
## Probes
|
|
14
|
+
|
|
15
|
+
Run as a script written outside the worktree, so the check leaves the change untouched.
|
|
16
|
+
|
|
17
|
+
- The template renders without error.
|
|
18
|
+
- Subject, preheader, and sender, against the stated values.
|
|
19
|
+
- No unresolved merge fields: no `{{`, `${`, `undefined`, `null`, or `NaN` in the rendered output.
|
|
20
|
+
- A plain-text part, when the mailer sends multipart.
|
|
21
|
+
- Every link absolute `https`, none pointing at `localhost`; an unsubscribe link when the message is marketing.
|
|
22
|
+
- Every image absolute, with `alt` text.
|
|
23
|
+
- HTML size under 102 KB, past which Gmail clips the message.
|
|
24
|
+
|
|
25
|
+
## Review
|
|
26
|
+
|
|
27
|
+
Screenshot the rendered HTML at 600 px and 375 px wide and read both against the criterion:
|
|
28
|
+
|
|
29
|
+
- Copy present and spelled as specified.
|
|
30
|
+
- Layout intact: nothing overflowing, overlapping, or missing.
|
|
31
|
+
- Buttons and links carry the stated labels.
|
|
32
|
+
|
|
33
|
+
A changed template is also rendered beside its base-branch version; every difference the criterion does not ask for is a finding.
|
|
34
|
+
|
|
35
|
+
## Inconclusive when
|
|
36
|
+
|
|
37
|
+
- The template cannot be rendered for an environment reason.
|
|
38
|
+
- No headless browser is available for the screenshots.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deliverable categories
|
|
2
2
|
|
|
3
|
-
A **deliverable** is the artifact an acceptance criterion is about. Its **deliverable category** is one of `web`, `video`, `image`, `audio`, `data`, `research` — the one list of categories, which other files reference rather than restate. No category is special-cased: each criterion is verified the way its category's flow file declares under **Verified by** — **test cases** (Muggle browser test cases, run against the app) or a **deliverable check** (obtain the artifact, probe it, review it, return a verdict). Scoping and the acceptance stage read that declaration from the criterion's category and never branch on a category's name.
|
|
3
|
+
A **deliverable** is the artifact an acceptance criterion is about. Its **deliverable category** is one of `web`, `video`, `image`, `audio`, `data`, `research`, `email` — the one list of categories, which other files reference rather than restate. No category is special-cased: each criterion is verified the way its category's flow file declares under **Verified by** — **test cases** (Muggle browser test cases, run against the app) or a **deliverable check** (obtain the artifact, probe it, review it, return a verdict). Scoping and the acceptance stage read that declaration from the criterion's category and never branch on a category's name.
|
|
4
4
|
|
|
5
5
|
## Tagging a criterion
|
|
6
6
|
|
|
@@ -31,6 +31,7 @@ Every category's flow lives in its own file, so it can be customized without tou
|
|
|
31
31
|
| audio | deliverable check | [`deliverable-categories/audio.md`](deliverable-categories/audio.md) |
|
|
32
32
|
| data | deliverable check | [`deliverable-categories/data.md`](deliverable-categories/data.md) |
|
|
33
33
|
| research | deliverable check | [`deliverable-categories/research.md`](deliverable-categories/research.md) |
|
|
34
|
+
| email | deliverable check | [`deliverable-categories/email.md`](deliverable-categories/email.md) |
|
|
34
35
|
|
|
35
36
|
Scoping and the acceptance stage open the flow file of every category the cycle's criteria carry before planning or running anything.
|
|
36
37
|
|
|
@@ -45,7 +46,7 @@ Scoping and the acceptance stage open the flow file of every category the cycle'
|
|
|
45
46
|
```
|
|
46
47
|
- (deliverable name):
|
|
47
48
|
- key: <DC key from the scope>
|
|
48
|
-
- category: <video | image | audio | data | research>
|
|
49
|
+
- category: <video | image | audio | data | research | email>
|
|
49
50
|
- artifact: <repo-relative path, or the build command and its scratch output>
|
|
50
51
|
- criteria: <the criteria it verifies>
|
|
51
52
|
- checks: [{ check, kind: probe | review, expected, observed, passed }, ...]
|
|
@@ -154,7 +154,7 @@ e2eScope:
|
|
|
154
154
|
instruction: <one atomic behavior to verify>
|
|
155
155
|
deliverableChecks:
|
|
156
156
|
- key: DC1
|
|
157
|
-
category: <video | image | audio | data | research>
|
|
157
|
+
category: <video | image | audio | data | research | email>
|
|
158
158
|
artifact: <repo-relative path | build: <command> → <output>>
|
|
159
159
|
probes:
|
|
160
160
|
- probe: <property>
|
|
@@ -60,7 +60,7 @@ Present **one `AskUserQuestion`** (or the platform's structured-selection equiva
|
|
|
60
60
|
4. **Validation strategy** — the single most important question. Options:
|
|
61
61
|
- **Local E2E** (Muggle Test Electron against a running localhost) — default if a dev server was detected.
|
|
62
62
|
- **Staging replay** — for changes already deployed to a preview URL.
|
|
63
|
-
- **Unit tests only** — skip browser E2E, acceptable for pure refactors or backend-only changes. Deliverable checks on video, image, audio, data, or
|
|
63
|
+
- **Unit tests only** — skip browser E2E, acceptable for pure refactors or backend-only changes. Deliverable checks on video, image, audio, data, research, or email artifacts still run.
|
|
64
64
|
- **Skip validation** — explicit opt-out; the PR title gets `[UNVERIFIED]`.
|
|
65
65
|
5. **Local URL** — only if validation is Local E2E. Default: the detected port. "Confirm `<detected>`" / "Type a different URL".
|
|
66
66
|
6. **Backend reachable?** — only if validation is Local E2E and a backend URL is declared. If the health probe failed, ask "Start the backend now and I'll re-probe" / "Proceed anyway" / "Skip to unit tests only".
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-pr-followup
|
|
3
|
-
model:
|
|
3
|
+
model: sonnet
|
|
4
4
|
description: "Use when the user wants a pull request's incoming review feedback handled for them — it watches a PR's review threads and, each time a reviewer submits new comments, dispatches the work to address them. Covers one PR or several: with no PR named it auto-tracks every PR opened in this session, one watcher each. Engage on PR-review-follow-up intent: \"watch my PR and address review comments as they come in\", \"keep polling the PR I just pushed and deal with reviews\", \"track all the PRs I opened this session and follow up on their reviews\", \"follow up on review threads for the branch I just pushed\", \"I'm stepping away — handle my PR's reviews while I'm gone\". Invoke it before looking the PRs up — finding them is its own first step. This is PR-review-specific automation — prefer it over the generic loop skill. It only watches and dispatches; the actual edits and replies are muggle-do. Not for posting test results to a PR (use muggle-pr-visual-walkthrough)."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -50,7 +50,7 @@ Append `adopted (from <previous-owner | unowned>)` to the slot's `followup.log`.
|
|
|
50
50
|
|
|
51
51
|
### Step 5 — Report
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Open the report with the Step 3 warning, repeated verbatim: stated at Step 3 it scrolls away under the claim and the arm, and the report is what the user reads. Then one line: `adopted <slug> → <owner>/<repo>#<n> — armed`.
|
|
54
54
|
|
|
55
55
|
## Invariants
|
|
56
56
|
|
|
@@ -8,6 +8,8 @@ How an orchestrating session starts the watch on one PR. Every arming point runs
|
|
|
8
8
|
bash "<abs>/scripts/pr-watch-arm.sh" --slot "<slot>" --repo "<owner>/<repo>" --pr <n> --base <base-branch>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
That command runs as a background shell command (Step 3), never a foreground Bash call: the script execs the loop, so a foreground call holds the turn until the loop exits and leaves no watch behind it.
|
|
12
|
+
|
|
11
13
|
The same argument that forbids authoring a per-slot `watch.sh` applies to arming itself. Performed from prose, the middle step — writing `watch-watermark.env` — is the one that gets dropped, and dropping it is silent: the loop gates every wake condition on that file, so it polls nothing, reports nothing and never reaches its terminal check, while still holding its PID lease and touching its heartbeat. Eleven such watchers once ran for hours across one session, missed nine merges and a red CI run between them, and every health signal said they were fine. Running one command cannot half-happen.
|
|
12
14
|
|
|
13
15
|
The `DRAIN` lines it prints, in the watch's output, are the outstanding work as of the arm. Review ids and threads the drain read are floored, so the loop will *not* tell you about those again. A stale branch or a red head is floored only when `last_seen.json` records a rebase or fix-ci cycle already attempted for that key or head ([`state-schemas.md`](state-schemas.md#watch-watermarkenv)); otherwise the drain line says `not floored: dispatch …` and the loop's first poll reports it as an event. A PR that spent its catch-up rebase budget is the exception: its drain line says the rebase is `held for the owner`, and nothing is dispatched — the tick would refuse it — until the owner raises [`maxCatchUpRebases`](../muggle-preferences/preference-gates/maxCatchUpRebases.md). Re-arming is never a way to set work aside — it neither handles nor silences anything. Steps 1-3 below are what the script does, and why.
|
|
@@ -22,7 +22,7 @@ When an idle tick is a durable human-block and `last_seen.blocked` is not alread
|
|
|
22
22
|
|
|
23
23
|
1. Increment `last_seen.idle_tick_count`.
|
|
24
24
|
2. Write `last_seen.blocked = { reason, since: <now>, fingerprint }` (reuse the `latest_review_id` / `ci_digest` already fetched this tick). Mirror the fingerprint's `ci_digest` into the watch-watermark's `BLOCKED_CIDIGEST` ([`state-schemas.md`](state-schemas.md#watch-watermarkenv)) — a whole-file rewrite of `watch-watermark.env` that leaves the other floors untouched. That non-empty value is what arms the loop's blocked-resume probe, so a block resumes on any CI move ([`arm-watcher.md`](arm-watcher.md)), not just red — without it the probe stays dormant.
|
|
25
|
-
3. **Remind the owner** — emit the one-line reminder per [`output-templates/blocked-reminder.md`](output-templates/blocked-reminder.md): the pending act plus a reference back to the decision context. This is the block's **only** reminder.
|
|
25
|
+
3. **Remind the owner** — emit the one-line reminder per [`output-templates/blocked-reminder.md`](output-templates/blocked-reminder.md): the pending act plus a reference back to the decision context. This is the block's **only** reminder. Print it as the first line of the tick's final message, after [`contract.md`](contract.md) Step 7.5 has armed the loop and cancelled any cron — printed earlier, it scrolls away under the steps that follow, and the final message is what the owner reads.
|
|
26
26
|
4. Append a `blocked reason=<reason>` line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md); emit a `tick` event with `idle: true`, `blocked: true`, and the same other fields as a transient idle. Exit.
|
|
27
27
|
|
|
28
28
|
## Remind or resume (the Step 2.5 gate)
|
|
@@ -27,8 +27,14 @@ If either file is missing or the PR is not in `prs.json`, the tick is a no-op. L
|
|
|
27
27
|
|
|
28
28
|
Every `increment`/`reset` this procedure applies to `last_seen.json`, and the `prs.json` refresh in Step 1, is a **whole-file rewrite** (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md). **Never** patch session JSON with the Edit tool — an exact-string match against these files silently fails ("malformed edit") and drops the update, so the counter never advances.
|
|
29
29
|
|
|
30
|
+
## Dispatching
|
|
31
|
+
|
|
32
|
+
Every hand-off below — address-reviews, rebase, fix-ci, post-merge cleanup — fires `/muggle-do` through the Skill tool: skill `muggle-do`, with the directive after the command as its arguments (e.g. `address reviews <ids> on <pr-url> slug=<slug>`). A directive printed in the reply dispatches nothing: no cycle runs, and the PR sits with its work due.
|
|
33
|
+
|
|
30
34
|
## Procedure
|
|
31
35
|
|
|
36
|
+
Every tick event this procedure emits is one call to `muggle-local-telemetry-event-emit` with `eventType: "muggle-pr-followup:tick"` and `skillName: "muggle-pr-followup"`, its fields as metadata ([`../_shared/telemetry-events/pr-followup-tick.md`](../_shared/telemetry-events/pr-followup-tick.md)). The bare word `tick` in a step below names that event, never the `eventType` value.
|
|
37
|
+
|
|
32
38
|
### Step 0 — Stopped-slot absorb, stale-fire guard, then record this cron's id
|
|
33
39
|
|
|
34
40
|
**Stopped-slot absorb — before anything else, including the disk reads above.** If the kill file `~/.muggle-ai/muggle-do/polling.disabled` exists, or the slot dir is absent but `~/.muggle-ai/muggle-do/sessions/<slug>.stopped/` exists (the owner ran [`stop.md`](stop.md)), absorb: no fetch, no state write, no log line — output one line (`stopped: <slug> — absorbed`) and exit. This gate is what makes an **unreachable orphaned cron** harmless: a cron whose handle a compaction severed can neither be enumerated by `CronList` nor deleted by its recorded id, so it fires until its session dies — the absorb caps each fire at one line of output and nothing else. Never "fix" an orphan by deleting ids `CronList` does surface; those belong to other live watchers.
|
|
@@ -191,10 +197,10 @@ Check the slot's `watch.pid` lease ([`state-schemas.md`](state-schemas.md#watchp
|
|
|
191
197
|
- **Dead or missing** — arm per [`arm-watcher.md`](arm-watcher.md) and append a `re-armed` line to `followup.log`. The arm carries the slot's review, comment and thread floors forward, so anything that landed after this tick's read still reaches the session.
|
|
192
198
|
- **Live** — a loop already owns the cadence (this tick was not its wake). Nothing to arm.
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
Then, when a **recovery cron** targets this slot — Step 0 recorded one, or `CronList` lists a job whose prompt ends `<slug> <n>` — cancel it per [`cancel-cron.md`](cancel-cron.md), after the arm. That cron is what delivered this tick, and its job ends there, never as the poller: every cron fire is a full model turn, while the loop polls token-free at the same `1m` cadence. The recorded cron is the signal: a cron-delivered tick carries no `--wake` or other flag saying a cron fired it.
|
|
195
201
|
|
|
196
202
|
Either way exactly one poller remains — the loop — and this tick was the only model turn spent.
|
|
197
203
|
|
|
198
204
|
## Output
|
|
199
205
|
|
|
200
|
-
The watcher stays invisible to the **GitHub reviewer** — it never posts to the PR from a tick. To the **loop owner** in the session it emits: the turn preamble, the one-line blocked reminder while awaiting the owner (Steps 2.5 / 7), and (when a dispatch fires) the `/muggle-do`
|
|
206
|
+
The watcher stays invisible to the **GitHub reviewer** — it never posts to the PR from a tick. To the **loop owner** in the session it emits: the turn preamble, the one-line blocked reminder while awaiting the owner (Steps 2.5 / 7), and (when a dispatch fires) the `/muggle-do` invocation it made through the Skill tool. Nothing else.
|
|
@@ -12,7 +12,7 @@ Stopping is not finalizing: the PR stays open on the provider, and no `result.md
|
|
|
12
12
|
|
|
13
13
|
Run per slot in scope (every dir under `~/.muggle-ai/muggle-do/sessions/` with a `prs.json` and no `result.md`, or just the named one):
|
|
14
14
|
|
|
15
|
-
1. **Stop the loop.** `TaskStop` the background task whose command references this slot — `pr-watch-arm.sh --slot <slot>`, or a legacy `<slot>/watch.sh`.
|
|
15
|
+
1. **Stop the loop.** `TaskStop` the background task whose command references this slot — `pr-watch-arm.sh --slot <slot>`, or a legacy `<slot>/watch.sh`. Then stop the loop itself: the arm execs `pr-watch-loop.sh --slot <slot>`, which writes its PID to `<slot>/watch.pid` ([`state-schemas.md`](state-schemas.md#watchpid)), and that loop can outlive the task — on Windows `TaskStop` reaps only the wrapper, and a task started by an ended session is gone while its loop keeps polling. If the PID in `watch.pid` is alive (`kill -0`), kill it, along with any other process whose command line carries this slot's `--slot` path (or a legacy `<slug>/watch.sh`). A survivor keeps touching `watch-heartbeat` and blocks the Step 3 rename (open handles inside the dir).
|
|
16
16
|
2. **Cancel the cron.** Per [`cancel-cron.md`](cancel-cron.md) — recorded-id first, `CronList` match as fallback. A cron neither can reach is orphaned; it stays inert via the absorb and dies with its session.
|
|
17
17
|
3. **Mark the slot stopped.** Rename `~/.muggle-ai/muggle-do/sessions/<slug>/` → `<slug>.stopped/`. This single rename is the load-bearing act: the tick absorb keys on it, [`reconcile.md`](reconcile.md) skips `*.stopped` dirs, and the muggle watch service ignores a stopped slot's [`watch-registration.json`](state-schemas.md#watch-registrationjson), so no recovery path of any kind re-arms the slot. To keep a watch running but stop the service from restarting its loop or waking its owner, use `muggle watch unregister --slot <slot>` instead.
|
|
18
18
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"release": "5.26.0",
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-11-
|
|
3
|
+
"buildId": "run-283-1",
|
|
4
|
+
"commitSha": "401e4005fe016f429e84c0bb04bb27d1af5e69e8",
|
|
5
|
+
"buildTime": "2026-11-02T20:15:21Z",
|
|
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.
|
|
4
|
+
"version": "5.32.0-staging.283",
|
|
5
5
|
"description": "13 years of Microsoft hyperscale engineering, packaged as a harness for your AI coding agent.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"muggleConfig": {
|
|
66
66
|
"electronAppVersion": "1.22.3",
|
|
67
67
|
"downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
|
|
68
|
-
"runtimeTargetDefault": "
|
|
68
|
+
"runtimeTargetDefault": "staging",
|
|
69
69
|
"checksumsByStream": {
|
|
70
70
|
"production": {
|
|
71
71
|
"win32-x64": "be2b541f81e40e74f084548dbb193d556e5c1a66107fe5b345c2b4408dd7febe",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.32.0-staging.283",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
package/plugin/.codex-mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.32.0-staging.283",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.32.0-staging.283",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/plugin/.mcp.json
CHANGED
|
@@ -22,7 +22,7 @@ You operate through skills — never call raw MCP tools directly.
|
|
|
22
22
|
|
|
23
23
|
Select the skill based on what the orchestrator asks you to do. If the task doesn't clearly map to one skill, ask for clarification.
|
|
24
24
|
|
|
25
|
-
A criterion about a video, image, audio, data, or
|
|
25
|
+
A criterion about a video, image, audio, data, research, or email artifact is verified by a deliverable check rather than a browser run, per [`../skills/_shared/deliverable-categories.md`](../skills/_shared/deliverable-categories.md). Its results are reported in the **Deliverables** block and never as a Muggle run.
|
|
26
26
|
|
|
27
27
|
The run loop these skills execute (replay/regen, timeouts, publish, screenshots) lives in [`../skills/_shared/dev-loop/run.md`](../skills/_shared/dev-loop/run.md) — you reach it through `muggle-test`, never by calling MCP tools yourself.
|
|
28
28
|
|
package/plugin/skills/CLAUDE.md
CHANGED
|
@@ -46,17 +46,17 @@ When a skill comes to depend on another Claude Code-only tool, add its row there
|
|
|
46
46
|
|
|
47
47
|
## Model tiers
|
|
48
48
|
|
|
49
|
-
Each skill sets a `model:` in its `SKILL.md` frontmatter sized to its cognitive load. `model:` is a native Claude Code field — the override applies while the skill is active and reverts to the session model when it exits. Cheaper, faster models run the mechanical skills; the default (Opus) is reserved for the ones that actually reason. Cost and latency scale with the model, and these skills run often
|
|
49
|
+
Each skill sets a `model:` in its `SKILL.md` frontmatter sized to its cognitive load. `model:` is a native Claude Code field — the override applies while the skill is active and reverts to the session model when it exits. Cheaper, faster models run the mechanical skills; the default (Opus) is reserved for the ones that actually reason. Cost and latency scale with the model, and these skills run often, so the tier is a real lever, not cosmetics.
|
|
50
50
|
|
|
51
51
|
| Model | Skills | Why this tier |
|
|
52
52
|
|-------|--------|---------------|
|
|
53
|
-
| `haiku` | `muggle`, `muggle-status`, `muggle-repair`, `muggle-upgrade`, `muggle-preferences`, `muggle-feedback
|
|
54
|
-
| `sonnet` | `muggle-pr-visual-walkthrough` (executes via the `visual-walkthrough-builder` agent), `muggle-test-regenerate-missing` | Multi-step orchestration with light judgment, short of deep reasoning: assemble run data and build a PR section with fit-vs-overflow handling; scan, filter, bulk-dispatch, and classify per-item failures into buckets. More moving parts than a router, but each step is well-defined. |
|
|
53
|
+
| `haiku` | `muggle`, `muggle-status`, `muggle-repair`, `muggle-upgrade`, `muggle-preferences`, `muggle-feedback` | Routers and dumb pipes. They follow an explicit procedure with no open-ended reasoning: route intent to a downstream skill, run a fixed CLI sequence, CRUD a config file, or format a status report. |
|
|
54
|
+
| `sonnet` | `muggle-pr-visual-walkthrough` (executes via the `visual-walkthrough-builder` agent), `muggle-test-regenerate-missing`, `muggle-pr-followup` | Multi-step orchestration with light judgment, short of deep reasoning: assemble run data and build a PR section with fit-vs-overflow handling; scan, filter, bulk-dispatch, and classify per-item failures into buckets; run a watcher tick across a dozen procedure files and fire the one `/muggle-do` directive its verdict calls for. More moving parts than a router, but each step is well-defined. The watcher moved up from `haiku`: its tick eval passed 3–4 of 23 scenarios there, describing the procedure instead of running it, and a tick now runs only when its token-free watch loop exits with something to act on. |
|
|
55
55
|
| `opus` (explicit pin) | `muggle-test-prepare` (executes via the `test-prepare-runner` agent) | Pinned for **reliability**, not raw reasoning load: it's flaky on smaller models, and since other skills gate on the environment it readies, a wrong call is expensive. Pin explicitly rather than leaving `model:` unset so it stays on Opus even when the user's session runs a cheaper model. |
|
|
56
56
|
|
|
57
57
|
| default (Opus) — no `model:` set | `muggle-do`, `muggle-test`, `muggle-test-feature-local`, `muggle-browser-task`, `muggle-test-import` | Reasoning-heavy. Authoring code to a PR, mapping a code diff to affected user flows and interpreting E2E results, reasoning about an arbitrary website's flow to drive a browser, translating Playwright/Cypress/PRD artifacts into Muggle test cases. Leave `model:` unset so the skill inherits the session model. |
|
|
58
58
|
|
|
59
|
-
**A `SKILL.md` `model:` only bites when the skill starts a fresh session; invoked mid-session the session model keeps running.** A skill whose pin must hold regardless resolves its user interaction in the `SKILL.md`, then dispatches a `plugin/agents/*` agent that carries the pin — the harness applies an agent's `model:` on every dispatch. That is why `muggle-pr-visual-walkthrough
|
|
59
|
+
**A `SKILL.md` `model:` only bites when the skill starts a fresh session; invoked mid-session the session model keeps running.** A skill whose pin must hold regardless resolves its user interaction in the `SKILL.md`, then dispatches a `plugin/agents/*` agent that carries the pin — the harness applies an agent's `model:` on every dispatch. That is why `muggle-pr-visual-walkthrough` and `muggle-test-prepare` execute through agents. Agents have no user channel; an unresolved decision comes back as `needs-input:` for the skill to resolve.
|
|
60
60
|
|
|
61
61
|
**Choosing a tier for a new skill.** Ask what the skill actually does. Pure routing / fixed procedure / CRUD / reporting → `haiku`. Several well-defined steps with some judgment or classification → `sonnet`. Open-ended reasoning, code authoring, or interpreting ambiguous real-world state → leave `model:` unset (Opus). When unsure between two tiers, pick the cheaper one and watch for misbehavior — the likeliest to need a bump is anything doing AI-based classification. If a skill proves flaky on its tier and reliability matters more than cost (other skills depend on it, or a wrong call is expensive), pin it up explicitly — `model: opus` — rather than leaving it unset, so the floor holds regardless of the user's session model.
|
|
62
62
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Email deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for an `[email]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A rendered email message — transactional, notification, or campaign: the template rendered to HTML (and its plain-text part) by the repo's own render or preview command, with the repo's fixture or preview data. A committed static HTML email is checked as committed.
|
|
10
|
+
|
|
11
|
+
An email a running flow sends and a user receives is a `[web]` criterion, read from the managed profile's inbox by a test case; this category covers the message itself.
|
|
12
|
+
|
|
13
|
+
## Probes
|
|
14
|
+
|
|
15
|
+
Run as a script written outside the worktree, so the check leaves the change untouched.
|
|
16
|
+
|
|
17
|
+
- The template renders without error.
|
|
18
|
+
- Subject, preheader, and sender, against the stated values.
|
|
19
|
+
- No unresolved merge fields: no `{{`, `${`, `undefined`, `null`, or `NaN` in the rendered output.
|
|
20
|
+
- A plain-text part, when the mailer sends multipart.
|
|
21
|
+
- Every link absolute `https`, none pointing at `localhost`; an unsubscribe link when the message is marketing.
|
|
22
|
+
- Every image absolute, with `alt` text.
|
|
23
|
+
- HTML size under 102 KB, past which Gmail clips the message.
|
|
24
|
+
|
|
25
|
+
## Review
|
|
26
|
+
|
|
27
|
+
Screenshot the rendered HTML at 600 px and 375 px wide and read both against the criterion:
|
|
28
|
+
|
|
29
|
+
- Copy present and spelled as specified.
|
|
30
|
+
- Layout intact: nothing overflowing, overlapping, or missing.
|
|
31
|
+
- Buttons and links carry the stated labels.
|
|
32
|
+
|
|
33
|
+
A changed template is also rendered beside its base-branch version; every difference the criterion does not ask for is a finding.
|
|
34
|
+
|
|
35
|
+
## Inconclusive when
|
|
36
|
+
|
|
37
|
+
- The template cannot be rendered for an environment reason.
|
|
38
|
+
- No headless browser is available for the screenshots.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deliverable categories
|
|
2
2
|
|
|
3
|
-
A **deliverable** is the artifact an acceptance criterion is about. Its **deliverable category** is one of `web`, `video`, `image`, `audio`, `data`, `research` — the one list of categories, which other files reference rather than restate. No category is special-cased: each criterion is verified the way its category's flow file declares under **Verified by** — **test cases** (Muggle browser test cases, run against the app) or a **deliverable check** (obtain the artifact, probe it, review it, return a verdict). Scoping and the acceptance stage read that declaration from the criterion's category and never branch on a category's name.
|
|
3
|
+
A **deliverable** is the artifact an acceptance criterion is about. Its **deliverable category** is one of `web`, `video`, `image`, `audio`, `data`, `research`, `email` — the one list of categories, which other files reference rather than restate. No category is special-cased: each criterion is verified the way its category's flow file declares under **Verified by** — **test cases** (Muggle browser test cases, run against the app) or a **deliverable check** (obtain the artifact, probe it, review it, return a verdict). Scoping and the acceptance stage read that declaration from the criterion's category and never branch on a category's name.
|
|
4
4
|
|
|
5
5
|
## Tagging a criterion
|
|
6
6
|
|
|
@@ -31,6 +31,7 @@ Every category's flow lives in its own file, so it can be customized without tou
|
|
|
31
31
|
| audio | deliverable check | [`deliverable-categories/audio.md`](deliverable-categories/audio.md) |
|
|
32
32
|
| data | deliverable check | [`deliverable-categories/data.md`](deliverable-categories/data.md) |
|
|
33
33
|
| research | deliverable check | [`deliverable-categories/research.md`](deliverable-categories/research.md) |
|
|
34
|
+
| email | deliverable check | [`deliverable-categories/email.md`](deliverable-categories/email.md) |
|
|
34
35
|
|
|
35
36
|
Scoping and the acceptance stage open the flow file of every category the cycle's criteria carry before planning or running anything.
|
|
36
37
|
|
|
@@ -45,7 +46,7 @@ Scoping and the acceptance stage open the flow file of every category the cycle'
|
|
|
45
46
|
```
|
|
46
47
|
- (deliverable name):
|
|
47
48
|
- key: <DC key from the scope>
|
|
48
|
-
- category: <video | image | audio | data | research>
|
|
49
|
+
- category: <video | image | audio | data | research | email>
|
|
49
50
|
- artifact: <repo-relative path, or the build command and its scratch output>
|
|
50
51
|
- criteria: <the criteria it verifies>
|
|
51
52
|
- checks: [{ check, kind: probe | review, expected, observed, passed }, ...]
|
|
@@ -154,7 +154,7 @@ e2eScope:
|
|
|
154
154
|
instruction: <one atomic behavior to verify>
|
|
155
155
|
deliverableChecks:
|
|
156
156
|
- key: DC1
|
|
157
|
-
category: <video | image | audio | data | research>
|
|
157
|
+
category: <video | image | audio | data | research | email>
|
|
158
158
|
artifact: <repo-relative path | build: <command> → <output>>
|
|
159
159
|
probes:
|
|
160
160
|
- probe: <property>
|
|
@@ -60,7 +60,7 @@ Present **one `AskUserQuestion`** (or the platform's structured-selection equiva
|
|
|
60
60
|
4. **Validation strategy** — the single most important question. Options:
|
|
61
61
|
- **Local E2E** (Muggle Test Electron against a running localhost) — default if a dev server was detected.
|
|
62
62
|
- **Staging replay** — for changes already deployed to a preview URL.
|
|
63
|
-
- **Unit tests only** — skip browser E2E, acceptable for pure refactors or backend-only changes. Deliverable checks on video, image, audio, data, or
|
|
63
|
+
- **Unit tests only** — skip browser E2E, acceptable for pure refactors or backend-only changes. Deliverable checks on video, image, audio, data, research, or email artifacts still run.
|
|
64
64
|
- **Skip validation** — explicit opt-out; the PR title gets `[UNVERIFIED]`.
|
|
65
65
|
5. **Local URL** — only if validation is Local E2E. Default: the detected port. "Confirm `<detected>`" / "Type a different URL".
|
|
66
66
|
6. **Backend reachable?** — only if validation is Local E2E and a backend URL is declared. If the health probe failed, ask "Start the backend now and I'll re-probe" / "Proceed anyway" / "Skip to unit tests only".
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-pr-followup
|
|
3
|
-
model:
|
|
3
|
+
model: sonnet
|
|
4
4
|
description: "Use when the user wants a pull request's incoming review feedback handled for them — it watches a PR's review threads and, each time a reviewer submits new comments, dispatches the work to address them. Covers one PR or several: with no PR named it auto-tracks every PR opened in this session, one watcher each. Engage on PR-review-follow-up intent: \"watch my PR and address review comments as they come in\", \"keep polling the PR I just pushed and deal with reviews\", \"track all the PRs I opened this session and follow up on their reviews\", \"follow up on review threads for the branch I just pushed\", \"I'm stepping away — handle my PR's reviews while I'm gone\". Invoke it before looking the PRs up — finding them is its own first step. This is PR-review-specific automation — prefer it over the generic loop skill. It only watches and dispatches; the actual edits and replies are muggle-do. Not for posting test results to a PR (use muggle-pr-visual-walkthrough)."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -50,7 +50,7 @@ Append `adopted (from <previous-owner | unowned>)` to the slot's `followup.log`.
|
|
|
50
50
|
|
|
51
51
|
### Step 5 — Report
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Open the report with the Step 3 warning, repeated verbatim: stated at Step 3 it scrolls away under the claim and the arm, and the report is what the user reads. Then one line: `adopted <slug> → <owner>/<repo>#<n> — armed`.
|
|
54
54
|
|
|
55
55
|
## Invariants
|
|
56
56
|
|
|
@@ -8,6 +8,8 @@ How an orchestrating session starts the watch on one PR. Every arming point runs
|
|
|
8
8
|
bash "<abs>/scripts/pr-watch-arm.sh" --slot "<slot>" --repo "<owner>/<repo>" --pr <n> --base <base-branch>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
That command runs as a background shell command (Step 3), never a foreground Bash call: the script execs the loop, so a foreground call holds the turn until the loop exits and leaves no watch behind it.
|
|
12
|
+
|
|
11
13
|
The same argument that forbids authoring a per-slot `watch.sh` applies to arming itself. Performed from prose, the middle step — writing `watch-watermark.env` — is the one that gets dropped, and dropping it is silent: the loop gates every wake condition on that file, so it polls nothing, reports nothing and never reaches its terminal check, while still holding its PID lease and touching its heartbeat. Eleven such watchers once ran for hours across one session, missed nine merges and a red CI run between them, and every health signal said they were fine. Running one command cannot half-happen.
|
|
12
14
|
|
|
13
15
|
The `DRAIN` lines it prints, in the watch's output, are the outstanding work as of the arm. Review ids and threads the drain read are floored, so the loop will *not* tell you about those again. A stale branch or a red head is floored only when `last_seen.json` records a rebase or fix-ci cycle already attempted for that key or head ([`state-schemas.md`](state-schemas.md#watch-watermarkenv)); otherwise the drain line says `not floored: dispatch …` and the loop's first poll reports it as an event. A PR that spent its catch-up rebase budget is the exception: its drain line says the rebase is `held for the owner`, and nothing is dispatched — the tick would refuse it — until the owner raises [`maxCatchUpRebases`](../muggle-preferences/preference-gates/maxCatchUpRebases.md). Re-arming is never a way to set work aside — it neither handles nor silences anything. Steps 1-3 below are what the script does, and why.
|
|
@@ -22,7 +22,7 @@ When an idle tick is a durable human-block and `last_seen.blocked` is not alread
|
|
|
22
22
|
|
|
23
23
|
1. Increment `last_seen.idle_tick_count`.
|
|
24
24
|
2. Write `last_seen.blocked = { reason, since: <now>, fingerprint }` (reuse the `latest_review_id` / `ci_digest` already fetched this tick). Mirror the fingerprint's `ci_digest` into the watch-watermark's `BLOCKED_CIDIGEST` ([`state-schemas.md`](state-schemas.md#watch-watermarkenv)) — a whole-file rewrite of `watch-watermark.env` that leaves the other floors untouched. That non-empty value is what arms the loop's blocked-resume probe, so a block resumes on any CI move ([`arm-watcher.md`](arm-watcher.md)), not just red — without it the probe stays dormant.
|
|
25
|
-
3. **Remind the owner** — emit the one-line reminder per [`output-templates/blocked-reminder.md`](output-templates/blocked-reminder.md): the pending act plus a reference back to the decision context. This is the block's **only** reminder.
|
|
25
|
+
3. **Remind the owner** — emit the one-line reminder per [`output-templates/blocked-reminder.md`](output-templates/blocked-reminder.md): the pending act plus a reference back to the decision context. This is the block's **only** reminder. Print it as the first line of the tick's final message, after [`contract.md`](contract.md) Step 7.5 has armed the loop and cancelled any cron — printed earlier, it scrolls away under the steps that follow, and the final message is what the owner reads.
|
|
26
26
|
4. Append a `blocked reason=<reason>` line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md); emit a `tick` event with `idle: true`, `blocked: true`, and the same other fields as a transient idle. Exit.
|
|
27
27
|
|
|
28
28
|
## Remind or resume (the Step 2.5 gate)
|
|
@@ -27,8 +27,14 @@ If either file is missing or the PR is not in `prs.json`, the tick is a no-op. L
|
|
|
27
27
|
|
|
28
28
|
Every `increment`/`reset` this procedure applies to `last_seen.json`, and the `prs.json` refresh in Step 1, is a **whole-file rewrite** (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md). **Never** patch session JSON with the Edit tool — an exact-string match against these files silently fails ("malformed edit") and drops the update, so the counter never advances.
|
|
29
29
|
|
|
30
|
+
## Dispatching
|
|
31
|
+
|
|
32
|
+
Every hand-off below — address-reviews, rebase, fix-ci, post-merge cleanup — fires `/muggle-do` through the Skill tool: skill `muggle-do`, with the directive after the command as its arguments (e.g. `address reviews <ids> on <pr-url> slug=<slug>`). A directive printed in the reply dispatches nothing: no cycle runs, and the PR sits with its work due.
|
|
33
|
+
|
|
30
34
|
## Procedure
|
|
31
35
|
|
|
36
|
+
Every tick event this procedure emits is one call to `muggle-local-telemetry-event-emit` with `eventType: "muggle-pr-followup:tick"` and `skillName: "muggle-pr-followup"`, its fields as metadata ([`../_shared/telemetry-events/pr-followup-tick.md`](../_shared/telemetry-events/pr-followup-tick.md)). The bare word `tick` in a step below names that event, never the `eventType` value.
|
|
37
|
+
|
|
32
38
|
### Step 0 — Stopped-slot absorb, stale-fire guard, then record this cron's id
|
|
33
39
|
|
|
34
40
|
**Stopped-slot absorb — before anything else, including the disk reads above.** If the kill file `~/.muggle-ai/muggle-do/polling.disabled` exists, or the slot dir is absent but `~/.muggle-ai/muggle-do/sessions/<slug>.stopped/` exists (the owner ran [`stop.md`](stop.md)), absorb: no fetch, no state write, no log line — output one line (`stopped: <slug> — absorbed`) and exit. This gate is what makes an **unreachable orphaned cron** harmless: a cron whose handle a compaction severed can neither be enumerated by `CronList` nor deleted by its recorded id, so it fires until its session dies — the absorb caps each fire at one line of output and nothing else. Never "fix" an orphan by deleting ids `CronList` does surface; those belong to other live watchers.
|
|
@@ -191,10 +197,10 @@ Check the slot's `watch.pid` lease ([`state-schemas.md`](state-schemas.md#watchp
|
|
|
191
197
|
- **Dead or missing** — arm per [`arm-watcher.md`](arm-watcher.md) and append a `re-armed` line to `followup.log`. The arm carries the slot's review, comment and thread floors forward, so anything that landed after this tick's read still reaches the session.
|
|
192
198
|
- **Live** — a loop already owns the cadence (this tick was not its wake). Nothing to arm.
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
Then, when a **recovery cron** targets this slot — Step 0 recorded one, or `CronList` lists a job whose prompt ends `<slug> <n>` — cancel it per [`cancel-cron.md`](cancel-cron.md), after the arm. That cron is what delivered this tick, and its job ends there, never as the poller: every cron fire is a full model turn, while the loop polls token-free at the same `1m` cadence. The recorded cron is the signal: a cron-delivered tick carries no `--wake` or other flag saying a cron fired it.
|
|
195
201
|
|
|
196
202
|
Either way exactly one poller remains — the loop — and this tick was the only model turn spent.
|
|
197
203
|
|
|
198
204
|
## Output
|
|
199
205
|
|
|
200
|
-
The watcher stays invisible to the **GitHub reviewer** — it never posts to the PR from a tick. To the **loop owner** in the session it emits: the turn preamble, the one-line blocked reminder while awaiting the owner (Steps 2.5 / 7), and (when a dispatch fires) the `/muggle-do`
|
|
206
|
+
The watcher stays invisible to the **GitHub reviewer** — it never posts to the PR from a tick. To the **loop owner** in the session it emits: the turn preamble, the one-line blocked reminder while awaiting the owner (Steps 2.5 / 7), and (when a dispatch fires) the `/muggle-do` invocation it made through the Skill tool. Nothing else.
|
|
@@ -12,7 +12,7 @@ Stopping is not finalizing: the PR stays open on the provider, and no `result.md
|
|
|
12
12
|
|
|
13
13
|
Run per slot in scope (every dir under `~/.muggle-ai/muggle-do/sessions/` with a `prs.json` and no `result.md`, or just the named one):
|
|
14
14
|
|
|
15
|
-
1. **Stop the loop.** `TaskStop` the background task whose command references this slot — `pr-watch-arm.sh --slot <slot>`, or a legacy `<slot>/watch.sh`.
|
|
15
|
+
1. **Stop the loop.** `TaskStop` the background task whose command references this slot — `pr-watch-arm.sh --slot <slot>`, or a legacy `<slot>/watch.sh`. Then stop the loop itself: the arm execs `pr-watch-loop.sh --slot <slot>`, which writes its PID to `<slot>/watch.pid` ([`state-schemas.md`](state-schemas.md#watchpid)), and that loop can outlive the task — on Windows `TaskStop` reaps only the wrapper, and a task started by an ended session is gone while its loop keeps polling. If the PID in `watch.pid` is alive (`kill -0`), kill it, along with any other process whose command line carries this slot's `--slot` path (or a legacy `<slug>/watch.sh`). A survivor keeps touching `watch-heartbeat` and blocks the Step 3 rename (open handles inside the dir).
|
|
16
16
|
2. **Cancel the cron.** Per [`cancel-cron.md`](cancel-cron.md) — recorded-id first, `CronList` match as fallback. A cron neither can reach is orphaned; it stays inert via the absorb and dies with its session.
|
|
17
17
|
3. **Mark the slot stopped.** Rename `~/.muggle-ai/muggle-do/sessions/<slug>/` → `<slug>.stopped/`. This single rename is the load-bearing act: the tick absorb keys on it, [`reconcile.md`](reconcile.md) skips `*.stopped` dirs, and the muggle watch service ignores a stopped slot's [`watch-registration.json`](state-schemas.md#watch-registrationjson), so no recovery path of any kind re-arms the slot. To keep a watch running but stop the service from restarting its loop or waking its owner, use `muggle watch unregister --slot <slot>` instead.
|
|
18
18
|
|