@skyramp/mcp 0.4.4 → 0.5.0-rc.10

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 (144) hide show
  1. package/build/entitlements/entitlementGate.d.ts +8 -0
  2. package/build/entitlements/entitlementGate.js +57 -0
  3. package/build/entitlements/runCodes.d.ts +18 -0
  4. package/build/entitlements/runCodes.js +16 -0
  5. package/build/index.js +32 -6
  6. package/build/playwright/PlaywrightTraceService.d.ts +17 -0
  7. package/build/playwright/PlaywrightTraceService.js +29 -1
  8. package/build/playwright/dataSafetyConfig.d.ts +74 -0
  9. package/build/playwright/dataSafetyConfig.js +109 -0
  10. package/build/playwright/essentialTools.d.ts +1 -0
  11. package/build/playwright/essentialTools.js +44 -0
  12. package/build/playwright/registerPlaywrightTools.d.ts +36 -1
  13. package/build/playwright/registerPlaywrightTools.js +87 -43
  14. package/build/playwright/safeResolveDataSafety.d.ts +24 -0
  15. package/build/playwright/safeResolveDataSafety.js +33 -0
  16. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
  17. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
  18. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  19. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  20. package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
  21. package/build/services/FleetExecutionService.d.ts +87 -16
  22. package/build/services/FleetExecutionService.js +487 -48
  23. package/build/services/FleetRunRegistry.d.ts +49 -0
  24. package/build/services/FleetRunRegistry.js +178 -0
  25. package/build/services/containerEnv.js +8 -2
  26. package/build/services/fleetLimitConfirmation.d.ts +19 -0
  27. package/build/services/fleetLimitConfirmation.js +126 -0
  28. package/build/tool-phases.d.ts +14 -8
  29. package/build/tool-phases.js +88 -43
  30. package/build/tools/code-refactor/assertion-state.js +3 -1
  31. package/build/tools/code-refactor/codeReuseTool.js +2 -1
  32. package/build/tools/code-refactor/utils-verify-gates.js +1 -1
  33. package/build/tools/executeFleetTool.d.ts +6 -4
  34. package/build/tools/executeFleetTool.js +67 -23
  35. package/build/tools/fixErrorTool.js +2 -1
  36. package/build/tools/fleetLifecycleTools.d.ts +20 -0
  37. package/build/tools/fleetLifecycleTools.js +124 -0
  38. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
  39. package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
  40. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
  41. package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
  42. package/build/tools/submitReportTool.js +4 -122
  43. package/build/tools/test-management/registerTestPlanTool.js +22 -6
  44. package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
  45. package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
  46. package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
  47. package/build/types/AssertionOutcome.d.ts +3 -0
  48. package/build/types/TestbotReport.d.ts +0 -37
  49. package/build/types/TestbotReport.js +0 -22
  50. package/build/types/index.d.ts +1 -1
  51. package/build/types/index.js +1 -1
  52. package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
  53. package/build/utils/assertion-verify/created-add-guard.js +173 -0
  54. package/build/utils/assertion-verify/index.d.ts +1 -0
  55. package/build/utils/assertion-verify/index.js +1 -0
  56. package/build/utils/assertion-verify/verify.d.ts +7 -3
  57. package/build/utils/assertion-verify/verify.js +9 -3
  58. package/build/utils/featureFlags.js +6 -3
  59. package/build/utils/repositorySlug.d.ts +14 -0
  60. package/build/utils/repositorySlug.js +46 -1
  61. package/build/utils/telemetry.d.ts +2 -0
  62. package/build/utils/telemetry.js +12 -0
  63. package/build/utils/utils-verify/action-key.d.ts +59 -2
  64. package/build/utils/utils-verify/action-key.js +162 -6
  65. package/build/utils/utils-verify/action-sites.d.ts +7 -0
  66. package/build/utils/utils-verify/action-sites.js +277 -35
  67. package/build/utils/utils-verify/call-sites.d.ts +8 -2
  68. package/build/utils/utils-verify/language-spec.js +1 -1
  69. package/build/utils/utils-verify/parse.d.ts +4 -0
  70. package/build/utils/utils-verify/parse.js +2 -1
  71. package/build/utils/versions.d.ts +3 -3
  72. package/build/utils/versions.js +1 -1
  73. package/build/workspace/workspace.d.ts +63 -121
  74. package/build/workspace/workspace.js +45 -48
  75. package/build/workspace/workspaceVerifier.d.ts +22 -0
  76. package/build/workspace/workspaceVerifier.js +65 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
  78. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
  79. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
  80. package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
  81. package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
  82. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
  83. package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
  84. package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
  85. package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
  86. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
  87. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
  88. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
  89. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
  90. package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
  91. package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
  92. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  93. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  94. package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
  95. package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
  96. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  97. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
  98. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
  99. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
  100. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
  101. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
  102. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
  103. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
  104. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
  105. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
  106. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  108. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
  109. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
  112. package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
  113. package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
  114. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  115. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  116. package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
  117. package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
  118. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
  119. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
  120. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
  121. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
  122. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
  123. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
  124. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
  125. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
  126. package/node_modules/playwright/package.json +1 -1
  127. package/package.json +4 -4
  128. package/plugin/prompts/plan-tests.md +2 -2
  129. package/plugin/prompts/testbot-task1.md +1 -1
  130. package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
  131. package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
  132. package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
  133. package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
  134. package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
  135. package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
  136. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
  137. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
  138. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
  139. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
  140. package/build/prompts/sut-setup/shared.d.ts +0 -62
  141. package/build/prompts/sut-setup/shared.js +0 -196
  142. package/build/resources/sutSetupResource.d.ts +0 -2
  143. package/build/resources/sutSetupResource.js +0 -63
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
@@ -8,7 +8,11 @@ import { PlaywrightTraceService, } from "./PlaywrightTraceService.js";
8
8
  import { blueprintJsonSpan } from "./blueprintDigest.js";
9
9
  import { recordBlueprintCapture, recordBrowserNavigation, } from "./blueprintDigestStore.js";
10
10
  import { recordTraceExport } from "./traceExportStore.js";
