@muggleai/works 5.9.0 → 5.11.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 (171) hide show
  1. package/README.md +8 -0
  2. package/dist/{chunk-LVUJ4E62.js → chunk-MSPMXBO5.js} +638 -468
  3. package/dist/{chunk-5PYK7DWB.js → chunk-WUMAJOBT.js} +17 -6
  4. package/dist/cli.js +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  7. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  8. package/dist/plugin/README.md +19 -13
  9. package/dist/plugin/agents/test-prepare-runner.md +4 -1
  10. package/dist/plugin/agents/visual-walkthrough-builder.md +12 -5
  11. package/dist/plugin/commands/mtest.md +1 -1
  12. package/dist/plugin/hooks/README.md +15 -3
  13. package/dist/plugin/hooks/hooks.json +79 -0
  14. package/dist/plugin/scripts/ensure-electron-app.sh +62 -47
  15. package/dist/plugin/scripts/guardrail-classify-gate.sh +23 -0
  16. package/dist/plugin/scripts/guardrail-debug-path-gate.sh +41 -0
  17. package/dist/plugin/scripts/guardrail-pr-terminal.sh +6 -2
  18. package/dist/plugin/scripts/guardrail-record-stage-read.sh +21 -0
  19. package/dist/plugin/scripts/guardrail-record-stage-signals.sh +23 -0
  20. package/dist/plugin/scripts/guardrail-record-tests.sh +11 -4
  21. package/dist/plugin/scripts/guardrail-report-format.sh +9 -5
  22. package/dist/plugin/scripts/guardrail-skill-stages.sh +35 -0
  23. package/dist/plugin/scripts/guardrail-stage-gate.sh +43 -0
  24. package/dist/plugin/scripts/guardrail-walkthrough-gate.sh +41 -0
  25. package/dist/plugin/scripts/guardrails.mjs +474 -80
  26. package/dist/plugin/scripts/pr-watch-events.sh +116 -0
  27. package/dist/plugin/scripts/pr-watch-guards.sh +8 -2
  28. package/dist/plugin/scripts/pr-watch-loop.sh +254 -0
  29. package/dist/plugin/scripts/reconcile-stale-watchers.sh +66 -18
  30. package/dist/plugin/scripts/sign-body.sh +88 -0
  31. package/dist/plugin/skills/CLAUDE.md +16 -0
  32. package/dist/plugin/skills/_shared/post-merge-cleanup.md +83 -3
  33. package/dist/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +6 -8
  34. package/dist/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +10 -18
  35. package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -0
  36. package/dist/plugin/skills/_shared/vcs/CLAUDE.md +1 -0
  37. package/dist/plugin/skills/_shared/vcs/github/pr-edit.md +3 -2
  38. package/dist/plugin/skills/_shared/vcs/github/reply-line-comment.md +3 -2
  39. package/dist/plugin/skills/_shared/vcs/github/top-level-comment.md +3 -2
  40. package/dist/plugin/skills/_shared/vcs/gitlab/mr-create.md +4 -1
  41. package/dist/plugin/skills/_shared/vcs/gitlab/mr-edit.md +4 -1
  42. package/dist/plugin/skills/_shared/vcs/gitlab/mr-note.md +4 -1
  43. package/dist/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +4 -1
  44. package/dist/plugin/skills/_shared/vcs/post-signature.md +23 -12
  45. package/dist/plugin/skills/do/open-prs/forward.md +2 -2
  46. package/dist/plugin/skills/do/per-comment-replies.md +2 -15
  47. package/dist/plugin/skills/muggle/SKILL.md +1 -1
  48. package/dist/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  49. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
  50. package/dist/plugin/skills/muggle-pr-followup/SKILL.md +6 -3
  51. package/dist/plugin/skills/muggle-pr-followup/adopt.md +60 -0
  52. package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +10 -14
  53. package/dist/plugin/skills/muggle-pr-followup/auto-track.md +2 -0
  54. package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +63 -1
  55. package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +11 -1
  56. package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +1 -7
  57. package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -4
  58. package/dist/plugin/skills/muggle-pr-followup/reconcile.md +32 -6
  59. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +18 -0
  60. package/dist/plugin/skills/muggle-preferences/SKILL.md +1 -1
  61. package/dist/plugin/skills/muggle-preferences/evals/evals.json +45 -0
  62. package/dist/plugin/skills/muggle-preferences/ops/change-one.md +1 -1
  63. package/dist/plugin/skills/muggle-preferences/ops/configure.md +4 -5
  64. package/dist/plugin/skills/muggle-preferences/ops/list.md +1 -1
  65. package/dist/plugin/skills/muggle-preferences/ops/set.md +3 -4
  66. package/dist/plugin/skills/muggle-preferences/preference-gates/README.md +3 -3
  67. package/dist/plugin/skills/muggle-preferences/preference-gates/autoSelectLocalHost.md +2 -2
  68. package/dist/plugin/skills/muggle-preferences/preference-gates/autoSelectProject.md +1 -1
  69. package/dist/plugin/skills/muggle-preferences/preference-gates/watcherLifetime.md +23 -0
  70. package/dist/plugin/skills/muggle-test/SKILL.md +23 -3
  71. package/dist/plugin/skills/muggle-test/execute-local.md +2 -0
  72. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
  73. package/dist/plugin/skills/muggle-test-import/SKILL.md +1 -1
  74. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +28 -15
  75. package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +1 -1
  76. package/dist/plugin/skills/muggle-test-prepare/steps/confirm-recipe.md +56 -0
  77. package/dist/plugin/skills/muggle-test-prepare/steps/derive-service-graph.md +51 -0
  78. package/dist/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +111 -0
  79. package/dist/plugin/skills/muggle-test-prepare/steps/identify-services.md +4 -2
  80. package/dist/plugin/skills/muggle-test-prepare/steps/readiness-report.md +12 -5
  81. package/dist/plugin/skills/muggle-test-prepare/steps/record-resolution.md +32 -0
  82. package/dist/plugin/skills/muggle-test-prepare/steps/replay-or-learn.md +20 -0
  83. package/dist/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +9 -6
  84. package/dist/plugin/skills/muggle-test-prepare/steps/smoke-test.md +5 -1
  85. package/dist/plugin/skills/muggle-test-prepare/steps/start-services.md +3 -1
  86. package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
  87. package/dist/release-manifest.json +4 -4
  88. package/dist/src-B5OTAZKM.js +1 -0
  89. package/package.json +7 -6
  90. package/plugin/.claude-plugin/plugin.json +1 -1
  91. package/plugin/.cursor-plugin/plugin.json +1 -1
  92. package/plugin/README.md +19 -13
  93. package/plugin/agents/test-prepare-runner.md +4 -1
  94. package/plugin/agents/visual-walkthrough-builder.md +12 -5
  95. package/plugin/commands/mtest.md +1 -1
  96. package/plugin/hooks/README.md +15 -3
  97. package/plugin/hooks/hooks.json +79 -0
  98. package/plugin/scripts/ensure-electron-app.sh +62 -47
  99. package/plugin/scripts/guardrail-classify-gate.sh +23 -0
  100. package/plugin/scripts/guardrail-debug-path-gate.sh +41 -0
  101. package/plugin/scripts/guardrail-pr-terminal.sh +6 -2
  102. package/plugin/scripts/guardrail-record-stage-read.sh +21 -0
  103. package/plugin/scripts/guardrail-record-stage-signals.sh +23 -0
  104. package/plugin/scripts/guardrail-record-tests.sh +11 -4
  105. package/plugin/scripts/guardrail-report-format.sh +9 -5
  106. package/plugin/scripts/guardrail-skill-stages.sh +35 -0
  107. package/plugin/scripts/guardrail-stage-gate.sh +43 -0
  108. package/plugin/scripts/guardrail-walkthrough-gate.sh +41 -0
  109. package/plugin/scripts/guardrails.mjs +474 -80
  110. package/plugin/scripts/pr-watch-events.sh +116 -0
  111. package/plugin/scripts/pr-watch-guards.sh +8 -2
  112. package/plugin/scripts/pr-watch-loop.sh +254 -0
  113. package/plugin/scripts/reconcile-stale-watchers.sh +66 -18
  114. package/plugin/scripts/sign-body.sh +88 -0
  115. package/plugin/skills/CLAUDE.md +16 -0
  116. package/plugin/skills/_shared/post-merge-cleanup.md +83 -3
  117. package/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +6 -8
  118. package/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +10 -18
  119. package/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -0
  120. package/plugin/skills/_shared/vcs/CLAUDE.md +1 -0
  121. package/plugin/skills/_shared/vcs/github/pr-edit.md +3 -2
  122. package/plugin/skills/_shared/vcs/github/reply-line-comment.md +3 -2
  123. package/plugin/skills/_shared/vcs/github/top-level-comment.md +3 -2
  124. package/plugin/skills/_shared/vcs/gitlab/mr-create.md +4 -1
  125. package/plugin/skills/_shared/vcs/gitlab/mr-edit.md +4 -1
  126. package/plugin/skills/_shared/vcs/gitlab/mr-note.md +4 -1
  127. package/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +4 -1
  128. package/plugin/skills/_shared/vcs/post-signature.md +23 -12
  129. package/plugin/skills/do/open-prs/forward.md +2 -2
  130. package/plugin/skills/do/per-comment-replies.md +2 -15
  131. package/plugin/skills/muggle/SKILL.md +1 -1
  132. package/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  133. package/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -1
  134. package/plugin/skills/muggle-pr-followup/SKILL.md +6 -3
  135. package/plugin/skills/muggle-pr-followup/adopt.md +60 -0
  136. package/plugin/skills/muggle-pr-followup/arm-watcher.md +10 -14
  137. package/plugin/skills/muggle-pr-followup/auto-track.md +2 -0
  138. package/plugin/skills/muggle-pr-followup/evals/evals.json +63 -1
  139. package/plugin/skills/muggle-pr-followup/output-templates/help.md +11 -1
  140. package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +1 -7
  141. package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +1 -4
  142. package/plugin/skills/muggle-pr-followup/reconcile.md +32 -6
  143. package/plugin/skills/muggle-pr-followup/state-schemas.md +18 -0
  144. package/plugin/skills/muggle-preferences/SKILL.md +1 -1
  145. package/plugin/skills/muggle-preferences/evals/evals.json +45 -0
  146. package/plugin/skills/muggle-preferences/ops/change-one.md +1 -1
  147. package/plugin/skills/muggle-preferences/ops/configure.md +4 -5
  148. package/plugin/skills/muggle-preferences/ops/list.md +1 -1
  149. package/plugin/skills/muggle-preferences/ops/set.md +3 -4
  150. package/plugin/skills/muggle-preferences/preference-gates/README.md +3 -3
  151. package/plugin/skills/muggle-preferences/preference-gates/autoSelectLocalHost.md +2 -2
  152. package/plugin/skills/muggle-preferences/preference-gates/autoSelectProject.md +1 -1
  153. package/plugin/skills/muggle-preferences/preference-gates/watcherLifetime.md +23 -0
  154. package/plugin/skills/muggle-test/SKILL.md +23 -3
  155. package/plugin/skills/muggle-test/execute-local.md +2 -0
  156. package/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
  157. package/plugin/skills/muggle-test-import/SKILL.md +1 -1
  158. package/plugin/skills/muggle-test-prepare/SKILL.md +28 -15
  159. package/plugin/skills/muggle-test-prepare/steps/check-running.md +1 -1
  160. package/plugin/skills/muggle-test-prepare/steps/confirm-recipe.md +56 -0
  161. package/plugin/skills/muggle-test-prepare/steps/derive-service-graph.md +51 -0
  162. package/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +111 -0
  163. package/plugin/skills/muggle-test-prepare/steps/identify-services.md +4 -2
  164. package/plugin/skills/muggle-test-prepare/steps/readiness-report.md +12 -5
  165. package/plugin/skills/muggle-test-prepare/steps/record-resolution.md +32 -0
  166. package/plugin/skills/muggle-test-prepare/steps/replay-or-learn.md +20 -0
  167. package/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +9 -6
  168. package/plugin/skills/muggle-test-prepare/steps/smoke-test.md +5 -1
  169. package/plugin/skills/muggle-test-prepare/steps/start-services.md +3 -1
  170. package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
  171. package/dist/src-FDK2KQSZ.js +0 -1
