@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
@@ -0,0 +1,20 @@
1
+ /** Every changed file of the run, whatever repository it sits in. */
2
+ export function allChangedFiles(ctx) {
3
+ return Object.values(ctx?.changedFiles ?? {}).flatMap((files) => files ?? []);
4
+ }
5
+ /** The changed files a planned test is checked against: its own repository's when it
6
+ * names one this run analyzed, every repository's when it names none. UNDEFINED
7
+ * when it names a repository this run does not know, which the caller objects to. */
8
+ export function changedFilesFor(ctx, plannedTest) {
9
+ const known = ctx?.changedFiles ?? {};
10
+ const repo = String(plannedTest?.repository ?? "").trim();
11
+ if (!repo)
12
+ return allChangedFiles(ctx);
13
+ const own = known[repo];
14
+ if (own)
15
+ return own;
16
+ // Only a MULTI-repository run reads an unmatched name as unknown: a single-repo
17
+ // run keys the primary as "the primary repository" when `metadata.repository` is
18
+ // absent, which no name the agent writes will ever match.
19
+ return Object.keys(known).length > 1 ? undefined : allChangedFiles(ctx);
20
+ }
@@ -0,0 +1,382 @@
1
+ /** What each plan-time check SAYS to the agent: the objection it emits, and the
2
+ * one-line question that closes it. Each suggestion also carries the judgment
3
+ * the check itself cannot make — the agent reads it at the moment it matters,
4
+ * when the objection arrives, and the skill does not repeat it. What to put in
5
+ * a field, and what the check reads, is stated once in that field's own
6
+ * `describe` in the plan tool's schema. A check compares a change the plan states
7
+ * against something the server already holds, needs no new parser or threshold,
8
+ * and can be answered by the agent. */
9
+ /** One objection's rendered text, carried verbatim from the verifier that emits it. */
10
+ export interface ContractObjection {
11
+ /** The objection. */
12
+ readonly message: string;
13
+ /** What the agent does about it. A `{placeholder}` is filled by the verifier:
14
+ * a contract holds the shape of an interpolation, never the interpolation. */
15
+ readonly suggestion: string;
16
+ }
17
+ export interface VerifierContract {
18
+ /** The verifier's own `Verifier.name`. */
19
+ readonly id: string;
20
+ /** What the registered plan has to carry for the check to have anything to read. */
21
+ readonly declarationFields: readonly string[];
22
+ /** One entry per message the verifier can emit, keyed by the case. A key naming a
23
+ * segment of the `objectionId` uses that segment's spelling. The keys are widened
24
+ * here so every contract satisfies the interface, while each contract is `as
25
+ * const`, so a reader naming a key its contract lacks fails `tsc`. */
26
+ readonly objections: Readonly<Record<string, ContractObjection>>;
27
+ /** The question the agent has to answer, in one sentence, for the schema and the
28
+ * prompt. It never names an answer that closes the check: an agent uses the
29
+ * phrase it was handed, and one check once handed out "no blueprint data". */
30
+ readonly suggestion: string;
31
+ }
32
+ export declare const CHANGED_FILE_CONTRACT: {
33
+ readonly id: "changedFile";
34
+ readonly declarationFields: readonly ["declarations.changedFile", "declarations.screenEvidence.file"];
35
+ readonly objections: {
36
+ readonly notStated: {
37
+ readonly message: "This test does not state which changed file it targets.";
38
+ readonly suggestion: "Name the repository-relative file this test is about in `changedFile`, spelled as the diff spells it.";
39
+ };
40
+ readonly unrelated: {
41
+ readonly message: "The file this test names as its changed file is not one this pull request changed.";
42
+ readonly suggestion: "Name a file from this PR's own diff in `changedFile`, repository-relative and spelled as the diff spells it — or say why this test belongs in the plan without one.";
43
+ };
44
+ readonly unknownRepository: {
45
+ readonly message: "This planned test names a repository this run did not analyze.";
46
+ readonly suggestion: "Name one of the repositories listed in the evidence, or leave `repository` out on a single-repository run. A name nothing analyzed leaves the planned test with no diff to be checked against.";
47
+ };
48
+ readonly screenEvidenceUnrelated: {
49
+ readonly message: "The file this UI test cites for its screen is not one this pull request changed.";
50
+ readonly suggestion: "Which changed file renders the screen this test opens — or which changed screen should it open instead? Name that file in `screenEvidence.file`, repository-relative, spelled as the diff spells it.";
51
+ };
52
+ };
53
+ readonly suggestion: "Which file that this pull request changed does this test target?";
54
+ };
55
+ export declare const SCREEN_ROUTE_CONTRACT: {
56
+ readonly id: "screenRoute";
57
+ readonly declarationFields: readonly ["declarations.elements.pageUrl", "declarations.screenEvidence.file", "declarations.changedFile"];
58
+ readonly objections: {
59
+ readonly mismatch: {
60
+ readonly message: "The page this UI test opens is not a page that renders the changed file it is about.";
61
+ readonly suggestion: "Open one of the routes in the evidence, or name the changed file that does render this page in `screenEvidence.file`. `skyramp_resolve_screen` gives the url to open, with the route parameters it can fill. A trace recorded on a screen the diff did not change proves nothing about the change.";
62
+ };
63
+ readonly unmounted: {
64
+ readonly message: "No route this run found renders the file this UI test is about.";
65
+ readonly suggestion: "If a route in this PR mounts it, name that route in `elements.pageUrl`. If nothing mounts it, the component is unintegrated: say so in one line and keep the planned test out of the browser.";
66
+ };
67
+ };
68
+ readonly suggestion: "Does the page this test opens render the changed file the test is about?";
69
+ };
70
+ export declare const ENDPOINT_GROUNDED_CONTRACT: {
71
+ readonly id: "endpointGrounded";
72
+ readonly declarationFields: readonly ["steps[]", "testType", "declarations.routes"];
73
+ readonly objections: {
74
+ readonly uncited: {
75
+ readonly message: "A call this test makes cites no file that declares it.";
76
+ readonly suggestion: "Add a `routes` entry naming the repository file that declares it — the route or operation need not appear verbatim in that file. A route the endpoint catalog missed is evidence of nothing: cite the file and keep the planned test.";
77
+ };
78
+ readonly disqualifiedCitation: {
79
+ readonly message: "The file this test cites for a call cannot be the file that declares it.";
80
+ readonly suggestion: "Cite the source file that declares it — not a test, not built output, not documentation. Any language or format is accepted, and the route or operation need not appear verbatim in the file.";
81
+ };
82
+ readonly uncitedAndDisqualified: {
83
+ readonly message: "This test has a call with no citation, and a call whose cited file cannot be the file that declares it.";
84
+ readonly suggestion: "Add `routes` for the call that has none, and point the other citation at the source file that declares its route or operation. Neither need appear verbatim in the file.";
85
+ };
86
+ };
87
+ readonly suggestion: "Which repository file declares this route or operation, and does an entry name that step's `order`?";
88
+ };
89
+ export declare const STATED_DIFFERENCE_CONTRACT: {
90
+ readonly id: "statedDifference";
91
+ readonly declarationFields: readonly ["steps[]", "testType", "scenarioName", "declarations.differsFrom"];
92
+ readonly objections: {
93
+ readonly unexplainedPair: {
94
+ readonly message: "Another planned test in this plan tests the same endpoint and neither says how they differ.";
95
+ readonly suggestion: "Two tests on one endpoint is a legitimate plan. This asks HOW they differ, not whether one should go. Add a `differsFrom` entry naming the other planned test and what this one asserts that it does not — or, if one really is redundant, drop it.";
96
+ };
97
+ readonly unknownPartner: {
98
+ readonly message: "A `differsFrom` entry names a partner that is not a planned test in this plan.";
99
+ readonly suggestion: "Spell the partner's `scenarioName` exactly as it appears in your own planned test list — the match is exact apart from surrounding space, so a difference in case names nothing.";
100
+ };
101
+ readonly subjectNotStated: {
102
+ readonly message: "This test makes more than one call and does not say which one it is about.";
103
+ readonly suggestion: "Set `declarations.stepUnderTest` to the `order` of the step under test. The other calls are setup, and nothing on the server ranks them for you.";
104
+ };
105
+ };
106
+ readonly suggestion: "Which call is this test about, and what does it assert that the other planned test on the same endpoint does not?";
107
+ };
108
+ export declare const EXISTING_COVERAGE_CONTRACT: {
109
+ readonly id: "existingCoverage";
110
+ readonly declarationFields: readonly ["declarations.existingTests"];
111
+ readonly objections: {
112
+ readonly citedTestMissing: {
113
+ readonly message: "This planned test names an existing test file that is not in the checkout.";
114
+ readonly suggestion: "Name the file repository-relative, spelled as the analysis lists it. Drop the entry if it names a test you did not read — naming none is an answer too.";
115
+ };
116
+ };
117
+ readonly suggestion: "Which existing tests did you read on this change, and how does this planned test differ from them?";
118
+ };
119
+ export declare const EXPECTED_OUTCOME_CONTRACT: {
120
+ readonly id: "expectedOutcome";
121
+ readonly declarationFields: readonly ["category", "declarations.expected.outcome", "declarations.expected.why"];
122
+ readonly objections: {
123
+ readonly passInShouldFailCategory: {
124
+ readonly message: "This planned test is categorised \"{category}\" but expects to pass on the app as it stands.";
125
+ readonly suggestion: "If it catches a defect, it should be red until the defect is fixed — say so. If it pins behaviour that is already correct, change the category.";
126
+ };
127
+ readonly executed: {
128
+ readonly message: "This test failed, but it did not reach the behavior it was written for — it fell over earlier. A failure like this is not a caught bug.";
129
+ readonly suggestion: "If the blocker is the test's own setup — sign-in, token, a wrong path — fix it and run the test again. If the blocker is the defect this run reports (a route that is not mounted, a server error before the behaviour), answer this objection naming that issue: the failure then stands as evidence of that issue, not as a caught bug.";
130
+ };
131
+ };
132
+ readonly suggestion: "Should this test fail until the defect is fixed, or is the category wrong for a test that pins behaviour the app already has?";
133
+ };
134
+ export declare const UI_ELEMENT_GROUNDED_CONTRACT: {
135
+ readonly id: "uiElementGrounded";
136
+ readonly declarationFields: readonly ["testType", "steps[]", "declarations.elements.items", "declarations.elements.pageUrl", "declarations.asserts", "scenarioName", "description", "declarations.expected.why"];
137
+ readonly objections: {
138
+ readonly nogrounding: {
139
+ readonly message: "This UI test names no `elements.items`, so nothing says the elements it targets are on the page.";
140
+ readonly suggestion: "Copy the elements this test targets out of the capture of the page it runs on into `elements.items`, and set `elements.pageUrl` to that page. If no captured page holds this test's elements — the changed component mounts nowhere the run reached, or the screen is behind a gate this run could not pass — answer this objection saying which, and leave `elements.items` null.";
141
+ };
142
+ readonly duplicate: {
143
+ readonly message: "`elements.items` names the same element more than once.";
144
+ readonly suggestion: "List each element once. Two entries that agree on role, name and identifiers describe one element, and the repeat states nothing the first entry did not.";
145
+ };
146
+ readonly pagecontext: {
147
+ readonly message: "This UI test names `elements.items` but no page they came from.";
148
+ readonly suggestion: "Set `elements.pageUrl` to the `url` of the capture the elements were lifted from. Without it there is no page to look them up on.";
149
+ };
150
+ readonly ungrounded: {
151
+ readonly message: "An element this UI test targets is in no blueprint captured for that page.";
152
+ readonly suggestion: "Cite what a capture shows you, never what the screen ought to have. A capture shows the page as it stood when it was taken, so an element behind a modal, a dropdown or an accordion is legitimately absent — answer this objection naming the action that reveals it. Otherwise copy the element from the capture as it is spelled there, or point `elements.pageUrl` at the page you actually captured.";
153
+ };
154
+ };
155
+ readonly suggestion: "Which capture of the page does each declared element come from, and for one that is in no capture, which action on that page reveals it?";
156
+ };
157
+ export declare const COVERAGE_CONTRACT: {
158
+ readonly id: "coverage";
159
+ readonly declarationFields: readonly ["changes", "declarations.changes", "declarations.changedFile", "declarations.routes", "declarations.screenEvidence.file", "declarations.startState", "changes[].cases", "changes[].surfaces"];
160
+ readonly objections: {
161
+ readonly change: {
162
+ readonly message: "This declared change has no test in the plan.";
163
+ readonly suggestion: "Plan a test that exercises it. If this run cannot write one, answer it and set `blocker` to what stopped the run — a service that is not running, a paired branch that no longer exists, the one credential the run holds. An answer with no blocker leaves this open: a change nothing prevented a test from reaching takes the test. Coverage per file is the basic minimum; coverage per change is what the plan is judged by, so a file covered by a shape test still leaves the change it makes untested.";
164
+ };
165
+ readonly noChanges: {
166
+ readonly message: "The plan declares no changes, so nothing says what the pull request must make different.";
167
+ readonly suggestion: "List the behaviours the pull request description promises, one sentence each, and cite them from the planned tests that test them — or answer in one line that it promises none, which a docs-only or cosmetic change legitimately does.";
168
+ };
169
+ readonly unknownChange: {
170
+ readonly message: "A planned test cites a change id that this plan does not declare.";
171
+ readonly suggestion: "Spell the change's `id` exactly as your own `changes` list gives it — the match is exact apart from surrounding space. Declare the change if it is missing.";
172
+ };
173
+ readonly file: {
174
+ readonly message: "This changed file has no test in the plan.";
175
+ readonly suggestion: "Add a planned test that cites it — as `changedFile`, as a `routes` file, or as `screenEvidence.file` — or answer in ONE line why it needs none: config only, covered by the test on the route it serves, or style-only and covered by the UI test on the page it belongs to. This check is a floor, never a cap: it never asks you to plan fewer tests.";
176
+ };
177
+ readonly stateTest: {
178
+ readonly message: "No test in this plan names the state the record is in when {route} mutates it.";
179
+ readonly suggestion: "Plan one test that puts a record in a stated state — through the API, the UI, or data the app already holds; a record with no rows yet counts — then runs the mutation on it, and name that state as `startState`. If the route can meet a record that is not freshly created, use that state. Only a planned test closes this: there is no answer for it.";
180
+ };
181
+ readonly cases: {
182
+ readonly message: "No test for this change sends the declared case {missing}.";
183
+ readonly suggestion: "Add the missing values to a test that cites this change, or answer in one line why they need no test.";
184
+ };
185
+ readonly reject: {
186
+ readonly message: "`{param}` accepts `{value}`, a record the plan creates, and no case rejects a record that is not there or belongs to another parent.";
187
+ readonly suggestion: "Add a reject case on `{param}` with a value that is not that record and a step that sends it, or answer why the route cannot refuse it.";
188
+ };
189
+ readonly surface: {
190
+ readonly message: "No {kind} test cites this change, which is on the {surface}.";
191
+ readonly suggestion: "Add a test on that surface that cites this change, or answer in one line why that surface needs no test.";
192
+ };
193
+ };
194
+ readonly suggestion: "Which planned test tests each behaviour this change promises and each file it touches, and for one nothing covers, why does it need none?";
195
+ };
196
+ export declare const REMOVED_ELEMENT_GUARDED_CONTRACT: {
197
+ readonly id: "removedElementGuarded";
198
+ readonly declarationFields: readonly ["declarations.elements.items", "declarations.asserts"];
199
+ readonly objections: {
200
+ readonly missing: {
201
+ readonly message: "The diff removes `{element}` and no planned test says it is gone.";
202
+ readonly suggestion: "Plan a UI test on the page that still renders, asserting this element is absent, and name it in that test's elements. Removing it is what this pull request ships, so the removal is what gets tested. If the diff shows it still rendering under a new identifier it is a rename, not a removal — say so as the answer.";
203
+ };
204
+ };
205
+ readonly suggestion: "Does every element this diff removes have a planned test that asserts it is gone?";
206
+ };
207
+ /** In the order of `PLAN_TIME_VERIFIERS`, which decides the order objections are
208
+ * reported in and carries no priority. References, not copies: iterating this
209
+ * array, or indexing it with a variable, widens the element to the union of them
210
+ * all and then no `objections` key resolves. */
211
+ export declare const VERIFIER_CONTRACTS: readonly [{
212
+ readonly id: "changedFile";
213
+ readonly declarationFields: readonly ["declarations.changedFile", "declarations.screenEvidence.file"];
214
+ readonly objections: {
215
+ readonly notStated: {
216
+ readonly message: "This test does not state which changed file it targets.";
217
+ readonly suggestion: "Name the repository-relative file this test is about in `changedFile`, spelled as the diff spells it.";
218
+ };
219
+ readonly unrelated: {
220
+ readonly message: "The file this test names as its changed file is not one this pull request changed.";
221
+ readonly suggestion: "Name a file from this PR's own diff in `changedFile`, repository-relative and spelled as the diff spells it — or say why this test belongs in the plan without one.";
222
+ };
223
+ readonly unknownRepository: {
224
+ readonly message: "This planned test names a repository this run did not analyze.";
225
+ readonly suggestion: "Name one of the repositories listed in the evidence, or leave `repository` out on a single-repository run. A name nothing analyzed leaves the planned test with no diff to be checked against.";
226
+ };
227
+ readonly screenEvidenceUnrelated: {
228
+ readonly message: "The file this UI test cites for its screen is not one this pull request changed.";
229
+ readonly suggestion: "Which changed file renders the screen this test opens — or which changed screen should it open instead? Name that file in `screenEvidence.file`, repository-relative, spelled as the diff spells it.";
230
+ };
231
+ };
232
+ readonly suggestion: "Which file that this pull request changed does this test target?";
233
+ }, {
234
+ readonly id: "endpointGrounded";
235
+ readonly declarationFields: readonly ["steps[]", "testType", "declarations.routes"];
236
+ readonly objections: {
237
+ readonly uncited: {
238
+ readonly message: "A call this test makes cites no file that declares it.";
239
+ readonly suggestion: "Add a `routes` entry naming the repository file that declares it — the route or operation need not appear verbatim in that file. A route the endpoint catalog missed is evidence of nothing: cite the file and keep the planned test.";
240
+ };
241
+ readonly disqualifiedCitation: {
242
+ readonly message: "The file this test cites for a call cannot be the file that declares it.";
243
+ readonly suggestion: "Cite the source file that declares it — not a test, not built output, not documentation. Any language or format is accepted, and the route or operation need not appear verbatim in the file.";
244
+ };
245
+ readonly uncitedAndDisqualified: {
246
+ readonly message: "This test has a call with no citation, and a call whose cited file cannot be the file that declares it.";
247
+ readonly suggestion: "Add `routes` for the call that has none, and point the other citation at the source file that declares its route or operation. Neither need appear verbatim in the file.";
248
+ };
249
+ };
250
+ readonly suggestion: "Which repository file declares this route or operation, and does an entry name that step's `order`?";
251
+ }, {
252
+ readonly id: "statedDifference";
253
+ readonly declarationFields: readonly ["steps[]", "testType", "scenarioName", "declarations.differsFrom"];
254
+ readonly objections: {
255
+ readonly unexplainedPair: {
256
+ readonly message: "Another planned test in this plan tests the same endpoint and neither says how they differ.";
257
+ readonly suggestion: "Two tests on one endpoint is a legitimate plan. This asks HOW they differ, not whether one should go. Add a `differsFrom` entry naming the other planned test and what this one asserts that it does not — or, if one really is redundant, drop it.";
258
+ };
259
+ readonly unknownPartner: {
260
+ readonly message: "A `differsFrom` entry names a partner that is not a planned test in this plan.";
261
+ readonly suggestion: "Spell the partner's `scenarioName` exactly as it appears in your own planned test list — the match is exact apart from surrounding space, so a difference in case names nothing.";
262
+ };
263
+ readonly subjectNotStated: {
264
+ readonly message: "This test makes more than one call and does not say which one it is about.";
265
+ readonly suggestion: "Set `declarations.stepUnderTest` to the `order` of the step under test. The other calls are setup, and nothing on the server ranks them for you.";
266
+ };
267
+ };
268
+ readonly suggestion: "Which call is this test about, and what does it assert that the other planned test on the same endpoint does not?";
269
+ }, {
270
+ readonly id: "existingCoverage";
271
+ readonly declarationFields: readonly ["declarations.existingTests"];
272
+ readonly objections: {
273
+ readonly citedTestMissing: {
274
+ readonly message: "This planned test names an existing test file that is not in the checkout.";
275
+ readonly suggestion: "Name the file repository-relative, spelled as the analysis lists it. Drop the entry if it names a test you did not read — naming none is an answer too.";
276
+ };
277
+ };
278
+ readonly suggestion: "Which existing tests did you read on this change, and how does this planned test differ from them?";
279
+ }, {
280
+ readonly id: "expectedOutcome";
281
+ readonly declarationFields: readonly ["category", "declarations.expected.outcome", "declarations.expected.why"];
282
+ readonly objections: {
283
+ readonly passInShouldFailCategory: {
284
+ readonly message: "This planned test is categorised \"{category}\" but expects to pass on the app as it stands.";
285
+ readonly suggestion: "If it catches a defect, it should be red until the defect is fixed — say so. If it pins behaviour that is already correct, change the category.";
286
+ };
287
+ readonly executed: {
288
+ readonly message: "This test failed, but it did not reach the behavior it was written for — it fell over earlier. A failure like this is not a caught bug.";
289
+ readonly suggestion: "If the blocker is the test's own setup — sign-in, token, a wrong path — fix it and run the test again. If the blocker is the defect this run reports (a route that is not mounted, a server error before the behaviour), answer this objection naming that issue: the failure then stands as evidence of that issue, not as a caught bug.";
290
+ };
291
+ };
292
+ readonly suggestion: "Should this test fail until the defect is fixed, or is the category wrong for a test that pins behaviour the app already has?";
293
+ }, {
294
+ readonly id: "uiElementGrounded";
295
+ readonly declarationFields: readonly ["testType", "steps[]", "declarations.elements.items", "declarations.elements.pageUrl", "declarations.asserts", "scenarioName", "description", "declarations.expected.why"];
296
+ readonly objections: {
297
+ readonly nogrounding: {
298
+ readonly message: "This UI test names no `elements.items`, so nothing says the elements it targets are on the page.";
299
+ readonly suggestion: "Copy the elements this test targets out of the capture of the page it runs on into `elements.items`, and set `elements.pageUrl` to that page. If no captured page holds this test's elements — the changed component mounts nowhere the run reached, or the screen is behind a gate this run could not pass — answer this objection saying which, and leave `elements.items` null.";
300
+ };
301
+ readonly duplicate: {
302
+ readonly message: "`elements.items` names the same element more than once.";
303
+ readonly suggestion: "List each element once. Two entries that agree on role, name and identifiers describe one element, and the repeat states nothing the first entry did not.";
304
+ };
305
+ readonly pagecontext: {
306
+ readonly message: "This UI test names `elements.items` but no page they came from.";
307
+ readonly suggestion: "Set `elements.pageUrl` to the `url` of the capture the elements were lifted from. Without it there is no page to look them up on.";
308
+ };
309
+ readonly ungrounded: {
310
+ readonly message: "An element this UI test targets is in no blueprint captured for that page.";
311
+ readonly suggestion: "Cite what a capture shows you, never what the screen ought to have. A capture shows the page as it stood when it was taken, so an element behind a modal, a dropdown or an accordion is legitimately absent — answer this objection naming the action that reveals it. Otherwise copy the element from the capture as it is spelled there, or point `elements.pageUrl` at the page you actually captured.";
312
+ };
313
+ };
314
+ readonly suggestion: "Which capture of the page does each declared element come from, and for one that is in no capture, which action on that page reveals it?";
315
+ }, {
316
+ readonly id: "screenRoute";
317
+ readonly declarationFields: readonly ["declarations.elements.pageUrl", "declarations.screenEvidence.file", "declarations.changedFile"];
318
+ readonly objections: {
319
+ readonly mismatch: {
320
+ readonly message: "The page this UI test opens is not a page that renders the changed file it is about.";
321
+ readonly suggestion: "Open one of the routes in the evidence, or name the changed file that does render this page in `screenEvidence.file`. `skyramp_resolve_screen` gives the url to open, with the route parameters it can fill. A trace recorded on a screen the diff did not change proves nothing about the change.";
322
+ };
323
+ readonly unmounted: {
324
+ readonly message: "No route this run found renders the file this UI test is about.";
325
+ readonly suggestion: "If a route in this PR mounts it, name that route in `elements.pageUrl`. If nothing mounts it, the component is unintegrated: say so in one line and keep the planned test out of the browser.";
326
+ };
327
+ };
328
+ readonly suggestion: "Does the page this test opens render the changed file the test is about?";
329
+ }, {
330
+ readonly id: "coverage";
331
+ readonly declarationFields: readonly ["changes", "declarations.changes", "declarations.changedFile", "declarations.routes", "declarations.screenEvidence.file", "declarations.startState", "changes[].cases", "changes[].surfaces"];
332
+ readonly objections: {
333
+ readonly change: {
334
+ readonly message: "This declared change has no test in the plan.";
335
+ readonly suggestion: "Plan a test that exercises it. If this run cannot write one, answer it and set `blocker` to what stopped the run — a service that is not running, a paired branch that no longer exists, the one credential the run holds. An answer with no blocker leaves this open: a change nothing prevented a test from reaching takes the test. Coverage per file is the basic minimum; coverage per change is what the plan is judged by, so a file covered by a shape test still leaves the change it makes untested.";
336
+ };
337
+ readonly noChanges: {
338
+ readonly message: "The plan declares no changes, so nothing says what the pull request must make different.";
339
+ readonly suggestion: "List the behaviours the pull request description promises, one sentence each, and cite them from the planned tests that test them — or answer in one line that it promises none, which a docs-only or cosmetic change legitimately does.";
340
+ };
341
+ readonly unknownChange: {
342
+ readonly message: "A planned test cites a change id that this plan does not declare.";
343
+ readonly suggestion: "Spell the change's `id` exactly as your own `changes` list gives it — the match is exact apart from surrounding space. Declare the change if it is missing.";
344
+ };
345
+ readonly file: {
346
+ readonly message: "This changed file has no test in the plan.";
347
+ readonly suggestion: "Add a planned test that cites it — as `changedFile`, as a `routes` file, or as `screenEvidence.file` — or answer in ONE line why it needs none: config only, covered by the test on the route it serves, or style-only and covered by the UI test on the page it belongs to. This check is a floor, never a cap: it never asks you to plan fewer tests.";
348
+ };
349
+ readonly stateTest: {
350
+ readonly message: "No test in this plan names the state the record is in when {route} mutates it.";
351
+ readonly suggestion: "Plan one test that puts a record in a stated state — through the API, the UI, or data the app already holds; a record with no rows yet counts — then runs the mutation on it, and name that state as `startState`. If the route can meet a record that is not freshly created, use that state. Only a planned test closes this: there is no answer for it.";
352
+ };
353
+ readonly cases: {
354
+ readonly message: "No test for this change sends the declared case {missing}.";
355
+ readonly suggestion: "Add the missing values to a test that cites this change, or answer in one line why they need no test.";
356
+ };
357
+ readonly reject: {
358
+ readonly message: "`{param}` accepts `{value}`, a record the plan creates, and no case rejects a record that is not there or belongs to another parent.";
359
+ readonly suggestion: "Add a reject case on `{param}` with a value that is not that record and a step that sends it, or answer why the route cannot refuse it.";
360
+ };
361
+ readonly surface: {
362
+ readonly message: "No {kind} test cites this change, which is on the {surface}.";
363
+ readonly suggestion: "Add a test on that surface that cites this change, or answer in one line why that surface needs no test.";
364
+ };
365
+ };
366
+ readonly suggestion: "Which planned test tests each behaviour this change promises and each file it touches, and for one nothing covers, why does it need none?";
367
+ }, {
368
+ readonly id: "removedElementGuarded";
369
+ readonly declarationFields: readonly ["declarations.elements.items", "declarations.asserts"];
370
+ readonly objections: {
371
+ readonly missing: {
372
+ readonly message: "The diff removes `{element}` and no planned test says it is gone.";
373
+ readonly suggestion: "Plan a UI test on the page that still renders, asserting this element is absent, and name it in that test's elements. Removing it is what this pull request ships, so the removal is what gets tested. If the diff shows it still rendering under a new identifier it is a rename, not a removal — say so as the answer.";
374
+ };
375
+ };
376
+ readonly suggestion: "Does every element this diff removes have a planned test that asserts it is gone?";
377
+ }];
378
+ /** Fills a contract sentence's `{placeholder}` spans from the run's or the
379
+ * planned test's own values. A placeholder with no value is left as written rather
380
+ * than blanked, so a missed key shows up as `{knob}` instead of reading as a gap
381
+ * in an otherwise ordinary sentence. */
382
+ export declare function fillPlaceholders(text: string, values: Record<string, string | number>): string;