@skyramp/mcp 0.2.0-rc.3 → 0.2.1-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/prompts/test-maintenance/drift-analysis-prompt.js +87 -98
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +60 -92
- package/build/prompts/test-maintenance/driftAnalysisSections.js +197 -139
- package/build/prompts/testbot/testbot-prompts.js +7 -4
- package/build/prompts/testbot/testbot-prompts.test.js +22 -17
- package/build/services/AnalyticsService.js +5 -1
- package/build/services/TestDiscoveryService.js +9 -39
- package/build/tools/submitReportTool.js +56 -0
- package/build/tools/submitReportTool.test.js +106 -0
- package/build/tools/test-management/actionsTool.js +148 -166
- package/build/tools/test-management/analyzeChangesTool.js +10 -2
- package/build/tools/test-management/analyzeTestHealthTool.js +22 -10
- package/build/utils/telemetry.js +70 -0
- package/build/utils/telemetry.test.js +70 -0
- package/package.json +1 -1
|
@@ -1,102 +1,91 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isTestbotEnabled } from "../../utils/featureFlags.js";
|
|
1
|
+
import { buildActionDecisionTree, buildCheckAdditiveFields, buildCheckEndpointExistence, buildCheckResponseShape, buildCheckAuthAndAuthorization, buildCheckBehavioralContract, buildCheckAssignAction, buildDriftOutputChecklist, buildUpdateExecutionRules, } from "./driftAnalysisSections.js";
|
|
3
2
|
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 ────────────────────────────────────────────────────────────
|
|
4
74
|
export function buildDriftAnalysisPrompt(params) {
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
let newEndpointCount = 0;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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>`;
|
|
75
|
+
// Pre-compute newEndpointCount from rawDiff only when caller did not supply it.
|
|
76
|
+
// Use strict undefined check — an explicit 0 means "no new endpoints" and must
|
|
77
|
+
// not trigger a diff read.
|
|
78
|
+
let newEndpointCount = params.newEndpointCount ?? 0;
|
|
79
|
+
if (params.newEndpointCount === undefined) {
|
|
80
|
+
const rawDiff = readDiffFile(params.diffFilePath);
|
|
81
|
+
if (rawDiff) {
|
|
82
|
+
const m = rawDiff.match(/\*\*New Endpoints\*\*\s+\((\d+)\)/);
|
|
83
|
+
if (m)
|
|
84
|
+
newEndpointCount = parseInt(m[1], 10);
|
|
85
|
+
}
|
|
92
86
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
${
|
|
97
|
-
${buildBreakingChangePatterns()}
|
|
98
|
-
${buildTestAssessmentGuidelines()}
|
|
99
|
-
${buildUpdateExecutionRules()}
|
|
100
|
-
${buildAddRecommendationGuidelines()}
|
|
101
|
-
${buildDriftOutputChecklist(existingTests.length, newEndpointCount, isTestbotEnabled(), stateFile)}`;
|
|
87
|
+
const resolvedParams = { ...params, newEndpointCount };
|
|
88
|
+
// Always emit the lean wrapped form — context is already in the conversation
|
|
89
|
+
// from skyramp_analyze_changes, which always runs before this tool.
|
|
90
|
+
return `<drift_analysis_rules>\n${_plan.render(resolvedParams)}\n</drift_analysis_rules>`;
|
|
102
91
|
}
|
|
@@ -1,116 +1,84 @@
|
|
|
1
|
-
import { buildDriftAnalysisPrompt } from "./drift-analysis-prompt.js";
|
|
1
|
+
import { buildDriftAnalysisPrompt, DRIFT_STEP_ASSESS, DRIFT_STEP_ENDPOINT_EXISTENCE, DRIFT_STEP_RESPONSE_SHAPE, DRIFT_STEP_ADDITIVE_FIELDS, DRIFT_STEP_AUTH_AUTHZ, DRIFT_STEP_BEHAVIORAL_CONTRACT, DRIFT_STEP_ASSIGN_ACTION, DRIFT_STEP_APPLY, DRIFT_STEP_CALL_TOOL, } from "./drift-analysis-prompt.js";
|
|
2
2
|
import { buildDriftOutputChecklist } from "./driftAnalysisSections.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const STATE_FILE = "/tmp/skyramp-analysis-abc123.json";
|
|
4
|
+
// ── Step label constants ──────────────────────────────────────────────────────
|
|
5
|
+
describe("DRIFT_STEP_* label constants", () => {
|
|
6
|
+
it("main steps are sequentially numbered from 1", () => {
|
|
7
|
+
expect(DRIFT_STEP_ASSESS).toBe("1");
|
|
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);
|
|
8
24
|
expect(checklist).toContain("recommendations");
|
|
9
|
-
// Must mention updateInstructions so the LLM knows to populate it
|
|
10
25
|
expect(checklist).toContain("updateInstructions");
|
|
11
|
-
// Must reference the stateFile path
|
|
12
26
|
expect(checklist).toContain(STATE_FILE);
|
|
13
|
-
// Must call skyramp_actions as the final action
|
|
14
27
|
expect(checklist).toContain("skyramp_actions");
|
|
15
28
|
});
|
|
16
|
-
it("
|
|
17
|
-
const checklist = buildDriftOutputChecklist(3, 0,
|
|
18
|
-
// The JSON shape was moved to inputSchema — prompt must not duplicate it
|
|
29
|
+
it("does not contain JSON shape — schema is authoritative", () => {
|
|
30
|
+
const checklist = buildDriftOutputChecklist(3, 0, STATE_FILE);
|
|
19
31
|
expect(checklist).not.toContain('"testFile":');
|
|
20
32
|
expect(checklist).not.toContain('"action":');
|
|
21
33
|
});
|
|
22
|
-
it("
|
|
23
|
-
const checklist = buildDriftOutputChecklist(3, 0,
|
|
24
|
-
|
|
25
|
-
expect(
|
|
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");
|
|
34
|
+
it("CTA appears exactly once", () => {
|
|
35
|
+
const checklist = buildDriftOutputChecklist(3, 0, STATE_FILE);
|
|
36
|
+
const ctaCount = (checklist.match(/call `skyramp_actions`/g) || []).length;
|
|
37
|
+
expect(ctaCount).toBe(1);
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
function inlinePrompt() {
|
|
40
|
+
// ── buildDriftAnalysisPrompt ──────────────────────────────────────────────────
|
|
41
|
+
describe("buildDriftAnalysisPrompt", () => {
|
|
42
|
+
function prompt() {
|
|
43
43
|
return buildDriftAnalysisPrompt({
|
|
44
44
|
existingTests: [],
|
|
45
45
|
scannedEndpoints: [],
|
|
46
46
|
repositoryPath: "/repo",
|
|
47
|
-
|
|
47
|
+
stateFile: STATE_FILE,
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
it("wraps
|
|
51
|
-
|
|
52
|
-
expect(prompt).toContain("
|
|
53
|
-
expect(prompt).toContain("</drift_analysis_rules>");
|
|
50
|
+
it("wraps output in drift_analysis_rules XML tags", () => {
|
|
51
|
+
expect(prompt()).toContain("<drift_analysis_rules>");
|
|
52
|
+
expect(prompt()).toContain("</drift_analysis_rules>");
|
|
54
53
|
});
|
|
55
|
-
it("does not contain the persona statement", () => {
|
|
56
|
-
|
|
57
|
-
expect(prompt).not.toContain("
|
|
54
|
+
it("does not contain the persona statement or context header", () => {
|
|
55
|
+
expect(prompt()).not.toContain("You are acting as a Skyramp Integration Architect");
|
|
56
|
+
expect(prompt()).not.toContain("# Test Health Analysis");
|
|
58
57
|
});
|
|
59
|
-
it("
|
|
60
|
-
|
|
61
|
-
expect(prompt).
|
|
58
|
+
it("includes recommendations guidance and updateInstructions", () => {
|
|
59
|
+
expect(prompt()).toContain("recommendations");
|
|
60
|
+
expect(prompt()).toContain("updateInstructions");
|
|
62
61
|
});
|
|
63
|
-
it("
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
expect(
|
|
62
|
+
it("includes all PromptPlan steps", () => {
|
|
63
|
+
const p = prompt();
|
|
64
|
+
expect(p).toContain(`### Step ${DRIFT_STEP_ASSESS}:`);
|
|
65
|
+
expect(p).toContain(`### Step ${DRIFT_STEP_ENDPOINT_EXISTENCE}:`);
|
|
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}:`);
|
|
67
73
|
});
|
|
68
|
-
|
|
69
|
-
|
|
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;
|
|
74
|
+
it("skyramp_actions CTA appears exactly once", () => {
|
|
75
|
+
const ctaCount = (prompt().match(/call `skyramp_actions`/g) || []).length;
|
|
101
76
|
expect(ctaCount).toBe(1);
|
|
102
77
|
});
|
|
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
|
-
});
|
|
116
78
|
});
|
|
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.
|