@muggleai/works 5.5.1 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/hooks/README.md +6 -3
- package/dist/plugin/hooks/hooks.json +34 -0
- package/dist/plugin/scripts/guardrail-offer-ran.sh +35 -0
- package/dist/plugin/scripts/guardrail-pr-terminal.sh +21 -0
- package/dist/plugin/scripts/guardrail-record-tests.sh +5 -4
- package/dist/plugin/scripts/guardrail-terminal-gate.sh +38 -0
- package/dist/plugin/scripts/guardrails.mjs +96 -3
- package/dist/plugin/skills/do/e2e-acceptance.md +2 -0
- package/dist/plugin/skills/do/respawn-watcher.md +4 -14
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +3 -1
- package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +12 -0
- package/dist/plugin/skills/muggle-pr-followup/auto-track.md +5 -11
- package/dist/plugin/skills/muggle-pr-followup/blocked-tick.md +10 -10
- package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +4 -8
- package/dist/plugin/skills/muggle-pr-followup/contract.md +5 -3
- package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +12 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +2 -2
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +10 -0
- package/dist/release-manifest.json +4 -4
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/hooks/README.md +6 -3
- package/plugin/hooks/hooks.json +34 -0
- package/plugin/scripts/guardrail-offer-ran.sh +35 -0
- package/plugin/scripts/guardrail-pr-terminal.sh +21 -0
- package/plugin/scripts/guardrail-record-tests.sh +5 -4
- package/plugin/scripts/guardrail-terminal-gate.sh +38 -0
- package/plugin/scripts/guardrails.mjs +96 -3
- package/plugin/skills/do/e2e-acceptance.md +2 -0
- package/plugin/skills/do/respawn-watcher.md +4 -14
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
- package/plugin/skills/muggle-pr-followup/SKILL.md +3 -1
- package/plugin/skills/muggle-pr-followup/arm-watcher.md +12 -0
- package/plugin/skills/muggle-pr-followup/auto-track.md +5 -11
- package/plugin/skills/muggle-pr-followup/blocked-tick.md +10 -10
- package/plugin/skills/muggle-pr-followup/bootstrap.md +4 -8
- package/plugin/skills/muggle-pr-followup/contract.md +5 -3
- package/plugin/skills/muggle-pr-followup/evals/evals.json +12 -0
- package/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +2 -2
- package/plugin/skills/muggle-pr-followup/state-schemas.md +10 -0
|
@@ -73,7 +73,7 @@ Write under `~/.muggle-ai/muggle-do/sessions/<slug>/`:
|
|
|
73
73
|
|
|
74
74
|
**`last_seen.json`** — see [`state-schemas.md`](state-schemas.md#last_seenjson). One key (`"<owner>/<repo>#<n>"`), `lastBodyReviewId` from Step 6, `last_pushed_sha: null`, `idle_tick_count: 0`, `cycles_completed: 0`, `escalated_review_ids: []`, `pushed_shas: []`. Omit `blocked` — the watcher starts unblocked.
|
|
75
75
|
|
|
76
|
-
**`cron.json`** — see [`state-schemas.md`](state-schemas.md#cronjson). `cron_id: null` (
|
|
76
|
+
**`cron.json`** — see [`state-schemas.md`](state-schemas.md#cronjson). `cron_id: null` (bootstrap arms no cron; a tick running under one recorded by [`reconcile.md`](reconcile.md) self-records its id per [`record-cron-id.md`](record-cron-id.md)), `command: "/muggle:muggle-pr-followup <slug> <n>"`, `interval: "1m"`, `recorded_at: <now>`.
|
|
77
77
|
|
|
78
78
|
**`state.md`** — see [`state-schemas.md`](state-schemas.md#statemd). `Bootstrapped from URL: yes`. Cache the loop-user login. If Step 6.5 resolved a validation context, append the `## Pre-flight answers` block with its fields, per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md#persisted-fields). If it seeded poll-only, write **no** such block — a missing block is a clean E2E skip.
|
|
79
79
|
|
|
@@ -81,17 +81,13 @@ Do **not** write `cycle.json` or `requirements.md` — those files are no longer
|
|
|
81
81
|
|
|
82
82
|
Create `iterations/` subdir (empty) for future caller use.
|
|
83
83
|
|
|
84
|
-
### Step 8 —
|
|
84
|
+
### Step 8 — Arm the watch
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
/loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
90
|
-
```
|
|
86
|
+
Arm per [`arm-watcher.md`](arm-watcher.md) as the last action of the turn: one tick drains anything already actionable — this is the first tick Step 6 promises — then a persistent, labeled monitor keeps watch — visible until the PR terminates. The cron path stays as the recovery substrate ([`reconcile.md`](reconcile.md)), so `cron.json` is still seeded in Step 7.
|
|
91
87
|
|
|
92
88
|
### Step 9 — Print the success summary
|
|
93
89
|
|
|
94
|
-
Use the success-summary template from [`output-templates/bootstrap.md`](output-templates/bootstrap.md). Print it **before** the
|
|
90
|
+
Use the success-summary template from [`output-templates/bootstrap.md`](output-templates/bootstrap.md). Print it **before** arming the watch so it's visible.
|
|
95
91
|
|
|
96
92
|
### Step 10 — Emit telemetry
|
|
97
93
|
|
|
@@ -33,6 +33,8 @@ Every `increment`/`reset` this procedure applies to `last_seen.json`, and the `p
|
|
|
33
33
|
|
|
34
34
|
If `prs.json[0].state` on disk is already `merged` or `closed`, this slot was finalized by a prior tick and this is a stale (queued) fire — per-minute cron fires enqueued while the session was busy still drain after the cron is cancelled. Defensively cancel any lingering cron for this slug per [`cancel-cron.md`](cancel-cron.md) (no-op if none), append a `stale-tick` line to `followup.log`, and exit. Do not re-fetch or re-finalize.
|
|
35
35
|
|
|
36
|
+
**Orphan escalation — on the third or later stale fire.** Count prior `stale-tick` lines for this slot in `followup.log` before appending this one. Three or more means both `cancel-cron.md` lookups have already run on earlier stale fires and the cron still fires — it is orphaned in a session runtime neither the recorded id nor `CronList` can reach, so it will keep firing until the 7-day expiry. Escalate to the owner **once**: if `followup.log` has no `stale-orphan-escalated` line, print one line — `orphaned cron: <slug> keeps firing after finalize; no in-session cancel can reach it — restarting the Claude session is the only clean clear` — and append a `stale-orphan-escalated` line so every later stale fire absorbs silently again. Never respond to an unreachable cron by guess-deleting ids `CronList` does surface: those belong to other live watchers, and deleting one silently kills a wanted loop.
|
|
37
|
+
|
|
36
38
|
Otherwise, self-record this watcher's cron id per [`record-cron-id.md`](record-cron-id.md) before proceeding. Recording every tick — while `CronList` can still see the cron — is what keeps the id a valid `CronDelete` target after a session continue / compaction blinds `CronList` to it, so teardown ([`finalize.md`](finalize.md), [`reconcile.md`](reconcile.md)) can always kill the orphan.
|
|
37
39
|
|
|
38
40
|
### Step 1 — Refresh PR state
|
|
@@ -58,7 +60,7 @@ If `state` is `MERGED` or `CLOSED`:
|
|
|
58
60
|
|
|
59
61
|
Only when `last_seen.blocked` is present (the watcher is awaiting the owner on a durable human-block, flagged in Step 7). When absent, skip straight to Step 3.
|
|
60
62
|
|
|
61
|
-
Run the
|
|
63
|
+
Run the resume gate per [`blocked-tick.md`](blocked-tick.md): recompute the fingerprint; while it holds, stay blocked **silently** — the single owner reminder went out when the block was flagged — and clear the block and fall through to Step 3 the moment it moves. The block never changes the poll; it only mutes dispatch until the state the user must act on moves.
|
|
62
64
|
|
|
63
65
|
### Step 3 — Compute the actionable set from live thread state
|
|
64
66
|
|
|
@@ -147,9 +149,9 @@ Any idle branch (Steps 4–6 that did not dispatch). First classify **why** this
|
|
|
147
149
|
|
|
148
150
|
Everything else that idles is **transient** — green and waiting for the next review, CI still pending, or `mergeable == UNKNOWN` — and must keep the responsive `1m` cadence; those turn a state on their own and the watcher should catch it promptly.
|
|
149
151
|
|
|
150
|
-
**Transient idle** (no durable block): unchanged — increment `last_seen.idle_tick_count`, append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md), emit a `tick` event with `idle: true`, `blocked: false`, `actionable_threads: 0`, `dispatched_review_ids: []`, `rebase_needed: <bool>`, `dispatched_rebase: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires in 1 min via `/loop`.
|
|
152
|
+
**Transient idle** (no durable block): unchanged — increment `last_seen.idle_tick_count`, append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md), emit a `tick` event with `idle: true`, `blocked: false`, `actionable_threads: 0`, `dispatched_review_ids: []`, `rebase_needed: <bool>`, `dispatched_rebase: false`, `checks_red: <count or 0>`, `dispatched_ci_fix: false`. Exit. The next tick fires from the arming loop ([`arm-watcher.md`](arm-watcher.md)) — or, under a recovery cron, in 1 min via `/loop`.
|
|
151
153
|
|
|
152
|
-
**Blocked pending a human** (a durable block, and `last_seen.blocked` not already set): enter the blocked path per [`blocked-tick.md`](blocked-tick.md) — flag `last_seen.blocked` and emit the one-line owner reminder
|
|
154
|
+
**Blocked pending a human** (a durable block, and `last_seen.blocked` not already set): enter the blocked path per [`blocked-tick.md`](blocked-tick.md) — flag `last_seen.blocked` and emit the one-line owner reminder, **once per block**. From the next tick on, the Step 2.5 gate carries the block silently. Exit.
|
|
153
155
|
|
|
154
156
|
## Output
|
|
155
157
|
|
|
@@ -84,6 +84,18 @@
|
|
|
84
84
|
{ "name": "does_not_refetch_or_refinalize", "text": "Plan does NOT re-fetch the PR, re-write result.md, or re-run the terminal handoff." },
|
|
85
85
|
{ "name": "defensively_cancels_and_logs", "text": "Plan defensively cancels any lingering cron for the slug and appends a stale-tick line to followup.log, then exits." }
|
|
86
86
|
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": 7,
|
|
90
|
+
"eval_name": "repeated-stale-fires-escalate-orphan-once",
|
|
91
|
+
"prompt": "Tick muggle-ai-works-pr507 507. prs.json records state merged and result.md exists. followup.log already holds four stale-tick lines for this slot and no stale-orphan-escalated line. Walk me through the plan for this tick.",
|
|
92
|
+
"files": [],
|
|
93
|
+
"assertions": [
|
|
94
|
+
{ "name": "detects_orphaned_cron", "text": "Plan concludes the cron is orphaned — repeated stale fires after finalize mean both cancel-cron lookups (recorded id, CronList match) keep missing it — rather than treating this as a normal queued-fire drain." },
|
|
95
|
+
{ "name": "escalates_to_owner_once", "text": "Plan surfaces a one-line owner notice that only a session restart clears the orphan, and appends a stale-orphan-escalated marker line so the notice never repeats." },
|
|
96
|
+
{ "name": "later_fires_absorb_silently", "text": "Plan treats any stale fire after the marker exists as a silent absorb — log stale-tick and exit, no repeated notice." },
|
|
97
|
+
{ "name": "never_guess_deletes_other_ids", "text": "Plan does NOT delete cron ids that CronList surfaces for other slugs — those belong to other live watchers." }
|
|
98
|
+
]
|
|
87
99
|
}
|
|
88
100
|
]
|
|
89
101
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Bootstrap output templates
|
|
2
2
|
|
|
3
|
-
## Success summary (printed just before
|
|
3
|
+
## Success summary (printed just before arming)
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
Bootstrapped PR follow-up for <owner>/<repo>#<n>
|
|
7
7
|
Slug: <slug>
|
|
8
8
|
Baseline: thread-state (unresolved threads picked up live); lastBodyReviewId=0 | =<id> (forward-only)
|
|
9
9
|
Working tree: <toplevel>
|
|
10
|
-
|
|
10
|
+
Arming: <owner>/<repo>#<n>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Aborts
|
|
@@ -163,3 +163,13 @@ Written exactly once when the PR's watcher exits terminally (PR merged or closed
|
|
|
163
163
|
## Not in the slot
|
|
164
164
|
|
|
165
165
|
`cycle.json` and `requirements.md` are not seeded or read. `/muggle-do` reads reviews off GitHub each invocation.
|
|
166
|
+
|
|
167
|
+
## `watch-watermark.env`
|
|
168
|
+
|
|
169
|
+
The watch loop's comparison floor — plain `KEY=VALUE` lines, one file per slot:
|
|
170
|
+
|
|
171
|
+
- `REV` — highest submitted-review id already handled
|
|
172
|
+
- `COM` — highest thread-comment id already handled
|
|
173
|
+
- `THREADS` — semicolon-joined ids of threads already known unresolved
|
|
174
|
+
|
|
175
|
+
Written whole-file by the orchestrating session — seeded at arm time from a post-drain fetch, advanced after every cycle from a post-replies fetch. Read by the watch loop each iteration; the loop never writes it. A stale watermark makes the next reported event the loop's own reply ([`arm-watcher.md`](arm-watcher.md)).
|