@@ -4,6 +4,86 @@ Gated by [`autoCleanup`](../muggle-preferences/preference-gates/autoCleanup.md).
4
4
 
5
5
  On `always`, the steps below run as one pre-authorized sequence (no per-step prompts). Stop on the first failure; do not force.
6
6
 
7
- 1. **Remove the worktree — link-safe.** `git worktree remove {worktreePath}`, only if a worktree was used. A worktree's dependency dir (e.g. `node_modules`) is often a **link** (symlink, or a Windows junction) to a shared tree, not a real copy — and a forced/recursive delete follows the link and wipes that shared target, breaking every worktree. So never `--force`: unlink the dependency link first (remove the link only, using the host OS's unlink), then a plain `git worktree remove {worktreePath}`.
8
- 2. `git branch -d {branch}` — **skip when no worktree was used**: the branch is the user's current live checkout (a bootstrap/auto-track watcher), and the checked-out branch must never be deleted. Then `git push origin --delete {branch}`.
9
- 3. Clear `.muggle-ai/` session folders for this branch's runs and stale `/tmp/muggle-prepare-*.log` files. Cloud results stay.
7
+ **Verify every step, assume none.** Each step states what proves it succeeded. A step whose side effect *usually* happens is not a step that ran — that assumption is how a branch survives a cleanup that reported success. The [report](#report) states verified state, never intent.
8
+
9
+ ## Preconditions
10
+
11
+ Confirm the PR is `MERGED` from provider state. A closed-unmerged PR keeps its branch and worktree: the work never landed, so deleting it destroys it.
12
+
13
+ ## 1. Remove the worktree — link-safe
14
+
15
+ Only if a worktree was used.
16
+
17
+ A worktree's dependency dir (`node_modules`, and nested workspace copies) is often a **link** — a symlink or a Windows junction — to a shared tree rather than a real copy. A forced or recursive delete follows the link and wipes that shared target, breaking every other worktree.
18
+
19
+ 1. **Never `--force`.** It is the one flag that follows links.
20
+ 2. Clear the dependency and build dirs first, nested workspace ones included (`packages/*/node_modules`, `dist`). A plain `git worktree remove` fails with `Directory not empty` while they remain, and the obvious fix for that error is exactly the forbidden flag.
21
+ - A **link** → unlink it first with the host OS's unlink, removing the link only and never its target.
22
+ - A **real directory** → delete it in place.
23
+ Check which it is before deleting; the two are indistinguishable from a listing but not from a `rm -r`.
24
+ 3. Then plain `git worktree remove {worktreePath}`.
25
+ 4. `git worktree prune` to drop the administrative entry when the directory went away out from under git.
26
+
27
+ **Verify:** the path is gone, it no longer appears in `git worktree list`, **and** the shared dependency tree the links pointed at still exists. That last check is the one that catches a link-follow.
28
+
29
+ ## 2. Delete the local branch
30
+
31
+ **Skip entirely when no worktree was used** — the branch is then the user's live checkout, and a checked-out branch must never be deleted.
32
+
33
+ `git branch -d` refuses after a **squash merge**, and always will: squashing mints a new commit carrying the same tree, so the branch tip is never an ancestor of the base. This is not a safety check that failed; it is one that cannot pass.
34
+
35
+ Do not reach for `-D` on faith. Replace the ancestry check with a content check:
36
+
37
+ 1. Confirm the merged content is on the base — files the PR added exist at `origin/<base>`, and anything it removed is absent there.
38
+ 2. Only then `git branch -D {branch}`.
39
+
40
+ **Verify:** the branch is absent from `git branch --list`.
41
+
42
+ ## 3. Delete the remote branch
43
+
44
+ **A provider that auto-deletes the head branch on merge is a setting, not a guarantee.** It can be off for the repo, off for a fork, or simply not fire. Treat auto-delete as something to detect, never as this step having run.
45
+
46
+ 1. Query the ref. Already gone → record it deleted and move on.
47
+ 2. Still present → delete it explicitly.
48
+
49
+ **Verify:** querying the ref returns not-found.
50
+
51
+ ## 4. Clear the session slot
52
+
53
+ The slot is `~/.muggle-ai/muggle-do/sessions/<slug>/` — the home directory, not the project.
54
+
55
+ Clear it only when `prs.json` records a terminal state. A slot for a still-open PR is live state that a watcher and [`reconcile`](../muggle-pr-followup/reconcile.md) both read. The merged PR is the durable record, so the slot's `result.md` is not lost history.
56
+
57
+ **Verify:** the slot directory is gone, and no non-terminal slot was touched.
58
+
59
+ ## 5. Clear this run's prepare artifacts
60
+
61
+ The PID tracker and per-service logs written during environment prep — `/tmp/muggle-test-prepare.json` and `/tmp/muggle-prepare-*.log`.
62
+
63
+ **Only this run's.** Artifacts belonging to another session are not yours to delete; it may be mid-prepare with services running. When the tracker is absent no services are tracked, so any remaining logs are orphaned — report them rather than removing them.
64
+
65
+ Cloud results always stay.
66
+
67
+ **Verify:** this run's artifacts are gone; anything left is named in the report as out of scope.
68
+
69
+ ## Report
70
+
71
+ Print as the last action, one row per step, filled from the verification checks — never from the fact that a command was issued:
72
+
73
+ ```
74
+ Cleanup — <slug> (PR #<n>, merged)
75
+
76
+ | Step | Result |
77
+ |:----------------------|:------------------------------------------------|
78
+ | Worktree removed | ✅ .claude/worktrees/<name> (shared deps intact) |
79
+ | Local branch deleted | ✅ users/<user>/<branch> |
80
+ | Remote branch deleted | ✅ (already gone — provider auto-delete) |
81
+ | Session slot cleared | ✅ <slug> |
82
+ | Prepare artifacts | — none for this run |
83
+ ```
84
+
85
+ Markers: `✅` verified done · `⚠️` done with a caveat worth reading · `❌` not done, with the reason · `—` nothing to do.
86
+
87
+ Every step gets a row. A step that did not run is `❌` plus its reason — never omitted, and never `✅` when its verification did not actually run. A silent gap is worse than a visible one, because it is the version the user ends up believing.
88
+
89
+ State anything deliberately left alone — another session's artifacts, a foreign worktree, an orphaned watcher — on a line below the table, so out-of-scope is declared rather than invisible.
@@ -1,22 +1,20 @@
1
1
  # Loop comment signature
2
2
 
3
- Every GitHub comment `/muggle-do` posts — inline thread replies, top-level reference comments, resolve-reminders — **must** end with the signature block below. It is the only reliable way to tell loop-authored comments from human comments: in single-account workflows the loop posts under the PR author's own identity, so `author.login` cannot distinguish them. Echo-protection and addressed-by-loop classification both depend on this marker.
3
+ Every comment `/muggle-do` posts on either provider — inline thread replies (`gitlab`: discussion notes), top-level reference comments, resolve-reminders — **must** carry the loop marker. It is the only reliable way to tell loop-authored comments from human comments: in single-account workflows the loop posts under the change author's own identity, so the author login cannot distinguish them. Echo-protection and addressed-by-loop classification both depend on this marker.
4
4
 
5
- ## The signature
6
-
7
- Append these two lines as the end of every loop-posted comment body:
5
+ ## The marker
8
6
 
9
7
  ```
10
8
  <!-- muggle-do:bot -->
11
- 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
12
9
  ```
13
10
 
14
- - `<!-- muggle-do:bot -->` — hidden HTML marker; GitHub renders it invisibly and humans never type it. This is the **detection token**. It must stay exactly as written — echo-protection and addressed-by-loop classification read this literal string.
15
- - The visible line is the shared Muggle Works signature ([`../vcs/post-signature.md`](../vcs/post-signature.md)) with `/muggle-do` as the command. It links a reader back to the tool and names the command that posted the comment.
11
+ Hidden HTML: both providers render it invisibly and humans never type it. It must stay exactly as written — echo-protection and addressed-by-loop classification read this literal string.
12
+
13
+ Signing a body with `--mode loop` ([`../vcs/post-signature.md`](../vcs/post-signature.md)) emits the marker above the visible Muggle Works line. Never hand-write either one; a body assembled by hand is the one that silently ships without them.
16
14
 
17
15
  ## Detection
18
16
 
19
17
  - **Loop-authored** — the comment body contains the literal `<!-- muggle-do:bot -->`.
20
18
  - **Human** — the body does not contain the marker.
21
19
 
22
- Classify by the marker, never by `author.login` alone — the login is ambiguous under a shared account. A comment that carries the marker is the loop's own and must never re-trigger a cycle; a comment without it is human intent to act on.
20
+ Classify by the marker, never by the author login alone (`github`: `author.login`, `gitlab`: `author.username`) — the login is ambiguous under a shared account. A comment that carries the marker is the loop's own and must never re-trigger a cycle; a comment without it is human intent to act on.
@@ -1,32 +1,24 @@
1
1
  # Reply routing
2
2
 
3
- GitHub's PR APIs are not uniform across comment types. Route by parent type.
3
+ Reply endpoints are not uniform across comment types. Route by parent type, then run the resolved provider's recipe for that route — resolve the provider once per [`../vcs/detect-vcs.md`](../vcs/detect-vcs.md). The routing decision below is provider-agnostic; the commands live with their tool.
4
4
 
5
5
  ## Line-level review comment (most common)
6
6
 
7
- A comment attached to a specific file:line that belongs to a review thread.
7
+ A comment attached to a specific file:line that belongs to a review thread. Reply in the same thread, so the reply keeps its context:
8
8
 
9
- ```bash
10
- gh api \
11
- --method POST \
12
- -H "Accept: application/vnd.github+json" \
13
- /repos/<owner>/<repo>/pulls/<number>/comments/<comment_id>/replies \
14
- -f body="Done in $(git rev-parse --short HEAD) — renamed \`fooBar\` to \`foo_bar\`."
15
- ```
16
-
17
- The reply lands in the same review thread with `in_reply_to_id = <comment_id>`.
9
+ - `github` — [`../vcs/github/reply-line-comment.md`](../vcs/github/reply-line-comment.md). The reply lands in the thread with `in_reply_to_id = <comment_id>`.
10
+ - `gitlab` — [`../vcs/gitlab/reply-discussion.md`](../vcs/gitlab/reply-discussion.md). The reply is a new note on the discussion.
18
11
 
19
- ## Review body (CHANGES_REQUESTED with no inline comments)
12
+ ## Review body with no inline comments
20
13
 
21
- A reviewer left a summary review with `state: CHANGES_REQUESTED` and a body, but **no** inline comments. GitHub has no "reply to review body" endpoint — post a top-level PR comment that references the review:
14
+ A reviewer left a summary review with a body but **no** inline comments. There is no thread to reply into, so answer with a top-level comment that names the review:
22
15
 
23
- ```bash
24
- gh pr comment <number> --repo <owner>/<repo> --body "Re: review #<review_id> — done in $(git rev-parse --short HEAD)."
25
- ```
16
+ - `github` — [`../vcs/github/top-level-comment.md`](../vcs/github/top-level-comment.md). GitHub has no reply-to-review-body endpoint, which is why this route exists at all.
17
+ - `gitlab` — does not arise. GitLab has no review envelope; every note belongs to a discussion, so it routes as a line-level reply above.
26
18
 
27
19
  ## Failing CI check
28
20
 
29
- No reply. The fix commit IS the response. Include the failing check name in the commit subject:
21
+ No reply on either provider. The fix commit IS the response. Include the failing check name in the commit subject:
30
22
 
31
23
  ```
32
24
  fix(ci): typecheck — narrow type of foo
@@ -36,5 +28,5 @@ fix(ci): lint — remove unused import
36
28
  ## Never
37
29
 
38
30
  - Never post a top-level comment in reply to a line-level comment. It loses thread context.
39
- - Never `gh pr review --comment` for replies — that endpoint is for *new* reviews.
31
+ - Never open a *new* review to carry a reply (`github`: `gh pr review --comment`) — those endpoints are for new reviews, not replies.
40
32
  - Never reply twice to the same comment. The loop marker on each posted reply is the re-entry guard — a thread whose newest comment is loop-marked is no longer actionable, so the next round won't re-reply.
@@ -25,6 +25,7 @@ Resolve without prompting; use as questionnaire defaults:
25
25
  3. Candidate projects — `muggle-remote-project-list`, ranked against the repo's dev URL and the PR title.
26
26
  4. Existing test-user secrets — `muggle-remote-secret-list` per candidate project (`managed_profile_email` / `managed_profile_password`).
27
27
  5. Auth0 tenant for local dev — grep the repo env file for `*AUTH0_DOMAIN*`.
28
+ 6. E2E run instructions — read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists, keyed on this stack's identity. It records startup order, manual steps, and local gotchas. Treat it as read-only input: surface its gotchas rather than re-deriving them, and never re-ask what it already answers. Absent is normal — proceed without it.
28
29
 
29
30
  ## Questions
30
31
 
@@ -5,6 +5,7 @@ Provider-agnostic seam for the watcher and dev-cycle skills. A caller resolves o
5
5
  ## Index
6
6
 
7
7
  - [`detect-vcs.md`](detect-vcs.md) — resolve `github` | `gitlab` from a URL argument or the origin remote.
8
+ - [`post-signature.md`](post-signature.md) — sign every posted body through `scripts/sign-body.sh`; provider-agnostic, required by every recipe below that writes a body.
8
9
  - [`github.md`](github.md) — `gh` / `git` recipe TOC; per-recipe files in [`github/`](github/).
9
10
  - [`gitlab.md`](gitlab.md) — `glab` / `git` recipe TOC; per-recipe files in [`gitlab/`](gitlab/).
10
11
  - `common/` — provider-agnostic entry points (`ci-rollup`, `branch-standing`, `push-to-branch`, `verify-working-tree`), indexed by both provider TOCs.
@@ -3,8 +3,9 @@
3
3
  For `open-prs/update.md` when E2E state flips (passing↔failing) or validation strategy changes.
4
4
 
5
5
  ```bash
6
+ bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command /muggle-do --mode editable < <draft-file> > <signed-file>
6
7
  gh pr edit <pr-number> --repo <owner>/<repo> --title "<new-title>"
7
- gh pr edit <pr-number> --repo <owner>/<repo> --body-file <file>
8
+ gh pr edit <pr-number> --repo <owner>/<repo> --body-file <signed-file>
8
9
  ```
9
10
 
10
- The `<file>` body must end with the Muggle Works signature. Because the description is re-posted on each refresh, strip the old signature (from the `<!-- muggle-works:signature -->` marker to the end) before re-appending it — see [`../post-signature.md`](../post-signature.md).
11
+ Sign with `--mode editable` per [`../post-signature.md`](../post-signature.md) — a description is re-posted on every refresh, and that mode cuts the previous signature before appending so they never stack.
@@ -3,10 +3,11 @@
3
3
  Used by `/muggle-do` per-comment inline replies.
4
4
 
5
5
  ```bash
6
+ body="$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command /muggle-do --mode loop < <draft-file>)"
6
7
  gh api --method POST \
7
8
  -H "Accept: application/vnd.github+json" \
8
9
  repos/<owner>/<repo>/pulls/<n>/comments/<comment-id>/replies \
9
- -f body="<reply-text>"
10
+ -f body="$body"
10
11
  ```
11
12
 
12
- The `<reply-text>` must end with the loop signature block — the `<!-- muggle-do:bot -->` detection marker above the Muggle Works line. See [`../post-signature.md`](../post-signature.md).
13
+ Sign with `--mode loop` per [`../post-signature.md`](../post-signature.md) — the `<!-- muggle-do:bot -->` marker it prefixes is what keeps the loop from re-triggering on its own reply.
@@ -3,7 +3,8 @@
3
3
  For the resolve-reminder stage and any non-threaded notice.
4
4
 
5
5
  ```bash
6
- gh pr comment <pr-number> --repo <owner>/<repo> --body "<text>"
6
+ body="$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command <command> --mode <mode> < <draft-file>)"
7
+ gh pr comment <pr-number> --repo <owner>/<repo> --body "$body"
7
8
  ```
8
9
 
9
- The `<text>` you post must end with the Muggle Works signature — see [`../post-signature.md`](../post-signature.md).
10
+ Sign per [`../post-signature.md`](../post-signature.md). A resolve-reminder takes `--mode loop`, so the loop recognises the reminder as its own on a later tick; a one-shot notice nothing needs to detect takes `--mode plain`.
@@ -3,9 +3,12 @@
3
3
  For `open-prs`. Push the branch first (see [`../common/push-to-branch.md`](../common/push-to-branch.md)), then open the MR.
4
4
 
5
5
  ```bash
6
+ bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command /muggle-do --mode editable < <draft-file> > <signed-file>
6
7
  glab mr create -R <group>/<project> \
7
8
  --source-branch <branch> --target-branch <base> \
8
- --title "<title>" --description "$(cat <file>)"
9
+ --title "<title>" --description "$(cat <signed-file>)"
9
10
  ```
10
11
 
12
+ Sign with `--mode editable` per [`../post-signature.md`](../post-signature.md) — the description this opens the MR with is the same body `mr-edit.md` refreshes later, so it carries the dedup marker from the start.
13
+
11
14
  `glab` prints the created MR's URL on success — capture stdout and store the URL for handoff (the watcher seeds from it, the user gets the link).
@@ -3,6 +3,9 @@
3
3
  For `open-prs/update.md` when E2E state flips (passing↔failing) or validation strategy changes. GitLab calls the body the **description**.
4
4
 
5
5
  ```bash
6
+ bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command /muggle-do --mode editable < <draft-file> > <signed-file>
6
7
  glab mr update <iid> -R <group>/<project> --title "<new-title>"
7
- glab mr update <iid> -R <group>/<project> --description "$(cat <file>)"
8
+ glab mr update <iid> -R <group>/<project> --description "$(cat <signed-file>)"
8
9
  ```
10
+
11
+ Sign with `--mode editable` per [`../post-signature.md`](../post-signature.md) — a description is re-posted on every refresh, and that mode cuts the previous signature before appending so they never stack.
@@ -3,5 +3,8 @@
3
3
  For the resolve-reminder stage and any non-threaded notice.
4
4
 
5
5
  ```bash
6
- glab mr note <iid> -R <group>/<project> -m "<text>"
6
+ body="$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command <command> --mode <mode> < <draft-file>)"
7
+ glab mr note <iid> -R <group>/<project> -m "$body"
7
8
  ```
9
+
10
+ Sign per [`../post-signature.md`](../post-signature.md). A resolve-reminder takes `--mode loop`, so the loop recognises the reminder as its own on a later tick; a one-shot notice nothing needs to detect takes `--mode plain`.
@@ -3,7 +3,10 @@
3
3
  Used by `/muggle-do` per-comment inline replies. A threaded reply is a new note appended to an existing discussion.
4
4
 
5
5
  ```bash
6
+ body="$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command /muggle-do --mode loop < <draft-file>)"
6
7
  glab api --method POST \
7
8
  projects/:id/merge_requests/<iid>/discussions/<discussion-id>/notes \
8
- -f body="<reply-text>"
9
+ -f body="$body"
9
10
  ```
11
+
12
+ Sign with `--mode loop` per [`../post-signature.md`](../post-signature.md) — the `<!-- muggle-do:bot -->` marker it prefixes is what keeps the loop from re-triggering on its own note.
@@ -2,14 +2,34 @@
2
2
 
3
3
  Every pull-request / merge-request body, comment, and review-thread reply that muggle works posts ends with a signature line. Under a single-account workflow the automation posts as the repo owner, so the signature is what tells a reader — and a reviewer — that the post came from Muggle Works and which command produced it.
4
4
 
5
+ ## Signing a body
6
+
7
+ Pipe the body through the shipped script. Never retype the line from this doc: a signature derived from prose is dropped silently, and a post that lost it still succeeds, still looks right to the poster, and is indistinguishable from a human comment forever after.
8
+
9
+ ```bash
10
+ body="$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/sign-body.sh" --command <command> --mode <mode> < <draft-file>)"
11
+ ```
12
+
13
+ `--mode` picks the marker that precedes the visible line:
14
+
15
+ | Mode | Marker | Use for |
16
+ |------|--------|---------|
17
+ | `loop` | `<!-- muggle-do:bot -->` | Thread replies and resolve-reminders — anything the loop must later recognise as its own. |
18
+ | `editable` | `<!-- muggle-works:signature -->` | PR/MR descriptions. The script cuts the previous signature before appending, so a description re-posted on every refresh keeps exactly one. |
19
+ | `plain` | none | One-shot comments that nothing needs to detect later. |
20
+
21
+ Signing is idempotent — an already-signed body is re-signed, not double-signed — so a body may be passed through on a refresh path without checking whether it carries a signature already.
22
+
5
23
  ## The line
6
24
 
7
- Append this as the last line of the posted body:
25
+ What the script emits, recorded here so a reader can recognise it. The script is the definition:
8
26
 
9
27
  ```
