@muggleai/works 5.18.0 → 5.19.0-staging.124

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.
@@ -1,4 +1,4 @@
1
- import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, toolRequiresAuth, getCallerCredentials, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, OnboardingBlanketChoice, PreferenceKey } from './chunk-EKSYEVM2.js';
1
+ import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, toolRequiresAuth, getCallerCredentials, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, OnboardingBlanketChoice, PreferenceKey } from './chunk-KBOWKNKR.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';
@@ -754,7 +754,7 @@ async function resolveGsScreenshotUrls(report, opts) {
754
754
  if (gsUrls.length === 0) {
755
755
  return report;
756
756
  }
757
- const mcps = await import('./src-52J7RTUV.js');
757
+ const mcps = await import('./src-3E7EWTIT.js');
758
758
  const credentials = await mcps.getCallerCredentialsAsync();
759
759
  if (!credentials.bearerToken && !credentials.apiKey) {
760
760
  stderrWrite(
@@ -807,7 +807,7 @@ var withSentinel = (s) => s ? `${REPORT_SECTION_SENTINEL}
807
807
  ${s}` : s;
808
808
  async function resolveDashboardBaseUrl(stderrWrite) {
809
809
  try {
810
- const mcps = await import('./src-52J7RTUV.js');
810
+ const mcps = await import('./src-3E7EWTIT.js');
811
811
  return mcps.resolveActiveProfile().dashboardBaseUrl;
812
812
  } catch (err) {
813
813
  stderrWrite(
@@ -972,6 +972,13 @@ var TestResultStatus = /* @__PURE__ */ ((TestResultStatus2) => {
972
972
  TestResultStatus2["Skipped"] = "skipped";
973
973
  return TestResultStatus2;
974
974
  })(TestResultStatus || {});
975
+ var DisplayResolution = /* @__PURE__ */ ((DisplayResolution2) => {
976
+ DisplayResolution2["R_0800x0600"] = "R_0800x0600";
977
+ DisplayResolution2["R_1024x0768"] = "R_1024x0768";
978
+ DisplayResolution2["R_0390x0844"] = "R_0390x0844";
979
+ DisplayResolution2["R_1920x1080"] = "R_1920x1080";
980
+ return DisplayResolution2;
981
+ })(DisplayResolution || {});
975
982
 
976
983
  // packages/mcps/src/mcp/local/services/stored-auth-target.ts
977
984
  function decodeJwtPayload(token) {
@@ -2423,6 +2430,22 @@ function applyLlmEnvOverrides(baseEnv, dataDirOverride2) {
2423
2430
  return { env: baseEnv, appliedNames };
2424
2431
  }
2425
2432
 
2433
+ // packages/mcps/src/mcp/local/services/display-resolution-constants.ts
2434
+ var DISPLAY_PARAMS_BY_RESOLUTION = {
2435
+ ["R_0800x0600" /* R_0800x0600 */]: { browserWindowWidth: 800, browserWindowHeight: 600 },
2436
+ ["R_1024x0768" /* R_1024x0768 */]: { browserWindowWidth: 1024, browserWindowHeight: 768 },
2437
+ ["R_0390x0844" /* R_0390x0844 */]: { browserWindowWidth: 390, browserWindowHeight: 844 },
2438
+ ["R_1920x1080" /* R_1920x1080 */]: { browserWindowWidth: 1920, browserWindowHeight: 1080 }
2439
+ };
2440
+ var DEFAULT_DISPLAY_RESOLUTION = "R_1920x1080" /* R_1920x1080 */;
2441
+
2442
+ // packages/mcps/src/mcp/local/services/display-resolution.ts
2443
+ function resolveDisplayParams(params) {
2444
+ const storedResolution = params.storedDisplayOptions?.[0];
2445
+ const effectiveResolution = params.requestedResolution ?? storedResolution ?? DEFAULT_DISPLAY_RESOLUTION;
2446
+ return DISPLAY_PARAMS_BY_RESOLUTION[effectiveResolution] ?? DISPLAY_PARAMS_BY_RESOLUTION[DEFAULT_DISPLAY_RESOLUTION];
2447
+ }
2448
+
2426
2449
  // packages/mcps/src/mcp/local/services/replay-url-rewrite.ts
2427
2450
  function rewriteActionScriptUrls(params) {
2428
2451
  const { actionScript, originalUrl, localUrl } = params;
@@ -2515,7 +2538,13 @@ function buildGenerationActionScript(params) {
2515
2538
  // projectId but NOT equal to it). The MCP has no client-side source for
2516
2539
  // it, so leave it empty here; STM-on-local needs a backend resolver.
2517
2540
  sharedTestMemoryId: "",
2518
- runEnvironmentType: "local"
2541
+ runEnvironmentType: "local",
2542
+ // Sizes the studio's webview. Absent, the electron-app falls back to its
2543
+ // own hardcoded 1920x1080, which is why a phone-viewport run needs this.
2544
+ displayParams: resolveDisplayParams({
2545
+ requestedResolution: params.displayResolution,
2546
+ storedDisplayOptions: params.testCase.displayOptions
2547
+ })
2519
2548
  },
2520
2549
  goal: params.testCase.goal,
2521
2550
  url: params.localUrl,
@@ -2573,7 +2602,12 @@ function buildReplayActionScript(params) {
2573
2602
  // projectId but NOT equal to it). The MCP has no client-side source for
2574
2603
  // it, so leave it empty here; STM-on-local needs a backend resolver.
2575
2604
  sharedTestMemoryId: "",
2576
- runEnvironmentType: "local"
2605
+ runEnvironmentType: "local",
2606
+ // Sizes the studio's webview. Absent, the electron-app falls back to its
2607
+ // own hardcoded 1920x1080, which is why a phone-viewport run needs this.
2608
+ displayParams: resolveDisplayParams({
2609
+ requestedResolution: params.displayResolution
2610
+ })
2577
2611
  },
2578
2612
  goal: params.testScript.name,
2579
2613
  url: params.localUrl,
@@ -3182,7 +3216,8 @@ async function runTestGenerationLocked(params) {
3182
3216
  localUrl,
3183
3217
  runId,
3184
3218
  localTestScriptId: localTestScript.id,
3185
- ownerUserId: authContent.userId
3219
+ ownerUserId: authContent.userId,
3220
+ displayResolution: params.displayResolution
3186
3221
  });
3187
3222
  const inputFilePath = await writeTempFile({
3188
3223
  filename: `${runId}_input.json`,
@@ -3369,7 +3404,8 @@ async function runReplayLocked(params) {
3369
3404
  actionScript,
3370
3405
  localUrl,
3371
3406
  runId,
3372
- ownerUserId: authContent.userId
3407
+ ownerUserId: authContent.userId,
3408
+ displayResolution: params.displayResolution
3373
3409
  });
3374
3410
  const inputFilePath = await writeTempFile({
3375
3411
  filename: `${runId}_input.json`,
@@ -6714,6 +6750,7 @@ __export(local_exports, {
6714
6750
  executeTool: () => executeTool,
6715
6751
  getTool: () => getTool
6716
6752
  });
6753
+ var DisplayResolutionSchema = z.nativeEnum(DisplayResolution);
6717
6754
  var TestCaseDetailsSchema = z.object({
6718
6755
  /** Cloud test case ID. */
6719
6756
  id: MuggleEntityIdSchema.describe("Cloud test case ID (UUID)"),
@@ -6732,7 +6769,11 @@ var TestCaseDetailsSchema = z.object({
6732
6769
  /** Cloud project ID (required for electron workflow context). */
6733
6770
  projectId: MuggleEntityIdSchema.describe("Cloud project ID (UUID)"),
6734
6771
  /** Cloud use case ID (required for electron workflow context). */
6735
- useCaseId: MuggleEntityIdSchema.describe("Cloud use case ID (UUID)")
6772
+ useCaseId: MuggleEntityIdSchema.describe("Cloud use case ID (UUID)"),
6773
+ /** Resolutions stored on the cloud test case; the first entry sizes the run unless the caller names one. */
6774
+ displayOptions: z.array(DisplayResolutionSchema).optional().describe(
6775
+ "Resolutions stored on the cloud test case (from muggle-remote-test-case-get). The first entry sizes the browser unless displayResolution overrides it."
6776
+ )
6736
6777
  });
6737
6778
  var TestScriptDetailsSchema = z.object({
6738
6779
  /** Cloud test script ID. */
@@ -6766,7 +6807,11 @@ var ExecuteTestGenerationInputSchema = z.object({
6766
6807
  /** Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state, such as registration, login, or cookie consent flows. */
6767
6808
  freshSession: z.boolean().optional().describe("Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state \u2014 e.g. registration, login, or cookie consent flows. Default: false."),
6768
6809
  /** Mutation parameters providing variable context for this generation run — a string[] where each element is a plain-English instruction or a local file path for uploads (e.g. "Attach the image at C:\\Users\\user\\photo.jpg"). Matches the electron-app's string[] mutation file format. */
6769
- mutations: z.array(z.string()).optional().describe(`Mutation parameters: string[] where each element is a plain-English instruction or a local file path (e.g. ["The post content should be 'Hello'", "Attach the image at C:\\\\Users\\\\user\\\\photo.jpg"]).`)
6810
+ mutations: z.array(z.string()).optional().describe(`Mutation parameters: string[] where each element is a plain-English instruction or a local file path (e.g. ["The post content should be 'Hello'", "Attach the image at C:\\\\Users\\\\user\\\\photo.jpg"]).`),
6811
+ /** Resolution for this run. Overrides `testCase.displayOptions`; both absent means 1920x1080. */
6812
+ displayResolution: DisplayResolutionSchema.optional().describe(
6813
+ "Browser resolution for this run, e.g. R_0390x0844 for a 390x844 phone viewport. Overrides testCase.displayOptions; defaults to R_1920x1080 when neither is given."
6814
+ )
6770
6815
  });
6771
6816
  var ExecuteReplayInputSchema = z.object({
6772
6817
  /** Test script metadata from muggle-remote-test-script-get. */
@@ -6784,7 +6829,11 @@ var ExecuteReplayInputSchema = z.object({
6784
6829
  /** Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state, such as registration, login, or cookie consent flows. */
6785
6830
  freshSession: z.boolean().optional().describe("Clear all session storage (cookies, localStorage, etc.) before execution. Use for test cases that require a clean browser state \u2014 e.g. registration, login, or cookie consent flows. Default: false."),
6786
6831
  /** Mutation parameters passed to the LLM step predictor — a string[] where each element is an instruction describing what varies this run. Supports plain-English instructions (e.g. "The post content should be 'Hello world'") and local file paths for uploads (e.g. "Attach the image at C:\\Users\\user\\photo.jpg"). Matches the electron-app's string[] mutation file format. When provided, the electron app uses LLM-driven step prediction instead of deterministic replay. */
6787
- mutations: z.array(z.string()).optional().describe(`Mutation parameters: string[] where each element is a plain-English instruction or a local file path (e.g. ["The post content should be 'Hello'", "Attach the image at C:\\\\Users\\\\user\\\\photo.jpg"]).`)
6832
+ mutations: z.array(z.string()).optional().describe(`Mutation parameters: string[] where each element is a plain-English instruction or a local file path (e.g. ["The post content should be 'Hello'", "Attach the image at C:\\\\Users\\\\user\\\\photo.jpg"]).`),
6833
+ /** Resolution for this run. A test script carries no stored resolution, so this is replay's only sizing input. */
6834
+ displayResolution: DisplayResolutionSchema.optional().describe(
6835
+ "Browser resolution for this run, e.g. R_0390x0844 for a 390x844 phone viewport. Defaults to R_1920x1080."
6836
+ )
6788
6837
  });
6789
6838
  var CancelExecutionInputSchema = z.object({
6790
6839
  runId: MuggleEntityIdSchema.describe("Run ID (UUID) to cancel")
@@ -7394,7 +7443,8 @@ var executeTestGenerationTool = {
7394
7443
  mutations: input.mutations,
7395
7444
  timeoutMs: input.timeoutMs,
7396
7445
  showUi,
7397
- freshSession: input.freshSession
7446
+ freshSession: input.freshSession,
7447
+ displayResolution: input.displayResolution
7398
7448
  });
7399
7449
  const content = [
7400
7450
  "## Test Generation " + (generationRun.status === "passed" ? "Successful" : "Failed"),
@@ -7436,7 +7486,8 @@ var executeReplayTool = {
7436
7486
  mutations: input.mutations,
7437
7487
  timeoutMs: input.timeoutMs,
7438
7488
  showUi,
7439
- freshSession: input.freshSession
7489
+ freshSession: input.freshSession,
7490
+ displayResolution: input.displayResolution
7440
7491
  });
7441
7492
  const content = [
7442
7493
  "## Test Replay " + (replayRun.status === "passed" ? "Successful" : "Failed"),
@@ -7864,6 +7915,8 @@ __export(local_exports2, {
7864
7915
  CleanupSessionsInputSchema: () => CleanupSessionsInputSchema,
7865
7916
  CloudMappingEntityType: () => CloudMappingEntityType,
7866
7917
  DeviceCodePollStatus: () => DeviceCodePollStatus,
7918
+ DisplayResolution: () => DisplayResolution,
7919
+ DisplayResolutionSchema: () => DisplayResolutionSchema,
7867
7920
  EmptyInputSchema: () => EmptyInputSchema2,
7868
7921
  EventTelemetryEmitInputSchema: () => EventTelemetryEmitInputSchema,
7869
7922
  ExecuteReplayInputSchema: () => ExecuteReplayInputSchema,
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-63RAPKGG.js';
3
- import './chunk-EKSYEVM2.js';
2
+ import { runCli } from './chunk-ECZAVRVK.js';
3
+ import './chunk-KBOWKNKR.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-63RAPKGG.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-EKSYEVM2.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-ECZAVRVK.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-KBOWKNKR.js';
@@ -6,11 +6,29 @@ On `always`, the steps below run as one pre-authorized sequence (no per-step pro
6
6
 
7
7
  **Verify every step, assume none.** Each step states what proves it succeeded. A step whose side effect *usually* happens is not a step that ran — that assumption is how a branch survives a cleanup that reported success. The [report](#report) states verified state, never intent.
8
8
 
9
+ **A blocked step is reported, never handed back.** On `always` the gate already authorized this whole sequence, so nobody is sitting on the prompt: stopping to describe an obstacle and wait turns cleanup into a run that hangs until the user next looks at the terminal. Every step ends exactly one of three ways — done, `—` nothing to do, or `❌` with its reason in the [report](#report) and the sequence stopped. Waiting for the user is not a fourth ending. An obstacle you can clear yourself (standing in the directory you are about to remove) you clear and continue; one you cannot (a lock held by another process, a permission you lack) you record and stop. Print the report either way — the report is what tells the user cleanup is over.
10
+
9
11
  ## Preconditions
10
12
 
11
13
  Confirm the PR is `MERGED` from provider state. A closed-unmerged PR keeps its branch and worktree: the work never landed, so deleting it destroys it.
12
14
 
13
- ## 1. Remove the worktree — link-safe
15
+ ## 1. Leave the worktree
16
+
17
+ Only if a worktree was used. This runs **before** anything is deleted — the working directory is the precondition the rest of the sequence stands on.
18
+
19
+ `git worktree remove` refuses to remove the worktree the command is standing in, and a shell whose directory is deleted out from under it fails everything afterwards with `getcwd`/`No such file or directory`. Both surface as "cleanup can't proceed", and the reflex — announcing that you are inside the directory and waiting for the user — is the stall this step exists to prevent. Moving is yours to do; it needs no one's permission, and the gate already gave it.
20
+
21
+ 1. **Resolve the anchor** — the repo's main working tree, which removing a worktree never touches:
22
+ ```bash
23
+ git -C {worktreePath} worktree list --porcelain # the first `worktree ` line is the main tree
24
+ ```
25
+ 2. **Compare resolved paths, not strings.** Resolve the current directory and `{worktreePath}` through symlinks before asking whether one contains the other; `/var/…` against `/private/var/…` reads as "already outside" and the removal then fails anyway. Match on path segments, so a sibling like `<name>-old` is not mistaken for being inside `<name>`.
26
+ 3. If the cwd is at or under `{worktreePath}`, move to the anchor now.
27
+ 4. Run every remaining command against an explicit repo — `git -C <anchor> …` — so no later step walks back into the tree it is deleting.
28
+
29
+ **Verify:** the resolved cwd is outside `{worktreePath}` **and** still exists (a directory that resolves, not just a string). A cwd that was deleted rather than left is the same stall arriving one step later.
30
+
31
+ ## 2. Remove the worktree — link-safe
14
32
 
15
33
  Only if a worktree was used.
16
34
 
@@ -26,7 +44,7 @@ A worktree's dependency dir (`node_modules`, and nested workspace copies) is oft
26
44
 
27
45
  **Verify:** the path is gone, it no longer appears in `git worktree list`, **and** the shared dependency tree the links pointed at still exists. That last check is the one that catches a link-follow.
28
46
 
29
- ## 2. Delete the local branch
47
+ ## 3. Delete the local branch
30
48
 
31
49
  **Skip entirely when no worktree was used** — the branch is then the user's live checkout, and a checked-out branch must never be deleted.
32
50
 
@@ -39,7 +57,7 @@ Do not reach for `-D` on faith. Replace the ancestry check with a content check:
39
57
 
40
58
  **Verify:** the branch is absent from `git branch --list`.
41
59
 
42
- ## 3. Delete the remote branch
60
+ ## 4. Delete the remote branch
43
61
 
44
62
  **A provider that auto-deletes the head branch on merge is a setting, not a guarantee.** It can be off for the repo, off for a fork, or simply not fire. Treat auto-delete as something to detect, never as this step having run.
45
63
 
@@ -48,7 +66,7 @@ Do not reach for `-D` on faith. Replace the ancestry check with a content check:
48
66
 
49
67
  **Verify:** querying the ref returns not-found.
50
68
 
51
- ## 4. Clear the session slot
69
+ ## 5. Clear the session slot
52
70
 
53
71
  The slot is `~/.muggle-ai/muggle-do/sessions/<slug>/` — the home directory, not the project.
54
72
 
@@ -56,7 +74,7 @@ Clear it only when `prs.json` records a terminal state. A slot for a still-open
56
74
 
57
75
  **Verify:** the slot directory is gone, and no non-terminal slot was touched.
58
76
 
59
- ## 5. Clear this run's prepare artifacts
77
+ ## 6. Clear this run's prepare artifacts
60
78
 
61
79
  The PID tracker and per-service logs written during environment prep — `/tmp/muggle-test-prepare.json` and `/tmp/muggle-prepare-*.log`.
62
80
 
@@ -75,6 +93,7 @@ Cleanup — <slug> (PR #<n>, merged)
75
93
 
76
94
  | Step | Result |
77
95
  |:----------------------|:------------------------------------------------|
96
+ | Left the worktree | ✅ moved to <repo root> (cwd was inside) |
78
97
  | Worktree removed | ✅ .claude/worktrees/<name> (shared deps intact) |
79
98
  | Local branch deleted | ✅ users/<user>/<branch> |
80
99
  | Remote branch deleted | ✅ (already gone — provider auto-delete) |
@@ -51,3 +51,5 @@ Use this for feature development, local validation, and PR iteration.
51
51
 
52
52
  After merge, remove the worktree.
53
53
  See [`post-merge-cleanup.md`](post-merge-cleanup.md).
54
+
55
+ **Never remove a worktree you are standing in.** Step back to the repo's main working tree first — `git worktree remove` refuses from inside, and a deleted working directory breaks every command that follows. Relocating is part of teardown, not a reason to stop and ask.
@@ -13,3 +13,5 @@ Invoked by `/muggle-do` when the watcher forwards a PR's terminal state. On `mer
13
13
  3. **Teardown (`merged` only).** When the PR is `merged`, run [`../_shared/post-merge-cleanup.md`](../_shared/post-merge-cleanup.md) with `{worktreePath}` and `{branch}`. That file owns the teardown sequence **and its safety rules** — including skipping worktree-remove and local branch deletion when no worktree was used. This stage adds no teardown logic of its own. On `closed`, skip teardown — the branch and any worktree stay intact.
14
14
  4. Append a cleanup line to the session's `followup.log`, recording whether teardown ran.
15
15
  5. Suggest the next step per [`next-step.md`](next-step.md), passing whether teardown ran. This is the stage's last action.
16
+
17
+ **Teardown never returns a question.** It finishes with its report — every step done, skipped, or blocked-with-a-reason — so steps 4 and 5 always run. A teardown that appears to be waiting on the user has stalled mid-sequence; the stage is not complete until the report and the next-step suggestion are both printed.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.18.0",
3
- "buildId": "run-122-1",
4
- "commitSha": "1170bfa0f0a22368c3ee36f85b65d708f0c99f83",
5
- "buildTime": "2026-09-19T08:03:02Z",
3
+ "buildId": "run-124-1",
4
+ "commitSha": "fd774f165bbdddcd5d9b6707295e6efb6c63fbd5",
5
+ "buildTime": "2026-09-19T08:45:18Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -1 +1 @@
1
- export { DEFAULT_PREFERENCES, ElectronAppReleaseStream, 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, RuntimeTarget, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildOnboardingPlan, calculateFileChecksum, completeOnboarding, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, markOnboardingCompleted, mcp_exports as mcp, needsOnboarding, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readPreferencesMetadata, reconcileProjectPreferences, recordOnboardingSkip, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppPathOrNull, resolveOnboardingAnswers, resolvePreferences, resolveRuntimeTarget, resolveTogglePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences, writePreferencesWithMetadata } from './chunk-EKSYEVM2.js';
1
+ export { DEFAULT_PREFERENCES, ElectronAppReleaseStream, 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, RuntimeTarget, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildOnboardingPlan, calculateFileChecksum, completeOnboarding, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, markOnboardingCompleted, mcp_exports as mcp, needsOnboarding, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readPreferencesMetadata, reconcileProjectPreferences, recordOnboardingSkip, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppPathOrNull, resolveOnboardingAnswers, resolvePreferences, resolveRuntimeTarget, resolveTogglePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences, writePreferencesWithMetadata } from './chunk-KBOWKNKR.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.18.0",
4
+ "version": "5.19.0-staging.124",
5
5
  "description": "Ship quality products with AI-powered E2E acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "muggleConfig": {
55
55
  "electronAppVersion": "1.10.6",
56
56
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
57
- "runtimeTargetDefault": "production",
57
+ "runtimeTargetDefault": "staging",
58
58
  "checksumsByStream": {
59
59
  "production": {
60
60
  "win32-x64": "c20acdc2fd7a05039c62f9e5b079e3068d1b3a23a1b3b37b75e2a650b5401de6",
@@ -6,11 +6,29 @@ On `always`, the steps below run as one pre-authorized sequence (no per-step pro
6
6
 
7
7
  **Verify every step, assume none.** Each step states what proves it succeeded. A step whose side effect *usually* happens is not a step that ran — that assumption is how a branch survives a cleanup that reported success. The [report](#report) states verified state, never intent.
8
8
 
9
+ **A blocked step is reported, never handed back.** On `always` the gate already authorized this whole sequence, so nobody is sitting on the prompt: stopping to describe an obstacle and wait turns cleanup into a run that hangs until the user next looks at the terminal. Every step ends exactly one of three ways — done, `—` nothing to do, or `❌` with its reason in the [report](#report) and the sequence stopped. Waiting for the user is not a fourth ending. An obstacle you can clear yourself (standing in the directory you are about to remove) you clear and continue; one you cannot (a lock held by another process, a permission you lack) you record and stop. Print the report either way — the report is what tells the user cleanup is over.
10
+
9
11
  ## Preconditions
10
12
 
11
13
  Confirm the PR is `MERGED` from provider state. A closed-unmerged PR keeps its branch and worktree: the work never landed, so deleting it destroys it.
12
14
 
13
- ## 1. Remove the worktree — link-safe
15
+ ## 1. Leave the worktree
16
+
17
+ Only if a worktree was used. This runs **before** anything is deleted — the working directory is the precondition the rest of the sequence stands on.
18
+
19
+ `git worktree remove` refuses to remove the worktree the command is standing in, and a shell whose directory is deleted out from under it fails everything afterwards with `getcwd`/`No such file or directory`. Both surface as "cleanup can't proceed", and the reflex — announcing that you are inside the directory and waiting for the user — is the stall this step exists to prevent. Moving is yours to do; it needs no one's permission, and the gate already gave it.
20
+
21
+ 1. **Resolve the anchor** — the repo's main working tree, which removing a worktree never touches:
22
+ ```bash
23
+ git -C {worktreePath} worktree list --porcelain # the first `worktree ` line is the main tree
24
+ ```
25
+ 2. **Compare resolved paths, not strings.** Resolve the current directory and `{worktreePath}` through symlinks before asking whether one contains the other; `/var/…` against `/private/var/…` reads as "already outside" and the removal then fails anyway. Match on path segments, so a sibling like `<name>-old` is not mistaken for being inside `<name>`.
26
+ 3. If the cwd is at or under `{worktreePath}`, move to the anchor now.
27
+ 4. Run every remaining command against an explicit repo — `git -C <anchor> …` — so no later step walks back into the tree it is deleting.
28
+
29
+ **Verify:** the resolved cwd is outside `{worktreePath}` **and** still exists (a directory that resolves, not just a string). A cwd that was deleted rather than left is the same stall arriving one step later.
30
+
31
+ ## 2. Remove the worktree — link-safe
14
32
 
15
33
  Only if a worktree was used.
16
34
 
@@ -26,7 +44,7 @@ A worktree's dependency dir (`node_modules`, and nested workspace copies) is oft
26
44
 
27
45
  **Verify:** the path is gone, it no longer appears in `git worktree list`, **and** the shared dependency tree the links pointed at still exists. That last check is the one that catches a link-follow.
28
46
 
29
- ## 2. Delete the local branch
47
+ ## 3. Delete the local branch
30
48
 
31
49
  **Skip entirely when no worktree was used** — the branch is then the user's live checkout, and a checked-out branch must never be deleted.
32
50
 
@@ -39,7 +57,7 @@ Do not reach for `-D` on faith. Replace the ancestry check with a content check:
39
57
 
40
58
  **Verify:** the branch is absent from `git branch --list`.
41
59
 
42
- ## 3. Delete the remote branch
60
+ ## 4. Delete the remote branch
43
61
 
44
62
  **A provider that auto-deletes the head branch on merge is a setting, not a guarantee.** It can be off for the repo, off for a fork, or simply not fire. Treat auto-delete as something to detect, never as this step having run.
45
63
 
@@ -48,7 +66,7 @@ Do not reach for `-D` on faith. Replace the ancestry check with a content check:
48
66
 
49
67
  **Verify:** querying the ref returns not-found.
50
68
 
51
- ## 4. Clear the session slot
69
+ ## 5. Clear the session slot
52
70
 
53
71
  The slot is `~/.muggle-ai/muggle-do/sessions/<slug>/` — the home directory, not the project.
54
72
 
@@ -56,7 +74,7 @@ Clear it only when `prs.json` records a terminal state. A slot for a still-open
56
74
 
57
75
  **Verify:** the slot directory is gone, and no non-terminal slot was touched.
58
76
 
59
- ## 5. Clear this run's prepare artifacts
77
+ ## 6. Clear this run's prepare artifacts
60
78
 
61
79
  The PID tracker and per-service logs written during environment prep — `/tmp/muggle-test-prepare.json` and `/tmp/muggle-prepare-*.log`.
62
80
 
@@ -75,6 +93,7 @@ Cleanup — <slug> (PR #<n>, merged)
75
93
 
76
94
  | Step | Result |
77
95
  |:----------------------|:------------------------------------------------|
96
+ | Left the worktree | ✅ moved to <repo root> (cwd was inside) |
78
97
  | Worktree removed | ✅ .claude/worktrees/<name> (shared deps intact) |
79
98
  | Local branch deleted | ✅ users/<user>/<branch> |
80
99
  | Remote branch deleted | ✅ (already gone — provider auto-delete) |
@@ -51,3 +51,5 @@ Use this for feature development, local validation, and PR iteration.
51
51
 
52
52
  After merge, remove the worktree.
53
53
  See [`post-merge-cleanup.md`](post-merge-cleanup.md).
54
+
55
+ **Never remove a worktree you are standing in.** Step back to the repo's main working tree first — `git worktree remove` refuses from inside, and a deleted working directory breaks every command that follows. Relocating is part of teardown, not a reason to stop and ask.
@@ -13,3 +13,5 @@ Invoked by `/muggle-do` when the watcher forwards a PR's terminal state. On `mer
13
13
  3. **Teardown (`merged` only).** When the PR is `merged`, run [`../_shared/post-merge-cleanup.md`](../_shared/post-merge-cleanup.md) with `{worktreePath}` and `{branch}`. That file owns the teardown sequence **and its safety rules** — including skipping worktree-remove and local branch deletion when no worktree was used. This stage adds no teardown logic of its own. On `closed`, skip teardown — the branch and any worktree stay intact.
14
14
  4. Append a cleanup line to the session's `followup.log`, recording whether teardown ran.
15
15
  5. Suggest the next step per [`next-step.md`](next-step.md), passing whether teardown ran. This is the stage's last action.
16
+
17
+ **Teardown never returns a question.** It finishes with its report — every step done, skipped, or blocked-with-a-reason — so steps 4 and 5 always run. A teardown that appears to be waiting on the user has stalled mid-sequence; the stage is not complete until the report and the next-step suggestion are both printed.