@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.
Files changed (229) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -1
  2. package/build/commands/commandLibrary.js +3 -3
  3. package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
  4. package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
  5. package/build/commands/testThisEndpointCommand.js +35 -19
  6. package/build/index.js +9 -3
  7. package/build/playwright/blueprintDigest.d.ts +15 -0
  8. package/build/playwright/blueprintDigest.js +152 -0
  9. package/build/playwright/blueprintDigestStore.d.ts +31 -0
  10. package/build/playwright/blueprintDigestStore.js +117 -0
  11. package/build/playwright/registerPlaywrightTools.js +60 -12
  12. package/build/playwright/traceRecordingPrompt.js +8 -7
  13. package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
  14. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
  15. package/build/prompts/promptAssets.d.ts +20 -0
  16. package/build/prompts/promptAssets.js +55 -0
  17. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
  18. package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
  19. package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
  20. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
  21. package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
  22. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
  23. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
  24. package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
  25. package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
  26. package/build/prompts/test-recommendation/recommendationSections.js +67 -309
  27. package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
  28. package/build/prompts/test-recommendation/recommendationShared.js +49 -155
  29. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
  30. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
  31. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
  32. package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
  33. package/build/prompts/testbot/planDeclarations.d.ts +6 -0
  34. package/build/prompts/testbot/planDeclarations.js +9 -0
  35. package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
  36. package/build/prompts/testbot/testbot-prompts.js +256 -381
  37. package/build/recommendation/answers.d.ts +35 -0
  38. package/build/recommendation/answers.js +96 -0
  39. package/build/recommendation/registerPlan.d.ts +49 -0
  40. package/build/recommendation/registerPlan.js +117 -0
  41. package/build/recommendation/runVerifiers.d.ts +10 -0
  42. package/build/recommendation/runVerifiers.js +49 -0
  43. package/build/recommendation/subjectStep.d.ts +42 -0
  44. package/build/recommendation/subjectStep.js +86 -0
  45. package/build/recommendation/types.d.ts +163 -0
  46. package/build/recommendation/types.js +20 -0
  47. package/build/recommendation/verifierContracts.d.ts +382 -0
  48. package/build/recommendation/verifierContracts.js +263 -0
  49. package/build/recommendation/verifiers/changedFile.d.ts +2 -0
  50. package/build/recommendation/verifiers/changedFile.js +82 -0
  51. package/build/recommendation/verifiers/citedPath.d.ts +12 -0
  52. package/build/recommendation/verifiers/citedPath.js +35 -0
  53. package/build/recommendation/verifiers/coverage.d.ts +7 -0
  54. package/build/recommendation/verifiers/coverage.js +617 -0
  55. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
  56. package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
  57. package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
  58. package/build/recommendation/verifiers/endpointGrounded.js +128 -0
  59. package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
  60. package/build/recommendation/verifiers/existingCoverage.js +51 -0
  61. package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
  62. package/build/recommendation/verifiers/expectedOutcome.js +105 -0
  63. package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
  64. package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
  65. package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
  66. package/build/recommendation/verifiers/reportedCategory.js +84 -0
  67. package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
  68. package/build/recommendation/verifiers/screenRoute.js +118 -0
  69. package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
  70. package/build/recommendation/verifiers/statedDifference.js +140 -0
  71. package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
  72. package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
  73. package/build/resources/analysisResources.js +1 -114
  74. package/build/resources/testbotResource.js +23 -13
  75. package/build/services/ModularizationService.js +2 -1
  76. package/build/services/TestDiscoveryService.d.ts +3 -72
  77. package/build/services/TestDiscoveryService.js +10 -303
  78. package/build/services/containerEnv.d.ts +1 -1
  79. package/build/services/containerEnv.js +12 -0
  80. package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
  81. package/build/skills/fixTestImportErrorsSkill.js +20 -0
  82. package/build/toolNames.d.ts +1 -0
  83. package/build/toolNames.js +1 -0
  84. package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
  85. package/build/tools/code-refactor/modularizationTool.js +2 -1
  86. package/build/tools/executeSkyrampTestTool.d.ts +80 -0
  87. package/build/tools/executeSkyrampTestTool.js +246 -19
  88. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
  89. package/build/tools/generate-tests/generateContractRestTool.js +3 -3
  90. package/build/tools/generate-tests/planGuard.d.ts +2 -2
  91. package/build/tools/generate-tests/planGuard.js +78 -18
  92. package/build/tools/one-click/oneClickTool.d.ts +0 -1
  93. package/build/tools/one-click/oneClickTool.js +0 -5
  94. package/build/tools/submitReportTool.d.ts +48 -42
  95. package/build/tools/submitReportTool.js +576 -193
  96. package/build/tools/test-management/actionsTool.js +72 -4
  97. package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
  98. package/build/tools/test-management/analyzeChangesTool.js +212 -1219
  99. package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
  100. package/build/tools/test-management/index.d.ts +1 -0
  101. package/build/tools/test-management/index.js +1 -0
  102. package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
  103. package/build/tools/test-management/registerTestPlanTool.js +609 -542
  104. package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
  105. package/build/tools/test-management/resolveScreenTool.js +289 -0
  106. package/build/types/BlueprintDigest.d.ts +34 -0
  107. package/build/types/BlueprintDigest.js +1 -0
  108. package/build/types/RepositoryAnalysis.d.ts +20 -1559
  109. package/build/types/RepositoryAnalysis.js +2 -58
  110. package/build/types/StepMethod.d.ts +40 -0
  111. package/build/types/StepMethod.js +77 -0
  112. package/build/types/TestAnalysis.d.ts +12 -0
  113. package/build/types/TestExecution.d.ts +4 -0
  114. package/build/types/TestRecommendation.d.ts +24 -24
  115. package/build/types/TestRecommendation.js +91 -89
  116. package/build/types/TestbotPromptOptions.d.ts +0 -4
  117. package/build/types/TestbotReport.d.ts +64 -2
  118. package/build/utils/AnalysisStateManager.d.ts +79 -113
  119. package/build/utils/AnalysisStateManager.js +147 -57
  120. package/build/utils/assertion-verify/api-shared-lints.js +1 -1
  121. package/build/utils/assertion-verify/metrics.js +85 -36
  122. package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
  123. package/build/utils/assertion-verify/ui-lints.js +32 -0
  124. package/build/utils/branchDiff.d.ts +63 -31
  125. package/build/utils/branchDiff.js +242 -94
  126. package/build/utils/containedPath.d.ts +18 -0
  127. package/build/utils/containedPath.js +73 -0
  128. package/build/utils/dartRouteExtractor.d.ts +18 -34
  129. package/build/utils/dartRouteExtractor.js +101 -173
  130. package/build/utils/featureFlags.d.ts +12 -0
  131. package/build/utils/featureFlags.js +14 -0
  132. package/build/utils/frontendSelectors.d.ts +48 -27
  133. package/build/utils/frontendSelectors.js +241 -80
  134. package/build/utils/pathMatching.d.ts +2 -4
  135. package/build/utils/pathMatching.js +2 -4
  136. package/build/utils/planMatchKeys.d.ts +38 -47
  137. package/build/utils/planMatchKeys.js +143 -81
  138. package/build/utils/rebaselineSnapshots.d.ts +24 -0
  139. package/build/utils/rebaselineSnapshots.js +65 -0
  140. package/build/utils/removedUiElements.d.ts +22 -0
  141. package/build/utils/removedUiElements.js +106 -0
  142. package/build/utils/reportVerification.d.ts +2 -6
  143. package/build/utils/reportVerification.js +61 -2
  144. package/build/utils/screenRoutes.d.ts +66 -0
  145. package/build/utils/screenRoutes.js +727 -0
  146. package/build/utils/sourceRouteExtractor.js +320 -112
  147. package/build/utils/testFileClassification.d.ts +11 -2
  148. package/build/utils/testFileClassification.js +44 -2
  149. package/build/utils/testFixtures.d.ts +5 -0
  150. package/build/utils/testFixtures.js +13 -0
  151. package/build/utils/utils.d.ts +0 -1
  152. package/build/utils/utils.js +0 -11
  153. package/build/utils/versions.d.ts +3 -3
  154. package/build/utils/versions.js +1 -1
  155. package/build/workspace/workspace.d.ts +12 -12
  156. package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
  157. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
  158. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
  159. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
  160. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
  161. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
  162. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  163. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  167. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  168. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
  170. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
  171. package/node_modules/playwright/package.json +1 -1
  172. package/package.json +4 -3
  173. package/plugin/.claude-plugin/plugin.json +8 -0
  174. package/plugin/plugin.json +6 -0
  175. package/plugin/prompts/declaring-a-plan.md +20 -0
  176. package/plugin/prompts/generate-tests/context-fetching.md +4 -0
  177. package/plugin/prompts/generate-tests/execution-plan.md +63 -0
  178. package/plugin/prompts/generate-tests/generation.md +108 -0
  179. package/plugin/prompts/generate-tests/path-parameters.md +1 -0
  180. package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
  181. package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
  182. package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
  183. package/plugin/prompts/plan-tests.md +42 -0
  184. package/plugin/prompts/testbot-task1.md +82 -0
  185. package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
  186. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
  187. package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
  188. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
  189. package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
  190. package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
  191. package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
  192. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
  193. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
  194. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
  195. package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
  196. package/build/recommendation/budgeters/shared.d.ts +0 -32
  197. package/build/recommendation/budgeters/shared.js +0 -246
  198. package/build/recommendation/discriminators.d.ts +0 -37
  199. package/build/recommendation/discriminators.js +0 -379
  200. package/build/recommendation/diversity.d.ts +0 -47
  201. package/build/recommendation/diversity.js +0 -101
  202. package/build/recommendation/planRanker.d.ts +0 -65
  203. package/build/recommendation/planRanker.js +0 -83
  204. package/build/recommendation/testFixtures.d.ts +0 -25
  205. package/build/recommendation/testFixtures.js +0 -45
  206. package/build/types/FrontendIntegration.d.ts +0 -28
  207. package/build/types/FrontendIntegration.js +0 -22
  208. package/build/types/Recommendation.d.ts +0 -146
  209. package/build/types/Recommendation.js +0 -74
  210. package/build/utils/changedRoutes.d.ts +0 -29
  211. package/build/utils/changedRoutes.js +0 -87
  212. package/build/utils/frontendIntegration.d.ts +0 -9
  213. package/build/utils/frontendIntegration.js +0 -243
  214. package/build/utils/importerHop.d.ts +0 -135
  215. package/build/utils/importerHop.js +0 -489
  216. package/build/utils/pathAffinityClassification.d.ts +0 -49
  217. package/build/utils/pathAffinityClassification.js +0 -180
  218. package/build/utils/pythonMountPrefixes.d.ts +0 -25
  219. package/build/utils/pythonMountPrefixes.js +0 -347
  220. package/build/utils/repoScanner.d.ts +0 -34
  221. package/build/utils/repoScanner.js +0 -300
  222. package/build/utils/routeParsers.d.ts +0 -95
  223. package/build/utils/routeParsers.js +0 -951
  224. package/build/utils/scenarioDrafting.d.ts +0 -92
  225. package/build/utils/scenarioDrafting.js +0 -951
  226. package/build/utils/subjectEndpoints.d.ts +0 -19
  227. package/build/utils/subjectEndpoints.js +0 -98
  228. package/build/utils/uiPageEnumerator.d.ts +0 -172
  229. 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
