@skyramp/mcp 0.3.6 → 0.3.7

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 (89) hide show
  1. package/build/prompts/code-reuse.js +17 -2
  2. package/build/prompts/enhance-assertions/sharedAssertionRules.js +1 -1
  3. package/build/prompts/modularization/ui-test-modularization.js +9 -6
  4. package/build/prompts/pom-aware-code-reuse.js +1 -1
  5. package/build/prompts/shared-helper-policy.js +5 -5
  6. package/build/services/TestGenerationService.js +15 -1
  7. package/build/tools/code-refactor/assertion-state.d.ts +91 -0
  8. package/build/tools/code-refactor/assertion-state.js +375 -0
  9. package/build/tools/code-refactor/codeReuseTool.js +6 -4
  10. package/build/tools/code-refactor/enhanceAssertionsTool.js +73 -18
  11. package/build/tools/code-refactor/retrofit-state.d.ts +53 -0
  12. package/build/tools/code-refactor/retrofit-state.js +162 -0
  13. package/build/tools/code-refactor/reuse-outcome.d.ts +7 -0
  14. package/build/tools/code-refactor/reuse-state.d.ts +9 -0
  15. package/build/tools/code-refactor/reuse-state.js +42 -4
  16. package/build/tools/code-refactor/utils-verify-gates.js +69 -15
  17. package/build/tools/executeSkyrampTestTool.js +19 -14
  18. package/build/tools/submitReportTool.js +81 -8
  19. package/build/types/AssertionOutcome.d.ts +68 -0
  20. package/build/types/AssertionOutcome.js +1 -0
  21. package/build/types/ReuseOutcome.d.ts +16 -0
  22. package/build/types/TestTypes.d.ts +4 -0
  23. package/build/types/TestTypes.js +8 -0
  24. package/build/types/TestbotReport.d.ts +13 -0
  25. package/build/types/index.d.ts +1 -1
  26. package/build/utils/AnalysisStateManager.d.ts +20 -7
  27. package/build/utils/assertion-verify/api-shared-lints.d.ts +5 -0
  28. package/build/utils/assertion-verify/api-shared-lints.js +315 -0
  29. package/build/utils/assertion-verify/contract-lints.d.ts +3 -0
  30. package/build/utils/assertion-verify/contract-lints.js +87 -0
  31. package/build/utils/assertion-verify/format.d.ts +5 -0
  32. package/build/utils/assertion-verify/format.js +65 -0
  33. package/build/utils/assertion-verify/helper-imports.d.ts +6 -0
  34. package/build/utils/assertion-verify/helper-imports.js +178 -0
  35. package/build/utils/assertion-verify/index.d.ts +3 -0
  36. package/build/utils/assertion-verify/index.js +7 -0
  37. package/build/utils/assertion-verify/integration-lints.d.ts +3 -0
  38. package/build/utils/assertion-verify/integration-lints.js +36 -0
  39. package/build/utils/assertion-verify/js-regex-blank.d.ts +1 -0
  40. package/build/utils/assertion-verify/js-regex-blank.js +153 -0
  41. package/build/utils/assertion-verify/lint-types.d.ts +33 -0
  42. package/build/utils/assertion-verify/lint-types.js +57 -0
  43. package/build/utils/assertion-verify/marker.d.ts +27 -0
  44. package/build/utils/assertion-verify/marker.js +61 -0
  45. package/build/utils/assertion-verify/metrics.d.ts +30 -0
  46. package/build/utils/assertion-verify/metrics.js +341 -0
  47. package/build/utils/assertion-verify/python-strip.d.ts +6 -0
  48. package/build/utils/assertion-verify/python-strip.js +75 -0
  49. package/build/utils/assertion-verify/strip-dispatch.d.ts +19 -0
  50. package/build/utils/assertion-verify/strip-dispatch.js +42 -0
  51. package/build/utils/assertion-verify/ui-lints.d.ts +8 -0
  52. package/build/utils/assertion-verify/ui-lints.js +244 -0
  53. package/build/utils/assertion-verify/verify.d.ts +61 -0
  54. package/build/utils/assertion-verify/verify.js +215 -0
  55. package/build/utils/featureFlags.d.ts +12 -2
  56. package/build/utils/featureFlags.js +33 -3
  57. package/build/utils/reportVerification.d.ts +4 -0
  58. package/build/utils/reportVerification.js +32 -4
  59. package/build/utils/utils-verify/allow.d.ts +22 -4
  60. package/build/utils/utils-verify/allow.js +8 -2
  61. package/build/utils/utils-verify/call-sites.d.ts +40 -1
  62. package/build/utils/utils-verify/call-sites.js +196 -30
  63. package/build/utils/utils-verify/importers.d.ts +31 -0
  64. package/build/utils/utils-verify/importers.js +78 -0
  65. package/build/utils/utils-verify/index.d.ts +1 -0
  66. package/build/utils/utils-verify/index.js +1 -0
  67. package/build/utils/utils-verify/language-spec.d.ts +13 -2
  68. package/build/utils/utils-verify/language-spec.js +12 -2
  69. package/build/utils/utils-verify/parse.d.ts +31 -3
  70. package/build/utils/utils-verify/parse.js +190 -9
  71. package/build/utils/utils-verify/retrofit-equivalence.d.ts +43 -0
  72. package/build/utils/utils-verify/retrofit-equivalence.js +218 -0
  73. package/build/utils/utils-verify/stage.d.ts +6 -0
  74. package/build/utils/utils-verify/stage.js +12 -2
  75. package/build/utils/utils-verify/verify.d.ts +54 -4
  76. package/build/utils/utils-verify/verify.js +224 -12
  77. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  78. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-CZfp96qZ.js → codeMirrorModule-LNgEKtdV.js} +1 -1
  79. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-gpLo02E0.js → defaultSettingsView-Bwr1eMKC.js} +135 -135
  80. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Bq1r1URj.js → index.-Id052Lr.js} +1 -1
  81. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  82. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.VEfqi1qN.js → uiMode.BPopbasy.js} +1 -1
  83. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  84. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  85. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  86. package/node_modules/playwright/package.json +1 -1
  87. package/package.json +2 -2
  88. package/build/tools/code-refactor/enhance-state.d.ts +0 -49
  89. package/build/tools/code-refactor/enhance-state.js +0 -109