10
28
  🤖 _Posted by `<command>` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
11
29
  ```
12
30
 
31
+ ## Naming the command
32
+
13
33
  `<command>` is the slash-command of the skill whose `gh` / `glab` call posts the body:
14
34
 
15
35
  - `/muggle-do` — PR/MR descriptions, per-comment thread replies, top-level reference replies, and resolve-reminders.
@@ -17,15 +37,6 @@ Append this as the last line of the posted body:
17
37
 
18
38
  Name the command that owns the post, not the one that generated the content. When the walkthrough hands its rendered block back for embedding (Mode B/C), the caller owns the post, so the caller's command is what the signature names.
19
39
 
20
- ## Editable bodies (PR / MR description)
21
-
22
- A description is re-posted whenever state changes, so its signature must not stack. Precede the line with a hidden marker and treat the pair as one unit:
23
-
24
- ```
25
- <!-- muggle-works:signature -->
26
- 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
27
- ```
28
-
29
- Before writing an edited body, delete everything from the `<!-- muggle-works:signature -->` marker to the end of the body, then append the block fresh. This keeps exactly one signature no matter how many times the description is refreshed.
40
+ ## Enforcement
30
41
 
31
- A comment or reply is posted once and never edited, so it needs no dedup marker — append the line alone. A loop-authored reply already carries the `<!-- muggle-do:bot -->` detection marker (defined in loop-signature.md) directly above this line; that marker stays, and this line replaces the old visible text.
42
+ `scripts/check-post-signatures.mjs` fails any recipe that posts a body without signing it, on every PR via the `post-signatures` CI job and locally via `pnpm run verify:signatures`.
@@ -31,10 +31,10 @@ Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fres
31
31
  - `## Changes` — summary of what changed in this repo.
