@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.
Files changed (159) hide show
  1. package/build/playwright/registerPlaywrightTools.js +92 -30
  2. package/build/playwright/traceRecordingPrompt.d.ts +6 -0
  3. package/build/playwright/traceRecordingPrompt.js +6 -2
  4. package/build/prompts/code-reuse.d.ts +1 -2
  5. package/build/prompts/code-reuse.js +182 -77
  6. package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
  7. package/build/prompts/modularization/integration-test-modularization.js +83 -41
  8. package/build/prompts/modularization/render.d.ts +18 -0
  9. package/build/prompts/modularization/render.js +12 -0
  10. package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
  11. package/build/prompts/modularization/ui-test-modularization.js +89 -47
  12. package/build/prompts/pom-aware-code-reuse.js +3 -1
  13. package/build/prompts/shared-helper-policy.d.ts +57 -0
  14. package/build/prompts/shared-helper-policy.js +135 -0
  15. package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
  16. package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
  17. package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
  18. package/build/prompts/test-recommendation/recommendationShared.js +90 -16
  19. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
  22. package/build/prompts/testbot/testbot-prompts.js +88 -33
  23. package/build/recommendation/budgeters/shared.js +105 -27
  24. package/build/recommendation/discriminators.js +13 -2
  25. package/build/recommendation/planRanker.d.ts +6 -6
  26. package/build/recommendation/planRanker.js +6 -61
  27. package/build/services/AnalyticsService.d.ts +7 -0
  28. package/build/services/AnalyticsService.js +7 -1
  29. package/build/services/ModularizationService.js +1 -3
  30. package/build/services/TestDiscoveryService.d.ts +0 -2
  31. package/build/services/TestDiscoveryService.js +2 -37
  32. package/build/services/TestGenerationService.d.ts +16 -0
  33. package/build/services/TestGenerationService.js +86 -10
  34. package/build/services/containerEnv.js +13 -12
  35. package/build/tools/code-refactor/codeReuseTool.js +279 -93
  36. package/build/tools/code-refactor/enhance-state.d.ts +49 -0
  37. package/build/tools/code-refactor/enhance-state.js +109 -0
  38. package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
  39. package/build/tools/code-refactor/modularizationTool.js +9 -2
  40. package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
  41. package/build/tools/code-refactor/reuse-outcome.js +14 -4
  42. package/build/tools/code-refactor/reuse-state.d.ts +127 -5
  43. package/build/tools/code-refactor/reuse-state.js +628 -16
  44. package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
  45. package/build/tools/code-refactor/utils-verify-gates.js +100 -0
  46. package/build/tools/code-refactor/verify-gates.d.ts +2 -1
  47. package/build/tools/code-refactor/verify-gates.js +90 -25
  48. package/build/tools/executeSkyrampTestTool.d.ts +19 -0
  49. package/build/tools/executeSkyrampTestTool.js +158 -8
  50. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
  51. package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
  52. package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
  53. package/build/tools/generate-tests/generateUIRestTool.js +22 -0
  54. package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
  55. package/build/tools/generate-tests/scenarioLint.js +127 -19
  56. package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
  57. package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
  58. package/build/tools/submitReportTool.d.ts +38 -38
  59. package/build/tools/submitReportTool.js +487 -104
  60. package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
  61. package/build/tools/test-management/analyzeChangesTool.js +75 -12
  62. package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
  63. package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
  64. package/build/tools/test-management/registerTestPlanTool.js +149 -23
  65. package/build/types/Recommendation.d.ts +34 -5
  66. package/build/types/RepositoryAnalysis.d.ts +133 -114
  67. package/build/types/RepositoryAnalysis.js +1 -1
  68. package/build/types/ReuseOutcome.d.ts +102 -6
  69. package/build/types/ReuseOutcome.js +16 -2
  70. package/build/types/TestRecommendation.js +21 -3
  71. package/build/types/TestTypes.js +14 -8
  72. package/build/types/TestbotReport.d.ts +25 -3
  73. package/build/types/index.d.ts +2 -2
  74. package/build/types/index.js +1 -1
  75. package/build/utils/AnalysisStateManager.d.ts +69 -1
  76. package/build/utils/AnalysisStateManager.js +69 -5
  77. package/build/utils/branchDiff.d.ts +10 -0
  78. package/build/utils/branchDiff.js +28 -0
  79. package/build/utils/changedRoutes.d.ts +29 -0
  80. package/build/utils/changedRoutes.js +87 -0
  81. package/build/utils/featureFlags.d.ts +21 -0
  82. package/build/utils/featureFlags.js +23 -0
  83. package/build/utils/frontendIntegration.js +34 -4
  84. package/build/utils/importerHop.d.ts +2 -8
  85. package/build/utils/importerHop.js +15 -53
  86. package/build/utils/pathMatching.d.ts +38 -0
  87. package/build/utils/pathMatching.js +71 -0
  88. package/build/utils/pathSignatures.d.ts +22 -0
  89. package/build/utils/pathSignatures.js +57 -0
  90. package/build/utils/planMatchKeys.d.ts +16 -3
  91. package/build/utils/planMatchKeys.js +26 -10
  92. package/build/utils/pluralization.d.ts +10 -0
  93. package/build/utils/pluralization.js +18 -0
  94. package/build/utils/pom-catalog-parse.d.ts +52 -0
  95. package/build/utils/pom-catalog-parse.js +141 -0
  96. package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
  97. package/build/utils/pom-scope/selector-extractor.js +34 -8
  98. package/build/utils/pom-verify/verify.d.ts +6 -5
  99. package/build/utils/pom-verify/verify.js +8 -6
  100. package/build/utils/reportLanguage.d.ts +43 -0
  101. package/build/utils/reportLanguage.js +125 -0
  102. package/build/utils/reportVerification.d.ts +74 -4
  103. package/build/utils/reportVerification.js +259 -3
  104. package/build/utils/reuseRouting.d.ts +3 -0
  105. package/build/utils/reuseRouting.js +50 -0
  106. package/build/utils/routeParsers.d.ts +2 -0
  107. package/build/utils/routeParsers.js +65 -8
  108. package/build/utils/scenarioDrafting.d.ts +1 -1
  109. package/build/utils/scenarioDrafting.js +57 -45
  110. package/build/utils/subjectEndpoints.d.ts +19 -0
  111. package/build/utils/subjectEndpoints.js +98 -0
  112. package/build/utils/testFileClassification.d.ts +11 -0
  113. package/build/utils/testFileClassification.js +47 -0
  114. package/build/utils/uiPageEnumerator.d.ts +45 -19
  115. package/build/utils/uiPageEnumerator.js +95 -51
  116. package/build/utils/utils-verify/allow.d.ts +16 -0
  117. package/build/utils/utils-verify/allow.js +68 -0
  118. package/build/utils/utils-verify/call-sites.d.ts +34 -0
  119. package/build/utils/utils-verify/call-sites.js +154 -0
  120. package/build/utils/utils-verify/index.d.ts +7 -0
  121. package/build/utils/utils-verify/index.js +7 -0
  122. package/build/utils/utils-verify/language-spec.d.ts +91 -0
  123. package/build/utils/utils-verify/language-spec.js +210 -0
  124. package/build/utils/utils-verify/locate.d.ts +39 -0
  125. package/build/utils/utils-verify/locate.js +199 -0
  126. package/build/utils/utils-verify/parse.d.ts +34 -0
  127. package/build/utils/utils-verify/parse.js +177 -0
  128. package/build/utils/utils-verify/stage.d.ts +24 -0
  129. package/build/utils/utils-verify/stage.js +107 -0
  130. package/build/utils/utils-verify/verify.d.ts +63 -0
  131. package/build/utils/utils-verify/verify.js +168 -0
  132. package/build/utils/utils.d.ts +3 -1
  133. package/build/utils/utils.js +3 -1
  134. package/build/workspace/workspace.d.ts +32 -32
  135. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
  136. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
  137. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
  138. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
  139. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
  140. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  141. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
  142. package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
  143. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
  145. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  146. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
  147. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
  148. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
  149. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  150. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
  151. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  152. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  153. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  154. package/node_modules/playwright/package.json +1 -1
  155. package/package.json +2 -2
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
  157. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
  158. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
  159. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stage every shared utils file relevant to `testFile`, located by content.
