@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
@@ -2,7 +2,7 @@
2
2
 
3
3
  The procedure for the **bootstrap mode** of `muggle-pr-followup` — invoked when a user dispatches the skill with a GitHub PR URL. Routing into this mode is documented in [`SKILL.md`](SKILL.md#routing).
4
4
 
5
- Bootstrap asks **one** questionnaire — the E2E validation context the loop will reuse — then runs through to the first watcher dispatch. The user is present at launch, so this is the only place to gather it; every later tick reads it from `state.md`. Without it, a URL-bootstrapped watcher has no `localUrl`/`projectId` and Stage 6 hard-halts instead of running E2E.
5
+ Bootstrap seeds watcher state and dispatches the first tick. The watcher itself is generic: it follows one PR's reviews, CI, and merge state whether or not E2E applies. Validation context is **optional** and gathered here only because this is the one moment the user is present — so later unattended ticks can run E2E without prompting. When the PR has a testable surface, bootstrap resolves that context once and every later tick reads it from `state.md`. When there's no testable surface, or the user declines, bootstrap seeds the watcher **poll-only** (no validation context), exactly like [`auto-track`](auto-track.md) — a watcher with no context yields a clean `SKIPPED` E2E verdict when `/muggle-do` runs, not a failure.
6
6
 
7
7
  ## Turn preamble
8
8
 
@@ -27,13 +27,13 @@ Extract `<owner>`, `<repo>`, `<pr-number>`. On malformed input, exit with the ma
27
27
 
28
28
  ### Step 2 — Fetch PR metadata
29
29
 
30
- Per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md).
30
+ Per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md).
31
31
 
32
32
  If `state` is `MERGED` or `CLOSED`, exit with the terminal-PR abort. If the `gh` call fails (not found, auth missing), surface the underlying error verbatim and exit.
33
33
 
34
34
  ### Step 3 — Verify the working tree
35
35
 
36
- Per [`../_shared/github-cli-recipes/verify-working-tree.md`](../_shared/github-cli-recipes/verify-working-tree.md). On any check failure, exit with the wrong-working-tree abort.
36
+ Per [`../_shared/vcs/github/verify-working-tree.md`](../_shared/vcs/github/verify-working-tree.md). On any check failure, exit with the wrong-working-tree abort.
37
37
 
38
38
  ### Step 4 — Resolve the slug
39
39
 
@@ -53,17 +53,19 @@ If `~/.muggle-ai/muggle-do/sessions/<slug>/` exists (including a slot just migra
53
53
  Line-comment threads need no seeding — the watcher derives them from live thread state on every tick, so existing unresolved threads are picked up on the first tick regardless of this step. This step only sets `lastBodyReviewId`, the narrow watermark for body-only reviews (a submitted review with no line comments).
54
54
 
55
55
  - **Default (no `--forward-only`):** `lastBodyReviewId = 0`. The watcher picks up every existing body-only review on its first tick. Matches the common case — the user opened the PR, left feedback they want addressed, and is now bootstrapping.
56
- - **With `--forward-only`:** fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md), then take `max(id)`. Body-only reviews at or below that id are treated as already-handled. This no longer hides existing line-comment threads — those are always picked up from thread state.
56
+ - **With `--forward-only`:** fetch reviews per [`../_shared/vcs/github/submitted-reviews.md`](../_shared/vcs/github/submitted-reviews.md), then take `max(id)`. Body-only reviews at or below that id are treated as already-handled. This no longer hides existing line-comment threads — those are always picked up from thread state.
57
57
 
58
- ### Step 6.5 — Resolve E2E validation context
58
+ ### Step 6.5 — Resolve the validation context (skip when there's nothing to E2E)
59
59
 
60
- The only step that may prompt the user. Run the gather in [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md): reuse an existing context if one is found (gated by `autoReuseValidationContext`), else silent detection + one `AskUserQuestion` (strategy, local URL, backend, project, credentials, re-auth). Record Step 3's verified working tree as `Working tree`.
60
+ The only step that may prompt the user, and only when the PR has a testable surface. Run silent detection from [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md) first. If it finds **no dev server / no testable surface** — a docs, config, skill, or library change, or a non-web repo — seed the watcher **poll-only**: no prompt, no `## Pre-flight answers` block, continue to Step 7 (same as [`auto-track`](auto-track.md)).
61
61
 
62
- Capture the fields for Step 7. Do **not** run E2E now — the first watcher tick that dispatches `/muggle-do` does that.
62
+ Otherwise resolve the context per that file: reuse an existing one (gated by `autoReuseValidationContext`), else one `AskUserQuestion` (strategy, local URL, backend, project, credentials, re-auth) with detected values as defaults; record Step 3's verified working tree as `Working tree`. The user may pick `skip`/`unit-only` or decline the prompt outright — either way, seed poll-only and continue. Never abort the watcher over validation.
63
+
64
+ Capture any resolved fields for Step 7. Do **not** run E2E now — the first watcher tick that dispatches `/muggle-do` does that.
63
65
 
64
66
  ### Step 7 — Seed state files
65
67
 
66
- Identify the loop user once per [`../_shared/github-cli-recipes/loop-user-identity.md`](../_shared/github-cli-recipes/loop-user-identity.md); cache in `state.md`.
68
+ Identify the loop user once per [`../_shared/vcs/github/loop-user-identity.md`](../_shared/vcs/github/loop-user-identity.md); cache in `state.md`.
67
69
 
68
70
  Write under `~/.muggle-ai/muggle-do/sessions/<slug>/`:
69
71
 
@@ -71,7 +73,7 @@ Write under `~/.muggle-ai/muggle-do/sessions/<slug>/`:
71
73
 
