@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.
- package/build/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +16 -0
- package/build/index.js +32 -6
- package/build/playwright/PlaywrightTraceService.d.ts +17 -0
- package/build/playwright/PlaywrightTraceService.js +29 -1
- package/build/playwright/dataSafetyConfig.d.ts +74 -0
- package/build/playwright/dataSafetyConfig.js +109 -0
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +44 -0
- package/build/playwright/registerPlaywrightTools.d.ts +36 -1
- package/build/playwright/registerPlaywrightTools.js +87 -43
- package/build/playwright/safeResolveDataSafety.d.ts +24 -0
- package/build/playwright/safeResolveDataSafety.js +33 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
- package/build/services/FleetExecutionService.d.ts +87 -16
- package/build/services/FleetExecutionService.js +487 -48
- package/build/services/FleetRunRegistry.d.ts +49 -0
- package/build/services/FleetRunRegistry.js +178 -0
- package/build/services/containerEnv.js +8 -2
- package/build/services/fleetLimitConfirmation.d.ts +19 -0
- package/build/services/fleetLimitConfirmation.js +126 -0
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +88 -43
- package/build/tools/code-refactor/assertion-state.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -1
- package/build/tools/code-refactor/utils-verify-gates.js +1 -1
- package/build/tools/executeFleetTool.d.ts +6 -4
- package/build/tools/executeFleetTool.js +67 -23
- package/build/tools/fixErrorTool.js +2 -1
- package/build/tools/fleetLifecycleTools.d.ts +20 -0
- package/build/tools/fleetLifecycleTools.js +124 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
- package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
- package/build/tools/submitReportTool.js +4 -122
- package/build/tools/test-management/registerTestPlanTool.js +22 -6
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
- package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
- package/build/types/AssertionOutcome.d.ts +3 -0
- package/build/types/TestbotReport.d.ts +0 -37
- package/build/types/TestbotReport.js +0 -22
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
- package/build/utils/assertion-verify/created-add-guard.js +173 -0
- package/build/utils/assertion-verify/index.d.ts +1 -0
- package/build/utils/assertion-verify/index.js +1 -0
- package/build/utils/assertion-verify/verify.d.ts +7 -3
- package/build/utils/assertion-verify/verify.js +9 -3
- package/build/utils/featureFlags.js +6 -3
- package/build/utils/repositorySlug.d.ts +14 -0
- package/build/utils/repositorySlug.js +46 -1
- package/build/utils/telemetry.d.ts +2 -0
- package/build/utils/telemetry.js +12 -0
- package/build/utils/utils-verify/action-key.d.ts +59 -2
- package/build/utils/utils-verify/action-key.js +162 -6
- package/build/utils/utils-verify/action-sites.d.ts +7 -0
- package/build/utils/utils-verify/action-sites.js +277 -35
- package/build/utils/utils-verify/call-sites.d.ts +8 -2
- package/build/utils/utils-verify/language-spec.js +1 -1
- package/build/utils/utils-verify/parse.d.ts +4 -0
- package/build/utils/utils-verify/parse.js +2 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +63 -121
- package/build/workspace/workspace.js +45 -48
- package/build/workspace/workspaceVerifier.d.ts +22 -0
- package/build/workspace/workspaceVerifier.js +65 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
- package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
- 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/cli/program.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -4
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
- package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
- package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
- package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
- package/build/prompts/sut-setup/shared.d.ts +0 -62
- package/build/prompts/sut-setup/shared.js +0 -196
- package/build/resources/sutSetupResource.d.ts +0 -2
- package/build/resources/sutSetupResource.js +0 -63
- 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,
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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,
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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.
|
|
130
|
-
process.env.
|
|
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
|
|
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()
|
package/build/utils/telemetry.js
CHANGED
|
@@ -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
|
|
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
|
|
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;
|