@muggleai/works 4.15.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/hooks/README.md +26 -0
- package/dist/plugin/hooks/hooks.json +39 -0
- package/dist/plugin/scripts/ensure-electron-app.sh +1 -1
- package/dist/plugin/scripts/guardrail-build-router.sh +9 -0
- package/dist/plugin/scripts/guardrail-e2e-gate.sh +8 -0
- package/dist/plugin/scripts/guardrail-pr-opened.sh +10 -0
- package/dist/plugin/scripts/guardrail-record-tests.sh +9 -0
- package/dist/plugin/scripts/guardrails.mjs +143 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +2 -1
- package/dist/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +16 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers.md +1 -0
- package/dist/plugin/skills/do/address-reviews.md +1 -1
- package/dist/plugin/skills/do/build.md +4 -0
- package/dist/plugin/skills/do/cleanup.md +6 -5
- package/dist/plugin/skills/do/fix-ci.md +1 -1
- package/dist/plugin/skills/do/input-routing.md +7 -6
- package/dist/plugin/skills/do/next-step.md +18 -0
- package/dist/plugin/skills/do/open-prs/forward.md +2 -0
- package/dist/plugin/skills/do/resolve-conflicts.md +62 -0
- package/dist/plugin/skills/muggle-do/SKILL.md +2 -1
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/contract.md +40 -16
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +5 -1
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoRouteBuildToMuggleDo.md +13 -0
- package/dist/release-manifest.json +4 -4
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/hooks/README.md +26 -0
- package/plugin/hooks/hooks.json +39 -0
- package/plugin/scripts/ensure-electron-app.sh +1 -1
- package/plugin/scripts/guardrail-build-router.sh +9 -0
- package/plugin/scripts/guardrail-e2e-gate.sh +8 -0
- package/plugin/scripts/guardrail-pr-opened.sh +10 -0
- package/plugin/scripts/guardrail-record-tests.sh +9 -0
- package/plugin/scripts/guardrails.mjs +143 -0
- package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +2 -1
- package/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +16 -0
- package/plugin/skills/_shared/pr-followup-helpers.md +1 -0
- package/plugin/skills/do/address-reviews.md +1 -1
- package/plugin/skills/do/build.md +4 -0
- package/plugin/skills/do/cleanup.md +6 -5
- package/plugin/skills/do/fix-ci.md +1 -1
- package/plugin/skills/do/input-routing.md +7 -6
- package/plugin/skills/do/next-step.md +18 -0
- package/plugin/skills/do/open-prs/forward.md +2 -0
- package/plugin/skills/do/resolve-conflicts.md +62 -0
- package/plugin/skills/muggle-do/SKILL.md +2 -1
- package/plugin/skills/muggle-pr-followup/SKILL.md +1 -1
- package/plugin/skills/muggle-pr-followup/contract.md +40 -16
- package/plugin/skills/muggle-pr-followup/state-schemas.md +5 -1
- package/plugin/skills/muggle-preferences/preference-gates/autoRouteBuildToMuggleDo.md +13 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "5.0.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Guardrail hooks
|
|
2
|
+
|
|
3
|
+
Condition-triggered hooks that make Muggle Test's high-value handoffs fire path-independently — no matter whether a change was built via muggle-do, superpowers, or ad-hoc edits.
|
|
4
|
+
|
|
5
|
+
## Two layers
|
|
6
|
+
|
|
7
|
+
"Harness" spans two layers, and the distinction is load-bearing:
|
|
8
|
+
|
|
9
|
+
- **Claude Code layer** — the agent runtime that fires these hooks. A guardrail is a Claude-Code-layer trigger, nothing more.
|
|
10
|
+
- **Muggle Test layer** — the product (muggle-do, muggle-test, the watcher). This is what a guardrail *invokes*.
|
|
11
|
+
|
|
12
|
+
A guardrail injects an advisory directive (`additionalContext`); the model then runs the Muggle Test flow. The guardrail never reimplements the flow.
|
|
13
|
+
|
|
14
|
+
Design rationale: `muggle-ai-brain/architecture/2026-06-02-harness-pipeline-integration-design.md`.
|
|
15
|
+
|
|
16
|
+
## Mechanism
|
|
17
|
+
|
|
18
|
+
Each guardrail is a thin bash wrapper in `../scripts/` registered in `hooks.json`. The wrapper pipes the event payload (stdin JSON) to the bundled `../scripts/guardrails.mjs <subcommand>`, which holds the decision logic (built from `src/guardrails/`, vitest-covered). Per-session state in `~/.muggle-ai/guardrails/<session_id>.json` makes each guardrail fire once. Any failure degrades to `{}` — a guardrail must never block a turn.
|
|
19
|
+
|
|
20
|
+
## Guardrails
|
|
21
|
+
|
|
22
|
+
| Hook event | Wrapper | Condition | Preference | Flow invoked |
|
|
23
|
+
| :--------- | :------ | :-------- | :--------- | :----------- |
|
|
24
|
+
| `PostToolUse` (Bash) | `guardrail-pr-opened.sh` | a `gh pr create`/`gh pr ready` just succeeded | `autoWatchPR` | start a `muggle-pr-followup` watcher on the new PR |
|
|
25
|
+
| `Stop` | `guardrail-e2e-gate.sh` | unit tests passed this session and no E2E ran yet (recorded by `guardrail-record-tests.sh`) | `autoE2ETest` | run change-driven E2E via `muggle-test` before finishing |
|
|
26
|
+
| `UserPromptSubmit` | `guardrail-build-router.sh` | a build/implement/fix request (first one this session) | `autoRouteBuildToMuggleDo` | route the work through `muggle-do` (build delegated to superpowers) |
|
|
@@ -11,6 +11,45 @@
|
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
14
|
+
],
|
|
15
|
+
"PostToolUse": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "Bash",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-pr-opened.sh\"",
|
|
22
|
+
"async": false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
|
|
27
|
+
"async": false
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"Stop": [
|
|
33
|
+
{
|
|
34
|
+
"hooks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-e2e-gate.sh\"",
|
|
38
|
+
"async": false
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"UserPromptSubmit": [
|
|
44
|
+
{
|
|
45
|
+
"hooks": [
|
|
46
|
+
{
|
|
47
|
+
"type": "command",
|
|
48
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-build-router.sh\"",
|
|
49
|
+
"async": false
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
14
53
|
]
|
|
15
54
|
}
|
|
16
55
|
}
|
|
@@ -109,7 +109,7 @@ if [ -f "$prefs_global_file" ]; then
|
|
|
109
109
|
postPRVisualWalkthrough:'ask', autoCreatePR:'ask',
|
|
110
110
|
checkForUpdates:'ask', verboseOutput:'ask',
|
|
111
111
|
autoUseWorktree:'ask', autoRebase:'ask', autoCleanup:'ask',
|
|
112
|
-
autoE2ETest:'always'
|
|
112
|
+
autoE2ETest:'always', autoRouteBuildToMuggleDo:'ask'
|
|
113
113
|
};
|
|
114
114
|
const cwd = process.env.CLAUDE_CWD || process.env.CURSOR_CWD || process.cwd();
|
|
115
115
|
const pPath = require('path').join(cwd, '.muggle-ai', 'preferences.json');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Front-door router (UserPromptSubmit). On the first build/implement/fix prompt
|
|
5
|
+
# of a session, offers to route the work through /muggle-do (build delegated to
|
|
6
|
+
# superpowers), gated by autoRouteBuildToMuggleDo. Fires once per session.
|
|
7
|
+
# Degrades to {} so it never blocks a turn.
|
|
8
|
+
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
9
|
+
node "${root}/scripts/guardrails.mjs" build-router 2>/dev/null || printf '{}'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# tests-green → E2E gate (Stop). When unit tests passed this session and no E2E
|
|
5
|
+
# acceptance run has happened, offer to run change-driven E2E (gated by
|
|
6
|
+
# autoE2ETest). Fires once per session. Degrades to {} so it never blocks a turn.
|
|
7
|
+
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
8
|
+
node "${root}/scripts/guardrails.mjs" e2e-gate 2>/dev/null || printf '{}'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# PR-opened guardrail (PostToolUse/Bash). When a `gh pr create`/`gh pr ready`
|
|
5
|
+
# just succeeded, offer to start a muggle-pr-followup watcher on the new PR
|
|
6
|
+
# (gated by autoWatchPR, deduped per session). Decision logic lives in the
|
|
7
|
+
# bundled guardrails.mjs; this wrapper just pipes the event payload through and
|
|
8
|
+
# degrades to {} so a guardrail can never block a turn.
|
|
9
|
+
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
10
|
+
node "${root}/scripts/guardrails.mjs" pr-opened 2>/dev/null || printf '{}'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# tests-green observer (PostToolUse/Bash). Records in per-session state when a
|
|
5
|
+
# unit-test command passed (and when a muggle E2E run happened). Emits no
|
|
6
|
+
# directive — the Stop gate (guardrail-e2e-gate.sh) reads the state. Degrades
|
|
7
|
+
# to {} so it never blocks a turn.
|
|
8
|
+
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
9
|
+
node "${root}/scripts/guardrails.mjs" record-tests 2>/dev/null || printf '{}'
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
// src/guardrails/cli.ts
|
|
6
|
+
var baseDir = (override) => override ?? join(homedir(), ".muggle-ai", "guardrails");
|
|
7
|
+
var fileFor = (sessionId2, override) => join(baseDir(override), `${sessionId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
|
|
8
|
+
function readState(sessionId2, dirOverride) {
|
|
9
|
+
const f = fileFor(sessionId2, dirOverride);
|
|
10
|
+
if (!existsSync(f)) return { sessionId: sessionId2, prsHandled: [] };
|
|
11
|
+
try {
|
|
12
|
+
const raw = JSON.parse(readFileSync(f, "utf-8"));
|
|
13
|
+
return { ...raw, sessionId: sessionId2, prsHandled: raw.prsHandled ?? [] };
|
|
14
|
+
} catch {
|
|
15
|
+
return { sessionId: sessionId2, prsHandled: [] };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function writeState(state, dirOverride) {
|
|
19
|
+
mkdirSync(baseDir(dirOverride), { recursive: true });
|
|
20
|
+
writeFileSync(fileFor(state.sessionId, dirOverride), JSON.stringify(state, null, 2));
|
|
21
|
+
}
|
|
22
|
+
function markPrHandled(sessionId2, prUrl, dirOverride) {
|
|
23
|
+
const state = readState(sessionId2, dirOverride);
|
|
24
|
+
if (!state.prsHandled.includes(prUrl)) state.prsHandled.push(prUrl);
|
|
25
|
+
writeState(state, dirOverride);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/guardrails/prOpened.ts
|
|
29
|
+
var PR_URL = /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+/;
|
|
30
|
+
var CREATE_CMD = /\bgh\s+pr\s+(create|ready)\b/;
|
|
31
|
+
function detectPrOpened(input2) {
|
|
32
|
+
if (input2.tool_name !== "Bash") return null;
|
|
33
|
+
const cmd = input2.tool_input?.command ?? "";
|
|
34
|
+
if (!CREATE_CMD.test(cmd)) return null;
|
|
35
|
+
const out = `${input2.tool_response?.stdout ?? ""}
|
|
36
|
+
${input2.tool_response?.output ?? ""}`;
|
|
37
|
+
const m = out.match(PR_URL);
|
|
38
|
+
return m ? m[0] : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/guardrails/testsGreen.ts
|
|
42
|
+
var TEST_CMD = /\b(pnpm|npm|yarn)\s+(run\s+)?test\b|\b(jest|vitest|pytest)\b|\bgo\s+test\b|\bcargo\s+test\b/;
|
|
43
|
+
var FAIL = /\b\d+\s+failed\b|\bFAIL\b|✗/;
|
|
44
|
+
var E2E_RUN = /\bmuggle\b[^\n]*\b(execute|test)\b/i;
|
|
45
|
+
function isTestCommand(cmd) {
|
|
46
|
+
return TEST_CMD.test(cmd);
|
|
47
|
+
}
|
|
48
|
+
function testsPassed(input2) {
|
|
49
|
+
const out = `${input2.tool_response?.stdout ?? ""}
|
|
50
|
+
${input2.tool_response?.stderr ?? ""}`;
|
|
51
|
+
if (!out.trim()) return false;
|
|
52
|
+
return !FAIL.test(out);
|
|
53
|
+
}
|
|
54
|
+
function isE2ERun(input2) {
|
|
55
|
+
const cmd = input2.tool_input?.command ?? "";
|
|
56
|
+
const tool = input2.tool_name ?? "";
|
|
57
|
+
return E2E_RUN.test(cmd) || /muggle.*(execute|test-generation|replay)/i.test(tool);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/guardrails/shouldRunE2E.ts
|
|
61
|
+
function shouldRunE2E(state) {
|
|
62
|
+
return state.unitTestsGreen === true && state.e2eRun !== true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/guardrails/detectBuildIntent.ts
|
|
66
|
+
var BUILD = /\b(implement|build|add|create|write|fix|refactor|wire up|hook up|make (a|the|it)|change the)\b/i;
|
|
67
|
+
var QUESTION = /^\s*(why|what|how|when|where|who|is|are|does|do|can you (explain|tell)|explain)\b/i;
|
|
68
|
+
function detectBuildIntent(prompt) {
|
|
69
|
+
const p = (prompt ?? "").trim();
|
|
70
|
+
if (!p || p.startsWith("/")) return false;
|
|
71
|
+
if (QUESTION.test(p)) return false;
|
|
72
|
+
return BUILD.test(p);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/guardrails/emit.ts
|
|
76
|
+
function envelope(eventName, context, host2) {
|
|
77
|
+
if (!context) return "{}";
|
|
78
|
+
if (host2 === "cursor") return JSON.stringify({ additional_context: context });
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
hookSpecificOutput: { hookEventName: eventName, additionalContext: context }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/guardrails/cli.ts
|
|
85
|
+
function readStdin() {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(readFileSync(0, "utf-8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
var host = process.env.CURSOR_PLUGIN_ROOT ? "cursor" : "claude";
|
|
93
|
+
var sub = process.argv[2];
|
|
94
|
+
var input = readStdin();
|
|
95
|
+
var sessionId = input.session_id ?? "unknown";
|
|
96
|
+
function prOpened() {
|
|
97
|
+
const url = detectPrOpened(input);
|
|
98
|
+
if (!url) return "{}";
|
|
99
|
+
if (readState(sessionId).prsHandled.includes(url)) return "{}";
|
|
100
|
+
markPrHandled(sessionId, url);
|
|
101
|
+
const ctx = `A pull request was just opened: ${url}
|
|
102
|
+
Per the autoWatchPR preference, a muggle-pr-followup watcher should handle its incoming reviews. If autoWatchPR=always, start it now by invoking /muggle:muggle-pr-followup with the PR URL; if =ask, offer it to the user; if =never, do nothing.`;
|
|
103
|
+
return envelope("PostToolUse", ctx, host);
|
|
104
|
+
}
|
|
105
|
+
function recordTests() {
|
|
106
|
+
const cmd = input.tool_input?.command ?? "";
|
|
107
|
+
const state = readState(sessionId);
|
|
108
|
+
let changed = false;
|
|
109
|
+
if (isTestCommand(cmd) && testsPassed(input)) {
|
|
110
|
+
state.unitTestsGreen = true;
|
|
111
|
+
changed = true;
|
|
112
|
+
}
|
|
113
|
+
if (isE2ERun(input)) {
|
|
114
|
+
state.e2eRun = true;
|
|
115
|
+
changed = true;
|
|
116
|
+
}
|
|
117
|
+
if (changed) writeState(state);
|
|
118
|
+
return "{}";
|
|
119
|
+
}
|
|
120
|
+
function e2eGate() {
|
|
121
|
+
const state = readState(sessionId);
|
|
122
|
+
if (!shouldRunE2E(state)) return "{}";
|
|
123
|
+
state.e2eRun = true;
|
|
124
|
+
writeState(state);
|
|
125
|
+
const ctx = `Unit tests passed this session and no E2E acceptance run has happened yet. Per the autoE2ETest preference (default: always), run change-driven E2E now via /muggle:muggle-test before finishing. If autoE2ETest=never, skip.`;
|
|
126
|
+
return envelope("Stop", ctx, host);
|
|
127
|
+
}
|
|
128
|
+
function buildRouter() {
|
|
129
|
+
if (!detectBuildIntent(input.prompt ?? "")) return "{}";
|
|
130
|
+
const state = readState(sessionId);
|
|
131
|
+
if (state.buildIntentRouted) return "{}";
|
|
132
|
+
state.buildIntentRouted = true;
|
|
133
|
+
writeState(state);
|
|
134
|
+
const ctx = `This looks like a build/implement/fix request. Per the autoRouteBuildToMuggleDo preference, route it through /muggle-do \u2014 which runs requirements \u2192 build (delegated to superpowers' design\u2192plan\u2192review) \u2192 impact \u2192 unit tests \u2192 E2E \u2192 PR \u2192 watcher. If autoRouteBuildToMuggleDo=always, enter that flow; if =ask, offer it; if =never, proceed normally.`;
|
|
135
|
+
return envelope("UserPromptSubmit", ctx, host);
|
|
136
|
+
}
|
|
137
|
+
var handlers = {
|
|
138
|
+
"pr-opened": prOpened,
|
|
139
|
+
"record-tests": recordTests,
|
|
140
|
+
"e2e-gate": e2eGate,
|
|
141
|
+
"build-router": buildRouter
|
|
142
|
+
};
|
|
143
|
+
process.stdout.write((handlers[sub] ?? (() => "{}"))());
|
|
@@ -4,9 +4,10 @@ Fetch the fields the watcher and bootstrap need in one call.
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
gh pr view <pr-number> --repo <owner>/<repo> \
|
|
7
|
-
--json url,number,headRefOid,headRefName,baseRefName,state,mergedAt,closedAt,body,title,author
|
|
7
|
+
--json url,number,headRefOid,headRefName,baseRefName,state,mergeable,mergeStateStatus,mergedAt,closedAt,body,title,author
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
- `state` is one of `OPEN`, `MERGED`, `CLOSED`.
|
|
11
11
|
- `headRefOid` is the current head SHA — store as `head_sha` in `prs.json`.
|
|
12
12
|
- `headRefName` is the branch — must match the working tree's branch in bootstrap.
|
|
13
|
+
- `mergeable` is `MERGEABLE`, `CONFLICTING`, or `UNKNOWN` (GitHub still computing — treat as not-conflicting this tick). `mergeStateStatus` (`DIRTY` = conflicts, `BEHIND`, `CLEAN`, …) corroborates it. The watcher uses these to detect a merge-conflict that no review or CI signal would surface.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Reply-echo skip
|
|
2
|
+
|
|
3
|
+
When `/muggle-do` posts a threaded reply to a review comment, GitHub surfaces that reply as a **new submitted review** under the same account. Left unchecked, the next watcher tick reads that review as fresh feedback and dispatches another cycle — which posts another reply, which becomes another review. The loop never converges.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
A submitted review is an **echo** when **every** comment in it carries the loop marker `<!-- muggle-do:bot -->` (see [`loop-signature.md`](loop-signature.md)). An echo is the loop's own reply wearing a review's clothing, never human intent.
|
|
8
|
+
|
|
9
|
+
On an echo review, the watcher must:
|
|
10
|
+
|
|
11
|
+
1. Advance `last_seen.reviewId` past the echo's id (so it is not seen again), and
|
|
12
|
+
2. **Skip it** — never dispatch `/muggle-do` for it.
|
|
13
|
+
|
|
14
|
+
## Detection
|
|
15
|
+
|
|
16
|
+
Classify by the marker, never by `author.login` — under a shared account the loop posts as the PR author, so the login cannot tell echo from human. Fetch the review's comments; if the set is non-empty and every comment body contains `<!-- muggle-do:bot -->`, it is an echo. A review with at least one marker-less comment is human feedback and must be processed normally.
|
|
@@ -12,3 +12,4 @@ Each section is its own file — load only what the current step needs.
|
|
|
12
12
|
| [`reply-routing`](pr-followup-helpers/reply-routing.md) | Pick the right reply endpoint per comment type (line, body-only, CI failure). |
|
|
13
13
|
| [`classify`](pr-followup-helpers/classify.md) | Per-review binary label: actionable vs ambiguous, with worked examples + borderline rule. |
|
|
14
14
|
| [`loop-signature`](pr-followup-helpers/loop-signature.md) | The signature every loop-posted comment carries, and how to detect loop vs human comments by it. |
|
|
15
|
+
| [`echo-skip`](pr-followup-helpers/echo-skip.md) | Skip reviews that are the loop's own replies (every comment loop-marked), so the watcher never re-triggers on itself. |
|
|
@@ -114,7 +114,7 @@ Refresh PR state per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared
|
|
|
114
114
|
1. Write `result.md` per [`../muggle-pr-followup/state-schemas.md`](../muggle-pr-followup/state-schemas.md#resultmd).
|
|
115
115
|
2. Do **not** respawn the watcher.
|
|
116
116
|
|
|
117
|
-
Otherwise, dispatch the next watcher as the last action of this turn:
|
|
117
|
+
Otherwise, dispatch the next watcher as the last action of this turn. The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 4), so this restart is the single live watcher — never a duplicate:
|
|
118
118
|
|
|
119
119
|
```
|
|
120
120
|
/loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
@@ -29,6 +29,10 @@ For each affected repo:
|
|
|
29
29
|
|
|
30
30
|
The body explains *why* when the why is non-obvious. The diff already says *what*.
|
|
31
31
|
|
|
32
|
+
## Delegation
|
|
33
|
+
|
|
34
|
+
For a non-trivial change — multiple files, real design surface, or anything you would otherwise brainstorm before coding — run the implementation through superpowers' design → plan → subagent-driven build, then return to this stage's Output. That is a runtime hand-off (an action), not a doc dependency; do not encode superpowers' internals here. Routing a build request into this pipeline (the `autoRouteBuildToMuggleDo` front-door guardrail) exists to combine superpowers' design rigor with this pipeline's impact analysis, E2E, PR, and watcher — neither delivers both alone.
|
|
35
|
+
|
|
32
36
|
## Output
|
|
33
37
|
|
|
34
38
|
Per repo:
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Post-Merge Cleanup Stage
|
|
2
2
|
|
|
3
|
-
Invoked by `/muggle-do` when the watcher forwards a PR's terminal
|
|
3
|
+
Invoked by `/muggle-do` when the watcher forwards a PR's terminal state. On `merged` it resolves the session's workspace and **delegates** teardown to the shared procedure (it does not restate the teardown steps); on `closed` (unmerged) it skips teardown. Either way it ends by suggesting the next step. Never runs while the PR is open.
|
|
4
4
|
|
|
5
5
|
## Input
|
|
6
6
|
|
|
7
|
-
`$ARGUMENTS` carries
|
|
7
|
+
`$ARGUMENTS` carries `slug=<slug>` and `state=<merged|closed>` (default `merged`). No PR URL, no review ids.
|
|
8
8
|
|
|
9
9
|
## Procedure
|
|
10
10
|
|
|
11
11
|
1. Read `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR `repo`, `number`, observed `state`) and `state.md` (`worktreePath` if a worktree was used, and the target branch `headRefName`).
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4. Append a cleanup line to the session's `followup.log
|
|
12
|
+
2. If `prs.json` shows the PR still open, do nothing and exit — this stage is terminal-only.
|
|
13
|
+
3. **Teardown (`merged` only).** When the PR is `merged`, run [`../_shared/post-merge-cleanup.md`](../_shared/post-merge-cleanup.md) with `{worktreePath}` and `{branch}`. That file owns the teardown sequence **and its safety rules** — including skipping worktree-remove and local branch deletion when no worktree was used. This stage adds no teardown logic of its own. On `closed`, skip teardown — the branch and any worktree stay intact.
|
|
14
|
+
4. Append a cleanup line to the session's `followup.log`, recording whether teardown ran.
|
|
15
|
+
5. Suggest the next step per [`next-step.md`](next-step.md), passing whether teardown ran. This is the stage's last action.
|
|
@@ -40,7 +40,7 @@ Commit per the `fix(ci): <check> — <what>` convention ([`../_shared/pr-followu
|
|
|
40
40
|
### Step 5 — Update state + respawn
|
|
41
41
|
|
|
42
42
|
- Increment `last_seen.ci_fix_attempts[red_sha]`.
|
|
43
|
-
- Respawn the watcher: `/loop 1m /muggle:muggle-pr-followup <slug> <n>`. CI on the new SHA is the verify loop — a still-red SHA returns as a fresh dispatch, bounded by the per-SHA fix budget (Step 6).
|
|
43
|
+
- Respawn the watcher: `/loop 1m /muggle:muggle-pr-followup <slug> <n>`. The watcher cancelled its own cron when it dispatched this fix-ci cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5), so this restart is the single live watcher. CI on the new SHA is the verify loop — a still-red SHA returns as a fresh dispatch, bounded by the per-SHA fix budget (Step 6).
|
|
44
44
|
|
|
45
45
|
### Step 6 — Escalate (budget spent or out of scope)
|
|
46
46
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Input routing
|
|
2
2
|
|
|
3
|
-
How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–
|
|
3
|
+
How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–4 are programmatic — dispatched by the watcher — so never ask on those. Inspect in order:
|
|
4
4
|
|
|
5
5
|
1. **Address-reviews** — a `github.com/.../pull/<n>` URL **and** one or more review ids (integers ≥ 100000000) → [`address-reviews.md`](address-reviews.md).
|
|
6
6
|
2. **Fix-CI** — a `github.com/.../pull/<n>` URL **and** a `fix ci` / `fix-ci` directive with failing check names (no review ids) → [`fix-ci.md`](fix-ci.md).
|
|
7
|
-
3. **
|
|
8
|
-
4. **
|
|
9
|
-
5. **
|
|
10
|
-
6. **
|
|
7
|
+
3. **Resolve-conflicts** — a `github.com/.../pull/<n>` URL **and** a `resolve conflicts` / `resolve-conflicts` directive (no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md).
|
|
8
|
+
4. **Post-merge cleanup** — a `cleanup` token and `slug=<slug>` (no PR URL, no review ids), optionally `state=<merged|closed>` (default `merged`) → [`cleanup.md`](cleanup.md).
|
|
9
|
+
5. **Empty / `help` / `menu` / `?`** → menu + session selector.
|
|
10
|
+
6. **Task automation** (perform an action on a website) → `muggle:muggle-browser-task`.
|
|
11
|
+
7. **Otherwise** → forward pipeline at Stage 1.
|
|
11
12
|
|
|
12
|
-
When in doubt between #
|
|
13
|
+
When in doubt between #6 and #7, ask one question.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Next-Step Suggestion
|
|
2
|
+
|
|
3
|
+
Closing step of a terminal `/muggle-do` turn (post-merge or post-close). Advances silently when the session still has a plan, stops and asks when it doesn't.
|
|
4
|
+
|
|
5
|
+
## Input
|
|
6
|
+
|
|
7
|
+
- `slug` — the session.
|
|
8
|
+
- `teardownRan` — whether [`cleanup.md`](cleanup.md) already ran teardown (true on `merged` + `autoCleanup: always`; false on `closed` or a skipped gate).
|
|
9
|
+
|
|
10
|
+
## Procedure
|
|
11
|
+
|
|
12
|
+
1. Read the session plan — the current session's TodoWrite list.
|
|
13
|
+
2. **Pending items remain** → do not prompt. Append `next-step: plan has <N> pending — advancing` to `followup.log` and continue to the next pending item. The user set a course; honor it.
|
|
14
|
+
3. **No pending items** → stop and ask for directions with one `AskUserQuestion` selector:
|
|
15
|
+
- **Clean up now** — offer only when `teardownRan` is false (a `closed` PR, or `merged` with `autoCleanup` not `always`). Runs the shared teardown [`../_shared/post-merge-cleanup.md`](../_shared/post-merge-cleanup.md) for this slug, under its own safety rules.
|
|
16
|
+
- **Move on / next task** — start a fresh `/muggle-do` forward run, or pick another open session.
|
|
17
|
+
- **Done — stop here** — exit with no further action.
|
|
18
|
+
4. Append the chosen outcome to `followup.log`.
|
|
@@ -78,3 +78,5 @@ Append one short reminder tied to the gate value:
|
|
|
78
78
|
- `always` → `Once merged, I'll run the cleanup sequence automatically.`
|
|
79
79
|
- `never` → omit.
|
|
80
80
|
- `ask` / absent → `Once merged, I'll check with you about cleanup.`
|
|
81
|
+
|
|
82
|
+
Regardless of the gate, also append: `Once it's merged or closed, I'll move to the next plan item — or ask where to go next if there's no plan.`
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Resolve-Conflicts (watcher-dispatched)
|
|
2
|
+
|
|
3
|
+
Rebase a PR whose branch conflicts with its base, resolve the conflicts behind a verify-or-rollback gate, and force-push — so a mergeable-blocked PR doesn't sit idle forever. A dumb-pipe dispatch like fix-ci: the watcher detects `mergeable == CONFLICTING` and hands off; the executor owns the rebase + resolution, never the decision to dispatch.
|
|
4
|
+
|
|
5
|
+
## Turn preamble
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
**/muggle-do resolve-conflicts** — rebasing <owner>/<repo>#<n> onto <base> to clear merge conflicts.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Input
|
|
12
|
+
|
|
13
|
+
`$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and a `resolve conflicts` directive (no review ids, no failing check names). Parse all three.
|
|
14
|
+
|
|
15
|
+
## Inputs from disk
|
|
16
|
+
|
|
17
|
+
From `~/.muggle-ai/muggle-do/sessions/<slug>/`: `prs.json` (PR + branch + `head_sha`), `last_seen.json` (`conflict_resolve_attempts`, `conflict_escalated_shas`, `pushed_shas`), `state.md` (worktree path, validation strategy, base branch).
|
|
18
|
+
|
|
19
|
+
## Procedure
|
|
20
|
+
|
|
21
|
+
### Step 1 — Re-attach
|
|
22
|
+
|
|
23
|
+
Materialize the PR branch in its worktree per [`../_shared/pr-branch-worktree.md`](../_shared/pr-branch-worktree.md) (or use `state.md`'s `worktreePath`). Capture `conflict_sha = prs.json[0].head_sha` and the base branch (`baseRefName` from [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md)).
|
|
24
|
+
|
|
25
|
+
### Step 2 — Rebase onto base + resolve
|
|
26
|
+
|
|
27
|
+
Run [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) against the base branch (it fires because a conflicting PR is behind). Conflict handling follows [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
|
|
28
|
+
|
|
29
|
+
- default `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub, or opts into `autoResolveConflicts=always`.
|
|
30
|
+
- `always` → resolve behind the verify-or-rollback gate in [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md).
|
|
31
|
+
|
|
32
|
+
### Step 3 — Verify the resolution
|
|
33
|
+
|
|
34
|
+
Build (typecheck + lint on the changed surface) + unit suite must pass. Run E2E per [`e2e-acceptance.md`](e2e-acceptance.md) when app logic changed and the session carries validation context. A resolution that does not verify is rolled back → escalate per Step 5. **Never push an unverified merge.**
|
|
35
|
+
|
|
36
|
+
### Step 4 — Force-push + respawn
|
|
37
|
+
|
|
38
|
+
Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[conflict_sha]`. Respawn the watcher as the last action:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
/loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The watcher cancelled its own cron when it dispatched this cycle ([`../muggle-pr-followup/contract.md`](../muggle-pr-followup/contract.md) Step 5b), so this restart is the single live watcher. Its next tick re-checks mergeability on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
|
|
45
|
+
|
|
46
|
+
### Step 5 — Escalate (can't resolve / budget spent)
|
|
47
|
+
|
|
48
|
+
When `autoResolveConflicts=never`, the resolution failed verification, or `conflict_resolve_attempts[conflict_sha]` has reached 2:
|
|
49
|
+
|
|
50
|
+
1. Add `conflict_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
|
|
51
|
+
2. Emit one terminal escalation naming the PR and the conflicting files.
|
|
52
|
+
3. Respawn the watcher (last action) — it keeps polling for the user's manual resolution or any new reviews.
|
|
53
|
+
|
|
54
|
+
### Step 6 — Telemetry
|
|
55
|
+
|
|
56
|
+
Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.md`](../_shared/telemetry-events/muggle-do-cycle.md)) with `outcome: "conflicts-resolved"` (a verified rebase pushed) or `"conflicts-escalated"`.
|
|
57
|
+
|
|
58
|
+
## Guardrails
|
|
59
|
+
|
|
60
|
+
- Max 2 resolve attempts per SHA; then escalate rather than churn.
|
|
61
|
+
- Never push an unverified merge — verify-or-rollback always.
|
|
62
|
+
- The default `autoResolveConflicts=never` escalates to the user rather than guessing a merge. Auto-resolution is strictly opt-in.
|
|
@@ -55,7 +55,7 @@ When invoked with the directive (PR URL + slug + review ids), routes to [`../do/
|
|
|
55
55
|
|
|
56
56
|
## Input routing
|
|
57
57
|
|
|
58
|
-
`/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and
|
|
58
|
+
`/muggle-do` serves one interactive mode (the forward pipeline, from a fresh task) and four programmatic modes the watcher dispatches (address-reviews, fix-ci, resolve-conflicts, post-merge cleanup). Resolve `$ARGUMENTS` to a mode per [`../do/input-routing.md`](../do/input-routing.md) before doing anything else.
|
|
59
59
|
|
|
60
60
|
## Preferences
|
|
61
61
|
|
|
@@ -63,6 +63,7 @@ When invoked with the directive (PR URL + slug + review ids), routes to [`../do/
|
|
|
63
63
|
| :--------- | :--- |
|
|
64
64
|
| `autoE2ETest` | Stage 6 — run E2E every cycle (default `always`), or fold into pre-flight |
|
|
65
65
|
| `autoResolveConflicts` | On rebase conflict — resolve autonomously behind a verify-or-rollback gate (opt-in), or abort + escalate (default `never`) |
|
|
66
|
+
| `autoRouteBuildToMuggleDo` | Front-door guardrail — route build/implement/fix requests through this pipeline (build delegated to superpowers); fired by the UserPromptSubmit guardrail, default `ask` |
|
|
66
67
|
|
|
67
68
|
`autoUseWorktree`, `autoRebase`, `autoResolveConflicts`, `autoCreatePR`, `autoCleanup` fire from per-stage files.
|
|
68
69
|
|
|
@@ -7,7 +7,7 @@ description: Use this skill when the user wants a pull request's incoming review
|
|
|
7
7
|
|
|
8
8
|
> Telemetry first step: see [`../_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-pr-followup"`.
|
|
9
9
|
|
|
10
|
-
A watcher that babysits one open PR's review thread and
|
|
10
|
+
A watcher that babysits one open PR's review thread, CI, and merge-conflict state. Polls for new submitted reviews, check-run state, and mergeability; when review feedback lands, CI goes red, or the branch conflicts with its base, hands the work to `/muggle-do` and exits. On merge or close, it hands the terminal wrap-up to `/muggle-do` the same way — teardown when merged, then a next-step suggestion. `/muggle-do` is the executor — it classifies the reviews, fixes the failing checks, or rebases-and-resolves the conflict, pushes, replies per comment, and respawns the watcher.
|
|
11
11
|
|
|
12
12
|
**The watcher is a dumb pipe.** It does not classify reviews, iterate cycles, post replies, or escalate. All of that lives in `/muggle-do`. See [stage-8 design](../../../../muggle-ai-brain/architecture/2026-05-08-muggle-do-pr-comment-loop-design.md) for the rationale.
|
|
13
13
|
|