@muggleai/works 5.0.4 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +2 -3
  2. package/dist/{chunk-XO5BQZM3.js → chunk-2BHC6TQR.js} +3 -4
  3. package/dist/{chunk-KDRSEHK5.js → chunk-MOHR7QUN.js} +197 -291
  4. package/dist/cli.js +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  7. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  8. package/dist/plugin/scripts/ensure-electron-app.sh +1 -1
  9. package/dist/plugin/scripts/guardrails.mjs +4 -2
  10. package/dist/plugin/skills/_shared/dev-loop/publish.md +4 -8
  11. package/dist/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
  12. package/dist/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +1 -1
  13. package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -1
  14. package/dist/plugin/skills/_shared/test-case-chain-readiness.md +1 -2
  15. package/dist/plugin/skills/_shared/vcs/CLAUDE.md +9 -0
  16. package/dist/plugin/skills/_shared/vcs/detect-vcs.md +28 -0
  17. package/{plugin/skills/_shared/github-cli-recipes → dist/plugin/skills/_shared/vcs/github}/unresolved-threads.md +1 -1
  18. package/{plugin/skills/_shared/github-cli-recipes → dist/plugin/skills/_shared/vcs/github}/verify-working-tree.md +1 -1
  19. package/dist/plugin/skills/_shared/vcs/github.md +21 -0
  20. package/dist/plugin/skills/_shared/vcs/gitlab/loop-user-identity.md +9 -0
  21. package/dist/plugin/skills/_shared/vcs/gitlab/mr-create.md +11 -0
  22. package/dist/plugin/skills/_shared/vcs/gitlab/mr-discussions.md +18 -0
  23. package/dist/plugin/skills/_shared/vcs/gitlab/mr-edit.md +8 -0
  24. package/dist/plugin/skills/_shared/vcs/gitlab/mr-metadata.md +22 -0
  25. package/dist/plugin/skills/_shared/vcs/gitlab/mr-note.md +7 -0
  26. package/dist/plugin/skills/_shared/vcs/gitlab/mr-pipeline.md +27 -0
  27. package/dist/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +9 -0
  28. package/dist/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +10 -0
  29. package/dist/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +18 -0
  30. package/dist/plugin/skills/_shared/vcs/gitlab.md +24 -0
  31. package/dist/plugin/skills/do/address-reviews.md +10 -6
  32. package/dist/plugin/skills/do/e2e-acceptance.md +4 -4
  33. package/dist/plugin/skills/do/fix-ci.md +1 -1
  34. package/dist/plugin/skills/do/input-routing.md +4 -4
  35. package/dist/plugin/skills/do/open-prs/forward.md +6 -4
  36. package/dist/plugin/skills/do/open-prs/update.md +8 -6
  37. package/dist/plugin/skills/do/per-comment-replies.md +22 -13
  38. package/dist/plugin/skills/do/resolve-conflicts.md +1 -1
  39. package/dist/plugin/skills/do/resolve-reminder.md +8 -6
  40. package/dist/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  41. package/dist/plugin/skills/muggle-do/SKILL.md +2 -2
  42. package/dist/plugin/skills/muggle-feedback/ops/submit.md +4 -4
  43. package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -2
  44. package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +13 -11
  45. package/dist/plugin/skills/muggle-pr-followup/contract.md +13 -9
  46. package/dist/plugin/skills/muggle-pr-followup/finalize.md +1 -1
  47. package/dist/plugin/skills/muggle-pr-followup/reconcile.md +1 -1
  48. package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +4 -1
  49. package/dist/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +13 -13
  50. package/dist/plugin/skills/muggle-preferences/ops/configure.md +2 -2
  51. package/dist/plugin/skills/muggle-test/SKILL.md +23 -126
  52. package/dist/plugin/skills/muggle-test/execute-local.md +60 -0
  53. package/dist/plugin/skills/muggle-test/execute-remote.md +49 -0
  54. package/dist/plugin/skills/muggle-test/reference.md +1 -2
  55. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +5 -5
  56. package/dist/release-manifest.json +4 -4
  57. package/dist/{src-2O5UH4FA.js → src-SS2AL3O7.js} +1 -1
  58. package/package.json +6 -6
  59. package/plugin/.claude-plugin/plugin.json +1 -1
  60. package/plugin/.cursor-plugin/plugin.json +1 -1
  61. package/plugin/scripts/ensure-electron-app.sh +1 -1
  62. package/plugin/scripts/guardrails.mjs +4 -2
  63. package/plugin/skills/_shared/dev-loop/publish.md +4 -8
  64. package/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
  65. package/plugin/skills/_shared/pr-followup-helpers/echo-skip.md +1 -1
  66. package/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -1
  67. package/plugin/skills/_shared/test-case-chain-readiness.md +1 -2
  68. package/plugin/skills/_shared/vcs/CLAUDE.md +9 -0
  69. package/plugin/skills/_shared/vcs/detect-vcs.md +28 -0
  70. package/{dist/plugin/skills/_shared/github-cli-recipes → plugin/skills/_shared/vcs/github}/unresolved-threads.md +1 -1
  71. package/{dist/plugin/skills/_shared/github-cli-recipes → plugin/skills/_shared/vcs/github}/verify-working-tree.md +1 -1
  72. package/plugin/skills/_shared/vcs/github.md +21 -0
  73. package/plugin/skills/_shared/vcs/gitlab/loop-user-identity.md +9 -0
  74. package/plugin/skills/_shared/vcs/gitlab/mr-create.md +11 -0
  75. package/plugin/skills/_shared/vcs/gitlab/mr-discussions.md +18 -0
  76. package/plugin/skills/_shared/vcs/gitlab/mr-edit.md +8 -0
  77. package/plugin/skills/_shared/vcs/gitlab/mr-metadata.md +22 -0
  78. package/plugin/skills/_shared/vcs/gitlab/mr-note.md +7 -0
  79. package/plugin/skills/_shared/vcs/gitlab/mr-pipeline.md +27 -0
  80. package/plugin/skills/_shared/vcs/gitlab/reply-discussion.md +9 -0
  81. package/plugin/skills/_shared/vcs/gitlab/resolve-discussion.md +10 -0
  82. package/plugin/skills/_shared/vcs/gitlab/unresolved-discussions.md +18 -0
  83. package/plugin/skills/_shared/vcs/gitlab.md +24 -0
  84. package/plugin/skills/do/address-reviews.md +10 -6
  85. package/plugin/skills/do/e2e-acceptance.md +4 -4
  86. package/plugin/skills/do/fix-ci.md +1 -1
  87. package/plugin/skills/do/input-routing.md +4 -4
  88. package/plugin/skills/do/open-prs/forward.md +6 -4
  89. package/plugin/skills/do/open-prs/update.md +8 -6
  90. package/plugin/skills/do/per-comment-replies.md +22 -13
  91. package/plugin/skills/do/resolve-conflicts.md +1 -1
  92. package/plugin/skills/do/resolve-reminder.md +8 -6
  93. package/plugin/skills/muggle-browser-task/SKILL.md +1 -1
  94. package/plugin/skills/muggle-do/SKILL.md +2 -2
  95. package/plugin/skills/muggle-feedback/ops/submit.md +4 -4
  96. package/plugin/skills/muggle-pr-followup/CLAUDE.md +2 -2
  97. package/plugin/skills/muggle-pr-followup/bootstrap.md +13 -11
  98. package/plugin/skills/muggle-pr-followup/contract.md +13 -9
  99. package/plugin/skills/muggle-pr-followup/finalize.md +1 -1
  100. package/plugin/skills/muggle-pr-followup/reconcile.md +1 -1
  101. package/plugin/skills/muggle-pr-followup/state-schemas.md +4 -1
  102. package/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +13 -13
  103. package/plugin/skills/muggle-preferences/ops/configure.md +2 -2
  104. package/plugin/skills/muggle-test/SKILL.md +23 -126
  105. package/plugin/skills/muggle-test/execute-local.md +60 -0
  106. package/plugin/skills/muggle-test/execute-remote.md +49 -0
  107. package/plugin/skills/muggle-test/reference.md +1 -2
  108. package/plugin/skills/muggle-test-feature-local/SKILL.md +5 -5
  109. package/dist/plugin/skills/_shared/github-cli-recipes.md +0 -21
  110. package/dist/plugin/skills/muggle-preferences/preference-gates/autoPublishLocalResults.md +0 -11
  111. package/plugin/skills/_shared/github-cli-recipes.md +0 -21
  112. package/plugin/skills/muggle-preferences/preference-gates/autoPublishLocalResults.md +0 -11
  113. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/line-comments-for-review.md +0 -0
  114. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/loop-user-identity.md +0 -0
  115. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-checks.md +0 -0
  116. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-edit.md +0 -0
  117. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-metadata.md +0 -0
  118. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/push-to-branch.md +0 -0
  119. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/reply-line-comment.md +0 -0
  120. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/submitted-reviews.md +0 -0
  121. /package/dist/plugin/skills/_shared/{github-cli-recipes → vcs/github}/top-level-comment.md +0 -0
  122. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/line-comments-for-review.md +0 -0
  123. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/loop-user-identity.md +0 -0
  124. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-checks.md +0 -0
  125. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-edit.md +0 -0
  126. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/pr-metadata.md +0 -0
  127. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/push-to-branch.md +0 -0
  128. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/reply-line-comment.md +0 -0
  129. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/submitted-reviews.md +0 -0
  130. /package/plugin/skills/_shared/{github-cli-recipes → vcs/github}/top-level-comment.md +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
