@muggleai/works 5.7.0 → 5.8.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.
Files changed (119) hide show
  1. package/dist/{chunk-LTULYOOW.js → chunk-5PYK7DWB.js} +15 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  5. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  6. package/dist/plugin/agents/test-prepare-runner.md +47 -0
  7. package/dist/plugin/agents/visual-walkthrough-builder.md +58 -0
  8. package/dist/plugin/hooks/README.md +11 -2
  9. package/dist/plugin/hooks/hooks.json +7 -1
  10. package/dist/plugin/scripts/gc-state.sh +66 -0
  11. package/dist/plugin/scripts/guard-run-job-object.ps1 +256 -0
  12. package/dist/plugin/scripts/guard-run.mjs +327 -0
  13. package/dist/plugin/scripts/guardrail-record-tests.sh +5 -4
  14. package/dist/plugin/scripts/guardrails.mjs +6 -3
  15. package/dist/plugin/scripts/pr-watch-guards.sh +46 -0
  16. package/dist/plugin/skills/CLAUDE.md +39 -36
  17. package/dist/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +3 -3
  18. package/dist/plugin/skills/_shared/vcs/CLAUDE.md +2 -1
  19. package/dist/plugin/skills/_shared/vcs/common/branch-standing.md +19 -0
  20. package/dist/plugin/skills/_shared/vcs/common/ci-rollup.md +16 -0
  21. package/dist/plugin/skills/_shared/vcs/common/push-to-branch.md +16 -0
  22. package/{plugin/skills/_shared/vcs/github → dist/plugin/skills/_shared/vcs/common}/verify-working-tree.md +4 -4
  23. package/dist/plugin/skills/_shared/vcs/github/pr-edit.md +2 -0
  24. package/dist/plugin/skills/_shared/vcs/github/reply-line-comment.md +2 -0
  25. package/dist/plugin/skills/_shared/vcs/github/signed-commits.md +28 -0
  26. package/dist/plugin/skills/_shared/vcs/github/top-level-comment.md +2 -0
  27. package/dist/plugin/skills/_shared/vcs/github/unresolved-threads.md +39 -39
  28. package/dist/plugin/skills/_shared/vcs/github.md +5 -2
  29. package/dist/plugin/skills/_shared/vcs/gitlab/loop-user-identity.md +1 -1
  30. package/dist/plugin/skills/_shared/vcs/gitlab/mr-create.md +1 -1
  31. package/dist/plugin/skills/_shared/vcs/gitlab/mr-discussions.md +1 -1
  32. package/dist/plugin/skills/_shared/vcs/gitlab/mr-metadata.md +2 -2
  33. package/dist/plugin/skills/_shared/vcs/gitlab/mr-pipeline.md +1 -1
  34. package/dist/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +1 -1
  35. package/dist/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +1 -1
  36. package/dist/plugin/skills/_shared/vcs/gitlab/signed-commits.md +15 -0
  37. package/dist/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +2 -2
  38. package/dist/plugin/skills/_shared/vcs/gitlab.md +6 -3
  39. package/dist/plugin/skills/_shared/vcs/post-signature.md +31 -0
  40. package/dist/plugin/skills/do/build.md +58 -56
  41. package/dist/plugin/skills/do/open-prs/forward.md +85 -84
  42. package/dist/plugin/skills/do/open-prs/update.md +3 -3
  43. package/dist/plugin/skills/do/per-comment-replies.md +2 -2
  44. package/dist/plugin/skills/do/resolve-conflicts.md +59 -58
  45. package/dist/plugin/skills/do/unit-tests.md +1 -1
  46. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -0
  47. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +11 -4
  48. package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +29 -5
  49. package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +3 -1
  50. package/dist/plugin/skills/muggle-pr-followup/contract.md +171 -158
  51. package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +221 -101
  52. package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +2 -2
  53. package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -1
  54. package/dist/plugin/skills/muggle-pr-followup/reconcile.md +9 -5
  55. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +191 -175
  56. package/dist/plugin/skills/muggle-pr-followup/stop.md +31 -0
  57. package/dist/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +41 -227
  58. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +130 -129
  59. package/dist/plugin/skills/muggle-test-prepare/steps/start-services.md +2 -2
  60. package/dist/release-manifest.json +4 -4
  61. package/package.json +7 -6
  62. package/plugin/.claude-plugin/plugin.json +1 -1
  63. package/plugin/.cursor-plugin/plugin.json +1 -1
  64. package/plugin/agents/test-prepare-runner.md +47 -0
  65. package/plugin/agents/visual-walkthrough-builder.md +58 -0
  66. package/plugin/hooks/README.md +11 -2
  67. package/plugin/hooks/hooks.json +7 -1
  68. package/plugin/scripts/gc-state.sh +66 -0
  69. package/plugin/scripts/guard-run-job-object.ps1 +256 -0
  70. package/plugin/scripts/guard-run.mjs +327 -0
  71. package/plugin/scripts/guardrail-record-tests.sh +5 -4
  72. package/plugin/scripts/guardrails.mjs +6 -3
  73. package/plugin/scripts/pr-watch-guards.sh +46 -0
  74. package/plugin/skills/CLAUDE.md +39 -36
  75. package/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +3 -3
  76. package/plugin/skills/_shared/vcs/CLAUDE.md +2 -1
  77. package/plugin/skills/_shared/vcs/common/branch-standing.md +19 -0
  78. package/plugin/skills/_shared/vcs/common/ci-rollup.md +16 -0
  79. package/plugin/skills/_shared/vcs/common/push-to-branch.md +16 -0
  80. package/{dist/plugin/skills/_shared/vcs/github → plugin/skills/_shared/vcs/common}/verify-working-tree.md +4 -4
  81. package/plugin/skills/_shared/vcs/github/pr-edit.md +2 -0
  82. package/plugin/skills/_shared/vcs/github/reply-line-comment.md +2 -0
  83. package/plugin/skills/_shared/vcs/github/signed-commits.md +28 -0
  84. package/plugin/skills/_shared/vcs/github/top-level-comment.md +2 -0
  85. package/plugin/skills/_shared/vcs/github/unresolved-threads.md +39 -39
  86. package/plugin/skills/_shared/vcs/github.md +5 -2
  87. package/plugin/skills/_shared/vcs/gitlab/loop-user-identity.md +1 -1
  88. package/plugin/skills/_shared/vcs/gitlab/mr-create.md +1 -1
  89. package/plugin/skills/_shared/vcs/gitlab/mr-discussions.md +1 -1
  90. package/plugin/skills/_shared/vcs/gitlab/mr-metadata.md +2 -2
  91. package/plugin/skills/_shared/vcs/gitlab/mr-pipeline.md +1 -1
  92. package/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +1 -1
  93. package/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +1 -1
  94. package/plugin/skills/_shared/vcs/gitlab/signed-commits.md +15 -0
  95. package/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +2 -2
  96. package/plugin/skills/_shared/vcs/gitlab.md +6 -3
  97. package/plugin/skills/_shared/vcs/post-signature.md +31 -0
  98. package/plugin/skills/do/build.md +58 -56
  99. package/plugin/skills/do/open-prs/forward.md +85 -84
  100. package/plugin/skills/do/open-prs/update.md +3 -3
  101. package/plugin/skills/do/per-comment-replies.md +2 -2
  102. package/plugin/skills/do/resolve-conflicts.md +59 -58
  103. package/plugin/skills/do/unit-tests.md +1 -1
  104. package/plugin/skills/muggle-pr-followup/CLAUDE.md +1 -0
  105. package/plugin/skills/muggle-pr-followup/SKILL.md +11 -4
  106. package/plugin/skills/muggle-pr-followup/arm-watcher.md +29 -5
  107. package/plugin/skills/muggle-pr-followup/bootstrap.md +3 -1
  108. package/plugin/skills/muggle-pr-followup/contract.md +171 -158
  109. package/plugin/skills/muggle-pr-followup/evals/evals.json +221 -101
  110. package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +2 -2
  111. package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -1
  112. package/plugin/skills/muggle-pr-followup/reconcile.md +9 -5
  113. package/plugin/skills/muggle-pr-followup/state-schemas.md +191 -175
  114. package/plugin/skills/muggle-pr-followup/stop.md +31 -0
  115. package/plugin/skills/muggle-pr-visual-walkthrough/SKILL.md +41 -227
  116. package/plugin/skills/muggle-test-prepare/SKILL.md +130 -129
  117. package/plugin/skills/muggle-test-prepare/steps/start-services.md +2 -2
  118. package/dist/plugin/skills/_shared/vcs/github/push-to-branch.md +0 -10
  119. package/plugin/skills/_shared/vcs/github/push-to-branch.md +0 -10
