@skyramp/mcp 0.3.9-rc.1 → 0.4.0-rc.2

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 (200) 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 +59 -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 +3 -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/drift-analysis-prompt.d.ts +0 -10
  19. package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
  20. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +5 -4
  21. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
  22. package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -466
  23. package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
  24. package/build/prompts/test-recommendation/recommendationSections.js +67 -309
  25. package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
  26. package/build/prompts/test-recommendation/recommendationShared.js +49 -155
  27. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
  28. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
  29. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
  30. package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
  31. package/build/prompts/testbot/planDeclarations.d.ts +6 -0
  32. package/build/prompts/testbot/planDeclarations.js +9 -0
  33. package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
  34. package/build/prompts/testbot/testbot-prompts.js +254 -382
  35. package/build/recommendation/answers.d.ts +35 -0
  36. package/build/recommendation/answers.js +96 -0
  37. package/build/recommendation/registerPlan.d.ts +49 -0
  38. package/build/recommendation/registerPlan.js +117 -0
  39. package/build/recommendation/runVerifiers.d.ts +10 -0
  40. package/build/recommendation/runVerifiers.js +49 -0
  41. package/build/recommendation/subjectStep.d.ts +42 -0
  42. package/build/recommendation/subjectStep.js +86 -0
  43. package/build/recommendation/types.d.ts +166 -0
  44. package/build/recommendation/types.js +20 -0
  45. package/build/recommendation/verifierContracts.d.ts +215 -0
  46. package/build/recommendation/verifierContracts.js +255 -0
  47. package/build/recommendation/verifiers/changedFile.d.ts +2 -0
  48. package/build/recommendation/verifiers/changedFile.js +82 -0
  49. package/build/recommendation/verifiers/citedPath.d.ts +12 -0
  50. package/build/recommendation/verifiers/citedPath.js +35 -0
  51. package/build/recommendation/verifiers/coverage.d.ts +7 -0
  52. package/build/recommendation/verifiers/coverage.js +617 -0
  53. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
  54. package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
  55. package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
  56. package/build/recommendation/verifiers/endpointGrounded.js +128 -0
  57. package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
  58. package/build/recommendation/verifiers/existingCoverage.js +51 -0
  59. package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
  60. package/build/recommendation/verifiers/expectedOutcome.js +105 -0
  61. package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
  62. package/build/recommendation/verifiers/removedElementGuarded.js +111 -0
  63. package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
  64. package/build/recommendation/verifiers/reportedCategory.js +84 -0
  65. package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
  66. package/build/recommendation/verifiers/screenRoute.js +118 -0
  67. package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
  68. package/build/recommendation/verifiers/statedDifference.js +140 -0
  69. package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
  70. package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
  71. package/build/resources/analysisResources.js +1 -114
  72. package/build/resources/testbotResource.js +23 -13
  73. package/build/services/ModularizationService.js +2 -1
  74. package/build/services/TestDiscoveryService.d.ts +3 -72
  75. package/build/services/TestDiscoveryService.js +10 -303
  76. package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
  77. package/build/skills/fixTestImportErrorsSkill.js +20 -0
  78. package/build/toolNames.d.ts +1 -0
  79. package/build/toolNames.js +1 -0
  80. package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
  81. package/build/tools/code-refactor/modularizationTool.js +2 -1
  82. package/build/tools/executeSkyrampTestTool.js +8 -0
  83. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
  84. package/build/tools/generate-tests/generateContractRestTool.js +3 -3
  85. package/build/tools/generate-tests/planGuard.d.ts +2 -2
  86. package/build/tools/generate-tests/planGuard.js +78 -18
  87. package/build/tools/one-click/oneClickTool.d.ts +0 -1
  88. package/build/tools/one-click/oneClickTool.js +0 -5
  89. package/build/tools/submitReportTool.d.ts +43 -37
  90. package/build/tools/submitReportTool.js +571 -192
  91. package/build/tools/test-management/actionsTool.js +26 -5
  92. package/build/tools/test-management/analyzeChangesTool.d.ts +168 -57
  93. package/build/tools/test-management/analyzeChangesTool.js +194 -1266
  94. package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
  95. package/build/tools/test-management/index.d.ts +1 -0
  96. package/build/tools/test-management/index.js +1 -0
  97. package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
  98. package/build/tools/test-management/registerTestPlanTool.js +610 -542
  99. package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
  100. package/build/tools/test-management/resolveScreenTool.js +289 -0
  101. package/build/types/BlueprintDigest.d.ts +34 -0
  102. package/build/types/BlueprintDigest.js +1 -0
  103. package/build/types/RepositoryAnalysis.d.ts +20 -1559
  104. package/build/types/RepositoryAnalysis.js +2 -58
  105. package/build/types/StepMethod.d.ts +40 -0
  106. package/build/types/StepMethod.js +77 -0
  107. package/build/types/TestRecommendation.d.ts +24 -24
  108. package/build/types/TestRecommendation.js +91 -89
  109. package/build/types/TestbotPromptOptions.d.ts +0 -4
  110. package/build/types/TestbotReport.d.ts +64 -2
  111. package/build/utils/AnalysisStateManager.d.ts +76 -115
  112. package/build/utils/AnalysisStateManager.js +147 -57
  113. package/build/utils/assertion-verify/api-shared-lints.js +1 -1
  114. package/build/utils/assertion-verify/metrics.js +85 -36
  115. package/build/utils/branchDiff.d.ts +63 -31
  116. package/build/utils/branchDiff.js +242 -94
  117. package/build/utils/containedPath.d.ts +18 -0
  118. package/build/utils/containedPath.js +73 -0
  119. package/build/utils/dartRouteExtractor.d.ts +18 -34
  120. package/build/utils/dartRouteExtractor.js +101 -173
  121. package/build/utils/featureFlags.d.ts +12 -0
  122. package/build/utils/featureFlags.js +14 -0
  123. package/build/utils/frontendSelectors.d.ts +34 -37
  124. package/build/utils/frontendSelectors.js +104 -154
  125. package/build/utils/pathMatching.d.ts +2 -4
  126. package/build/utils/pathMatching.js +2 -4
  127. package/build/utils/planMatchKeys.d.ts +38 -47
  128. package/build/utils/planMatchKeys.js +143 -81
  129. package/build/utils/removedUiElements.d.ts +8 -13
  130. package/build/utils/removedUiElements.js +21 -55
  131. package/build/utils/screenRoutes.d.ts +66 -0
  132. package/build/utils/screenRoutes.js +727 -0
  133. package/build/utils/sourceRouteExtractor.js +320 -112
  134. package/build/utils/testFileClassification.d.ts +11 -2
  135. package/build/utils/testFileClassification.js +44 -2
  136. package/build/utils/testFixtures.d.ts +5 -0
  137. package/build/utils/testFixtures.js +13 -0
  138. package/build/utils/utils.d.ts +0 -1
  139. package/build/utils/utils.js +0 -11
  140. package/build/utils/versions.d.ts +3 -3
  141. package/build/utils/versions.js +1 -1
  142. package/build/workspace/workspace.d.ts +12 -12
  143. package/package.json +3 -2
  144. package/plugin/.claude-plugin/plugin.json +8 -0
  145. package/plugin/plugin.json +6 -0
  146. package/plugin/prompts/declaring-a-plan.md +20 -0
  147. package/plugin/prompts/generate-tests/context-fetching.md +4 -0
  148. package/plugin/prompts/generate-tests/execution-plan.md +63 -0
  149. package/plugin/prompts/generate-tests/generation.md +108 -0
  150. package/plugin/prompts/generate-tests/path-parameters.md +1 -0
  151. package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
  152. package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
  153. package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
  154. package/plugin/prompts/plan-tests.md +42 -0
  155. package/plugin/prompts/testbot-task1.md +82 -0
  156. package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
  157. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
  158. package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
  159. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
  160. package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
  161. package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
  162. package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
  163. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
  164. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
  165. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
  166. package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
  167. package/build/recommendation/budgeters/shared.d.ts +0 -32
  168. package/build/recommendation/budgeters/shared.js +0 -246
  169. package/build/recommendation/discriminators.d.ts +0 -37
  170. package/build/recommendation/discriminators.js +0 -379
  171. package/build/recommendation/diversity.d.ts +0 -47
  172. package/build/recommendation/diversity.js +0 -101
  173. package/build/recommendation/planRanker.d.ts +0 -65
  174. package/build/recommendation/planRanker.js +0 -83
  175. package/build/recommendation/testFixtures.d.ts +0 -25
  176. package/build/recommendation/testFixtures.js +0 -45
  177. package/build/types/FrontendIntegration.d.ts +0 -28
  178. package/build/types/FrontendIntegration.js +0 -22
  179. package/build/types/Recommendation.d.ts +0 -146
  180. package/build/types/Recommendation.js +0 -74
  181. package/build/utils/changedRoutes.d.ts +0 -29
  182. package/build/utils/changedRoutes.js +0 -87
  183. package/build/utils/frontendIntegration.d.ts +0 -9
  184. package/build/utils/frontendIntegration.js +0 -243
  185. package/build/utils/importerHop.d.ts +0 -135
  186. package/build/utils/importerHop.js +0 -489
  187. package/build/utils/pathAffinityClassification.d.ts +0 -49
  188. package/build/utils/pathAffinityClassification.js +0 -180
  189. package/build/utils/pythonMountPrefixes.d.ts +0 -25
  190. package/build/utils/pythonMountPrefixes.js +0 -347
  191. package/build/utils/repoScanner.d.ts +0 -34
  192. package/build/utils/repoScanner.js +0 -300
  193. package/build/utils/routeParsers.d.ts +0 -95
  194. package/build/utils/routeParsers.js +0 -951
  195. package/build/utils/scenarioDrafting.d.ts +0 -92
  196. package/build/utils/scenarioDrafting.js +0 -951
  197. package/build/utils/subjectEndpoints.d.ts +0 -19
  198. package/build/utils/subjectEndpoints.js +0 -98
  199. package/build/utils/uiPageEnumerator.d.ts +0 -172
  200. package/build/utils/uiPageEnumerator.js +0 -474
