@muggleai/works 5.10.0 → 5.11.1
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/dist/{chunk-OCLMXAVJ.js → chunk-MSPMXBO5.js} +418 -326
- package/dist/{chunk-7NC2DJTK.js → chunk-WUMAJOBT.js} +9 -4
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/agents/test-prepare-runner.md +3 -1
- package/dist/plugin/agents/visual-walkthrough-builder.md +12 -5
- package/dist/plugin/commands/mtest.md +1 -1
- package/dist/plugin/hooks/README.md +15 -3
- package/dist/plugin/hooks/hooks.json +79 -0
- package/dist/plugin/scripts/guardrail-classify-gate.sh +23 -0
- package/dist/plugin/scripts/guardrail-debug-path-gate.sh +41 -0
- package/dist/plugin/scripts/guardrail-pr-terminal.sh +6 -2
- package/dist/plugin/scripts/guardrail-record-stage-read.sh +21 -0
- package/dist/plugin/scripts/guardrail-record-stage-signals.sh +23 -0
- package/dist/plugin/scripts/guardrail-record-tests.sh +11 -4
- package/dist/plugin/scripts/guardrail-report-format.sh +9 -5
- package/dist/plugin/scripts/guardrail-skill-stages.sh +35 -0
- package/dist/plugin/scripts/guardrail-stage-gate.sh +43 -0
- package/dist/plugin/scripts/guardrail-walkthrough-gate.sh +41 -0
- package/dist/plugin/scripts/guardrails.mjs +428 -35
- package/dist/plugin/scripts/pr-watch-events.sh +116 -0
- package/dist/plugin/scripts/pr-watch-guards.sh +8 -2
- package/dist/plugin/scripts/pr-watch-loop.sh +254 -0
- package/dist/plugin/scripts/reconcile-stale-watchers.sh +66 -18
- package/dist/plugin/scripts/sign-body.sh +88 -0
- package/dist/plugin/skills/CLAUDE.md +16 -0
- package/dist/plugin/skills/_shared/post-merge-cleanup.md +83 -3
- package/dist/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +6 -8
- package/dist/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +10 -18
- package/dist/plugin/skills/_shared/vcs/CLAUDE.md +1 -0
- package/dist/plugin/skills/_shared/vcs/github/pr-edit.md +3 -2
- package/dist/plugin/skills/_shared/vcs/github/reply-line-comment.md +3 -2
- package/dist/plugin/skills/_shared/vcs/github/top-level-comment.md +3 -2
- package/dist/plugin/skills/_shared/vcs/gitlab/mr-create.md +4 -1
- package/dist/plugin/skills/_shared/vcs/gitlab/mr-edit.md +4 -1
- package/dist/plugin/skills/_shared/vcs/gitlab/mr-note.md +4 -1
- package/dist/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +4 -1
- package/dist/plugin/skills/_shared/vcs/post-signature.md +23 -12
- package/dist/plugin/skills/do/open-prs/forward.md +2 -2
- package/dist/plugin/skills/do/per-comment-replies.md +2 -15
- package/dist/plugin/skills/muggle-browser-task/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +6 -3
- package/dist/plugin/skills/muggle-pr-followup/adopt.md +60 -0
- package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +10 -14
- package/dist/plugin/skills/muggle-pr-followup/auto-track.md +2 -0
- package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +63 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +11 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +1 -7
- package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -4
- package/dist/plugin/skills/muggle-pr-followup/reconcile.md +32 -6
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +18 -0
- package/dist/plugin/skills/muggle-preferences/preference-gates/watcherLifetime.md +23 -0
- package/dist/plugin/skills/muggle-test/SKILL.md +22 -2
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +3 -0
- package/dist/plugin/skills/muggle-test-prepare/SKILL.md +19 -12
- package/dist/plugin/skills/muggle-test-prepare/steps/confirm-recipe.md +56 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/derive-service-graph.md +51 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +13 -2
- package/dist/plugin/skills/muggle-test-prepare/steps/identify-services.md +4 -2
- package/dist/plugin/skills/muggle-test-prepare/steps/readiness-report.md +3 -1
- package/dist/plugin/skills/muggle-test-prepare/steps/record-resolution.md +32 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/replay-or-learn.md +20 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +4 -2
- package/dist/plugin/skills/muggle-test-prepare/steps/smoke-test.md +5 -1
- package/dist/plugin/skills/muggle-test-prepare/steps/start-services.md +3 -1
- package/dist/release-manifest.json +4 -4
- package/dist/src-B5OTAZKM.js +1 -0
- package/package.json +7 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/agents/test-prepare-runner.md +3 -1
- package/plugin/agents/visual-walkthrough-builder.md +12 -5
- package/plugin/commands/mtest.md +1 -1
- package/plugin/hooks/README.md +15 -3
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/scripts/guardrail-classify-gate.sh +23 -0
- package/plugin/scripts/guardrail-debug-path-gate.sh +41 -0
- package/plugin/scripts/guardrail-pr-terminal.sh +6 -2
- package/plugin/scripts/guardrail-record-stage-read.sh +21 -0
- package/plugin/scripts/guardrail-record-stage-signals.sh +23 -0
- package/plugin/scripts/guardrail-record-tests.sh +11 -4
- package/plugin/scripts/guardrail-report-format.sh +9 -5
- package/plugin/scripts/guardrail-skill-stages.sh +35 -0
- package/plugin/scripts/guardrail-stage-gate.sh +43 -0
- package/plugin/scripts/guardrail-walkthrough-gate.sh +41 -0
- package/plugin/scripts/guardrails.mjs +428 -35
- package/plugin/scripts/pr-watch-events.sh +116 -0
- package/plugin/scripts/pr-watch-guards.sh +8 -2
- package/plugin/scripts/pr-watch-loop.sh +254 -0
- package/plugin/scripts/reconcile-stale-watchers.sh +66 -18
- package/plugin/scripts/sign-body.sh +88 -0
- package/plugin/skills/CLAUDE.md +16 -0
- package/plugin/skills/_shared/post-merge-cleanup.md +83 -3
- package/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +6 -8
- package/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +10 -18
- package/plugin/skills/_shared/vcs/CLAUDE.md +1 -0
- package/plugin/skills/_shared/vcs/github/pr-edit.md +3 -2
- package/plugin/skills/_shared/vcs/github/reply-line-comment.md +3 -2
- package/plugin/skills/_shared/vcs/github/top-level-comment.md +3 -2
- package/plugin/skills/_shared/vcs/gitlab/mr-create.md +4 -1
- package/plugin/skills/_shared/vcs/gitlab/mr-edit.md +4 -1
- package/plugin/skills/_shared/vcs/gitlab/mr-note.md +4 -1
- package/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +4 -1
- package/plugin/skills/_shared/vcs/post-signature.md +23 -12
- package/plugin/skills/do/open-prs/forward.md +2 -2
- package/plugin/skills/do/per-comment-replies.md +2 -15
- package/plugin/skills/muggle-browser-task/SKILL.md +1 -1
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
- package/plugin/skills/muggle-pr-followup/SKILL.md +6 -3
- package/plugin/skills/muggle-pr-followup/adopt.md +60 -0
- package/plugin/skills/muggle-pr-followup/arm-watcher.md +10 -14
- package/plugin/skills/muggle-pr-followup/auto-track.md +2 -0
- package/plugin/skills/muggle-pr-followup/evals/evals.json +63 -1
- package/plugin/skills/muggle-pr-followup/output-templates/help.md +11 -1
- package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +1 -7
- package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -4
- package/plugin/skills/muggle-pr-followup/reconcile.md +32 -6
- package/plugin/skills/muggle-pr-followup/state-schemas.md +18 -0
- package/plugin/skills/muggle-preferences/preference-gates/watcherLifetime.md +23 -0
- package/plugin/skills/muggle-test/SKILL.md +22 -2
- package/plugin/skills/muggle-test-feature-local/SKILL.md +3 -0
- package/plugin/skills/muggle-test-prepare/SKILL.md +19 -12
- package/plugin/skills/muggle-test-prepare/steps/confirm-recipe.md +56 -0
- package/plugin/skills/muggle-test-prepare/steps/derive-service-graph.md +51 -0
- package/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +13 -2
- package/plugin/skills/muggle-test-prepare/steps/identify-services.md +4 -2
- package/plugin/skills/muggle-test-prepare/steps/readiness-report.md +3 -1
- package/plugin/skills/muggle-test-prepare/steps/record-resolution.md +32 -0
- package/plugin/skills/muggle-test-prepare/steps/replay-or-learn.md +20 -0
- package/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +4 -2
- package/plugin/skills/muggle-test-prepare/steps/smoke-test.md +5 -1
- package/plugin/skills/muggle-test-prepare/steps/start-services.md +3 -1
- package/dist/src-AWUORQZZ.js +0 -1
|
@@ -4,12 +4,9 @@ Posted via `gh api .../comments/<comment-id>/replies` per cycle, one per line co
|
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
|
|
7
|
-
|
|
8
|
-
<!-- muggle-do:bot -->
|
|
9
|
-
🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
|
|
10
7
|
```
|
|
11
8
|
|
|
12
|
-
`<short-sha>` is the first 7 chars of the new SHA; the body must contain that substring so the resolve-reminder stage knows *which push* addressed the thread.
|
|
9
|
+
`<short-sha>` is the first 7 chars of the new SHA; the body must contain that substring so the resolve-reminder stage knows *which push* addressed the thread. Write the content only — the posting recipe signs it with `--mode loop` ([`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md)), appending the `<!-- muggle-do:bot -->` marker that identifies the comment as loop-authored.
|
|
13
10
|
|
|
14
11
|
## Top-level fallback (review-body-only)
|
|
15
12
|
|
|
@@ -17,7 +14,4 @@ When an actionable review has a non-empty body but zero line comments, GitHub ha
|
|
|
17
14
|
|
|
18
15
|
```
|
|
19
16
|
Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
|
|
20
|
-
|
|
21
|
-
<!-- muggle-do:bot -->
|
|
22
|
-
🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
|
|
23
17
|
```
|
|
@@ -7,9 +7,6 @@ These threads are addressed and still open — mark them resolved if satisfied,
|
|
|
7
7
|
- #<thread-id-1>
|
|
8
8
|
- #<thread-id-2>
|
|
9
9
|
- ...
|
|
10
|
-
|
|
11
|
-
<!-- muggle-do:bot -->
|
|
12
|
-
🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
If no such thread exists, no comment is posted (silent).
|
|
12
|
+
If no such thread exists, no comment is posted (silent). Sign with `--mode loop` ([`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md)) — the marker it appends keeps the loop from later mistaking its own reminder for a human comment.
|
|
@@ -12,11 +12,15 @@ Three ways in, all running the same procedure:
|
|
|
12
12
|
- **Auto-track** — the top of a no-arg invocation ([`auto-track.md`](auto-track.md)).
|
|
13
13
|
- **Session start** — the `reconcile-stale-watchers.sh` hook ([`../../hooks/README.md`](../../hooks/README.md)) runs this sweep, catching a watcher that died with its session (end, or 7-day `/loop` cron expiry) before its PR's merge was observed.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Two invariants hold on every trigger, however the sweep was reached: it never seeds a first watcher, and it never re-arms a slot this session does not own (see Invariants). A no-arg auto-track and a session-start nudge are the two paths that fire without the user naming a PR, so they are exactly the ones that must not widen what the session watches.
|
|
16
16
|
|
|
17
17
|
## Watching is session-scoped
|
|
18
18
|
|
|
19
|
-
A watcher lives and dies with its session: monitors and `/loop` crons are both session-bound, so a session that ends or hits its usage limit takes every watch down with it. Nothing polls out of session — there is no detached daemon. This is deliberate: a review is addressed only inside a session that carries the context to address it, never by a headless process that would reply without that context.
|
|
19
|
+
A watcher lives and dies with its session: monitors and `/loop` crons are both session-bound, so a session that ends or hits its usage limit takes every watch down with it. Nothing polls out of session — there is no detached daemon. This is deliberate: a review is addressed only inside a session that carries the context to address it, never by a headless process that would reply without that context.
|
|
20
|
+
|
|
21
|
+
**Ownership is session-scoped too, and that is what bounds recovery.** Each slot records the session that armed it in [`owner.json`](state-schemas.md#ownerjson). Step 3.6 re-arms only slots the running session owns — watchers it armed itself, whose poller died under it. A slot owned by another session, or by none, is **never** re-armed here however dead its poller looks. The same argument that forbids a headless daemon forbids a fresh session inheriting a stranger's PR: it would be picking up review work it has no context for, on a branch it never built, and every push it made would be as context-blind as the daemon the design already rejected. Adoption exists precisely so that step is a deliberate act by the owner ([`adopt.md`](adopt.md)) rather than a side effect of starting a session.
|
|
22
|
+
|
|
23
|
+
The cost is real and accepted: a PR whose owning session is gone stops being watched and stays unwatched until someone adopts it. Reconcile makes that visible instead of silent — Step 4 lists every orphan it declined to touch — and it still finalizes an orphan whose PR went terminal, which needs no context at all.
|
|
20
24
|
|
|
21
25
|
## Input
|
|
22
26
|
|
|
@@ -24,17 +28,28 @@ A watcher lives and dies with its session: monitors and `/loop` crons are both s
|
|
|
24
28
|
|
|
25
29
|
## Procedure
|
|
26
30
|
|
|
27
|
-
### Step 1 — Enumerate slots
|
|
31
|
+
### Step 1 — Enumerate and partition slots
|
|
28
32
|
|
|
29
33
|
List `~/.muggle-ai/muggle-do/sessions/*/` dirs that contain a `prs.json`. Skip any that already have a `result.md` — those are finalized — and any whose dir name ends in `.stopped` — the owner killed those per [`stop.md`](stop.md), and no recovery path may revive them (a `.stopped` dir still holds a `prs.json`, so this name check is the only thing standing between the owner's stop and a resurrected watcher). Scope to a single `<slug>` if the arg gave one.
|
|
30
34
|
|
|
35
|
+
Read `$CLAUDE_CODE_SESSION_ID` once, then split what remains on each slot's [`owner.json`](state-schemas.md#ownerjson):
|
|
36
|
+
|
|
37
|
+
- **Owned** — its `session_id` equals this session's. Eligible for every step below, re-arm included.
|
|
38
|
+
- **Foreign** — its `session_id` differs, or the file is absent (a legacy slot, or one armed before ownership was recorded). Eligible for finalize (Step 3) and the cron sweep (Step 3.5) only. Step 3.6 never re-arms a foreign slot, and no step may write one's `owner.json` — a sweep that claimed what it touched would adopt the whole disk on first run.
|
|
39
|
+
|
|
40
|
+
If `$CLAUDE_CODE_SESSION_ID` is unset, treat **every** slot as foreign. A session that cannot identify itself owns nothing, so the sweep degrades to finalize-and-report rather than guessing.
|
|
41
|
+
|
|
31
42
|
### Step 2 — Refresh live state
|
|
32
43
|
|
|
33
44
|
For each candidate, fetch the PR per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md) using `prs.json[0].url`. A `gh` failure on one slot (deleted repo, missing auth) → log it to that slot's `followup.log` and skip; never abort the whole sweep.
|
|
34
45
|
|
|
35
46
|
### Step 3 — Finalize the terminal ones
|
|
36
47
|
|
|
37
|
-
For each candidate whose live `state` is `MERGED` or `CLOSED
|
|
48
|
+
For each candidate whose live `state` is `MERGED` or `CLOSED` — **owned or foreign alike** — run [`finalize.md`](finalize.md), which unschedules the cron recorded-id-first per [`cancel-cron.md`](cancel-cron.md), killing it even when `CronList` has gone blind. `finalize.md` dispatches nothing, so a backfilled merge gets no post-merge cleanup — its branch is typically long gone, and the `autoCleanup` gate governs if the user runs cleanup later.
|
|
49
|
+
|
|
50
|
+
Finalizing is deliberately ownership-free: it writes a `result.md` for a PR that already reached its end state on the provider and stops a dead cron. It resumes no watch, pushes nothing, and reads no review — so none of the context argument that gates re-arming applies, and leaving foreign terminal slots un-finalized would grow the orphan list forever with PRs that merged months ago.
|
|
51
|
+
|
|
52
|
+
Slots still `open` are carried to Step 3.6, which re-arms the **owned** ones whose watcher went silent.
|
|
38
53
|
|
|
39
54
|
### Step 3.5 — Sweep orphaned crons
|
|
40
55
|
|
|
@@ -48,11 +63,13 @@ Call `CronList`. For every job whose command ends with `/muggle:muggle-pr-follow
|
|
|
48
63
|
|
|
49
64
|
This reaches only crons `CronList` still enumerates. A cron that both survived a compaction (invisible to `CronList`) **and** lost its slot (recorded id gone with it) is beyond either mechanism — see the residual note below.
|
|
50
65
|
|
|
51
|
-
### Step 3.6 — Re-arm a silently-stopped
|
|
66
|
+
### Step 3.6 — Re-arm a silently-stopped watcher this session owns
|
|
52
67
|
|
|
53
68
|
The recovery net for a **dropped respawn**: a `/muggle-do` cycle cancels the watcher's cron when it dispatches ([`contract.md`](contract.md) Steps 4 / 5 / 5b) and is responsible for respawning it when the cycle ends, but a cycle that crashes or errors out before it respawns can leave an open slot with no poller and no next tick — the watch stops silently. This step re-arms it.
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
Consider only the **owned** candidates still `open` after Step 3. A foreign slot is skipped here outright, before any beacon is read — its staleness is not evidence this session should take it, and counting it here is what let a fresh session inherit every watcher on the machine. Collect the foreign ones for Step 4 instead.
|
|
71
|
+
|
|
72
|
+
For each owned candidate, first check the slot's `watch.pid` ([`state-schemas.md`](state-schemas.md#watchpid)): if it names a live process (`kill -0 "$pid"`), a monitor loop already owns the slot — **leave it, do not re-arm**. Arming a recovery cron on top of a live monitor is exactly the duplicate poller this sweep must avoid, and the PID lease is a direct signal a stale beacon is not. Only when no live watcher holds the lease, fall back to the liveness beacons: the `watch-heartbeat` file's mtime (a live monitor touches it every iteration, even when quiet — [`arm-watcher.md`](arm-watcher.md)) and the newest **tick line** in `followup.log` — a line whose timestamp is followed by `tick` or `stale-tick` (a live `1m` recovery cron logs a tick every fire; fall back to `cron.json.recorded_at` if both are absent). Non-tick lines are **not** beacons: arming announcements (`armed …` / `re-armed …`), cycle notes, and error lines record activity by a session that may already be dead — logging is not polling, and counting them masks a dead watcher for the whole window. If the **freshest beacon is older than 15 minutes**, the poller is gone → re-arm:
|
|
56
73
|
|
|
57
74
|
- Re-arm per [`arm-watcher.md`](arm-watcher.md) — drain tick, watermark seed, persistent monitor. Append a `re-armed (silent watcher)` line to the slot's `followup.log`. **Never re-arm with a recurring cron**: every cron fire is a full model turn, and the `1m` cadence already lives token-free in the monitor loop. A cron's only legitimate job is delivering a single recovery tick, and [`contract.md`](contract.md) Step 7.5 converts even that back to a monitor. Skip re-arming entirely while the global kill file `~/.muggle-ai/muggle-do/polling.disabled` exists ([`stop.md`](stop.md)).
|
|
58
75
|
|
|
@@ -62,9 +79,18 @@ A fresh beacon (within the window) means the poller is alive — a quiet monitor
|
|
|
62
79
|
|
|
63
80
|
One line: slots scanned, finalized (with final state each), left open, re-armed (silently-stopped watchers recovered), and orphan crons swept. Silent only when zero slots exist and nothing was swept.
|
|
64
81
|
|
|
82
|
+
When any **foreign** open slot remains, add one line naming them and how to take one back:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
orphaned (other sessions): <slug> → <owner>/<repo>#<n>, … — adopt with /muggle:muggle-pr-followup adopt <slug>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
List them once, as fact. Do **not** offer to adopt, pick a likely candidate, or adopt one because it looks important or recently active — the whole point of the gate is that taking a stranger's PR is the user's call, and a sweep that nudges toward adoption every session start relitigates that call until someone says yes.
|
|
89
|
+
|
|
65
90
|
## Invariants
|
|
66
91
|
|
|
67
92
|
- **Idempotent.** A slot with `result.md` is never re-finalized; a still-ticking open slot is never re-armed; once everything terminal is swept and every open watcher is live, re-running is a no-op.
|
|
68
93
|
- **Recover, don't seed.** Reconcile finalizes terminal slots, sweeps orphan crons, and re-arms an open slot whose watcher went silent (a dropped respawn — Step 3.6). It never arms a PR that was never watched — seeding a first watcher is [`auto-track.md`](auto-track.md)'s / bootstrap's job.
|
|
94
|
+
- **Never adopts.** No path through this sweep re-arms a slot another session owns, or writes `owner.json` for a slot it did not already own. Running reconcile in a brand-new session re-arms exactly nothing, and running it a hundred times never widens what that session watches. Ownership changes only through [`adopt.md`](adopt.md), on the user's explicit instruction.
|
|
69
95
|
- **Per-slot isolation.** One slot's `gh` failure never blocks finalizing the others.
|
|
70
96
|
- **Residual orphan.** A cron that survived a compaction (blind to `CronList`) whose slot was also deleted has no on-disk id left to `CronDelete` and no `CronList` entry to match — only a session restart clears it. Recording the id durably while the slot lives ([`record-cron-id.md`](record-cron-id.md)) shrinks this window to slots removed before their first tick.
|
|
@@ -30,6 +30,24 @@ A list of one entry. (Historical: the file is an array for forward-compat with t
|
|
|
30
30
|
- `state` is the **observed** state from the last `gh pr view`. The watcher refreshes it each tick.
|
|
31
31
|
- Terminal states (`merged`, `closed`) are sticky — once set, the watcher writes `result.md` and exits without rescheduling.
|
|
32
32
|
|
|
33
|
+
## `owner.json`
|
|
34
|
+
|
|
35
|
+
The Claude Code session that owns this slot's watch. Written when a session arms the slot ([`arm-watcher.md`](arm-watcher.md)) or deliberately takes it over ([`adopt.md`](adopt.md)); read by [`reconcile.md`](reconcile.md) to decide whether the running session may re-arm a dead watcher.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"session_id": "<claude-code-session-id>",
|
|
40
|
+
"claimed_at": "<ISO-8601>"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `session_id`: `$CLAUDE_CODE_SESSION_ID` as the arming session observes it. It survives compaction and resume, so a session that continues its own work keeps its watchers; a genuinely new session gets a new id and therefore owns nothing it did not arm.
|
|
45
|
+
- `claimed_at`: when the current owner took the slot. Rewritten on every claim, so an adopted slot records the adopting session rather than the original one.
|
|
46
|
+
|
|
47
|
+
**Absent ⇒ unowned.** Slots written before this file existed carry no owner, and every recovery path reads that as *not mine*: an unowned slot is still finalized when its PR goes terminal, but never re-armed. There is no backfill — inferring an owner would guess at exactly the thing this file exists to record.
|
|
48
|
+
|
|
49
|
+
Distinct from [`watch.pid`](#watchpid), and the two are not interchangeable. `watch.pid` leases the slot to one OS **process** so two loops never poll it at once; `owner.json` leases it to one **session** so a foreign session never revives a watch it has no context for. A dead PID marks a watcher to recover; a foreign `session_id` marks one to leave alone. Reconcile consults both, in that order.
|
|
50
|
+
|
|
33
51
|
## `cron.json`
|
|
34
52
|
|
|
35
53
|
A durable, on-disk handle to this slot's watcher cron. Its whole reason to exist: `CronList` goes **blind to crons that outlive a session continue / compaction** (the watcher's `/loop` cron survives, but the tool can no longer enumerate it), so a teardown that can only find crons through `CronList` can never delete the orphan — it re-fires until the 7-day `/loop` expiry. A cron id recorded to disk **while the cron was still visible** stays a valid `CronDelete` target afterward. See [`record-cron-id.md`](record-cron-id.md) (who writes it) and [`cancel-cron.md`](cancel-cron.md) (who deletes by it).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `watcherLifetime`
|
|
2
|
+
|
|
3
|
+
How long a `muggle-pr-followup` watch loop polls before retiring itself.
|
|
4
|
+
|
|
5
|
+
**Not gated.** This is a configuration value — no Picker 1, no silent footer. Nothing prompts; the saved value is read when a watch is armed. The `muggle-preferences` skill exposes it through Configure and Set so users can change it.
|
|
6
|
+
|
|
7
|
+
| Value | Lifetime |
|
|
8
|
+
|:------|:---------|
|
|
9
|
+
| `1d` | 86400s |
|
|
10
|
+
| `7d` | 604800s — default |
|
|
11
|
+
| `never` | Unbounded |
|
|
12
|
+
|
|
13
|
+
## Why a bound exists
|
|
14
|
+
|
|
15
|
+
A watch loop is a detached process. On Windows it survives the session that launched it, so an abandoned loop keeps polling the provider indefinitely. This cap is the only **time-based** reaper for such a loop — `watcher_superseded` retires one only when a *newer* arm claims the same slot, which never happens if nothing re-arms.
|
|
16
|
+
|
|
17
|
+
**`never` removes that reaper.** It is a legitimate choice, since it ends the re-arm cycle entirely, but an orphaned loop then polls until the machine restarts or someone kills it.
|
|
18
|
+
|
|
19
|
+
## Applying it
|
|
20
|
+
|
|
21
|
+
The loop is plain `sh` and cannot read preferences. Resolve this value at arm time, convert it to seconds, and export `MUGGLE_PR_WATCH_MAX_LIFETIME` into the loop's environment. `never` exports `0`, which the guard library reads as unbounded.
|
|
22
|
+
|
|
23
|
+
An already-set `MUGGLE_PR_WATCH_MAX_LIFETIME` wins and is never overwritten, so an operator can pin any value without changing the preference.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-test
|
|
3
|
-
description: "Change-driven E2E acceptance testing
|
|
3
|
+
description: "Change-driven E2E acceptance testing: detects the user's recent changes (local diff or PR), maps them to affected user flows, and runs real-browser tests on localhost or a preview/staging URL. Invoke it before inspecting anything — what changed and what it affects is its own first step; reading the diff by hand duplicates it. Use when the user wants to test or regression-test their own work — \"make sure I didn't break anything\", \"did my recent commits break any user flows?\", \"test before I push\" — the pre-merge acceptance gate. The signal: validation tied to a commit, push, PR, or merge; unstated scope (a chunk of work, a refactor, a sprint, a deploy preview) is this skill's input, not a reason to investigate first. Beyond a Playwright/Cypress runner: plain-English tests, self-healing scripts, login/pay-wall flows, parallel cloud replay, test-plan-graph prerequisites, grouped dashboard results, and a PR visual walkthrough. One named flow: muggle-test-feature-local; importing spec files: muggle-test-import."
|
|
4
|
+
mandatoryStages:
|
|
5
|
+
- ../_shared/failure-mode-handling.md
|
|
6
|
+
- ../_shared/debug-failed-run.md
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
# Muggle Test — Change-Driven E2E Acceptance Router
|
|
@@ -9,6 +12,20 @@ description: "Change-driven E2E acceptance testing with Muggle AI: detect the us
|
|
|
9
12
|
|
|
10
13
|
A router skill that detects code changes, resolves impacted test cases, executes them locally or remotely, reads the cloud results from the Muggle AI dashboard (local runs are published by the studio during execution; remote runs publish cloud-side), and posts E2E acceptance summaries to the PR. The user can invoke this at any moment, in any state.
|
|
11
14
|
|
|
15
|
+
## Beyond a Playwright/Cypress runner
|
|
16
|
+
|
|
17
|
+
Reach for these when the user is weighing Muggle Test against hand-written specs.
|
|
18
|
+
|
|
19
|
+
| Capability | Mechanism |
|
|
20
|
+
| :--------- | :-------- |
|
|
21
|
+
| Flows behind login and pay walls | Per-project encrypted credentials and managed login profiles, injected at replay as `secretInput` steps — `muggle-remote-secret-*` |
|
|
22
|
+
| Self-healing scripts | UI-drift or stale-script signals route the run to regen instead of a doomed replay, then re-replay — [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) sections A and B |
|
|
23
|
+
| Parallel replay at scale | `muggle-remote-workflow-start-test-script-replay-bulk` replays a whole suite cloud-side in one dispatch; its `intervalSec` makes the batch recurring |
|
|
24
|
+
| Test-graph resolution | `muggle-remote-test-case-ancestors-get` walks the backend's test-plan graph root-first, so a dependent flow runs only once its prerequisites have ready scripts — [`_shared/test-case-chain-readiness.md`](../_shared/test-case-chain-readiness.md) |
|
|
25
|
+
| Grouped results and reports | Dashboard groups runs by use case and project; `muggle-remote-report-final-generate` exports the run report |
|
|
26
|
+
| PR visual walkthrough | Per-step screenshots and per-test-case dashboard links posted to the PR — [`../muggle-pr-visual-walkthrough/SKILL.md`](../muggle-pr-visual-walkthrough/SKILL.md) |
|
|
27
|
+
| Unattended runs | `muggle-remote-auth-api-key-create` issues long-lived keys for headless auth; `muggle-remote-recommend-cicd-setup` emits GitHub Actions / GitLab CI / Azure DevOps templates |
|
|
28
|
+
|
|
12
29
|
## UX Guidelines — Minimize Typing
|
|
13
30
|
|
|
14
31
|
**Every selection-based question MUST use the `AskUserQuestion` tool** (or the platform's equivalent structured selection tool). Never ask the user to "reply with a number" in a plain text message — always present clickable options.
|
|
@@ -253,6 +270,8 @@ For every run with `status: "failed"` (or any non-passing terminal state) return
|
|
|
253
270
|
|
|
254
271
|
Pass it per failed run: the `runId` (local) or workflow runtime id (remote), the `mode` that failed, `testCaseId`, `projectId`, and the execution handle (local: [`execute-local.md`](execute-local.md); remote: [`execute-remote.md`](execute-remote.md)) so a rerun re-enters the same path. Process failures one at a time so the user isn't drowning in pickers.
|
|
255
272
|
|
|
273
|
+
**This step is not an exit.** Whatever the user picks — feedback, rerun, or skip — control returns to Steps 8 and 9. A failed run is the *highest-value* walkthrough: a reviewer needs the screenshots of what broke far more than confirmation that a passing flow passed. Ending the turn inside this step with results unposted is what the walkthrough Stop gate blocks.
|
|
274
|
+
|
|
256
275
|
## Step 8: Open Results in Browser
|
|
257
276
|
|
|
258
277
|
After execution, open the Muggle AI dashboard so the user can inspect results and screenshots. The studio published every local run during execution, so each run result already carries its `viewUrl` (read it from `muggle-local-run-result-get`). Key off the uniform runs list:
|
|
@@ -271,7 +290,7 @@ Tell the user:
|
|
|
271
290
|
|
|
272
291
|
## Step 9: Offer to Post Visual Walkthrough to PR
|
|
273
292
|
|
|
274
|
-
After reporting results:
|
|
293
|
+
Posts **every** run from Step 7 — failed and inconclusive as readily as passed. After reporting results:
|
|
275
294
|
|
|
276
295
|
1. Fire [`postPRVisualWalkthrough`](../muggle-preferences/preference-gates/postPRVisualWalkthrough.md). On skip → Step 9.5.
|
|
277
296
|
2. `gh pr view --json number,title,url 2>/dev/null` — find the PR.
|
|
@@ -312,5 +331,6 @@ Each rule below is covered in-step above; these are the ones this skill most oft
|
|
|
312
331
|
- **Every selection uses `AskUserQuestion`** — never ask the user to type a number; the user picks the project (never auto-select).
|
|
313
332
|
- **Parallelize independent cloud jobs**; the only sequential loop is local Electron execution (one browser).
|
|
314
333
|
- **Read cloud refs off the run result** (the studio published during execution) and delegate PR posting to `muggle-pr-visual-walkthrough` — never inline the walkthrough or call `gh pr comment` here.
|
|
334
|
+
- **Results reach the PR, pass or fail** — Step 7C's debug path never terminates the run; Step 9 posts every run. A Stop gate holds the turn open while an acceptance run has no walkthrough on its PR, so a result that genuinely shouldn't be posted needs `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` rather than silence.
|
|
315
335
|
|
|
316
336
|
Phase→tool map and multi-agent (acceptance-tester) dispatch: [`reference.md`](reference.md).
|
|
@@ -184,6 +184,8 @@ Pass it: the `runId`, the `mode` that failed (replay if the user picked an exist
|
|
|
184
184
|
|
|
185
185
|
Skip only when the run passed cleanly — the debug path is by definition about failures.
|
|
186
186
|
|
|
187
|
+
**This step is not an exit.** Whatever the user picks — feedback, rerun, or skip — control returns to the reporting and walkthrough steps. A failed run is the *highest-value* walkthrough: a reviewer needs the screenshots of what broke far more than confirmation that a passing flow passed. Ending the turn inside this step with results unposted is what the walkthrough Stop gate blocks.
|
|
188
|
+
|
|
187
189
|
### 9b. Remind the user to guide the agent (every Electron invocation)
|
|
188
190
|
|
|
189
191
|
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.
|
|
@@ -222,6 +224,7 @@ The `/mprfollowup` shortcut starts the same watcher manually at any time.
|
|
|
222
224
|
## Non-negotiables
|
|
223
225
|
|
|
224
226
|
- No silent auth skip.
|
|
227
|
+
- **Results reach the PR, pass or fail** — the debug path never terminates the run, and the walkthrough covers every run. A Stop gate holds the turn open while an acceptance run has no walkthrough on its PR, so a result that genuinely shouldn't be posted needs `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` rather than silence.
|
|
225
228
|
- **Never prompt for Electron launch approval** before execution — invoking this skill is the approval. Just run.
|
|
226
229
|
- **Never diagnose a failed run from `execute`'s response stdout tail.** Always call `muggle-local-run-result-get` first; classify only from its structured fields and (when present) the artifacts it names. The execute tail is an excerpt and routinely truncates the failure cause.
|
|
227
230
|
- Satisfy the prerequisite chain (Step 4a) before generating or replaying the target. Read it from `muggle-remote-test-case-ancestors-get` — never infer parents from `precondition` text. Generate any not-ready ancestor test-generation-only, root-first.
|
|
@@ -18,8 +18,8 @@ The skill runs in two phases because a dispatched agent has no channel back to t
|
|
|
18
18
|
|
|
19
19
|
This skill touches the user's local machine — processes, ports, directories outside the current repo. Every action is explicit and confirmed.
|
|
20
20
|
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
21
|
+
- **Reading is granted, never assumed.** [derive-service-graph](./steps/derive-service-graph.md) asks first: scan a folder the user names, to a depth they set, or skip the scan and take their list of paths and services instead. The granted scope is recorded with the recipe so later runs reuse the permission rather than re-asking; widening it needs a fresh ask.
|
|
22
|
+
- **Outside the granted scope, names only.** You may list directory names to offer them as candidates, but never read inside one the user hasn't named. Once they name it, its top-level indicator files (`package.json`, `Makefile`, `Cargo.toml`, `go.mod`, `pyproject.toml`, `docker-compose.yml`) are readable to determine the start command.
|
|
23
23
|
- **Never traverse upward more than one level** from the current working directory to list folders.
|
|
24
24
|
|
|
25
25
|
## PID Tracking
|
|
@@ -73,22 +73,29 @@ Gates run per [`preference-gates/README.md`](../muggle-preferences/preference-ga
|
|
|
73
73
|
|
|
74
74
|
## Workflow
|
|
75
75
|
|
|
76
|
-
**
|
|
76
|
+
**The skill learns once, then replays.** [replay-or-learn](./steps/replay-or-learn.md) decides which, and owns what each mode may do.
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
78
|
+
A **replay** executes the saved recipe and asks nothing. A **learning run** works through the Decide stages below, then the execute phase, then a single gate — [confirm-recipe](./steps/confirm-recipe.md) — which is the only thing that persists anything.
|
|
79
|
+
|
|
80
|
+
Decide stages, in order; read each detail file when you reach it:
|
|
81
|
+
|
|
82
|
+
| Stage | Summary |
|
|
83
|
+
|:------|:--------|
|
|
84
|
+
| [replay-or-learn](./steps/replay-or-learn.md) | Replay the saved recipe, or learn a new one |
|
|
85
|
+
| [derive-service-graph](./steps/derive-service-graph.md) | Derive services from workspace manifests |
|
|
86
|
+
| [rebase-check](./steps/rebase-check.md) | Rebase onto default branch |
|
|
87
|
+
| [scope](./steps/scope.md) | Frontend / backend / full stack |
|
|
88
|
+
| [viability-check](./steps/viability-check.md) | Exclude services that can't run locally |
|
|
89
|
+
| [identify-services](./steps/identify-services.md) | Confirm the derived graph; fill the gaps |
|
|
90
|
+
| [e2e-instructions](./steps/e2e-instructions.md) | Startup order, manual steps, local gotchas |
|
|
86
91
|
|
|
87
92
|
The Decide phase's output is the **resolved prepare plan**: `services[]` (name, dir, start command, expected port, `external` flag, approval granted), `testingScope`, `excludedServices[]`, the recorded dev-server URL, the E2E run instructions, and resolved gate outcomes.
|
|
88
93
|
|
|
89
94
|
**Execute (agent).** Dispatch the `test-prepare-runner` agent (subagent type `muggle:test-prepare-runner`; bare `test-prepare-runner` where the plugin namespace is absent), synchronously, passing the resolved plan; it returns `READY` / `DEGRADED` plus the readiness table. The agent's own definition lists its stage files; in a harness with no agent/subagent facility, run the execute-phase stages ([check-running](./steps/check-running.md) through [readiness-report](./steps/readiness-report.md)) inline instead.
|
|
90
95
|
|
|
91
|
-
Relay the readiness table to the user or calling skill verbatim. A `needs-input:` line from the agent names an unresolved decision — resolve it here (asking the user if needed) and re-dispatch; the agent never asks.
|
|
96
|
+
Relay the readiness table to the user or calling skill verbatim. A `needs-input:` line from the agent names an unresolved decision — resolve it here (asking the user if needed) and re-dispatch; the agent never asks. That path is also how a problem hit mid-preparation reaches the user when it can't be resolved autonomously, per [record-resolution](./steps/record-resolution.md).
|
|
97
|
+
|
|
98
|
+
The agent also returns the problems it hit and what cleared them. On a learning run those feed [confirm-recipe](./steps/confirm-recipe.md); on a replay they are already in the recipe and are applied without asking.
|
|
92
99
|
|
|
93
100
|
## Cleanup
|
|
94
101
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Confirm the recipe
|
|
2
|
+
|
|
3
|
+
The one gate of a learning run. Everything the run discovered is held until here, so the user makes a single decision instead of approving facts one at a time.
|
|
4
|
+
|
|
5
|
+
Needs a verified-ready environment — a recipe for a preparation that did not work is worth nothing.
|
|
6
|
+
|
|
7
|
+
## Show what was done
|
|
8
|
+
|
|
9
|
+
Summarise the actual run, not the plan that preceded it:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Prepared your environment. Here's what it took:
|
|
13
|
+
|
|
14
|
+
Services started, in order
|
|
15
|
+
1. api pnpm --filter api dev :8080
|
|
16
|
+
2. worker pnpm --filter worker dev —
|
|
17
|
+
3. ui pnpm dev :3999
|
|
18
|
+
|
|
19
|
+
Manual steps
|
|
20
|
+
• pnpm db:migrate — run once before the api comes up
|
|
21
|
+
|
|
22
|
+
Problems hit, and what fixed them
|
|
23
|
+
• ui — HTTP 500 on /dashboard while api was still booting
|
|
24
|
+
→ started api first and waited for its ready signal
|
|
25
|
+
• worker — exited immediately, missing .env.local
|
|
26
|
+
→ copied .env.example, you filled in QUEUE_URL
|
|
27
|
+
|
|
28
|
+
Not running locally
|
|
29
|
+
• payment-gateway — needs production certificates
|
|
30
|
+
|
|
31
|
+
Remember this as your prepare recipe, so future runs just do it?
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`AskUserQuestion`:
|
|
35
|
+
|
|
36
|
+
- `Remember it` — `Future E2E runs prepare this way without asking.`
|
|
37
|
+
- `Don't remember` — `Prepare from scratch again next time.`
|
|
38
|
+
|
|
39
|
+
## On accept
|
|
40
|
+
|
|
41
|
+
Write both halves of the recipe together, so they cannot drift:
|
|
42
|
+
|
|
43
|
+
- The machine-readable service graph to `~/.muggle-ai/prepare-plans.json`, keyed on the stack, per [readiness-report](./readiness-report.md).
|
|
44
|
+
- The prose — startup order, manual steps, gotchas, and the recorded resolutions from [record-resolution](./record-resolution.md) — to `~/.muggle-ai/e2e-instructions/<key>.md`, per [e2e-instructions](./e2e-instructions.md).
|
|
45
|
+
|
|
46
|
+
## On decline
|
|
47
|
+
|
|
48
|
+
Write nothing. The next run learns again. Do not ask a second time in the same run, and do not record the refusal as a preference — the user is declining this recipe, not the idea of recipes.
|
|
49
|
+
|
|
50
|
+
## Hard block
|
|
51
|
+
|
|
52
|
+
The only thing that lets a replay deviate: **a failure the recipe's recorded resolutions do not cover, which autonomous attempts cannot clear.**
|
|
53
|
+
|
|
54
|
+
Anything the recipe already handles is not a hard block, however loudly it fails. Neither is a transient that clears on retry. The bar is deliberately high, because every re-prompt undoes the learning.
|
|
55
|
+
|
|
56
|
+
On a hard block: resolve it with the user, then offer to update the recipe with the new resolution — the same accept/decline, scoped to the one addition. Otherwise the recipe changes only when the user asks for it.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Derive the service graph
|
|
2
|
+
|
|
3
|
+
Derive the service graph from what the workspace declares, so the interview only covers what a scan cannot know. Reading manifests beats interrogating the user about their own repo layout.
|
|
4
|
+
|
|
5
|
+
Applies only to a [learning run](./replay-or-learn.md). A replay never scans.
|
|
6
|
+
|
|
7
|
+
## Ask before reading anything
|
|
8
|
+
|
|
9
|
+
Nothing is read until the user says where. `AskUserQuestion`:
|
|
10
|
+
|
|
11
|
+
> "To work out your services, I can scan the code — or you can just tell me. Which?"
|
|
12
|
+
|
|
13
|
+
- `Scan <cwd>` — `Read manifests here, 2 levels deep, to find services and their start commands.`
|
|
14
|
+
- `Scan a different folder` — `Name the folder and how deep to look.`
|
|
15
|
+
- `I'll list them` — `Give me the service paths yourself; I won't read anything you haven't named.`
|
|
16
|
+
|
|
17
|
+
The default depth is **2 levels**, which reaches `packages/*` and `services/*` without walking a whole disk. The user may set it higher or lower when naming the folder.
|
|
18
|
+
|
|
19
|
+
`I'll list them` skips the scan entirely: take the paths and service names from the user and continue to the interview with those as the graph. Read only the top-level indicator files of the paths they named, and only to determine the start command.
|
|
20
|
+
|
|
21
|
+
Record the granted scope — folder and depth — with the recipe, so a later run reuses the same permission rather than asking again. Widening it needs a fresh ask.
|
|
22
|
+
|
|
23
|
+
## Read, in this order
|
|
24
|
+
|
|
25
|
+
Within the granted scope only:
|
|
26
|
+
|
|
27
|
+
1. **Workspace manifests** — `pnpm-workspace.yaml`, the `workspaces` field of a root `package.json`, `turbo.json`, `lerna.json`, `nx.json`, `Cargo.toml` `[workspace]`, `go.work`. These name the members directly, so a monorepo needs no guessing.
|
|
28
|
+
2. **Compose and process files** — `docker-compose.yml`, `Procfile`, `Makefile`, `Tiltfile`, `skaffold.yaml`. These carry service names, ports, and often the dependency order outright (`depends_on`).
|
|
29
|
+
3. **Per-service manifests** for each member found — `package.json` scripts (`dev`, `start`, `serve`), `Cargo.toml`, `go.mod`, `pyproject.toml`. This is where the start command comes from.
|
|
30
|
+
4. **Port declarations** — `.env.example`, `vite.config.*`, `next.config.*`, a `PORT` in the start script. Never a framework default: an undeclared port is unknown, not `:3000`.
|
|
31
|
+
|
|
32
|
+
## Emit
|
|
33
|
+
|
|
34
|
+
A proposed service graph — for each: name, directory, start command with its source, expected port with its source, and whether the scan believes it is required for the declared scope.
|
|
35
|
+
|
|
36
|
+
Plus what the scan **could not** determine. That list is the interview's agenda; everything else is already settled.
|
|
37
|
+
|
|
38
|
+
Show the user what was found before asking anything else. A scan they can see is a scan they can correct.
|
|
39
|
+
|
|
40
|
+
## What a scan cannot know
|
|
41
|
+
|
|
42
|
+
State these as unknowns rather than guessing, because each is a judgement about intent rather than a fact in a file:
|
|
43
|
+
|
|
44
|
+
- Which services this user actually needs running for the tests they care about.
|
|
45
|
+
- Which cannot run locally at all (a payment gateway needing production certificates).
|
|
46
|
+
- Startup ordering that no `depends_on` declares but which the app requires anyway.
|
|
47
|
+
- Anything that has to happen by hand before a service is usable.
|
|
48
|
+
|
|
49
|
+
## Boundaries
|
|
50
|
+
|
|
51
|
+
Read only inside the folder the user granted, to the depth they set. A directory outside that scope may have its name listed to offer it as a candidate; its contents stay unread until the user names it.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# E2E run instructions
|
|
2
2
|
|
|
3
3
|
Capture what `prepare-plan.json` cannot express: the order services must come up in, steps that aren't a single command, and the local gotchas that make a healthy stack look broken. Persisted under `~/.muggle-ai/e2e-instructions/` and reused on later runs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Needs the resolved service list from [identify-services](./identify-services.md): startup order and per-service gotchas are unanswerable until the service set is known.
|
|
6
6
|
|
|
7
7
|
## Scope boundary
|
|
8
8
|
|
|
@@ -77,8 +77,19 @@ Run `pnpm db:migrate` once after a fresh clone; the dev servers don't migrate on
|
|
|
77
77
|
- First build after a clean install takes ~4 min. It is not hung.
|
|
78
78
|
- UI is on :3999, not the Next.js default :3000.
|
|
79
79
|
- Auth0 dev tenant rate-limits past ~20 logins/hour.
|
|
80
|
+
|
|
81
|
+
## Resolutions
|
|
82
|
+
|
|
83
|
+
Observed by prepare runs. Each is a signal that occurred and the fix that cleared it, applied automatically when the same signal recurs.
|
|
84
|
+
|
|
85
|
+
- **ui** — HTTP 500 on `/dashboard` while api was still booting
|
|
86
|
+
→ start api first and wait for its ready signal *(user-directed, 2026-08-04)*
|
|
87
|
+
- **worker** — exited immediately, `.env.local` absent
|
|
88
|
+
→ copy `.env.example`, user supplies `QUEUE_URL` *(user-directed, 2026-08-04)*
|
|
80
89
|
```
|
|
81
90
|
|
|
91
|
+
The sections above the Resolutions heading are the user's own words and are never rewritten by a run. Resolutions are what the machine observed, kept separate so the user can tell which is which and delete either with confidence.
|
|
92
|
+
|
|
82
93
|
### Sentinel
|
|
83
94
|
|
|
84
95
|
```markdown
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Identify required services & startup mode
|
|
2
2
|
|
|
3
|
-
> Skipped
|
|
3
|
+
> Skipped entirely on a replay run — the recipe supplies the service list and startup mode. See [replay-or-learn](./replay-or-learn.md).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Start from the scan.** [derive-service-graph](./derive-service-graph.md) has already derived services, directories, start commands and ports from the workspace's own manifests. Present that graph for confirmation and ask only about what the scan listed as undetermined — which services this user actually needs, what can't run locally, ordering no manifest declares, and steps that happen by hand. Re-asking something the scan established wastes the scan.
|
|
6
|
+
|
|
7
|
+
When the scan found nothing (no workspace manifests, an unfamiliar layout), fall back to listing folder names in the **parent directory** of the current working directory:
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
ls -d "$(dirname "$PWD")"/*/ | xargs -I{} basename {}
|
|
@@ -27,7 +27,9 @@ Cleanup: say "stop services" or re-invoke this skill.
|
|
|
27
27
|
|
|
28
28
|
## Save the plan
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Only on a learning run, and only once the user has accepted the gate in [confirm-recipe](./confirm-recipe.md). Nothing is persisted before that — a recipe the user didn't agree to is a recipe they'll have to undo, and a recipe for a preparation that failed is worse than none.
|
|
31
|
+
|
|
32
|
+
A replay run writes nothing here; it already has the recipe.
|
|
31
33
|
|
|
32
34
|
Build the JSON from the in-memory tracking file, dropping runtime fields:
|
|
33
35
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Recording a resolution
|
|
2
|
+
|
|
3
|
+
How a problem hit during preparation becomes something the next run already knows. Referenced by [start-services](./start-services.md), [smoke-test](./smoke-test.md), and [fresh-install](./fresh-install.md) — every stage that can fail.
|
|
4
|
+
|
|
5
|
+
## On a replay: consult before attempting
|
|
6
|
+
|
|
7
|
+
When a step fails and the recipe holds a resolution for the same signal, apply that resolution first. A problem solved before is solved the same way again, silently. Re-deriving a fix the user already sat through is the failure this whole mechanism exists to prevent.
|
|
8
|
+
|
|
9
|
+
Only when no recorded resolution matches, or the recorded one does not clear it, does the failure become a [hard block](./confirm-recipe.md#hard-block).
|
|
10
|
+
|
|
11
|
+
## Resolving
|
|
12
|
+
|
|
13
|
+
1. **Attempt autonomously** where the fix is known and safe — a clean restart, a fresh install, waiting out a slow boot that is still making progress.
|
|
14
|
+
2. **Escalate** when it is not. During execution that means returning `needs-input:` naming the service and the concrete signal; the dispatching skill resolves it with the user and re-dispatches. Never guess at a fix that could destroy state.
|
|
15
|
+
|
|
16
|
+
## What to record
|
|
17
|
+
|
|
18
|
+
Per resolved problem, three things and nothing else:
|
|
19
|
+
|
|
20
|
+
- The **signal** — the concrete observation, not a paraphrase. An HTTP status, a matched log line, an exit code.
|
|
21
|
+
- The **service** it occurred on.
|
|
22
|
+
- The **resolution** that actually cleared it, and whether it was autonomous or user-directed.
|
|
23
|
+
|
|
24
|
+
A problem that was never cleared is not a resolution. Record it as an exclusion with its reason instead, so the next run does not retry something known to be hopeless.
|
|
25
|
+
|
|
26
|
+
## What not to record
|
|
27
|
+
|
|
28
|
+
- A transient that cleared on retry with no intervention. Nothing was learned; it was a blip.
|
|
29
|
+
- A signal already recorded for the same service with the same resolution. Update the existing entry rather than appending a duplicate.
|
|
30
|
+
- Anything containing a credential. Record the pointer form per [e2e-instructions](./e2e-instructions.md#secrets).
|
|
31
|
+
|
|
32
|
+
Resolutions accumulate through the run and are written **only** when the user accepts the gate in [confirm-recipe](./confirm-recipe.md). Nothing is persisted mid-run.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Replay or learn
|
|
2
|
+
|
|
3
|
+
One decision, made before anything else: does a prepare recipe already exist for this stack?
|
|
4
|
+
|
|
5
|
+
Resolve the recipe location per [confirm-recipe](./confirm-recipe.md).
|
|
6
|
+
|
|
7
|
+
- **Recipe exists** → **replay run**. Skip straight to the execute phase and run it. No scan, no interview, no questions. The whole point of having learned is not to ask again.
|
|
8
|
+
- **No recipe** → **learning run**. Run the Decide stages in order, then the single gate at the end.
|
|
9
|
+
|
|
10
|
+
A recipe the user declined to save is the same as no recipe: the next run learns again. Declining is not a permanent no, it just means nothing was written.
|
|
11
|
+
|
|
12
|
+
## What a replay is allowed to do
|
|
13
|
+
|
|
14
|
+
Execute the recorded steps, in the recorded order, using the recorded commands. When a step fails, consult the recipe's recorded resolutions **first** — a problem solved before is solved the same way again, without asking.
|
|
15
|
+
|
|
16
|
+
Only a [hard block](./confirm-recipe.md#hard-block) permits a replay to deviate or prompt.
|
|
17
|
+
|
|
18
|
+
## Reuse gate
|
|
19
|
+
|
|
20
|
+
`reusePreparePlan` still governs whether a found recipe is used at all. `never` forces a learning run even when a recipe exists — the escape hatch for a stack that has been re-architected.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Resolving the saved plan
|
|
2
2
|
|
|
3
|
-
A previously saved **prepare plan** is the durable recipe for this stack
|
|
3
|
+
How [replay-or-learn](./replay-or-learn.md) finds the machine-readable half of the recipe. A previously saved **prepare plan** is the durable recipe for this stack, paired with the prose half in `~/.muggle-ai/e2e-instructions/<key>.md`. Distinct from the ephemeral `/tmp/muggle-test-prepare.json` tracker — that file holds live PIDs/logs and is rebuilt every run.
|
|
4
|
+
|
|
5
|
+
Finding a plan means this is a **replay run**: execute it, ask nothing. Finding none means a **learning run**.
|
|
4
6
|
|
|
5
7
|
## Resolve
|
|
6
8
|
|
|
@@ -12,7 +12,9 @@ Use the primitives in `dev-server-readiness.md`. Don't re-implement.
|
|
|
12
12
|
|
|
13
13
|
## Diagnose-and-fix loop
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Consult the recipe first.** When a recorded resolution matches this signal on this service, apply it without asking, per [record-resolution](./record-resolution.md). The user already answered this question once.
|
|
16
|
+
|
|
17
|
+
Otherwise show the concrete signal (HTTP code, sniff hit, or log line) and `AskUserQuestion`:
|
|
16
18
|
|
|
17
19
|
> "**<service-name>** isn't healthy: `<signal>`. How do you want to proceed?"
|
|
18
20
|
|
|
@@ -23,4 +25,6 @@ On failure, show the concrete signal (HTTP code, sniff hit, or log line) and `As
|
|
|
23
25
|
|
|
24
26
|
Loop per service until pass or skip. Cap at **3 iterations** — then force a manual-intervention pause.
|
|
25
27
|
|
|
28
|
+
Whatever cleared the failure is a resolution: capture the signal, the service, and the fix per [record-resolution](./record-resolution.md), so the next run applies it silently instead of asking again. A service skipped instead of fixed is recorded as an exclusion with its reason, not as a resolution.
|
|
29
|
+
|
|
26
30
|
For `external: true`, only Options 3 and 4 apply.
|
|
@@ -11,7 +11,9 @@ Capture the PID. Write all service entries to `/tmp/muggle-test-prepare.json`.
|
|
|
11
11
|
|
|
12
12
|
**Startup verification** — confirm PID alive (`kill -0 <pid> 2>/dev/null`), then run the two-stage readiness probe per [`../../_shared/dev-server-readiness.md`](../../_shared/dev-server-readiness.md) against the log. Cap log-tail at 60 s. Halt on whatever surfaces.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
A boot that takes most of the 60 s cap but keeps making progress is a property of this stack, not a fault — record it per [record-resolution](./record-resolution.md) so the next run expects the wait instead of treating it as a hang.
|
|
15
|
+
|
|
16
|
+
If a PID dies immediately, consult the recipe's recorded resolutions for this service first; apply a match without asking. Otherwise show the last 20 log lines:
|
|
15
17
|
|
|
16
18
|
> "**backend-api** exited right after starting. Here's the tail of its log:"
|
|
17
19
|
|
package/dist/src-AWUORQZZ.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, reconcileProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-OCLMXAVJ.js';
|