@skyramp/mcp 0.2.8 → 0.2.10-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +1 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
- package/build/prompts/personas.d.ts +2 -2
- package/build/prompts/personas.js +2 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +6 -1
- package/build/prompts/sut-setup/shared.d.ts +20 -0
- package/build/prompts/sut-setup/shared.js +67 -7
- package/build/prompts/test-maintenance/actionsInstructions.js +5 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +15 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +63 -3
- package/build/prompts/test-maintenance/driftAnalysisSections.js +7 -25
- package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +20 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +80 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +32 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +212 -0
- package/build/prompts/test-recommendation/recommendationShared.js +1 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +12 -2
- package/build/prompts/testbot/testbot-prompts.js +10 -9
- package/build/resources/testbotResource.js +1 -1
- package/build/services/TestDiscoveryService.d.ts +12 -10
- package/build/services/TestDiscoveryService.js +137 -83
- package/build/services/TestDiscoveryService.test.js +370 -15
- package/build/services/TestExecutionService.d.ts +1 -1
- package/build/services/TestExecutionService.js +7 -7
- package/build/services/TestExecutionService.test.js +4 -1
- package/build/tools/executeSkyrampTestTool.js +40 -71
- package/build/tools/submitReportTool.d.ts +25 -25
- package/build/tools/submitReportTool.js +123 -34
- package/build/tools/submitReportTool.test.js +413 -94
- package/build/tools/test-management/actionsTool.js +177 -77
- package/build/tools/test-management/actionsTool.test.d.ts +1 -0
- package/build/tools/test-management/actionsTool.test.js +297 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
- package/build/tools/test-management/analyzeChangesTool.js +74 -169
- package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
- package/build/tools/test-management/analyzeTestHealthTool.js +63 -22
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeTestHealthTool.test.js +268 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +37 -24
- package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
- package/build/types/FrontendIntegration.d.ts +25 -0
- package/build/types/FrontendIntegration.js +19 -0
- package/build/types/RepositoryAnalysis.d.ts +8 -8
- package/build/types/TestAnalysis.d.ts +18 -49
- package/build/types/TestAnalysis.js +0 -28
- package/build/types/TestTypes.d.ts +3 -1
- package/build/types/TestTypes.js +3 -1
- package/build/types/TestbotReport.d.ts +1 -1
- package/build/utils/AnalysisStateManager.d.ts +16 -22
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.d.ts +1 -1
- package/build/utils/featureFlags.js +1 -1
- package/build/utils/frontendIntegration.d.ts +9 -0
- package/build/utils/frontendIntegration.js +237 -0
- package/build/utils/frontendIntegration.test.d.ts +1 -0
- package/build/utils/frontendIntegration.test.js +229 -0
- package/build/utils/pr-comment-parser.d.ts +3 -3
- package/build/utils/pr-comment-parser.js +6 -6
- package/build/utils/pr-comment-parser.test.js +3 -3
- package/build/utils/repoScanner.d.ts +7 -0
- package/build/utils/repoScanner.js +14 -6
- package/build/utils/repoScanner.test.js +9 -9
- package/build/utils/routeParsers.d.ts +32 -0
- package/build/utils/routeParsers.js +205 -2
- package/build/utils/routeParsers.test.js +279 -29
- package/build/utils/sourceRouteExtractor.js +174 -5
- package/build/utils/sourceRouteExtractor.test.js +173 -0
- package/build/utils/utils.d.ts +11 -0
- package/build/utils/utils.js +19 -0
- package/build/utils/utils.test.js +23 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +21 -21
- package/build/workspace/workspace.js +7 -4
- package/build/workspace/workspace.test.js +65 -6
- package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
- package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
|
@@ -16,7 +16,7 @@ export declare const targetElementSchema: z.ZodObject<{
|
|
|
16
16
|
stableId: string | null;
|
|
17
17
|
contextText: string[] | null;
|
|
18
18
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
19
|
-
widgetType?: "
|
|
19
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
20
20
|
}, {
|
|
21
21
|
role: string;
|
|
22
22
|
accessibleName: string;
|
|
@@ -24,7 +24,7 @@ export declare const targetElementSchema: z.ZodObject<{
|
|
|
24
24
|
stableId: string | null;
|
|
25
25
|
contextText: string[] | null;
|
|
26
26
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
27
|
-
widgetType?: "
|
|
27
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
28
28
|
}>;
|
|
29
29
|
export declare const pageContextSchema: z.ZodObject<{
|
|
30
30
|
url: z.ZodString;
|
|
@@ -39,10 +39,10 @@ export declare const pageContextSchema: z.ZodObject<{
|
|
|
39
39
|
export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
40
40
|
testId: z.ZodString;
|
|
41
41
|
testType: z.ZodNativeEnum<typeof TestType>;
|
|
42
|
-
category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "
|
|
42
|
+
category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
|
|
43
43
|
endpoint: z.ZodString;
|
|
44
44
|
fileName: z.ZodString;
|
|
45
|
-
description: z.
|
|
45
|
+
description: z.ZodString;
|
|
46
46
|
scenarioFile: z.ZodOptional<z.ZodString>;
|
|
47
47
|
traceFile: z.ZodOptional<z.ZodString>;
|
|
48
48
|
frontendTrace: z.ZodOptional<z.ZodString>;
|
|
@@ -63,7 +63,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
63
63
|
stableId: string | null;
|
|
64
64
|
contextText: string[] | null;
|
|
65
65
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
66
|
-
widgetType?: "
|
|
66
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
67
67
|
}, {
|
|
68
68
|
role: string;
|
|
69
69
|
accessibleName: string;
|
|
@@ -71,7 +71,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
71
71
|
stableId: string | null;
|
|
72
72
|
contextText: string[] | null;
|
|
73
73
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
74
|
-
widgetType?: "
|
|
74
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
75
75
|
}>, "many">>>;
|
|
76
76
|
pageContext: z.ZodOptional<z.ZodObject<{
|
|
77
77
|
url: z.ZodString;
|
|
@@ -84,13 +84,13 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
84
84
|
pageHash?: string | undefined;
|
|
85
85
|
}>>;
|
|
86
86
|
}, "strip", z.ZodTypeAny, {
|
|
87
|
+
description: string;
|
|
87
88
|
testType: TestType;
|
|
88
|
-
category: "
|
|
89
|
+
category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
89
90
|
fileName: string;
|
|
90
91
|
endpoint: string;
|
|
91
92
|
testId: string;
|
|
92
93
|
reasoning: string;
|
|
93
|
-
description?: string | undefined;
|
|
94
94
|
repository?: string | undefined;
|
|
95
95
|
traceFile?: string | undefined;
|
|
96
96
|
scenarioFile?: string | undefined;
|
|
@@ -102,19 +102,19 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
102
102
|
stableId: string | null;
|
|
103
103
|
contextText: string[] | null;
|
|
104
104
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
105
|
-
widgetType?: "
|
|
105
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
106
106
|
}[] | null | undefined;
|
|
107
107
|
pageContext?: {
|
|
108
108
|
url: string;
|
|
109
109
|
pageHash?: string | undefined;
|
|
110
110
|
} | undefined;
|
|
111
111
|
}, {
|
|
112
|
+
description: string;
|
|
112
113
|
testType: TestType;
|
|
113
114
|
fileName: string;
|
|
114
115
|
endpoint: string;
|
|
115
116
|
testId: string;
|
|
116
117
|
reasoning: string;
|
|
117
|
-
description?: string | undefined;
|
|
118
118
|
repository?: string | undefined;
|
|
119
119
|
category?: unknown;
|
|
120
120
|
traceFile?: string | undefined;
|
|
@@ -127,20 +127,20 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
127
127
|
stableId: string | null;
|
|
128
128
|
contextText: string[] | null;
|
|
129
129
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
130
|
-
widgetType?: "
|
|
130
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
131
131
|
}[] | null | undefined;
|
|
132
132
|
pageContext?: {
|
|
133
133
|
url: string;
|
|
134
134
|
pageHash?: string | undefined;
|
|
135
135
|
} | undefined;
|
|
136
136
|
}>, {
|
|
137
|
+
description: string;
|
|
137
138
|
testType: TestType;
|
|
138
|
-
category: "
|
|
139
|
+
category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
139
140
|
fileName: string;
|
|
140
141
|
endpoint: string;
|
|
141
142
|
testId: string;
|
|
142
143
|
reasoning: string;
|
|
143
|
-
description?: string | undefined;
|
|
144
144
|
repository?: string | undefined;
|
|
145
145
|
traceFile?: string | undefined;
|
|
146
146
|
scenarioFile?: string | undefined;
|
|
@@ -152,19 +152,19 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
152
152
|
stableId: string | null;
|
|
153
153
|
contextText: string[] | null;
|
|
154
154
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
155
|
-
widgetType?: "
|
|
155
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
156
156
|
}[] | null | undefined;
|
|
157
157
|
pageContext?: {
|
|
158
158
|
url: string;
|
|
159
159
|
pageHash?: string | undefined;
|
|
160
160
|
} | undefined;
|
|
161
161
|
}, {
|
|
162
|
+
description: string;
|
|
162
163
|
testType: TestType;
|
|
163
164
|
fileName: string;
|
|
164
165
|
endpoint: string;
|
|
165
166
|
testId: string;
|
|
166
167
|
reasoning: string;
|
|
167
|
-
description?: string | undefined;
|
|
168
168
|
repository?: string | undefined;
|
|
169
169
|
category?: unknown;
|
|
170
170
|
traceFile?: string | undefined;
|
|
@@ -177,7 +177,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
177
177
|
stableId: string | null;
|
|
178
178
|
contextText: string[] | null;
|
|
179
179
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
180
|
-
widgetType?: "
|
|
180
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
181
181
|
}[] | null | undefined;
|
|
182
182
|
pageContext?: {
|
|
183
183
|
url: string;
|
|
@@ -187,7 +187,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
|
|
|
187
187
|
export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
188
188
|
testId: z.ZodString;
|
|
189
189
|
testType: z.ZodNativeEnum<typeof TestType>;
|
|
190
|
-
category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "
|
|
190
|
+
category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
|
|
191
191
|
primaryEndpoint: z.ZodOptional<z.ZodString>;
|
|
192
192
|
scenarioName: z.ZodOptional<z.ZodString>;
|
|
193
193
|
steps: z.ZodArray<z.ZodObject<{
|
|
@@ -234,7 +234,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
234
234
|
stableId: string | null;
|
|
235
235
|
contextText: string[] | null;
|
|
236
236
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
237
|
-
widgetType?: "
|
|
237
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
238
238
|
}, {
|
|
239
239
|
role: string;
|
|
240
240
|
accessibleName: string;
|
|
@@ -242,7 +242,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
242
242
|
stableId: string | null;
|
|
243
243
|
contextText: string[] | null;
|
|
244
244
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
245
|
-
widgetType?: "
|
|
245
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
246
246
|
}>, "many">>>;
|
|
247
247
|
pageContext: z.ZodOptional<z.ZodObject<{
|
|
248
248
|
url: z.ZodString;
|
|
@@ -258,7 +258,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
258
258
|
description: string;
|
|
259
259
|
priority: "high" | "medium" | "low";
|
|
260
260
|
testType: TestType;
|
|
261
|
-
category: "
|
|
261
|
+
category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
262
262
|
steps: {
|
|
263
263
|
description: string;
|
|
264
264
|
path?: string | undefined;
|
|
@@ -279,7 +279,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
279
279
|
stableId: string | null;
|
|
280
280
|
contextText: string[] | null;
|
|
281
281
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
282
|
-
widgetType?: "
|
|
282
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
283
283
|
}[] | null | undefined;
|
|
284
284
|
pageContext?: {
|
|
285
285
|
url: string;
|
|
@@ -313,7 +313,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
313
313
|
stableId: string | null;
|
|
314
314
|
contextText: string[] | null;
|
|
315
315
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
316
|
-
widgetType?: "
|
|
316
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
317
317
|
}[] | null | undefined;
|
|
318
318
|
pageContext?: {
|
|
319
319
|
url: string;
|
|
@@ -326,7 +326,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
326
326
|
description: string;
|
|
327
327
|
priority: "high" | "medium" | "low";
|
|
328
328
|
testType: TestType;
|
|
329
|
-
category: "
|
|
329
|
+
category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
330
330
|
steps: {
|
|
331
331
|
description: string;
|
|
332
332
|
path?: string | undefined;
|
|
@@ -347,7 +347,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
347
347
|
stableId: string | null;
|
|
348
348
|
contextText: string[] | null;
|
|
349
349
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
350
|
-
widgetType?: "
|
|
350
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
351
351
|
}[] | null | undefined;
|
|
352
352
|
pageContext?: {
|
|
353
353
|
url: string;
|
|
@@ -381,7 +381,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
|
|
|
381
381
|
stableId: string | null;
|
|
382
382
|
contextText: string[] | null;
|
|
383
383
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
384
|
-
widgetType?: "
|
|
384
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
385
385
|
}[] | null | undefined;
|
|
386
386
|
pageContext?: {
|
|
387
387
|
url: string;
|
|
@@ -8,7 +8,7 @@ import { TestType, HttpMethod } from "../types/TestTypes.js";
|
|
|
8
8
|
import { DriftAction } from "../types/TestAnalysis.js";
|
|
9
9
|
import { TestExecutionStatus } from "../types/TestExecution.js";
|
|
10
10
|
import { StateManager } from "../utils/AnalysisStateManager.js";
|
|
11
|
-
import { toolError } from "../utils/utils.js";
|
|
11
|
+
import { toolError, testFileMatches } from "../utils/utils.js";
|
|
12
12
|
// Drift actions that actually modify a test file. VERIFY and IGNORE are
|
|
13
13
|
// no-ops (the test was assessed but left unchanged), so they must not count
|
|
14
14
|
// toward "tests maintained" telemetry.
|
|
@@ -77,7 +77,7 @@ export const newTestSchema = z.object({
|
|
|
77
77
|
category: z.preprocess((val) => externalCategory(val), z.enum(TEST_CATEGORIES)).describe("Test category — critical categories (security_boundary, business_rule, data_integrity, breaking_change) get generation priority over workflow"),
|
|
78
78
|
endpoint: z.string().describe("HTTP verb and path, e.g. 'GET /api/v1/products'"),
|
|
79
79
|
fileName: z.string().describe("Name of the generated test file"),
|
|
80
|
-
description: z.string().
|
|
80
|
+
description: z.string().trim().min(1).describe("What the test does — the steps and assertions, not the bugs it finds. e.g. 'Creates a collection, adds a link, then verifies the link exists'. Do NOT describe expected failures or bugs here — those belong in issuesFound."),
|
|
81
81
|
scenarioFile: z.string().optional().describe("Path to the scenario JSON file if one was generated (e.g. 'tests/scenario_collections-links.json')"),
|
|
82
82
|
traceFile: z.string().optional().describe("Path to the backend trace file if used or created"),
|
|
83
83
|
frontendTrace: z.string().optional().describe("Path to the Playwright/UI trace file if used or created"),
|
|
@@ -149,7 +149,7 @@ export const newTestSchema = z.object({
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
|
-
const
|
|
152
|
+
const issueFoundSchema = z.object({
|
|
153
153
|
description: z.string().describe("One-line description. Do NOT prefix with the severity level — severity is a separate field. Include code logic bugs from the diff, test generation/execution failures, and environment misconfiguration."),
|
|
154
154
|
severity: z
|
|
155
155
|
.enum(["critical", "high", "medium", "low"])
|
|
@@ -244,10 +244,14 @@ export const additionalRecommendationSchema = z.object({
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
});
|
|
247
|
+
// TODO(multi-repo maintenance): no `repository` field yet — see readData() TODO below.
|
|
247
248
|
const testMaintenanceSchema = z.object({
|
|
248
249
|
testType: z.nativeEnum(TestType).describe("Type of test."),
|
|
249
250
|
endpoint: z.string().describe("HTTP verb and path, e.g. 'GET /api/v1/products'"),
|
|
250
|
-
|
|
251
|
+
testFilePath: z
|
|
252
|
+
.string()
|
|
253
|
+
.refine((p) => path.isAbsolute(p), { message: "testFilePath must be an absolute path" })
|
|
254
|
+
.describe("Absolute path of the test file that was maintained, e.g. '/repo/tests/products_smoke_test.py' — the same path you passed to skyramp_execute_test's testFile param. Consumers should basename this for display."),
|
|
251
255
|
action: z.nativeEnum(DriftAction).describe("The drift action assigned to this test during maintenance triage."),
|
|
252
256
|
description: z.string().describe("What was changed and why"),
|
|
253
257
|
beforeDetails: z.string().describe("One line only — no embedded newlines, no raw HTTP headers or JSON blobs. " +
|
|
@@ -269,6 +273,14 @@ const testMaintenanceLLMSchema = testMaintenanceSchema.omit({
|
|
|
269
273
|
beforeStatus: true,
|
|
270
274
|
afterStatus: true,
|
|
271
275
|
});
|
|
276
|
+
// LLM only drafts beforeDetails/afterDetails per file — action/testType/endpoint/description
|
|
277
|
+
// come from stateData.maintenanceVerdicts. Picked from testMaintenanceLLMSchema to avoid
|
|
278
|
+
// redeclaring those fields.
|
|
279
|
+
const testMaintenanceDetailSchema = testMaintenanceLLMSchema.pick({
|
|
280
|
+
testFilePath: true,
|
|
281
|
+
beforeDetails: true,
|
|
282
|
+
afterDetails: true,
|
|
283
|
+
});
|
|
272
284
|
/**
|
|
273
285
|
* Derive per-run analytics counts from a submitted report. These power the
|
|
274
286
|
* alpha-launch dashboards (tests generated/maintained, suite growth, bugs vs
|
|
@@ -327,6 +339,12 @@ function deduplicateById(items) {
|
|
|
327
339
|
}
|
|
328
340
|
export function registerSubmitReportTool(server) {
|
|
329
341
|
server.registerTool(TOOL_NAME, {
|
|
342
|
+
annotations: {
|
|
343
|
+
readOnlyHint: false,
|
|
344
|
+
destructiveHint: true, // overwrites summaryOutputFile on disk
|
|
345
|
+
idempotentHint: false,
|
|
346
|
+
openWorldHint: false,
|
|
347
|
+
},
|
|
330
348
|
description: "Submit the final testbot report. Call this tool once after completing all test analysis, generation, and execution. " +
|
|
331
349
|
"This is the ONLY way to submit the report — do NOT write the report to a file manually.",
|
|
332
350
|
inputSchema: {
|
|
@@ -335,7 +353,8 @@ export function registerSubmitReportTool(server) {
|
|
|
335
353
|
.describe("The file path where the report should be written (provided in the task instructions)"),
|
|
336
354
|
businessCaseAnalysis: z
|
|
337
355
|
.string()
|
|
338
|
-
.
|
|
356
|
+
.min(1, "businessCaseAnalysis is required — describe what the PR does, even for no-test runs (config-only, non-application, Testbot onboarding). It is never optional and must not be empty.")
|
|
357
|
+
.describe("1-2 sentences describing what user-facing interactions this PR enables or changes (e.g. 'customers can now leave and view product reviews'). Focus on the user journey, not technical implementation. Flag backend-only or frontend-only gaps. Always required — even when no tests apply (config-only diffs, Testbot onboarding PRs); describe the PR's purpose and why no tests apply."),
|
|
339
358
|
newTestsCreated: z
|
|
340
359
|
.array(newTestSchema)
|
|
341
360
|
.describe("List of new tests created. Use empty array [] if none."),
|
|
@@ -344,15 +363,15 @@ export function registerSubmitReportTool(server) {
|
|
|
344
363
|
.optional()
|
|
345
364
|
.default([])
|
|
346
365
|
.describe("Recommended tests that were not generated (lower priority). Only include recommendations that add distinct coverage beyond generated tests — do not pad with variants testing the same endpoint and flow."),
|
|
347
|
-
|
|
348
|
-
.array(
|
|
366
|
+
testMaintenanceDetails: z
|
|
367
|
+
.array(testMaintenanceDetailSchema)
|
|
349
368
|
.optional()
|
|
350
|
-
.describe("One entry per test
|
|
369
|
+
.describe("One entry per maintained test that had a before/after execution recorded, with beforeDetails/afterDetails drafted from what you actually saw when you ran it. Omit an entry entirely when no execution was recorded for that test."),
|
|
351
370
|
testResults: z
|
|
352
371
|
.array(testResultSchema)
|
|
353
372
|
.describe("List of ALL test execution results. One entry per test executed."),
|
|
354
373
|
issuesFound: z
|
|
355
|
-
.array(
|
|
374
|
+
.array(issueFoundSchema)
|
|
356
375
|
.describe("List of issues, failures, or bugs found. Use empty array [] if none."),
|
|
357
376
|
nextSteps: z
|
|
358
377
|
.array(z.string())
|
|
@@ -360,6 +379,7 @@ export function registerSubmitReportTool(server) {
|
|
|
360
379
|
.default([])
|
|
361
380
|
.describe("Actionable follow-ups for the PR author. Each entry must be a single-line string (no embedded newlines). " +
|
|
362
381
|
"Include a next step for every critical/high severity issue in issuesFound. No next steps for low-severity issues. " +
|
|
382
|
+
"Also include a next step for every external test (Playwright, Cypress, RTL) assigned REGENERATE or DELETE in testMaintenance — the developer must act on these manually since they cannot be auto-applied (e.g. 'Regenerate frontend/tests/cart_pom.spec.ts — CartLine structure changed, all selectors need re-recording' or 'Delete frontend/tests/homepage.spec.ts — /cart route removed'). " +
|
|
363
383
|
"If multiple tests fail with 404 or connection refused: suggest checking targetSetupCommand/targetReadyCheckCommand. " +
|
|
364
384
|
"If 401/403 on auth endpoints: suggest authTokenCommand. " +
|
|
365
385
|
"When referencing code, use file name and relevant code pattern — no line numbers unless certain."),
|
|
@@ -385,31 +405,93 @@ export function registerSubmitReportTool(server) {
|
|
|
385
405
|
let errorResult;
|
|
386
406
|
const dedupedNewTests = deduplicateById([...params.newTestsCreated]);
|
|
387
407
|
const dedupedRecommendations = deduplicateById([...(params.additionalRecommendations ?? [])]);
|
|
408
|
+
const stateManager = StateManager.fromStatePath(params.stateFile);
|
|
409
|
+
let stateData;
|
|
410
|
+
try {
|
|
411
|
+
// Only reads the primary repo — see the related-repo check below for why related
|
|
412
|
+
// repos' maintenanceVerdicts are never folded in here.
|
|
413
|
+
stateData = await stateManager.readData();
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
errorResult = toolError(`Failed to load stateFile: ${err.message}`);
|
|
417
|
+
return errorResult;
|
|
418
|
+
}
|
|
419
|
+
if (!stateData) {
|
|
420
|
+
errorResult = toolError(`stateFile not found or empty: ${params.stateFile}`);
|
|
421
|
+
return errorResult;
|
|
422
|
+
}
|
|
423
|
+
// undefined + existing tests present means skyramp_actions was never called this
|
|
424
|
+
// run — not "no maintenance needed" (that case writes an empty array). Reject rather
|
|
425
|
+
// than silently reporting an empty testMaintenance section indistinguishable from a
|
|
426
|
+
// real "nothing to do". With zero existing tests there was nothing to lose, so
|
|
427
|
+
// undefined is safe there — no round-trip through skyramp_actions required.
|
|
428
|
+
if (stateData.maintenanceVerdicts === undefined && (stateData.existingTests?.length ?? 0) > 0) {
|
|
429
|
+
errorResult = toolError("stateFile has existingTests but no maintenanceVerdicts — skyramp_actions was not called for this run. " +
|
|
430
|
+
"Call skyramp_actions (with recommendations: [] if no existing tests needed action) before skyramp_submit_report.");
|
|
431
|
+
return errorResult;
|
|
432
|
+
}
|
|
433
|
+
// TestbotReport.testMaintenance has no repository field, so a related repo's
|
|
434
|
+
// reconciled verdicts have nowhere to go in this report. Fail loud instead of
|
|
435
|
+
// silently omitting real maintenance work skyramp_actions already did and persisted.
|
|
436
|
+
// Multi-repo maintenance reporting is a separate, unscoped feature (would also need
|
|
437
|
+
// testbot prompt / drift-analysis / test-bot.git renderer changes) — not done here.
|
|
438
|
+
const relatedReposWithVerdicts = [];
|
|
439
|
+
for (const repo of await stateManager.listRelatedRepos()) {
|
|
440
|
+
const relatedData = await stateManager.readRepoData(repo);
|
|
441
|
+
if ((relatedData?.maintenanceVerdicts?.length ?? 0) > 0)
|
|
442
|
+
relatedReposWithVerdicts.push(repo);
|
|
443
|
+
}
|
|
444
|
+
if (relatedReposWithVerdicts.length > 0) {
|
|
445
|
+
errorResult = toolError(`Related repo(s) have reconciled maintenance verdicts that cannot be included in this report yet ` +
|
|
446
|
+
`(multi-repo maintenance reporting is not supported — testMaintenance has no repository field): ${relatedReposWithVerdicts.join(", ")}.`);
|
|
447
|
+
return errorResult;
|
|
448
|
+
}
|
|
449
|
+
// action/testType/endpoint/description come entirely from stateData.maintenanceVerdicts
|
|
450
|
+
// (written by skyramp_actions right after drift analysis) — the LLM never supplies
|
|
451
|
+
// testMaintenance directly, so there's nothing to re-transcribe here.
|
|
452
|
+
const rawMaintenance = (stateData.maintenanceVerdicts ?? []).map((v) => ({
|
|
453
|
+
testFilePath: v.testFilePath,
|
|
454
|
+
testType: v.testType,
|
|
455
|
+
endpoint: v.endpoint,
|
|
456
|
+
action: v.action,
|
|
457
|
+
description: v.rationale,
|
|
458
|
+
beforeDetails: "",
|
|
459
|
+
afterDetails: "",
|
|
460
|
+
}));
|
|
388
461
|
let testMaintenance;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
462
|
+
// Rows missing a required detail (a real execution happened but the LLM didn't
|
|
463
|
+
// draft a summary for it) — collected here and rejected below, rather than
|
|
464
|
+
// silently shipping a blank field.
|
|
465
|
+
const missingDetails = [];
|
|
466
|
+
if (rawMaintenance.length > 0) {
|
|
467
|
+
// beforeStatus/afterStatus are always stateFile-authoritative. beforeDetails/
|
|
468
|
+
// afterDetails come from the LLM's drafted summary (testMaintenanceDetails) —
|
|
469
|
+
// required whenever the stateFile shows a matching execution actually ran.
|
|
470
|
+
testMaintenance = rawMaintenance.map((m) => {
|
|
471
|
+
const recorded = stateData.existingTests?.find((t) => testFileMatches(t.testFile, m.testFilePath));
|
|
472
|
+
const detail = params.testMaintenanceDetails?.find((d) => d.testFilePath === m.testFilePath);
|
|
473
|
+
const displayName = path.basename(m.testFilePath);
|
|
474
|
+
const defaultBeforeStatus = MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
|
|
475
|
+
const defaultAfterStatus = m.action === DriftAction.Delete ? TestExecutionStatus.Skipped
|
|
476
|
+
: MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
|
|
477
|
+
const beforeStatus = recorded?.executionBefore?.status ?? defaultBeforeStatus;
|
|
478
|
+
const afterStatus = recorded?.executionAfter?.status ?? defaultAfterStatus;
|
|
479
|
+
// Trim before checking — a whitespace-only string is semantically blank and
|
|
480
|
+
// must not satisfy the "drafted a summary" requirement.
|
|
481
|
+
const beforeDetails = detail?.beforeDetails?.trim() ?? "";
|
|
482
|
+
const afterDetails = detail?.afterDetails?.trim() ?? "";
|
|
483
|
+
if (recorded?.executionBefore && !beforeDetails)
|
|
484
|
+
missingDetails.push(`${displayName} (beforeDetails)`);
|
|
485
|
+
if (recorded?.executionAfter && !afterDetails)
|
|
486
|
+
missingDetails.push(`${displayName} (afterDetails)`);
|
|
487
|
+
logger.info(`${displayName}: before=${beforeStatus} after=${afterStatus}`);
|
|
488
|
+
return { ...m, beforeDetails, afterDetails, beforeStatus, afterStatus };
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
if (missingDetails.length > 0) {
|
|
492
|
+
errorResult = toolError(`${missingDetails.length} maintenance row(s) have a recorded execution but no drafted summary: ${missingDetails.join(", ")}. ` +
|
|
493
|
+
"Add a testMaintenanceDetails entry with the missing field(s), drafted from the execution output you already saw.");
|
|
494
|
+
return errorResult;
|
|
413
495
|
}
|
|
414
496
|
// Strip generation-artifact fields from newTestsCreated before writing.
|
|
415
497
|
// scenarioFile, traceFile, frontendTrace are internal paths used during
|
|
@@ -421,7 +503,14 @@ export function registerSubmitReportTool(server) {
|
|
|
421
503
|
businessCaseAnalysis: params.businessCaseAnalysis,
|
|
422
504
|
newTestsCreated: sanitizedNewTests,
|
|
423
505
|
additionalRecommendations: dedupedRecommendations.map(normalizeRepository),
|
|
424
|
-
|
|
506
|
+
// Report wire format keeps main's original `fileName` (basename) — testFilePath is
|
|
507
|
+
// an internal-only field, needed for matching but never meant to reach the report.
|
|
508
|
+
// TODO(multi-repo maintenance): map(normalizeRepository) once testMaintenanceSchema
|
|
509
|
+
// has a repository field (see TODO above).
|
|
510
|
+
testMaintenance: testMaintenance?.map(({ testFilePath, ...row }) => ({
|
|
511
|
+
...row,
|
|
512
|
+
fileName: path.basename(testFilePath),
|
|
513
|
+
})),
|
|
425
514
|
testResults: params.testResults.map(normalizeRepository),
|
|
426
515
|
issuesFound: params.issuesFound.map(normalizeRepository),
|
|
427
516
|
nextSteps: params.nextSteps ?? [],
|