@muggleai/works 5.4.1 → 5.5.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/README.md +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/hooks/hooks.json +14 -7
- package/dist/plugin/scripts/ensure-electron-app.sh +22 -2
- package/dist/plugin/scripts/guardrail-build-router.sh +17 -3
- package/dist/plugin/scripts/guardrail-e2e-gate.sh +33 -3
- package/dist/plugin/scripts/guardrail-pr-opened.sh +15 -4
- package/dist/plugin/scripts/guardrail-record-tests.sh +19 -6
- package/dist/plugin/scripts/guardrail-report-format.sh +17 -3
- package/dist/plugin/skills/CLAUDE.md +6 -0
- package/dist/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/dist/plugin/skills/_shared/rebase-before-e2e.md +2 -2
- package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -1
- package/dist/plugin/skills/_shared/resolve-rebase-conflicts.md +9 -33
- package/dist/plugin/skills/_shared/session-state-writes.md +22 -0
- package/dist/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +2 -0
- package/dist/plugin/skills/_shared/use-worktrees.md +0 -1
- package/dist/plugin/skills/_shared/vcs/github/verify-working-tree.md +1 -1
- package/dist/plugin/skills/_shared/verify-or-rollback-gate.md +39 -0
- package/dist/plugin/skills/do/address-reviews.md +4 -11
- package/dist/plugin/skills/do/fix-ci.md +4 -3
- package/dist/plugin/skills/do/open-prs/update.md +1 -1
- package/dist/plugin/skills/do/resolve-conflicts.md +5 -11
- package/dist/plugin/skills/do/respawn-watcher.md +27 -0
- package/dist/plugin/skills/muggle-do/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -2
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +5 -1
- package/dist/plugin/skills/muggle-pr-followup/auto-track.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/blocked-tick.md +38 -0
- package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +3 -1
- package/dist/plugin/skills/muggle-pr-followup/cancel-cron.md +12 -0
- package/dist/plugin/skills/muggle-pr-followup/contract.md +28 -6
- package/dist/plugin/skills/muggle-pr-followup/finalize.md +1 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates/blocked-reminder.md +30 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +11 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates.md +2 -1
- package/dist/plugin/skills/muggle-pr-followup/reconcile.md +28 -5
- package/dist/plugin/skills/muggle-pr-followup/record-cron-id.md +12 -0
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +32 -2
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoResolveConflicts.md +1 -1
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +1 -1
- package/dist/plugin/skills/skill-deps.config.json +8 -0
- package/dist/release-manifest.json +4 -4
- package/package.json +11 -7
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +14 -7
- package/plugin/scripts/ensure-electron-app.sh +22 -2
- package/plugin/scripts/guardrail-build-router.sh +17 -3
- package/plugin/scripts/guardrail-e2e-gate.sh +33 -3
- package/plugin/scripts/guardrail-pr-opened.sh +15 -4
- package/plugin/scripts/guardrail-record-tests.sh +19 -6
- package/plugin/scripts/guardrail-report-format.sh +17 -3
- package/plugin/skills/CLAUDE.md +6 -0
- package/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/plugin/skills/_shared/rebase-before-e2e.md +2 -2
- package/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -1
- package/plugin/skills/_shared/resolve-rebase-conflicts.md +9 -33
- package/plugin/skills/_shared/session-state-writes.md +22 -0
- package/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +2 -0
- package/plugin/skills/_shared/use-worktrees.md +0 -1
- package/plugin/skills/_shared/vcs/github/verify-working-tree.md +1 -1
- package/plugin/skills/_shared/verify-or-rollback-gate.md +39 -0
- package/plugin/skills/do/address-reviews.md +4 -11
- package/plugin/skills/do/fix-ci.md +4 -3
- package/plugin/skills/do/open-prs/update.md +1 -1
- package/plugin/skills/do/resolve-conflicts.md +5 -11
- package/plugin/skills/do/respawn-watcher.md +27 -0
- package/plugin/skills/muggle-do/SKILL.md +1 -1
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -2
- package/plugin/skills/muggle-pr-followup/SKILL.md +5 -1
- package/plugin/skills/muggle-pr-followup/auto-track.md +1 -1
- package/plugin/skills/muggle-pr-followup/blocked-tick.md +38 -0
- package/plugin/skills/muggle-pr-followup/bootstrap.md +3 -1
- package/plugin/skills/muggle-pr-followup/cancel-cron.md +12 -0
- package/plugin/skills/muggle-pr-followup/contract.md +28 -6
- package/plugin/skills/muggle-pr-followup/finalize.md +1 -1
- package/plugin/skills/muggle-pr-followup/output-templates/blocked-reminder.md +30 -0
- package/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +11 -1
- package/plugin/skills/muggle-pr-followup/output-templates.md +2 -1
- package/plugin/skills/muggle-pr-followup/reconcile.md +28 -5
- package/plugin/skills/muggle-pr-followup/record-cron-id.md +12 -0
- package/plugin/skills/muggle-pr-followup/state-schemas.md +32 -2
- package/plugin/skills/muggle-preferences/preference-gates/autoResolveConflicts.md +1 -1
- package/plugin/skills/muggle-test-feature-local/SKILL.md +1 -1
- package/plugin/skills/skill-deps.config.json +8 -0
package/README.md
CHANGED
|
@@ -555,7 +555,7 @@ CI/CD and publishing
|
|
|
555
555
|
| `verify-end-user-upgrade.yml` | Weekly + manual | Existing-user upgrade validation (cleanup + re-download + health checks) |
|
|
556
556
|
| `publish-works-to-npm.yml` | Tag `v*` or manual | Verify (including release checksums), audit, smoke-install, publish to npm |
|
|
557
557
|
|
|
558
|
-
**Publishing `@muggleai/works`:** use the maintainer-only skill **`.claude/skills/muggle-works-npm-release/SKILL.md`** — a repo-local project skill (invoke `/muggle-works-npm-release` in Claude Code, or `/mrelease`, while working in this repo; mirrored to `.cursor/skills/` for Cursor). It is intentionally not in the published plugin (`plugin/skills/`) since it is maintainer-only. It does the bump + `pnpm run sync:versions`, local verify, `chore(release)` PR, merge, then `workflow_dispatch` with an explicit `version`. Do not rely on tagging alone while `package.json` / marketplace manifests on `master` are still old — CI can publish a version that does not match the checked-in manifests. Tag `v*` push remains a valid workflow trigger when it matches the merged release commit.
|
|
558
|
+
**Publishing `@muggleai/works`:** use the maintainer-only skill **`.claude/skills/muggle-works-npm-release/SKILL.md`** — a repo-local project skill (invoke `/muggle-works-npm-release` in Claude Code, or `/mrelease`, while working in this repo; mirrored to `.cursor/skills/` for Cursor). To invoke it from any project — not only when the cwd is this repo — run `pnpm run link:maintainer-skills`, which symlinks the maintainer skills from `.claude/skills/` into `~/.claude/skills/`; it is idempotent and safe to re-run if a skill moves. It is intentionally not in the published plugin (`plugin/skills/`) since it is maintainer-only. It does the bump + `pnpm run sync:versions`, local verify, `chore(release)` PR, merge, then `workflow_dispatch` with an explicit `version`. Do not rely on tagging alone while `package.json` / marketplace manifests on `master` are still old — CI can publish a version that does not match the checked-in manifests. Tag `v*` push remains a valid workflow trigger when it matches the merged release commit.
|
|
559
559
|
|
|
560
560
|
Release tag strategy
|
|
561
561
|
|
|
@@ -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": "5.
|
|
4
|
+
"version": "5.5.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.
|
|
5
|
+
"version": "5.5.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
9
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-electron-app.sh\"",
|
|
10
|
-
"async": false
|
|
10
|
+
"async": false,
|
|
11
|
+
"timeout": 120
|
|
11
12
|
}
|
|
12
13
|
]
|
|
13
14
|
}
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
{
|
|
20
21
|
"type": "command",
|
|
21
22
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-report-format.sh\"",
|
|
22
|
-
"async": false
|
|
23
|
+
"async": false,
|
|
24
|
+
"timeout": 10
|
|
23
25
|
}
|
|
24
26
|
]
|
|
25
27
|
}
|
|
@@ -31,12 +33,14 @@
|
|
|
31
33
|
{
|
|
32
34
|
"type": "command",
|
|
33
35
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-pr-opened.sh\"",
|
|
34
|
-
"async": false
|
|
36
|
+
"async": false,
|
|
37
|
+
"timeout": 10
|
|
35
38
|
},
|
|
36
39
|
{
|
|
37
40
|
"type": "command",
|
|
38
41
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
|
|
39
|
-
"async": false
|
|
42
|
+
"async": false,
|
|
43
|
+
"timeout": 10
|
|
40
44
|
}
|
|
41
45
|
]
|
|
42
46
|
},
|
|
@@ -46,7 +50,8 @@
|
|
|
46
50
|
{
|
|
47
51
|
"type": "command",
|
|
48
52
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
|
|
49
|
-
"async": false
|
|
53
|
+
"async": false,
|
|
54
|
+
"timeout": 10
|
|
50
55
|
}
|
|
51
56
|
]
|
|
52
57
|
}
|
|
@@ -57,7 +62,8 @@
|
|
|
57
62
|
{
|
|
58
63
|
"type": "command",
|
|
59
64
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-e2e-gate.sh\"",
|
|
60
|
-
"async": false
|
|
65
|
+
"async": false,
|
|
66
|
+
"timeout": 10
|
|
61
67
|
}
|
|
62
68
|
]
|
|
63
69
|
}
|
|
@@ -68,7 +74,8 @@
|
|
|
68
74
|
{
|
|
69
75
|
"type": "command",
|
|
70
76
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-build-router.sh\"",
|
|
71
|
-
"async": false
|
|
77
|
+
"async": false,
|
|
78
|
+
"timeout": 10
|
|
72
79
|
}
|
|
73
80
|
]
|
|
74
81
|
}
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
+
# Epoch mtime of a file, or 0 if unreadable. GNU coreutils (Linux/WSL/Git Bash)
|
|
6
|
+
# need `stat -c %Y`; BSD/macOS need `stat -f %m`. The wrong form doesn't fail
|
|
7
|
+
# cleanly — GNU `stat -f` prints filesystem info to stdout and exits non-zero —
|
|
8
|
+
# so each form is tried alone and accepted only when all-digits, keeping stray
|
|
9
|
+
# text like `File:` out of the caller's arithmetic.
|
|
10
|
+
file_mtime() {
|
|
11
|
+
local f="$1" m
|
|
12
|
+
m=$(stat -c %Y "$f" 2>/dev/null) || m=""
|
|
13
|
+
case "$m" in ''|*[!0-9]*) ;; *) printf '%s\n' "$m"; return 0 ;; esac
|
|
14
|
+
m=$(stat -f %m "$f" 2>/dev/null) || m=""
|
|
15
|
+
case "$m" in ''|*[!0-9]*) ;; *) printf '%s\n' "$m"; return 0 ;; esac
|
|
16
|
+
printf '0\n'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Sourcing with MUGGLE_ENSURE_ELECTRON_LIB_ONLY=1 exposes the helper above
|
|
20
|
+
# without running the hook — used by the unit test.
|
|
21
|
+
if [ -n "${MUGGLE_ENSURE_ELECTRON_LIB_ONLY:-}" ]; then
|
|
22
|
+
return 0 2>/dev/null || exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
5
25
|
# Ensure the Electron browser test runner is installed/up to date (silent, best-effort).
|
|
6
26
|
#
|
|
7
27
|
# Bounded + cached: this script runs from a SessionStart hook on every Claude
|
|
@@ -16,7 +36,7 @@ ensure_ttl=$((24 * 60 * 60))
|
|
|
16
36
|
ensure_now=$(date +%s)
|
|
17
37
|
ensure_last=0
|
|
18
38
|
if [ -f "${ensure_marker}" ]; then
|
|
19
|
-
ensure_last=$(
|
|
39
|
+
ensure_last=$(file_mtime "${ensure_marker}")
|
|
20
40
|
fi
|
|
21
41
|
|
|
22
42
|
if [ $((ensure_now - ensure_last)) -ge "${ensure_ttl}" ]; then
|
|
@@ -60,7 +80,7 @@ version_check() {
|
|
|
60
80
|
now=$(date +%s)
|
|
61
81
|
|
|
62
82
|
if [ -f "$cache_file" ]; then
|
|
63
|
-
mtime=$(
|
|
83
|
+
mtime=$(file_mtime "$cache_file")
|
|
64
84
|
age=$((now - mtime))
|
|
65
85
|
if [ "$age" -lt "$ttl" ]; then
|
|
66
86
|
cached=$(cat "$cache_file" 2>/dev/null || true)
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
set -
|
|
2
|
+
set -uo pipefail
|
|
3
3
|
|
|
4
4
|
# Front-door router (UserPromptSubmit). On the first build/implement/fix prompt
|
|
5
5
|
# of a session, offers to route the work through /muggle-do (build delegated to
|
|
6
6
|
# superpowers), gated by autoRouteBuildToMuggleDo. Fires once per session.
|
|
7
|
-
#
|
|
7
|
+
#
|
|
8
|
+
# Node cold-start (spawn + module load) stalls the turn on a loaded box, and this
|
|
9
|
+
# hook runs on EVERY prompt. A cheap in-shell keyword pre-filter mirrors the build
|
|
10
|
+
# verbs guardrails.mjs looks for, so the vast majority of prompts (questions,
|
|
11
|
+
# status checks, chit-chat) never spawn Node. Node runs only on a keyword hit,
|
|
12
|
+
# then applies the real detectBuildIntent logic (question/slash exclusions,
|
|
13
|
+
# once-per-session dedupe). Over-matching here only costs an occasional needless
|
|
14
|
+
# spawn; it can never emit a spurious offer. Degrades to {} so it never blocks.
|
|
15
|
+
payload="$(cat)"
|
|
16
|
+
|
|
17
|
+
if ! grep -Eiq '(implement|build|add|create|write|fix|refactor|wire up|hook up|make|change the|conflict|merged|passing|green)' <<<"$payload"; then
|
|
18
|
+
printf '{}'
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
8
22
|
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
9
|
-
node "${root}/scripts/guardrails.mjs" build-router 2>/dev/null || printf '{}'
|
|
23
|
+
printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" build-router 2>/dev/null || printf '{}'
|
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
set -
|
|
2
|
+
set -uo pipefail
|
|
3
3
|
|
|
4
4
|
# tests-green → E2E gate (Stop). When unit tests passed this session and no E2E
|
|
5
5
|
# acceptance run has happened, offer to run change-driven E2E (gated by
|
|
6
|
-
# autoE2ETest). Fires once per session.
|
|
6
|
+
# autoE2ETest). Fires once per session.
|
|
7
|
+
#
|
|
8
|
+
# This must stay synchronous (only a sync Stop hook can block the turn end), and
|
|
9
|
+
# it fires on EVERY turn end. There is no command payload to key off, so the
|
|
10
|
+
# pre-filter reads the same per-session state file guardrails.mjs uses and only
|
|
11
|
+
# spawns Node when the gate could actually fire — i.e. shouldRunE2E: unit tests
|
|
12
|
+
# went green and no E2E run is recorded yet. On the overwhelming majority of
|
|
13
|
+
# turns (no test run this session) the state file is absent or unitTestsGreen is
|
|
14
|
+
# unset, so we return {} in-shell and never pay Node cold-start. Degrades to {}.
|
|
15
|
+
payload="$(cat)"
|
|
16
|
+
|
|
17
|
+
raw_sid="$(printf '%s' "$payload" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/')"
|
|
18
|
+
[ -n "$raw_sid" ] || raw_sid="unknown"
|
|
19
|
+
sid="$(printf '%s' "$raw_sid" | sed 's/[^A-Za-z0-9_-]/_/g')"
|
|
20
|
+
|
|
21
|
+
# Resolve the same home dir Node's os.homedir() uses. HOME is correct on
|
|
22
|
+
# macOS/Linux and on most Git Bash setups; fall back to converting USERPROFILE
|
|
23
|
+
# when HOME doesn't hold the state dir (some Windows shells point HOME elsewhere).
|
|
24
|
+
home="${HOME:-}"
|
|
25
|
+
if [ ! -d "$home/.muggle-ai" ] && command -v cygpath >/dev/null 2>&1 && [ -n "${USERPROFILE:-}" ]; then
|
|
26
|
+
home="$(cygpath -u "$USERPROFILE" 2>/dev/null || printf '%s' "$home")"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
state_file="$home/.muggle-ai/guardrails/$sid.json"
|
|
30
|
+
if [ ! -f "$state_file" ] \
|
|
31
|
+
|| ! grep -q '"unitTestsGreen": true' "$state_file" \
|
|
32
|
+
|| grep -q '"e2eRun": true' "$state_file"; then
|
|
33
|
+
printf '{}'
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
7
37
|
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
8
|
-
node "${root}/scripts/guardrails.mjs" e2e-gate 2>/dev/null || printf '{}'
|
|
38
|
+
printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" e2e-gate 2>/dev/null || printf '{}'
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
set -
|
|
2
|
+
set -uo pipefail
|
|
3
3
|
|
|
4
4
|
# PR-opened guardrail (PostToolUse/Bash). When a `gh pr create`/`gh pr ready`
|
|
5
5
|
# just succeeded, offer to start a muggle-pr-followup watcher on the new PR
|
|
6
6
|
# (gated by autoWatchPR, deduped per session). Decision logic lives in the
|
|
7
|
-
# bundled guardrails.mjs
|
|
8
|
-
#
|
|
7
|
+
# bundled guardrails.mjs.
|
|
8
|
+
#
|
|
9
|
+
# This fires after EVERY Bash call, so a keyword pre-filter for the PR-open
|
|
10
|
+
# commands keeps Node off the hot path — only a `gh pr create|ready` or
|
|
11
|
+
# `glab mr create|update` even reaches guardrails.mjs, which then confirms the
|
|
12
|
+
# command succeeded and extracts the URL. Degrades to {} so it never blocks.
|
|
13
|
+
payload="$(cat)"
|
|
14
|
+
|
|
15
|
+
if ! grep -Eiq 'gh[[:space:]]+pr[[:space:]]+(create|ready)|glab[[:space:]]+mr[[:space:]]+(create|update)' <<<"$payload"; then
|
|
16
|
+
printf '{}'
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
9
20
|
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
10
|
-
node "${root}/scripts/guardrails.mjs" pr-opened 2>/dev/null || printf '{}'
|
|
21
|
+
printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" pr-opened 2>/dev/null || printf '{}'
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
set -
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
# tests-green observer (PostToolUse/Bash + muggle E2E tools). Records in
|
|
5
|
+
# per-session state when a unit-test command passed (and when a muggle E2E run
|
|
6
|
+
# happened). Emits no directive — the Stop gate (guardrail-e2e-gate.sh) reads
|
|
7
|
+
# the state.
|
|
8
|
+
#
|
|
9
|
+
# Fires after every Bash call and every muggle execute/replay, so a keyword
|
|
10
|
+
# pre-filter for test runners and the muggle E2E tool names keeps Node off the
|
|
11
|
+
# hot path. Only a `test` command (npm/pnpm/yarn/jest/vitest/pytest/go/cargo) or
|
|
12
|
+
# a muggle execute/replay/test-generation event reaches guardrails.mjs, which
|
|
13
|
+
# then inspects the output for pass/fail and updates state. Degrades to {}.
|
|
14
|
+
payload="$(cat)"
|
|
15
|
+
|
|
16
|
+
if ! grep -Eiq '(pnpm|npm|yarn)[[:space:]]+(run[[:space:]]+)?test|jest|vitest|pytest|go[[:space:]]+test|cargo[[:space:]]+test|muggle.*(execute|test-generation|replay)' <<<"$payload"; then
|
|
17
|
+
printf '{}'
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
3
20
|
|
|
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
21
|
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
9
|
-
node "${root}/scripts/guardrails.mjs" record-tests 2>/dev/null || printf '{}'
|
|
22
|
+
printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" record-tests 2>/dev/null || printf '{}'
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
set -
|
|
2
|
+
set -uo pipefail
|
|
3
3
|
|
|
4
4
|
# Report-format gate (PreToolUse, Bash). Denies a `gh pr comment|create|edit`
|
|
5
5
|
# whose body reads like a hand-written E2E report — one that lacks the
|
|
6
6
|
# build-pr-section sentinel — so every posted walkthrough goes through the
|
|
7
|
-
# deterministic renderer.
|
|
7
|
+
# deterministic renderer.
|
|
8
|
+
#
|
|
9
|
+
# This must stay synchronous (only a sync PreToolUse hook can deny), and it fires
|
|
10
|
+
# before every Bash call. A keyword pre-filter for the three PR-posting commands
|
|
11
|
+
# keeps Node off the hot path: a plain `ls`/`git status`/build command returns {}
|
|
12
|
+
# in-shell and never pays cold-start. Only a `gh pr comment|create|edit` reaches
|
|
13
|
+
# guardrails.mjs, which reads the body (incl. --body-file) and decides. Degrades
|
|
14
|
+
# to {} so it never blocks an unrelated command.
|
|
15
|
+
payload="$(cat)"
|
|
16
|
+
|
|
17
|
+
if ! grep -Eiq 'gh[[:space:]]+pr[[:space:]]+(comment|create|edit)' <<<"$payload"; then
|
|
18
|
+
printf '{}'
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
8
22
|
root="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}"
|
|
9
|
-
node "${root}/scripts/guardrails.mjs" report-gate 2>/dev/null || printf '{}'
|
|
23
|
+
printf '%s' "$payload" | node "${root}/scripts/guardrails.mjs" report-gate 2>/dev/null || printf '{}'
|
|
@@ -14,6 +14,12 @@ A reverse reference (A → B and B → A) couples the depended-on skill to its c
|
|
|
14
14
|
|
|
15
15
|
When you feel the urge to link "up" to a caller, that is the smell — restructure so the caller passes what is needed in.
|
|
16
16
|
|
|
17
|
+
### Enforcement
|
|
18
|
+
|
|
19
|
+
`scripts/check-skill-deps.mjs` derives the cross-skill link graph and fails on any cycle. A "reference" is a markdown file-link into another skill's directory — runtime slash-command dispatch is not a link and is not counted. It runs three ways: the `skill-deps` CI job on every PR, a `PreToolUse` hook (`.claude/settings.json`) that blocks the write mid-session with the offending link named, and `pnpm run verify:skill-deps` locally.
|
|
20
|
+
|
|
21
|
+
`plugin/skills/skill-deps.config.json` declares support dirs grouped into their owning skill (`do/` → `muggle-do`), shared namespaces exploded to per-file nodes (`_shared`), and `knownReverseDeps` — pre-existing violations grandfathered so CI stays green. That list is debt: fix each link and delete its entry. A new reverse dependency is blocked whether or not it is on the list.
|
|
22
|
+
|
|
17
23
|
## Model tiers
|
|
18
24
|
|
|
19
25
|
Each skill sets a `model:` in its `SKILL.md` frontmatter sized to its cognitive load. `model:` is a native Claude Code field — the override applies while the skill is active and reverts to the session model when it exits. Cheaper, faster models run the mechanical skills; the default (Opus) is reserved for the ones that actually reason. Cost and latency scale with the model, and these skills run often (the watcher fires every minute), so the tier is a real lever, not cosmetics.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Dev Loop — Run a Test
|
|
2
2
|
|
|
3
|
-
> The local dev loop: run one test case in the browser (replay an existing script, or regenerate from the case), then record the result. Source of truth for the run mechanics; the sibling files in this folder own each invariant.
|
|
3
|
+
> The local dev loop: run one test case in the browser (replay an existing script, or regenerate from the case), then record the result. Source of truth for the run mechanics; the sibling files in this folder own each invariant.
|
|
4
4
|
|
|
5
5
|
Not owned here — the caller resolves and passes in: which test cases to run, replay-vs-regen classification and failure routing ([`../failure-mode-handling.md`](../failure-mode-handling.md)), dev-server readiness ([`../dev-server-readiness.md`](../dev-server-readiness.md)), validation context ([`../resolve-e2e-validation-context.md`](../resolve-e2e-validation-context.md)), and PR posting ([`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md)).
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ Pass `{behind}` and `{default}` to the picker prompts. On `always`:
|
|
|
15
15
|
1. Capture the rollback point: `pre_rebase_sha=$(git rev-parse HEAD)`.
|
|
16
16
|
2. `git rebase origin/${default}`.
|
|
17
17
|
3. On conflict, branch by [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
|
|
18
|
-
- `never`
|
|
19
|
-
- `always` → hand off to [`resolve-rebase-conflicts.md`](resolve-rebase-conflicts.md) with `pre_rebase_sha
|
|
18
|
+
- `never` → `git rebase --abort`; stop and report, naming the conflicted files. Never auto-resolve.
|
|
19
|
+
- `always` → hand off to [`resolve-rebase-conflicts.md`](resolve-rebase-conflicts.md) with `pre_rebase_sha` to resolve the conflicts, then run the [`verify-or-rollback-gate.md`](verify-or-rollback-gate.md) with `pre_rebase_sha`; it either proceeds or restores `pre_rebase_sha` and escalates.
|
|
20
20
|
|
|
21
21
|
Stale branches produce false failures and false greens — that's why this gate exists.
|
|
@@ -28,7 +28,7 @@ Resolve without prompting; use as questionnaire defaults:
|
|
|
28
28
|
|
|
29
29
|
## Questions
|
|
30
30
|
|
|
31
|
-
One `AskUserQuestion` for the validation subset, detected values as defaults.
|
|
31
|
+
One `AskUserQuestion` for the validation subset, detected values as defaults. Ask each with the same intent the interactive pre-flight uses; the caller supplies the exact option wording:
|
|
32
32
|
|
|
33
33
|
- Validation strategy — pre-flight Q4
|
|
34
34
|
- Local URL — pre-flight Q5 (defers to [`autoSelectLocalHost`](../muggle-preferences/preference-gates/autoSelectLocalHost.md))
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Resolve Rebase Conflicts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
How to resolve the conflicts a rebase onto `origin/{default}` reports, when [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md) is `always`. Under `never` the caller aborts and escalates instead, and this file never runs. The caller hands off the inputs below and, once resolution completes, runs the [`verify-or-rollback-gate.md`](verify-or-rollback-gate.md) before anything ships. This file names no caller — the dependency runs one way.
|
|
4
4
|
|
|
5
|
-
Contract:
|
|
5
|
+
Contract: resolve deterministically where the change is mechanical, reason about intent where it is semantic, and never guess in load-bearing logic — hand an unreconcilable conflict back to the caller rather than fabricating a merge.
|
|
6
6
|
|
|
7
7
|
## Inputs
|
|
8
8
|
|
|
9
|
-
- `pre_rebase_sha` — branch HEAD captured by the caller **before** `git rebase`; the rollback point.
|
|
9
|
+
- `pre_rebase_sha` — branch HEAD captured by the caller **before** `git rebase`; the caller passes it on to the verify-or-rollback gate as the rollback point.
|
|
10
10
|
- `default` — the branch being rebased onto.
|
|
11
|
-
- Session context: slug, PR url/number, and the persisted validation strategy (for the E2E step).
|
|
12
11
|
|
|
13
12
|
## Procedure
|
|
14
13
|
|
|
@@ -26,35 +25,12 @@ Classify each conflicted path:
|
|
|
26
25
|
### Step 2 — Resolve
|
|
27
26
|
|
|
28
27
|
- **Mechanical** → resolve deterministically: regenerate the lockfile with the repo's package manager; regenerate or take-incoming for generated files; run the repo's formatter. Never hand-merge a lockfile.
|
|
29
|
-
- **Semantic** → a reasoned 3-way resolution preserving the intent of **both** sides (ours = the PR's change, theirs = the new default-branch line). If intent in load-bearing logic can't be confidently reconciled, do not guess
|
|
28
|
+
- **Semantic** → a reasoned 3-way resolution preserving the intent of **both** sides (ours = the PR's change, theirs = the new default-branch line). If intent in load-bearing logic can't be confidently reconciled, do not guess: `git rebase --abort` and hand the unreconcilable paths back to the caller, which restores `pre_rebase_sha` and escalates via the verify-or-rollback gate.
|
|
30
29
|
|
|
31
|
-
Then `git add -A && git rebase --continue`, and repeat Steps 1–2 for each remaining conflicted commit until the rebase completes.
|
|
32
|
-
|
|
33
|
-
### Step 3 — Verify gate (mandatory)
|
|
34
|
-
|
|
35
|
-
Each must pass, in order:
|
|
36
|
-
|
|
37
|
-
1. **Build** — typecheck + lint on the changed surface, per [`../do/build.md`](../do/build.md).
|
|
38
|
-
2. **Unit suite** — per [`../do/unit-tests.md`](../do/unit-tests.md); record PASS.
|
|
39
|
-
3. **E2E** — per [`../do/e2e-acceptance.md`](../do/e2e-acceptance.md) and the persisted [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md) strategy. A poll-only session with no validation context reports `SKIPPED`, same as the normal cycle.
|
|
40
|
-
|
|
41
|
-
### Step 4 — Pass → proceed
|
|
42
|
-
|
|
43
|
-
Return success. The caller resumes the normal flow; the push happens downstream, so a resolved rebase ships only after it has verified.
|
|
44
|
-
|
|
45
|
-
### Step 5 — Fail → restore + escalate
|
|
46
|
-
|
|
47
|
-
On an unreconcilable semantic conflict (Step 2) or any verify failure (Step 3):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
git rebase --abort 2>/dev/null || true
|
|
51
|
-
git reset --hard <pre_rebase_sha>
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The branch is now byte-for-byte its pre-rebase state. Emit one terminal escalation per [`../muggle-pr-followup/output-templates/escalation.md`](../muggle-pr-followup/output-templates/escalation.md) naming the conflicted files and the failing step, plus the `muggle-do:escalation` event with `kind: "rebase-conflict"` ([`telemetry-events/muggle-do-escalation.md`](telemetry-events/muggle-do-escalation.md)). Do not push.
|
|
30
|
+
Then `git add -A && git rebase --continue`, and repeat Steps 1–2 for each remaining conflicted commit until the rebase completes. On success, return to the caller, which runs the verify-or-rollback gate before the resolved tree ships.
|
|
55
31
|
|
|
56
32
|
## Invariants
|
|
57
33
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
34
|
+
- Never fabricate a merge in load-bearing logic; an unreconcilable conflict goes back to the caller.
|
|
35
|
+
- This file resolves; it does not verify or push. The [`verify-or-rollback-gate.md`](verify-or-rollback-gate.md) owns the ship-or-rollback decision.
|
|
36
|
+
- Runs only under `autoResolveConflicts = always`; `never` is the caller's unchanged abort-and-escalate path.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Writing session-state files
|
|
2
|
+
|
|
3
|
+
How to update the session-state JSON — `last_seen.json` and `prs.json` under `~/.muggle-ai/muggle-do/sessions/<slug>/`. Field shapes: see the [`## Field map`](#field-map) below.
|
|
4
|
+
|
|
5
|
+
## Mechanism — tool-based, OS-agnostic
|
|
6
|
+
|
|
7
|
+
To apply any `increment` / `reset` / `set` / `append` a procedure calls for:
|
|
8
|
+
|
|
9
|
+
1. **Read** the whole file (Read tool).
|
|
10
|
+
2. Change the one field in the parsed JSON.
|
|
11
|
+
3. **Write** the whole file back (Write tool).
|
|
12
|
+
|
|
13
|
+
A whole-file rewrite with the Read and Write tools. These are platform-independent — no shell — so the same three steps hold on Windows, macOS, and Linux.
|
|
14
|
+
|
|
15
|
+
**Never use the Edit tool on these files.** Edit needs its `old_string` to match the file's exact bytes, but the on-disk formatting isn't guaranteed to match the shapes documented in `state-schemas.md` — a writer may emit the JSON on a single line. A mismatched `old_string` silently fails: the edit is dropped ("malformed edit") and the value never changes. A whole-file Write can't miss.
|
|
16
|
+
|
|
17
|
+
If you script the rewrite instead of using the Write tool, any tool that replaces the **whole file** is fine — e.g. `jq '…' file > tmp && mv tmp file` in a POSIX shell, or the equivalent `Get-Content`/`Set-Content` in PowerShell. The rule is only: whole file, never a partial Edit.
|
|
18
|
+
|
|
19
|
+
## Field map
|
|
20
|
+
|
|
21
|
+
- `last_seen.json` — one object keyed by `"<owner>/<repo>#<n>"`. Mutate fields under that key: `idle_tick_count`, `cycles_completed` (counters), `last_pushed_sha`, `lastBodyReviewId` (scalars), `pushed_shas`, `escalated_review_ids`, `ci_escalated_shas`, `conflict_escalated_shas` (arrays), `ci_fix_attempts[<sha>]`, `conflict_resolve_attempts[<sha>]` (per-SHA maps).
|
|
22
|
+
- `prs.json` — a one-element array. Mutate `[0]`: `head_sha`, `state`.
|
|
@@ -17,6 +17,7 @@ One per watcher iteration (idle or not).
|
|
|
17
17
|
"dispatched_ci_fix": true | false,
|
|
18
18
|
"terminal": true | false,
|
|
19
19
|
"idle": true | false,
|
|
20
|
+
"blocked": true | false,
|
|
20
21
|
"tick_duration_ms": <int>
|
|
21
22
|
}
|
|
22
23
|
```
|
|
@@ -29,3 +30,4 @@ One per watcher iteration (idle or not).
|
|
|
29
30
|
- `dispatched_ci_fix`: true when this tick dispatched `/muggle-do` with a fix-ci directive.
|
|
30
31
|
- `terminal`: true when this tick observed the PR merged or closed and wrote `result.md`.
|
|
31
32
|
- `idle`: true when nothing was dispatched this tick.
|
|
33
|
+
- `blocked`: true on a tick that idled on a durable human-block (escalated rebase/CI, or an ambiguous review awaiting the user) and emitted the one-line owner reminder. `false` otherwise. Always accompanies `idle: true`. The block reminds at the normal `1m` cadence — it never backs off — so a `blocked: true` tick reminds exactly once.
|
|
@@ -7,7 +7,6 @@ Use this for feature development, local validation, and PR iteration.
|
|
|
7
7
|
- **One worktree per branch.** Never switch branches inside a long-lived checkout.
|
|
8
8
|
- **Isolate runtime resources.** Parallel worktrees need unique ports and isolated mutable test state.
|
|
9
9
|
- **Keep worktrees disposable.** Create for focused work, remove after merge.
|
|
10
|
-
- **Existing PR branch?** Materialize it via [`pr-branch-worktree.md`](pr-branch-worktree.md) — same `<repo>/.claude/worktrees/` path scheme, checking out the existing branch instead of creating a new one.
|
|
11
10
|
|
|
12
11
|
## Start new change work
|
|
13
12
|
|
|
@@ -14,4 +14,4 @@ Accept any remote URL form for `<owner>/<repo>` (with or without trailing `.git`
|
|
|
14
14
|
- `git@github.com:<owner>/<repo>`
|
|
15
15
|
- `ssh://git@github.com/<owner>/<repo>`
|
|
16
16
|
|
|
17
|
-
Any mismatch →
|
|
17
|
+
Any mismatch → abort; the calling skill reports the wrong checkout to the user with its own message.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Verify-or-Rollback Gate
|
|
2
|
+
|
|
3
|
+
The mandatory gate a caller runs after an operation has mutated the working tree and that tree must be proven safe before anything ships — e.g. a resolved rebase onto `origin/{default}`. It verifies the changed surface; on any failure it restores the branch to its pre-mutation state and escalates. The caller hands off the inputs below; this file names no caller — the dependency runs one way.
|
|
4
|
+
|
|
5
|
+
Contract: never let a mutated tree ship without passing verification, and always keep the branch restorable to its pre-mutation state.
|
|
6
|
+
|
|
7
|
+
## Inputs
|
|
8
|
+
|
|
9
|
+
- `pre_rebase_sha` — branch HEAD the caller captured **before** the mutating operation; the rollback point.
|
|
10
|
+
- Session context: slug, PR url/number, and the persisted validation strategy (for the E2E step).
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
### Step 1 — Verify (mandatory)
|
|
15
|
+
|
|
16
|
+
Each must pass, in order:
|
|
17
|
+
|
|
18
|
+
1. **Build** — typecheck + lint on the changed surface, run via the caller's build step.
|
|
19
|
+
2. **Unit suite** — the caller's unit run; record PASS.
|
|
20
|
+
3. **E2E** — the caller's E2E step under the persisted [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md) strategy. A poll-only session with no validation context reports `SKIPPED`, same as the normal cycle.
|
|
21
|
+
|
|
22
|
+
### Step 2 — Pass → proceed
|
|
23
|
+
|
|
24
|
+
Return success. The caller resumes the normal flow; the push happens downstream, so a mutated tree ships only after it has verified.
|
|
25
|
+
|
|
26
|
+
### Step 3 — Fail → restore + escalate
|
|
27
|
+
|
|
28
|
+
On any verify failure — or when the caller reports the preceding operation could not complete:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git reset --hard <pre_rebase_sha>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The branch is now byte-for-byte its pre-mutation state. Emit one terminal escalation — the caller's escalation message — naming the failing step, plus the `muggle-do:escalation` event with `kind: "rebase-conflict"` ([`telemetry-events/muggle-do-escalation.md`](telemetry-events/muggle-do-escalation.md)). Do not push.
|
|
35
|
+
|
|
36
|
+
## Invariants
|
|
37
|
+
|
|
38
|
+
- A push never follows a verify failure.
|
|
39
|
+
- The branch is always restorable to `pre_rebase_sha`.
|
|
@@ -29,7 +29,7 @@ Read from `~/.muggle-ai/muggle-do/sessions/<slug>/`:
|
|
|
29
29
|
|
|
30
30
|
### Step 0 — Track the default branch
|
|
31
31
|
|
|
32
|
-
Before assembling work, rebase onto the latest default branch so the cycle addresses reviews against current master, not a stale base. Run [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) — gated by [`autoRebase`](../muggle-preferences/preference-gates/autoRebase.md), fires only when `behind > 0`. Conflict handling follows [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md): the default `never` aborts and escalates (`kind: "rebase-conflict"`); `always` resolves behind the verify-or-rollback gate. If the rebase escalates, stop the cycle — do not push.
|
|
32
|
+
Before assembling work, rebase onto the latest default branch so the cycle addresses reviews against current master, not a stale base. Run [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before-e2e.md) — gated by [`autoRebase`](../muggle-preferences/preference-gates/autoRebase.md), fires only when `behind > 0`. Conflict handling follows [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md): the default `never` aborts and escalates (`kind: "rebase-conflict"`); `always` resolves behind the verify-or-rollback gate. If the rebase escalates, stop the cycle — do not push, do not address reviews — but **skip to Step 6 to respawn the watcher**. Escalating the rebase does not end the PR; the poller must keep running (it will remind on the `conflict_escalated` block), so skipping respawn here is exactly the silent-stop bug [`respawn-watcher.md`](respawn-watcher.md) exists to prevent.
|
|
33
33
|
|
|
34
34
|
### Step 1 — Assemble the work set
|
|
35
35
|
|
|
@@ -103,6 +103,8 @@ Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable re
|
|
|
103
103
|
|
|
104
104
|
### Step 5 — Update session state
|
|
105
105
|
|
|
106
|
+
Apply each field write below as a whole-file rewrite (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md) — never the Edit tool.
|
|
107
|
+
|
|
106
108
|
- `last_seen.cycles_completed` += 1
|
|
107
109
|
- `last_seen.last_pushed_sha` = the new head SHA (update.md already wrote this; verify)
|
|
108
110
|
- `last_seen.lastBodyReviewId` = max(body-only input review ids ∪ last_seen.lastBodyReviewId) — line-comment threads need no watermark; they fall out of the actionable set once the per-comment reply carries the loop marker.
|
|
@@ -113,16 +115,7 @@ Invoke [`resolve-reminder.md`](resolve-reminder.md) once, regardless of whether
|
|
|
113
115
|
|
|
114
116
|
### Step 6 — Respawn the watcher
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
1. Write `result.md` per [`../muggle-pr-followup/state-schemas.md`](../muggle-pr-followup/state-schemas.md#resultmd).
|
|
119
|
-
2. Do **not** respawn the watcher.
|
|
120
|
-
|
|
121
|
-
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:
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
/loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
125
|
-
```
|
|
118
|
+
Respawn per [`respawn-watcher.md`](respawn-watcher.md): refresh PR state, finalize (write `result.md`, no respawn) if the PR is now merged or closed, otherwise restart the single live watcher as the turn's last action. **Every** exit path in this procedure lands here — the actionable-and-pushed happy path, the ambiguous-only branch (Step 3), the design-adjustment escalation, and the Step 0 rebase-escalation — so an open PR is never left un-watched.
|
|
126
119
|
|
|
127
120
|
### Step 7 — Telemetry
|
|
128
121
|
|
|
@@ -39,8 +39,8 @@ Commit per the `fix(ci): <check> — <what>` convention ([`../_shared/pr-followu
|
|
|
39
39
|
|
|
40
40
|
### Step 5 — Update state + respawn
|
|
41
41
|
|
|
42
|
-
- Increment `last_seen.ci_fix_attempts[red_sha]
|
|
43
|
-
- Respawn the watcher
|
|
42
|
+
- Increment `last_seen.ci_fix_attempts[red_sha]` — a whole-file rewrite (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md), never the Edit tool.
|
|
43
|
+
- Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md). 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
|
|
|
@@ -48,7 +48,8 @@ When the failing checks are all out of scope, or `ci_fix_attempts[red_sha]` has
|
|
|
48
48
|
|
|
49
49
|
1. Add `red_sha` to `last_seen.ci_escalated_shas` so the SHA is not re-fixed.
|
|
50
50
|
2. Emit one terminal message naming the unresolved checks.
|
|
51
|
-
3. Emit the cycle event with `outcome: "ci-escalated"` (Step 7).
|
|
51
|
+
3. Emit the cycle event with `outcome: "ci-escalated"` (Step 7).
|
|
52
|
+
4. **Respawn the watcher** per [`respawn-watcher.md`](respawn-watcher.md). Escalation stops fix-ci from re-dispatching this SHA — but the poller must keep running: it reminds on the `ci_escalated` block and catches a new push, a new review, or the user's own fix. Skipping respawn here is the silent-stop bug the helper exists to prevent.
|
|
52
53
|
|
|
53
54
|
### Step 7 — Telemetry
|
|
54
55
|
|
|
@@ -24,7 +24,7 @@ Resolve the provider once per [`../../_shared/vcs/detect-vcs.md`](../../_shared/
|
|
|
24
24
|
|
|
25
25
|
1. **Push:** per [`../../_shared/vcs/github/push-to-branch.md`](../../_shared/vcs/github/push-to-branch.md). Capture the new SHA.
|
|
26
26
|
|
|
27
|
-
2. **Append new SHA** to `last_seen.json[<key>].pushed_shas` (the resolve-reminder stage uses this to recognize threads addressed by the loop). Set `last_seen.last_pushed_sha` to the new SHA too.
|
|
27
|
+
2. **Append new SHA** to `last_seen.json[<key>].pushed_shas` (the resolve-reminder stage uses this to recognize threads addressed by the loop). Set `last_seen.last_pushed_sha` to the new SHA too. Both are whole-file rewrites (Read → change field → Write) per [`../../_shared/session-state-writes.md`](../../_shared/session-state-writes.md) — never the Edit tool.
|
|
28
28
|
|
|
29
29
|
3. **Refresh title if state changed.** Compare the new state against the current PR title prefix:
|
|
30
30
|
- E2E now passing, current title has `[E2E FAILING]` → strip the prefix per [`../../_shared/vcs/github/pr-edit.md`](../../_shared/vcs/github/pr-edit.md).
|
|
@@ -28,8 +28,8 @@ Run the rebase from [`../_shared/rebase-before-e2e.md`](../_shared/rebase-before
|
|
|
28
28
|
|
|
29
29
|
- **Clean replay** — a behind-only branch (and any rebase that hits no conflicts) replays without intervention. Proceed to Step 3.
|
|
30
30
|
- **Conflicts** — handle per [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
|
|
31
|
-
-
|
|
32
|
-
- `always` → resolve
|
|
31
|
+
- `never` → abort and escalate per Step 5 (`kind: "rebase-conflict"`). The watcher keeps polling; the user resolves on GitHub.
|
|
32
|
+
- `always` → resolve via [`../_shared/resolve-rebase-conflicts.md`](../_shared/resolve-rebase-conflicts.md); Steps 3 and 5 below are this mode's instance of the [`../_shared/verify-or-rollback-gate.md`](../_shared/verify-or-rollback-gate.md).
|
|
33
33
|
|
|
34
34
|
### Step 3 — Verify the resolution
|
|
35
35
|
|
|
@@ -37,13 +37,7 @@ Build (typecheck + lint on the changed surface) + unit suite must pass. Run E2E
|
|
|
37
37
|
|
|
38
38
|
### Step 4 — Force-push + respawn
|
|
39
39
|
|
|
40
|
-
Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_sha]
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
/loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
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 the branch against its base on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
|
|
40
|
+
Push with `--force-with-lease` (the rebase rewrote history). Append the new SHA to `last_seen.pushed_shas`; increment `last_seen.conflict_resolve_attempts[rebase_sha]` — both whole-file rewrites (Read → change field → Write) per [`../_shared/session-state-writes.md`](../_shared/session-state-writes.md), never the Edit tool. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md). Its next tick re-checks the branch against its base on the new head — the rebase is its own verify loop, bounded by the per-SHA attempt budget.
|
|
47
41
|
|
|
48
42
|
### Step 5 — Escalate (can't resolve / budget spent)
|
|
49
43
|
|
|
@@ -51,7 +45,7 @@ When `autoResolveConflicts=never`, the resolution failed verification, or `confl
|
|
|
51
45
|
|
|
52
46
|
1. Add `rebase_sha` to `last_seen.conflict_escalated_shas` so the watcher does not re-dispatch this SHA.
|
|
53
47
|
2. Emit one terminal escalation naming the PR and the conflicting files (or the failing verification, for a behind-only rebase that didn't verify).
|
|
54
|
-
3. Respawn the watcher (
|
|
48
|
+
3. Respawn the watcher per [`respawn-watcher.md`](respawn-watcher.md) — it keeps polling for the user's manual resolution or any new reviews.
|
|
55
49
|
|
|
56
50
|
### Step 6 — Telemetry
|
|
57
51
|
|
|
@@ -61,4 +55,4 @@ Emit one `muggle-do:cycle` event ([`../_shared/telemetry-events/muggle-do-cycle.
|
|
|
61
55
|
|
|
62
56
|
- Max 2 rebase attempts per SHA; then escalate rather than churn.
|
|
63
57
|
- Never push an unverified rebase — verify-or-rollback always.
|
|
64
|
-
-
|
|
58
|
+
- Resolve `autoResolveConflicts` from the configured preference (per the gate contract — don't assume a default): `always` resolves conflicts behind the verify-or-rollback gate, `never` escalates to the user. A clean behind-only rebase needs neither.
|