@muggleai/works 5.28.0-staging.249 → 5.28.0-staging.251
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-HBCASFGN.js → chunk-JRYEMMKU.js} +39 -0
- package/dist/{chunk-USALT4LJ.js → chunk-XEFALLR5.js} +27 -48
- 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/agents/acceptance-tester.md +4 -0
- package/dist/plugin/skills/_shared/deliverable-categories/audio.md +29 -0
- package/dist/plugin/skills/_shared/deliverable-categories/data.md +31 -0
- package/dist/plugin/skills/_shared/deliverable-categories/image.md +34 -0
- package/dist/plugin/skills/_shared/deliverable-categories/research.md +28 -0
- package/dist/plugin/skills/_shared/deliverable-categories/video.md +32 -0
- package/dist/plugin/skills/_shared/deliverable-categories/web.md +25 -0
- package/dist/plugin/skills/_shared/deliverable-categories.md +69 -0
- package/dist/plugin/skills/_shared/dev-loop/failures.md +2 -1
- package/dist/plugin/skills/_shared/failure-mode-handling.md +5 -0
- package/dist/plugin/skills/do/e2e-acceptance.md +14 -3
- package/dist/plugin/skills/do/e2e-repair.md +3 -1
- package/dist/plugin/skills/do/impact-analysis.md +17 -3
- package/dist/plugin/skills/do/open-prs/forward.md +2 -2
- package/dist/plugin/skills/do/pre-flight.md +1 -1
- package/dist/plugin/skills/do/requirements.md +3 -3
- package/dist/plugin/skills/muggle-do/SKILL.md +2 -1
- package/dist/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +2 -0
- package/dist/release-manifest.json +3 -3
- package/dist/{src-ZOCXV6PT.js → src-7OY7RXTE.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/agents/acceptance-tester.md +4 -0
- package/plugin/skills/_shared/deliverable-categories/audio.md +29 -0
- package/plugin/skills/_shared/deliverable-categories/data.md +31 -0
- package/plugin/skills/_shared/deliverable-categories/image.md +34 -0
- package/plugin/skills/_shared/deliverable-categories/research.md +28 -0
- package/plugin/skills/_shared/deliverable-categories/video.md +32 -0
- package/plugin/skills/_shared/deliverable-categories/web.md +25 -0
- package/plugin/skills/_shared/deliverable-categories.md +69 -0
- package/plugin/skills/_shared/dev-loop/failures.md +2 -1
- package/plugin/skills/_shared/failure-mode-handling.md +5 -0
- package/plugin/skills/do/e2e-acceptance.md +14 -3
- package/plugin/skills/do/e2e-repair.md +3 -1
- package/plugin/skills/do/impact-analysis.md +17 -3
- package/plugin/skills/do/open-prs/forward.md +2 -2
- package/plugin/skills/do/pre-flight.md +1 -1
- package/plugin/skills/do/requirements.md +3 -3
- package/plugin/skills/muggle-do/SKILL.md +2 -1
- package/plugin/skills/muggle-pr-visual-walkthrough/e2e-report-assembly.md +2 -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.28.0-staging.
|
|
12
|
+
"version": "5.28.0-staging.251"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
|
@@ -991,6 +991,16 @@ var HealthStatus = /* @__PURE__ */ ((HealthStatus2) => {
|
|
|
991
991
|
HealthStatus2["Unhealthy"] = "unhealthy";
|
|
992
992
|
return HealthStatus2;
|
|
993
993
|
})(HealthStatus || {});
|
|
994
|
+
var StudioVerdict = /* @__PURE__ */ ((StudioVerdict2) => {
|
|
995
|
+
StudioVerdict2["Success"] = "success";
|
|
996
|
+
StudioVerdict2["Active"] = "active";
|
|
997
|
+
StudioVerdict2["BugDetected"] = "bug_detected";
|
|
998
|
+
StudioVerdict2["GoalNotAchievable"] = "goal_not_achievable";
|
|
999
|
+
StudioVerdict2["Failure"] = "failure";
|
|
1000
|
+
StudioVerdict2["Failed"] = "failed";
|
|
1001
|
+
StudioVerdict2["Timeout"] = "timeout";
|
|
1002
|
+
return StudioVerdict2;
|
|
1003
|
+
})(StudioVerdict || {});
|
|
994
1004
|
var TestResultStatus = /* @__PURE__ */ ((TestResultStatus2) => {
|
|
995
1005
|
TestResultStatus2["Passed"] = "passed";
|
|
996
1006
|
TestResultStatus2["Failed"] = "failed";
|
|
@@ -9883,6 +9893,33 @@ function formatElectronBrowserVisibilityLine(visibility) {
|
|
|
9883
9893
|
return `**UI:** ${label} \u2014 showElectronBrowser is "${visibility.showElectronBrowserPreference}", so showUi: ${ignoredShowUi} was ignored`;
|
|
9884
9894
|
}
|
|
9885
9895
|
|
|
9896
|
+
// packages/mcps/src/mcp/tools/local/studio-verdict-line-constants.ts
|
|
9897
|
+
var STUDIO_VERDICT_LABELS = {
|
|
9898
|
+
["success" /* Success */]: "Passed",
|
|
9899
|
+
["active" /* Active */]: "Passed",
|
|
9900
|
+
["bug_detected" /* BugDetected */]: "Bug Detected",
|
|
9901
|
+
["goal_not_achievable" /* GoalNotAchievable */]: "Goal Not Achievable",
|
|
9902
|
+
["failure" /* Failure */]: "Failed",
|
|
9903
|
+
["failed" /* Failed */]: "Failed",
|
|
9904
|
+
["timeout" /* Timeout */]: "Timed Out"
|
|
9905
|
+
};
|
|
9906
|
+
|
|
9907
|
+
// packages/mcps/src/mcp/tools/local/studio-verdict-line.ts
|
|
9908
|
+
function isStudioVerdict(rawStatus) {
|
|
9909
|
+
return Object.values(StudioVerdict).includes(rawStatus);
|
|
9910
|
+
}
|
|
9911
|
+
function formatStudioVerdictLine(studioReturnedResult) {
|
|
9912
|
+
if (typeof studioReturnedResult !== "object" || studioReturnedResult === null) {
|
|
9913
|
+
return "";
|
|
9914
|
+
}
|
|
9915
|
+
const rawStatus = studioReturnedResult.status;
|
|
9916
|
+
if (typeof rawStatus !== "string" || rawStatus.length === 0) {
|
|
9917
|
+
return "";
|
|
9918
|
+
}
|
|
9919
|
+
const verdictText = isStudioVerdict(rawStatus) ? `${STUDIO_VERDICT_LABELS[rawStatus]} (${rawStatus})` : rawStatus;
|
|
9920
|
+
return `**Studio Verdict:** ${verdictText}`;
|
|
9921
|
+
}
|
|
9922
|
+
|
|
9886
9923
|
// packages/mcps/src/mcp/tools/local/run-checkout-refusal.ts
|
|
9887
9924
|
function formatRunCheckoutRefusal(report, cwd) {
|
|
9888
9925
|
const baseName = report.baseRef ? toDisplayRef(report.baseRef) : "";
|
|
@@ -10067,6 +10104,7 @@ var runResultGetTool = {
|
|
|
10067
10104
|
`**ID:** ${storedRunResult.id}`,
|
|
10068
10105
|
`**Type:** ${storedRunResult.runType}`,
|
|
10069
10106
|
`**Status:** ${storedRunResult.status}`,
|
|
10107
|
+
formatStudioVerdictLine(storedRunResult.studioReturnedResult),
|
|
10070
10108
|
`**Cloud Test Case:** ${storedRunResult.cloudTestCaseId}`,
|
|
10071
10109
|
`**Duration:** ${storedRunResult.executionTimeMs ?? 0}ms`,
|
|
10072
10110
|
storedRunResult.errorMessage ? `**Error:** ${storedRunResult.errorMessage}` : "",
|
|
@@ -10756,6 +10794,7 @@ __export(local_exports2, {
|
|
|
10756
10794
|
SessionStatus: () => SessionStatus,
|
|
10757
10795
|
SkillTelemetryEmitInputSchema: () => SkillTelemetryEmitInputSchema,
|
|
10758
10796
|
StorageService: () => StorageService,
|
|
10797
|
+
StudioVerdict: () => StudioVerdict,
|
|
10759
10798
|
TestCaseDetailsSchema: () => TestCaseDetailsSchema,
|
|
10760
10799
|
TestResultStatus: () => TestResultStatus,
|
|
10761
10800
|
TestScriptDetailsSchema: () => TestScriptDetailsSchema,
|
|
@@ -1,11 +1,11 @@
|
|
|
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-
|
|
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-JRYEMMKU.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';
|
|
5
5
|
import { z, ZodError } from 'zod';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
|
-
import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync, readlinkSync, realpathSync, openSync, writeSync, closeSync, unlinkSync, copyFileSync, renameSync, appendFileSync, readSync
|
|
8
|
+
import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync, readlinkSync, realpathSync, openSync, writeSync, closeSync, unlinkSync, copyFileSync, renameSync, appendFileSync, readSync } from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { dirname, resolve, join, basename } from 'path';
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -1151,7 +1151,7 @@ async function resolveGsScreenshotUrls(report, opts) {
|
|
|
1151
1151
|
if (gsUrls.length === 0) {
|
|
1152
1152
|
return report;
|
|
1153
1153
|
}
|
|
1154
|
-
const mcps = await import('./src-
|
|
1154
|
+
const mcps = await import('./src-7OY7RXTE.js');
|
|
1155
1155
|
const credentials = await mcps.getCallerCredentialsAsync();
|
|
1156
1156
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
1157
1157
|
stderrWrite(
|
|
@@ -2400,8 +2400,6 @@ var TERMINAL_PR_RECHECK_MS = 6 * 60 * 6e4;
|
|
|
2400
2400
|
var WATCH_COMMAND_TIMEOUT_MS = 3e4;
|
|
2401
2401
|
var WATCH_COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
2402
2402
|
var SESSION_FILE_HEAD_BYTES = 64 * 1024;
|
|
2403
|
-
var SESSION_FILE_TAIL_BYTES = 64 * 1024;
|
|
2404
|
-
var USAGE_LIMIT_NOTICE_PATTERN = /\bhit your (?:session|usage|weekly|daily) limit\b|\busage limit reached\b/i;
|
|
2405
2403
|
var WATCH_LOCK_FILE = "watch.lock";
|
|
2406
2404
|
var WATCH_LOCK_WAIT_MS = 6e4;
|
|
2407
2405
|
var PR_WATCH_SLOTS_RELATIVE_PATH = ["muggle-do", "sessions"];
|
|
@@ -2434,6 +2432,8 @@ var PR_WATCH_LOOP_PATTERN = /pr-watch-loop\.sh\b.*?--slot\s+(?:"([^"]+)"|(\S+))/
|
|
|
2434
2432
|
var CLAUDE_SESSION_REGISTRY_DIR_NAME = "sessions";
|
|
2435
2433
|
var CLAUDE_PROJECTS_DIR_NAME = "projects";
|
|
2436
2434
|
var CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH = ["plugins", "installed_plugins.json"];
|
|
2435
|
+
var CLAUDE_GLOBAL_CONFIG_FILE_NAME = ".claude.json";
|
|
2436
|
+
var USAGE_WINDOW_EXHAUSTED_PERCENT = 100;
|
|
2437
2437
|
var RELOAD_PLUGIN_SCOPE = "user";
|
|
2438
2438
|
var MUGGLE_PLUGIN_ID = "muggleai@muggle-works";
|
|
2439
2439
|
var CODEX_ROLLOUT_PREFIX = "rollout-";
|
|
@@ -2487,6 +2487,7 @@ function resolveWatchPaths(env = process.env) {
|
|
|
2487
2487
|
claudeSessionRegistryDir: join(claudeConfigDir, CLAUDE_SESSION_REGISTRY_DIR_NAME),
|
|
2488
2488
|
claudeProjectsDir: join(claudeConfigDir, CLAUDE_PROJECTS_DIR_NAME),
|
|
2489
2489
|
claudeInstalledPluginsFile: join(claudeConfigDir, ...CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH),
|
|
2490
|
+
claudeGlobalConfigFile: join(env.CLAUDE_CONFIG_DIR || homedir(), CLAUDE_GLOBAL_CONFIG_FILE_NAME),
|
|
2490
2491
|
codexSessionsDir: join(codexHome, "sessions"),
|
|
2491
2492
|
muggleInstallLogFile: join(muggleDataDir, MUGGLE_INSTALL_LOG_FILE)
|
|
2492
2493
|
};
|
|
@@ -4638,21 +4639,6 @@ function readFileHead(filePath) {
|
|
|
4638
4639
|
if (fileDescriptor !== void 0) closeSync(fileDescriptor);
|
|
4639
4640
|
}
|
|
4640
4641
|
}
|
|
4641
|
-
function readFileTail(filePath) {
|
|
4642
|
-
let fileDescriptor;
|
|
4643
|
-
try {
|
|
4644
|
-
fileDescriptor = openSync(filePath, "r");
|
|
4645
|
-
const fileSize = fstatSync(fileDescriptor).size;
|
|
4646
|
-
const length = Math.min(fileSize, SESSION_FILE_TAIL_BYTES);
|
|
4647
|
-
const buffer = Buffer.alloc(length);
|
|
4648
|
-
const bytesRead = readSync(fileDescriptor, buffer, 0, length, fileSize - length);
|
|
4649
|
-
return buffer.subarray(0, bytesRead).toString("utf-8");
|
|
4650
|
-
} catch {
|
|
4651
|
-
return void 0;
|
|
4652
|
-
} finally {
|
|
4653
|
-
if (fileDescriptor !== void 0) closeSync(fileDescriptor);
|
|
4654
|
-
}
|
|
4655
|
-
}
|
|
4656
4642
|
function cwdFromSessionHead(head) {
|
|
4657
4643
|
const parts = head.match(/"cwd"\s*:\s*("(?:[^"\\]|\\.)*")/);
|
|
4658
4644
|
if (!parts) return void 0;
|
|
@@ -4747,36 +4733,12 @@ function findTranscriptPath(projectsDir, sessionId) {
|
|
|
4747
4733
|
}
|
|
4748
4734
|
return void 0;
|
|
4749
4735
|
}
|
|
4750
|
-
function assistantText(entry) {
|
|
4751
|
-
if (entry.type !== "assistant") return void 0;
|
|
4752
|
-
const content = entry.message?.content;
|
|
4753
|
-
if (typeof content === "string") return content;
|
|
4754
|
-
if (!Array.isArray(content)) return "";
|
|
4755
|
-
return content.map((part) => typeof part?.text === "string" ? part.text : "").join(" ");
|
|
4756
|
-
}
|
|
4757
4736
|
function findClaudeSessionCwd(projectsDir, sessionId) {
|
|
4758
4737
|
const transcriptPath = findTranscriptPath(projectsDir, sessionId);
|
|
4759
4738
|
if (transcriptPath === void 0) return void 0;
|
|
4760
4739
|
const head = readFileHead(transcriptPath);
|
|
4761
4740
|
return head === void 0 ? void 0 : cwdFromSessionHead(head);
|
|
4762
4741
|
}
|
|
4763
|
-
function isStoppedAtUsageLimit(projectsDir, sessionId) {
|
|
4764
|
-
const transcriptPath = findTranscriptPath(projectsDir, sessionId);
|
|
4765
|
-
const tail = transcriptPath === void 0 ? void 0 : readFileTail(transcriptPath);
|
|
4766
|
-
if (tail === void 0) return false;
|
|
4767
|
-
const lines = tail.split("\n");
|
|
4768
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
4769
|
-
let entry;
|
|
4770
|
-
try {
|
|
4771
|
-
entry = JSON.parse(lines[index]);
|
|
4772
|
-
} catch {
|
|
4773
|
-
continue;
|
|
4774
|
-
}
|
|
4775
|
-
const text = assistantText(entry);
|
|
4776
|
-
if (text !== void 0) return USAGE_LIMIT_NOTICE_PATTERN.test(text);
|
|
4777
|
-
}
|
|
4778
|
-
return false;
|
|
4779
|
-
}
|
|
4780
4742
|
var ROLLOUT_SESSION_ID = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
4781
4743
|
function sortedChildDirsDescending(dir) {
|
|
4782
4744
|
try {
|
|
@@ -5516,7 +5478,7 @@ function findRunningOwner(sessionsById, ownerSessionId) {
|
|
|
5516
5478
|
const ownerShortId = shortSessionId(ownerSessionId);
|
|
5517
5479
|
return sessionsById.get(ownerSessionId) ?? [...sessionsById.values()].find((session) => session.shortId === ownerShortId);
|
|
5518
5480
|
}
|
|
5519
|
-
function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDisabled, sessionsById = /* @__PURE__ */ new Map(),
|
|
5481
|
+
function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDisabled, sessionsById = /* @__PURE__ */ new Map(), isUsageLimitInForce = false) {
|
|
5520
5482
|
if (isPollingDisabled) return [];
|
|
5521
5483
|
const slugsByOwner = /* @__PURE__ */ new Map();
|
|
5522
5484
|
for (const assessment of assessments) {
|
|
@@ -5533,9 +5495,8 @@ function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDis
|
|
|
5533
5495
|
const recentAttemptsMs = (attemptsByOwner[ownerSessionId] ?? []).map((attemptedAt) => Date.parse(attemptedAt)).filter((attemptedAtMs) => !Number.isNaN(attemptedAtMs) && nowMs - attemptedAtMs < RECOVERY_ATTEMPT_WINDOW_MS);
|
|
5534
5496
|
const lastAttemptMs = Math.max(-Infinity, ...recentAttemptsMs);
|
|
5535
5497
|
const runningOwner = findRunningOwner(sessionsById, ownerSessionId);
|
|
5536
|
-
const isRunningAtUsageLimit = runningOwner !== void 0 && [ownerSessionId, runningOwner.sessionId].some((sessionId) => isStoppedAtUsageLimit2(sessionId));
|
|
5537
5498
|
let outcome = "resume-due" /* ResumeDue */;
|
|
5538
|
-
if (
|
|
5499
|
+
if (isUsageLimitInForce) outcome = "awaiting-usage-limit-reset" /* AwaitingUsageLimitReset */;
|
|
5539
5500
|
else if (recentAttemptsMs.length >= RECOVERY_MAX_ATTEMPTS) outcome = "attempts-exhausted" /* AttemptsExhausted */;
|
|
5540
5501
|
else if (nowMs - lastAttemptMs < RECOVERY_COOLDOWN_MS) outcome = "cooling-down" /* CoolingDown */;
|
|
5541
5502
|
else if (resumesPlanned >= MAX_RESUMES_PER_TICK) outcome = "deferred-by-tick-cap" /* DeferredByTickCap */;
|
|
@@ -5795,6 +5756,24 @@ function classifyTrackedProcesses(table, platform4) {
|
|
|
5795
5756
|
}
|
|
5796
5757
|
return tracked;
|
|
5797
5758
|
}
|
|
5759
|
+
function isUsageWindowExhausted(window, nowMs) {
|
|
5760
|
+
if (typeof window !== "object" || window === null) return false;
|
|
5761
|
+
const { utilization, resets_at: resetsAt, locked_reason: lockedReason } = window;
|
|
5762
|
+
const resetsAtMs = typeof resetsAt === "string" ? Date.parse(resetsAt) : Number.NaN;
|
|
5763
|
+
if (Number.isNaN(resetsAtMs) || resetsAtMs <= nowMs) return false;
|
|
5764
|
+
const isUtilizationExhausted = typeof utilization === "number" && utilization >= USAGE_WINDOW_EXHAUSTED_PERCENT;
|
|
5765
|
+
return isUtilizationExhausted || lockedReason !== null && lockedReason !== void 0;
|
|
5766
|
+
}
|
|
5767
|
+
function isClaudeUsageLimitInForce(claudeGlobalConfigFile, nowMs) {
|
|
5768
|
+
let usageWindows;
|
|
5769
|
+
try {
|
|
5770
|
+
const config = JSON.parse(readFileSync(claudeGlobalConfigFile, "utf-8"));
|
|
5771
|
+
usageWindows = config.cachedUsageUtilization?.utilization ?? {};
|
|
5772
|
+
} catch {
|
|
5773
|
+
return false;
|
|
5774
|
+
}
|
|
5775
|
+
return Object.values(usageWindows).some((usageWindow) => isUsageWindowExhausted(usageWindow, nowMs));
|
|
5776
|
+
}
|
|
5798
5777
|
function readJsonFile(filePath) {
|
|
5799
5778
|
try {
|
|
5800
5779
|
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
@@ -6055,7 +6034,7 @@ function runTickUnlocked(deps) {
|
|
|
6055
6034
|
deps.nowMs,
|
|
6056
6035
|
isPollingDisabled,
|
|
6057
6036
|
new Map(runningSessions.map((session) => [session.sessionId, session])),
|
|
6058
|
-
(
|
|
6037
|
+
isClaudeUsageLimitInForce(deps.paths.claudeGlobalConfigFile, deps.nowMs)
|
|
6059
6038
|
);
|
|
6060
6039
|
const executed = executeWatcherRecoveries(planned, recoveryAttemptsByOwner, {
|
|
6061
6040
|
runCommand: deps.runCommand,
|
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-XEFALLR5.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-JRYEMMKU.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.28.0-staging.
|
|
4
|
+
"version": "5.28.0-staging.251",
|
|
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.28.0-staging.
|
|
3
|
+
"version": "5.28.0-staging.251",
|
|
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.28.0-staging.
|
|
5
|
+
"version": "5.28.0-staging.251",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/.mcp.json
CHANGED
|
@@ -22,6 +22,8 @@ You operate through skills — never call raw MCP tools directly.
|
|
|
22
22
|
|
|
23
23
|
Select the skill based on what the orchestrator asks you to do. If the task doesn't clearly map to one skill, ask for clarification.
|
|
24
24
|
|
|
25
|
+
A criterion about a video, image, audio, data, or research artifact is verified by a deliverable check rather than a browser run, per [`../skills/_shared/deliverable-categories.md`](../skills/_shared/deliverable-categories.md). Its results are reported in the **Deliverables** block and never as a Muggle run.
|
|
26
|
+
|
|
25
27
|
The run loop these skills execute (replay/regen, timeouts, publish, screenshots) lives in [`../skills/_shared/dev-loop/run.md`](../skills/_shared/dev-loop/run.md) — you reach it through `muggle-test`, never by calling MCP tools yourself.
|
|
26
28
|
|
|
27
29
|
## Input Contract
|
|
@@ -58,6 +60,8 @@ Verdict policy (matches the CLI's `computeVerdict`):
|
|
|
58
60
|
|
|
59
61
|
A test is **inconclusive** when the run could not yield a pass/fail signal for reasons outside the product itself: no replayable script, environment precondition unmet, infra error blocked execution, agent stalled before reaching the assertion (auth/cookie banner, missing secret). The product is not implicated. If you are tempted to call a run "passed except for one that didn't really run," that one is inconclusive.
|
|
60
62
|
|
|
63
|
+
A run whose studio verdict is `bug_detected` (`**Studio Verdict:** Bug Detected (bug_detected)` on `muggle-local-run-result-get`) is **failed**: it is a product bug the run proved. Report it as a FAIL whose blocking issue is that bug in the user's app; do not retry, regenerate, or debug the test.
|
|
64
|
+
|
|
61
65
|
**Section 2 — Per-Test Highlights**
|
|
62
66
|
|
|
63
67
|
Order failures first, then inconclusives, then passes.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Audio deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for an `[audio]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
An audio file: committed at head, or produced by the repo's render or text-to-speech command.
|
|
10
|
+
|
|
11
|
+
## Probes
|
|
12
|
+
|
|
13
|
+
Read with `ffprobe` unless a step names another tool.
|
|
14
|
+
|
|
15
|
+
- Codec.
|
|
16
|
+
- Duration, against the stated length or range.
|
|
17
|
+
- Sample rate and channel count.
|
|
18
|
+
- Integrated loudness and true peak (`ffmpeg` `ebur128`), against any stated target.
|
|
19
|
+
- Silence stretches (`silencedetect`) longer than two seconds, unless the criterion calls for them.
|
|
20
|
+
|
|
21
|
+
## Review
|
|
22
|
+
|
|
23
|
+
Transcribe spoken content with a speech-to-text tool available on the machine, and compare the transcript with the script or the criterion.
|
|
24
|
+
|
|
25
|
+
## Inconclusive when
|
|
26
|
+
|
|
27
|
+
- `ffprobe` or `ffmpeg` is missing.
|
|
28
|
+
- No transcription is available. Only the spoken-content review is `INCONCLUSIVE`, with reason `no transcription available`; the probes still run and report.
|
|
29
|
+
- The audio cannot be produced for an environment reason.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Data deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for a `[data]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A dataset or export — CSV, JSON, JSONL, Parquet, seed data: committed at head, or produced by the repo's export command.
|
|
10
|
+
|
|
11
|
+
## Probes
|
|
12
|
+
|
|
13
|
+
Run as a script written outside the worktree, so the check leaves the change untouched.
|
|
14
|
+
|
|
15
|
+
- The file parses in its format.
|
|
16
|
+
- Schema fields and types.
|
|
17
|
+
- Row count, against the expected count or range.
|
|
18
|
+
- Required fields filled.
|
|
19
|
+
- Key uniqueness.
|
|
20
|
+
- Values inside stated ranges or enumerations.
|
|
21
|
+
- References to other files or tables resolve.
|
|
22
|
+
|
|
23
|
+
A changed dataset is also diffed against its base-branch version: fields added or removed, type changes, row-count delta.
|
|
24
|
+
|
|
25
|
+
## Review
|
|
26
|
+
|
|
27
|
+
Read the first rows and a random sample of rows against the criterion.
|
|
28
|
+
|
|
29
|
+
## Inconclusive when
|
|
30
|
+
|
|
31
|
+
- The dataset cannot be produced for an environment reason.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Image deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for an `[image]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A raster or vector image: committed at head, or produced by the repo's build or export command.
|
|
10
|
+
|
|
11
|
+
## Probes
|
|
12
|
+
|
|
13
|
+
Read with `ffprobe` for raster images; read an SVG's markup directly.
|
|
14
|
+
|
|
15
|
+
- Format.
|
|
16
|
+
- Pixel dimensions, or the `viewBox` for SVG.
|
|
17
|
+
- Aspect ratio.
|
|
18
|
+
- File size, against any stated budget.
|
|
19
|
+
- Alpha channel, when the criterion requires transparency.
|
|
20
|
+
|
|
21
|
+
## Review
|
|
22
|
+
|
|
23
|
+
View the image against the criterion:
|
|
24
|
+
|
|
25
|
+
- Text legible and spelled as specified.
|
|
26
|
+
- Required elements present.
|
|
27
|
+
- Nothing cropped or clipped.
|
|
28
|
+
|
|
29
|
+
A changed image is also viewed beside its base-branch version; every difference the criterion does not ask for is a finding.
|
|
30
|
+
|
|
31
|
+
## Inconclusive when
|
|
32
|
+
|
|
33
|
+
- `ffprobe` is missing for a raster image.
|
|
34
|
+
- The image cannot be produced for an environment reason.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Research deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for a `[research]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A report or brief: committed at head, or produced by the repo's generation command.
|
|
10
|
+
|
|
11
|
+
## Probes
|
|
12
|
+
|
|
13
|
+
- Every question the criterion poses has an answer, cited by section.
|
|
14
|
+
- Every claim a criterion depends on cites a source.
|
|
15
|
+
- Every cited source resolves.
|
|
16
|
+
- No placeholders remain: `TODO`, `TBD`, `[citation needed]`, an empty section.
|
|
17
|
+
- A figure quoted in two places agrees.
|
|
18
|
+
- Every source falls inside any stated recency bound.
|
|
19
|
+
|
|
20
|
+
## Review
|
|
21
|
+
|
|
22
|
+
Read each cited source and confirm it says what the claim says.
|
|
23
|
+
|
|
24
|
+
A source that answers gone or not found is a `FAIL`: a dead citation is a defect in the deliverable.
|
|
25
|
+
|
|
26
|
+
## Inconclusive when
|
|
27
|
+
|
|
28
|
+
- A source is unreachable for a network reason.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Video deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for a `[video]` criterion. Shared rules — obtaining the artifact, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** deliverable check.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
A rendered video file: committed at head, or produced by the repo's render command.
|
|
10
|
+
|
|
11
|
+
## Probes
|
|
12
|
+
|
|
13
|
+
Read with `ffprobe` unless a step names another tool.
|
|
14
|
+
|
|
15
|
+
- Container and codec.
|
|
16
|
+
- Duration, against the stated length or range.
|
|
17
|
+
- Resolution and frame rate.
|
|
18
|
+
- Audio-track presence, when the criterion implies sound.
|
|
19
|
+
- Black stretches (`ffmpeg` `blackdetect`) and frozen stretches (`freezedetect`) longer than one second, unless the criterion calls for them.
|
|
20
|
+
|
|
21
|
+
## Review
|
|
22
|
+
|
|
23
|
+
Extract a frame with `ffmpeg` at every timestamp a criterion names, plus the first frame, the last, and one every 5 seconds — at most 24 in all. View each one against the criterion:
|
|
24
|
+
|
|
25
|
+
- On-screen text present and spelled as specified.
|
|
26
|
+
- Scenes in the stated order.
|
|
27
|
+
- Captions on screen when they should be.
|
|
28
|
+
|
|
29
|
+
## Inconclusive when
|
|
30
|
+
|
|
31
|
+
- `ffprobe` or `ffmpeg` is missing.
|
|
32
|
+
- The render fails for an environment reason.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Web deliverable check
|
|
2
|
+
|
|
3
|
+
The flow for a `[web]` criterion. Shared rules — tagging, verdicts, the report entry — live in the `deliverable-categories.md` index beside this folder, which opens this file.
|
|
4
|
+
|
|
5
|
+
**Verified by:** test cases.
|
|
6
|
+
|
|
7
|
+
## Artifact
|
|
8
|
+
|
|
9
|
+
The running app at the cycle's validation target: the dev server the readiness check brings up from the PR's worktree, or the preview URL a staging replay names.
|
|
10
|
+
|
|
11
|
+
## Test cases
|
|
12
|
+
|
|
13
|
+
Each criterion maps to the impacted or new Muggle test cases that exercise it, recorded as `coveredBy` in the scope. A new test case names one atomic behavior to verify, with prerequisites resolved from the project's test-plan graph rather than written into the case.
|
|
14
|
+
|
|
15
|
+
## Review
|
|
16
|
+
|
|
17
|
+
The run's step screenshots, read against the criterion: the element the criterion names is on screen, in the stated state, at the step that asserts it.
|
|
18
|
+
|
|
19
|
+
## Inconclusive when
|
|
20
|
+
|
|
21
|
+
- No replayable script exists and generation fails.
|
|
22
|
+
- The test case's prerequisites cannot be satisfied.
|
|
23
|
+
- The runner fails before reaching the assertion for a reason outside the product: an infrastructure error, a missing secret, an auth or cookie banner it could not clear.
|
|
24
|
+
|
|
25
|
+
An app that never answers is not inconclusive: the cycle halts for readiness and owes the run.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Deliverable categories
|
|
2
|
+
|
|
3
|
+
A **deliverable** is the artifact an acceptance criterion is about. Its **deliverable category** is one of `web`, `video`, `image`, `audio`, `data`, `research` — the one list of categories, which other files reference rather than restate. No category is special-cased: each criterion is verified the way its category's flow file declares under **Verified by** — **test cases** (Muggle browser test cases, run against the app) or a **deliverable check** (obtain the artifact, probe it, review it, return a verdict). Scoping and the acceptance stage read that declaration from the criterion's category and never branch on a category's name.
|
|
4
|
+
|
|
5
|
+
## Tagging a criterion
|
|
6
|
+
|
|
7
|
+
Every criterion carries exactly one category, written as a leading tag: `- [video] The promo renders at 1080×1920 and runs 28–32 s`. A criterion spanning two categories (a page that embeds a new video) is split into one criterion per category. A criterion about something a user does in a browser is `[web]`. A criterion no category can observe — a log line, a config value, a unit-test requirement — is still tagged with the category of the artifact it concerns, and impact analysis scopes it `unobservable`.
|
|
8
|
+
|
|
9
|
+
## Obtaining the artifact
|
|
10
|
+
|
|
11
|
+
- **Committed** — a file at the branch head is checked as committed, read from the PR's worktree.
|
|
12
|
+
- **Built** — an artifact the change produces (a render, an export, a generated report) is built with the repo's own command, run from the PR's worktree at head, writing into a scratch directory outside the worktree.
|
|
13
|
+
|
|
14
|
+
A check never commits, moves, or edits what it obtains. A build that fails because the change is broken is a `FAIL`; a build that fails because the environment lacks something (a missing binary, no network) is `INCONCLUSIVE`, naming it.
|
|
15
|
+
|
|
16
|
+
## Probes and review
|
|
17
|
+
|
|
18
|
+
A **probe** measures one property and compares it with the expected value the criterion states, or the value the scope planned when the criterion implies one. A rerun on the same artifact measures the same value. A **review** is the agent's inspection of the content against the criterion; the report labels every review finding as agent judgment.
|
|
19
|
+
|
|
20
|
+
Media probes use `ffprobe`, and frame extraction and media filters use `ffmpeg`. A probe whose tool is missing makes the check `INCONCLUSIVE` with reason `probe tool missing: <tool>` — never a silent pass, never a skipped probe.
|
|
21
|
+
|
|
22
|
+
## Category flows
|
|
23
|
+
|
|
24
|
+
Every category's flow lives in its own file, so it can be customized without touching the others. Each file declares **Verified by**, then names the artifact, how it is verified, and when the verification is `INCONCLUSIVE`; the rules on this page apply to all of them. Changing a category's **Verified by** changes how its criteria are scoped and run, with no edit anywhere else.
|
|
25
|
+
|
|
26
|
+
| Category | Verified by | Flow |
|
|
27
|
+
| :-- | :-- | :-- |
|
|
28
|
+
| web | test cases | [`deliverable-categories/web.md`](deliverable-categories/web.md) |
|
|
29
|
+
| video | deliverable check | [`deliverable-categories/video.md`](deliverable-categories/video.md) |
|
|
30
|
+
| image | deliverable check | [`deliverable-categories/image.md`](deliverable-categories/image.md) |
|
|
31
|
+
| audio | deliverable check | [`deliverable-categories/audio.md`](deliverable-categories/audio.md) |
|
|
32
|
+
| data | deliverable check | [`deliverable-categories/data.md`](deliverable-categories/data.md) |
|
|
33
|
+
| research | deliverable check | [`deliverable-categories/research.md`](deliverable-categories/research.md) |
|
|
34
|
+
|
|
35
|
+
Scoping and the acceptance stage open the flow file of every category the cycle's criteria carry before planning or running anything.
|
|
36
|
+
|
|
37
|
+
## Verdict
|
|
38
|
+
|
|
39
|
+
- **PASS** — every probe and review passed.
|
|
40
|
+
- **FAIL** — a probe or review failed on an artifact that was obtained. The entry names the probe or review, the expected value, and the observed one.
|
|
41
|
+
- **INCONCLUSIVE** — the artifact could not be obtained for an environment reason, a required tool is missing, or a source could not be reached. The product is not implicated.
|
|
42
|
+
|
|
43
|
+
## Report entry
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
- (deliverable name):
|
|
47
|
+
- key: <DC key from the scope>
|
|
48
|
+
- category: <video | image | audio | data | research>
|
|
49
|
+
- artifact: <repo-relative path, or the build command and its scratch output>
|
|
50
|
+
- criteria: <the criteria it verifies>
|
|
51
|
+
- checks: [{ check, kind: probe | review, expected, observed, passed }, ...]
|
|
52
|
+
- verdict: PASS | FAIL | INCONCLUSIVE
|
|
53
|
+
- reason: <one short sentence; FAIL and INCONCLUSIVE only>
|
|
54
|
+
- evidenceDir: <scratch directory holding extracted frames, transcripts, probe output>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Evidence on the PR
|
|
58
|
+
|
|
59
|
+
Deliverable checks are run by the agent and are not recorded on the Muggle dashboard, so they never go under the walkthrough heading or its slot marker — that heading is Muggle vouching for a recorded run. They go in the PR body's `## Validation` section as one table per check:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
**[video] Promo render** — PASS
|
|
63
|
+
| Check | Kind | Expected | Observed |
|
|
64
|
+
| :-- | :-- | :-- | :-- |
|
|
65
|
+
| duration | probe | 28–32 s | 30.04 s |
|
|
66
|
+
| end-card text | review (agent judgment) | "Try Muggle" | "Try Muggle" |
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The table is followed by one line: `Deliverable checks ran in this PR's worktree at <short sha>; they are not recorded on the Muggle dashboard.` An image committed at head is embedded under its table by its blob URL at that commit, with `?raw=true`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Dev Loop — Reading the Result
|
|
2
2
|
|
|
3
|
-
For each `runId`, call `muggle-local-run-result-get` and read **structured fields**, never `execute`'s stdout tail (a truncated display excerpt). Order: `Status` → `Error` → `Artifacts`. Retain per run: `testCaseId`, `testScriptId` (if any), `runId`, `status`, `artifactsDir`, plus the record it ran: `runSettingsId` from the `cloudRunSettingsId` the result prints as `**Cloud Run Settings ID:**` (absent on a run filed under no record), and the `resolutionLabel` of the plan record it ran.
|
|
3
|
+
For each `runId`, call `muggle-local-run-result-get` and read **structured fields**, never `execute`'s stdout tail (a truncated display excerpt). Order: `Status` → `Studio Verdict` → `Error` → `Artifacts`. Retain per run: `testCaseId`, `testScriptId` (if any), `runId`, `status`, `artifactsDir`, plus the record it ran: `runSettingsId` from the `cloudRunSettingsId` the result prints as `**Cloud Run Settings ID:**` (absent on a run filed under no record), and the `resolutionLabel` of the plan record it ran.
|
|
4
4
|
|
|
5
5
|
## Artifacts
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ Per-step frames are **not** absent on the failure path — the `Artifacts` secti
|
|
|
11
11
|
## Common failures
|
|
12
12
|
|
|
13
13
|
- `Electron execution timed out after 300000ms` → orchestration wait too short; see [`timeouts.md`](timeouts.md).
|
|
14
|
+
- **`Studio Verdict: Bug Detected (bug_detected)`** → status `failed`: it is a product bug the run proved. Surface it to the user as a bug in their app; do not retry, regenerate, or debug the test.
|
|
14
15
|
- **Exit code 26** (with "LLM failed to generate / replay action script") is usually a completed exploration whose outcome was **goal not achievable** (`goal_not_achievable`, summary with `halt`) — e.g. asserting "view a completed run" on an account that has none. Read the summary; don't assume a crash. Fix the precondition: pick a project/account that already has the needed state, or narrow the goal so generation isn't forced to create resources from scratch.
|
|
15
16
|
|
|
16
17
|
Then route via [`../failure-mode-handling.md`](../failure-mode-handling.md).
|
|
@@ -41,10 +41,13 @@ When the subagent aggregates per-script results from sections B and C into one v
|
|
|
41
41
|
| `infra` (replay or regen) | B / C | **BLOCKED** |
|
|
42
42
|
| `stale-script` (replay) | B | **INCONCLUSIVE** |
|
|
43
43
|
| `product-defect` (replay) | B | **FAIL** |
|
|
44
|
+
| studio verdict `bug_detected` (replay or regen) | B / C | **FAIL** |
|
|
44
45
|
| `product-uxux` (regen) | C | **FAIL** |
|
|
45
46
|
| `agent-course` (regen) | C | **INCONCLUSIVE** (agent couldn't complete, not a PR defect) |
|
|
46
47
|
| `transient` (regen) | C | retry once; if still transient → **INCONCLUSIVE** |
|
|
47
48
|
|
|
49
|
+
A deliverable check ([`deliverable-categories.md`](deliverable-categories.md)) carries no bucket; its own `FAIL` or `INCONCLUSIVE` verdict counts as a test case's **FAIL** or **INCONCLUSIVE** here.
|
|
50
|
+
|
|
48
51
|
When a run mixes buckets across multiple test cases, take the most-significant verdict in this priority order: **FAIL > PARTIAL > BLOCKED > INCONCLUSIVE > SKIPPED > PASS**. If any one test case maps to FAIL, the run is FAIL. If some passed and some were INCONCLUSIVE, the run is PARTIAL.
|
|
49
52
|
|
|
50
53
|
### How orchestrators consume the verdict
|
|
@@ -123,6 +126,7 @@ Call `muggle-local-run-result-get` (local) or the remote equivalent and read **s
|
|
|
123
126
|
- **infra** signals: `electron-crash`, `chromium-error`, `click-no-effect-on-clickable-element`, `timeout-on-trivial-wait`, `internal-error-in-mcp-output`.
|
|
124
127
|
- **stale-script** signals: `element-not-found`, `selector-timeout`, `label-path-mismatch`, `nav-target-404`, `aria-label-changed`.
|
|
125
128
|
- **product-defect** signals: `assertion-failed-on-passing-step`, `unexpected-error-toast`, `wrong-page-after-action`, `network-500-from-app`, `form-validation-rejected-valid-input`.
|
|
129
|
+
- Studio verdict `bug_detected` (`**Studio Verdict:** Bug Detected (bug_detected)` on `muggle-local-run-result-get`) settles the bucket as **product-defect**, status `failed`: it is a product bug the run proved. Surface it to the user as a bug in their app; do not retry, regenerate, or debug the test.
|
|
126
130
|
|
|
127
131
|
If signals span multiple buckets, pick the most specific one and list all signals in telemetry — the user can override.
|
|
128
132
|
|
|
@@ -213,6 +217,7 @@ Order:
|
|
|
213
217
|
- **infra**: `electron-mcp-handler-crash`, `internal-validation-error`, `pipeline-stuck`, identical failure repeated more than twice.
|
|
214
218
|
- **agent-course**: `goal_not_achievable` with summary mentioning the agent picked a different element, looped on a modal, kept trying the same wrong action; many steps but no real progress.
|
|
215
219
|
- **product-uxux**: server 5xx in step screenshots, "page not found" reached repeatedly, expected element provably absent (visible in screenshot), product clearly broken in the artifact log.
|
|
220
|
+
- Studio verdict `bug_detected` (`**Studio Verdict:** Bug Detected (bug_detected)` on `muggle-local-run-result-get`) settles the bucket as **product-uxux**, status `failed`: it is a product bug the run proved. Surface it to the user as a bug in their app; do not retry, regenerate, or debug the test.
|
|
216
221
|
|
|
217
222
|
### Suggestions per bucket
|
|
218
223
|
|