@skyramp/mcp 0.4.4 → 0.5.0-rc.10

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 (144) hide show
  1. package/build/entitlements/entitlementGate.d.ts +8 -0
  2. package/build/entitlements/entitlementGate.js +57 -0
  3. package/build/entitlements/runCodes.d.ts +18 -0
  4. package/build/entitlements/runCodes.js +16 -0
  5. package/build/index.js +32 -6
  6. package/build/playwright/PlaywrightTraceService.d.ts +17 -0
  7. package/build/playwright/PlaywrightTraceService.js +29 -1
  8. package/build/playwright/dataSafetyConfig.d.ts +74 -0
  9. package/build/playwright/dataSafetyConfig.js +109 -0
  10. package/build/playwright/essentialTools.d.ts +1 -0
  11. package/build/playwright/essentialTools.js +44 -0
  12. package/build/playwright/registerPlaywrightTools.d.ts +36 -1
  13. package/build/playwright/registerPlaywrightTools.js +87 -43
  14. package/build/playwright/safeResolveDataSafety.d.ts +24 -0
  15. package/build/playwright/safeResolveDataSafety.js +33 -0
  16. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
  17. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
  18. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  19. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  20. package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
  21. package/build/services/FleetExecutionService.d.ts +87 -16
  22. package/build/services/FleetExecutionService.js +487 -48
  23. package/build/services/FleetRunRegistry.d.ts +49 -0
  24. package/build/services/FleetRunRegistry.js +178 -0
  25. package/build/services/containerEnv.js +8 -2
  26. package/build/services/fleetLimitConfirmation.d.ts +19 -0
  27. package/build/services/fleetLimitConfirmation.js +126 -0
  28. package/build/tool-phases.d.ts +14 -8
  29. package/build/tool-phases.js +88 -43
  30. package/build/tools/code-refactor/assertion-state.js +3 -1
  31. package/build/tools/code-refactor/codeReuseTool.js +2 -1
  32. package/build/tools/code-refactor/utils-verify-gates.js +1 -1
  33. package/build/tools/executeFleetTool.d.ts +6 -4
  34. package/build/tools/executeFleetTool.js +67 -23
  35. package/build/tools/fixErrorTool.js +2 -1
  36. package/build/tools/fleetLifecycleTools.d.ts +20 -0
  37. package/build/tools/fleetLifecycleTools.js +124 -0
  38. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
  39. package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
  40. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
  41. package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
  42. package/build/tools/submitReportTool.js +4 -122
  43. package/build/tools/test-management/registerTestPlanTool.js +22 -6
  44. package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
  45. package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
  46. package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
  47. package/build/types/AssertionOutcome.d.ts +3 -0
  48. package/build/types/TestbotReport.d.ts +0 -37
  49. package/build/types/TestbotReport.js +0 -22
  50. package/build/types/index.d.ts +1 -1
  51. package/build/types/index.js +1 -1
  52. package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
  53. package/build/utils/assertion-verify/created-add-guard.js +173 -0
  54. package/build/utils/assertion-verify/index.d.ts +1 -0
  55. package/build/utils/assertion-verify/index.js +1 -0
  56. package/build/utils/assertion-verify/verify.d.ts +7 -3
  57. package/build/utils/assertion-verify/verify.js +9 -3
  58. package/build/utils/featureFlags.js +6 -3
  59. package/build/utils/repositorySlug.d.ts +14 -0
  60. package/build/utils/repositorySlug.js +46 -1
  61. package/build/utils/telemetry.d.ts +2 -0
  62. package/build/utils/telemetry.js +12 -0
  63. package/build/utils/utils-verify/action-key.d.ts +59 -2
  64. package/build/utils/utils-verify/action-key.js +162 -6
  65. package/build/utils/utils-verify/action-sites.d.ts +7 -0
  66. package/build/utils/utils-verify/action-sites.js +277 -35
  67. package/build/utils/utils-verify/call-sites.d.ts +8 -2
  68. package/build/utils/utils-verify/language-spec.js +1 -1
  69. package/build/utils/utils-verify/parse.d.ts +4 -0
  70. package/build/utils/utils-verify/parse.js +2 -1
  71. package/build/utils/versions.d.ts +3 -3
  72. package/build/utils/versions.js +1 -1
  73. package/build/workspace/workspace.d.ts +63 -121
  74. package/build/workspace/workspace.js +45 -48
  75. package/build/workspace/workspaceVerifier.d.ts +22 -0
  76. package/build/workspace/workspaceVerifier.js +65 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
  78. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
  79. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
  80. package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
  81. package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
  82. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
  83. package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
  84. package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
  85. package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
  86. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
  87. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
  88. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
  89. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
  90. package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
  91. package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
  92. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  93. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  94. package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
  95. package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
  96. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  97. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
  98. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
  99. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
  100. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
  101. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
  102. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
  103. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
  104. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
  105. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
  106. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  108. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
  109. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
  112. package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
  113. package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
  114. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  115. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  116. package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
  117. package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
  118. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
  119. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
  120. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
  121. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
  122. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
  123. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
  124. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
  125. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
  126. package/node_modules/playwright/package.json +1 -1
  127. package/package.json +4 -4
  128. package/plugin/prompts/plan-tests.md +2 -2
  129. package/plugin/prompts/testbot-task1.md +1 -1
  130. package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
  131. package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
  132. package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
  133. package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
  134. package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
  135. package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
  136. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
  137. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
  138. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
  139. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
  140. package/build/prompts/sut-setup/shared.d.ts +0 -62
  141. package/build/prompts/sut-setup/shared.js +0 -196
  142. package/build/resources/sutSetupResource.d.ts +0 -2
  143. package/build/resources/sutSetupResource.js +0 -63
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Guard for the integration-teardown registration lines codegen emits
3
+ * (`--emit-integration-teardown`): each created record is registered with
4
+ * `tracker.track_for_cleanup(...)` (Python) or `tracker.trackForCleanup(...)`
5
+ * (TS/JS/Java) right after its id capture — only a generated comment line sits
6
+ * between them — and the client library deletes the registered records when
7
+ * the scope exits. TS/JS and Python registrations span several lines; Java's
8
+ * fit on one. An enhancement pass that
9
+ * edits, moves or removes one of those lines — or inserts an assertion between
10
+ * the capture and its registration so a failing assertion skips the
11
+ * registration — silently leaks records.
12
+ */
13
+ const TRACK_FOR_CLEANUP_PREFIXES = [
14
+ "tracker.track_for_cleanup(",
15
+ "tracker.trackForCleanup(",
16
+ ];
17
+ const CAPTURE_CALL_RE = /(?:get_response_value|getResponseValue|getValue)\s*\(/;
18
+ // A single `=` that is not part of `==`, `!=`, `<=`, `>=`, `=>` or `===`.
19
+ const ASSIGNMENT_RE = /^(.*?)(?<![=!<>])=(?![=>])(.*)$/;
20
+ const COMMENT_PREFIXES = ["//", "#"];
21
+ const QUOTES = new Set(['"', "'", "`"]);
22
+ function isTrackForCleanupLine(trimmed) {
23
+ return TRACK_FOR_CLEANUP_PREFIXES.some((p) => trimmed.startsWith(p));
24
+ }
25
+ function isSkippable(trimmed) {
26
+ return (trimmed.length === 0 || COMMENT_PREFIXES.some((p) => trimmed.startsWith(p)));
27
+ }
28
+ /** The registration call that opens on `lines[start]`: its trimmed lines up to
29
+ * the one whose `)` closes the call, joined by a space. A call that never
30
+ * closes is just its opening line. */
31
+ function registrationAt(lines, start) {
32
+ const parts = [];
33
+ let depth = 0;
34
+ let quote;
35
+ for (let i = start; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ parts.push(line.trim());
38
+ for (let j = 0; j < line.length; j++) {
39
+ const c = line[j];
40
+ if (quote !== undefined) {
41
+ if (c === "\\")
42
+ j++;
43
+ else if (c === quote)
44
+ quote = undefined;
45
+ }
46
+ else if (QUOTES.has(c))
47
+ quote = c;
48
+ else if (c === "(")
49
+ depth++;
50
+ else if (c === ")" && --depth === 0)
51
+ return parts.join(" ");
52
+ }
53
+ }
54
+ return lines[start].trim();
55
+ }
56
+ /** The call with the whitespace outside string literals removed, so a
57
+ * re-indented or re-wrapped registration still matches its baseline. */
58
+ function canonical(call) {
59
+ let out = "";
60
+ let quote;
61
+ for (let j = 0; j < call.length; j++) {
62
+ const c = call[j];
63
+ if (quote !== undefined) {
64
+ out += c;
65
+ if (c === "\\")
66
+ out += call[++j] ?? "";
67
+ else if (c === quote)
68
+ quote = undefined;
69
+ }
70
+ else if (QUOTES.has(c)) {
71
+ quote = c;
72
+ out += c;
73
+ }
74
+ else if (!/\s/.test(c))
75
+ out += c;
76
+ }
77
+ return out;
78
+ }
79
+ /** Each whole `tracker.track_for_cleanup(...)` / `tracker.trackForCleanup(...)`
80
+ * call of a file, in order — the baseline the enhanced file is compared
81
+ * against. */
82
+ export function collectCreatedAddLines(content) {
83
+ const lines = content.split("\n");
84
+ const calls = [];
85
+ lines.forEach((line, i) => {
86
+ if (isTrackForCleanupLine(line.trim()))
87
+ calls.push(registrationAt(lines, i));
88
+ });
89
+ return calls;
90
+ }
91
+ function isIdCapture(line) {
92
+ const m = ASSIGNMENT_RE.exec(line);
93
+ return m !== null && m[1].trim().length > 0 && CAPTURE_CALL_RE.test(m[2]);
94
+ }
95
+ const REMEDIATION = "Restore the generated `tracker.track_for_cleanup(...)` / `tracker.trackForCleanup(...)` calls exactly as emitted, right after the id capture they belong to (only the generated comment line may sit between them), and put assertions only after the registration.";
96
+ /**
97
+ * Hard findings when the enhanced file's registration lines differ from the
98
+ * baseline's (sequence must match exactly: same lines, same count, same order)
99
+ * or when anything but blank or comment lines sits between an id capture and
100
+ * its registration. With no baseline only the adjacency check runs.
101
+ */
102
+ export function lintCreatedAddLines(content, baselineLines) {
103
+ const findings = [];
104
+ const lines = content.split("\n");
105
+ if (baselineLines !== undefined) {
106
+ const current = collectCreatedAddLines(content);
107
+ const offending = firstSequenceMismatch(baselineLines, current);
108
+ if (offending !== undefined)
109
+ findings.push(offending);
110
+ }
111
+ for (let i = 0; i < lines.length; i++) {
112
+ if (!isTrackForCleanupLine(lines[i].trim()))
113
+ continue;
114
+ let prev = i - 1;
115
+ while (prev >= 0 && isSkippable(lines[prev].trim()))
116
+ prev--;
117
+ if (prev >= 0 && isIdCapture(lines[prev].trim()))
118
+ continue;
119
+ findings.push({
120
+ rule: "created-add-separated-from-capture",
121
+ severity: "hard",
122
+ line: i + 1,
123
+ message: `\`${registrationAt(lines, i)}\` no longer directly follows its id capture (the line before it is ${prev >= 0 ? `\`${lines[prev].trim()}\`` : "missing"}). Nothing may sit between an id capture and its registration.`,
124
+ remediation: REMEDIATION,
125
+ });
126
+ break; // The first offender is enough to name; one fix pass restores all.
127
+ }
128
+ return findings;
129
+ }
130
+ /** Compares the baseline and current registration sequences (whitespace
131
+ * outside strings ignored) and names the first call that is missing, out of
132
+ * order, or unexpected. */
133
+ function firstSequenceMismatch(baseline, current) {
134
+ const baselineKeys = baseline.map(canonical);
135
+ const currentKeys = current.map(canonical);
136
+ const currentCounts = new Map();
137
+ for (const key of currentKeys)
138
+ currentCounts.set(key, (currentCounts.get(key) ?? 0) + 1);
139
+ const baselineCounts = new Map();
140
+ for (const key of baselineKeys)
141
+ baselineCounts.set(key, (baselineCounts.get(key) ?? 0) + 1);
142
+ for (const [i, line] of baseline.entries()) {
143
+ const key = baselineKeys[i];
144
+ if ((currentCounts.get(key) ?? 0) < (baselineCounts.get(key) ?? 0)) {
145
+ return {
146
+ rule: "created-add-removed",
147
+ severity: "hard",
148
+ message: `The generated \`${line}\` line was removed or edited.`,
149
+ remediation: REMEDIATION,
150
+ };
151
+ }
152
+ }
153
+ for (let i = 0; i < current.length; i++) {
154
+ const line = current[i];
155
+ if (!baselineCounts.has(currentKeys[i])) {
156
+ return {
157
+ rule: "created-add-added",
158
+ severity: "hard",
159
+ message: `\`${line}\` is not one of the generated registration lines.`,
160
+ remediation: REMEDIATION,
161
+ };
162
+ }
163
+ if (currentKeys[i] !== baselineKeys[i]) {
164
+ return {
165
+ rule: "created-add-moved",
166
+ severity: "hard",
167
+ message: `\`${line}\` was moved: the generated file had \`${baseline[i]}\` at this position.`,
168
+ remediation: REMEDIATION,
169
+ };
170
+ }
171
+ }
172
+ return undefined;
173
+ }
@@ -1,3 +1,4 @@
1
1
  export { detectAssertionLanguage, computeAssertionMetrics } from "./metrics.js";
