@skyramp/mcp 0.2.1-rc.1 → 0.2.150-rc.sut
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 +7 -2
- package/build/playwright/registerPlaywrightTools.js +1 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +82 -0
- package/build/prompts/sut-setup/registerSetupSutPrompt.js +49 -0
- package/build/prompts/sut-setup/shared.js +80 -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/testbot/testbot-prompts.js +4 -7
- package/build/prompts/testbot/testbot-prompts.test.js +17 -22
- package/build/resources/sutSetupResource.js +47 -0
- package/build/services/TestDiscoveryService.js +39 -9
- package/build/tools/test-management/actionsTool.js +166 -148
- package/build/tools/test-management/analyzeChangesTool.js +2 -10
- package/build/tools/test-management/analyzeTestHealthTool.js +10 -22
- package/node_modules/playwright/lib/mcp/skyramp/assertApiRequestTool.js +46 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +298 -51
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +5 -0
- package/package.json +1 -1
- 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
package/build/index.js
CHANGED
|
@@ -26,6 +26,8 @@ import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBa
|
|
|
26
26
|
import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
|
|
27
27
|
import { registerAnalyzeChangesTool, registerUiAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, } from "./tools/test-management/index.js";
|
|
28
28
|
import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
|
|
29
|
+
import { registerSetupSutPrompt } from "./prompts/sut-setup/registerSetupSutPrompt.js";
|
|
30
|
+
import { registerSutSetupResource } from "./resources/sutSetupResource.js";
|
|
29
31
|
import { registerTestbotResource } from "./resources/testbotResource.js";
|
|
30
32
|
import { registerSubmitReportTool } from "./tools/submitReportTool.js";
|
|
31
33
|
import { registerInitializeWorkspaceTool } from "./tools/workspace/initializeWorkspaceTool.js";
|
|
@@ -123,7 +125,10 @@ const prompts = [
|
|
|
123
125
|
if (isTestbotEnabled()) {
|
|
124
126
|
prompts.push(registerTestbotPrompt);
|
|
125
127
|
registerTestbotResource(server);
|
|
126
|
-
logger.info("
|
|
128
|
+
logger.info("Testbot prompt enabled via SKYRAMP_FEATURE_TESTBOT");
|
|
129
|
+
prompts.push(registerSetupSutPrompt);
|
|
130
|
+
registerSutSetupResource(server);
|
|
131
|
+
logger.info("Testbot SUT setup prompt and resource enabled via SKYRAMP_FEATURE_TESTBOT");
|
|
127
132
|
}
|
|
128
133
|
prompts.forEach((registerPrompt) => registerPrompt(server));
|
|
129
134
|
logger.info("All prompts registered successfully");
|
|
@@ -174,7 +179,7 @@ const infrastructureTools = [
|
|
|
174
179
|
];
|
|
175
180
|
if (isTestbotEnabled()) {
|
|
176
181
|
infrastructureTools.push(registerSubmitReportTool);
|
|
177
|
-
logger.info("
|
|
182
|
+
logger.info("Testbot tools enabled via SKYRAMP_FEATURE_TESTBOT");
|
|
178
183
|
}
|
|
179
184
|
infrastructureTools.forEach((registerTool) => registerTool(server));
|
|
180
185
|
// Register Playwright browser tools (trace recording via browser automation)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { TESTBOT_WORKFLOW_PATH, buildContextBlock, buildCommonSutErrorsSection, buildLocalValidationSection, buildTestbotLifecycleInputsSection, getPersonaPrefix, } from "../shared.js";
|
|
2
|
+
export function getAdaptWorkflowPrompt(args) {
|
|
3
|
+
const sourceLine = args.sutSourceWorkflowFile
|
|
4
|
+
? `The user pointed at \`${args.sutSourceWorkflowFile}\` as the source workflow whose setup/teardown steps should be copied into the Testbot workflow. Use it as your primary source of setup steps — but still read the rest of the repo (other workflows, docker-compose, Kubernetes, Helm) to understand the full SUT shape.`
|
|
5
|
+
: `No specific source workflow was provided. Read ALL available GitHub workflows in this repository to learn how services are built, started, and authenticated, and pick the source workflow whose setup steps best match a SUT for testing (typically an e2e/integration workflow, not lint/release/security workflows).`;
|
|
6
|
+
const scanReadStep = args.sutSourceWorkflowFile
|
|
7
|
+
? `Read the given source GitHub workflow file \`${args.sutSourceWorkflowFile}\` (the user's hint about which steps to lift), then ALSO read the rest of \`.github/workflows/\` and all infrastructure files (docker-compose, Kubernetes manifests, Helm charts) — the full SUT shape often lives across multiple files, so do NOT tunnel into the source workflow alone. Workflows show *how* the SUT is started; infrastructure files show *what* the SUT is. When a workflow step references an infra file (e.g., \`docker compose -f infra/compose.yml up\`, \`helm upgrade <chart>\`, \`kubectl apply -f k8s/\`), open that file too — services, ports, env vars, and dependencies live there.`
|
|
8
|
+
: `Read all GitHub workflow files in \`.github/workflows/\` AND all infrastructure files (docker-compose, Kubernetes manifests, Helm charts). Workflows show *how* the SUT is started; infrastructure files show *what* the SUT is. When a workflow step references an infra file (e.g., \`docker compose -f infra/compose.yml up\`, \`helm upgrade <chart>\`, \`kubectl apply -f k8s/\`), open that file too — services, ports, env vars, and dependencies live there. Pick the source workflow (the one whose SETUP/TEST/TEARDOWN steps will be lifted) once you have the full picture.`;
|
|
9
|
+
const thinkingSourceClause = args.sutSourceWorkflowFile
|
|
10
|
+
? `confirms you are using \`${args.sutSourceWorkflowFile}\` as the source of setup steps (or explains why a different workflow is a better fit)`
|
|
11
|
+
: `names the workflow you selected as the source of setup steps and explains why`;
|
|
12
|
+
return `${getPersonaPrefix()}
|
|
13
|
+
### Goal
|
|
14
|
+
1. Generate working System Under Test (SUT) files that enable the Skyramp Testbot workflow (\`${TESTBOT_WORKFLOW_PATH}\`) to test any PR-specific code in this repository end-to-end for the services supported (REST or gRPC service APIs, queues, frontend UIs).
|
|
15
|
+
2. Source workflow: ${sourceLine}
|
|
16
|
+
3. Required output:
|
|
17
|
+
a. Output a <thinking> block that: (i) ${thinkingSourceClause}, (ii) summarizes what you learned about the SUT — services, secrets, auth, runner size — pulling from ALL workflows and infrastructure files, and (iii) decides how the SUT will be brought up — prefer Testbot lifecycle commands alone (targetSetupCommand, targetReadyCheckCommand, targetTeardownCommand) when the setup can be expressed as a single shell command; add GHA pre-steps only for tooling that cannot run in a shell (Buildx, cache, registry login) while still using lifecycle commands for service start; escalate to full GHA steps replacing service startup only when a single command cannot express it; use fully wrapped GHA steps for both setup and teardown only as a last resort. State clearly which approach was chosen and why.
|
|
18
|
+
b. Adapt the Testbot workflow file (\`${TESTBOT_WORKFLOW_PATH}\`) for bringing up the system under test.
|
|
19
|
+
${buildContextBlock(args)}
|
|
20
|
+
|
|
21
|
+
### Scan repo and understand the SUT
|
|
22
|
+
1. ${scanReadStep}
|
|
23
|
+
2. From the source workflow, classify each step by its *purpose* — not by its name or surface mechanism (\`env:\`, \`with:\`, and \`\${{ secrets.* }}\` can appear on any category and do not determine it):
|
|
24
|
+
a. SETUP — prepares the SUT (install, build, start services, migrate, seed, configure env or inject secrets for the SUT). Common attributes: \`env:\` blocks (e.g. \`DATABASE_URL\`, build flags), \`with:\` parameters (e.g. \`actions/cache\`, \`actions/setup-node\`), \`\${{ secrets.* }}\` references (e.g. registry login, deploy keys), dependency installs (e.g. \`npm ci\` before build).
|
|
25
|
+
b. TEST — runs the test suite (will be replaced by \`skyramp/testbot\`). Common attributes: \`env:\` blocks (e.g. \`API_BASE_URL\`, \`AUTH_TOKEN\`), \`with:\` parameters (e.g. \`actions/cache\`, custom test-runner actions), \`\${{ secrets.* }}\` references (e.g. test credentials, live-integration keys), dependency installs (e.g. \`npm ci\` right before \`npm test\` in the same step).
|
|
26
|
+
c. TEARDOWN — stops or cleans up.
|
|
27
|
+
d. UNRELATED — does none of those (lint, format, notifications, badges).
|
|
28
|
+
|
|
29
|
+
Output as:
|
|
30
|
+
\`\`\`
|
|
31
|
+
Step: "<step name>" → SETUP | TEST | TEARDOWN | UNRELATED
|
|
32
|
+
\`\`\`
|
|
33
|
+
3. Summarize the SUT, pulling from all sources:
|
|
34
|
+
a. Setup commands needed to bring up all services (may come from multiple workflows / infra files).
|
|
35
|
+
b. Required secrets and env vars (with their \`\${{ secrets.* }}\` references and pass-through style — \`env:\` blocks vs. \`secrets: inherit\`).
|
|
36
|
+
c. Runner needed (\`runs-on\` value; large-ubuntu for heavy builds).
|
|
37
|
+
d. How an auth token is obtained (script, step output, or secret).
|
|
38
|
+
|
|
39
|
+
### Decide SUT pattern
|
|
40
|
+
The SUT must always be built from the PR's source code — Testbot reuses this workflow for every future PR, so it must validate the code in the PR being tested, not a stale snapshot. Use build: blocks in compose files (or docker build against the PR checkout) so images come from PR source. Pull-only image references and pinned upstream tags are fine for sidecars (databases, queues, caches), but the application services under test must be built locally.
|
|
41
|
+
Evaluate the four patterns in strict priority order — always start with Pattern A and only escalate when it genuinely cannot express the setup. Full semantics for each lifecycle input are in the reference section below.
|
|
42
|
+
1. Pattern A — Testbot lifecycle commands only (try this first, always)
|
|
43
|
+
Use when the entire setup — build, start, seed — can be expressed as one or two shell commands (e.g., docker compose up -d --build, make start, ./scripts/start.sh). Do NOT add any GHA steps. Set targetSetupCommand, targetReadyCheckCommand, targetReadyCheckTimeout, and targetTeardownCommand on the Testbot action; leave skipTargetSetup unset.
|
|
44
|
+
2. Pattern B — Hybrid: GHA pre-steps for tool/env setup + Testbot lifecycle commands for service start
|
|
45
|
+
Use when the source workflow requires GHA-specific steps purely for environment or tooling (e.g., docker/setup-buildx-action, actions/cache, private registry login) but the actual service start can still be expressed as a single command. Copy only those tool/env pre-steps BEFORE the Testbot action. Set targetSetupCommand, targetReadyCheckCommand, targetReadyCheckTimeout, and targetTeardownCommand on the Testbot action; leave skipTargetSetup unset. Preserve every env:, with:, and \${{ secrets.* }} reference from the pre-steps exactly as written.
|
|
46
|
+
3. Pattern C — GHA steps replace service startup + Testbot lifecycle validation
|
|
47
|
+
Use when the service startup itself requires multiple GHA steps that cannot be collapsed into a single command. Copy the source workflow's SETUP steps verbatim BEFORE the Testbot action. Set skipTargetSetup: 'true' and do not set targetSetupCommand. Set targetReadyCheckCommand and targetReadyCheckTimeout on the Testbot action. Add TEARDOWN steps AFTER the Testbot action with if: always(), or set targetTeardownCommand if a single command suffices — pick one, not both. Preserve every env:, with:, and \${{ secrets.* }} reference exactly as written.
|
|
48
|
+
4. Pattern D — Fully wrapped by GHA steps
|
|
49
|
+
Use when teardown is also too complex for a single command. Copy the SETUP steps BEFORE and TEARDOWN steps AFTER the Testbot action (with if: always()). Set skipTargetSetup: 'true'. Do not set targetSetupCommand or targetTeardownCommand. Preserve every env:, with:, and \${{ secrets.* }} reference exactly as written.
|
|
50
|
+
|
|
51
|
+
${buildTestbotLifecycleInputsSection()}
|
|
52
|
+
|
|
53
|
+
### Adapt Testbot workflow
|
|
54
|
+
#### What to do
|
|
55
|
+
1. Edit \`${TESTBOT_WORKFLOW_PATH}\` (already created by the Testbot installer):
|
|
56
|
+
a. Apply the chosen pattern:
|
|
57
|
+
- Pattern A: set lifecycle inputs on the Testbot action, no GHA steps.
|
|
58
|
+
- Pattern B: add GHA tool/env pre-steps BEFORE the Testbot action, set lifecycle inputs (\`targetSetupCommand\`, \`targetReadyCheckCommand\`, \`targetTeardownCommand\`), leave \`skipTargetSetup\` unset.
|
|
59
|
+
- Pattern C: add GHA SETUP steps BEFORE the Testbot action, set \`skipTargetSetup: 'true'\`, set \`targetReadyCheckCommand\`, add TEARDOWN steps AFTER or set \`targetTeardownCommand\` — do NOT set \`targetSetupCommand\`.
|
|
60
|
+
- Pattern D: add GHA SETUP steps BEFORE and TEARDOWN steps AFTER the Testbot action, set \`skipTargetSetup: 'true'\`, do NOT set \`targetSetupCommand\` or \`targetTeardownCommand\`.
|
|
61
|
+
b. Update \`runs-on\` to match the runner needed by the source workflow's setup (some repos need large-ubuntu).
|
|
62
|
+
c. Pass all required secrets through — reuse the same \`\${{ secrets.* }}\` references or \`secrets: inherit\`. For GHA steps (Patterns B, C, D), set secrets via \`env:\` on the individual steps that need them; for lifecycle inputs (Patterns A, B), set them on the Testbot action or at the job level.
|
|
63
|
+
d. The basic file already contains the \`skyramp/testbot\` action — REPLACE the original workflow's test-run step with it, do NOT add a duplicate test runner step.
|
|
64
|
+
2. Configure auth — use the lifecycle input reference above for full semantics of \`authTokenCommand\` and \`uiCredentials\`:
|
|
65
|
+
a. If the source workflow exports or seeds an auth token, set \`authTokenCommand\` on the Testbot action (or create \`.skyramp/sut/get-auth-token.sh\`).
|
|
66
|
+
b. If any service requires browser login, set \`uiCredentials\` on the Testbot action.
|
|
67
|
+
c. Omit both when the SUT is unauthenticated.
|
|
68
|
+
3. Handle source-workflow shape:
|
|
69
|
+
a. If the original workflow uses \`matrix\` builds, pick ONE configuration for the Testbot workflow (matrix runs would multiply Testbot invocations).
|
|
70
|
+
b. If the original workflow uses \`needs:\` (job dependencies), inline the dependent job's steps into the Testbot job — Testbot runs as a single job.
|
|
71
|
+
|
|
72
|
+
#### What not to do
|
|
73
|
+
1. Do not modify any other GitHub workflow files referenced. The only github workflow file you may edit is \`${TESTBOT_WORKFLOW_PATH}\`; every other workflow under \`.github/workflows/\` (including the source workflow) is read-only reference material.
|
|
74
|
+
2. Do not change the \`sutSetupMode\` input in \`${TESTBOT_WORKFLOW_PATH}\`. Testbot manages this value automatically — it must stay as provided so the next CI run knows to validate the adapted workflow rather than run tests. Changing it to \`none\` or any other value will break the bootstrap cycle.
|
|
75
|
+
3. Do not point the SUT at a prebuilt upstream image (e.g. \`image: org/app:latest\` or a fixed commit SHA from \`main\`) for any application service under test. The image will lag the PR and Testbot will validate stale code.
|
|
76
|
+
4. Do not point the SUT at a remote staging or production environment for application services under test. Staging code drifts from PR source and turns Testbot's results into noise. External infra is acceptable only for sidecar dependencies the PR does not change (e.g. a managed test database).
|
|
77
|
+
|
|
78
|
+
### Verify
|
|
79
|
+
${buildCommonSutErrorsSection()}
|
|
80
|
+
${buildLocalValidationSection()}
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
4
|
+
import { getAdaptWorkflowPrompt } from "./modes/adaptWorkflowPrompt.js";
|
|
5
|
+
export var SutSetupMode;
|
|
6
|
+
(function (SutSetupMode) {
|
|
7
|
+
SutSetupMode["None"] = "none";
|
|
8
|
+
SutSetupMode["AdaptWorkflow"] = "adapt_workflow";
|
|
9
|
+
})(SutSetupMode || (SutSetupMode = {}));
|
|
10
|
+
export function registerSetupSutPrompt(server) {
|
|
11
|
+
logger.info("Registering SUT setup prompt");
|
|
12
|
+
server.registerPrompt("skyramp_testbot_sut", {
|
|
13
|
+
description: "Scan a repository and generate SUT (System Under Test) setup files for Skyramp Testbot. " +
|
|
14
|
+
"First milestone: adapts an existing CI/e2e workflow into the Testbot workflow.",
|
|
15
|
+
argsSchema: {
|
|
16
|
+
repositoryPath: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Absolute path to the repository root"),
|
|
19
|
+
sutSetupMode: z
|
|
20
|
+
.nativeEnum(SutSetupMode)
|
|
21
|
+
.default(SutSetupMode.None)
|
|
22
|
+
.describe("SUT setup mode: none | adapt_workflow"),
|
|
23
|
+
sutSourceWorkflowFile: z
|
|
24
|
+
.string()
|
|
25
|
+
.default("")
|
|
26
|
+
.describe("Path to an existing workflow file to adapt. Example: .github/workflows/e2e-tests.yml"),
|
|
27
|
+
},
|
|
28
|
+
}, async (args) => {
|
|
29
|
+
const prompt = buildSutPrompt(args);
|
|
30
|
+
AnalyticsService.pushMCPToolEvent("skyramp_testbot_sut_prompt", undefined, { mode: args.sutSetupMode }).catch(() => { });
|
|
31
|
+
return {
|
|
32
|
+
messages: [
|
|
33
|
+
{
|
|
34
|
+
role: "user",
|
|
35
|
+
content: { type: "text", text: prompt },
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function buildSutPrompt(args) {
|
|
42
|
+
switch (args.sutSetupMode) {
|
|
43
|
+
case SutSetupMode.AdaptWorkflow:
|
|
44
|
+
return getAdaptWorkflowPrompt(args);
|
|
45
|
+
case SutSetupMode.None:
|
|
46
|
+
default:
|
|
47
|
+
return "sutSetupMode is 'none'. No SUT setup required.";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getPersonaPrefix } from "../personas.js";
|
|
2
|
+
/**
|
|
3
|
+
* Path of the Testbot workflow file relative to the repo root. Created by the
|
|
4
|
+
* Testbot installer and edited in-place by every SUT setup mode.
|
|
5
|
+
*/
|
|
6
|
+
export const TESTBOT_WORKFLOW_PATH = ".github/workflows/skyramp-testbot.yml";
|
|
7
|
+
export function buildContextBlock(args) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`<context>`);
|
|
10
|
+
lines.push(`Repository path: ${args.repositoryPath}`);
|
|
11
|
+
lines.push(`Setup mode: ${args.sutSetupMode}`);
|
|
12
|
+
if (args.sutSourceWorkflowFile)
|
|
13
|
+
lines.push(`Source workflow file: ${args.sutSourceWorkflowFile}`);
|
|
14
|
+
lines.push(`</context>`);
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
export function buildLocalValidationSection() {
|
|
18
|
+
return `Before reporting success, exercise the adapted workflow locally. Testbot's external fix loop only retries when SUT lifecycle commands are set on the Testbot action. When the SUT is brought up by surrounding GHA steps (skipTargetSetup: 'true'), the fix loop is skipped — so the local check below is the only safety net before the workflow is committed.
|
|
19
|
+
Run commands one at a time in your shell — individual execution pinpoints failures far faster than running the whole workflow at once. Any non-zero exit is a fix-needed signal: adjust the workflow, re-run the failing command, and do not proceed until it passes.
|
|
20
|
+
1. Validate build scripts and helper programs first — run these before anything else so build and auth failures surface cheaply:
|
|
21
|
+
a. Every helper script the workflow calls (e.g., \`./scripts/*.sh\`, \`make <target>\`, \`./gradlew <task>\`) — run each and confirm exit 0.
|
|
22
|
+
b. \`.skyramp/sut/get-auth-token.sh\` if present — run it and confirm it prints a non-empty token to stdout.
|
|
23
|
+
c. Every Dockerfile referenced by build steps — run \`docker build\` against each.
|
|
24
|
+
d. Every docker-compose file the workflow references — run \`docker compose -f <path> config\` to validate the YAML, then \`docker compose -f <path> build\`.
|
|
25
|
+
2. Validate the SUT lifecycle — choose the branch that matches the chosen pattern:
|
|
26
|
+
a. If lifecycle commands are set on the Testbot action (skipTargetSetup is unset):
|
|
27
|
+
i. Run \`targetSetupCommand\` — confirm exit 0.
|
|
28
|
+
ii. Poll \`targetReadyCheckCommand\` until it exits 0 within \`targetReadyCheckTimeout\` seconds.
|
|
29
|
+
iii. Run \`targetTeardownCommand\` — confirm cleanup and exit 0.
|
|
30
|
+
b. If GHA steps wrap the Testbot action (skipTargetSetup: 'true'):
|
|
31
|
+
i. Extract the shell body of each SETUP step and run them in order. If a step depends entirely on a \${{ secrets.* }} value your shell cannot resolve, note it in <thinking> and skip — do not fabricate a value.
|
|
32
|
+
ii. Poll \`targetReadyCheckCommand\` until it exits 0.
|
|
33
|
+
iii. Extract and run each TEARDOWN step's body in order so the next iteration starts clean.
|
|
34
|
+
Only proceed to the success report once setup → health check → teardown all pass.`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns a reference block describing every Testbot action input that controls
|
|
38
|
+
* the SUT lifecycle and auth. Embed this wherever a prompt needs the LLM to
|
|
39
|
+
* understand what knobs are available before choosing how to wire up the SUT.
|
|
40
|
+
*/
|
|
41
|
+
export function buildTestbotLifecycleInputsSection() {
|
|
42
|
+
return `#### Testbot action inputs — target lifecycle and auth
|
|
43
|
+
Target lifecycle inputs (control how Testbot starts, validates, and stops the SUT):
|
|
44
|
+
1. \`targetSetupCommand\` — shell command that builds and starts all SUT services. Testbot runs this before tests.
|
|
45
|
+
a. Default when unset: \`docker compose up -d\` (starts existing images, does NOT build).
|
|
46
|
+
b. For PR-source builds use \`docker compose up -d --build\` or the equivalent for helm/script-based SUTs.
|
|
47
|
+
c. Override whenever the source workflow's start sequence differs from the default.
|
|
48
|
+
2. \`targetReadyCheckCommand\` — polling command (e.g., \`curl -sf http://localhost:8080/health\`) that Testbot runs repeatedly after setup until it exits 0 (service is ready) or the timeout is reached.
|
|
49
|
+
a. Always set this — without it Testbot proceeds immediately and tests may hit a not-yet-ready service.
|
|
50
|
+
3. \`targetReadyCheckTimeout\` — seconds to wait for \`targetReadyCheckCommand\` to succeed.
|
|
51
|
+
a. Default: \`30\`. Cold Docker builds routinely take 60–180 s; always set this to \`'120'\` or higher for compose-based SUTs.
|
|
52
|
+
4. \`targetTeardownCommand\` — shell command that stops and cleans up the SUT after tests (e.g., \`docker compose down -v\`).
|
|
53
|
+
a. Always set this so each run starts from a clean state.
|
|
54
|
+
b. Omit only when teardown is handled by surrounding GHA steps with \`if: always()\`.
|
|
55
|
+
5. \`skipTargetSetup\` — when \`'true'\`, Testbot skips running \`targetSetupCommand\` entirely.
|
|
56
|
+
a. Set this only when GHA steps surrounding the Testbot action already bring the SUT up.
|
|
57
|
+
b. Leave it unset (or \`'false'\`) when Testbot should run the full lifecycle via the inputs above.
|
|
58
|
+
Auth inputs (used by Testbot to authenticate against the running SUT during test recording and execution):
|
|
59
|
+
6. \`authTokenCommand\` — shell command whose stdout is the auth credential for REST/gRPC API testing (e.g., a Bearer token, API key, or session cookie). Testbot captures the output and injects it as the \`Authorization\` header.
|
|
60
|
+
a. Set this when the SUT requires authentication for API calls.
|
|
61
|
+
b. If the source workflow exports a token via a step output or script, wire that same script here (e.g., \`bash .skyramp/sut/get-auth-token.sh\`).
|
|
62
|
+
c. If the workflow seeds a test user during setup, create \`.skyramp/sut/get-auth-token.sh\` that logs in with those credentials and prints the token.
|
|
63
|
+
d. Omit when the SUT APIs are unauthenticated.
|
|
64
|
+
7. \`uiCredentials\` — \`username:password\` pair typed into the browser login form during UI test recording (format: \`myuser:mypassword\`).
|
|
65
|
+
a. Set for any frontend service that requires browser-based login.
|
|
66
|
+
b. Use \`\${{ secrets.SKYRAMP_UI_CREDENTIALS }}\` if the secret exists, otherwise use credentials seeded during SUT setup.`;
|
|
67
|
+
}
|
|
68
|
+
export function buildCommonSutErrorsSection() {
|
|
69
|
+
return `Before finishing, verify the adapted Testbot workflow setup against common errors seen during SUT setup:
|
|
70
|
+
1. port_conflict — another container is already using the port. Pick a free host port or stop the conflicting container.
|
|
71
|
+
2. image_not_found / build_failed — the referenced image does not exist for this SHA, or the build step has the wrong context/Dockerfile path. Build from PR source, do not rely on upstream commit-SHA tags.
|
|
72
|
+
3. healthcheck_timeout — the service is slow to come up. Use \`targetReadyCheckTimeout >= 120\` and ensure the health endpoint actually exists.
|
|
73
|
+
4. connection_refused — the service is not listening on the expected host/port (often binding to 127.0.0.1 inside the container; bind to 0.0.0.0).
|
|
74
|
+
5. dependency_error — required service (DB, cache, queue) is missing from compose or not healthy before the app starts. Add \`depends_on\` with \`condition: service_healthy\`.
|
|
75
|
+
6. permission_denied — script is not executable (\`chmod +x\`) or volume mount has wrong ownership.
|
|
76
|
+
7. auth_endpoint_404 / auth_credentials_invalid — the auth script hits a wrong path, or the database has no seeded user. The auth script must create the user before login when starting from a fresh DB.
|
|
77
|
+
8. missing env vars / secrets — required env vars are unset on the runner. Either default them in the script or pass them through workflow \`env:\`.
|
|
78
|
+
Do not add an in-prompt fix loop — Testbot's external validator will retry with concrete error context if any of these occur.`;
|
|
79
|
+
}
|
|
80
|
+
export { getPersonaPrefix };
|
|
@@ -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.
|