@skyramp/mcp 0.3.5 → 0.3.6-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/build/playwright/registerPlaywrightTools.js +92 -30
  2. package/build/playwright/traceRecordingPrompt.d.ts +6 -0
  3. package/build/playwright/traceRecordingPrompt.js +6 -2
  4. package/build/prompts/code-reuse.d.ts +1 -2
  5. package/build/prompts/code-reuse.js +182 -77
  6. package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
  7. package/build/prompts/modularization/integration-test-modularization.js +83 -41
  8. package/build/prompts/modularization/render.d.ts +18 -0
  9. package/build/prompts/modularization/render.js +12 -0
  10. package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
  11. package/build/prompts/modularization/ui-test-modularization.js +89 -47
  12. package/build/prompts/pom-aware-code-reuse.js +3 -1
  13. package/build/prompts/shared-helper-policy.d.ts +57 -0
  14. package/build/prompts/shared-helper-policy.js +135 -0
  15. package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
  16. package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
  17. package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
  18. package/build/prompts/test-recommendation/recommendationShared.js +90 -16
  19. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
  22. package/build/prompts/testbot/testbot-prompts.js +80 -34
  23. package/build/recommendation/budgeters/shared.js +105 -27
  24. package/build/recommendation/discriminators.js +13 -2
  25. package/build/recommendation/planRanker.d.ts +6 -6
  26. package/build/recommendation/planRanker.js +6 -61
  27. package/build/services/AnalyticsService.d.ts +7 -0
  28. package/build/services/AnalyticsService.js +7 -1
  29. package/build/services/ModularizationService.js +1 -3
  30. package/build/services/TestDiscoveryService.d.ts +0 -2
  31. package/build/services/TestDiscoveryService.js +2 -37
  32. package/build/services/TestGenerationService.d.ts +16 -0
  33. package/build/services/TestGenerationService.js +86 -10
  34. package/build/services/containerEnv.js +13 -12
  35. package/build/tools/code-refactor/codeReuseTool.js +279 -93
  36. package/build/tools/code-refactor/enhance-state.d.ts +49 -0
  37. package/build/tools/code-refactor/enhance-state.js +109 -0
  38. package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
  39. package/build/tools/code-refactor/modularizationTool.js +9 -2
  40. package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
  41. package/build/tools/code-refactor/reuse-outcome.js +14 -4
  42. package/build/tools/code-refactor/reuse-state.d.ts +127 -5
  43. package/build/tools/code-refactor/reuse-state.js +628 -16
  44. package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
  45. package/build/tools/code-refactor/utils-verify-gates.js +100 -0
  46. package/build/tools/code-refactor/verify-gates.d.ts +2 -1
  47. package/build/tools/code-refactor/verify-gates.js +90 -25
  48. package/build/tools/executeSkyrampTestTool.d.ts +19 -0
  49. package/build/tools/executeSkyrampTestTool.js +158 -8
  50. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
  51. package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
  52. package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
  53. package/build/tools/generate-tests/generateUIRestTool.js +22 -0
  54. package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
  55. package/build/tools/generate-tests/scenarioLint.js +127 -19
  56. package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
  57. package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
  58. package/build/tools/submitReportTool.d.ts +38 -38
  59. package/build/tools/submitReportTool.js +411 -114
  60. package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
  61. package/build/tools/test-management/analyzeChangesTool.js +71 -10
  62. package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
  63. package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
  64. package/build/tools/test-management/registerTestPlanTool.js +70 -12
  65. package/build/types/Recommendation.d.ts +34 -5
  66. package/build/types/RepositoryAnalysis.d.ts +133 -114
  67. package/build/types/RepositoryAnalysis.js +1 -1
  68. package/build/types/ReuseOutcome.d.ts +102 -6
  69. package/build/types/ReuseOutcome.js +16 -2
  70. package/build/types/TestRecommendation.js +21 -3
  71. package/build/types/TestTypes.js +14 -8
  72. package/build/types/TestbotReport.d.ts +10 -1
  73. package/build/types/index.d.ts +2 -2
  74. package/build/types/index.js +1 -1
  75. package/build/utils/AnalysisStateManager.d.ts +57 -1
  76. package/build/utils/AnalysisStateManager.js +54 -5
  77. package/build/utils/branchDiff.d.ts +10 -0
  78. package/build/utils/branchDiff.js +28 -0
  79. package/build/utils/changedRoutes.d.ts +29 -0
  80. package/build/utils/changedRoutes.js +87 -0
  81. package/build/utils/featureFlags.d.ts +21 -0
  82. package/build/utils/featureFlags.js +23 -0
  83. package/build/utils/frontendIntegration.js +34 -4
  84. package/build/utils/importerHop.d.ts +2 -8
  85. package/build/utils/importerHop.js +15 -53
  86. package/build/utils/pathMatching.d.ts +38 -0
  87. package/build/utils/pathMatching.js +71 -0
  88. package/build/utils/pathSignatures.d.ts +22 -0
  89. package/build/utils/pathSignatures.js +57 -0
  90. package/build/utils/planMatchKeys.d.ts +16 -3
  91. package/build/utils/planMatchKeys.js +26 -10
  92. package/build/utils/pluralization.d.ts +10 -0
  93. package/build/utils/pluralization.js +18 -0
  94. package/build/utils/pom-catalog-parse.d.ts +52 -0
  95. package/build/utils/pom-catalog-parse.js +141 -0
  96. package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
  97. package/build/utils/pom-scope/selector-extractor.js +34 -8
  98. package/build/utils/pom-verify/verify.d.ts +6 -5
  99. package/build/utils/pom-verify/verify.js +8 -6
  100. package/build/utils/reportVerification.d.ts +64 -4
  101. package/build/utils/reportVerification.js +228 -3
  102. package/build/utils/reuseRouting.d.ts +3 -0
  103. package/build/utils/reuseRouting.js +50 -0
  104. package/build/utils/routeParsers.d.ts +2 -0
  105. package/build/utils/routeParsers.js +65 -8
  106. package/build/utils/scenarioDrafting.d.ts +1 -1
  107. package/build/utils/scenarioDrafting.js +57 -45
  108. package/build/utils/subjectEndpoints.d.ts +19 -0
  109. package/build/utils/subjectEndpoints.js +98 -0
  110. package/build/utils/testFileClassification.d.ts +11 -0
  111. package/build/utils/testFileClassification.js +47 -0
  112. package/build/utils/uiPageEnumerator.d.ts +45 -19
  113. package/build/utils/uiPageEnumerator.js +95 -51
  114. package/build/utils/utils-verify/allow.d.ts +16 -0
  115. package/build/utils/utils-verify/allow.js +68 -0
  116. package/build/utils/utils-verify/call-sites.d.ts +34 -0
  117. package/build/utils/utils-verify/call-sites.js +154 -0
  118. package/build/utils/utils-verify/index.d.ts +7 -0
  119. package/build/utils/utils-verify/index.js +7 -0
  120. package/build/utils/utils-verify/language-spec.d.ts +91 -0
  121. package/build/utils/utils-verify/language-spec.js +210 -0
  122. package/build/utils/utils-verify/locate.d.ts +39 -0
  123. package/build/utils/utils-verify/locate.js +199 -0
  124. package/build/utils/utils-verify/parse.d.ts +34 -0
  125. package/build/utils/utils-verify/parse.js +177 -0
  126. package/build/utils/utils-verify/stage.d.ts +24 -0
  127. package/build/utils/utils-verify/stage.js +107 -0
  128. package/build/utils/utils-verify/verify.d.ts +63 -0
  129. package/build/utils/utils-verify/verify.js +168 -0
  130. package/build/utils/utils.d.ts +3 -1
  131. package/build/utils/utils.js +3 -1
  132. package/build/workspace/workspace.d.ts +32 -32
  133. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
  134. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
  135. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
  136. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
  137. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
  138. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  139. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
  140. package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
  141. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
  142. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
  143. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
  145. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
  146. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
  147. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  148. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
  149. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  150. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  151. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  152. package/node_modules/playwright/package.json +1 -1
  153. package/package.json +2 -2
  154. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
  155. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
  157. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