3
+ *
4
+ * The reuse tool stages only the test file, and does so BEFORE the utils file exists,
5
+ * so a utils file the agent then creates reaches the output commit only through a
6
+ * prompt-level `git add` or the action's testDirectory sweep — neither of which the
7
+ * eval harness has, and neither of which is deterministic (SKYR-4196 Part B run 2
8
+ * shipped a test importing a module the PR did not contain).
9
+ *
10
+ * Called from the utils verify pass AND from the tools that follow reuse on the same
11
+ * spec (`skyramp_enhance_assertions`, `skyramp_execute_test`), so staging never
12
+ * depends on the agent choosing to verify. Best-effort: a staging failure is logged,
13
+ * never surfaced — it must not fail a tool that otherwise succeeded. Returns the paths
14
+ * it staged. No-op outside a testbot run (see stageGeneratedPaths).
15
+ */
16
+ export interface StagingResult {
17
+ /** Files that reached the index. */
18
+ staged: string[];
19
+ /** Utils files that `git add` refused (gitignored, outside the repo, git failure) —
20
+ * the tests importing them will fail in the output commit, so the verify pass
21
+ * treats a non-empty list as a failed verification. */
22
+ failed: string[];
23
+ }
24
+ export declare function stageUtilsArtifacts(testFile: string, language?: string): Promise<StagingResult>;
@@ -0,0 +1,107 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import { detectGitRoot, stageGeneratedPaths } from "../gitStaging.js";
6
+ import { logger } from "../logger.js";
7
+ import { isTestbotEnabled } from "../featureFlags.js";
8
+ import { locateUtilsFiles, realpath } from "./locate.js";
9
+ import { CODEGEN_MARKER_RE } from "./call-sites.js";
10
+ import { utilsSpecFor, utilsSpecForFile } from "./language-spec.js";
11
+ const execFileAsync = promisify(execFile);
12
+ export async function stageUtilsArtifacts(testFile, language) {
13
+ const staged = [];
14
+ const failed = [];
15
+ const result = { staged, failed };
16
+ // Outside a testbot run stageGeneratedPaths is a no-op; report nothing as staged
17
+ // rather than a list of files nothing touched.
18
+ if (!isTestbotEnabled())
19
+ return result;
20
+ let files = [];
21
+ try {
22
+ files = await locateUtilsFiles({ testFile, language });
23
+ }
24
+ catch (err) {
25
+ logger.warning("Could not locate shared utils files to stage", {
26
+ testFile,
27
+ error: String(err),
28
+ });
29
+ return result;
30
+ }
31
+ for (const file of files) {
32
+ try {
33
+ await stageGeneratedPaths(file);
34
+ staged.push(file);
35
+ }
36
+ catch (err) {
37
+ // A gitignored utils file lands here (`git add` refuses it): the test would
38
+ // import a module the output commit cannot contain.
39
+ logger.warning("Could not stage shared utils file — if it is gitignored, the tests importing it will fail in the output commit", { file, error: String(err) });
40
+ failed.push(file);
41
+ }
42
+ }
43
+ // Sibling generated tests this run EDITED to import from the utils file (STEP 5c, or
44
+ // a maintenance update): tracked, modified, codegen-marked, importing a located utils
45
+ // stem. Discovery staged only the current test and the loop above only the utils
46
+ // file, so without this the sibling's edit drops out of the output commit while the
47
+ // utils change lands — the same class of hole as the unstaged utils file.
48
+ if (files.length > 0) {
49
+ for (const sibling of await modifiedSiblingsImporting(testFile, files, language)) {
50
+ try {
51
+ await stageGeneratedPaths(sibling);
52
+ staged.push(sibling);
53
+ }
54
+ catch (err) {
55
+ logger.warning("Could not stage an edited sibling test", {
56
+ file: sibling,
57
+ error: String(err),
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ async function modifiedSiblingsImporting(testFile, utilsFiles, language) {
65
+ const spec = utilsSpecFor(language) ?? utilsSpecForFile(testFile);
66
+ const root = detectGitRoot(testFile);
67
+ if (!spec || !root)
68
+ return [];
69
+ let modified;
70
+ try {
71
+ // `-z`: unquoted, NUL-separated — git C-quotes non-ASCII paths otherwise.
72
+ const { stdout } = await execFileAsync("git", ["diff", "-z", "--name-only"], {
73
+ cwd: root,
74
+ encoding: "utf8",
75
+ });
76
+ modified = stdout.split("\0").filter(Boolean);
77
+ }
78
+ catch (err) {
79
+ logger.warning("Could not list modified files — edited sibling tests will not be staged", {
80
+ root,
81
+ error: String(err),
82
+ });
83
+ return [];
84
+ }
85
+ const stems = utilsFiles.map((f) => path.basename(f).replace(/\.[^.]+$/, ""));
86
+ const self = await realpath(testFile);
87
+ const out = [];
88
+ for (const rel of modified) {
89
+ const abs = await realpath(path.resolve(root, rel));
90
+ if (abs === self || utilsFiles.includes(abs))
91
+ continue;
92
+ if (!spec.extensions.some((e) => abs.toLowerCase().endsWith(e)))
93
+ continue;
94
+ let content;
95
+ try {
96
+ content = await fs.promises.readFile(abs, "utf8");
97
+ }
98
+ catch {
99
+ continue;
100
+ }
101
+ if (!CODEGEN_MARKER_RE.test(content.split("\n").slice(0, 5).join("\n")))
102
+ continue;
103
+ if (stems.some((stem) => spec.importedNames(content, stem).length > 0))
104
+ out.push(abs);
105
+ }
106
+ return out;
107
+ }
@@ -0,0 +1,63 @@
1
+ import { type InlineCallSite } from "./call-sites.js";
2
+ import { type UtilsViolationKind } from "./allow.js";
3
+ import { type HelperFamilySpec, type UtilsLanguageSpec } from "./language-spec.js";
4
+ export interface UtilsViolation {
5
+ kind: UtilsViolationKind;
6
+ /** Absolute path of the utils file. */
7
+ file: string;
8
+ helper: string;
9
+ line: number;
10
+ detail: string;
11
+ }
12
+ export interface UtilsVerifyResult {
13
+ /** No unallowed violations and no malformed allow markers. */
14
+ ok: boolean;
15
+ /** Utils files located for this spec (absolute). Empty = nothing was written or imported. */
16
+ utilsFiles: string[];
17
+ /** Helpers defined across the utils files. */
18
+ helpers: number;
19
+ /** Helper names the delivered spec imports from those files. */
20
+ imported: number;
21
+ violations: UtilsViolation[];
22
+ /** Violations covered by a `reuse-verify: allow` marker — a documented decline. */
23
+ allowed: UtilsViolation[];
24
+ /** Findings that inform but never fail the pass. `scenario-name` lives here: a naming
25
+ * heuristic is a quality signal, not a broken shared helper, and blocking a
26
+ * customer's run on a false positive of it is the worse outcome. */
27
+ advisories: UtilsViolation[];
28
+ /** Allow-marker lines the grammar cannot read, per file. */
29
+ malformedAllows: {
30
+ file: string;
31
+ line: string;
32
+ }[];
33
+ spec?: UtilsLanguageSpec;
34
+ /** Which invariant set applied — decided by the test type, see helperFamilyFor. */
35
+ family: HelperFamilySpec;
36
+ /** Advisory (does not affect `ok`): inline request calls in sibling generated tests
37
+ * that a utils helper already wraps — see findSiblingInlineCallSites. API family only. */
38
+ inlineCallSites: InlineCallSite[];
39
+ }
40
+ export interface VerifyUtilsParams {
41
+ testFile: string;
42
+ language?: string;
43
+ /** Selects the helper family (api vs browser). Absent → api. */
44
+ testType?: string;
45
+ cwd?: string;
46
+ }
47
+ /**
48
+ * Deterministic post-reuse check of the shared utils file(s) a spec depends on.
49
+ *
50
+ * Three invariants, each a class of drift measured in the SKYR-4196 Part B evals:
51
+ * - `duplicate-helper`: two helpers for one method+path (run 4 — `create_product`
52
+ * beside `create_list_reflect_product`); the shared file must hold one canonical
53
+ * helper per step, extended with defaulted parameters rather than duplicated.
54
+ * - `body-assertion`: any assertion other than a status-code comparison inside a
55
+ * helper (runs 5 and 11 — `assert total_amount == 16.0` in `update_order`); a
56
+ * body assertion carries one scenario's values into a function every scenario calls.
57
+ * - `scenario-name` (ADVISORY only): a helper named for its scenario rather than its step.
58
+ *
59
+ * Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
60
+ * checks a spec's cross-file references — so it reads the utils files, not the spec,
61
+ * except to count what the spec imports.
62
+ */
63
+ export declare function verifyUtils(params: VerifyUtilsParams): Promise<UtilsVerifyResult>;
@@ -0,0 +1,168 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { logger } from "../logger.js";
4
+ import { locateUtilsFiles } from "./locate.js";
5
+ import { parseUtilsFile } from "./parse.js";
6
+ import { findSiblingInlineCallSites, } from "./call-sites.js";
7
+ import { parseUtilsAllows } from "./allow.js";
8
+ import { helperFamilyFor, scenarioNameViolation, utilsSpecFor, utilsSpecForFile, } from "./language-spec.js";
9
+ /**
10
+ * Deterministic post-reuse check of the shared utils file(s) a spec depends on.
11
+ *
12
+ * Three invariants, each a class of drift measured in the SKYR-4196 Part B evals:
13
+ * - `duplicate-helper`: two helpers for one method+path (run 4 — `create_product`
14
+ * beside `create_list_reflect_product`); the shared file must hold one canonical
15
+ * helper per step, extended with defaulted parameters rather than duplicated.
16
+ * - `body-assertion`: any assertion other than a status-code comparison inside a
17
+ * helper (runs 5 and 11 — `assert total_amount == 16.0` in `update_order`); a
18
+ * body assertion carries one scenario's values into a function every scenario calls.
19
+ * - `scenario-name` (ADVISORY only): a helper named for its scenario rather than its step.
20
+ *
21
+ * Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
22
+ * checks a spec's cross-file references — so it reads the utils files, not the spec,
23
+ * except to count what the spec imports.
24
+ */
25
+ export async function verifyUtils(params) {
26
+ const spec = utilsSpecFor(params.language) ?? utilsSpecForFile(params.testFile);
27
+ const family = helperFamilyFor(params.testType);
28
+ const utilsFiles = spec
29
+ ? await locateUtilsFiles({
30
+ testFile: params.testFile,
31
+ language: spec.language,
32
+ cwd: params.cwd,
33
+ })
34
+ : [];
35
+ const result = {
36
+ ok: true,
37
+ utilsFiles,
38
+ helpers: 0,
39
+ imported: 0,
40
+ violations: [],
41
+ allowed: [],
42
+ advisories: [],
43
+ malformedAllows: [],
44
+ spec,
45
+ family,
46
+ inlineCallSites: [],
47
+ };
48
+ if (!spec || utilsFiles.length === 0)
49
+ return result;
50
+ let specContent = "";
51
+ try {
52
+ specContent = await fs.promises.readFile(params.testFile, "utf8");
53
+ }
54
+ catch {
55
+ /* the spec may be absent outside a run; import counting then reports 0 */
56
+ }
57
+ const allHelpers = [];
58
+ const importedNames = new Set();
59
+ // Route map spans every utils file: the reuse prompt allows the helpers to split
60
+ // across files, and the same method+path in two of them is still one duplicate.
61
+ const byRoute = new Map();
62
+ for (const file of utilsFiles) {
63
+ let content;
64
+ try {
65
+ content = await fs.promises.readFile(file, "utf8");
66
+ }
67
+ catch (err) {
68
+ if (err?.code === "ENOENT")
69
+ continue; // removed since locate
70
+ logger.warning("Could not read a located utils file", {
71
+ file,
72
+ error: String(err),
73
+ });
74
+ throw err; // the caller records a verify error, which fails OPEN — never a silent PASS
75
+ }
76
+ const helpers = parseUtilsFile(content, spec, (expr) => family.isAllowed(spec, expr));
77
+ allHelpers.push(...helpers);
78
+ const { allows, malformed } = parseUtilsAllows(content, spec);
79
+ result.helpers += helpers.length;
80
+ result.malformedAllows.push(...malformed.map((line) => ({ file, line })));
81
+ const stem = path.basename(file).replace(/\.[^.]+$/, "");
82
+ const defined = new Set(helpers.map((h) => h.name));
83
+ for (const n of spec.importedNames(specContent, stem))
84
+ if (defined.has(n))
85
+ importedNames.add(n);
86
+ // One marker documents ONE violation of that kind in that helper: a second,
87
+ // undocumented scenario claim in the same helper is not covered by the first
88
+ // decline. Markers are consumed in order.
89
+ const budget = new Map();
90
+ for (const a of allows) {
91
+ const k = `${a.kind}\0${a.helper}`;
92
+ budget.set(k, (budget.get(k) ?? 0) + 1);
93
+ }
94
+ for (const v of violationsIn(helpers, file, family, byRoute)) {
95
+ const k = `${v.kind}\0${v.helper}`;
96
+ const left = budget.get(k) ?? 0;
97
+ if (left > 0) {
98
+ budget.set(k, left - 1);
99
+ result.allowed.push(v);
100
+ }
101
+ else if (v.kind === "scenario-name") {
102
+ result.advisories.push(v);
103
+ }
104
+ else {
105
+ result.violations.push(v);
106
+ }
107
+ }
108
+ }
109
+ result.imported = importedNames.size;
110
+ result.ok =
111
+ result.violations.length === 0 && result.malformedAllows.length === 0;
112
+ // Integration only — the same gate as the prompt rule. `contract`, `load` and an
113
+ // absent type also resolve to the API family, but the reuse flow never targets them,
114
+ // so an advisory (and a report count) there would describe reuse the feature could
115
+ // not have taken.
116
+ if ((params.testType ?? "").toLowerCase() === "integration") {
117
+ try {
118
+ result.inlineCallSites = await findSiblingInlineCallSites(params.testFile, utilsFiles, allHelpers, spec);
119
+ }
120
+ catch {
121
+ /* advisory only — never fails a verify */
122
+ }
123
+ }
124
+ return result;
125
+ }
126
+ function violationsIn(helpers, file, family, byRoute) {
127
+ const out = [];
128
+ for (const h of helpers) {
129
+ if (family.routeDuplicates && h.method && h.normalizedPath) {
130
+ const key = `${h.method} ${h.normalizedPath}`;
131
+ const first = byRoute.get(key);
132
+ if (first) {
133
+ out.push({
134
+ kind: "duplicate-helper",
135
+ file,
136
+ helper: h.name,
137
+ line: h.line,
138
+ detail: `${h.name} and ${first.name} (${path.basename(first.file)}:${first.line}) both wrap ${key}; keep one helper per method+path and lift the differing literals into defaulted parameters`,
139
+ });
140
+ }
141
+ else {
142
+ byRoute.set(key, { ...h, file });
143
+ }
144
+ }
145
+ for (const a of h.assertions) {
146
+ if (!a.allowed) {
147
+ out.push({
148
+ kind: "body-assertion",
149
+ file,
150
+ helper: h.name,
151
+ line: a.line,
152
+ detail: `\`${a.text}\` — ${family.assertionRule}`,
153
+ });
154
+ }
155
+ }
156
+ const naming = scenarioNameViolation(h.name);
157
+ if (naming) {
158
+ out.push({
159
+ kind: "scenario-name",
160
+ file,
161
+ helper: h.name,
162
+ line: h.line,
163
+ detail: naming,
164
+ });
165
+ }
166
+ }
167
+ return out;
168
+ }
@@ -32,7 +32,9 @@ export declare function toolError(message: string): CallToolResult;
32
32
  *
33
33
  * 2. Each agent CLI testbot supports reads a different channel and none falls
34
34
  * back to the other: Claude Code takes `structuredContent` and drops
35
- * `content[]`; Cursor takes `content[]` and ignores `structuredContent`;
35
+ * `content[]` WHEN BOTH ARE PRESENT — a content-only result still reaches it,
36
+ * which is what SKYR-4267 relies on for `browser_blueprint`; Cursor takes
37
+ * `content[]` and ignores `structuredContent`;
36
38
  * GitHub Copilot CLI surfaces both and de-duplicates them only when the text
37
39
  * is the literal JSON serialization of `structuredContent` (MCP spec 5.2.6),
38
40
  * otherwise it concatenates. Hence `JSON.stringify` rather than the raw text,
@@ -49,7 +49,9 @@ export function toolError(message) {
49
49
  *
50
50
  * 2. Each agent CLI testbot supports reads a different channel and none falls
51
51
  * back to the other: Claude Code takes `structuredContent` and drops
52
- * `content[]`; Cursor takes `content[]` and ignores `structuredContent`;
52
+ * `content[]` WHEN BOTH ARE PRESENT — a content-only result still reaches it,
53
+ * which is what SKYR-4267 relies on for `browser_blueprint`; Cursor takes
54
+ * `content[]` and ignores `structuredContent`;
53
55
  * GitHub Copilot CLI surfaces both and de-duplicates them only when the text
54
56
  * is the literal JSON serialization of `structuredContent` (MCP spec 5.2.6),
55
57
  * otherwise it concatenates. Hence `JSON.stringify` rather than the raw text,
@@ -55,26 +55,26 @@ export declare const serviceSchema: z.ZodObject<{
55
55
  }, "strict", z.ZodTypeAny, {
56
56
  authHeader?: string | undefined;
57
57
  authScheme?: string | undefined;
58
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
59
+ baseUrl?: string | undefined;
58
60
  schemaPath?: string | undefined;
59
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
60
61
  defaultQueryParams?: Record<string, string> | undefined;
61
62
  queryParamOverrides?: {
62
63
  values: Record<string, string>;
63
64
  pathPattern: string;
64
65
  }[] | undefined;
65
- baseUrl?: string | undefined;
66
66
  skipTLSVerify?: boolean | undefined;
67
67
  }, {
68
68
  authHeader?: string | undefined;
69
69
  authScheme?: string | undefined;
70
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
71
+ baseUrl?: string | undefined;
70
72
  schemaPath?: string | undefined;
71
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
72
73
  defaultQueryParams?: Record<string, string> | undefined;
73
74
  queryParamOverrides?: {
74
75
  values: Record<string, string>;
75
76
  pathPattern: string;
76
77
  }[] | undefined;
77
- baseUrl?: string | undefined;
78
78
  skipTLSVerify?: boolean | undefined;
79
79
  }>>;
80
80
  runtimeDetails: z.ZodOptional<z.ZodObject<{
@@ -143,20 +143,20 @@ export declare const serviceSchema: z.ZodObject<{
143
143
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
144
144
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
145
145
  repository?: string | undefined;
146
- testDirectory?: string | undefined;
147
146
  api?: {
148
147
  authHeader?: string | undefined;
149
148
  authScheme?: string | undefined;
149
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
150
+ baseUrl?: string | undefined;
150
151
  schemaPath?: string | undefined;
151
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
152
152
  defaultQueryParams?: Record<string, string> | undefined;
153
153
  queryParamOverrides?: {
154
154
  values: Record<string, string>;
155
155
  pathPattern: string;
156
156
  }[] | undefined;
157
- baseUrl?: string | undefined;
158
157
  skipTLSVerify?: boolean | undefined;
159
158
  } | undefined;
159
+ testDirectory?: string | undefined;
160
160
  runtimeDetails?: {
161
161
  runtime: import("../types/TestTypes.js").RuntimeEnvironment;
162
162
  dockerNetwork?: string | undefined;
@@ -178,20 +178,20 @@ export declare const serviceSchema: z.ZodObject<{
178
178
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
179
179
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
180
180
  repository?: string | undefined;
181
- testDirectory?: string | undefined;
182
181
  api?: {
183
182
  authHeader?: string | undefined;
184
183
  authScheme?: string | undefined;
184
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
185
+ baseUrl?: string | undefined;
185
186
  schemaPath?: string | undefined;
186
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
187
187
  defaultQueryParams?: Record<string, string> | undefined;
188
188
  queryParamOverrides?: {
189
189
  values: Record<string, string>;
190
190
  pathPattern: string;
191
191
  }[] | undefined;
192
- baseUrl?: string | undefined;
193
192
  skipTLSVerify?: boolean | undefined;
194
193
  } | undefined;
194
+ testDirectory?: string | undefined;
195
195
  runtimeDetails?: {
196
196
  runtime?: unknown;
197
197
  dockerNetwork?: string | undefined;
@@ -292,26 +292,26 @@ export declare const workspaceConfigSchema: z.ZodObject<{
292
292
  }, "strict", z.ZodTypeAny, {
293
293
  authHeader?: string | undefined;
294
294
  authScheme?: string | undefined;
295
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
296
+ baseUrl?: string | undefined;
295
297
  schemaPath?: string | undefined;
296
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
297
298
  defaultQueryParams?: Record<string, string> | undefined;
298
299
  queryParamOverrides?: {
299
300
  values: Record<string, string>;
300
301
  pathPattern: string;
301
302
  }[] | undefined;
302
- baseUrl?: string | undefined;
303
303
  skipTLSVerify?: boolean | undefined;
304
304
  }, {
305
305
  authHeader?: string | undefined;
306
306
  authScheme?: string | undefined;
307
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
308
+ baseUrl?: string | undefined;
307
309
  schemaPath?: string | undefined;
308
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
309
310
  defaultQueryParams?: Record<string, string> | undefined;
310
311
  queryParamOverrides?: {
311
312
  values: Record<string, string>;
312
313
  pathPattern: string;
313
314
  }[] | undefined;
314
- baseUrl?: string | undefined;
315
315
  skipTLSVerify?: boolean | undefined;
316
316
  }>>;
317
317
  runtimeDetails: z.ZodOptional<z.ZodObject<{
@@ -380,20 +380,20 @@ export declare const workspaceConfigSchema: z.ZodObject<{
380
380
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
381
381
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
382
382
  repository?: string | undefined;
383
- testDirectory?: string | undefined;
384
383
  api?: {
385
384
  authHeader?: string | undefined;
386
385
  authScheme?: string | undefined;
386
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
387
+ baseUrl?: string | undefined;
387
388
  schemaPath?: string | undefined;
388
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
389
389
  defaultQueryParams?: Record<string, string> | undefined;
390
390
  queryParamOverrides?: {
391
391
  values: Record<string, string>;
392
392
  pathPattern: string;
393
393
  }[] | undefined;
394
- baseUrl?: string | undefined;
395
394
  skipTLSVerify?: boolean | undefined;
396
395
  } | undefined;
396
+ testDirectory?: string | undefined;
397
397
  runtimeDetails?: {
398
398
  runtime: import("../types/TestTypes.js").RuntimeEnvironment;
399
399
  dockerNetwork?: string | undefined;
@@ -415,20 +415,20 @@ export declare const workspaceConfigSchema: z.ZodObject<{
415
415
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
416
416
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
417
417
  repository?: string | undefined;
418
- testDirectory?: string | undefined;
419
418
  api?: {
420
419
  authHeader?: string | undefined;
421
420
  authScheme?: string | undefined;
421
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
422
+ baseUrl?: string | undefined;
422
423
  schemaPath?: string | undefined;
423
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
424
424
  defaultQueryParams?: Record<string, string> | undefined;
425
425
  queryParamOverrides?: {
426
426
  values: Record<string, string>;
427
427
  pathPattern: string;
428
428
  }[] | undefined;
429
- baseUrl?: string | undefined;
430
429
  skipTLSVerify?: boolean | undefined;
431
430
  } | undefined;
431
+ testDirectory?: string | undefined;
432
432
  runtimeDetails?: {
433
433
  runtime?: unknown;
434
434
  dockerNetwork?: string | undefined;
@@ -463,20 +463,20 @@ export declare const workspaceConfigSchema: z.ZodObject<{
463
463
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
464
464
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
465
465
  repository?: string | undefined;
466
- testDirectory?: string | undefined;
467
466
  api?: {
468
467
  authHeader?: string | undefined;
469
468
  authScheme?: string | undefined;
469
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
470
+ baseUrl?: string | undefined;
470
471
  schemaPath?: string | undefined;
471
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
472
472
  defaultQueryParams?: Record<string, string> | undefined;
473
473
  queryParamOverrides?: {
474
474
  values: Record<string, string>;
475
475
  pathPattern: string;
476
476
  }[] | undefined;
477
- baseUrl?: string | undefined;
478
477
  skipTLSVerify?: boolean | undefined;
479
478
  } | undefined;
479
+ testDirectory?: string | undefined;
480
480
  runtimeDetails?: {
481
481
  runtime: import("../types/TestTypes.js").RuntimeEnvironment;
482
482
  dockerNetwork?: string | undefined;
@@ -511,20 +511,20 @@ export declare const workspaceConfigSchema: z.ZodObject<{
511
511
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
512
512
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
513
513
  repository?: string | undefined;
514
- testDirectory?: string | undefined;
515
514
  api?: {
516
515
  authHeader?: string | undefined;
517
516
  authScheme?: string | undefined;
517
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
518
+ baseUrl?: string | undefined;
518
519
  schemaPath?: string | undefined;
519
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
520
520
  defaultQueryParams?: Record<string, string> | undefined;
521
521
  queryParamOverrides?: {
522
522
  values: Record<string, string>;
523
523
  pathPattern: string;
524
524
  }[] | undefined;
525
- baseUrl?: string | undefined;
526
525
  skipTLSVerify?: boolean | undefined;
527
526
  } | undefined;
527
+ testDirectory?: string | undefined;
528
528
  runtimeDetails?: {
529
529
  runtime?: unknown;
530
530
  dockerNetwork?: string | undefined;
@@ -568,20 +568,20 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
568
568
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
569
569
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
570
570
  repository?: string | undefined;
571
- testDirectory?: string | undefined;
572
571
  api?: {
573
572
  authHeader?: string | undefined;
574
573
  authScheme?: string | undefined;
574
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
575
+ baseUrl?: string | undefined;
575
576
  schemaPath?: string | undefined;
576
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
577
577
  defaultQueryParams?: Record<string, string> | undefined;
578
578
  queryParamOverrides?: {
579
579
  values: Record<string, string>;
580
580
  pathPattern: string;
581
581
  }[] | undefined;
582
- baseUrl?: string | undefined;
583
582
  skipTLSVerify?: boolean | undefined;
584
583
  } | undefined;
584
+ testDirectory?: string | undefined;
585
585
  runtimeDetails?: {
586
586
  runtime?: unknown;
587
587
  dockerNetwork?: string | undefined;
@@ -616,20 +616,20 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
616
616
  language?: "python" | "typescript" | "javascript" | "java" | undefined;
617
617
  framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
618
618
  repository?: string | undefined;
619
- testDirectory?: string | undefined;
620
619
  api?: {
621
620
  authHeader?: string | undefined;
622
621
  authScheme?: string | undefined;
622
+ authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | "token" | undefined;
623
+ baseUrl?: string | undefined;
623
624
  schemaPath?: string | undefined;
624
- authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
625
625
  defaultQueryParams?: Record<string, string> | undefined;
626
626
  queryParamOverrides?: {
627
627
  values: Record<string, string>;
628
628
  pathPattern: string;
629
629
  }[] | undefined;
630
- baseUrl?: string | undefined;
631
630
  skipTLSVerify?: boolean | undefined;
632
631
  } | undefined;
632
+ testDirectory?: string | undefined;
633
633
  runtimeDetails?: {
634
634
  runtime: import("../types/TestTypes.js").RuntimeEnvironment;
635
635
  dockerNetwork?: string | undefined;