@@ -55,7 +55,7 @@ export async function uploadFileToRFI(page, fileName, tabName) {
55
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
56
 
57
57
  \`\`\`ts
58
- export async function uploadFileViaChooser(page, fileName) {
58
+ export async function uploadFileViaChooser(page: Page, fileName: string) {
59
59
  await page.waitForTimeout(1500);
60
60
  const responsePromise = page.waitForResponse("**/api/files/upload**");
61
61
  const fileUploadPromise = page.waitForEvent("filechooser");
@@ -69,6 +69,15 @@ export async function uploadFileViaChooser(page, fileName) {
69
69
 
70
70
  Both test files import and call \`uploadFileViaChooser(page, fileName)\`.`;
71
71
  }
72
+ /** Every step that authors an import of the shared module states this: STEP 4b
73
+ * merges, STEP 5b seeds, STEP 5c rewires a sibling — and a run with no siblings
74
+ * jumps straight to 5b, so a rule living in one step only is skippable. The
75
+ * verifier is deterministic, so a skipped rule is a failed run. */
76
+ const pyImportRule = (fileName) => {
77
+ const stem = fileName.replace(/\.[^.]+$/, "");
78
+ return `**Import form (Python)**: if the test's directory contains \`__init__.py\`, a bare \`from ${stem} import …\` will NOT resolve when the customer runs pytest from their repo root — the test dies at collection. Write the fallback pair, which works there AND in the Skyramp executor: \`try:\` / \` from .${stem} import …\` / \`except ImportError:\` / \` from ${stem} import …\` — with \`…\` the names this test actually uses, unchanged between the two arms. Without \`__init__.py\` the bare form alone is correct — do not add the fallback.`;
79
+ };
80
+ const pyImportRuleLine = (fileName) => `- ${pyImportRule(fileName)}`;
72
81
  export function getCodeReusePrompt(testFile, language, framework, scopedPoms, testType) {
73
82
  const lang = language.toLowerCase();
74
83
  if (isPomAwareTarget(lang, framework, testType)) {
@@ -111,6 +120,8 @@ export function getCodeReusePrompt(testFile, language, framework, scopedPoms, te
111
120
 
112
121
  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
122
 
123
+ ${pyImportRuleLine(fileName)}
124
+
114
125
  For each helper function still defined locally in ${testFile} (i.e. not already merged by STEP 4b):
115
126
  1. **CREATE** \`${fileName}\` if it does not exist, with the standard header:
116
127
  \`\`\`${ext}
@@ -148,6 +159,8 @@ This step MOVES helpers that skyramp_modularization already created — it does
148
159
 
149
160
  **Run this step EVEN IF STEP 3 already satisfied ${testFile} and STEPS 4–5b did nothing.** ${inlineRequestRule}
150
161
 
162
+ ${pyImportRuleLine(fileName)} (this applies to the import you add to the SIBLING file too — its directory is what decides the form.)
163
+
151
164
  1. Grep for the other Skyramp-generated test files exactly as in STEP 4 (codegen marker \`${codegenMarker}\`, excluding ${testFile} and \`${fileName}\`).
152
165
  2. In each, find every inline request block whose method+path matches a helper now defined in \`${fileName}\`.
153
166
  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.
@@ -286,7 +299,8 @@ For each helper defined locally in ${testFile}:
286
299
 
287
300
  - **Conservative bias**: a wrong merge breaks both tests; a missed merge leaves a duplicate. When in doubt, do NOT merge.
288
301
  - **No "cleanup"**: the merged body must mirror the original primitive sequence — same waits, same correlation patterns, same order. This is not refactoring.
289
- - **Preserve typing style**: if originals used \`page: any\` (or untyped \`page\`), keep that convention.
302
+ ${pyImportRuleLine(fileName)}
303
+ - **Typed parameters (TypeScript)**: the merged helper types every parameter (\`page: Page\`, values by their type); add \`import type { Page } from '@playwright/test'\` to the utils file if it is missing. Never \`page: any\` or an untyped \`page\` — that fails a repo that type-checks its tests under \`noImplicitAny\`.
290
304
  - **Don't merge with helpers already in \`${fileName}\`**: those are STEP 3's territory. STEP 4b is strictly cross-test merge between sibling Skyramp-generated test files.
291
305
 
292
306
  ---
@@ -336,6 +350,7 @@ ${seedStepBlock}${siblingCallSitesStep}## STEP 6: VERIFY AND VALIDATE
336
350
  8. **REMOVE** any helper functions that are not being used after refactoring
337
351
  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)" : ""}
338
352
  10. **RUN TESTS** to ensure functionality is preserved after refactoring
353
+ 11. **NEVER** drop an assertion or a cross-cutting guard (\`page.on('pageerror'...)\`, error collectors and their final \`expect\`) while moving code — the verify step compares the assertion count against the modularization hand-out and fails on a loss
339
354
 
340
355
  **FINAL REMINDER: DO NOT CREATE HELPER FUNCTIONS FROM SCRATCH**
341
356
  ${finalReminderScope}
@@ -206,7 +206,7 @@ Before editing the given file, you must output a \`<thinking>\` block. The aim o
206
206
  1. Scan the given test file and expected responses based on the test recommendations for the code change tested.
207
207
  2. Classify each response first by its response status type and then assign the applicable assertion rules to the response.
208
208
  1. Success with body (2xx with a response body): all assertion rules below may apply — echo-back of request fields, computed response fields, array / items validation, and chained values across steps.
209
- 2. Success with no body (204, or 202 with empty body): assert the status code only. Also apply chained-values rules if a follow-up step uses this response's ID.
209
+ 2. Success with no body (200/202/204 with an empty body — e.g. logout/cancel/submit action endpoints): assert the status code only. Also apply chained-values rules if a follow-up step uses this response's ID.
210
210
  3. Error response (4xx/5xx with a body): assert every error body field with its exact value plus array / items validation on the \`errors[]\` array (exact length + per-item fields + next index undefined). Status code alone is never sufficient when a body is present — for example, also assert \`errors.0.extensions.code == 'INVALID_PAYLOAD'\` and that \`errors.1\` is undefined.
211
211
  3. For each in-scope response, output one JSON object using the template below. The output is an array — one object per in-scope response.
212
212
  - \`step\`: the HTTP method, path, and response variable name for this request (e.g. \`POST /products → products_POST_response\`).
@@ -41,13 +41,13 @@ ${p.utilsReuse ? "6" : "5"}. **SKIP to Step ${_plan.labels.VERIFY}** for verific
41
41
  - Examples: "create product", "fill form", "verify result"
42
42
  - Must be cohesive - related operations that achieve one goal${p.utilsReuse
43
43
  ? `
44
- - This project consolidates test helpers into a shared utils file, so EVERY logical section that performs a user action (open a form, fill and submit it, add or remove an item, apply an option) becomes a named helper the section does NOT need to repeat. Another scenario's test will call the same helper with its own values.`
44
+ - This project consolidates test helpers into a shared utils file. Extract a user-action section as a helper when another scenario's test would perform it AS-IS with its own values — sign in, open an entity's form or dialog, add or remove an item, apply a filter or option, dismiss a bannereven if it appears once here. Do NOT extract a section that exists only in this scenario's narrative (a specific multi-step data entry, a check sequence over this test's own values) just because the utils file exists: a single-use helper adds code and hides this scenario's locators behind a name, and the shared file then carries it for every later run.`
45
45
  : ""}
46
46
 
47
47
  **Don't extract:**
48
48
  - Code less than 5 lines (unless highly repetitive${p.utilsReuse
49
- ? ", or a cohesive user-action section — those become helpers regardless of length, per B above"
50
- : ""})
49
+ ? ", or a cross-test user action per B above — those become helpers regardless of length"
50
+ : ""})${p.utilsReuse ? "\n- A section only this scenario performs AND that appears once in this file — keep it inline (see B above; if it repeats 2+ times in this file, scenario A still applies and it is extracted)" : ""}
51
51
  - Navigation sequences (\`navbar-\` clicks, \`page.goto\`)`)
