@skyramp/mcp 0.1.8 → 0.2.0-rc.2
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 +4 -2
- package/build/playwright/registerPlaywrightTools.js +12 -0
- package/build/playwright/traceRecordingPrompt.js +15 -0
- package/build/prompts/code-reuse.js +106 -7
- package/build/prompts/pom-aware-code-reuse.js +106 -7
- package/build/prompts/startTraceCollectionPrompts.js +37 -15
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
- package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
- package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
- package/build/prompts/test-recommendation/promptPlan.js +290 -0
- package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
- package/build/prompts/test-recommendation/recommendationSections.js +4 -3
- package/build/prompts/test-recommendation/recommendationShared.js +23 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
- package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
- package/build/prompts/testbot/testbot-prompts.js +73 -13
- package/build/prompts/testbot/testbot-prompts.test.js +114 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/ScenarioGenerationService.integration.test.js +158 -0
- package/build/services/ScenarioGenerationService.js +47 -4
- package/build/services/ScenarioGenerationService.test.js +158 -22
- package/build/services/TestExecutionService.js +73 -15
- package/build/services/TestExecutionService.test.js +105 -0
- package/build/services/TestGenerationService.js +11 -1
- package/build/tools/executeSkyrampTestTool.js +1 -10
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
- package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +2 -0
- package/build/tools/test-management/actionsTool.js +152 -63
- package/build/tools/test-management/analyzeChangesTool.js +178 -64
- package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
- package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
- package/build/tools/trace/resolveSaveStoragePath.js +16 -0
- package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
- package/build/tools/trace/resolveSessionPaths.js +39 -0
- package/build/tools/trace/resolveSessionPaths.test.js +103 -0
- package/build/tools/trace/sessionState.js +14 -0
- package/build/tools/trace/sessionState.test.js +17 -0
- package/build/tools/trace/startTraceCollectionTool.js +84 -14
- package/build/tools/trace/stopTraceCollectionTool.js +9 -2
- package/build/types/TestAnalysis.js +50 -0
- package/build/types/TestRecommendation.js +6 -58
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.js +22 -11
- package/build/utils/branchDiff.js +11 -2
- package/build/utils/docker.test.js +1 -1
- package/build/utils/gitStaging.js +52 -3
- package/build/utils/gitStaging.test.js +19 -1
- package/build/utils/repoScanner.js +18 -10
- package/build/utils/repoScanner.test.js +92 -0
- package/build/utils/routeParsers.js +180 -25
- package/build/utils/routeParsers.test.js +180 -1
- package/build/utils/scenarioDrafting.js +220 -17
- package/build/utils/scenarioDrafting.test.js +182 -9
- package/build/utils/sourceRouteExtractor.js +806 -0
- package/build/utils/sourceRouteExtractor.test.js +565 -0
- package/build/utils/uiPageEnumerator.js +319 -0
- package/build/utils/uiPageEnumerator.test.js +422 -0
- package/build/utils/utils.js +27 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.js +33 -4
- package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
- package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
- package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
- package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
- package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
- package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
- package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
- package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
- package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
- package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
- package/node_modules/playwright/package.json +1 -1
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
- package/package.json +3 -3
- package/build/services/TestHealthService.js +0 -694
- package/build/services/TestHealthService.test.js +0 -241
- package/build/types/TestDriftAnalysis.js +0 -1
- package/build/types/TestHealth.js +0 -4
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
3
|
import { StateManager, } from "../../utils/AnalysisStateManager.js";
|
|
4
|
-
import { TestSource } from "../../types/TestAnalysis.js";
|
|
4
|
+
import { TestSource, DriftAction, RecommendationPriority, EstimatedWork } from "../../types/TestAnalysis.js";
|
|
5
5
|
import { TestType } from "../../types/TestTypes.js";
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
9
|
+
import { toolError } from "../../utils/utils.js";
|
|
9
10
|
/**
|
|
10
11
|
* Compute a suggested new filename when an endpoint is renamed.
|
|
11
12
|
*/
|
|
@@ -59,25 +60,74 @@ function selectTestTypesForEndpoint(method) {
|
|
|
59
60
|
return [TestType.CONTRACT, TestType.SMOKE];
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
const recommendationSchema = z.object({
|
|
64
|
+
testFile: z
|
|
65
|
+
.string()
|
|
66
|
+
.refine((p) => path.isAbsolute(p), { message: "testFile must be an absolute path" })
|
|
67
|
+
.describe("Absolute path to the test file — use the path as reported by skyramp_analyze_changes or skyramp_analyze_test_health"),
|
|
68
|
+
action: z
|
|
69
|
+
.nativeEnum(DriftAction)
|
|
70
|
+
.describe("Drift action assigned by the LLM health assessment"),
|
|
71
|
+
priority: z
|
|
72
|
+
.nativeEnum(RecommendationPriority)
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Update priority"),
|
|
75
|
+
rationale: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("1-2 sentence explanation of why this action is needed"),
|
|
79
|
+
estimatedWork: z
|
|
80
|
+
.nativeEnum(EstimatedWork)
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Estimated effort to apply the update"),
|
|
83
|
+
updateInstructions: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Free-form summary of what this test must change — written for the downstream LLM that will edit the file. " +
|
|
87
|
+
"Specificity prevents incomplete or mismatched edits. Include diff-specific details: " +
|
|
88
|
+
"new response fields to assert, constraint details (types, ranges, defaults), " +
|
|
89
|
+
"auth changes, new request params, removed fields, or other drift-related changes. " +
|
|
90
|
+
"Example: 'Added stock_count: int (ge=0, default=0) to ProductBase. " +
|
|
91
|
+
"Test hits GET /products — assert stock_count is present and non-negative.'"),
|
|
92
|
+
renamedEndpoints: z
|
|
93
|
+
.array(z.object({
|
|
94
|
+
oldPath: z.string().describe("Previous endpoint path"),
|
|
95
|
+
newPath: z.string().describe("New endpoint path after rename"),
|
|
96
|
+
method: z.string().describe("HTTP method, e.g. GET"),
|
|
97
|
+
}))
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("Renamed endpoints — supply this array when action is UPDATE and the endpoint path has changed. Omit if action is not UPDATE."),
|
|
100
|
+
});
|
|
62
101
|
const actionsSchema = {
|
|
63
102
|
stateFile: z
|
|
64
103
|
.string()
|
|
65
|
-
.
|
|
104
|
+
.refine((p) => path.isAbsolute(p), { message: "stateFile must be an absolute path" })
|
|
105
|
+
.describe("Path to state file from skyramp_analyze_changes"),
|
|
106
|
+
recommendations: z
|
|
107
|
+
.array(recommendationSchema)
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("LLM drift assessment — one entry per test assessed. Required for UPDATE instructions to be emitted; omitting results in no maintenance actions."),
|
|
66
110
|
};
|
|
67
111
|
const TOOL_NAME = "skyramp_actions";
|
|
68
112
|
export function registerActionsTool(server) {
|
|
69
113
|
server.registerTool(TOOL_NAME, {
|
|
114
|
+
annotations: {
|
|
115
|
+
destructiveHint: true,
|
|
116
|
+
readOnlyHint: false,
|
|
117
|
+
idempotentHint: false,
|
|
118
|
+
openWorldHint: true,
|
|
119
|
+
},
|
|
70
120
|
description: `Execute test maintenance and generation actions — final step of the unified Test Health Analysis Flow.
|
|
71
121
|
|
|
72
|
-
**PREREQUISITE:** Call \`skyramp_analyze_test_health\`.
|
|
122
|
+
**PREREQUISITE:** Call \`skyramp_analyze_changes\` (produces the stateFile), then \`skyramp_analyze_test_health\` (runs the drift assessment). This tool reads the stateFile from \`skyramp_analyze_changes\`.
|
|
73
123
|
|
|
74
|
-
|
|
124
|
+
Call this tool after completing the drift assessment. It executes maintenance actions automatically from the stateFile — no user confirmation required.
|
|
75
125
|
|
|
76
126
|
**EXECUTING ACTIONS:**
|
|
77
|
-
- UPDATE:
|
|
78
|
-
- REGENERATE:
|
|
79
|
-
- VERIFY:
|
|
80
|
-
- ADD: Auto-
|
|
127
|
+
- UPDATE: Tests with drift — emits targeted per-file edit instructions driven by updateInstructions and renamedEndpoints
|
|
128
|
+
- REGENERATE: Emits file-level summary; follow up by calling the appropriate generation tool (e.g. skyramp_integration_test_generation) with the same filename to overwrite
|
|
129
|
+
- VERIFY: Emits file-level summary for human review — no automated edits
|
|
130
|
+
- ADD: Auto-generates tests for new endpoints via LLM instructions
|
|
81
131
|
|
|
82
132
|
**OUTPUT:**
|
|
83
133
|
Comprehensive report with executed actions, summary, and instructions for ADD recommendations
|
|
@@ -93,43 +143,78 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
93
143
|
const fullState = await stateManager.readFullState();
|
|
94
144
|
const repositoryPath = fullState?.metadata.repositoryPath || "";
|
|
95
145
|
if (!stateData) {
|
|
96
|
-
errorResult = {
|
|
97
|
-
content: [
|
|
98
|
-
{
|
|
99
|
-
type: "text",
|
|
100
|
-
text: JSON.stringify({
|
|
101
|
-
error: "State file is empty or invalid",
|
|
102
|
-
stateFile: args.stateFile,
|
|
103
|
-
}, null, 2),
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
isError: true,
|
|
107
|
-
};
|
|
146
|
+
errorResult = toolError(`State file is empty or invalid: ${args.stateFile}. Call skyramp_analyze_changes first to generate a valid state file.`);
|
|
108
147
|
return errorResult;
|
|
109
148
|
}
|
|
110
149
|
// External tests must not be candidates for UPDATE/REGENERATE/DELETE actions.
|
|
111
150
|
// Default source to Skyramp for backwards compat with state files created before the source field existed.
|
|
112
151
|
const testAnalysisResults = (stateData.existingTests || []).filter((t) => (t.source ?? TestSource.Skyramp) !== TestSource.External);
|
|
113
152
|
const newEndpoints = stateData.newEndpoints || [];
|
|
114
|
-
//
|
|
153
|
+
// Resolve repo root for path normalization and security checks.
|
|
154
|
+
const repoRoot = repositoryPath ? path.resolve(repositoryPath) : "";
|
|
155
|
+
// Set of non-external (Skyramp-generated) test file paths — the only files
|
|
156
|
+
// that may receive UPDATE/REGENERATE/DELETE actions. Using the allowlist rather
|
|
157
|
+
// than a blocklist catches both external tests AND hallucinated paths the LLM
|
|
158
|
+
// may supply that are not present in the scanned catalog at all.
|
|
159
|
+
const skyrampTestFiles = new Set(testAnalysisResults.map((t) => t.testFile));
|
|
160
|
+
// ── Build recommendations from LLM-supplied drift assessment ──
|
|
161
|
+
// The LLM performs the drift assessment in context after skyramp_analyze_test_health
|
|
162
|
+
// and passes results here directly — analyzeTestHealthTool never writes assessment
|
|
163
|
+
// data back to the state file.
|
|
115
164
|
const recommendations = [];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
165
|
+
(args.recommendations ?? []).forEach((rec) => {
|
|
166
|
+
// Schema requires absolute paths; resolve any relative paths defensively
|
|
167
|
+
// against repoRoot in case the LLM sends a relative path despite the schema.
|
|
168
|
+
const resolvedFile = path.isAbsolute(rec.testFile)
|
|
169
|
+
? rec.testFile
|
|
170
|
+
: repoRoot
|
|
171
|
+
? path.resolve(repoRoot, rec.testFile)
|
|
172
|
+
: rec.testFile;
|
|
173
|
+
// Reject files outside the repo root (path-traversal guard).
|
|
174
|
+
if (repoRoot && !resolvedFile.startsWith(repoRoot + path.sep) && resolvedFile !== repoRoot) {
|
|
175
|
+
logger.warning(`Skipping recommendation for path outside repo root: ${rec.testFile}`);
|
|
176
|
+
return;
|
|
127
177
|
}
|
|
178
|
+
// Guard: only Skyramp-generated tests may receive UPDATE/REGENERATE/DELETE.
|
|
179
|
+
// Using an allowlist (skyrampTestFiles) rather than a blocklist catches both
|
|
180
|
+
// external tests and hallucinated paths the LLM may supply that are not in
|
|
181
|
+
// the scanned catalog. IGNORE/VERIFY are informational and pass through.
|
|
182
|
+
const isActionable = [DriftAction.Update, DriftAction.Regenerate, DriftAction.Delete].includes(rec.action);
|
|
183
|
+
if (isActionable && !skyrampTestFiles.has(resolvedFile) && !skyrampTestFiles.has(rec.testFile)) {
|
|
184
|
+
logger.warning(`Skipping ${rec.action} for non-Skyramp or unknown test: ${rec.testFile}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
recommendations.push({
|
|
188
|
+
testFile: resolvedFile,
|
|
189
|
+
action: rec.action,
|
|
190
|
+
priority: rec.priority ?? RecommendationPriority.Medium,
|
|
191
|
+
rationale: rec.rationale ?? "",
|
|
192
|
+
estimatedWork: rec.estimatedWork ?? EstimatedWork.Small,
|
|
193
|
+
updateInstructions: rec.updateInstructions ?? "",
|
|
194
|
+
renamedEndpoints: rec.renamedEndpoints ?? [],
|
|
195
|
+
});
|
|
128
196
|
});
|
|
129
197
|
// ── Process UPDATE recommendations ──
|
|
130
|
-
|
|
131
|
-
|
|
198
|
+
// Deduplicate by testFile — keep the highest-priority entry when the LLM
|
|
199
|
+
// repeats a file. Priority order: high > medium > low.
|
|
200
|
+
const priorityRank = {
|
|
201
|
+
[RecommendationPriority.High]: 2,
|
|
202
|
+
[RecommendationPriority.Medium]: 1,
|
|
203
|
+
[RecommendationPriority.Low]: 0,
|
|
204
|
+
};
|
|
205
|
+
const updateByFile = new Map();
|
|
206
|
+
for (const rec of recommendations) {
|
|
207
|
+
if (rec.action !== DriftAction.Update)
|
|
208
|
+
continue;
|
|
209
|
+
const existing = updateByFile.get(rec.testFile);
|
|
210
|
+
if (!existing || priorityRank[rec.priority] > priorityRank[existing.priority]) {
|
|
211
|
+
updateByFile.set(rec.testFile, rec);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const updateRecommendations = Array.from(updateByFile.values());
|
|
215
|
+
const fileInstructions = [];
|
|
132
216
|
const testFilesToUpdate = [];
|
|
217
|
+
const testFileContentMap = new Map();
|
|
133
218
|
for (const rec of updateRecommendations) {
|
|
134
219
|
if (!rec.testFile) {
|
|
135
220
|
logger.warning("Recommendation missing testFile", rec);
|
|
@@ -138,11 +223,11 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
138
223
|
testFilesToUpdate.push(rec.testFile);
|
|
139
224
|
const testData = testAnalysisResults.find((t) => t.testFile === rec.testFile);
|
|
140
225
|
const driftData = testData?.drift;
|
|
141
|
-
const issues = rec.issues || [];
|
|
142
226
|
const driftChanges = driftData?.changes || [];
|
|
143
227
|
let testFileContent = "";
|
|
144
228
|
try {
|
|
145
229
|
testFileContent = fs.readFileSync(rec.testFile, "utf-8");
|
|
230
|
+
testFileContentMap.set(rec.testFile, testFileContent);
|
|
146
231
|
}
|
|
147
232
|
catch (error) {
|
|
148
233
|
logger.error(`Failed to read test file ${rec.testFile}: ${error.message}`);
|
|
@@ -152,7 +237,7 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
152
237
|
const isRenameUpdate = renames.length > 0;
|
|
153
238
|
let instruction = `\n### ${rec.testFile}\n\n`;
|
|
154
239
|
instruction += `**Priority:** ${rec.priority} | `;
|
|
155
|
-
instruction += `**Estimated Effort:** ${rec.estimatedWork ||
|
|
240
|
+
instruction += `**Estimated Effort:** ${rec.estimatedWork || EstimatedWork.Small}\n\n`;
|
|
156
241
|
instruction += `**Why Update Needed:** ${rec.rationale}\n\n`;
|
|
157
242
|
if (isRenameUpdate) {
|
|
158
243
|
instruction += `**Endpoint Rename Detected — Path Substitution Required:**\n\n`;
|
|
@@ -172,11 +257,21 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
172
257
|
rec._suggestedNewFile = suggestedNewFile;
|
|
173
258
|
}
|
|
174
259
|
}
|
|
260
|
+
const recUpdateInstructions = rec.updateInstructions ?? "";
|
|
261
|
+
if (recUpdateInstructions) {
|
|
262
|
+
instruction += `**What to change:**\n\n${recUpdateInstructions}\n\n`;
|
|
263
|
+
instruction += `Match the assertion style already used in the file. `;
|
|
264
|
+
instruction += `Preserve all existing test logic — only add or adjust what is described above.\n\n`;
|
|
265
|
+
}
|
|
266
|
+
else if (!isRenameUpdate) {
|
|
267
|
+
instruction += `**Action:** Update this test file per the rationale above. `;
|
|
268
|
+
instruction += `Match the assertion style already used in the file. `;
|
|
269
|
+
instruction += `Preserve all existing test logic — only add or adjust the minimum required assertions.\n\n`;
|
|
270
|
+
}
|
|
175
271
|
if (driftData) {
|
|
176
272
|
instruction += `**Analysis:**\n`;
|
|
177
|
-
instruction += `- Drift Score: ${driftData.driftScore ?? "N/A"}\n`;
|
|
178
273
|
instruction += `- Changes Detected: ${driftData.changes?.length || 0}\n`;
|
|
179
|
-
instruction += `- Affected Files: ${driftData.affectedFiles.files || 0}\n\n`;
|
|
274
|
+
instruction += `- Affected Files: ${driftData.affectedFiles.files?.length || 0}\n\n`;
|
|
180
275
|
}
|
|
181
276
|
if (driftChanges.length > 0) {
|
|
182
277
|
instruction += `**Changes Detected:**\n`;
|
|
@@ -191,18 +286,8 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
191
286
|
});
|
|
192
287
|
instruction += `\n`;
|
|
193
288
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
issues.forEach((issue) => {
|
|
197
|
-
instruction += `**${issue.type}** (Severity: ${issue.severity}): ${issue.description}\n`;
|
|
198
|
-
if (issue.details) {
|
|
199
|
-
instruction += ` └─ ${issue.details}\n`;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
instruction += `\n`;
|
|
203
|
-
}
|
|
204
|
-
instruction += `**Test File Content:**\n\`\`\`\n${testFileContent}\n\`\`\`\n\n`;
|
|
205
|
-
updateInstructions.push(instruction);
|
|
289
|
+
// File content is provided in LLM_INSTRUCTIONS.update_context.current_content — omit here to avoid duplication.
|
|
290
|
+
fileInstructions.push(instruction);
|
|
206
291
|
}
|
|
207
292
|
// ── Build ADD section for new endpoints ──
|
|
208
293
|
const wsBaseUrl = stateData.repositoryAnalysis?.wsBaseUrl || "";
|
|
@@ -261,7 +346,7 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
261
346
|
responseText += `${idx + 1}. \`${file}\`\n`;
|
|
262
347
|
});
|
|
263
348
|
responseText += `\n---\n`;
|
|
264
|
-
responseText +=
|
|
349
|
+
responseText += fileInstructions.join("\n---\n");
|
|
265
350
|
}
|
|
266
351
|
if (newEndpoints.length > 0) {
|
|
267
352
|
responseText += `\n## New Endpoint Tests to Generate (${newEndpoints.length} endpoints)\n\n`;
|
|
@@ -271,7 +356,7 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
271
356
|
responseText += `\nThe following tests will be generated automatically.\n`;
|
|
272
357
|
}
|
|
273
358
|
if (updateRecommendations.length === 0 && newEndpoints.length === 0) {
|
|
274
|
-
const otherRecs = recommendations.filter((rec) => rec.action !==
|
|
359
|
+
const otherRecs = recommendations.filter((rec) => rec.action !== DriftAction.Update);
|
|
275
360
|
if (otherRecs.length > 0) {
|
|
276
361
|
responseText += `## Recommendations (${otherRecs.length})\n\n`;
|
|
277
362
|
otherRecs.forEach((rec) => {
|
|
@@ -329,6 +414,20 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
329
414
|
"After updating path content in each file, rename the file using 'mv' or equivalent. Use git mv if the repo tracks the file.";
|
|
330
415
|
}
|
|
331
416
|
}
|
|
417
|
+
// Update context: per-file guidance + current content for the downstream LLM.
|
|
418
|
+
// Including file content avoids re-reads on each Edit turn, reducing token usage.
|
|
419
|
+
const updateInstructionsFiles = [];
|
|
420
|
+
for (const rec of updateRecommendations) {
|
|
421
|
+
if (rec.updateInstructions) {
|
|
422
|
+
const current_content = testFileContentMap.get(rec.testFile);
|
|
423
|
+
updateInstructionsFiles.push({ file: rec.testFile, context: rec.updateInstructions, ...(current_content !== undefined && { current_content }) });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (updateInstructionsFiles.length > 0) {
|
|
427
|
+
llmInstructionsObj.update_context = updateInstructionsFiles;
|
|
428
|
+
llmInstructionsObj.update_strategy =
|
|
429
|
+
"For each file in update_context, apply the changes described in context to the provided current_content. Write the result using the Edit tool. Do NOT re-read the file first. Match the assertion style already used in the file. Preserve all existing test logic. After applying all edits, call skyramp_enhance_assertions with each updated file path to strengthen the assertions.";
|
|
430
|
+
}
|
|
332
431
|
const llmInstructions = `<!-- LLM_INSTRUCTIONS:\n${JSON.stringify(llmInstructionsObj, null, 2)}\n-->\n`;
|
|
333
432
|
const contentBlocks = [
|
|
334
433
|
{
|
|
@@ -369,17 +468,7 @@ Comprehensive report with executed actions, summary, and instructions for ADD re
|
|
|
369
468
|
}
|
|
370
469
|
catch (error) {
|
|
371
470
|
logger.error(`Actions tool failed: ${error.message}`, error);
|
|
372
|
-
errorResult = {
|
|
373
|
-
content: [
|
|
374
|
-
{
|
|
375
|
-
type: "text",
|
|
376
|
-
text: JSON.stringify({
|
|
377
|
-
error: error.message,
|
|
378
|
-
}, null, 2),
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
isError: true,
|
|
382
|
-
};
|
|
471
|
+
errorResult = toolError(`Actions tool failed: ${error.message}`);
|
|
383
472
|
return errorResult;
|
|
384
473
|
}
|
|
385
474
|
finally {
|