@skyramp/mcp 0.2.1-rc.1 → 0.2.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/playwright/registerPlaywrightTools.js +10 -0
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +98 -87
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +92 -60
- package/build/prompts/test-maintenance/driftAnalysisSections.js +139 -197
- package/build/prompts/test-recommendation/scopeAssessment.js +106 -5
- package/build/prompts/test-recommendation/scopeAssessment.test.js +128 -1
- package/build/prompts/testbot/testbot-prompts.js +6 -9
- package/build/prompts/testbot/testbot-prompts.test.js +38 -22
- package/build/services/TestDiscoveryService.js +39 -9
- package/build/tools/test-management/actionsTool.js +166 -148
- package/build/tools/test-management/analyzeChangesTool.js +10 -12
- package/build/tools/test-management/analyzeTestHealthTool.js +10 -22
- package/build/tools/test-management/uiAnalyzeChangesTool.js +8 -2
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +47 -0
- package/build/utils/dartRouteExtractor.js +319 -0
- package/build/utils/dartRouteExtractor.test.js +307 -0
- package/build/utils/docker.test.js +1 -1
- package/build/utils/uiPageEnumerator.js +67 -0
- package/build/utils/uiPageEnumerator.test.js +222 -0
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/assertApiRequestTool.js +46 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +313 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +146 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +519 -52
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +32 -14
- package/package.json +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +0 -261
- 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
|
@@ -40,12 +40,22 @@ export async function registerPlaywrightTools(server, options) {
|
|
|
40
40
|
'browser_select_option',
|
|
41
41
|
'browser_hover',
|
|
42
42
|
'browser_drag',
|
|
43
|
+
'browser_mouse_move_xy',
|
|
44
|
+
'browser_mouse_click_xy',
|
|
45
|
+
'browser_mouse_drag_xy',
|
|
46
|
+
'browser_mouse_down',
|
|
47
|
+
'browser_mouse_up',
|
|
48
|
+
'browser_file_upload',
|
|
49
|
+
'browser_evaluate',
|
|
43
50
|
'browser_tabs',
|
|
44
51
|
'browser_navigate_back',
|
|
45
52
|
'browser_wait_for',
|
|
46
53
|
'browser_take_screenshot',
|
|
47
54
|
'browser_assert',
|
|
55
|
+
'browser_assert_api_request',
|
|
56
|
+
'browser_assert_table_cell',
|
|
48
57
|
'skyramp_export_zip',
|
|
58
|
+
'skyramp_load_trace',
|
|
49
59
|
// DOM Analyzer tools (Phase C)
|
|
50
60
|
'browser_blueprint',
|
|
51
61
|
'browser_blueprint_diff',
|
|
@@ -1,91 +1,102 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildActionDecisionMatrix, buildBreakingChangePatterns, buildTestAssessmentGuidelines, buildAddRecommendationGuidelines, buildDriftOutputChecklist, buildUpdateExecutionRules, } from "./driftAnalysisSections.js";
|
|
2
|
+
import { isTestbotEnabled } from "../../utils/featureFlags.js";
|
|
2
3
|
import { readDiffFile } from "../../utils/utils.js";
|
|
3
|
-
import { PromptPlan } from "../test-recommendation/promptPlan.js";
|
|
4
|
-
// ── Private body helpers ──────────────────────────────────────────────────────
|
|
5
|
-
// Each receives DriftAnalysisPromptParams and returns the step body string.
|
|
6
|
-
// The "### Step N: Title" header is added by PromptPlan.render().
|
|
7
|
-
function _assessBody(_p) {
|
|
8
|
-
return buildActionDecisionTree();
|
|
9
|
-
}
|
|
10
|
-
function _checkAdditiveFieldsBody(_p) {
|
|
11
|
-
return buildCheckAdditiveFields();
|
|
12
|
-
}
|
|
13
|
-
function _checkEndpointExistenceBody(_p) {
|
|
14
|
-
return buildCheckEndpointExistence();
|
|
15
|
-
}
|
|
16
|
-
function _checkResponseShapeBody(_p) {
|
|
17
|
-
return buildCheckResponseShape();
|
|
18
|
-
}
|
|
19
|
-
function _checkAuthAndAuthorizationBody(_p) {
|
|
20
|
-
return buildCheckAuthAndAuthorization();
|
|
21
|
-
}
|
|
22
|
-
function _checkBehavioralContractBody(_p) {
|
|
23
|
-
return buildCheckBehavioralContract();
|
|
24
|
-
}
|
|
25
|
-
function _checkAssignActionBody(_p) {
|
|
26
|
-
return buildCheckAssignAction();
|
|
27
|
-
}
|
|
28
|
-
function _applyBody(_p) {
|
|
29
|
-
return buildUpdateExecutionRules();
|
|
30
|
-
}
|
|
31
|
-
function _callToolBody(p) {
|
|
32
|
-
return buildDriftOutputChecklist(p.existingTests.length, p.newEndpointCount ?? 0, p.stateFile);
|
|
33
|
-
}
|
|
34
|
-
// ── PromptPlan declaration ────────────────────────────────────────────────────
|
|
35
|
-
// All steps are unconditional — both MCP and testbot callers render the same
|
|
36
|
-
// five steps. The only per-caller variation is skipContextHeader (context
|
|
37
|
-
// section prepended by buildDriftAnalysisPrompt, not inside the plan).
|
|
38
|
-
const _plan = new PromptPlan()
|
|
39
|
-
.addPhase("maintenance", "Test Maintenance Assessment", {
|
|
40
|
-
headerLevel: "##",
|
|
41
|
-
stepFormat: "hash",
|
|
42
|
-
})
|
|
43
|
-
.step("ASSESS", "Action Decision Tree — assess each existing test against the diff", _assessBody)
|
|
44
|
-
.subStep("ENDPOINT_EXISTENCE", "Endpoint existence", _checkEndpointExistenceBody)
|
|
45
|
-
.subStep("RESPONSE_SHAPE", "Request/response shape (breaking changes)", _checkResponseShapeBody)
|
|
46
|
-
.subStep("ADDITIVE_FIELDS", "Additive response fields (coverage gaps)", _checkAdditiveFieldsBody)
|
|
47
|
-
.subStep("AUTH_AUTHZ", "Auth and authorization changes", _checkAuthAndAuthorizationBody)
|
|
48
|
-
.subStep("BEHAVIORAL_CONTRACT", "Behavioral and semantic contract changes", _checkBehavioralContractBody)
|
|
49
|
-
.subStep("ASSIGN_ACTION", "Assign action", _checkAssignActionBody)
|
|
50
|
-
.step("APPLY", "Apply update execution rules", _applyBody)
|
|
51
|
-
.step("CALL_TOOL", "Submit recommendations", _callToolBody)
|
|
52
|
-
.done();
|
|
53
|
-
// ── Exported step label constants ─────────────────────────────────────────────
|
|
54
|
-
// Static — safe to export at module load; renumber automatically on insertion.
|
|
55
|
-
/** "1" — Assess each test against the diff */
|
|
56
|
-
export const DRIFT_STEP_ASSESS = _plan.labels.ASSESS; // "1"
|
|
57
|
-
/** "1.1" — Endpoint existence check */
|
|
58
|
-
export const DRIFT_STEP_ENDPOINT_EXISTENCE = _plan.labels.ENDPOINT_EXISTENCE; // "1.1"
|
|
59
|
-
/** "1.2" — Request/response shape check */
|
|
60
|
-
export const DRIFT_STEP_RESPONSE_SHAPE = _plan.labels.RESPONSE_SHAPE; // "1.2"
|
|
61
|
-
/** "1.3" — Additive response fields check */
|
|
62
|
-
export const DRIFT_STEP_ADDITIVE_FIELDS = _plan.labels.ADDITIVE_FIELDS; // "1.3"
|
|
63
|
-
/** "1.4" — Auth and authorization changes check */
|
|
64
|
-
export const DRIFT_STEP_AUTH_AUTHZ = _plan.labels.AUTH_AUTHZ; // "1.4"
|
|
65
|
-
/** "1.5" — Behavioral and semantic contract changes check */
|
|
66
|
-
export const DRIFT_STEP_BEHAVIORAL_CONTRACT = _plan.labels.BEHAVIORAL_CONTRACT; // "1.5"
|
|
67
|
-
/** "1.6" — Assign action */
|
|
68
|
-
export const DRIFT_STEP_ASSIGN_ACTION = _plan.labels.ASSIGN_ACTION; // "1.6"
|
|
69
|
-
/** "2" — Apply update execution rules */
|
|
70
|
-
export const DRIFT_STEP_APPLY = _plan.labels.APPLY; // "2"
|
|
71
|
-
/** "3" — Submit recommendations (calls skyramp_actions) */
|
|
72
|
-
export const DRIFT_STEP_CALL_TOOL = _plan.labels.CALL_TOOL; // "3"
|
|
73
|
-
// ── Public builder ────────────────────────────────────────────────────────────
|
|
74
4
|
export function buildDriftAnalysisPrompt(params) {
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
let newEndpointCount =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
5
|
+
const { existingTests, scannedEndpoints, repositoryPath, stateFile, routerMountContext, candidateRouteFiles, diffFilePath } = params;
|
|
6
|
+
// Read raw diff once — used for both the inline summary block and the per-line file reference.
|
|
7
|
+
const rawDiff = readDiffFile(diffFilePath);
|
|
8
|
+
let newEndpointCount = 0;
|
|
9
|
+
let diffSection = "";
|
|
10
|
+
if (rawDiff) {
|
|
11
|
+
const lines = rawDiff.split("\n");
|
|
12
|
+
const newEndpointMatch = rawDiff.match(/\*\*New Endpoints\*\*\s+\((\d+)\)/);
|
|
13
|
+
if (newEndpointMatch)
|
|
14
|
+
newEndpointCount = parseInt(newEndpointMatch[1], 10);
|
|
15
|
+
diffSection = `## Branch Diff
|
|
16
|
+
\`\`\`
|
|
17
|
+
${lines.slice(0, 200).join("\n")}
|
|
18
|
+
\`\`\`
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
const testListSection = existingTests.length > 0
|
|
22
|
+
? `## Existing Test Files (${existingTests.length})
|
|
23
|
+
${existingTests.map((t) => `- ${t.testFile} (${t.testType})`).join("\n")}
|
|
24
|
+
`
|
|
25
|
+
: `## Existing Test Files
|
|
26
|
+
No existing Skyramp tests found in repository.
|
|
27
|
+
`;
|
|
28
|
+
const scannedSection = scannedEndpoints.length > 0
|
|
29
|
+
? `## Scanned Endpoints (${scannedEndpoints.length})
|
|
30
|
+
Note: paths below come from static analysis and may be incomplete for nested resources or unsupported frameworks. Use the Routing entry-point files section below to verify and reconstruct full paths.
|
|
31
|
+
${scannedEndpoints.map((ep) => {
|
|
32
|
+
let methods;
|
|
33
|
+
if (Array.isArray(ep.methods)) {
|
|
34
|
+
methods = ep.methods.map((m) => (typeof m === "string" ? m : m.method)).join("|");
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
methods = ep.method;
|
|
38
|
+
}
|
|
39
|
+
return `- ${methods} ${ep.path}`;
|
|
40
|
+
}).join("\n")}
|
|
41
|
+
`
|
|
42
|
+
: "";
|
|
43
|
+
const mountSection = routerMountContext?.length
|
|
44
|
+
? `## Routing entry-point files
|
|
45
|
+
Read these to trace the full router/module hierarchy when verifying endpoint paths:
|
|
46
|
+
${routerMountContext.map(f => `- \`${f}\``).join("\n")}
|
|
47
|
+
`
|
|
48
|
+
: "";
|
|
49
|
+
const hasJavaFiles = candidateRouteFiles?.some(f => /\.(java|kt)$/.test(f)) ?? false;
|
|
50
|
+
const candidateFilesSection = candidateRouteFiles && candidateRouteFiles.length > 0
|
|
51
|
+
? `## Route Files (read these to find endpoints from any framework)
|
|
52
|
+
${candidateRouteFiles.map(f => `- ${f}`).join("\n")}
|
|
53
|
+
${hasJavaFiles ? "Note — Java Spring: full URL = class-level `@RequestMapping` prefix + method-level path. If the prefix is a constant reference (e.g. `@RequestMapping(Url.PAGE_URL)`), find the constant — same file, inner class, or a separate `Url.java` — and resolve it (including `+` concatenation)." : ""}
|
|
54
|
+
`
|
|
55
|
+
: "";
|
|
56
|
+
const diffFileSection = diffFilePath
|
|
57
|
+
? `## Raw Diff File
|
|
58
|
+
Read \`${diffFilePath}\` to get the full line-by-line diff. Use it to detect:
|
|
59
|
+
- Additive response fields: lines starting with \`+\` inside a view/serializer/controller (e.g. \`+ "newField":\`, \`+ newField =\`)
|
|
60
|
+
- Renamed routes: \`- @app.route("/old")\` / \`+ @app.route("/new")\` or similar framework patterns
|
|
61
|
+
- Status code changes: \`- return 200\` / \`+ return 201\`, \`- res.status(200)\` / \`+ res.status(204)\`
|
|
62
|
+
- Auth additions/removals: \`+ @require_auth\`, \`- @login_required\`, middleware changes
|
|
63
|
+
Read the file once and cache its contents — it is the primary source for per-line breaking-change detection. Use it as evidence for Checks A–D below.
|
|
64
|
+
`
|
|
65
|
+
: "";
|
|
66
|
+
// In inline mode (testbot), skip the context header — existing tests and diff
|
|
67
|
+
// are provided by skyramp_analyze_changes at runtime, not at prompt-build time.
|
|
68
|
+
const contextSection = isTestbotEnabled()
|
|
69
|
+
? ""
|
|
70
|
+
: `# Test Health Analysis
|
|
71
|
+
|
|
72
|
+
**Repository**: \`${repositoryPath}\`
|
|
73
|
+
**Existing tests**: ${existingTests.length}
|
|
74
|
+
**New endpoints in diff**: ${newEndpointCount}
|
|
75
|
+
|
|
76
|
+
${diffSection}
|
|
77
|
+
${diffFileSection}
|
|
78
|
+
${testListSection}
|
|
79
|
+
${scannedSection}
|
|
80
|
+
${mountSection}
|
|
81
|
+
${candidateFilesSection}`;
|
|
82
|
+
if (isTestbotEnabled()) {
|
|
83
|
+
// Testbot inline mode: all maintenance logic lives here so the testbot
|
|
84
|
+
// prompt only orchestrates steps without duplicating rules.
|
|
85
|
+
// No persona statement here — the outer testbot prompt already establishes
|
|
86
|
+
// the agent's context; a nested identity statement causes role confusion.
|
|
87
|
+
return `<drift_analysis_rules>
|
|
88
|
+
${buildActionDecisionMatrix()}
|
|
89
|
+
${buildUpdateExecutionRules()}
|
|
90
|
+
${buildDriftOutputChecklist(existingTests.length, newEndpointCount, isTestbotEnabled())}
|
|
91
|
+
</drift_analysis_rules>`;
|
|
86
92
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
return `You are acting as a Skyramp Integration Architect. Your responsibility is to assess each existing test against the branch diff and determine the correct maintenance action.
|
|
94
|
+
|
|
95
|
+
${contextSection}
|
|
96
|
+
${buildActionDecisionMatrix()}
|
|
97
|
+
${buildBreakingChangePatterns()}
|
|
98
|
+
${buildTestAssessmentGuidelines()}
|
|
99
|
+
${buildUpdateExecutionRules()}
|
|
100
|
+
${buildAddRecommendationGuidelines()}
|
|
101
|
+
${buildDriftOutputChecklist(existingTests.length, newEndpointCount, isTestbotEnabled(), stateFile)}`;
|
|
91
102
|
}
|
|
@@ -1,84 +1,116 @@
|
|
|
1
|
-
import { buildDriftAnalysisPrompt
|
|
1
|
+
import { buildDriftAnalysisPrompt } from "./drift-analysis-prompt.js";
|
|
2
2
|
import { buildDriftOutputChecklist } from "./driftAnalysisSections.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
expect(DRIFT_STEP_APPLY).toBe("2");
|
|
9
|
-
expect(DRIFT_STEP_CALL_TOOL).toBe("3");
|
|
10
|
-
});
|
|
11
|
-
it("sub-steps are numbered within their parent", () => {
|
|
12
|
-
expect(DRIFT_STEP_ENDPOINT_EXISTENCE).toBe("1.1");
|
|
13
|
-
expect(DRIFT_STEP_RESPONSE_SHAPE).toBe("1.2");
|
|
14
|
-
expect(DRIFT_STEP_ADDITIVE_FIELDS).toBe("1.3");
|
|
15
|
-
expect(DRIFT_STEP_AUTH_AUTHZ).toBe("1.4");
|
|
16
|
-
expect(DRIFT_STEP_BEHAVIORAL_CONTRACT).toBe("1.5");
|
|
17
|
-
expect(DRIFT_STEP_ASSIGN_ACTION).toBe("1.6");
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
// ── buildDriftOutputChecklist ─────────────────────────────────────────────────
|
|
21
|
-
describe("buildDriftOutputChecklist", () => {
|
|
22
|
-
it("includes recommendations, updateInstructions, and skyramp_actions CTA", () => {
|
|
23
|
-
const checklist = buildDriftOutputChecklist(3, 0, STATE_FILE);
|
|
3
|
+
describe("buildDriftOutputChecklist — final-step recommendations guidance", () => {
|
|
4
|
+
const STATE_FILE = "/tmp/skyramp-analysis-abc123.json";
|
|
5
|
+
it("non-inline mode includes recommendations and updateInstructions in final step", () => {
|
|
6
|
+
const checklist = buildDriftOutputChecklist(3, 0, false, STATE_FILE);
|
|
7
|
+
// Must instruct the LLM to pass recommendations to skyramp_actions
|
|
24
8
|
expect(checklist).toContain("recommendations");
|
|
9
|
+
// Must mention updateInstructions so the LLM knows to populate it
|
|
25
10
|
expect(checklist).toContain("updateInstructions");
|
|
11
|
+
// Must reference the stateFile path
|
|
26
12
|
expect(checklist).toContain(STATE_FILE);
|
|
13
|
+
// Must call skyramp_actions as the final action
|
|
27
14
|
expect(checklist).toContain("skyramp_actions");
|
|
28
15
|
});
|
|
29
|
-
it("does not contain JSON shape — schema is authoritative", () => {
|
|
30
|
-
const checklist = buildDriftOutputChecklist(3, 0, STATE_FILE);
|
|
16
|
+
it("non-inline mode does not contain JSON shape — schema is authoritative", () => {
|
|
17
|
+
const checklist = buildDriftOutputChecklist(3, 0, false, STATE_FILE);
|
|
18
|
+
// The JSON shape was moved to inputSchema — prompt must not duplicate it
|
|
31
19
|
expect(checklist).not.toContain('"testFile":');
|
|
32
20
|
expect(checklist).not.toContain('"action":');
|
|
33
21
|
});
|
|
34
|
-
it("
|
|
35
|
-
const checklist = buildDriftOutputChecklist(3, 0, STATE_FILE);
|
|
36
|
-
|
|
37
|
-
expect(
|
|
22
|
+
it("inline mode does not reference skyramp_actions or stateFile", () => {
|
|
23
|
+
const checklist = buildDriftOutputChecklist(3, 0, true, STATE_FILE);
|
|
24
|
+
// Inline mode applies changes directly — no skyramp_actions call
|
|
25
|
+
expect(checklist).not.toContain("skyramp_actions");
|
|
26
|
+
expect(checklist).not.toContain(STATE_FILE);
|
|
27
|
+
});
|
|
28
|
+
it("full prompt (non-inline) includes recommendations guidance", () => {
|
|
29
|
+
const prompt = buildDriftAnalysisPrompt({
|
|
30
|
+
existingTests: [],
|
|
31
|
+
scannedEndpoints: [],
|
|
32
|
+
repositoryPath: "/repo",
|
|
33
|
+
stateFile: STATE_FILE,
|
|
34
|
+
});
|
|
35
|
+
expect(prompt).toContain("recommendations");
|
|
36
|
+
expect(prompt).toContain("updateInstructions");
|
|
38
37
|
});
|
|
39
38
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
describe("buildDriftAnalysisPrompt - inline mode", () => {
|
|
40
|
+
beforeEach(() => { process.env.SKYRAMP_FEATURE_TESTBOT = "1"; });
|
|
41
|
+
afterEach(() => { delete process.env.SKYRAMP_FEATURE_TESTBOT; });
|
|
42
|
+
function inlinePrompt() {
|
|
43
43
|
return buildDriftAnalysisPrompt({
|
|
44
44
|
existingTests: [],
|
|
45
45
|
scannedEndpoints: [],
|
|
46
46
|
repositoryPath: "/repo",
|
|
47
|
-
stateFile
|
|
47
|
+
// stateFile omitted → inline mode
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
it("wraps
|
|
51
|
-
|
|
52
|
-
expect(prompt
|
|
50
|
+
it("wraps inline rules in drift_analysis_rules XML tags", () => {
|
|
51
|
+
const prompt = inlinePrompt();
|
|
52
|
+
expect(prompt).toContain("<drift_analysis_rules>");
|
|
53
|
+
expect(prompt).toContain("</drift_analysis_rules>");
|
|
53
54
|
});
|
|
54
|
-
it("does not contain the persona statement
|
|
55
|
-
|
|
56
|
-
expect(prompt
|
|
55
|
+
it("does not contain the persona statement", () => {
|
|
56
|
+
const prompt = inlinePrompt();
|
|
57
|
+
expect(prompt).not.toContain("You are acting as a Skyramp Integration Architect");
|
|
57
58
|
});
|
|
58
|
-
it("
|
|
59
|
-
|
|
60
|
-
expect(prompt
|
|
59
|
+
it("does not contain the standalone Test Health Analysis header", () => {
|
|
60
|
+
const prompt = inlinePrompt();
|
|
61
|
+
expect(prompt).not.toContain("# Test Health Analysis");
|
|
61
62
|
});
|
|
62
|
-
it("
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_RESPONSE_SHAPE}:`);
|
|
67
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_ADDITIVE_FIELDS}:`);
|
|
68
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_AUTH_AUTHZ}:`);
|
|
69
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_BEHAVIORAL_CONTRACT}:`);
|
|
70
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_ASSIGN_ACTION}:`);
|
|
71
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_APPLY}:`);
|
|
72
|
-
expect(p).toContain(`### Step ${DRIFT_STEP_CALL_TOOL}:`);
|
|
63
|
+
it("does not contain the skyramp_actions CTA (that belongs to standalone mode)", () => {
|
|
64
|
+
const prompt = inlinePrompt();
|
|
65
|
+
// Inline mode final step directs applying changes directly, not calling skyramp_actions
|
|
66
|
+
expect(prompt).not.toContain("call `skyramp_actions`");
|
|
73
67
|
});
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
});
|
|
69
|
+
describe("buildDriftAnalysisPrompt - scanned endpoints rendering", () => {
|
|
70
|
+
// Reproduces the [object Object] bug: skeletonEndpoints from analyzeChangesTool
|
|
71
|
+
// stores methods as objects { method: string, ... }, not plain strings.
|
|
72
|
+
const skeletonMethodObjects = [
|
|
73
|
+
{
|
|
74
|
+
path: "/api/v1/",
|
|
75
|
+
methods: [{ method: "GET", description: "", queryParams: [], authRequired: true, sourceFile: "main.py", interactions: [] }],
|
|
76
|
+
resourceGroup: "v1",
|
|
77
|
+
pathParams: [],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
path: "/api/v1/orders",
|
|
81
|
+
methods: [
|
|
82
|
+
{ method: "GET", description: "", queryParams: [], authRequired: true, sourceFile: "orders.py", interactions: [] },
|
|
83
|
+
{ method: "POST", description: "", queryParams: [], authRequired: true, sourceFile: "orders.py", interactions: [] },
|
|
84
|
+
],
|
|
85
|
+
resourceGroup: "orders",
|
|
86
|
+
pathParams: [],
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
it("renders HTTP methods as strings, not [object Object]", () => {
|
|
90
|
+
const prompt = buildDriftAnalysisPrompt({
|
|
91
|
+
existingTests: [],
|
|
92
|
+
scannedEndpoints: skeletonMethodObjects,
|
|
93
|
+
repositoryPath: "/repo",
|
|
94
|
+
stateFile: "/tmp/state.json",
|
|
95
|
+
});
|
|
96
|
+
expect(prompt).not.toContain("[object Object]");
|
|
97
|
+
expect(prompt).toContain("GET /api/v1/");
|
|
98
|
+
expect(prompt).toContain("GET|POST /api/v1/orders");
|
|
99
|
+
// CTA should appear exactly once (not duplicated)
|
|
100
|
+
const ctaCount = (prompt.match(/call `skyramp_actions`/g) || []).length;
|
|
76
101
|
expect(ctaCount).toBe(1);
|
|
77
102
|
});
|
|
103
|
+
it("also works with plain string methods (ScannedEndpoint format)", () => {
|
|
104
|
+
const stringMethods = [
|
|
105
|
+
{ path: "/api/v1/products", methods: ["GET", "POST"], sourceFile: "products.py" },
|
|
106
|
+
];
|
|
107
|
+
const prompt = buildDriftAnalysisPrompt({
|
|
108
|
+
existingTests: [],
|
|
109
|
+
scannedEndpoints: stringMethods,
|
|
110
|
+
repositoryPath: "/repo",
|
|
111
|
+
stateFile: "/tmp/state.json",
|
|
112
|
+
});
|
|
113
|
+
expect(prompt).not.toContain("[object Object]");
|
|
114
|
+
expect(prompt).toContain("GET|POST /api/v1/products");
|
|
115
|
+
});
|
|
78
116
|
});
|
|
79
|
-
// ── Scanned endpoints no longer in prompt output ─────────────────────────────
|
|
80
|
-
// The context header (repo, diff, test list, scanned endpoints) was removed —
|
|
81
|
-
// skyramp_analyze_changes already delivers that context to the conversation.
|
|
82
|
-
// The scanned endpoints rendering tests were removed along with the header.
|
|
83
|
-
// The [object Object] bug that was guarded against is no longer reachable via
|
|
84
|
-
// this prompt path.
|