52
52
  .step("EXTRACT", "EXTRACT INTO HELPERS", (p) => `**GOLDEN RULE: Copy the original code exactly, only add parameters for values that differ**
53
53
 
@@ -123,8 +123,10 @@ async function createOrderWithItems(...) { }
123
123
 
124
124
  **RIGHT - Good practices:**
125
125
  \`\`\`typescript
126
+ import type { Page } from '@playwright/test';
127
+
126
128
  // CORRECT - Extract with parameter, no new logic
127
- async function addItem(page, name: string) {
129
+ async function addItem(page: Page, name: string) {
128
130
  await page.fill("#name", name); // Replace literal with parameter
129
131
  }
130
132
 
@@ -136,8 +138,8 @@ await addItem(page, "item3");
136
138
  \`\`\``)
137
139
  .step("USE", "CALL HELPERS WITH EXACT SAME VALUES", () => `Use the EXACT same values from the original test when calling helpers.`)
138
140
  .step("VERIFY", "VERIFY - CRITICAL CHECKS", (p) => `**BUGS TO AVOID:**
139
- - [ ] **NO TYPE ANNOTATIONS FOR \`page\` IN FUNCTION SIGNATURES** - Parameters must be untyped (e.g., \`page\`, not \`page: Page\`)
140
- - [ ] **NO RETURN TYPES** - Do not add \`: Promise<void>\` or any return type annotations
141
+ - [ ] **EVERY PARAMETER IS TYPED (TypeScript)** - \`page: Page\` with \`import type { Page } from '@playwright/test'\` once at the top of the file; other parameters by their value type (\`name: string\`, \`quantity: number\`). An untyped parameter fails a repo that type-checks its tests under \`noImplicitAny\`. JavaScript files carry no annotations.
142
+ - [ ] **Return types are optional** - inferred returns are fine under strict mode; if you write one, it is the real type, never \`any\`
141
143
  - [ ] **ALL field mappings verified** - EVERY \`.fill()\` uses the correct parameter. Check field gets quantity parameter, NOT hardcoded
142
144
  - [ ] **No duplicate helpers** - Consolidate similar helpers (e.g., one createOrder vs createSimpleOrder + createOrderWithItems)
143
145
  - [ ] **No nested helpers** - Helpers don't call other helpers unnecessarily
@@ -150,6 +152,7 @@ ${p.utilsReuse
150
152
  - [ ] Every helper name is a canonical intent (\`openEditOrderForm\`, \`applyDiscount\`) with no scenario value or adjective in it
151
153
  - [ ] No helper contains a value assertion (${BROWSER_VALUE_ASSERTIONS}) — each sits in the test body, right after the helper call it verifies
152
154
  - [ ] Every selector inside a helper is byte-identical to the original test
155
+ - [ ] The assertion COUNT is unchanged: every \`expect(\` from the original is still in this file or inside a helper it calls, and the \`page.on('pageerror'...)\` guard and its final \`expect\` over the collected errors are still here — cross-cutting guards are never removed (the verify step counts both)
153
156
  `
154
157
  : ""}