72
74
  **`last_seen.json`** — see [`state-schemas.md`](state-schemas.md#last_seenjson). One key (`"<owner>/<repo>#<n>"`), `lastBodyReviewId` from Step 6, `last_pushed_sha: null`, `idle_tick_count: 0`, `cycles_completed: 0`, `escalated_review_ids: []`, `pushed_shas: []`.
73
75
 
74
- **`state.md`** — see [`state-schemas.md`](state-schemas.md#statemd). `Bootstrapped from URL: yes`. Cache the loop-user login. Append the `## Pre-flight answers` block with the fields resolved in Step 6.5, per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md#persisted-fields).
76
+ **`state.md`** — see [`state-schemas.md`](state-schemas.md#statemd). `Bootstrapped from URL: yes`. Cache the loop-user login. If Step 6.5 resolved a validation context, append the `## Pre-flight answers` block with its fields, per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md#persisted-fields). If it seeded poll-only, write **no** such block — a missing block is a clean E2E skip.
75
77
 
76
78
  Do **not** write `cycle.json` or `requirements.md` — those files are no longer part of the session slot.
77
79
 
@@ -95,7 +97,7 @@ Emit one event per [`../_shared/telemetry-events/pr-followup-bootstrap.md`](../_
95
97
 
96
98
  ## Invariants
97
99
 
98
- - Step 6.5 is the **only** user prompt. If the user cancels it, abort leaving nothing on disk.
99
- - All state writes happen in Step 7 — earlier aborts (including a cancelled Step 6.5) leave nothing on disk.
100
+ - Step 6.5 is the **only** user prompt, and only when a testable surface exists. Cancelling or declining it is **not** an abort — fall back to a poll-only watcher (no `## Pre-flight answers` block) and continue to Step 7.
101
+ - All state writes happen in Step 7. Only the earlier aborts (malformed URL, terminal PR, wrong working tree, slot conflict) leave nothing on disk.
100
102
  - If Step 7 fails mid-write, surface the OS error and tell the user to `rm -rf <slot>` and re-run; do not dispatch the watcher.
101
103
  - Bootstrap never retries.
@@ -31,7 +31,7 @@ If `prs.json[0].state` on disk is already `merged` or `closed`, this slot was fi
31
31
 
32
32
  ### Step 1 — Refresh PR state
33
33
 
34
- Per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). Update `prs.json[0].head_sha` and `prs.json[0].state` from the response; keep `mergeable` (conflict signal) for Step 5, and run the recipe's `compare` call to capture `behind_by` (out-of-date signal) for Step 5.
34
+ Per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md). Update `prs.json[0].head_sha` and `prs.json[0].state` from the response; keep `mergeable` (conflict signal) for Step 5, and run the recipe's `compare` call to capture `behind_by` (out-of-date signal) for Step 5.
35
35
 
36
36
  ### Step 2 — Termination check
37
37
 
@@ -50,24 +50,28 @@ If `state` is `MERGED` or `CLOSED`:
50
50
 
51
51
  ### Step 3 — Compute the actionable set from live thread state
52
52
 
53
- The watcher's dispatch trigger is **derived from current GitHub state**, not a stored review-id cursor — see the [thread-state baseline design](../../../../muggle-ai-brain/architecture/2026-06-06-pr-followup-thread-state-baseline-design.md). Two sources, unioned:
53
+ The watcher's dispatch trigger is **derived from current provider state**, not a stored review-id cursor — see the [thread-state baseline design](../../../../muggle-ai-brain/architecture/2026-06-06-pr-followup-thread-state-baseline-design.md). Resolve the provider per [`../_shared/vcs/detect-vcs.md`](../_shared/vcs/detect-vcs.md), then:
54
54
 
55
- **(a) Actionable threads.** Fetch unresolved review threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). A thread is **actionable** when `isResolved == false` **and** `isOutdated == false` **and** its newest comment lacks the loop marker `<!-- muggle-do:bot -->` — classify by the marker, never `author.login` (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)). The marker rule makes echo intrinsic: once the loop has replied, the thread's newest comment is the loop's own, so the thread is no longer actionable — no cursor to advance, no self-recursion (see [`../_shared/pr-followup-helpers/echo-skip.md`](../_shared/pr-followup-helpers/echo-skip.md)).
55
+ - **`github`** — two sources, unioned:
56
56
 
57
- **(b) Actionable body-only reviews.** A body-only review — a submitted `CHANGES_REQUESTED`/`COMMENTED` review with no line comments — has no thread to derive state from, so it keeps a narrow watermark. Fetch submitted reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md); a body-only review is actionable when `id > last_seen.lastBodyReviewId` **and** `id ∉ last_seen.escalated_review_ids`.
57
+ **(a) Actionable threads.** Fetch unresolved review threads per [`../_shared/vcs/github/unresolved-threads.md`](../_shared/vcs/github/unresolved-threads.md). A thread is **actionable** when `isResolved == false` **and** `isOutdated == false` **and** its newest comment lacks the loop marker `<!-- muggle-do:bot -->` — classify by the marker, never `author.login` (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)). The marker rule makes echo intrinsic: once the loop has replied, the thread's newest comment is the loop's own, so the thread is no longer actionable — no cursor to advance, no self-recursion (see [`../_shared/pr-followup-helpers/echo-skip.md`](../_shared/pr-followup-helpers/echo-skip.md)).
58
58
 
59
- Collect the **owning review ids** for dispatch: for each actionable thread, the owning review of its newest comment (`pullRequestReview.databaseId` from the query); plus every actionable body-only review id. The dedup'd union is the dispatch list.
59
+ **(b) Actionable body-only reviews — GitHub only.** A body-only review — a submitted `CHANGES_REQUESTED`/`COMMENTED` review with no line comments — has no thread to derive state from, so it keeps a narrow watermark. GitLab has no review envelope (feedback is always a discussion note), so this sub-branch is GitHub-only and has no GitLab analogue. Fetch submitted reviews per [`../_shared/vcs/github/submitted-reviews.md`](../_shared/vcs/github/submitted-reviews.md); a body-only review is actionable when `id > last_seen.lastBodyReviewId` **and** `id ∉ last_seen.escalated_review_ids`.
60
+
61
+ Collect the **owning review ids** for dispatch: for each actionable thread, the owning review of its newest comment (`pullRequestReview.databaseId` from the query); plus every actionable body-only review id. The dedup'd union is the dispatch list.
62
+
63
+ - **`gitlab`** — single source. 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 the raw notes if a thread's classification needs them). A discussion is **actionable** when it is unresolved **and** its newest note lacks the loop marker `<!-- muggle-do:bot -->` — same marker classification, never `author.username`. There is no body-only watermark: discussion state is the sole authority. The dispatch list is the **discussion ids** of the actionable discussions.
60
64
 
61
65
  ### Step 4 — If the actionable set is non-empty → dispatch (reviews preempt CI)
62
66
 
63
- The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher hands over the owning review ids and exits — `/muggle-do`'s address-reviews re-derives the unresolved threads itself (its authority), so the watcher only needs to decide *that* there is work, not enumerate it exhaustively.
67
+ The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher hands over the dispatch ids from Step 3 (GitHub: owning review ids; GitLab: discussion ids) and exits — `/muggle-do`'s address-reviews re-derives the unresolved threads itself (its authority), so the watcher only needs to decide *that* there is work, not enumerate it exhaustively.
64
68
 
