@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.
Files changed (44) hide show
  1. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  2. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  3. package/dist/plugin/hooks/README.md +6 -3
  4. package/dist/plugin/hooks/hooks.json +34 -0
  5. package/dist/plugin/scripts/guardrail-offer-ran.sh +35 -0
  6. package/dist/plugin/scripts/guardrail-pr-terminal.sh +21 -0
  7. package/dist/plugin/scripts/guardrail-record-tests.sh +5 -4
  8. package/dist/plugin/scripts/guardrail-terminal-gate.sh +38 -0
  9. package/dist/plugin/scripts/guardrails.mjs +96 -3
  10. package/dist/plugin/skills/do/e2e-acceptance.md +2 -0
  11. package/dist/plugin/skills/do/respawn-watcher.md +4 -14
  12. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
  13. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +3 -1
  14. package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +12 -0
  15. package/dist/plugin/skills/muggle-pr-followup/auto-track.md +5 -11
  16. package/dist/plugin/skills/muggle-pr-followup/blocked-tick.md +10 -10
  17. package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +4 -8
  18. package/dist/plugin/skills/muggle-pr-followup/contract.md +5 -3
  19. package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +12 -0
  20. package/dist/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +2 -2
  21. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +10 -0
  22. package/dist/release-manifest.json +4 -4
  23. package/package.json +1 -1
  24. package/plugin/.claude-plugin/plugin.json +1 -1
  25. package/plugin/.cursor-plugin/plugin.json +1 -1
  26. package/plugin/hooks/README.md +6 -3
  27. package/plugin/hooks/hooks.json +34 -0
  28. package/plugin/scripts/guardrail-offer-ran.sh +35 -0
  29. package/plugin/scripts/guardrail-pr-terminal.sh +21 -0
  30. package/plugin/scripts/guardrail-record-tests.sh +5 -4
  31. package/plugin/scripts/guardrail-terminal-gate.sh +38 -0
  32. package/plugin/scripts/guardrails.mjs +96 -3
  33. package/plugin/skills/do/e2e-acceptance.md +2 -0
  34. package/plugin/skills/do/respawn-watcher.md +4 -14
  35. package/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
  36. package/plugin/skills/muggle-pr-followup/SKILL.md +3 -1
  37. package/plugin/skills/muggle-pr-followup/arm-watcher.md +12 -0
  38. package/plugin/skills/muggle-pr-followup/auto-track.md +5 -11
  39. package/plugin/skills/muggle-pr-followup/blocked-tick.md +10 -10
  40. package/plugin/skills/muggle-pr-followup/bootstrap.md +4 -8
  41. package/plugin/skills/muggle-pr-followup/contract.md +5 -3
  42. package/plugin/skills/muggle-pr-followup/evals/evals.json +12 -0
  43. package/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +2 -2
  44. 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` (Step 8 dispatches `/loop` as the last action, so the id isn't observable yet — the first tick self-records it per [`record-cron-id.md`](record-cron-id.md)), `command: "/muggle:muggle-pr-followup <slug> <n>"`, `interval: "1m"`, `recorded_at: <now>`.
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 — Dispatch the first watcher
84
+ ### Step 8 — Arm the watch
85
85
 
86
- The last action of this turn:
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 `/loop` dispatch so it's visible.
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 remind-or-resume gate per [`blocked-tick.md`](blocked-tick.md): recompute the fingerprint, re-emit the one-line owner reminder and stay blocked while it holds, or clear the block and fall through to Step 3 the moment it moves. While blocked the watcher keeps the normal `1m` cadence and reminds the owner each tick — the block reminds rather than backs off, and never changes the poll interval.
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 (the watcher reminds each tick at the normal `1m` cadence rather than backing off). From the next tick on, the Step 2.5 gate carries the block. Exit.
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 dispatch)
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
- Dispatching: /loop 1m /muggle:muggle-pr-followup <slug> <n>
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)).