- * Method-aware coverage keys for external test dedup — one per recorded subject
31
- * endpoint. Method-aware so that an external test covering "GET /orders" does
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
- * The overlap key names the endpoint AND what the test asserts about it.
77
- *
78
- * It deliberately does NOT reuse `coverageKey`. That key's other operand comes
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
- * Measured on 839 nightly fixtures: 781 of them change exactly ONE route, so
92
- * every candidate resolves to the same subject and `resource::testType` yields
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 buildExternalCoverageSet(testLocations) {
120
- const coverage = new Set();
121
- let externalWithoutCoverage = 0;
122
- for (const [testType, fileList] of Object.entries(testLocations)) {
123
- const externalCount = (fileList.match(/\[external\]/g) || []).length;
124
- const coveredCount = (fileList.match(/\[external\]\s*\(covers:/g) || []).length;
125
- externalWithoutCoverage += externalCount - coveredCount;
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
- if (externalWithoutCoverage > 0) {
148
- logger.info(`${externalWithoutCoverage} external test file(s) have no extractable endpoint coverage — ` +
149
- `programmatic dedup skipped for these; prompt-level semantic coverage check is the fallback.`);
150
- }
151
- return coverage;
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
- export const TASK_UI_PRESCAN = "0";
156
- export const TASK_ANALYZE_MAINTAIN = "1";
157
- export const TASK_GENERATE = "2";
158
- export const TASK_SUBMIT = "3";
159
- export const TESTBOT_TASK1_STEP_ANALYZE = "1";
160
- export const TESTBOT_TASK1_STEP_MAINTAIN = "2";
161
- export const TESTBOT_TASK1_STEP_CODE_REVIEW = "3";
162
- export const TESTBOT_TASK1_STEP_UI_GROUNDING = "4";
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
- export const taskStepRef = (taskId, stepId) => `Task ${taskId} Step ${stepId}`;
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 { ScenarioSource, AnalysisScope } from "../../types/RepositoryAnalysis.js";
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 ranked test recommendations split into " +
110
- "GENERATE (call generation tools immediately) and ADDITIONAL (deferred, describe only).\n\n" +
111
- "**Output contract:** Every GENERATE integration test targeting a business rule or formula " +
112
- "MUST include a non-empty bugCatchingTarget. Parameters for generation tools must derive " +
113
- "from the repository analysis, enrichedScenarios, or source code you read — no invented " +
114
- "field names, no guessed URLs.\n\n" +
115
- "**Ranking:** Prioritize business_rule and security_boundary over crud. Within a category, " +
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 effectiveTopN = args.topN;
207
- const enrichedRaw = args.enrichedScenarios;
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, DraftedScenario } from "../../types/RepositoryAnalysis.js";
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
- import { Novelty, PriorityTier } from "../../types/TestRecommendation.js";
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;