@muggleai/works 5.30.0 → 5.31.0-staging.268

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.
@@ -9,7 +9,7 @@
9
9
  "name": "muggleai",
10
10
  "source": "./plugin",
11
11
  "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.",
12
- "version": "5.30.0"
12
+ "version": "5.31.0-staging.268"
13
13
  }
14
14
  ]
15
15
  }
@@ -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.30.0",
4
+ "version": "5.31.0-staging.268",
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.30.0",
7
+ "@muggleai/works@5.31.0-staging.268",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.30.0",
3
+ "version": "5.31.0-staging.268",
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.30.0",
5
+ "version": "5.31.0-staging.268",
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.30.0",
7
+ "@muggleai/works@5.31.0-staging.268",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -37,7 +37,7 @@ Pre-filters are bash builtins only (`guardrail-lib.sh`), so an irrelevant event
37
37
  | Hook event | Wrapper | Strength | Condition | Preference | Effect |
38
38
  | :--------- | :------ | :------- | :-------- | :--------- | :----- |
39
39
  | `PostToolUse` (Bash) | `guardrail-pr-opened.sh` | advise | a `gh pr create`/`gh pr ready` just succeeded | `autoWatchPR` | start a `muggle-pr-followup` watcher on the new PR |
40
- | `PostToolUse` (Bash + muggle execute/replay MCP tools) | `guardrail-record-tests.sh` | record | a unit-test command passed, an E2E run happened (execute/replay call), or an `echo "MUGGLE_E2E_SKIP: <reason>"` marker declared E2E un-runnable, or a walkthrough carrying the `build-pr-section` sentinel was posted / declared un-postable via `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` | — | set `unitTestsGreen` / `e2eRun` / `e2eSkipped` / `walkthroughPostedPrUrls` / `walkthroughSkipped` session state. A post settles only the PR its output names (`gh pr comment`/`create` print it; a `gh api` comment edit returns `html_url`); a post whose output names none is timestamped (`walkthroughPostedAt`), and a scan begun after it decides. An accepted skip records the change it was verified for — the working tree's origin repo and checked-out branch (`e2eSkipVerifiedChange`) — and settles only that change: the PRs opened for it (`prChanges`, tracked in `e2eSkipCoveredPrs`) and its checked-out branch's PR. A PR's change is its URL's repo plus the command's `--head`/`--source-branch`, or else the hook tree's branch when the command never changed directory and that tree is in the PR's repo; a PR whose change is unknown is never covered, so `Push-Location`/`cd`/`--repo` cannot hand it another change's skip |
40
+ | `PostToolUse` (Bash + muggle execute/replay MCP tools) | `guardrail-record-tests.sh` | record | a unit-test command passed, an E2E run happened (execute/replay call), or an `echo "MUGGLE_E2E_SKIP: <reason>"` marker declared E2E un-runnable, or a walkthrough carrying the `build-pr-section` sentinel was posted / declared un-postable via `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` | — | set `unitTestsGreen` / `e2eRun` / `e2eSkipped` / `walkthroughPostedPrUrls` / `walkthroughSkipped` session state. A post settles only the PR its output names (`gh pr comment`/`create` print it; a `gh api` comment edit returns `html_url`); a post whose output names none is timestamped (`walkthroughPostedAt`), and a scan begun after it decides. An accepted skip records the change it was verified for — the working tree's origin repo and checked-out branch (`e2eSkipVerifiedChange`) — and settles only that change: the PRs opened for it (`prChanges`, tracked in `e2eSkipCoveredPrs`) and its checked-out branch's PR. One whose tree's change cannot be resolved records none, never the previous declaration's, and a PR an earlier skip covered keeps that skip's record. A PR's change is its URL's repo plus the command's `--head`/`--source-branch`, or else the hook tree's branch when the command never changed directory and that tree is in the PR's repo; a PR whose change is unknown is never covered, so `Push-Location`/`cd`/`--repo` cannot hand it another change's skip |
41
41
  | `PostToolUse` (Bash) | `guardrail-pr-terminal.sh` | advise | a PR just went terminal — a `gh pr merge`/`gh pr close` success line or the `TERMINAL pr=N` line the watch scripts print in a shell result (never bare `"state":"MERGED"` metadata). Not registered on `Monitor`: a Monitor call returns before its script prints anything, so its result never carries that line | — | record `terminalPending`, direct the post-merge handoff: finalize the watcher slot, tear down per `autoCleanup`, offer next options via AskUserQuestion — or as text in the closing message while the session still owns an open PR's watch slot, since a pending AskUserQuestion holds the turn and that watcher with it |