@@ -4,36 +4,111 @@
4
4
  */
5
5
  import { z } from "zod";
6
6
  import { logger } from "../utils/logger.js";
7
- import { dualChannelResult } from "../utils/utils.js";
8
7
  import { PlaywrightTraceService, } from "./PlaywrightTraceService.js";
9
8
  let _service = null;
10
9
  export function getPlaywrightTraceService() {
11
10
  return _service;
12
11
  }
13
12
  /**
14
- * The one browser tool whose result is re-shaped on the way out — see
15
- * toStructuredBlueprintResult.
13
+ * The one browser tool whose text payload is shrunk on the way out — see
14
+ * shrinkBlueprintResult.
16
15
  */
17
16
  const BLUEPRINT_TOOL = "browser_blueprint";
18
17
  /**
19
- * Re-shape a browser_blueprint result onto both MCP result channels.
18
+ * Shrink a browser_blueprint result on the text channel it already arrives on.
20
19
  *
21
- * SKYR-4194. The browser tools are registered here as passthroughs, and the fork
22
- * returns the captured DOM as one `content[]` text block with no
23
- * `structuredContent` the shape whose spill the agent's file reader refuses.
24
- * See `dualChannelResult` for why both channels carry the payload.
20
+ * SKYR-4267. SKYR-4194 (#752) also copied the capture into `structuredContent`
21
+ * as a JSON string. Claude Code measures that copy against its size cap and
22
+ * drops `content[]` when both channels are present, so the escape added to
23
+ * every quote and newline counted against the cap. On the largest real capture
24
+ * (`sb20-bucket-visibility`) the text is 26,542 bytes and the escaped string
25
+ * 29,411 — which BOTH channels carried, because `dualChannelResult` puts the
26
+ * JSON serialization of `structuredContent` into `content[]`, so 58,822 went on
27
+ * the wire.
25
28
  *
26
- * An error result is returned untouched: the SDK's outputSchema check returns
27
- * early on `isError`, so it needs no structuredContent.
29
+ * The escaping also collapsed the whole payload onto ONE line, so a spill file
30
+ * read at a line offset returned nothing at all
31
+ * (`repo14-U06-field-reorder-toast`). Dropping it does not give the capture its
32
+ * newlines back: the fork pretty-prints the JSON, but shrinkBlueprintText below
33
+ * re-serializes with no indent, so the body is still one line. What returns are
34
+ * the `### ` section headers around it, so an offset read now lands on a header
35
+ * instead of erroring — the failure is mitigated, not closed.
36
+ *
37
+ * The text channel is the one every agent CLI reads, and it is what the other
38
+ * browser tools already return, so the capture rides it alone.
39
+ *
40
+ * An error result is returned untouched.
28
41
  */
