@skyramp/mcp 0.3.8 → 0.4.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/commandLibrary.d.ts +1 -1
- package/build/commands/commandLibrary.js +3 -3
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
- package/build/commands/testThisEndpointCommand.js +35 -19
- package/build/index.js +9 -3
- package/build/playwright/blueprintDigest.d.ts +15 -0
- package/build/playwright/blueprintDigest.js +152 -0
- package/build/playwright/blueprintDigestStore.d.ts +31 -0
- package/build/playwright/blueprintDigestStore.js +117 -0
- package/build/playwright/registerPlaywrightTools.js +60 -12
- package/build/playwright/traceRecordingPrompt.js +8 -7
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
- package/build/prompts/promptAssets.d.ts +20 -0
- package/build/prompts/promptAssets.js +55 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
- package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
- package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
- package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
- package/build/prompts/test-recommendation/recommendationSections.js +67 -309
- package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
- package/build/prompts/test-recommendation/recommendationShared.js +49 -155
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
- package/build/prompts/testbot/planDeclarations.d.ts +6 -0
- package/build/prompts/testbot/planDeclarations.js +9 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
- package/build/prompts/testbot/testbot-prompts.js +256 -381
- package/build/recommendation/answers.d.ts +35 -0
- package/build/recommendation/answers.js +96 -0
- package/build/recommendation/registerPlan.d.ts +49 -0
- package/build/recommendation/registerPlan.js +117 -0
- package/build/recommendation/runVerifiers.d.ts +10 -0
- package/build/recommendation/runVerifiers.js +49 -0
- package/build/recommendation/subjectStep.d.ts +42 -0
- package/build/recommendation/subjectStep.js +86 -0
- package/build/recommendation/types.d.ts +163 -0
- package/build/recommendation/types.js +20 -0
- package/build/recommendation/verifierContracts.d.ts +382 -0
- package/build/recommendation/verifierContracts.js +263 -0
- package/build/recommendation/verifiers/changedFile.d.ts +2 -0
- package/build/recommendation/verifiers/changedFile.js +82 -0
- package/build/recommendation/verifiers/citedPath.d.ts +12 -0
- package/build/recommendation/verifiers/citedPath.js +35 -0
- package/build/recommendation/verifiers/coverage.d.ts +7 -0
- package/build/recommendation/verifiers/coverage.js +617 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
- package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
- package/build/recommendation/verifiers/endpointGrounded.js +128 -0
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +51 -0
- package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
- package/build/recommendation/verifiers/expectedOutcome.js +105 -0
- package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
- package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
- package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
- package/build/recommendation/verifiers/reportedCategory.js +84 -0
- package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
- package/build/recommendation/verifiers/screenRoute.js +118 -0
- package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
- package/build/recommendation/verifiers/statedDifference.js +140 -0
- package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
- package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
- package/build/resources/analysisResources.js +1 -114
- package/build/resources/testbotResource.js +23 -13
- package/build/services/ModularizationService.js +2 -1
- package/build/services/TestDiscoveryService.d.ts +3 -72
- package/build/services/TestDiscoveryService.js +10 -303
- package/build/services/containerEnv.d.ts +1 -1
- package/build/services/containerEnv.js +12 -0
- package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
- package/build/skills/fixTestImportErrorsSkill.js +20 -0
- package/build/toolNames.d.ts +1 -0
- package/build/toolNames.js +1 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
- package/build/tools/code-refactor/modularizationTool.js +2 -1
- package/build/tools/executeSkyrampTestTool.d.ts +80 -0
- package/build/tools/executeSkyrampTestTool.js +246 -19
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
- package/build/tools/generate-tests/generateContractRestTool.js +3 -3
- package/build/tools/generate-tests/planGuard.d.ts +2 -2
- package/build/tools/generate-tests/planGuard.js +78 -18
- package/build/tools/one-click/oneClickTool.d.ts +0 -1
- package/build/tools/one-click/oneClickTool.js +0 -5
- package/build/tools/submitReportTool.d.ts +48 -42
- package/build/tools/submitReportTool.js +576 -193
- package/build/tools/test-management/actionsTool.js +72 -4
- package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
- package/build/tools/test-management/analyzeChangesTool.js +212 -1219
- package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
- package/build/tools/test-management/registerTestPlanTool.js +609 -542
- package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
- package/build/tools/test-management/resolveScreenTool.js +289 -0
- package/build/types/BlueprintDigest.d.ts +34 -0
- package/build/types/BlueprintDigest.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +20 -1559
- package/build/types/RepositoryAnalysis.js +2 -58
- package/build/types/StepMethod.d.ts +40 -0
- package/build/types/StepMethod.js +77 -0
- package/build/types/TestAnalysis.d.ts +12 -0
- package/build/types/TestExecution.d.ts +4 -0
- package/build/types/TestRecommendation.d.ts +24 -24
- package/build/types/TestRecommendation.js +91 -89
- package/build/types/TestbotPromptOptions.d.ts +0 -4
- package/build/types/TestbotReport.d.ts +64 -2
- package/build/utils/AnalysisStateManager.d.ts +79 -113
- package/build/utils/AnalysisStateManager.js +147 -57
- package/build/utils/assertion-verify/api-shared-lints.js +1 -1
- package/build/utils/assertion-verify/metrics.js +85 -36
- package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
- package/build/utils/assertion-verify/ui-lints.js +32 -0
- package/build/utils/branchDiff.d.ts +63 -31
- package/build/utils/branchDiff.js +242 -94
- package/build/utils/containedPath.d.ts +18 -0
- package/build/utils/containedPath.js +73 -0
- package/build/utils/dartRouteExtractor.d.ts +18 -34
- package/build/utils/dartRouteExtractor.js +101 -173
- package/build/utils/featureFlags.d.ts +12 -0
- package/build/utils/featureFlags.js +14 -0
- package/build/utils/frontendSelectors.d.ts +48 -27
- package/build/utils/frontendSelectors.js +241 -80
- package/build/utils/pathMatching.d.ts +2 -4
- package/build/utils/pathMatching.js +2 -4
- package/build/utils/planMatchKeys.d.ts +38 -47
- package/build/utils/planMatchKeys.js +143 -81
- package/build/utils/rebaselineSnapshots.d.ts +24 -0
- package/build/utils/rebaselineSnapshots.js +65 -0
- package/build/utils/removedUiElements.d.ts +22 -0
- package/build/utils/removedUiElements.js +106 -0
- package/build/utils/reportVerification.d.ts +2 -6
- package/build/utils/reportVerification.js +61 -2
- package/build/utils/screenRoutes.d.ts +66 -0
- package/build/utils/screenRoutes.js +727 -0
- package/build/utils/sourceRouteExtractor.js +320 -112
- package/build/utils/testFileClassification.d.ts +11 -2
- package/build/utils/testFileClassification.js +44 -2
- package/build/utils/testFixtures.d.ts +5 -0
- package/build/utils/testFixtures.js +13 -0
- package/build/utils/utils.d.ts +0 -1
- package/build/utils/utils.js +0 -11
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +12 -12
- package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/plugin.json +6 -0
- package/plugin/prompts/declaring-a-plan.md +20 -0
- package/plugin/prompts/generate-tests/context-fetching.md +4 -0
- package/plugin/prompts/generate-tests/execution-plan.md +63 -0
- package/plugin/prompts/generate-tests/generation.md +108 -0
- package/plugin/prompts/generate-tests/path-parameters.md +1 -0
- package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
- package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
- package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
- package/plugin/prompts/plan-tests.md +42 -0
- package/plugin/prompts/testbot-task1.md +82 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
- package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
- package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
- package/build/recommendation/budgeters/shared.d.ts +0 -32
- package/build/recommendation/budgeters/shared.js +0 -246
- package/build/recommendation/discriminators.d.ts +0 -37
- package/build/recommendation/discriminators.js +0 -379
- package/build/recommendation/diversity.d.ts +0 -47
- package/build/recommendation/diversity.js +0 -101
- package/build/recommendation/planRanker.d.ts +0 -65
- package/build/recommendation/planRanker.js +0 -83
- package/build/recommendation/testFixtures.d.ts +0 -25
- package/build/recommendation/testFixtures.js +0 -45
- package/build/types/FrontendIntegration.d.ts +0 -28
- package/build/types/FrontendIntegration.js +0 -22
- package/build/types/Recommendation.d.ts +0 -146
- package/build/types/Recommendation.js +0 -74
- package/build/utils/changedRoutes.d.ts +0 -29
- package/build/utils/changedRoutes.js +0 -87
- package/build/utils/frontendIntegration.d.ts +0 -9
- package/build/utils/frontendIntegration.js +0 -243
- package/build/utils/importerHop.d.ts +0 -135
- package/build/utils/importerHop.js +0 -489
- package/build/utils/pathAffinityClassification.d.ts +0 -49
- package/build/utils/pathAffinityClassification.js +0 -180
- package/build/utils/pythonMountPrefixes.d.ts +0 -25
- package/build/utils/pythonMountPrefixes.js +0 -347
- package/build/utils/repoScanner.d.ts +0 -34
- package/build/utils/repoScanner.js +0 -300
- package/build/utils/routeParsers.d.ts +0 -95
- package/build/utils/routeParsers.js +0 -951
- package/build/utils/scenarioDrafting.d.ts +0 -92
- package/build/utils/scenarioDrafting.js +0 -951
- package/build/utils/subjectEndpoints.d.ts +0 -19
- package/build/utils/subjectEndpoints.js +0 -98
- package/build/utils/uiPageEnumerator.d.ts +0 -172
- package/build/utils/uiPageEnumerator.js +0 -474
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `data-*` attributes and selector literals a frontend diff touched.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* filename-token (pass 1) and import-edge (pass 2) promotion signals in
|
|
7
|
-
* TestDiscoveryService Step 2 both miss them: a page object `table.component.ts` shares
|
|
8
|
-
* no name token with the changed `CascadeFields.tsx` and does not import it — it selects
|
|
9
|
-
* `.t--table-filter-columns-dropdown` at runtime.
|
|
4
|
+
* One reader now: the removal guard (SKYR-4305), which reports an element the
|
|
5
|
+
* diff deleted from a page that survives so the agent can assert it is gone.
|
|
10
6
|
*
|
|
11
|
-
* This module
|
|
12
|
-
* discovery
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the test that needs maintenance.
|
|
16
|
-
*
|
|
17
|
-
* This is the frontend analog of importerHop's `extractChangedSymbols` (backend type
|
|
18
|
-
* names) — kept in its own frontend-scoped module rather than in the backend-resource
|
|
19
|
-
* importerHop, per the codebase convention of keeping domain vocabularies local.
|
|
7
|
+
* This module also used to lift every selector literal a diff added or removed,
|
|
8
|
+
* so discovery could promote a page object whose body referenced one. That was
|
|
9
|
+
* the server deciding which existing tests a change touches — the external
|
|
10
|
+
* tests are loaded uncut up to the discovery cap now, and the agent reads them.
|
|
20
11
|
*/
|
|
21
|
-
// Bound the extracted set so a large diff can't produce an unbounded match list. The
|
|
22
|
-
// downstream promotion is separately capped by MAX_UI_PROMOTED, so this is a belt on the
|
|
23
|
-
// literal set itself. Exported so the caller can detect + log when a single file's
|
|
24
|
-
// extraction is truncated by this cap.
|
|
25
|
-
export const MAX_CHANGED_SELECTORS = 80;
|
|
26
12
|
// Utility / design-token CSS classes (Tailwind/Bootstrap-style) are ALSO multi-hyphen kebab
|
|
27
13
|
// tokens, but they are shared *styling* vocabulary, not component identity — matching them
|
|
28
14
|
// floods Pass 3, because snapshot files (`__snapshots__/*.snap`) serialize `className`
|
|
@@ -53,71 +39,246 @@ function isDistinctiveSelector(token, trusted) {
|
|
|
53
39
|
return hasBEM || (hyphens >= 2 && !COMMON_CSS_SELECTOR_TOKENS.test(token));
|
|
54
40
|
}
|
|
55
41
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
42
|
+
* The test-facing selector grammar, shared by every extractor in this module so the
|
|
43
|
+
* two cannot drift: `data-testid` / `data-test` / `data-test-id` / `data-cy` / `data-qa`
|
|
44
|
+
* attribute values (string literals only) and `getByTestId(...)` arguments.
|
|
45
|
+
* Case-insensitive on purpose: JSX authors write `data-testId` (demoshop) and the
|
|
46
|
+
* browser lowercases attribute names, so `getByTestId` matches either spelling.
|
|
47
|
+
*/
|
|
48
|
+
const TESTID_ATTR_RE = /\b(data-(?:test-?id|test|cy|qa))\b\s*=\s*\{?\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/gi;
|
|
49
|
+
const GET_BY_TEST_ID_RE = /getByTestId\(\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/gi;
|
|
50
|
+
/**
|
|
51
|
+
* Any other `data-*` attribute with a static string value (`data-section="session-id-debug"`).
|
|
52
|
+
* Untrusted: the value must pass the stricter distinctiveness rule, and attributes that carry
|
|
53
|
+
* component STATE rather than identity are excluded — they flip at runtime, so "absent" is
|
|
54
|
+
* meaningless for them.
|
|
55
|
+
*/
|
|
56
|
+
const GENERIC_DATA_ATTR_RE = /\b(data-[a-z][a-z0-9]*(?:-[a-z0-9]+)*)\s*=\s*\{?\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/gi;
|
|
57
|
+
const STATE_DATA_ATTRS = new Set([
|
|
58
|
+
"data-state", "data-theme", "data-orientation", "data-side", "data-align", "data-value",
|
|
59
|
+
"data-index", "data-slot", "data-disabled", "data-checked", "data-active", "data-open",
|
|
60
|
+
"data-loading", "data-size", "data-variant", "data-selected", "data-highlighted",
|
|
61
|
+
"data-focus", "data-hover", "data-pressed", "data-expanded", "data-placeholder", "data-status",
|
|
62
|
+
]);
|
|
63
|
+
/**
|
|
64
|
+
* Identifying `data-*` attributes on one source line: the trusted test-facing set (any
|
|
65
|
+
* casing), `getByTestId(...)` arguments (attributed to `data-testid`), and any other
|
|
66
|
+
* static `data-*` whose value is distinctive and whose attribute is not a state carrier.
|
|
67
|
+
*/
|
|
68
|
+
function attributesOnLine(line) {
|
|
69
|
+
const out = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
const push = (attribute, value) => {
|
|
72
|
+
const key = `${attribute}=${value}`;
|
|
73
|
+
if (!seen.has(key)) {
|
|
74
|
+
seen.add(key);
|
|
75
|
+
out.push({ attribute, value });
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
let m;
|
|
79
|
+
TESTID_ATTR_RE.lastIndex = 0;
|
|
80
|
+
while ((m = TESTID_ATTR_RE.exec(line)) !== null) {
|
|
81
|
+
if (isDistinctiveSelector(m[2], true))
|
|
82
|
+
push(m[1].toLowerCase(), m[2]);
|
|
83
|
+
}
|
|
84
|
+
GET_BY_TEST_ID_RE.lastIndex = 0;
|
|
85
|
+
while ((m = GET_BY_TEST_ID_RE.exec(line)) !== null) {
|
|
86
|
+
if (isDistinctiveSelector(m[1], true))
|
|
87
|
+
push("data-testid", m[1]);
|
|
88
|
+
}
|
|
89
|
+
GENERIC_DATA_ATTR_RE.lastIndex = 0;
|
|
90
|
+
while ((m = GENERIC_DATA_ATTR_RE.exec(line)) !== null) {
|
|
91
|
+
const attribute = m[1].toLowerCase();
|
|
92
|
+
if (STATE_DATA_ATTRS.has(attribute))
|
|
93
|
+
continue;
|
|
94
|
+
if (seen.has(`${attribute}=${m[2]}`))
|
|
95
|
+
continue; // already captured by the trusted pass
|
|
96
|
+
if (isDistinctiveSelector(m[2], false))
|
|
97
|
+
push(attribute, m[2]);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Tag name of the JSX/HTML element a diff line opens, or "" — also "" for a comment
|
|
103
|
+
* line (`//`, `/*`, `*`, `{/*`, `<!--`), whose `<div …>` is text, not an element.
|
|
66
104
|
*/
|
|
67
|
-
|
|
105
|
+
function openingTagOf(line) {
|
|
106
|
+
const t = line.trim();
|
|
107
|
+
if (/^(?:\/\/|\/\*|\*|\{\s*\/\*|<!--)/.test(t))
|
|
108
|
+
return "";
|
|
109
|
+
return /<\s*([A-Za-z][\w.-]*)/.exec(t)?.[1]?.toLowerCase() ?? "";
|
|
110
|
+
}
|
|
111
|
+
/** Identifying attribute VALUES that appear on `+` lines of a diff (any number of files). */
|
|
112
|
+
export function extractAddedAttributeValues(diff) {
|
|
68
113
|
const out = new Set();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// non-literal expressions like `data-testid={someVar}`, which would otherwise inject a
|
|
72
|
-
// variable name into the selector set and cause noisy promotion matches.
|
|
73
|
-
// Require a quote/backtick on BOTH sides so only FULLY-literal values are captured —
|
|
74
|
-
// `data-testid="x"`, `data-testid={'x'}`, `data-testid={`x`}`. The closing delimiter
|
|
75
|
-
// deliberately rejects interpolated template literals (`data-testid={`members-${id}`}`),
|
|
76
|
-
// which would otherwise leak a partial prefix (`members-`) into the selector set.
|
|
77
|
-
const ATTR = /\bdata-(?:test-?id|test|cy|qa)\b\s*=\s*\{?\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/g;
|
|
78
|
-
const GET_BY_TEST_ID = /getByTestId\(\s*[`'"]([A-Za-z][\w:-]{2,})[`'"]/g;
|
|
79
|
-
const KEBAB = /[a-z][a-z0-9]*(?:-{1,2}[a-z0-9]+)+/g;
|
|
80
|
-
// BEM base whose modifier is composed DYNAMICALLY, so no concrete class literal exists
|
|
81
|
-
// in the diff to grep: `ads-button--${kind}`, "ads-button--" + kind, clsx(base && `x--${y}`).
|
|
82
|
-
// Match a kebab base + `--` followed by a NON-alphanumeric (interpolation `$`, quote,
|
|
83
|
-
// paren, whitespace) and capture the `<base>--` prefix. The page objects' concrete
|
|
84
|
-
// modifier classes (ads-button--primary, ads-button--secondary) all start with that
|
|
85
|
-
// prefix, so a substring match on it promotes them — the shared/design-system component
|
|
86
|
-
// case a static-literal scan (KEBAB) misses. (A STATIC modifier like `foo--bar` is left
|
|
87
|
-
// to KEBAB, which captures the whole class.)
|
|
88
|
-
const BEM_DYNAMIC = /([a-z][a-z0-9]*(?:-[a-z0-9]+)+--)(?![a-z0-9])/g;
|
|
89
|
-
for (const raw of fileDiff.split("\n")) {
|
|
90
|
-
if (!(raw.startsWith("+") || raw.startsWith("-")))
|
|
114
|
+
for (const raw of diff.split("\n")) {
|
|
115
|
+
if (!raw.startsWith("+") || raw.startsWith("+++"))
|
|
91
116
|
continue;
|
|
92
|
-
|
|
117
|
+
for (const a of attributesOnLine(raw.slice(1)))
|
|
118
|
+
out.add(a.value);
|
|
119
|
+
}
|
|
120
|
+
return [...out];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Identifying `data-*` attributes one file's diff REMOVED: literals on `-` lines, minus the
|
|
124
|
+
* cases where the element still renders somewhere:
|
|
125
|
+
*
|
|
126
|
+
* - **moved / extracted** — the same value appears on a `+` line of this file or, via
|
|
127
|
+
* `addedValuesElsewhere`, anywhere else in the diff (a component extracted into a new
|
|
128
|
+
* file is the common shape);
|
|
129
|
+
* - **not an element** — a `-` line counts only when it opens a JSX/HTML tag or is a
|
|
130
|
+
* wrapped attribute line of a tag opened above it, on a changed or an unchanged
|
|
131
|
+
* line (the tag is carried until the `>` that closes it; a `>` inside a quoted
|
|
132
|
+
* value, a `{…}` expression or an `=>` arrow is content and does not close it). A deleted comment, docs snippet, story arg or page-object
|
|
133
|
+
* `getByTestId(...)` call that merely mentions the attribute is not an element
|
|
134
|
+
* removal — the element itself may well survive on an unchanged context line;
|
|
135
|
+
* - **renamed** — a rename cannot be told apart by literal identity (old and new
|
|
136
|
+
* value differ by definition), so it is paired structurally: within the same hunk,
|
|
137
|
+
* each removed ELEMENT is paired with AT MOST ONE added element that opens the same
|
|
138
|
+
* tag, carries a NEW identifying attribute, and keeps every other attribute NAME
|
|
139
|
+
* the removed element set on its changed lines (a rename may add attributes or
|
|
140
|
+
* edit their values, never drop one). With the tag kept on a context line, the
|
|
141
|
+
* changed lines are exactly the attributes that differ, so an in-place rename
|
|
142
|
+
* compares only what it touched while an in-place replacement that drops the old
|
|
143
|
+
* element's handlers does not pair. An element whose changed lines carry nothing
|
|
144
|
+
* but the identifier pairs by tag alone: a relabelled rename (`clear-state` →
|
|
145
|
+
* `reset-session`, "Clear State" → "Reset Session") and a same-shape replacement
|
|
146
|
+
* are the same diff, and the tie goes to rename — a guard on a retired identifier
|
|
147
|
+
* passes vacuously, and the agent still reads the diff for what the list lacks. A paired added element is consumed, so one addition
|
|
148
|
+
* suppresses one removal, not every removal in the hunk. SKYR-4339: pairing by tag
|
|
149
|
+
* alone consumed a genuinely removed `<a href="#">` with a genuinely added
|
|
150
|
+
* `<a href="https://…">` in the same hunk (judgement eval RG03), so the guard was
|
|
151
|
+
* never listed.
|
|
152
|
+
*
|
|
153
|
+
* Feeds `uiContext.removedElements` (SKYR-4305) — the grounding for a removal guard
|
|
154
|
+
* (`toBeHidden()` on `getByTestId(value)` for `data-testid`, on
|
|
155
|
+
* `locator('[<attribute>="<value>"]')` for any other `data-*`).
|
|
156
|
+
*/
|
|
157
|
+
export function extractRemovedAttributes(fileDiff, addedValuesElsewhere = []) {
|
|
158
|
+
const addedAnywhere = new Set(addedValuesElsewhere);
|
|
159
|
+
for (const v of extractAddedAttributeValues(fileDiff))
|
|
160
|
+
addedAnywhere.add(v);
|
|
161
|
+
// Split into hunks so rename pairing stays local.
|
|
162
|
+
const hunks = [];
|
|
163
|
+
for (const raw of fileDiff.split("\n")) {
|
|
164
|
+
if (raw.startsWith("@@")) {
|
|
165
|
+
hunks.push([]);
|
|
93
166
|
continue;
|
|
94
|
-
const line = raw.slice(1);
|
|
95
|
-
for (const re of [ATTR, GET_BY_TEST_ID]) {
|
|
96
|
-
re.lastIndex = 0;
|
|
97
|
-
let m;
|
|
98
|
-
while ((m = re.exec(line)) !== null) {
|
|
99
|
-
if (isDistinctiveSelector(m[1], true))
|
|
100
|
-
out.add(m[1]);
|
|
101
|
-
if (out.size >= MAX_CHANGED_SELECTORS)
|
|
102
|
-
return [...out];
|
|
103
|
-
}
|
|
104
167
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
168
|
+
if (hunks.length === 0)
|
|
169
|
+
hunks.push([]);
|
|
170
|
+
hunks[hunks.length - 1].push(raw);
|
|
171
|
+
}
|
|
172
|
+
// Names of the attributes an element's changed lines set (`name=` at attribute
|
|
173
|
+
// position: line start or after whitespace, so `=>`, `a=b` inside a handler body and
|
|
174
|
+
// `id=` inside a regex do not count), minus the identifying data-* attributes. Names,
|
|
175
|
+
// not values: a rename may edit or reformat a neighbouring attribute, never drop one.
|
|
176
|
+
const otherAttrNames = (text, ids) => {
|
|
177
|
+
const out = new Set();
|
|
178
|
+
const re = /(?:^|\s)([A-Za-z][\w:.-]*)=/g;
|
|
179
|
+
let m;
|
|
180
|
+
while ((m = re.exec(text)) !== null) {
|
|
181
|
+
const name = m[1].toLowerCase();
|
|
182
|
+
if (ids.some((a) => name === a.attribute))
|
|
183
|
+
continue;
|
|
184
|
+
out.add(name);
|
|
112
185
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
186
|
+
return out;
|
|
187
|
+
};
|
|
188
|
+
// Does this line close the opening tag? A `>` inside a quoted value, inside a `{…}`
|
|
189
|
+
// expression (`onClick={() => x}`) or as part of `=>` is content, not the tag end.
|
|
190
|
+
const closesOpeningTag = (line) => {
|
|
191
|
+
let t = line.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, '""').replace(/=>/g, "");
|
|
192
|
+
for (let prev = ""; prev !== t;) {
|
|
193
|
+
prev = t;
|
|
194
|
+
t = t.replace(/\{[^{}]*\}/g, "");
|
|
195
|
+
}
|
|
196
|
+
return t.includes(">");
|
|
197
|
+
};
|
|
198
|
+
const out = [];
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
for (const hunk of hunks) {
|
|
201
|
+
// Group changed lines into elements per side: an element starts on the line that
|
|
202
|
+
// opens its tag and carries on until the `>` that closes the opening tag, so a
|
|
203
|
+
// wrapped attribute line (`- data-testid="x"`) belongs to the tag opened above it,
|
|
204
|
+
// and a later comment or unrelated line in the hunk does not inherit it.
|
|
205
|
+
// The tag may also be opened on an unchanged context line (`<a` kept, attributes
|
|
206
|
+
// edited underneath): then each side's changed lines form an element of that tag,
|
|
207
|
+
// until the context `>` that closes it.
|
|
208
|
+
const removed = [];
|
|
209
|
+
const added = [];
|
|
210
|
+
let curMinus = null;
|
|
211
|
+
let curPlus = null;
|
|
212
|
+
let contextTag = "";
|
|
213
|
+
for (const raw of hunk) {
|
|
214
|
+
if (raw.startsWith("+++") || raw.startsWith("---"))
|
|
215
|
+
continue;
|
|
216
|
+
if (!(raw.startsWith("+") || raw.startsWith("-"))) {
|
|
217
|
+
const line = raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
218
|
+
const tag = openingTagOf(line);
|
|
219
|
+
if (tag) {
|
|
220
|
+
contextTag = tag;
|
|
221
|
+
curMinus = null;
|
|
222
|
+
curPlus = null;
|
|
223
|
+
}
|
|
224
|
+
if (closesOpeningTag(line)) {
|
|
225
|
+
contextTag = "";
|
|
226
|
+
curMinus = null;
|
|
227
|
+
curPlus = null;
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const line = raw.slice(1);
|
|
232
|
+
const minus = raw.startsWith("-");
|
|
233
|
+
const ownTag = openingTagOf(line);
|
|
234
|
+
let cur = minus ? curMinus : curPlus;
|
|
235
|
+
if (ownTag) {
|
|
236
|
+
cur = { tag: ownTag, text: "", ids: [], consumed: false };
|
|
237
|
+
(minus ? removed : added).push(cur);
|
|
238
|
+
}
|
|
239
|
+
else if (!cur && contextTag) {
|
|
240
|
+
cur = { tag: contextTag, text: "", ids: [], consumed: false };
|
|
241
|
+
(minus ? removed : added).push(cur);
|
|
242
|
+
}
|
|
243
|
+
if (cur) {
|
|
244
|
+
cur.text += `${line}\n`;
|
|
245
|
+
cur.ids.push(...attributesOnLine(line));
|
|
246
|
+
}
|
|
247
|
+
// Only an element (a tag opened on this line or carried from above) can be a
|
|
248
|
+
// removed element; a bare mention in a comment / docs / page object is not.
|
|
249
|
+
if (closesOpeningTag(line))
|
|
250
|
+
cur = null;
|
|
251
|
+
if (minus)
|
|
252
|
+
curMinus = cur;
|
|
253
|
+
else
|
|
254
|
+
curPlus = cur;
|
|
255
|
+
}
|
|
256
|
+
const candidates = added.filter((a) => a.ids.length > 0);
|
|
257
|
+
for (const r of removed) {
|
|
258
|
+
if (r.ids.length === 0)
|
|
259
|
+
continue;
|
|
260
|
+
const rNames = otherAttrNames(r.text, r.ids);
|
|
261
|
+
const partner = candidates.find((a) => {
|
|
262
|
+
if (a.consumed || a.tag !== r.tag)
|
|
263
|
+
return false;
|
|
264
|
+
const aNames = otherAttrNames(a.text, a.ids);
|
|
265
|
+
for (const name of rNames)
|
|
266
|
+
if (!aNames.has(name))
|
|
267
|
+
return false;
|
|
268
|
+
return true;
|
|
269
|
+
});
|
|
270
|
+
if (partner) {
|
|
271
|
+
partner.consumed = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
for (const a of r.ids) {
|
|
275
|
+
const key = `${a.attribute}=${a.value}`;
|
|
276
|
+
if (!addedAnywhere.has(a.value) && !seen.has(key)) {
|
|
277
|
+
seen.add(key);
|
|
278
|
+
out.push(a);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
120
281
|
}
|
|
121
282
|
}
|
|
122
|
-
return
|
|
283
|
+
return out;
|
|
123
284
|
}
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
* are both true even though the paths name different resources. These
|
|
6
6
|
* helpers require the match to land on a "/" (or path-end) boundary.
|
|
7
7
|
*
|
|
8
|
-
* Lives in its own module
|
|
9
|
-
*
|
|
10
|
-
* `routeParsers.ts`; `scenarioDrafting.ts` imports `importerHop.ts`, which
|
|
11
|
-
* imports `routeParsers.ts`) can both use it without risking an import cycle.
|
|
8
|
+
* Lives in its own module so any caller can use it without risking an import
|
|
9
|
+
* cycle through whichever module owns the route predicates.
|
|
12
10
|
*/
|
|
13
11
|
/**
|
|
14
12
|
* Returns true when `a` starts with `b` at a path-segment boundary.
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
* are both true even though the paths name different resources. These
|
|
6
6
|
* helpers require the match to land on a "/" (or path-end) boundary.
|
|
7
7
|
*
|
|
8
|
-
* Lives in its own module
|
|
9
|
-
*
|
|
10
|
-
* `routeParsers.ts`; `scenarioDrafting.ts` imports `importerHop.ts`, which
|
|
11
|
-
* imports `routeParsers.ts`) can both use it without risking an import cycle.
|
|
8
|
+
* Lives in its own module so any caller can use it without risking an import
|
|
9
|
+
* cycle through whichever module owns the route predicates.
|
|
12
10
|
*/
|
|
13
11
|
/**
|
|
14
12
|
* Returns true when `a` starts with `b` at a path-segment boundary.
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* consumers — planGuard (generation-tool hard gate) and submitReportTool
|
|
6
|
-
* (report cross-check) — only ever do a Set/array membership check, never
|
|
7
|
-
* re-derive normalization logic against a live candidate.
|
|
2
|
+
* Shared match-key computation for the generation-tool plan gate. The generation
|
|
3
|
+
* tools are called with a scenario name and an endpoint, never a `plannedTestId`,
|
|
4
|
+
* so this joins a call back to a plan planned test; the report compares ids.
|
|
8
5
|
*
|
|
9
6
|
* Two kinds of key:
|
|
10
7
|
* - `name:<slug>` — normalized scenarioName, for multi-step
|
|
11
|
-
*
|
|
8
|
+
* planned tests (batch-scenario / integration) where the agent's own name is
|
|
12
9
|
* the natural join key.
|
|
13
10
|
* - `ep:<testType>:<METHOD>:<path>` — one per step, for single-endpoint
|
|
14
11
|
* matching (contract tests, or a fallback when names drift between
|
|
15
12
|
* register and generate).
|
|
16
13
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
/** Query shape built from a generation-tool call or a
|
|
14
|
+
import { PlanScenario } from "../types/RepositoryAnalysis.js";
|
|
15
|
+
import type { PlannedTest } from "../recommendation/types.js";
|
|
16
|
+
/** Query shape built from a generation-tool call or a reported test entry. */
|
|
20
17
|
export interface PlanMatchQuery {
|
|
21
18
|
scenarioName?: string;
|
|
22
19
|
testType?: string;
|
|
@@ -25,51 +22,45 @@ export interface PlanMatchQuery {
|
|
|
25
22
|
}
|
|
26
23
|
/** Lowercase, collapse non-alphanumeric runs to '-', trim leading/trailing '-'. */
|
|
27
24
|
export declare function slugifyName(name: string | undefined): string;
|
|
25
|
+
export declare function isParamSegment(segment: string): boolean;
|
|
26
|
+
export declare function isOpaqueIdSegment(segment: string): boolean;
|
|
28
27
|
/**
|
|
29
|
-
* Normalize a path so param
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* rather than a second inline copy — this module previously had its own
|
|
34
|
-
* looser hex rule (`/^[0-9a-fA-F-]{16,}$/`, accepting `-`) and no
|
|
35
|
-
* bracket-syntax rule at all, so a Next.js `[id]` segment never collapsed
|
|
36
|
-
* and kept a match key from matching its `{id}`-spelled counterpart (the
|
|
37
|
-
* SKYR-4123 failure mode: a generated test's key stops matching its plan
|
|
38
|
-
* item). The characterization test in
|
|
39
|
-
* `pathSegmentClassification.characterization.test.ts` confirms every
|
|
40
|
-
* previously-agreeing input (UUID, numeric, 16+/24-char hex) still
|
|
41
|
-
* collapses to `:p` after this change — safe within one run because the
|
|
42
|
-
* register-time key and the generation-time query are computed by the same
|
|
43
|
-
* build; a plan file is never persisted across builds. Mirrors
|
|
44
|
-
* discriminators.ts's normalizePath (duplicated rather than imported —
|
|
45
|
-
* that function verifies discriminator claims against a diff, a different
|
|
46
|
-
* concern from plan matching, and keeping them independent avoids coupling
|
|
47
|
-
* this module's stability to phase 1's internal helper).
|
|
28
|
+
* Normalize a path so param and id segments collapse to `:p`.
|
|
29
|
+
*
|
|
30
|
+
* The two predicates above are the only rule for it — an earlier inline copy had
|
|
31
|
+
* no bracket rule, so a Next.js `[id]` stopped matching its `{id}` spelling.
|
|
48
32
|
*/
|
|
49
33
|
export declare function normalizeMatchPath(rawPath: string | undefined): string;
|
|
50
|
-
/** Name key (if a name exists) plus one endpoint key per step.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export declare function
|
|
34
|
+
/** Name key (if a name exists) plus one endpoint key per step. BOTH kinds carry
|
|
35
|
+
* the planned test's test type, or a contract call matches an integration-only
|
|
36
|
+
* planned test sharing its name. A slug holds no `:`. */
|
|
37
|
+
export declare function computeMatchKeys(scenario: PlanScenario): string[];
|
|
54
38
|
/**
|
|
55
|
-
* True when `query` matches
|
|
56
|
-
* - by scenarioName slug (`name:<slug>`), or
|
|
39
|
+
* True when `query` matches `keys`:
|
|
40
|
+
* - by testType + scenarioName slug (`name:<testType>:<slug>`), or
|
|
57
41
|
* - by testType + path (`ep:<testType>:*:<path>`), method-exact when
|
|
58
42
|
* `query.method` is given, method-agnostic (any method) otherwise — the
|
|
59
43
|
* contract tool's `method: ""` ("test all methods") case. Path comparison
|
|
60
44
|
* tolerates a base-path prefix on either side (see normalizedPathsMatch).
|
|
45
|
+
* The planned test's test type gates BOTH channels: gating the endpoint channel
|
|
46
|
+
* alone let a contract call pass against an integration-only planned test of the
|
|
47
|
+
* same name. Where the query carries an endpoint the name match must agree on it
|
|
48
|
+
* too, since `slugifyName` folds case and every run of non-alphanumerics.
|
|
61
49
|
*/
|
|
62
|
-
export declare function
|
|
50
|
+
export declare function matchesMatchKeys(keys: string[], query: PlanMatchQuery): boolean;
|
|
63
51
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* for a config-only PR) there is nothing to backfill, so ADDITIONAL must not
|
|
69
|
-
* admit generation. Shared by planGuard and submitReportTool so the gate and
|
|
70
|
-
* the report cross-check can never disagree on admissibility.
|
|
52
|
+
* The planned tests whose scenarioName and testType match but whose planned steps
|
|
53
|
+
* name no endpoint the query addresses — the near miss a boolean cannot report,
|
|
54
|
+
* where the real disagreement is one step's path spelling. Empty when the query
|
|
55
|
+
* names no path.
|
|
71
56
|
*/
|
|
72
|
-
export declare function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
57
|
+
export declare function nameMatchedEndpointMismatches(plannedTests: PlannedTest[], query: PlanMatchQuery): PlannedTest[];
|
|
58
|
+
/** The planned tests whose scenarioName matches but whose testType does not — the
|
|
59
|
+
* plan holds this scenario and the call asked a different generator for it, so
|
|
60
|
+
* "not in the plan" is the wrong thing to say. Empty when the query names no
|
|
61
|
+
* type, since `nameAgrees` then matches on the name alone. */
|
|
62
|
+
export declare function nameMatchedTypeMismatches(plannedTests: PlannedTest[], query: PlanMatchQuery): PlannedTest[];
|
|
63
|
+
/** True when `query` matches at least one planned test of the registered plan.
|
|
64
|
+
* The keys are computed per call rather than stored with the plan, so there is
|
|
65
|
+
* one description of a planned test and no second shape to drift from it. */
|
|
66
|
+
export declare function matchesPlanCandidates(plannedTests: PlannedTest[], query: PlanMatchQuery): boolean;
|