155
158
  **DATA INTEGRITY:**
@@ -559,7 +559,7 @@ For each helper defined locally in ${testFile}:
559
559
 
560
560
  - **Conservative bias**: a wrong merge breaks both tests; a missed merge leaves a duplicate. When in doubt, do NOT merge.
561
561
  - **No "cleanup"**: the merged body must mirror the original primitive sequence — same waits, same correlation patterns, same order. This is not refactoring.
562
- - **Preserve typing style**: if originals used \`page: any\` (or untyped \`page\`), keep that convention.
562
+ - **Typed parameters (TypeScript)**: the merged helper types every parameter (\`page: Page\`, values by their type); add \`import type { Page } from '@playwright/test'\` if the file lacks it. Never \`page: any\` or an untyped \`page\`.
563
563
  - **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.
564
564
 
565
565
  ---
@@ -45,11 +45,11 @@ export const API_HELPERS = {
45
45
  sameStep: "the SAME method+path",
46
46
  liftableLiterals: API_LIFTABLE,
47
47
  structuralNoise: "",
48
- namingRule: "**NAME** each helper by method + resource ONLY: `create_product`, `update_order`, `get_order`, `get_orders_list`. Scenario adjectives in names are FORBIDDEN (`create_discount_test_product`, `update_order_50_discount`) — other tests define the same step, and a shared utils file can hold only one canonical name per step. Scenario data belongs in ARGUMENTS, never in the name.",
48
+ namingRule: "**NAME** each helper by method + resource ONLY, written in the naming convention of the language it is emitted in — Python `create_product`, `update_order`, `get_order`, `get_orders_list`; TypeScript/JavaScript `createProduct`, `updateOrder`, `getOrder`, `getOrdersList`. Match the surrounding test file: snake_case helpers in a camelCase TS suite read as foreign code the repo did not write. Scenario adjectives in names are FORBIDDEN (`create_discount_test_product`, `updateOrder50Discount`) — other tests define the same step, and a shared utils file can hold only one canonical name per step. Scenario data belongs in ARGUMENTS, never in the name.",
49
49
  assertionRule: `**THE ONLY ASSERTION INSIDE A SHARED HELPER IS THE STATUS CODE**, written in the test's own assertion syntax (Python \`assert response.status_code == expected_status\`; TypeScript/JavaScript \`expect(response.status).toBe(expectedStatus)\`), with the expected status a parameter defaulted to this scenario's expected code. EVERY response-body assertion (echo-backs, computed totals, field checks) stays in the test body, applied to the response the helper returns. ${SHARED_HELPER_WHY}`,
50
50
  relocationTarget: "the test body, applied to the response the helper returns",
51
51
  moveRule: "",
52
- namingExample: "E.g. `update_order`, not `update_order_discount` or `update_order_50_discount`.",
52
+ namingExample: "E.g. `update_order` (Python) / `updateOrder` (TypeScript), not `update_order_discount` or `updateOrder50Discount`.",
53
53
  oneHelperRule: (fileName) => `One method+path = one helper in \`${fileName}\`.`,
