@muggleai/works 5.11.1 → 5.12.0-staging.73
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/config/runtime-targets.json +33 -0
- package/dist/{chunk-MSPMXBO5.js → chunk-FCDHJD6I.js} +441 -347
- package/dist/{chunk-WUMAJOBT.js → chunk-O7LFABOC.js} +39 -35
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/hooks/README.md +4 -1
- package/dist/plugin/hooks/hooks.json +24 -0
- package/dist/plugin/scripts/guardrail-capability-claim.sh +38 -0
- package/dist/plugin/scripts/guardrail-comment-reply-gate.sh +42 -0
- package/dist/plugin/scripts/guardrail-debug-path-gate.sh +1 -1
- package/dist/plugin/scripts/guardrail-e2e-gate.sh +1 -1
- package/dist/plugin/scripts/guardrail-record-comment-replies.sh +28 -0
- package/dist/plugin/scripts/guardrail-resolve-gate.sh +22 -0
- package/dist/plugin/scripts/guardrail-stage-gate.sh +1 -1
- package/dist/plugin/scripts/guardrail-terminal-gate.sh +1 -1
- package/dist/plugin/scripts/guardrail-walkthrough-gate.sh +1 -1
- package/dist/plugin/scripts/guardrail-watch-gate.sh +1 -1
- package/dist/plugin/scripts/guardrails.mjs +506 -32
- package/dist/plugin/scripts/pr-watch-loop.sh +9 -24
- package/dist/plugin/scripts/pr-watch-state.jq +52 -0
- package/dist/plugin/skills/_shared/identity-and-inbox.md +39 -0
- package/dist/plugin/skills/_shared/sync-branch-with-base.md +29 -0
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +3 -2
- package/dist/plugin/skills/_shared/vcs/github/unresolved-threads.md +2 -0
- package/dist/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +3 -1
- package/dist/plugin/skills/_shared/vcs/gitlab.md +0 -1
- package/dist/plugin/skills/do/address-reviews.md +5 -2
- package/dist/plugin/skills/do/build.md +9 -0
- package/dist/plugin/skills/do/e2e-acceptance.md +2 -0
- package/dist/plugin/skills/do/e2e-repair.md +98 -0
- package/dist/plugin/skills/do/open-prs/forward.md +17 -9
- package/dist/plugin/skills/do/open-prs/update.md +9 -2
- package/dist/plugin/skills/do/per-comment-replies.md +13 -1
- package/dist/plugin/skills/do/pre-flight.md +2 -2
- package/dist/plugin/skills/do/resolve-conflicts.md +59 -59
- package/dist/plugin/skills/muggle-browser-task/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-do/SKILL.md +6 -5
- package/dist/plugin/skills/muggle-pr-followup/arm-watcher.md +1 -1
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoRebase.md +5 -5
- package/dist/plugin/skills/muggle-status/SKILL.md +15 -6
- package/dist/plugin/skills/muggle-test/SKILL.md +1 -0
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +5 -3
- package/dist/plugin/skills/muggle-test-prepare/steps/rebase-check.md +1 -1
- package/dist/release-manifest.json +3 -3
- package/dist/src-TFNPZD5X.js +1 -0
- package/package.json +17 -8
- package/plugin/hooks/README.md +4 -1
- package/plugin/hooks/hooks.json +24 -0
- package/plugin/scripts/guardrail-capability-claim.sh +38 -0
- package/plugin/scripts/guardrail-comment-reply-gate.sh +42 -0
- package/plugin/scripts/guardrail-debug-path-gate.sh +1 -1
- package/plugin/scripts/guardrail-e2e-gate.sh +1 -1
- package/plugin/scripts/guardrail-record-comment-replies.sh +28 -0
- package/plugin/scripts/guardrail-resolve-gate.sh +22 -0
- package/plugin/scripts/guardrail-stage-gate.sh +1 -1
- package/plugin/scripts/guardrail-terminal-gate.sh +1 -1
- package/plugin/scripts/guardrail-walkthrough-gate.sh +1 -1
- package/plugin/scripts/guardrail-watch-gate.sh +1 -1
- package/plugin/scripts/guardrails.mjs +506 -32
- package/plugin/scripts/pr-watch-loop.sh +9 -24
- package/plugin/scripts/pr-watch-state.jq +52 -0
- package/plugin/skills/_shared/identity-and-inbox.md +39 -0
- package/plugin/skills/_shared/sync-branch-with-base.md +29 -0
- package/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +3 -2
- package/plugin/skills/_shared/vcs/github/unresolved-threads.md +2 -0
- package/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +3 -1
- package/plugin/skills/_shared/vcs/gitlab.md +0 -1
- package/plugin/skills/do/address-reviews.md +5 -2
- package/plugin/skills/do/build.md +9 -0
- package/plugin/skills/do/e2e-acceptance.md +2 -0
- package/plugin/skills/do/e2e-repair.md +98 -0
- package/plugin/skills/do/open-prs/forward.md +17 -9
- package/plugin/skills/do/open-prs/update.md +9 -2
- package/plugin/skills/do/per-comment-replies.md +13 -1
- package/plugin/skills/do/pre-flight.md +2 -2
- package/plugin/skills/do/resolve-conflicts.md +59 -59
- package/plugin/skills/muggle-browser-task/SKILL.md +1 -1
- package/plugin/skills/muggle-do/SKILL.md +6 -5
- package/plugin/skills/muggle-pr-followup/arm-watcher.md +1 -1
- package/plugin/skills/muggle-preferences/preference-gates/autoRebase.md +5 -5
- package/plugin/skills/muggle-status/SKILL.md +15 -6
- package/plugin/skills/muggle-test/SKILL.md +1 -0
- package/plugin/skills/muggle-test-feature-local/SKILL.md +5 -3
- package/plugin/skills/muggle-test-prepare/steps/rebase-check.md +1 -1
- package/scripts/postinstall.mjs +59 -2
- package/dist/plugin/skills/_shared/rebase-before-e2e.md +0 -21
- package/dist/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +0 -10
- package/dist/src-B5OTAZKM.js +0 -1
- package/plugin/skills/_shared/rebase-before-e2e.md +0 -21
- package/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +0 -10
|
@@ -45,6 +45,12 @@ for lib in pr-watch-guards.sh pr-watch-events.sh; do
|
|
|
45
45
|
. "${script_dir}/${lib}"
|
|
46
46
|
done
|
|
47
47
|
|
|
48
|
+
# Same reasoning as the guards: a missing projection means the plugin moved or
|
|
49
|
+
# upgraded underneath this loop, so step down rather than poll with no state to
|
|
50
|
+
# compare against.
|
|
51
|
+
[ -f "${script_dir}/pr-watch-state.jq" ] || exit 0
|
|
52
|
+
state_projection="$(cat "${script_dir}/pr-watch-state.jq")"
|
|
53
|
+
|
|
48
54
|
echo "$$" > "${slot}/watch.pid"
|
|
49
55
|
started=$(date +%s)
|
|
50
56
|
fails=0
|
|
@@ -88,39 +94,18 @@ query($owner: String!, $name: String!, $number: Int!) {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
|
-
reviews(last: 20, states: [COMMENTED, APPROVED, CHANGES_REQUESTED, DISMISSED]) { nodes { databaseId } }
|
|
97
|
+
reviews(last: 20, states: [COMMENTED, APPROVED, CHANGES_REQUESTED, DISMISSED]) { nodes { databaseId body } }
|
|
92
98
|
reviewThreads(first: 100) {
|
|
93
99
|
nodes {
|
|
94
100
|
id
|
|
95
101
|
isResolved
|
|
96
102
|
isOutdated
|
|
97
|
-
comments(last: 1) { nodes { databaseId pullRequestReview { state } } }
|
|
103
|
+
comments(last: 1) { nodes { databaseId body pullRequestReview { databaseId state } } }
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
|
-
}' --jq
|
|
103
|
-
.data.repository.pullRequest as $pr
|
|
104
|
-
| (($pr.commits.nodes[0].commit.statusCheckRollup.contexts.nodes) // []) as $contexts
|
|
105
|
-
| ($contexts | map(
|
|
106
|
-
if .__typename == "CheckRun"
|
|
107
|
-
then {name: .name, verdict: (if .status != "COMPLETED" then "PENDING" else (.conclusion // "NEUTRAL") end)}
|
|
108
|
-
else {name: .context, verdict: (.state // "PENDING")}
|
|
109
|
-
end)) as $checks
|
|
110
|
-
| [
|
|
111
|
-
$pr.state,
|
|
112
|
-
$pr.headRefOid,
|
|
113
|
-
$pr.baseRefOid,
|
|
114
|
-
$pr.mergeable,
|
|
115
|
-
(([$pr.reviews.nodes[].databaseId] | max) // 0),
|
|
116
|
-
(([$pr.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[]
|
|
117
|
-
| select((.pullRequestReview.state // "SUBMITTED") != "PENDING") | .databaseId] | max) // 0),
|
|
118
|
-
([$pr.reviewThreads.nodes[] | select(.isResolved == false) | select(.isOutdated == false)
|
|
119
|
-
| select((.comments.nodes[0].pullRequestReview.state // "SUBMITTED") != "PENDING") | .id] | join(";")),
|
|
120
|
-
($checks | map(select(.verdict == "PENDING")) | length),
|
|
121
|
-
($checks | map(select(.verdict == "FAILURE" or .verdict == "ERROR" or .verdict == "TIMED_OUT" or .verdict == "STARTUP_FAILURE")) | length),
|
|
122
|
-
($checks | sort_by(.name) | map(.name + ":" + .verdict) | join(","))
|
|
123
|
-
] | @tsv' 2>>"${slot}/watch-fetch.log"
|
|
108
|
+
}' --jq "$state_projection" 2>>"${slot}/watch-fetch.log"
|
|
124
109
|
}
|
|
125
110
|
|
|
126
111
|
# behind_by needs its own call — see watch_wake_rebase for why no field on the
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Projects one PR's GraphQL snapshot into the tab-separated state line the watch
|
|
2
|
+
# loop compares against its floors. Lives in its own file so it can be exercised
|
|
3
|
+
# directly against fixtures — embedded in the loop it was unreachable by tests,
|
|
4
|
+
# which is how the echo wake below survived unnoticed.
|
|
5
|
+
#
|
|
6
|
+
# Field order is the contract pr-watch-loop.sh reads positionally:
|
|
7
|
+
# state, headRefOid, baseRefOid, mergeable, latest-review, latest-comment,
|
|
8
|
+
# unresolved-thread-ids, pending-checks, failed-checks, check-digest
|
|
9
|
+
|
|
10
|
+
def loop_marked: ((.body // "") | contains("<!-- muggle-do:bot -->"));
|
|
11
|
+
|
|
12
|
+
.data.repository.pullRequest as $pr
|
|
13
|
+
| (($pr.commits.nodes[0].commit.statusCheckRollup.contexts.nodes) // []) as $contexts
|
|
14
|
+
| ($contexts | map(
|
|
15
|
+
if .__typename == "CheckRun"
|
|
16
|
+
then {name: .name, verdict: (if .status != "COMPLETED" then "PENDING" else (.conclusion // "NEUTRAL") end)}
|
|
17
|
+
else {name: .context, verdict: (.state // "PENDING")}
|
|
18
|
+
end)) as $checks
|
|
19
|
+
|
|
20
|
+
# Every comment on a live thread that belongs to a submitted review. A PENDING
|
|
21
|
+
# review is the reviewer's own draft and is not feedback yet.
|
|
22
|
+
| [$pr.reviewThreads.nodes[]
|
|
23
|
+
| select(.isResolved == false)
|
|
24
|
+
| .comments.nodes[]
|
|
25
|
+
| select((.pullRequestReview.state // "SUBMITTED") != "PENDING")] as $liveComments
|
|
26
|
+
|
|
27
|
+
# Posting a threaded reply also mints a review envelope around it. That envelope
|
|
28
|
+
# is the loop's own echo: without excluding it the loop wakes itself every time
|
|
29
|
+
# it answers a comment, and the tick it triggers can only idle.
|
|
30
|
+
| ($liveComments | map(select(loop_marked) | .pullRequestReview.databaseId | values)) as $loopOwnedReviews
|
|
31
|
+
| ($liveComments | map(select(loop_marked | not) | .pullRequestReview.databaseId | values)) as $humanOwnedReviews
|
|
32
|
+
# `index` evaluates its argument against the array it is indexing, so the review
|
|
33
|
+
# has to be bound before either membership test can name its id.
|
|
34
|
+
| def is_echo_review:
|
|
35
|
+
. as $review
|
|
36
|
+
| ((($review.body // "") == "") or ($review | loop_marked))
|
|
37
|
+
and (($loopOwnedReviews | index($review.databaseId)) != null)
|
|
38
|
+
and (($humanOwnedReviews | index($review.databaseId)) == null);
|
|
39
|
+
|
|
40
|
+
[
|
|
41
|
+
$pr.state,
|
|
42
|
+
$pr.headRefOid,
|
|
43
|
+
$pr.baseRefOid,
|
|
44
|
+
$pr.mergeable,
|
|
45
|
+
(([$pr.reviews.nodes[] | select(is_echo_review | not) | .databaseId] | max) // 0),
|
|
46
|
+
(($liveComments | map(select(loop_marked | not) | .databaseId) | max) // 0),
|
|
47
|
+
([$pr.reviewThreads.nodes[] | select(.isResolved == false) | select(.isOutdated == false)
|
|
48
|
+
| select((.comments.nodes[0].pullRequestReview.state // "SUBMITTED") != "PENDING") | .id] | join(";")),
|
|
49
|
+
($checks | map(select(.verdict == "PENDING")) | length),
|
|
50
|
+
($checks | map(select(.verdict == "FAILURE" or .verdict == "ERROR" or .verdict == "TIMED_OUT" or .verdict == "STARTUP_FAILURE")) | length),
|
|
51
|
+
($checks | sort_by(.name) | map(.name + ":" + .verdict) | join(","))
|
|
52
|
+
] | @tsv
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Identity and inbox
|
|
2
|
+
|
|
3
|
+
Muggle drives a real browser under a real identity. That is the capability a bare Playwright or Cypress harness lacks, and the reason an email-gated or login-gated flow is ordinary work here rather than a blocker.
|
|
4
|
+
|
|
5
|
+
## Receiving email
|
|
6
|
+
|
|
7
|
+
Every managed login profile is minted its own live inbox. A generated script reaches it with a `readEmail` step, which opens the newest message in that mailbox and answers a question about it, so the code, link, or confirmation lands back in the run as a value later steps use.
|
|
8
|
+
|
|
9
|
+
Testable end to end, with no mail stub, no local catcher (MailHog, Mailpit, Mailtrap), and no test-only backdoor in the product:
|
|
10
|
+
|
|
11
|
+
- Magic-link and sign-in-link login
|
|
12
|
+
- Emailed OTP and one-time codes
|
|
13
|
+
- Email-based two-factor challenges
|
|
14
|
+
- Address-verification and confirmation mail
|
|
15
|
+
- Password-reset links
|
|
16
|
+
|
|
17
|
+
The product sends real mail to a real address. Nothing in the app under test needs a test hook for this to work.
|
|
18
|
+
|
|
19
|
+
## Credentials
|
|
20
|
+
|
|
21
|
+
Managed login profiles carry stored secrets, injected at run time. A flow behind a password, a paywall, or a seat licence is reachable — "there is no logged-in session" is not a reason to decline, nor to narrow scope to logged-out pages.
|
|
22
|
+
|
|
23
|
+
## CAPTCHA
|
|
24
|
+
|
|
25
|
+
A `solveCaptcha` step handles reCAPTCHA v2 and v3, hCaptcha, image, and slider challenges.
|
|
26
|
+
|
|
27
|
+
## Out of scope
|
|
28
|
+
|
|
29
|
+
Genuine limits. Say so plainly when one of them is what blocks a flow, and never promise a run that cannot happen:
|
|
30
|
+
|
|
31
|
+
- SMS and phone-call OTP — no phone number is minted
|
|
32
|
+
- Authenticator-app TOTP
|
|
33
|
+
- OAuth-only social login (Google, GitHub) where the flow does not explicitly require it
|
|
34
|
+
|
|
35
|
+
A non-email second factor routes to a user-intervene step rather than completing on its own.
|
|
36
|
+
|
|
37
|
+
## Wrong conclusions
|
|
38
|
+
|
|
39
|
+
Before writing that a flow is untestable, unreachable, or needs a stub, check the lists above. The recurring error is declaring a magic-link or email-verification flow un-E2E-able and falling back to unit tests: the flow is supported, and the fallback silently drops the acceptance coverage the user asked for.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Sync the Branch With Its Base
|
|
2
|
+
|
|
3
|
+
Bring the working branch up to date with the branch it merges into — before a dev server or E2E run, and before any push that opens or updates a change. Gated by [`autoRebase`](../muggle-preferences/preference-gates/autoRebase.md). Follow the standard procedure in [`preference-gates/README.md`](../muggle-preferences/preference-gates/README.md).
|
|
4
|
+
|
|
5
|
+
## Resolving the base
|
|
6
|
+
|
|
7
|
+
`base` is the branch the work merges into, which is not automatically the remote default — pre-flight lets the user target a different branch, and once a change exists its `baseRefName` is authoritative. A caller holding a base passes it in; fall back to the remote default only when no target was chosen.
|
|
8
|
+
|
|
9
|
+
**Fire only when `behind > 0`:**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git fetch origin
|
|
13
|
+
base="${base:-$(git symbolic-ref refs/remotes/origin/HEAD --short | sed 's|origin/||')}"
|
|
14
|
+
behind=$(git rev-list --count "HEAD..origin/${base}")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Pass `{behind}` and `{base}` to the picker prompts. On `always`:
|
|
18
|
+
|
|
19
|
+
1. Capture the rollback point: `pre_rebase_sha=$(git rev-parse HEAD)`.
|
|
20
|
+
2. `git rebase origin/${base}`.
|
|
21
|
+
3. On conflict, branch by [`autoResolveConflicts`](../muggle-preferences/preference-gates/autoResolveConflicts.md):
|
|
22
|
+
- `never` → `git rebase --abort`; stop and report, naming the conflicted files. Never auto-resolve.
|
|
23
|
+
- `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.
|
|
24
|
+
|
|
25
|
+
## Branches already on the remote
|
|
26
|
+
|
|
27
|
+
The rebase rewrites history, so a branch the remote already has needs a force-push to land it. Never force-push from here. A caller in that position hands the sync to its own rebase mode, which owns the per-SHA attempt budget and the verify-or-rollback gate wrapped around the force-push.
|
|
28
|
+
|
|
29
|
+
Stale branches produce false failures and false greens, and a branch that goes stale between validation and push opens a change that cannot merge — that is why this gate exists.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `muggle-do:escalation`
|
|
2
2
|
|
|
3
|
-
Zero or one per address-reviews invocation. Fires when `/muggle-do` emits a terminal escalation message to the user.
|
|
3
|
+
Zero or one per address-reviews invocation, plus zero or one per forward cycle that waives a failing E2E run. Fires when `/muggle-do` emits a terminal escalation message to the user.
|
|
4
4
|
|
|
5
5
|
```json
|
|
6
6
|
{
|
|
@@ -9,7 +9,7 @@ Zero or one per address-reviews invocation. Fires when `/muggle-do` emits a term
|
|
|
9
9
|
"session_slug": "<slug>",
|
|
10
10
|
"repo": "<owner>/<repo>",
|
|
11
11
|
"pr_number": <int>,
|
|
12
|
-
"kind": "ambiguous-review" | "design-adjustment" | "rebase-conflict",
|
|
12
|
+
"kind": "ambiguous-review" | "design-adjustment" | "rebase-conflict" | "e2e-unrepairable",
|
|
13
13
|
"review_ids": [<int>, ...]
|
|
14
14
|
}
|
|
15
15
|
```
|
|
@@ -18,3 +18,4 @@ Zero or one per address-reviews invocation. Fires when `/muggle-do` emits a term
|
|
|
18
18
|
- `"ambiguous-review"` — one or more reviews classified ambiguous in this batch.
|
|
19
19
|
- `"design-adjustment"` — mid-cycle, the work surfaced a design-level conflict.
|
|
20
20
|
- `"rebase-conflict"` — an opt-in auto-rebase hit conflicts that couldn't be resolved and verified; the branch was restored to its pre-rebase SHA. `review_ids` may be empty.
|
|
21
|
+
- `"e2e-unrepairable"` — the E2E repair loop shipped with failures it could not fix: the user waived, an unattended run had nobody to ask, or the iteration cap ran out. `review_ids` is empty.
|
|
@@ -37,3 +37,5 @@ Filter client-side to `isResolved == false`. Walk each thread's comments in `cre
|
|
|
37
37
|
Each comment exposes its owning review as `pullRequestReview.databaseId` — the watcher collects this from an actionable thread's newest comment to build its dispatch list.
|
|
38
38
|
|
|
39
39
|
A loop comment also cites a `<short-sha>` from `last_seen.pushed_shas[]` in its body, which tells *which* push addressed the thread.
|
|
40
|
+
|
|
41
|
+
Resolving a thread is the reviewer's act, never the loop's: the marker classification above already retires an addressed thread, so a `resolveReviewThread` mutation would only hide it before the reviewer has verified the fix. A `PreToolUse` guardrail denies the call.
|
|
@@ -15,4 +15,6 @@ Walk each unresolved thread's `notes[]` in `created_at` order and classify by th
|
|
|
15
15
|
- **Unaddressed human comment** — the newest note lacks the marker and is newer than the thread's newest loop-marked note (or the thread has none yet). → actionable: the round should address it.
|
|
16
16
|
- **Not addressed** — indeterminate (e.g. no notes).
|
|
17
17
|
|
|
18
|
-
Each thread carries its `id` (the `discussion_id`) — the watcher collects this from an actionable thread to build its dispatch list
|
|
18
|
+
Each thread carries its `id` (the `discussion_id`) — the watcher collects this from an actionable thread to build its dispatch list.
|
|
19
|
+
|
|
20
|
+
Marking a discussion resolved is the reviewer's act, never the loop's: the marker classification above already retires an addressed thread, so a resolve would only hide it. A `PreToolUse` guardrail denies the call.
|
|
@@ -18,7 +18,6 @@ Skills assume a working `glab auth status`. Auth errors surface verbatim from `g
|
|
|
18
18
|
| [`unresolved-discussions`](gitlab/unresolved-discussions.md) | Unresolved-discussion state — watcher's dispatch trigger + resolve-reminder. |
|
|
19
19
|
| [`reply-discussion`](gitlab/reply-discussion.md) | POST a threaded reply on a discussion. |
|
|
20
20
|
| [`mr-note`](gitlab/mr-note.md) | POST a top-level MR note — resolve-reminder + overflow. |
|
|
21
|
-
| [`resolve-discussion`](gitlab/resolve-discussion.md) | Mark a discussion thread resolved. |
|
|
22
21
|
| [`mr-edit`](gitlab/mr-edit.md) | Refresh title or description when address-reviews mode flips state. |
|
|
23
22
|
| [`mr-create`](gitlab/mr-create.md) | Open an MR + capture its URL for handoff. |
|
|
24
23
|
| [`loop-user-identity`](gitlab/loop-user-identity.md) | Resolve the GitLab username of the loop user. |
|
|
@@ -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/
|
|
32
|
+
Before assembling work, rebase onto the latest default branch so the cycle addresses reviews against current master, not a stale base. Run [`../_shared/sync-branch-with-base.md`](../_shared/sync-branch-with-base.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
|
|
|
@@ -46,6 +46,8 @@ Resolve the provider per [`../_shared/vcs/detect-vcs.md`](../_shared/vcs/detect-
|
|
|
46
46
|
|
|
47
47
|
Group (a) and (b) into one combined batch.
|
|
48
48
|
|
|
49
|
+
**Claim each thread in the batch.** Claiming is what records that this round took the thread as work; the reply gate settles from claims, not from whether a push happened. Release the claims when the round ends. A claim whose holder died is not a claim — the entry reads unprocessed again and the next round re-claims it — so a crashed round leaves nothing to clean up.
|
|
50
|
+
|
|
49
51
|
**`gitlab`** — single source (no review-id watermark; discussion state is the sole authority). Fetch unresolved discussions per [`../_shared/vcs/gitlab/unresolved-discussions.md`](../_shared/vcs/gitlab/unresolved-discussions.md) (drop to [`../_shared/vcs/gitlab/mr-discussions.md`](../_shared/vcs/gitlab/mr-discussions.md) for raw notes where classification needs them). The input ids are discussion ids; the batch is every discussion classified **unaddressed human comment** — newest note lacks the marker and post-dates the loop's last marked note. **Exclude** discussions whose id is in `last_seen.escalated_review_ids`. A discussion is the unit of work in place of GitHub's review + line-comment pair.
|
|
50
52
|
|
|
51
53
|
### Step 2 — Classify each review
|
|
@@ -62,6 +64,7 @@ Build two sets: `actionable_review_ids` and `ambiguous_review_ids`. Their union
|
|
|
62
64
|
For each id in `ambiguous_review_ids`:
|
|
63
65
|
|
|
64
66
|
1. Append it to `last_seen.escalated_review_ids` so the watcher won't re-dispatch it.
|
|
67
|
+
2. Run `echo "MUGGLE_REPLY_SKIP: <comment-id> deferred to the user"` naming its comment ids (`gitlab`: discussion ids). An escalated thread is deferred, not answered, so the reply gate in [`per-comment-replies.md`](per-comment-replies.md) would otherwise hold the turn open waiting on a reply that is not coming. The deferral is recorded against those comments durably, so it still holds after this session ends; a marker naming no tracked comment falls back to a session-wide skip.
|
|
65
68
|
|
|
66
69
|
Emit **one** terminal escalation message (not one per ambiguous review) per [`../muggle-pr-followup/output-templates/escalation.md`](../muggle-pr-followup/output-templates/escalation.md) (ambiguous template). The message lists every ambiguous review and its comments inline. Emit an event with `kind: "ambiguous-review"` per [`../_shared/telemetry-events/muggle-do-escalation.md`](../_shared/telemetry-events/muggle-do-escalation.md).
|
|
67
70
|
|
|
@@ -97,7 +100,7 @@ Invoke [`open-prs/update.md`](open-prs/update.md) (pass the PR URL + slug + exis
|
|
|
97
100
|
|
|
98
101
|
#### 4f. Post per-comment inline replies
|
|
99
102
|
|
|
100
|
-
Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable reviews (`gitlab`: discussions) and the new SHA. One reply per comment, in its own thread, describing what was done for it
|
|
103
|
+
Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable reviews (`gitlab`: discussions) and the new SHA. One reply per comment, in its own thread, describing what was done for it. The reply is the whole of it — neither provider's threads get resolved by the loop.
|
|
101
104
|
|
|
102
105
|
(The resolve-reminder runs once per round in Step 5.5 below — not only after a push — so a round that pushed nothing still nudges addressed-but-unresolved threads.)
|
|
103
106
|
|
|
@@ -56,3 +56,12 @@ The address-reviews orchestrator ([`address-reviews.md`](address-reviews.md)) in
|
|
|
56
56
|
- Continue on the existing branch — do not re-create the worktree.
|
|
57
57
|
- After this stage, the orchestrator runs unit-tests → ONE E2E pass → create-or-update PR (push to the existing branch; refresh title/desc if state changed) → per-comment inline replies → resolve-reminder → respawn the watcher.
|
|
58
58
|
- If the requested work cannot be implemented without rethinking design (e.g. a load-bearing invariant must change), return `failed: design-adjustment` and let the orchestrator escalate via the design-adjustment terminal message. Do not partially implement.
|
|
59
|
+
|
|
60
|
+
## Re-entry from the E2E repair loop
|
|
61
|
+
|
|
62
|
+
[`e2e-repair.md`](e2e-repair.md) invokes this stage when a failing acceptance run root-causes to a product defect the change introduced. When re-entered:
|
|
63
|
+
|
|
64
|
+
- The loop passes the named defects — failing step, expected-versus-actual, and the implicated code path — as the requirements amendment for this iteration. Treat them as additions to the goal/AC.
|
|
65
|
+
- Continue on the existing branch; the worktree and the PR already exist.
|
|
66
|
+
- After this stage, the loop runs unit-tests → a re-run scoped to the affected test cases → `open-prs/update.md`. Impact analysis does not re-run.
|
|
67
|
+
- If the defect can't be fixed without rethinking design, return `failed: design-adjustment`. The loop routes it to its user decision instead of burning an iteration on it.
|
|
@@ -147,6 +147,8 @@ For each test case:
|
|
|
147
147
|
|
|
148
148
|
**Overall:** PASS | FAIL | PARTIAL | INCONCLUSIVE | BLOCKED | SKIPPED — see [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) section F for the canonical taxonomy.
|
|
149
149
|
|
|
150
|
+
A non-passing verdict does not end the cycle. [`e2e-repair.md`](e2e-repair.md) consumes this report at Stage 7.5 and root-causes every Failed and Inconclusive entry before a watcher is armed, so write those blocks in full — the bucket, failing step, and `artifactsDir` recorded here are that stage's entire input.
|
|
151
|
+
|
|
150
152
|
Failed runs use the same evidence + diagnosis assembly as the interactive debug path ([`../_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) Steps 1–2) — write that evidence into the **Failed** block above. This stage is autonomous, so it skips Step 3's interactive offer.
|
|
151
153
|
|
|
152
154
|
## Hard constraints
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# E2E Repair Loop (Stage 7.5)
|
|
2
|
+
|
|
3
|
+
Runs between Stage 7's posted walkthrough and Stage 8's watcher dispatch. A failing acceptance run means the change is not ready for a reviewer, so this stage root-causes every failure and either repairs it in-pipeline or puts one decision in front of the user. The watcher is armed only once this stage clears.
|
|
4
|
+
|
|
5
|
+
## Turn preamble
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
**Stage 7.5 — E2E repair** — root-causing the failing acceptance runs before handing off to the watcher.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Inputs
|
|
12
|
+
|
|
13
|
+
- The Stage 6 acceptance report from [`e2e-acceptance.md`](e2e-acceptance.md) — its Failed and Inconclusive blocks and the run-level verdict.
|
|
14
|
+
- `state.md` — validation strategy, per-repo worktree path, project id.
|
|
15
|
+
- The PR opened by [`open-prs/forward.md`](open-prs/forward.md) — repo and number, for the escalation event and the refresh path.
|
|
16
|
+
- `repairIteration` — how many times this stage has already run in this session, read from `iterations/<NNN>.md`.
|
|
17
|
+
|
|
18
|
+
## Step 0: Clearance check
|
|
19
|
+
|
|
20
|
+
Read the Stage 6 run-level verdict, defined in [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) section F.
|
|
21
|
+
|
|
22
|
+
- `PASS` or `SKIPPED` → nothing to repair. Append `e2e-repair: clear (<verdict>)` to the iteration log and hand to Stage 8.
|
|
23
|
+
- A verdict the user waived earlier in this session → clear on the recorded waiver; never re-ask.
|
|
24
|
+
- `FAIL`, `PARTIAL`, `INCONCLUSIVE`, or `BLOCKED` → the loop runs over every entry in the Failed and Inconclusive blocks.
|
|
25
|
+
|
|
26
|
+
## Step 1: Root-cause each failure
|
|
27
|
+
|
|
28
|
+
Per failing test case, assemble evidence and a diagnosis with [`../_shared/debug-failed-run.md`](../_shared/debug-failed-run.md) Steps 1–2 — attempted steps, halt reason, the failing step's screenshot, then the bucket. That doc's Step 3 is the interactive debug card; this stage is autonomous and does not run it.
|
|
29
|
+
|
|
30
|
+
Stage 6 already wrote this evidence into its Failed block. Re-derive a bucket only for entries that arrived without one.
|
|
31
|
+
|
|
32
|
+
The bucket is the root cause. Anything that does not land in a bucket is `unclassified` — never force-fit one to reach an auto-repair disposition.
|
|
33
|
+
|
|
34
|
+
## Step 2: Disposition
|
|
35
|
+
|
|
36
|
+
Map each bucket to exactly one disposition. Buckets are defined in [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) sections B and C.
|
|
37
|
+
|
|
38
|
+
| Bucket | Disposition | Why |
|
|
39
|
+
| :----- | :---------- | :-- |
|
|
40
|
+
| `product-defect` (replay) | `repair` | The change broke a flow that used to work. That is this pipeline's own bug to fix. |
|
|
41
|
+
| `product-uxux` (regen) | `repair` | The product blocks the agent because the feature does not work. |
|
|
42
|
+
| `stale-script` (replay) | `regenerate` | Selectors moved; the product is fine. Regenerating is self-healing, not a code change. |
|
|
43
|
+
| `transient` (regen) | `retry` | Once, then re-bucket the second result. A twice-transient failure is `infra`. |
|
|
44
|
+
| `infra` (either) | `ask` | A Muggle Test bug. Nothing in this repo fixes it. |
|
|
45
|
+
| `agent-course` (regen) | `ask` | The agent needs steering, which is the user's `muggle-feedback` call to make. |
|
|
46
|
+
| `unclassified` | `ask` | No bucket, no automatic action. |
|
|
47
|
+
|
|
48
|
+
A `repair` disposition requires a named defect: the failing step, expected-versus-actual, and the code path it implicates. Without one it is `ask`, whatever the bucket said.
|
|
49
|
+
|
|
50
|
+
## Step 3: Act
|
|
51
|
+
|
|
52
|
+
Process dispositions in order — `retry`, then `regenerate`, then `repair` — so cheap self-healing runs before any code change.
|
|
53
|
+
|
|
54
|
+
- **`retry`** — re-execute the case unchanged through the Stage 6 loop. Re-bucket the result and fold it back into Step 2.
|
|
55
|
+
- **`regenerate`** — regenerate the script for that test case, then re-replay. One regeneration per test case per iteration, matching Stage 6's own cap.
|
|
56
|
+
- **`repair`** — re-enter [`build.md`](build.md) with the named defects as this iteration's requirements amendment, then run [`unit-tests.md`](unit-tests.md) and refresh the PR through [`open-prs/update.md`](open-prs/update.md). Impact analysis does not re-run; the change is scoped to a known defect.
|
|
57
|
+
- **`ask`** — collect every `ask` failure and present **one** `AskUserQuestion` (Step 4). Never one picker per failure.
|
|
58
|
+
|
|
59
|
+
## Step 4: The user decision
|
|
60
|
+
|
|
61
|
+
One question, listing each unrepairable failure with its bucket, failing step, and dashboard link. Options:
|
|
62
|
+
|
|
63
|
+
1. **Repair anyway** — the user names what the fix should be; the loop treats it as a `repair` disposition and re-enters Step 3.
|
|
64
|
+
2. **Give feedback and rerun** — invoke `muggle-feedback` for the run, then re-execute in regen mode. The right answer for `agent-course`.
|
|
65
|
+
3. **Waive and ship** — record the waiver in `state.md`, retitle the PR `[E2E FAILING]`, and clear to Stage 8.
|
|
66
|
+
4. **Stop here** — leave the PR open and unwatched; the user takes it from there.
|
|
67
|
+
|
|
68
|
+
**Unattended runs.** When the cycle was started autonomously there is nobody to ask. Do not skip the loop — run Steps 1–3 in full, then treat a surviving `ask` as an automatic waive: emit the escalation event, retitle `[E2E FAILING]`, and clear to Stage 8. Silently arming the watcher on a red run is the failure this stage exists to prevent.
|
|
69
|
+
|
|
70
|
+
## Step 5: Close the loop
|
|
71
|
+
|
|
72
|
+
After a `repair`, `regenerate`, or `retry` pass, re-run [`e2e-acceptance.md`](e2e-acceptance.md) over the affected test cases only, increment `repairIteration`, and return to Step 0.
|
|
73
|
+
|
|
74
|
+
The loop is bounded at **3** repair iterations, the same bound the cycle guardrail already applies to E2E failures. On the third exhausted iteration, waive automatically: retitle `[E2E FAILING]`, record the surviving failures in `result.md`, and clear to Stage 8. A PR a reviewer can see is worth more than a loop that never terminates.
|
|
75
|
+
|
|
76
|
+
## Telemetry
|
|
77
|
+
|
|
78
|
+
Steps 1–3 emit the `*-classified` / `*-resolved` pair per [`../_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) section D, with `userAction` set to the disposition this stage took (`repair`, `regenerate`, `retry`) rather than a human pick. A failure that reaches Step 4 records the user's actual choice.
|
|
79
|
+
|
|
80
|
+
Every waive — user-picked, unattended, or iteration-capped — also emits `muggle-do:escalation` with `kind: "e2e-unrepairable"` per [`../_shared/telemetry-events/muggle-do-escalation.md`](../_shared/telemetry-events/muggle-do-escalation.md).
|
|
81
|
+
|
|
82
|
+
## Output
|
|
83
|
+
|
|
84
|
+
**Verdict in:** `<Stage 6 verdict>`
|
|
85
|
+
**Repair iterations:** `<n>` of 3
|
|
86
|
+
**Repaired:** test case → root cause → commit subject
|
|
87
|
+
**Regenerated:** test case → new script id
|
|
88
|
+
**Waived:** test case → bucket → reason
|
|
89
|
+
**Verdict out:** `<verdict after the final re-run>`
|
|
90
|
+
**Clearance:** `green` | `waived — <reason>` | `iteration-cap`
|
|
91
|
+
|
|
92
|
+
## Invariants
|
|
93
|
+
|
|
94
|
+
- Stage 8 is unreachable while clearance is unresolved — the only exits are green, waived, or iteration-capped.
|
|
95
|
+
- No bucket auto-repairs without a named defect.
|
|
96
|
+
- One `AskUserQuestion` per stage run, never one per failure.
|
|
97
|
+
- An unattended run still executes Steps 1–3; only Step 4 collapses.
|
|
98
|
+
- Re-runs are scoped to the affected test cases, never the whole suite.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Open PR — forward mode
|
|
2
2
|
|
|
3
|
-
Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fresh feature. Opens the change — a PR on GitHub (`gh pr create`) or an MR on GitLab (`glab mr create`), provider resolved in Step
|
|
3
|
+
Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fresh feature. Opens the change — a PR on GitHub (`gh pr create`) or an MR on GitLab (`glab mr create`), provider resolved in Step 5 — then seeds session state and dispatches the first watcher.
|
|
4
4
|
|
|
5
5
|
## Turn preamble
|
|
6
6
|
|
|
@@ -18,14 +18,16 @@ Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fres
|
|
|
18
18
|
|
|
19
19
|
0. **`autoCreatePR` gate** — apply per [`../../muggle-preferences/preference-gates/autoCreatePR.md`](../../muggle-preferences/preference-gates/autoCreatePR.md). On skip, record the reason in `result.md` and move on.
|
|
20
20
|
|
|
21
|
-
1. **
|
|
21
|
+
1. **Sync with the base:** run [`../../_shared/sync-branch-with-base.md`](../../_shared/sync-branch-with-base.md) with `base` set to the PR target recorded in `state.md` — gated by [`autoRebase`](../../muggle-preferences/preference-gates/autoRebase.md), fires only when `behind > 0`. The branch is not on the remote yet, so the rebase lands without a force-push. Sync here rather than trusting stage 6: a `unit-only` or `skip` run never reaches the E2E stage's sync at all, and on a run that does, the base moves freely while stages 3–6 build and test. If the sync escalates — conflicts under `autoResolveConflicts=never`, or a resolution that fails the verify-or-rollback gate — stop and report; never open a PR on a tree that did not verify.
|
|
22
22
|
|
|
23
|
-
2. **
|
|
23
|
+
2. **Push:** Execute per [`../../_shared/vcs/common/push-to-branch.md`](../../_shared/vcs/common/push-to-branch.md) — the tool-agnostic instruction for pushing commits (handles the signing gate and directs to provider-specific recipes).
|
|
24
|
+
|
|
25
|
+
3. **Title** (under 70 chars):
|
|
24
26
|
- E2E report exists and has failures → `[E2E FAILING] <goal>`
|
|
25
27
|
- No E2E report (validation was `unit-only` or `skip`) → `[UNVERIFIED] <goal>` or `[UNIT-ONLY] <goal>` to match the validation strategy
|
|
26
28
|
- Otherwise → `<goal>`
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
4. **Body** — assemble in order:
|
|
29
31
|
- `## Goal` — from requirements.
|
|
30
32
|
- `## Acceptance Criteria` — bulleted; omit if empty.
|
|
31
33
|
- `## Changes` — summary of what changed in this repo.
|
|
@@ -33,15 +35,19 @@ Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fres
|
|
|
33
35
|
- **Walkthrough block** — only when an E2E report exists. Fire [`postPRVisualWalkthrough`](../../muggle-preferences/preference-gates/postPRVisualWalkthrough.md); on skip, omit this block. Otherwise invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode B and embed the returned `body` verbatim. No report → skip the block.
|
|
34
36
|
- **Signature** — write the assembled body to a file and sign it with `--command /muggle-do --mode editable` per [`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md). The signature lands last, after the walkthrough block; `editable` is the mode a description carries so later refreshes replace it instead of stacking.
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
- `github` → `gh pr create --title "..." --body-file <signed-file> --head <branch>`, passing the file signed in Step
|
|
38
|
+
5. **Create:** resolve the provider per [`../../_shared/vcs/detect-vcs.md`](../../_shared/vcs/detect-vcs.md).
|
|
39
|
+
- `github` → `gh pr create --title "..." --body-file <signed-file> --head <branch>`, passing the file signed in Step 4. Capture the PR URL and number.
|
|
38
40
|
- `gitlab` → open the change via [`../../_shared/vcs/gitlab/mr-create.md`](../../_shared/vcs/gitlab/mr-create.md): `glab mr create --source-branch <branch> --target-branch <base> --title "..." --description "..."`. Capture the MR URL and iid.
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
6. **Overflow comment:** if the walkthrough skill returned a non-null `comment`, post it once using the provider resolved in Step 5 — `github` per [`../../_shared/vcs/github/top-level-comment.md`](../../_shared/vcs/github/top-level-comment.md), `gitlab` per [`../../_shared/vcs/gitlab/mr-note.md`](../../_shared/vcs/gitlab/mr-note.md). End the posted body with the signature line (command `/muggle-do`) per [`../../_shared/vcs/post-signature.md`](../../_shared/vcs/post-signature.md). Never post when `comment` is `null`.
|
|
43
|
+
|
|
44
|
+
## Stage 7.5 gate
|
|
45
|
+
|
|
46
|
+
The E2E repair loop ([`../e2e-repair.md`](../e2e-repair.md)) runs once this stage has created the PR and posted the walkthrough, and before the handoff below. Proceed only on its `green`, `waived`, or `iteration-cap` clearance — handing a red run to a watcher puts it in front of reviewers with nobody looking at the failures. When Stage 6 recorded `PASS` or `SKIPPED` the loop clears in its own Step 0 at no cost, so there is never a reason to skip it.
|
|
41
47
|
|
|
42
48
|
## Stage 8 handoff
|
|
43
49
|
|
|
44
|
-
After every repo is processed, build the watcher manifest and dispatch one watcher loop per opened PR. The dispatches are the LAST action this stage takes.
|
|
50
|
+
After every repo is processed and Stage 7.5 has cleared, build the watcher manifest and dispatch one watcher loop per opened PR. The dispatches are the LAST action this stage takes.
|
|
45
51
|
|
|
46
52
|
Write `~/.muggle-ai/muggle-do/sessions/<slug>/prs.json` per [`../../muggle-pr-followup/state-schemas.md`](../../muggle-pr-followup/state-schemas.md#prsjson):
|
|
47
53
|
|
|
@@ -62,13 +68,15 @@ If `prs.json` is empty, **do not dispatch** — record the reason in `result.md`
|
|
|
62
68
|
|
|
63
69
|
## Invariants
|
|
64
70
|
|
|
65
|
-
- PR creation per non-skipped repo; walkthrough block via Mode B; `prs.json`+`last_seen.json` seeded (no `cycle.json`, no `requirements.md`); `/loop` dispatch is the last action.
|
|
71
|
+
- Branch synced with its base before the push, never after; PR creation per non-skipped repo; walkthrough block via Mode B; `prs.json`+`last_seen.json` seeded (no `cycle.json`, no `requirements.md`); Stage 7.5 cleared before the dispatch; `/loop` dispatch is the last action.
|
|
66
72
|
|
|
67
73
|
## Output
|
|
68
74
|
|
|
75
|
+
**Synced with base:** repo → `rebased onto <base> (<n> behind)` | `already up to date` | `skipped (autoRebase)`
|
|
69
76
|
**PRs Created:** repo → URL
|
|
70
77
|
**Skipped:** repo → reason (when `autoCreatePR` short-circuited)
|
|
71
78
|
**Overflow comments posted:** repo → PR #
|
|
79
|
+
**Stage 7.5:** `green` | `waived — <reason>` | `iteration-cap`
|
|
72
80
|
**Stage 8:** `Watching <N> PR(s) — one /loop 1m /muggle:muggle-pr-followup <slug> <pr#> per PR` | `No PRs to watch — stage 8 not dispatched`
|
|
73
81
|
**Errors:** repo → message
|
|
74
82
|
|
|
@@ -22,7 +22,13 @@ Skip `autoCreatePR` (it gates creation, not update). The PR's title is left inta
|
|
|
22
22
|
|
|
23
23
|
Resolve the provider once per [`../../_shared/vcs/detect-vcs.md`](../../_shared/vcs/detect-vcs.md). Wherever Steps 3–4 below edit title/description: `github` uses `gh pr edit` per [`../../_shared/vcs/github/pr-edit.md`](../../_shared/vcs/github/pr-edit.md); `gitlab` uses `glab mr update --title --description` per [`../../_shared/vcs/gitlab/mr-edit.md`](../../_shared/vcs/gitlab/mr-edit.md).
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
0. **Sync with the base:** `git fetch origin`, then count `behind` against the base branch recorded in `state.md`. [`../address-reviews.md`](../address-reviews.md) syncs at the top of the cycle, but stages 3–6 build, test, and run E2E in between — by the time this stage pushes, that sync is old and the PR can land behind its base.
|
|
26
|
+
|
|
27
|
+
When `behind > 0`, run Steps 2–3 of [`../resolve-conflicts.md`](../resolve-conflicts.md) — rebase onto the base, then the verify-or-rollback gate — and force-push in Step 1, since the rebase rewrote commits the remote already has. Borrow that mode's procedure only; do not dispatch the mode itself, which force-pushes and respawns the watcher on its own, and the address-reviews orchestrator already owns the respawn for this cycle.
|
|
28
|
+
|
|
29
|
+
If the rebase escalates — conflicts under `autoResolveConflicts=never`, or a resolution that fails verification — stop without pushing and let the orchestrator escalate. A PR sitting behind its base is recoverable: the watcher re-detects it and dispatches the rebase mode properly. A force-pushed unverified tree is not.
|
|
30
|
+
|
|
31
|
+
1. **Push:** per [`../../_shared/vcs/common/push-to-branch.md`](../../_shared/vcs/common/push-to-branch.md), with `git push --force-with-lease` when Step 0 rebased. Capture the new SHA.
|
|
26
32
|
|
|
27
33
|
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
34
|
|
|
@@ -44,11 +50,12 @@ Return control to `/muggle-do`'s address-reviews orchestrator. The orchestrator
|
|
|
44
50
|
|
|
45
51
|
## Invariants
|
|
46
52
|
|
|
47
|
-
-
|
|
53
|
+
- Branch synced with its base before the push, never after; push; new SHA appended to `pushed_shas`; title/body refreshed only on state change; walkthrough comment via Mode A.
|
|
48
54
|
- No `gh pr create`, no `/loop` dispatch.
|
|
49
55
|
|
|
50
56
|
## Output
|
|
51
57
|
|
|
58
|
+
**Synced with base:** `rebased onto <base> (<n> behind, force-pushed)` | `already up to date` | `skipped (autoRebase)`
|
|
52
59
|
**PR updated:** URL (new SHA: `<short-sha>`)
|
|
53
60
|
**Title refreshed:** yes | no
|
|
54
61
|
**Body refreshed:** yes | no
|
|
@@ -29,7 +29,7 @@ For each comment id with a description, post the reply with the resolved provide
|
|
|
29
29
|
|
|
30
30
|
- **`github`** — nested reply on the line comment, one per actionable comment, per [`../_shared/vcs/github/reply-line-comment.md`](../_shared/vcs/github/reply-line-comment.md).
|
|
31
31
|
|
|
32
|
-
- **`gitlab`** — one threaded note per actionable discussion per [`../_shared/vcs/gitlab/reply-discussion.md`](../_shared/vcs/gitlab/reply-discussion.md) (the discussion id stands in for the comment id)
|
|
32
|
+
- **`gitlab`** — one threaded note per actionable discussion per [`../_shared/vcs/gitlab/reply-discussion.md`](../_shared/vcs/gitlab/reply-discussion.md) (the discussion id stands in for the comment id). Reply only: **never resolve the discussion.** GitLab classifies by the same `<!-- muggle-do:bot -->` marker GitHub does ([`../_shared/vcs/gitlab/unresolved-discussions.md`](../_shared/vcs/gitlab/unresolved-discussions.md)), so the marked note alone folds it out of the next tick's actionable set — the resolve adds nothing the reply has not already done, and MRs that gate merging on "all threads resolved" would have the loop clearing its own merge gate.
|
|
33
33
|
|
|
34
34
|
Reply body uses the template in [`../muggle-pr-followup/output-templates/inline-reply.md`](../muggle-pr-followup/output-templates/inline-reply.md):
|
|
35
35
|
|
|
@@ -57,7 +57,19 @@ Posted per [`../_shared/vcs/github/top-level-comment.md`](../_shared/vcs/github/
|
|
|
57
57
|
- `gh api` returns an error for an individual reply → log to `followup.log`, continue with the remaining comments. Do not abort the whole step over one failure; the push has already happened and other replies still need posting.
|
|
58
58
|
- All replies fail → surface the most-recent `gh` error to the user, but do not abort the overall `/muggle-do` invocation. The resolve-reminder stage still runs; the watcher still respawns. The next cycle on this PR will produce more replies and the missing ones can be picked up by the human reviewer.
|
|
59
59
|
|
|
60
|
+
## Enforcement
|
|
61
|
+
|
|
62
|
+
A Stop-hook guardrail (`guardrail-comment-reply-gate.sh`) holds the turn open when this session claimed a review thread and left it unanswered. It settles from signals this cycle already produces — the unresolved-thread fetch [`address-reviews.md`](address-reviews.md) Step 1 runs, which claims each thread, and the reply calls Step 2 makes — so running this step is what clears it; nothing extra is owed.
|
|
63
|
+
|
|
64
|
+
Two properties worth knowing while writing replies:
|
|
65
|
+
|
|
66
|
+
- **A comment is answered only when the provider confirms the reply.** A reply that fails leaves the obligation open, which is why the failure mode above says to log and continue rather than assume the thread is settled.
|
|
67
|
+
- **One reply may answer several comments.** A thread carrying three consecutive questions is three obligations; a single reply that addresses all three settles all three, while a reply addressing only the newest leaves the other two open and named.
|
|
68
|
+
|
|
69
|
+
A thread the round deferred to the user instead of answering (the ambiguous escalation in [`address-reviews.md`](address-reviews.md) Step 3) is cleared with `echo "MUGGLE_REPLY_SKIP: <comment-id> <reason>"`. The deferral is recorded durably against those comments, so it survives the session; naming no tracked comment degrades to a session-wide skip.
|
|
70
|
+
|
|
60
71
|
## Invariants
|
|
61
72
|
|
|
62
73
|
- One reply per line comment (`gitlab`: per discussion). No per-review summary reply anywhere.
|
|
74
|
+
- The loop never resolves a review thread, on either provider. A `PreToolUse` guardrail denies both calls.
|
|
63
75
|
- Every reply body contains the new SHA's 7-char prefix (which push addressed it) and ends with the loop signature block — the `<!-- muggle-do:bot -->` marker, not the author login, is what identifies loop-authored comments.
|
|
@@ -32,7 +32,7 @@ Before asking anything, gather every fact you can resolve without the user:
|
|
|
32
32
|
6. **Candidate Muggle Test projects.** Call `muggle-remote-project-list` and rank by semantic match against the task description and the repo's dev URL.
|
|
33
33
|
7. **Existing test-user secrets.** For each candidate Muggle Test project, call `muggle-remote-secret-list` and note whether `managed_profile_email` / `managed_profile_password` exist.
|
|
34
34
|
8. **Auth0 tenant in use for local dev.** Grep the repo's env file for `*AUTH0_DOMAIN*`; record the tenant. This tells the user whether the staging-tenant test user will work or not.
|
|
35
|
-
9. **Branch hygiene signals** for the `autoUseWorktree` and `autoRebase` gates (see [`../_shared/use-worktrees.md`](../_shared/use-worktrees.md), [`../_shared/
|
|
35
|
+
9. **Branch hygiene signals** for the `autoUseWorktree` and `autoRebase` gates (see [`../_shared/use-worktrees.md`](../_shared/use-worktrees.md), [`../_shared/sync-branch-with-base.md`](../_shared/sync-branch-with-base.md)):
|
|
36
36
|
- Is the current checkout already a worktree? `git -C <repo> rev-parse --is-inside-work-tree` plus `git -C <repo> worktree list`.
|
|
37
37
|
- How many commits behind `origin/<default>`? `git -C <repo> fetch origin && git -C <repo> rev-list --count "HEAD..origin/$(git -C <repo> symbolic-ref refs/remotes/origin/HEAD --short | sed 's|origin/||')"`.
|
|
38
38
|
10. **`autoE2ETest` preference.** Read the session-context preferences line. Unset → treat as `always` (this gate's default — opposite of the system-wide `ask` default). `ask` → surface in Q13. `always` → no question; stage 6 runs.
|
|
@@ -56,7 +56,7 @@ Present **one `AskUserQuestion`** (or the platform's structured-selection equiva
|
|
|
56
56
|
9. **PR target branch** — default: the repo's default branch. "Use default" / "Target a different branch".
|
|
57
57
|
10. **Re-auth Muggle Test MCP?** — only if auth was missing/expired. "Log in now" / "Abort".
|
|
58
58
|
11. **Worktree for this change?** — gate: [`autoUseWorktree`](../muggle-preferences/preference-gates/autoUseWorktree.md). Options: create a dedicated worktree (per [`../_shared/use-worktrees.md`](../_shared/use-worktrees.md)), or work in the current checkout.
|
|
59
|
-
12. **
|
|
59
|
+
12. **Sync onto the base branch first?** — gate: [`autoRebase`](../muggle-preferences/preference-gates/autoRebase.md), only if `behind > 0`. The answer governs every sync this cycle, not just one: before the dev server / E2E, and again immediately before stage 7 pushes. Options: sync with the base, or run as-is.
|
|
60
60
|
13. **Run E2E at the end of every cycle?** — gate: [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md), only if step 10's silent detection resolved to `ask`. Options: always run stage 6, or ask each cycle.
|
|
61
61
|
|
|
62
62
|
If fewer than two of the above need the user, still gather them in a single turn — never open a second round.
|