@muggleai/works 5.27.0-staging.244 → 5.27.0-staging.246
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/{chunk-FI6VHHZN.js → chunk-6BAB7IVG.js} +25 -2
- package/dist/{chunk-EPIM27JX.js → chunk-PT5EGMBA.js} +154 -31
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-mcp.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/.mcp.json +1 -1
- package/dist/plugin/skills/do/e2e-repair.md +2 -0
- package/dist/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +1 -0
- package/dist/release-manifest.json +3 -3
- package/dist/{src-33XQK7NM.js → src-MCJFH7AB.js} +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-mcp.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/skills/do/e2e-repair.md +2 -0
- package/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +1 -0
|
@@ -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.27.0-staging.
|
|
12
|
+
"version": "5.27.0-staging.246"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
|
@@ -5705,7 +5705,7 @@ async function checkServiceStack(params) {
|
|
|
5705
5705
|
const snapshot = await readGenerationStore({ directory: ledgerDir(), emptyRecord: EMPTY_STACK_RECORD });
|
|
5706
5706
|
const swept = sweepStackRecord({ record: snapshot.record, now: Date.now(), isAlive: isProcessAlive });
|
|
5707
5707
|
const ownLease = swept.leases.find((lease) => lease.cwd === cwd) ?? null;
|
|
5708
|
-
const base = { cwd, generation: snapshot.generation, mismatches: [], peerLease: null };
|
|
5708
|
+
const base = { cwd, generation: snapshot.generation, mismatches: [], peerLease: null, servedNeeds: [] };
|
|
5709
5709
|
const isClaimedByCaller = ownLease?.needs.some((need) => need.port === params.port) ?? false;
|
|
5710
5710
|
const peerLease = params.port === void 0 || isClaimedByCaller ? void 0 : swept.leases.find((lease) => lease.cwd !== cwd && lease.needs.some((need) => need.port === params.port));
|
|
5711
5711
|
if (peerLease !== void 0) {
|
|
@@ -5734,7 +5734,12 @@ async function checkServiceStack(params) {
|
|
|
5734
5734
|
mismatches
|
|
5735
5735
|
};
|
|
5736
5736
|
}
|
|
5737
|
-
return {
|
|
5737
|
+
return {
|
|
5738
|
+
...base,
|
|
5739
|
+
verdict: "VALID" /* Valid */,
|
|
5740
|
+
reason: "every claimed port serves the claimed code",
|
|
5741
|
+
servedNeeds: ownLease.needs
|
|
5742
|
+
};
|
|
5738
5743
|
}
|
|
5739
5744
|
async function startStackRun(params) {
|
|
5740
5745
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit: params.runGit ?? runGitCommand });
|
|
@@ -9596,6 +9601,20 @@ async function findRunStackRefusal(params) {
|
|
|
9596
9601
|
});
|
|
9597
9602
|
return REFUSING_VERDICTS.has(report.verdict) ? report : null;
|
|
9598
9603
|
}
|
|
9604
|
+
async function readServedStack(params) {
|
|
9605
|
+
if (!isLoopbackUrl(params.localUrl)) return [];
|
|
9606
|
+
const report = await checkServiceStack({ cwd: params.cwd, runGit: params.runGit, readListener: params.readListener });
|
|
9607
|
+
return report.servedNeeds;
|
|
9608
|
+
}
|
|
9609
|
+
function recordServedStack(params) {
|
|
9610
|
+
if (params.servedStack.length === 0 || params.stackChange !== null) return;
|
|
9611
|
+
const storage = getRunResultStorageService();
|
|
9612
|
+
const runResult = storage.getRunResult(params.runId);
|
|
9613
|
+
if (!runResult) return;
|
|
9614
|
+
storage.updateRunResult(params.runId, {
|
|
9615
|
+
localExecutionContext: { ...runResult.localExecutionContext, servedStack: params.servedStack }
|
|
9616
|
+
});
|
|
9617
|
+
}
|
|
9599
9618
|
async function runUnderStackLease(params) {
|
|
9600
9619
|
if (!isLoopbackUrl(params.localUrl)) return await params.execute();
|
|
9601
9620
|
const runId = randomUUID();
|
|
@@ -9985,6 +10004,7 @@ var executeTestGenerationTool = {
|
|
|
9985
10004
|
logger7.warn("Test generation refused: service stack", { verdict: stackRefusal.verdict, cwd: input.cwd });
|
|
9986
10005
|
return { content: formatRunStackRefusal(stackRefusal, input.cwd), isError: true, data: stackRefusal };
|
|
9987
10006
|
}
|
|
10007
|
+
const servedStack = await readServedStack({ cwd: input.cwd, localUrl: input.localUrl });
|
|
9988
10008
|
try {
|
|
9989
10009
|
const generationRun = await runUnderStackLease({
|
|
9990
10010
|
cwd: input.cwd,
|
|
@@ -10012,6 +10032,7 @@ var executeTestGenerationTool = {
|
|
|
10012
10032
|
generationRun.errorMessage ? `**Error:** ${generationRun.errorMessage}` : ""
|
|
10013
10033
|
].filter(Boolean).join("\n");
|
|
10014
10034
|
const stackChange = await findStackChangedDuringRun({ cwd: input.cwd, localUrl: input.localUrl });
|
|
10035
|
+
recordServedStack({ runId: generationRun.id, servedStack, stackChange });
|
|
10015
10036
|
const reportedContent = stackChange ? content + formatStackChangedDuringRun(stackChange) : content;
|
|
10016
10037
|
return {
|
|
10017
10038
|
content: reportedContent,
|
|
@@ -10051,6 +10072,7 @@ var executeReplayTool = {
|
|
|
10051
10072
|
logger7.warn("Test replay refused: service stack", { verdict: stackRefusal.verdict, cwd: input.cwd });
|
|
10052
10073
|
return { content: formatRunStackRefusal(stackRefusal, input.cwd), isError: true, data: stackRefusal };
|
|
10053
10074
|
}
|
|
10075
|
+
const servedStack = await readServedStack({ cwd: input.cwd, localUrl: input.localUrl });
|
|
10054
10076
|
try {
|
|
10055
10077
|
const replayRun = await runUnderStackLease({
|
|
10056
10078
|
cwd: input.cwd,
|
|
@@ -10079,6 +10101,7 @@ var executeReplayTool = {
|
|
|
10079
10101
|
replayRun.errorMessage ? `**Error:** ${replayRun.errorMessage}` : ""
|
|
10080
10102
|
].filter(Boolean).join("\n");
|
|
10081
10103
|
const stackChange = await findStackChangedDuringRun({ cwd: input.cwd, localUrl: input.localUrl });
|
|
10104
|
+
recordServedStack({ runId: replayRun.id, servedStack, stackChange });
|
|
10082
10105
|
const reportedContent = stackChange ? content + formatStackChangedDuringRun(stackChange) : content;
|
|
10083
10106
|
return {
|
|
10084
10107
|
content: reportedContent,
|
|
@@ -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 } from './chunk-
|
|
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';
|
|
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';
|
|
@@ -356,6 +356,16 @@ function parentIsAlive(pid) {
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
// src/pr-walkthrough/constants.ts
|
|
360
|
+
var REPORT_SENTINEL = "muggle-pr-section";
|
|
361
|
+
var WALKTHROUGH_SLOT_MARKER = "<!-- muggle-pr-walkthrough:v1 -->";
|
|
362
|
+
var WALKTHROUGH_SKIPPED_MARKER = "<!-- muggle-pr-walkthrough-status:skipped -->";
|
|
363
|
+
var WALKTHROUGH_COMMENT_HEADING = "### Muggle AI \u2014 PR visual walkthrough";
|
|
364
|
+
var RERUN_NOTICE_OPENING = "<!-- muggle-pr-walkthrough-rerun -->";
|
|
365
|
+
var SHORT_SHA_LENGTH = 7;
|
|
366
|
+
var GH_COMMENT_TIMEOUT_MS = 1e4;
|
|
367
|
+
var DEPENDENCY_BOT_LOGINS = ["dependabot[bot]", "renovate[bot]"];
|
|
368
|
+
|
|
359
369
|
// src/cli/pr-section/constants.ts
|
|
360
370
|
var DETAIL_IMAGE_WIDTH = 720;
|
|
361
371
|
var STEP_IMAGE_WIDTH = 640;
|
|
@@ -369,6 +379,84 @@ var SHORT_ID_LABEL_SEPARATOR = " \xB7 ";
|
|
|
369
379
|
var TEST_DETAILS_ANCHOR_PREFIX = "muggle-test-";
|
|
370
380
|
var STEP_PAIR_IMAGE_WIDTH = 420;
|
|
371
381
|
var MAX_COMMENT_BYTES = 64e3;
|
|
382
|
+
var FULL_COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
383
|
+
var BASELINE_FAILED_STATUS = "failed";
|
|
384
|
+
|
|
385
|
+
// src/cli/pr-section/baseline.ts
|
|
386
|
+
var refused = (refusalReason) => ({ isCredited: false, refusalReason });
|
|
387
|
+
async function resolveBaseRange(deps) {
|
|
388
|
+
const baseRef = deps.baseBranch ? `refs/remotes/origin/${deps.baseBranch}` : await deps.runGit(["symbolic-ref", "refs/remotes/origin/HEAD"], deps.cwd);
|
|
389
|
+
if (!baseRef) return null;
|
|
390
|
+
const baseTipSha = await deps.runGit(["rev-parse", "--verify", `${baseRef}^{commit}`], deps.cwd);
|
|
391
|
+
if (!baseTipSha) return null;
|
|
392
|
+
const mergeBaseSha = await deps.runGit(["merge-base", "HEAD", baseTipSha], deps.cwd);
|
|
393
|
+
return mergeBaseSha ? { mergeBaseSha, baseTipSha } : null;
|
|
394
|
+
}
|
|
395
|
+
async function isOnBaseSinceFork(sha, range, deps) {
|
|
396
|
+
const isAncestor = async (ancestor, descendant) => await deps.runGit(["merge-base", "--is-ancestor", ancestor, descendant], deps.cwd) !== null;
|
|
397
|
+
return await isAncestor(range.mergeBaseSha, sha) && await isAncestor(sha, range.baseTipSha);
|
|
398
|
+
}
|
|
399
|
+
function servedCommitsOf(baselineRun) {
|
|
400
|
+
const servedStack = baselineRun.localExecutionContext?.servedStack;
|
|
401
|
+
if (!Array.isArray(servedStack)) return [];
|
|
402
|
+
return servedStack.map((servedNeed) => servedNeed?.headSha).filter((sha) => typeof sha === "string" && FULL_COMMIT_SHA_PATTERN.test(sha));
|
|
403
|
+
}
|
|
404
|
+
async function judgeBaseline(test, baselineRunId, range, deps) {
|
|
405
|
+
if (range === null) return refused("the base branch could not be resolved in this checkout");
|
|
406
|
+
const baselineRun = deps.readRunResult(baselineRunId);
|
|
407
|
+
if (!baselineRun) return refused("no such run in the local run store");
|
|
408
|
+
if (baselineRun.id === test.runId) return refused("it is the failing run itself");
|
|
409
|
+
if (baselineRun.cloudTestCaseId !== test.testCaseId) return refused(`it ran test case ${baselineRun.cloudTestCaseId}`);
|
|
410
|
+
if (baselineRun.status !== BASELINE_FAILED_STATUS) return refused(`its status is ${baselineRun.status}`);
|
|
411
|
+
if (test.runSettingsId && baselineRun.cloudRunSettingsId && baselineRun.cloudRunSettingsId !== test.runSettingsId) {
|
|
412
|
+
return refused("it ran under a different runSettings record");
|
|
413
|
+
}
|
|
414
|
+
const servedCommits = servedCommitsOf(baselineRun);
|
|
415
|
+
if (servedCommits.length === 0) return refused("it recorded no stack it served");
|
|
416
|
+
for (const servedSha of servedCommits) {
|
|
417
|
+
if (!await isOnBaseSinceFork(servedSha, range, deps)) continue;
|
|
418
|
+
return {
|
|
419
|
+
isCredited: true,
|
|
420
|
+
failsOnBase: {
|
|
421
|
+
baselineRunId: baselineRun.id,
|
|
422
|
+
baselineSha: servedSha,
|
|
423
|
+
...baselineRun.viewUrl && { viewUrl: baselineRun.viewUrl }
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const span = `${range.mergeBaseSha.slice(0, SHORT_SHA_LENGTH)}..${range.baseTipSha.slice(0, SHORT_SHA_LENGTH)}`;
|
|
428
|
+
return refused(`none of the commits it served is on the base between ${span}`);
|
|
429
|
+
}
|
|
430
|
+
function withoutFailsOnBase(test) {
|
|
431
|
+
const uncredited = { ...test };
|
|
432
|
+
delete uncredited.failsOnBase;
|
|
433
|
+
return uncredited;
|
|
434
|
+
}
|
|
435
|
+
async function verifyBaselineFailures(report, deps) {
|
|
436
|
+
let range;
|
|
437
|
+
const tests = [];
|
|
438
|
+
for (const test of report.tests) {
|
|
439
|
+
if (test.status !== "failed") {
|
|
440
|
+
tests.push(test);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const uncredited = withoutFailsOnBase(test);
|
|
444
|
+
if (!test.baselineRunId) {
|
|
445
|
+
tests.push(uncredited);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (range === void 0) range = await resolveBaseRange(deps);
|
|
449
|
+
const judgment = await judgeBaseline(uncredited, test.baselineRunId, range, deps);
|
|
450
|
+
if (judgment.isCredited) {
|
|
451
|
+
tests.push({ ...uncredited, failsOnBase: judgment.failsOnBase });
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
deps.stderrWrite(`build-pr-section: baseline ${test.baselineRunId} for "${test.name}" not credited: ${judgment.refusalReason}
|
|
455
|
+
`);
|
|
456
|
+
tests.push(uncredited);
|
|
457
|
+
}
|
|
458
|
+
return { ...report, tests };
|
|
459
|
+
}
|
|
372
460
|
|
|
373
461
|
// src/e2e-result/types.ts
|
|
374
462
|
var UnpassedCaseStatus = /* @__PURE__ */ ((UnpassedCaseStatus2) => {
|
|
@@ -400,8 +488,12 @@ function describeUnpassedCase(unpassed) {
|
|
|
400
488
|
const outcome = unpassed.status === "failed" /* Failed */ && unpassed.failedStep !== void 0 ? `failed at step ${unpassed.failedStep}` : unpassed.status;
|
|
401
489
|
return `${outcome}: ${escapeHtml(unpassed.detail)}`;
|
|
402
490
|
}
|
|
491
|
+
function describeFailsOnBase(failsOnBase) {
|
|
492
|
+
const baselineLink = failsOnBase.viewUrl ? ` ([baseline run](${failsOnBase.viewUrl}))` : "";
|
|
493
|
+
return ` \xB7 also fails on base \`${failsOnBase.baselineSha.slice(0, SHORT_SHA_LENGTH)}\`${baselineLink}`;
|
|
494
|
+
}
|
|
403
495
|
function renderUnpassedCaseBullet(unpassed, linkText, linkTarget) {
|
|
404
|
-
return `- ${UNPASSED_STATUS_EMOJI[unpassed.status]} **${escapeHtml(withResolutionLabel(unpassed.name, unpassed.resolutionLabel))}** \u2014 ${describeUnpassedCase(unpassed)} \xB7 [${linkText}](${linkTarget})
|
|
496
|
+
return `- ${UNPASSED_STATUS_EMOJI[unpassed.status]} **${escapeHtml(withResolutionLabel(unpassed.name, unpassed.resolutionLabel))}** \u2014 ${describeUnpassedCase(unpassed)} \xB7 [${linkText}](${linkTarget})` + (unpassed.failsOnBase ? describeFailsOnBase(unpassed.failsOnBase) : "");
|
|
405
497
|
}
|
|
406
498
|
var UnpassedCaseSchema = z.object({
|
|
407
499
|
name: z.string(),
|
|
@@ -411,7 +503,8 @@ var UnpassedCaseSchema = z.object({
|
|
|
411
503
|
status: z.enum(UnpassedCaseStatus),
|
|
412
504
|
failedStep: z.number().int().positive().optional(),
|
|
413
505
|
detail: z.string(),
|
|
414
|
-
viewUrl: z.string()
|
|
506
|
+
viewUrl: z.string(),
|
|
507
|
+
failsOnBase: z.object({ baselineRunId: z.string(), baselineSha: z.string(), viewUrl: z.string().optional() }).optional()
|
|
415
508
|
});
|
|
416
509
|
var E2eResultRecordSchema = z.object({
|
|
417
510
|
total: z.number().int().nonnegative(),
|
|
@@ -467,7 +560,8 @@ function toUnpassedCase(test) {
|
|
|
467
560
|
status: "failed" /* Failed */,
|
|
468
561
|
failedStep: failureOrdinal(test, test.failureStepIndex),
|
|
469
562
|
detail: oneLineDetail(test.error),
|
|
470
|
-
viewUrl: test.viewUrl
|
|
563
|
+
viewUrl: test.viewUrl,
|
|
564
|
+
...test.failsOnBase && { failsOnBase: test.failsOnBase }
|
|
471
565
|
};
|
|
472
566
|
}
|
|
473
567
|
if (test.status === "inconclusive") {
|
|
@@ -532,6 +626,9 @@ var FailedTestSchema = z.object({
|
|
|
532
626
|
steps: z.array(StepSchema),
|
|
533
627
|
failureStepIndex: z.number().int().nonnegative(),
|
|
534
628
|
error: z.string().min(1),
|
|
629
|
+
// A run of the same test case on the PR's base the author says failed too; build-pr-section verifies it before
|
|
630
|
+
// the walkthrough credits it.
|
|
631
|
+
baselineRunId: z.string().min(1).optional(),
|
|
535
632
|
artifactsDir: z.string().min(1).optional(),
|
|
536
633
|
useCaseName: z.string().min(1).optional(),
|
|
537
634
|
description: z.string().min(1).optional(),
|
|
@@ -1054,7 +1151,7 @@ async function resolveGsScreenshotUrls(report, opts) {
|
|
|
1054
1151
|
if (gsUrls.length === 0) {
|
|
1055
1152
|
return report;
|
|
1056
1153
|
}
|
|
1057
|
-
const mcps = await import('./src-
|
|
1154
|
+
const mcps = await import('./src-MCJFH7AB.js');
|
|
1058
1155
|
const credentials = await mcps.getCallerCredentialsAsync();
|
|
1059
1156
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
1060
1157
|
stderrWrite(
|
|
@@ -1148,7 +1245,15 @@ ${err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}
|
|
|
1148
1245
|
}
|
|
1149
1246
|
return 1;
|
|
1150
1247
|
}
|
|
1151
|
-
const
|
|
1248
|
+
const verifyBaselines = opts.verifyBaselines ?? ((parsedReport) => verifyBaselineFailures(parsedReport, {
|
|
1249
|
+
cwd: process.cwd(),
|
|
1250
|
+
baseBranch: opts.baseBranch,
|
|
1251
|
+
runGit: runGitCommand,
|
|
1252
|
+
readRunResult: (runId) => local_exports.getRunResultStorageService().getRunResult(runId),
|
|
1253
|
+
stderrWrite: opts.stderrWrite
|
|
1254
|
+
}));
|
|
1255
|
+
const verifiedReport = await verifyBaselines(report);
|
|
1256
|
+
const resolvedReport = await resolveGsScreenshotUrls(verifiedReport, { stderrWrite: opts.stderrWrite });
|
|
1152
1257
|
const sentinelCost = Buffer.byteLength(`${REPORT_SECTION_SENTINEL}
|
|
1153
1258
|
`, "utf-8");
|
|
1154
1259
|
const renderedSection = buildPrSection(resolvedReport, {
|
|
@@ -1174,7 +1279,8 @@ async function buildPrSectionCommand(options) {
|
|
|
1174
1279
|
stdin: process.stdin,
|
|
1175
1280
|
stdoutWrite: (s) => process.stdout.write(s),
|
|
1176
1281
|
stderrWrite: (s) => process.stderr.write(s),
|
|
1177
|
-
maxBodyBytes
|
|
1282
|
+
maxBodyBytes,
|
|
1283
|
+
baseBranch: options.base
|
|
1178
1284
|
});
|
|
1179
1285
|
if (code !== 0) {
|
|
1180
1286
|
process.exitCode = code;
|
|
@@ -3192,16 +3298,6 @@ var NON_APP_PATH_PATTERNS = [
|
|
|
3192
3298
|
/^\.gitlab-ci\.yml$/
|
|
3193
3299
|
];
|
|
3194
3300
|
|
|
3195
|
-
// src/pr-walkthrough/constants.ts
|
|
3196
|
-
var REPORT_SENTINEL = "muggle-pr-section";
|
|
3197
|
-
var WALKTHROUGH_SLOT_MARKER = "<!-- muggle-pr-walkthrough:v1 -->";
|
|
3198
|
-
var WALKTHROUGH_SKIPPED_MARKER = "<!-- muggle-pr-walkthrough-status:skipped -->";
|
|
3199
|
-
var WALKTHROUGH_COMMENT_HEADING = "### Muggle AI \u2014 PR visual walkthrough";
|
|
3200
|
-
var RERUN_NOTICE_OPENING = "<!-- muggle-pr-walkthrough-rerun -->";
|
|
3201
|
-
var SHORT_SHA_LENGTH = 7;
|
|
3202
|
-
var GH_COMMENT_TIMEOUT_MS = 1e4;
|
|
3203
|
-
var DEPENDENCY_BOT_LOGINS = ["dependabot[bot]", "renovate[bot]"];
|
|
3204
|
-
|
|
3205
3301
|
// src/pr-walkthrough/comment.ts
|
|
3206
3302
|
function renderReservedComment() {
|
|
3207
3303
|
return `${WALKTHROUGH_SLOT_MARKER}
|
|
@@ -3411,6 +3507,7 @@ var CHECK_SUMMARY_MAX_CASES = 50;
|
|
|
3411
3507
|
var OUTCOME_CONCLUSIONS = {
|
|
3412
3508
|
["settled" /* Settled */]: "success" /* Success */,
|
|
3413
3509
|
["e2e-not-passing" /* E2eNotPassing */]: "failure" /* Failure */,
|
|
3510
|
+
["e2e-fails-on-base" /* E2eFailsOnBase */]: "neutral" /* Neutral */,
|
|
3414
3511
|
["rerun-in-progress" /* RerunInProgress */]: null,
|
|
3415
3512
|
["unsettled" /* Unsettled */]: "failure" /* Failure */,
|
|
3416
3513
|
["unreadable" /* Unreadable */]: "neutral" /* Neutral */
|
|
@@ -3427,15 +3524,32 @@ function countLabel(count, status) {
|
|
|
3427
3524
|
return count > 0 ? `${count} ${status}` : null;
|
|
3428
3525
|
}
|
|
3429
3526
|
function renderNotPassingSummary(record) {
|
|
3527
|
+
return renderCaseSummary(
|
|
3528
|
+
record,
|
|
3529
|
+
`E2E acceptance did not pass \u2014 ${unpassedTally(record)} of ${testCaseCount(record)}. Fix them, re-run the whole suite, and update the walkthrough comment; editing it re-runs this check.`
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
function renderFailsOnBaseSummary(record) {
|
|
3533
|
+
return renderCaseSummary(
|
|
3534
|
+
record,
|
|
3535
|
+
`E2E acceptance: ${unpassedTally(record)} of ${testCaseCount(record)}, each verified to fail on the base too. This PR did not cause them; fix them on the base branch.`
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
function testCaseCount(record) {
|
|
3539
|
+
return `${record.total} test ${record.total === 1 ? "case" : "cases"}`;
|
|
3540
|
+
}
|
|
3541
|
+
function unpassedTally(record) {
|
|
3430
3542
|
const failed2 = record.unpassed.filter((c) => c.status === "failed" /* Failed */).length;
|
|
3431
|
-
|
|
3543
|
+
return [
|
|
3432
3544
|
countLabel(failed2, "failed" /* Failed */),
|
|
3433
3545
|
countLabel(record.unpassed.length - failed2, "inconclusive" /* Inconclusive */)
|
|
3434
3546
|
].filter((part) => part !== null).join(", ");
|
|
3547
|
+
}
|
|
3548
|
+
function renderCaseSummary(record, headline) {
|
|
3435
3549
|
const listed = record.unpassed.slice(0, CHECK_SUMMARY_MAX_CASES).map((unpassed) => renderUnpassedCaseBullet(unpassed, "open run", unpassed.viewUrl));
|
|
3436
3550
|
const hidden = record.unpassed.length - listed.length;
|
|
3437
3551
|
return [
|
|
3438
|
-
|
|
3552
|
+
headline,
|
|
3439
3553
|
"",
|
|
3440
3554
|
...listed,
|
|
3441
3555
|
...hidden > 0 ? [`- \u2026and ${hidden} more \u2014 see the walkthrough comment.`] : []
|
|
@@ -3469,7 +3583,10 @@ function judgeComments(pr, comments, run) {
|
|
|
3469
3583
|
}
|
|
3470
3584
|
const record = latestE2eResult(bodies);
|
|
3471
3585
|
if (record && record.unpassed.length > 0) {
|
|
3472
|
-
|
|
3586
|
+
const isEveryUnpassedOnBase = record.unpassed.every(
|
|
3587
|
+
(unpassed) => unpassed.status === "failed" /* Failed */ && unpassed.failsOnBase !== void 0
|
|
3588
|
+
);
|
|
3589
|
+
return isEveryUnpassedOnBase ? { outcome: "e2e-fails-on-base" /* E2eFailsOnBase */, summary: renderFailsOnBaseSummary(record) } : { outcome: "e2e-not-passing" /* E2eNotPassing */, summary: renderNotPassingSummary(record) };
|
|
3473
3590
|
}
|
|
3474
3591
|
const outcome = verdictNow === "satisfied" /* Satisfied */ ? "settled" /* Settled */ : "unsettled" /* Unsettled */;
|
|
3475
3592
|
return { outcome, summary: FIXED_SUMMARIES[outcome] };
|
|
@@ -5375,7 +5492,7 @@ function readProcessTable(platform4, runCommand, nowMs) {
|
|
|
5375
5492
|
function resumeOnReconcile(target, runCommand, waitForExit) {
|
|
5376
5493
|
if (target.runningShortId !== void 0) {
|
|
5377
5494
|
runCommand("claude", ["stop", target.runningShortId]);
|
|
5378
|
-
if (!waitForExit(target.sessionId)) return false;
|
|
5495
|
+
if (!waitForExit(target.runningSessionId ?? target.sessionId)) return false;
|
|
5379
5496
|
}
|
|
5380
5497
|
const resumed = runCommand("claude", ["--resume", target.sessionId, "--bg", RECOVERY_RESUME_PROMPT], { cwd: target.cwd });
|
|
5381
5498
|
return resumed?.exitCode === 0;
|
|
@@ -5383,17 +5500,21 @@ function resumeOnReconcile(target, runCommand, waitForExit) {
|
|
|
5383
5500
|
|
|
5384
5501
|
// src/watch/recovery.ts
|
|
5385
5502
|
function isRecoverable(assessment, ownerSession) {
|
|
5386
|
-
const isOwnerRestartable =
|
|
5503
|
+
const isOwnerRestartable = ownerSession === void 0 ? assessment.isOwnerSessionActive === false : isIdleBackgroundSession(ownerSession);
|
|
5387
5504
|
return assessment.isRegistered && assessment.watcher === "dead" /* Dead */ && assessment.live?.state === "OPEN" /* Open */ && assessment.ownerSessionId !== void 0 && isOwnerRestartable;
|
|
5388
5505
|
}
|
|
5389
5506
|
function isSlotCloseOutDue(assessment) {
|
|
5390
5507
|
return isTerminalPrState(assessment.live?.state) && assessment.watcher === "dead" /* Dead */ && assessment.isOwnerSessionActive !== true;
|
|
5391
5508
|
}
|
|
5509
|
+
function findRunningOwner(sessionsById, ownerSessionId) {
|
|
5510
|
+
const ownerShortId = shortSessionId(ownerSessionId);
|
|
5511
|
+
return sessionsById.get(ownerSessionId) ?? [...sessionsById.values()].find((session) => session.shortId === ownerShortId);
|
|
5512
|
+
}
|
|
5392
5513
|
function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDisabled, sessionsById = /* @__PURE__ */ new Map(), isStoppedAtUsageLimit2 = () => false) {
|
|
5393
5514
|
if (isPollingDisabled) return [];
|
|
5394
5515
|
const slugsByOwner = /* @__PURE__ */ new Map();
|
|
5395
5516
|
for (const assessment of assessments) {
|
|
5396
|
-
const ownerSession = assessment.ownerSessionId === void 0 ? void 0 : sessionsById
|
|
5517
|
+
const ownerSession = assessment.ownerSessionId === void 0 ? void 0 : findRunningOwner(sessionsById, assessment.ownerSessionId);
|
|
5397
5518
|
if (!isRecoverable(assessment, ownerSession)) continue;
|
|
5398
5519
|
const ownerSessionId = assessment.ownerSessionId;
|
|
5399
5520
|
const slugs = slugsByOwner.get(ownerSessionId) ?? /* @__PURE__ */ new Set();
|
|
@@ -5405,18 +5526,20 @@ function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDis
|
|
|
5405
5526
|
for (const ownerSessionId of [...slugsByOwner.keys()].sort()) {
|
|
5406
5527
|
const recentAttemptsMs = (attemptsByOwner[ownerSessionId] ?? []).map((attemptedAt) => Date.parse(attemptedAt)).filter((attemptedAtMs) => !Number.isNaN(attemptedAtMs) && nowMs - attemptedAtMs < RECOVERY_ATTEMPT_WINDOW_MS);
|
|
5407
5528
|
const lastAttemptMs = Math.max(-Infinity, ...recentAttemptsMs);
|
|
5408
|
-
const
|
|
5529
|
+
const runningOwner = findRunningOwner(sessionsById, ownerSessionId);
|
|
5530
|
+
const isRunningAtUsageLimit = runningOwner !== void 0 && [ownerSessionId, runningOwner.sessionId].some((sessionId) => isStoppedAtUsageLimit2(sessionId));
|
|
5409
5531
|
let outcome = "resume-due" /* ResumeDue */;
|
|
5410
|
-
if (
|
|
5532
|
+
if (isRunningAtUsageLimit) outcome = "awaiting-usage-limit-reset" /* AwaitingUsageLimitReset */;
|
|
5533
|
+
else if (recentAttemptsMs.length >= RECOVERY_MAX_ATTEMPTS) outcome = "attempts-exhausted" /* AttemptsExhausted */;
|
|
5411
5534
|
else if (nowMs - lastAttemptMs < RECOVERY_COOLDOWN_MS) outcome = "cooling-down" /* CoolingDown */;
|
|
5412
5535
|
else if (resumesPlanned >= MAX_RESUMES_PER_TICK) outcome = "deferred-by-tick-cap" /* DeferredByTickCap */;
|
|
5413
5536
|
if (outcome === "resume-due" /* ResumeDue */) resumesPlanned++;
|
|
5414
|
-
const runningOwner = sessionsById.get(ownerSessionId);
|
|
5415
5537
|
recoveries.push({
|
|
5416
5538
|
ownerSessionId,
|
|
5417
5539
|
slotSlugs: [...slugsByOwner.get(ownerSessionId) ?? []].sort(),
|
|
5418
5540
|
outcome,
|
|
5419
|
-
...runningOwner?.shortId === void 0 ? {} : { ownerShortId: runningOwner.shortId }
|
|
5541
|
+
...runningOwner?.shortId === void 0 ? {} : { ownerShortId: runningOwner.shortId },
|
|
5542
|
+
...runningOwner === void 0 || runningOwner.sessionId === ownerSessionId ? {} : { ownerRunningSessionId: runningOwner.sessionId }
|
|
5420
5543
|
});
|
|
5421
5544
|
}
|
|
5422
5545
|
return recoveries;
|
|
@@ -5444,7 +5567,7 @@ function executeWatcherRecoveries(planned, attemptsByOwner, deps) {
|
|
|
5444
5567
|
return { ...recovery, cwd, outcome: "restart-refused" /* RestartRefused */ };
|
|
5445
5568
|
}
|
|
5446
5569
|
const isResumed = resumeOnReconcile(
|
|
5447
|
-
{ sessionId: recovery.ownerSessionId, cwd, runningShortId: recovery.ownerShortId },
|
|
5570
|
+
{ sessionId: recovery.ownerSessionId, cwd, runningShortId: recovery.ownerShortId, runningSessionId: recovery.ownerRunningSessionId },
|
|
5448
5571
|
deps.runCommand,
|
|
5449
5572
|
deps.waitForExit
|
|
5450
5573
|
);
|
|
@@ -6052,8 +6175,8 @@ function renderReloadReport(report, isDryRun) {
|
|
|
6052
6175
|
const lines = report.decisions.map((decision) => {
|
|
6053
6176
|
const label = decision.name ?? decision.shortId ?? decision.sessionId;
|
|
6054
6177
|
const failed2 = report.failedSessionIds.includes(decision.sessionId) ? " (restart failed)" : "";
|
|
6055
|
-
const
|
|
6056
|
-
return ` ${decision.action.padEnd(16)} ${label} ${decision.reason}${failed2}${
|
|
6178
|
+
const refused2 = report.untrustedSessionIds.includes(decision.sessionId) ? ` (left running: ${decision.cwd ?? "its workspace"} refuses a background restart \u2014 run /reload-plugins in it)` : "";
|
|
6179
|
+
return ` ${decision.action.padEnd(16)} ${label} ${decision.reason}${failed2}${refused2}`;
|
|
6057
6180
|
});
|
|
6058
6181
|
const isRestart = (action) => action === "respawn" /* Respawn */ || action === "resume-reconcile" /* ResumeReconcile */;
|
|
6059
6182
|
const respawnVerb = isDryRun ? "would restart" : "restarted";
|
|
@@ -6131,7 +6254,7 @@ function createProgram() {
|
|
|
6131
6254
|
program.command("login").description("Authenticate with Muggle AI (uses device code flow)").option("--key-name <name>", "Name for the API key").option("--key-expiry <expiry>", "API key expiry: 30d, 90d, 1y, never", "90d").action(loginCommand);
|
|
6132
6255
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
6133
6256
|
program.command("status").description("Show authentication status").action(statusCommand);
|
|
6134
|
-
program.command("build-pr-section").description("Render a muggle-do PR body evidence block from an e2e report on stdin").option("--max-body-bytes <n>", "Max UTF-8 byte budget for the PR body (default 60000)").action(buildPrSectionCommand);
|
|
6257
|
+
program.command("build-pr-section").description("Render a muggle-do PR body evidence block from an e2e report on stdin").option("--max-body-bytes <n>", "Max UTF-8 byte budget for the PR body (default 60000)").option("--base <branch>", "Branch the PR merges into, for verifying cited baseline runs (default: the remote's default branch)").action(buildPrSectionCommand);
|
|
6135
6258
|
program.command("run-view").argument("[runId]", "Run id, or a unique prefix of one (default: the newest run)").description("Show a local run's steps beside the frames they produced").option("--html", "Render a self-contained HTML page instead of terminal text").option("--open", "Open the rendered page in the default browser (implies --html)").option("--out-dir <dir>", "Directory to write the page into (default: current directory)").action(runViewCommand);
|
|
6136
6259
|
program.command("pr-walkthrough-check").description("Check that a PR's Muggle AI walkthrough comment carries a result or a stated skip").option("--repo <owner/repo>", "Repository (default: $GITHUB_REPOSITORY)").option("--pr <number>", "Pull request number (default: the Actions event payload)").option("--head-sha <sha>", "Commit the check run attaches to (default: the PR head)").option("--check-run", "Publish the verdict as a check run instead of failing the process").action(prWalkthroughCheckCommand);
|
|
6137
6260
|
program.command("checkout-freshness").description("Fetch the base branch and check that a checkout is built on its latest commit").option("--cwd <dir>", "Directory inside the checkout (default: the current directory)").option("--base <branch>", "Branch the change merges into (default: origin's default branch)").option("--no-fetch", "Check against the last fetched base instead of fetching").option("--json", "Print the full report as JSON").action(checkoutFreshnessCommand);
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
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-
|
|
1
|
+
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-PT5EGMBA.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,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-staging.
|
|
4
|
+
"version": "5.27.0-staging.246",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.27.0-staging.
|
|
3
|
+
"version": "5.27.0-staging.246",
|
|
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-staging.
|
|
5
|
+
"version": "5.27.0-staging.246",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/.mcp.json
CHANGED
|
@@ -54,6 +54,8 @@ Map each bucket to exactly one disposition. Buckets are defined in [`../_shared/
|
|
|
54
54
|
|
|
55
55
|
A `repair` disposition requires a named defect: the failing step, expected-versus-actual, and the code path it implicates. Without one it is `ask`, whatever the bucket said.
|
|
56
56
|
|
|
57
|
+
**A failure the diff cannot explain may predate it.** When a `product-defect` or `product-uxux` failure names no code path the diff touched, replay the same test case against a checkout of the base before repairing anything: a worktree at the base tip's full SHA, with its own stack claim and services, under the same runSettings record. If that run fails the same way, its disposition is `baseline` — the defect is on the base, not this change's to fix — and the entry carries that run's id as `baselineRunId` into the walkthrough report ([`../muggle-pr-visual-walkthrough/e2e-report-assembly.md`](../muggle-pr-visual-walkthrough/e2e-report-assembly.md)). If it passes, the change caused the failure and the original disposition stands. A `baseline` entry is never re-run or waived; when every remaining failure is `baseline`, the loop clears.
|
|
58
|
+
|
|
57
59
|
An Inconclusive entry Stage 6 recorded as `prerequisites unmet` never executed. It carries no bucket and none may be derived from it — there is no run to read. Its disposition is `ask`, and the question names the missing prerequisite: what failed is the project's test-plan chain, not the diff.
|
|
58
60
|
|
|
59
61
|
An Inconclusive entry Stage 6 recorded as `primary failed` never executed either: it is a record held back by its test case's failed primary. It takes no disposition of its own; the primary's entry is the one repaired, and re-running the test case runs it again.
|
|
@@ -30,6 +30,7 @@ For each result:
|
|
|
30
30
|
2. `viewUrl` — from the run result (deep-links to the specific run).
|
|
31
31
|
3. `status` — from `muggle-local-run-result-get`.
|
|
32
32
|
4. If failed: also capture `failureStepIndex`, `error`, `artifactsDir` from the run result.
|
|
33
|
+
5. If the caller hands over a `baselineRunId` for a failed entry — a run of the same test case against the base that failed too — set it on the entry. `build-pr-section` credits it only after checking the run itself: same test case, failed, same runSettings record, and a stack that served a base commit between the PR's fork point and the base tip. A credited failure reads "also fails on base" and stops holding the walkthrough check red; a refused one is named on stderr and stays a plain failure. Pass `--base <branch>` when the PR does not target the remote's default branch.
|
|
33
34
|
|
|
34
35
|
## Last-resort: no cloud refs on the run result
|
|
35
36
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"release": "5.26.0",
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-10-
|
|
3
|
+
"buildId": "run-246-1",
|
|
4
|
+
"commitSha": "a4306b9b72399d0b48c1242356d766a492e6fc1b",
|
|
5
|
+
"buildTime": "2026-10-29T17:19:18Z",
|
|
6
6
|
"serviceName": "muggle-ai-works-mcp"
|
|
7
7
|
}
|
|
@@ -1 +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, StackMismatchKind, StackPortAction, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, applyLlmSourceSelection, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppDirName, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildLlmSourcePlan, buildOnboardingPlan, buildToolInputJsonSchema, calculateFileChecksum, checkServiceStack, claimServiceStack, 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, 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-
|
|
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, StackMismatchKind, StackPortAction, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, applyLlmSourceSelection, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppDirName, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildLlmSourcePlan, buildOnboardingPlan, buildToolInputJsonSchema, calculateFileChecksum, checkServiceStack, claimServiceStack, 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, 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-6BAB7IVG.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-staging.
|
|
4
|
+
"version": "5.27.0-staging.246",
|
|
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.27.0-staging.
|
|
4
|
+
"version": "5.27.0-staging.246",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
package/plugin/.codex-mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.27.0-staging.
|
|
3
|
+
"version": "5.27.0-staging.246",
|
|
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-staging.
|
|
5
|
+
"version": "5.27.0-staging.246",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/plugin/.mcp.json
CHANGED
|
@@ -54,6 +54,8 @@ Map each bucket to exactly one disposition. Buckets are defined in [`../_shared/
|
|
|
54
54
|
|
|
55
55
|
A `repair` disposition requires a named defect: the failing step, expected-versus-actual, and the code path it implicates. Without one it is `ask`, whatever the bucket said.
|
|
56
56
|
|
|
57
|
+
**A failure the diff cannot explain may predate it.** When a `product-defect` or `product-uxux` failure names no code path the diff touched, replay the same test case against a checkout of the base before repairing anything: a worktree at the base tip's full SHA, with its own stack claim and services, under the same runSettings record. If that run fails the same way, its disposition is `baseline` — the defect is on the base, not this change's to fix — and the entry carries that run's id as `baselineRunId` into the walkthrough report ([`../muggle-pr-visual-walkthrough/e2e-report-assembly.md`](../muggle-pr-visual-walkthrough/e2e-report-assembly.md)). If it passes, the change caused the failure and the original disposition stands. A `baseline` entry is never re-run or waived; when every remaining failure is `baseline`, the loop clears.
|
|
58
|
+
|
|
57
59
|
An Inconclusive entry Stage 6 recorded as `prerequisites unmet` never executed. It carries no bucket and none may be derived from it — there is no run to read. Its disposition is `ask`, and the question names the missing prerequisite: what failed is the project's test-plan chain, not the diff.
|
|
58
60
|
|
|
59
61
|
An Inconclusive entry Stage 6 recorded as `primary failed` never executed either: it is a record held back by its test case's failed primary. It takes no disposition of its own; the primary's entry is the one repaired, and re-running the test case runs it again.
|
|
@@ -30,6 +30,7 @@ For each result:
|
|
|
30
30
|
2. `viewUrl` — from the run result (deep-links to the specific run).
|
|
31
31
|
3. `status` — from `muggle-local-run-result-get`.
|
|
32
32
|
4. If failed: also capture `failureStepIndex`, `error`, `artifactsDir` from the run result.
|
|
33
|
+
5. If the caller hands over a `baselineRunId` for a failed entry — a run of the same test case against the base that failed too — set it on the entry. `build-pr-section` credits it only after checking the run itself: same test case, failed, same runSettings record, and a stack that served a base commit between the PR's fork point and the base tip. A credited failure reads "also fails on base" and stops holding the walkthrough check red; a refused one is named on stderr and stays a plain failure. Pass `--base <branch>` when the PR does not target the remote's default branch.
|
|
33
34
|
|
|
34
35
|
## Last-resort: no cloud refs on the run result
|
|
35
36
|
|