11
+ import { ESSENTIAL_TOOLS } from "./essentialTools.js";
12
+ import { isOffLimitsUrl } from "./dataSafetyConfig.js";
13
+ import { safeResolveDataSafety } from "./safeResolveDataSafety.js";
11
14
  let _service = null;
15
+ let _dataSafety = null;
12
16
  export function getPlaywrightTraceService() {
13
17
  return _service;
14
18
  }
@@ -156,6 +160,78 @@ function shrinkBlueprintText(text) {
156
160
  return text;
157
161
  }
158
162
  }
163
+ /** Courtesy pre-check so the agent gets an immediate, readable refusal
164
+ * instead of a page-level network error. The fork's network layer remains
165
+ * the hard guarantee — this is UX, not enforcement — so a throw from the
166
+ * matcher (e.g. the vendored fork's require() resolving a partial install)
167
+ * must degrade to "not off-limits" rather than block navigation outright. */
168
+ export function precheckNavigate(url, config) {
169
+ let offLimits;
170
+ try {
171
+ offLimits = isOffLimitsUrl(url, config);
172
+ }
173
+ catch (error) {
174
+ logger.warning("offLimits matcher threw during the navigate pre-check — proceeding as not off-limits", { error });
175
+ return null;
176
+ }
177
+ if (!offLimits)
178
+ return null;
179
+ return (`### Blocked by data safety\n` +
180
+ `Navigation to ${url} is off-limits (matches a configured offLimits pattern). ` +
181
+ `Do not retry this URL; plan the flow using permitted surfaces.`);
182
+ }
183
+ /**
184
+ * Pure regression guard for the per-call browser_navigate pre-check: the
185
+ * `tool.name === "browser_navigate" && config` gate, the params.url
186
+ * extraction, and the isError result shape, all extracted out of the tool
187
+ * handler closure so they can be exercised directly under Jest without a
188
+ * real McpServer/PlaywrightTraceService. The registration call site becomes
189
+ * `const refusal = maybeRefuseNavigate(...); if (refusal) return refusal;`.
190
+ * Returns undefined for every "let the call through" case: wrong tool, no
191
+ * url param, or no config (data safety not configured for this run).
192
+ */
193
+ export function maybeRefuseNavigate(toolName, params, config) {
194
+ if (toolName !== "browser_navigate" || !config)
195
+ return undefined;
196
+ const url = params?.url;
197
+ if (url === undefined)
198
+ return undefined;
199
+ const refusal = precheckNavigate(String(url), config);
200
+ if (!refusal)
201
+ return undefined;
202
+ return { content: [{ type: "text", text: refusal }], isError: true };
203
+ }
204
+ /**
205
+ * Resolves the data-safety pre-check config for `registerPlaywrightTools`
206
+ * and normalizes an inert result (mode "off" with
207
+ * no offLimits) to `null`. MUST go through `safeResolveDataSafety` — this
208
+ * runs after `_service.initialize()` has already succeeded, so an unguarded
209
+ * rejection here (e.g. the vendored fork's `require()` resolving a partial
210
+ * install) would abort registration of all 23 Playwright browser tools, not
211
+ * just the advisory pre-check. The inner try/catch is defense in depth in
212
+ * case `safeResolveDataSafety` itself ever regresses its no-throw contract —
213
+ * either way, an undefined/failed resolution degrades to "pre-check inert"
214
+ * (`null`), never a thrown error.
215
+ *
216
+ * Exported (rather than inlined) so it can be exercised directly under Jest
217
+ * without driving the McpServer-dependent registration flow — see
218
+ * `registerPlaywrightTools.dataSafety.test.ts`.
219
+ */
220
+ export async function resolveDataSafetyForRegistration(cwd) {
221
+ let resolved;
222
+ try {
223
+ resolved = await safeResolveDataSafety(cwd);
224
+ }
225
+ catch (error) {
226
+ logger.error("Failed to resolve data-safety config for the navigate pre-check — proceeding without it", { error });
227
+ return null;
228
+ }
229
+ if (!resolved)
230
+ return null;
231
+ return resolved.mode === "off" && resolved.offLimits.length === 0
232
+ ? null
233
+ : resolved;
234
+ }
159
235
  export async function registerPlaywrightTools(server, options) {
160
236
  _service = new PlaywrightTraceService(options);
161
237
  try {
@@ -167,51 +243,16 @@ export async function registerPlaywrightTools(server, options) {
167
243
  _service = null;
168
244
  return;
169
245
  }
246
+ // This pre-check config is scoped to the whole running server process
247
+ // (module-level `_dataSafety`, set once here, read by every browser_navigate
248
+ // call — see maybeRefuseNavigate below), not to any one caller's request.
249
+ // Registration happens once at startup with no per-call `repositoryPath`, so
250
+ // process.cwd() is the only path to resolve from. When SKYRAMP_DATA_SAFETY is
251
+ // set (the testbot lane) the workspace file is not read: the env value wins,
252
+ // as it does in the fork's own parseDataSafetyConfig.
253
+ _dataSafety = await resolveDataSafetyForRegistration(process.cwd());
170
254
  const tools = _service.getTools();
171
255
  logger.info(`Registering ${tools.length} Playwright browser tools`);
172
- // Only expose essential browser tools to reduce LLM confusion.
173
- // The full set (23 tools) overwhelms the LLM and causes it to use
174
- // browser_evaluate/browser_run_code instead of the simpler tools.
175
- //
176
- // browser_blueprint is the only DOM Analyzer tool registered — it handles
177
- // both full captures and same-URL deltas via the URL-cache (isFullCapture).
178
- // browser_blueprint_diff, browser_sitemap_build, browser_sitemap_query fire
179
- // 0 times in production evals and have no active call path in the testbot prompt.
180
- //
181
- // browser_widget_contract_lookup / browser_widget_contract_cache are
182
- // intentionally excluded: the curated fingerprint library only covers
183
- // test fixtures (not production widgets), and the LLM can handle custom
184
- // widgets via snapshot-driven trial-and-error without the tool.
185
- // The widgetType field on blueprint elements still signals "custom" to
186
- // the agent — no guidance is lost.
187
- const ESSENTIAL_TOOLS = new Set([
188
- "browser_navigate",
189
- "browser_snapshot",
190
- "browser_click",
191
- "browser_type",
192
- "browser_press_key",
193
- "browser_select_option",
194
- "browser_hover",
195
- "browser_drag",
196
- "browser_file_upload",
197
- "browser_evaluate",
198
- "browser_tabs",
199
- "browser_navigate_back",
200
- "browser_wait_for",
201
- "browser_take_screenshot",
202
- "browser_assert",
203
- "browser_assert_hidden",
204
- "browser_assert_api_request",
205
- "browser_assert_table_cell",
206
- "browser_mouse_action",
207
- "browser_visual_snapshot",
208
- "browser_gojs_add_node",
209
- "browser_gojs_link",
210
- "skyramp_export_zip",
211
- "skyramp_load_trace",
212
- // DOM Analyzer: blueprint capture (capture-act-capture pattern)
213
- "browser_blueprint",
214
- ]);
215
256
  const filteredTools = tools.filter((t) => ESSENTIAL_TOOLS.has(t.name));
216
257
  logger.info(`Filtering to ${filteredTools.length} essential tools (from ${tools.length} total)`);
217
258
  for (const tool of filteredTools) {
@@ -233,6 +274,9 @@ export async function registerPlaywrightTools(server, options) {
233
274
  isError: true,
234
275
  };
235
276
  }
277
+ const refusal = maybeRefuseNavigate(tool.name, params, _dataSafety ?? undefined);
278
+ if (refusal)
279
+ return refusal;
236
280
  const result = await _service.callTool(tool.name, params);
237
281
  // Counted BEFORE any early return and REGARDLESS of isError: an export
238
282
  // that failed still cost the agent a recording, and the report gate that
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `effectiveDataSafetyConfig` does a synchronous
3
+ * `require("playwright/lib/mcp/skyramp")` with no guard against that module
4
+ * resolution failing (partial install, vendored-tarball mismatch, monorepo
5
+ * hoisting difference) — an unhandled rejection here would propagate straight
6
+ * out of any async caller that doesn't expect it. Fail OPEN here (and only
7
+ * here): the `browser_navigate` pre-check is advisory UX — real enforcement
8
+ * lives in the fork's network layer — so a broken resolution must degrade to
9
+ * "no pre-check" rather than break server startup,
10
+ * mirroring the `readWorkspaceConfigRaw` convention for workspace-file reads
11
+ * (src/utils/workspaceAuth.ts).
12
+ *
13
+ * Kept in its own module (not inlined into dataSafetyConfig.ts) so it can
14
+ * import `effectiveDataSafetyConfig` across a real module boundary — that's
15
+ * what lets tests substitute a rejecting implementation. Under this
16
+ * project's native-ESM Jest setup, an imported module's namespace is
17
+ * read-only, so a same-module call cannot be swapped out from a test the
18
+ * way a cross-module import can (see src/utils/uiPageEnumerator.test.ts).
19
+ *
20
+ * Its only caller is `resolveDataSafetyForRegistration`
21
+ * (registerPlaywrightTools.ts), which builds the navigate pre-check.
22
+ */
23
+ import { type DataSafetyConfig } from "./dataSafetyConfig.js";
24
+ export declare function safeResolveDataSafety(cwd: string): Promise<DataSafetyConfig | undefined>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `effectiveDataSafetyConfig` does a synchronous
3
+ * `require("playwright/lib/mcp/skyramp")` with no guard against that module
4
+ * resolution failing (partial install, vendored-tarball mismatch, monorepo
5
+ * hoisting difference) — an unhandled rejection here would propagate straight
6
+ * out of any async caller that doesn't expect it. Fail OPEN here (and only
7
+ * here): the `browser_navigate` pre-check is advisory UX — real enforcement
8
+ * lives in the fork's network layer — so a broken resolution must degrade to
9
+ * "no pre-check" rather than break server startup,
10
+ * mirroring the `readWorkspaceConfigRaw` convention for workspace-file reads
11
+ * (src/utils/workspaceAuth.ts).
12
+ *
13
+ * Kept in its own module (not inlined into dataSafetyConfig.ts) so it can
14
+ * import `effectiveDataSafetyConfig` across a real module boundary — that's
15
+ * what lets tests substitute a rejecting implementation. Under this
16
+ * project's native-ESM Jest setup, an imported module's namespace is
17
+ * read-only, so a same-module call cannot be swapped out from a test the
18
+ * way a cross-module import can (see src/utils/uiPageEnumerator.test.ts).
19
+ *
20
+ * Its only caller is `resolveDataSafetyForRegistration`
21
+ * (registerPlaywrightTools.ts), which builds the navigate pre-check.
22
+ */
23
+ import { logger } from "../utils/logger.js";
24
+ import { effectiveDataSafetyConfig, } from "./dataSafetyConfig.js";
25
+ export async function safeResolveDataSafety(cwd) {
26
+ try {
27
+ return await effectiveDataSafetyConfig(cwd);
28
+ }
29
+ catch (error) {
30
+ logger.error("Data-safety resolution failed — proceeding without data-safety context", { error });
31
+ return undefined;
32
+ }
33
+ }
@@ -1 +1,18 @@
1
1
  export declare const INIT_WORKSPACE_INSTRUCTIONS: string;