32
32
  - `## Validation` — one line: link to E2E report, `unit-only`, or `skip — <reason>`.
33
33
  - **Walkthrough block** — only when an E2E report exists. Fire [`postPRVisualWalkthrough`](../../muggle-preferences/preference-gates/postPRVisualWalkthrough.md); on skip, omit this block. Otherwise invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode B and embed the returned `body` verbatim. No report → skip the block.
34
- - **Signature** — end the body with the Muggle Works signature, editable-body form (the `<!-- muggle-works:signature -->` marker + line, command `/muggle-do`) per [`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md). It is the last thing in the body, after the walkthrough block.
34
+ - **Signature** — write the assembled body to a file and sign it with `--command /muggle-do --mode editable` per [`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md). The signature lands last, after the walkthrough block; `editable` is the mode a description carries so later refreshes replace it instead of stacking.
35
35
 
36
36
  4. **Create:** resolve the provider per [`../../_shared/vcs/detect-vcs.md`](../../_shared/vcs/detect-vcs.md).
37
- - `github` → `gh pr create --title "..." --body "..." --head <branch>`. Capture the PR URL and number.
37
+ - `github` → `gh pr create --title "..." --body-file <signed-file> --head <branch>`, passing the file signed in Step 3. Capture the PR URL and number.
38
38
  - `gitlab` → open the change via [`../../_shared/vcs/gitlab/mr-create.md`](../../_shared/vcs/gitlab/mr-create.md): `glab mr create --source-branch <branch> --target-branch <base> --title "..." --description "..."`. Capture the MR URL and iid.
