@skyramp/mcp 0.3.8 → 0.4.0-rc.1
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/commands/commandLibrary.d.ts +1 -1
- package/build/commands/commandLibrary.js +3 -3
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
- package/build/commands/testThisEndpointCommand.js +35 -19
- package/build/index.js +9 -3
- package/build/playwright/blueprintDigest.d.ts +15 -0
- package/build/playwright/blueprintDigest.js +152 -0
- package/build/playwright/blueprintDigestStore.d.ts +31 -0
- package/build/playwright/blueprintDigestStore.js +117 -0
- package/build/playwright/registerPlaywrightTools.js +60 -12
- package/build/playwright/traceRecordingPrompt.js +8 -7
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
- package/build/prompts/promptAssets.d.ts +20 -0
- package/build/prompts/promptAssets.js +55 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
- package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
- package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
- package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
- package/build/prompts/test-recommendation/recommendationSections.js +67 -309
- package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
- package/build/prompts/test-recommendation/recommendationShared.js +49 -155
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
- package/build/prompts/testbot/planDeclarations.d.ts +6 -0
- package/build/prompts/testbot/planDeclarations.js +9 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
- package/build/prompts/testbot/testbot-prompts.js +256 -381
- package/build/recommendation/answers.d.ts +35 -0
- package/build/recommendation/answers.js +96 -0
- package/build/recommendation/registerPlan.d.ts +49 -0
- package/build/recommendation/registerPlan.js +117 -0
- package/build/recommendation/runVerifiers.d.ts +10 -0
- package/build/recommendation/runVerifiers.js +49 -0
- package/build/recommendation/subjectStep.d.ts +42 -0
- package/build/recommendation/subjectStep.js +86 -0
- package/build/recommendation/types.d.ts +163 -0
- package/build/recommendation/types.js +20 -0
- package/build/recommendation/verifierContracts.d.ts +382 -0
- package/build/recommendation/verifierContracts.js +263 -0
- package/build/recommendation/verifiers/changedFile.d.ts +2 -0
- package/build/recommendation/verifiers/changedFile.js +82 -0
- package/build/recommendation/verifiers/citedPath.d.ts +12 -0
- package/build/recommendation/verifiers/citedPath.js +35 -0
- package/build/recommendation/verifiers/coverage.d.ts +7 -0
- package/build/recommendation/verifiers/coverage.js +617 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
- package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
- package/build/recommendation/verifiers/endpointGrounded.js +128 -0
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +51 -0
- package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
- package/build/recommendation/verifiers/expectedOutcome.js +105 -0
- package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
- package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
- package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
- package/build/recommendation/verifiers/reportedCategory.js +84 -0
- package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
- package/build/recommendation/verifiers/screenRoute.js +118 -0
- package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
- package/build/recommendation/verifiers/statedDifference.js +140 -0
- package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
- package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
- package/build/resources/analysisResources.js +1 -114
- package/build/resources/testbotResource.js +23 -13
- package/build/services/ModularizationService.js +2 -1
- package/build/services/TestDiscoveryService.d.ts +3 -72
- package/build/services/TestDiscoveryService.js +10 -303
- package/build/services/containerEnv.d.ts +1 -1
- package/build/services/containerEnv.js +12 -0
- package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
- package/build/skills/fixTestImportErrorsSkill.js +20 -0
- package/build/toolNames.d.ts +1 -0
- package/build/toolNames.js +1 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
- package/build/tools/code-refactor/modularizationTool.js +2 -1
- package/build/tools/executeSkyrampTestTool.d.ts +80 -0
- package/build/tools/executeSkyrampTestTool.js +246 -19
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
- package/build/tools/generate-tests/generateContractRestTool.js +3 -3
- package/build/tools/generate-tests/planGuard.d.ts +2 -2
- package/build/tools/generate-tests/planGuard.js +78 -18
- package/build/tools/one-click/oneClickTool.d.ts +0 -1
- package/build/tools/one-click/oneClickTool.js +0 -5
- package/build/tools/submitReportTool.d.ts +48 -42
- package/build/tools/submitReportTool.js +576 -193
- package/build/tools/test-management/actionsTool.js +72 -4
- package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
- package/build/tools/test-management/analyzeChangesTool.js +212 -1219
- package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
- package/build/tools/test-management/registerTestPlanTool.js +609 -542
- package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
- package/build/tools/test-management/resolveScreenTool.js +289 -0
- package/build/types/BlueprintDigest.d.ts +34 -0
- package/build/types/BlueprintDigest.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +20 -1559
- package/build/types/RepositoryAnalysis.js +2 -58
- package/build/types/StepMethod.d.ts +40 -0
- package/build/types/StepMethod.js +77 -0
- package/build/types/TestAnalysis.d.ts +12 -0
- package/build/types/TestExecution.d.ts +4 -0
- package/build/types/TestRecommendation.d.ts +24 -24
- package/build/types/TestRecommendation.js +91 -89
- package/build/types/TestbotPromptOptions.d.ts +0 -4
- package/build/types/TestbotReport.d.ts +64 -2
- package/build/utils/AnalysisStateManager.d.ts +79 -113
- package/build/utils/AnalysisStateManager.js +147 -57
- package/build/utils/assertion-verify/api-shared-lints.js +1 -1
- package/build/utils/assertion-verify/metrics.js +85 -36
- package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
- package/build/utils/assertion-verify/ui-lints.js +32 -0
- package/build/utils/branchDiff.d.ts +63 -31
- package/build/utils/branchDiff.js +242 -94
- package/build/utils/containedPath.d.ts +18 -0
- package/build/utils/containedPath.js +73 -0
- package/build/utils/dartRouteExtractor.d.ts +18 -34
- package/build/utils/dartRouteExtractor.js +101 -173
- package/build/utils/featureFlags.d.ts +12 -0
- package/build/utils/featureFlags.js +14 -0
- package/build/utils/frontendSelectors.d.ts +48 -27
- package/build/utils/frontendSelectors.js +241 -80
- package/build/utils/pathMatching.d.ts +2 -4
- package/build/utils/pathMatching.js +2 -4
- package/build/utils/planMatchKeys.d.ts +38 -47
- package/build/utils/planMatchKeys.js +143 -81
- package/build/utils/rebaselineSnapshots.d.ts +24 -0
- package/build/utils/rebaselineSnapshots.js +65 -0
- package/build/utils/removedUiElements.d.ts +22 -0
- package/build/utils/removedUiElements.js +106 -0
- package/build/utils/reportVerification.d.ts +2 -6
- package/build/utils/reportVerification.js +61 -2
- package/build/utils/screenRoutes.d.ts +66 -0
- package/build/utils/screenRoutes.js +727 -0
- package/build/utils/sourceRouteExtractor.js +320 -112
- package/build/utils/testFileClassification.d.ts +11 -2
- package/build/utils/testFileClassification.js +44 -2
- package/build/utils/testFixtures.d.ts +5 -0
- package/build/utils/testFixtures.js +13 -0
- package/build/utils/utils.d.ts +0 -1
- package/build/utils/utils.js +0 -11
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +12 -12
- package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/plugin.json +6 -0
- package/plugin/prompts/declaring-a-plan.md +20 -0
- package/plugin/prompts/generate-tests/context-fetching.md +4 -0
- package/plugin/prompts/generate-tests/execution-plan.md +63 -0
- package/plugin/prompts/generate-tests/generation.md +108 -0
- package/plugin/prompts/generate-tests/path-parameters.md +1 -0
- package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
- package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
- package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
- package/plugin/prompts/plan-tests.md +42 -0
- package/plugin/prompts/testbot-task1.md +82 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
- package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
- package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
- package/build/recommendation/budgeters/shared.d.ts +0 -32
- package/build/recommendation/budgeters/shared.js +0 -246
- package/build/recommendation/discriminators.d.ts +0 -37
- package/build/recommendation/discriminators.js +0 -379
- package/build/recommendation/diversity.d.ts +0 -47
- package/build/recommendation/diversity.js +0 -101
- package/build/recommendation/planRanker.d.ts +0 -65
- package/build/recommendation/planRanker.js +0 -83
- package/build/recommendation/testFixtures.d.ts +0 -25
- package/build/recommendation/testFixtures.js +0 -45
- package/build/types/FrontendIntegration.d.ts +0 -28
- package/build/types/FrontendIntegration.js +0 -22
- package/build/types/Recommendation.d.ts +0 -146
- package/build/types/Recommendation.js +0 -74
- package/build/utils/changedRoutes.d.ts +0 -29
- package/build/utils/changedRoutes.js +0 -87
- package/build/utils/frontendIntegration.d.ts +0 -9
- package/build/utils/frontendIntegration.js +0 -243
- package/build/utils/importerHop.d.ts +0 -135
- package/build/utils/importerHop.js +0 -489
- package/build/utils/pathAffinityClassification.d.ts +0 -49
- package/build/utils/pathAffinityClassification.js +0 -180
- package/build/utils/pythonMountPrefixes.d.ts +0 -25
- package/build/utils/pythonMountPrefixes.js +0 -347
- package/build/utils/repoScanner.d.ts +0 -34
- package/build/utils/repoScanner.js +0 -300
- package/build/utils/routeParsers.d.ts +0 -95
- package/build/utils/routeParsers.js +0 -951
- package/build/utils/scenarioDrafting.d.ts +0 -92
- package/build/utils/scenarioDrafting.js +0 -951
- package/build/utils/subjectEndpoints.d.ts +0 -19
- package/build/utils/subjectEndpoints.js +0 -98
- package/build/utils/uiPageEnumerator.d.ts +0 -172
- package/build/utils/uiPageEnumerator.js +0 -474
|
@@ -1,164 +1,58 @@
|
|
|
1
|
-
import { extractResourceFromPath } from "../../utils/routeParsers.js";
|
|
2
|
-
import { logger } from "../../utils/logger.js";
|
|
3
|
-
/** Resolve the primary step and inferred test type for a scenario. */
|
|
4
|
-
function resolvePrimaryStep(scenario) {
|
|
5
|
-
const testType = scenario.testType ?? (scenario.steps.length === 1 ? "contract" : "integration");
|
|
6
|
-
const mutatingSteps = scenario.steps.filter(st => ["POST", "PUT", "PATCH", "DELETE"].includes(st.method));
|
|
7
|
-
// Use the last mutating step — earlier steps are typically prerequisite setup
|
|
8
|
-
// (e.g. POST /products before PATCH /orders), while the final mutation is the
|
|
9
|
-
// primary action under test.
|
|
10
|
-
const primaryStep = mutatingSteps[mutatingSteps.length - 1] ?? scenario.steps[scenario.steps.length - 1];
|
|
11
|
-
return { primaryStep, testType };
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* The one spelling of a coverage key, `METHOD::resource::testType`. Both
|
|
15
|
-
* operands of the dedup comparison call it: a proposed test through
|
|
16
|
-
* `externalDedupKeys`, and an existing test through `buildExternalCoverageSet`.
|
|
17
|
-
* They used to build the same string in two places from two different inputs —
|
|
18
|
-
* a typed step list on one side, regex-scraped prose on the other — so a fix to
|
|
19
|
-
* one side left the other producing keys that could never match (SKYR-4214).
|
|
20
|
-
*/
|
|
21
|
-
export function coverageKey(input) {
|
|
22
|
-
const method = (input.method ?? "GET").toUpperCase();
|
|
23
|
-
return `${method}::${extractResourceFromPath(input.path ?? "")}::${input.testType}`;
|
|
24
|
-
}
|
|
25
|
-
/** Test type of a scenario: its own label, else one step means a contract test. */
|
|
26
|
-
function resolveTestType(scenario) {
|
|
27
|
-
return scenario.testType ?? (scenario.steps.length === 1 ? "contract" : "integration");
|
|
28
|
-
}
|
|
29
1
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* not block a test for "PUT /orders", a different operation on the same
|
|
33
|
-
* resource.
|
|
34
|
-
*
|
|
35
|
-
* A caller must remove a candidate ONLY when the coverage set holds EVERY key
|
|
36
|
-
* returned here. Removing it on one match would discard the coverage of its
|
|
37
|
-
* other endpoints. An empty list must never remove anything.
|
|
2
|
+
* Shared prompt primitives, kept out of the prompt modules that use them to
|
|
3
|
+
* avoid circular imports.
|
|
38
4
|
*/
|
|
39
|
-
export function externalDedupKeys(scenario) {
|
|
40
|
-
const testType = resolveTestType(scenario);
|
|
41
|
-
const subjects = scenario.subjectEndpoints;
|
|
42
|
-
if (subjects && subjects.length > 0) {
|
|
43
|
-
return subjects.map((s) => coverageKey({ ...s, testType }));
|
|
44
|
-
}
|
|
45
|
-
if (subjects)
|
|
46
|
-
return [];
|
|
47
|
-
const { primaryStep } = resolvePrimaryStep(scenario);
|
|
48
|
-
if (!primaryStep)
|
|
49
|
-
return [];
|
|
50
|
-
return [coverageKey({ method: primaryStep.method, path: primaryStep.path, testType })];
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Resource+type keys (no method) for the GENERATE/ADDITIONAL overlap filter —
|
|
54
|
-
* one per recorded subject endpoint. Same rule as `externalDedupKeys`: remove
|
|
55
|
-
* only on ALL keys, never on an empty list.
|
|
56
|
-
*/
|
|
57
|
-
export function scenarioCoverageKeys(scenario) {
|
|
58
|
-
const testType = resolveTestType(scenario);
|
|
59
|
-
const steps = scenario.steps ?? [];
|
|
60
|
-
const subjects = scenario.subjectEndpoints;
|
|
61
|
-
// No recorded subject: fall back to the primary step, as externalDedupKeys does.
|
|
62
|
-
if (!subjects) {
|
|
63
|
-
const { primaryStep } = resolvePrimaryStep(scenario);
|
|
64
|
-
if (!primaryStep)
|
|
65
|
-
return [];
|
|
66
|
-
return [
|
|
67
|
-
overlapKey(primaryStep.method, extractResourceFromPath(primaryStep.path ?? ""), testType, primaryStep),
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
return subjects.map((subject) => {
|
|
71
|
-
const step = steps.find((s) => s.method === subject.method && s.path === subject.path);
|
|
72
|
-
return overlapKey(subject.method, extractResourceFromPath(subject.path ?? ""), testType, step);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
5
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* from `buildExternalCoverageSet`, which scrapes prose and can only ever know a
|
|
80
|
-
* method and a path — adding a status there would stop every external key from
|
|
81
|
-
* matching. Both operands of the OVERLAP comparison are drafted candidates with
|
|
82
|
-
* a step list, so both sides know the method, the interaction type and the
|
|
83
|
-
* status.
|
|
84
|
-
*
|
|
85
|
-
* The method is part of the key because `extractResourceFromPath` maps
|
|
86
|
-
* `/api/orders` and `/api/orders/{id}` to the same resource. Without it a
|
|
87
|
-
* GENERATE `PATCH /api/orders/{id}` (success, 200) removed an ADDITIONAL
|
|
88
|
-
* `GET /api/orders/{id}` (success, 200) as a duplicate — a read test and an
|
|
89
|
-
* update test on one resource are not duplicates.
|
|
6
|
+
* Labels for a hand-written ordered list, taken from declaration order. Insert a
|
|
7
|
+
* key and every later label moves with it — and so does every reference built
|
|
8
|
+
* from the constant. A number counts up; a single letter walks the alphabet.
|
|
90
9
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* 2 keys for the whole pool. In 11 fixtures that removed EVERY ADDITIONAL
|
|
94
|
-
* candidate — `01-site-stats-endpoint` lost all 18. Adding the interaction type
|
|
95
|
-
* and the status roughly doubles the distinct keys on those pools. It does not
|
|
96
|
-
* make the key complete: two tests that assert different things about the same
|
|
97
|
-
* 200 response still collide, and no key built from a route can separate them.
|
|
98
|
-
*/
|
|
99
|
-
function overlapKey(method, resource, testType, step) {
|
|
100
|
-
const interaction = step?.interactionType ?? "any";
|
|
101
|
-
const status = step?.expectedStatusCode ?? 0;
|
|
102
|
-
return `${(method ?? "ANY").toUpperCase()}::${resource}::${testType}::${interaction}::${status}`;
|
|
103
|
-
}
|
|
104
|
-
export function isAttackSurfaceSecurityBoundary(scenario) {
|
|
105
|
-
return scenario.category === "security_boundary" &&
|
|
106
|
-
(scenario.isAttackSurfaceSecurityBoundary === true ||
|
|
107
|
-
scenario.description.startsWith("Attack-surface auth boundary:"));
|
|
108
|
-
}
|
|
109
|
-
export function isOrdinaryDirectAuthBoundary(scenario) {
|
|
110
|
-
return scenario.category === "security_boundary" &&
|
|
111
|
-
!isAttackSurfaceSecurityBoundary(scenario) &&
|
|
112
|
-
scenario.description.startsWith("Auth boundary:");
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Build a set of coverage keys from external (non-Skyramp) tests.
|
|
116
|
-
* Parses `testLocations` entries tagged with `[external]` to extract the
|
|
117
|
-
* method-aware `METHOD::resource::testType` keys they cover.
|
|
10
|
+
* `PromptPlan` numbers the steps it also RENDERS. Use this for a list whose
|
|
11
|
+
* text is written by hand, where a plan's step headers would change the output.
|
|
118
12
|
*/
|
|
119
|
-
export function
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
for (const m of fileList.matchAll(/\[external\]\s*\(covers:\s*([^)]+)\)/g)) {
|
|
127
|
-
const endpoints = m[1].split(",").map(e => e.trim());
|
|
128
|
-
for (const ep of endpoints) {
|
|
129
|
-
const spaceIdx = ep.indexOf(" ");
|
|
130
|
-
if (spaceIdx < 0)
|
|
131
|
-
continue;
|
|
132
|
-
const method = ep.slice(0, spaceIdx).toUpperCase();
|
|
133
|
-
const epPath = ep.slice(spaceIdx + 1);
|
|
134
|
-
const resource = extractResourceFromPath(epPath);
|
|
135
|
-
if (resource !== "unknown") {
|
|
136
|
-
if (testType === "unknown") {
|
|
137
|
-
coverage.add(coverageKey({ method, path: epPath, testType: "integration" }));
|
|
138
|
-
coverage.add(coverageKey({ method, path: epPath, testType: "contract" }));
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
coverage.add(coverageKey({ method, path: epPath, testType }));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
13
|
+
export function orderedLabels(keys, start = 1) {
|
|
14
|
+
const numeric = typeof start === "number";
|
|
15
|
+
const firstCode = numeric ? start : start.charCodeAt(0);
|
|
16
|
+
const out = {};
|
|
17
|
+
keys.forEach((key, i) => {
|
|
18
|
+
if (out[key] !== undefined) {
|
|
19
|
+
throw new Error(`orderedLabels: duplicate key "${key}"`);
|
|
145
20
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
21
|
+
if (!numeric && firstCode + i > "z".charCodeAt(0)) {
|
|
22
|
+
throw new Error(`orderedLabels: ran past "z" at key "${key}"`);
|
|
23
|
+
}
|
|
24
|
+
out[key] = numeric
|
|
25
|
+
? String(firstCode + i)
|
|
26
|
+
: String.fromCharCode(firstCode + i);
|
|
27
|
+
});
|
|
28
|
+
return out;
|
|
152
29
|
}
|
|
153
30
|
// Shared Testbot task and step labels used by prompt modules that cannot import
|
|
154
|
-
// testbot-prompts.ts directly without creating a circular dependency.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
export const
|
|
159
|
-
export const
|
|
160
|
-
export const
|
|
161
|
-
|
|
162
|
-
export const
|
|
31
|
+
// testbot-prompts.ts directly without creating a circular dependency. Every
|
|
32
|
+
// label comes from declaration order, so a task or step inserted here renumbers
|
|
33
|
+
// its successors and every cross-reference at once.
|
|
34
|
+
const TASK_LABELS = orderedLabels(["UI_PRESCAN", "ANALYZE_MAINTAIN", "GENERATE", "SUBMIT"], 0);
|
|
35
|
+
export const TASK_ANALYZE_MAINTAIN = TASK_LABELS.ANALYZE_MAINTAIN;
|
|
36
|
+
export const TASK_GENERATE = TASK_LABELS.GENERATE;
|
|
37
|
+
export const TASK_SUBMIT = TASK_LABELS.SUBMIT;
|
|
38
|
+
/** Top-level numbered steps of Task 1 ("Analyze & Maintain"). */
|
|
39
|
+
export const TESTBOT_TASK1_STEP_LABELS = orderedLabels([
|
|
40
|
+
"ANALYZE",
|
|
41
|
+
"MAINTAIN",
|
|
42
|
+
"CODE_REVIEW",
|
|
43
|
+
"UI_GROUNDING",
|
|
44
|
+
]);
|
|
45
|
+
/** Lettered sub-steps of Task 1's "Maintain existing tests" step. */
|
|
46
|
+
export const TESTBOT_TASK1_MAINTAIN_LABELS = orderedLabels([
|
|
47
|
+
"CONFIRM_EXTERNAL",
|
|
48
|
+
"TEST_HEALTH",
|
|
49
|
+
"UPDATE_INSTRUCTIONS",
|
|
50
|
+
"BASELINE",
|
|
51
|
+
"APPLY_ACTIONS",
|
|
52
|
+
"VERIFY_EXTERNAL",
|
|
53
|
+
], "a");
|
|
54
|
+
/** Lettered bullets of the multi-repo block inside Task 1's analyze step. */
|
|
55
|
+
export const TESTBOT_TASK1_MULTIREPO_LABELS = orderedLabels(["REGISTER_SERVICES", "ANALYZE_REPO", "ON_FAILURE"], "a");
|
|
163
56
|
export const taskRef = (taskId) => `Task ${taskId}`;
|
|
164
|
-
|
|
57
|
+
/** A lettered sub-step, spelled the way the prompt body spells it: `step 2(a)`. */
|
|
58
|
+
export const stepSubRef = (stepId, subId) => `step ${stepId}(${subId})`;
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { DraftedScenario } from "../../types/RepositoryAnalysis.js";
|
|
3
|
-
export declare function mergeEnrichedScenarios(serverScenarios: DraftedScenario[], raw: string): {
|
|
4
|
-
scenarios: DraftedScenario[];
|
|
5
|
-
rejectionNotes: string[];
|
|
6
|
-
};
|
|
7
2
|
export declare function registerRecommendTestsPrompt(server: McpServer): void;
|
|
@@ -2,147 +2,20 @@ import { z } from "zod";
|
|
|
2
2
|
import { StateManager, hasSessionData, getSessionData, } from "../../utils/AnalysisStateManager.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
4
|
import { buildRecommendationPrompt } from "./test-recommendation-prompt.js";
|
|
5
|
-
import {
|
|
6
|
-
import { SCENARIO_CATEGORIES } from "../../types/TestRecommendation.js";
|
|
7
|
-
import { inferExpectedStatus } from "../../utils/httpDefaults.js";
|
|
8
|
-
/**
|
|
9
|
-
* True when two step lists name the same method+path sequence, ignoring
|
|
10
|
-
* everything else (body, description, ...). Used to decide whether a
|
|
11
|
-
* previously-resolved `subjectEndpoints` still applies to an enriched
|
|
12
|
-
* replacement — this function has no PR diff in scope, so it cannot call
|
|
13
|
-
* `resolveSubjectEndpoints` itself (SKYR-4214).
|
|
14
|
-
*/
|
|
15
|
-
function sameStepRouteSequence(a, b) {
|
|
16
|
-
if (a.length !== b.length)
|
|
17
|
-
return false;
|
|
18
|
-
return a.every((step, i) => (step.method ?? "").toUpperCase() === (b[i].method ?? "").toUpperCase() && step.path === b[i].path);
|
|
19
|
-
}
|
|
20
|
-
export function mergeEnrichedScenarios(serverScenarios, raw) {
|
|
21
|
-
const rejectionNotes = [];
|
|
22
|
-
let parsed;
|
|
23
|
-
try {
|
|
24
|
-
const result = JSON.parse(raw);
|
|
25
|
-
if (!Array.isArray(result)) {
|
|
26
|
-
return { scenarios: serverScenarios, rejectionNotes: ["enrichedScenarios: expected a JSON array, got " + typeof result] };
|
|
27
|
-
}
|
|
28
|
-
parsed = result;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
logger.warning("enrichedScenarios: invalid JSON — using server-side scenarios only");
|
|
32
|
-
return { scenarios: serverScenarios, rejectionNotes: ["enrichedScenarios: invalid JSON — all scenarios skipped"] };
|
|
33
|
-
}
|
|
34
|
-
const agentScenarios = [];
|
|
35
|
-
for (const s of parsed) {
|
|
36
|
-
const name = s?.scenarioName ? String(s.scenarioName) : null;
|
|
37
|
-
const label = name ? `"${name}"` : "(unnamed)";
|
|
38
|
-
if (!name) {
|
|
39
|
-
rejectionNotes.push(`rejected ${label}: missing scenarioName`);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (!Array.isArray(s?.steps) || s.steps.length === 0) {
|
|
43
|
-
rejectionNotes.push(`rejected ${label}: missing or empty steps array`);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (!s?.category) {
|
|
47
|
-
rejectionNotes.push(`rejected ${label}: missing category`);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (!SCENARIO_CATEGORIES.includes(s.category)) {
|
|
51
|
-
rejectionNotes.push(`rejected ${label}: unknown category "${s.category}" — valid: ${SCENARIO_CATEGORIES.join(", ")}`);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
agentScenarios.push({
|
|
55
|
-
scenarioName: name,
|
|
56
|
-
description: s.description ?? "",
|
|
57
|
-
category: s.category,
|
|
58
|
-
priority: s.priority ?? "high",
|
|
59
|
-
bugCatchingTarget: s.bugCatchingTarget,
|
|
60
|
-
testType: s.testType,
|
|
61
|
-
steps: s.steps.map((st, idx) => ({
|
|
62
|
-
order: st.order ?? idx + 1,
|
|
63
|
-
method: String(st.method ?? "GET").toUpperCase(),
|
|
64
|
-
path: String(st.path ?? "/"),
|
|
65
|
-
description: st.description ?? `${st.method} ${st.path}`,
|
|
66
|
-
interactionType: st.interactionType ?? "success",
|
|
67
|
-
requestBody: st.requestBody,
|
|
68
|
-
queryParams: st.queryParams,
|
|
69
|
-
responseBody: st.responseBody,
|
|
70
|
-
// Default status code by method if omitted to avoid `statusCode: undefined` in tool calls
|
|
71
|
-
expectedStatusCode: st.expectedStatusCode ?? inferExpectedStatus(String(st.method ?? "GET")),
|
|
72
|
-
expectedResponseFields: st.expectedResponseFields,
|
|
73
|
-
bodyMustInclude: st.bodyMustInclude,
|
|
74
|
-
chainsFrom: st.chainsFrom,
|
|
75
|
-
})),
|
|
76
|
-
chainingKeys: s.chainingKeys ?? [],
|
|
77
|
-
requiresAuth: s.requiresAuth ?? true,
|
|
78
|
-
estimatedComplexity: s.estimatedComplexity ?? "moderate",
|
|
79
|
-
source: ScenarioSource.AgentEnriched,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
if (agentScenarios.length === 0) {
|
|
83
|
-
return { scenarios: serverScenarios, rejectionNotes };
|
|
84
|
-
}
|
|
85
|
-
const merged = new Map(serverScenarios.map(s => [s.scenarioName, s]));
|
|
86
|
-
for (const s of agentScenarios) {
|
|
87
|
-
const replaced = merged.get(s.scenarioName);
|
|
88
|
-
// The agent-submitted copy never carries subjectEndpoints — it is built
|
|
89
|
-
// fresh from the enriched JSON, not through resolveSubjectEndpoints. When
|
|
90
|
-
// the replaced server scenario had one and the steps still name the same
|
|
91
|
-
// endpoints, carry it forward so this candidate keeps the one recorded
|
|
92
|
-
// subject instead of falling back to a fresh last-mutating-step guess
|
|
93
|
-
// that could disagree with its siblings (SKYR-4214).
|
|
94
|
-
if (replaced?.subjectEndpoints && sameStepRouteSequence(replaced.steps, s.steps)) {
|
|
95
|
-
s.subjectEndpoints = replaced.subjectEndpoints;
|
|
96
|
-
}
|
|
97
|
-
merged.set(s.scenarioName, s);
|
|
98
|
-
}
|
|
99
|
-
logger.info("Merged agent-enriched scenarios", {
|
|
100
|
-
server: serverScenarios.length,
|
|
101
|
-
agent: agentScenarios.length,
|
|
102
|
-
total: merged.size,
|
|
103
|
-
rejected: rejectionNotes.length,
|
|
104
|
-
});
|
|
105
|
-
return { scenarios: Array.from(merged.values()), rejectionNotes };
|
|
106
|
-
}
|
|
5
|
+
import { AnalysisScope } from "../../types/RepositoryAnalysis.js";
|
|
107
6
|
export function registerRecommendTestsPrompt(server) {
|
|
108
7
|
server.registerPrompt("skyramp_recommend_tests", {
|
|
109
|
-
description: "Given the repository analysis in stateFile, produce
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"from the repository analysis, enrichedScenarios,
|
|
114
|
-
"field names, no guessed URLs
|
|
115
|
-
"
|
|
116
|
-
"prefer tests that catch specific formulas, constraints, or state transitions over generic " +
|
|
117
|
-
"happy-path coverage. Provide a stateFile path from skyramp_analyze_changes.",
|
|
8
|
+
description: "Given the repository analysis in stateFile, produce the test recommendations this change " +
|
|
9
|
+
"warrants. The server drafts candidates and states no count; you decide which of them, and " +
|
|
10
|
+
"which of your own, this change is worth.\n\n" +
|
|
11
|
+
"**Output contract:** Every recommendation names the specific bug or failure it targets. " +
|
|
12
|
+
"Parameters for generation tools must derive from the repository analysis, enrichedScenarios, " +
|
|
13
|
+
"or source code you read — no invented field names, no guessed URLs. Provide a stateFile path " +
|
|
14
|
+
"from skyramp_analyze_changes.",
|
|
118
15
|
argsSchema: {
|
|
119
16
|
stateFile: z
|
|
120
17
|
.string()
|
|
121
18
|
.describe("State file path returned by skyramp_analyze_changes"),
|
|
122
|
-
topN: z
|
|
123
|
-
.number()
|
|
124
|
-
.int()
|
|
125
|
-
.positive()
|
|
126
|
-
.default(10)
|
|
127
|
-
.optional()
|
|
128
|
-
.describe("Maximum number of ranked recommendations to return (default: 10)"),
|
|
129
|
-
enrichedScenarios: z
|
|
130
|
-
.string()
|
|
131
|
-
.optional()
|
|
132
|
-
.refine((val) => {
|
|
133
|
-
if (val === undefined || val === "")
|
|
134
|
-
return true;
|
|
135
|
-
try {
|
|
136
|
-
const parsed = JSON.parse(val);
|
|
137
|
-
return Array.isArray(parsed);
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}, { message: "enrichedScenarios must be a valid JSON array string (e.g. '[{\"scenarioName\":\"...\"}]')" })
|
|
143
|
-
.describe("JSON array of agent-drafted scenarios (DraftedScenario[]). Each must have " +
|
|
144
|
-
"scenarioName, category, and steps (array with method, path, order). " +
|
|
145
|
-
"Agent scenarios override server-side ones by scenarioName and are prioritized in ranking."),
|
|
146
19
|
},
|
|
147
20
|
}, async (args) => {
|
|
148
21
|
const stateFile = args.stateFile;
|
|
@@ -203,24 +76,8 @@ export function registerRecommendTestsPrompt(server) {
|
|
|
203
76
|
const analysisScope = rawScope === "branch_diff" || rawScope === AnalysisScope.CurrentBranchDiff
|
|
204
77
|
? AnalysisScope.CurrentBranchDiff
|
|
205
78
|
: AnalysisScope.FullRepo;
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
let mergedAnalysis = fullAnalysis;
|
|
209
|
-
let rejectionWarning = "";
|
|
210
|
-
if (enrichedRaw) {
|
|
211
|
-
const { scenarios, rejectionNotes } = mergeEnrichedScenarios(fullAnalysis.businessContext.draftedScenarios ?? [], enrichedRaw);
|
|
212
|
-
mergedAnalysis = {
|
|
213
|
-
...fullAnalysis,
|
|
214
|
-
businessContext: {
|
|
215
|
-
...fullAnalysis.businessContext,
|
|
216
|
-
draftedScenarios: scenarios,
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
if (rejectionNotes.length > 0) {
|
|
220
|
-
rejectionWarning = `\n⚠️ enrichedScenarios — ${rejectionNotes.length} scenario(s) rejected and not used in recommendations:\n${rejectionNotes.map(n => ` - ${n}`).join("\n")}\nFix and re-call skyramp_recommend_tests to incorporate corrections.\n`;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const prompt = buildRecommendationPrompt(mergedAnalysis, analysisScope, effectiveTopN, undefined, wsAuthHeader, wsAuthType, wsAuthScheme, undefined, sessionId);
|
|
79
|
+
const rejectionWarning = "";
|
|
80
|
+
const prompt = buildRecommendationPrompt(fullAnalysis, analysisScope, undefined, wsAuthHeader, wsAuthType, wsAuthScheme, sessionId);
|
|
224
81
|
logger.info("Serving recommendation prompt via MCP Prompt", {
|
|
225
82
|
stateFile,
|
|
226
83
|
analysisScope,
|
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
import { RepositoryAnalysis, AnalysisScope
|
|
1
|
+
import { RepositoryAnalysis, AnalysisScope } from "../../types/RepositoryAnalysis.js";
|
|
2
2
|
import { WorkspaceAuthType } from "../../utils/workspaceAuth.js";
|
|
3
3
|
import { PRTestContext } from "../../utils/pr-comment-parser.js";
|
|
4
|
-
|
|
5
|
-
import { buildExternalCoverageSet, externalDedupKeys } from "./recommendationShared.js";
|
|
6
|
-
export { buildExternalCoverageSet, externalDedupKeys };
|
|
7
|
-
/** Result of {@link computeScoredCandidates} — the scoring/classification
|
|
8
|
-
* inputs shared between prompt rendering and the SKYR-3879 register-plan
|
|
9
|
-
* pre-seed (analyzeChangesTool.ts), so both derive the exact same numbers. */
|
|
10
|
-
export interface ScoredCandidatesResult {
|
|
11
|
-
scored: Array<{
|
|
12
|
-
scenario: DraftedScenario;
|
|
13
|
-
priority: PriorityTier;
|
|
14
|
-
novelty: Novelty;
|
|
15
|
-
}>;
|
|
16
|
-
filteredChangedFiles: string[];
|
|
17
|
-
isUIOnlyPR: boolean;
|
|
18
|
-
hasFrontendChanges: boolean;
|
|
19
|
-
hasApiChanges: boolean;
|
|
20
|
-
maxGen: number;
|
|
21
|
-
seed: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Compute the pre-ranked scenario scoring, the UI/API classification flags,
|
|
25
|
-
* and the effective GENERATE cap — extracted from buildRecommendationPrompt
|
|
26
|
-
* so skyramp_analyze_changes (SKYR-3879 Path B pre-seed) can derive the exact
|
|
27
|
-
* same candidate set the Execution Plan prompt is built from, without
|
|
28
|
-
* duplicating (and risking drift from) this scoring logic.
|
|
29
|
-
*/
|
|
30
|
-
export declare function computeScoredCandidates(analysis: RepositoryAnalysis, analysisScope?: AnalysisScope, topN?: number, maxGenerateOverride?: number): ScoredCandidatesResult;
|
|
31
|
-
export declare function buildRecommendationPrompt(analysis: RepositoryAnalysis, analysisScope?: AnalysisScope, topN?: number, prContext?: PRTestContext, workspaceAuthHeader?: string, workspaceAuthType?: WorkspaceAuthType, workspaceAuthScheme?: string, maxGenerateOverride?: number, sessionId?: string): string;
|
|
4
|
+
export declare function buildRecommendationPrompt(analysis: RepositoryAnalysis, analysisScope?: AnalysisScope, prContext?: PRTestContext, workspaceAuthHeader?: string, workspaceAuthType?: WorkspaceAuthType, workspaceAuthScheme?: string, sessionId?: string): string;
|