@muggleai/works 5.27.0 → 5.28.0-staging.249

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 (31) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/{chunk-6BAB7IVG.js → chunk-HBCASFGN.js} +300 -99
  3. package/dist/{chunk-B5BWBZZ5.js → chunk-USALT4LJ.js} +21 -12
  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/.codex-mcp.json +1 -1
  8. package/dist/plugin/.codex-plugin/plugin.json +1 -1
  9. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  10. package/dist/plugin/.mcp.json +1 -1
  11. package/dist/plugin/skills/_shared/dev-loop/run.md +1 -1
  12. package/dist/plugin/skills/do/pre-flight.md +1 -1
  13. package/dist/plugin/skills/muggle-test/execute-local.md +8 -6
  14. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +4 -2
  15. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +7 -4
  16. package/dist/plugin/skills/muggle-test-prepare/steps/claim-stack.md +10 -4
  17. package/dist/release-manifest.json +3 -3
  18. package/dist/src-ZOCXV6PT.js +1 -0
  19. package/package.json +2 -2
  20. package/plugin/.claude-plugin/plugin.json +1 -1
  21. package/plugin/.codex-mcp.json +1 -1
  22. package/plugin/.codex-plugin/plugin.json +1 -1
  23. package/plugin/.cursor-plugin/plugin.json +1 -1
  24. package/plugin/.mcp.json +1 -1
  25. package/plugin/skills/_shared/dev-loop/run.md +1 -1
  26. package/plugin/skills/do/pre-flight.md +1 -1
  27. package/plugin/skills/muggle-test/execute-local.md +8 -6
  28. package/plugin/skills/muggle-test-feature-local/SKILL.md +4 -2
  29. package/plugin/skills/muggle-test-prepare/SKILL.md +7 -4
  30. package/plugin/skills/muggle-test-prepare/steps/claim-stack.md +10 -4
  31. package/dist/src-MCJFH7AB.js +0 -1
