@skyramp/mcp 0.3.1 → 0.3.2-rc.pom-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 (177) hide show
  1. package/build/index.js +2 -1
  2. package/build/prompts/code-reuse.d.ts +7 -1
  3. package/build/prompts/code-reuse.js +8 -5
  4. package/build/prompts/code-reuse.test.d.ts +1 -0
  5. package/build/prompts/code-reuse.test.js +62 -0
  6. package/build/prompts/pom-aware-code-reuse.d.ts +6 -1
  7. package/build/prompts/pom-aware-code-reuse.js +98 -52
  8. package/build/prompts/pom-aware-code-reuse.test.d.ts +1 -0
  9. package/build/prompts/pom-aware-code-reuse.test.js +11 -0
  10. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +4 -2
  11. package/build/prompts/test-recommendation/diffExecutionPlan.js +11 -65
  12. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +2 -2
  13. package/build/prompts/test-recommendation/recommendationSections.js +5 -2
  14. package/build/prompts/test-recommendation/scopeAssessment.js +1 -1
  15. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +26 -1
  16. package/build/prompts/test-recommendation/test-recommendation-prompt.js +68 -56
  17. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +48 -11
  18. package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
  19. package/build/prompts/testbot/testbot-prompts.js +67 -21
  20. package/build/prompts/testbot/testbot-prompts.test.js +44 -0
  21. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +7 -0
  22. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +71 -0
  23. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +1 -0
  24. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +75 -0
  25. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +7 -0
  26. package/build/recommendation/budgeters/fixedNBudgeter.js +11 -0
  27. package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +1 -0
  28. package/build/recommendation/budgeters/fixedNBudgeter.test.js +66 -0
  29. package/build/recommendation/budgeters/shared.d.ts +19 -0
  30. package/build/recommendation/budgeters/shared.js +66 -0
  31. package/build/recommendation/discriminators.d.ts +31 -0
  32. package/build/recommendation/discriminators.js +355 -0
  33. package/build/recommendation/discriminators.test.d.ts +1 -0
  34. package/build/recommendation/discriminators.test.js +324 -0
  35. package/build/recommendation/diversity.d.ts +47 -0
  36. package/build/recommendation/diversity.js +101 -0
  37. package/build/recommendation/diversity.test.d.ts +1 -0
  38. package/build/recommendation/diversity.test.js +77 -0
  39. package/build/recommendation/planRanker.d.ts +50 -0
  40. package/build/recommendation/planRanker.js +67 -0
  41. package/build/recommendation/planRanker.test.d.ts +1 -0
  42. package/build/recommendation/planRanker.test.js +110 -0
  43. package/build/recommendation/testFixtures.d.ts +25 -0
  44. package/build/recommendation/testFixtures.js +45 -0
  45. package/build/resources/testbotResource.js +4 -1
  46. package/build/services/ScenarioGenerationService.d.ts +5 -0
  47. package/build/services/ScenarioGenerationService.js +16 -1
  48. package/build/services/ScenarioGenerationService.test.js +44 -0
  49. package/build/services/TestExecutionService.d.ts +15 -1
  50. package/build/services/TestExecutionService.js +210 -55
  51. package/build/services/TestExecutionService.test.js +397 -0
  52. package/build/services/TestGenerationService.js +19 -1
  53. package/build/services/TestGenerationService.test.js +58 -0
  54. package/build/tool-phases.js +1 -0
  55. package/build/toolNames.d.ts +19 -0
  56. package/build/toolNames.js +19 -0
  57. package/build/tools/code-refactor/codeReuseTool.d.ts +3 -0
  58. package/build/tools/code-refactor/codeReuseTool.js +76 -4
  59. package/build/tools/code-refactor/codeReuseTool.test.d.ts +1 -0
  60. package/build/tools/code-refactor/codeReuseTool.test.js +51 -0
  61. package/build/tools/executeSkyrampTestTool.js +8 -2
  62. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +6 -1
  63. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +110 -17
  64. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +147 -0
  65. package/build/tools/generate-tests/generateContractRestTool.js +11 -1
  66. package/build/tools/generate-tests/generateIntegrationRestTool.js +24 -1
  67. package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +1 -0
  68. package/build/tools/generate-tests/generateIntegrationRestTool.test.js +159 -0
  69. package/build/tools/generate-tests/planGuard.d.ts +13 -0
  70. package/build/tools/generate-tests/planGuard.js +78 -0
  71. package/build/tools/generate-tests/planGuard.test.d.ts +1 -0
  72. package/build/tools/generate-tests/planGuard.test.js +185 -0
  73. package/build/tools/generate-tests/scenarioFileIdentity.d.ts +10 -0
  74. package/build/tools/generate-tests/scenarioFileIdentity.js +46 -0
  75. package/build/tools/generate-tests/scenarioLint.d.ts +30 -0
  76. package/build/tools/generate-tests/scenarioLint.js +150 -0
  77. package/build/tools/generate-tests/scenarioLint.test.d.ts +1 -0
  78. package/build/tools/generate-tests/scenarioLint.test.js +100 -0
  79. package/build/tools/submitReportTool.js +78 -0
  80. package/build/tools/submitReportTool.test.js +255 -0
  81. package/build/tools/test-management/analyzeChangesTool.js +55 -2
  82. package/build/tools/test-management/analyzeChangesTool.test.js +12 -0
  83. package/build/tools/test-management/index.d.ts +1 -0
  84. package/build/tools/test-management/index.js +1 -0
  85. package/build/tools/test-management/registerTestPlanTool.d.ts +2 -0
  86. package/build/tools/test-management/registerTestPlanTool.js +329 -0
  87. package/build/tools/test-management/registerTestPlanTool.test.d.ts +1 -0
  88. package/build/tools/test-management/registerTestPlanTool.test.js +296 -0
  89. package/build/types/Recommendation.d.ts +97 -0
  90. package/build/types/Recommendation.js +48 -0
  91. package/build/types/RepositoryAnalysis.d.ts +14 -14
  92. package/build/types/TestExecution.d.ts +2 -0
  93. package/build/types/TestRecommendation.d.ts +12 -1
  94. package/build/types/TestRecommendation.js +26 -11
  95. package/build/types/TestTypes.js +1 -1
  96. package/build/utils/AnalysisStateManager.d.ts +47 -0
  97. package/build/utils/docker.test.js +1 -1
  98. package/build/utils/planMatchKeys.d.ts +61 -0
  99. package/build/utils/planMatchKeys.js +125 -0
  100. package/build/utils/pom-scope/import-expansion.d.ts +5 -0
  101. package/build/utils/pom-scope/import-expansion.js +32 -0
  102. package/build/utils/pom-scope/index.d.ts +39 -0
  103. package/build/utils/pom-scope/index.js +120 -0
  104. package/build/utils/pom-scope/index.test.d.ts +1 -0
  105. package/build/utils/pom-scope/index.test.js +239 -0
  106. package/build/utils/pom-scope/pom-files.d.ts +3 -0
  107. package/build/utils/pom-scope/pom-files.js +48 -0
  108. package/build/utils/pom-scope/pom-files.test.d.ts +1 -0
  109. package/build/utils/pom-scope/pom-files.test.js +29 -0
  110. package/build/utils/pom-scope/scoring.d.ts +20 -0
  111. package/build/utils/pom-scope/scoring.js +45 -0
  112. package/build/utils/pom-scope/scoring.test.d.ts +1 -0
  113. package/build/utils/pom-scope/scoring.test.js +39 -0
  114. package/build/utils/pom-scope/selector-extractor.d.ts +7 -0
  115. package/build/utils/pom-scope/selector-extractor.js +57 -0
  116. package/build/utils/pom-scope/selector-extractor.test.d.ts +1 -0
  117. package/build/utils/pom-scope/selector-extractor.test.js +67 -0
  118. package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.d.ts +5 -0
  119. package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.js +5 -0
  120. package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.d.ts +5 -0
  121. package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.js +9 -0
  122. package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.d.ts +4 -0
  123. package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.js +4 -0
  124. package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.d.ts +4 -0
  125. package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.js +4 -0
  126. package/build/utils/pom-verify/bindings.d.ts +19 -0
  127. package/build/utils/pom-verify/bindings.js +161 -0
  128. package/build/utils/pom-verify/bindings.test.d.ts +1 -0
  129. package/build/utils/pom-verify/bindings.test.js +164 -0
  130. package/build/utils/pom-verify/calls.d.ts +16 -0
  131. package/build/utils/pom-verify/calls.js +42 -0
  132. package/build/utils/pom-verify/calls.test.d.ts +1 -0
  133. package/build/utils/pom-verify/calls.test.js +61 -0
  134. package/build/utils/pom-verify/index.d.ts +4 -0
  135. package/build/utils/pom-verify/index.js +4 -0
  136. package/build/utils/pom-verify/resolve.d.ts +7 -0
  137. package/build/utils/pom-verify/resolve.js +27 -0
  138. package/build/utils/pom-verify/resolve.test.d.ts +1 -0
  139. package/build/utils/pom-verify/resolve.test.js +68 -0
  140. package/build/utils/pom-verify/strip.d.ts +9 -0
  141. package/build/utils/pom-verify/strip.js +89 -0
  142. package/build/utils/pom-verify/verify.d.ts +14 -0
  143. package/build/utils/pom-verify/verify.js +158 -0
  144. package/build/utils/pom-verify/verify.test.d.ts +1 -0
  145. package/build/utils/pom-verify/verify.test.js +325 -0
  146. package/build/utils/reportVerification.d.ts +61 -0
  147. package/build/utils/reportVerification.js +104 -0
  148. package/build/utils/reportVerification.test.d.ts +1 -0
  149. package/build/utils/reportVerification.test.js +185 -0
  150. package/build/utils/scenarioDrafting.js +5 -5
  151. package/build/utils/versions.d.ts +3 -3
  152. package/build/utils/versions.js +1 -1
  153. package/build/utils/workspaceAuth.d.ts +9 -1
  154. package/build/utils/workspaceAuth.js +25 -5
  155. package/build/utils/workspaceAuth.test.js +48 -0
  156. package/build/workspace/workspace.d.ts +20 -0
  157. package/build/workspace/workspace.js +4 -0
  158. package/build/workspace/workspace.test.js +10 -0
  159. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +77 -8
  160. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  161. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  162. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +101 -0
  163. package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-aszq5EdG.js → codeMirrorModule-Bzd72-bG.js} +1 -1
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-BxS7Jm4s.js → defaultSettingsView-DzxTioTK.js} +101 -101
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.D4JTTy4R.js → index.BGc30U3S.js} +1 -1
  167. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  168. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.DaRMQKOI.js → uiMode.IaDrb29A.js} +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  170. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  171. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  172. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  173. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +131 -0
  174. package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
  175. package/node_modules/playwright/package.json +1 -1
  176. package/package.json +3 -3
  177. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
