@muggleai/works 5.31.0-staging.278 → 5.31.0-staging.279

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.31.0-staging.278"
12
+ "version": "5.31.0-staging.279"
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.31.0-staging.278",
4
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
7
+ "@muggleai/works@5.31.0-staging.279",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.278",
3
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
5
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
7
+ "@muggleai/works@5.31.0-staging.279",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -446,6 +446,7 @@ var USER_WAIVE_PHRASE = "SKIP E2E";
446
446
  var MIN_NOT_APPLICABLE_REASON_WORDS = 4;
447
447
  var DEV_SERVER_PROBE_TIMEOUT_MS = 2e3;
448
448
  var GIT_PROBE_TIMEOUT_MS = 5e3;
449
+ var BASE_REMOTE = "origin";
449
450
  var WEB_SERVER_COMMAND = /\b(vite|next\s+(dev|start)|react-scripts|ng\s+serve|nuxt|astro\s+dev|remix\s+dev|webpack(-dev)?-server|http-server|serve\s+-s|gatsby\s+develop|vue-cli-service\s+serve)\b/;
450
451
  var BROWSER_FRAMEWORK_PACKAGES = [
451
452
  "react-dom",
@@ -635,6 +636,68 @@ var defaultSkipProbes = {
635
636
  isReachable
636
637
  };
637
638
 
639
+ // src/git-dir/constants.ts
640
+ var DOT_GIT = ".git";
641
+ var GITDIR_POINTER = /^gitdir:\s*(.+?)\s*$/m;
642
+ var HEAD_BRANCH_REF = /^ref:\s*refs\/heads\/(.+?)\s*$/;
643
+ var REMOTE_HEAD_REF = /^ref:\s*refs\/remotes\/(.+?)\s*$/;
644
+ var CONFIG_SECTION_HEADER = /^\s*\[\s*([A-Za-z0-9.-]+)(?:\s+"((?:[^"\\]|\\.)*)")?\s*\]/;
645
+ var CONFIG_KEY_VALUE = /^\s*([A-Za-z][A-Za-z0-9-]*)\s*=\s*(.*)$/;
646
+ var CONFIG_INDIRECTION = /^\s*(?:\[\s*include(?:If)?\b|[^#;]*\binsteadOf\s*=)/im;
647
+
648
+ // src/git-dir/gitDir.ts
649
+ function locateGitDir(cwd, readTextFile2) {
650
+ let dir = resolve(cwd);
651
+ for (; ; ) {
652
+ const dotGit = join(dir, DOT_GIT);
653
+ const pointer = readTextFile2(dotGit)?.match(GITDIR_POINTER);
654
+ const gitDir = pointer ? resolve(dir, pointer[1]) : readTextFile2(join(dotGit, "HEAD")) !== null ? dotGit : null;
655
+ if (gitDir) {
656
+ const commonDirPointer = readTextFile2(join(gitDir, "commondir"))?.trim();
657
+ return { gitDir, commonDir: commonDirPointer ? resolve(gitDir, commonDirPointer) : gitDir };
658
+ }
659
+ const parent = dirname(dir);
660
+ if (parent === dir) return null;
661
+ dir = parent;
662
+ }
663
+ }
664
+ function readCurrentBranch(cwd, readTextFile2) {
665
+ const location = locateGitDir(cwd, readTextFile2);
666
+ const head = location ? readTextFile2(join(location.gitDir, "HEAD")) : null;
667
+ if (head === null) return null;
668
+ return head.match(HEAD_BRANCH_REF)?.[1] ?? "";
669
+ }
670
+ function parseConfigValue(rawValue) {
671
+ const value = rawValue.trim();
672
+ if (value.startsWith('"')) {
673
+ const closingQuote = value.slice(1).search(/(?<!\\)"/);
674
+ const quoted = closingQuote < 0 ? value.slice(1) : value.slice(1, closingQuote + 1);
675
+ return quoted.replace(/\\(["\\])/g, "$1");
676
+ }
677
+ return value.replace(/\s*[#;].*$/, "");
678
+ }
679
+ function readRemoteUrl(cwd, remote, readTextFile2) {
680
+ const location = locateGitDir(cwd, readTextFile2);
681
+ const config = location ? readTextFile2(join(location.commonDir, "config")) : null;
682
+ if (config === null || CONFIG_INDIRECTION.test(config)) return null;
683
+ let isInRemoteSection = false;
684
+ for (const line of config.split(/\r?\n/)) {
685
+ const header = line.match(CONFIG_SECTION_HEADER);
686
+ if (header) {
687
+ isInRemoteSection = header[1].toLowerCase() === "remote" && header[2] === remote;
688
+ continue;
689
+ }
690
+ const entry = isInRemoteSection ? line.match(CONFIG_KEY_VALUE) : null;
691
+ if (entry && entry[1].toLowerCase() === "url") return parseConfigValue(entry[2]) || null;
692
+ }
693
+ return null;
694
+ }
695
+ function readRemoteHeadBranch(cwd, remote, readTextFile2) {
696
+ const location = locateGitDir(cwd, readTextFile2);
697
+ const ref = location ? readTextFile2(join(location.commonDir, "refs", "remotes", remote, "HEAD")) : null;
698
+ return ref?.match(REMOTE_HEAD_REF)?.[1] ?? null;
699
+ }
700
+
638
701
  // src/guardrails/changeIdentity.ts
639
702
  function toRepoKey(host2, path) {
640
703
  const repoPath = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
@@ -661,21 +724,24 @@ function repoKeyOfRemoteUrl(remoteUrl) {
661
724
  return void 0;
662
725
  }
663
726
  }
664
- function resolveTreeChange(cwd, runGit2 = defaultSkipProbes.runGit) {
727
+ function resolveTreeChange(cwd, probes = defaultSkipProbes) {
665
728
  if (!cwd) return void 0;
666
- const remoteUrl = runGit2(["remote", "get-url", CHANGE_REPO_REMOTE], cwd);
667
- const repo = remoteUrl ? repoKeyOfRemoteUrl(remoteUrl) : void 0;
668
- const branch = runGit2(["branch", "--show-current"], cwd)?.trim();
669
- return repo && branch ? { repo, branch } : void 0;
729
+ const writtenUrl = readRemoteUrl(cwd, CHANGE_REPO_REMOTE, probes.readTextFile);
730
+ const writtenRepo = writtenUrl ? repoKeyOfRemoteUrl(writtenUrl) : void 0;
731
+ const remoteUrl = writtenRepo ? null : probes.runGit(["remote", "get-url", CHANGE_REPO_REMOTE], cwd);
732
+ const repo = writtenRepo ?? (remoteUrl ? repoKeyOfRemoteUrl(remoteUrl) : void 0);
733
+ if (!repo) return void 0;
734
+ const branch = (readCurrentBranch(cwd, probes.readTextFile) ?? probes.runGit(["branch", "--show-current"], cwd))?.trim();
735
+ return branch ? { repo, branch } : void 0;
670
736
  }
671
- function resolveOpenedPrChange(command, prUrl, cwd, runGit2 = defaultSkipProbes.runGit) {
737
+ function resolveOpenedPrChange(command, prUrl, cwd, probes = defaultSkipProbes) {
672
738
  const repo = repoKeyOfPrUrl(prUrl);
673
739
  if (!repo) return void 0;
674
740
  const openAt = command.search(PR_OPEN_COMMAND);
675
741
  const headFlag = openAt < 0 ? null : command.slice(openAt).match(PR_HEAD_BRANCH_FLAG);
676
742
  if (headFlag) return { repo, branch: headFlag[1].slice(headFlag[1].lastIndexOf(":") + 1) };
677
743
  if (DIRECTORY_CHANGE_COMMAND.test(command)) return void 0;
678
- const treeChange = resolveTreeChange(cwd, runGit2);
744
+ const treeChange = resolveTreeChange(cwd, probes);
679
745
  return treeChange?.repo === repo ? treeChange : void 0;
680
746
  }
681
747
  function isSameChange(left, right) {
@@ -2378,8 +2444,7 @@ var verifyDevServerUnreachable = (context, detail) => {
2378
2444
  };
2379
2445
  var verifyEmptyDiff = (context) => {
2380
2446
  if (!context.cwd) return refuted("no working directory was available to inspect");
2381
- const head = context.probe.runGit(["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], context.cwd);
2382
- const base = head?.trim();
2447
+ const base = (readRemoteHeadBranch(context.cwd, BASE_REMOTE, context.probe.readTextFile) ?? context.probe.runGit(["symbolic-ref", `refs/remotes/${BASE_REMOTE}/HEAD`, "--short"], context.cwd))?.trim();
2383
2448
  if (!base) return refuted("the repo's base branch could not be resolved");
2384
2449
  const diff = context.probe.runGit(["diff", `${base}...HEAD`, "--stat"], context.cwd);
2385
2450
  if (diff === null) return refuted("the diff against the base branch could not be read");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.26.0",
3
- "buildId": "run-278-1",
4
- "commitSha": "eb4ff94a05a224818f6e1e2c273fbf00e6b9b76c",
5
- "buildTime": "2026-11-01T21:49:32Z",
3
+ "buildId": "run-279-1",
4
+ "commitSha": "999126987b03bfc3a3b2f8fbc337415e695fbbab",
5
+ "buildTime": "2026-11-02T08:18:32Z",
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.31.0-staging.278",
4
+ "version": "5.31.0-staging.279",
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",
@@ -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.31.0-staging.278",
4
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
7
+ "@muggleai/works@5.31.0-staging.279",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.278",
3
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
5
+ "version": "5.31.0-staging.279",
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.31.0-staging.278",
7
+ "@muggleai/works@5.31.0-staging.279",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -446,6 +446,7 @@ var USER_WAIVE_PHRASE = "SKIP E2E";
446
446
  var MIN_NOT_APPLICABLE_REASON_WORDS = 4;
447
447
  var DEV_SERVER_PROBE_TIMEOUT_MS = 2e3;
448
448
  var GIT_PROBE_TIMEOUT_MS = 5e3;
449
+ var BASE_REMOTE = "origin";
449
450
  var WEB_SERVER_COMMAND = /\b(vite|next\s+(dev|start)|react-scripts|ng\s+serve|nuxt|astro\s+dev|remix\s+dev|webpack(-dev)?-server|http-server|serve\s+-s|gatsby\s+develop|vue-cli-service\s+serve)\b/;
450
451
  var BROWSER_FRAMEWORK_PACKAGES = [
451
452
  "react-dom",
@@ -635,6 +636,68 @@ var defaultSkipProbes = {
635
636
  isReachable
636
637
  };
637
638
 
639
+ // src/git-dir/constants.ts
640
+ var DOT_GIT = ".git";
641
+ var GITDIR_POINTER = /^gitdir:\s*(.+?)\s*$/m;
642
+ var HEAD_BRANCH_REF = /^ref:\s*refs\/heads\/(.+?)\s*$/;
643
+ var REMOTE_HEAD_REF = /^ref:\s*refs\/remotes\/(.+?)\s*$/;
644
+ var CONFIG_SECTION_HEADER = /^\s*\[\s*([A-Za-z0-9.-]+)(?:\s+"((?:[^"\\]|\\.)*)")?\s*\]/;
645
+ var CONFIG_KEY_VALUE = /^\s*([A-Za-z][A-Za-z0-9-]*)\s*=\s*(.*)$/;
646
+ var CONFIG_INDIRECTION = /^\s*(?:\[\s*include(?:If)?\b|[^#;]*\binsteadOf\s*=)/im;
647
+
648
+ // src/git-dir/gitDir.ts
649
+ function locateGitDir(cwd, readTextFile2) {
650
+ let dir = resolve(cwd);
651
+ for (; ; ) {
652
+ const dotGit = join(dir, DOT_GIT);
653
+ const pointer = readTextFile2(dotGit)?.match(GITDIR_POINTER);
654
+ const gitDir = pointer ? resolve(dir, pointer[1]) : readTextFile2(join(dotGit, "HEAD")) !== null ? dotGit : null;
655
+ if (gitDir) {
656
+ const commonDirPointer = readTextFile2(join(gitDir, "commondir"))?.trim();
657
+ return { gitDir, commonDir: commonDirPointer ? resolve(gitDir, commonDirPointer) : gitDir };
658
+ }
659
+ const parent = dirname(dir);
660
+ if (parent === dir) return null;
661
+ dir = parent;
662
+ }
663
+ }
664
+ function readCurrentBranch(cwd, readTextFile2) {
665
+ const location = locateGitDir(cwd, readTextFile2);
666
+ const head = location ? readTextFile2(join(location.gitDir, "HEAD")) : null;
667
+ if (head === null) return null;
668
+ return head.match(HEAD_BRANCH_REF)?.[1] ?? "";
669
+ }
670
+ function parseConfigValue(rawValue) {
671
+ const value = rawValue.trim();
672
+ if (value.startsWith('"')) {
673
+ const closingQuote = value.slice(1).search(/(?<!\\)"/);
674
+ const quoted = closingQuote < 0 ? value.slice(1) : value.slice(1, closingQuote + 1);
675
+ return quoted.replace(/\\(["\\])/g, "$1");
676
+ }
677
+ return value.replace(/\s*[#;].*$/, "");
678
+ }
679
+ function readRemoteUrl(cwd, remote, readTextFile2) {
680
+ const location = locateGitDir(cwd, readTextFile2);
681
+ const config = location ? readTextFile2(join(location.commonDir, "config")) : null;
682
+ if (config === null || CONFIG_INDIRECTION.test(config)) return null;
683
+ let isInRemoteSection = false;
684
+ for (const line of config.split(/\r?\n/)) {
685
+ const header = line.match(CONFIG_SECTION_HEADER);
686
+ if (header) {
687
+ isInRemoteSection = header[1].toLowerCase() === "remote" && header[2] === remote;
688
+ continue;
689
+ }
690
+ const entry = isInRemoteSection ? line.match(CONFIG_KEY_VALUE) : null;
691
+ if (entry && entry[1].toLowerCase() === "url") return parseConfigValue(entry[2]) || null;
692
+ }
693
+ return null;
694
+ }
695
+ function readRemoteHeadBranch(cwd, remote, readTextFile2) {
696
+ const location = locateGitDir(cwd, readTextFile2);
697
+ const ref = location ? readTextFile2(join(location.commonDir, "refs", "remotes", remote, "HEAD")) : null;
698
+ return ref?.match(REMOTE_HEAD_REF)?.[1] ?? null;
699
+ }
700
+
638
701
  // src/guardrails/changeIdentity.ts
639
702
  function toRepoKey(host2, path) {
640
703
  const repoPath = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
@@ -661,21 +724,24 @@ function repoKeyOfRemoteUrl(remoteUrl) {
661
724
  return void 0;
662
725
  }
663
726
  }
664
- function resolveTreeChange(cwd, runGit2 = defaultSkipProbes.runGit) {
727
+ function resolveTreeChange(cwd, probes = defaultSkipProbes) {
665
728
  if (!cwd) return void 0;
666
- const remoteUrl = runGit2(["remote", "get-url", CHANGE_REPO_REMOTE], cwd);
667
- const repo = remoteUrl ? repoKeyOfRemoteUrl(remoteUrl) : void 0;
668
- const branch = runGit2(["branch", "--show-current"], cwd)?.trim();
669
- return repo && branch ? { repo, branch } : void 0;
729
+ const writtenUrl = readRemoteUrl(cwd, CHANGE_REPO_REMOTE, probes.readTextFile);
730
+ const writtenRepo = writtenUrl ? repoKeyOfRemoteUrl(writtenUrl) : void 0;
731
+ const remoteUrl = writtenRepo ? null : probes.runGit(["remote", "get-url", CHANGE_REPO_REMOTE], cwd);
732
+ const repo = writtenRepo ?? (remoteUrl ? repoKeyOfRemoteUrl(remoteUrl) : void 0);
733
+ if (!repo) return void 0;
734
+ const branch = (readCurrentBranch(cwd, probes.readTextFile) ?? probes.runGit(["branch", "--show-current"], cwd))?.trim();
735
+ return branch ? { repo, branch } : void 0;
670
736
  }
671
- function resolveOpenedPrChange(command, prUrl, cwd, runGit2 = defaultSkipProbes.runGit) {
737
+ function resolveOpenedPrChange(command, prUrl, cwd, probes = defaultSkipProbes) {
672
738
  const repo = repoKeyOfPrUrl(prUrl);
673
739
  if (!repo) return void 0;
674
740
  const openAt = command.search(PR_OPEN_COMMAND);
675
741
  const headFlag = openAt < 0 ? null : command.slice(openAt).match(PR_HEAD_BRANCH_FLAG);
676
742
  if (headFlag) return { repo, branch: headFlag[1].slice(headFlag[1].lastIndexOf(":") + 1) };
677
743
  if (DIRECTORY_CHANGE_COMMAND.test(command)) return void 0;
678
- const treeChange = resolveTreeChange(cwd, runGit2);
744
+ const treeChange = resolveTreeChange(cwd, probes);
679
745
  return treeChange?.repo === repo ? treeChange : void 0;
680
746
  }
681
747
  function isSameChange(left, right) {
@@ -2378,8 +2444,7 @@ var verifyDevServerUnreachable = (context, detail) => {
2378
2444
  };
2379
2445
  var verifyEmptyDiff = (context) => {
2380
2446
  if (!context.cwd) return refuted("no working directory was available to inspect");
2381
- const head = context.probe.runGit(["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], context.cwd);
2382
- const base = head?.trim();
2447
+ const base = (readRemoteHeadBranch(context.cwd, BASE_REMOTE, context.probe.readTextFile) ?? context.probe.runGit(["symbolic-ref", `refs/remotes/${BASE_REMOTE}/HEAD`, "--short"], context.cwd))?.trim();
2383
2448
  if (!base) return refuted("the repo's base branch could not be resolved");
2384
2449
  const diff = context.probe.runGit(["diff", `${base}...HEAD`, "--stat"], context.cwd);
2385
2450
  if (diff === null) return refuted("the diff against the base branch could not be read");