@skyramp/mcp 0.2.150-rc.mntnc → 0.2.150-rc.ui.mntnc
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.
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +15 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +56 -3
- package/build/prompts/test-maintenance/driftAnalysisSections.js +6 -20
- package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +12 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +60 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +37 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +226 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +76 -2
- package/build/prompts/test-recommendation/scopeAssessment.js +12 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +91 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +16 -1
- package/build/prompts/testbot/testbot-prompts.js +94 -9
- package/build/prompts/testbot/testbot-prompts.test.js +95 -1
- package/build/resources/testbotResource.js +2 -2
- package/build/services/TestDiscoveryService.d.ts +12 -9
- package/build/services/TestDiscoveryService.js +125 -51
- package/build/services/TestDiscoveryService.test.js +235 -15
- package/build/services/TestExecutionService.d.ts +1 -1
- package/build/tools/executeSkyrampTestTool.js +9 -3
- package/build/tools/submitReportTool.d.ts +10 -0
- package/build/tools/submitReportTool.js +33 -5
- package/build/tools/submitReportTool.test.js +32 -0
- package/build/tools/test-management/actionsTool.js +49 -32
- package/build/tools/test-management/analyzeChangesTool.d.ts +1 -0
- package/build/tools/test-management/analyzeChangesTool.js +36 -20
- package/build/tools/test-management/analyzeChangesTool.test.js +17 -7
- package/build/tools/test-management/analyzeTestHealthTool.js +191 -14
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeTestHealthTool.test.js +468 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +39 -29
- package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
- package/build/tools/workspace/serviceUpsert.d.ts +12 -0
- package/build/tools/workspace/serviceUpsert.js +23 -0
- package/build/tools/workspace/serviceUpsert.test.d.ts +1 -0
- package/build/tools/workspace/serviceUpsert.test.js +50 -0
- package/build/types/TestAnalysis.d.ts +3 -0
- package/build/utils/AnalysisStateManager.d.ts +64 -22
- package/build/utils/AnalysisStateManager.js +134 -2
- package/build/utils/AnalysisStateManager.test.js +98 -0
- package/build/utils/docker.test.js +1 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +17 -17
- package/build/workspace/workspace.js +12 -9
- package/build/workspace/workspace.test.js +67 -8
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +31 -13
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +75 -6
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +79 -19
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/aliasRemapper.js +84 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +15 -4
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-CPZKmjFR.js → codeMirrorModule-D0BjbCb7.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +193 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B5kqh2EV.js → codeMirrorModule-Cqp3cwEJ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-CZ9npQ3N.js → defaultSettingsView-CYf9adZh.js} +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BCnxj-_b.js → index.B_7ywgmr.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.1Ym0Ivn8.js → uiMode.u4_8VnCV.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +129 -43
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/aliasRemapper.ts +119 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +33 -10
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/node_modules/playwright/LICENSE +0 -202
- package/node_modules/playwright/NOTICE +0 -5
- package/node_modules/playwright/README.md +0 -168
- package/node_modules/playwright/node_modules/playwright-core/LICENSE +0 -202
- package/node_modules/playwright/node_modules/playwright-core/NOTICE +0 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-CsgPR2wy.js +0 -193
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type UiDriftParams = {
|
|
2
|
+
tests: Array<{
|
|
3
|
+
testFile: string;
|
|
4
|
+
}>;
|
|
5
|
+
changedFrontendFiles: string[];
|
|
6
|
+
blueprintCaptured?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Build the drift analysis prompt.
|
|
10
|
+
* - apiTests: tests to assess for API endpoint drift (always included unless empty and ui is provided).
|
|
11
|
+
* - ui: when provided, appends a UI drift section for component/browser tests.
|
|
12
|
+
* Omit when no frontend files changed or no UI tests exist.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildDriftAnalysisPrompt(stateFile: string | undefined, apiTests: Array<{
|
|
2
15
|
testFile: string;
|
|
3
16
|
source?: string;
|
|
4
|
-
}
|
|
17
|
+
}>, ui?: UiDriftParams): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildActionDecisionTree, buildCheckAdditiveFields, buildCheckEndpointExistence, buildCheckResponseShape, buildCheckAuthAndAuthorization, buildCheckBehavioralContract, buildCheckAssignAction, buildDriftOutputChecklist, } from "./driftAnalysisSections.js";
|
|
2
|
+
import { buildUiActionDecisionTree, buildUiCheckRouteExistence, buildUiCheckSelectors, buildUiCheckPageObjects, buildUiCheckBehavioralChanges, buildUiCheckAssignAction, buildUiCheckExportedSymbols, buildUiDriftOutputChecklist, } from "./uiDriftAnalysisSections.js";
|
|
2
3
|
import { PromptPlan } from "../test-recommendation/promptPlan.js";
|
|
3
|
-
const
|
|
4
|
+
const _apiPlan = new PromptPlan()
|
|
4
5
|
.addPhase("maintenance", "Test Maintenance Assessment", {
|
|
5
6
|
headerLevel: "##",
|
|
6
7
|
stepFormat: "hash",
|
|
@@ -14,6 +15,58 @@ const _plan = new PromptPlan()
|
|
|
14
15
|
.subStep("ASSIGN_ACTION", "Assign action", () => buildCheckAssignAction())
|
|
15
16
|
.step("CALL_TOOL", "Submit recommendations", (p) => buildDriftOutputChecklist(p.stateFile, p.existingTests))
|
|
16
17
|
.done();
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const _uiPlan = new PromptPlan()
|
|
19
|
+
.addPhase("ui_maintenance", "UI Test Drift Assessment", {
|
|
20
|
+
headerLevel: "##",
|
|
21
|
+
stepFormat: "hash",
|
|
22
|
+
})
|
|
23
|
+
.step("UI_SYMBOL_PRESCAN", "Exported symbol pre-scan — find additional impacted tests via grep", (p) => buildUiCheckExportedSymbols(p.changedFrontendFiles))
|
|
24
|
+
.step("UI_ASSESS", "UI Action Decision Tree — assess each UI test against the diff and blueprints", () => buildUiActionDecisionTree())
|
|
25
|
+
.subStep("UI_ROUTE_EXISTENCE", "Route existence", () => buildUiCheckRouteExistence())
|
|
26
|
+
.subStep("UI_SELECTORS", "Selector validity", () => buildUiCheckSelectors())
|
|
27
|
+
.subStep("UI_PAGE_OBJECTS", "Page object/fixture existence", () => buildUiCheckPageObjects())
|
|
28
|
+
.subStep("UI_BEHAVIOR", "Component behavior changes", () => buildUiCheckBehavioralChanges())
|
|
29
|
+
.subStep("UI_ASSIGN_ACTION", "Assign action", () => buildUiCheckAssignAction())
|
|
30
|
+
.step("UI_CALL_TOOL", "Submit recommendations", (p) => buildUiDriftOutputChecklist(p.stateFile, p.tests))
|
|
31
|
+
.done();
|
|
32
|
+
/**
|
|
33
|
+
* Build the drift analysis prompt.
|
|
34
|
+
* - apiTests: tests to assess for API endpoint drift (always included unless empty and ui is provided).
|
|
35
|
+
* - ui: when provided, appends a UI drift section for component/browser tests.
|
|
36
|
+
* Omit when no frontend files changed or no UI tests exist.
|
|
37
|
+
*/
|
|
38
|
+
export function buildDriftAnalysisPrompt(stateFile, apiTests, ui) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
// Include API drift when there are API tests, or when UI drift is not running
|
|
41
|
+
// (ensures skyramp_actions is always reachable even if apiTests is empty).
|
|
42
|
+
if (apiTests.length > 0 || !ui) {
|
|
43
|
+
parts.push(`<drift_analysis_rules>\n${_apiPlan.render({ stateFile, existingTests: apiTests })}\n</drift_analysis_rules>`);
|
|
44
|
+
}
|
|
45
|
+
if (ui) {
|
|
46
|
+
const signalNote = ui.blueprintCaptured
|
|
47
|
+
? `blueprintCaptured=true — use \`browser_blueprint_diff\` for tests that navigate pages.`
|
|
48
|
+
: `blueprintCaptured=false — use source diff only. Read each test to determine what it exercises.`;
|
|
49
|
+
const contextHeader = `<ui_analysis_context>
|
|
50
|
+
**UI Test Drift Analysis Context**
|
|
51
|
+
|
|
52
|
+
**Signal:** ${signalNote}
|
|
53
|
+
|
|
54
|
+
**Changed Frontend Files:**
|
|
55
|
+
${ui.changedFrontendFiles.length > 0
|
|
56
|
+
? ui.changedFrontendFiles.map(f => `- ${f}`).join("\n")
|
|
57
|
+
: "(none)"}
|
|
58
|
+
|
|
59
|
+
**Tests to Assess:**
|
|
60
|
+
${ui.tests.length > 0
|
|
61
|
+
? ui.tests.map(t => `- ${t.testFile}`).join("\n")
|
|
62
|
+
: "(none)"}
|
|
63
|
+
|
|
64
|
+
**Instructions:**
|
|
65
|
+
For each test above, read it to determine its type, then apply the scope gate from the decision tree. Assign the appropriate action (DELETE, REGENERATE, UPDATE, VERIFY, IGNORE).
|
|
66
|
+
</ui_analysis_context>
|
|
67
|
+
|
|
68
|
+
`;
|
|
69
|
+
parts.push(contextHeader + `<ui_drift_analysis_rules>\n${_uiPlan.render({ stateFile, tests: ui.tests, changedFrontendFiles: ui.changedFrontendFiles, blueprintCaptured: ui.blueprintCaptured })}\n</ui_drift_analysis_rules>`);
|
|
70
|
+
}
|
|
71
|
+
return parts.join("\n\n");
|
|
19
72
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Modular section builders for the Drift Analysis prompt,
|
|
2
|
+
* Modular section builders for the API Drift Analysis prompt,
|
|
3
3
|
* mirroring the recommendationSections.ts pattern.
|
|
4
4
|
*/
|
|
5
5
|
import { AUTH_MIDDLEWARE_PATTERNS_STR } from "../../utils/workspaceAuth.js";
|
|
6
|
+
import { buildApiAssignAction } from "./driftAnalysisShared.js";
|
|
6
7
|
// TODO: Replace the open-ended diff-line categories below with a two-tier structure:
|
|
7
8
|
// Tier 1 — mechanical patterns (specific token + file-path criteria, zero judgment needed).
|
|
8
9
|
// Tier 2 — residual open-ended scan for anything observable not covered by tier 1.
|
|
@@ -177,24 +178,9 @@ Diff signals and actions:
|
|
|
177
178
|
**Reachability for behavioral changes:** The service/interface scope gate still applies — if the test targets a completely different service or protocol, IGNORE. However, do NOT use the absence of a route or output layer file in the diff as grounds for IGNORE. Behavioral changes (new error branches, new validation, status-code changes) are observable from any test calling the same endpoint, even when the logic lives in an internal handler, middleware, or utility file rather than the route file itself.`;
|
|
178
179
|
}
|
|
179
180
|
export function buildCheckAssignAction() {
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
**Every action requires a specific rationale — including IGNORE:**
|
|
183
|
-
- UPDATE / REGENERATE / DELETE: quote the specific diff line that triggered it.
|
|
184
|
-
- VERIFY: name the uncertain element (e.g. "model-only change, cannot confirm field is exposed without checking the output layer").
|
|
185
|
-
- IGNORE: name the changed file(s) from the diff AND explain why they cannot reach this test's endpoint (e.g. "diff only touches \`auth/session-service.js\` — this test targets \`/api/v1/orders\` which has no session dependency"). You must reference at least one specific changed file from the diff. Generic "unrelated endpoint" or "service boundary" without naming the changed file is not sufficient.
|
|
186
|
-
|
|
187
|
-
- If the Additive Fields check flagged a new field with output layer signal confirmed in the diff → action is UPDATE. If the Additive Fields check returned VERIFY (model-only signal, no output layer change) → action remains VERIFY.
|
|
181
|
+
return buildApiAssignAction(`- If the Additive Fields check flagged a new field with output layer signal confirmed in the diff → action is UPDATE. If the Additive Fields check returned VERIFY (model-only signal, no output layer change) → action remains VERIFY.
|
|
188
182
|
- **Scope gate:** If the changed code is clearly not reachable through the service or base URL this test targets, assign IGNORE.
|
|
189
|
-
- **When uncertain, use VERIFY not IGNORE:** If the diff touches a model or migration and this test's endpoint reads from that model, assign VERIFY — you cannot confirm from the diff alone whether the field is exposed
|
|
190
|
-
- **Pre-commit verification — confirm all three before finalizing UPDATE/REGENERATE/DELETE:**
|
|
191
|
-
1. You can quote a specific diff line this test's endpoint observes that triggered the action.
|
|
192
|
-
2. The changed code is reachable through this test's service and base URL.
|
|
193
|
-
3. For REGENERATE: every assertion in the file is invalid, not just some — if you can patch N paths, it is UPDATE.
|
|
194
|
-
If any check fails, downgrade to VERIFY or IGNORE.
|
|
195
|
-
- **For user-written (external) tests** marked \`[external]\` in the test list:
|
|
196
|
-
- UPDATE is permitted — targeted edits only (fix renamed URL, add assertion for new field, add a new test case for a new scenario). Match the style of the existing file: use the same test framework, assertion helpers, and request pattern already present.
|
|
197
|
-
- REGENERATE and DELETE are **not permitted** — assign those actions in your recommendations but \`skyramp_actions\` will surface them as report-only findings for the developer to act on. Do NOT attempt to rewrite or delete a user-authored test file.`;
|
|
183
|
+
- **When uncertain, use VERIFY not IGNORE:** If the diff touches a model or migration and this test's endpoint reads from that model, assign VERIFY — you cannot confirm from the diff alone whether the field is exposed.`);
|
|
198
184
|
}
|
|
199
185
|
export function buildCheckAdditiveFields() {
|
|
200
186
|
return `Even if existing assertions still pass, new response fields on a covered endpoint need a new assertion. A field being optional or nullable does not make it IGNORE — it still needs to be covered.
|
|
@@ -225,7 +211,7 @@ export function buildDriftOutputChecklist(stateFile, existingTests) {
|
|
|
225
211
|
? `\nNo existing tests were found. Call \`skyramp_actions\` with \`recommendations: []\` and explain in your report why no maintenance was needed (e.g. "no existing tests cover the changed endpoints" or "PR adds a new endpoint with no prior coverage").\n`
|
|
226
212
|
: "";
|
|
227
213
|
const symbolDiscoveryStep = hasTests
|
|
228
|
-
? `**Symbol import discovery (before assessing tests):** If the diff adds or modifies class, function, or type definitions, grep the repository for test files that import those symbol names. Add any matched test files not already in the list below to your assessment — these files may need updating even if their path doesn't match the changed resource name. Include all discovered files in the \`recommendations[]\` passed to \`skyramp_actions
|
|
214
|
+
? `**Symbol import discovery (before assessing tests):** If the diff adds or modifies class, function, or type definitions, grep the repository for test files that import those symbol names. Add any matched test files not already in the list below to your assessment — these files may need updating even if their path doesn't match the changed resource name. Include all discovered files in the \`recommendations[]\` passed to \`skyramp_actions\`.`
|
|
229
215
|
: "";
|
|
230
216
|
const existingTestSection = `**Existing tests (${existingTests?.length ?? 0} total) — assess ALL of the following:**
|
|
231
217
|
${testList}
|
|
@@ -257,7 +243,7 @@ Test: {testFile}
|
|
|
257
243
|
Action: DELETE
|
|
258
244
|
Rationale: DELETE because {quoted diff signal}; all covered endpoints removed
|
|
259
245
|
\`\`\`
|
|
260
|
-
Include ALL actions — IGNORE, VERIFY, UPDATE, REGENERATE, DELETE — in the \`recommendations[]\` passed to \`skyramp_actions\`.
|
|
246
|
+
Include ALL actions — IGNORE, VERIFY, UPDATE, REGENERATE, DELETE — in the \`recommendations[]\` passed to \`skyramp_actions\`.
|
|
261
247
|
Be concise — one line per IGNORE/VERIFY entry.`;
|
|
262
248
|
const sections = [symbolDiscoveryStep, existingTestSection, finalStep].filter(Boolean);
|
|
263
249
|
return `<output_format>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shared drift prompt sections (assign-action, external test policy) for API and UI prompts. */
|
|
2
|
+
export declare function buildSharedExternalTestPolicy(): string;
|
|
3
|
+
/**
|
|
4
|
+
* Assign-action section for the API drift prompt.
|
|
5
|
+
* @param domainNotes API-specific bullets (additive-fields guidance, scope-gate note).
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildApiAssignAction(domainNotes: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Assign-action section for the UI drift prompt.
|
|
10
|
+
* @param domainNotes UI-specific bullets (component/widget matching notes).
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildUiAssignAction(domainNotes: string): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Shared drift prompt sections (assign-action, external test policy) for API and UI prompts. */
|
|
2
|
+
export function buildSharedExternalTestPolicy() {
|
|
3
|
+
return `**For user-written (external) tests** marked \`[external]\` in the test list:
|
|
4
|
+
- UPDATE is permitted — pass the exact file that needs editing as \`testFile\`. This can be the test file itself, a snapshot file, a page object, or a fixture. \`skyramp_actions\` will edit that file using \`updateInstructions\`; application source files are automatically rejected.
|
|
5
|
+
- REGENERATE and DELETE: assign these actions when the evidence clearly warrants them (page gone → DELETE, component structure fundamentally changed → REGENERATE). \`skyramp_actions\` handles execution restrictions at apply time — assign the action that the evidence supports. Leave user-authored test files unchanged; edits go only through \`skyramp_actions\` with explicit \`updateInstructions\`. VERIFY means "uncertain, need more context" — assign DELETE or REGENERATE when you have confirmed the failure, and VERIFY only when you genuinely cannot determine impact without more information.`;
|
|
6
|
+
}
|
|
7
|
+
/** Shared boilerplate: VERIFY rule, pre-commit structure, and external policy. */
|
|
8
|
+
function buildAssignActionCore(opts) {
|
|
9
|
+
return `Based on the above checks, choose the action (IGNORE / VERIFY / UPDATE / REGENERATE / DELETE) and provide a 1-2 sentence rationale.
|
|
10
|
+
|
|
11
|
+
**Every action requires a specific rationale — including IGNORE:**
|
|
12
|
+
- UPDATE / REGENERATE / DELETE: ${opts.triggerEvidence}.
|
|
13
|
+
- VERIFY: name the uncertain element (e.g. ${opts.verifyExample}).
|
|
14
|
+
- IGNORE: ${opts.ignoreGuidance}.
|
|
15
|
+
|
|
16
|
+
${opts.domainNotes}
|
|
17
|
+
|
|
18
|
+
**When uncertain, use VERIFY not IGNORE:** If the diff touches code this test could exercise but you cannot confirm impact without reading more context, assign VERIFY — do not default to IGNORE.
|
|
19
|
+
|
|
20
|
+
**Pre-commit verification — confirm before finalizing UPDATE/REGENERATE/DELETE:**
|
|
21
|
+
1. You can quote a specific diff line${opts.blueprintSuffix} this test observes that triggered the action.
|
|
22
|
+
2. ${opts.reachabilityCheck}
|
|
23
|
+
3. ${opts.regenerateCheck}
|
|
24
|
+
4. ${opts.deleteCheck}
|
|
25
|
+
If any check fails, downgrade to VERIFY or IGNORE.
|
|
26
|
+
|
|
27
|
+
${buildSharedExternalTestPolicy()}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Assign-action section for the API drift prompt.
|
|
31
|
+
* @param domainNotes API-specific bullets (additive-fields guidance, scope-gate note).
|
|
32
|
+
*/
|
|
33
|
+
export function buildApiAssignAction(domainNotes) {
|
|
34
|
+
return buildAssignActionCore({
|
|
35
|
+
triggerEvidence: "quote the specific diff line that triggered it",
|
|
36
|
+
ignoreGuidance: `name the changed file(s) from the diff AND explain why they cannot reach this test's endpoint (e.g. "diff only touches \`auth/session-service.js\` — this test targets \`/api/v1/orders\` which has no session dependency"). You must reference at least one specific changed file. Generic "unrelated endpoint" or "service boundary" without naming the changed file is not sufficient`,
|
|
37
|
+
verifyExample: `"model-only change, cannot confirm field is exposed without checking the output layer"`,
|
|
38
|
+
blueprintSuffix: "",
|
|
39
|
+
reachabilityCheck: "The changed code is reachable through this test's service and base URL.",
|
|
40
|
+
regenerateCheck: "For REGENERATE: every assertion in the file is invalid, not just some — if you can patch N paths, it is UPDATE.",
|
|
41
|
+
deleteCheck: "For DELETE: all covered endpoints no longer exist.",
|
|
42
|
+
domainNotes,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Assign-action section for the UI drift prompt.
|
|
47
|
+
* @param domainNotes UI-specific bullets (component/widget matching notes).
|
|
48
|
+
*/
|
|
49
|
+
export function buildUiAssignAction(domainNotes) {
|
|
50
|
+
return buildAssignActionCore({
|
|
51
|
+
triggerEvidence: "quote the specific diff line and/or blueprint element that triggered it",
|
|
52
|
+
ignoreGuidance: `name the specific route or component and why the diff cannot reach it (e.g. "diff only touches \`/api/v1/users\` — this test targets \`/dashboard\`")`,
|
|
53
|
+
verifyExample: `"component is imported but not directly rendered — cannot confirm impact without reading the parent"`,
|
|
54
|
+
blueprintSuffix: " or blueprint element",
|
|
55
|
+
reachabilityCheck: "The changed code is reachable through this test's routes (browser tests) or a render/mount call in the test that references a changed component (component/widget tests).",
|
|
56
|
+
regenerateCheck: "For REGENERATE: the interaction model changed fundamentally — the test's action sequence has no migration path. If selectors changed but the flow (navigate → interact → assert) can still be expressed with targeted edits, it is UPDATE regardless of how many selectors changed.",
|
|
57
|
+
deleteCheck: "For DELETE: the route is completely gone (browser tests) or the rendered component was deleted (component/widget tests).",
|
|
58
|
+
domainNotes,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the UI Action Decision Tree section.
|
|
3
|
+
* This is the main entry point for UI drift analysis.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildUiActionDecisionTree(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Pre-scan step: agent greps changed files' exported symbols across the test
|
|
8
|
+
* directory to catch tests that server-side discovery missed — aliased imports,
|
|
9
|
+
* indirect renders, or any test the blueprint diff doesn't directly reference.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildUiCheckExportedSymbols(changedFrontendFiles?: string[]): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check if routes targeted by the UI test still exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildUiCheckRouteExistence(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Check if selectors used by the UI test still exist in the blueprints.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildUiCheckSelectors(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Check if page objects/fixtures referenced by the test still exist.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildUiCheckPageObjects(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Check for component behavior changes that affect assertions.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildUiCheckBehavioralChanges(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Assign the final UI drift action.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildUiCheckAssignAction(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Build the UI drift output checklist.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildUiDriftOutputChecklist(stateFile?: string, tests?: Array<{
|
|
36
|
+
testFile: string;
|
|
37
|
+
}>): string;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Drift Analysis prompt sections for Component 2.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the driftAnalysisSections.ts pattern but for UI-specific drift detection.
|
|
5
|
+
* These sections are included whenever `uiContext.changedFrontendFiles` is non-empty —
|
|
6
|
+
* both browser tests (Playwright/Cypress/Selenium) and component/widget tests
|
|
7
|
+
* (RTL/Flutter widget/vue-test-utils/angular-testbed) go through the same gate.
|
|
8
|
+
* Component tests always use source-only analysis regardless of blueprintCaptured.
|
|
9
|
+
*/
|
|
10
|
+
import { buildUiAssignAction } from "./driftAnalysisShared.js";
|
|
11
|
+
/**
|
|
12
|
+
* Build the UI Action Decision Tree section.
|
|
13
|
+
* This is the main entry point for UI drift analysis.
|
|
14
|
+
*/
|
|
15
|
+
export function buildUiActionDecisionTree() {
|
|
16
|
+
return `<ui_decision_rules>
|
|
17
|
+
For each UI test listed in the \`<ui_analysis_context>\` above, assess whether the test is still valid given the diff (and blueprint diff when blueprints were captured). The diff may have changed:
|
|
18
|
+
- Element selectors (CSS class changes, component restructuring)
|
|
19
|
+
- Page structure (new containers, removed sections)
|
|
20
|
+
- Route paths (URL changes)
|
|
21
|
+
- Component behavior (state changes, visibility conditions)
|
|
22
|
+
|
|
23
|
+
**Scope gate:** Read each test file to determine its type, then apply the correct gate:
|
|
24
|
+
- **Browser test** (has \`page.goto()\`, \`cy.visit()\`, \`driver.get()\`): check that the test navigates to a page the changed frontend file serves. Infer from the changed file's directory path (e.g. \`modules/bookings/\` → bookings page) or explicit route-path diff signals.
|
|
25
|
+
- **Component/widget test** (has \`render(<…>)\`, \`mount()\`, \`shallowMount()\`): check if it directly renders/mounts a changed component. If none of the rendered components match a changed file, the test is unaffected.
|
|
26
|
+
If neither condition applies, assign **IGNORE** and skip remaining checks.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
**Severity order (highest first): DELETE > REGENERATE > UPDATE > VERIFY > IGNORE**
|
|
30
|
+
|
|
31
|
+
**Pre-scan the diff for UI-relevant changes:**
|
|
32
|
+
- Component file changes: \`+ import { NewComponent }\`, \`- <OldComponent />\` / \`+ <NewComponent />\`
|
|
33
|
+
- CSS/Tailwind changes: \`+ className="..."\`, \`+ tw="..."\`, \`+ styles.\`
|
|
34
|
+
- Route changes: \`- path: "/old"\` / \`+ path: "/new"\`
|
|
35
|
+
- Selector changes: \`+ data-testid="..."\`, \`- data-testid="..."\`, \`+ aria-label="..."\`
|
|
36
|
+
- State/visibility changes: \`+ isOpen\`, \`+ isVisible\`, \`+ showModal\`
|
|
37
|
+
|
|
38
|
+
Build a detection list first: for each matched diff line, write one line: \`{pattern type} — "{diff line}" — affects {route/component}\`.
|
|
39
|
+
|
|
40
|
+
**Action definitions:**
|
|
41
|
+
- **DELETE**: The test targets a page/route that no longer exists, or all selectors the test uses have been removed and there's no migration path.
|
|
42
|
+
- **REGENERATE**: The interaction model changed so fundamentally that the test flow itself is broken — not just selector values, but the sequence of actions (e.g. a form replaced by a modal, buttons replaced by a dropdown, a multi-step flow collapsed into one). There is no rename mapping; re-recording from scratch is the only viable approach.
|
|
43
|
+
- **UPDATE**: Specific selectors need updating, or assertions need adjustment. The test structure is salvageable with targeted edits.
|
|
44
|
+
- **VERIFY**: The test may be impacted but you cannot confirm breakage without reading more context (e.g. selectors are hidden in a page object file you have not read, or the component is imported indirectly).
|
|
45
|
+
- **IGNORE**: The changed code cannot reach this test's routes or rendered components — the test covers unchanged functionality. If you cannot point to a diff line that affects this test's routes, selectors, or rendered components, the action is IGNORE or VERIFY, not UPDATE.
|
|
46
|
+
|
|
47
|
+
**Rules:**
|
|
48
|
+
- DELETE when all routes the test covers were removed or all critical selectors are gone.
|
|
49
|
+
- REGENERATE when the interaction model changed fundamentally — the test's sequence of actions is broken with no migration path (e.g. a click-to-update button replaced by an auto-updating stepper; a remove button moved into a dropdown). The number of broken selectors alone does not determine REGENERATE; what matters is whether the flow can still be expressed with targeted edits.
|
|
50
|
+
- UPDATE when specific selectors changed (renamed classes, moved data-testid) but the test flow remains valid.
|
|
51
|
+
- VERIFY when a page object or indirect import hides the affected selectors — read the imported file before escalating to UPDATE.
|
|
52
|
+
- IGNORE when the diff touches unrelated frontend code (different routes, different components).
|
|
53
|
+
|
|
54
|
+
**Additional rule for component/widget tests** ([rtl], [flutter-widget], [vue-test-utils], [angular-testbed]):
|
|
55
|
+
- Rule #7: Component deleted — source file removed with no re-export → **DELETE** (report-only for external tests). These tests have no navigation; the rendered component is their only target.
|
|
56
|
+
- Skip the route-existence check (step 1.1) for component/widget tests — they have no \`page.goto()\` or equivalent navigation calls.
|
|
57
|
+
- Primary matching signal: read the test to check if it directly renders a changed component — if so, it is impacted.
|
|
58
|
+
</ui_decision_rules>`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pre-scan step: agent greps changed files' exported symbols across the test
|
|
62
|
+
* directory to catch tests that server-side discovery missed — aliased imports,
|
|
63
|
+
* indirect renders, or any test the blueprint diff doesn't directly reference.
|
|
64
|
+
*/
|
|
65
|
+
export function buildUiCheckExportedSymbols(changedFrontendFiles) {
|
|
66
|
+
const files = (changedFrontendFiles ?? []).join(", ") || "the changed frontend files";
|
|
67
|
+
return `The server pre-discovered the tests listed above using exported symbols and UI signals (testids, aria-labels, i18n keys, CSS classes) from the diff. Before assessing, do a quick complementary grep to catch any tests the server may have missed — aliased imports, indirect renders, or tests using symbols not present in the diff:
|
|
68
|
+
|
|
69
|
+
1. For each changed frontend file (${files}):
|
|
70
|
+
a. Read the file and extract exported symbols — component names, function names, hook names, class names.
|
|
71
|
+
b. Grep the test directory for each symbol name (case-insensitive).
|
|
72
|
+
c. For any matching test file not already in the \`<ui_analysis_context>\` list — read it and apply the decision tree.
|
|
73
|
+
|
|
74
|
+
This catches tests that import a changed symbol under an alias, exercise a shared hook, or render the changed component indirectly via a parent.`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if routes targeted by the UI test still exist.
|
|
78
|
+
*/
|
|
79
|
+
export function buildUiCheckRouteExistence() {
|
|
80
|
+
return `Does the route/page this UI test navigates to still exist?
|
|
81
|
+
|
|
82
|
+
**Skip this check for component/widget tests** ([rtl], [flutter-widget], [vue-test-utils], [angular-testbed]) — they render components directly with no navigation calls. Proceed to selector checks.
|
|
83
|
+
|
|
84
|
+
Diff signals to look for (browser tests only):
|
|
85
|
+
- Route removed: \`- path: "/old-path"\`, \`- @app.route("/old-path")\`, \`- router.get("/old-path")\`
|
|
86
|
+
- Route renamed: paired \`-\` and \`+\` on route definitions with different paths
|
|
87
|
+
- Redirect added: \`+ redirect: "/old-path" → "/new-path"\`, \`+ res.redirect(301, "/new-path")\`
|
|
88
|
+
|
|
89
|
+
Actions:
|
|
90
|
+
- ALL routes the test uses were removed → **DELETE**
|
|
91
|
+
- SOME routes renamed with no redirect → **UPDATE** (update navigation calls to new URLs)
|
|
92
|
+
- Routes renamed with redirect kept → **UPDATE** (verify redirect behavior, test should still work)
|
|
93
|
+
- Routes unchanged → proceed to selector checks`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if selectors used by the UI test still exist in the blueprints.
|
|
97
|
+
*/
|
|
98
|
+
export function buildUiCheckSelectors() {
|
|
99
|
+
return `Do the selectors/locators this UI test uses still match the current UI?
|
|
100
|
+
|
|
101
|
+
Read the test (and any imported page object/fixture files) to extract the selectors/locators it uses — \`getByTestId(...)\`, \`locator(...)\`, \`.find('...')\`, \`data-testid\`, \`aria-label\`, CSS class names.
|
|
102
|
+
|
|
103
|
+
Diff signals to look for:
|
|
104
|
+
- CSS class changes: \`- className="old-class"\` / \`+ className="new-class"\`
|
|
105
|
+
- Tailwind changes: \`- tw="p-4"\` / \`+ tw="p-6"\`
|
|
106
|
+
- Test ID changes: \`- data-testid="old-id"\` / \`+ data-testid="new-id"\`
|
|
107
|
+
- ARIA changes: \`- aria-label="Old"\` / \`+ aria-label="New"\`
|
|
108
|
+
- Component restructuring: \`+ <NewWrapper>\` around existing content, \`- <OldWrapper>\`
|
|
109
|
+
|
|
110
|
+
**When blueprints are available** (\`blueprintCaptured=true\`): compare extracted selectors against \`browser_blueprint_diff\` for the affected page — elements absent from the new blueprint signal breakage.
|
|
111
|
+
|
|
112
|
+
**When blueprints are not available** (\`blueprintCaptured=false\`): use diff signals only. If a selector the test uses matches a value renamed or removed in the diff, assign UPDATE. If you cannot confirm breakage from the diff alone (e.g. selectors live in a page object you have not read), assign VERIFY rather than guessing.
|
|
113
|
+
|
|
114
|
+
Actions:
|
|
115
|
+
- Selectors confirmed unchanged (diff or blueprint) → **IGNORE** (no drift)
|
|
116
|
+
- Selectors renamed (class, testid, aria-label changed) → **UPDATE** (patch selector values)
|
|
117
|
+
- Selectors removed with no confirmed replacement → **UPDATE** (find replacement; use blueprint if available, diff otherwise)
|
|
118
|
+
- Major restructuring (most selectors gone) → **REGENERATE** (page structure fundamentally changed)
|
|
119
|
+
- All selectors gone, no replacement elements → **DELETE** (page no longer testable)
|
|
120
|
+
- Cannot confirm without reading page object or additional files → **VERIFY**`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if page objects/fixtures referenced by the test still exist.
|
|
124
|
+
*/
|
|
125
|
+
export function buildUiCheckPageObjects() {
|
|
126
|
+
return `Do the page objects or fixture files imported by this UI test still exist, and are their underlying selectors still valid?
|
|
127
|
+
|
|
128
|
+
**Always follow imports to find page objects — regardless of whether blueprints were captured.**
|
|
129
|
+
Page objects can live anywhere in the repo (not just \`page-objects/\` or \`fixtures/\`). Do not assume a fixed directory. The only reliable way to find them is to follow the spec file's \`import\` statements.
|
|
130
|
+
|
|
131
|
+
**Protocol for every browser test (Playwright/Cypress/Selenium):**
|
|
132
|
+
1. Read the spec file and collect all local \`import\` statements (skip \`@playwright/test\`, \`vitest\`, etc. — only follow relative or repo-local paths).
|
|
133
|
+
2. For each imported file, read it and extract the selectors it encapsulates — \`locator()\`, \`getByTestId()\`, \`getByRole()\`, \`getByLabel()\`, \`data-test-*\`, \`aria-label\`, CSS class strings.
|
|
134
|
+
3. Compare those selectors against the diff: if a selector value the page object uses was renamed or removed in a \`-\` diff line → the page object needs **UPDATE**.
|
|
135
|
+
4. **Update the page object file, not the spec file** — the page object is the selector source of truth. In the recommendation: set \`testFile\` to the **spec file** (so it shows correctly in the maintenance report) and set \`pomFile\` to the **page object path** (so \`skyramp_actions\` edits the right file). If the edit is rejected at action time, the agent or developer can address it separately.
|
|
136
|
+
5. If the imported file is missing or unreadable → **VERIFY** (cannot confirm without the file).
|
|
137
|
+
|
|
138
|
+
A clean spec with no raw selector calls is not a reason to assign VERIFY — it is expected when page objects are used. Assign the action based on the POM's selectors.
|
|
139
|
+
|
|
140
|
+
Diff signals to look for in the spec's imports:
|
|
141
|
+
- Import deleted: \`- import { OldPage } from './pages/OldPage'\` → page object removed
|
|
142
|
+
- Import path changed: paired \`-\`/\`+\` on the same import → page object moved or renamed
|
|
143
|
+
- New import added: \`+ import { NewFixture } from '../fixtures/new'\` → new dependency
|
|
144
|
+
|
|
145
|
+
Actions:
|
|
146
|
+
- Page object's selectors match a renamed/removed diff value → **UPDATE** (patch the page object)
|
|
147
|
+
- Page object import path changed in the diff → **UPDATE** (fix the import in the spec)
|
|
148
|
+
- Page object file deleted, no replacement found → **UPDATE** (inline selectors or find alternative)
|
|
149
|
+
- Selectors in page object unchanged and diff does not affect them → **IGNORE**
|
|
150
|
+
- **All POM selectors are gone with no migration path in the diff** (the component's interaction model changed fundamentally — no ` + ` lines showing replacement selectors) → **REGENERATE**. Do not stay at VERIFY after reading the POM and confirming this — escalate once you have confirmed the failure.`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Check for component behavior changes that affect assertions.
|
|
154
|
+
*/
|
|
155
|
+
export function buildUiCheckBehavioralChanges() {
|
|
156
|
+
return `Have component behaviors changed in ways that would break test assertions?
|
|
157
|
+
|
|
158
|
+
Diff signals to look for:
|
|
159
|
+
- State changes: \`+ isLoading\`, \`+ isOpen\`, \`+ showModal\`, \`+ disabled\`
|
|
160
|
+
- Visibility conditions: \`+ if (condition) return null\`, \`+ {condition && <Component />}\`
|
|
161
|
+
- Interaction changes: \`+ onClick={() => setOpen(true)}\`, \`+ require('auth')\`
|
|
162
|
+
- Async behavior: \`+ setTimeout\`, \`+ debounce\`, \`+ async/await\` in handlers
|
|
163
|
+
|
|
164
|
+
Actions:
|
|
165
|
+
- New loading states → **UPDATE** (add waitFor or assertion for loading state)
|
|
166
|
+
- New conditional rendering → **UPDATE** (update assertions to match new conditions)
|
|
167
|
+
- Auth behavior changed → **UPDATE** (adjust login flow in test)
|
|
168
|
+
- Async delays added → **UPDATE** (add appropriate waits/timeouts)`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Assign the final UI drift action.
|
|
172
|
+
*/
|
|
173
|
+
export function buildUiCheckAssignAction() {
|
|
174
|
+
return buildUiAssignAction(`**Component/widget test matching (source-only):**
|
|
175
|
+
- Read the test to check if it directly renders a changed component — if so, it is impacted.
|
|
176
|
+
- If the test only imports but does not directly render the component, assign VERIFY (component-import signal — cannot confirm impact without reading the parent).
|
|
177
|
+
- If neither signal fires, assign **IGNORE**.`);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Build the UI drift output checklist.
|
|
181
|
+
*/
|
|
182
|
+
export function buildUiDriftOutputChecklist(stateFile, tests) {
|
|
183
|
+
const finalStep = `Call \`skyramp_actions\` with \`stateFile: "${stateFile ?? "<stateFile>"}"\` and a \`recommendations\` entry for every test assessed.`;
|
|
184
|
+
const hasTests = (tests?.length ?? 0) > 0;
|
|
185
|
+
const testList = hasTests
|
|
186
|
+
? tests.map(t => `- ${t.testFile}`).join("\n")
|
|
187
|
+
: "- (none)";
|
|
188
|
+
const noTestsNote = !hasTests
|
|
189
|
+
? `\nNo existing tests were found. Call \`skyramp_actions\` with \`recommendations: []\`.\n`
|
|
190
|
+
: "";
|
|
191
|
+
const existingTestSection = `**Tests (${tests?.length ?? 0} total) — assess ALL of the following:**
|
|
192
|
+
${testList}
|
|
193
|
+
${noTestsNote}
|
|
194
|
+
|
|
195
|
+
**Execution policy:**
|
|
196
|
+
- Skyramp-generated tests: UPDATE/REGENERATE/DELETE applied automatically by \`skyramp_actions\`. Run via \`skyramp_execute_test\`.
|
|
197
|
+
- External tests (Playwright, Cypress, RTL, etc.): \`skyramp_actions\` applies UPDATE edits to \`testFile\` — which can be the test itself, a snapshot file, a page object, or a fixture. Application source files are automatically rejected. After a successful UPDATE, call \`skyramp_execute_test\` to confirm the patched test passes (the test environment is already running). Record the result as the after-state in the report. For DELETE and REGENERATE on external tests, see the execution policy in the decision rules above.
|
|
198
|
+
|
|
199
|
+
For each UI test above, output one structured entry in \`recommendations[]\`:
|
|
200
|
+
- **IGNORE**: \`{testFile} — IGNORE: {specific reason the diff cannot reach this test — name the route or component that differs}\`
|
|
201
|
+
- **VERIFY**: \`{testFile} — VERIFY: {uncertain element, e.g. "selectors hidden in page object not yet read"}\` — include in \`recommendations[]\` for developer review
|
|
202
|
+
\`\`\`
|
|
203
|
+
Test: {testFile}
|
|
204
|
+
Action: UPDATE
|
|
205
|
+
Rationale: UPDATE because {quoted diff line}; {selector/route/behavior change}
|
|
206
|
+
UpdateInstructions: {what must change — specific selectors to update, routes to patch, assertions to add}
|
|
207
|
+
\`\`\`
|
|
208
|
+
\`\`\`
|
|
209
|
+
Test: {testFile}
|
|
210
|
+
Action: REGENERATE
|
|
211
|
+
Rationale: REGENERATE because {quoted diff line}; page structure fundamentally changed
|
|
212
|
+
\`\`\`
|
|
213
|
+
\`\`\`
|
|
214
|
+
Test: {testFile}
|
|
215
|
+
Action: DELETE
|
|
216
|
+
Rationale: DELETE because {quoted diff line}; all routes removed or no valid selectors remain
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
**Every assessed test must appear in \`recommendations[]\`** — including IGNORE and VERIFY. IGNORE entries are omitted from \`testMaintenance[]\` in the report (a clean test is not a maintenance action). UPDATE, REGENERATE, DELETE, and VERIFY entries must appear in \`testMaintenance[]\`. A missing entry means the test was not assessed.`;
|
|
220
|
+
const sections = [existingTestSection, finalStep];
|
|
221
|
+
return `<ui_output_format>
|
|
222
|
+
Complete ALL of the following:
|
|
223
|
+
|
|
224
|
+
${sections.join("\n\n")}
|
|
225
|
+
</ui_output_format>`;
|
|
226
|
+
}
|
|
@@ -154,6 +154,69 @@ function prioritizeAttackSurfaceBundles(items) {
|
|
|
154
154
|
}
|
|
155
155
|
return reordered;
|
|
156
156
|
}
|
|
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
|
+
}
|
|
157
220
|
export function buildExecutionPlan(scored, maxGen, topN, baseUrl, authHeaderValue, authSchemeSnippet, authTypeValue, seed, endpointCount, isUIOnlyPR, hasFrontendChanges = false, hasTraces = false, externalCoverage = new Set(), relevantExternalTestPaths = []) {
|
|
158
221
|
const frontendUrl = "<frontend_url>";
|
|
159
222
|
// Slot allocation:
|
|
@@ -183,8 +246,19 @@ export function buildExecutionPlan(scored, maxGen, topN, baseUrl, authHeaderValu
|
|
|
183
246
|
})
|
|
184
247
|
: scored;
|
|
185
248
|
const slotOrderedItems = prioritizeAttackSurfaceBundles(scoredAfterExternalDedup);
|
|
186
|
-
|
|
187
|
-
|
|
249
|
+
// Distribute the backend GENERATE slots evenly across the test types present
|
|
250
|
+
// (contract vs integration) with spillover, instead of a pure top-N rank slice —
|
|
251
|
+
// so a backend change isn't starved of (say) integration coverage just because
|
|
252
|
+
// contract scenarios out-ranked it. See roundRobinByType.
|
|
253
|
+
const generateItems = roundRobinByType(slotOrderedItems, Math.min(backendGenerateCount, slotOrderedItems.length));
|
|
254
|
+
// ADDITIONAL = everything not chosen for GENERATE, in original rank order, capped
|
|
255
|
+
// at the remaining budget. Computed by set-difference (not a positional slice)
|
|
256
|
+
// because round-robin may pick items that aren't the first backendGenerateCount by
|
|
257
|
+
// rank — a positional slice would double-list a generated item or drop one.
|
|
258
|
+
const generateSet = new Set(generateItems);
|
|
259
|
+
const rawAdditionalItems = slotOrderedItems
|
|
260
|
+
.filter((it) => !generateSet.has(it))
|
|
261
|
+
.slice(0, Math.max(0, topN - backendGenerateCount));
|
|
188
262
|
// Filter additional items whose primary resource + test type already appear in GENERATE
|
|
189
263
|
const generatedCoverage = new Set(generateItems.map((item) => scenarioCoverageKey(item.scenario)));
|
|
190
264
|
const additionalItems = rawAdditionalItems.filter((item) => !generatedCoverage.has(scenarioCoverageKey(item.scenario)));
|
|
@@ -11,7 +11,12 @@ const FLUTTER_DART_EXT = /\.dart$/i;
|
|
|
11
11
|
const LIKELY_FRONTEND_EXT = /\.(tsx|jsx)$/i;
|
|
12
12
|
// Ambiguous extensions need directory context to distinguish frontend from backend.
|
|
13
13
|
const AMBIGUOUS_FRONTEND_PATTERN = /\.(tsx?|jsx?|css|scss|less|html?|erb|jsp|asp|jinja2?|twig)$/i;
|
|
14
|
-
|
|
14
|
+
// /api/ always signals backend — even .tsx files (e.g. Next.js pages/api/*.tsx handlers).
|
|
15
|
+
const STRICT_API_DIR_PATTERN = /\/(api)\//i;
|
|
16
|
+
// Framework route dirs that contain React page components in modern full-stack apps
|
|
17
|
+
// (Medusa Admin, Remix, React Router v6). These only block non-.tsx/.jsx files —
|
|
18
|
+
// a .tsx file in routes/ is a page component, not a route handler.
|
|
19
|
+
const FRAMEWORK_ROUTE_DIR_PATTERN = /\/(routes?|controllers?|routers?|handlers?|endpoints?|server)\//i;
|
|
15
20
|
const FRONTEND_DIR_PATTERN = /(^|\/)(components?|pages?|views?|layouts?|app|src\/app|frontend|client|public|styles?|templates?)\//i;
|
|
16
21
|
/**
|
|
17
22
|
* Returns true if the repository root's `pubspec.yaml` declares a Flutter
|
|
@@ -120,7 +125,12 @@ export function isFrontendFile(filePath, { hasFlutterSdkDep = false } = {}) {
|
|
|
120
125
|
return true;
|
|
121
126
|
if (hasFlutterSdkDep && FLUTTER_DART_EXT.test(filePath))
|
|
122
127
|
return true;
|
|
123
|
-
|
|
128
|
+
// /api/ always means backend regardless of extension (Next.js API routes use .tsx).
|
|
129
|
+
if (STRICT_API_DIR_PATTERN.test(filePath))
|
|
130
|
+
return false;
|
|
131
|
+
// routes/, controllers/ etc. block .ts/.js but not .tsx/.jsx — Medusa Admin and
|
|
132
|
+
// React Router place page components under routes/, not API handlers.
|
|
133
|
+
if (!LIKELY_FRONTEND_EXT.test(filePath) && FRAMEWORK_ROUTE_DIR_PATTERN.test(filePath))
|
|
124
134
|
return false;
|
|
125
135
|
if (LIKELY_FRONTEND_EXT.test(filePath))
|
|
126
136
|
return true;
|