package/build/index.js CHANGED
@@ -24,7 +24,7 @@ import { registerCodeReuseTool } from "./tools/code-refactor/codeReuseTool.js";
24
24
  import { registerEnhanceAssertionsTool } from "./tools/code-refactor/enhanceAssertionsTool.js";
25
25
  import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBatchScenarioRestTool.js";
26
26
  import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
27
- import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, } from "./tools/test-management/index.js";
27
+ import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, registerRegisterTestPlanTool, } from "./tools/test-management/index.js";
28
28
  import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
29
29
  import { registerSutSetupResource } from "./resources/sutSetupResource.js";
30
30
  import { registerTestbotResource } from "./resources/testbotResource.js";
@@ -151,6 +151,7 @@ registerProgressResource(server);
151
151
  registerAnalyzeChangesTool(server);
152
152
  registerAnalyzeTestHealthTool(server);
153
153
  registerActionsTool(server);
154
+ registerRegisterTestPlanTool(server);
154
155
  // Register workspace management tools
155
156
  registerInitScanWorkspaceTool(server);
156
157
  registerInitializeWorkspaceTool(server);
@@ -1 +1,7 @@
1
- export declare function getCodeReusePrompt(testFile: string, language: string, framework?: string): string;
1
+ import type { ScopedPom } 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?: {
4
+ tier1: ScopedPom[];
5
+ tier2: ScopedPom[];
6
+ scannedNoOverlap?: boolean;
7
+ }): string;
@@ -18,11 +18,14 @@ const LANGUAGE_MAP = {
18
18
  fileName: "skyrampUtils.ts",
19
19
  },
20
20
  };
