@skyramp/mcp 0.3.4 → 0.3.6-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/playwright/registerPlaywrightTools.js +92 -30
- package/build/playwright/traceRecordingPrompt.d.ts +6 -0
- package/build/playwright/traceRecordingPrompt.js +6 -2
- package/build/prompts/code-reuse.d.ts +1 -2
- package/build/prompts/code-reuse.js +182 -77
- package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
- package/build/prompts/modularization/integration-test-modularization.js +83 -41
- package/build/prompts/modularization/render.d.ts +18 -0
- package/build/prompts/modularization/render.js +12 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
- package/build/prompts/modularization/ui-test-modularization.js +89 -47
- package/build/prompts/pom-aware-code-reuse.js +3 -1
- package/build/prompts/shared-helper-policy.d.ts +57 -0
- package/build/prompts/shared-helper-policy.js +135 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
- package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
- package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
- package/build/prompts/test-recommendation/recommendationShared.js +90 -16
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
- package/build/prompts/testbot/testbot-prompts.js +88 -33
- package/build/recommendation/budgeters/shared.js +105 -27
- package/build/recommendation/discriminators.js +13 -2
- package/build/recommendation/planRanker.d.ts +6 -6
- package/build/recommendation/planRanker.js +6 -61
- package/build/services/AnalyticsService.d.ts +7 -0
- package/build/services/AnalyticsService.js +7 -1
- package/build/services/ModularizationService.js +1 -3
- package/build/services/TestDiscoveryService.d.ts +0 -2
- package/build/services/TestDiscoveryService.js +2 -37
- package/build/services/TestGenerationService.d.ts +16 -0
- package/build/services/TestGenerationService.js +86 -10
- package/build/services/containerEnv.js +13 -12
- package/build/tools/code-refactor/codeReuseTool.js +279 -93
- package/build/tools/code-refactor/enhance-state.d.ts +49 -0
- package/build/tools/code-refactor/enhance-state.js +109 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
- package/build/tools/code-refactor/modularizationTool.js +9 -2
- package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
- package/build/tools/code-refactor/reuse-outcome.js +14 -4
- package/build/tools/code-refactor/reuse-state.d.ts +127 -5
- package/build/tools/code-refactor/reuse-state.js +628 -16
- package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
- package/build/tools/code-refactor/utils-verify-gates.js +100 -0
- package/build/tools/code-refactor/verify-gates.d.ts +2 -1
- package/build/tools/code-refactor/verify-gates.js +90 -25
- package/build/tools/executeSkyrampTestTool.d.ts +19 -0
- package/build/tools/executeSkyrampTestTool.js +158 -8
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateUIRestTool.js +22 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
- package/build/tools/generate-tests/scenarioLint.js +127 -19
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
- package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
- package/build/tools/submitReportTool.d.ts +38 -38
- package/build/tools/submitReportTool.js +487 -104
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +75 -12
- package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
- package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
- package/build/tools/test-management/registerTestPlanTool.js +149 -23
- package/build/types/Recommendation.d.ts +34 -5
- package/build/types/RepositoryAnalysis.d.ts +133 -114
- package/build/types/RepositoryAnalysis.js +1 -1
- package/build/types/ReuseOutcome.d.ts +102 -6
- package/build/types/ReuseOutcome.js +16 -2
- package/build/types/TestRecommendation.js +21 -3
- package/build/types/TestTypes.js +14 -8
- package/build/types/TestbotReport.d.ts +25 -3
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +69 -1
- package/build/utils/AnalysisStateManager.js +69 -5
- package/build/utils/branchDiff.d.ts +10 -0
- package/build/utils/branchDiff.js +28 -0
- package/build/utils/changedRoutes.d.ts +29 -0
- package/build/utils/changedRoutes.js +87 -0
- package/build/utils/featureFlags.d.ts +21 -0
- package/build/utils/featureFlags.js +23 -0
- package/build/utils/frontendIntegration.js +34 -4
- package/build/utils/importerHop.d.ts +2 -8
- package/build/utils/importerHop.js +15 -53
- package/build/utils/pathMatching.d.ts +38 -0
- package/build/utils/pathMatching.js +71 -0
- package/build/utils/pathSignatures.d.ts +22 -0
- package/build/utils/pathSignatures.js +57 -0
- package/build/utils/planMatchKeys.d.ts +16 -3
- package/build/utils/planMatchKeys.js +26 -10
- package/build/utils/pluralization.d.ts +10 -0
- package/build/utils/pluralization.js +18 -0
- package/build/utils/pom-catalog-parse.d.ts +52 -0
- package/build/utils/pom-catalog-parse.js +141 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
- package/build/utils/pom-scope/selector-extractor.js +34 -8
- package/build/utils/pom-verify/verify.d.ts +6 -5
- package/build/utils/pom-verify/verify.js +8 -6
- package/build/utils/reportLanguage.d.ts +43 -0
- package/build/utils/reportLanguage.js +125 -0
- package/build/utils/reportVerification.d.ts +74 -4
- package/build/utils/reportVerification.js +259 -3
- package/build/utils/reuseRouting.d.ts +3 -0
- package/build/utils/reuseRouting.js +50 -0
- package/build/utils/routeParsers.d.ts +2 -0
- package/build/utils/routeParsers.js +65 -8
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +57 -45
- package/build/utils/subjectEndpoints.d.ts +19 -0
- package/build/utils/subjectEndpoints.js +98 -0
- package/build/utils/testFileClassification.d.ts +11 -0
- package/build/utils/testFileClassification.js +47 -0
- package/build/utils/uiPageEnumerator.d.ts +45 -19
- package/build/utils/uiPageEnumerator.js +95 -51
- package/build/utils/utils-verify/allow.d.ts +16 -0
- package/build/utils/utils-verify/allow.js +68 -0
- package/build/utils/utils-verify/call-sites.d.ts +34 -0
- package/build/utils/utils-verify/call-sites.js +154 -0
- package/build/utils/utils-verify/index.d.ts +7 -0
- package/build/utils/utils-verify/index.js +7 -0
- package/build/utils/utils-verify/language-spec.d.ts +91 -0
- package/build/utils/utils-verify/language-spec.js +210 -0
- package/build/utils/utils-verify/locate.d.ts +39 -0
- package/build/utils/utils-verify/locate.js +199 -0
- package/build/utils/utils-verify/parse.d.ts +34 -0
- package/build/utils/utils-verify/parse.js +177 -0
- package/build/utils/utils-verify/stage.d.ts +24 -0
- package/build/utils/utils-verify/stage.js +107 -0
- package/build/utils/utils-verify/verify.d.ts +63 -0
- package/build/utils/utils-verify/verify.js +168 -0
- package/build/utils/utils.d.ts +3 -1
- package/build/utils/utils.js +3 -1
- package/build/workspace/workspace.d.ts +32 -32
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -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-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
- 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.VEfqi1qN.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- 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/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
* Next.js (app/ and pages/), Nuxt / Vue Router file-based, and SvelteKit.
|
|
15
15
|
* Gated on detection of a framework config file in the repo.
|
|
16
16
|
*
|
|
17
|
+
* Strategy 1b — Import graph
|
|
18
|
+
* For a changed file that is not itself a route (a component, a constants
|
|
19
|
+
* module), map the production files that import it through the same
|
|
20
|
+
* filesystem-route mapper. Uses the importer list the production-importer integration
|
|
21
|
+
* check already computed. Merged with strategy 1 rather than tried after it.
|
|
22
|
+
*
|
|
17
23
|
* Strategy 2 — Source-grounded routes
|
|
18
24
|
* Walks router/App files with the TS Compiler API for code-defined
|
|
19
25
|
* route declarations like `<Route path="/cart" element={<Cart/>}>`.
|
|
@@ -26,6 +32,10 @@
|
|
|
26
32
|
* Strategy 3 — Root fallback
|
|
27
33
|
* Read the workspace's frontend baseUrl and treat it as the single
|
|
28
34
|
* candidate page. Always available when a frontend service is configured.
|
|
35
|
+
*
|
|
36
|
+
* When no frontend baseUrl can be resolved, strategies 1/1b still run and their
|
|
37
|
+
* results are returned as bare URL paths (`baseUrlResolved: false`) so the agent
|
|
38
|
+
* can prefix the base URL it discovers; strategies 2–3 need a host and are skipped.
|
|
29
39
|
*/
|
|
30
40
|
import * as fs from "fs";
|
|
31
41
|
import * as path from "path";
|
|
@@ -33,6 +43,13 @@ import { extractDartRoutes } from "./dartRouteExtractor.js";
|
|
|
33
43
|
import { extractSourceRoutes } from "./sourceRouteExtractor.js";
|
|
34
44
|
import { hasFlutterSdkDep } from "../prompts/test-recommendation/scopeAssessment.js";
|
|
35
45
|
import { readWorkspaceConfigRaw } from "./workspaceAuth.js";
|
|
46
|
+
/**
|
|
47
|
+
* Upper bound on candidate pages. Strategy 1b is bounded by changed files ×
|
|
48
|
+
* importers, and every candidate costs the agent a navigate + a capture, so the
|
|
49
|
+
* list is cut here — direct route matches first, then import-graph pages that
|
|
50
|
+
* render the most changed files.
|
|
51
|
+
*/
|
|
52
|
+
export const MAX_CANDIDATE_PAGES = 10;
|
|
36
53
|
// ── Strategy 1: framework route grep ────────────────────────────────────────
|
|
37
54
|
/**
|
|
38
55
|
* File-system → URL mapping for filesystem-routed frameworks.
|
|
@@ -61,8 +78,9 @@ export function frameworkFileToUrlPath(filePath) {
|
|
|
61
78
|
return "/" + stripRouteGroups(nextAppMatch[1]).replace(/\[(\.\.\.)?(\w+)\]/g, ":$2");
|
|
62
79
|
}
|
|
63
80
|
// Next.js Pages Router: pages/foo.tsx → /foo, pages/index.tsx → /
|
|
64
|
-
// Excludes pages/api/* (
|
|
65
|
-
|
|
81
|
+
// Excludes pages/api/* (API handlers) and the framework files _app, _document,
|
|
82
|
+
// _error (they wrap every page and are not routes of their own).
|
|
83
|
+
const nextPagesMatch = normalized.match(/(?:^|\/)pages\/(?!api\/)(?!_(?:app|document|error)\.)(.+?)\.(tsx|jsx|ts|js)$/);
|
|
66
84
|
if (nextPagesMatch) {
|
|
67
85
|
let urlPath = nextPagesMatch[1];
|
|
68
86
|
// /index → /, /foo/index → /foo
|
|
@@ -140,43 +158,64 @@ export function detectsFilesystemRouting(repositoryPath) {
|
|
|
140
158
|
return false;
|
|
141
159
|
}
|
|
142
160
|
/**
|
|
143
|
-
*
|
|
144
|
-
* route
|
|
161
|
+
* Strategies 1 + 1b: map changed route files (1) and the route files that import
|
|
162
|
+
* changed non-route files (1b) to URL paths. Deduped by path; when both name the
|
|
163
|
+
* same path the strategy-1 entry wins and the extra source is appended.
|
|
145
164
|
*
|
|
146
165
|
* Suppressed when the repo doesn't have a recognized filesystem-routing
|
|
147
166
|
* framework config — see `detectsFilesystemRouting`. Without that guard,
|
|
148
167
|
* repos with conventional `pages/` directories but code-defined routes
|
|
149
168
|
* (e.g. React with React Router) produce false-positive URLs.
|
|
150
169
|
*/
|
|
151
|
-
|
|
170
|
+
function collectRoutePaths(frontendFiles, integration, repositoryPath) {
|
|
152
171
|
// When repositoryPath is provided, gate on framework detection. When
|
|
153
172
|
// omitted (e.g. older callers, unit tests), preserve original behavior.
|
|
154
173
|
if (repositoryPath !== undefined && !detectsFilesystemRouting(repositoryPath)) {
|
|
155
174
|
return [];
|
|
156
175
|
}
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
if (urlPath === null)
|
|
161
|
-
continue;
|
|
162
|
-
const normalizedBase = baseUrl.replace(/\/$/, "");
|
|
163
|
-
const fullUrl = normalizedBase + urlPath;
|
|
164
|
-
const existing = byUrl.get(fullUrl);
|
|
176
|
+
const byPath = new Map();
|
|
177
|
+
const add = (urlPath, sourceFile, strategy, via) => {
|
|
178
|
+
const existing = byPath.get(urlPath);
|
|
165
179
|
if (existing) {
|
|
166
|
-
// Same
|
|
167
|
-
if (!existing.sourcedFrom.includes(
|
|
168
|
-
existing.sourcedFrom.push(
|
|
169
|
-
|
|
180
|
+
// Same path surfaced by multiple files — track all sources.
|
|
181
|
+
if (!existing.sourcedFrom.includes(sourceFile))
|
|
182
|
+
existing.sourcedFrom.push(sourceFile);
|
|
183
|
+
if (via && existing.via && !existing.via.includes(via))
|
|
184
|
+
existing.via.push(via);
|
|
170
185
|
}
|
|
171
186
|
else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
187
|
+
byPath.set(urlPath, { path: urlPath, sourcedFrom: [sourceFile], strategy, ...(via ? { via: [via] } : {}) });
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
for (const file of frontendFiles) {
|
|
191
|
+
const urlPath = frameworkFileToUrlPath(file);
|
|
192
|
+
if (urlPath !== null)
|
|
193
|
+
add(urlPath, file, "framework-route-grep");
|
|
194
|
+
}
|
|
195
|
+
for (const entry of integration ?? []) {
|
|
196
|
+
if (!entry.integrated)
|
|
197
|
+
continue;
|
|
198
|
+
for (const importer of entry.importers) {
|
|
199
|
+
const urlPath = frameworkFileToUrlPath(importer);
|
|
200
|
+
if (urlPath !== null)
|
|
201
|
+
add(urlPath, entry.file, "import-graph", importer);
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
|
-
|
|
204
|
+
const all = Array.from(byPath.values());
|
|
205
|
+
const rank = (c) => (c.strategy === "framework-route-grep" ? 1 : 0) * 1000 + c.sourcedFrom.length;
|
|
206
|
+
return all.sort((a, b) => rank(b) - rank(a)).slice(0, MAX_CANDIDATE_PAGES);
|
|
207
|
+
}
|
|
208
|
+
function joinBaseUrl(baseUrl, candidate) {
|
|
209
|
+
return {
|
|
210
|
+
url: baseUrl.replace(/\/$/, "") + candidate.path,
|
|
211
|
+
sourcedFrom: candidate.sourcedFrom,
|
|
212
|
+
strategy: candidate.strategy,
|
|
213
|
+
...(candidate.via ? { via: candidate.via } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** Strategy 1 only (no import graph), joined to `baseUrl`. */
|
|
217
|
+
export function findCandidatePagesByFrameworkRoute(frontendFiles, baseUrl, repositoryPath) {
|
|
218
|
+
return collectRoutePaths(frontendFiles, undefined, repositoryPath).map((c) => joinBaseUrl(baseUrl, c));
|
|
180
219
|
}
|
|
181
220
|
// ── Strategy 2: source-grounded routes ──────────────────────────────────────
|
|
182
221
|
/**
|
|
@@ -302,13 +341,10 @@ export function findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendF
|
|
|
302
341
|
*/
|
|
303
342
|
export async function findRootFallbackPage(repositoryPath) {
|
|
304
343
|
const baseUrl = await pickFrontendBaseUrl(repositoryPath);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
sourcedFrom: [],
|
|
310
|
-
strategy: "root-fallback",
|
|
311
|
-
};
|
|
344
|
+
return baseUrl ? rootFallbackPage(baseUrl) : null;
|
|
345
|
+
}
|
|
346
|
+
function rootFallbackPage(baseUrl) {
|
|
347
|
+
return { url: baseUrl, sourcedFrom: [], strategy: "root-fallback" };
|
|
312
348
|
}
|
|
313
349
|
/**
|
|
314
350
|
* Mirror of testbot.git src/services.ts exportServiceBaseUrlEnvVars sanitize().
|
|
@@ -369,19 +405,21 @@ function pickFrontendService(services) {
|
|
|
369
405
|
* is the picked frontend service's serviceName, sanitized via
|
|
370
406
|
* `sanitizeServiceName`)
|
|
371
407
|
* 2. Global override: `SKYRAMP_TEST_BASE_URL` (single service or all
|
|
372
|
-
* services share one URL)
|
|
408
|
+
* services share one URL). Also honored when the workspace declares no
|
|
409
|
+
* services at all — the action can know the URL without a workspace entry.
|
|
373
410
|
* 3. Static workspace.yml value: `api.baseUrl` from the picked frontend
|
|
374
411
|
* service
|
|
375
412
|
*
|
|
376
|
-
* Returns null when
|
|
413
|
+
* Returns null when neither the environment nor any service has a usable URL.
|
|
377
414
|
*
|
|
378
415
|
* Exported (vs private) so analyze_changes and future tools can reuse the
|
|
379
416
|
* same selection without re-implementing it.
|
|
380
417
|
*/
|
|
381
418
|
export async function pickFrontendBaseUrl(repositoryPath) {
|
|
382
419
|
const config = await readWorkspaceConfigRaw(repositoryPath);
|
|
420
|
+
const global = process.env.SKYRAMP_TEST_BASE_URL?.trim();
|
|
383
421
|
if (!config?.services || !Array.isArray(config.services))
|
|
384
|
-
return null;
|
|
422
|
+
return global || null;
|
|
385
423
|
const picked = pickFrontendService(config.services);
|
|
386
424
|
// 1. Per-service override (multi-service, distinct URLs).
|
|
387
425
|
// readWorkspaceConfigRaw may fall back to unvalidated YAML, so serviceName
|
|
@@ -392,7 +430,6 @@ export async function pickFrontendBaseUrl(repositoryPath) {
|
|
|
392
430
|
return perSvc;
|
|
393
431
|
}
|
|
394
432
|
// 2. Global override (single service / all services share a URL).
|
|
395
|
-
const global = process.env.SKYRAMP_TEST_BASE_URL?.trim();
|
|
396
433
|
if (global)
|
|
397
434
|
return global;
|
|
398
435
|
// 3. Static workspace.yml value.
|
|
@@ -400,31 +437,38 @@ export async function pickFrontendBaseUrl(repositoryPath) {
|
|
|
400
437
|
}
|
|
401
438
|
// ── Composer ────────────────────────────────────────────────────────────────
|
|
402
439
|
/**
|
|
403
|
-
* Run the strategy ladder
|
|
404
|
-
* routes (2) → root fallback (3). Each
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
440
|
+
* Run the strategy ladder: framework route grep + import graph (1/1b) →
|
|
441
|
+
* source-grounded routes (2) → Dart GoRouter (2.5) → root fallback (3). Each
|
|
442
|
+
* later strategy is consulted only if the earlier ones produced no candidates.
|
|
443
|
+
*
|
|
444
|
+
* `integration` is the production-importer check for the same files; when given,
|
|
445
|
+
* changed non-route files resolve to the pages that import them.
|
|
446
|
+
*
|
|
447
|
+
* Without a resolvable frontend baseUrl only strategies 1/1b can run; their
|
|
448
|
+
* paths are returned with `baseUrlResolved: false` for the agent to prefix.
|
|
449
|
+
* Returns empty only when nothing resolves at all.
|
|
408
450
|
*/
|
|
409
|
-
export async function enumerateCandidateUiPages(repositoryPath, frontendFiles) {
|
|
451
|
+
export async function enumerateCandidateUiPages(repositoryPath, frontendFiles, integration) {
|
|
410
452
|
if (frontendFiles.length === 0)
|
|
411
453
|
return [];
|
|
412
454
|
const baseUrl = await pickFrontendBaseUrl(repositoryPath);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
455
|
+
const routePaths = collectRoutePaths(frontendFiles, integration, repositoryPath);
|
|
456
|
+
if (!baseUrl) {
|
|
457
|
+
return routePaths.map((c) => ({
|
|
458
|
+
url: c.path,
|
|
459
|
+
sourcedFrom: c.sourcedFrom,
|
|
460
|
+
strategy: c.strategy,
|
|
461
|
+
baseUrlResolved: false,
|
|
462
|
+
...(c.via ? { via: c.via } : {}),
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
if (routePaths.length > 0)
|
|
466
|
+
return routePaths.map((c) => joinBaseUrl(baseUrl, c));
|
|
422
467
|
const fromSource = findCandidatePagesBySourceRoute(frontendFiles, baseUrl, repositoryPath);
|
|
423
468
|
if (fromSource.length > 0)
|
|
424
469
|
return fromSource;
|
|
425
470
|
const fromDart = findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendFiles);
|
|
426
471
|
if (fromDart.length > 0)
|
|
427
472
|
return fromDart;
|
|
428
|
-
|
|
429
|
-
return fallback ? [fallback] : [];
|
|
473
|
+
return [rootFallbackPage(baseUrl)];
|
|
430
474
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { UtilsLanguageSpec } from "./language-spec.js";
|
|
2
|
+
export type UtilsViolationKind = "duplicate-helper" | "body-assertion" | "scenario-name";
|
|
3
|
+
export declare const UTILS_VIOLATION_KINDS: readonly UtilsViolationKind[];
|
|
4
|
+
export interface UtilsAllow {
|
|
5
|
+
kind: UtilsViolationKind;
|
|
6
|
+
helper: string;
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
/** The exact line to paste for a violation — what the failure text hands the agent. */
|
|
10
|
+
export declare function allowMarkerLine(spec: UtilsLanguageSpec, kind: UtilsViolationKind, helper: string): string;
|
|
11
|
+
export declare function parseUtilsAllows(content: string, spec: UtilsLanguageSpec): {
|
|
12
|
+
allows: UtilsAllow[];
|
|
13
|
+
/** Marker lines the loose pattern recognises but the grammar cannot read — a decline
|
|
14
|
+
* the gate would otherwise drop silently. Reported back so the agent fixes the line. */
|
|
15
|
+
malformed: string[];
|
|
16
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { blankStrings } from "./parse.js";
|
|
2
|
+
export const UTILS_VIOLATION_KINDS = [
|
|
3
|
+
"duplicate-helper",
|
|
4
|
+
"body-assertion",
|
|
5
|
+
"scenario-name",
|
|
6
|
+
];
|
|
7
|
+
/**
|
|
8
|
+
* The documented-decline marker for a utils-file invariant, written in the utils file:
|
|
9
|
+
*
|
|
10
|
+
* # reuse-verify: allow <kind> <helper> — <reason> (Python)
|
|
11
|
+
* // reuse-verify: allow <kind> <helper> — <reason> (TS/JS)
|
|
12
|
+
*
|
|
13
|
+
* ONE grammar, used by both the gate and the report. The POM path's `// kept inline:`
|
|
14
|
+
* marker had two — the gate's regex and the report parser disagreed on the separator
|
|
15
|
+
* and on the file extension — and the gap was a run where the gate blocked execution
|
|
16
|
+
* on declines the report listed correctly (mcp#716, run 30965653402). Here the gate
|
|
17
|
+
* and the report call this one function.
|
|
18
|
+
*
|
|
19
|
+
* The separator is an em/en dash or a hyphen with whitespace on BOTH sides, so a
|
|
20
|
+
* hyphen inside a helper name cannot be mistaken for it. `(?!<reason)` rejects the
|
|
21
|
+
* paste-ready line from the failure text when it was pasted unedited: publishing the
|
|
22
|
+
* placeholder as the stated reason is worse than no marker.
|
|
23
|
+
*/
|
|
24
|
+
const ALLOW_RE = /reuse-verify:\s*allow\s+(duplicate-helper|body-assertion|scenario-name)\s+([A-Za-z_$][\w$]*)\s*(?:[—–]+|\s+-+\s+)\s*(?!<reason)(\S.*?)\s*$/;
|
|
25
|
+
const ALLOW_LOOSE_RE = /reuse-verify:\s*allow\b/;
|
|
26
|
+
/** The exact line to paste for a violation — what the failure text hands the agent. */
|
|
27
|
+
export function allowMarkerLine(spec, kind, helper) {
|
|
28
|
+
return `${spec.commentPrefix} reuse-verify: allow ${kind} ${helper} — <reason this helper must stay as it is>`;
|
|
29
|
+
}
|
|
30
|
+
export function parseUtilsAllows(content, spec) {
|
|
31
|
+
const allows = [];
|
|
32
|
+
const malformed = [];
|
|
33
|
+
// String contents are blanked first, so a marker (or a comment token) quoted in
|
|
34
|
+
// code can neither be read as a decline nor make one look like a comment.
|
|
35
|
+
const rawLines = content.split("\n");
|
|
36
|
+
const codeLines = blankStrings(content, spec).split("\n");
|
|
37
|
+
for (let n = 0; n < rawLines.length; n++) {
|
|
38
|
+
const raw = rawLines[n];
|
|
39
|
+
const code = codeLines[n] ?? "";
|
|
40
|
+
// The marker may sit in a trailing comment, a block comment or a JSDoc line — any
|
|
41
|
+
// comment form. Match from the marker itself, so the comment syntax around it does
|
|
42
|
+
// not decide whether a decline is read.
|
|
43
|
+
const at = code.search(ALLOW_LOOSE_RE);
|
|
44
|
+
if (at === -1)
|
|
45
|
+
continue;
|
|
46
|
+
const before = code.slice(0, at);
|
|
47
|
+
const inComment = spec.commentPrefix === "#"
|
|
48
|
+
? /#/.test(before)
|
|
49
|
+
: /\/\/|\/\*|^\s*\*/.test(before);
|
|
50
|
+
if (!inComment)
|
|
51
|
+
continue;
|
|
52
|
+
const line = raw
|
|
53
|
+
.slice(at)
|
|
54
|
+
.replace(/\s*\*\/\s*$/, "")
|
|
55
|
+
.trim();
|
|
56
|
+
const m = ALLOW_RE.exec(line);
|
|
57
|
+
if (!m) {
|
|
58
|
+
malformed.push(raw.trim());
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
allows.push({
|
|
62
|
+
kind: m[1],
|
|
63
|
+
helper: m[2],
|
|
64
|
+
reason: m[3],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { allows, malformed };
|
|
68
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type UtilsHelper } from "./parse.js";
|
|
2
|
+
import { type UtilsLanguageSpec } from "./language-spec.js";
|
|
3
|
+
/** The codegen marker every Skyramp-GENERATED TEST carries on line 1 (`Generated by
|
|
4
|
+
* Skyramp v<version>`), as opposed to the utils header (`Generated by Skyramp on …`).
|
|
5
|
+
* The same discriminator STEP 4 of the reuse prompt hands the agent. */
|
|
6
|
+
export declare const CODEGEN_MARKER_RE: RegExp;
|
|
7
|
+
export interface InlineCallSite {
|
|
8
|
+
/** Absolute path of the sibling test. */
|
|
9
|
+
file: string;
|
|
10
|
+
/** 1-based line of the request call. */
|
|
11
|
+
line: number;
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
/** The utils helper that wraps the same method+path. */
|
|
15
|
+
helper: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Inline request calls in OTHER Skyramp-generated tests beside `testFile` that a
|
|
19
|
+
* utils helper already wraps — the reuse the prompt asks for and the agent does not
|
|
20
|
+
* perform (measured 0/2 seeded eval runs with the rule as its own mandatory step).
|
|
21
|
+
*
|
|
22
|
+
* A call is INLINE when it sits in a test function, not in a module-level helper of
|
|
23
|
+
* the sibling's own: a sibling that defines `create_order` locally is STEP 4b's
|
|
24
|
+
* helper-vs-helper case, not a call site. Equivalence is `c3b9157b`'s: same method
|
|
25
|
+
* and same normalised path, nothing else — literals are what the call's arguments
|
|
26
|
+
* are for.
|
|
27
|
+
*
|
|
28
|
+
* Advisory by design: it names a change to a PRE-EXISTING test, which the maintenance
|
|
29
|
+
* flow owns, so it informs rather than gates. Never throws.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findSiblingInlineCallSites(testFile: string, utilsFiles: string[], _helpers: UtilsHelper[], spec: UtilsLanguageSpec): Promise<InlineCallSite[]>;
|
|
32
|
+
/** The keyword names of a request call (`path=`, `method=`, `query_params=` … / TS
|
|
33
|
+
* object keys), less the liftable ones — a cheap normalised call shape. */
|
|
34
|
+
export declare function shapeKeys(call: string): Set<string>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { SKYRAMP_UTILS_HEADER } from "../utils.js";
|
|
4
|
+
import { parseUtilsFile } from "./parse.js";
|
|
5
|
+
import { normalizePath } from "./language-spec.js";
|
|
6
|
+
import { realpath } from "./locate.js";
|
|
7
|
+
/** The codegen marker every Skyramp-GENERATED TEST carries on line 1 (`Generated by
|
|
8
|
+
* Skyramp v<version>`), as opposed to the utils header (`Generated by Skyramp on …`).
|
|
9
|
+
* The same discriminator STEP 4 of the reuse prompt hands the agent. */
|
|
10
|
+
export const CODEGEN_MARKER_RE = new RegExp(`${SKYRAMP_UTILS_HEADER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+v\\d`);
|
|
11
|
+
const HEADER_SCAN_LINES = 5;
|
|
12
|
+
const REQUEST_CALL_RE = /\b(?:send_request|sendRequest)\s*\(/g;
|
|
13
|
+
/**
|
|
14
|
+
* Inline request calls in OTHER Skyramp-generated tests beside `testFile` that a
|
|
15
|
+
* utils helper already wraps — the reuse the prompt asks for and the agent does not
|
|
16
|
+
* perform (measured 0/2 seeded eval runs with the rule as its own mandatory step).
|
|
17
|
+
*
|
|
18
|
+
* A call is INLINE when it sits in a test function, not in a module-level helper of
|
|
19
|
+
* the sibling's own: a sibling that defines `create_order` locally is STEP 4b's
|
|
20
|
+
* helper-vs-helper case, not a call site. Equivalence is `c3b9157b`'s: same method
|
|
21
|
+
* and same normalised path, nothing else — literals are what the call's arguments
|
|
22
|
+
* are for.
|
|
23
|
+
*
|
|
24
|
+
* Advisory by design: it names a change to a PRE-EXISTING test, which the maintenance
|
|
25
|
+
* flow owns, so it informs rather than gates. Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export async function findSiblingInlineCallSites(testFile, utilsFiles, _helpers, spec) {
|
|
28
|
+
// route → helper name + the keyword set of the helper's own request call, so a
|
|
29
|
+
// sibling call is reported only when its SHAPE matches too (same keywords once the
|
|
30
|
+
// liftable ones are set aside) — criterion 2 of the API near-duplicate definition.
|
|
31
|
+
const byRoute = new Map();
|
|
32
|
+
for (const utilsFile of utilsFiles) {
|
|
33
|
+
let content;
|
|
34
|
+
try {
|
|
35
|
+
content = await fs.promises.readFile(utilsFile, "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Owners come from THIS file's own parse: `helpers` spans every utils file, and
|
|
41
|
+
// a line range alone would attribute file B's request to file A's helper.
|
|
42
|
+
const fileHelpers = parseUtilsFile(content, spec);
|
|
43
|
+
for (const m of content.matchAll(REQUEST_CALL_RE)) {
|
|
44
|
+
const start = m.index ?? 0;
|
|
45
|
+
const line = content.slice(0, start).split("\n").length;
|
|
46
|
+
const owner = fileHelpers.find((h) => h.line <= line && line <= h.endLine);
|
|
47
|
+
if (!owner?.method || !owner.normalizedPath)
|
|
48
|
+
continue;
|
|
49
|
+
const call = callText(content, start + m[0].length - 1);
|
|
50
|
+
byRoute.set(`${owner.method} ${owner.normalizedPath}`, {
|
|
51
|
+
helper: owner.name,
|
|
52
|
+
keys: shapeKeys(call),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (byRoute.size === 0)
|
|
57
|
+
return [];
|
|
58
|
+
// Canonical, like the utils paths: otherwise a tool-supplied /tmp path and a
|
|
59
|
+
// realpath'd /private/tmp utils file look like different directories to the renderer.
|
|
60
|
+
const dir = path.dirname(await realpath(testFile));
|
|
61
|
+
// Canonical like `dir`, or a spec reached through a symlink is reported as its
|
|
62
|
+
// own sibling.
|
|
63
|
+
const exclude = new Set(await Promise.all([testFile, ...utilsFiles].map((f) => realpath(f))));
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = await fs.promises.readdir(dir);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const name of entries.sort()) {
|
|
73
|
+
const file = path.join(dir, name);
|
|
74
|
+
if (exclude.has(path.resolve(file)))
|
|
75
|
+
continue;
|
|
76
|
+
if (!spec.extensions.some((e) => name.toLowerCase().endsWith(e)))
|
|
77
|
+
continue;
|
|
78
|
+
let content;
|
|
79
|
+
try {
|
|
80
|
+
content = await fs.promises.readFile(file, "utf8");
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!CODEGEN_MARKER_RE.test(content.split("\n").slice(0, HEADER_SCAN_LINES).join("\n")))
|
|
86
|
+
continue;
|
|
87
|
+
// Line ranges of the sibling's own non-test helpers: calls inside them are not inline.
|
|
88
|
+
const localHelpers = parseUtilsFile(content, spec).filter((h) => !/^test/i.test(h.name));
|
|
89
|
+
const inLocalHelper = (line) => localHelpers.some((h) => line >= h.line && line <= h.endLine);
|
|
90
|
+
for (const m of content.matchAll(REQUEST_CALL_RE)) {
|
|
91
|
+
const start = m.index ?? 0;
|
|
92
|
+
const line = content.slice(0, start).split("\n").length;
|
|
93
|
+
if (inLocalHelper(line))
|
|
94
|
+
continue;
|
|
95
|
+
const call = callText(content, start + m[0].length - 1);
|
|
96
|
+
const method = spec.methodRe.exec(call)?.[1]?.toUpperCase();
|
|
97
|
+
const p = spec.pathRe.exec(call)?.[1];
|
|
98
|
+
if (!method || !p)
|
|
99
|
+
continue;
|
|
100
|
+
const match = byRoute.get(`${method} ${normalizePath(p)}`);
|
|
101
|
+
if (!match)
|
|
102
|
+
continue;
|
|
103
|
+
if (!sameShape(shapeKeys(call), match.keys))
|
|
104
|
+
continue;
|
|
105
|
+
out.push({ file, line, method, path: p, helper: match.helper });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/** Keywords whose presence differs legitimately between two versions of one request:
|
|
111
|
+
* the literals the merge rule lifts to parameters. Everything else is call shape. */
|
|
112
|
+
const LIFTABLE_KEYS = new Set([
|
|
113
|
+
"body",
|
|
114
|
+
"data",
|
|
115
|
+
"data_override",
|
|
116
|
+
"dataOverride",
|
|
117
|
+
"headers",
|
|
118
|
+
"url",
|
|
119
|
+
"path_params",
|
|
120
|
+
"pathParams",
|
|
121
|
+
]);
|
|
122
|
+
/** The keyword names of a request call (`path=`, `method=`, `query_params=` … / TS
|
|
123
|
+
* object keys), less the liftable ones — a cheap normalised call shape. */
|
|
124
|
+
export function shapeKeys(call) {
|
|
125
|
+
const keys = new Set();
|
|
126
|
+
for (const m of call.matchAll(/\b([A-Za-z_]\w*)\s*[=:](?![=])/g)) {
|
|
127
|
+
if (!LIFTABLE_KEYS.has(m[1]))
|
|
128
|
+
keys.add(m[1]);
|
|
129
|
+
}
|
|
130
|
+
return keys;
|
|
131
|
+
}
|
|
132
|
+
function sameShape(a, b) {
|
|
133
|
+
if (a.size !== b.size)
|
|
134
|
+
return false;
|
|
135
|
+
for (const k of a)
|
|
136
|
+
if (!b.has(k))
|
|
137
|
+
return false;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
/** Text of the parenthesised argument list starting at the `(` at `open`. */
|
|
141
|
+
function callText(content, open) {
|
|
142
|
+
let depth = 0;
|
|
143
|
+
for (let i = open; i < content.length; i++) {
|
|
144
|
+
const ch = content[i];
|
|
145
|
+
if (ch === "(")
|
|
146
|
+
depth++;
|
|
147
|
+
else if (ch === ")") {
|
|
148
|
+
depth--;
|
|
149
|
+
if (depth === 0)
|
|
150
|
+
return content.slice(open, i + 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return content.slice(open);
|
|
154
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-parametric description of a shared SkyrampUtils file, and the invariants
|
|
3
|
+
* every helper in it must hold. Encoded as DATA rather than as per-language checks so
|
|
4
|
+
* that adding a language (or a helper family with a different assertion rule, e.g.
|
|
5
|
+
* browser helpers) is a table entry, not a second verifier.
|
|
6
|
+
*
|
|
7
|
+
* The invariants come from the prompts that author these files — the HELPER SHAPE
|
|
8
|
+
* block in the integration modularization prompt and the API near-duplicate rules in
|
|
9
|
+
* the reuse prompt — and exist here because prompt rules are LLM-enforced only:
|
|
10
|
+
* SKYR-4196 Part B runs 5 and 11 leaked a scenario-literal body assertion into a shared
|
|
11
|
+
* helper despite both rules being in the prompt.
|
|
12
|
+
*/
|
|
13
|
+
export type UtilsLanguage = "python" | "typescript" | "javascript";
|
|
14
|
+
export interface UtilsLanguageSpec {
|
|
15
|
+
language: UtilsLanguage;
|
|
16
|
+
/** File extensions (with dot) a utils file in this language can carry. */
|
|
17
|
+
extensions: string[];
|
|
18
|
+
commentPrefix: "#" | "//";
|
|
19
|
+
/** Matches the FIRST line of a module-level helper definition; group 1 is the name. */
|
|
20
|
+
helperStart: RegExp;
|
|
21
|
+
/** `method="POST"` / `method: "POST"` inside a helper body; group 1 is the verb. */
|
|
22
|
+
methodRe: RegExp;
|
|
23
|
+
/** `path="/api/…"` / `path: "/…"` inside a helper body; group 1 is the path. */
|
|
24
|
+
pathRe: RegExp;
|
|
25
|
+
/** A line that is an assertion at all. */
|
|
26
|
+
assertionRe: RegExp;
|
|
27
|
+
/** The one assertion shape an API helper may carry: a status-code comparison. Any
|
|
28
|
+
* assertion line not matching this is a body assertion — the scenario-specific
|
|
29
|
+
* claim that fails the next scenario calling the helper. */
|
|
30
|
+
allowedAssertion: RegExp;
|
|
31
|
+
/** The assertions a BROWSER helper may carry: structural waits that an element
|
|
32
|
+
* rendered / is interactable (`toBeVisible`, `toBeEnabled`, …). Value assertions
|
|
33
|
+
* (`toHaveText`, `toHaveValue`, `toHaveCount`, `toHaveURL`, any `expect` on a
|
|
34
|
+
* literal) are the browser analogue of a body assertion. Mirrors the
|
|
35
|
+
* BROWSER_HELPERS policy the modularization and reuse prompts state. */
|
|
36
|
+
allowedBrowserAssertion: RegExp;
|
|
37
|
+
/** Names `specContent` imports from the utils module whose basename stem is `stem`. */
|
|
38
|
+
importedNames(specContent: string, stem: string): string[];
|
|
39
|
+
}
|
|
40
|
+
/** Path parameters spelled `{id}`, `${id}`, `:id` all normalise to `{}` so two helpers
|
|
41
|
+
* for `/orders/{order_id}` and `/orders/{id}` compare equal. */
|
|
42
|
+
export declare function normalizePath(p: string): string;
|
|
43
|
+
export declare const UTILS_LANGUAGE_SPECS: Record<UtilsLanguage, UtilsLanguageSpec>;
|
|
44
|
+
/**
|
|
45
|
+
* The two kinds of shared helper, keyed by the test type's substrate — the same
|
|
46
|
+
* split the modularization and reuse prompts make (API_HELPERS / BROWSER_HELPERS):
|
|
47
|
+
* - `api` (integration, contract, load …): one SDK request; equivalence is
|
|
48
|
+
* method+path; the only assertion is the status code.
|
|
49
|
+
* - `browser` (ui, e2e): a Playwright action sequence; no route to key duplicates
|
|
50
|
+
* on, so the duplicate check is inert; the only assertions are structural waits.
|
|
51
|
+
*/
|
|
52
|
+
export type HelperFamily = "api" | "browser";
|
|
53
|
+
export interface HelperFamilySpec {
|
|
54
|
+
family: HelperFamily;
|
|
55
|
+
/** Whether two helpers wrapping the same method+path are a violation. */
|
|
56
|
+
routeDuplicates: boolean;
|
|
57
|
+
/** Picks the permitted-assertion pattern out of a language spec. */
|
|
58
|
+
allowedAssertion(spec: UtilsLanguageSpec): RegExp;
|
|
59
|
+
/** Whether one complete assertion expression is permitted for this family. */
|
|
60
|
+
isAllowed(spec: UtilsLanguageSpec, expr: string): boolean;
|
|
61
|
+
/** One clause for the failure text: what a helper of this family may assert. */
|
|
62
|
+
assertionRule: string;
|
|
63
|
+
}
|
|
64
|
+
export declare const HELPER_FAMILIES: Record<HelperFamily, HelperFamilySpec>;
|
|
65
|
+
/** The helper family a test type's shared helpers follow. Unlike `isBrowserTestType`,
|
|
66
|
+
* an ABSENT test type resolves to `api`: this runs against a utils file that already
|
|
67
|
+
* exists, and the browser rules are the more permissive of the two on duplicates, so
|
|
68
|
+
* guessing "browser" would silently switch a check off. */
|
|
69
|
+
export declare function helperFamilyFor(testType: string | undefined): HelperFamilySpec;
|
|
70
|
+
/** The spec for a language string as the tools receive it, or `undefined` for a
|
|
71
|
+
* language this verifier does not cover (Java: utils reuse is disabled for it). */
|
|
72
|
+
export declare function utilsSpecFor(language: string | undefined): UtilsLanguageSpec | undefined;
|
|
73
|
+
/** The spec whose extensions cover `file`, for callers that know a path but not a language. */
|
|
74
|
+
export declare function utilsSpecForFile(file: string): UtilsLanguageSpec | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Helper names are `<verb>_<resource>` only (`create_order`, `update_order`); scenario
|
|
77
|
+
* data belongs in arguments. A name carrying a scenario adjective or a literal is the
|
|
78
|
+
* signature of a helper that will not merge with the same step from the next test
|
|
79
|
+
* (`update_order_50_discount` beside `update_order_discount`, SKYR-4196 run 4).
|
|
80
|
+
*
|
|
81
|
+
* Tokens are matched after splitting on `_` and camelCase boundaries, so the rule
|
|
82
|
+
* applies unchanged to Python and TS/JS names.
|
|
83
|
+
*/
|
|
84
|
+
export declare const SCENARIO_NAME_RULE: {
|
|
85
|
+
forbiddenTokens: Set<string>;
|
|
86
|
+
/** A bare number as its OWN token — `update_order_50_discount` — never a digit inside
|
|
87
|
+
* an identifier (`s3`, `oauth2`, `ipv4`, `sha256`, `base64`, `iso8601`, `2fa`). */
|
|
88
|
+
digitToken: RegExp;
|
|
89
|
+
};
|
|
90
|
+
/** The reason a name is scenario-flavoured, or `undefined` when it is canonical. */
|
|
91
|
+
export declare function scenarioNameViolation(name: string): string | undefined;
|