@@ -8,5 +8,5 @@ export declare function getCommandIds(): OneClickCommandId[];
8
8
  */
9
9
  export declare function lookupCommand(id: string): OneClickCommandDef;
10
10
  export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
11
- export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
11
+ export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
12
12
  export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
@@ -1,11 +1,11 @@
1
1
  import { isLocalDevEnabled, isOneClickEnabled } from "../utils/featureFlags.js";
2
2
  import { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
3
- import { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
3
+ import { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
4
4
  import { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
5
5
  /** Base predefined one-click commands */
6
6
  const BASE_COMMAND_LIBRARY = {
7
7
  test_given_endpoint_comprehensively: TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND,
8
- full_repo_scan_recommend_generate_and_execute_top_n_tests: FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND,
8
+ full_repo_scan_recommend_generate_and_execute_top_n_tests: FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND,
9
9
  };
10
10
  /** All enabled predefined one-click commands */
11
11
  function getCommandLibrary() {
@@ -39,5 +39,5 @@ export function lookupCommand(id) {
39
39
  }
40
40
  // Re-export commands for consumers that import from commandLibrary
41
41
  export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
42
- export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
42
+ export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
43
43
  export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
@@ -1,2 +1,2 @@
1
1
  import type { OneClickCommandDef } from "../types/OneClickCommands.js";
2
- export declare const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND: OneClickCommandDef;
2
+ export declare const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND: OneClickCommandDef;
@@ -2,11 +2,15 @@
2
2
  * Predefined One-Click Command: Recommend tests and generate and execute top recommended tests
3
3
  *
4
4
  * Spec order:
5
- * skyramp_analyze_changes (combined analyze + discover + recommend)
6
- * → Generate tests for top N recommended types
5
+ * skyramp_analyze_changes (combined analyze + discover + suggest)
6
+ * → Register a test plan, then generate the tests it holds
7
7
  * → Execute each via skyramp_execute_test
8
+ *
9
+ * Step 2 works from the REGISTERED PLAN, not a ranked list. Full-repo scope follows
10
+ * the same path a diff does: the agent decides the candidate list itself and no
11
+ * server-side tier order picks the tests.
8
12
  */
9
- const fullRepoRecommendGenerateExecuteTopNSteps = [
13
+ const fullRepoRecommendGenerateExecuteSteps = [
10
14
  {
11
15
  stepIndex: 0,
12
16
  title: "Ensure workspace is initialized",
@@ -22,24 +26,36 @@ const fullRepoRecommendGenerateExecuteTopNSteps = [
22
26
  },
23
27
  {
24
28
  stepIndex: 1,
25
- title: "Analyze changes and get recommendations",
26
- description: "Call skyramp_analyze_changes with repositoryPath and analysisScope from user. This single tool scans endpoints, discovers existing tests, and returns ranked test recommendations inline. Capture the stateFile path and the ranked recommendations for step 2.",
29
+ title: "Analyze the repository and read the candidate suggestions",
30
+ description: "Call skyramp_analyze_changes with repositoryPath and analysisScope from user. This single tool scans endpoints, discovers existing tests, and returns candidate test suggestions inline. The suggestions are candidates, not a selection: nothing is ranked, approved or mandatory. Capture the stateFile path — step 2 needs it — and read the suggestions alongside the endpoint and existing-test data the response carries.",
27
31
  toolCall: {
28
32
  toolName: "skyramp_analyze_changes",
29
- description: "Analyze repo and generate ranked recommendations inline; returns stateFile",
33
+ description: "Analyze repo and return candidate suggestions inline; returns stateFile",
30
34
  inputs: {
31
35
  repositoryPath: { source: "user", paramKey: "repositoryPath" },
32
36
  scope: { source: "literal", value: "full_repo" },
33
37
  },
34
- outputs: ["stateFile", "recommendations"],
38
+ outputs: ["stateFile"],
35
39
  },
36
- conditionalGuidance: "Always call skyramp_analyze_changes — do not skip this step or use a pre-existing stateFile. Capture stateFile and ranked recommendations for step 2.",
40
+ conditionalGuidance: "Always call skyramp_analyze_changes — do not skip this step or use a pre-existing stateFile. Capture the stateFile for steps 2 and 3.",
37
41
  },
38
42
  {
39
43
  stepIndex: 2,
40
- title: "Generate tests for top N recommended types",
41
- description: "From the ranked recommendations returned by skyramp_analyze_changes in step 1, take the top N recommended test types (high priority first, then medium; N = topN from user, default 3).\n\nIMPORTANTUI or E2E in top N: If UI or E2E test types are among the top N, treat each recommendation independently. For each such recommendation, first search the repository for existing trace files suitable for the endpoint or path associated with that recommendation (look for .zip or .json Skyramp trace files in the repo that reference the recommended endpoint path or URL). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo for that recommendation, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with the recommended endpoint/path in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation for that specific recommendation.\n\nFor all other types in the top N, for each recommendation call the corresponding generation tool using an endpoint URL derived from that recommendation (api.baseUrl + endpoint path, or a full endpoint URL included in the recommendation). If a recommendation does not specify a concrete endpoint, follow the scope indicated in the recommendation (e.g. service-wide or repo-wide) and omit endpointURL or use only api.baseUrl as appropriate. In all cases also pass apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path for step 3.",
42
- conditionalGuidance: "Tool mapping by type — smoke: skyramp_smoke_test_generation, contract: skyramp_contract_test_generation, integration: skyramp_integration_test_generation, fuzz: skyramp_fuzz_test_generation, load: skyramp_load_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Limit to top N types. For UI/E2E: for each recommendation, (1) search the repo for existing traces for the endpoint or path associated with that recommendation; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction with the recommended endpoint/path → skyramp_stop_trace_collection, then use the produced traces. Record each generated test file path for step 3.",
44
+ title: "Register a test plan, then generate the tests it holds",
45
+ description: "Decide which tests this repository is missing, then register that decision with skyramp_register_test_plan (stateFile from step 1). Nothing in step 1 selected anything for you: keep a suggestion, keep none, keep all, or draft your own. How many distinct tests to write is your own judgment call — this command sets no ceiling and nothing supplies you with one. Each candidate declares what it asserts and whether it should fail or pass against the app as it stands; a full-repo scan has no diff, so no candidate needs a changed-line anchor. The tool stores every registration and returns objections objections to a named candidate with the fact behind each one. Fix the plan and register it again (there is no limit), or answer an objection by its id through answers.\n\nThen generate one test per plan candidate. The generation tools admit a scenario the registered plan covers, so register the plan BEFORE the first generation call.\n\nIMPORTANT — UI or E2E candidates: treat each one independently. For each, first search the repository for existing trace files suitable for that candidate's endpoint or path (look for .zip or .json Skyramp trace files in the repo that reference it). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo for that candidate, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with that endpoint or page in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation for that specific candidate.\n\nFor every other candidate, call the generation tool its testType names, using an endpoint URL derived from the candidate's own steps (api.baseUrl + the step path). Also pass apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path for step 3.",
46
+ toolCall: {
47
+ toolName: "skyramp_register_test_plan",
48
+ description: "Register the complete test plan for this run, then generate from it. Every registration is stored and none is refused; the objections it returns are answered or fixed, never blocking.",
49
+ inputs: {
50
+ stateFile: { source: "step", stepIndex: 1, outputKey: "stateFile" },
51
+ candidates: {
52
+ source: "literal",
53
+ value: "the tests you decided to write, each with scenarioName, description, category, testType, steps and declarations — see the tool schema for each field's rule",
54
+ },
55
+ },
56
+ outputs: ["objections"],
57
+ },
58
+ conditionalGuidance: "Register the plan first, then generate — a scenario the plan does not cover is rejected by the generation tools. Tool mapping by testType — contract: skyramp_contract_test_generation, integration: skyramp_batch_scenario_test_generation then skyramp_integration_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Only these four types: never smoke, never fuzz, never load. Generate exactly the candidates the registered plan holds — no more, and drop none of them silently; if the work shows you a test the plan lacks, register the plan again with it first. For UI/E2E candidates: (1) search the repo for existing traces for that candidate; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction → skyramp_stop_trace_collection, then use the produced traces. Record each generated test file path for step 3.",
43
59
  },
44
60
  {
45
61
  stepIndex: 3,
@@ -62,24 +78,24 @@ const fullRepoRecommendGenerateExecuteTopNSteps = [
62
78
  conditionalGuidance: "Skip if step 2 generated no tests. Iterate over each generated test file path returned directly from the tools invoked in step 2 and call skyramp_execute_test once per file. Token resolution: (1) user-provided token; (2) token from .skyramp/workspace.yml or repo config; (3) empty string '' — let skyramp_execute_test surface auth errors, then ask the user for a Bearer token to re-run.",
63
79
  },
64
80
  ];
65
- export const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND = {
81
+ export const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND = {
66
82
  id: "full_repo_scan_recommend_generate_and_execute_top_n_tests",
67
- name: "Full Repo: Recommend, Generate and Run TopN Tests",
68
- description: "Run skyramp_analyze_changes to scan the repo and get ranked recommendations, generate tests for the top N recommended types, then execute the generated tests.",
83
+ name: "Full Repo: Recommend, Generate and Run Tests",
84
+ description: "Run skyramp_analyze_changes to scan the repo, register a test plan for the tests it is missing, generate the tests that plan holds, then execute them.",
69
85
  intent: {
70
86
  contextIndicators: [
71
- "Use when the user wants to scan the entire repository with no specific endpoint or PR diff in mind — to get ranked test recommendations across all endpoints, generate the top N recommended test types, and execute them",
87
+ "Use when the user wants to scan the entire repository with no specific endpoint or PR diff in mind — to find the tests it is missing across all endpoints, generate them, and execute them",
72
88
  "Scope is always the full repository — the user has not mentioned a specific endpoint URL, path, or name",
73
89
  "Use when the user asks to recommend, generate, and execute tests for the whole repo or says something like 'run the full repo test workflow'",
74
90
  "Do NOT use when the user specifies a particular endpoint — use test_given_endpoint_comprehensively instead",
75
91
  "Do NOT use when the user asks about a PR diff or branch-scoped analysis — use skyramp_analyze_changes directly instead",
76
92
  "Do NOT use for simple single-tool requests such as 'generate a smoke test' or 'recommend tests for this PR'",
77
93
  ],
78
- purpose: "Full repo scan: get recommendationsGenerate top N types Execute generated tests (no specific endpoint, no PR diff). Cleanup is handled automatically.",
79
- workflowSummary: "Full Repo Scan → Recommend → Generate top N → Execute each test (cleanup is automatic)",
94
+ purpose: "Full repo scan: read the candidate suggestions register a plangenerate the tests it holds → execute them (no specific endpoint, no PR diff). Cleanup is handled automatically.",
95
+ workflowSummary: "Full Repo Scan → Register plan → Generate the plan's tests → Execute each test (cleanup is automatic)",
80
96
  examples: {
81
97
  use: [
82
- "scan the full repo and recommend and execute top 3 tests",
98
+ "scan the full repo and recommend and execute the tests it needs",
83
99
  "run the full repo test workflow",
84
100
  "recommend generate and execute tests for the whole repo",
85
101
  "find the best tests to write for this codebase and run them",
@@ -91,11 +107,10 @@ export const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND = {
91
107
  ],
92
108
  },
93
109
  },
94
- steps: fullRepoRecommendGenerateExecuteTopNSteps,
110
+ steps: fullRepoRecommendGenerateExecuteSteps,
95
111
  inputParams: {
96
112
  required: ["repositoryPath"],
97
113
  optional: [
98
- "topN",
99
114
  "token",
100
115
  "endpointURL",
101
116
  "apiSchema",
@@ -2,11 +2,15 @@
2
2
  * Predefined One-Click Command: Test this endpoint
3
3
  *
4
4
  * Spec order:
5
- * skyramp_analyze_changes (combined analyze + discover + recommend)
6
- * → Evaluate missing (recommended minus existing)
7
- * → Generate missing tests (by type)
5
+ * skyramp_analyze_changes (combined analyze + discover + suggest)
6
+ * → Register a test plan for the target endpoint
7
+ * → Generate the tests the plan holds (by type)
8
8
  * → Execute generated tests
9
9
  * → [if existing tests found] Analyze test health → Optional batch execute → Actions
10
+ * Step 2 works from the REGISTERED PLAN, not a ranked list and not a server-side
11
+ * coverage pre-filter; the coverage question survives as a plan-time objection.
12
+ * KNOWN DEFECT, its own ticket: step 1 passes `scope: "full_repo"` so the scan
13
+ * finds a named endpoint — a scope flag doing endpoint discovery.
10
14
  */
11
15
  const comprehensivelyTestGivenEndpointSteps = [
12
16
  {
@@ -24,30 +28,42 @@ const comprehensivelyTestGivenEndpointSteps = [
24
28
  },
25
29
  {
26
30
  stepIndex: 1,
27
- title: "Analyze changes and get recommendations",
28
- description: "Call skyramp_analyze_changes with repositoryPath and scope from the one-click invocation. It scans endpoints, discovers existing tests, and returns ranked test recommendations along with a stateFile path. The response includes a JSON block with stateFile, sessionId, and a summary (including existingTestCount), plus a ranked recommendations section. Capture the stateFile path and the ranked recommendations for later steps. The full list of existing tests is not in the response — it is stored in the stateFile on disk.",
31
+ title: "Analyze the repository and read the candidate suggestions",
32
+ description: "Call skyramp_analyze_changes with repositoryPath and scope from the one-click invocation. It scans endpoints, discovers existing tests, and returns candidate test suggestions along with a stateFile path. The suggestions are candidates, not a selection: nothing is ranked, approved or mandatory. The response includes a JSON block with stateFile, sessionId, and a summary (including existingTestCount), plus the suggestions section. Capture the stateFile path — step 2 needs it — and read the suggestions and the endpoint data for the target endpoint. The full list of existing tests is not in the response — it is stored in the stateFile on disk.",
29
33
  toolCall: {
30
34
  toolName: "skyramp_analyze_changes",
31
- description: "Analyze repo, discover tests, and generate ranked recommendations; returns stateFile",
35
+ description: "Analyze repo, discover tests, and return candidate suggestions; returns stateFile",
32
36
  inputs: {
33
37
  repositoryPath: { source: "user", paramKey: "repositoryPath" },
34
38
  scope: { source: "literal", value: "full_repo" },
35
39
  },
36
- outputs: ["stateFile", "recommendations"],
40
+ outputs: ["stateFile"],
37
41
  },
38
- conditionalGuidance: "If endpointURL is provided by the user, note it for use in generation steps. Capture the stateFile path and ranked recommendations from the response. The existing test list is not returned directly — it is embedded in the stateFile and will be used by skyramp_analyze_test_health in step 5.",
42
+ conditionalGuidance: "If endpointURL is provided by the user, note it step 2's plan is about that endpoint. Capture the stateFile path from the response. The existing test list is not returned directly — it is embedded in the stateFile and will be used by skyramp_analyze_test_health in step 5.",
39
43
  },
40
44
  {
41
45
  stepIndex: 2,
42
- title: "Evaluate missing tests",
43
- description: "Using the ranked recommendations from step 1, determine which test types are missing coverage for the target endpoint. The step 1 response includes an existingTestCount in its summary if it is 0 for the target endpoint, all recommended types are missing. If existingTestCount > 0, the full existing test list is in the stateFile (it will be processed by skyramp_analyze_test_health in step 5); for now, conservatively treat all recommended types as candidates to generate unless the recommendations explicitly note coverage. Build the list of types to generate in step 3. If all recommended types are already covered, skip steps 3 and 4.",
44
- conditionalGuidance: "For each recommended type (high priority first, then medium), check whether the recommendations indicate existing coverage for the target endpoint. If not indicated, add the type to the 'generate' list. If existingTestCount from step 1 is 0, all types are missing. If all recommended types have coverage, steps 3 and 4 can be skipped.",
46
+ title: "Register a test plan for the target endpoint",
47
+ description: "Decide which tests the target endpoint is missing, then register that decision with skyramp_register_test_plan (stateFile from step 1). Every candidate must be about the target endpoint — that is what this workflow is for; use other endpoints only as setup or cleanup steps. Cover it the way its own source warrants: the success path, the error and boundary paths its validation implies, its auth boundary, and a multi-step scenario where it takes part in a real flow. Nothing in step 1 selected anything for you, and nothing removed a candidate for being already covered.\n\nEach candidate declares what it asserts and whether it should fail or pass against the app as it stands; this run has no diff, so no candidate needs a changed-line anchor. The tool stores every registration and returns objections. One of them names an endpoint an existing repository test already covers answer it saying how your test differs, or make that candidate an in-place UPDATE of the existing test and drop it from the plan when you register it again. There is no registration limit and no objection blocks you.\n\nIf the plan ends up empty because every test the endpoint warrants already exists, skip steps 3 and 4 and go to step 5.",
48
+ toolCall: {
49
+ toolName: "skyramp_register_test_plan",
50
+ description: "Register the complete test plan for the target endpoint. Every registration is stored and none is refused; the objections it returns are answered or fixed, never blocking.",
51
+ inputs: {
52
+ stateFile: { source: "step", stepIndex: 1, outputKey: "stateFile" },
53
+ candidates: {
54
+ source: "literal",
55
+ value: "the tests you decided to write for the target endpoint, each with scenarioName, description, category, testType, steps and declarations — see the tool schema for each field's rule",
56
+ },
57
+ },
58
+ outputs: ["objections"],
59
+ },
60
+ conditionalGuidance: "Register the plan before any generation call — a scenario the plan does not cover is rejected by the generation tools. Every candidate's subject step must be the target endpoint. Existing coverage is an objection on the registration, not a reason to plan nothing: answer it or convert that candidate to an UPDATE of the existing test. If the registered plan holds no candidates, skip steps 3 and 4 and go to step 5.",
45
61
  },
46
62
  {
47
63
  stepIndex: 3,
48
- title: "Generate missing tests (by type)",
49
- description: "IMPORTANT — UI or E2E in missing list: If UI or E2E test types are in the 'missing' list, first search the repository for existing trace files suitable for the target endpoint (look for .zip or .json Skyramp trace files in the repo that reference the endpoint path or URL). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with the target endpoint in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation.\n\nFor all other types in the 'missing' list, call the corresponding generation tool using endpointURL from the one-click invocation (or api.baseUrl + endpoint path from recommendation), apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path.",
50
- conditionalGuidance: "Tool mapping by typesmoke: skyramp_smoke_test_generation, contract: skyramp_contract_test_generation, integration: skyramp_integration_test_generation, fuzz: skyramp_fuzz_test_generation, load: skyramp_load_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Only generate types in the 'missing' list. For UI/E2E: (1) search repo for existing traces for the target endpoint; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction → skyramp_stop_trace_collection, then use produced traces. Record each generated test file path for step 4.",
64
+ title: "Generate the tests your plan holds (by type)",
65
+ description: "IMPORTANT — UI or E2E candidates in the plan: first search the repository for existing trace files suitable for the target endpoint (look for .zip or .json Skyramp trace files in the repo that reference the endpoint path or URL). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with the target endpoint in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation.\n\nFor every other candidate in the plan, call the generation tool its testType names, using endpointURL from the one-click invocation (or api.baseUrl + the candidate's own step path), apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path.",
66
+ conditionalGuidance: "Tool mapping by testType — contract: skyramp_contract_test_generation, integration: skyramp_batch_scenario_test_generation then skyramp_integration_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Only these four types: never smoke, never fuzz, never load. Generate exactly the candidates the plan registered in step 2 holds — a scenario the plan does not cover is rejected; if the work shows you a test the plan lacks, register the plan again with it first. For UI/E2E: (1) search repo for existing traces for the target endpoint; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction → skyramp_stop_trace_collection, then use produced traces. Record each generated test file path for step 4.",
51
67
  },
52
68
  {
53
69
  stepIndex: 4,
@@ -119,17 +135,17 @@ const comprehensivelyTestGivenEndpointSteps = [
119
135
  export const TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND = {
120
136
  id: "test_given_endpoint_comprehensively",
121
137
  name: "Test this endpoint comprehensively",
122
- description: "Comprehensively test a REST API endpoint: analyze changes + discover existing tests + get recommendations in one step, then evaluate missing tests, generate only missing tests, execute them — then (if existing tests were found) run the test health analysis toolset (health assessment → optional batch execute → actions). State files stay in system temp.",
138
+ description: "Comprehensively test a REST API endpoint: analyze changes + discover existing tests + read candidate suggestions in one step, then register a test plan for that endpoint, generate the tests the plan holds, execute them — then (if existing tests were found) run the test health analysis toolset (health assessment → optional batch execute → actions). State files stay in system temp.",
123
139
  intent: {
124
140
  contextIndicators: [
125
141
  "Use when the user names or references a specific REST API endpoint (by path such as /api/products, full URL, or name such as 'products endpoint') and wants a complete multi-step test pipeline for it",
126
- "Covers the full endpoint testing lifecycle: discover existing tests → evaluate missing coverage → generate missing tests → execute → health analysis → maintenance actions",
142
+ "Covers the full endpoint testing lifecycle: discover existing tests → register a test plan for the endpoint → generate the tests it holds → execute → health analysis → maintenance actions",
127
143
  "Only use when the user explicitly signals a full or deep pipeline — words like 'comprehensively', 'thoroughly', 'deep test', 'full test pipeline', or 'all test types'. Do NOT trigger on bare phrases like 'test this endpoint' or 'test the products endpoint' alone — those are ambiguous and may only mean a single smoke test",
128
144
  "Do NOT use for broad repo-level requests where no specific endpoint is named — use skyramp_analyze_changes directly instead",
129
145
  "Do NOT use for simple single-tool requests such as 'generate a smoke test for this endpoint' — those go directly to the generation tool",
130
146
  ],
131
- purpose: "Deep test a given endpoint: discover existing → evaluate missing → generate missing → execute → (if existing found) health analysis → maintenance actions. Cleanup is handled automatically.",
132
- workflowSummary: "Analyze Changes → Evaluate missing → Generate missing → Execute generated → [if existing] Test Health → Batch execute → Actions (cleanup is automatic)",
147
+ purpose: "Deep test a given endpoint: discover existing → register a plan for the endpoint → generate the plan's tests → execute → (if existing found) health analysis → maintenance actions. Cleanup is handled automatically.",
148
+ workflowSummary: "Analyze Changes → Register plan → Generate the plan's tests → Execute generated → [if existing] Test Health → Batch execute → Actions (cleanup is automatic)",
133
149
  examples: {
134
150
  use: [
135
151
  "comprehensively test the products endpoint",
@@ -148,6 +164,6 @@ export const TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND = {
148
164
  steps: comprehensivelyTestGivenEndpointSteps,
149
165
  inputParams: {
150
166
  required: ["repositoryPath"],
151
- optional: ["endpointURL", "apiSchema", "topN", "token"],
167
+ optional: ["endpointURL", "apiSchema", "token"],
152
168
  },
153
169
  };
package/build/index.js CHANGED
@@ -26,7 +26,7 @@ import { registerEnhanceAssertionsTool } from "./tools/code-refactor/enhanceAsse
26
26
  import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBatchScenarioRestTool.js";
27
27
  import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
28
28
  import { registerBatchMockTool } from "./tools/generate-tests/batchMockGenerationTool.js";
29
- import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, registerRegisterTestPlanTool, } from "./tools/test-management/index.js";
29
+ import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, registerRegisterTestPlanTool, registerResolveScreenTool, } from "./tools/test-management/index.js";
30
30
  import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
31
31
  import { registerSutSetupResource } from "./resources/sutSetupResource.js";
32
32
  import { registerTestbotResource } from "./resources/testbotResource.js";
@@ -44,7 +44,7 @@ import { registerAnalysisResources } from "./resources/analysisResources.js";
44
44
  import { registerProgressResource } from "./resources/progressResource.js";
45
45
  import { AnalyticsService } from "./services/AnalyticsService.js";
46
46
  import { registerInitTriggerOnMCPInitialized } from "./utils/initAgent.js";
47
- import { isTestbotEnabled, isLocalDevEnabled } from "./utils/featureFlags.js";
47
+ import { isTestbotEnabled, isLocalDevEnabled, isSkillsLoaded, } from "./utils/featureFlags.js";
48
48
  import { findRoot, readTestIdAttribute } from "./utils/pom-scope/index.js";
49
49
  import { discoverTestIdAttribute } from "./utils/pom-scope/testIdDiscovery.js";
50
50
  import { registerPlaywrightTools, registerTraceRecordingPrompt, getPlaywrightTraceService, } from "./playwright/index.js";
@@ -170,10 +170,15 @@ testGenerationTools.forEach((registerTool) => registerTool(server));
170
170
  // Register modularization and code quality tools
171
171
  const codeQualityTools = [
172
172
  registerModularizationTool,
173
- registerFixErrorTool,
174
173
  registerCodeReuseTool,
175
174
  registerEnhanceAssertionsTool,
176
175
  ];
176
+ // With the Skyramp plugin loaded the agent already has the
177
+ // fix-test-import-errors skill; the tool would be a second copy of
178
+ // the same text (SKYR-4296).
179
+ if (!isSkillsLoaded()) {
180
+ codeQualityTools.push(registerFixErrorTool);
181
+ }
177
182
  codeQualityTools.forEach((registerTool) => registerTool(server));
178
183
  // Register analysis resources (MCP Resources for enriched data access)
179
184
  registerAnalysisResources(server);
@@ -183,6 +188,7 @@ registerAnalyzeChangesTool(server);
183
188
  registerAnalyzeTestHealthTool(server);
184
189
  registerActionsTool(server);
185
190
  registerRegisterTestPlanTool(server);
191
+ registerResolveScreenTool(server);
186
192
  // Register workspace management tools
187
193
  registerInitScanWorkspaceTool(server);
188
194
  registerInitializeWorkspaceTool(server);
@@ -0,0 +1,15 @@
1
+ import type { BlueprintCaptureDigest } from "../types/BlueprintDigest.js";
2
+ /** Where the JSON body of a `browser_blueprint` payload starts and ends. ONE
3
+ * reader for both users; a second copy would drift and deliver an unshrunk
4
+ * payload rather than fail. The body STOPS at the next `### ` header, since the
5
+ * fork appends sections after `### Result` and only that one holds JSON. */
6
+ export declare function blueprintJsonSpan(text: string): {
7
+ start: number;
8
+ end: number;
9
+ } | undefined;
10
+ export declare const MAX_ELEMENTS = 2000;
11
+ /** The digest of one payload, or undefined when nothing in it keys to a page.
12
+ * `previousUrl` is the url of the capture before this one: a repeat capture
13
+ * carries no url of its own, so without it every element an action revealed is
14
+ * dropped. Never throws — an unparseable payload costs a digest, not the capture. */
15
+ export declare function digestFromBlueprintText(payload: string, previousUrl?: string): BlueprintCaptureDigest | undefined;
@@ -0,0 +1,152 @@
1
+ /** Where the JSON body of a `browser_blueprint` payload starts and ends. ONE
2
+ * reader for both users; a second copy would drift and deliver an unshrunk
3
+ * payload rather than fail. The body STOPS at the next `### ` header, since the
4
+ * fork appends sections after `### Result` and only that one holds JSON. */
5
+ export function blueprintJsonSpan(text) {
6
+ if (typeof text !== "string")
7
+ return undefined;
8
+ const start = text.indexOf("{");
9
+ if (start === -1)
10
+ return undefined;
11
+ const nextSection = text.slice(start).search(/\n### /);
12
+ return { start, end: nextSection === -1 ? text.length : start + nextSection };
13
+ }
14
+ /** A capture is bounded, but the payload is not this module's to trust. */
15
+ const MAX_DEPTH = 12;
16
+ export const MAX_ELEMENTS = 2000;
17
+ function text(value) {
18
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
19
+ }
20
+ function rowText(value) {
21
+ if (!Array.isArray(value))
22
+ return undefined;
23
+ const rows = value.filter((entry) => typeof entry === "string");
24
+ return rows.length > 0 ? rows : undefined;
25
+ }
26
+ /** Fill a repeating element's `{param}` placeholders from one row. A literal
27
+ * substitution of what the capture recorded, never a pattern match; an unfilled
28
+ * placeholder is left as written so the template still grounds a citation. */
29
+ function fillTemplate(template, parameters) {
30
+ if (!parameters || typeof parameters !== "object")
31
+ return template;
32
+ const values = parameters;
33
+ return template.replace(/\{([^{}]+)\}/g, (placeholder, name) => {
34
+ const value = values[name];
35
+ return typeof value === "string" || typeof value === "number" ? String(value) : placeholder;
36
+ });
37
+ }
38
+ function identity(element) {
39
+ // Serialised rather than joined on a separator: a row's context text is page
40
+ // content and can hold any character, so any separator could be in it.
41
+ return JSON.stringify([element.role, element.accessibleName, element.testId ?? "", element.stableId ?? "", element.contextText ?? []]);
42
+ }
43
+ /** Every element this record describes. A repeating one keeps BOTH its template
44
+ * and each filled-in row, because the agent lifts a row's rendered name. */
45
+ function elementsOf(record) {
46
+ const role = text(record.role);
47
+ if (!role)
48
+ return [];
49
+ const testId = text(record.testId);
50
+ const stableId = text(record.stableId);
51
+ const base = (accessibleName, contextText) => ({
52
+ role,
53
+ accessibleName,
54
+ ...(testId ? { testId } : {}),
55
+ ...(stableId ? { stableId } : {}),
56
+ ...(contextText ? { contextText } : {}),
57
+ });
58
+ const name = text(record.accessibleName);
59
+ if (name)
60
+ return [base(name, rowText(record.contextText))];
61
+ const template = text(record.accessibleNameTemplate);
62
+ // An identifier with no name still counts: `uiElementGrounded` grounds on
63
+ // `testId` or `stableId` ALONE, so dropping it hid a citable capture.
64
+ if (!template)
65
+ return testId || stableId ? [base("", rowText(record.contextText))] : [];
66
+ const found = [base(template)];
67
+ for (const item of Array.isArray(record.items) ? record.items : []) {
68
+ if (!item || typeof item !== "object")
69
+ continue;
70
+ const row = item;
71
+ found.push(base(fillTemplate(template, row.parameters), rowText(row.contextText)));
72
+ }
73
+ return found;
74
+ }
75
+ /** Every element anywhere in the capture. WALKED structurally rather than read at
76
+ * known paths: the fork nests them in three places today and may add a fourth.
77
+ * The cost is an element-shaped record from somewhere unexpected, which only
78
+ * makes the pool larger and the check softer. */
79
+ function collectElements(root) {
80
+ const found = [];
81
+ let truncated = false;
82
+ const seen = new Set();
83
+ const stack = [{ value: root, depth: 0 }];
84
+ while (stack.length > 0) {
85
+ if (found.length >= MAX_ELEMENTS) {
86
+ truncated = true;
87
+ break;
88
+ }
89
+ const { value, depth } = stack.pop();
90
+ if (!value || typeof value !== "object" || depth > MAX_DEPTH)
91
+ continue;
92
+ if (Array.isArray(value)) {
93
+ for (const entry of value)
94
+ stack.push({ value: entry, depth: depth + 1 });
95
+ continue;
96
+ }
97
+ const record = value;
98
+ for (const element of elementsOf(record)) {
99
+ // Per ELEMENT, not per record: one repeating element materialises a row per
100
+ // entry of an unbounded `items` array, so one record can blow the cap.
101
+ if (found.length >= MAX_ELEMENTS) {
102
+ truncated = true;
103
+ break;
104
+ }
105
+ const key = identity(element);
106
+ if (seen.has(key))
107
+ continue;
108
+ seen.add(key);
109
+ found.push(element);
110
+ }
111
+ for (const child of Object.values(record)) {
112
+ if (child && typeof child === "object")
113
+ stack.push({ value: child, depth: depth + 1 });
114
+ }
115
+ }
116
+ return { elements: found, truncated };
117
+ }
118
+ function urlOf(parsed) {
119
+ const blueprint = parsed.blueprint;
120
+ const inner = blueprint && typeof blueprint === "object" ? blueprint : undefined;
121
+ return text(parsed.url) ?? (inner ? text(inner.url) : undefined);
122
+ }
123
+ function pageHashOf(parsed) {
124
+ const blueprint = parsed.blueprint;
125
+ const inner = blueprint && typeof blueprint === "object" ? blueprint : undefined;
126
+ return text(parsed.pageHash) ?? (inner ? text(inner.pageHash) : undefined);
127
+ }
128
+ /** The digest of one payload, or undefined when nothing in it keys to a page.
129
+ * `previousUrl` is the url of the capture before this one: a repeat capture
130
+ * carries no url of its own, so without it every element an action revealed is
131
+ * dropped. Never throws — an unparseable payload costs a digest, not the capture. */
132
+ export function digestFromBlueprintText(payload, previousUrl) {
133
+ const span = blueprintJsonSpan(payload);
134
+ if (!span)
135
+ return undefined;
136
+ let parsed;
137
+ try {
138
+ parsed = JSON.parse(payload.slice(span.start, span.end).trimEnd());
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
144
+ return undefined;
145
+ const record = parsed;
146
+ const url = urlOf(record) ?? text(previousUrl);
147
+ if (!url)
148
+ return undefined;
149
+ const pageHash = pageHashOf(record);
150
+ const { elements, truncated } = collectElements(record);
151
+ return { url, ...(pageHash ? { pageHash } : {}), elements, ...(truncated ? { elementsTruncated: true } : {}) };
152
+ }
@@ -0,0 +1,31 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { BlueprintCaptureDigest } from "../types/BlueprintDigest.js";
3
+ /** A run that takes more captures than this has a browsing problem of its own;
4
+ * the cap is here so a runaway loop cannot grow the process without bound. */
5
+ export declare const MAX_STORED_CAPTURES = 200;
6
+ /** Record one capture, or do nothing. NOTHING HERE MAY COST THE CAPTURE: the
7
+ * payload the agent receives is untouched and every failure is swallowed with a
8
+ * log line. Gated on `currentRunStateFile()`, not `resolveRunStatePath()`, because
9
+ * this gates BEHAVIOUR and `RUNNER_TEMP` is set in this repository's own CI. */
10
+ export declare function recordBlueprintCapture(result: CallToolResult | undefined): void;
11
+ /**
12
+ * Record where a `browser_navigate` took the browser.
13
+ *
14
+ * The server forwards the navigation, so it knows the destination without asking
15
+ * the agent — which is the point: a tool that exists to correct an agent's idea of
16
+ * which page it is on cannot take that page from the agent. Swallows everything,
17
+ * like the capture recorder: nothing here may cost a navigation.
18
+ */
19
+ export declare function recordBrowserNavigation(url: unknown): void;
20
+ /** The page the browser is on, and how the server came to know it: a navigation it
21
+ * forwarded (`currentUrl`) or the last capture that named a page (`blueprint`). */
22
+ export declare function lastKnownBrowserUrl(): {
23
+ url: string;
24
+ source: "currentUrl" | "blueprint";
25
+ } | undefined;
26
+ /** This run's digests, and no other run's — the same key the capture stored
27
+ * them under. A copy: a reader that spliced the array would delete the run's
28
+ * evidence. */
29
+ export declare function capturedBlueprintDigests(): BlueprintCaptureDigest[];
30
+ /** For tests, and as a safety hook between runs. */
31
+ export declare function clearBlueprintDigests(): void;