29
- function toStructuredBlueprintResult(result) {
42
+ function shrinkBlueprintResult(result) {
30
43
  if (result?.isError)
31
44
  return result;
32
- const blueprint = (result?.content ?? [])
33
- .filter((block) => block?.type === "text")
34
- .map((block) => block.text)
35
- .join("\n");
36
- return dualChannelResult({ blueprint });
45
+ return {
46
+ ...result,
47
+ content: (result?.content ?? []).map((block) => block?.type === "text"
48
+ ? { ...block, text: shrinkBlueprintText(block.text) }
49
+ : block),
50
+ };
51
+ }
52
+ /**
53
+ * Drop every null-valued key, at any depth.
54
+ *
55
+ * SKYR-4208. Array entries are left alone — a null there holds a position, and
56
+ * removing it would renumber everything after it.
57
+ */
58
+ function dropNulls(value) {
59
+ if (Array.isArray(value))
60
+ return value.map(dropNulls);
61
+ if (value === null || typeof value !== "object")
62
+ return value;
63
+ return Object.fromEntries(Object.entries(value)
64
+ .filter(([, v]) => v !== null)
65
+ .map(([k, v]) => [k, dropNulls(v)]));
66
+ }
67
+ /**
68
+ * Minify the JSON body of a browser_blueprint payload and drop its null keys.
69
+ *
70
+ * SKYR-4208. The fork pretty-prints the capture behind a `### Result` header.
71
+ * On eval run 32458076607 (962 payloads, 11,368 elements) the indentation is
72
+ * 35.4% of the payload's tokens, and the null keys a further 6.8% — `stableId`,
73
+ * `testId` and `fingerprint` are null on 72-94% of elements and cost their key
74
+ * name on every one.
75
+ *
76
+ * Both steps are lossless: the agent parses the same fields with the same
77
+ * values, and an absent key already says what a null key says. Keys that merely
78
+ * hold their DEFAULT (`framePath: []`, `shadowRoot: false`) are deliberately
79
+ * kept — dropping those makes the agent infer the default from an absence, and
80
+ * `framePath` stops being empty as soon as the page has an iframe.
81
+ *
82
+ * The header and anything else outside the JSON survive unchanged, and a body
83
+ * that does not parse is returned as it arrived.
84
+ */
85
+ function shrinkBlueprintText(text) {
86
+ const start = text.indexOf("{");
87
+ if (start === -1)
88
+ return text;
89
+ // The fork can append more sections after `### Result` — `### Page` whenever
90
+ // the title or URL changed since the last call, and `### Modal state`,
91
+ // `### Snapshot` or `### Events` when the page produced them. Only the Result
92
+ // section holds JSON, so the body has to stop at the next header; reading to
93
+ // the end of the string makes the parse throw and costs the whole saving.
94
+ // A `### ` at the start of a line cannot sit inside the JSON, because JSON
95
+ // escapes the newline that would have to precede it.
96
+ const nextSection = text.slice(start).search(/\n### /);
97
+ const end = nextSection === -1 ? text.length : start + nextSection;
98
+ // The blank line the fork puts after a section belongs to the payload, not to
99
+ // the JSON, so it is put back byte-for-byte rather than parsed away.
100
+ const body = text.slice(start, end);
101
+ const json = body.trimEnd();
102
+ try {
103
+ return (text.slice(0, start) +
104
+ JSON.stringify(dropNulls(JSON.parse(json))) +
105
+ body.slice(json.length) +
106
+ text.slice(end));
107
+ }
108
+ catch {
109
+ logger.warning("browser_blueprint payload did not parse as JSON — delivering it unshrunk (SKYR-4208)");
110
+ return text;
111
+ }
37
112
  }
38
113
  export async function registerPlaywrightTools(server, options) {
39
114
  _service = new PlaywrightTraceService(options);
@@ -98,19 +173,6 @@ export async function registerPlaywrightTools(server, options) {
98
173
  server.registerTool(tool.name, {
99
174
  description: tool.description || `Playwright tool: ${tool.name}`,
100
175
  inputSchema: zodSchema,
101
- // SKYR-4194: only browser_blueprint returns its payload via
102
- // structuredContent, which requires a declared outputSchema. The other
103
- // browser tools return small results, and some return images, which this
104
- // text-only re-shaping would drop.
105
- ...(isBlueprint
106
- ? {
107
- outputSchema: {
108
- blueprint: z
109
- .string()
110
- .describe("The captured PageBlueprint — sections, elements, and their logical names. Read and follow this field."),
111
- },
112
- }
113
- : {}),
114
176
  }, async (params) => {
115
177
  if (!_service?.isInitialized()) {
116
178
  return {
@@ -124,7 +186,7 @@ export async function registerPlaywrightTools(server, options) {
124
186
  };
125
187
  }
126
188
  const result = await _service.callTool(tool.name, params);
127
- return isBlueprint ? toStructuredBlueprintResult(result) : result;
189
+ return isBlueprint ? shrinkBlueprintResult(result) : result;
128
190
  });
129
191
  }
130
192
  logger.info(`Registered ${filteredTools.length} Playwright tools: ${filteredTools.map((t) => t.name).join(", ")}`);
@@ -8,6 +8,12 @@ export interface TraceRecordingPromptOpts {
8
8
  outputDir?: string;
9
9
  /** Whether to run skyramp_modularization after generation. Default: true. Set false in CI. */
10
10
  modularize?: boolean;
11
+ /**
12
+ * With modularize=false: the generation result itself may still instruct a
13
+ * modularization pass (codeReuse flows that seed a shared utils file). When
14
+ * true, the note defers to that result instead of banning the tool outright.
15
+ */
16
+ modularizeViaGenerationResult?: boolean;
11
17
  }
12
18
  export declare function getTraceRecordingPromptText(opts?: TraceRecordingPromptOpts): string;
13
19
  export declare function registerTraceRecordingPrompt(server: McpServer): void;
@@ -13,10 +13,14 @@ export function getTraceRecordingPromptText(opts) {
13
13
  : `Call \`skyramp_export_zip\` with \`outputPath\` set to the absolute zip path (same directory and base name as the test file, replacing \`.spec.ts\` with \`.zip\`).`;
14
14
  const generateInstruction = modularize
15
15
  ? `Call \`skyramp_ui_test_generation\` with \`playwrightInput\` set to the absolute zip path from the Export step.`
16
- : `Call \`skyramp_ui_test_generation\` with \`playwrightInput\` set to the absolute zip path from step 5 and \`modularizeCode: false\`.`;
16
+ : opts?.modularizeViaGenerationResult
17
+ ? `Call \`skyramp_ui_test_generation\` with \`playwrightInput\` set to the absolute zip path from step 5, \`modularizeCode: false\`, and \`codeReuse: true\` — its result lists the post-generation steps (assertion enhancement, modularization, reuse) with their arguments.`
18
+ : `Call \`skyramp_ui_test_generation\` with \`playwrightInput\` set to the absolute zip path from step 5 and \`modularizeCode: false\`.`;
17
19
  const modularizeNote = modularize
18
20
  ? `- **After generating the test**, run \`skyramp_modularization\` for code quality.`
19
- : `- Do NOT run \`skyramp_modularization\` — skip modularization in CI.`;
21
+ : opts?.modularizeViaGenerationResult
22
+ ? `- Run \`skyramp_modularization\` ONLY when the generation result's CRITICAL NEXT STEPS instruct it (with the arguments they give) — never on your own initiative.`
23
+ : `- Do NOT run \`skyramp_modularization\` — skip modularization in CI.`;
20
24
  return `## Skyramp UI Test Recording
21
25
 
22
26
  ${getPersonaPrefix()} For UI recording, every action must be grounded in what \`browser_snapshot\` returns. If an element is not visible in the snapshot, do not interact with it.
@@ -1,3 +1,2 @@
1
1
  import type { ScopedPomInput } from "../utils/pom-scope/index.js";
2
- export declare function isPomAwareTarget(language: string, framework?: string): boolean;
3
- export declare function getCodeReusePrompt(testFile: string, language: string, framework?: string, scopedPoms?: ScopedPomInput): string;
2
+ export declare function getCodeReusePrompt(testFile: string, language: string, framework?: string, scopedPoms?: ScopedPomInput, testType?: string): string;
@@ -1,6 +1,8 @@
1
1
  import { generateSkyrampHeader, SKYRAMP_UTILS_HEADER } from "../utils/utils.js";
2
- import { isPomReuseEnabled } from "../utils/featureFlags.js";
2
+ import { isUtilsReuseEnabled } from "../utils/featureFlags.js";
3
+ import { isModularizeFirstTarget, isPomAwareTarget, } from "../utils/reuseRouting.js";
3
4
  import { getPomAwareCodeReusePrompt } from "./pom-aware-code-reuse.js";
5
+ import { NO_SHADOWING_RULE, selectHelperPolicy, } from "./shared-helper-policy.js";
4
6
  const LANGUAGE_MAP = {
5
7
  python: {
6
8
  extension: "py",
@@ -19,23 +21,159 @@ const LANGUAGE_MAP = {
19
21
  fileName: "skyrampUtils.ts",
20
22
  },
21
23
  };
22
- export function isPomAwareTarget(language, framework) {
23
- if (!isPomReuseEnabled())
24
- return false;
25
- const lang = language.toLowerCase();
26
- return (lang === "typescript" || lang === "javascript") && framework?.toLowerCase() === "playwright";
24
+ function genericNearDupExample(testFile) {
25
+ return `### Worked example
26
+
27
+ \`flow-1.spec.ts\` has:
28
+ \`\`\`ts
29
+ export async function upload_translation_file(page, fileName) {
30
+ await page.waitForTimeout(1500);
31
+ const responsePromise1 = page.waitForResponse("**/api/files/upload**");
32
+ const fileUploadPromise0 = page.waitForEvent("filechooser");
33
+ await page.getByText("Choose a file").first().click();
34
+ const fileChooser0 = await fileUploadPromise0;
35
+ await fileChooser0.setFiles(fileName);
36
+ const response1 = await responsePromise1;
37
+ await page.waitForTimeout(1500);
27
38
  }
28
- export function getCodeReusePrompt(testFile, language, framework, scopedPoms) {
39
+ \`\`\`
40
+
41
+ \`${testFile}\` (current) has:
42
+ \`\`\`ts
43
+ export async function uploadFileToRFI(page, fileName, tabName) {
44
+ await page.waitForTimeout(1500);
45
+ const responsePromise1 = page.waitForResponse("**/api/files/upload**");
46
+ const fileUploadPromise0 = page.waitForEvent("filechooser");
47
+ await page.getByText("Choose a file").click();
48
+ const fileChooser0 = await fileUploadPromise0;
49
+ await fileChooser0.setFiles(fileName);
50
+ const response1 = await responsePromise1;
51
+ await page.waitForTimeout(1500);
52
+ }
53
+ \`\`\`
54
+
55
+ These ARE near-duplicates. Same primitive sequence; the only material difference is the \`.first()\` qualifier on the locator. \`tabName\` is unused in the body — recorder noise, drop it. Correct merge:
56
+
57
+ \`\`\`ts
58
+ export async function uploadFileViaChooser(page, fileName) {
59
+ await page.waitForTimeout(1500);
60
+ const responsePromise = page.waitForResponse("**/api/files/upload**");
61
+ const fileUploadPromise = page.waitForEvent("filechooser");
62
+ await page.getByText("Choose a file").first().click();
63
+ const fileChooser = await fileUploadPromise;
64
+ await fileChooser.setFiles(fileName);
65
+ await responsePromise;
66
+ await page.waitForTimeout(1500);
67
+ }
68
+ \`\`\`
69
+
70
+ Both test files import and call \`uploadFileViaChooser(page, fileName)\`.`;
71
+ }
72
+ export function getCodeReusePrompt(testFile, language, framework, scopedPoms, testType) {
29
73
  const lang = language.toLowerCase();
30
- if (isPomAwareTarget(lang, framework)) {
74
+ if (isPomAwareTarget(lang, framework, testType)) {
31
75
  return getPomAwareCodeReusePrompt(testFile, lang, scopedPoms);
32
76
  }
33
- const ext = LANGUAGE_MAP[language].extension || "py";
34
- const fileName = LANGUAGE_MAP[language].fileName || "SkyrampUtils.py";
77
+ const languageEntry = LANGUAGE_MAP[lang] ?? LANGUAGE_MAP.python;
78
+ const ext = languageEntry.extension;
79
+ const fileName = languageEntry.fileName;
35
80
  const codegenMarker = `${ext === "py" ? "#" : "//"} Generated by Skyramp v`;
81
+ // Modularize-first flows (see isModularizeFirstTarget) reach reuse already
82
+ // modularized, so the trailing hand-off must not send the agent back into
83
+ // modularization a second time — it would re-extract the helpers this pass
84
+ // just consolidated.
85
+ const modularizeFirst = isModularizeFirstTarget(testType, lang);
86
+ const modularizationTail = modularizeFirst
87
+ ? `**MANDATORY**: ${testType} tests are modularized BEFORE code reuse (see the PRECONDITION at the top). This reuse pass is the FINAL step for this file — do NOT call skyramp_modularization after it; a second modularization pass would re-extract the helpers this pass just consolidated.`
88
+ : `**MANDATORY**: After code reuse is complete, proceed to modularization by calling skyramp_modularization tool ONLY for UI, E2E, or INTEGRATION test types generated from traces.`;
89
+ // Modularize-first tests reach reuse freshly modularized: their helpers exist
90
+ // only in the current file, and STEP 5's other-files-only rule would strand
91
+ // them there forever — the FIRST test in a repo could never create the utils
92
+ // file. STEP 5b seeds the utils file from the current test's own helpers so
93
+ // this run's output carries it and later tests import instead of re-defining.
94
+ const seedsUtils = modularizeFirst;
95
+ // Naming, equivalence and assertion rules for helpers headed to the shared
96
+ // file are the helper policy's — selected once by test type, stated once.
97
+ const policy = selectHelperPolicy(testType);
98
+ // The generic worked example treats a locator qualifier (.first()) as a
99
+ // liftable difference; a shared-file browser helper must not, so seeded
100
+ // browser flows show an identical-selector merge instead.
101
+ const workedExample = seedsUtils
102
+ ? policy.nearDupExample(testFile)
103
+ : genericNearDupExample(testFile);
104
+ const skipTarget = seedsUtils ? "STEP 5b" : "STEP 6";
105
+ const noSiblingsRule = seedsUtils
106
+ ? `**IF NO OTHER SKYRAMP-GENERATED TEST FILES ARE FOUND, SKIP DIRECTLY TO STEP 5b — do not process STEP 4b or STEP 5.**`
107
+ : `**STOP HERE IF NO OTHER SKYRAMP-GENERATED TEST FILES FOUND**
108
+ **IF NO OTHER SKYRAMP-GENERATED TEST FILES ARE FOUND, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**`;
109
+ const seedStepBlock = seedsUtils
110
+ ? `## STEP 5b: MOVE THIS TEST'S OWN HELPERS INTO \`${fileName}\`
111
+
112
+ skyramp_modularization already ran on ${testFile} and extracted its steps into named helper functions. Those helpers belong in the shared \`${fileName}\` so later tests import them instead of re-defining them.
113
+
114
+ For each helper function still defined locally in ${testFile} (i.e. not already merged by STEP 4b):
115
+ 1. **CREATE** \`${fileName}\` if it does not exist, with the standard header:
116
+ \`\`\`${ext}
117
+ ${generateSkyrampHeader(lang)}
118
+ \`\`\`
119
+ 2. **CHECK ITS ASSERTIONS FIRST**: ${policy.assertionRule} Relocate a trailing excluded assertion to ${policy.relocationTarget} before the move.${policy.moveRule ? ` ${policy.moveRule}` : ""}
120
+ 3. **MOVE** the helper into \`${fileName}\` — no renames, no signature changes, no changes beyond the trailing-assertion relocation above.
121
+ 4. **IMPORT** it from \`${fileName}\` into ${testFile}; keep every call site unchanged.
122
+ 5. If \`${fileName}\` already contains a helper for ${policy.sameStep} (or the same name): identical bodies → delete the local copy and import the utils version; differs only by ${policy.liftableLiterals} → apply the defaulted-parameter merge rule from STEP 4b (extend the utils helper with parameters defaulted to its current literals, import it, pass this test's values); differs structurally → leave the local helper in place and do not overwrite the utils version. ${policy.structuralNoise ? `${policy.structuralNoise}. ` : ""}${NO_SHADOWING_RULE}
123
+
124
+ This step MOVES helpers that skyramp_modularization already created — it does not author new ones. If ${testFile} defines no helper functions, skip this step and do not create an empty \`${fileName}\`.
125
+ `
126
+ : "";
127
+ const nearDupRules = seedsUtils ? policy.nearDupRules(fileName) : "";
128
+ const utilsMergeRule = seedsUtils
129
+ ? `
130
+ **IF \`${fileName}\` ALREADY CONTAINS A HELPER FOR ${policy.sameStep}** that differs from what ${testFile} needs only by ${policy.liftableLiterals}: do NOT define a second helper — EXTEND the utils helper by lifting the differing literals to parameters whose defaults are the utils version's current literals (existing call sites stay valid unchanged), then import and call it with this test's values. ${policy.oneHelperRule(fileName)}${policy.structuralNoise ? ` ${policy.structuralNoise}.` : ""} ${NO_SHADOWING_RULE}
131
+ `
132
+ : "";
133
+ // SKYR-4219: an inline request block is a CALL SITE for an existing helper, not a
134
+ // helper to move. Without this, a direct-mode sibling (plain literals, no
135
+ // \`data_override\`/\`uniqueSuffix\`) never meets the scenario-mode helper for the
136
+ // same request, and a step still inline in the current test never meets the utils
137
+ // helper — reuse degrades to a no-op in any mixed directory. Explicit test-type
138
+ // gate, not \`seedsUtils\`: the rule is stated in API terms.
139
+ const inlineRequestRule = testType === "integration"
140
+ ? `**INLINE REQUEST BLOCKS ARE CALL SITES, NOT HELPERS.** A request step written inline — one \`client.send_request(...)\` / \`client.sendRequest(...)\` call plus its status-code assertion, not wrapped in a function — is never something to move or author a helper from. But when a helper for the SAME method+path already exists, the block is that helper's near-duplicate (judge it by the API near-duplicate criteria in STEP 4b: same method+path and call structure; differences limited to body literals, \`data_override\` values, or expected status): REPLACE the block with a call to the helper, passing the block's own literals as arguments, and lift any literal the helper does not yet take to a parameter DEFAULTED to the helper's current value (existing call sites unchanged). KEEP the block's response variable — \`<same variable> = <helper>(...)\` — so the assertions that read it afterwards are unchanged. A value the block wraps in \`uniqueSuffix(...)\` or routes through \`data_override\` where the helper uses a plain literal (or vice versa) is a LITERAL difference, not a structural one — pass the expression as the argument. A block whose request differs structurally (extra step, different method or path) stays as it is.`
141
+ : "";
142
+ // Its own step, placed after 5b: the agent routinely satisfies ${testFile} in STEP 3
143
+ // and jumps straight to verification, never entering STEP 4 — so a sibling rule parked
144
+ // there is never read (measured: eval run u4219b). This is where the shared helper is
145
+ // guaranteed to exist and the agent is guaranteed to still be running.
146
+ const siblingCallSitesStep = testType === "integration"
147
+ ? `## STEP 5c: REPLACE INLINE REQUEST BLOCKS IN OTHER SKYRAMP-GENERATED TESTS WITH CALLS TO \`${fileName}\`
148
+
149
+ **Run this step EVEN IF STEP 3 already satisfied ${testFile} and STEPS 4–5b did nothing.** ${inlineRequestRule}
150
+
151
+ 1. Grep for the other Skyramp-generated test files exactly as in STEP 4 (codegen marker \`${codegenMarker}\`, excluding ${testFile} and \`${fileName}\`).
152
+ 2. In each, find every inline request block whose method+path matches a helper now defined in \`${fileName}\`.
153
+ 3. For each match: import the helper into that file and replace the block with the call, passing the block's literals (or expressions) as arguments; lift a literal the helper lacks to a defaulted parameter in \`${fileName}\`. Change NOTHING else in that file — its other steps, assertions and order stay verbatim.
154
+ 4. A block with no matching helper, or one that differs structurally, stays as it is. Conservative bias: a wrong replacement breaks a passing test; a missed one leaves a duplicate.
155
+
156
+ `
157
+ : "";
158
+ const finalReminderScope = seedsUtils
159
+ ? `**ONLY MOVE HELPERS THAT ALREADY EXIST IN TEST FILES — NEVER AUTHOR NEW ONES**`
160
+ : `**ONLY MOVE EXISTING HELPERS FROM OTHER TEST FILES**`;
161
+ const decisionTreeTail = seedsUtils
162
+ ? `3. Found other test files with only repetitive patterns? → Step 5b (seed utils from this test's own helpers)
163
+ 4. Found no other test files? → Step 5b
164
+ 5. Other test files identical to current? → Step 5b
165
+ 6. This test file defines no helpers either? → Step 6, NO utils file`
166
+ : `3. Found other test files with only repetitive patterns? → SKIP to Step 6, NO utils file
167
+ 4. Found no other test files? → SKIP to Step 6, NO utils file
168
+ 5. Other test files identical to current? → SKIP to Step 6, NO utils file`;
169
+ const precondition = modularizeFirst
170
+ ? `
171
+ **PRECONDITION — check your own tool-call history, not the file**: this flow moves the helpers skyramp_modularization created into \`${fileName}\` (STEP 5b). If you have NOT called skyramp_modularization on ${testFile} in this run, STOP: call it now with \`testFile\` set to ${testFile}, \`testType: "${testType}"\`, and \`isTraceBased: true\`, apply its instructions, then call skyramp_reuse_code again with the same arguments as this call. If you HAVE called it and the file still defines no helper functions, that is a legitimate outcome — proceed; STEP 5b will simply have nothing to move.
172
+ `
173
+ : "";
36
174
  return `# CODE REUSE - 6 CLEAR STEPS
37
175
  **CRITICAL WARNING: VIOLATION OF THESE RULES WILL RESULT IN ERROR**
38
-
176
+ ${precondition}
39
177
  ## DEFINITIONS (READ FIRST):
40
178
  **What is a "Helper Function"?**
41
179
  - A helper function is a STANDALONE, DISTINCT function that is ALREADY DEFINED in the code
@@ -65,10 +203,14 @@ Use the Grep tool to search for files containing "${SKYRAMP_UTILS_HEADER}":
65
203
  Read the matching non-test files and check if any helpers can be reused in ${testFile}.
66
204
 
67
205
  ## STEP 3: USE EXISTING HELPERS FROM UTILS SOURCE FILES FOUND IN STEP 2
68
- If helpers exist in util file that can be reused in ${testFile} without modifying them:
206
+ If helpers exist in util file that can be reused in ${testFile} without modifying them${inlineRequestRule ? " — or, for API request helpers, by EXTENDING them with defaulted parameters as the rules below allow" : ""}:
69
207
  - Import them into ${testFile}
70
208
  - Remove any duplicate code in ${testFile}
71
- - Test that ${testFile} still works without any errors and logical is same as original test file.
209
+ ${inlineRequestRule
210
+ ? `- ${inlineRequestRule} This applies to every request step still inline in ${testFile} that matches a utils helper.
211
+ `
212
+ : ""}- Test that ${testFile} still works without any errors and logical is same as original test file.
213
+ ${utilsMergeRule}
72
214
 
73
215
  ## STEP 4: FIND LOCAL HELPERS IN OTHER SKYRAMP-GENERATED TEST FILES
74
216
 
@@ -85,8 +227,7 @@ The result will mix two kinds of files:
85
227
  - **Utils file(s)** — already handled by STEP 2/STEP 3.
86
228
  - **Other Skyramp-generated test files** — these are the targets of STEP 4 / 4b / 5. Identify them by the \`${codegenMarker}\` codegen marker on line 1 (NOT the bare utils header).
87
229
 
88
- **STOP HERE IF NO OTHER SKYRAMP-GENERATED TEST FILES FOUND**
89
- **IF NO OTHER SKYRAMP-GENERATED TEST FILES ARE FOUND, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**
230
+ ${noSiblingsRule}
90
231
 
91
232
  If other Skyramp-generated test files are found, read those files and look for ALREADY DEFINED helper functions with clear function signatures.
92
233
 
@@ -101,8 +242,12 @@ NOT A HELPER FUNCTION (do not extract):
101
242
  - No function definition/signature
102
243
  - Example: Multiple \`await page.getByTestId("xyz").click()\` directly in test
103
244
 
104
- **IF OTHER TEST FILES ONLY CONTAIN REPETITIVE PATTERNS (NO ACTUAL HELPER FUNCTIONS), SKIP TO STEP 6**
105
- **IF OTHER SKYRAMP-GENERATED TEST FILES ARE BYTE-FOR-BYTE IDENTICAL TO ${testFile}, SKIP TO STEP 6** (note: helpers being NEAR-duplicates does NOT count as identical proceed to STEP 4b)
245
+ ${inlineRequestRule
246
+ ? `(Inline request blocks in OTHER test files are not helpers to move STEP 5c replaces them with calls to the shared helpers.)
247
+
248
+ `
249
+ : ""}**IF OTHER TEST FILES ONLY CONTAIN REPETITIVE PATTERNS (NO ACTUAL HELPER FUNCTIONS), SKIP TO ${skipTarget}**
250
+ **IF OTHER SKYRAMP-GENERATED TEST FILES ARE BYTE-FOR-BYTE IDENTICAL TO ${testFile}, SKIP TO ${skipTarget}** (note: helpers being NEAR-duplicates does NOT count as identical — proceed to STEP 4b)
106
251
 
107
252
  ## STEP 4b: PARAMETERIZE NEAR-DUPLICATE HELPERS ACROSS SKYRAMP-GENERATED TEST FILES
108
253
 
@@ -117,53 +262,8 @@ Two helpers (one in ${testFile}, one in another Skyramp-generated test file foun
117
262
  3. **The differing literals are trivially liftable to parameters** — no logic needs to be invented to derive them.
118
263
 
119
264
  If any of the three is false → NOT near-duplicates. Leave them alone.
120
-
121
- ### Worked example
122
-
123
- \`flow-1.spec.ts\` has:
124
- \`\`\`ts
125
- export async function upload_translation_file(page, fileName) {
126
- await page.waitForTimeout(1500);
127
- const responsePromise1 = page.waitForResponse("**/api/files/upload**");
128
- const fileUploadPromise0 = page.waitForEvent("filechooser");
129
- await page.getByText("Choose a file").first().click();
130
- const fileChooser0 = await fileUploadPromise0;
131
- await fileChooser0.setFiles(fileName);
132
- const response1 = await responsePromise1;
133
- await page.waitForTimeout(1500);
134
- }
135
- \`\`\`
136
-
137
- \`${testFile}\` (current) has:
138
- \`\`\`ts
139
- export async function uploadFileToRFI(page, fileName, tabName) {
140
- await page.waitForTimeout(1500);
141
- const responsePromise1 = page.waitForResponse("**/api/files/upload**");
142
- const fileUploadPromise0 = page.waitForEvent("filechooser");
143
- await page.getByText("Choose a file").click();
144
- const fileChooser0 = await fileUploadPromise0;
145
- await fileChooser0.setFiles(fileName);
146
- const response1 = await responsePromise1;
147
- await page.waitForTimeout(1500);
148
- }
149
- \`\`\`
150
-
151
- These ARE near-duplicates. Same primitive sequence; the only material difference is the \`.first()\` qualifier on the locator. \`tabName\` is unused in the body — recorder noise, drop it. Correct merge:
152
-
153
- \`\`\`ts
154
- export async function uploadFileViaChooser(page, fileName) {
155
- await page.waitForTimeout(1500);
156
- const responsePromise = page.waitForResponse("**/api/files/upload**");
157
- const fileUploadPromise = page.waitForEvent("filechooser");
158
- await page.getByText("Choose a file").first().click();
159
- const fileChooser = await fileUploadPromise;
160
- await fileChooser.setFiles(fileName);
161
- await responsePromise;
162
- await page.waitForTimeout(1500);
163
- }
164
- \`\`\`
165
-
166
- Both test files import and call \`uploadFileViaChooser(page, fileName)\`.
265
+ ${nearDupRules}
266
+ ${workedExample}
167
267
 
168
268
  ### Procedure
169
269
 
@@ -172,7 +272,7 @@ For each helper defined locally in ${testFile}:
172
272
  1. **Scan** each OTHER Skyramp-generated test file from STEP 4 for a helper that matches the near-duplicate criteria above.
173
273
  2. **If no match** → skip this helper, move on.
174
274
  3. **If exactly one match** → parameterize and merge:
175
- - **Name**: pick a name describing INTENT, not the originating test. E.g. \`uploadFileViaChooser\`, not \`upload_translation_file\` or \`uploadFileToRFI\`.
275
+ - **Name**: pick a name describing INTENT, not the originating test. ${seedsUtils ? policy.namingExample : "E.g. `uploadFileViaChooser`, not `upload_translation_file` or `uploadFileToRFI`."}
176
276
  - **Parameters**: the literal values that differ across the two call sites. Drop any parameter that is unused in the body (recorder noise).
177
277
  - **Body**: the common primitive sequence with the differing literals replaced by parameters.
178
278
  - **Forbidden**: do NOT add if/else, ternaries, defaults, or any logic to bridge the differences. If a single body can't cover both call sites without new logic, they are NOT near-duplicates — leave them alone.
@@ -201,7 +301,7 @@ For each helper defined locally in ${testFile}:
201
301
  - The helper functions are ALREADY IMPLEMENTED and working in those OTHER test files
202
302
  - The helper in the OTHER test file is byte-for-byte usable in ${testFile} without any modification — if a helper would need parameterizing to fit both call sites, it is a near-duplicate and STEP 4b owns it; do NOT process those helpers here
203
303
 
204
- **IF ANY CONDITION IS NOT MET, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**
304
+ **IF ANY CONDITION IS NOT MET, SKIP TO ${skipTarget}${seedsUtils ? "" : " - DO NOT CREATE ANY UTILS FILES"}.**
205
305
 
206
306
  **Examples of when to SKIP STEP 5:**
207
307
  - Other test files have identical repetitive code but no helper functions
@@ -212,10 +312,10 @@ For each helper defined locally in ${testFile}:
212
312
  **CRITICAL:** For helpers found in STEP 4:
213
313
  1. **CREATE** \`${fileName}\` file if it doesn't exist with the following header:
214
314
  \`\`\`${ext}
215
- ${generateSkyrampHeader(language)}
315
+ ${generateSkyrampHeader(lang)}
216
316
  \`\`\`
217
317
  **CRITICAL: IF \`${fileName}\` is already big, create a new file with a different name and add the header to the new file.**
218
- 2. **COPY** those local helper functions from original test source files to \`${fileName}\` without modifying them
318
+ 2. **COPY** those local helper functions from original test source files to \`${fileName}\` without modifying them${seedsUtils ? ` — with ONE exception, checked BEFORE the copy: ${policy.assertionRule} Relocate a trailing excluded assertion into the owning test's body first; the copy that lands in \`${fileName}\` carries none.${policy.moveRule ? ` ${policy.moveRule}` : ""}` : ""}
219
319
  3. **DELETE** those local helper functions from test source files (REMOVE THEM COMPLETELY) WITHOUT ANY OTHER CHANGES.
220
320
  4. **IMPORT** those local helper functions from \`${fileName}\` back into test source files WITHOUT MAKING ANY OTHER CHANGE.
221
321
  5. **IMPORT** those local helper functions from \`${fileName}\` into ${testFile}
@@ -224,7 +324,7 @@ For each helper defined locally in ${testFile}:
224
324
  **CRITICAL: DO NOT CREATE UNNECESSARY HELPER FUNCTIONS**
225
325
  **DO NOT CREATE HELPERS BASED ON PATTERNS IN THE CURRENT FILE**
226
326
 
227
- ## STEP 6: VERIFY AND VALIDATE
327
+ ${seedStepBlock}${siblingCallSitesStep}## STEP 6: VERIFY AND VALIDATE
228
328
  1. **BEFORE** making any changes, read the original test file and identify ALL helper functions
229
329
  2. **AFTER** copying helpers to utils, read the original file again to confirm helpers are GONE
230
330
  3. **VERIFY** that helper functions are NO LONGER in original test files
@@ -234,15 +334,15 @@ For each helper defined locally in ${testFile}:
234
334
  6. **VERIFY** that no unnecessary helper functions were created (functions that duplicate existing functionality)
235
335
  7. **VERIFY** that all helper functions in utils are actually imported and used in the test files
236
336
  8. **REMOVE** any helper functions that are not being used after refactoring
237
- 9. **NEVER** refactor, reorganize, or restructure existing source test files beyond moving helpers
337
+ 9. **NEVER** refactor, reorganize, or restructure existing source test files beyond moving helpers${inlineRequestRule ? " — and, in this flow, replacing an inline request block with a call to the shared helper it duplicates (STEP 3 / STEP 5c)" : ""}
238
338
  10. **RUN TESTS** to ensure functionality is preserved after refactoring
239
339
 
240
- **FINAL REMINDER: DO NOT CREATE HELPER FUNCTIONS FROM SCRATCH**
241
- **ONLY MOVE EXISTING HELPERS FROM OTHER TEST FILES**
340
+ **FINAL REMINDER: DO NOT CREATE HELPER FUNCTIONS FROM SCRATCH**
341
+ ${finalReminderScope}
242
342
 
243
343
  **IMPORTANT NOTES:**
244
344
  - The process is designed to REUSE existing helper functions, not create new ones
245
- - If no existing helpers are found, the test file should remain unchanged
345
+ - ${seedsUtils ? `If no existing helpers are found in utils or sibling tests, STEP 5b still moves this test's own helpers into \`${fileName}\` and imports them back — that is the expected first-run outcome, not a no-op` : "If no existing helpers are found, the test file should remain unchanged"}
246
346
  - Code reuse is about moving existing, working helper functions between files
247
347
  - Never create helper functions based on patterns you see in the current test file
248
348
  - Helper functions must have explicit function signatures/definitions
@@ -251,11 +351,16 @@ For each helper defined locally in ${testFile}:
251
351
  **DECISION TREE:**
252
352
  1. Found existing utils file with helpers? → Import and use them (Step 3)
253
353
  2. Found other test files with ACTUAL helper functions? → Move to utils (Step 5)
254
- 3. Found other test files with only repetitive patterns? → SKIP to Step 6, NO utils file
255
- 4. Found no other test files? → SKIP to Step 6, NO utils file
256
- 5. Other test files identical to current? → SKIP to Step 6, NO utils file
354
+ ${decisionTreeTail}
355
+
356
+ ${modularizationTail}
257
357
 
258
- **MANDATORY**: After code reuse is complete, proceed to modularization by calling skyramp_modularization tool ONLY for UI, E2E, INTEGRATION or LOAD test types generated from traces.
358
+ ${
359
+ // Gated with the debt it describes: with SkyrampUtils reuse off nothing refuses, and
360
+ // this text would send the agent to spend a call being told the feature is disabled.
361
+ isUtilsReuseEnabled()
362
+ ? `**MANDATORY FINAL STEP — VERIFY**: once every edit above is written, call skyramp_reuse_code again with the SAME testFile, language, framework and testType and \`verify: true\`. It stages the shared utils file for the output commit and checks its invariants (for API helpers: one helper per method+path, status-code-only assertions, method+resource names; for browser helpers: actions and structural waits only, intent names). Resolve anything it reports and re-verify; finish only when it reports PASSED. It passes immediately when no utils file was written. skyramp_enhance_assertions and skyramp_execute_test refuse this test until that verify pass has run.\n`
363
+ : ""}
259
364
 
260
365
  SUMMARIZE THE CODE REUSE PROCESS AND THE RESULTS.
261
366
  `;
@@ -1 +1,3 @@
1
1
  export declare function getModularizationPrompt(filePath: string): string;
2
+ /** Structured view of which steps render for the given inputs (for tests). */
3
+ export declare function previewModularizationPlan(filePath: string): import("../test-recommendation/promptPlan.js").PlanPreview;