@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,31 +1,74 @@
|
|
|
1
1
|
import { AnalysisScope } from "../../types/RepositoryAnalysis.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
import { PromptPlan } from "./promptPlan.js";
|
|
3
|
+
import { isFrontendFile } from "./scopeAssessment.js";
|
|
4
|
+
import { readDiffFile } from "../../utils/utils.js";
|
|
5
|
+
// ── Step label constants ─────────────────────────────────────────────────────
|
|
6
|
+
// Derived from the PromptPlan declaration below. Non-conditional steps export
|
|
7
|
+
// static string constants (safe to use in tests without passing params).
|
|
8
|
+
// The one conditional sub-step (TRACE_CALLERS) exports a function accessor.
|
|
9
|
+
//
|
|
10
|
+
// Labels:
|
|
11
|
+
// READ_FILES → "1" (main step, always present)
|
|
12
|
+
// RESOLVE_PATHS → "1.1" (sub-step of 1, always present)
|
|
13
|
+
// EXTRACT → "2" (main step, always present)
|
|
14
|
+
// TRACE_CALLERS → "2.1" (sub-step of 2, conditional — use getStepTraceCallers())
|
|
15
|
+
// DRAFT → "3" (main step, always present)
|
|
16
|
+
// CALL_TOOL → "4" (main step, always present)
|
|
17
|
+
// Extensions that indicate a backend code file (has symbols/functions to trace).
|
|
18
|
+
// Used to filter unmatchedFiles before emitting the trace-callers sub-step.
|
|
19
|
+
const BACKEND_CODE_EXT = /\.(ts|js|mjs|cjs|py|java|kt|rb|go|cs|php|rs|scala|swift|c|cpp|h|hpp)$/i;
|
|
20
|
+
// ── Inline note for Java Spring route resolution ──────────────────────────────
|
|
21
|
+
// Appended to any step where the LLM reads Java source files. Java Spring has no
|
|
22
|
+
// router-mounting file — each controller defines its own class-level prefix, and
|
|
23
|
+
// that prefix may reference a constant defined elsewhere.
|
|
24
|
+
const JAVA_SPRING_NOTE = `For 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).`;
|
|
25
|
+
const INLINE_DIFF_LIMIT = 12_000;
|
|
26
|
+
function _deriveContext(p) {
|
|
27
|
+
const diffFiles = p.parsedDiff?.changedFiles ?? [];
|
|
28
|
+
const preDetectedEndpoints = !!(p.parsedDiff &&
|
|
29
|
+
(p.parsedDiff.newEndpoints.length > 0 ||
|
|
30
|
+
p.parsedDiff.modifiedEndpoints.length > 0 ||
|
|
31
|
+
(p.parsedDiff.removedEndpoints?.length ?? 0) > 0));
|
|
32
|
+
const isUIOnly = diffFiles.length > 0 &&
|
|
33
|
+
!preDetectedEndpoints &&
|
|
34
|
+
diffFiles.every((f) => isFrontendFile(f));
|
|
35
|
+
const canInline = !!(p.diffContent && p.diffContent.length <= INLINE_DIFF_LIMIT);
|
|
36
|
+
return { diffFiles, preDetectedEndpoints, isUIOnly, canInline };
|
|
37
|
+
}
|
|
38
|
+
// ── Private step title / body helpers ────────────────────────────────────────
|
|
39
|
+
// Each function receives AnalysisOutputParams and returns either the step title
|
|
40
|
+
// string or the step body text (WITHOUT the "### Step N: Title" header — that is
|
|
41
|
+
// added by PromptPlan.render()). Using function declarations so they are hoisted
|
|
42
|
+
// and can be referenced in the _plan definition below.
|
|
43
|
+
function _readFilesBody(p) {
|
|
44
|
+
const changedFiles = p.parsedDiff?.changedFiles.join(", ") ?? "";
|
|
45
|
+
const { canInline } = _deriveContext(p);
|
|
46
|
+
const diffFileRef = canInline
|
|
47
|
+
? `\n<diff>\n${p.diffContent}\n</diff>\n`
|
|
48
|
+
: p.diffFilePath
|
|
49
|
+
? `\n**Full diff file**: \`${p.diffFilePath}\` — **you MUST read this file before proceeding to Step ${ANALYSIS_STEP_EXTRACT}.** It contains the complete unified diff for this PR.\n`
|
|
50
|
+
: "";
|
|
51
|
+
return `${changedFiles}${diffFileRef}`;
|
|
52
|
+
}
|
|
53
|
+
function _resolvePathsTitle(p) {
|
|
54
|
+
if (p.wsSchemaPath && p.specFetchSucceeded) {
|
|
55
|
+
return "Validate all endpoint paths against the OpenAPI spec";
|
|
56
|
+
}
|
|
57
|
+
if (p.routerMountContext.length) {
|
|
58
|
+
return "Build path resolution table";
|
|
59
|
+
}
|
|
60
|
+
return "Verify endpoint paths from source files";
|
|
61
|
+
}
|
|
62
|
+
function _resolvePathsBody(p) {
|
|
16
63
|
if (p.wsSchemaPath && p.specFetchSucceeded) {
|
|
17
|
-
return
|
|
18
|
-
Fetch \`${p.wsSchemaPath}\` and extract all keys from \`spec.paths\`.
|
|
64
|
+
return `Fetch \`${p.wsSchemaPath}\` and extract all keys from \`spec.paths\`.
|
|
19
65
|
**Before placing any path in a tool call**, confirm it exists in that list.
|
|
20
66
|
If a path is NOT in the spec **and it did not come from the PR diff**, find the correct spelling by matching resource name — do NOT use it unverified.
|
|
21
|
-
Paths the PR explicitly added or modified may not yet appear in the spec (spec lag) — treat those as valid
|
|
22
|
-
`;
|
|
67
|
+
Paths the PR explicitly added or modified may not yet appear in the spec (spec lag) — treat those as valid.`;
|
|
23
68
|
}
|
|
24
|
-
// Case B: no spec (or spec unreachable) but router mount context available
|
|
25
69
|
if (p.routerMountContext.length) {
|
|
26
70
|
const hasInlined = (p.routerFileContents?.length ?? 0) > 0;
|
|
27
|
-
return
|
|
28
|
-
${hasInlined
|
|
71
|
+
return `${hasInlined
|
|
29
72
|
? "The **Routing entry-point files** section above contains the inlined file contents — use them directly to trace every router mount call"
|
|
30
73
|
: "The **Routing entry-point files** section above lists the files to read.\n\n**Read each of those files** and trace every router mount call"} to understand nesting — the pattern varies by framework but the structure is universal: a parent attaches a child router with an optional extra prefix segment. If a prefix is a variable (e.g. \`prefix=api_prefix\`), resolve the variable's value by reading the assignment or the config/settings file it comes from. Examples of what to look for (non-exhaustive):
|
|
31
74
|
- Python (FastAPI/Flask): \`parent.include_router(child, prefix="...")\`, \`app.register_blueprint(...)\`
|
|
@@ -41,88 +84,28 @@ Chain all segments from the app root down through every intermediate mount to ea
|
|
|
41
84
|
|-------------|----------------|
|
|
42
85
|
| (leaf router file) | (fully chained prefix, e.g. /api/v1/products/{id}/reviews) |
|
|
43
86
|
|
|
44
|
-
**This table is authoritative.** Before placing any URL in a tool call, look up the source file. If the pre-built catalog shows a different path, use the table value
|
|
45
|
-
|
|
46
|
-
`;
|
|
87
|
+
**This table is authoritative.** Before placing any URL in a tool call, look up the source file. If the pre-built catalog shows a different path, use the table value.`;
|
|
47
88
|
}
|
|
48
|
-
// Case C: no spec AND no router context — source-verify fallback
|
|
49
|
-
// Note: also fires when a spec was configured (wsSchemaPath set) but could not be
|
|
50
|
-
// fetched at analysis time (specFetchSucceeded = false). When that happens the LLM
|
|
51
|
-
// should know a spec was expected so it can be extra-skeptical about path correctness.
|
|
52
89
|
const specFailedNote = p.wsSchemaPath && !p.specFetchSucceeded
|
|
53
90
|
? `\n> ⚠️ A spec was configured (\`${p.wsSchemaPath}\`) but could not be loaded at analysis time — treat all paths as unverified until confirmed against source.`
|
|
54
91
|
: "";
|
|
55
|
-
return
|
|
56
|
-
The endpoint catalog below was produced by static regex analysis and is **unverified**.
|
|
92
|
+
return `The endpoint catalog below was produced by static regex analysis and is **unverified**.
|
|
57
93
|
Before using any path in a tool call, read the route definition file identified in the "Source" column and confirm the path string exactly.
|
|
58
|
-
Pay special attention to mount prefixes — a router at \`/api/v1\` + route \`/version\` → path is \`/api/v1/version\`, not \`/api/server-version
|
|
59
|
-
${specFailedNote}
|
|
60
|
-
`;
|
|
94
|
+
Pay special attention to mount prefixes — a router at \`/api/v1\` + route \`/version\` → path is \`/api/v1/version\`, not \`/api/server-version\`.${specFailedNote}`;
|
|
61
95
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
**Your only job is to present it.**
|
|
77
|
-
|
|
78
|
-
1. Fill in every \`<…from source>\` placeholder using the field names, computed formulas, and auth details you found in Steps 1–2.
|
|
79
|
-
2. Output the completed catalog **exactly as formatted — grouped by test type (### E2E / ### UI / ### Integration / ### Contract)**. Do NOT restructure, reorder, rename sections, or generate a new format.
|
|
80
|
-
3. Do NOT call any Skyramp generation tools. The catalog shows ready-to-use tool calls that can be executed on demand.
|
|
81
|
-
|
|
82
|
-
**If** Steps 1–2 revealed additional scenarios the catalog does not cover (e.g. a computed formula or FK relationship that was missed), you may optionally call \`skyramp_recommend_tests\` with \`stateFile: "${p.stateFile ?? p.sessionId}"\` and \`enrichedScenarios\` to regenerate a more complete catalog — but only after presenting the current one.`;
|
|
83
|
-
const hasJavaFiles = p.candidateRouteFiles?.some(f => /\.(java|kt)$/.test(f)) ?? false;
|
|
84
|
-
const routeFilesSection = p.candidateRouteFiles && p.candidateRouteFiles.length > 0
|
|
85
|
-
? `\nRoute/controller files found by static scan (read these to discover endpoints — the regex-based catalog below may be incomplete for your framework):\n${p.candidateRouteFiles.map(f => `- ${f}`).join("\n")}\n`
|
|
86
|
-
: "";
|
|
87
|
-
const resolvePathsNote = p.routerMountContext.length
|
|
88
|
-
? `**Resolve nested paths** using your Step 1.5 table — a router in the table with prefix \`/api/v1/products/{product_id}/reviews\` means every endpoint in that file lives under that full path.`
|
|
89
|
-
: `**Resolve full paths** using the prefixes you identified in Step 1 (e.g. Java Spring class-level \`@RequestMapping\` prefix + method-level path).`;
|
|
90
|
-
return `## Your Task — Fill in and Present the Catalog (full repo)
|
|
91
|
-
|
|
92
|
-
### Step 1: Read key files
|
|
93
|
-
${routeFilesSection}Read the route/controller files above **and** model/schema files (Pydantic models, Zod schemas, DTOs) to find: required request body fields, computed response fields and formulas, auth middleware type, storage backend, and how sub-routers are mounted (cross-check against Router Mounting section above).
|
|
94
|
-
${hasJavaFiles ? JAVA_SPRING_NOTE : ""}
|
|
95
|
-
If the endpoint catalog below is missing endpoints visible in these files (e.g. from a framework the static scanner doesn't recognise), extract them now and include them in Step 3's \`enrichedScenarios\`.
|
|
96
|
-
|
|
97
|
-
${buildPathResolutionTableStep(p)}### Step 2: Map cross-resource relationships and resolve endpoint paths
|
|
98
|
-
(Distinct from Step 1 — Step 1 reads individual schemas; Step 2 maps how endpoints relate to each other.)
|
|
99
|
-
For each endpoint: which POST creates resources consumed by other endpoints?
|
|
100
|
-
${resolvePathsNote}
|
|
101
|
-
For GET list endpoints: identify query params (\`limit\`, \`offset\`, \`order\`, \`orderBy\`) from framework annotations (FastAPI \`Query()\`, Express \`req.query\`, etc.).
|
|
102
|
-
|
|
103
|
-
${nextStep}`;
|
|
104
|
-
}
|
|
105
|
-
const changedFiles = p.parsedDiff?.changedFiles.join(", ") ?? "";
|
|
106
|
-
// Whether the scanner found API endpoints in any changed file.
|
|
107
|
-
const preDetectedEndpoints = p.parsedDiff && (p.parsedDiff.newEndpoints.length > 0 || p.parsedDiff.modifiedEndpoints.length > 0 || (p.parsedDiff.removedEndpoints?.length ?? 0) > 0);
|
|
108
|
-
const diffFiles = p.parsedDiff?.changedFiles ?? [];
|
|
109
|
-
const isUIOnly = diffFiles.length > 0 &&
|
|
110
|
-
!preDetectedEndpoints &&
|
|
111
|
-
diffFiles.every(f => FRONTEND_EXT.test(f));
|
|
112
|
-
const diffHasJavaFiles = diffFiles.some(f => /\.(java|kt)$/.test(f));
|
|
113
|
-
// Inline small diffs so the LLM sees them without a tool call. Large diffs
|
|
114
|
-
// stay as a temp file reference to avoid bloating the prompt.
|
|
115
|
-
const INLINE_DIFF_LIMIT = 12_000; // chars — roughly 300 lines
|
|
116
|
-
const canInline = p.diffContent && p.diffContent.length <= INLINE_DIFF_LIMIT;
|
|
117
|
-
const diffFileRef = canInline
|
|
118
|
-
? `\n<diff>\n${p.diffContent}\n</diff>\n`
|
|
119
|
-
+ (p.diffFilePath ? `Full diff also available at \`${p.diffFilePath}\`.\n` : "")
|
|
120
|
-
: p.diffFilePath
|
|
121
|
-
? `\n**Full diff file**: \`${p.diffFilePath}\` — **you MUST read this file before proceeding to Step 2.** It contains the complete unified diff for this PR.\n`
|
|
122
|
-
: "";
|
|
123
|
-
const step2 = isUIOnly
|
|
124
|
-
? `### Step 2: Identify consumed API endpoints and integration status
|
|
125
|
-
UI-only PR — perform two checks:
|
|
96
|
+
function _extractTitle(p) {
|
|
97
|
+
const { isUIOnly, canInline } = _deriveContext(p);
|
|
98
|
+
if (isUIOnly)
|
|
99
|
+
return "Identify consumed API endpoints and integration status";
|
|
100
|
+
if (canInline || p.diffFilePath)
|
|
101
|
+
return "Extract new, modified, and removed API endpoints from the diff";
|
|
102
|
+
return "Extract new, modified, and removed API endpoints from source files";
|
|
103
|
+
}
|
|
104
|
+
function _extractBody(p) {
|
|
105
|
+
const { diffFiles, isUIOnly, canInline, preDetectedEndpoints } = _deriveContext(p);
|
|
106
|
+
const diffHasJavaFiles = diffFiles.some((f) => /\.(java|kt)$/.test(f));
|
|
107
|
+
if (isUIOnly) {
|
|
108
|
+
return `UI-only PR — perform two checks:
|
|
126
109
|
1. Read changed frontend files to find API calls (fetch, axios, hooks).
|
|
127
110
|
2. For each changed component file (skip CSS/HTML/style-only files — they have no exported component name to search for): check whether any production source file imports, re-exports, or renders it.
|
|
128
111
|
- Search for both the component's exported name AND its module path/filename to catch aliased and default imports (e.g. \`import Foo from './CartLine'\`).
|
|
@@ -132,10 +115,16 @@ UI-only PR — perform two checks:
|
|
|
132
115
|
If no production file imports, re-exports, or renders a changed component, mark it as **unintegrated** in the Execution Plan output.
|
|
133
116
|
Exception: if the same PR also adds a route/page file (e.g. under Next.js \`pages/\` or \`app/\`) that imports the component, the route IS the integration point — do NOT mark it as unintegrated.
|
|
134
117
|
Do NOT apply the unintegrated heuristic to route/entrypoint files themselves — those are always reachable by convention.
|
|
135
|
-
An unintegrated non-route component has no DOM node in the running app and cannot be browser-tested — it qualifies as a dead-code / unintegrated-component no-surface PR regardless of how complex the component logic is
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
An unintegrated non-route component has no DOM node in the running app and cannot be browser-tested — it qualifies as a dead-code / unintegrated-component no-surface PR regardless of how complex the component logic is.
|
|
119
|
+
|
|
120
|
+
**While reading, also note these patterns** (tag findings with their category for Step ${ANALYSIS_STEP_DRAFT}):
|
|
121
|
+
- **Unique constraints**: \`@unique\`, \`unique: true\`, unique indexes, \`.refine()\` uniqueness checks, \`UNIQUE\` in SQL migrations → _data_integrity_
|
|
122
|
+
- **Cascade deletes**: \`ON DELETE CASCADE\`, \`.onDelete("cascade")\`, manual cascade logic in delete handlers → _data_integrity_
|
|
123
|
+
- **Permission checks**: auth middleware, ownership guards (\`req.user.id === resource.ownerId\`), role-based access control, \`isOwner\` assertions → _security_boundary_
|
|
124
|
+
- **Breaking changes in diff**: route renames, deleted route definitions, auth header changes, removed required fields, changed status codes → _breaking_change_`;
|
|
125
|
+
}
|
|
126
|
+
if (canInline || p.diffFilePath) {
|
|
127
|
+
return `${canInline ? "Read the \`<diff>\` above" : `Read the diff file at \`${p.diffFilePath}\``} and identify every new or modified API endpoint — route registrations, handler methods, controller annotations. Then use the **Router Mounting / Nesting** section above to reconstruct the full URL path for each endpoint by chaining all parent router prefixes down to the handler (e.g. a handler in a file with \`prefix="/reviews"\` that is mounted at \`/{product_id}\` under a router mounted at \`/api/v1/products\` → full path \`/api/v1/products/{product_id}/reviews\`).
|
|
139
128
|
${diffHasJavaFiles ? JAVA_SPRING_NOTE : ""}
|
|
140
129
|
For each endpoint found: note the HTTP method, full path, and source file.
|
|
141
130
|
${preDetectedEndpoints ? "The endpoint catalog above already lists some changed endpoints — verify and augment with anything it missed." : "No endpoints were pre-detected in the changed files — extract them from the diff."}
|
|
@@ -143,54 +132,55 @@ ${preDetectedEndpoints ? "The endpoint catalog above already lists some changed
|
|
|
143
132
|
**CRITICAL — Query params vs body:** For GET endpoints (especially search/filter/list),
|
|
144
133
|
identify which parameters are URL query params vs request body. Look at framework-specific
|
|
145
134
|
annotations (FastAPI \`Query()\`, Express \`req.query\`, Spring \`@RequestParam\`, etc.).
|
|
146
|
-
Pass these as \`queryParams\` (not \`requestBody\`) when generating scenarios
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
Pass these as \`queryParams\` (not \`requestBody\`) when generating scenarios.
|
|
136
|
+
|
|
137
|
+
**While reading, also note these patterns** (tag findings with their category for Step ${ANALYSIS_STEP_DRAFT}):
|
|
138
|
+
- **Unique constraints**: \`@unique\`, \`unique: true\`, unique indexes, \`.refine()\` uniqueness checks, \`UNIQUE\` in SQL migrations → _data_integrity_
|
|
139
|
+
- **Cascade deletes**: \`ON DELETE CASCADE\`, \`.onDelete("cascade")\`, manual cascade logic in delete handlers → _data_integrity_
|
|
140
|
+
- **Permission checks**: auth middleware, ownership guards (\`req.user.id === resource.ownerId\`), role-based access control, \`isOwner\` assertions → _security_boundary_
|
|
141
|
+
- **Breaking changes in diff**: route renames, deleted route definitions, auth header changes, removed required fields, changed status codes → _breaking_change_`;
|
|
142
|
+
}
|
|
143
|
+
return `No diff was available — read the changed source files listed above directly to identify new or modified API endpoints. Use the **Router Mounting / Nesting** section to reconstruct full paths.
|
|
149
144
|
${diffHasJavaFiles ? JAVA_SPRING_NOTE : ""}
|
|
150
145
|
For each endpoint found: note the HTTP method, full path, and source file.
|
|
151
|
-
Also compare against the endpoint catalog to identify any endpoints that appear in the catalog but are no longer present in the source files — these are removed endpoints
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const traceableUnmatched = (p.unmatchedFiles ?? []).filter(f => BACKEND_CODE_EXT.test(f));
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
146
|
+
Also compare against the endpoint catalog to identify any endpoints that appear in the catalog but are no longer present in the source files — these are removed endpoints.
|
|
147
|
+
|
|
148
|
+
**While reading, also note these patterns** (tag findings with their category for Step ${ANALYSIS_STEP_DRAFT}):
|
|
149
|
+
- **Unique constraints**: \`@unique\`, \`unique: true\`, unique indexes, \`.refine()\` uniqueness checks, \`UNIQUE\` in SQL migrations → _data_integrity_
|
|
150
|
+
- **Cascade deletes**: \`ON DELETE CASCADE\`, \`.onDelete("cascade")\`, manual cascade logic in delete handlers → _data_integrity_
|
|
151
|
+
- **Permission checks**: auth middleware, ownership guards (\`req.user.id === resource.ownerId\`), role-based access control, \`isOwner\` assertions → _security_boundary_
|
|
152
|
+
- **Breaking changes in diff**: route renames, deleted route definitions, auth header changes, removed required fields, changed status codes → _breaking_change_`;
|
|
153
|
+
}
|
|
154
|
+
function _traceCallersCondition(p) {
|
|
155
|
+
// Only emit in diff scope — the plan may be preview()'d with full-repo params.
|
|
156
|
+
const isDiffScope = p.analysisScope === AnalysisScope.CurrentBranchDiff;
|
|
157
|
+
const { isUIOnly } = _deriveContext(p);
|
|
158
|
+
const traceableUnmatched = (p.unmatchedFiles ?? []).filter((f) => BACKEND_CODE_EXT.test(f));
|
|
159
|
+
return isDiffScope && !isUIOnly && traceableUnmatched.length > 0;
|
|
160
|
+
}
|
|
161
|
+
function _traceCallersBody(p) {
|
|
162
|
+
const traceableUnmatched = (p.unmatchedFiles ?? []).filter((f) => BACKEND_CODE_EXT.test(f));
|
|
163
|
+
return `The following changed files contain **no HTTP endpoint registrations** (no route annotations, controller mappings, or handler decorators). Their changes will only be tested if you find and target the HTTP endpoints that *call* them:
|
|
168
164
|
|
|
169
|
-
${traceableUnmatched.map(f => `- \`${f}\``).join("\n")}
|
|
165
|
+
${traceableUnmatched.map((f) => `- \`${f}\``).join("\n")}
|
|
170
166
|
|
|
171
167
|
For each file above:
|
|
172
168
|
1. **Find the changed symbols** — read the diff (or the file) to identify which functions, methods, or classes were modified.
|
|
173
169
|
2. **Search for callers** — look for import statements and call sites of those symbols across service, handler, and controller files. Use fully qualified names (e.g. \`DataUtils.addFileData\`, not just \`addFileData\`) to avoid false matches in large monorepos.
|
|
174
170
|
3. **Trace to HTTP registration** — from each caller, follow up to the route/controller registration (Spring \`@PostMapping\`, Express \`router.post\`, FastAPI \`@router.post\`, etc.) to identify the endpoint(s) that invoke the changed logic.
|
|
175
|
-
4. **Augment the endpoint list** from Step
|
|
176
|
-
5. If an execution or processing endpoint is found (path ending in \`/execute\`, \`/run\`, \`/trigger\`, \`/process\`, \`/invoke\`, or similar), it **MUST** be included in the test candidates. Do not produce coverage consisting solely of CRUD endpoints when an execution-path endpoint was found — CRUD tests may still be included but must not be the only coverage
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Assess which existing tests may be broken by the changes in this diff.
|
|
189
|
-
|
|
190
|
-
### Step 4: Call analyze test health
|
|
191
|
-
Call \`skyramp_analyze_test_health\` with \`stateFile: "${p.stateFile ?? p.sessionId}"\``
|
|
192
|
-
: `### Step 3: Draft integration scenarios
|
|
193
|
-
Draft multi-step scenarios simulating realistic user workflows:
|
|
171
|
+
4. **Augment the endpoint list** from Step ${ANALYSIS_STEP_EXTRACT} with these execution-path endpoints.
|
|
172
|
+
5. If an execution or processing endpoint is found (path ending in \`/execute\`, \`/run\`, \`/trigger\`, \`/process\`, \`/invoke\`, or similar), it **MUST** be included in the test candidates. Do not produce coverage consisting solely of CRUD endpoints when an execution-path endpoint was found — CRUD tests may still be included but must not be the only coverage.`;
|
|
173
|
+
}
|
|
174
|
+
function _draftTitle(p) {
|
|
175
|
+
return p.nextTool === "skyramp_analyze_test_health"
|
|
176
|
+
? "Identify tests at risk of drift"
|
|
177
|
+
: "Draft integration scenarios";
|
|
178
|
+
}
|
|
179
|
+
function _draftBody(p) {
|
|
180
|
+
if (p.nextTool === "skyramp_analyze_test_health") {
|
|
181
|
+
return "Assess which existing tests may be broken by the changes in this diff.";
|
|
182
|
+
}
|
|
183
|
+
return `Draft multi-step scenarios simulating realistic user workflows:
|
|
194
184
|
- **Cross-resource data flow**: Foreign key relationships, parent→child creation, verification
|
|
195
185
|
- **Search/filter verification**: Create data, search for it using \`queryParams\`, verify results
|
|
196
186
|
- **Negative/error paths**: Invalid references → appropriate errors
|
|
@@ -208,23 +198,122 @@ response data verification, actual field names for chaining.
|
|
|
208
198
|
- \`"discount formula: total_amount = subtotal * (1 - discount_value / 100) — wrong if addition is used instead of subtraction"\`
|
|
209
199
|
- \`"items not recalculated after PATCH — total_amount stays at old value if collection update is ignored"\`
|
|
210
200
|
- \`"missing 404 guard on resource ID — returns 500 instead of 404 for unknown IDs"\`
|
|
211
|
-
This field is used at test generation time to compute exact assertion values. Leave it empty only if no specific formula or constraint applies
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
201
|
+
This field is used at test generation time to compute exact assertion values. Leave it empty only if no specific formula or constraint applies.`;
|
|
202
|
+
}
|
|
203
|
+
function _callToolTitle(p) {
|
|
204
|
+
return p.nextTool === "skyramp_analyze_test_health"
|
|
205
|
+
? "Call analyze test health"
|
|
206
|
+
: "Call recommend tests";
|
|
207
|
+
}
|
|
208
|
+
function _callToolBody(p) {
|
|
209
|
+
if (p.nextTool === "skyramp_analyze_test_health") {
|
|
210
|
+
return `Call \`skyramp_analyze_test_health\` with \`stateFile: "${p.stateFile ?? p.sessionId}"\``;
|
|
211
|
+
}
|
|
212
|
+
return `Call \`skyramp_recommend_tests\` with:
|
|
215
213
|
- \`stateFile: "${p.stateFile}"\`
|
|
216
|
-
- \`enrichedScenarios\`: (optional) JSON array of your Step
|
|
217
|
-
|
|
214
|
+
- \`enrichedScenarios\`: (optional) JSON array of your Step ${ANALYSIS_STEP_DRAFT} scenarios — see the tool's inputSchema for the exact shape. Your enriched scenarios override server-side ones with the same \`scenarioName\` and are prioritized in ranking. Omit if you drafted nothing in Step ${ANALYSIS_STEP_DRAFT}.`;
|
|
215
|
+
}
|
|
216
|
+
// ── PromptPlan declaration ─────────────────────────────────────────────────────
|
|
217
|
+
// Defines the diff-scope prompt structure: phases, steps, sub-steps, and conditions.
|
|
218
|
+
// Used to:
|
|
219
|
+
// 1. Derive the exported step label constants below.
|
|
220
|
+
// 2. Render the diff-scope prompt via _plan.render(p).
|
|
221
|
+
// 3. Enable developer review via _plan.preview(p).
|
|
222
|
+
const _plan = new PromptPlan()
|
|
223
|
+
.addPhase("analysis", "Your Task — Enrich & Recommend (PR-scoped)", {
|
|
224
|
+
headerLevel: "##",
|
|
225
|
+
stepFormat: "hash",
|
|
226
|
+
})
|
|
227
|
+
.step("READ_FILES", "Read the changed files and diff", _readFilesBody)
|
|
228
|
+
.subStep("RESOLVE_PATHS", _resolvePathsTitle, _resolvePathsBody)
|
|
229
|
+
.step("EXTRACT", _extractTitle, _extractBody)
|
|
230
|
+
.subStep("TRACE_CALLERS", "Trace callers of changed non-route files", _traceCallersBody, {
|
|
231
|
+
when: _traceCallersCondition,
|
|
232
|
+
whenDesc: "non-frontend unmatched backend files present in diff scope",
|
|
233
|
+
})
|
|
234
|
+
.step("DRAFT", _draftTitle, _draftBody)
|
|
235
|
+
.step("CALL_TOOL", _callToolTitle, _callToolBody)
|
|
236
|
+
.done();
|
|
237
|
+
// ── Exported step label constants ─────────────────────────────────────────────
|
|
238
|
+
/** "1" — Read source files / changed files */
|
|
239
|
+
export const ANALYSIS_STEP_READ_FILES = _plan.labels.READ_FILES; // "1"
|
|
240
|
+
/** "1.1" — Verify/build/validate endpoint path resolution table */
|
|
241
|
+
export const ANALYSIS_STEP_RESOLVE_PATHS = _plan.labels.RESOLVE_PATHS; // "1.1"
|
|
242
|
+
/** "2" — Extract or map endpoints */
|
|
243
|
+
export const ANALYSIS_STEP_EXTRACT = _plan.labels.EXTRACT; // "2"
|
|
244
|
+
/** "3" — Draft scenarios / present catalog / identify drift */
|
|
245
|
+
export const ANALYSIS_STEP_DRAFT = _plan.labels.DRAFT; // "3"
|
|
246
|
+
/** "4" — Call the downstream tool (recommend_tests / analyze_test_health) */
|
|
247
|
+
export const ANALYSIS_STEP_CALL_TOOL = _plan.labels.CALL_TOOL; // "4"
|
|
248
|
+
/**
|
|
249
|
+
* Returns the step label for the trace-callers sub-step given runtime params.
|
|
250
|
+
* Returns "2.1" when the step is active (non-frontend unmatched backend files
|
|
251
|
+
* present in diff scope), or null when the step is absent.
|
|
252
|
+
*
|
|
253
|
+
* Use in tests instead of hardcoding "2.1":
|
|
254
|
+
* const label = getStepTraceCallers(params);
|
|
255
|
+
* if (label) expect(output).toContain(`### Step ${label}: Trace callers`);
|
|
256
|
+
* else expect(output).not.toContain("Trace callers");
|
|
257
|
+
*/
|
|
258
|
+
export function getStepTraceCallers(p) {
|
|
259
|
+
return _plan.labelFor("TRACE_CALLERS", p);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Returns the full "### Step N.N: Title\nbody" block for the path resolution
|
|
263
|
+
* sub-step. Used by the full-repo scope path which builds its prompt manually
|
|
264
|
+
* rather than calling _plan.render().
|
|
265
|
+
*/
|
|
266
|
+
function buildPathResolutionTableSection(p) {
|
|
267
|
+
const label = ANALYSIS_STEP_RESOLVE_PATHS;
|
|
268
|
+
const title = _resolvePathsTitle(p);
|
|
269
|
+
const body = _resolvePathsBody(p);
|
|
270
|
+
return `### Step ${label}: ${title}\n${body}\n`;
|
|
271
|
+
}
|
|
272
|
+
function buildEnrichmentInstructions(p) {
|
|
273
|
+
const isDiffScope = p.analysisScope === AnalysisScope.CurrentBranchDiff;
|
|
274
|
+
const useHealthFlow = p.nextTool === "skyramp_analyze_test_health";
|
|
275
|
+
if (!isDiffScope) {
|
|
276
|
+
const nextStep = useHealthFlow
|
|
277
|
+
? `### Step ${ANALYSIS_STEP_DRAFT}: Identify tests at risk of drift
|
|
278
|
+
Call \`skyramp_analyze_test_health\` with \`stateFile: "${p.stateFile ?? p.sessionId}"\``
|
|
279
|
+
: `### Step ${ANALYSIS_STEP_DRAFT}: Present the catalog
|
|
280
|
+
The ranked test recommendation catalog is pre-built and shown below (after the separator line).
|
|
281
|
+
|
|
282
|
+
**Your only job is to present it.**
|
|
218
283
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
${callerTracingStep}
|
|
223
|
-
${criticalPatternStep}
|
|
284
|
+
1. Fill in every \`<…from source>\` placeholder using the field names, computed formulas, and auth details you found in Steps 1–2.
|
|
285
|
+
2. Output the completed catalog **exactly as formatted — grouped by test type (### E2E / ### UI / ### Integration / ### Contract)**. Do NOT restructure, reorder, rename sections, or generate a new format.
|
|
286
|
+
3. Do NOT call any Skyramp generation tools. The catalog shows ready-to-use tool calls that can be executed on demand.
|
|
224
287
|
|
|
225
|
-
${
|
|
288
|
+
**If** Steps 1–2 revealed additional scenarios the catalog does not cover (e.g. a computed formula or FK relationship that was missed), you may optionally call \`skyramp_recommend_tests\` with \`stateFile: "${p.stateFile ?? p.sessionId}"\` and \`enrichedScenarios\` to regenerate a more complete catalog — but only after presenting the current one.`;
|
|
289
|
+
const hasJavaFiles = p.candidateRouteFiles?.some((f) => /\.(java|kt)$/.test(f)) ?? false;
|
|
290
|
+
const routeFilesSection = p.candidateRouteFiles && p.candidateRouteFiles.length > 0
|
|
291
|
+
? `\nRoute/controller files found by static scan (read these to discover endpoints — the regex-based catalog below may be incomplete for your framework):\n${p.candidateRouteFiles.map((f) => `- ${f}`).join("\n")}\n`
|
|
292
|
+
: "";
|
|
293
|
+
const resolvePathsNote = p.routerMountContext.length
|
|
294
|
+
? `**Resolve nested paths** using your Step ${ANALYSIS_STEP_RESOLVE_PATHS} table — a router in the table with prefix \`/api/v1/products/{product_id}/reviews\` means every endpoint in that file lives under that full path.`
|
|
295
|
+
: `**Resolve full paths** using the prefixes you identified in Step ${ANALYSIS_STEP_READ_FILES} (e.g. Java Spring class-level \`@RequestMapping\` prefix + method-level path).`;
|
|
296
|
+
return `## Your Task — Fill in and Present the Catalog (full repo)
|
|
297
|
+
|
|
298
|
+
### Step ${ANALYSIS_STEP_READ_FILES}: Read key files
|
|
299
|
+
${routeFilesSection}Read the route/controller files above **and** model/schema files (Pydantic models, Zod schemas, DTOs) to find: required request body fields, computed response fields and formulas, auth middleware type, storage backend, and how sub-routers are mounted (cross-check against Router Mounting section above).
|
|
300
|
+
${hasJavaFiles ? JAVA_SPRING_NOTE : ""}
|
|
301
|
+
If the endpoint catalog below is missing endpoints visible in these files (e.g. from a framework the static scanner doesn't recognise), extract them now${useHealthFlow ? "." : " and include them in Step ${ANALYSIS_STEP_DRAFT}'s `enrichedScenarios`."}
|
|
302
|
+
|
|
303
|
+
${buildPathResolutionTableSection(p)}### Step ${ANALYSIS_STEP_EXTRACT}: Map cross-resource relationships and resolve endpoint paths
|
|
304
|
+
(Distinct from Step ${ANALYSIS_STEP_READ_FILES} — Step ${ANALYSIS_STEP_READ_FILES} reads individual schemas; Step ${ANALYSIS_STEP_EXTRACT} maps how endpoints relate to each other.)
|
|
305
|
+
For each endpoint: which POST creates resources consumed by other endpoints?
|
|
306
|
+
${resolvePathsNote}
|
|
307
|
+
For GET list endpoints: identify query params (\`limit\`, \`offset\`, \`order\`, \`orderBy\`) from framework annotations (FastAPI \`Query()\`, Express \`req.query\`, etc.).
|
|
308
|
+
|
|
309
|
+
${nextStep}`;
|
|
310
|
+
}
|
|
311
|
+
// Diff scope — delegate entirely to the plan.
|
|
312
|
+
return _plan.render(p);
|
|
226
313
|
}
|
|
227
314
|
export function buildAnalysisOutputText(p) {
|
|
315
|
+
// Centralize diff reading here — resolve content from file once so helpers don't each read it.
|
|
316
|
+
p = { ...p, diffContent: readDiffFile(p.diffFilePath) };
|
|
228
317
|
const isDiffScope = p.analysisScope === AnalysisScope.CurrentBranchDiff;
|
|
229
318
|
// Router mounting context is unique to this prompt; shown whenever mount context
|
|
230
319
|
// is available, regardless of whether a spec is configured.
|
|
@@ -232,18 +321,51 @@ export function buildAnalysisOutputText(p) {
|
|
|
232
321
|
? `
|
|
233
322
|
## Routing entry-point files
|
|
234
323
|
${p.routerFileContents?.length
|
|
235
|
-
? p.routerFileContents
|
|
236
|
-
.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
324
|
+
? p.routerFileContents
|
|
325
|
+
.map(({ file, content }) => `### \`${file}\`\n\`\`\`\n${content}\n\`\`\``)
|
|
326
|
+
.join("\n\n") +
|
|
327
|
+
(p.routerMountContext.length > (p.routerFileContents?.length ?? 0)
|
|
328
|
+
? `\n\nAdditional files (too large to inline — read manually if needed):\n` +
|
|
329
|
+
p.routerMountContext
|
|
330
|
+
.filter((f) => !(p.routerFileContents ?? []).some((r) => r.file === f))
|
|
331
|
+
.map((f) => `- \`${f}\``)
|
|
332
|
+
.join("\n")
|
|
333
|
+
: "")
|
|
334
|
+
: `Read these in Step ${ANALYSIS_STEP_RESOLVE_PATHS} to trace the full router/module hierarchy:\n` +
|
|
335
|
+
p.routerMountContext.map((f) => `- \`${f}\``).join("\n")}`
|
|
245
336
|
: "";
|
|
246
337
|
const enrichment = buildEnrichmentInstructions(p);
|
|
338
|
+
// LLM fallback: when heuristic scanning may have missed backend route files.
|
|
339
|
+
const scannerFallbackHasCatalog = p.scannedEndpoints.length > 0;
|
|
340
|
+
const scannerFallbackReason = scannerFallbackHasCatalog
|
|
341
|
+
? "The heuristic endpoint scanner may have missed route/controller files in this diff, even though endpoint data is present from another source"
|
|
342
|
+
: "The heuristic endpoint scanner found **0 endpoints**";
|
|
343
|
+
const scannerFallbackInstruction = scannerFallbackHasCatalog
|
|
344
|
+
? "Treat this as a supplemental gap check: merge only HTTP endpoints that are visibly missing from the existing catalog/spec data, and keep the existing catalog as the primary endpoint source."
|
|
345
|
+
: "Build a table of discovered endpoints (method, full path, source file) and use it as the authoritative endpoint list for all subsequent steps.";
|
|
346
|
+
const llmFallbackSection = (isDiffScope || p.scannedEndpoints.length === 0) &&
|
|
347
|
+
p.candidateRouteFiles &&
|
|
348
|
+
p.candidateRouteFiles.length > 0
|
|
349
|
+
? `
|
|
350
|
+
## Scanner Fallback — Manual Endpoint Discovery Required
|
|
351
|
+
|
|
352
|
+
${scannerFallbackReason}, and this repository contains ${p.candidateRouteFiles.length} file(s) that appear to define HTTP routes. The scanner likely failed due to multi-line definitions, framework-specific patterns, indirection the regex does not cover, or endpoint data being supplied by an OpenAPI spec instead of source scanning.
|
|
353
|
+
|
|
354
|
+
**Read the following controller/router files and identify all HTTP route registrations (method + path).** Include these discovered endpoints when executing the steps above.
|
|
355
|
+
|
|
356
|
+
${p.candidateRouteFiles.slice(0, 15).map((f) => `- \`${f}\``).join("\n")}
|
|
357
|
+
${p.candidateRouteFiles.length > 15 ? `\n_(${p.candidateRouteFiles.length - 15} more files not shown)_` : ""}
|
|
358
|
+
|
|
359
|
+
For each file, look for:
|
|
360
|
+
- Express/Fastify/Koa/Hapi: \`router.<method>('/path', ...)\` or \`app.<method>('/path', ...)\`
|
|
361
|
+
- FastAPI/Flask: \`@router.<method>('/path')\` or \`@app.route('/path', ...)\`
|
|
362
|
+
- Spring/NestJS: \`@GetMapping\`, \`@PostMapping\`, \`@Controller\`, etc.
|
|
363
|
+
- Go (Gin/Echo/Chi): \`r.GET("/path", ...)\`, \`r.Group("/prefix")\`
|
|
364
|
+
- GraphQL: schema/resolver artifacts are unsupported for REST test generation; do not invent REST endpoints from them
|
|
365
|
+
- Any other framework-specific route registration pattern
|
|
366
|
+
|
|
367
|
+
${scannerFallbackInstruction}`
|
|
368
|
+
: "";
|
|
247
369
|
return `# Repository Analysis
|
|
248
370
|
|
|
249
371
|
**Session ID**: \`${p.sessionId}\`
|
|
@@ -251,6 +373,7 @@ ${p.routerFileContents?.length
|
|
|
251
373
|
**Analysis Scope**: \`${p.analysisScope}\`
|
|
252
374
|
${isDiffScope ? `**Diff endpoints**: ${(p.parsedDiff?.newEndpoints.length ?? 0) + (p.parsedDiff?.modifiedEndpoints.length ?? 0) + (p.parsedDiff?.removedEndpoints?.length ?? 0)}` : `**Pre-scanned endpoints**: ${p.scannedEndpoints.length}`}
|
|
253
375
|
${routerSection}
|
|
376
|
+
${llmFallbackSection}
|
|
254
377
|
${enrichment}
|
|
255
378
|
|
|
256
379
|
**CRITICAL**: No .json/.md file creation. Prioritize cross-resource workflows.`;
|