@skyramp/mcp 0.3.2 → 0.3.4
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/build/index.js +47 -2
- package/build/playwright/PlaywrightTraceService.d.ts +8 -0
- package/build/playwright/PlaywrightTraceService.js +1 -0
- package/build/playwright/registerPlaywrightTools.js +42 -1
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +19 -0
- package/build/prompts/pom-aware-code-reuse.js +17 -8
- package/build/prompts/test-maintenance/actionsInstructions.js +2 -2
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -4
- package/build/prompts/test-recommendation/recommendationSections.d.ts +1 -1
- package/build/prompts/test-recommendation/recommendationSections.js +5 -5
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +13 -7
- package/build/prompts/testbot/testbot-prompts.d.ts +2 -12
- package/build/prompts/testbot/testbot-prompts.js +28 -16
- package/build/recommendation/discriminators.d.ts +7 -1
- package/build/recommendation/discriminators.js +16 -3
- package/build/resources/testbotResource.js +20 -4
- package/build/services/ScenarioGenerationService.js +5 -2
- package/build/services/TestExecutionService.d.ts +13 -8
- package/build/services/TestExecutionService.js +73 -26
- package/build/services/TestGenerationService.js +24 -9
- package/build/services/containerEnv.d.ts +12 -1
- package/build/services/containerEnv.js +118 -1
- package/build/tools/executeSkyrampTestTool.d.ts +9 -0
- package/build/tools/executeSkyrampTestTool.js +20 -6
- package/build/tools/execution-video-state.d.ts +21 -0
- package/build/tools/execution-video-state.js +51 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +31 -11
- package/build/tools/generate-tests/planGuard.d.ts +5 -5
- package/build/tools/generate-tests/planGuard.js +5 -17
- package/build/tools/queryProxyMocksTool.js +0 -1
- package/build/tools/submitReportTool.d.ts +83 -10
- package/build/tools/submitReportTool.js +179 -29
- package/build/tools/test-management/actionsTool.js +52 -41
- package/build/tools/test-management/analyzeChangesTool.d.ts +11 -0
- package/build/tools/test-management/analyzeChangesTool.js +37 -33
- package/build/tools/test-management/analyzeTestHealthTool.js +3 -3
- package/build/tools/test-management/registerTestPlanTool.js +113 -31
- package/build/types/TestAnalysis.d.ts +7 -3
- package/build/types/TestExecution.d.ts +14 -0
- package/build/types/TestTypes.js +3 -2
- package/build/types/TestbotPromptOptions.d.ts +34 -0
- package/build/types/TestbotPromptOptions.js +1 -0
- package/build/types/TestbotReport.d.ts +10 -0
- package/build/types/TestbotReport.js +10 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +36 -2
- package/build/utils/AnalysisStateManager.js +34 -13
- package/build/utils/frontendSelectors.js +0 -1
- package/build/utils/gitStaging.d.ts +5 -0
- package/build/utils/gitStaging.js +1 -1
- package/build/utils/pom-catalog.d.ts +23 -0
- package/build/utils/pom-catalog.js +30 -0
- package/build/utils/pom-scope/pom-files.d.ts +14 -0
- package/build/utils/pom-scope/pom-files.js +32 -6
- package/build/utils/pom-scope/testIdDiscovery.d.ts +40 -0
- package/build/utils/pom-scope/testIdDiscovery.js +104 -0
- package/build/utils/reportVerification.d.ts +7 -2
- package/build/utils/reportVerification.js +9 -3
- package/build/utils/scenarioDrafting.js +7 -1
- package/build/utils/skyrampMdContent.d.ts +1 -1
- package/build/utils/skyrampMdContent.js +1 -1
- package/build/utils/urlPath.d.ts +37 -0
- package/build/utils/urlPath.js +55 -0
- package/build/utils/utils.d.ts +45 -0
- package/build/utils/utils.js +50 -0
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +15 -15
- package/build/utils/workspaceAuth.js +32 -17
- package/build/workspace/queryParamResolution.d.ts +93 -0
- package/build/workspace/queryParamResolution.js +201 -0
- package/build/workspace/workspace.d.ts +104 -0
- package/build/workspace/workspace.js +24 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +319 -266
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +154 -27
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +6 -2
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +3 -2
- package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +4 -3
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +15 -3
- package/node_modules/playwright/lib/mcp/skyramp/specImport.js +781 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -3
- package/node_modules/playwright/lib/mcp/test/resultCode.test.js +2 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +1 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +30 -0
- package/node_modules/playwright/lib/transform/babelBundleImpl.js +200 -199
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/lambda-edge/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/helper/websocket/index.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/context-storage/index.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/language/language.d.ts +18 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/body.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/types.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/package.json +29 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/specReader.js +781 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +25 -6
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +253 -27
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-D0BjbCb7.js → codeMirrorModule-DtudTj_v.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +422 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bzd72-bG.js → codeMirrorModule-FNMuBzX1.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +1035 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +4 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/specReader.ts +1028 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +31 -8
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +10 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/tsconfig.build.tsbuildinfo +0 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +0 -193
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DzxTioTK.js +0 -809
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.BGc30U3S.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.IaDrb29A.js +0 -5
|
@@ -6,6 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
import yaml from "js-yaml";
|
|
7
7
|
import { simpleGit } from "simple-git";
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
|
+
import { dualChannelResult } from "../../utils/utils.js";
|
|
9
10
|
import { parseWorkspaceAuthType, getDefaultAuthHeader, WorkspaceAuthType, readWorkspaceConfigRaw } from "../../utils/workspaceAuth.js";
|
|
10
11
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
11
12
|
import { makeProgressReporter } from "../../utils/progress.js";
|
|
@@ -367,6 +368,14 @@ These components have no DOM presence in the running app: no route, page, or oth
|
|
|
367
368
|
- Log an \`issuesFound\` entry (high severity) naming the unintegrated file(s) above and stating that the changed code is unreachable in the running app.
|
|
368
369
|
- Backend/other work in this PR is unaffected — proceed with it normally.`;
|
|
369
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Assemble the tool result. See `dualChannelResult` for why the payload is
|
|
373
|
+
* carried on both result channels.
|
|
374
|
+
*/
|
|
375
|
+
export function buildAnalyzeChangesResult(parts) {
|
|
376
|
+
const executionPlan = `\`\`\`json\n${parts.structuredSummary}\n\`\`\`\n\n## UI Blueprint Capture — do this BEFORE writing UI recommendation reasoning\n${parts.uiInstructions}\n\n${parts.outputText}\n\n---\n\n## Pre-built Test Catalog — Fill in placeholders from source code, then display verbatim\n⚠️ Do NOT reformat, rename sections, or generate a new catalog. Replace \`<…from source>\` values, then show this output exactly as-is, grouped by test type.\n\n${parts.recommendationPrompt}`;
|
|
377
|
+
return dualChannelResult({ executionPlan });
|
|
378
|
+
}
|
|
370
379
|
export function registerAnalyzeChangesTool(server) {
|
|
371
380
|
server.registerTool(TOOL_NAME, {
|
|
372
381
|
annotations: {
|
|
@@ -376,8 +385,14 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
376
385
|
openWorldHint: true, // may fetch PR comments from GitHub
|
|
377
386
|
},
|
|
378
387
|
description: `Scan repository API endpoints and discover existing tests — first step of the unified Test Health Analysis Flow. Returns a stateFile path and ranked test recommendations. Pass stateFile to skyramp_analyze_test_health and skyramp_actions.`,
|
|
379
|
-
//
|
|
380
|
-
//
|
|
388
|
+
// SKYR-4182: the payload is returned via structuredContent, which requires a
|
|
389
|
+
// declared outputSchema. This also keeps the result out of the single-line
|
|
390
|
+
// `.json` spill shape that Read cannot paginate — see buildAnalyzeChangesResult.
|
|
391
|
+
outputSchema: {
|
|
392
|
+
executionPlan: z
|
|
393
|
+
.string()
|
|
394
|
+
.describe("Analysis summary, UI blueprint capture instructions, and the ranked Execution Plan. Read and follow this field."),
|
|
395
|
+
},
|
|
381
396
|
inputSchema: analyzeChangesInputSchema,
|
|
382
397
|
}, async (params, extra) => {
|
|
383
398
|
let errorResult;
|
|
@@ -419,12 +434,9 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
419
434
|
logger.info("All user-changed files are non-application — skipping analysis", {
|
|
420
435
|
changedFiles: filesToCheck,
|
|
421
436
|
});
|
|
422
|
-
return {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
text: `All ${filesToCheck.length} changed file(s) are non-application (CI/CD, docs, lock files, config). No test analysis needed for this diff.\n\nChanged files: ${filesToCheck.join(", ")}`,
|
|
426
|
-
}],
|
|
427
|
-
};
|
|
437
|
+
return dualChannelResult({
|
|
438
|
+
executionPlan: `All ${filesToCheck.length} changed file(s) are non-application (CI/CD, docs, lock files, config). No test analysis needed for this diff.\n\nChanged files: ${filesToCheck.join(", ")}`,
|
|
439
|
+
});
|
|
428
440
|
}
|
|
429
441
|
}
|
|
430
442
|
// ── Step 2: Scan endpoints ──
|
|
@@ -569,22 +581,17 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
569
581
|
logger.info("GraphQL-only diff detected — REST testing not supported", {
|
|
570
582
|
changedFiles: diffData.changedFiles,
|
|
571
583
|
});
|
|
572
|
-
return {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
"",
|
|
584
|
-
`Changed files: ${diffData.changedFiles.join(", ")}`,
|
|
585
|
-
].join("\n"),
|
|
586
|
-
}],
|
|
587
|
-
};
|
|
584
|
+
return dualChannelResult({ executionPlan: [
|
|
585
|
+
"**GraphQL-only diff detected.**",
|
|
586
|
+
"",
|
|
587
|
+
"The changed files appear to be GraphQL schema, artifact, or endpoint implementation files.",
|
|
588
|
+
"Skyramp currently supports REST API testing only — GraphQL introspection,",
|
|
589
|
+
"query validation, and type-name grounding are not yet supported.",
|
|
590
|
+
"",
|
|
591
|
+
"No test recommendations can be generated for this diff.",
|
|
592
|
+
"",
|
|
593
|
+
`Changed files: ${diffData.changedFiles.join(", ")}`,
|
|
594
|
+
].join("\n") });
|
|
588
595
|
}
|
|
589
596
|
}
|
|
590
597
|
await sendProgress(50, 100, "Discovering existing tests...");
|
|
@@ -1601,15 +1608,12 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1601
1608
|
unmatchedFiles: classifiedEndpoints?.unmatchedFiles,
|
|
1602
1609
|
nextTool: "skyramp_analyze_test_health",
|
|
1603
1610
|
});
|
|
1604
|
-
return {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
],
|
|
1611
|
-
isError: false,
|
|
1612
|
-
};
|
|
1611
|
+
return buildAnalyzeChangesResult({
|
|
1612
|
+
structuredSummary,
|
|
1613
|
+
uiInstructions,
|
|
1614
|
+
outputText,
|
|
1615
|
+
recommendationPrompt,
|
|
1616
|
+
});
|
|
1613
1617
|
}
|
|
1614
1618
|
catch (error) {
|
|
1615
1619
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -62,7 +62,7 @@ export function registerAnalyzeTestHealthTool(server) {
|
|
|
62
62
|
inputSchema: {
|
|
63
63
|
stateFile: z
|
|
64
64
|
.string()
|
|
65
|
-
.describe("Path to the analyze-changes-state.json file returned by skyramp_analyze_changes (the stateFile field in its output). Do NOT pass
|
|
65
|
+
.describe("Path to the analyze-changes-state.json file returned by skyramp_analyze_changes (the stateFile field in its output). Do NOT pass testbot-result.txt here."),
|
|
66
66
|
blueprintCaptured: z
|
|
67
67
|
.boolean()
|
|
68
68
|
.optional()
|
|
@@ -88,10 +88,10 @@ export function registerAnalyzeTestHealthTool(server) {
|
|
|
88
88
|
return toolError(`State file is empty or invalid: ${args.stateFile}${args.repository ? ` (no section for repository ${args.repository})` : ""}. Call skyramp_analyze_changes first to generate a valid state file.`);
|
|
89
89
|
}
|
|
90
90
|
if (!repositoryPath || typeof repositoryPath !== "string") {
|
|
91
|
-
return toolError(`repositoryPath not found in state file metadata. The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not
|
|
91
|
+
return toolError(`repositoryPath not found in state file metadata. The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not testbot-result.txt. Re-run skyramp_analyze_changes to regenerate it.`);
|
|
92
92
|
}
|
|
93
93
|
if (!Array.isArray(stateData.existingTests)) {
|
|
94
|
-
return toolError(`stateFile does not contain test discovery data (existingTests missing). The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not
|
|
94
|
+
return toolError(`stateFile does not contain test discovery data (existingTests missing). The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not testbot-result.txt.`);
|
|
95
95
|
}
|
|
96
96
|
// External tests: UPDATE only — REGENERATE/DELETE are report-only (enforced in
|
|
97
97
|
// skyramp_actions). Already scoped to relevant ones by discovery.
|
|
@@ -128,38 +128,57 @@ function toDraftedScenario(input) {
|
|
|
128
128
|
testType: input.testType,
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
-
/** Build the agent-submitted candidates
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
/** Build the agent-submitted candidates. Discriminator claims are verified
|
|
132
|
+
* later, by {@link applyDiscriminatorClaims}, once the merge has settled which
|
|
133
|
+
* scenario each name resolves to. */
|
|
134
|
+
function buildAgentCandidates(candidates) {
|
|
135
|
+
return candidates.map((input) => {
|
|
136
136
|
const scenario = toDraftedScenario(input);
|
|
137
|
-
|
|
138
|
-
let verifiedDiscriminator;
|
|
139
|
-
if (input.discriminator) {
|
|
140
|
-
const claim = input.discriminator;
|
|
141
|
-
const result = validateDiscriminator(scenario, claim, diffText);
|
|
142
|
-
if (result.verified) {
|
|
143
|
-
verifiedDiscriminator = claim.kind;
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
demotions.push({
|
|
147
|
-
candidateId,
|
|
148
|
-
reason: result.reason ?? `${claim.kind} discriminator claim could not be verified.`,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const candidate = {
|
|
137
|
+
return {
|
|
153
138
|
scenario,
|
|
154
139
|
priority: derivePriorityTier(scenario),
|
|
155
140
|
novelty: Novelty.NEW,
|
|
156
141
|
source: CandidateSource.AGENT,
|
|
157
|
-
candidateId,
|
|
158
|
-
...(verifiedDiscriminator ? { verifiedDiscriminator } : {}),
|
|
142
|
+
candidateId: computeCandidateId(scenario),
|
|
159
143
|
};
|
|
160
|
-
return candidate;
|
|
161
144
|
});
|
|
162
|
-
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Verify each declared discriminator against the candidate that actually won
|
|
148
|
+
* the merge, and mark it. Failed claims demote (never reject).
|
|
149
|
+
*
|
|
150
|
+
* Runs AFTER the merge, not while building the agent's candidates: a server
|
|
151
|
+
* pre-seeded candidate wins the name, so verifying the agent's copy first
|
|
152
|
+
* attaches the result to a candidate that is about to be discarded — silently
|
|
153
|
+
* stripping the verification. Verification is against the WINNER's
|
|
154
|
+
* steps, so a claim that only held for the agent's rewrite is correctly refused
|
|
155
|
+
* rather than carried across.
|
|
156
|
+
*
|
|
157
|
+
* The marker is cleared before the current claim is applied. Each call replaces
|
|
158
|
+
* the agent's previous submission (see the `candidates` schema), and
|
|
159
|
+
* recoverServerCandidates restores whatever the LAST plan recorded — so keeping
|
|
160
|
+
* it would let a boost earned by an earlier registration outlive the claim that
|
|
161
|
+
* earned it, on a submission that no longer makes that claim or whose new claim
|
|
162
|
+
* fails.
|
|
163
|
+
*/
|
|
164
|
+
function applyDiscriminatorClaims(candidates, claimsByKey, diffText, reposWithoutDiff = []) {
|
|
165
|
+
const demotions = [];
|
|
166
|
+
const marked = candidates.map((candidate) => {
|
|
167
|
+
const { verifiedDiscriminator: _stale, ...unmarked } = candidate;
|
|
168
|
+
const claim = claimsByKey.get(scenarioMergeKey(candidate.scenario.scenarioName) || candidate.candidateId);
|
|
169
|
+
if (!claim)
|
|
170
|
+
return unmarked;
|
|
171
|
+
const result = validateDiscriminator(candidate.scenario, claim, diffText, reposWithoutDiff);
|
|
172
|
+
if (result.verified) {
|
|
173
|
+
return { ...unmarked, verifiedDiscriminator: claim.kind };
|
|
174
|
+
}
|
|
175
|
+
demotions.push({
|
|
176
|
+
candidateId: candidate.candidateId,
|
|
177
|
+
reason: result.reason ?? `${claim.kind} discriminator claim could not be verified.`,
|
|
178
|
+
});
|
|
179
|
+
return unmarked;
|
|
180
|
+
});
|
|
181
|
+
return { candidates: marked, demotions };
|
|
163
182
|
}
|
|
164
183
|
/** Recover full Candidates for pre-seeded ("server") plan items by matching
|
|
165
184
|
* candidateId back to the analyzed scenarios (repositoryAnalysis.scenarios) —
|
|
@@ -190,15 +209,49 @@ function recoverServerCandidates(priorPlan, allScenarios) {
|
|
|
190
209
|
}
|
|
191
210
|
return recovered;
|
|
192
211
|
}
|
|
193
|
-
|
|
212
|
+
/** Whether a repo section's persisted diff classification contains any new,
|
|
213
|
+
* modified, or removed endpoint — the same signal `computeScoredCandidates`
|
|
214
|
+
* derives `hasApiChanges` from at analyze time. */
|
|
215
|
+
function sectionHasApiChanges(data) {
|
|
216
|
+
const diff = data?.repositoryAnalysis?.diff;
|
|
217
|
+
return ((diff?.newEndpoints?.length ?? 0) > 0 ||
|
|
218
|
+
(diff?.modifiedEndpoints?.length ?? 0) > 0 ||
|
|
219
|
+
(diff?.removedEndpoints?.length ?? 0) > 0);
|
|
220
|
+
}
|
|
221
|
+
function resolveBudgetContext(stateData, fullState) {
|
|
194
222
|
const pbc = stateData.planBudgetContext;
|
|
195
|
-
|
|
223
|
+
const base = {
|
|
196
224
|
maxGenerate: pbc?.maxGenerate ?? MAX_TESTS_TO_GENERATE,
|
|
197
225
|
maxTotal: pbc?.maxTotal ?? MAX_RECOMMENDATIONS,
|
|
198
226
|
isUIOnlyPR: pbc?.isUIOnlyPR ?? false,
|
|
199
227
|
hasFrontendChanges: pbc?.hasFrontendChanges ?? false,
|
|
200
228
|
externalCoverage: new Set(pbc?.externalCoverageKeys ?? []),
|
|
201
229
|
};
|
|
230
|
+
const relatedSections = Object.values(fullState?.relatedRepos ?? {}).map((section) => section.data);
|
|
231
|
+
if (relatedSections.length === 0)
|
|
232
|
+
return base;
|
|
233
|
+
// SKYR-4204: isUIOnlyPR/hasFrontendChanges are per-SECTION facts — each
|
|
234
|
+
// analyze call computes them from its own repo's diff. The approved plan is
|
|
235
|
+
// one run-wide decision, so a frontend-only PRIMARY diff must not put the
|
|
236
|
+
// budgeter on its all-UI branch (backendGenerateCount() = 0) while a related
|
|
237
|
+
// repo carries endpoint changes; that pinned GENERATE to UI placeholders and
|
|
238
|
+
// left every API candidate in ADDITIONAL regardless of maxGenerate. Derive
|
|
239
|
+
// the flags across ALL sections. A related repo checked out on its default
|
|
240
|
+
// branch classifies no endpoint changes, so it leaves a UI-only run UI-only.
|
|
241
|
+
// Budget numbers come from the root (primary) section: the testbot prompt
|
|
242
|
+
// passes topN/maxGenerate only to the primary analyze call, so related
|
|
243
|
+
// sections carry defaults.
|
|
244
|
+
const sections = [fullState ?? undefined, ...relatedSections];
|
|
245
|
+
const hasFrontendChanges = sections.some((s) => s?.planBudgetContext?.hasFrontendChanges ?? false);
|
|
246
|
+
const hasApiChanges = sections.some(sectionHasApiChanges);
|
|
247
|
+
const rootPbc = fullState?.planBudgetContext;
|
|
248
|
+
return {
|
|
249
|
+
maxGenerate: rootPbc?.maxGenerate ?? base.maxGenerate,
|
|
250
|
+
maxTotal: rootPbc?.maxTotal ?? base.maxTotal,
|
|
251
|
+
isUIOnlyPR: hasFrontendChanges && !hasApiChanges,
|
|
252
|
+
hasFrontendChanges,
|
|
253
|
+
externalCoverage: base.externalCoverage,
|
|
254
|
+
};
|
|
202
255
|
}
|
|
203
256
|
function describeGenerationCall(item) {
|
|
204
257
|
switch (item.testType) {
|
|
@@ -316,9 +369,27 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
316
369
|
errorResult = toolError(`State file is empty or invalid: ${params.stateFile}. Call skyramp_analyze_changes first to generate a valid state file.`);
|
|
317
370
|
return errorResult;
|
|
318
371
|
}
|
|
319
|
-
|
|
372
|
+
// SKYR-4203: one register call carries candidates for EVERY repo of the
|
|
373
|
+
// run, but `stateData` is a single repo's section — and the candidate
|
|
374
|
+
// schema has no per-candidate repository, so a related repo's anchors
|
|
375
|
+
// were unverifiable by construction. Verify anchors (and rank diff-hunk
|
|
376
|
+
// proximity) against the union of ALL sections' diffs: an anchor
|
|
377
|
+
// grounded in a related repo's change is as real as one in the
|
|
378
|
+
// primary's. Sections with no diff (a related repo checked out on its
|
|
379
|
+
// default branch) are collected so an unmatched anchor reads as
|
|
380
|
+
// uncheckable rather than ungrounded.
|
|
381
|
+
const fullState = await stateManager.readFullState();
|
|
382
|
+
const diffSections = [
|
|
383
|
+
{ repo: fullState?.metadata?.repository ?? "the primary repository", diff: fullState?.diffText },
|
|
384
|
+
...Object.entries(fullState?.relatedRepos ?? {}).map(([repo, section]) => ({
|
|
385
|
+
repo,
|
|
386
|
+
diff: section.data?.diffText,
|
|
387
|
+
})),
|
|
388
|
+
];
|
|
389
|
+
const diffText = diffSections.map((s) => s.diff).filter(Boolean).join("\n");
|
|
390
|
+
const reposWithoutDiff = diffSections.filter((s) => !s.diff).map((s) => s.repo);
|
|
320
391
|
const allScenarios = stateData.repositoryAnalysis?.scenarios ?? [];
|
|
321
|
-
const
|
|
392
|
+
const agentCandidates = buildAgentCandidates(params.candidates ?? []);
|
|
322
393
|
const serverCandidates = recoverServerCandidates(stateData.approvedPlan, allScenarios);
|
|
323
394
|
// Merge by scenario-name identity, NOT the full content-hashed candidateId:
|
|
324
395
|
// the server (analyze_changes) and the agent frequently draft their own
|
|
@@ -341,7 +412,18 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
341
412
|
merged.set(mergeKey(candidate), candidate);
|
|
342
413
|
for (const candidate of serverCandidates)
|
|
343
414
|
merged.set(mergeKey(candidate), candidate);
|
|
344
|
-
|
|
415
|
+
// The agent declares a claim per scenario NAME, so key the claims the
|
|
416
|
+
// same way the merge does and verify against whichever copy survived.
|
|
417
|
+
const claimsByKey = new Map();
|
|
418
|
+
// Keyed through mergeKey, not scenarioMergeKey alone: a name that
|
|
419
|
+
// slugifies to nothing (e.g. an all-non-ASCII name) would be stored
|
|
420
|
+
// under "" while the lookup falls back to the candidateId, so the claim
|
|
421
|
+
// would be neither verified nor demoted.
|
|
422
|
+
(params.candidates ?? []).forEach((input, i) => {
|
|
423
|
+
if (input.discriminator)
|
|
424
|
+
claimsByKey.set(mergeKey(agentCandidates[i]), input.discriminator);
|
|
425
|
+
});
|
|
426
|
+
const { candidates: allCandidates, demotions } = applyDiscriminatorClaims([...merged.values()], claimsByKey, diffText, reposWithoutDiff);
|
|
345
427
|
// An empty union would persist an authoritative plan with an empty
|
|
346
428
|
// GENERATE list, which the generation gate then enforces — bricking
|
|
347
429
|
// the run until re-registration. Reject instead so the agent can
|
|
@@ -351,7 +433,7 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
351
433
|
"Submit your complete candidate list (every test you would generate or recommend) in `candidates` — an empty plan would block all generation.");
|
|
352
434
|
return errorResult;
|
|
353
435
|
}
|
|
354
|
-
const budgetContext = resolveBudgetContext(stateData);
|
|
436
|
+
const budgetContext = resolveBudgetContext(stateData, fullState);
|
|
355
437
|
const result = selectPlan(allCandidates, { ...budgetContext, demotions, diffText });
|
|
356
438
|
const approvedPlan = {
|
|
357
439
|
planId: crypto.randomUUID(),
|
|
@@ -34,12 +34,16 @@ export interface MaintenanceActionCore {
|
|
|
34
34
|
testType: TestType;
|
|
35
35
|
endpoint: string;
|
|
36
36
|
rationale: string;
|
|
37
|
+
/** When selectors are abstracted into a page object, pomFile is the POM path to edit.
|
|
38
|
+
* testFilePath remains the spec (for catalog lookup and report); pomFile is what gets
|
|
39
|
+
* patched. Part of the core shape because the EXECUTED file (the spec, which carries the
|
|
40
|
+
* execution records) and the EDITED file (the POM) are structurally different, and every
|
|
41
|
+
* downstream stage that reasons about the edit needs both — notably the report-time
|
|
42
|
+
* working-tree check, which otherwise calls a POM-backed UPDATE unbacked (SKYR-4129). */
|
|
43
|
+
pomFile?: string;
|
|
37
44
|
}
|
|
38
45
|
/** Normalized internal recommendation built from LLM-supplied args.recommendations. */
|
|
39
46
|
export interface DriftRecommendation extends MaintenanceActionCore {
|
|
40
|
-
/** When selectors are abstracted into a page object, pomFile is the POM path to edit.
|
|
41
|
-
* testFilePath remains the spec (for catalog lookup and report); pomFile is what gets patched. */
|
|
42
|
-
pomFile?: string;
|
|
43
47
|
priority: RecommendationPriority;
|
|
44
48
|
estimatedWork: EstimatedWork;
|
|
45
49
|
updateInstructions: string;
|
|
@@ -26,6 +26,20 @@ export interface TestExecutionResult extends TestExecutionData {
|
|
|
26
26
|
testFile: string;
|
|
27
27
|
videoPath?: string;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* One browser execution's recorded video, as persisted in run state by
|
|
31
|
+
* skyramp_execute_test and read back by skyramp_submit_report (SKYR-4156).
|
|
32
|
+
*
|
|
33
|
+
* `status`/`executedAt` are not rendered in the report — they identify WHICH
|
|
34
|
+
* execution the referenced recording belongs to, which matters because a retried
|
|
35
|
+
* test leaves a video directory behind for every attempt.
|
|
36
|
+
*/
|
|
37
|
+
export interface VideoRecord {
|
|
38
|
+
/** Host path to the recorded video.webm. */
|
|
39
|
+
videoPath: string;
|
|
40
|
+
status: TestExecutionStatus;
|
|
41
|
+
executedAt: string;
|
|
42
|
+
}
|
|
29
43
|
export interface BatchExecutionResult {
|
|
30
44
|
totalTests: number;
|
|
31
45
|
passed: number;
|
package/build/types/TestTypes.js
CHANGED
|
@@ -216,8 +216,9 @@ export const baseTestSchema = {
|
|
|
216
216
|
.string()
|
|
217
217
|
.default("")
|
|
218
218
|
.describe("MUST be string of comma separated values like 'id=1,name=John' for URL query parameters. "
|
|
219
|
-
+ "Workspace-configured api.defaultQueryParams (if set) are merged in automatically
|
|
220
|
-
+ "
|
|
219
|
+
+ "Workspace-configured api.defaultQueryParams (if set) are merged in automatically, with any "
|
|
220
|
+
+ "api.queryParamOverrides entry whose pathPattern matches this endpoint layered on top — "
|
|
221
|
+
+ "no need to repeat them here. An explicit value for the same key here overrides both."),
|
|
221
222
|
formParams: z
|
|
222
223
|
.string()
|
|
223
224
|
.default("")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Service } from "../workspace/workspace.js";
|
|
2
|
+
/** One related repository to analyze as shared context in multi-repo mode. */
|
|
3
|
+
export interface RelatedRepository {
|
|
4
|
+
repositoryPath: string;
|
|
5
|
+
baseBranch?: string;
|
|
6
|
+
/** `owner/repo` slug, resolved by the action from the checkout's git remote.
|
|
7
|
+
* Used verbatim as the `repository` argument/attribution for this repo so the
|
|
8
|
+
* agent never has to infer it. */
|
|
9
|
+
repo: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Options accepted by getTestbotPrompt (src/prompts/testbot/testbot-prompts.ts).
|
|
13
|
+
* Shared here — rather than declared inline in the prompt module — so testbot.git
|
|
14
|
+
* can import this exact type instead of hand-maintaining its own mirror.
|
|
15
|
+
*/
|
|
16
|
+
export interface TestbotPromptOptions {
|
|
17
|
+
prTitle: string;
|
|
18
|
+
prDescription: string;
|
|
19
|
+
repositoryPath: string;
|
|
20
|
+
baseBranch?: string;
|
|
21
|
+
maxRecommendations?: number;
|
|
22
|
+
maxGenerate?: number;
|
|
23
|
+
/** Reserved — accepted for API compat but not yet wired into the prompt. */
|
|
24
|
+
maxCritical?: number;
|
|
25
|
+
prNumber?: number;
|
|
26
|
+
userPrompt?: string;
|
|
27
|
+
services?: Service[];
|
|
28
|
+
uiCredentials?: string;
|
|
29
|
+
testsRepoDir?: string;
|
|
30
|
+
relatedRepositories?: RelatedRepository[];
|
|
31
|
+
primaryRepo?: string;
|
|
32
|
+
planOnly?: boolean;
|
|
33
|
+
language?: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,6 +2,15 @@ import type { DriftAction } from "./TestAnalysis.js";
|
|
|
2
2
|
import type { TestExecutionStatus } from "./TestExecution.js";
|
|
3
3
|
import type { HttpMethod, TestType } from "./TestTypes.js";
|
|
4
4
|
import type { ReuseOutcome } from "./ReuseOutcome.js";
|
|
5
|
+
/** Classification of an issuesFound entry. Bug is a product/code defect; the
|
|
6
|
+
* other values are tooling noise that the Testbot report renders in its
|
|
7
|
+
* Configuration Errors section instead of Issues Found. */
|
|
8
|
+
export declare enum IssueFoundCategory {
|
|
9
|
+
Bug = "bug",
|
|
10
|
+
Lint = "lint",
|
|
11
|
+
Type = "type",
|
|
12
|
+
Config = "config"
|
|
13
|
+
}
|
|
5
14
|
/**
|
|
6
15
|
* Shape of the JSON report written by skyramp_submit_report and read by testbot
|
|
7
16
|
* for rendering as Markdown. All fields mirror the corresponding Zod schemas in
|
|
@@ -69,6 +78,7 @@ export interface TestbotReport {
|
|
|
69
78
|
issuesFound: {
|
|
70
79
|
description: string;
|
|
71
80
|
severity?: "critical" | "high" | "medium" | "low";
|
|
81
|
+
category: IssueFoundCategory;
|
|
72
82
|
}[];
|
|
73
83
|
nextSteps: string[];
|
|
74
84
|
commitMessage: string;
|
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/** Classification of an issuesFound entry. Bug is a product/code defect; the
|
|
2
|
+
* other values are tooling noise that the Testbot report renders in its
|
|
3
|
+
* Configuration Errors section instead of Issues Found. */
|
|
4
|
+
export var IssueFoundCategory;
|
|
5
|
+
(function (IssueFoundCategory) {
|
|
6
|
+
IssueFoundCategory["Bug"] = "bug";
|
|
7
|
+
IssueFoundCategory["Lint"] = "lint";
|
|
8
|
+
IssueFoundCategory["Type"] = "type";
|
|
9
|
+
IssueFoundCategory["Config"] = "config";
|
|
10
|
+
})(IssueFoundCategory || (IssueFoundCategory = {}));
|
package/build/types/index.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ export { TestExecutionStatus } from "./TestExecution.js";
|
|
|
2
2
|
export { DriftAction } from "./TestAnalysis.js";
|
|
3
3
|
export { TestType, HttpMethod } from "./TestTypes.js";
|
|
4
4
|
export type { TestbotReport } from "./TestbotReport.js";
|
|
5
|
+
export { IssueFoundCategory } from "./TestbotReport.js";
|
|
5
6
|
export { ReuseDeclinedBy, ReuseVerificationOutcome } from "./ReuseOutcome.js";
|
|
6
7
|
export type { ReuseOutcome, ReuseSkippedEntry } from "./ReuseOutcome.js";
|
|
8
|
+
export type { RelatedRepository, TestbotPromptOptions, } from "./TestbotPromptOptions.js";
|
package/build/types/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { TestExecutionStatus } from "./TestExecution.js";
|
|
2
2
|
export { DriftAction } from "./TestAnalysis.js";
|
|
3
3
|
export { TestType, HttpMethod } from "./TestTypes.js";
|
|
4
|
+
export { IssueFoundCategory } from "./TestbotReport.js";
|
|
4
5
|
export { ReuseDeclinedBy, ReuseVerificationOutcome } from "./ReuseOutcome.js";
|
|
@@ -7,13 +7,33 @@ import type { CandidateUiPage } from "./uiPageEnumerator.js";
|
|
|
7
7
|
import type { FrontendFileIntegration } from "../types/FrontendIntegration.js";
|
|
8
8
|
import type { ApprovedPlanItem } from "../types/Recommendation.js";
|
|
9
9
|
import type { ExternalTestRunRecord } from "../types/ExternalTestExecution.js";
|
|
10
|
+
import type { VideoRecord } from "../types/TestExecution.js";
|
|
10
11
|
export type { CandidateUiPage } from "./uiPageEnumerator.js";
|
|
11
12
|
export declare function setTestsRepoDir(dir: string | undefined): void;
|
|
12
13
|
export declare function getTestsRepoDir(): string | undefined;
|
|
13
|
-
/** Filename of the run-scoped analysis state file under
|
|
14
|
+
/** Filename of the run-scoped analysis state file under `runArtifactDir()`.
|
|
14
15
|
* Single-sourced so the constructor that WRITES there and `resolveRunStatePath`
|
|
15
16
|
* which LOOKS there cannot drift apart. */
|
|
16
17
|
export declare const RUN_STATE_FILE_NAME = "analyze-changes-state.json";
|
|
18
|
+
/**
|
|
19
|
+
* Directory holding this run's Skyramp artifacts — the state file, and the report written
|
|
20
|
+
* beside it (SKYR-4147) — or undefined outside a run.
|
|
21
|
+
*
|
|
22
|
+
* `RUNNER_TEMP` is a fresh directory per CI job, and the caller and this server can each
|
|
23
|
+
* work it out from the environment on their own. So neither has to tell the other where
|
|
24
|
+
* the run's files live, and the path never passes through the model.
|
|
25
|
+
*
|
|
26
|
+
* The GitHub Action is the only caller customers run, and GitHub sets `RUNNER_TEMP` for
|
|
27
|
+
* it; the eval harness sets it itself. Nothing customers run is on another CI system, so
|
|
28
|
+
* the GitHub-specific name is only a naming problem — it does not break anything today.
|
|
29
|
+
* If a caller on another CI system is added, rename this then, or let it read that
|
|
30
|
+
* system's variable. Do not add a second variable for a caller that does not exist.
|
|
31
|
+
*
|
|
32
|
+
* Kept in one place for the same reason `RUN_STATE_FILE_NAME` is: the directory used to be
|
|
33
|
+
* written out at three call sites, so only the filename was safe from the three copies
|
|
34
|
+
* drifting apart. testbot keeps its matching copy in one place too, in `skyrampTempDir()`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runArtifactDir(): string | undefined;
|
|
17
37
|
export declare function setActiveRunStatePath(stateFilePath: string): void;
|
|
18
38
|
export declare function getActiveRunStatePath(): string | undefined;
|
|
19
39
|
/** Reset the run anchor — for tests, and as a safety hook between runs. */
|
|
@@ -25,7 +45,7 @@ export declare function clearActiveRunStatePath(): void;
|
|
|
25
45
|
* `setActiveRunStatePath` is only called by skyramp_analyze_changes on multi-repo
|
|
26
46
|
* runs (the ones carrying a `repository` param), but a single-repo Testbot run's
|
|
27
47
|
* path is still deterministic in CI: the StateManager constructor above roots
|
|
28
|
-
* "analysis" state under
|
|
48
|
+
* "analysis" state under `runArtifactDir()`. Falling back to that covers
|
|
29
49
|
* single-repo runs without a new anchor mechanism. Standalone/IDE usage has no
|
|
30
50
|
* RUNNER_TEMP and resolves to undefined — correct, since there is no run state.
|
|
31
51
|
*
|
|
@@ -166,6 +186,20 @@ export interface UnifiedAnalysisState {
|
|
|
166
186
|
* which computes every value in-process; skyramp_submit_report merges it into
|
|
167
187
|
* the report's `reuse` field. Never supplied by the LLM. */
|
|
168
188
|
reuseOutcomes?: Record<string, ReuseRecord>;
|
|
189
|
+
/**
|
|
190
|
+
* SKYR-4156. Recorded video per executed browser test, keyed by test-file
|
|
191
|
+
* BASENAME (the same key `reuseOutcomes` uses, so matching needs no path
|
|
192
|
+
* normalization). Written in-process by skyramp_execute_test — the only place
|
|
193
|
+
* the path is known — and merged into the report's `testResults[].videoPath` by
|
|
194
|
+
* skyramp_submit_report. Never supplied by the LLM.
|
|
195
|
+
*
|
|
196
|
+
* Deliberately separate from `existingTests[].executionBefore/After`: those only
|
|
197
|
+
* exist for tests skyramp_analyze_changes discovered, so a NEWLY generated spec
|
|
198
|
+
* has nowhere to record into — which is exactly how one run recorded three videos
|
|
199
|
+
* and referenced none of them. Last write wins, so a retried execution replaces
|
|
200
|
+
* the earlier attempt's now-stale directory.
|
|
201
|
+
*/
|
|
202
|
+
executionVideos?: Record<string, VideoRecord>;
|
|
169
203
|
}
|
|
170
204
|
/**
|
|
171
205
|
* State file metadata
|
|
@@ -35,10 +35,32 @@ export function getTestsRepoDir() {
|
|
|
35
35
|
* Single-repo / standalone calls (no `repository`) never set or read this.
|
|
36
36
|
*/
|
|
37
37
|
let _activeRunStatePath;
|
|
38
|
-
/** Filename of the run-scoped analysis state file under
|
|
38
|
+
/** Filename of the run-scoped analysis state file under `runArtifactDir()`.
|
|
39
39
|
* Single-sourced so the constructor that WRITES there and `resolveRunStatePath`
|
|
40
40
|
* which LOOKS there cannot drift apart. */
|
|
41
41
|
export const RUN_STATE_FILE_NAME = "analyze-changes-state.json";
|
|
42
|
+
/**
|
|
43
|
+
* Directory holding this run's Skyramp artifacts — the state file, and the report written
|
|
44
|
+
* beside it (SKYR-4147) — or undefined outside a run.
|
|
45
|
+
*
|
|
46
|
+
* `RUNNER_TEMP` is a fresh directory per CI job, and the caller and this server can each
|
|
47
|
+
* work it out from the environment on their own. So neither has to tell the other where
|
|
48
|
+
* the run's files live, and the path never passes through the model.
|
|
49
|
+
*
|
|
50
|
+
* The GitHub Action is the only caller customers run, and GitHub sets `RUNNER_TEMP` for
|
|
51
|
+
* it; the eval harness sets it itself. Nothing customers run is on another CI system, so
|
|
52
|
+
* the GitHub-specific name is only a naming problem — it does not break anything today.
|
|
53
|
+
* If a caller on another CI system is added, rename this then, or let it read that
|
|
54
|
+
* system's variable. Do not add a second variable for a caller that does not exist.
|
|
55
|
+
*
|
|
56
|
+
* Kept in one place for the same reason `RUN_STATE_FILE_NAME` is: the directory used to be
|
|
57
|
+
* written out at three call sites, so only the filename was safe from the three copies
|
|
58
|
+
* drifting apart. testbot keeps its matching copy in one place too, in `skyrampTempDir()`.
|
|
59
|
+
*/
|
|
60
|
+
export function runArtifactDir() {
|
|
61
|
+
const runnerTemp = process.env.RUNNER_TEMP?.trim();
|
|
62
|
+
return runnerTemp ? path.join(runnerTemp, "skyramp") : undefined;
|
|
63
|
+
}
|
|
42
64
|
export function setActiveRunStatePath(stateFilePath) {
|
|
43
65
|
_activeRunStatePath = stateFilePath;
|
|
44
66
|
}
|
|
@@ -56,7 +78,7 @@ export function clearActiveRunStatePath() {
|
|
|
56
78
|
* `setActiveRunStatePath` is only called by skyramp_analyze_changes on multi-repo
|
|
57
79
|
* runs (the ones carrying a `repository` param), but a single-repo Testbot run's
|
|
58
80
|
* path is still deterministic in CI: the StateManager constructor above roots
|
|
59
|
-
* "analysis" state under
|
|
81
|
+
* "analysis" state under `runArtifactDir()`. Falling back to that covers
|
|
60
82
|
* single-repo runs without a new anchor mechanism. Standalone/IDE usage has no
|
|
61
83
|
* RUNNER_TEMP and resolves to undefined — correct, since there is no run state.
|
|
62
84
|
*
|
|
@@ -70,8 +92,9 @@ export function resolveRunStatePath(explicitPath) {
|
|
|
70
92
|
const anchored = getActiveRunStatePath();
|
|
71
93
|
if (anchored)
|
|
72
94
|
return anchored;
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
const runDir = runArtifactDir();
|
|
96
|
+
if (runDir) {
|
|
97
|
+
return path.join(runDir, RUN_STATE_FILE_NAME);
|
|
75
98
|
}
|
|
76
99
|
return undefined;
|
|
77
100
|
}
|
|
@@ -169,17 +192,15 @@ export class StateManager {
|
|
|
169
192
|
constructor(stateType = "analysis", sessionId, stateDir, stateFilePath) {
|
|
170
193
|
this.stateType = stateType;
|
|
171
194
|
this.sessionId = sessionId || crypto.randomUUID();
|
|
195
|
+
const runDir = stateType === "analysis" ? runArtifactDir() : undefined;
|
|
172
196
|
if (stateFilePath) {
|
|
173
197
|
this.stateFile = stateFilePath;
|
|
174
198
|
}
|
|
175
|
-
else if (
|
|
176
|
-
// In CI
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
// (Jenkins, GitLab, Buildkite) must set it explicitly. Once merged, path flows through
|
|
181
|
-
// summaryOutputFile which is already testbot-controlled and CI-agnostic.
|
|
182
|
-
this.stateFile = path.join(process.env.RUNNER_TEMP, "skyramp", RUN_STATE_FILE_NAME);
|
|
199
|
+
else if (runDir) {
|
|
200
|
+
// In CI, the run directory is fresh per job and the caller and this tool each work
|
|
201
|
+
// it out from the environment — no LLM input needed. It is also where
|
|
202
|
+
// skyramp_submit_report writes the report (SKYR-4147).
|
|
203
|
+
this.stateFile = path.join(runDir, RUN_STATE_FILE_NAME);
|
|
183
204
|
}
|
|
184
205
|
else {
|
|
185
206
|
const baseDir = stateDir || os.tmpdir();
|
|
@@ -435,7 +456,7 @@ export class StateManager {
|
|
|
435
456
|
* @returns Number of files deleted
|
|
436
457
|
*/
|
|
437
458
|
static async cleanupOldFiles(maxAgeHours = 24, stateDir, stateTypes) {
|
|
438
|
-
const baseDir = stateDir || (
|
|
459
|
+
const baseDir = stateDir || runArtifactDir() || os.tmpdir();
|
|
439
460
|
const files = await fs.promises.readdir(baseDir).catch(() => []);
|
|
440
461
|
const statePrefixes = stateTypes
|
|
441
462
|
? stateTypes.map((t) => STATE_FILE_PREFIXES[t])
|
|
@@ -74,7 +74,6 @@ export function extractChangedSelectors(fileDiff) {
|
|
|
74
74
|
// `data-testid="x"`, `data-testid={'x'}`, `data-testid={`x`}`. The closing delimiter
|
|
75
75
|
// deliberately rejects interpolated template literals (`data-testid={`members-${id}`}`),
|
|
76
76
|
// which would otherwise leak a partial prefix (`members-`) into the selector set.
|
|
77
|
-
// eslint-disable-next-line no-useless-escape
|
|
78
77
|
const ATTR = /\bdata-(?:test-?id|test|cy|qa)\b\s*=\s*\{?\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/g;
|
|
79
78
|
const GET_BY_TEST_ID = /getByTestId\(\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/g;
|
|
80
79
|
const KEBAB = /[a-z][a-z0-9]*(?:-{1,2}[a-z0-9]+)+/g;
|