21
- export function getCodeReusePrompt(testFile, language, framework) {
22
- const isTypescriptPlaywright = language.toLowerCase() === "typescript" &&
23
- framework?.toLowerCase() === "playwright";
24
- if (isTypescriptPlaywright) {
25
- return getPomAwareCodeReusePrompt(testFile);
21
+ export function isPomAwareTarget(language, framework) {
22
+ const lang = language.toLowerCase();
23
+ return (lang === "typescript" || lang === "javascript") && framework?.toLowerCase() === "playwright";
24
+ }
25
+ export function getCodeReusePrompt(testFile, language, framework, scopedPoms) {
26
+ const lang = language.toLowerCase();
27
+ if (isPomAwareTarget(lang, framework)) {
28
+ return getPomAwareCodeReusePrompt(testFile, lang, scopedPoms);
26
29
  }
27
30
  const ext = LANGUAGE_MAP[language].extension || "py";
28
31
  const fileName = LANGUAGE_MAP[language].fileName || "SkyrampUtils.py";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import { getCodeReusePrompt } from "./code-reuse.js";
2
+ const POM = "POM-AWARE CODE REUSE"; // unique to the POM branch
3
+ const pom = (file, score = 2, rareHits = 2, matchedTokens = ["a-b-c"]) => ({ file, score, rareHits, matchedTokens });
4
+ describe("getCodeReusePrompt routing", () => {
5
+ it("routes javascript + playwright to the POM-aware prompt", () => {
6
+ expect(getCodeReusePrompt("t.spec.js", "javascript", "playwright")).toContain(POM);
7
+ });
8
+ it("still routes typescript + playwright to the POM-aware prompt", () => {
9
+ expect(getCodeReusePrompt("t.spec.ts", "typescript", "playwright")).toContain(POM);
10
+ });
11
+ it("routes javascript + non-playwright to SkyrampUtils", () => {
12
+ expect(getCodeReusePrompt("t.spec.js", "javascript", "jest")).not.toContain(POM);
13
+ });
14
+ it("routes python to SkyrampUtils (no regression)", () => {
15
+ expect(getCodeReusePrompt("t.py", "python", "pytest")).not.toContain(POM);
16
+ });
17
+ it("does not trigger POM path when framework is undefined", () => {
18
+ expect(getCodeReusePrompt("t.spec.ts", "typescript")).not.toContain(POM);
19
+ });
20
+ });
21
+ describe("getCodeReusePrompt scoped POM list", () => {
22
+ it("emits the pre-detected two-tier STEP 1 when scoped files are supplied", () => {
23
+ const p = getCodeReusePrompt("t.spec.js", "javascript", "playwright", { tier1: [pom("/repo/pages/loginPage.js")], tier2: [pom("/repo/pages/homePage.js", 0.3, 1, ["menu-link-/alerts-item"])] });
24
+ expect(p).toContain("ALREADY DETECTED");
25
+ expect(p).toContain("/repo/pages/loginPage.js");
26
+ expect(p).toContain("catalog fully");
27
+ expect(p).toContain("/repo/pages/homePage.js");
28
+ expect(p).toContain("signature level");
29
+ expect(p).toContain("menu-link-/alerts-item"); // matched tokens surface for tier-2 mapping evidence
30
+ expect(p).not.toContain("DETECT POM CLASSES");
31
+ });
32
+ it("emits the glob STEP 1 when no scoped files are supplied", () => {
33
+ expect(getCodeReusePrompt("t.spec.js", "javascript", "playwright")).toContain("DETECT POM CLASSES");
34
+ });
35
+ it("emits the glob STEP 1 when both tiers are empty (fallback)", () => {
36
+ expect(getCodeReusePrompt("t.spec.js", "javascript", "playwright", { tier1: [], tier2: [] }))
37
+ .toContain("DETECT POM CLASSES");
38
+ });
39
+ it("returns the short skip response on a clean scan with zero overlap", () => {
40
+ const p = getCodeReusePrompt("t.spec.js", "javascript", "playwright", {
41
+ tier1: [], tier2: [], scannedNoOverlap: true,
42
+ });
43
+ expect(p).toContain("No reusable POM layer detected");
44
+ expect(p).toContain("verify: true"); // escape hatch mandates re-verify
45
+ expect(p).not.toContain("COMPLIANCE RULES"); // NOT the full workflow prompt
46
+ expect(p.length).toBeLessThan(2000);
47
+ });
48
+ it("keeps the full agent-driven fallback when scoping was inconclusive", () => {
49
+ // no scopedPoms argument = scan didn't run or didn't conclude
50
+ const p = getCodeReusePrompt("t.spec.js", "javascript", "playwright");
51
+ expect(p).toContain("COMPLIANCE RULES");
52
+ expect(p).not.toContain("No reusable POM layer detected");
53
+ });
54
+ it("leaves the SkyrampUtils path untouched for non-Playwright languages", () => {
55
+ const p = getCodeReusePrompt("t.py", "python", undefined, { tier1: [], tier2: [], scannedNoOverlap: true });
56
+ expect(p).not.toContain("No reusable POM layer detected");
57
+ expect(p).toContain("SkyrampUtils");
58
+ });
59
+ it("does not annotate when scoping simply was not attempted", () => {
60
+ expect(getCodeReusePrompt("t.spec.js", "javascript", "playwright")).not.toContain("No reusable POM layer detected");
61
+ });
62
+ });
@@ -1 +1,6 @@
1
- export declare function getPomAwareCodeReusePrompt(testFile: string): string;
1
+ import type { ScopedPom } from "../utils/pom-scope/index.js";
2
+ export declare function getPomAwareCodeReusePrompt(testFile: string, language?: string, scopedPoms?: {
3
+ tier1: ScopedPom[];
4
+ tier2: ScopedPom[];
5
+ scannedNoOverlap?: boolean;
6
+ }): string;
@@ -1,8 +1,52 @@
1
1
  import { generateSkyrampHeader, SKYRAMP_UTILS_HEADER } from "../utils/utils.js";
2
- const TS_UTILS_FILE = "skyrampUtils.ts";
3
- const TS_CODEGEN_MARKER = "// Generated by Skyramp v";
4
- export function getPomAwareCodeReusePrompt(testFile) {
5
- return `# POM-AWARE CODE REUSE TYPESCRIPT/PLAYWRIGHT
2
+ export function getPomAwareCodeReusePrompt(testFile, language = "typescript", scopedPoms) {
3
+ const isJs = language.toLowerCase() === "javascript";
4
+ const ext = isJs ? "js" : "ts";
5
+ const utilsFile = isJs ? "skyrampUtils.js" : "skyrampUtils.ts";
6
+ const codegenMarker = "// Generated by Skyramp v";
7
+ const headerLang = isJs ? "javascript" : "typescript";
8
+ const scopedActive = scopedPoms && scopedPoms.tier1.length + scopedPoms.tier2.length > 0;
9
+ if (!scopedActive && scopedPoms?.scannedNoOverlap) {
10
+ return `No reusable POM layer detected: a repo-wide scan found zero overlap between this test's selectors and any non-test source file. Code reuse is skipped — continue with the generated test as-is.
11
+
12
+ Caveat: detection matches literal selector tokens; a POM layer built on computed/dynamic selectors could be missed. If you have concrete evidence of one (e.g. a pageobjects/ directory with classes covering this flow), you may refactor ${testFile} manually following the repo's existing spec conventions — and if you do, you MUST call this tool again with \`verify: true\` and follow its report before finishing. Do NOT call skyramp_modularization.`;
13
+ }
14
+ const renderPom = (p) => `- \`${p.file}\`${p.matchedTokens.length > 0 ? ` — matched: ${p.matchedTokens.map((t) => `\`${t}\``).join(", ")}` : ""}`;
15
+ const step1Section = scopedActive
16
+ ? `## STEP 1: POM FILES — ALREADY DETECTED
17
+
18
+ POM detection has been performed programmatically from this test's selectors.
19
+
20
+ **Tier 1 — high relevance (catalog fully — every property and method):**
21
+ ${scopedPoms.tier1.map(renderPom).join("\n") || "(none)"}
22
+
23
+ **Tier 2 — supporting/possible (catalog at signature level ONLY — class name, method names, and the matched selectors shown; deepen a file only if STEP 2b maps an action to it):**
24
+ ${scopedPoms.tier2.map(renderPom).join("\n") || "(none)"}
25
+
26
+ **Caution on matched tokens:** the tokens above are raw substring evidence from the detector, not a guarantee of the file's full frame id — a matched token like \`#application-new-applied\` can hide that the file's ACTUAL frame is a prefix-sibling such as \`#application-new-applied-type\`. When you catalog each file in STEP 2, record its ACTUAL full selector/frame id from source rather than the bare matched token.
27
+
28
+ Do NOT glob or scan for other POM files. Proceed to STEP 2 and catalog exactly these files (plus any file they import or extend that you find essential while reading them).
29
+
30
+ **Catalog-completeness guard for STEP 2a:** when checking for an existing \`skyramp-pom-catalog.md\`, it is only valid for THIS run if it already contains an entry for EVERY file listed above. If any listed file is missing from the catalog, do NOT reuse it wholesale — keep its existing entries and generate + append entries for the missing listed files (then use the updated catalog).
31
+
32
+ **Decision:** continue to STEP 2.`
33
+ : `## STEP 1: DETECT POM CLASSES IN THE PROJECT
34
+
35
+ Use the Glob tool to search for POM files starting from the directory containing \`${testFile}\`:
36
+ - Pattern 1: \`**/pageobjects/**/*.{ts,js}\`
37
+ - Pattern 2: \`**/page-objects/**/*.{ts,js}\`
38
+ - Pattern 3: \`**/pages/**/*.{ts,js}\`
39
+ - Pattern 4: \`**/*.page.{ts,js}\`
40
+ - Pattern 5: \`**/*Page.{ts,js}\`
41
+ - Pattern 6: \`**/pom/**/*.{ts,js}\`
42
+
43
+ Exclude test/spec files by **basename only** — never match on the directory path. POM layers commonly live under a \`tests/\` directory (e.g. \`tests/ui-testing/pages/\`), and path-matching would wrongly drop every POM. A file is excluded only if its **basename** ends in \`.spec.ts\`, \`.spec.js\`, \`.test.ts\`, or \`.test.js\`. Do NOT exclude on the bare substrings \`test\` or \`spec\`: they falsely match legitimate POMs (e.g. \`searchJobInspectorPage.js\` contains "spec") and directory names (\`tests/\`).
44
+ Also exclude \`node_modules\`.
45
+
46
+ **Decision:**
47
+ - Zero matches found → **SKIP TO STEP 6 (FALLBACK)**
48
+ - One or more matches found → continue to STEP 2`;
49
+ return `# POM-AWARE CODE REUSE — TYPESCRIPT/JAVASCRIPT + PLAYWRIGHT
6
50
 
7
51
  ## COMPLIANCE RULES — READ THIS FIRST, APPLY TO EVERY STEP
8
52
 
@@ -27,22 +71,7 @@ This is NOT the same as SkyrampUtils consolidation. Do NOT create a SkyrampUtils
27
71
 
28
72
  ---
29
73
 
30
- ## STEP 1: DETECT POM CLASSES IN THE PROJECT
31
-
32
- Use the Glob tool to search for POM files starting from the directory containing \`${testFile}\`:
33
- - Pattern 1: \`**/pageobjects/**/*.ts\`
34
- - Pattern 2: \`**/page-objects/**/*.ts\`
35
- - Pattern 3: \`**/pages/**/*.ts\`
36
- - Pattern 4: \`**/*.page.ts\`
37
- - Pattern 5: \`**/*Page.ts\`
38
- - Pattern 6: \`**/pom/**/*.ts\`
39
-
40
- Exclude any file with \`test\`, \`spec\`, \`.test.\`, or \`.spec.\` in its name.
41
- Also exclude \`node_modules\`.
42
-
43
- **Decision:**
44
- - Zero matches found → **SKIP TO STEP 6 (FALLBACK)**
45
- - One or more matches found → continue to STEP 2
74
+ ${step1Section}
46
75
 
47
76
  ---
48
77
 
@@ -50,7 +79,7 @@ Also exclude \`node_modules\`.
50
79
 
51
80
  **Step 2a: Check for existing catalog (MANDATORY FIRST STEP)**
52
81
 
53
- Check if \`skyramp-pom-catalog.md\` exists in the same directory as \`tsconfig.json\`:
82
+ Check if \`skyramp-pom-catalog.md\` exists in the same directory as \`tsconfig.json\` (or \`jsconfig.json\`, or the test root if neither exists):
54
83
  - **If it exists:** Read it and verify it starts with \`<!-- skyramp-pom-catalog: auto-generated -->\`
55
84
  - Valid → Output: *"Using existing POM catalog from /path/to/skyramp-pom-catalog.md"* and **skip to STEP 2b**
56
85
  - Invalid → Continue to full catalog generation below
@@ -62,12 +91,17 @@ Check if \`skyramp-pom-catalog.md\` exists in the same directory as \`tsconfig.j
62
91
 
63
92
  Before writing a single line of refactored code, do the following:
64
93
 
65
- can 1. **Read existing refactored tests** — Find any already-refactored test files in the repository and note their established conventions: how imports are grouped, what comment style sections use, and how \`waitForTimeout\`, \`waitForResponse\`, and dynamic locators are handled. Where existing tests provide a pattern, follow it exactly.
94
+ 1. **Extract conventions from ONE existing test** — Open one existing test spec (prefer one that imports the POM layer). Extract ONLY: (a) the instantiation convention (central PageManager, e.g. \`const pm = new PageManager(page)\`, vs direct \`new X(page)\`), (b) import style and path aliases, (c) the section-comment style. Ignore everything else in that file — do NOT mimic its test logic, waits, or assertions.
66
95
 
67
96
  2. **Read tsconfig.json / jsconfig.json** — Capture path alias conventions (e.g., \`@pageobjects/*\`, \`@common-function/*\`). All imports in the refactored test must use these aliases exactly.
68
97
 
69
- 3. **Read every POM file found in STEP 1** — For each file, document:
98
+ 3. **Read the POM files from STEP 1** — Depth depends on how STEP 1 listed them: files marked **Tier 2 (signature level)** get a REDUCED entry — class name, method names (signatures only), and the matched selectors STEP 1 showed; do NOT read their full bodies or record per-method timing/network details unless STEP 2b later maps an action to that file (then deepen just that file). **Tier 1 files — and ALL files when STEP 1 did not use tiers (agent-detected mode)** — get the FULL treatment below. For each full-depth file, document:
70
99
  - Class name, import path (using tsconfig aliases), and what page/component it represents
100
+ **Locator styles to capture — a POM class may expose selectors in ANY of these forms; catalog all of them, recording the selector string passed to \`.locator()\` / \`getByRole\` / \`getByTestId\` etc.:**
101
+ - Constructor-assigned: \`this.loginButton = page.locator('[data-test="login-sign-in"]')\`
102
+ - Getter accessors: \`get firstRow() { return this.page.locator('[data-test^="row-"]').first(); }\`
103
+ - Object-literal maps: \`this.locators = { menuItem: '[data-test="menu-item"]', ... }\` (each key is a named selector)
104
+ - Inline in methods: a \`.locator(...)\` used directly inside an action method (still a reusable target, named by its method)
71
105
  - Iframe selector used in the constructor (e.g. \`page.frameLocator('iframe#asset-list')\`), or "top-level page" if no iframe
72
106
  - Every property (locator): name and its exact selector string from the constructor assignment
73
107
  - Every method: name, parameters, what action it performs
@@ -77,7 +111,7 @@ can 1. **Read existing refactored tests** — Find any already-refactored test f
77
111
  - Whether the method wraps a multi-step sequence using sub-properties or components (e.g. \`searchAssetsByCondition\` internally calls \`searchConditionArea.expandWhenClosed()\`, \`clearBtn.click()\`, \`selectItemName()\`, \`valueKeyword.fill()\`, \`searchBtn.click()\`) — flag these as **wrapping methods**
78
112
  - Flag methods as **timing-aware**, **network-aware**, or **wrapping** as appropriate — all three are high-value substitution targets
79
113
 
80
- 4. **Write the POM catalog — MANDATORY** — Write the complete catalog to \`skyramp-pom-catalog.md\` in the same directory as \`tsconfig.json\`. This gives you a clean structured reference for STEP 2b and leaves a record for debugging.
114
+ 4. **Write the POM catalog — MANDATORY** — Write the complete catalog to \`skyramp-pom-catalog.md\` in the same directory as \`tsconfig.json\` (or \`jsconfig.json\`, or the test root if neither exists). This gives you a clean structured reference for STEP 2b and leaves a record for debugging.
81
115
 
82
116
  **BLOCKING: Do not proceed to STEP 2b until you have written this file and confirmed the path in a single output line:**
83
117
  *"POM catalog written to /path/to/skyramp-pom-catalog.md"*
@@ -98,13 +132,15 @@ Use this exact format for the file:
98
132
  - \`methodName(param1, param2)\` — description; ⏱ timing-aware: waitForTimeout(Xms) + sleep(Ys) | 🌐 network-aware: waitForResponse('**/pattern**') | (omit tags if neither)
99
133
  \`\`\`
100
134
 
135
+ **Tier-2 entries in the catalog:** for files cataloged at signature level, the entry contains the class name, the import path, a \`- **Methods:**\` list of names/signatures only, and the matched selectors from STEP 1 under \`- **Properties:**\`. Mark such entries with \`<!-- depth: signature -->\` on the line after the class heading so a later run knows the entry is shallow.
136
+
101
137
  ---
102
138
 
103
139
  ## STEP 2b: BUILD AND OUTPUT A MAPPING TABLE
104
140
 
105
141
  **This step is MANDATORY. Do not write any code until it is complete.**
106
142
 
107
- For every raw locator and action sequence in \`${testFile}\`, find the best POM match and record it in a table. Output the complete table to chat before proceeding to STEP 3.
143
+ For every raw locator and action sequence in \`${testFile}\`, find the best POM match; record the PREFERRED and HIGH matches in a table (all others simply remain inline). Output the complete table to chat before proceeding to STEP 3.
108
144
 
109
145
  ### Matching criteria
110
146
 
@@ -138,6 +174,8 @@ Before considering any POM match, verify the iframe context matches:
138
174
 
139
175
  ### Mapping table format
140
176
 
177
+ List ONLY rows with PREFERRED or HIGH confidence — these are the substitutions you will make. Do NOT enumerate LOW/UNCERTAIN or UNMAPPED locators in the table; every locator not in the table remains inline as-is.
178
+
141
179
  Output the table in this format:
142
180
 
143
181
  \`\`\`
@@ -145,16 +183,16 @@ Output the table in this format:
145
183
  |---|---|---|---|---|
146
184
  | goto + fill email/password + click login | LoginPage | login(email, password) | method is timing-aware (waitForTimeout+sleep) covering login sequence | PREFERRED |
147
185
  | frameLocator('iframe#asset-list').locator('button:has-text("資産登録")') | AssetListPage | assetEntryBtn | iframe.locator('button:has-text("資産登録")') | HIGH |
148
- | page.locator('input[name="email"]').fill(...) | LoginPage | emailInput | getByPlaceholder('メールアドレス(半角)') | LOW |
149
- | frameLocator('iframe#wf-input').locator('input[name="changeQty"]') | — | — | No match | UNMAPPED |
150
186
  \`\`\`
151
187
 
188
+ All other locators remain inline.
189
+
152
190
  ### Rules after completing the table
153
191
 
154
192
  - **PREFERRED entries MUST be substituted** — use the POM method and drop the raw test's explicit waits/correlations that fall inside the method's scope
155
193
  - **HIGH entries MUST be substituted** — use the POM method or property; do NOT write an inline locator
156
194
  - **Always prefer the highest level of abstraction available** — if a wrapping method exists that covers a multi-step sequence, use it instead of calling the individual sub-properties directly. Example: use \`searchAssetsByCondition("資産名", assetName)\` not \`searchConditionArea.expandWhenClosed()\` + \`getConditionRow(1)\` + \`selectItemName()\` etc. Use \`bulkDelete()\` not \`bulkDeleteBtn.click()\` + \`confirmDeleteDialog\` + \`noticeDialog\` inline.
157
- - **Do NOT upgrade LOW or UNCERTAIN** because the substitution seems reasonable
195
+ - **Do NOT upgrade LOW or UNCERTAIN to PREFERRED/HIGH** because the substitution seems reasonable — leave them inline and out of the table
158
196
  - **Conservative default**: an incorrect substitution that breaks the test is worse than leaving something inline — when in doubt, leave it inline
159
197
  - If the table has ZERO PREFERRED or HIGH entries → **SKIP TO STEP 6 (FALLBACK)**
160
198
  - If AT LEAST ONE PREFERRED or HIGH entry exists → continue to STEP 3
@@ -264,6 +302,8 @@ When a module operates inside an iframe, determine the correct frame handle by r
264
302
 
265
303
  **Never create standalone \`frameLocator()\` references in the test body.** If an action is UNMAPPED and must stay inline, access the frame through the POM's own \`iframe\` property (e.g. \`assetSelectPage.iframe.locator(...)\`, \`inputPage.iframe.getByRole(...)\`). Do not create a separate \`const inputPageFrame = skyrampPage.frameLocator('#application-new-applied')\` — this duplicates the frame reference that already exists in the POM.
266
304
 
305
+ **Exception — orphan frame:** if NO cataloged POM class scopes to the frame a raw action uses, there is no POM \`iframe\` property to borrow. In that case a standalone inline \`frameLocator(...)\` for that action is CORRECT — keep it, and mark it with a brief comment (e.g. \`// no POM owns this frame — kept inline\`). Do not force-fit a method from a different-frame class (the gate above still forbids that) and do not invent a POM to cover it.
306
+
267
307
  ### 3h. Dynamic Locators — Prefer Positional Module Methods
268
308
 
269
309
  Before keeping a dynamic locator inline, check whether a module method exists that targets the same element by position rather than by value. If one exists, use it with the appropriate position (typically \`0\` for the first result) and add a comment noting it replaces the original dynamic locator. If no positional equivalent exists, keep the raw dynamic locator inline as-is.
@@ -278,6 +318,12 @@ If the existing POM files have an established pattern for checkbox interactions,
278
318
 
279
319
  Screenshot assertions (\`toHaveScreenshot\`) must stay in their **exact original position** relative to surrounding actions from the raw test. Note what actions appear immediately before and after the screenshot in the raw test, then place the screenshot at that same relative position in the refactored test. If a POM method would move the screenshot, inline that method's steps around it with a comment: \`// POM method not used — screenshot position must be preserved\`
280
320
 
321
+ ### 3k. Page Object Instantiation — Follow the Repo's Convention
322
+
323
+ **Instantiation convention — follow the repo's existing pattern (read the existing specs to detect it):**
324
+ - If the repo uses a central PageManager (e.g. \`const pm = new PageManager(page)\` then \`pm.loginPage.login()\`), the refactored test MUST use that accessor convention — do NOT instantiate page objects directly.
325
+ - Otherwise, instantiate page objects directly, matching what the existing specs do.
326
+
281
327
  ---
282
328
 
283
329
  ## STEP 4: WRITE THE REFACTORED TEST
@@ -288,13 +334,11 @@ Using your catalog from STEP 2 and analysis from STEP 3, produce the refactored
288
334
  Before writing each line of refactored code, look up that action in your STEP 2b mapping table:
289
335
  - If it is marked **PREFERRED** → use the POM method and drop any raw waits/correlations inside its scope. This is the highest-value substitution.
290
336
  - If it is marked **HIGH** → you MUST use the POM method or property. Do NOT write an inline locator.
291
- - If it is marked **LOW / UNCERTAIN / UNMAPPED** → write it inline exactly as in the original test.
337
+ - If it is NOT in the table (LOW / UNCERTAIN / UNMAPPED) → write it inline exactly as in the original test.
292
338
 
293
- Do not skip this lookup for any action. The mapping table is your source of truth not the raw test's locators.
339
+ A locator with a PREFERRED/HIGH mapping that is written inline anyway is a DEFECT the customer reads that as a missed reuse.
294
340
 
295
- ## STEP 4b: VERIFY AGAINST MAPPING TABLE
296
-
297
- Before proceeding to STEP 5, scan your refactored test output and for each HIGH entry in the mapping table confirm the corresponding POM call is present in the output. If any HIGH entry is missing its POM call and was written as inline code instead, go back and fix it before writing the file.
341
+ Do not skip this lookup for any action. The mapping table is your source of truth — not the raw test's locators.
298
342
 
299
343
  **Section Comments:** Divide the test body into clearly labeled sections corresponding to the logical phases of the user journey. Use the comment style from the repository's existing tests. If none exists, use \`/*----- Section Name -----*/\`.
300
344
 
@@ -308,17 +352,19 @@ Before proceeding to STEP 5, scan your refactored test output and for each HIGH
308
352
  - Screenshot assertions (\`toHaveScreenshot\`): always keep inline; if mid-method sequence, inline that method's steps around it
309
353
  - Page readiness checks: use module methods such as \`pageToBeVisible()\` if they exist
310
354
 
311
- **IMPORTANT:** Do NOT create a \`skyrampUtils.ts\` or any SkyrampUtils file on this path. Do NOT call \`skyramp_modularization\` after completing POM-aware code reuse — POM refactoring replaces that step.
355
+ **IMPORTANT:** Do NOT create a \`${utilsFile}\` or any SkyrampUtils file on this path. Do NOT call \`skyramp_modularization\` after completing POM-aware code reuse — POM refactoring replaces that step.
312
356
 
313
357
  ---
314
358
 
315
359
  ## STEP 5: WRITE THE OUTPUT
316
360
 
317
- Write the complete, runnable refactored test directly to \`${testFile}\`, overwriting it in place. The file must contain every line — never use placeholder comments like \`// ...continue\` or \`// ...existing code\`. If the test is long, write it in multiple passes until the file is complete. Confirm the file path written to in a single line once done.
361
+ Write the complete, runnable refactored test directly to \`${testFile}\`, overwriting it in place. Before overwriting, save the current (pre-refactor) content to \`${testFile}.raw.bak\` — verification uses it to restore demoted substitutions. The file must contain every line — never use placeholder comments like \`// ...continue\` or \`// ...existing code\`. If the test is long, write it in multiple passes until the file is complete. Confirm the file path written to in a single line once done.
318
362
 
319
363
  Do not output a section breakdown, modularization summary, or any analysis to the chat. Perform all analysis internally.
320
364
 
321
- **Do NOT run the test. Do NOT call any other tools. Your work is complete once the file is written.**
365
+ **A low substitution count can be the correct outcome.** When many actions stay inline because the POM layer simply does not model that flow (iframe-gate rejections, orphan frames, vocabulary drift from the cataloged classes), that is honest reuse, not a shortfall — do not pad the mapping table to inflate the count. In your one-line confirmation, state how many actions were substituted vs. kept inline and why in one clause (e.g. "6 substituted; 9 kept inline — POM layer does not model the apply-from-asset flow").
366
+
367
+ **Do NOT run the test.** After writing the file, you MUST call \`skyramp_reuse_code\` once more with the same \`testFile\`/\`language\`/\`framework\` and \`verify: true\`, and follow its report: re-verify after every fix pass; a violation may be remapped at most once — if flagged again, demote it to the raw inline form; finish only when it reports PASSED (unverifiable notes are acceptable). Include \`verification: passed\` (or the unresolved count) in your final confirmation line.
322
368
 
323
369
  ---
324
370
 
@@ -360,7 +406,7 @@ Read ${testFile} and look for ALREADY DEFINED helper functions (not just repetit
360
406
  ## STEP 2: FIND EXISTING UTILS
361
407
  Use the Grep tool to search for files containing "${SKYRAMP_UTILS_HEADER}":
362
408
  - Pattern: "${SKYRAMP_UTILS_HEADER}"
363
- - Type: "ts"
409
+ - Type: "${ext}"
364
410
  - Output mode: "files_with_matches"
365
411
  **CRITICAL: Only look for util files with header ${SKYRAMP_UTILS_HEADER}** - exclude test files (files with "test", "spec", ".test.", ".spec." in the name).
366
412
  Read the matching non-test files and check if any helpers can be reused in ${testFile}.
@@ -373,18 +419,18 @@ If helpers exist in util file that can be reused in ${testFile} without modifyin
373
419
 
374
420
  ## STEP 4: FIND LOCAL HELPERS IN OTHER SKYRAMP-GENERATED TEST FILES
375
421
 
376
- You are looking for sibling test files generated by Skyramp's codegen. They start with the comment line \`${TS_CODEGEN_MARKER}…\` (note: the version suffix is what distinguishes them from the SkyrampUtils header). The substring \`${SKYRAMP_UTILS_HEADER}\` is contained in that line, so:
422
+ You are looking for sibling test files generated by Skyramp's codegen. They start with the comment line \`${codegenMarker}…\` (note: the version suffix is what distinguishes them from the SkyrampUtils header). The substring \`${SKYRAMP_UTILS_HEADER}\` is contained in that line, so:
377
423
 
378
424
  Use the Grep tool to search for other test files containing "${SKYRAMP_UTILS_HEADER}":
379
425
  - Pattern: "${SKYRAMP_UTILS_HEADER}"
380
- - Type: "ts"
426
+ - Type: "${ext}"
381
427
  - Output mode: "files_with_matches"
382
428
 
383
429
  **CRITICAL: Exclude ${testFile} from the results** — only look at OTHER files, not the current file.
384
430
 
385
431
  The result will mix two kinds of files:
386
432
  - **Utils file(s)** — already handled by STEP 2/STEP 3.
387
- - **Other Skyramp-generated test files** — these are the targets of STEP 4 / 4b / 5. Identify them by the \`${TS_CODEGEN_MARKER}\` codegen marker on line 1 (NOT the bare utils header).
433
+ - **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).
388
434
 
389
435
  **STOP HERE IF NO OTHER SKYRAMP-GENERATED TEST FILES FOUND**
390
436
  **IF NO OTHER SKYRAMP-GENERATED TEST FILES ARE FOUND, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**
@@ -407,7 +453,7 @@ NOT A HELPER FUNCTION (do not extract):
407
453
 
408
454
  ## STEP 4b: PARAMETERIZE NEAR-DUPLICATE HELPERS ACROSS SKYRAMP-GENERATED TEST FILES
409
455
 
410
- **This step handles the cross-test case: ${testFile} AND another Skyramp-generated test file each define their own helper that does the SAME shape of work with only literal-value differences. These should become ONE parameterized helper in \`${TS_UTILS_FILE}\`.**
456
+ **This step handles the cross-test case: ${testFile} AND another Skyramp-generated test file each define their own helper that does the SAME shape of work with only literal-value differences. These should become ONE parameterized helper in \`${utilsFile}\`.**
411
457
 
412
458
  ### Definition: "near-duplicate" helpers
413
459
 
@@ -478,9 +524,9 @@ For each helper defined locally in ${testFile}:
478
524
  - **Body**: the common primitive sequence with the differing literals replaced by parameters.
479
525
  - **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.
480
526
  4. **If multiple matches** → process each pair independently. Merge every pair that clearly meets all three near-duplicate criteria. Skip any pair you're uncertain about — a missed merge is recoverable, a wrong merge breaks both tests.
481
- 5. **Write** the merged helper into \`${TS_UTILS_FILE}\` (create the file with the standard header if it doesn't exist; same header as STEP 5 below).
527
+ 5. **Write** the merged helper into \`${utilsFile}\` (create the file with the standard header if it doesn't exist; same header as STEP 5 below).
482
528
  6. **Delete** both original helpers from their test files.
483
- 7. **Import** the merged helper from \`${TS_UTILS_FILE}\` into both test files.
529
+ 7. **Import** the merged helper from \`${utilsFile}\` into both test files.
484
530
  8. **Replace** the two original call sites with calls to the merged helper, passing the values that were originally hardcoded or argument-passed in each test.
485
531
 
486
532
  ### Hard rules
@@ -488,7 +534,7 @@ For each helper defined locally in ${testFile}:
488
534
  - **Conservative bias**: a wrong merge breaks both tests; a missed merge leaves a duplicate. When in doubt, do NOT merge.
489
535
  - **No "cleanup"**: the merged body must mirror the original primitive sequence — same waits, same correlation patterns, same order. This is not refactoring.
490
536
  - **Preserve typing style**: if originals used \`page: any\` (or untyped \`page\`), keep that convention.
491
- - **Don't merge with helpers already in \`${TS_UTILS_FILE}\`**: those are STEP 3's territory. STEP 4b is strictly cross-test merge between sibling Skyramp-generated test files.
537
+ - **Don't merge with helpers already in \`${utilsFile}\`**: those are STEP 3's territory. STEP 4b is strictly cross-test merge between sibling Skyramp-generated test files.
492
538
 
493
539
  ---
494
540
 
@@ -505,15 +551,15 @@ For each helper defined locally in ${testFile}:
505
551
  **IF ANY CONDITION IS NOT MET, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**
506
552
 
507
553
  **CRITICAL:** For helpers found in STEP 4:
508
- 1. **CREATE** \`${TS_UTILS_FILE}\` file if it doesn't exist with the following header:
554
+ 1. **CREATE** \`${utilsFile}\` file if it doesn't exist with the following header:
509
555
  \`\`\`ts
510
- ${generateSkyrampHeader("typescript")}
556
+ ${generateSkyrampHeader(headerLang)}
511
557
  \`\`\`
512
- **CRITICAL: IF \`${TS_UTILS_FILE}\` is already big, create a new file with a different name and add the header to the new file.**
513
- 2. **COPY** those local helper functions from original test source files to \`${TS_UTILS_FILE}\` without modifying them
558
+ **CRITICAL: IF \`${utilsFile}\` is already big, create a new file with a different name and add the header to the new file.**
559
+ 2. **COPY** those local helper functions from original test source files to \`${utilsFile}\` without modifying them
514
560
  3. **DELETE** those local helper functions from test source files (REMOVE THEM COMPLETELY) WITHOUT ANY OTHER CHANGES.
515
- 4. **IMPORT** those local helper functions from \`${TS_UTILS_FILE}\` back into test source files WITHOUT MAKING ANY OTHER CHANGE.
516
- 5. **IMPORT** those local helper functions from \`${TS_UTILS_FILE}\` into ${testFile}
561
+ 4. **IMPORT** those local helper functions from \`${utilsFile}\` back into test source files WITHOUT MAKING ANY OTHER CHANGE.
562
+ 5. **IMPORT** those local helper functions from \`${utilsFile}\` into ${testFile}
517
563
  6. **REPLACE** duplicate code in ${testFile} with calls to imported helper functions WITHOUT MAKING ANY OTHER CHANGE.
518
564
 
519
565
  **CRITICAL: DO NOT CREATE UNNECESSARY HELPER FUNCTIONS**
@@ -525,14 +571,14 @@ For each helper defined locally in ${testFile}:
525
571
  3. **VERIFY** that helper functions are NO LONGER in original test files
526
572
  4. **VERIFY** that the original test files only have import statements and no duplicate code
527
573
  5. **VERIFY** that both original and new test files import from utils and use the helper functions
528
- 5a. **VERIFY STEP 4b OUTCOMES** — for every near-duplicate pair you merged in STEP 4b: the merged helper exists in \`${TS_UTILS_FILE}\` exactly once; BOTH original helpers are deleted from their respective test files; BOTH test files import the merged helper from \`${TS_UTILS_FILE}\`; BOTH original call sites are replaced with calls to the merged helper. If any of these is false, fix it before finishing.
574
+ 5a. **VERIFY STEP 4b OUTCOMES** — for every near-duplicate pair you merged in STEP 4b: the merged helper exists in \`${utilsFile}\` exactly once; BOTH original helpers are deleted from their respective test files; BOTH test files import the merged helper from \`${utilsFile}\`; BOTH original call sites are replaced with calls to the merged helper. If any of these is false, fix it before finishing.
529
575
  6. **VERIFY** that no unnecessary helper functions were created
530
576
  7. **VERIFY** that all helper functions in utils are actually imported and used in the test files
531
577
  8. **REMOVE** any helper functions that are not being used after refactoring
532
578
  9. **NEVER** refactor, reorganize, or restructure existing source test files beyond moving helpers
533
579
  10. **Do NOT run the test** — your work is complete once the file is written
534
580
 
535
- **Do NOT call skyramp_modularization.** Code reuse is complete. The test file has been refactored in place — no further tools should be called.
581
+ **Do NOT call skyramp_modularization.** Code reuse is complete. The test file has been refactored in place — no further tools should be called on this FALLBACK path. (If you took the POM-aware path instead, STEP 5's verification requirement still applies.)
536
582
 
537
583
  SUMMARIZE THE CODE REUSE PROCESS AND THE RESULTS.
538
584
  `;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { getPomAwareCodeReusePrompt } from "./pom-aware-code-reuse.js";
2
+ describe("getPomAwareCodeReusePrompt language derivation", () => {
3
+ it("uses .ts utils filename for typescript (default)", () => {
4
+ expect(getPomAwareCodeReusePrompt("t.spec.ts")).toContain("skyrampUtils.ts");
5
+ });
6
+ it("uses .js utils filename for javascript", () => {
7
+ const p = getPomAwareCodeReusePrompt("t.spec.js", "javascript");
8
+ expect(p).toContain("skyrampUtils.js");
9
+ expect(p).not.toContain("skyrampUtils.ts");
10
+ });
11
+ });
@@ -1,5 +1,5 @@
1
1
  import { DraftedScenario } from "../../types/RepositoryAnalysis.js";
2
- import { PriorityTier } from "../../types/TestRecommendation.js";
2
+ import { Novelty, PriorityTier } from "../../types/TestRecommendation.js";
3
3
  /** "0" — Code Review: correctness analysis */
4
4
  export declare const EXEC_STEP_CODE_REVIEW: string;
5
5
  /** "1" — External test coverage verification */
@@ -10,10 +10,12 @@ export declare const EXEC_STEP_ENRICH: string;
10
10
  export declare const EXEC_STEP_DIVERSITY: string;
11
11
  /** "4" — Execute merged plan */
12
12
  export declare const EXEC_STEP_EXECUTE: string;
13
+ /** "5" — Register test plan (SKYR-3879 Path B checkpoint) */
14
+ export declare const EXEC_STEP_REGISTER: string;
13
15
  export declare function buildExecutionPlan(scored: Array<{
14
16
  scenario: DraftedScenario;
15
17
  priority: PriorityTier;
16
- novelty: string;
18
+ novelty: Novelty;
17
19
  }>, maxGen: number, topN: number, baseUrl: string, authHeaderValue: string, authSchemeSnippet: string, authTypeValue: string, seed: string, endpointCount: number, isUIOnlyPR: boolean, hasFrontendChanges?: boolean, hasTraces?: boolean, externalCoverage?: Set<string>, relevantExternalTestPaths?: string[],
18
20
  /**
19
21
  * Whether the diff classified at least one new/modified/removed endpoint.
@@ -1,3 +1,4 @@
1
+ import { roundRobinByType } from "../../recommendation/diversity.js";
1
2
  import { AUTH_MIDDLEWARE_PATTERNS_STR } from "../../utils/workspaceAuth.js";
2
3
  import { resolveServiceDetailsRef } from "../../utils/utils.js";
3
4
  import { logger } from "../../utils/logger.js";
@@ -21,7 +22,7 @@ The highest-severity \`<bug_found>\` block from this code review triggers a mand
21
22
  }
22
23
  function _execCoverageBody(ctx) {
23
24
  return `${ctx.externalTestFilesList}For every GENERATE item below, check its endpoint path and test type against the Existing Tests list (further down in the prompt).
24
- - **\`[external]\` tests**: If the endpoint is already covered by an \`[external]\` test of the same type → skip the resource entirely (do NOT create or update), **except for \`bug_caught\` and attack-surface \`security_boundary\` items**. A protected item may be skipped only if the external test directly asserts the same flaw/bypass and would fail/pass based on that same bug; same endpoint/resource coverage alone is not enough. Backfill from ADDITIONAL using the priority order below:
25
+ - **\`[external]\` tests**: If the endpoint is already covered by an \`[external]\` test of the same type **that exercises the behavior this PR changes** → skip the resource entirely (do NOT create or update). Resource-level overlap alone is NOT coverage: when the PR tightens or adds a constraint (e.g. a max-items/max-length/enum bound), an external test that never crosses the new bound does not cover it — generate the boundary test. This matters most for single-resource APIs (e.g. one CRD served by the kube-apiserver), where resource-level dedup would permanently block all generation. \`bug_caught\` and attack-surface \`security_boundary\` items get the strictest reading: they may be skipped only if the external test directly asserts the same flaw/bypass and would fail/pass based on that same bug. Backfill from ADDITIONAL using the priority order below:
25
26
  1. **BUG-CATCHING TESTS FIRST (CRITICAL)**: If source code analysis revealed a bug, logic error, or incorrect formula (e.g. discount math adding instead of subtracting, off-by-one errors, missing validation), CREATE A TEST THAT EXPOSES IT. The test SHOULD FAIL — that's the point. Document the bug. Example: if discount formula is wrong, test with discount=20% and assert correct math. If no bug found, skip to #2.
26
27
  2. **PR-endpoint edge cases**: Look for integration test candidates covering error paths, boundary values, or alternative scenarios for the SAME endpoints changed in the PR diff. If no suitable candidate exists in ADDITIONAL, derive one from your source-code enrichment findings.
27
28
  3. **Same-resource other scenarios**: Other HTTP methods or flows on the same resource group touched by the PR.
@@ -94,6 +95,11 @@ For each pair of GENERATE items, ask: same HTTP method + path + step sequence +
94
95
 
95
96
  Same step sequence with only payload differences (e.g. 10% vs 5% discount both returning 200) = same code path = duplicate. Different scenario names do not make duplicate tests distinct.`;
96
97
  }
98
+ function _execRegisterBody(_ctx) {
99
+ return `Register your complete candidate list — every test you would generate OR recommend — via \`skyramp_register_test_plan\` (\`stateFile\` required). Include a discriminator claim (\`discriminator\` field — valid kinds and anchor rules are in the tool schema) for candidates probing the changed logic identified in Step ${EXEC_STEP_CODE_REVIEW}/Step ${EXEC_STEP_ENRICH}.
100
+
101
+ The returned GENERATE list is mandatory and final — generation tools reject unregistered scenarios. If the tool demotes a discriminator claim (returned in \`demotions\` with a reason), either strengthen the claim — a step that actually exercises the declared \`kind\`, or a verbatim anchor that occurs in the diff — or drop it; the candidate itself stays in the plan either way.`;
102
+ }
97
103
  function _execExecuteBody(ctx) {
98
104
  return `Replace any scenario that pairs unrelated resources with one reflecting actual foreign-key relationships in the codebase.
99
105
  Use the field names and values from the \`<source_evidence>\` blocks you quoted in Step ${EXEC_STEP_ENRICH} to fill all tool call parameters. Prefer reusing Step ${EXEC_STEP_ENRICH} evidence when it already resolves a placeholder, but if a placeholder cannot be replaced with concrete values from files already read, you may read the specific schema, model, or handler file needed to resolve it. Assert response field values, not just status codes.
@@ -127,6 +133,7 @@ const _execPlan = new PromptPlan({ startFrom: 0 })
127
133
  .step("ENRICH", "Parameter Grounding & Priority Assignment", _execEnrichBody)
128
134
  .step("DIVERSITY", (ctx) => `Diversity check (using enriched knowledge from Step ${ctx.enrichStepLabel})`, _execDiversityBody)
129
135
  .step("EXECUTE", "Execute merged plan in rank order", _execExecuteBody)
136
+ .step("REGISTER", "Register your test plan", _execRegisterBody)
130
137
  .done();
131
138
  // ── Exported step label constants ─────────────────────────────────────────────
132
139
  /** "0" — Code Review: correctness analysis */
@@ -139,6 +146,8 @@ export const EXEC_STEP_ENRICH = _execPlan.labels.ENRICH; // "2"
139
146
  export const EXEC_STEP_DIVERSITY = _execPlan.labels.DIVERSITY; // "3"
140
147
  /** "4" — Execute merged plan */
141
148
  export const EXEC_STEP_EXECUTE = _execPlan.labels.EXECUTE; // "4"
149
+ /** "5" — Register test plan (SKYR-3879 Path B checkpoint) */
150
+ export const EXEC_STEP_REGISTER = _execPlan.labels.REGISTER; // "5"
142
151
  const SERVICE_REFS = resolveServiceDetailsRef();
143
152
  function prioritizeAttackSurfaceBundles(items) {
144
153
  const reordered = [];
@@ -154,69 +163,6 @@ function prioritizeAttackSurfaceBundles(items) {
154
163
  }
155
164
  return reordered;
156
165
  }
157
- /**
158
- * Select `count` items from a rank-ordered list, distributing GENERATE slots
159
- * EVENLY across the test types present, with spillover.
160
- *
161
- * Policy (kept identical to the multi-repo prose in testbot-prompts.ts's
162
- * "Cross-repo test generation" block — change both together):
163
- * - Protected items first: CRITICAL-priority and attack-surface security_boundary
164
- * scenarios always take a slot before round-robin (they must stay in GENERATE).
165
- * - Bucket the rest by inferred test type (contract vs integration — the same
166
- * inference used when rendering: `testType ?? (steps===1 ? contract : integration)`).
167
- * - Round-robin one item per non-empty bucket per round, in the buckets' order of
168
- * first appearance in the rank-ordered list (so the highest-ranked type wins
169
- * round 1), preserving rank order within each bucket.
170
- * - Spillover: an exhausted bucket is skipped on later rounds, so its freed slots
171
- * go to the next type's next-highest item.
172
- *
173
- * Degenerate cases match the previous pure rank-order slice exactly: a single type
174
- * present, or `count >= items.length`, returns the same items in the same order —
175
- * so backend-only / single-type runs are unchanged (no regression).
176
- */
177
- function roundRobinByType(rankOrdered, count) {
178
- if (count <= 0)
179
- return [];
180
- // Everything fits → no need to bucket; identical to the old slice.
181
- if (count >= rankOrdered.length)
182
- return rankOrdered.slice(0, count);
183
- const inferType = (s) => s.testType ?? (s.steps.length === 1 ? "contract" : "integration");
184
- // Protected items occupy GENERATE slots first, in rank order.
185
- const selected = [];
186
- const remaining = [];
187
- for (const item of rankOrdered) {
188
- if (selected.length < count &&
189
- (item.priority === "CRITICAL" || isAttackSurfaceSecurityBoundary(item.scenario))) {
190
- selected.push(item);
191
- }
192
- else {
193
- remaining.push(item);
194
- }
195
- }
196
- // Bucket the remainder by inferred type, preserving rank order and first-appearance
197
- // bucket order.
198
- const order = [];
199
- const buckets = new Map();
200
- for (const item of remaining) {
201
- const t = inferType(item.scenario);
202
- if (!buckets.has(t)) {
203
- buckets.set(t, []);
204
- order.push(t);
205
- }
206
- buckets.get(t).push(item);
207
- }
208
- // Round-robin one per non-empty bucket per round until full.
209
- while (selected.length < count && order.some((t) => buckets.get(t).length > 0)) {
210
- for (const t of order) {
211
- if (selected.length >= count)
212
- break;
213
- const bucket = buckets.get(t);
214
- if (bucket.length > 0)
215
- selected.push(bucket.shift());
216
- }
217
- }
218
- return selected;
219
- }
220
166
  export function buildExecutionPlan(scored, maxGen, topN, baseUrl, authHeaderValue, authSchemeSnippet, authTypeValue, seed, endpointCount, isUIOnlyPR, hasFrontendChanges = false, hasTraces = false, externalCoverage = new Set(), relevantExternalTestPaths = [],
221
167
  /**
222
168
  * Whether the diff classified at least one new/modified/removed endpoint.
@@ -473,7 +419,7 @@ ${buildScopeAssessmentSection(topN, maxGen, isUIOnlyPR, isUIOnlyPR ? 100 : hasFr
473
419
 
474
420
  ${_execPlan.render(_ctx)}
475
421
 
476
- ### GENERATE (after completing Steps ${EXEC_STEP_CODE_REVIEW}–${EXEC_STEP_EXECUTE} above) — generate exactly these items in order; add variations to ADDITIONAL instead. If Step ${EXEC_STEP_COVERAGE} converts an item to UPDATE, backfill from ADDITIONAL (priority order in Step ${EXEC_STEP_COVERAGE})
422
+ ### GENERATE (after completing Steps ${EXEC_STEP_CODE_REVIEW}–${EXEC_STEP_EXECUTE} above and registering via Step ${EXEC_STEP_REGISTER}) — the list below is a starting point; \`skyramp_register_test_plan\`'s returned GENERATE list is the final, mandatory one. Generate exactly those items in order; add variations to ADDITIONAL instead. If Step ${EXEC_STEP_COVERAGE} converts an item to UPDATE, backfill from ADDITIONAL (priority order in Step ${EXEC_STEP_COVERAGE})
477
423
 
478
424
  ${isUIOnlyPR
479
425
  ? uiGenerateBlocks ||