2
+ /** A repository of the Target other than the workspace's own, scanned into the same workspace.yml. */
3
+ export interface ScanRelatedRepository {
4
+ /** owner/repo (org/project/repo on Azure DevOps), set verbatim as each of its services' `repository`. */
5
+ repository: string;
6
+ repositoryPath: string;
7
+ }
8
+ /**
9
+ * The scan instructions for one workspace. With related repositories, every
10
+ * repository is scanned into the one workspace.yml at `workspacePath`, each
11
+ * related repository's services carrying its `repository`. With a test
12
+ * repository, testDirectory is read from, and relative to, that checkout.
13
+ */
14
+ export declare function buildInitWorkspaceInstructions(target: {
15
+ workspacePath: string;
16
+ relatedRepositories?: ScanRelatedRepository[];
17
+ testRepositoryPath?: string;
18
+ }): string;
@@ -1,178 +1,47 @@
1
1
  import { getPersonaPrefix } from "../personas.js";
2
- import { AUTH_TYPES_PROMPT_LIST } from "../../utils/workspaceAuth.js";
3
- export const INIT_WORKSPACE_INSTRUCTIONS = `${getPersonaPrefix()}Your task is to scan this repository, discover ALL services, and call the skyramp_init_workspace tool with the discovered services array and the scanToken.
4
-
5
- After scanning the workspace, before calling the skyramp_init_workspace tool, you MUST:
6
- 1. Output a <thinking> block to justify the reasoning behind each field mapping for every discovered service. Follow the scanning sections first, then field definitions, then verification.
7
- 2. Then output a Discovery Summary with the exact services array you will pass to the tool:
8
- \`\`\`json
9
- [
10
- {
11
- "serviceName": "<name>",
12
- "language": "<language>",
13
- "framework": "<framework>",
14
- "testDirectory": "<path>",
15
- "api": { "schemaPath": "<path-or-url>", "baseUrl": "<url>", "authType": "<type>", "authHeader": "<header>" },
16
- "runtimeDetails": { "runtime": "<runtime>", "serverStartCommand": "<command>", "serverStopCommand": "<docker stop command>", "dockerNetwork": "<network>" }
17
- }
18
- ]
19
- \`\`\`
20
-
21
- ### List all top-level directories
22
- <list_directories>
23
- Run a directory listing of the workspace root. Every top-level directory is a potential service. Common layouts:
24
- | Layout | Example dirs | Expect |
25
- |--------|-------------|--------|
26
- | Monorepo | apps/web, apps/api, packages/shared | 1 service per app |
27
- | Microservices | services/auth, services/orders | 1 service per service dir |
28
- | Single service | src/, lib/ | 1 service (the root) |
29
-
30
- If the repo is a monorepo, check for workspace manifests BEFORE scanning individual directories:
31
- 1. JavaScript/TypeScript monorepos:
32
- - If pnpm-workspace.yaml exists, read the packages array to find all package directories (for example, ["apps/*", "packages/*"]).
33
- - If turbo.json exists, read the root package.json workspaces field to enumerate all packages.
34
- - If lerna.json exists, read the packages array in lerna.json (for example, ["packages/*"]).
35
- - If the root package.json has a "workspaces" field, use the glob patterns to identify all packages.
36
- - If nx.json exists, scan the projects in nx.json or project.json files in subdirectories.
37
- 2. Java monorepos:
38
- - If a parent pom.xml has a <modules> section, each listed module is a separate service (Maven multi-module project).
39
- - If settings.gradle or settings.gradle.kts has include statements, each included project is a separate service (Gradle multi-project).
40
- 3. Python monorepos:
41
- - If a pyproject.toml has a [tool.poetry.packages] or [tool.setuptools.packages] section with multiple paths, each is a potential service.
42
- - If multiple directories each contain their own pyproject.toml or requirements.txt, treat each as an independent service.
43
- When any of these exist, use the workspace manifest to enumerate ALL packages and apps. Each package with its own server entry point, API routes, or UI framework is a separate service. Do NOT skip frontend packages. A Next.js app, React SPA, or Vue app in the monorepo is a service.
44
- </list_directories>
45
-
46
- ### Inspect every candidate directory
47
- <inspect_directories>
48
- For each top-level directory, check for service indicator files.
49
-
50
- Language indicators (presence of any means an independent service):
51
- 1. package.json indicates typescript or javascript
52
- 2. requirements.txt, pyproject.toml, or Pipfile indicates python
53
- 3. pom.xml or build.gradle indicates java
54
-
55
- Test framework (look inside the service directory):
56
- 1. playwright.config.* indicates playwright
57
- 2. pytest.ini, conftest.py, or pyproject.toml [tool.pytest] indicates pytest
58
- 3. junit in pom.xml indicates junit
59
-
60
- API schemas (look inside the service directory and check known framework defaults):
61
- 1. openapi.json/yaml or swagger.json/yaml provides the schema file path
62
- 2. FastAPI projects serve the schema at http://localhost:{port}/openapi.json
63
- 3. Express with swagger-ui serves at http://localhost:{port}/api-docs
64
- 4. Spring Boot serves at http://localhost:{port}/v3/api-docs
65
- </inspect_directories>
66
-
67
- ### Check root-level runtime config
68
- <runtime_config>
69
- Inspect the repo root (and subdirectories like .devcontainer/) for shared runtime configuration:
70
- 1. CLAUDE.md or AGENTS.md: if either file exists at the repo root, check it for dev/test/CI setup instructions including how to start services, required environment variables, and runtime configuration.
71
- 2. Docker Compose files: scan for ALL compose files including docker-compose.yml, docker-compose.yaml, docker-compose.*.yml (such as docker-compose.testbot.yml or docker-compose.dev.yml), compose.yml, and compose.*.yaml in the repo root and subdirectories. Distinguish between application compose files and infrastructure-only compose files. Infrastructure compose files contain only supporting services like databases, Redis, Minio, mail servers, or message queues and do NOT run the application itself. Application compose files contain the actual application services that build from the repo source code (look for "build:" directives pointing to the repo, or services that map to discovered application directories). Only use application compose files for determining runtime, serverStartCommand, and serverStopCommand. When a non-default compose file is found, the serverStartCommand and serverStopCommand must reference it explicitly (such as "docker compose -f docker-compose.testbot.yml up -d --build <service-name>" and "docker compose -f docker-compose.testbot.yml stop <service-name>"). Docker Compose ALWAYS prefixes the network name with the project name. If compose has "networks: { my-net: ... }", the actual network name is "<project-name>_my-net". If there is no explicit networks section, the default network is "<project-name>_default". The project name is the basename of the working directory where docker compose runs.
72
- 3. Makefile: extract start and dev targets.
73
- 4. Root package.json scripts: extract workspace-level commands.
74
- </runtime_config>
75
-
76
- ### Build the complete services array
77
- <build_services>
78
- Create one service entry per deployable unit. You MUST include every backend/API service (Python, Java, Go, Node.js) and every frontend service (React, Vue, Angular, Next.js). Set runtime fields from docker-compose.yml if present.
79
- </build_services>
80
-
81
- ### Basic fields
82
- <basic_fields>
83
- 1. serviceName (required): A unique identifier for the service. Use the format <repoName>-<serviceDirName>-<frontend or backend> for consistency. For example: directus-api-backend, directus-app-frontend. If the directory name already ends with "backend" or "frontend", do not duplicate the suffix (use "demoshop-fullstack-backend" not "demoshop-fullstack-backend-backend"). For single-service repos where the service directory is the root, omit the directory portion such as onlineBoutique-frontend.
84
- 2. language (required): One of python, typescript, javascript, or java. Detect from package.json (typescript or javascript), requirements.txt or pyproject.toml (python), or pom.xml or build.gradle (java).
85
- 3. framework (required): One of playwright, pytest, robot, or junit. Detect from pytest.ini, playwright.config, jest.config, or junit in pom.xml. The framework MUST match the language: python uses pytest or robot, typescript or javascript uses playwright, and java uses junit.
86
- 4. testDirectory (required): A stable path relative to the repo root where generated tests for this service will be placed. For each service, use the test directory configured by that service's test framework when one is discoverable:
87
- - Playwright: Read playwright.config.ts (or .js/.mjs) and extract the testDir value.
88
- - pytest: Read pytest.ini, pyproject.toml [tool.pytest.ini_options], or setup.cfg [tool:pytest] for testpaths.
89
- - JUnit: Usually src/test/java. Check pom.xml or build.gradle for custom test source directories.
90
- If no framework-configured test directory is available, use the Skyramp deterministic fallback:
91
- - Single service: set testDirectory to tests/skyramp.
92
- - Multiple services or monorepos: set testDirectory to tests/skyramp/<serviceDirName>, where <serviceDirName> is the service directory name with path separators and whitespace replaced by hyphens.
93
- Framework config takes precedence. Use the Skyramp deterministic fallback only when no framework-configured test directory is available.
94
- NEVER set testDirectory to \`.skyramp\`, to any path inside it, or to \`.\` or the repo root, whatever the framework config or existing test files say. \`.skyramp\` is the executor's own working area: it holds the run videos and executor artefacts. skyramp_init_workspace rejects all of these, and the generation tools refuse to write there.
95
- </basic_fields>
96
-
97
- ### API fields
98
- <api_fields>
99
- 1. api.schemaPath: Path or URL to an OpenAPI or Swagger schema. Search for openapi.json, openapi.yaml, swagger.json, or swagger.yaml files. Framework defaults are: FastAPI serves /openapi.json, Express serves /api-docs, and Spring serves /v3/api-docs. For locally-run services, use a localhost URL. For cloud or externally hosted services (such as Salesforce, Vercel, or Cloudflare), use the actual deployment URL found in config or documentation.
100
- 2. api.baseUrl (required): The base URL where the service is reachable, such as "http://localhost:3000" or "https://api.example.com". Derive from docker-compose ports, app config, README, or environment variables. Use localhost for services run locally and the actual deployment URL for cloud or externally hosted services. NEVER fabricate a URL. Only use URLs found in config files, README, or environment variables.
101
- Include the API path prefix (e.g. \`/api\`, \`/api/v1\`, \`/v1\`) when one applies — not just \`host:port\`. A bare host means every test request hits the root and 404s. Derive the prefix from the strongest available signal: OpenAPI \`servers[].url\` or \`basePath\`; the app's routing config (FastAPI \`root_path\`, Express \`app.use('/api/v1', ...)\`, Rails routes prefix, Django \`urlpatterns\` mount); or the longest common leading path across the existing test suite's endpoints. If none of those yield a prefix, leave it bare AND flag it in your summary so the user can review.
102
- 3. api.authType (required): The authentication type. Valid values are: ${AUTH_TYPES_PROMPT_LIST}
103
- Detect by checking in the following order (language-agnostic, apply whichever signals match):
104
- a. Dependencies and packages (package.json, requirements.txt, go.mod, Gemfile, composer.json, pom.xml, build.gradle):
105
- - jsonwebtoken, passport-jwt, @nestjs/jwt, jose, fastapi[security], PyJWT, python-jose, github.com/golang-jwt/jwt, jjwt, or spring-security-oauth2 indicates bearer. Spring exception: if spring-security is present but the security config uses HttpSecurity.formLogin() or sessionManagement() without a JwtDecoder bean, the actual auth is session cookies so use cookie. Check the SecurityConfig or WebSecurityConfigurerAdapter class before assigning bearer to any Spring service.
106
- - passport-http or Spring basic auth indicates basic.
107
- - passport-oauth2, openid-client, doorkeeper, keycloak-connect, spring-security-oauth2-resource-server, or github.com/coreos/go-oidc indicates oauth.
108
- - rest_framework with TokenAuthentication or djangorestframework-simplejwt (Token scheme) indicates token.
109
- - express-session, cookie-session, iron-session, next-auth, gorilla/sessions, or laravel/session indicates cookie.
110
- - laravel/sanctum or laravel/passport on API routes indicates bearer; on frontend web routes indicates cookie.
111
- b. Environment variables (.env, docker-compose, README):
112
- - JWT_SECRET, ACCESS_TOKEN_SECRET, FIREBASE_SECRET, or SUPABASE_JWT_SECRET indicates bearer.
113
- - API_KEY, X_API_KEY, ADMIN_KEY, or SERVICE_KEY (used as header value, not JWT signing) indicates apiKey.
114
- - CLIENT_ID paired with CLIENT_SECRET, OAUTH_CLIENT_*, or OIDC_* indicates oauth.
115
- - SESSION_SECRET, COOKIE_SECRET, or NEXTAUTH_SECRET indicates cookie.
116
- c. Source code and middleware patterns (auth, middleware, or security config files):
117
- - Node/Express: req.headers.authorization split "Bearer" indicates bearer. "Authorization: Token" indicates token. Custom header check indicates apiKey. Session or cookie middleware indicates cookie.
118
- - FastAPI/Starlette: HTTPBearer, OAuth2PasswordBearer, or Depends(get_current_user) indicates bearer. SessionMiddleware indicates cookie.
119
- - Django/DRF: TokenAuthentication indicates token. JWTAuthentication indicates bearer. SessionAuthentication indicates cookie.
120
- - Spring: JwtDecoder or JwtAuthenticationFilter indicates bearer. HttpSecurity.formLogin() with sessions indicates cookie.
121
- - Go: ParseWithClaims or GetHeader("Authorization") indicates bearer. gorilla/sessions indicates cookie.
122
- - Rails: authenticate_or_request_with_http_token indicates bearer. before_action :authenticate_user! (Devise) indicates cookie.
123
- - Laravel: auth:sanctum on API indicates bearer. middleware("auth") on web indicates cookie.
124
- - Rust/Axum: Authorization<Bearer> extraction indicates bearer.
125
- d. Query-parameter auth: Some APIs pass credentials as a URL query param rather than a header (such as ?key=<key>, ?api_key=<key>, or ?access_token=<token>). Signals include source code reading credentials from req.query.key, request.query_params["api_key"], or @RequestParam("token"), and API docs or README showing auth examples like /endpoint?key=<your-key>.
126
- e. Fallback: For frontend or UI services, use none. For backend APIs with no header-based auth signals, use bearer.
127
- 4. api.authHeader: The exact HTTP header name carrying the credential.
128
- - For bearer, basic, oauth, or token: always "Authorization" (inferred automatically, so you may omit it).
129
- - For cookie or session: always "Cookie" (also inferred automatically).
130
- - For apiKey: this is required. Set to the actual custom header name such as "X-API-Key" or "X-Admin-Key".
131
- - For none: set authHeader to "".
132
- 5. api.authScheme: The Authorization header prefix (the word before the token). Standard types derive this automatically: bearer uses "Bearer", token uses "Token", and basic uses "Basic". Set explicitly only for custom or non-standard schemes (for example, Hawk uses "Hawk" and Digest uses "Digest"). Omit for cookie, session, or apiKey since they do not use the Authorization header.
133
- </api_fields>
134
-
135
- ### Runtime fields
136
- <runtime_fields>
137
- 1. runtimeDetails.runtime (required): One of local, docker, or k8s. Detect per service:
138
- - If the service is listed in an application docker compose file (one that builds or runs the application, not infrastructure-only files containing only databases, caches, or queues) or has a Dockerfile as its intended run method, use "docker".
139
- - If k8s manifests exist (charts/, k8s/, deploy/) AND the service is intended to run in K8s for local development, use "k8s".
140
- - If the service has no Docker or k8s configuration and is run directly via a language runtime (npm, python, java, etc.), use "local".
141
- A repo may have MIXED runtimes. A backend in docker-compose.yml uses "docker" while a frontend run with pnpm or npm locally uses "local". Include ALL services regardless of runtime.
142
- If the frontend is bundled inside the API Docker container (no separate frontend service in the compose file), both frontend and backend services should use runtime "docker", share the same baseUrl (the container's exposed URL), and use the same serverStartCommand since they run in the same container.
143
- IMPORTANT — proxy parameters are NOT runtime indicators: If proxyKubeconfig, proxyNamespace, or proxyUrl appear in the conversation context, they are for a Skyramp proxy worker that collects traffic samples for mock generation. They do NOT indicate the repo's runtime. Do not use proxy K8s params to set runtime, k8sNamespace, or k8sContext. Determine the runtime solely from the repo's own files (docker-compose.yml, Dockerfile, k8s manifests that are part of the repo's own local dev workflow). Note that many repos have K8s deployment manifests for staging/production but use Docker Compose for local development — check which setup the repo actually uses for local dev.
144
- 2. runtimeDetails.serverStartCommand: The command to start or deploy the service. It MUST match the runtime:
145
- - For "docker" runtime: Use a Docker command that rebuilds application services from the working tree before starting them, such as "docker compose up -d --build <service-name>" for the default docker-compose.yml, or "docker compose -f <compose-file> up -d --build <service-name>" when using a non-default compose file. This is always derivable from the application docker compose file and service name. If the compose service has no build context and only references an image, note that the command cannot rebuild local code.
146
- - For "k8s" runtime: Use a deploy command such as "kubectl apply -f deploy/", "helm install myrelease .", or "skaffold run". This is always derivable from the manifests or charts present in the repo.
147
- - For "local" runtime: Use an application command such as "uvicorn main:app", "npm run dev", or "java -jar app.jar". Derive from Makefile, package.json scripts, or README. If no start command is discoverable, omit this field entirely.
148
- NEVER mix runtime types with incompatible commands (for example, using "uvicorn" with runtime "docker" will cause errors). For "local" runtime, NEVER fabricate a command. Only use commands found in Makefile, package.json scripts, or README.
149
- 3. runtimeDetails.serverStopCommand: Optional command to stop the service after test execution. ONLY set when runtime is "docker" and serverStartCommand starts persistent Docker infrastructure. For docker compose starts, prefer matching stop/down commands from the same compose file. Omit this field for "local" and "k8s" runtimes, or when teardown is unsafe or not discoverable.
150
- 4. runtimeDetails.dockerNetwork: Docker network name. ONLY set when runtime is "docker". NEVER set for "local" or "k8s".
151
- 5. runtimeDetails.k8sNamespace: Kubernetes namespace. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
152
- 6. runtimeDetails.k8sContext: Kubernetes context. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
153
- </runtime_fields>
154
-
155
- ### Verification
156
- <verification>
157
- Before calling skyramp_init_workspace, confirm all of the following:
158
- 1. Always scan the repo and find services. A repo should have at least one service.
159
- 2. ALL services are included, both backend and frontend. The workspace config is a complete registry of the entire repo, not just the service relevant to your current task. A fullstack or monorepo MUST have multiple services. If you found only one, re-scan every top-level directory before proceeding.
160
- 3. Services NOT in docker-compose.yml (such as a frontend run with pnpm or npm locally) MUST still be included with runtime "local".
161
- 4. Every service has api.baseUrl set to a valid, discoverable URL. Use localhost for local services or the actual deployment URL for cloud or external services. Never fabricate a URL.
162
- 5. Every service with authType apiKey has authHeader explicitly set to the actual custom header name (such as "X-API-Key" or "X-Admin-Key"). If you cannot find the header name in the source code, env vars, or README, do NOT use authType apiKey. Use authType none instead and add a YAML comment explaining auth is unresolved.
163
- 6. framework matches language (python uses pytest or robot, typescript or javascript uses playwright, java uses junit).
164
- 7. testDirectory follows the stable resolution rules above: framework config file when present (Playwright testDir in playwright.config.ts, pytest testpaths in pytest.ini or pyproject.toml, JUnit test source dir in pom.xml or build.gradle); otherwise the deterministic default (tests/skyramp for a single service, tests/skyramp/<serviceDirName> for multiple services). No service uses \`.skyramp\`, \`.\` or the repo root.
165
- 8. If serverStartCommand is provided, it matches the runtime. If serverStopCommand is provided, runtime is "docker" and the command is a Docker command.
166
- 9. For services in docker-compose.yml: runtime MUST be "docker" and the command MUST be a docker command such as "docker compose up -d --build <service-name>" when the service has a build context. Always include it since it is derivable from the service name.
167
- 10. NEVER use application-level commands (uvicorn, npm, node, python, java, etc.) with runtime "docker".
168
- 11. For "local" runtime: if no start command is discoverable from Makefile, package.json scripts, or README, omit serverStartCommand rather than guessing.
169
- 12. dockerNetwork is set only when runtime is "docker".
170
- 13. k8sNamespace and k8sContext are set only when runtime is "k8s".
171
- 14. If force-recreating a workspace.yml due to a schema validation error, correct the schema error but keep any valid system-under-test setup (serviceName, baseUrl, auth config, runtime details) as it is.
172
- </verification>
173
-
174
- Once verified, call skyramp_init_workspace with:
175
- - workspacePath: the repository root path
176
- - services: the array built above
177
- - scanToken: the token returned by the first call to skyramp_init_scan
178
- - force: defaults to false. Set to true only if the user explicitly asks to overwrite an existing .skyramp/workspace.yml, or if the existing file must be regenerated due to a validation failure such as a schema mismatch, unknown fields, or a parse error.`;
2
+ export const INIT_WORKSPACE_INSTRUCTIONS = `${getPersonaPrefix()}Scan this repository and register every component of the system under test in .skyramp/workspace.yml.
3
+
4
+ ### Find the components
5
+ A component runs on its own: a backend API, a frontend app, or a worker with its own entry point. Shared libraries are not components. Read the workspace manifests, compose and container files, CI workflows, README, CLAUDE.md and AGENTS.md, then check every top-level directory. Register every component, frontends included; a monorepo usually has several.
6
+
7
+ ### Fields
8
+ Take every value from a file you read or a rule below; never invent one. For each component:
9
+ - serviceName: always \`<repoName>-<componentDir>\`, or \`<repoName>\` for a component at the root.
10
+ - language: the language of its tests, or of its code when it has none, in lowercase.
11
+ - framework: the framework of its API or UI tests; when it has none, the one its repository or language commonly uses. Never leave it out.
12
+ - testDirectory: where generated tests go, relative to the repository root. Use the directory its test framework config names; otherwise \`tests/skyramp\` for a single component, or \`tests/skyramp/<componentDir>\` for several, with \`/\` in the directory replaced by \`-\`. Never \`.skyramp\`, a path inside it, or the root.
13
+ - api.baseUrl: the URL its API paths are appended to: scheme, host and port where it runs locally (or its hosted URL), plus a base path only when its OpenAPI \`servers\` URL or \`basePath\` holds one the paths leave out. A frontend the backend serves shares the backend's. api.schemaPath: its OpenAPI file or URL, if any.
14
+ - api.authType: from its auth code, dependencies or environment variables; \`none\` without a signal. For \`apiKey\`, set api.authHeader to the header name.
15
+ - runtimeDetails.runtime: \`docker\` when it runs in containers, \`k8s\` when it runs on Kubernetes, else \`local\`, as the repository runs it. dockerNetwork: the network its containers join, if any.
16
+
17
+ ### Call the tool
18
+ First write a Discovery Summary: the services array, and for each value the file or rule that set it. Then call skyramp_init_workspace with workspacePath (the repository root), services and scanToken. Set force only when the user asks to overwrite, or the existing file fails validation; then keep its valid values. When the tool refuses, fix every problem it lists and call it again with the full array and the same scanToken.`;
19
+ /**
20
+ * The scan instructions for one workspace. With related repositories, every
21
+ * repository is scanned into the one workspace.yml at `workspacePath`, each
22
+ * related repository's services carrying its `repository`. With a test
23
+ * repository, testDirectory is read from, and relative to, that checkout.
24
+ */
25
+ export function buildInitWorkspaceInstructions(target) {
26
+ const related = target.relatedRepositories ?? [];
27
+ const deliveryRoot = target.testRepositoryPath ?? target.workspacePath;
28
+ const blocks = [];
29
+ if (related.length > 0) {
30
+ blocks.push(`### Target repositories
31
+ <target_repositories>
32
+ Scan each repository below as above; pass the components of all ${related.length + 1} to one skyramp_init_workspace call with workspacePath "${target.workspacePath}":
33
+ - ${target.workspacePath}: this workspace's own, with no repository.
34
+ ${related.map((r) => `- ${r.repositoryPath}: set repository to "${r.repository}" on each of its components.`).join("\n")}
35
+ Write a related repository's paths from its own root, except testDirectory: every testDirectory is relative to ${deliveryRoot}, and a related repository's fallback is \`tests/skyramp/<repoName>\`, or \`tests/skyramp/<repoName>-<componentDir>\` with several components.
36
+ </target_repositories>`);
37
+ }
38
+ if (target.testRepositoryPath) {
39
+ blocks.push(`### Test repository
40
+ <test_repository>
41
+ Generated tests go to the test repository at ${target.testRepositoryPath}. Read testDirectory from its framework config, relative to its root, with the same fallback. Register no component for it.
42
+ </test_repository>`);
43
+ }
44
+ return blocks.length
45
+ ? `${INIT_WORKSPACE_INSTRUCTIONS}\n\n${blocks.join("\n\n")}`
46
+ : INIT_WORKSPACE_INSTRUCTIONS;
47
+ }
@@ -1,3 +1,4 @@
1
+ export declare const PICKED_OPTION_RULE = "ONE exception, because there the selector IS the value this scenario selects: the locator of the option or item the scenario picks from a list, when its test id or role name carries that option's label (`getByTestId(\"add-order-product-option-Adidas Running Shoes\")`, `getByRole(\"option\", { name: \"Adidas Running Shoes\" })`). That one argument becomes a parameter, and the call site passes the original string whole and byte-identical (`getByTestId(productOptionTestId)` called with `\"add-order-product-option-Adidas Running Shoes\"`; `getByRole(\"option\", { name: productName })` called with `\"Adidas Running Shoes\"`) \u2014 never rebuilt from a template or a shorter value. Every other selector, including the control that opens the list and the button that confirms the pick, stays verbatim.";
1
2
  export declare function getModularizationPrompt(filePath: string, testType: string, language?: string): string;