42
42
  | `PostToolUse` (AskUserQuestion) | `guardrail-offer-ran.sh` | record | a next-options offer ran while a terminal PR was pending | — | clear `terminalPending` — the only exit for the post-merge Stop gate |
43
43
  | `PostToolUse` (Agent, Task) | `guardrail-record-delegation.sh` | record | a `muggle:acceptance-tester` was launched into the background — its response carries no finished report yet | — | append to `e2eDelegations` with the PR URLs its brief names; for a bounded window the E2E and debug-path gates hold off, and the walkthrough gate sets those PRs aside, so the session is never steered into a second browser run alongside the agent's. Shares the AskUserQuestion group's dispatch |
@@ -520,8 +520,11 @@ function withoutDelegatedPrs(prUrls, state, now) {
520
520
 
521
521
  // src/guardrails/shouldRunE2E.ts
522
522
  var MAX_E2E_BLOCKS = 3;
523
+ function isEnvironmentSkip(skip) {
524
+ return skip !== null && !CYCLE_SCOPED_SKIP_CODES.includes(skip.code);
525
+ }
523
526
  function applyE2eSkipDeclaration(state, skip) {
524
- if (!skip || CYCLE_SCOPED_SKIP_CODES.includes(skip.code)) return state;
527
+ if (!isEnvironmentSkip(skip)) return state;
525
528
  return { ...state, e2eSkipDeclaration: { code: skip.code, detail: skip.detail } };
526
529
  }
527
530
  function shouldRunE2E(state) {
@@ -682,6 +685,9 @@ function isSameChange(left, right) {
682
685
  function isPrOfSkipChange(state, prUrl) {
683
686
  return isSameChange(state.prChanges?.[prUrl], state.e2eSkipVerifiedChange);
684
687
  }
688
+ function prsOfSkipChange(state) {
689
+ return state.prsHandled.filter((prUrl) => isPrOfSkipChange(state, prUrl));
690
+ }
685
691
  function withCoveredPrs(state, prUrls) {
686
692
  const covered = new Set(state.e2eSkipCoveredPrs ?? []);
687
693
  const uncovered = prUrls.filter((prUrl) => !covered.has(prUrl));
@@ -697,12 +703,14 @@ function applyPrOpenedToSkipScope(state, prUrl, prChange) {
697
703
  return isPrOfSkipChange(recorded, prUrl) && recorded.e2eSkipDeclaration ? withCoveredPrs(recorded, [prUrl]) : recorded;
698
704
  }
699
705
  function applyE2eSkipScope(state, verifiedChange) {
700
- if (!verifiedChange || !state.e2eSkipDeclaration) return state;
706
+ if (!state.e2eSkipDeclaration) return state;
707
+ if (!verifiedChange) {
708
+ if (!state.e2eSkipVerifiedChange) return state;
709
+ const { e2eSkipVerifiedChange: _previousDeclarationChange, ...unscoped } = state;
710
+ return unscoped;
711
+ }
701
712
  const scoped = { ...state, e2eSkipVerifiedChange: verifiedChange };
702
- return withCoveredPrs(
703
- scoped,
704
- scoped.prsHandled.filter((prUrl) => isPrOfSkipChange(scoped, prUrl))
705
- );
713
+ return withCoveredPrs(scoped, prsOfSkipChange(scoped));
706
714
  }
707
715
 
708
716
  // src/pr-walkthrough/types.ts
@@ -1855,7 +1863,7 @@ function classificationGateDecision(state, input2) {
1855
1863
  // src/guardrails/stateCommit.ts
1856
1864
  function applyToolOutcome(state, outcome) {
1857
1865
  const declared = applyE2eSkipDeclaration(applyRecordedRun(state, outcome.run), outcome.acceptedSkip);
1858
- const recorded = applyE2eSkipScope(declared, outcome.skipVerifiedChange);
1866
+ const recorded = isEnvironmentSkip(outcome.acceptedSkip) ? applyE2eSkipScope(declared, outcome.skipVerifiedChange) : declared;
1859
1867
  const withWatchSkip = applyWatchSkip(recorded, outcome.isWatchSkip);
1860
1868
  const withBuildSkip = applyBuildSkip(withWatchSkip, outcome.isBuildSkip);
1861
1869
  const withWalkthroughPost = applyWalkthroughPosted(
@@ -3130,7 +3138,7 @@ function recordTests() {
3130
3138
  const judgedSkip = judgeE2eSkip(input, stateBefore);
3131
3139
  const acceptedSkip = judgedSkip?.accepted ? judgedSkip.skip : null;
3132
3140
  const walkthroughPosted = detectWalkthroughPost(input);
3133
- const isDeclaredHere = acceptedSkip !== null && !CYCLE_SCOPED_SKIP_CODES.includes(acceptedSkip.code);
3141
+ const isDeclaredHere = isEnvironmentSkip(acceptedSkip);
3134
3142
  const outcome = {
3135
3143
  kind: "tool-outcome" /* ToolOutcome */,
3136
3144
  run: {
@@ -3163,7 +3171,7 @@ function recordSkipReasonOnPrs(state, skip) {
3163
3171
  startBackgroundJob("settle-walkthrough-no-app-logic-change" /* SettleWalkthroughNoAppLogicChange */, [skip.detail], input.cwd);
3164
3172
  return;
3165
3173
  }
3166
- for (const prUrl of state.prsHandled.filter((handledPrUrl) => isPrCoveredBySkip(state, handledPrUrl))) {
3174
+ for (const prUrl of prsOfSkipChange(state)) {
3167
3175
  startBackgroundJob("settle-walkthrough-skipped" /* SettleWalkthroughSkipped */, [prUrl, skip.code, skip.detail]);
3168
3176
  }
3169
3177
  startBackgroundJob("settle-branch-walkthrough-skipped" /* SettleBranchWalkthroughSkipped */, [skip.code, skip.detail], input.cwd);
@@ -3379,7 +3387,7 @@ function walkthroughGate() {
3379
3387
  if (decision.action !== "block" /* Block */) return "{}";
3380
3388
  recordGateBlock("walkthroughBlockCount", decision.blockCount);
3381
3389
  const declaredSkip = state.e2eSkipDeclaration;
3382
- const skipOwedPrUrls = declaredSkip ? decision.owed.filter((prUrl) => isPrCoveredBySkip(state, prUrl)) : [];
3390
+ const skipOwedPrUrls = declaredSkip ? decision.owed.filter((prUrl) => isPrOfSkipChange(state, prUrl)) : [];
3383
3391
  if (declaredSkip && skipOwedPrUrls.length > 0) {
3384
3392
  return blockOnUnsettledSkip(declaredSkip, skipOwedPrUrls, decision.blockCount);
3385
3393
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.26.0",
3
- "buildId": "run-266-1",
4
- "commitSha": "c79ba47696868d4d7e5d1d884f4edf77ee3fc393",
5
- "buildTime": "2026-10-31T06:04:17Z",
3
+ "buildId": "run-268-1",
4
+ "commitSha": "034abfdee292104743ae296d7c24aa85de74066e",
5
+ "buildTime": "2026-10-31T20:23:14Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
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.30.0",
4
+ "version": "5.31.0-staging.268",
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",
@@ -62,21 +62,21 @@
62
62
  "typecheck:browser-bench": "tsc --noEmit -p internal/browser-bench/tsconfig.json"
63
63
  },
64
64
  "muggleConfig": {
65
- "electronAppVersion": "1.17.0",
65
+ "electronAppVersion": "1.22.3",
66
66
  "downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
67
- "runtimeTargetDefault": "production",
67
+ "runtimeTargetDefault": "staging",
68
68
  "checksumsByStream": {
69
69
  "production": {
70
- "win32-x64": "03687e6c511f407875354954cb1b0f8e95d4ad0602174d76388a52d1aa1554e4",
71
- "linux-x64": "10bb4d6450d52cf7872800cebfa1243f4e29668adb79117afc518473219d7aad",
72
- "darwin-arm64": "c47dfa3b3289562aba4e43ed90aba302bdbc604fa2b08949763a7f65444c8e27",
73
- "darwin-x64": "cca36704aed389bb886bb70ade6e555c93d55a540fe05b8ea531de5bde4f9d8c"
70
+ "win32-x64": "be2b541f81e40e74f084548dbb193d556e5c1a66107fe5b345c2b4408dd7febe",
71
+ "linux-x64": "e0810a4e62bd94f6935f5a216ede71725b64eaba576bb7a8cda6731c5fff8bf6",
72
+ "darwin-arm64": "12015e835605fb98a58e00ca78be0b4b854e0042de143e3546a29fb2eca49e40",
73
+ "darwin-x64": "14987dbe6e47ddab4aa09688def0f49cc65bce78398ab890059e9ff96ea8c417"
74
74
  },
75
75
  "staging": {
76
- "darwin-arm64": "2512aded2d5fa4afee86d491b154a46ba367cf34072fbd609fd16531dab1f418",
77
- "linux-x64": "88c70c34c0764c1648600c72bbdc2625e5fdfdd1bb61f9a9e664c73302211b60",
78
- "win32-x64": "597464cd252050daac65e0c3f3a210b36abc33ec4a85a882cb95fc21e369243f",
79
- "darwin-x64": "0c1520ffa260feae7f373ce9840fd9fc847803401b73e52d930d7c00a0aa45d7"
76
+ "darwin-arm64": "e5fc6928ff29239e9eba3f34dea8080f1dba9953fc444e4af5387193b55d2416",
77
+ "linux-x64": "064a9dbcfb833e3d1e67613a681d9d390503bc38f877699ce434f48c390f5ac5",
78
+ "win32-x64": "8c83ba98faab1e89416088b42ff4bfb22f57a4de43598b881ef2cb6da7af58a8",
79
+ "darwin-x64": "56ac2d80c98057b8360c526734001ef0fd1c60f60e492f11e97aae04eee955c8"
80
80
  }
81
81
  },
82
82
  "electronAppSignedFromVersion": "1.10.0",
@@ -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.30.0",
4
+ "version": "5.31.0-staging.268",
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.30.0",
7
+ "@muggleai/works@5.31.0-staging.268",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.30.0",
3
+ "version": "5.31.0-staging.268",
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.30.0",
5
+ "version": "5.31.0-staging.268",
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.30.0",
7
+ "@muggleai/works@5.31.0-staging.268",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -37,7 +37,7 @@ Pre-filters are bash builtins only (`guardrail-lib.sh`), so an irrelevant event
37
37
  | Hook event | Wrapper | Strength | Condition | Preference | Effect |
38
38
  | :--------- | :------ | :------- | :-------- | :--------- | :----- |
39
39
  | `PostToolUse` (Bash) | `guardrail-pr-opened.sh` | advise | a `gh pr create`/`gh pr ready` just succeeded | `autoWatchPR` | start a `muggle-pr-followup` watcher on the new PR |
40
- | `PostToolUse` (Bash + muggle execute/replay MCP tools) | `guardrail-record-tests.sh` | record | a unit-test command passed, an E2E run happened (execute/replay call), or an `echo "MUGGLE_E2E_SKIP: <reason>"` marker declared E2E un-runnable, or a walkthrough carrying the `build-pr-section` sentinel was posted / declared un-postable via `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` | — | set `unitTestsGreen` / `e2eRun` / `e2eSkipped` / `walkthroughPostedPrUrls` / `walkthroughSkipped` session state. A post settles only the PR its output names (`gh pr comment`/`create` print it; a `gh api` comment edit returns `html_url`); a post whose output names none is timestamped (`walkthroughPostedAt`), and a scan begun after it decides. An accepted skip records the change it was verified for — the working tree's origin repo and checked-out branch (`e2eSkipVerifiedChange`) — and settles only that change: the PRs opened for it (`prChanges`, tracked in `e2eSkipCoveredPrs`) and its checked-out branch's PR. A PR's change is its URL's repo plus the command's `--head`/`--source-branch`, or else the hook tree's branch when the command never changed directory and that tree is in the PR's repo; a PR whose change is unknown is never covered, so `Push-Location`/`cd`/`--repo` cannot hand it another change's skip |
40
+ | `PostToolUse` (Bash + muggle execute/replay MCP tools) | `guardrail-record-tests.sh` | record | a unit-test command passed, an E2E run happened (execute/replay call), or an `echo "MUGGLE_E2E_SKIP: <reason>"` marker declared E2E un-runnable, or a walkthrough carrying the `build-pr-section` sentinel was posted / declared un-postable via `echo "MUGGLE_WALKTHROUGH_SKIP: <reason>"` | — | set `unitTestsGreen` / `e2eRun` / `e2eSkipped` / `walkthroughPostedPrUrls` / `walkthroughSkipped` session state. A post settles only the PR its output names (`gh pr comment`/`create` print it; a `gh api` comment edit returns `html_url`); a post whose output names none is timestamped (`walkthroughPostedAt`), and a scan begun after it decides. An accepted skip records the change it was verified for — the working tree's origin repo and checked-out branch (`e2eSkipVerifiedChange`) — and settles only that change: the PRs opened for it (`prChanges`, tracked in `e2eSkipCoveredPrs`) and its checked-out branch's PR. One whose tree's change cannot be resolved records none, never the previous declaration's, and a PR an earlier skip covered keeps that skip's record. A PR's change is its URL's repo plus the command's `--head`/`--source-branch`, or else the hook tree's branch when the command never changed directory and that tree is in the PR's repo; a PR whose change is unknown is never covered, so `Push-Location`/`cd`/`--repo` cannot hand it another change's skip |
41
41
  | `PostToolUse` (Bash) | `guardrail-pr-terminal.sh` | advise | a PR just went terminal — a `gh pr merge`/`gh pr close` success line or the `TERMINAL pr=N` line the watch scripts print in a shell result (never bare `"state":"MERGED"` metadata). Not registered on `Monitor`: a Monitor call returns before its script prints anything, so its result never carries that line | — | record `terminalPending`, direct the post-merge handoff: finalize the watcher slot, tear down per `autoCleanup`, offer next options via AskUserQuestion — or as text in the closing message while the session still owns an open PR's watch slot, since a pending AskUserQuestion holds the turn and that watcher with it |
42
42
  | `PostToolUse` (AskUserQuestion) | `guardrail-offer-ran.sh` | record | a next-options offer ran while a terminal PR was pending | — | clear `terminalPending` — the only exit for the post-merge Stop gate |
43
43
  | `PostToolUse` (Agent, Task) | `guardrail-record-delegation.sh` | record | a `muggle:acceptance-tester` was launched into the background — its response carries no finished report yet | — | append to `e2eDelegations` with the PR URLs its brief names; for a bounded window the E2E and debug-path gates hold off, and the walkthrough gate sets those PRs aside, so the session is never steered into a second browser run alongside the agent's. Shares the AskUserQuestion group's dispatch |
@@ -520,8 +520,11 @@ function withoutDelegatedPrs(prUrls, state, now) {
520
520
 
521
521
  // src/guardrails/shouldRunE2E.ts
522
522
  var MAX_E2E_BLOCKS = 3;
523
+ function isEnvironmentSkip(skip) {
524
+ return skip !== null && !CYCLE_SCOPED_SKIP_CODES.includes(skip.code);
525
+ }
523
526
  function applyE2eSkipDeclaration(state, skip) {
524
- if (!skip || CYCLE_SCOPED_SKIP_CODES.includes(skip.code)) return state;
527
+ if (!isEnvironmentSkip(skip)) return state;
525
528
  return { ...state, e2eSkipDeclaration: { code: skip.code, detail: skip.detail } };
526
529
  }
527
530
  function shouldRunE2E(state) {
@@ -682,6 +685,9 @@ function isSameChange(left, right) {
682
685
  function isPrOfSkipChange(state, prUrl) {
683
686
  return isSameChange(state.prChanges?.[prUrl], state.e2eSkipVerifiedChange);
684
687
  }
688
+ function prsOfSkipChange(state) {
689
+ return state.prsHandled.filter((prUrl) => isPrOfSkipChange(state, prUrl));
690
+ }
685
691
  function withCoveredPrs(state, prUrls) {
686
692
  const covered = new Set(state.e2eSkipCoveredPrs ?? []);
687
693
  const uncovered = prUrls.filter((prUrl) => !covered.has(prUrl));
@@ -697,12 +703,14 @@ function applyPrOpenedToSkipScope(state, prUrl, prChange) {
697
703
  return isPrOfSkipChange(recorded, prUrl) && recorded.e2eSkipDeclaration ? withCoveredPrs(recorded, [prUrl]) : recorded;
698
704
  }
699
705
  function applyE2eSkipScope(state, verifiedChange) {
700
- if (!verifiedChange || !state.e2eSkipDeclaration) return state;
706
+ if (!state.e2eSkipDeclaration) return state;
707
+ if (!verifiedChange) {
708
+ if (!state.e2eSkipVerifiedChange) return state;
709
+ const { e2eSkipVerifiedChange: _previousDeclarationChange, ...unscoped } = state;
710
+ return unscoped;
711
+ }
701
712
  const scoped = { ...state, e2eSkipVerifiedChange: verifiedChange };
702
- return withCoveredPrs(
703
- scoped,
704
- scoped.prsHandled.filter((prUrl) => isPrOfSkipChange(scoped, prUrl))
705
- );
713
+ return withCoveredPrs(scoped, prsOfSkipChange(scoped));
706
714
  }
707
715
 
708
716
  // src/pr-walkthrough/types.ts
@@ -1855,7 +1863,7 @@ function classificationGateDecision(state, input2) {
1855
1863
  // src/guardrails/stateCommit.ts
1856
1864
  function applyToolOutcome(state, outcome) {
1857
1865
  const declared = applyE2eSkipDeclaration(applyRecordedRun(state, outcome.run), outcome.acceptedSkip);
1858
- const recorded = applyE2eSkipScope(declared, outcome.skipVerifiedChange);
1866
+ const recorded = isEnvironmentSkip(outcome.acceptedSkip) ? applyE2eSkipScope(declared, outcome.skipVerifiedChange) : declared;
1859
1867
  const withWatchSkip = applyWatchSkip(recorded, outcome.isWatchSkip);
1860
1868
  const withBuildSkip = applyBuildSkip(withWatchSkip, outcome.isBuildSkip);
1861
1869
  const withWalkthroughPost = applyWalkthroughPosted(
@@ -3130,7 +3138,7 @@ function recordTests() {
3130
3138
  const judgedSkip = judgeE2eSkip(input, stateBefore);
3131
3139
  const acceptedSkip = judgedSkip?.accepted ? judgedSkip.skip : null;
3132
3140
  const walkthroughPosted = detectWalkthroughPost(input);
3133
- const isDeclaredHere = acceptedSkip !== null && !CYCLE_SCOPED_SKIP_CODES.includes(acceptedSkip.code);
3141
+ const isDeclaredHere = isEnvironmentSkip(acceptedSkip);
3134
3142
  const outcome = {
3135
3143
  kind: "tool-outcome" /* ToolOutcome */,
3136
3144
  run: {
@@ -3163,7 +3171,7 @@ function recordSkipReasonOnPrs(state, skip) {
3163
3171
  startBackgroundJob("settle-walkthrough-no-app-logic-change" /* SettleWalkthroughNoAppLogicChange */, [skip.detail], input.cwd);
3164
3172
  return;
3165
3173
  }
3166
- for (const prUrl of state.prsHandled.filter((handledPrUrl) => isPrCoveredBySkip(state, handledPrUrl))) {
3174
+ for (const prUrl of prsOfSkipChange(state)) {
3167
3175
  startBackgroundJob("settle-walkthrough-skipped" /* SettleWalkthroughSkipped */, [prUrl, skip.code, skip.detail]);
3168
3176
  }
3169
3177
  startBackgroundJob("settle-branch-walkthrough-skipped" /* SettleBranchWalkthroughSkipped */, [skip.code, skip.detail], input.cwd);
@@ -3379,7 +3387,7 @@ function walkthroughGate() {
3379
3387
  if (decision.action !== "block" /* Block */) return "{}";
3380
3388
  recordGateBlock("walkthroughBlockCount", decision.blockCount);
3381
3389
  const declaredSkip = state.e2eSkipDeclaration;
3382
- const skipOwedPrUrls = declaredSkip ? decision.owed.filter((prUrl) => isPrCoveredBySkip(state, prUrl)) : [];
3390
+ const skipOwedPrUrls = declaredSkip ? decision.owed.filter((prUrl) => isPrOfSkipChange(state, prUrl)) : [];
3383
3391
  if (declaredSkip && skipOwedPrUrls.length > 0) {
3384
3392
  return blockOnUnsettledSkip(declaredSkip, skipOwedPrUrls, decision.blockCount);
3385
3393
  }