@muggleai/works 5.4.2 → 5.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/hooks/README.md +6 -0
- package/dist/plugin/hooks/hooks.json +20 -7
- 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/scripts/reconcile-stale-watchers.sh +59 -0
- package/dist/plugin/skills/CLAUDE.md +6 -0
- package/dist/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/dist/plugin/skills/_shared/post-merge-cleanup.md +2 -3
- package/dist/plugin/skills/_shared/rebase-before-e2e.md +1 -1
- 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 +2 -2
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
- 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 +2 -11
- package/dist/plugin/skills/do/fix-ci.md +3 -2
- package/dist/plugin/skills/do/resolve-conflicts.md +7 -13
- package/dist/plugin/skills/do/respawn-watcher.md +27 -0
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +7 -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 +6 -5
- package/dist/plugin/skills/muggle-pr-followup/contract.md +29 -7
- package/dist/plugin/skills/muggle-pr-followup/evals/evals.json +89 -0
- 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 +38 -5
- package/dist/plugin/skills/muggle-pr-followup/record-cron-id.md +12 -0
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +38 -4
- package/dist/plugin/skills/skill-deps.config.json +8 -0
- package/dist/release-manifest.json +4 -4
- package/package.json +7 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/hooks/README.md +6 -0
- package/plugin/hooks/hooks.json +20 -7
- 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/scripts/reconcile-stale-watchers.sh +59 -0
- package/plugin/skills/CLAUDE.md +6 -0
- package/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/plugin/skills/_shared/post-merge-cleanup.md +2 -3
- package/plugin/skills/_shared/rebase-before-e2e.md +1 -1
- 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 +2 -2
- package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
- 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 +2 -11
- package/plugin/skills/do/fix-ci.md +3 -2
- package/plugin/skills/do/resolve-conflicts.md +7 -13
- package/plugin/skills/do/respawn-watcher.md +27 -0
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
- package/plugin/skills/muggle-pr-followup/SKILL.md +7 -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 +6 -5
- package/plugin/skills/muggle-pr-followup/contract.md +29 -7
- package/plugin/skills/muggle-pr-followup/evals/evals.json +89 -0
- 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 +38 -5
- package/plugin/skills/muggle-pr-followup/record-cron-id.md +12 -0
- package/plugin/skills/muggle-pr-followup/state-schemas.md +38 -4
- package/plugin/skills/skill-deps.config.json +8 -0
|
@@ -30,6 +30,23 @@ A list of one entry. (Historical: the file is an array for forward-compat with t
|
|
|
30
30
|
- `state` is the **observed** state from the last `gh pr view`. The watcher refreshes it each tick.
|
|
31
31
|
- Terminal states (`merged`, `closed`) are sticky — once set, the watcher writes `result.md` and exits without rescheduling.
|
|
32
32
|
|
|
33
|
+
## `cron.json`
|
|
34
|
+
|
|
35
|
+
A durable, on-disk handle to this slot's watcher cron. Its whole reason to exist: `CronList` goes **blind to crons that outlive a session continue / compaction** (the watcher's `/loop` cron survives, but the tool can no longer enumerate it), so a teardown that can only find crons through `CronList` can never delete the orphan — it re-fires until the 7-day `/loop` expiry. A cron id recorded to disk **while the cron was still visible** stays a valid `CronDelete` target afterward. See [`record-cron-id.md`](record-cron-id.md) (who writes it) and [`cancel-cron.md`](cancel-cron.md) (who deletes by it).
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"cron_id": "<scheduler-id-or-null>",
|
|
40
|
+
"command": "/muggle:muggle-pr-followup <slug> <n>",
|
|
41
|
+
"interval": "1m",
|
|
42
|
+
"recorded_at": "<ISO-8601>"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `cron_id`: the scheduler id of the live `/loop` cron for this slot. Bootstrap seeds `null` (it dispatches `/loop` as its last action and cannot yet see the id); the first tick self-records the real id per [`record-cron-id.md`](record-cron-id.md). `null` again for the one tick after `/muggle-do` respawns the watcher (a dispatch cancels the old cron and the respawn arms a new one whose id is unknown until the next tick observes it).
|
|
47
|
+
- `command`: the exact two-arg dispatch, the same string [`cancel-cron.md`](cancel-cron.md) matches on as its `CronList` fallback.
|
|
48
|
+
- `interval`: the poll cadence — always `1m`. The watcher polls at `1m` whether or not the PR is blocked; a blocked PR reminds at `1m`, it does not back off (see [`blocked-tick.md`](blocked-tick.md) and [`contract.md`](contract.md) Steps 2.5 / 7). Recorded for teardown/forensics.
|
|
49
|
+
|
|
33
50
|
## `last_seen.json`
|
|
34
51
|
|
|
35
52
|
Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
|
|
@@ -45,8 +62,17 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
|
|
|
45
62
|
"pushed_shas": ["<sha>", ...],
|
|
46
63
|
"ci_fix_attempts": { "<sha>": <int> },
|
|
47
64
|
"ci_escalated_shas": ["<sha>", ...],
|
|
48
|
-
"conflict_resolve_attempts": { "<sha>": <int> },
|
|
49
|
-
"
|
|
65
|
+
"conflict_resolve_attempts": { "<head-sha>..<base-tip-sha>": <int> },
|
|
66
|
+
"conflict_escalated_keys": ["<head-sha>..<base-tip-sha>", ...],
|
|
67
|
+
"blocked": {
|
|
68
|
+
"reason": "conflict_escalated" | "ci_escalated" | "reviews_escalated",
|
|
69
|
+
"since": "<ISO-8601>",
|
|
70
|
+
"fingerprint": {
|
|
71
|
+
"head_sha": "<sha>",
|
|
72
|
+
"latest_review_id": <int>,
|
|
73
|
+
"ci_digest": "<string>"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
78
|
```
|
|
@@ -59,8 +85,16 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
|
|
|
59
85
|
- `pushed_shas`: every SHA `/muggle-do` has pushed for this PR. Append-only. Used by the resolve-reminder stage to recognize threads addressed by the loop.
|
|
60
86
|
- `ci_fix_attempts`: per-SHA count of fix-ci cycles `/muggle-do` has run. The watcher stops dispatching fix-ci for a SHA once its count reaches 3. Keyed by head SHA.
|
|
61
87
|
- `ci_escalated_shas`: head SHAs whose CI the fix-ci stage gave up on (attempts exhausted or only out-of-scope checks). The watcher excludes these from CI dispatch so a hopeless SHA is never re-fixed.
|
|
62
|
-
- `conflict_resolve_attempts`:
|
|
63
|
-
- `
|
|
88
|
+
- `conflict_resolve_attempts`: count of rebase cycles `/muggle-do` has run (behind-only or conflicting — both rebase onto the base). The watcher stops dispatching once a key's count reaches 2. Keyed by `rebase_key` — `"<head_sha>..<base_tip_sha>"`, the head paired with the base branch tip it was measured against.
|
|
89
|
+
- `conflict_escalated_keys`: `rebase_key`s whose rebase `/muggle-do` gave up on (attempts exhausted, or a conflict under `autoResolveConflicts=never`). The watcher excludes these from rebase dispatch so a hopeless pairing is never re-attempted.
|
|
90
|
+
|
|
91
|
+
Both are keyed on the pair, not the head alone, because whether a branch conflicts depends on both sides. Under a head-only key, a base that moves produces a genuinely new conflict against an unchanged head — and the stale entry suppresses it permanently, because nothing can change the head while the branch sits blocked. Pairing re-arms the budget whenever either side moves. Legacy entries written before this change are bare SHAs with no `..`; readers ignore them, which un-wedges any slot they had blocked.
|
|
92
|
+
|
|
93
|
+
Unlike these, `ci_fix_attempts` / `ci_escalated_shas` stay keyed on the head SHA alone — a CI result is a function of the head only, so base movement must not re-arm them.
|
|
94
|
+
- `blocked`: present only while the watcher is **awaiting the owner** on a PR that cannot progress without a human ([`contract.md`](contract.md) Step 7). Absent ⇒ the watcher is in its normal dispatch flow. When present, the watcher **keeps the normal `1m` cadence** and each tick is a reminder-or-resume check ([`contract.md`](contract.md) Step 2.5): it re-emits a one-line reminder to the owner, recomputes the `fingerprint`, and clears the block the moment any component moves. Its value is the reason-specific reminder plus fingerprint auto-resume.
|
|
95
|
+
- `reason`: which durable block is being awaited — `conflict_escalated` (`rebase_key` ∈ `conflict_escalated_keys`), `ci_escalated` (`head_sha` ∈ `ci_escalated_shas`), or `reviews_escalated` (a review sits in `escalated_review_ids` awaiting the user, actionable set empty). Selects the reminder wording; the resume decision is fingerprint-driven, not reason-driven.
|
|
96
|
+
- `since`: when the block was first flagged — lets the reminder state how long the owner has been the blocker.
|
|
97
|
+
- `fingerprint`: the external state the block is waiting on. `head_sha` moves on a new push (which also clears the per-SHA escalation sets, keyed by SHA); `latest_review_id` is `max(id)` over submitted reviews and moves when a reviewer submits anything new; `ci_digest` is a stable digest of the head SHA's CI rollup (bucket + each check's name/conclusion, sorted) and moves when a check flips, a rerun lands, or an external check such as a staging deploy posts. Any change clears the block and resumes evaluation.
|
|
64
98
|
|
|
65
99
|
## `state.md`
|
|
66
100
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"supportDirs": { "do": "muggle-do" },
|
|
3
|
+
"sharedNamespaces": ["_shared"],
|
|
4
|
+
"knownReverseDeps": {
|
|
5
|
+
"comment": "Reverse dependencies grandfathered so CI stays green — each is a genuine violation of plugin/skills/CLAUDE.md (a shared module or lower-level skill linking UP into a caller). Currently empty: the tree is one-way. If an entry is ever added here, it is debt to fix and delete, not a pattern to copy. The guard blocks any NEW cycle regardless of this list.",
|
|
6
|
+
"edges": []
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release": "5.
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-07-
|
|
2
|
+
"release": "5.5.1",
|
|
3
|
+
"buildId": "run-64-1",
|
|
4
|
+
"commitSha": "60e0213974d01fae642c7b480589d2e6b512f2c7",
|
|
5
|
+
"buildTime": "2026-07-20T23:29:20Z",
|
|
6
6
|
"serviceName": "muggle-ai-works-mcp"
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muggleai/works",
|
|
3
3
|
"mcpName": "io.github.multiplex-ai/muggle",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.5.1",
|
|
5
5
|
"description": "Ship quality products with AI-powered E2E acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"build:release": "npm run build",
|
|
23
23
|
"verify:plugin": "node scripts/verify-plugin-marketplace.mjs",
|
|
24
24
|
"verify:contracts": "node scripts/verify-compatibility-contracts.mjs",
|
|
25
|
+
"verify:skill-deps": "node scripts/check-skill-deps.mjs",
|
|
25
26
|
"smoke:cli": "node scripts/smoke-cli.mjs",
|
|
26
27
|
"verify:electron-release-checksums": "node scripts/verify-electron-release-checksums.mjs",
|
|
27
28
|
"verify:upgrade-experience": "node scripts/verify-upgrade-experience.mjs",
|
|
@@ -45,14 +46,14 @@
|
|
|
45
46
|
"eval:studio-gen": "tsx internal/studio-gen-eval/src/run.ts"
|
|
46
47
|
},
|
|
47
48
|
"muggleConfig": {
|
|
48
|
-
"electronAppVersion": "1.6.
|
|
49
|
+
"electronAppVersion": "1.6.10",
|
|
49
50
|
"downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
|
|
50
51
|
"runtimeTargetDefault": "production",
|
|
51
52
|
"checksums": {
|
|
52
|
-
"darwin-arm64": "
|
|
53
|
-
"darwin-x64": "
|
|
54
|
-
"linux-x64": "
|
|
55
|
-
"win32-x64": "
|
|
53
|
+
"darwin-arm64": "11672d444a151125a4d2f3413a7f0f4f8cb2de64ecbd2a43eddca4f364743b13",
|
|
54
|
+
"darwin-x64": "6277246853cdcc3aa3fbbaae703d428cfbf85c53fda6720a71a0cadbe8f5a5f6",
|
|
55
|
+
"linux-x64": "9485e0f9f4a0fb90666e63e87bd83da3c4b865a8cff8da40dc4d1b0ad2aba242",
|
|
56
|
+
"win32-x64": "e68471bcbf4f00d501067a282e835d00bb2f4941dc42878a8f041a5dab06acdf"
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
@@ -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.1",
|
|
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.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/plugin/hooks/README.md
CHANGED
|
@@ -35,3 +35,9 @@ Each guardrail is a thin bash wrapper in `../scripts/` registered in `hooks.json
|
|
|
35
35
|
| `PreToolUse` (Bash) | `guardrail-report-format.sh` | **enforce** | a `gh pr comment\|create\|edit` body reads like an E2E report but lacks the `build-pr-section` sentinel | — | **deny** — render via `muggle build-pr-section` instead |
|
|
36
36
|
| `Stop` | `guardrail-e2e-gate.sh` | **enforce** | unit tests passed this session and no E2E ran yet | `autoE2ETest` | **block** the turn until E2E runs via `muggle-test` (releases after 3 blocks) |
|
|
37
37
|
| `UserPromptSubmit` | `guardrail-build-router.sh` | advise | a build/implement/fix request (first one this session) | `autoRouteBuildToMuggleDo` | route the work through `muggle-do` (build delegated to superpowers) |
|
|
38
|
+
|
|
39
|
+
## Session-start reconcile nudge
|
|
40
|
+
|
|
41
|
+
`SessionStart` (`scripts/reconcile-stale-watchers.sh`) — a standalone advisory, not part of the `guardrails.mjs` decision tree above.
|
|
42
|
+
|
|
43
|
+
`muggle-pr-followup` watchers are session-only `/loop` crons; they die on session end and the 7-day `/loop` expiry, leaving open PRs with no live poller. The skill's [`reconcile`](../skills/muggle-pr-followup/reconcile.md) procedure recovers them — finalizes slots whose PR went terminal, sweeps orphan crons, re-arms silently-stopped open watchers — but re-arming needs the `CronCreate` tool, which a shell hook can't call. So this hook nudges rather than acts: it scans `~/.muggle-ai/muggle-do/sessions/*/` for open slots (a `prs.json` with no `result.md`) and, **only when one or more exist**, injects `additionalContext` telling the agent to run `/muggle:muggle-pr-followup reconcile`. Zero open slots → it emits nothing. A pure directory scan (no `gh`, no writes), so it's cheap enough for every session start.
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
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
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "command",
|
|
15
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/reconcile-stale-watchers.sh\"",
|
|
16
|
+
"async": false,
|
|
17
|
+
"timeout": 10
|
|
11
18
|
}
|
|
12
19
|
]
|
|
13
20
|
}
|
|
@@ -19,7 +26,8 @@
|
|
|
19
26
|
{
|
|
20
27
|
"type": "command",
|
|
21
28
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-report-format.sh\"",
|
|
22
|
-
"async": false
|
|
29
|
+
"async": false,
|
|
30
|
+
"timeout": 10
|
|
23
31
|
}
|
|
24
32
|
]
|
|
25
33
|
}
|
|
@@ -31,12 +39,14 @@
|
|
|
31
39
|
{
|
|
32
40
|
"type": "command",
|
|
33
41
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-pr-opened.sh\"",
|
|
34
|
-
"async": false
|
|
42
|
+
"async": false,
|
|
43
|
+
"timeout": 10
|
|
35
44
|
},
|
|
36
45
|
{
|
|
37
46
|
"type": "command",
|
|
38
47
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
|
|
39
|
-
"async": false
|
|
48
|
+
"async": false,
|
|
49
|
+
"timeout": 10
|
|
40
50
|
}
|
|
41
51
|
]
|
|
42
52
|
},
|
|
@@ -46,7 +56,8 @@
|
|
|
46
56
|
{
|
|
47
57
|
"type": "command",
|
|
48
58
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-record-tests.sh\"",
|
|
49
|
-
"async": false
|
|
59
|
+
"async": false,
|
|
60
|
+
"timeout": 10
|
|
50
61
|
}
|
|
51
62
|
]
|
|
52
63
|
}
|
|
@@ -57,7 +68,8 @@
|
|
|
57
68
|
{
|
|
58
69
|
"type": "command",
|
|
59
70
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-e2e-gate.sh\"",
|
|
60
|
-
"async": false
|
|
71
|
+
"async": false,
|
|
72
|
+
"timeout": 10
|
|
61
73
|
}
|
|
62
74
|
]
|
|
63
75
|
}
|
|
@@ -68,7 +80,8 @@
|
|
|
68
80
|
{
|
|
69
81
|
"type": "command",
|
|
70
82
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/guardrail-build-router.sh\"",
|
|
71
|
-
"async": false
|
|
83
|
+
"async": false,
|
|
84
|
+
"timeout": 10
|
|
72
85
|
}
|
|
73
86
|
]
|
|
74
87
|
}
|
|
@@ -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 '{}'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
# muggle-pr-followup watchers are session-only crons — they die on session end
|
|
6
|
+
# and on the 7-day /loop expiry, leaving open PRs with no live poller. Re-arming
|
|
7
|
+
# needs CronCreate, a Claude tool a shell hook can't call, so this hook can't
|
|
8
|
+
# recover a watcher itself. It nudges instead: on session start, if any open slot
|
|
9
|
+
# exists, it tells the agent to run reconcile (which finalizes terminal slots and
|
|
10
|
+
# re-arms silently-dead open watchers). A pure directory scan — no gh, no writes —
|
|
11
|
+
# so it stays cheap enough to run on every session start.
|
|
12
|
+
|
|
13
|
+
escape_for_json() {
|
|
14
|
+
local s="$1"
|
|
15
|
+
s="${s//\\/\\\\}"
|
|
16
|
+
s="${s//\"/\\\"}"
|
|
17
|
+
s="${s//$'\n'/\\n}"
|
|
18
|
+
s="${s//$'\r'/\\r}"
|
|
19
|
+
s="${s//$'\t'/\\t}"
|
|
20
|
+
printf '%s' "$s"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# A slot is a live-or-dead open watcher iff it has prs.json (a tracked PR) but no
|
|
24
|
+
# result.md (not yet finalized). result.md is the terminal marker.
|
|
25
|
+
sessions_dir="${HOME}/.muggle-ai/muggle-do/sessions"
|
|
26
|
+
stale_count=0
|
|
27
|
+
if [ -d "$sessions_dir" ]; then
|
|
28
|
+
for slot in "$sessions_dir"/*/; do
|
|
29
|
+
[ -d "$slot" ] || continue
|
|
30
|
+
if [ -f "${slot}prs.json" ] && [ ! -f "${slot}result.md" ]; then
|
|
31
|
+
stale_count=$((stale_count + 1))
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Clean state: no open slots to reconcile → stay completely silent, never nudge.
|
|
37
|
+
if [ "$stale_count" -eq 0 ]; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [ "$stale_count" -eq 1 ]; then
|
|
42
|
+
slot_word="slot"
|
|
43
|
+
else
|
|
44
|
+
slot_word="slots"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
context="muggle-pr-followup: ${stale_count} open watcher ${slot_word} found (a tracked PR with no result.md). Session-only watcher crons die on session end and after the 7-day /loop expiry, so these may have no live poller. Run \`/muggle:muggle-pr-followup reconcile\` to finalize any whose PR went terminal and re-arm silently-stopped open watchers. Reconcile is idempotent — it re-arms only genuinely dead watchers and stays quiet on the rest."
|
|
48
|
+
|
|
49
|
+
escaped_context=$(escape_for_json "$context")
|
|
50
|
+
|
|
51
|
+
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
|
|
52
|
+
printf '{\n "additional_context": "%s"\n}\n' "$escaped_context"
|
|
53
|
+
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
54
|
+
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$escaped_context"
|
|
55
|
+
else
|
|
56
|
+
printf '{\n "additional_context": "%s"\n}\n' "$escaped_context"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
exit 0
|
package/plugin/skills/CLAUDE.md
CHANGED
|
@@ -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
|
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Gated by [`autoCleanup`](../muggle-preferences/preference-gates/autoCleanup.md). Follow the standard procedure in [`preference-gates/README.md`](../muggle-preferences/preference-gates/README.md). Fire only after the PR is **merged** — never while it's still open.
|
|
4
4
|
|
|
5
|
-
On `always`, the
|
|
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. `git worktree remove {worktreePath}
|
|
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
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
9
|
3. Clear `.muggle-ai/` session folders for this branch's runs and stale `/tmp/muggle-prepare-*.log` files. Cloud results stay.
|
|
10
|
-
4. Invoke `commit-commands:clean_gone` via the `Skill` tool.
|
|
@@ -16,6 +16,6 @@ Pass `{behind}` and `{default}` to the picker prompts. On `always`:
|
|
|
16
16
|
2. `git rebase origin/${default}`.
|
|
17
17
|
3. On conflict, branch by [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
|
|
18
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
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Writing session-state files
|
|
2
2
|
|
|
3
|
-
How to update the session-state JSON — `last_seen.json` and `prs.json` under `~/.muggle-ai/muggle-do/sessions/<slug>/`. Field shapes: [
|
|
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
4
|
|
|
5
5
|
## Mechanism — tool-based, OS-agnostic
|
|
6
6
|
|
|
@@ -18,5 +18,5 @@ If you script the rewrite instead of using the Write tool, any tool that replace
|
|
|
18
18
|
|
|
19
19
|
## Field map
|
|
20
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`, `
|
|
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_keys` (arrays), `ci_fix_attempts[<sha>]` (keyed by head SHA), `conflict_resolve_attempts[<head-sha>..<base-tip-sha>]` (keyed by the head/base-tip pair).
|
|
22
22
|
- `prs.json` — a one-element array. Mutate `[0]`: `head_sha`, `state`.
|
|
@@ -30,6 +30,6 @@ One per address-reviews invocation, regardless of outcome.
|
|
|
30
30
|
- `"ci-fixed"` — a watcher-dispatched fix-ci cycle pushed a fix for one or more red checks.
|
|
31
31
|
- `"ci-escalated"` — fix-ci exhausted its 3 attempts for the SHA or the failing checks were out of scope; the SHA was added to `ci_escalated_shas`. No further auto-fix on it.
|
|
32
32
|
- `"rebased"` — a watcher-dispatched rebase cycle rebased the branch onto its base (behind-only or conflicts resolved), verified, and force-pushed.
|
|
33
|
-
- `"rebase-escalated"` — the rebase couldn't be completed (a conflict under `autoResolveConflicts=never`, verification failed, or the 2-attempt budget for the
|
|
33
|
+
- `"rebase-escalated"` — the rebase couldn't be completed (a conflict under `autoResolveConflicts=never`, verification failed, or the 2-attempt budget for the head/base-tip pair was spent); the pair was added to `conflict_escalated_keys`. No further auto-rebase until either side moves.
|
|
34
34
|
|
|
35
35
|
For fix-ci cycles (`ci-fixed` / `ci-escalated`) the `review_ids_*` arrays are empty and the `ci_checks_*` arrays carry the data: `ci_checks_in` (red checks dispatched), `ci_checks_fixed` (made green and pushed), `ci_checks_escalated` (out-of-scope or unresolved). For rebase cycles (`rebased` / `rebase-escalated`) all the `review_ids_*` and `ci_checks_*` arrays are empty; the SHA fields carry the before/after of the rebase.
|
|
@@ -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
|
|