2
3
  /** Structured view of which steps render for the given inputs (for tests). */
3
4
  export declare function previewModularizationPlan(filePath: string, testType: string, language?: string): import("../test-recommendation/promptPlan.js").PlanPreview;
@@ -151,7 +151,7 @@ ${p.utilsReuse
151
151
  **SHARED HELPER SHAPE:**
152
152
  - [ ] Every helper name is a canonical intent (\`openEditOrderForm\`, \`applyDiscount\`) with no scenario value or adjective in it
153
153
  - [ ] No helper contains a value assertion (${BROWSER_VALUE_ASSERTIONS}) — each sits in the test body, right after the helper call it verifies
154
- - [ ] Every selector inside a helper is byte-identical to the original test
154
+ - [ ] Every selector inside a helper is byte-identical to the original test, except a picked option's locator argument, which is a parameter whose call-site argument is byte-identical
155
155
  - [ ] The assertion COUNT is unchanged: every \`expect(\` from the original is still in this file or inside a helper it calls, and the \`page.on('pageerror'...)\` guard and its final \`expect\` over the collected errors are still here — cross-cutting guards are never removed (the verify step counts both)
156
156
  `
157
157
  : ""}
@@ -184,10 +184,17 @@ If ANY answer is "no", fix it before submitting.
184
184
  // scenario's data — a `toHaveText("Total: $899.98")` inside a shared helper
185
185
  // fails every other caller. Selectors stay verbatim regardless (a selector is
186
186
  // not scenario data; parameterizing it invents a template the recorder never saw).
187
+ // SKYR-4537: the one selector that is scenario data. An option picked from a list
188
+ // is often located by its own label (test id or role name), so the value the
189
+ // scenario selects lives only inside that locator. Kept verbatim in the helper it
190
+ // pins every caller to this scenario's option, and the reuse scan can never match
191
+ // another test that picks a different one. Passing the whole string (no template)
192
+ // keeps the selector byte-identical at the call site.
193
+ export const PICKED_OPTION_RULE = 'ONE exception, because there the selector IS the value this scenario selects: the locator of the option or item the scenario picks from a list, when its test id or role name carries that option\'s label (`getByTestId("add-order-product-option-Adidas Running Shoes")`, `getByRole("option", { name: "Adidas Running Shoes" })`). That one argument becomes a parameter, and the call site passes the original string whole and byte-identical (`getByTestId(productOptionTestId)` called with `"add-order-product-option-Adidas Running Shoes"`; `getByRole("option", { name: productName })` called with `"Adidas Running Shoes"`) — never rebuilt from a template or a shorter value. Every other selector, including the control that opens the list and the button that confirms the pick, stays verbatim.';
187
194
  function sharedHelperShape() {
188
195
  return `**SHARED HELPER SHAPE (this project consolidates helpers into a shared utils file):**
189
196
  - ${BROWSER_HELPERS.namingRule}
190
- - **PARAMETERS**: \`page\`, plus every value this scenario types or selects (${BROWSER_HELPERS.liftableLiterals}). Selectors are NEVER parameters — they stay verbatim inside the helper body.
197
+ - **PARAMETERS**: \`page\`, plus every value this scenario types or selects (${BROWSER_HELPERS.liftableLiterals}). Selectors are NEVER parameters — they stay verbatim inside the helper body. ${PICKED_OPTION_RULE}
191
198
  - ${BROWSER_HELPERS.assertionRule} ${ASSERTION_BOUNDARY_RULE_EXTRACT}
192
199
  - Helpers are module-level named functions in this file; the test body becomes navigation + a sequence of helper calls + this scenario's assertions, in the original order.`;
193
200
  }
@@ -57,7 +57,7 @@ ${opts.domainNotes}
57
57
 
58
58
  **If your rationale concludes the test is unaffected, assign IGNORE, not VERIFY.** Assign VERIFY only for a doubt that the code you read does not settle. A rule that forbids IGNORE still applies.
59
59
 
60
- **A stated requirement wins over the code.** Before you write a new value or refresh a screenshot in an existing test, compare it with what the pull request title, the description and any requirements file they name state. If the new value breaks a stated requirement, do not write it: keep that assertion on the requirement's value and make only the other edits. If no other edit remains, mark the test VERIFY. The test stays red; do not relax it. If the requirements file is unchanged and the description presents the new behaviour as intended, the file is out of date: update the test.
60
+ **A stated requirement wins over the code.** Before you write a new value or refresh a screenshot in an existing test, compare it with what the pull request title, the description and any requirements file they name state. If the new value breaks a stated requirement, do not write it: keep that assertion on the requirement's value and make only the other edits. If no other edit remains, mark the test VERIFY. The test stays red; do not relax it. Authority runs requirements > PR description > code change: an unchanged requirements file that the description contradicts still wins — keep the assertion on the file's value and report the requirement conflict.
61
61
 
62
62
  **Pre-commit verification — confirm before finalizing UPDATE/REGENERATE/DELETE:**
63
63
  1. You can quote a specific diff line${opts.blueprintSuffix} this test observes that triggered the action.