4
- "version": "5.0.4",
4
+ "version": "5.1.0",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
5
- "version": "5.0.4",
5
+ "version": "5.1.0",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -104,7 +104,7 @@ if [ -f "$prefs_global_file" ]; then
104
104
  const defaults = {
105
105
  autoLogin:'ask', autoSelectProject:'ask', autoSelectLocalHost:'ask',
106
106
  showElectronBrowser:'ask', openTestResultsAfterRun:'ask',
107
- defaultExecutionMode:'ask', autoPublishLocalResults:'ask',
107
+ defaultExecutionMode:'ask',
108
108
  suggestRelatedUseCases:'ask', suggestRelatedTestCases:'ask', autoDetectChanges:'ask',
109
109
  postPRVisualWalkthrough:'ask', autoCreatePR:'ask',
110
110
  checkForUpdates:'ask', verboseOutput:'ask',
@@ -27,14 +27,16 @@ function markPrHandled(sessionId2, prUrl, dirOverride) {
27
27
 
28
28
  // src/guardrails/prOpened.ts
29
29
  var PR_URL = /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+/;
30
+ var MR_URL = /https?:\/\/[^/\s]+\/[^\s]+\/-\/merge_requests\/\d+/;
30
31
  var CREATE_CMD = /\bgh\s+pr\s+(create|ready)\b/;
32
+ var MR_CREATE_CMD = /\bglab\s+mr\s+create\b|\bglab\s+mr\s+update\b.*--ready\b/;
31
33
  function detectPrOpened(input2) {
32
34
  if (input2.tool_name !== "Bash") return null;
33
35
  const cmd = input2.tool_input?.command ?? "";
34
- if (!CREATE_CMD.test(cmd)) return null;
36
+ if (!CREATE_CMD.test(cmd) && !MR_CREATE_CMD.test(cmd)) return null;
35
37
  const out = `${input2.tool_response?.stdout ?? ""}
36
38
  ${input2.tool_response?.output ?? ""}`;
37
- const m = out.match(PR_URL);
39
+ const m = out.match(PR_URL) ?? out.match(MR_URL);
38
40
  return m ? m[0] : null;
39
41
  }
40
42
 
@@ -1,13 +1,9 @@
1
- # Dev Loop — Publish & Screenshots
1
+ # Dev Loop — Cloud Refs & Screenshots
2
2
 
3
- ## Publish
3
+ ## Cloud refs
4
4
 
5
- After every completed run — pass or fail — publish: failed runs still need cloud-hosted screenshots and per-step actions for the walkthrough, and the upload `status` tells the backend whether to promote the action script as the canonical replay script (pass → promote; fail → record only).
6
-
7
- `muggle-local-publish-test-script` with `runId` and `cloudTestCaseId`. Retain `testScriptId`, `actionScriptId`, `viewUrl`. Batch publishes in parallel.
8
-
9
- If publish rejects with `has no generated actionScript steps to publish` (a true zero-step run), fall back to `muggle-remote-local-run-upload` with whatever exists (`summaryStep`, `errorMessage`, empty `actionScript`); capture `actionScriptId` and `viewUrl` the same way.
5
+ The studio publishes every completed run — pass or fail — to the cloud during execution. There is no publish step to run. The run result already carries the cloud identifiers: read `viewUrl`, `cloudTestScriptId`, and `cloudActionScriptId` from `muggle-local-run-result-get` for the run. (`cloudTestScriptId` is absent for a failed generation — the backend records the action script and `viewUrl` but no test script.)
10
6
 
11
7
  ## Screenshots
12
8
 
13
- Per published script, `muggle-remote-test-script-get` with the `testScriptId` from publish → per step `operation.screenshotUrl` and `operation.action`. Retain `{ stepIndex, action, screenshotUrl }`. Batch in parallel. These feed the `E2eReport` consumed by [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) — the dev loop does not post to the PR.
9
+ Per run, `muggle-remote-test-script-get` with the `cloudTestScriptId` from the run result → per step `operation.screenshotUrl` and `operation.action`. Retain `{ stepIndex, action, screenshotUrl }`. Batch in parallel. These feed the `E2eReport` consumed by [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) — the dev loop does not post to the PR.
@@ -2,7 +2,7 @@
2
2
 
3
3
  The address-reviews flow only acts on reviews submitted by users in the **allow-list** = (requested reviewers ∪ CODEOWNERS ∪ {PR author}) − bots. Re-resolve every invocation — never cache across cycles.
4
4
 
5
- The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. Self-loop is prevented at the watcher's filter layer rather than here: `POST /pulls/<n>/comments/<id>/replies` does create an implicit review under the loop user's identity, and the watcher drops it via the loop-echo clause in [`../github-cli-recipes/submitted-reviews.md`](../github-cli-recipes/submitted-reviews.md) — matched by the loop signature ([`loop-signature.md`](loop-signature.md)), so the author's *genuine* reviews and thread replies still get through. Including the PR author in this allow-list is therefore safe.
5
+ The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. Self-loop is prevented at the watcher's filter layer rather than here: `POST /pulls/<n>/comments/<id>/replies` does create an implicit review under the loop user's identity, and the watcher drops it via the loop-echo clause in [`../vcs/github/submitted-reviews.md`](../vcs/github/submitted-reviews.md) — matched by the loop signature ([`loop-signature.md`](loop-signature.md)), so the author's *genuine* reviews and thread replies still get through. Including the PR author in this allow-list is therefore safe.
6
6
 
7
7
  ## Step 1: requested reviewers
8
8
 
@@ -5,6 +5,6 @@ When `/muggle-do` posts a threaded reply to a review comment, GitHub surfaces th
5
5
  Under the thread-state dispatch trigger this is **intrinsic** — there is no "advance past the echo" step to get wrong:
6
6
 
7
7
  - **Line-comment threads.** A thread is actionable only when its newest comment lacks the loop marker `<!-- muggle-do:bot -->` (see [`loop-signature.md`](loop-signature.md)). After the loop replies, the newest comment is the loop's own and carries the marker, so the thread drops out of the actionable set on its own.
8
- - **Body-only reviews.** A loop reply always carries a line comment, so it is never body-only; the body-only check (no line comments, `id > lastBodyReviewId` — see [`../github-cli-recipes/submitted-reviews.md`](../github-cli-recipes/submitted-reviews.md)) excludes echoes structurally.
8
+ - **Body-only reviews.** A loop reply always carries a line comment, so it is never body-only; the body-only check (no line comments, `id > lastBodyReviewId` — see [`../vcs/github/submitted-reviews.md`](../vcs/github/submitted-reviews.md)) excludes echoes structurally.
9
9
 
10
10
  Classify by the marker, never by `author.login` — under a shared account the loop posts as the PR author, so the login cannot tell echo from human.
@@ -53,4 +53,4 @@ Write to `state.md` under a `## Pre-flight answers` block:
53
53
  - `Auth status: <ok | re-authed | N/A>`
54
54
  - `Working tree: <path>` — the verified checkout the cycle runs against
55
55
 
56
- Missing any required field is a seeding bug: escalate with the session path and halt. Never silently skip E2E.
56
+ Once the strategy is `local-e2e`, missing any of its required fields (`Local URL`, `Muggle Test project`) is a seeding bug — escalate with the session path and halt; don't silently downgrade a chosen `local-e2e` to a skip. But `unit-only` and `skip` are valid first-class strategies for a PR with no testable surface — they need none of those fields, and a session seeded with no block at all is itself a clean skip.
@@ -19,8 +19,7 @@ Run once the target `testCaseId` is chosen and the local URL + services are conf
19
19
  - Not ready → **generate its script only (never replay):**
20
20
  1. `muggle-remote-test-case-get` for the ancestor.
21
21
  2. Determine `freshSession` for that ancestor from its own content — same rules as Step 6.
22
- 3. `muggle-local-execute-test-generation` with the ancestor test case, `localUrl`, `cwd`, and a long `timeoutMs` (see Step 6's timeout guidance). Do **not** call `muggle-local-execute-replay`.
23
- 4. `muggle-local-publish-test-script` (`runId`, `cloudTestCaseId` = ancestor) so the generated script is promoted as that ancestor's canonical replay script — it now reads as ready for any case downstream.
22
+ 3. `muggle-local-execute-test-generation` with the ancestor test case, `localUrl`, `cwd`, and a long `timeoutMs` (see Step 6's timeout guidance). Do **not** call `muggle-local-execute-replay`. The studio publishes the generated run during execution, promoting its script as that ancestor's canonical replay script — it now reads as ready for any case downstream (confirm via `muggle-local-run-result-get` carrying a `cloudActionScriptId`).
24
23
 
25
24
  3. **All ancestors ready** → continue to Step 5 for the target test case.
26
25
 
@@ -0,0 +1,9 @@
1
+ # VCS provider recipes
2
+
3
+ Provider-agnostic seam for the watcher and dev-cycle skills. A caller resolves one provider token via [`detect-vcs.md`](detect-vcs.md) — `github` (`gh`) or `gitlab` (`glab`) — then loads the matching provider's recipe for each agnostic step. The two recipe sets mirror each other call-for-call; callers branch on the token, never fork procedure.
4
+
5
+ ## Index
6
+
7
+ - [`detect-vcs.md`](detect-vcs.md) — resolve `github` | `gitlab` from a URL argument or the origin remote.
8
+ - [`github.md`](github.md) — `gh` / `git` recipe TOC; per-recipe files in [`github/`](github/).
9
+ - [`gitlab.md`](gitlab.md) — `glab` / `git` recipe TOC; per-recipe files in [`gitlab/`](gitlab/).
@@ -0,0 +1,28 @@
1
+ # Detect VCS provider
2
+
3
+ Resolve a single provider token — `github` or `gitlab` — before a caller picks its recipe set. GitHub uses `gh`; GitLab uses `glab`. Auth errors from either surface verbatim.
4
+
5
+ ## Resolution order
6
+
7
+ 1. **From a URL argument**, if one was passed:
8
+ - `github.com/<owner>/<repo>/pull/<n>` → `github`.
9
+ - `<host>/<group>/<project>/-/merge_requests/<iid>` → `gitlab`. The `/-/merge_requests/` segment is the tell, on any host.
10
+
11
+ 2. **From the repo**, when no URL — parse the origin remote:
12
+
13
+ ```bash
14
+ git remote get-url origin
15
+ ```
16
+
17
+ - Host `github.com` → `github`.
18
+ - Host `gitlab.com`, **or** any other host where `glab auth status` succeeds → `gitlab`.
19
+
20
+ ## GitLab wrinkles
21
+
22
+ - **Nested namespaces.** The path between host and `/-/` can be `group/subgroup/project`, any depth. Do not assume two segments — split on `/-/`, the project path is everything before it.
23
+ - **The `/-/` segment** separates the project path from the resource (`/-/merge_requests/<iid>`). GitHub has no equivalent; its `/pull/<n>` sits directly under `<owner>/<repo>`.
24
+ - **Self-hosted hosts.** GitLab is not just `gitlab.com` — `git.acme.com`, `gitlab.internal`, etc. Resolve the host from the remote and confirm it against `glab`'s configured host (`GITLAB_HOST`, or `glab auth status`'s active host). A remote host that matches `glab`'s host → `gitlab`, even when unknown to this doc.
25
+
26
+ ## Output
27
+
28
+ The bare token `github` or `gitlab` — nothing else.
@@ -28,7 +28,7 @@ query($owner: String!, $name: String!, $number: Int!) {
28
28
  }'
29
29
  ```
30
30
 
31
- Filter client-side to `isResolved == false`. Walk each thread's comments in `createdAt` order and classify by the loop marker (see [`../pr-followup-helpers/loop-signature.md`](../pr-followup-helpers/loop-signature.md)), not by `author.login` — the login is ambiguous under a shared account:
31
+ Filter client-side to `isResolved == false`. Walk each thread's comments in `createdAt` order and classify by the loop marker (see [`../../pr-followup-helpers/loop-signature.md`](../../pr-followup-helpers/loop-signature.md)), not by `author.login` — the login is ambiguous under a shared account:
32
32
 
33
33
  - **Addressed, awaiting resolve** — the **newest** comment carries the loop marker `<!-- muggle-do:bot -->`. The loop has replied and nothing newer is waiting. → resolve-reminder.
34
34
  - **Unaddressed human comment** — the newest comment lacks the marker and is newer than the thread's newest loop-marked comment (or the thread has no loop comment yet). → actionable: the round should address it. The **watcher's dispatch trigger** additionally requires `isOutdated == false` — a thread whose anchored line has since changed is skipped, since re-surfacing a stale anchor risks answering a concern the change already mooted. The resolve-reminder stage ignores `isOutdated`.
@@ -14,4 +14,4 @@ Accept any remote URL form for `<owner>/<repo>` (with or without trailing `.git`
14
14
  - `git@github.com:<owner>/<repo>`
15
15
  - `ssh://git@github.com/<owner>/<repo>`
16
16
 
17
- Any mismatch → bootstrap aborts using the wrong-checkout template in [`../../muggle-pr-followup/output-templates/bootstrap.md`](../../muggle-pr-followup/output-templates/bootstrap.md).
17
+ Any mismatch → bootstrap aborts using the wrong-checkout template in [`../../../muggle-pr-followup/output-templates/bootstrap.md`](../../../muggle-pr-followup/output-templates/bootstrap.md).
@@ -0,0 +1,21 @@
1
+ # GitHub CLI Recipes
2
+
3
+ Reusable `gh` / `git` snippets used by `muggle-pr-followup` (watcher + bootstrap) and `/muggle-do` (address-reviews + open-prs). Each recipe is one file — load only what you need.
4
+
5
+ Skills assume a working `gh auth status`. Auth errors surface verbatim from `gh`.
6
+
7
+ ## Index
8
+
9
+ | Recipe | Use case |
10
+ | :----- | :------- |
11
+ | [`pr-metadata`](github/pr-metadata.md) | Snapshot PR state, head SHA, branch, conflict + `behind_by` out-of-date detection — watcher + bootstrap. |
12
+ | [`submitted-reviews`](github/submitted-reviews.md) | Fetch a review by id / watcher's body-only-review check. |
13
+ | [`pr-checks`](github/pr-checks.md) | Check-run rollup for the head SHA — watcher's CI poll. |
14
+ | [`line-comments-for-review`](github/line-comments-for-review.md) | Pull a review's line comments — `/muggle-do` per-comment routing. |
15
+ | [`unresolved-threads`](github/unresolved-threads.md) | GraphQL unresolved-thread state — watcher's dispatch trigger + resolve-reminder. |
16
+ | [`reply-line-comment`](github/reply-line-comment.md) | POST a threaded reply on a line comment. |
17
+ | [`top-level-comment`](github/top-level-comment.md) | POST a top-level PR comment — resolve-reminder + overflow. |
18
+ | [`push-to-branch`](github/push-to-branch.md) | Push + capture new SHA after address-reviews work. |
19
+ | [`verify-working-tree`](github/verify-working-tree.md) | Three checks bootstrap runs before seeding state. |
20
+ | [`pr-edit`](github/pr-edit.md) | Refresh title or body when address-reviews mode flips state. |
21
+ | [`loop-user-identity`](github/loop-user-identity.md) | Resolve the GitHub login of the loop user. |
@@ -0,0 +1,9 @@
1
+ # Identify the loop user
2
+
3
+ The GitLab identity that owns the authenticated `glab` token. Resolve-reminder thread classification and reply attribution need this.
4
+
5
+ ```bash
6
+ glab api user --jq '.username'
7
+ ```
8
+
9
+ Cache in `state.md` under `Loop user:`; re-resolve only when missing.
@@ -0,0 +1,11 @@
1
+ # Open a merge request
2
+
3
+ For `open-prs`. Push the branch first (see [`../github/push-to-branch.md`](../github/push-to-branch.md)), then open the MR.
4
+
5
+ ```bash
6
+ glab mr create -R <group>/<project> \
7
+ --source-branch <branch> --target-branch <base> \
8
+ --title "<title>" --description "$(cat <file>)"
9
+ ```
10
+
11
+ `glab` prints the created MR's URL on success — capture stdout and store the URL for handoff (the watcher seeds from it, the user gets the link).
@@ -0,0 +1,18 @@
1
+ # MR discussions
2
+
3
+ Incoming feedback for the watcher's poll and the address-reviews fetch. GitLab has **no review envelope** — there is no submitted-review object grouping a summary body with line comments. Feedback arrives as individual notes, each belonging to a discussion (a thread). This recipe is the `submitted-reviews` analogue.
4
+
5
+ ```bash
6
+ glab api projects/:id/merge_requests/:iid/discussions --paginate
7
+ ```
8
+
9
+ Each discussion has `id` and a `notes[]` array; each note has `id`, `author.username`, `body`, `created_at`, `system` (a `true` flag marks GitLab's own activity entries — skip them).
10
+
11
+ Common filter:
12
+
13
+ - `system == false` (skip "added 2 commits", "changed the description", etc.)
14
+ - `author.username` in the resolved allow-list
15
+
16
+ Cursor: track the highest note/discussion `id` seen in `last_seen`; a note whose `id` exceeds it is new this tick. (GitLab ids are monotonic, so id ordering is reliable where `created_at` ties.)
17
+
18
+ Loop-echo skip: a note the loop itself posted carries the `<!-- muggle-do:bot -->` marker in its `body`. Classify by that marker per note, never by `author.username` alone — under a shared account the login is ambiguous. A marked note is the loop's own and must never re-trigger a cycle; an unmarked note from an allow-listed author is actionable.
@@ -0,0 +1,8 @@
1
+ # Refresh the MR title or description
2
+
3
+ For `open-prs/update.md` when E2E state flips (passing↔failing) or validation strategy changes. GitLab calls the body the **description**.
4
+
5
+ ```bash
6
+ glab mr update <iid> -R <group>/<project> --title "<new-title>"
7
+ glab mr update <iid> -R <group>/<project> --description "$(cat <file>)"
8
+ ```
@@ -0,0 +1,22 @@
1
+ # MR metadata snapshot
2
+
3
+ Fetch the fields the watcher and bootstrap need.
4
+
5
+ ```bash
6
+ glab mr view <iid> -R <group>/<project> -F json
7
+ ```
8
+
9
+ - `state` is one of `opened`, `merged`, `closed`, `locked` (lowercase — unlike GitHub's uppercase).
10
+ - `sha` is the current head SHA — store as `head_sha` in `prs.json`.
11
+ - `source_branch` is the branch — must match the working tree's branch in bootstrap. `target_branch` is the base.
12
+ - conflict comes from `detailed_merge_status`, not a `mergeable` + `mergeStateStatus` pair. The watcher's **conflict** signal is `detailed_merge_status == "broken_status"` or `"conflict"`. `"checking"`/`"unchecked"` means GitLab is still computing — treat as not-conflicting this tick.
13
+
14
+ ## Behind-by (out-of-date detection)
15
+
16
+ `detailed_merge_status == "need_rebase"` reports a behind branch only when the project enforces "fast-forward merge"; otherwise it stays `mergeable` while behind. Detect out-of-date straight from commit ancestry instead — independent of merge-method config:
17
+
18
+ ```bash
19
+ glab api projects/:id/repository/compare?from=<target_branch>&to=<head_sha> --jq '.commits | length'
20
+ ```
21
+
22
+ GitLab's compare lists only the commits `to` is ahead by, so flip the direction: compare `from=<head_sha>&to=<target_branch>` and a non-empty `.commits` ⇒ the base has commits the head lacks ⇒ out of date. Empty ⇒ current with base.
@@ -0,0 +1,7 @@
1
+ # Top-level MR note
2
+
3
+ For the resolve-reminder stage and any non-threaded notice.
4
+
5
+ ```bash
6
+ glab mr note <iid> -R <group>/<project> -m "<text>"
7
+ ```
@@ -0,0 +1,27 @@
1
+ # MR pipeline rollup
2
+
3
+ Fetch the CI state for an MR's head — what the watcher polls to detect red CI. GitLab runs **one** pipeline of jobs per commit, not independent check-runs, so the rollup is over that pipeline's jobs.
4
+
5
+ ```bash
6
+ glab ci status -R <group>/<project> -b <source_branch>
7
+ ```
8
+
9
+ Or straight from the API for the latest pipeline and its jobs:
10
+
11
+ ```bash
12
+ glab api projects/:id/merge_requests/:iid/pipelines --jq '.[0].id'
13
+ glab api projects/:id/pipelines/<pipeline-id>/jobs --paginate
14
+ ```
15
+
16
+ Each job:
17
+
18
+ - `name` — the job's name (e.g. `lint`, `test`, `build`).
19
+ - `status` — one of `success` / `failed` / `running` / `pending` / `created` / `canceled` / `skipped` / `manual`.
20
+
21
+ Classify for the watcher:
22
+
23
+ - **red** — any job `failed`. Candidate for fix-ci.
24
+ - **pending** — no job `failed`, but any job `running` / `pending` / `created`. Pipeline hasn't settled → idle.
25
+ - **green** — every job `success` / `skipped` / `manual` / `canceled`, or no jobs at all.
26
+
27
+ The fix-ci dispatch carries the `name`s of the `failed` jobs.
@@ -0,0 +1,9 @@
1
+ # Reply to a discussion (threaded)
2
+
3
+ Used by `/muggle-do` per-comment inline replies. A threaded reply is a new note appended to an existing discussion.
4
+
5
+ ```bash
6
+ glab api --method POST \
7
+ projects/:id/merge_requests/:iid/discussions/<discussion-id>/notes \
8
+ -f body="<reply-text>"
9
+ ```
@@ -0,0 +1,10 @@
1
+ # Resolve a discussion thread
2
+
3
+ Mark a thread resolved once the loop's reply has addressed it — the resolve-reminder stage's action.
4
+
5
+ ```bash
6
+ glab api --method PUT \
7
+ "projects/:id/merge_requests/:iid/discussions/<discussion-id>?resolved=true"
8
+ ```
9
+
10
+ Resolves every resolvable note in the thread at once. Only resolvable (diff/line) threads accept this; a non-resolvable discussion returns an error.
@@ -0,0 +1,18 @@
1
+ # Unresolved discussions
2
+
3
+ For the watcher's dispatch trigger and the resolve-reminder stage. REST exposes resolution directly — no GraphQL needed, unlike GitHub.
4
+
5
+ ```bash
6
+ glab api projects/:id/merge_requests/:iid/discussions --paginate \
7
+ --jq '[.[] | select(.notes[0].resolvable == true) | select(any(.notes[]; .resolved == false))]'
8
+ ```
9
+
10
+ A discussion is resolvable when its notes carry `resolvable == true` (diff/line threads are; the MR description and system notes are not). A thread is **unresolved** when any of its notes has `resolved == false`.
11
+
12
+ Walk each unresolved thread's `notes[]` in `created_at` order and classify by the loop marker (see [`../../pr-followup-helpers/loop-signature.md`](../../pr-followup-helpers/loop-signature.md)), not by `author.username` — the login is ambiguous under a shared account:
13
+
14
+ - **Addressed, awaiting resolve** — the **newest** note carries `<!-- muggle-do:bot -->`. The loop has replied and nothing newer waits. → resolve-reminder.
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
+ - **Not addressed** — indeterminate (e.g. no notes).
17
+
18
+ Each thread carries its `id` (the `discussion_id`) — the watcher collects this from an actionable thread to build its dispatch list and to target the resolve call later.
@@ -0,0 +1,24 @@
1
+ # GitLab CLI Recipes
2
+
3
+ Reusable `glab` / `git` snippets used by `muggle-pr-followup` (watcher + bootstrap) and `/muggle-do` (address-reviews + open-prs), mirroring the `gh` set for GitLab merge requests. Each recipe is one file — load only what you need.
4
+
5
+ Skills assume a working `glab auth status`. Auth errors surface verbatim from `glab`.
6
+
7
+ The project ref `:id` is `<group>/<project>` — URL-encode it for `glab api` (`mygroup/myproj` → `mygroup%2Fmyproj`). `:iid` is the MR's per-project internal id (the `!123` number), not the global id.
8
+
9
+ ## Index
10
+
11
+ | Recipe | Use case |
12
+ | :----- | :------- |
13
+ | [`mr-metadata`](gitlab/mr-metadata.md) | Snapshot MR state, head SHA, branch, conflict + out-of-date detection — watcher + bootstrap. |
14
+ | [`mr-pipeline`](gitlab/mr-pipeline.md) | Pipeline-job rollup for the head SHA — watcher's CI poll. |
15
+ | [`mr-discussions`](gitlab/mr-discussions.md) | Fetch incoming notes/discussions — watcher's feedback poll. |
16
+ | [`unresolved-discussions`](gitlab/unresolved-discussions.md) | Unresolved-discussion state — watcher's dispatch trigger + resolve-reminder. |
17
+ | [`reply-discussion`](gitlab/reply-discussion.md) | POST a threaded reply on a discussion. |
18
+ | [`mr-note`](gitlab/mr-note.md) | POST a top-level MR note — resolve-reminder + overflow. |
19
+ | [`resolve-discussion`](gitlab/resolve-discussion.md) | Mark a discussion thread resolved. |
20
+ | [`mr-edit`](gitlab/mr-edit.md) | Refresh title or description when address-reviews mode flips state. |
21
+ | [`mr-create`](gitlab/mr-create.md) | Open an MR + capture its URL for handoff. |
22
+ | [`loop-user-identity`](gitlab/loop-user-identity.md) | Resolve the GitLab username of the loop user. |
23
+ | [`push-to-branch`](github/push-to-branch.md) | Push + capture new SHA after address-reviews work (provider-agnostic). |
24
+ | [`verify-working-tree`](github/verify-working-tree.md) | Three checks bootstrap runs before seeding state (provider-agnostic). |
@@ -33,17 +33,21 @@ Before assembling work, rebase onto the latest default branch so the cycle addre
33
33
 
34
34
  ### Step 1 — Assemble the work set
35
35
 
36
- Two sources, combined into one batch (dedupe by comment id):
36
+ Resolve the provider per [`../_shared/vcs/detect-vcs.md`](../_shared/vcs/detect-vcs.md), then assemble.
37
+
38
+ **`github`** — two sources, combined into one batch (dedupe by comment id):
37
39
 
38
40
  **(a) The dispatched reviews.** For each review id in the input:
39
41
 
40
- - Fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md) (no watermark; filter to the specific id).
41
- - Fetch its line comments per [`../_shared/github-cli-recipes/line-comments-for-review.md`](../_shared/github-cli-recipes/line-comments-for-review.md).
42
+ - Fetch reviews per [`../_shared/vcs/github/submitted-reviews.md`](../_shared/vcs/github/submitted-reviews.md) (no watermark; filter to the specific id).
43
+ - Fetch its line comments per [`../_shared/vcs/github/line-comments-for-review.md`](../_shared/vcs/github/line-comments-for-review.md).
42
44
 
43
- **(b) Unaddressed comments on every unresolved thread.** Fetch unresolved threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). For each thread classified **unaddressed human comment** — newest comment lacks the loop marker `<!-- muggle-do:bot -->` ([`loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)) and post-dates the loop's last marked reply — add it to the batch — unresolved thread state, not any review-id watermark, is the authority here. This is how a human thread follow-up (a marker-less reply) gets addressed. **Exclude** comments whose review id is in `last_seen.escalated_review_ids` — paused awaiting the user, not re-work.
45
+ **(b) Unaddressed comments on every unresolved thread.** Fetch unresolved threads per [`../_shared/vcs/github/unresolved-threads.md`](../_shared/vcs/github/unresolved-threads.md). For each thread classified **unaddressed human comment** — newest comment lacks the loop marker `<!-- muggle-do:bot -->` ([`loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)) and post-dates the loop's last marked reply — add it to the batch — unresolved thread state, not any review-id watermark, is the authority here. This is how a human thread follow-up (a marker-less reply) gets addressed. **Exclude** comments whose review id is in `last_seen.escalated_review_ids` — paused awaiting the user, not re-work.
44
46
 
45
47
  Group (a) and (b) into one combined batch.
46
48
 
49
+ **`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
+
47
51
  ### Step 2 — Classify each review
48
52
 
49
53
  Apply the classify rule in [`../_shared/pr-followup-helpers/classify.md`](../_shared/pr-followup-helpers/classify.md). Two outcomes per review:
@@ -93,7 +97,7 @@ Invoke [`open-prs/update.md`](open-prs/update.md) (pass the PR URL + slug + exis
93
97
 
94
98
  #### 4f. Post per-comment inline replies
95
99
 
96
- Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable reviews and the new SHA. One reply per comment, in its own thread, describing what was done for it.
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; on `gitlab` the same step also resolves each fully-addressed discussion.
97
101
 
98
102
  (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.)
99
103
 
@@ -109,7 +113,7 @@ Invoke [`resolve-reminder.md`](resolve-reminder.md) once, regardless of whether
109
113
 
110
114
  ### Step 6 — Respawn the watcher
111
115
 
112
- Refresh PR state per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). If the PR is now merged or closed:
116
+ Refresh PR state with the provider resolved in Step 1 — `github` per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md), `gitlab` per [`../_shared/vcs/gitlab/mr-metadata.md`](../_shared/vcs/gitlab/mr-metadata.md) (`state == merged`/`closed`, lowercase). If the PR is now merged or closed:
113
117
 
114
118
  1. Write `result.md` per [`../muggle-pr-followup/state-schemas.md`](../muggle-pr-followup/state-schemas.md#resultmd).
115
119
  2. Do **not** respawn the watcher.
@@ -27,7 +27,7 @@ For local runs, the tool boundaries are:
27
27
  | Scope | MCP tools |
28
28
  | :---- | :-------- |
29
29
  | Cloud (projects, cases, scripts, auth) | `muggle-remote-*` |
30
- | Local (Electron run, publish, results) | `muggle-local-*` |
30
+ | Local (Electron run, results — the studio publishes during the run) | `muggle-local-*` |
31
31
 
32
32
  This keeps execution deterministic: local runs do not depend on cloud replay execution availability.
33
33
 
@@ -96,9 +96,9 @@ Based on the changed files and the requirements goal, determine which test cases
96
96
  - Test cases that cover areas potentially affected by the changes
97
97
  - When in doubt, include the test case (better to over-test than miss a regression)
98
98
 
99
- ### Step 4: Run the dev loop, publish, gather screenshots
99
+ ### Step 4: Run the dev loop, gather screenshots
100
100
 
101
- For each relevant test case, run the shared loop in [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md): `muggle-remote-test-script-list` by `testCaseId` to pick [replay vs regen](../_shared/dev-loop/run.md), [execute with `timeoutMs`](../_shared/dev-loop/timeouts.md), [fetch the result](../_shared/dev-loop/failures.md) and [interpret failures](../_shared/dev-loop/failures.md), [publish](../_shared/dev-loop/publish.md), and gather [per-step screenshots](../_shared/dev-loop/publish.md).
101
+ For each relevant test case, run the shared loop in [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md): `muggle-remote-test-script-list` by `testCaseId` to pick [replay vs regen](../_shared/dev-loop/run.md), [execute with `timeoutMs`](../_shared/dev-loop/timeouts.md), [fetch the result](../_shared/dev-loop/failures.md) and [interpret failures](../_shared/dev-loop/failures.md), then read the studio-published [cloud refs and per-step screenshots](../_shared/dev-loop/publish.md) off the run result.
102
102
 
103
103
  Inputs to the loop: `mode` from the script-exists check, `localUrl`/project from Step 1.7, `cwd` = the working tree recorded in `state.md`.
104
104
 
@@ -161,5 +161,5 @@ Failed runs use the same evidence + diagnosis assembly as the interactive debug
161
161
  - Replay/timeout/result discipline per [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md) — never hand-build `actionScript`, always pass `timeoutMs`, read structured run-result fields.
162
162
  - No hiding failures: surface errors, exit codes, and artifact paths.
163
163
  - In `local-e2e` mode, every relevant test case must be executed — generate a new script if none exists (no skips).
164
- - Always publish after execution to ensure screenshots are cloud-accessible for PR comments.
164
+ - The studio publishes every run during execution, so screenshots are cloud-accessible for PR comments — read the refs off the run result, never re-publish.
165
165
  - **Never drop a test case from the report because it "couldn't run cleanly."** A test that didn't reach its assertion is `inconclusive`, not absent. Dropping it produces misleading verdicts and pushes downstream PR-comment renderers to hand-write the comment — which is the failure mode this stage exists to prevent.
@@ -10,7 +10,7 @@ Resolve red CI on a PR's head — lint/format, typecheck, and failing unit tests
10
10
 
11
11
  ## Input
12
12
 
13
- `$ARGUMENTS` carries a `github.com/.../pull/<n>` URL, `slug=<slug>`, and the failing check names (no review ids). Parse all three.
13
+ `$ARGUMENTS` carries a PR/MR URL (`github.com/.../pull/<n>` or `<host>/<group>/<project>/-/merge_requests/<iid>`), `slug=<slug>`, and the failing check names (no review ids). Parse all three. The names are GitHub check-runs or — when the URL resolves `gitlab` per [`../_shared/vcs/detect-vcs.md`](../_shared/vcs/detect-vcs.md) — the failed pipeline-job names the watcher read off [`../_shared/vcs/gitlab/mr-pipeline.md`](../_shared/vcs/gitlab/mr-pipeline.md); the fix cycle below is identical for either.
14
14
 
15
15
  ## Inputs from disk
16
16
 
@@ -1,10 +1,10 @@
1
1
  # Input routing
2
2
 
3
- How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–4 are programmatic — dispatched by the watcher — so never ask on those. Inspect in order:
3
+ How `/muggle-do` resolves `$ARGUMENTS` to a mode. Modes 1–4 are programmatic — dispatched by the watcher — so never ask on those. A change-URL in modes 1–3 is either a GitHub PR (`github.com/.../pull/<n>`) or a GitLab MR (`<host>/.../-/merge_requests/<iid>`); provider is resolved via [`../_shared/vcs/detect-vcs.md`](../_shared/vcs/detect-vcs.md). Inspect in order:
4
4
 
5
- 1. **Address-reviews** — a `github.com/.../pull/<n>` URL **and** one or more review ids (integers ≥ 100000000) → [`address-reviews.md`](address-reviews.md).
6
- 2. **Fix-CI** — a `github.com/.../pull/<n>` URL **and** a `fix ci` / `fix-ci` directive with failing check names (no review ids) → [`fix-ci.md`](fix-ci.md).
7
- 3. **Rebase** — a `github.com/.../pull/<n>` URL **and** a `rebase` directive (or legacy `resolve conflicts` / `resolve-conflicts`; no review ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md). Rebases the branch onto its base whether it's merely behind or actually conflicting.
5
+ 1. **Address-reviews** — a PR/MR URL **and** an address-reviews directive carrying review/discussion ids → [`address-reviews.md`](address-reviews.md). On GitHub these ids are integers ≥ 100000000; that magnitude is GitHub-specific lore, **not** a portable test. Under GitLab, tell this apart from other directives by the directive keyword plus the presence of discussion ids, never by id size.
6
+ 2. **Fix-CI** — a PR/MR URL **and** a `fix ci` / `fix-ci` directive with failing check names (no review/discussion ids) → [`fix-ci.md`](fix-ci.md).
7
+ 3. **Rebase** — a PR/MR URL **and** a `rebase` directive (or legacy `resolve conflicts` / `resolve-conflicts`; no review/discussion ids, no check names) → [`resolve-conflicts.md`](resolve-conflicts.md). Rebases the branch onto its base whether it's merely behind or actually conflicting.
8
8
  4. **Post-merge cleanup** — a `cleanup` token and `slug=<slug>` (no PR URL, no review ids), optionally `state=<merged|closed>` (default `merged`) → [`cleanup.md`](cleanup.md).
9
9
  5. **Empty / `help` / `menu` / `?`** → menu + session selector.
10
10
  6. **Task automation** (perform an action on a website) → `muggle:muggle-browser-task`.
@@ -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. Creates the PR via `gh pr create`, seeds session state, dispatches the first watcher.
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 4 — then seeds session state and dispatches the first watcher.
4
4
 
5
5
  ## Turn preamble
6
6
 
@@ -30,11 +30,13 @@ Forward pipeline's Stage 7. Invoked by `/muggle-do` after stages 1–6 of a fres
30
30
  - `## Acceptance Criteria` — bulleted; omit if empty.
31
31
  - `## Changes` — summary of what changed in this repo.
32
32
  - `## Validation` — one line: link to E2E report, `unit-only`, or `skip — <reason>`.
33
- - **If an E2E report exists,** invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode B to render the walkthrough block. Embed the returned `body` verbatim. If no report, skip this block entirely.
33
+ - **Walkthrough block** — only when an E2E report exists. Fire [`postPRVisualWalkthrough`](../../muggle-preferences/preference-gates/postPRVisualWalkthrough.md); on skip, omit this block. Otherwise invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode B and embed the returned `body` verbatim. No report → skip the block.
34
34
 
35
- 4. **Create:** `gh pr create --title "..." --body "..." --head <branch>`. Capture the PR URL and number.
35
+ 4. **Create:** resolve the provider per [`../../_shared/vcs/detect-vcs.md`](../../_shared/vcs/detect-vcs.md).
36
+ - `github` → `gh pr create --title "..." --body "..." --head <branch>`. Capture the PR URL and number.
37
+ - `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.
36
38
 
37
- 5. **Overflow comment:** if the walkthrough skill returned a non-null `comment`, post it once per [`../../_shared/github-cli-recipes/top-level-comment.md`](../../_shared/github-cli-recipes/top-level-comment.md). Never post when `comment` is `null`.
39
+ 5. **Overflow comment:** if the walkthrough skill returned a non-null `comment`, post it once using the provider resolved in Step 4 — `github` per [`../../_shared/vcs/github/top-level-comment.md`](../../_shared/vcs/github/top-level-comment.md), `gitlab` per [`../../_shared/vcs/gitlab/mr-note.md`](../../_shared/vcs/gitlab/mr-note.md). Never post when `comment` is `null`.
38
40
 
39
41
  ## Stage 8 handoff
40
42
 
@@ -20,21 +20,23 @@ Does **not** create a PR, seed session state, or dispatch a watcher (`/muggle-do
20
20
 
21
21
  Skip `autoCreatePR` (it gates creation, not update). The PR's title is left intact unless state changed in Step 3.
22
22
 
23
- 1. **Push:** per [`../../_shared/github-cli-recipes/push-to-branch.md`](../../_shared/github-cli-recipes/push-to-branch.md). Capture the new SHA.
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
+
25
+ 1. **Push:** per [`../../_shared/vcs/github/push-to-branch.md`](../../_shared/vcs/github/push-to-branch.md). Capture the new SHA.
24
26
 
25
27
  2. **Append new SHA** to `last_seen.json[<key>].pushed_shas` (the resolve-reminder stage uses this to recognize threads addressed by the loop). Set `last_seen.last_pushed_sha` to the new SHA too.
26
28
 
27
29
  3. **Refresh title if state changed.** Compare the new state against the current PR title prefix:
28
- - E2E now passing, current title has `[E2E FAILING]` → strip the prefix per [`../../_shared/github-cli-recipes/pr-edit.md`](../../_shared/github-cli-recipes/pr-edit.md).
30
+ - E2E now passing, current title has `[E2E FAILING]` → strip the prefix per [`../../_shared/vcs/github/pr-edit.md`](../../_shared/vcs/github/pr-edit.md).
29
31
  - E2E now failing, current title has no `[E2E FAILING]` → add the prefix.
30
32
  - Validation now ran (was unit-only/skip, now has E2E report) → strip `[UNVERIFIED]` or `[UNIT-ONLY]`.
31
33
  - Otherwise → no title change.
32
34
 
33
- 4. **Refresh body when validation outcome changed** — only when the `## Validation` section's content differs from what's in the body. Use the `--body-file` form in [`../../_shared/github-cli-recipes/pr-edit.md`](../../_shared/github-cli-recipes/pr-edit.md). Preserve `## Goal` and `## Acceptance Criteria` verbatim.
35
+ 4. **Refresh body when validation outcome changed** — only when the `## Validation` section's content differs from what's in the body. Use the `--body-file` form in [`../../_shared/vcs/github/pr-edit.md`](../../_shared/vcs/github/pr-edit.md). Preserve `## Goal` and `## Acceptance Criteria` verbatim.
34
36
 
35
- 5. **Visual walkthrough comment** — if an E2E report exists, invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode A. Always a fresh comment per cycle; do not edit prior walkthrough comments.
37
+ 5. **Visual walkthrough comment** — only when an E2E report exists. Fire [`postPRVisualWalkthrough`](../../muggle-preferences/preference-gates/postPRVisualWalkthrough.md) (PR number from `prs.json`); on skip, record `skipped (gate)` and continue. Otherwise invoke [`../../muggle-pr-visual-walkthrough/SKILL.md`](../../muggle-pr-visual-walkthrough/SKILL.md) Mode A — a fresh comment per cycle; do not edit prior walkthrough comments.
36
38
 
37
- 6. **Overflow comment** — same rule as forward mode: post when the walkthrough skill returns non-null `comment`, via [`../../_shared/github-cli-recipes/top-level-comment.md`](../../_shared/github-cli-recipes/top-level-comment.md).
39
+ 6. **Overflow comment** — same rule as forward mode: post when the walkthrough skill returns non-null `comment`, via [`../../_shared/vcs/github/top-level-comment.md`](../../_shared/vcs/github/top-level-comment.md).
38
40
 
39
41
  ## Handoff
40
42
 
@@ -50,5 +52,5 @@ Return control to `/muggle-do`'s address-reviews orchestrator. The orchestrator
50
52
  **PR updated:** URL (new SHA: `<short-sha>`)
51
53
  **Title refreshed:** yes | no
52
54
  **Body refreshed:** yes | no
53
- **Walkthrough comment:** posted | skipped (no report)
55
+ **Walkthrough comment:** posted | skipped (no report) | skipped (gate)
54
56
  **Overflow comment:** posted | skipped