@@ -1,101 +1,221 @@
1
- {
2
- "skill_name": "muggle-pr-followup",
3
- "notes": "These evals test the TICK DECISION behavior. A real tick needs live GitHub state and the Cron tools, so each prompt hands the model a concrete slot + provider state and asks for the step-by-step plan it would execute; assertions check the plan takes the branch the contract mandates. Run via skill-creator's eval runner — CI does not execute these (ci.yml gates on vitest/check-skill-deps; skill-eval.yml runs gate scenarios sourced from muggle-ai-brain and the routing eval).",
4
- "evals": [
5
- {
6
- "id": 0,
7
- "eval_name": "actionable-threads-dispatch-address-reviews",
8
- "prompt": "Tick muggle-ai-works-pr500 500. The slot is open. The PR has two unresolved, not-outdated review threads whose newest comments are from a human (no muggle-do:bot marker), CI is fully green, and the branch is level with master. Walk me through the plan for this tick.",
9
- "files": [],
10
- "assertions": [
11
- { "name": "computes_actionable_from_live_thread_state", "text": "Plan derives the actionable set from current unresolved-thread state, not from a stored review-id cursor." },
12
- { "name": "classifies_by_loop_marker", "text": "Plan decides a thread is actionable by the absence of the muggle-do:bot marker on the newest comment, never by author login." },
13
- { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's own cron before dispatching, so no tick overlaps the running cycle." },
14
- { "name": "dispatches_address_reviews", "text": "Plan dispatches /muggle-do with an address-reviews directive carrying the PR URL, slug, and owning review ids, then exits." },
15
- { "name": "stays_a_dumb_pipe", "text": "Plan does NOT classify reviews, post replies, resolve threads, or escalate — those belong to /muggle-do." }
16
- ]
17
- },
18
- {
19
- "id": 1,
20
- "eval_name": "reviews-preempt-ci",
21
- "prompt": "Tick muggle-ai-works-pr501 501. The slot is open. There is one unresolved human review thread AND two red required checks on the head SHA. Walk me through the plan for this tick.",
22
- "files": [],
23
- "assertions": [
24
- { "name": "dispatches_address_reviews_only", "text": "Plan dispatches address-reviews for the review thread." },
25
- { "name": "does_not_poll_or_dispatch_ci", "text": "Plan does NOT dispatch fix-ci on this tick — actionable feedback preempts the CI branch entirely." },
26
- { "name": "single_dispatch_then_exit", "text": "Plan performs exactly one dispatch and exits rather than handling both concerns in one tick." }
27
- ]
28
- },
29
- {
30
- "id": 2,
31
- "eval_name": "behind-branch-dispatches-rebase",
32
- "prompt": "Tick muggle-ai-works-pr502 502. The slot is open, there are no unresolved review threads, CI is green. GitHub reports mergeStateStatus BLOCKED and mergeable MERGEABLE, and the compare call shows behind_by 4. Walk me through the plan for this tick.",
33
- "files": [],
34
- "assertions": [
35
- { "name": "reads_staleness_from_compare", "text": "Plan determines the branch is out of date from behind_by on the compare call (commit ancestry)." },
36
- { "name": "does_not_trust_mergeStateStatus", "text": "Plan does NOT conclude the branch is current just because mergeStateStatus reads BLOCKED rather than BEHIND." },
37
- { "name": "dispatches_rebase", "text": "Plan dispatches /muggle-do with a rebase directive (PR URL + slug, no review ids, no check names)." },
38
- { "name": "keys_dedup_on_head_and_base_pair", "text": "Plan keys the rebase attempt/escalation lookup on the head..base_tip pair, not the head SHA alone." }
39
- ]
40
- },
41
- {
42
- "id": 3,
43
- "eval_name": "red-ci-within-budget-dispatches-fix-ci",
44
- "prompt": "Tick muggle-ai-works-pr503 503. The slot is open, no unresolved review threads, branch is level with its base. Two checks are red on the head SHA. ci_fix_attempts for that SHA is 1 and the SHA is not in ci_escalated_shas. Walk me through the plan for this tick.",
45
- "files": [],
46
- "assertions": [
47
- { "name": "checks_attempt_budget", "text": "Plan verifies ci_fix_attempts for the head SHA is under the cap of 3 before dispatching." },
48
- { "name": "checks_escalated_set", "text": "Plan verifies the head SHA is not in ci_escalated_shas." },
49
- { "name": "dispatches_fix_ci_with_check_names", "text": "Plan dispatches /muggle-do with a fix-ci directive carrying the red check names." },
50
- { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's cron before dispatching." }
51
- ]
52
- },
53
- {
54
- "id": 4,
55
- "eval_name": "red-ci-over-budget-idles",
56
- "prompt": "Tick muggle-ai-works-pr504 504. The slot is open, no unresolved review threads, branch is level with its base. Three checks are red on the head SHA, ci_fix_attempts for that SHA is 3, and the SHA is in ci_escalated_shas. Walk me through the plan for this tick.",
57
- "files": [],
58
- "assertions": [
59
- { "name": "does_not_dispatch", "text": "Plan does NOT dispatch fix-ci — the budget is spent and the SHA is already escalated." },
60
- { "name": "treats_as_durable_block", "text": "Plan classifies this idle as blocked pending a human (reason ci_escalated), not as a transient idle." },
61
- { "name": "reminds_owner_at_1m", "text": "Plan emits a one-line owner reminder and keeps the normal 1m cadence — it does not back off, slow down, or stop polling." },
62
- { "name": "no_pr_side_post", "text": "Plan does NOT post anything to the PR for a blocked tick; the reminder goes to the loop owner in-session." }
63
- ]
64
- },
65
- {
66
- "id": 5,
67
- "eval_name": "merged-pr-finalizes-without-respawn",
68
- "prompt": "Tick muggle-ai-works-pr505 505. Refreshing the PR shows state MERGED. The slot on disk still says open. Walk me through the plan for this tick.",
69
- "files": [],
70
- "assertions": [
71
- { "name": "finalizes_the_slot", "text": "Plan finalizes the slot: marks it terminal, writes result.md, logs, and unschedules this watcher's cron." },
72
- { "name": "does_not_respawn", "text": "Plan does NOT respawn or re-arm a watcher — a terminal PR needs none." },
73
- { "name": "hands_off_terminal_wrapup", "text": "Plan hands the terminal wrap-up to /muggle-do post-merge cleanup as the turn's last action." },
74
- { "name": "unschedules_recorded_id_first", "text": "Plan cancels the cron by the id recorded in cron.json first, falling back to a CronList match — so teardown works even after CronList has gone blind." }
75
- ]
76
- },
77
- {
78
- "id": 6,
79
- "eval_name": "stale-queued-fire-does-not-refinalize",
80
- "prompt": "Tick muggle-ai-works-pr506 506. Reading the slot, prs.json already records state merged and result.md exists — this fire was queued before the cron was cancelled. Walk me through the plan for this tick.",
81
- "files": [],
82
- "assertions": [
83
- { "name": "detects_stale_fire", "text": "Plan recognises this as a stale queued fire from the on-disk terminal state before fetching anything." },
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
- { "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
- ]
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
- ]
99
- }
100
- ]
101
- }
1
+ {
2
+ "skill_name": "muggle-pr-followup",
3
+ "notes": "These evals test the TICK DECISION behavior plus the recovery/stop substrate. A real tick needs live GitHub state and the Cron tools, so each prompt hands the model a concrete slot + provider state and asks for the step-by-step plan it would execute; assertions check the plan takes the branch the contract mandates. Cases 0–10 cover per-tick dispatch/idle decisions; cases 11–17 cover cron-delivered ticks handing back to the token-free monitor, the owner stop switch, and the stopped-slot / kill-file absorb that makes an unreachable orphaned cron inert. Run via skill-creator's eval runner — CI does not execute these (ci.yml gates on vitest/check-skill-deps; skill-eval.yml runs gate scenarios sourced from muggle-ai-brain and the routing eval).",
4
+ "evals": [
5
+ {
6
+ "id": 0,
7
+ "eval_name": "actionable-threads-dispatch-address-reviews",
8
+ "prompt": "Tick muggle-ai-works-pr500 500. The slot is open. The PR has two unresolved, not-outdated review threads whose newest comments are from a human (no muggle-do:bot marker), CI is fully green, and the branch is level with master. Walk me through the plan for this tick.",
9
+ "files": [],
10
+ "assertions": [
11
+ { "name": "computes_actionable_from_live_thread_state", "text": "Plan derives the actionable set from current unresolved-thread state, not from a stored review-id cursor." },
12
+ { "name": "classifies_by_loop_marker", "text": "Plan decides a thread is actionable by the absence of the muggle-do:bot marker on the newest comment, never by author login." },
13
+ { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's own cron before dispatching, so no tick overlaps the running cycle." },
14
+ { "name": "dispatches_address_reviews", "text": "Plan dispatches /muggle-do with an address-reviews directive carrying the PR URL, slug, and owning review ids, then exits." },
15
+ { "name": "stays_a_dumb_pipe", "text": "Plan does NOT classify reviews, post replies, resolve threads, or escalate — those belong to /muggle-do." }
16
+ ]
17
+ },
18
+ {
19
+ "id": 1,
20
+ "eval_name": "reviews-preempt-ci",
21
+ "prompt": "Tick muggle-ai-works-pr501 501. The slot is open. There is one unresolved human review thread AND two red required checks on the head SHA. Walk me through the plan for this tick.",
22
+ "files": [],
23
+ "assertions": [
24
+ { "name": "dispatches_address_reviews_only", "text": "Plan dispatches address-reviews for the review thread." },
25
+ { "name": "does_not_poll_or_dispatch_ci", "text": "Plan does NOT dispatch fix-ci on this tick — actionable feedback preempts the CI branch entirely." },
26
+ { "name": "single_dispatch_then_exit", "text": "Plan performs exactly one dispatch and exits rather than handling both concerns in one tick." }
27
+ ]
28
+ },
29
+ {
30
+ "id": 2,
31
+ "eval_name": "behind-branch-dispatches-rebase",
32
+ "prompt": "Tick muggle-ai-works-pr502 502. The slot is open, there are no unresolved review threads, CI is green. GitHub reports mergeStateStatus BLOCKED and mergeable MERGEABLE, and the compare call shows behind_by 4. Walk me through the plan for this tick.",
33
+ "files": [],
34
+ "assertions": [
35
+ { "name": "reads_staleness_from_compare", "text": "Plan determines the branch is out of date from behind_by on the compare call (commit ancestry)." },
36
+ { "name": "does_not_trust_mergeStateStatus", "text": "Plan does NOT conclude the branch is current just because mergeStateStatus reads BLOCKED rather than BEHIND." },
37
+ { "name": "dispatches_rebase", "text": "Plan dispatches /muggle-do with a rebase directive (PR URL + slug, no review ids, no check names)." },
38
+ { "name": "keys_dedup_on_head_and_base_pair", "text": "Plan keys the rebase attempt/escalation lookup on the head..base_tip pair, not the head SHA alone." }
39
+ ]
40
+ },
41
+ {
42
+ "id": 3,
43
+ "eval_name": "red-ci-within-budget-dispatches-fix-ci",
44
+ "prompt": "Tick muggle-ai-works-pr503 503. The slot is open, no unresolved review threads, branch is level with its base. Two checks are red on the head SHA. ci_fix_attempts for that SHA is 1 and the SHA is not in ci_escalated_shas. Walk me through the plan for this tick.",
45
+ "files": [],
46
+ "assertions": [
47
+ { "name": "checks_attempt_budget", "text": "Plan verifies ci_fix_attempts for the head SHA is under the cap of 3 before dispatching." },
48
+ { "name": "checks_escalated_set", "text": "Plan verifies the head SHA is not in ci_escalated_shas." },
49
+ { "name": "dispatches_fix_ci_with_check_names", "text": "Plan dispatches /muggle-do with a fix-ci directive carrying the red check names." },
50
+ { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's cron before dispatching." }
51
+ ]
52
+ },
53
+ {
54
+ "id": 4,
55
+ "eval_name": "red-ci-over-budget-idles",
56
+ "prompt": "Tick muggle-ai-works-pr504 504. The slot is open, no unresolved review threads, branch is level with its base. Three checks are red on the head SHA, ci_fix_attempts for that SHA is 3, and the SHA is in ci_escalated_shas. Walk me through the plan for this tick.",
57
+ "files": [],
58
+ "assertions": [
59
+ { "name": "does_not_dispatch", "text": "Plan does NOT dispatch fix-ci — the budget is spent and the SHA is already escalated." },
60
+ { "name": "treats_as_durable_block", "text": "Plan classifies this idle as blocked pending a human (reason ci_escalated), not as a transient idle." },
61
+ { "name": "reminds_owner_at_1m", "text": "Plan emits a one-line owner reminder and keeps the normal 1m cadence — it does not back off, slow down, or stop polling." },
62
+ { "name": "no_pr_side_post", "text": "Plan does NOT post anything to the PR for a blocked tick; the reminder goes to the loop owner in-session." }
63
+ ]
64
+ },
65
+ {
66
+ "id": 5,
67
+ "eval_name": "merged-pr-finalizes-without-respawn",
68
+ "prompt": "Tick muggle-ai-works-pr505 505. Refreshing the PR shows state MERGED. The slot on disk still says open. Walk me through the plan for this tick.",
69
+ "files": [],
70
+ "assertions": [
71
+ { "name": "finalizes_the_slot", "text": "Plan finalizes the slot: marks it terminal, writes result.md, logs, and unschedules this watcher's cron." },
72
+ { "name": "does_not_respawn", "text": "Plan does NOT respawn or re-arm a watcher — a terminal PR needs none." },
73
+ { "name": "hands_off_terminal_wrapup", "text": "Plan hands the terminal wrap-up to /muggle-do post-merge cleanup as the turn's last action." },
74
+ { "name": "unschedules_recorded_id_first", "text": "Plan cancels the cron by the id recorded in cron.json first, falling back to a CronList match — so teardown works even after CronList has gone blind." }
75
+ ]
76
+ },
77
+ {
78
+ "id": 6,
79
+ "eval_name": "stale-queued-fire-does-not-refinalize",
80
+ "prompt": "Tick muggle-ai-works-pr506 506. Reading the slot, prs.json already records state merged and result.md exists — this fire was queued before the cron was cancelled. Walk me through the plan for this tick.",
81
+ "files": [],
82
+ "assertions": [
83
+ { "name": "detects_stale_fire", "text": "Plan recognises this as a stale queued fire from the on-disk terminal state before fetching anything." },
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
+ { "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
+ ]
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
+ ]
99
+ },
100
+ {
101
+ "id": 8,
102
+ "eval_name": "arm-monitor-wakes-on-settled-red-ci",
103
+ "prompt": "Arm the watch for muggle-ai-works-pr508 508 after a clean drain (no reviews, no unresolved threads, checks green at seed time). Describe the watch.sh monitor loop you would write and what makes it wake the session.",
104
+ "files": [],
105
+ "assertions": [
106
+ { "name": "polls_check_run_rollup", "text": "The loop polls the head SHA's check-run rollup each iteration, not just reviews and threads." },
107
+ { "name": "wakes_only_on_settled_red", "text": "The loop wakes the session when the checks have settled with a failure (no check pending and one or more in the fail bucket) — it does NOT wake while any check is still pending." },
108
+ { "name": "dedups_ci_on_head_sha", "text": "The loop dedups the CI wake on the head SHA (fires once per red head) rather than on a monotonic id, because the check rollup is non-monotonic and resets on each push." },
109
+ { "name": "stays_a_dumb_pipe", "text": "The loop only signals that CI went red and hands off to a tick; it does NOT apply the ci_fix_attempts budget, classify the failure, or dispatch fix-ci itself — those belong to the tick and /muggle-do." },
110
+ { "name": "self_terminates_not_unbounded", "text": "The loop is self-terminating, not a bare while-true: it claims the slot by writing its PID to watch.pid, and each iteration exits when superseded by a newer arm (watch.pid changed) or past the lifetime cap — so it cannot keep polling forever after its session ends." }
111
+ ]
112
+ },
113
+ {
114
+ "id": 9,
115
+ "eval_name": "outdated-thread-with-unanswered-question-still-actionable",
116
+ "prompt": "Tick muggle-ai-works-pr509 509. The slot is open. The PR has one unresolved review thread whose newest comment is a human question with no muggle-do:bot marker, but GitHub reports isOutdated=true on the thread because a later push moved the line it was anchored to. CI is green and the branch is level with master. Walk me through the plan for this tick.",
117
+ "files": [],
118
+ "assertions": [
119
+ { "name": "treats_outdated_thread_as_actionable", "text": "Plan marks the thread actionable even though isOutdated=true — the gates are resolution state and the loop marker, never outdatedness." },
120
+ { "name": "does_not_skip_on_isOutdated", "text": "Plan does NOT skip or idle the thread on the grounds that its anchored line moved; the unanswered question survives the line move and must be surfaced." },
121
+ { "name": "dispatches_address_reviews", "text": "Plan dispatches /muggle-do with an address-reviews directive for the thread's owning review id, rather than idling." },
122
+ { "name": "leaves_mootness_to_mdo", "text": "Plan leaves the judgment of whether the moved code already mooted the concern to /muggle-do's reply — it is not a reason for the watcher to filter the thread out." }
123
+ ]
124
+ },
125
+ {
126
+ "id": 10,
127
+ "eval_name": "outdated-thread-loop-already-replied-stays-quiet",
128
+ "prompt": "Tick muggle-ai-works-pr510 510. The slot is open. The PR has one unresolved review thread with isOutdated=true whose newest comment carries the muggle-do:bot marker — the loop has already replied. CI is green and the branch is level with master. Walk me through the plan for this tick.",
129
+ "files": [],
130
+ "assertions": [
131
+ { "name": "marker_makes_it_non_actionable", "text": "Plan treats the thread as non-actionable because its newest comment carries the muggle-do:bot marker — the marker rule holds regardless of outdatedness." },
132
+ { "name": "no_dispatch_no_echo", "text": "Plan does NOT dispatch address-reviews; re-dispatching would echo the loop's own reply." },
133
+ { "name": "idles_transiently", "text": "Plan idles this tick as a transient idle (green, branch current, awaiting the next human comment) and keeps the 1m cadence — it is not a durable human-block." }
134
+ ]
135
+ },
136
+ {
137
+ "id": 11,
138
+ "eval_name": "cron-delivered-idle-with-live-monitor-cancels-duplicate",
139
+ "prompt": "Tick muggle-ai-works-pr520 520 — this fire was delivered by a /loop recovery cron, not a monitor wake. The slot is open, watch-heartbeat was touched 40 seconds ago, there are no unresolved review threads, CI is green, and the branch is level with master. Walk me through the plan for this tick.",
140
+ "files": [],
141
+ "assertions": [
142
+ { "name": "recognises_cron_delivered_fire", "text": "Plan recognises this tick was delivered by a /loop recovery cron and therefore runs the hand-back-to-monitor step — a monitor wake would skip it." },
143
+ { "name": "reads_heartbeat_as_live_monitor", "text": "Plan reads watch-heartbeat mtime, sees it is fresh (within 3 minutes), and concludes a live monitor already owns the 1m cadence." },
144
+ { "name": "cancels_duplicate_cron", "text": "Plan cancels this cron per cancel-cron.md (recorded-id first) because it is a duplicate poller — there is nothing to arm." },
145
+ { "name": "never_recreates_cron_or_double_arms", "text": "Plan does NOT CronCreate a recurring cron and does NOT start a second monitor over the live one; exactly one poller (the monitor) remains." }
146
+ ]
147
+ },
148
+ {
149
+ "id": 12,
150
+ "eval_name": "cron-delivered-idle-with-dead-monitor-rearms-monitor",
151
+ "prompt": "Tick muggle-ai-works-pr521 521 — this fire was delivered by a /loop recovery cron. The slot is open, watch-heartbeat is missing (the last monitor died), there are no unresolved review threads, CI is green, and the branch is level with master. Walk me through the plan for this tick.",
152
+ "files": [],
153
+ "assertions": [
154
+ { "name": "detects_dead_monitor_from_beacon", "text": "Plan reads watch-heartbeat, finds it missing or older than 3 minutes, and concludes the monitor is dead and this cron has become the primary poller." },
155
+ { "name": "finishes_arming_a_monitor", "text": "Plan finishes the arm-watcher sequence — seed the watermark to the ids this tick itself read (its own observed max, not a fresh post-tick fetch), start the persistent labeled monitor — using this tick as the drain." },
156
+ { "name": "cancels_cron_after_arming", "text": "Plan then cancels this cron per cancel-cron.md and appends a re-armed (monitor restored) line to followup.log, so the 1m cadence lives in the token-free monitor rather than a per-minute model turn." },
157
+ { "name": "never_rearms_with_a_cron", "text": "Plan does NOT restore the poller by creating a recurring /loop cron; the cron is only ever a one-shot recovery delivery." }
158
+ ]
159
+ },
160
+ {
161
+ "id": 13,
162
+ "eval_name": "reconcile-rearms-silent-open-watcher-with-monitor",
163
+ "prompt": "Reconcile the slots. muggle-ai-works-pr522 522 is open (no result.md), but its watch-heartbeat is 40 minutes stale and the newest followup.log line is 40 minutes old — the watcher stopped silently (a crashed cycle that never respawned). The global kill file is absent. Walk me through the plan for this slot.",
164
+ "files": [],
165
+ "assertions": [
166
+ { "name": "classifies_silent_watcher_from_freshest_beacon", "text": "Plan judges the watcher dead because the freshest beacon (watch-heartbeat mtime or newest followup.log line) is older than the staleness window." },
167
+ { "name": "rearms_via_arm_watcher_monitor", "text": "Plan re-arms per arm-watcher.md — drain tick, watermark seed, persistent monitor — restoring a token-free poller." },
168
+ { "name": "never_rearms_with_cron", "text": "Plan does NOT re-arm by creating a recurring /loop cron; the 1m cadence belongs in the monitor and a cron fire would be a full model turn." },
169
+ { "name": "would_skip_under_killfile", "text": "Plan notes it would skip re-arming entirely if polling.disabled existed — here it is absent, so it proceeds." }
170
+ ]
171
+ },
172
+ {
173
+ "id": 14,
174
+ "eval_name": "stop-single-slot-teardown-and-stopped-rename",
175
+ "prompt": "The owner runs stop for muggle-ai-works-pr523 523 (one slot). The slot has a live monitor, a recorded cron id in cron.json, and on Windows an inner bash watch.sh grandchild. Walk me through the plan.",
176
+ "files": [],
177
+ "assertions": [
178
+ { "name": "stops_monitor_including_windows_grandchild", "text": "Plan TaskStops the monitor AND kills the surviving bash <slug>/watch.sh grandchild (enumerated by command line) that outlives TaskStop on Windows — otherwise it keeps touching watch-heartbeat and its open handles block the rename." },
179
+ { "name": "cancels_cron_recorded_id_first", "text": "Plan cancels the cron per cancel-cron.md, recorded-id first with a CronList-match fallback, and admits a cron neither lookup reaches is orphaned but inert." },
180
+ { "name": "renames_slot_to_stopped", "text": "Plan renames the slot dir to <slug>.stopped — the load-bearing act the tick absorb, reconcile, and auto-track all key on to stay away." },
181
+ { "name": "no_global_killfile_for_single_slot", "text": "Plan does NOT write the global polling.disabled kill file for a scoped single-slot stop, and writes no result.md — stopping is resumable, not finalizing." }
182
+ ]
183
+ },
184
+ {
185
+ "id": 15,
186
+ "eval_name": "stop-everything-writes-global-killfile",
187
+ "prompt": "The owner runs stop with no slug — take every watcher down. Several slots are open with live monitors and recorded crons. Walk me through the plan.",
188
+ "files": [],
189
+ "assertions": [
190
+ { "name": "stops_and_renames_every_open_slot", "text": "Plan stops each open slot (monitor + Windows grandchild + cron) and renames each to <slug>.stopped." },
191
+ { "name": "writes_global_killfile", "text": "Plan writes the global kill file ~/.muggle-ai/muggle-do/polling.disabled so any surviving orphaned cron fire absorbs itself before resolving a slot, and reconcile refuses to re-arm while it exists." },
192
+ { "name": "stopping_is_not_finalizing", "text": "Plan writes no result.md — a stopped slot stays open on the provider and is resumable by renaming <slug>.stopped back and re-arming." },
193
+ { "name": "honest_about_orphan_floor", "text": "Plan reports an orphaned cron honestly — it may keep firing one-line absorbs until its host session ends, which is the harness's floor, not a teardown failure." }
194
+ ]
195
+ },
196
+ {
197
+ "id": 16,
198
+ "eval_name": "stopped-slot-absorb-before-any-io",
199
+ "prompt": "Tick agent-pin-conversions 137 — this fire was delivered by an orphaned /loop cron whose handle a compaction severed. On disk the live slot is gone and only agent-pin-conversions.stopped exists (the owner ran stop). Walk me through the plan for this tick.",
200
+ "files": [],
201
+ "assertions": [
202
+ { "name": "absorbs_before_any_fetch_or_write", "text": "Plan checks for the .stopped slot (and the polling.disabled kill file) at the very top of Step 0, before any GitHub fetch, state write, or log line." },
203
+ { "name": "one_line_absorb_then_exit", "text": "Plan outputs a single line (stopped: <slug> — absorbed) and exits — no fetch, no state write, no followup.log line, no dispatch." },
204
+ { "name": "does_not_resurrect_or_guess_delete", "text": "Plan does NOT recreate the slot, re-arm a monitor, or delete cron ids CronList surfaces for other slugs — those belong to other live watchers." },
205
+ { "name": "absorb_caps_the_orphan", "text": "Plan recognises this absorb gate is exactly what caps an unreachable orphaned cron at one harmless line per fire until its session dies." }
206
+ ]
207
+ },
208
+ {
209
+ "id": 17,
210
+ "eval_name": "bootstrap-clears-killfile-on-explicit-url",
211
+ "prompt": "Bootstrap a watcher from the URL https://github.com/multiplex-ai/muggle-ai-works/pull/524. The global polling.disabled kill file is present from an earlier stop-everything. Walk me through the plan.",
212
+ "files": [],
213
+ "assertions": [
214
+ { "name": "clears_killfile_as_first_act", "text": "Plan deletes the polling.disabled kill file during bootstrap, treating a pasted PR URL as an explicit request to watch that overrides a prior stop-everything." },
215
+ { "name": "surfaces_the_clear_in_one_line", "text": "Plan says in one line that it cleared the kill file, so the state change is visible." },
216
+ { "name": "then_seeds_and_arms", "text": "Plan then seeds the slot and arms the watch normally — leaving the kill file in place would make every tick of the new watch absorb itself stillborn." },
217
+ { "name": "only_bootstrap_clears_it", "text": "Plan notes only bootstrap (an explicit URL) clears the kill file; auto-track and reconcile never do." }
218
+ ]
219
+ }
220
+ ]
221
+ }
@@ -6,7 +6,7 @@ Posted via `gh api .../comments/<comment-id>/replies` per cycle, one per line co
6
6
  Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
7
7
 
8
8
  <!-- muggle-do:bot -->
9
- 🤖 _Automated reply from muggle-do._
9
+ 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
10
10
  ```
11
11
 
12
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. The trailing signature block — defined in [`../../_shared/pr-followup-helpers/loop-signature.md`](../../_shared/pr-followup-helpers/loop-signature.md) — is mandatory; its `<!-- muggle-do:bot -->` marker is what identifies the comment as loop-authored.
@@ -19,5 +19,5 @@ When an actionable review has a non-empty body but zero line comments, GitHub ha
19
19
  Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
20
20
 
21
21
  <!-- muggle-do:bot -->
22
- 🤖 _Automated reply from muggle-do._
22
+ 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
23
23
  ```
@@ -9,7 +9,7 @@ These threads are addressed and still open — mark them resolved if satisfied,
9
9
  - ...
10
10
 
11
11
  <!-- muggle-do:bot -->
12
- 🤖 _Automated reply from muggle-do._
12
+ 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
13
13
  ```
14
14
 
15
15
  If no such thread exists, no comment is posted (silent). The trailing signature block ([`loop-signature.md`](../../_shared/pr-followup-helpers/loop-signature.md)) keeps the loop from later mistaking its own reminder for a human comment.
@@ -14,6 +14,10 @@ Three ways in, all running the same procedure:
14
14
 
15
15
  Recover-don't-seed holds on every trigger: a session-start run still never seeds a first watcher (see Invariants).
16
16
 
17
+ ## Watching is session-scoped
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. The recovery point is the **next session start**, where the `reconcile-stale-watchers.sh` hook nudges this sweep and Step 3.6 re-arms every open slot whose watcher is dead. The cost is that a review landing while no session runs waits until the next one starts — the accepted trade for never acting context-blind.
20
+
17
21
  ## Input
18
22
 
19
23
  `$ARGUMENTS` is `reconcile` (or `sweep`), optionally followed by a `<slug>` to scope the sweep to one slot.
@@ -22,7 +26,7 @@ Recover-don't-seed holds on every trigger: a session-start run still never seeds
22
26
 
23
27
  ### Step 1 — Enumerate slots
24
28
 
25
- List `~/.muggle-ai/muggle-do/sessions/*/` dirs that contain a `prs.json`. Skip any that already have a `result.md` — those are finalized. Scope to a single `<slug>` if the arg gave one.
29
+ 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.
26
30
 
27
31
  ### Step 2 — Refresh live state
28
32
 
@@ -46,13 +50,13 @@ This reaches only crons `CronList` still enumerates. A cron that both survived a
46
50
 
47
51
  ### Step 3.6 — Re-arm a silently-stopped open watcher
48
52
 
49
- 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 cron and no next tick — the poller stops silently. This step re-arms it.
53
+ 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.
50
54
 
51
- For each candidate still `open` after Step 3, check when its watcher last ticked — the newest ISO-8601 line in `followup.log` (or `cron.json.recorded_at` if the log is empty). If that is **older than 15 minutes** (comfortably beyond the `1m` cadence, so a live cron would have logged many times inside the window), the poller is gone → re-arm:
55
+ For each candidate still `open` after Step 3, 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:
52
56
 
53
- - `CronCreate` a recurring cron (call the **tool**, never a shell) with `cron: "* * * * *"` and prompt `/muggle:muggle-pr-followup <slug> <n>`, then record its id and `interval: "1m"` to `cron.json` (whole-file rewrite per [`state-schemas.md`](state-schemas.md#cronjson)). Append a `re-armed (silent watcher)` line to the slot's `followup.log`.
57
+ - 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)).
54
58
 
55
- A fresh log line (within the window) means the cron is alive — even one `CronList` has gone blind to — so this step leaves it untouched; re-arming can never double an already-live poller. This recovers only a slot that was **already being watched**; a PR that never had a watcher is seeded by [`auto-track.md`](auto-track.md) / bootstrap, not here.
59
+ A fresh beacon (within the window) means the poller is alive — a quiet monitor still touching its heartbeat, or a cron `CronList` has gone blind to (which hands itself back to a monitor on its next tick — [`contract.md`](contract.md) Step 7.5) — so this step leaves it untouched; re-arming can never double an already-live poller. This recovers only a slot that was **already being watched**; a PR that never had a watcher is seeded by [`auto-track.md`](auto-track.md) / bootstrap, not here.
56
60
 
57
61
  ### Step 4 — Report
58
62