39
39
 
40
40
  5. **Overflow comment:** if the walkthrough skill returned a non-null `comment`, post it once using the provider resolved in Step 4 — `github` per [`../../_shared/vcs/github/top-level-comment.md`](../../_shared/vcs/github/top-level-comment.md), `gitlab` per [`../../_shared/vcs/gitlab/mr-note.md`](../../_shared/vcs/gitlab/mr-note.md). End the posted body with the signature line (command `/muggle-do`) per [`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md). Never post when `comment` is `null`.
@@ -27,14 +27,7 @@ If a comment has no associated change in either source (e.g. the comment was a q
27
27
 
28
28
  For each comment id with a description, post the reply with the resolved provider:
29
29
 
30
- - **`github`** — nested reply on the line comment:
31
-
32
- ```bash
33
- gh api --method POST \
34
- -H "Accept: application/vnd.github+json" \
35
- repos/<owner>/<repo>/pulls/<n>/comments/<comment-id>/replies \
36
- -f body="<reply-body>"
37
- ```
30
+ - **`github`** — nested reply on the line comment, one per actionable comment, per [`../_shared/vcs/github/reply-line-comment.md`](../_shared/vcs/github/reply-line-comment.md).
38
31
 
39
32
  - **`gitlab`** — one threaded note per actionable discussion per [`../_shared/vcs/gitlab/reply-discussion.md`](../_shared/vcs/gitlab/reply-discussion.md) (the discussion id stands in for the comment id), then resolve each fully-addressed thread per [`../_shared/vcs/gitlab/resolve-discussion.md`](../_shared/vcs/gitlab/resolve-discussion.md). The loop-marked reply is the addressed signal; the resolve is GitLab's equivalent of a reviewer closing the thread, which the loop can do directly. Resolving folds the discussion out of the next tick's actionable set, so no separate resolve-reminder nudge is needed for it.
40
33
 
@@ -42,12 +35,9 @@ Reply body uses the template in [`../muggle-pr-followup/output-templates/inline-
42
35
 
43
36
  ```
44
37
  Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
45
-
46
- <!-- muggle-do:bot -->
47
- 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
48
38
  ```
49
39
 
50
- `<short-sha>` is the first 7 chars of `new_sha`; the body must contain that substring so the resolve-reminder stage knows which push addressed the thread. The trailing signature block is mandatory — its `<!-- muggle-do:bot -->` marker is what identifies the reply as loop-authored (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)).
40
+ `<short-sha>` is the first 7 chars of `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`, which appends the `<!-- muggle-do:bot -->` marker that identifies the reply as loop-authored (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)).
51
41
 