@@ -1,4 +1,4 @@
1
- import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir2, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, resolveElectronAppExecutablePath, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, getActiveElectronAppReleaseStream, buildToolInputJsonSchema, toolRequiresAuth, getCallerCredentials, parseElectronAppDirName, getElectronAppReleaseTagPrefix, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, ElectronAppReleaseStream, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, buildLlmSourcePlan, describeLlmSourceStatus, resolveLlmSourceStatus, readRunView, RunViewError, renderRunViewText, writeRunViewHtml, openBrowserUrl, evaluateCheckoutFreshness, runGitCommand, BLOCKING_FRESHNESS_VERDICTS, claimServiceStack, BUSY_EXIT_CODE, checkServiceStack, releaseServiceStack, readServiceStackStatus, resolvePreferences, OnboardingBlanketChoice, PreferenceKey, LlmSource, applyLlmSourceSelection, describeStackMismatch, getDataDir, DEFAULT_LOCAL_LLM_BASE_URL, isValidLocalBaseUrl, local_exports } from './chunk-6BAB7IVG.js';
1
+ import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir2, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, resolveElectronAppExecutablePath, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, getActiveElectronAppReleaseStream, buildToolInputJsonSchema, toolRequiresAuth, getCallerCredentials, parseElectronAppDirName, getElectronAppReleaseTagPrefix, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, ElectronAppReleaseStream, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, buildLlmSourcePlan, describeLlmSourceStatus, resolveLlmSourceStatus, readRunView, RunViewError, renderRunViewText, writeRunViewHtml, openBrowserUrl, evaluateCheckoutFreshness, runGitCommand, BLOCKING_FRESHNESS_VERDICTS, claimServiceStack, BUSY_EXIT_CODE, checkServiceStack, releaseServiceStack, readServiceStackStatus, resolvePreferences, OnboardingBlanketChoice, PreferenceKey, LlmSource, applyLlmSourceSelection, describeStackMismatch, getDataDir, DEFAULT_LOCAL_LLM_BASE_URL, isValidLocalBaseUrl, local_exports } from './chunk-HBCASFGN.js';
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { v4 } from 'uuid';
@@ -1151,7 +1151,7 @@ async function resolveGsScreenshotUrls(report, opts) {
1151
1151
  if (gsUrls.length === 0) {
1152
1152
  return report;
1153
1153
  }
1154
- const mcps = await import('./src-MCJFH7AB.js');
1154
+ const mcps = await import('./src-ZOCXV6PT.js');
1155
1155
  const credentials = await mcps.getCallerCredentialsAsync();
1156
1156
  if (!credentials.bearerToken && !credentials.apiKey) {
1157
1157
  stderrWrite(
@@ -2542,14 +2542,14 @@ function renderSystemdService(job) {
2542
2542
  ].join("\n");
2543
2543
  }
2544
2544
  function renderSystemdTimer(job) {
2545
- const minutes = Math.max(1, Math.round(job.intervalSeconds / 60));
2545
+ const minutes2 = Math.max(1, Math.round(job.intervalSeconds / 60));
2546
2546
  return [
2547
2547
  "[Unit]",
2548
- `Description=Run the Muggle watch every ${minutes} min`,
2548
+ `Description=Run the Muggle watch every ${minutes2} min`,
2549
2549
  "",
2550
2550
  "[Timer]",
2551
- `OnActiveSec=${minutes}min`,
2552
- `OnUnitActiveSec=${minutes}min`,
2551
+ `OnActiveSec=${minutes2}min`,
2552
+ `OnUnitActiveSec=${minutes2}min`,
2553
2553
  `Unit=${WATCH_SYSTEMD_UNIT_NAME}.service`,
2554
2554
  "",
2555
2555
  "[Install]",
@@ -2558,9 +2558,9 @@ function renderSystemdTimer(job) {
2558
2558
  ].join("\n");
2559
2559
  }
2560
2560
  function renderCronLine(job) {
2561
- const minutes = Math.max(1, Math.round(job.intervalSeconds / 60));
2561
+ const minutes2 = Math.max(1, Math.round(job.intervalSeconds / 60));
2562
2562
  const command = launcherArgs(job).map(shellQuote).join(" ").replace(/%/g, "\\%");
2563
- return `*/${minutes} * * * * ${command} ${WATCH_CRON_MARKER}`;
2563
+ return `*/${minutes2} * * * * ${command} ${WATCH_CRON_MARKER}`;
2564
2564
  }
2565
2565
  function renderWatchEnvFile(pathValue) {
2566
2566
  return [
@@ -4092,6 +4092,11 @@ The archive may be corrupted or in an unexpected format.`
4092
4092
 
4093
4093
  // src/cli/service-stack.ts
4094
4094
  var NEED_PATTERN = /^(\d+)=(.+?)(?:@([0-9a-f]{7,40}))?$/i;
4095
+ var FAILING_CHECK_VERDICTS = /* @__PURE__ */ new Set([
4096
+ "STALE" /* Stale */,
4097
+ "PEER_LEASE" /* PeerLease */,
4098
+ "ENDED" /* Ended */
4099
+ ]);
4095
4100
  function parseStackNeed(value) {
4096
4101
  const match = NEED_PATTERN.exec(value.trim());
4097
4102
  if (!match) throw new Error(`--need "${value}" is not <port>=<run-dir>[@<sha>]`);
@@ -4135,7 +4140,8 @@ function renderStackStatusSummary(report) {
4135
4140
  }
4136
4141
  report.queue.forEach((ticket, index) => {
4137
4142
  const ports = ticket.needs.map((need) => need.port).join(", ");
4138
- lines.push(`waiting ${index + 1}: ${ticket.label ?? ticket.cwd} \u2014 port ${ports} (asked ${seconds(now - ticket.lastSeenAt)} ago)`);
4143
+ const rank = ticket.pullRequest ? `PR #${ticket.pullRequest.number} opened ${minutes(now - ticket.pullRequest.createdAt)} ago` : "no open PR";
4144
+ lines.push(`waiting ${index + 1}: ${ticket.label ?? ticket.cwd} \u2014 port ${ports}, ${rank} (asked ${seconds(now - ticket.lastSeenAt)} ago)`);
4139
4145
  });
4140
4146
  for (const service of report.services) {
4141
4147
  lines.push(`service: ${service.port} ${service.name} \u2014 ${service.dir} @ ${shortSha2(service.headSha)} (PID ${service.pid})`);
@@ -4159,7 +4165,7 @@ async function stackCheckCommand(flags) {
4159
4165
  port: flags.port === void 0 ? void 0 : Number(flags.port)
4160
4166
  });
4161
4167
  writeReport({ json: flags.json, report, summary: renderStackCheckSummary(report) });
4162
- if (report.verdict === "STALE" /* Stale */ || report.verdict === "PEER_LEASE" /* PeerLease */) process.exitCode = 1;
4168
+ if (FAILING_CHECK_VERDICTS.has(report.verdict)) process.exitCode = 1;
4163
4169
  }
4164
4170
  async function stackReleaseCommand(flags) {
4165
4171
  const report = await releaseServiceStack({ cwd: flags.cwd ?? process.cwd() });
@@ -4184,6 +4190,9 @@ function shortSha2(sha) {
4184
4190
  function seconds(ms) {
4185
4191
  return `${Math.max(0, Math.round(ms / 1e3))}s`;
4186
4192
  }
4193
+ function minutes(ms) {
4194
+ return `${Math.max(0, Math.round(ms / 6e4))}m`;
4195
+ }
4187
4196
  var logger7 = getLogger();
4188
4197
  var GITHUB_RELEASES_API = "https://api.github.com/repos/multiplex-ai/muggle-ai-works/releases?per_page=100";
4189
4198
  var INSTALL_METADATA_FILE_NAME2 = ".install-metadata.json";
@@ -5453,8 +5462,8 @@ function readWindowsProcessTable(runCommand, nowMs) {
5453
5462
  function parsePsElapsedSeconds(elapsed) {
5454
5463
  const parts = elapsed.match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
5455
5464
  if (!parts) return void 0;
5456
- const [, days, hours, minutes, seconds2] = parts;
5457
- return Number(days ?? 0) * 86400 + Number(hours ?? 0) * 3600 + Number(minutes) * 60 + Number(seconds2);
5465
+ const [, days, hours, minutes2, seconds2] = parts;
5466
+ return Number(days ?? 0) * 86400 + Number(hours ?? 0) * 3600 + Number(minutes2) * 60 + Number(seconds2);
5458
5467
  }
5459
5468
  function executableNameOf(commandLine) {
5460
5469
  const firstToken = commandLine.trim().split(/\s+/)[0] ?? "";
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-B5BWBZZ5.js';
3
- import './chunk-6BAB7IVG.js';
2
+ import { runCli } from './chunk-USALT4LJ.js';
3
+ import './chunk-HBCASFGN.js';
4
4
 
5
5
  // src/cli/main.ts
6
6
  runCli().catch((error) => {
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-B5BWBZZ5.js';
2
- export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-6BAB7IVG.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-USALT4LJ.js';
2
+ export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-HBCASFGN.js';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
4
- "version": "5.27.0",
4
+ "version": "5.28.0-staging.249",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.27.0",
7
+ "@muggleai/works@5.28.0-staging.249",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.27.0",
3
+ "version": "5.28.0-staging.249",
4
4
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
5
5
  "author": {
6
6
  "name": "Muggle AI",
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
5
- "version": "5.27.0",
5
+ "version": "5.28.0-staging.249",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.27.0",
7
+ "@muggleai/works@5.28.0-staging.249",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -48,4 +48,4 @@ Store the returned `runId`, tagged with its `mode` and record. If a run fails, l
48
48
 
49
49
  **A refused run is not a failure.** A result headed `Run refused — checkout is not on the latest …` launched nothing and has no `runId`: the `cwd` checkout lacks its base's latest commit (`BEHIND_BASE`) or carries another branch's commits (`FOREIGN_BASE`). Sync it per [`../sync-branch-with-base.md`](../sync-branch-with-base.md), re-invoke [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) so the dev server serves the synced code, and run the case again. Never route it through failure handling or count it as a result.
50
50
 
51
- **A stack refusal is not a failure either.** One local service stack serves every session on the machine, and [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) claims the ports this checkout needs before it starts anything. A result headed `Run refused — the target port is leased to another checkout` or `Run refused — the service stack changed since this checkout claimed it` launched nothing: re-invoke prepare, which re-claims the stack and restarts what moved, or reports it busy with this checkout's place in line. A run result carrying the `service stack changed during this run` warning describes code that may not be this checkout's — re-run it before counting it. Never kill a peer's service to get a port, and never count any of these as a result.
51
+ **A stack refusal is not a failure either.** One local service stack serves every session on the machine, and [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) claims the ports this checkout needs before it starts anything. A result headed `Run refused — the target port is leased to another checkout`, `Run refused — the service stack changed since this checkout claimed it` or `Run refused — this checkout's stack lease ended and its services were stopped` launched nothing: re-invoke prepare, which re-claims the stack and restarts what moved, or reports it busy with this checkout's place in line. A run result carrying the `service stack changed during this run` warning describes code that may not be this checkout's — re-run it before counting it. Never kill a peer's service to get a port, and never count any of these as a result.
@@ -37,7 +37,7 @@ Before asking anything, gather every fact you can resolve without the user:
37
37
 
38
38
  1. **Candidate repo(s).** Match keywords in the task description against configured repo names. If one repo is an obvious match, propose it as the default; if two or three are plausible, list them.
39
39
  2. **Current branch and default branch** for each candidate repo. Run `git -C <repo> symbolic-ref refs/remotes/origin/HEAD --short` and `git -C <repo> branch --show-current`. If the current branch is the default, the pre-flight must collect a new branch name.
40
- 3. **Local environment readiness.** Invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the owner of dev-server/backend detection and service start (idempotent; it probes via `dev-server-readiness.md` internally). Use what it reports as the dev-server/URL pre-flight signal.
40
+ 3. **Local environment readiness.** Invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the owner of dev-server/backend detection and service start (idempotent; it probes via `dev-server-readiness.md` internally). Use what it reports as the dev-server/URL pre-flight signal, then release its lease at once: `muggle stack release --cwd "<checkout>"`. The cycle's first run is far off — after the questionnaire and the coding — and E2E acceptance invokes prepare again right before it; a lease held meanwhile blocks every other session's E2E.
41
41
  4. **Backend health.** Covered by the same `muggle-test-prepare` invocation above (it probes backend health); note up/down.
42
42
  5. **Muggle Test MCP auth.** Call `muggle-remote-auth-status`. If expired, you will ask to re-auth in the questionnaire.
43
43
  6. **Candidate Muggle Test projects.** Call `muggle-remote-project-list` and rank by semantic match against the task description and the repo's dev URL.
@@ -8,14 +8,10 @@
8
8
  - Per-case `mode` — `replay` or `regen`, chosen during replay-vs-regen classification.
9
9
  - `cwd` — the PR-branch worktree if one exists, else the repo root.
10
10
 
11
- ## Local environment readiness
12
-
13
- Before anything else, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from `cwd` — the readiness/service-start owner (idempotent; halt on what it surfaces). It syncs `cwd` onto the latest base before starting anything, and serves every other repo from its latest default branch. The URL gate below only *selects* the target; prepare is what guarantees the right code is listening and compiled. Confirm its readiness table shows `cwd` as the `Source` of this repo's services, at `cwd`'s current HEAD — a different source means the run would test someone else's code; halt.
14
-
15
- Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. Use it to interpret what you see: a gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
16
-
17
11
  ## Pre-flight question — Local URL (gated by `autoSelectLocalHost`)
18
12
 
13
+ Both pre-flight gates resolve before prepare runs: prepare's grant holds ports peers wait for, and its lease ends if the first run does not follow soon after the services come up.
14
+
19
15
  Skill responsibilities (the rest is in `preference-gates/autoSelectLocalHost.md`):
20
16
  - **Read the cache**: `Muggle Test Last Host: <url>` session-context line, or `muggle-local-last-host-get`. Pass as `{lastHost}` substitution.
21
17
  - **Auto-detect a suggested URL**: `lsof -iTCP -sTCP:LISTEN -nP | grep -E ':(3000|3001|4200|5173|8080)'`. Pass as `{suggestedHost}`.
@@ -30,6 +26,12 @@ Gate `showElectronBrowser` (per `preference-gates/README.md`). Resolve once; app
30
26
  - `never` → pass `showUi: false`.
31
27
  - `ask` → run Picker 1 from `preference-gates/showElectronBrowser.md` via `AskUserQuestion`; map the answer back to one of the actions above.
32
28
 
29
+ ## Local environment readiness
30
+
31
+ Once both pre-flight questions are answered, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from `cwd` — the readiness/service-start owner (idempotent; halt on what it surfaces). It syncs `cwd` onto the latest base before starting anything, and serves every other repo from its latest default branch. The URL gate above only *selects* the target; prepare is what guarantees the right code is listening and compiled. Confirm its readiness table shows `cwd` as the `Source` of this repo's services, at `cwd`'s current HEAD — a different source means the run would test someone else's code; halt. From here to the first run, ask nothing.
32
+
33
+ Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. Use it to interpret what you see: a gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
34
+
33
35
  ## Prerequisite chain
34
36
 
35
37
  Before the first run, resolve each selected test case's prerequisite chain and make every ancestor ready, per [`../_shared/test-case-chain-readiness.md`](../_shared/test-case-chain-readiness.md). Ancestors are generated, never replayed, root-first; the session an ancestor leaves behind is what the target starts from, so ordering here is what makes `freshSession: false` mean anything below.
@@ -31,7 +31,7 @@ Three gates apply, each per the standard procedure in [`preference-gates/README.
31
31
 
32
32
  ## Local environment prerequisites
33
33
 
34
- Before any workflow step, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from the checkout under test. Halt on what it surfaces — a checkout it cannot bring onto the latest base included.
34
+ Before the first run, once every question that run needs is answered (Step 3), invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from the checkout under test. Halt on what it surfaces — a checkout it cannot bring onto the latest base included.
35
35
 
36
36
  Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. A gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
37
37
 
@@ -112,7 +112,9 @@ Prompt for projects: "Pick the project to group this test into:"
112
112
 
113
113
  ### 3. Ensure Local Services Are Ready
114
114
 
115
- Before detecting the local URL, invoke the `muggle:muggle-test-prepare` skill via the `Skill` tool from the checkout under test — every run, even when services look up. Live PIDs prove nothing about which code they serve: prepare syncs this checkout onto the latest base, starts sibling services from base checkouts on the latest default branch, and keeps a running service only when it still serves the right commit. It fast-exits when everything is proven. Once it reports ready, check its readiness table: this checkout's services must show this checkout as their `Source` at its current HEAD. Then proceed to Step 4.
115
+ **Ask first.** Prepare's grant holds ports other sessions wait for, and its lease ends four minutes after the services come up with no run. So before invoking it, resolve every question asked on the way to the first run: Step 4's URL gate, Step 5's script choice (applied after Step 4a), and Step 7's `showElectronBrowser` gate. From prepare to the first execute call, ask nothing.
116
+
117
+ Then invoke the `muggle:muggle-test-prepare` skill via the `Skill` tool from the checkout under test — every run, even when services look up. Live PIDs prove nothing about which code they serve: prepare syncs this checkout onto the latest base, starts sibling services from base checkouts on the latest default branch, and keeps a running service only when it still serves the right commit. It fast-exits when everything is proven. Once it reports ready, check its readiness table: this checkout's services must show this checkout as their `Source` at its current HEAD. Then proceed to Step 4.
116
118
 
117
119
  This step is especially important when the user's app depends on sibling services (a backend API, an auth service, etc.) that may not be running yet. The prepare skill handles discovery, startup, and cleanup so this skill doesn't have to.
118
120
 
@@ -91,11 +91,11 @@ Decide stages, in order; read each detail file when you reach it:
91
91
  | [derive-service-graph](./steps/derive-service-graph.md) | Derive services from workspace manifests |
92
92
  | [rebase-check](./steps/rebase-check.md) | Sync every checkout under test onto the latest base — replays too |
93
93
  | [resolve-sources](./steps/resolve-sources.md) | Pick the checkout each service runs from — replays too |
94
- | [claim-stack](./steps/claim-stack.md) | Claim the shared ports this run needs — replays too; busy returns to the caller |
95
94
  | [scope](./steps/scope.md) | Frontend / backend / full stack |
96
95
  | [viability-check](./steps/viability-check.md) | Exclude services that can't run locally |
97
96
  | [identify-services](./steps/identify-services.md) | Confirm the derived graph; fill the gaps |
98
97
  | [e2e-instructions](./steps/e2e-instructions.md) | Startup order, manual steps, local gotchas |
98
+ | [claim-stack](./steps/claim-stack.md) | Claim the shared ports this run needs, after the last question — replays too; busy returns to the caller |
99
99
 
100
100
  The Decide phase's output is the **resolved prepare plan**: `services[]` (name, run directory from [resolve-sources](./steps/resolve-sources.md), start command, expected port, `external` flag, approval granted, and the port's `keep` / `start` / `restart` from [claim-stack](./steps/claim-stack.md)), `testingScope`, `excludedServices[]`, the recorded dev-server URL, the E2E run instructions, and resolved gate outcomes.
101
101
 
@@ -132,9 +132,11 @@ Stopped 3 services:
132
132
 
133
133
  ## Integration Contract (for other skills)
134
134
 
135
- `muggle-test-feature-local`, `muggle-do`, and local-mode `muggle-test` MUST invoke this skill before any workflow step. Idempotent — fast exit when healthy. Treat success as short-lived; re-invoke if more than a few minutes pass before testing. Never bypass on "the user knows their stack is up" — that assumption is why this skill exists.
135
+ `muggle-test-feature-local`, `muggle-do`, and local-mode `muggle-test` MUST invoke this skill before their first run. Idempotent — fast exit when healthy. Never bypass on "the user knows their stack is up" — that assumption is why this skill exists.
136
136
 
137
- After a test run, the caller re-invokes for cleanup once the batch is done. Services stay up only while runs keep coming: two minutes after the last run, the lease ends and its services stop ([claim-stack](./steps/claim-stack.md#the-lease)).
137
+ **Ask first, then prepare.** Every question a caller asks before its first run — target, local URL, which script, browser visibility — is answered before it invokes this skill. From the grant to the first run nothing waits on the user or on any other work: a lease whose services are up ends four minutes later with no run, and its services stop ([claim-stack](./steps/claim-stack.md#the-lease)).
138
+
139
+ After a test run, the caller re-invokes for cleanup once the batch is done. Services stay up only while runs keep coming: two minutes after the last run, the lease ends and its services stop.
138
140
 
139
141
  **The caller's checkout.** Invoke from the checkout under test — the worktree the change lives in. Its repo is the repo under test, and its services start from that checkout; every other repo's services start from that repo's base checkout ([resolve-sources](./steps/resolve-sources.md)). Pass `base` when the change targets a branch other than the remote default.
140
142
 
@@ -158,7 +160,8 @@ The readiness table names the directory and commit each service runs from, so th
158
160
  - **Never leave orphan processes untracked** — every background PID goes into its checkout's tracking file.
159
161
  - **Never kill a process the user started independently** — `external: true` survives cleanup.
160
162
  - **Never kill or test against another checkout's service** — a listener tracked under a different checkout belongs to a peer session; see [check-running](./steps/check-running.md). The one exception is a port the stack claim planned as `restart`: no session's lease needs that service any more.
161
- - **Never start, restart or kill a service on a port this run has not claimed** — [claim-stack](./steps/claim-stack.md) runs first, and a busy stack goes back to the caller instead of being worked around.
163
+ - **Never start, restart or kill a service on a port this run has not claimed** — [claim-stack](./steps/claim-stack.md) runs before anything is started, and a busy stack goes back to the caller instead of being worked around.
164
+ - **Never hold a lease while waiting** — every question goes before [claim-stack](./steps/claim-stack.md); a `needs-input:` from the runner is a question too, so release before asking it and claim again after.
162
165
  - **Never start code that is not on the latest base** — no replay, preference, or approval skips [rebase-check](./steps/rebase-check.md) or [resolve-sources](./steps/resolve-sources.md).
163
166
  - **Never assume start commands** — verify via indicator file; confirm with user.
164
167
  - **Bail early on non-viable services** — don't start what can't run locally.
@@ -1,6 +1,6 @@
1
1
  # Claim the stack
2
2
 
3
- The machine runs **one** set of local services: one process per port, shared by every session on it. Before anything is checked, started or restarted, claim the ports this run needs, so two sessions never restart each other's services mid-run, or test against code that isn't theirs. Runs on every invocation, learning runs and replays alike, right after [resolve-sources](./resolve-sources.md) — in-session, never in the agent, because a busy stack has to reach the caller.
3
+ The machine runs **one** set of local services: one process per port, shared by every session on it. Before anything is checked, started or restarted, claim the ports this run needs, so two sessions never restart each other's services mid-run, or test against code that isn't theirs. Runs on every invocation, learning runs and replays alike, as the **last** Decide stage — after every question this run asks is answered, right before dispatching the runner — in-session, never in the agent, because a busy stack has to reach the caller. A claim made before a question holds peers' ports while the user reads it.
4
4
 
5
5
  ## Claim
6
6
 
@@ -25,23 +25,29 @@ The claim answers at once. It never waits on a peer unless asked to.
25
25
 
26
26
  ## The lease
27
27
 
28
- A granted claim is a lease, and it is held only while this session is testing. Each local execute run records itself on the lease while it is in flight. Nothing else extends it: not a re-claim, and not `muggle stack check`. So go straight from the grant to starting services and running. Never hold a lease while rebasing, editing code or waiting on CI; a renew loop cannot keep it. The lease ends, and its ports pass to whoever is next in line, when:
28
+ A granted claim is a lease, and it is held only while this session is testing. Each local execute run records itself on the lease while it is in flight. Nothing else extends it: not a re-claim, and not `muggle stack check`. So go straight from the grant to starting services and running. Never hold a lease while asking the user anything, rebasing, editing code or waiting on CI; a renew loop cannot keep it. The lease ends, and its ports pass to whoever is next in line, when:
29
29
 
30
30
  - **Two minutes pass with no run in flight** after the last run ended. That covers the turn between consecutive runs of one batch. Debugging a failure, rebasing or fixing code takes longer: the lease ends, and prepare re-claims before the next run.
31
- - **15 minutes pass after the grant with no run started.** That is the time prepare has to start its services.
31
+ - **Eight minutes pass after the grant before every claimed port serves the claimed code.** That is the time prepare has to install and start its services. A port counts as served once a service tracked at the claimed directory and commit is alive on it.
32
+ - **Four minutes pass after the services come up with no run started.** That covers the dev server's compile, the smoke test and the first execute call — not a question to the user.
32
33
  - **The session died.** Each run records the session's MCP server as a holder. Once every holder has exited and a minute has passed, the lease ends. The minute rides out an MCP server restart.
33
34
  - **It is released** in [Cleanup](../SKILL.md#cleanup).
34
35
 
36
+ A lease that ends any way but a release is remembered for 30 minutes: the checkout's next run is refused `Run refused — this checkout's stack lease ended and its services were stopped` (`muggle stack check` answers `ENDED`). Re-invoke prepare; it claims again.
37
+
35
38
  **A lease that ends takes its services with it.** Whatever listens on a port whose code no remaining lease needs is stopped. That covers services prepare started and ones started by hand, along with their launchers. The session's own MCP server ends the lease two minutes after its last run. Otherwise the next claim, run or release on the machine does it. A service marked `external: true` is never stopped, and neither is one another lease still needs.
36
39
 
37
40
  The lease belongs to the checkout, not to one run. Every test case of the same change, including cases run in parallel, runs under it, and none of them waits on the others.
38
41
 
39
- The line is first come, first served per port, and it is pulled, not pushed: nothing calls a waiting session back. A session keeps its place by asking again within the suggested delay, and is served before anyone behind it who wants the same port. A session that stops asking keeps its turn for 30 seconds after the port frees, and its place for 10 minutes.
42
+ The line is ordered per port by the checkout's open pull request: the oldest-opened PR first, so the work nearest to merging drains the backlog, and a re-run after a rebase or a review fix goes back ahead of newer PRs rather than to the end. Checkouts with no open PR yet follow, in the order they first asked. The claim finds the PR itself with `gh pr view`; without `gh` or a PR, the checkout ranks as one with none. `muggle stack status` shows each waiter's PR. The machine-wide execution lock every local run takes orders its waiters the same way.
43
+
44
+ The line is pulled, not pushed: nothing calls a waiting session back. A session keeps its place by asking again within the suggested delay, and is served before anyone behind it who wants the same port. A session that stops asking keeps its turn for 30 seconds after the port frees, and its place for 10 minutes.
40
45
 
41
46
  Holding a lease is optimistic, not exclusive. Nothing stops another process from binding a port. Instead, the local execute tools check the lease before every run and again when it ends:
42
47
 
43
48
  - A run whose port another session's lease holds, from a checkout that never claimed it, is refused: `Run refused — the target port is leased to another checkout`.
44
49
  - A run whose claimed port no longer serves the claimed code is refused: `Run refused — the service stack changed since this checkout claimed it`. Re-invoke prepare: it re-claims and restarts what moved.
50
+ - A run whose lease ended unreleased is refused: `Run refused — this checkout's stack lease ended and its services were stopped`. Re-invoke prepare.
45
51
  - A run during which the stack changed returns its result with a warning, and that result is not trusted until it has been re-run.
46
52
 
47
53
  None of these is a test result. Never route them through failure handling.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.26.0",
3
- "buildId": "run-248-1",
4
- "commitSha": "3eea4fbf2d58e0f14db22bfa300ac8bef95e47d7",
5
- "buildTime": "2026-10-29T22:19:04Z",
3
+ "buildId": "run-249-1",
4
+ "commitSha": "82c1359671b126a641e46f27fc99a83313d48826",
5
+ "buildTime": "2026-10-29T23:11:54Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -0,0 +1 @@
1
+ export { ACTION_SCRIPT_FILE_NAME, BASE_REMOTE_NAME, BLOCKING_FRESHNESS_VERDICTS, BRANCH_CREATION_REFLOG_PREFIX, BUSY_EXIT_CODE, CLAUDE_CODE_AUTH_STATUS_ARGS, CLAUDE_CODE_CHECK_TIMEOUT_MS, CLAUDE_CODE_EXECUTABLE, CLAUDE_CODE_EXECUTABLE_NOT_FOUND_CODE, CLAUDE_CODE_SESSION_LINK_ENV_VARS, CLAUDE_CODE_VERSION_ARGS, CheckoutFreshnessVerdict, ClaudeCodeReadiness, DEFAULT_LOCAL_LLM_BASE_URL, DEFAULT_PREFERENCES, ELECTRON_APP_EXECUTABLE_PATH_SEGMENTS, ElectronAppPlatform, ElectronAppReleaseStream, HTML_VIEW_OFFER_LINE, LLM_CREDENTIALS_FILE_MODE, LLM_CREDENTIALS_FILE_NAME, LLM_SOURCE_HEADER, LLM_SOURCE_OPTIONS, LLM_SOURCE_OWNED_ENV_NAMES, LLM_SOURCE_PROMPT, LLM_SOURCE_SCOPE_NOTE, LlmSource, MASKED_API_KEY_PREFIX, MASKED_API_KEY_VISIBLE_CHARS, MAX_LISTED_INHERITED_COMMITS, ONBOARDING_BLANKET_OPTIONS, ONBOARDING_CHOICE_GROUPS, ONBOARDING_MAX_OFFERS, ONBOARDING_PRIMER_BULLETS, ONBOARDING_PRIMER_HEADLINE, ONBOARDING_TOGGLE_GROUPS, ONBOARDING_TOGGLE_PROMPT, ONBOARDING_UNSELECTED_TOGGLE_VALUE, OnboardingBlanketChoice, OnboardingGroupKind, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RUN_SESSIONS_DIR_NAME, RUN_VIEW_DASHBOARD_LINK_LABEL, RunViewError, RuntimeTarget, SCREENSHOT_DIR_RELATIVE_PATH, SCREENSHOT_FILE_EXTENSION, StackCheckVerdict, StackClaimVerdict, StackLeaseEndReason, StackMismatchKind, StackPortAction, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, applyLlmSourceSelection, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppDirName, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildLlmSourcePlan, buildOnboardingPlan, buildToolInputJsonSchema, calculateFileChecksum, checkServiceStack, claimServiceStack, compareQueuePlaces, completeOnboarding, createApiKeyWithToken, createChildLogger, decideStackClaim, decideStackReap, decideStackRelease, decideStackRunEnd, decideStackRunStart, deleteApiKeyData, deleteCredentials, describeLlmSourceStatus, describeStackMismatch, e2e_exports as e2e, evaluateCheckoutFreshness, findVacatedNeeds, finishStackRun, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDashboardBaseUrl, getDataDir2 as getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLlmSourceOption, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isElectronAppPlatform, isFirstRun, isValidLocalBaseUrl, loadApiKeyData, loadCredentials, local_exports as localQa, lookUpCheckoutPullRequest, markOnboardingCompleted, maskLlmApiKey, mcp_exports as mcp, needsConflict, needsOnboarding, openBrowserUrl, parseElectronAppDirName, performLogin, performLogout, pollDeviceCode, portOfUrl, e2e_exports as qa, readPreferencesMetadata, readRunView, readServiceStackStatus, reapServiceStack, reconcileProjectPreferences, recordOnboardingSkip, releaseServiceStack, renderRunViewHtml, renderRunViewText, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppExecutablePath, resolveElectronAppPathOrNull, resolveLlmSourceStatus, resolveOnboardingAnswers, resolvePreferences, resolveRunSessionPath, resolveRuntimeTarget, resolveTogglePreferences, runGitCommand, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, startStackRun, stopVacatedServices, toDisplayRef, toolRequiresAuth, validatePreference, verifyFileChecksum, writeLlmEnvBlock, writePreferences, writePreferencesWithMetadata, writeRunViewHtml } from './chunk-HBCASFGN.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@muggleai/works",
3
3
  "mcpName": "io.github.multiplex-ai/muggle",
4
- "version": "5.27.0",
4
+ "version": "5.28.0-staging.249",
5
5
  "description": "13 years of Microsoft hyperscale engineering, packaged as a harness for your AI coding agent.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -63,7 +63,7 @@
63
63
  "muggleConfig": {
64
64
  "electronAppVersion": "1.17.0",
65
65
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
66
- "runtimeTargetDefault": "production",
66
+ "runtimeTargetDefault": "staging",
67
67
  "checksumsByStream": {
68
68
  "production": {
69
69
  "win32-x64": "03687e6c511f407875354954cb1b0f8e95d4ad0602174d76388a52d1aa1554e4",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muggle",
3
3
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
4
- "version": "5.27.0",
4
+ "version": "5.28.0-staging.249",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.27.0",
7
+ "@muggleai/works@5.28.0-staging.249",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.27.0",
3
+ "version": "5.28.0-staging.249",
4
4
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
5
5
  "author": {
6
6
  "name": "Muggle AI",
@@ -2,7 +2,7 @@
2
2
  "name": "muggle",
3
3
  "displayName": "Muggle AI",
4
4
  "description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
5
- "version": "5.27.0",
5
+ "version": "5.28.0-staging.249",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
package/plugin/.mcp.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.27.0",
7
+ "@muggleai/works@5.28.0-staging.249",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -48,4 +48,4 @@ Store the returned `runId`, tagged with its `mode` and record. If a run fails, l
48
48
 
49
49
  **A refused run is not a failure.** A result headed `Run refused — checkout is not on the latest …` launched nothing and has no `runId`: the `cwd` checkout lacks its base's latest commit (`BEHIND_BASE`) or carries another branch's commits (`FOREIGN_BASE`). Sync it per [`../sync-branch-with-base.md`](../sync-branch-with-base.md), re-invoke [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) so the dev server serves the synced code, and run the case again. Never route it through failure handling or count it as a result.
50
50
 
51
- **A stack refusal is not a failure either.** One local service stack serves every session on the machine, and [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) claims the ports this checkout needs before it starts anything. A result headed `Run refused — the target port is leased to another checkout` or `Run refused — the service stack changed since this checkout claimed it` launched nothing: re-invoke prepare, which re-claims the stack and restarts what moved, or reports it busy with this checkout's place in line. A run result carrying the `service stack changed during this run` warning describes code that may not be this checkout's — re-run it before counting it. Never kill a peer's service to get a port, and never count any of these as a result.
51
+ **A stack refusal is not a failure either.** One local service stack serves every session on the machine, and [`muggle-test-prepare`](../../muggle-test-prepare/SKILL.md) claims the ports this checkout needs before it starts anything. A result headed `Run refused — the target port is leased to another checkout`, `Run refused — the service stack changed since this checkout claimed it` or `Run refused — this checkout's stack lease ended and its services were stopped` launched nothing: re-invoke prepare, which re-claims the stack and restarts what moved, or reports it busy with this checkout's place in line. A run result carrying the `service stack changed during this run` warning describes code that may not be this checkout's — re-run it before counting it. Never kill a peer's service to get a port, and never count any of these as a result.
@@ -37,7 +37,7 @@ Before asking anything, gather every fact you can resolve without the user:
37
37
 
38
38
  1. **Candidate repo(s).** Match keywords in the task description against configured repo names. If one repo is an obvious match, propose it as the default; if two or three are plausible, list them.
39
39
  2. **Current branch and default branch** for each candidate repo. Run `git -C <repo> symbolic-ref refs/remotes/origin/HEAD --short` and `git -C <repo> branch --show-current`. If the current branch is the default, the pre-flight must collect a new branch name.
40
- 3. **Local environment readiness.** Invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the owner of dev-server/backend detection and service start (idempotent; it probes via `dev-server-readiness.md` internally). Use what it reports as the dev-server/URL pre-flight signal.
40
+ 3. **Local environment readiness.** Invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the owner of dev-server/backend detection and service start (idempotent; it probes via `dev-server-readiness.md` internally). Use what it reports as the dev-server/URL pre-flight signal, then release its lease at once: `muggle stack release --cwd "<checkout>"`. The cycle's first run is far off — after the questionnaire and the coding — and E2E acceptance invokes prepare again right before it; a lease held meanwhile blocks every other session's E2E.
41
41
  4. **Backend health.** Covered by the same `muggle-test-prepare` invocation above (it probes backend health); note up/down.
42
42
  5. **Muggle Test MCP auth.** Call `muggle-remote-auth-status`. If expired, you will ask to re-auth in the questionnaire.
43
43
  6. **Candidate Muggle Test projects.** Call `muggle-remote-project-list` and rank by semantic match against the task description and the repo's dev URL.
@@ -8,14 +8,10 @@
8
8
  - Per-case `mode` — `replay` or `regen`, chosen during replay-vs-regen classification.
9
9
  - `cwd` — the PR-branch worktree if one exists, else the repo root.
10
10
 
11
- ## Local environment readiness
12
-
13
- Before anything else, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from `cwd` — the readiness/service-start owner (idempotent; halt on what it surfaces). It syncs `cwd` onto the latest base before starting anything, and serves every other repo from its latest default branch. The URL gate below only *selects* the target; prepare is what guarantees the right code is listening and compiled. Confirm its readiness table shows `cwd` as the `Source` of this repo's services, at `cwd`'s current HEAD — a different source means the run would test someone else's code; halt.
14
-
15
- Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. Use it to interpret what you see: a gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
16
-
17
11
  ## Pre-flight question — Local URL (gated by `autoSelectLocalHost`)
18
12
 
13
+ Both pre-flight gates resolve before prepare runs: prepare's grant holds ports peers wait for, and its lease ends if the first run does not follow soon after the services come up.
14
+
19
15
  Skill responsibilities (the rest is in `preference-gates/autoSelectLocalHost.md`):
20
16
  - **Read the cache**: `Muggle Test Last Host: <url>` session-context line, or `muggle-local-last-host-get`. Pass as `{lastHost}` substitution.
21
17
  - **Auto-detect a suggested URL**: `lsof -iTCP -sTCP:LISTEN -nP | grep -E ':(3000|3001|4200|5173|8080)'`. Pass as `{suggestedHost}`.
@@ -30,6 +26,12 @@ Gate `showElectronBrowser` (per `preference-gates/README.md`). Resolve once; app
30
26
  - `never` → pass `showUi: false`.
31
27
  - `ask` → run Picker 1 from `preference-gates/showElectronBrowser.md` via `AskUserQuestion`; map the answer back to one of the actions above.
32
28
 
29
+ ## Local environment readiness
30
+
31
+ Once both pre-flight questions are answered, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from `cwd` — the readiness/service-start owner (idempotent; halt on what it surfaces). It syncs `cwd` onto the latest base before starting anything, and serves every other repo from its latest default branch. The URL gate above only *selects* the target; prepare is what guarantees the right code is listening and compiled. Confirm its readiness table shows `cwd` as the `Source` of this repo's services, at `cwd`'s current HEAD — a different source means the run would test someone else's code; halt. From here to the first run, ask nothing.
32
+
33
+ Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. Use it to interpret what you see: a gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
34
+
33
35
  ## Prerequisite chain
34
36
 
35
37
  Before the first run, resolve each selected test case's prerequisite chain and make every ancestor ready, per [`../_shared/test-case-chain-readiness.md`](../_shared/test-case-chain-readiness.md). Ancestors are generated, never replayed, root-first; the session an ancestor leaves behind is what the target starts from, so ordering here is what makes `freshSession: false` mean anything below.
@@ -31,7 +31,7 @@ Three gates apply, each per the standard procedure in [`preference-gates/README.
31
31
 
32
32
  ## Local environment prerequisites
33
33
 
34
- Before any workflow step, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from the checkout under test. Halt on what it surfaces — a checkout it cannot bring onto the latest base included.
34
+ Before the first run, once every question that run needs is answered (Step 3), invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) from the checkout under test. Halt on what it surfaces — a checkout it cannot bring onto the latest base included.
35
35
 
36
36
  Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. A gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
37
37
 
@@ -112,7 +112,9 @@ Prompt for projects: "Pick the project to group this test into:"
112
112
 
113
113
  ### 3. Ensure Local Services Are Ready
114
114
 
115
- Before detecting the local URL, invoke the `muggle:muggle-test-prepare` skill via the `Skill` tool from the checkout under test — every run, even when services look up. Live PIDs prove nothing about which code they serve: prepare syncs this checkout onto the latest base, starts sibling services from base checkouts on the latest default branch, and keeps a running service only when it still serves the right commit. It fast-exits when everything is proven. Once it reports ready, check its readiness table: this checkout's services must show this checkout as their `Source` at its current HEAD. Then proceed to Step 4.
115
+ **Ask first.** Prepare's grant holds ports other sessions wait for, and its lease ends four minutes after the services come up with no run. So before invoking it, resolve every question asked on the way to the first run: Step 4's URL gate, Step 5's script choice (applied after Step 4a), and Step 7's `showElectronBrowser` gate. From prepare to the first execute call, ask nothing.
116
+
117
+ Then invoke the `muggle:muggle-test-prepare` skill via the `Skill` tool from the checkout under test — every run, even when services look up. Live PIDs prove nothing about which code they serve: prepare syncs this checkout onto the latest base, starts sibling services from base checkouts on the latest default branch, and keeps a running service only when it still serves the right commit. It fast-exits when everything is proven. Once it reports ready, check its readiness table: this checkout's services must show this checkout as their `Source` at its current HEAD. Then proceed to Step 4.
116
118
 
117
119
  This step is especially important when the user's app depends on sibling services (a backend API, an auth service, etc.) that may not be running yet. The prepare skill handles discovery, startup, and cleanup so this skill doesn't have to.
118
120
 
@@ -91,11 +91,11 @@ Decide stages, in order; read each detail file when you reach it:
91
91
  | [derive-service-graph](./steps/derive-service-graph.md) | Derive services from workspace manifests |
92
92
  | [rebase-check](./steps/rebase-check.md) | Sync every checkout under test onto the latest base — replays too |
93
93
  | [resolve-sources](./steps/resolve-sources.md) | Pick the checkout each service runs from — replays too |
94
- | [claim-stack](./steps/claim-stack.md) | Claim the shared ports this run needs — replays too; busy returns to the caller |
95
94
  | [scope](./steps/scope.md) | Frontend / backend / full stack |
96
95
  | [viability-check](./steps/viability-check.md) | Exclude services that can't run locally |
97
96
  | [identify-services](./steps/identify-services.md) | Confirm the derived graph; fill the gaps |
98
97
  | [e2e-instructions](./steps/e2e-instructions.md) | Startup order, manual steps, local gotchas |
98
+ | [claim-stack](./steps/claim-stack.md) | Claim the shared ports this run needs, after the last question — replays too; busy returns to the caller |
99
99
 
100
100
  The Decide phase's output is the **resolved prepare plan**: `services[]` (name, run directory from [resolve-sources](./steps/resolve-sources.md), start command, expected port, `external` flag, approval granted, and the port's `keep` / `start` / `restart` from [claim-stack](./steps/claim-stack.md)), `testingScope`, `excludedServices[]`, the recorded dev-server URL, the E2E run instructions, and resolved gate outcomes.
101
101
 
@@ -132,9 +132,11 @@ Stopped 3 services:
132
132
 
133
133
  ## Integration Contract (for other skills)
134
134
 
135
- `muggle-test-feature-local`, `muggle-do`, and local-mode `muggle-test` MUST invoke this skill before any workflow step. Idempotent — fast exit when healthy. Treat success as short-lived; re-invoke if more than a few minutes pass before testing. Never bypass on "the user knows their stack is up" — that assumption is why this skill exists.
135
+ `muggle-test-feature-local`, `muggle-do`, and local-mode `muggle-test` MUST invoke this skill before their first run. Idempotent — fast exit when healthy. Never bypass on "the user knows their stack is up" — that assumption is why this skill exists.
136
136
 
137
- After a test run, the caller re-invokes for cleanup once the batch is done. Services stay up only while runs keep coming: two minutes after the last run, the lease ends and its services stop ([claim-stack](./steps/claim-stack.md#the-lease)).
137
+ **Ask first, then prepare.** Every question a caller asks before its first run — target, local URL, which script, browser visibility — is answered before it invokes this skill. From the grant to the first run nothing waits on the user or on any other work: a lease whose services are up ends four minutes later with no run, and its services stop ([claim-stack](./steps/claim-stack.md#the-lease)).
138
+
139
+ After a test run, the caller re-invokes for cleanup once the batch is done. Services stay up only while runs keep coming: two minutes after the last run, the lease ends and its services stop.
138
140
 
139
141
  **The caller's checkout.** Invoke from the checkout under test — the worktree the change lives in. Its repo is the repo under test, and its services start from that checkout; every other repo's services start from that repo's base checkout ([resolve-sources](./steps/resolve-sources.md)). Pass `base` when the change targets a branch other than the remote default.
140
142
 
@@ -158,7 +160,8 @@ The readiness table names the directory and commit each service runs from, so th
158
160
  - **Never leave orphan processes untracked** — every background PID goes into its checkout's tracking file.
159
161
  - **Never kill a process the user started independently** — `external: true` survives cleanup.
160
162
  - **Never kill or test against another checkout's service** — a listener tracked under a different checkout belongs to a peer session; see [check-running](./steps/check-running.md). The one exception is a port the stack claim planned as `restart`: no session's lease needs that service any more.
161
- - **Never start, restart or kill a service on a port this run has not claimed** — [claim-stack](./steps/claim-stack.md) runs first, and a busy stack goes back to the caller instead of being worked around.
163
+ - **Never start, restart or kill a service on a port this run has not claimed** — [claim-stack](./steps/claim-stack.md) runs before anything is started, and a busy stack goes back to the caller instead of being worked around.
164
+ - **Never hold a lease while waiting** — every question goes before [claim-stack](./steps/claim-stack.md); a `needs-input:` from the runner is a question too, so release before asking it and claim again after.
162
165
  - **Never start code that is not on the latest base** — no replay, preference, or approval skips [rebase-check](./steps/rebase-check.md) or [resolve-sources](./steps/resolve-sources.md).
163
166
  - **Never assume start commands** — verify via indicator file; confirm with user.
164
167
  - **Bail early on non-viable services** — don't start what can't run locally.