2
2
  export { verifyAssertionEnhancement, sha256Of, type AssertionBaseline, type AssertionEnhanceType, type AssertionVerifyResult, } from "./verify.js";
3
3
  export { formatAssertionVerifyReport } from "./format.js";
4
+ export { collectCreatedAddLines } from "./created-add-guard.js";
@@ -5,3 +5,4 @@
5
5
  export { detectAssertionLanguage, computeAssertionMetrics } from "./metrics.js";
6
6
  export { verifyAssertionEnhancement, sha256Of, } from "./verify.js";
7
7
  export { formatAssertionVerifyReport } from "./format.js";
8
+ export { collectCreatedAddLines } from "./created-add-guard.js";
@@ -7,6 +7,9 @@ export interface AssertionBaseline {
7
7
  count?: number;
8
8
  strengthScore?: number;
9
9
  fingerprints?: string[];
10
+ /** Trimmed `tracker.track_for_cleanup(` registration lines at hand-out time, in order
11
+ * (integration teardown). The enhanced file must keep them verbatim. */
12
+ createdAddLines?: string[];
10
13
  /** Copy of the generated file at hand-out time (run artifact dir). */
11
14
  baselineFilePath?: string;
12
15
  }
@@ -54,9 +57,10 @@ export interface AssertionVerifyResult {
54
57
  export declare function sha256Of(content: string): string;
55
58
  /**
56
59
  * Deterministic verification that the enhancement instructions were applied:
57
- * hash delta, no assertions removed, rubric lints clean, and (generation only)
58
- * strength strictly increased unless a documented `assertions complete` marker
59
- * declines it. Reads the file as delivered — throws if it cannot be read.
60
+ * hash delta, no assertions removed, rubric lints clean, teardown
61
+ * `tracker.track_for_cleanup(...)` registrations untouched, and (generation only) strength
62
+ * strictly increased unless a documented `assertions complete` marker declines
63
+ * it. Reads the file as delivered — throws if it cannot be read.
60
64
  */
61
65
  export declare function verifyAssertionEnhancement(params: {
62
66
  testFile: string;
@@ -8,6 +8,7 @@ import { lintIntegrationTest } from "./integration-lints.js";
8
8
  import { lintContractTest } from "./contract-lints.js";
9
9
  import { findAssertionsCompleteMarker, findUnparsableAssertionsComplete, markerCommentToken, } from "./marker.js";
10
10
  import { importedHelperSubjectCapacities } from "./helper-imports.js";
11
+ import { lintCreatedAddLines } from "./created-add-guard.js";
11
12
  /** The mean weight the assertions added since the baseline must reach.
12
13
  * Weights are 3 exact / 2 partial-or-format / 1 existence-tier, so 2 means
13
14
  * "on average at least a format check". A lean enhancement of exact values
@@ -37,9 +38,10 @@ function addedSites(current, baselineFingerprints) {
37
38
  }
38
39
  /**
39
40
  * Deterministic verification that the enhancement instructions were applied:
40
- * hash delta, no assertions removed, rubric lints clean, and (generation only)
41
- * strength strictly increased unless a documented `assertions complete` marker
42
- * declines it. Reads the file as delivered — throws if it cannot be read.
41
+ * hash delta, no assertions removed, rubric lints clean, teardown
42
+ * `tracker.track_for_cleanup(...)` registrations untouched, and (generation only) strength
43
+ * strictly increased unless a documented `assertions complete` marker declines
44
+ * it. Reads the file as delivered — throws if it cannot be read.
43
45
  */
44
46
  export async function verifyAssertionEnhancement(params) {
45
47
  const { testFile, testType, enhanceType, baseline } = params;
@@ -158,6 +160,10 @@ export async function verifyAssertionEnhancement(params) {
158
160
  : [];
159
161
  const marker = findAssertionsCompleteMarker(content, testFile, language);
160
162
  const hardFindings = findings.filter((f) => f.severity === "hard");
163
+ // Teardown registrations are structural, not rubric: they run for every
164
+ // language (Java included) and are never marker-clearable — a missing or
165
+ // displaced `tracker.track_for_cleanup(...)` leaks a record on the shared target.
166
+ hardFindings.push(...lintCreatedAddLines(content, baseline?.createdAddLines));
161
167
  const commentToken = markerCommentToken(language);
162
168
  for (const line of findUnparsableAssertionsComplete(content, language)) {
163
169
  hardFindings.push({
@@ -123,12 +123,15 @@ export function isUtilsReuseEnabled() {
123
123
  const UTILS_REUSE_DEFAULT_ORGS = ["skyramp-handson"];
124
124
  const POM_REUSE_DEFAULT_ORGS = ["skyramp-handson"];
125
125
  function isDefaultOrg(orgs) {
126
+ // SKYRAMP_REPOSITORY is set by the engine on every provider; its first part
127
+ // is the owner, or the organization of an Azure DevOps "org/project/repo".
126
128
  // GITHUB_REPOSITORY_OWNER is set on GitHub Actions runners; fall back to
127
129
  // the owner half of GITHUB_REPOSITORY ("owner/repo") for environments that
128
130
  // only set the latter.
129
- const owner = process.env.GITHUB_REPOSITORY_OWNER ??
130
- process.env.GITHUB_REPOSITORY?.split("/")[0] ??
131
- "";
131
+ const owner = process.env.SKYRAMP_REPOSITORY?.split("/")[0] ||
132
+ (process.env.GITHUB_REPOSITORY_OWNER ??
133
+ process.env.GITHUB_REPOSITORY?.split("/")[0] ??
134
+ "");
132
135
  return orgs.includes(owner.toLowerCase());
133
136
  }
134
137
  /**
@@ -1,12 +1,26 @@
1
+ /** An Azure DevOps `org/project/repo`; the org is letters, digits and hyphens. */
2
+ export declare const AZURE_REPOSITORY_SLUG: RegExp;
3
+ /** A GitHub `owner/repo` or an Azure DevOps `org/project/repo`. */
4
+ export declare function isRepositorySlug(value: string): boolean;
1
5
  /**
2
6
  * `owner/repo` from a git remote URL. Accepts the two spellings a checkout's
3
7
  * `origin` carries — ssh (`git@host:owner/repo.git`) and https
4
8
  * (`https://host/owner/repo`) — with or without the `.git` suffix.
5
9
  *
10
+ * An Azure DevOps remote gives `org/project/repo` instead, with its names
11
+ * decoded (`%20` is a space), or undefined when the decoded names do not make
12
+ * an `AZURE_REPOSITORY_SLUG`.
13
+ *
6
14
  * Returns undefined when the URL yields no owner/repo pair, which the callers
7
15
  * treat differently: see `repositorySlugOf` and `tryRepositorySlug` below.
8
16
  */
9
17
  export declare function parseOwnerRepo(remoteUrl: string): string | undefined;
18
+ /**
19
+ * The repository a CI run works on: `SKYRAMP_REPOSITORY`, which the engine sets
20
+ * on every provider (`org/project/repo` on Azure DevOps), else GitHub Actions'
21
+ * own `GITHUB_REPOSITORY`. Undefined outside a run.
22
+ */
23
+ export declare function runRepository(env?: NodeJS.ProcessEnv): string | undefined;
10
24
  /**
11
25
  * `owner/repo` of the checkout at `dir`, or undefined when it is not a git repo,
12
26
  * has no `origin` remote, or the remote URL names no owner/repo pair.
@@ -3,21 +3,66 @@ import { promisify } from "util";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
5
5
  const execFileAsync = promisify(execFile);
6
+ // The https and ssh remotes Azure DevOps gives, on dev.azure.com and on the
7
+ // older {org}.visualstudio.com. An https URL without a project names the
8
+ // project's default repository, which has the project's name.
9
+ const AZURE_REMOTES = [
10
+ /^https?:\/\/(?:[^@/]+@)?dev\.azure\.com(?::\d+)?\/(?<org>[^/]+)\/(?:(?<project>[^/]+)\/)?_git\/(?<repo>[^/]+?)(?:\.git)?\/?$/i,
11
+ /^https?:\/\/(?:[^@/]+@)?(?<org>[^./]+)\.visualstudio\.com(?::\d+)?\/(?:DefaultCollection\/)?(?:(?<project>[^/]+)\/)?_git\/(?<repo>[^/]+?)(?:\.git)?\/?$/i,
12
+ /^(?:[^@/]+@)?(?:ssh\.dev\.azure\.com|vs-ssh\.visualstudio\.com):v3\/(?<org>[^/]+)\/(?<project>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?\/?$/i,
13
+ /^ssh:\/\/(?:[^@/]+@)?(?:ssh\.dev\.azure\.com|vs-ssh\.visualstudio\.com)(?::\d+)?\/v3\/(?<org>[^/]+)\/(?<project>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?\/?$/i,
14
+ ];
15
+ // An Azure DevOps project or repo name: any character but `/`, `\` and control
16
+ // characters, whitespace only inside it, and never `.` or `..`.
17
+ const AZURE_NAME = String.raw `(?!\.\.?(?:/|$))[^/\\\s\x00-\x1f\x7f-\x9f](?:[^/\\\x00-\x1f\x7f-\x9f]*[^/\\\s\x00-\x1f\x7f-\x9f])?`;
18
+ /** An Azure DevOps `org/project/repo`; the org is letters, digits and hyphens. */
19
+ export const AZURE_REPOSITORY_SLUG = new RegExp(`^[A-Za-z0-9][A-Za-z0-9-]*/${AZURE_NAME}/${AZURE_NAME}$`);
20
+ const GITHUB_REPOSITORY_SLUG = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
21
+ /** A GitHub `owner/repo` or an Azure DevOps `org/project/repo`. */
22
+ export function isRepositorySlug(value) {
23
+ return (GITHUB_REPOSITORY_SLUG.test(value) || AZURE_REPOSITORY_SLUG.test(value));
24
+ }
6
25
  /**
7
26
  * `owner/repo` from a git remote URL. Accepts the two spellings a checkout's
8
27
  * `origin` carries — ssh (`git@host:owner/repo.git`) and https
9
28
  * (`https://host/owner/repo`) — with or without the `.git` suffix.
10
29
  *
30
+ * An Azure DevOps remote gives `org/project/repo` instead, with its names
31
+ * decoded (`%20` is a space), or undefined when the decoded names do not make
32
+ * an `AZURE_REPOSITORY_SLUG`.
33
+ *
11
34
  * Returns undefined when the URL yields no owner/repo pair, which the callers
12
35
  * treat differently: see `repositorySlugOf` and `tryRepositorySlug` below.
13
36
  */
14
37
  export function parseOwnerRepo(remoteUrl) {
15
- const match = remoteUrl.trim().match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
38
+ const url = remoteUrl.trim();
39
+ const withoutQuery = url.replace(/[?#].*$/, "");
40
+ const azure = AZURE_REMOTES.map((form) => withoutQuery.match(form)).find(Boolean);
41
+ if (azure) {
42
+ const { org, project, repo } = azure.groups;
43
+ let slug;
44
+ try {
45
+ slug = [org, project ?? repo, repo].map(decodeURIComponent).join("/");
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ return AZURE_REPOSITORY_SLUG.test(slug) ? slug : undefined;
51
+ }
52
+ const match = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
16
53
  if (!match)
17
54
  return undefined;
18
55
  const [, owner, repo] = match;
19
56
  return `${owner}/${repo}`;
20
57
  }
58
+ /**
59
+ * The repository a CI run works on: `SKYRAMP_REPOSITORY`, which the engine sets
60
+ * on every provider (`org/project/repo` on Azure DevOps), else GitHub Actions'
61
+ * own `GITHUB_REPOSITORY`. Undefined outside a run.
62
+ */
63
+ export function runRepository(env = process.env) {
64
+ return env.SKYRAMP_REPOSITORY || env.GITHUB_REPOSITORY || undefined;
65
+ }
21
66
  // One `git remote get-url` per directory for the life of the process. The
22
67
  // value is the resolved slug, or undefined when the directory cannot name one.
23
68
  const slugCache = new Map();
@@ -21,6 +21,8 @@ export declare function getLocalRepositoryInfo(cwd: string): Promise<RepositoryI
21
21
  *
22
22
  * - In GitHub Actions (testbot), reads GITHUB_REPOSITORY / GITHUB_REPOSITORY_OWNER
23
23
  * which the MCP subprocess inherits — fast, no shelling out.
24
+ * - In Azure Pipelines, reads the engine's SKYRAMP_REPOSITORY ("org/project/repo")
25
+ * and reports the organization as the owner.
24
26
  * - In local/IDE usage, derives "owner/repo" from `git remote get-url origin`.
25
27
  * The lookup directory is the caller-supplied `cwd` (a tool's repositoryPath
26
28
  * or workspacePath) when available, since the MCP server's own process.cwd()
@@ -25,6 +25,9 @@ export function getCIPlatform() {
25
25
  return "jenkins";
26
26
  if (process.env.CIRCLECI === "true")
27
27
  return "circleci";
28
+ // Azure Pipelines sets TF_BUILD to "True".
29
+ if (process.env.TF_BUILD?.toLowerCase() === "true")
30
+ return "azure-pipelines";
28
31
  return undefined;
29
32
  }
30
33
  /**
@@ -73,6 +76,8 @@ export async function getLocalRepositoryInfo(cwd) {
73
76
  *
74
77
  * - In GitHub Actions (testbot), reads GITHUB_REPOSITORY / GITHUB_REPOSITORY_OWNER
75
78
  * which the MCP subprocess inherits — fast, no shelling out.
79
+ * - In Azure Pipelines, reads the engine's SKYRAMP_REPOSITORY ("org/project/repo")
80
+ * and reports the organization as the owner.
76
81
  * - In local/IDE usage, derives "owner/repo" from `git remote get-url origin`.
77
82
  * The lookup directory is the caller-supplied `cwd` (a tool's repositoryPath
78
83
  * or workspacePath) when available, since the MCP server's own process.cwd()
@@ -93,5 +98,12 @@ export async function getRepositoryInfo(cwd) {
93
98
  }
94
99
  return info;
95
100
  }
101
+ const engineRepository = process.env.SKYRAMP_REPOSITORY;
102
+ if (getCIPlatform() === "azure-pipelines" && engineRepository) {
103
+ return {
104
+ repository: engineRepository,
105
+ repositoryOwner: engineRepository.split("/")[0],
106
+ };
107
+ }
96
108
  return getLocalRepositoryInfo(cwd || process.cwd());
97
109
  }
@@ -37,7 +37,9 @@ export interface ActionStep {
37
37
  * do not balance. A helper with one such step has no key; a spec's other steps
38
38
  * are still read, so an unreadable step is a boundary no matched run crosses. */
39
39
  key?: string;
40
- /** 1-based line of the action verb. */
40
+ /** 1-based line where the action's chain starts (`page` in a prettier-wrapped
41
+ * `await page\n .getByTestId(…)\n .click()`); the verb's line when the chain
42
+ * starts inside the previous action. */
41
43
  line: number;
42
44
  /** The source between the previous action's closing `)` (or the start of the
43
45
  * body) and this action's chain: what stands between two consecutive actions.
@@ -47,6 +49,11 @@ export interface ActionStep {
47
49
  /** The source after this action's closing `)` up to the next action's chain, or
48
50
  * to the end of the body for the last step — the next step's `gapBefore`. */
49
51
  gapAfter: string;
52
+ /** 1-based line of the action's closing `)`, where `gapAfter` starts. */
53
+ endLine: number;
54
+ /** The action's first argument as written (`""` when it has none): the value a
55
+ * `fill` types, which the key leaves out. */
56
+ value: string;
50
57
  }
51
58
  /**
52
59
  * Every action in `body`, in order, each keyed as `actionKeyOf` keys it. Where
@@ -55,12 +62,62 @@ export interface ActionStep {
55
62
  * copy of a helper's sequence needs. Lines survive the Python and alias rewrites:
56
63
  * neither adds or removes a newline.
57
64
  */
58
- export declare function actionStepsOf(body: string, language?: UtilsLanguage): ActionStep[];
65
+ export declare function actionStepsOf(body: string, language?: UtilsLanguage,
66
+ /** A helper's parameter names: a locator argument (or `name:` option) that is
67
+ * one of them keys as the wildcard `paramSlot(name)` instead of failing. */
68
+ params?: ReadonlySet<string>): ActionStep[];
59
69
  /** `click empty-cart-btn; fill qty` → `click \`empty-cart-btn\`; fill \`qty\`` — the
60
70
  * key as the agent reads it, selectors quoted so a `#id` never reads as prose. */
61
71
  export declare function describeActionKey(key: string): string;
62
72
  /** Index of the `)` that closes the `(` at `open`, on a string-blanked copy. */
63
73
  export declare function matchingClose(structure: string, open: number): number;
74
+ /** The text a parameter-fed locator argument keys as: a string no selector holds.
75
+ * A key holding SLOT_MARK holds a wildcard. */
76
+ export declare const SLOT_MARK = "\u0001";
77
+ export declare function paramSlot(name: string): string;
78
+ /** What an action's value is, as `resolveStepValue` reads it: one of the reading
79
+ * helper's parameters, a literal, or another expression (kept as written). */
80
+ export interface StepValue {
81
+ kind: "param" | "literal" | "expr";
82
+ text: string;
83
+ /** The zero-argument calls chained on a resolved `getValue(…)`, spaces dropped:
84
+ * `.toString()` for a number the fill types. */
85
+ suffix?: string;
86
+ }
87
+ /**
88
+ * An action's value (`ActionStep.value`) as the sibling scan compares it: a bare
89
+ * parameter of the helper, a literal, or the generated `getValue(playwrightRequestN,
90
+ * "key")` — a dotted key read through nested objects, a chained `.toString()` kept
91
+ * as `suffix` — read through the binding that holds it (`requestBindingsOf`), to
92
+ * the parameter or literal stored under that key. Anything else is an expression,
93
+ * compared as written.
94
+ */
95
+ export declare function resolveStepValue(raw: string, bindings: ReadonlyMap<string, string>, params?: ReadonlySet<string>): StepValue;
96
+ /** The generator's `const playwrightRequestN: Record<…> = { … }` bindings in `text`,
97
+ * by name, to the object literal as written. A name bound twice is left out. */
98
+ export declare function requestBindingsOf(text: string): Map<string, string>;
99
+ /** The normalised arguments of every `page.waitForResponse(…)` in `text`. */
100
+ export declare function responseWaitsOf(text: string): string[];
101
+ /** A use of the identifier `name`: not part of a longer name or a property access. */
102
+ export declare function identifierUseRe(name: string, flags?: string): RegExp;
103
+ /** A declared parameter's name (see paramName), or `undefined` for a rest or
104
+ * destructured one: no single value a call or a wildcard can carry. */
105
+ export declare function simpleParamName(raw: string): string | undefined;
106
+ /** A browser helper's sequence as the sibling scan matches it (SKYR-4537). */
107
+ export interface ActionPattern {
108
+ /** One key per step, as `actionStepKeys` keys it, except that a locator argument
109
+ * fed by a parameter is the wildcard `paramSlot(name)`. */
110
+ steps: string[];
111
+ /** Each step's value, read through the helper's own request bindings. */
112
+ values: StepValue[];
113
+ /** The helper's `waitForResponse` arguments (see responseWaitsOf). */
114
+ responseWaits: string[];
115
+ /** The parameters the body reads: one a call leaves out changes what it does. */
116
+ usedParams: string[];
117
+ }
118
+ /** The helper's ActionPattern, or `undefined` when a step stays unreadable with
119
+ * its parameters as wildcards. `params` are the declared parameters as written. */
120
+ export declare function actionPatternOf(body: string, language: UtilsLanguage, params: readonly string[]): ActionPattern | undefined;
64
121
  /** Whitespace and braces dropped outside string literals, `'` normalised to `"` —
65
122
  * the spelling a locator chain has inside a key. */
66
123
  export declare function normalizeChain(text: string): string;