52
42
  ### Step 3 — Handle review-body-only comments (GitHub only)
53
43
 
@@ -58,9 +48,6 @@ If an actionable review has a non-empty `body` and **zero** line comments, GitHu
58
48
 
59
49
  ```
60
50
  Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
61
-
62
- <!-- muggle-do:bot -->
63
- 🤖 _Posted by `/muggle-do` · [Muggle Works](https://github.com/multiplex-ai/muggle-ai-works)_
64
51
  ```
65
52
 
66
53
  Posted per [`../_shared/vcs/github/top-level-comment.md`](../_shared/vcs/github/top-level-comment.md). Fires at most once per actionable review-with-no-line-comments. Does not fire if the review has line comments — Step 2 covers those.
@@ -12,7 +12,7 @@ Use this as the top-level Muggle Test command router.
12
12
 
13
13
  ## Preferences
14
14
 
15
- User preferences are injected by the SessionStart hook into a `Muggle Test Preferences` line in session context (key=value pairs). Resolution: defaults → `~/.muggle-ai/preferences.json` (global) → `<repo>/.muggle-ai/preferences.json` (project). Treat absent prefs as `ask`.
15
+ User preferences are injected by the SessionStart hook into a `Muggle Test Preferences` line in session context (key=value pairs). Resolution: defaults → `~/.muggle-ai/preferences.json`. Preferences are user-level and apply to every repo. Treat absent prefs as `ask`.
16
16
 