54
54
  // The API section below carries its own worked example; the generic
55
55
  // Playwright one would teach intent-naming and locator merges to a Python prompt.
@@ -81,7 +81,7 @@ export const BROWSER_HELPERS = {
81
81
  kind: "browser",
82
82
  sameStep: "the SAME action sequence (same Playwright primitives in the same order, selectors identical)",
83
83
  liftableLiterals: BROWSER_LIFTABLE,
84
- structuralNoise: "`waitForTimeout(...)` lines and the `getValue(playwrightRequestN, ...)` value wrapper around a fill are RECORDER NOISE, not structure — two bodies that differ only by them perform the same step; keep the utils version (it carries the waits) and import it",
84
+ structuralNoise: "`waitForTimeout(...)` lines and the `getValue(playwrightRequestN, ...)` value wrapper around a fill are RECORDER NOISE, not structure — two bodies that differ only by them perform the same step, so merge them; keep the body WITHOUT the `waitForTimeout` lines. A fixed sleep is not a structural wait: it is the one line of the pair that carries no meaning, and in a shared helper every importer pays it on every run. Where a step genuinely needs to wait, wait on the element the next line touches (`toBeVisible`, `toBeEnabled`)",
85
85
  namingRule: "**NAME** each helper by the user INTENT it performs, shared across tests: `openEditOrderForm`, `applyDiscount`, `submitProductForm`, `removeOrderItem`. Scenario values or adjectives in names are FORBIDDEN (`applyFiftyPercentDiscount`, `editOrder1Discount`, `createExpensiveProduct`) — another test performs the same action with different data, and a shared utils file holds ONE canonical name per action. Scenario data belongs in ARGUMENTS, never in the name.",
86
86
  assertionRule: `**A SHARED HELPER CONTAINS ACTIONS AND STRUCTURAL WAITS ONLY**: clicks, fills, selects, ${BROWSER_STRUCTURAL_WAITS}. EVERY VALUE ASSERTION (${BROWSER_VALUE_ASSERTIONS}) stays in the test body, placed immediately after the helper call it verifies, with its selector verbatim and its expected value unchanged. ${SHARED_HELPER_WHY}`,
87
87
  relocationTarget: "the test body, immediately after the helper call, selector verbatim",
@@ -100,7 +100,7 @@ export async function applyDiscount(page) {
100
100
 
101
101
  \`${testFile}\` (current) has:
102
102
  \`\`\`ts
103
- export async function applyTwentyPercentDiscount(page) {
103
+ export async function applyTwentyPercentDiscount(page: Page) {
104
104
  await page.getByTestId("edit-order-input-discount").fill("20");
105
105
  await page.getByTestId("edit-order-submit-btn").click();
106
106
  }
@@ -109,7 +109,7 @@ export async function applyTwentyPercentDiscount(page) {
109
109
  These ARE near-duplicates: same primitive sequence, selectors byte-identical, the only difference is the fill literal. Correct merge (name = intent, the literal becomes a parameter passed exactly as it was written — no conversion or other logic added, selectors untouched):
110
110
 
111
111
  \`\`\`ts
112
- export async function applyDiscount(page, discountPercent) {
112
+ export async function applyDiscount(page: Page, discountPercent: string) {
113
113
  await page.getByTestId("edit-order-input-discount").fill(discountPercent);
114
114
  await page.getByTestId("edit-order-submit-btn").click();
115
115
  }
@@ -7,13 +7,14 @@ import { extractPathname, resolveQueryParamsForPath, contributesNothing, } from
7
7
  import { getPathParameterValidationError, OUTPUT_DIR_FIELD_NAME, PATH_PARAMS_FIELD_NAME, QUERY_PARAMS_FIELD_NAME, FORM_PARAMS_FIELD_NAME, validateParams, validatePath, validateRequestData, } from "../utils/utils.js";
8
8
  import { getEntryPoint } from "../utils/telemetry.js";
9
9
  import { getLanguageSteps } from "../utils/language-helper.js";
10
- import { TestType } from "../types/TestTypes.js";
10
+ import { ASSERTION_ENHANCEABLE_TEST_TYPES, TestType, MOCK_TYPE, } from "../types/TestTypes.js";
11
11
  import { logger } from "../utils/logger.js";
12
12
  import { normalizeLanguageParams } from "../utils/normalizeParams.js";
13
13
  import { stageGeneratedPaths, resolveOutputDir } from "../utils/gitStaging.js";
14
14
  import { isInsideExecutorWorkDir, executorWorkDirRefusal, generationTargets, } from "../utils/executorWorkDir.js";
15
15
  import { getTestsRepoDir } from "../utils/AnalysisStateManager.js";
16
16
  import { recordReuseHandOff } from "../tools/code-refactor/reuse-state.js";
17
+ import { recordAssertionBaseline } from "../tools/code-refactor/assertion-state.js";
17
18
  import { isModularizeFirstTarget } from "../utils/reuseRouting.js";
18
19
  /**
19
20
  * Derive the framework actually used for generation from the (normalized)
@@ -106,7 +107,20 @@ export class TestGenerationService {
106
107
  // The files this call wrote, as codegen reports them — the reuse hand-off is
107
108
  // recorded per FILE (SKYR-4220).
108
109
  const written = generatedFilesFrom(result);
110
+ // Seed the assertion-enhancement baseline SERVER-SIDE at generation time
111
+ // (same pattern as recordReuseHandOff below): the execute-time
112
+ // verification gate arms even if the agent never calls
113
+ // skyramp_enhance_assertions at all — the last prompt-mediated entry
114
+ // point. Best-effort; no run state means the gate stays open as before.
109
115
  const testType = this.getTestType();
116
+ if (params.enhanceAssertions &&
117
+ testType !== MOCK_TYPE &&
118
+ ASSERTION_ENHANCEABLE_TEST_TYPES.includes(testType) &&
119
+ (testType !== TestType.CONTRACT || params.providerMode !== false)) {
120
+ for (const file of written) {
121
+ await recordAssertionBaseline(file, testType, "generation");
122
+ }
123
+ }
110
124
  const languageSteps = getLanguageSteps({
111
125
  language: params.language || "",
112
126
  testType: testType,
@@ -0,0 +1,91 @@
1
+ import { type AssertionBaseline, type AssertionEnhanceType, type AssertionVerifyResult } from "../../utils/assertion-verify/index.js";
2
+ import type { AssertionOutcome, AssertionRecord } from "../../types/AssertionOutcome.js";
3
+ import type { TestType } from "../../types/TestTypes.js";
4
+ import type { RepoCheckout } from "../../utils/reportVerification.js";
5
+ /**
6
+ * Persistence for the assertion-enhancement verification loop, keyed by the
7
+ * spec's CANONICAL absolute path in `UnifiedAnalysisState.assertionOutcomes`.
8
+ * Path keying (not basename, unlike reuseOutcomes — nothing here needs to
9
+ * match report rows) removes the multi-repo basename-collision class outright,
10
+ * and canonicalization closes the alias bypass: `dir//x.spec.ts` and
11
+ * `dir/./x.spec.ts` must hit the same record as `dir/x.spec.ts`.
12
+ *
13
+ * Every write is best-effort: verification must never be able to fail an
14
+ * enhancement that otherwise succeeded, so a missing/unreadable state file
15
+ * degrades to stateless checks and open gates — always with a warning.
16
+ */
17
+ /** Canonical identity of a test file — all keying and ownership comparisons
18
+ * go through this so a non-normalized agent-supplied path cannot fork or
19
+ * bypass a record. realpath also collapses symlinked directories; a path
20
+ * that does not (yet) exist falls back to plain resolution. */
21
+ export declare function canonicalTestPath(testFile: string): string;
22
+ /** A record only counts for `testFile` when its stored path canonicalizes to
23
+ * the same spec — the single home for the ownership invariant (previously
24
+ * copied in baselineFrom, recordAssertionVerdict, and the enhance tool). */
25
+ export declare function trustedRecord(record: AssertionRecord | undefined, testFile: string): AssertionRecord | undefined;
26
+ /** One spec's record, or undefined when there is no run state / no record. */
27
+ export declare function readAssertionRecord(testFile: string, explicitStateFile?: string): Promise<AssertionRecord | undefined>;
28
+ /**
29
+ * Snapshot the file at instruction hand-out time — the baseline verify measures
30
+ * against. Keep-first rule: while no Passed verdict exists for the same file and
31
+ * enhanceType, a repeat hand-out does NOT re-baseline (re-calling the tool after
32
+ * half-editing must not shrink the required delta). A hand-out after a Passed
33
+ * verdict, or with a different enhanceType, starts a new enhancement round.
34
+ */
35
+ export declare function recordAssertionBaseline(testFile: string, testType: TestType, enhanceType: AssertionEnhanceType, explicitStateFile?: string): Promise<void>;
36
+ /** The baseline verify measures against, from a record; undefined when the
37
+ * record carries no snapshot or belongs to a different canonical path. */
38
+ export declare function baselineFrom(record: AssertionRecord | undefined, testFile: string): AssertionBaseline | undefined;
39
+ /** Record what one verify pass established. The verdict is written to the
40
+ * state file the record actually LIVES in (not blindly to the explicit path —
41
+ * a mismatched `stateFile` would otherwise split verdict from baseline).
42
+ * Never writes onto a record owned by a different canonical path, and never
43
+ * fails silently. */
44
+ export declare function recordAssertionVerdict(testFile: string, result: AssertionVerifyResult, explicitStateFile?: string): Promise<void>;
45
+ /**
46
+ * Deterministic execute-time verification: skyramp_execute_test calls this
47
+ * itself, so the check runs whether or not the agent ever called `verify: true`
48
+ * — a prose instruction can be ignored, a server-side call cannot. Returns the
49
+ * feedback text when the assertions are insufficient (the caller returns it
50
+ * instead of executing), or undefined to proceed.
51
+ *
52
+ * Self-clearing: the checks run on the file's CURRENT content, so fixing the
53
+ * assertions and re-executing passes — no separate verify call is required.
54
+ * Skipped once a verdict has PASSED: sanctioned later rewrites (a `.raw.bak`
55
+ * restore during execution fix-up) would otherwise false-fail the differential
56
+ * gates. Modularization needs no special handling: the shared-helper policy
57
+ * keeps every value assertion in the test body, so the baseline comparison
58
+ * stays valid across it. Fails open on no baseline and unreadable state/file.
59
+ */
60
+ export declare function assertionFeedbackForExecution(testFile: string, explicitStateFile?: string): Promise<string | undefined>;
61
+ /**
62
+ * Count one execution of this spec (called by skyramp_execute_test after its
63
+ * gates pass, best-effort). Creates a minimal record when none exists so an
64
+ * executed-but-never-enhanced spec is still visible to the report cross-check.
65
+ */
66
+ export declare function recordAssertionExecution(testFile: string, testType: TestType, explicitStateFile?: string): Promise<void>;
67
+ /** A repo checkout as listRepoCheckouts returns it — the canonical type from
68
+ * reportVerification, re-exported (a local re-declaration had drifted:
69
+ * `root` weakened to optional, forcing a redundant guard at each use). */
70
+ export type RepoCheckoutRef = RepoCheckout;
71
+ /** The record for a report row, matched by the canonical-path keys' basenames
72
+ * and then GUARDED: the record's testType must match the row's, and when the
73
+ * run's checkouts are known, the record's path must live in the row's repo
74
+ * (primary when the row names none). Without the guards, a basename shared
75
+ * across repos published one spec's proof-of-work under another's name — and
76
+ * the victim was exactly the never-enhanced spec the field exists to expose.
77
+ * Ambiguity after filtering still fails open: wrong attribution is worse
78
+ * than omission. */
79
+ export declare function findAssertionRecordByFileName(outcomes: Record<string, AssertionRecord>, row: {
80
+ fileName: string;
81
+ testType?: string;
82
+ repository?: string;
83
+ }, checkouts: RepoCheckoutRef[]): AssertionRecord | undefined;
84
+ /**
85
+ * Report-facing proof-of-work summary re-derived from the spec as delivered
86
+ * NOW — final metrics come from the file, not from verify-time state, so
87
+ * post-verify edits cannot publish stale counts. Always returns the status
88
+ * fields (verification, declinedReason, executionCount — they come from the
89
+ * record); the metric fields are omitted when the file cannot supply them.
90
+ */
91
+ export declare function rederiveAssertionOutcome(record: AssertionRecord): Promise<AssertionOutcome>;