65
69
  1. Reset `last_seen.idle_tick_count` to 0.
66
70
  2. **Stop this watcher (single-thread).** Cancel its cron so no tick fires while the dev cycle runs: `CronList`, find the job whose command ends with `/muggle:muggle-pr-followup <slug> <n>` (exact two-arg match), `CronDelete` it. `/muggle-do` respawns the watcher when the cycle finishes — exactly one cron ever, and no tick overlaps a running cycle.
67
71
  3. Dispatch `/muggle-do` with an *address-reviews* directive carrying:
68
72
  - PR URL (from `prs.json[0].url`)
69
73
  - Session slug (from the invocation arguments)
70
- - The owning review ids from Step 3, as a space-separated list
74
+ - The dispatch ids from Step 3 (GitHub owning review ids / GitLab discussion ids), as a space-separated list
71
75
 
72
76
  Exact phrasing belongs to `/muggle-do`'s intent-routing. A reasonable shape is:
73
77
  ```
@@ -82,7 +86,7 @@ The watcher does **not** classify. Classification, batching, replying, escalatio
82
86
  A merge-ready branch is **current with its base** — neither conflicting nor behind. From the Step 1 metadata, the branch needs a rebase when either:
83
87
 
84
88
  - `mergeable == CONFLICTING` (corroborated by `mergeStateStatus == DIRTY`) — conflicts with the base, **or**
85
- - `behind_by > 0` — out of date with the base. Read this from the `compare` call (commit ancestry), **never** from `mergeStateStatus == BEHIND`: GitHub masks `BEHIND` behind `DIRTY`/`BLOCKED` and only surfaces it under "require branches up to date" protection, so a stale PR that is also awaiting review or has a red required check reports `BLOCKED` — and its staleness would go unseen. See [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md#behind-by-out-of-date-detection).
89
+ - `behind_by > 0` — out of date with the base. Read this from the `compare` call (commit ancestry), **never** from `mergeStateStatus == BEHIND`: GitHub masks `BEHIND` behind `DIRTY`/`BLOCKED` and only surfaces it under "require branches up to date" protection, so a stale PR that is also awaiting review or has a red required check reports `BLOCKED` — and its staleness would go unseen. See [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md#behind-by-out-of-date-detection). On `gitlab`, the same behind-by comes from the compare in [`../_shared/vcs/gitlab/mr-metadata.md`](../_shared/vcs/gitlab/mr-metadata.md#behind-by-out-of-date-detection) (commit ancestry, not `detailed_merge_status`); conflict is `detailed_merge_status` in `{broken_status, conflict}`.
86
90
 
87
91
  This trigger is **independent of approval and CI state**: an out-of-date branch is rebased whether or not it has been reviewed, approved, or has green checks. The watcher acts on staleness directly — it never waits for an approval to surface it.
88
92
 
@@ -102,7 +106,7 @@ Otherwise — `behind_by == 0` and not conflicting (`mergeable == UNKNOWN` is fi
102
106
 
103
107
  ### Step 6 — No actionable feedback, branch current → poll CI for the head SHA
104
108
 
105
- Fetch the check-run rollup for `prs.json[0].head_sha` per [`../_shared/github-cli-recipes/pr-checks.md`](../_shared/github-cli-recipes/pr-checks.md), then:
109
+ Fetch the CI rollup for `prs.json[0].head_sha`, provider resolved as in Step 3 — `github` → the check-run rollup per [`../_shared/vcs/github/pr-checks.md`](../_shared/vcs/github/pr-checks.md); `gitlab` → the pipeline-job rollup per [`../_shared/vcs/gitlab/mr-pipeline.md`](../_shared/vcs/gitlab/mr-pipeline.md) (failed/running/success jobs fold into the same red/pending/green buckets). Then, on the bucket:
106
110
 
107
111
  - **Any check still pending** (`bucket == "pending"`) → idle (wait for checks to settle).
108
112
  - **All checks green / skipped, or no checks** → idle (green path).
@@ -7,7 +7,7 @@ This step only finalizes — marks the slot terminal, writes the record, unsched
7
7
  ## Inputs
8
8
 
9
9
  - `<slug>`, `<owner>/<repo>`, `<n>` — the slot's PR.
10
- - `state` — `MERGED` or `CLOSED`, from a fresh [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md).
10
+ - `state` — `MERGED` or `CLOSED`, from a fresh [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md).
11
11
  - `mergeCommit` + `mergedAt` when `MERGED`.
12
12
 
13
13
  ## Procedure
@@ -16,7 +16,7 @@ List `~/.muggle-ai/muggle-do/sessions/*/` dirs that contain a `prs.json`. Skip a
16
16
 
17
17
  ### Step 2 — Refresh live state
18
18
 
19
- For each candidate, fetch the PR per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md) using `prs.json[0].url`. A `gh` failure on one slot (deleted repo, missing auth) → log it to that slot's `followup.log` and skip; never abort the whole sweep.
19
+ For each candidate, fetch the PR per [`../_shared/vcs/github/pr-metadata.md`](../_shared/vcs/github/pr-metadata.md) using `prs.json[0].url`. A `gh` failure on one slot (deleted repo, missing auth) → log it to that slot's `followup.log` and skip; never abort the whole sweep.
20
20
 
21
21
  ### Step 3 — Finalize the terminal ones
22
22
 
@@ -16,6 +16,7 @@ A list of one entry. (Historical: the file is an array for forward-compat with t
16
16
  [
17
17
  {
18
18
  "repo": "<owner>/<repo>",
19
+ "provider": "github" | "gitlab",
19
20
  "number": <int>,
20
21
  "url": "https://github.com/<owner>/<repo>/pull/<number>",
21
22
  "head_sha": "<40-char-hex-sha>",
@@ -24,6 +25,8 @@ A list of one entry. (Historical: the file is an array for forward-compat with t
24
25
  ]
25
26
  ```
26
27
 
28
+ - `provider` selects the recipe set (`gh` vs `glab`). Absent ⇒ `github` — existing slots predate the field and stay GitHub.
29
+ - Under GitLab, `number` holds the MR `iid` (per-project, not the global MR id); `head_sha` is unchanged. GitLab's `opened` normalizes to `open`; `merged` and `closed` already align.
27
30
  - `state` is the **observed** state from the last `gh pr view`. The watcher refreshes it each tick.
28
31
  - Terminal states (`merged`, `closed`) are sticky — once set, the watcher writes `result.md` and exits without rescheduling.
29
32
 
@@ -48,7 +51,7 @@ Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
48
51
  }
49
52
  ```
50
53
 
51
- - `lastBodyReviewId`: narrow watermark for **body-only** reviews (a submitted review carrying no line comments). The watcher dispatches a body-only review only when `id > lastBodyReviewId`. Line-comment threads do **not** use it — they are dispatched from live thread state (unresolved + not outdated + newest comment unmarked by the loop), so there is no cursor that can pin past them. Bootstrap sets it to the highest existing submitted review id with `--forward-only`, else `0`.
54
+ - `lastBodyReviewId`: narrow watermark for **body-only** reviews (a submitted review carrying no line comments). The watcher dispatches a body-only review only when `id > lastBodyReviewId`. Line-comment threads do **not** use it — they are dispatched from live thread state (unresolved + not outdated + newest comment unmarked by the loop), so there is no cursor that can pin past them. Bootstrap sets it to the highest existing submitted review id with `--forward-only`, else `0`. The cursor keeps its name under GitLab, where it holds the highest note / discussion id.
52
55
  - `last_pushed_sha`: most recent SHA `/muggle-do` pushed in this PR's life; `null` until the first push.
53
56
  - `idle_tick_count`: incremented each tick whose actionable set is empty. Reset to 0 on any tick that dispatches `/muggle-do`. Diagnostic only — does not gate behavior.
54
57
  - `cycles_completed`: incremented each time `/muggle-do` completes an address-reviews invocation (regardless of actionable/ambiguous/mixed).
@@ -10,27 +10,27 @@ Include **all** runs — passed, failed, **and inconclusive**. Never drop a run.
10
10
  | `failed` | Run completed and the assertion failed, or the product itself broke before the assertion could be made (server error, 500, broken page). | `failureStepIndex`, `error` |
11
11
  | `inconclusive` | Run could not yield a pass/fail signal for reasons **outside the product**: no replayable script existed, environment precondition unmet, infra/Electron error, agent stalled on a cookie banner or login wall, missing secrets, agent went off-course. | `reason` (one short sentence; `steps[]` may be empty) |
12
12
 
13
- If a test should be inconclusive but you don't have a real `runId` / `viewUrl` (e.g., generation never ran), use the same project-level dashboard fallback as the "last-resort" failed branch below: emit a stub run via `muggle-remote-local-run-upload` if at all possible; otherwise synthesize a UUID-shaped runId and use `https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs` as `viewUrl`. The schema requires both fields — but a working dashboard link is better than nothing.
13
+ If a test should be inconclusive but you don't have a real `runId` / `viewUrl` (e.g., generation never ran), use the same project-level dashboard fallback as the "last-resort" failed branch below: synthesize a UUID-shaped runId and use `https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs` as `viewUrl`. The schema requires both fields — but a working dashboard link is better than nothing.
14
14
 
15
- ## Uploaded run (passed or failed; the common path)
15
+ ## Published run (passed or failed; the common path)
16
16
 
17
- Every run that completed locally — pass or fail, with or without action steps — should reach the cloud via Step 8 of the caller skill. The upload response carries `actionScriptId` and `viewUrl`; `testScriptId` is **only present for passing runs and replays** (failed generations skip the test script wrapper to avoid clobbering the canonical replay target).
17
+ The studio publishes every completed local run — pass or fail, with or without action steps — to the cloud during execution. `muggle-local-run-result-get` carries `viewUrl` and `cloudActionScriptId`; `cloudTestScriptId` is **only present for passing runs and replays** (failed generations skip the test script wrapper to avoid clobbering the canonical replay target).
18
18
 
19
- Fetch step screenshots in parallel — pick the right tool per upload:
19
+ Fetch step screenshots in parallel — pick the right tool per run result:
20
20
 
21
- - **`testScriptId` present** → `muggle-remote-test-script-get` with that id.
22
- - **`testScriptId` missing** (failed generation, or zero-step fallback via `muggle-remote-local-run-upload`) → `muggle-remote-action-script-get` with `actionScriptId` from the upload response. Same `steps[]` + `summaryStep` shape; just one less hop.
21
+ - **`cloudTestScriptId` present** → `muggle-remote-test-script-get` with that id.
22
+ - **`cloudTestScriptId` missing** (failed generation) → `muggle-remote-action-script-get` with `cloudActionScriptId`. Same `steps[]` + `summaryStep` shape; just one less hop.
23
23
 
24
24
  For each result:
25
25
 
26
26
  1. Build `steps[]`: `[{ stepIndex: <index>, action: steps[i].operation.action, screenshotUrl: steps[i].operation.screenshotUrl }, ...]`. Empty array is fine — zero-step runs still render the failure summary header in the walkthrough.
27
- 2. `viewUrl` — from upload response (deep-links to the specific run via `actionScriptId`).
27
+ 2. `viewUrl` — from the run result (deep-links to the specific run).
28
28
  3. `status` — from `muggle-local-run-result-get`.
29
29
  4. If failed: also capture `failureStepIndex`, `error`, `artifactsDir` from the run result.
30
30
 
31
- ## Last-resort: upload genuinely failed
31
+ ## Last-resort: no cloud refs on the run result
32
32
 
33
- Reach this branch only when **both** `muggle-local-publish-test-script` AND the `muggle-remote-local-run-upload` fallback errored (network failure, auth issue, etc.) — not for ordinary zero-step runs, which the fallback handles. Don't drop the run; render a stub entry so reviewers still see something happened.
33
+ Reach this branch only when the run result carries no `viewUrl` / `cloudActionScriptId` (the studio publish never landed — older Electron build, network failure, auth issue). Don't drop the run; render a stub entry so reviewers still see something happened.
34
34
 
35
35
  1. `steps: []`.
36
36
  2. `viewUrl`: `https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs` (generic dashboard).
@@ -52,9 +52,9 @@ If called from `muggle-do`: `e2e-acceptance.md` already produces this shape —
52
52
  "description": "<one-line description (recommended)>",
53
53
  "useCaseName": "<parent use case title (recommended)>",
54
54
  "testCaseId": "<testCaseId from execution step>",
55
- "testScriptId": "<testScriptId from publish>",
55
+ "testScriptId": "<cloudTestScriptId from run result>",
56
56
  "runId": "<runId from muggle-local-execute-test-generation>",
57
- "viewUrl": "<viewUrl from publish>",
57
+ "viewUrl": "<viewUrl from run result>",
58
58
  "status": "passed",
59
59
  "steps": [{ "stepIndex": 0, "action": "...", "screenshotUrl": "..." }]
60
60
  },
@@ -64,7 +64,7 @@ If called from `muggle-do`: `e2e-acceptance.md` already produces this shape —
64
64
  "useCaseName": "<parent use case title (recommended)>",
65
65
  "testCaseId": "<testCaseId from execution step>",
66
66
  "runId": "<runId from muggle-local-execute-test-generation>",
67
- "viewUrl": "<viewUrl from upload response, deep-linked via actionScriptId>",
67
+ "viewUrl": "<viewUrl from run result>",
68
68
  "status": "failed",
69
69
  "steps": [],
70
70
  "failureStepIndex": 0,
@@ -76,7 +76,7 @@ If called from `muggle-do`: `e2e-acceptance.md` already produces this shape —
76
76
  "useCaseName": "<parent use case title (recommended)>",
77
77
  "testCaseId": "<testCaseId from execution step>",
78
78
  "runId": "<runId from execute, or synthesized UUID if no run started>",
79
- "viewUrl": "<viewUrl from upload response, or project-level fallback URL>",
79
+ "viewUrl": "<viewUrl from run result, or project-level fallback URL>",
80
80
  "status": "inconclusive",
81
81
  "steps": [],
82
82
  "reason": "<one short sentence: why neither pass nor fail applies>"
@@ -27,7 +27,7 @@ For each option: label = key name, description = first paragraph of `preference-
27
27
 
28
28
  - `multiSelect: true`, `header: "Auth & session"` — `autoLogin`, `autoSelectProject`, `checkForUpdates`, `verboseOutput`
29
29
  - `multiSelect: true`, `header: "Test setup"` — `autoSelectLocalHost`, `autoDetectChanges`, `autoReuseValidationContext`
30
- - `multiSelect: true`, `header: "Test run"` — `showElectronBrowser`, `openTestResultsAfterRun`, `autoPublishLocalResults`
30
+ - `multiSelect: true`, `header: "Test run"` — `showElectronBrowser`, `openTestResultsAfterRun`
31
31
  - `multiSelect: true`, `header: "Suggestions"` — `suggestRelatedUseCases`, `suggestRelatedTestCases`
32
32
  - `multiSelect: true`, `header: "PR"` — `postPRVisualWalkthrough`, `autoCreatePR`, `autoWatchPR`
33
33
  - `multiSelect: true`, `header: "Branch hygiene"` — `autoUseWorktree`, `autoRebase`, `autoCleanup`
@@ -43,7 +43,7 @@ For each toggled key (multi-select questions): `muggle-local-preferences-set` wi
43
43
 
44
44
  ## Step 5 — `never` follow-up
45
45
 
46
- Ask: `Want any of these set to "never" (auto-skip without asking)? Name them, e.g. "never on autoPublishLocalResults", or say "no".`. For named keys, call `muggle-local-preferences-set` with `value: "never"`, same scope.
46
+ Ask: `Want any of these set to "never" (auto-skip without asking)? Name them, e.g. "never on showElectronBrowser", or say "no".`. For named keys, call `muggle-local-preferences-set` with `value: "never"`, same scope.
47
47
 
48
48
  ## Step 6 — confirm
49
49
 
@@ -7,7 +7,7 @@ description: "Change-driven E2E acceptance testing with Muggle AI: detect the us
7
7
 
8
8
  > Telemetry first step: see [`_shared/telemetry-emit.md`](../_shared/telemetry-emit.md). Use `skillName: "muggle-test"`.
9
9
 
10
- A router skill that detects code changes, resolves impacted test cases, executes them locally or remotely, publishes results to the Muggle AI dashboard, and posts E2E acceptance summaries to the PR. The user can invoke this at any moment, in any state.
10
+ A router skill that detects code changes, resolves impacted test cases, executes them locally or remotely, reads the cloud results from the Muggle AI dashboard (local runs are published by the studio during execution; remote runs publish cloud-side), and posts E2E acceptance summaries to the PR. The user can invoke this at any moment, in any state.
11
11
 
12
12
  ## UX Guidelines — Minimize Typing
13
13
 
@@ -42,11 +42,10 @@ Gates run per `preference-gates/README.md`.
42
42
  |------------|------|-------------------|
43
43
  | `autoLogin` | 3 | Reuse saved credentials when auth is required |
44
44
  | `autoSelectProject` | 4 | Reuse last-used Muggle Test project for this repo |
45
- | `autoSelectLocalHost` | 7A | Reuse last-used local dev server URL for this repo |
45
+ | `autoSelectLocalHost` | execute-local | Reuse last-used local dev server URL for this repo |
46
46
  | `autoDetectChanges` | 2 | Scan local git changes and map to affected test cases |
47
47
  | `defaultExecutionMode` | 1 | Default to local or remote test execution |
48
- | `autoPublishLocalResults` | 7A | Upload local results to Muggle Test cloud after run |
49
- | `showElectronBrowser` | 7A | Show the Electron browser window during local test execution (vs. run headless) |
48
+ | `showElectronBrowser` | execute-local | Show the Electron browser window during local test execution (vs. run headless) |
50
49
  | `postPRVisualWalkthrough` | 9 | Post visual walkthrough to PR after results are available |
51
50
  | `autoCreatePR` | 9 (if no PR) | Auto-create the PR when posting the walkthrough has no PR to target |
52
51
  | `autoWatchPR` | 9.5 (if a PR exists) | Start a `muggle-pr-followup` watcher on the PR after the run |
@@ -235,139 +234,37 @@ Then show the per-case decision in one `AskUserQuestion`:
235
234
 
236
235
  If the user picks "Override one or more", let them flip the mode for any test case via a second multi-select `AskUserQuestion`. Emit a follow-up `pre-execution-classification` event with `userAction` set whenever the user overrides.
237
236
 
238
- ## Step 7A: Execute — Local Mode
237
+ ## Step 7: Execute
239
238
 
240
- ### Local environment readiness
239
+ ### Fetch test case details (both modes)
241
240
 
242
- Before anything else, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the readiness/service-start owner (idempotent; halt on what it surfaces). The URL gate below only *selects* the target; prepare is what guarantees something is listening and compiled.
241
+ Hydrate every selected test case **once**, before dispatch: issue **all** `muggle-remote-test-case-get` calls in parallel (single message, multiple tool calls). Both paths consume the result — never re-fetch inside a path.
243
242
 
244
- ### Pre-flight question — Local URL (gated by `autoSelectLocalHost`)
243
+ ### Dispatch by mode
245
244
 
246
- Skill responsibilities (the rest is in `preference-gates/autoSelectLocalHost.md`):
247
- - **Read the cache**: `Muggle Test Last Host: <url>` session-context line, or `muggle-local-last-host-get`. Pass as `{lastHost}` substitution.
248
- - **Auto-detect a suggested URL**: `lsof -iTCP -sTCP:LISTEN -nP | grep -E ':(3000|3001|4200|5173|8080)'`. Pass as `{suggestedHost}`.
249
- - **Save the cache**: call `muggle-local-last-host-set` after the user picks (the gate file requires this on every pick).
245
+ Run the path matching the mode confirmed in Step 1. Each path owns its own process and returns a **uniform runs list** — `[{ testCaseId, mode, runId | runtimeId, status, viewUrl? }]` — that Steps 7C–10 consume mode-agnostically.
250
246
 
251
- Gate `autoSelectLocalHost` per `preference-gates/README.md` + `preference-gates/autoSelectLocalHost.md`.
252
-
253
- ### Pre-flight visibility (gated by `showElectronBrowser`)
254
-
255
- Gate `showElectronBrowser` (per `preference-gates/README.md`). Resolve once; apply same `showUi` to every test case.
256
- - `always` → omit `showUi` (defaults visible).
257
- - `never` → pass `showUi: false`.
258
- - `ask` → run Picker 1 from `preference-gates/showElectronBrowser.md` via `AskUserQuestion`; map the answer back to one of the actions above.
259
-
260
- ### Fetch test case details (in parallel)
261
-
262
- Before execution, fetch full test case details for all selected test cases by issuing **all** `muggle-remote-test-case-get` calls in parallel (single message, multiple tool calls).
263
-
264
- ### Run the dev loop
265
-
266
- Execute each selected test case via the shared loop in [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md): [sequential replay/regen](../_shared/dev-loop/run.md), [`actionScript` as-is](../_shared/dev-loop/action-script.md), [`freshSession`](../_shared/dev-loop/fresh-session.md), and [`timeoutMs`](../_shared/dev-loop/timeouts.md).
267
-
268
- Caller glue:
269
- - `mode` per test case comes from Step 6f; `localUrl` from the pre-flight question; `showUi` from the `showElectronBrowser` resolution.
270
- - `cwd` = the PR-branch worktree from Step 2 if one was created, else the user's repo root — it drives the cross-worktree single-flight lock so concurrent muggle-test runs from different branches serialize.
271
- - On a failed run, continue the batch and route it through Step 7C after completion.
272
-
273
- ### Collect results
274
-
275
- Fetch every `runId` per [`../_shared/dev-loop/failures.md`](../_shared/dev-loop/failures.md), reading structured fields and [interpreting failures](../_shared/dev-loop/failures.md) — never `execute`'s stdout tail. Issue the `muggle-local-run-result-get` calls in parallel; use `Error` as the headline for failures and route through Step 7C.
276
-
277
- ### Publish each run to cloud (gated by `autoPublishLocalResults`)
278
-
279
- Gate `autoPublishLocalResults` (per `preference-gates/README.md`):
280
- - `always` → proceed to publish logic below.
281
- - `never` → skip to report summary; tell user Steps 8/9 and per-step screenshots are unavailable without publishing.
282
- - `ask` → run Picker 1 from `preference-gates/autoPublishLocalResults.md` via `AskUserQuestion`; map the answer back to one of the actions above.
283
-
284
- ### Publish logic (when publishing is enabled)
285
-
286
- Publish every completed run per [`../_shared/dev-loop/publish.md`](../_shared/dev-loop/publish.md) — parallel `muggle-local-publish-test-script` with the zero-step `muggle-remote-local-run-upload` fallback. Store every `viewUrl`, `testScriptId`, `actionScriptId` — used in the next steps.
287
-
288
- ### Report summary
289
-
290
- ```
291
- Test Case Status Duration Steps View Steps on Muggle AI
292
- ─────────────────────────────────────────────────────────────────────────
293
- Login with valid creds PASSED 12.3s 8 https://www.muggle-ai.com/...
294
- Login with invalid creds PASSED 9.1s 6 https://www.muggle-ai.com/...
295
- Checkout flow FAILED 15.7s 12 https://www.muggle-ai.com/...
296
- ─────────────────────────────────────────────────────────────────────────
297
- Total: 3 tests | 2 passed | 1 failed | 37.1s
298
- ```
299
-
300
- For failures, don't hand-write a verdict in the summary — route each through the debug path (Step 7C).
301
-
302
- ## Step 7B: Execute — Remote Mode
303
-
304
- ### Ask for target URL
305
-
306
- > "What's the preview/staging URL to test against?"
307
-
308
- ### Fetch test case details (in parallel)
309
-
310
- Issue all `muggle-remote-test-case-get` calls in parallel (single message, multiple tool calls) to hydrate the test case bodies.
311
-
312
- ### Trigger remote workflows (in parallel)
313
-
314
- Branch each test case on the mode chosen in Step 6f, then issue **all** workflow-start calls in parallel — never loop them sequentially. Mix regen and replay starts in the same parallel batch.
315
-
316
- **Regen-mode test case** — `muggle-remote-workflow-start-test-script-generation`:
317
-
318
- - `projectId`: The project ID
319
- - `useCaseId`: The use case ID
320
- - `testCaseId`: The test case ID
321
- - `name`: `"muggle-test: {test case title}"`
322
- - `url`: The preview/staging URL
323
- - `goal`: From the test case
324
- - `precondition`: From the test case (use `"None"` if empty)
325
- - `instructions`: From the test case
326
- - `expectedResult`: From the test case
327
-
328
- **Replay-mode test case** — `muggle-remote-workflow-start-test-script-replay` against the latest replayable script for that test case (resolve via `muggle-remote-test-script-list` if not already in hand from Step 6f). Tag results with `mode: "replay"` so Step 7C can route failures correctly.
329
-
330
- Store each returned workflow runtime ID along with its mode tag.
331
-
332
- ### Monitor and report (in parallel)
333
-
334
- Issue all `muggle-remote-wf-get-ts-gen-latest-run` calls in parallel, one per runtime ID.
335
-
336
- ```
337
- Test Case Workflow Status Runtime ID
338
- ────────────────────────────────────────────────────────
339
- Login with valid creds RUNNING rt-abc123
340
- Login with invalid creds COMPLETED rt-def456
341
- Checkout flow QUEUED rt-ghi789
342
- ```
247
+ - **Mode A (Local)** → [`execute-local.md`](execute-local.md). Inputs: the hydrated test cases, per-case `mode` from Step 6f, and `cwd` (the PR-branch worktree from Step 2 if one exists, else the repo root).
248
+ - **Mode B (Remote)** → [`execute-remote.md`](execute-remote.md). Inputs: the hydrated test cases, per-case `mode` from Step 6f, `projectId` / `useCaseId`.
343
249
 
344
250
  ## Step 7C: Route every failed run through the debug path
345
251
 
346
- For every run with `status: "failed"` (or any non-passing terminal state) from 7A or 7B, route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md). This is **mandatory** — a failure is never reported without it. The debug path gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always an option and "skip" is never the default.
252
+ For every run with `status: "failed"` (or any non-passing terminal state) returned by Step 7 (either path), route through [`_shared/debug-failed-run.md`](../_shared/debug-failed-run.md). This is **mandatory** — a failure is never reported without it. The debug path gathers evidence (attempted steps + reasoning + screenshot), diagnoses via [`_shared/failure-mode-handling.md`](../_shared/failure-mode-handling.md) (§B replay / §C regen), shows the debug card, and presents the guaranteed selection in which **"give feedback & rerun"** is always an option and "skip" is never the default.
347
253
 
348
- Pass it per failed run: the `runId` (local) or workflow runtime id (remote), the `mode` that failed, `testCaseId`, `projectId`, and the execution handle (local: the dev loop from "Run the dev loop"; remote: 7B's workflow-start) so a rerun re-enters the same path. Process failures one at a time so the user isn't drowning in pickers.
254
+ Pass it per failed run: the `runId` (local) or workflow runtime id (remote), the `mode` that failed, `testCaseId`, `projectId`, and the execution handle (local: [`execute-local.md`](execute-local.md); remote: [`execute-remote.md`](execute-remote.md)) so a rerun re-enters the same path. Process failures one at a time so the user isn't drowning in pickers.
349
255
 
350
256
  ## Step 8: Open Results in Browser
351
257
 
352
- After execution and publishing are complete, open the Muggle AI dashboard so the user can visually inspect results and screenshots.
353
-
354
- ### Mode A (Local) — open each published viewUrl
355
-
356
- For each published run's `viewUrl`:
357
- ```bash
358
- open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/scripts?modal=script-details&testCaseId={testCaseId}"
359
- ```
360
-
361
- If there are many runs (>3), open just the project-level runs page instead of individual tabs:
362
- ```bash
363
- open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs"
364
- ```
365
-
366
- ### Mode B (Remote) — open the project runs page
258
+ After execution, open the Muggle AI dashboard so the user can inspect results and screenshots. The studio published every local run during execution, so each run result already carries its `viewUrl` (read it from `muggle-local-run-result-get`). Key off the uniform runs list:
367
259
 
368
- ```bash
369
- open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs"
370
- ```
260
+ - **Runs carry a `viewUrl` and there are ≤3** — open each:
261
+ ```bash
262
+ open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/scripts?modal=script-details&testCaseId={testCaseId}"
263
+ ```
264
+ - **Otherwise** (more than 3 runs, or a run with no `viewUrl`) — open the project runs page:
265
+ ```bash
266
+ open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/runs"
267
+ ```
371
268
 
372
269
  Tell the user:
373
270
  > "I've opened the Muggle AI dashboard in your browser — you can see the test results, step-by-step screenshots, and action scripts there."
@@ -379,7 +276,7 @@ After reporting results:
379
276
  1. Fire [`postPRVisualWalkthrough`](../muggle-preferences/preference-gates/postPRVisualWalkthrough.md). On skip → Step 9.5.
380
277
  2. `gh pr view --json number,title,url 2>/dev/null` — find the PR.
381
278
  3. If no PR: fire [`autoCreatePR`](../muggle-preferences/preference-gates/autoCreatePR.md). On skip → Step 9.5.
382
- 4. Assemble the `E2eReport` — see [`../muggle-pr-visual-walkthrough/e2e-report-assembly.md`](../muggle-pr-visual-walkthrough/e2e-report-assembly.md). Include all runs from Step 7A (passed and failed).
279
+ 4. Assemble the `E2eReport` — see [`../muggle-pr-visual-walkthrough/e2e-report-assembly.md`](../muggle-pr-visual-walkthrough/e2e-report-assembly.md). Include all runs from Step 7 (passed and failed).
383
280
  5. Invoke [`../muggle-pr-visual-walkthrough/SKILL.md`](../muggle-pr-visual-walkthrough/SKILL.md) Mode A with the `E2eReport`.
384
281
 
385
282
  ## Step 9.5: Offer to watch the PR for review follow-ups
@@ -414,6 +311,6 @@ Each rule below is covered in-step above; these are the ones this skill most oft
414
311
  - **PR URLs run in a dedicated worktree** — never switch the user's main checkout; pass that worktree as `cwd`.
415
312
  - **Every selection uses `AskUserQuestion`** — never ask the user to type a number; the user picks the project (never auto-select).
416
313
  - **Parallelize independent cloud jobs**; the only sequential loop is local Electron execution (one browser).
417
- - **Publish before opening the browser**, and delegate PR posting to `muggle-pr-visual-walkthrough` — never inline the walkthrough or call `gh pr comment` here.
314
+ - **Read cloud refs off the run result** (the studio published during execution) and delegate PR posting to `muggle-pr-visual-walkthrough` — never inline the walkthrough or call `gh pr comment` here.
418
315
 
419
316
  Phase→tool map and multi-agent (acceptance-tester) dispatch: [`reference.md`](reference.md).
@@ -0,0 +1,60 @@
1
+ # Muggle Test — Local Execution Path (Mode A)
2
+
3
+ > The Local execution process for the `muggle-test` router: run the selected test cases against localhost via the Electron browser and collect results. The studio publishes each run to the cloud during execution, so the run result already carries the cloud refs. Returns a uniform runs list to the router. Per-test-case mechanics are shared with [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md).
4
+
5
+ ## Inputs (the router passes these in)
6
+
7
+ - The hydrated test cases (fetched once by the router before dispatch).
8
+ - Per-case `mode` — `replay` or `regen`, chosen during replay-vs-regen classification.
9
+ - `cwd` — the PR-branch worktree if one exists, else the repo root.
10
+
11
+ ## Local environment readiness
12
+
13
+ Before anything else, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the readiness/service-start owner (idempotent; halt on what it surfaces). The URL gate below only *selects* the target; prepare is what guarantees something is listening and compiled.
14
+
15
+ ## Pre-flight question — Local URL (gated by `autoSelectLocalHost`)
16
+
17
+ Skill responsibilities (the rest is in `preference-gates/autoSelectLocalHost.md`):
18
+ - **Read the cache**: `Muggle Test Last Host: <url>` session-context line, or `muggle-local-last-host-get`. Pass as `{lastHost}` substitution.
19
+ - **Auto-detect a suggested URL**: `lsof -iTCP -sTCP:LISTEN -nP | grep -E ':(3000|3001|4200|5173|8080)'`. Pass as `{suggestedHost}`.
20
+ - **Save the cache**: call `muggle-local-last-host-set` after the user picks (the gate file requires this on every pick).
21
+
22
+ Gate `autoSelectLocalHost` per `preference-gates/README.md` + `preference-gates/autoSelectLocalHost.md`.
23
+
24
+ ## Pre-flight visibility (gated by `showElectronBrowser`)
25
+
26
+ Gate `showElectronBrowser` (per `preference-gates/README.md`). Resolve once; apply same `showUi` to every test case.
27
+ - `always` → omit `showUi` (defaults visible).
28
+ - `never` → pass `showUi: false`.
29
+ - `ask` → run Picker 1 from `preference-gates/showElectronBrowser.md` via `AskUserQuestion`; map the answer back to one of the actions above.
30
+
31
+ ## Run the dev loop
32
+
33
+ Execute each test case via the shared loop in [`../_shared/dev-loop/run.md`](../_shared/dev-loop/run.md): [sequential replay/regen](../_shared/dev-loop/run.md), [`actionScript` as-is](../_shared/dev-loop/action-script.md), [`freshSession`](../_shared/dev-loop/fresh-session.md), and [`timeoutMs`](../_shared/dev-loop/timeouts.md).
34
+
35
+ Caller glue:
36
+ - `mode` per test case is the input from the router; `localUrl` from the pre-flight question; `showUi` from the `showElectronBrowser` resolution.
37
+ - `cwd` is the input from the router — it drives the cross-worktree single-flight lock so concurrent muggle-test runs from different branches serialize.
38
+ - On a failed run, continue the batch; the router routes failures through the debug path after this path returns.
39
+
40
+ ## Collect results
41
+
42
+ Fetch every `runId` per [`../_shared/dev-loop/failures.md`](../_shared/dev-loop/failures.md), reading structured fields and [interpreting failures](../_shared/dev-loop/failures.md) — never `execute`'s stdout tail. Issue the `muggle-local-run-result-get` calls in parallel; use `Error` as the headline for failures. The studio already published each run, so the same call surfaces `viewUrl` / `cloudTestScriptId` / `cloudActionScriptId` — retain them per [`../_shared/dev-loop/publish.md`](../_shared/dev-loop/publish.md) for the dashboard and walkthrough.
43
+
44
+ ## Report summary
45
+
46
+ ```
47
+ Test Case Status Duration Steps View Steps on Muggle AI
48
+ ─────────────────────────────────────────────────────────────────────────
49
+ Login with valid creds PASSED 12.3s 8 https://www.muggle-ai.com/...
50
+ Login with invalid creds PASSED 9.1s 6 https://www.muggle-ai.com/...
51
+ Checkout flow FAILED 15.7s 12 https://www.muggle-ai.com/...
52
+ ─────────────────────────────────────────────────────────────────────────
53
+ Total: 3 tests | 2 passed | 1 failed | 37.1s
54
+ ```
55
+
56
+ For failures, don't hand-write a verdict — the router routes each through the debug path.
57
+
58
+ ## Output
59
+
60
+ Return the uniform runs list the router consumes: `[{ testCaseId, mode, runId, status, viewUrl?, cloudTestScriptId?, cloudActionScriptId? }]`. The studio published each run during execution, so `viewUrl` / `cloudTestScriptId` / `cloudActionScriptId` come straight off the run result (`muggle-local-run-result-get`).
@@ -0,0 +1,49 @@
1
+ # Muggle Test — Remote Execution Path (Mode B)
2
+
3
+ > The Remote execution process for the `muggle-test` router: trigger cloud test-script generation/replay against a preview/staging URL and monitor. Returns a uniform runs list to the router.
4
+
5
+ ## Inputs (the router passes these in)
6
+
7
+ - The hydrated test cases (fetched once by the router before dispatch).
8
+ - Per-case `mode` — `replay` or `regen`, chosen during replay-vs-regen classification.
9
+ - `projectId`, `useCaseId`.
10
+
11
+ ## Ask for target URL
12
+
13
+ > "What's the preview/staging URL to test against?"
14
+
15
+ ## Trigger remote workflows (in parallel)
16
+
17
+ Branch each test case on its `mode`, then issue **all** workflow-start calls in parallel — never loop them sequentially. Mix regen and replay starts in the same parallel batch.
18
+
19
+ **Regen-mode test case** — `muggle-remote-workflow-start-test-script-generation`:
20
+
21
+ - `projectId`: The project ID
22
+ - `useCaseId`: The use case ID
23
+ - `testCaseId`: The test case ID
24
+ - `name`: `"muggle-test: {test case title}"`
25
+ - `url`: The preview/staging URL
26
+ - `goal`: From the test case
27
+ - `precondition`: From the test case (use `"None"` if empty)
28
+ - `instructions`: From the test case
29
+ - `expectedResult`: From the test case
30
+
31
+ **Replay-mode test case** — `muggle-remote-workflow-start-test-script-replay` against the latest replayable script for that test case (resolve via `muggle-remote-test-script-list` if not already in hand from the classification step). Tag results with `mode: "replay"` so the router routes failures correctly.
32
+
33
+ Store each returned workflow runtime ID along with its mode tag.
34
+
35
+ ## Monitor and report (in parallel)
36
+
37
+ Issue all `muggle-remote-wf-get-ts-gen-latest-run` calls in parallel, one per runtime ID.
38
+
39
+ ```
40
+ Test Case Workflow Status Runtime ID
41
+ ────────────────────────────────────────────────────────
42
+ Login with valid creds RUNNING rt-abc123
43
+ Login with invalid creds COMPLETED rt-def456
44
+ Checkout flow QUEUED rt-ghi789
45
+ ```
46
+
47
+ ## Output
48
+
49
+ Return the uniform runs list the router consumes: `[{ testCaseId, mode, runtimeId, status }]`.