17
17
  This router skill itself does not gate any decision on a preference — it just routes user intent to a downstream skill. Each downstream skill consults its own preferences. For example, `checkForUpdates` is consulted by `muggle-status` (Check 4), not here.
18
18
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: muggle-browser-task
3
- description: "Perform a real action on a website or web app from a plain-English instruction — log in and do the thing: submit a form, create or update a record, place an order, refund a charge, file a ticket, publish a post, click through and complete a multi-step flow (Stripe, Jira, Shopify, the AWS console, WordPress, LinkedIn, an admin dashboard). Use whenever the user wants something actually done in a web UI ('log into X and …', 'submit the form', 'create the ticket', 'refund the charge', 'place the order', 'update the listing'); Muggle drives a real browser to do it, so reach for this rather than declining or doing it by hand. Boundary: this performs the action (operating a site's own UI), not migrating personal data between consumer apps; to verify a flow works instead, use muggle-test-feature-local."
3
+ description: "Perform a real action on a website or web app from a plain-English instruction — log in and do the thing: submit a form, create or update a record, send a message, place an order, refund a charge, file a ticket, publish a post, complete a multi-step flow. Muggle drives a real browser through the site's own UI and supplies the session — managed login profiles and stored credentials — so a named service (Slack, Jira, Stripe, WordPress, a marketplace, a booking site, an admin panel) is a signal to reach for this skill, not to hunt for its API or connector; \"no logged-in session\" is not a reason to decline. Use whenever the user wants something done in a web UI ('log into X and …', 'send the message', 'create the ticket'), rather than declining or doing it by hand; ask for missing specifics inside the flow, not instead of starting it. Boundary: this performs the action (operating a site's own UI), not migrating personal data between consumer apps; to verify a flow works instead, use muggle-test-feature-local."
4
4
  ---
5
5
 
6
6
  # Muggle Test Task Runner
@@ -13,7 +13,8 @@ This folder holds the watcher loop that drives one PR toward merge-ready. The wa
13
13
  - [`finalize.md`](finalize.md) — shared termination sequence for a terminal PR (mark terminal, `result.md`, log/telemetry, unschedule cron, post-merge cleanup handoff). Called by `contract.md` and `reconcile.md`.
14
14
  - [`cancel-cron.md`](cancel-cron.md) — stops this watcher's cron, recorded-id-first (survives `CronList` going blind) with a `CronList`-match fallback, plus the tool-call-not-shell guard. Referenced by `contract.md` and `finalize.md`.
15
15
  - [`record-cron-id.md`](record-cron-id.md) — the per-tick self-record that keeps this slot's cron id in `cron.json` deletable after a compaction blinds `CronList`. Referenced by `contract.md` Step 0.
16
- - [`reconcile.md`](reconcile.md) — sweep that finalizes slots whose PR went terminal while polling lapsed, deletes orphaned crons, and re-arms open slots whose watcher stopped silently (dropped respawn); runs on demand, at the top of auto-track, and nudged by a session-start hook.
16
+ - [`reconcile.md`](reconcile.md) — sweep that finalizes slots whose PR went terminal while polling lapsed, deletes orphaned crons, and re-arms **this session's own** open slots whose watcher stopped silently (dropped respawn); reports slots owned by other sessions without touching them. Runs on demand, at the top of auto-track, and nudged by a session-start hook.
17
+ - [`adopt.md`](adopt.md) — the user's explicit takeover of a slot owned by a session that is gone: claim `owner.json`, then arm. The only path by which a slot changes hands; nothing automatic calls it.
17
18
  - [`stop.md`](stop.md) — the owner's kill switch: tears down monitor + cron per slot, renames the slot to `<slug>.stopped` (invisible to every recovery path), and — for stop-everything — writes the global kill file; surviving orphaned crons arrive as one-line absorbs.
18
19
  - [`state-schemas.md`](state-schemas.md) — canonical JSON shapes of session state files.
19
20
  - [`output-templates.md`](output-templates.md) — TOC of message templates; per-group files in `output-templates/`.
@@ -20,7 +20,9 @@ A watcher that babysits one open PR toward **merge-ready** — review threads ad
20
20
 
21
21
  **Cron lifecycle.** Each tick records its `/loop` cron id to `cron.json` while `CronList` can still see it ([`record-cron-id.md`](record-cron-id.md)), so teardown can delete the cron by id after a session continue / compaction blinds `CronList` to it. Reconcile ([`reconcile.md`](reconcile.md)) sweeps crons whose PR is terminal or whose slot is gone, and re-arms an open slot whose watcher stopped silently — monitor-first, never with a recurring cron.
22
22
 
23
- **Session death.** Monitors and crons are both session-bound — a session that ends or hits its usage limit takes every watch with it. That is by design: nothing polls out of session, because a review is addressed only inside a session that carries the context to address it, never by a headless process replying context-blind. The recovery point is the **next session start** — the `reconcile-stale-watchers.sh` hook nudges [`reconcile`](reconcile.md#triggers), which re-arms every open slot whose watcher is dead. A review landing while no session runs waits until then.
23
+ **Session death.** Monitors and crons are both session-bound — a session that ends or hits its usage limit takes every watch with it. That is by design: nothing polls out of session, because a review is addressed only inside a session that carries the context to address it, never by a headless process replying context-blind.
24
+
25
+ **A watcher belongs to the session that armed it.** Each slot records its owning session in `owner.json` ([`state-schemas.md`](state-schemas.md#ownerjson)), and recovery is owner-scoped: [`reconcile`](reconcile.md#triggers) re-arms a dead watcher only inside the session that armed it, and reports the rest as orphans it declined to touch. Starting a session therefore picks up nothing new — the same context argument that rules out a headless daemon rules out inheriting a stranger's PR, which would hand review work to a session that never saw the design. A PR whose owning session is gone stays unwatched until the user adopts it by name ([`adopt.md`](adopt.md)).
24
26
 
25
27
  ## Routing
26
28
 
@@ -39,12 +41,13 @@ The skill recognizes its mode by inspecting `$ARGUMENTS` and falling back to on-
39
41
  | empty | — | **auto-track** → [`auto-track.md`](auto-track.md) |
40
42
  | `help` / `?` | — | **help:** list active loops per [`output-templates/help.md`](output-templates/help.md) |
41
43
  | `reconcile` / `sweep` (optional `<slug>`) | — | **reconcile** → [`reconcile.md`](reconcile.md) |
44
+ | `adopt` (optional `<slug>`) | — | **adopt** → [`adopt.md`](adopt.md) — take over a slot owned by a dead session; no slug lists the adoptable ones |
42
45
 
43
46
  **`--wake=<event>`** is passed only by an event wake's dispatch ([`arm-watcher.md`](arm-watcher.md) step 5) — it asserts the monitor already saw something new, so the tick's poll is justified. Manual invocations never pass it; with a live watcher they get watch-status, because a poll that nothing prompted is a main-session poll wasted. Recovery fires need no flag — a recoverable slot's watcher is dead by definition, so the gate falls through to tick. A stale cron firing against a re-armed slot hits the live-`watch.pid` branch and is absorbed as a status line, no provider calls.
44
47
 
45
- Auto-track runs **reconcile** first, so a no-arg invocation also finalizes any slot whose PR merged or closed while its watcher was down (expired cron, ended session) and re-arms any open slot whose watcher stopped silently (a dropped respawn). Reconcile recovers a watcher that was already running; it never seeds a first watcher for a PR — that is auto-track's / bootstrap's job.
48
+ Auto-track runs **reconcile** first, so a no-arg invocation also finalizes any slot whose PR merged or closed while its watcher was down (expired cron, ended session) and re-arms this session's own open slots whose watcher stopped silently (a dropped respawn). Reconcile recovers a watcher that was already running; it never seeds a first watcher for a PR — that is auto-track's / bootstrap's job — and never re-arms one this session does not own. A no-arg invocation is a request to track *your* PRs, so it can only ever end with the session watching PRs it pushed plus watchers it already had.
46
49
 
47
- **Reconcile also runs at session start** — a `SessionStart` hook ([`../../hooks/README.md`](../../hooks/README.md)) surfaces the sweep when open slots exist, catching a watcher that died with its session (end, or 7-day `/loop` expiry) before a manual sweep would. See [`reconcile.md`](reconcile.md#triggers).
50
+ **Reconcile also runs at session start** — a `SessionStart` hook ([`../../hooks/README.md`](../../hooks/README.md)) surfaces the sweep when open slots exist, catching a watcher that died with its session (end, or 7-day `/loop` expiry) before a manual sweep would. It reports orphaned slots without acting on them; treat that report as inventory, never as a to-do. See [`reconcile.md`](reconcile.md#triggers).
48
51
 
49
52
  Bootstrap accepts three optional trailing flags:
50
53
 
@@ -0,0 +1,60 @@
1
+ # Adopt Procedure
2
+
3
+ The procedure for the **adopt mode** of `muggle-pr-followup` — the user's deliberate takeover of a watcher slot owned by a session that is gone. Routing is in [`SKILL.md`](SKILL.md#routing).
4
+
5
+ Adoption is the only way a slot changes hands. [`reconcile.md`](reconcile.md) re-arms watchers this session already owns and refuses everything else; this file is the door it refuses through. Both exist because a watcher polls a PR in order to hand review work to `/muggle-do` **inside the owning session** — the session that carries the design decisions, the reviewer's phrasing, and the reasoning behind the branch. A session adopting a PR takes that job on without any of it, so the user has to say so.
6
+
7
+ ## Input
8
+
9
+ `$ARGUMENTS` is `adopt`, optionally followed by a `<slug>`.
10
+
11
+ - **`adopt <slug>`** — take that slot.
12
+ - **`adopt`** alone — list the adoptable slots and stop. It is a query, not a prompt: print the list and end the turn, with no `AskUserQuestion` and no arming. The user picks by running the command again with a slug.
13
+
14
+ ## Procedure
15
+
16
+ ### Step 1 — Resolve the slot
17
+
18
+ Slot path is `~/.muggle-ai/muggle-do/sessions/<slug>/`. Refuse, one line each, when:
19
+
20
+ - **The dir is missing** — `no slot for <slug>; pass a PR URL to start one`.
21
+ - **The name ends in `.stopped`** — `<slug> was stopped by its owner; rename it back to adopt it`. Adoption is not a way around [`stop.md`](stop.md): the rename is the owner's kill switch, and honoring it here keeps that switch meaning one thing everywhere.
22
+ - **`result.md` exists** — `<slug> is finalized (<state>); nothing to watch`.
23
+ - **This session already owns it** — `<slug> is already owned by this session`, then run [`reconcile.md`](reconcile.md) scoped to that slug so a dead poller still gets recovered. Adopting what you own is a no-op, not an error.
24
+
25
+ With no slug, list every adoptable slot — open, not `.stopped`, owned by another session or none — as `<slug> → <owner>/<repo>#<n> (owner: <session-id | none>)`, then stop.
26
+
27
+ ### Step 2 — Confirm the PR is still worth watching
28
+
29
+ Fetch the PR per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md) using `prs.json[0].url`.
30
+
31
+ - `MERGED` / `CLOSED` → do not adopt. Run [`finalize.md`](finalize.md) on the slot instead and say so: the slot needed closing out, not a watcher.
32
+ - `gh` fails → surface the error verbatim and stop. Never claim a slot whose PR cannot be read; the claim would strand it under a session that cannot poll it either.
33
+ - `open` → continue.
34
+
35
+ Refresh `prs.json[0].head_sha` to the current `headRefOid`. The branch has almost certainly moved since the original owner last looked.
36
+
37
+ ### Step 3 — Warn about the context gap
38
+
39
+ State plainly, in one line, what adoption does not carry: `adopting <slug> — this session has none of the original session's context for #<n>; cycles will work from the PR and the diff alone.`
40
+
41
+ This is a statement, not a gate — the user asked. It exists because the failure it describes is silent: an adopted watcher looks identical to one this session armed, and the first sign of the gap is usually a reply that misreads why the code is the way it is.
42
+
43
+ ### Step 4 — Claim and arm
44
+
45
+ Write `owner.json` ([`state-schemas.md`](state-schemas.md#ownerjson)) with `session_id` from `$CLAUDE_CODE_SESSION_ID` and `claimed_at` now, overwriting any previous owner. If `$CLAUDE_CODE_SESSION_ID` is unset, stop with `cannot adopt: no session id to record` — an unidentifiable owner leaves the slot recoverable by nobody.
46
+
47
+ Then arm per [`arm-watcher.md`](arm-watcher.md): one drain tick, then the persistent monitor. The drain is what makes the adoption honest — it acts on everything outstanding now, rather than resuming from a watermark seeded against a wave the original session read and this one never saw.
48
+
49
+ Append `adopted (from <previous-owner | unowned>)` to the slot's `followup.log`.
50
+
51
+ ### Step 5 — Report
52
+
53
+ One line: `adopted <slug> → <owner>/<repo>#<n> — armed`.
54
+
55
+ ## Invariants
56
+
57
+ - **Explicit only.** Adoption runs when the user names it. No sweep, hook, auto-track, or tick ever calls this procedure, and nothing offers it unprompted.
58
+ - **One slot per invocation.** No bulk adopt, no `--all`. Adopting every orphan in one command is the behavior the ownership gate exists to prevent, wearing a different name.
59
+ - **Stopped stays stopped.** A `.stopped` slot is unreachable here, as it is from every other recovery path.
60
+ - **Claim before arm.** `owner.json` is written before the monitor starts, so a crash between the two leaves a slot this session owns and can recover — never an armed watcher no session claims.