import { AUTH_MIDDLEWARE_PATTERNS_STR } from "../../utils/workspaceAuth.js";
import { resolveServiceDetailsRef } from "../../utils/utils.js";
import { logger } from "../../utils/logger.js";
import { TEST_CATEGORIES, } from "../../types/TestRecommendation.js";
import { buildScopeAssessmentSection } from "./scopeAssessment.js";
import { PromptPlan } from "./promptPlan.js";
import { buildTestPatternGuidelines, buildTestQualityCriteria, buildGenerationRules, MAX_CRITICAL_TESTS, } from "./recommendationSections.js";
import { TASK_ANALYZE_MAINTAIN, TESTBOT_TASK1_STEP_CODE_REVIEW, externalDedupKey, isAttackSurfaceSecurityBoundary, isOrdinaryDirectAuthBoundary, scenarioCoverageKey, taskStepRef, } from "./recommendationShared.js";
// ── Step body functions ───────────────────────────────────────────────────────
function _execCodeReviewBody(_ctx) {
    const codeReviewRef = taskStepRef(TASK_ANALYZE_MAINTAIN, TESTBOT_TASK1_STEP_CODE_REVIEW);
    return `If you already performed Code Review in ${codeReviewRef}, carry forward ALL \`<function_review>\` and \`<bug_found>\` blocks from that step.

If no prior \`<function_review>\` blocks exist (for example, standalone \`skyramp_analyze_changes\` usage), do the code review now: read all changed files and produce a \`<function_review>\` block for every changed function before proceeding.

The highest-severity \`<bug_found>\` block from this code review triggers a mandatory test in the GENERATE list:
- Category: \`bug_caught\`, priority: CRITICAL
- The promoted bug-catching test displaces the lowest-priority non-bug, non-protected GENERATE item. Preserve attack-surface \`security_boundary\` items for sibling destructive operations unless no other non-bug slot exists.
- **At most one promotion per run** — if multiple \`<bug_found>\` blocks exist, promote the HIGHEST severity flaw (break ties by the order they appear in the code review). Additional bug-catching tests go into ADDITIONAL with a note that they should be generated if budget allows.
- If the GENERATE list is empty (no pre-ranked items), the promoted bug_caught test becomes the GENERATE list`;
}
function _execCoverageBody(ctx) {
    return `${ctx.externalTestFilesList}For every GENERATE item below, check its endpoint path and test type against the Existing Tests list (further down in the prompt).
- **\`[external]\` tests**: If the endpoint is already covered by an \`[external]\` test of the same type → skip the resource entirely (do NOT create or update), **except for \`bug_caught\` and attack-surface \`security_boundary\` items**. A protected item may be skipped only if the external test directly asserts the same flaw/bypass and would fail/pass based on that same bug; same endpoint/resource coverage alone is not enough. Backfill from ADDITIONAL using the priority order below:
  1. **BUG-CATCHING TESTS FIRST (CRITICAL)**: If source code analysis revealed a bug, logic error, or incorrect formula (e.g. discount math adding instead of subtracting, off-by-one errors, missing validation), CREATE A TEST THAT EXPOSES IT. The test SHOULD FAIL — that's the point. Document the bug. Example: if discount formula is wrong, test with discount=20% and assert correct math. If no bug found, skip to #2.
  2. **PR-endpoint edge cases**: Look for integration test candidates covering error paths, boundary values, or alternative scenarios for the SAME endpoints changed in the PR diff. If no suitable candidate exists in ADDITIONAL, derive one from your source-code enrichment findings.
  3. **Same-resource other scenarios**: Other HTTP methods or flows on the same resource group touched by the PR.
  4. **Cross-resource workflows involving the PR endpoint**: Integration scenarios that include the PR's changed endpoint as one of the steps.
  5. **Unrelated endpoint coverage (last resort)**: Tests for endpoints with no connection to the PR diff, only when ALL options above have been exhausted.
  **Avoid backfilling with a test for a completely unrelated resource (e.g. \`POST /reviews\` when the PR only changes \`/orders\`) if any PR-endpoint edge-case integration test is feasible.**
- **Contract tests (\`[skyramp]\`)**: If an existing \`[skyramp]\` contract test already covers that resource path → UPDATE the existing test file instead of creating a new one. A new test case is a new test even if the file already exists — count it toward \`newTestsCreated\`.
- **Integration/scenario tests**: Always generate as a new file via the scenario pipeline, even if an existing integration test covers the same resource. A new multi-step scenario is a distinct test. Count it toward \`newTestsCreated\`.
- **UI tests**: Always generate as a new file. Count toward \`newTestsCreated\`.`;
}
function _execEnrichBody(ctx) {
    return `Using the source code you read in the Code Review step, quote relevant source in \`<source_evidence>\` blocks — include route handler signatures, request body schema fields, response shapes, and computed field formulas. Use these quotes to derive tool call parameters. Resolve:
- **Auth middleware** — check for known signals (${AUTH_MIDDLEWARE_PATTERNS_STR}). If any match, override \`authHeader\` and \`authScheme\` even if workspace.yml says authType: none. **If no known signal matches but the diff shows security-adjacent code** (decorators like \`@requiresRole\`/\`@Protected\`, function names like \`validateToken\`/\`checkPermission\`/\`verifyHMAC\`, or imports from auth/security packages), read the relevant source file to determine the actual auth scheme before proceeding. Auth handling for \`skyramp_integration_test_generation\` with \`scenarioFile\` is covered in the Tool Workflows section below.
- Business rules and formulas (e.g. total_cost = compute * rate + memory * rate)
- State transitions and domain constraints (e.g. budget cannot drop below current spend)
- Validation logic (field constraints, cross-field dependencies)
- Security boundaries not covered by the structural candidates below

For non-bug candidates from the pre-ranked list, evaluate against these 6 dimensions and assign priority:
  | Dimension | What to assess |
  | Production Safety | Guards a critical boundary (auth, unique constraint, cascade delete, data integrity, breaking migration)? → HIGH |
  | Bug-Finding Potential | Targets a known failure mode (race condition, data consistency, state transition, cascade effect)? → HIGH |
  | Mutation Side Effects | Does PUT/PATCH modify a collection of child items (line items, cart entries) and trigger recalculation (totals, counts, amounts)? → HIGH — this is the most common source of user-reported bugs |
  | User Journey Relevance | Reflects how real users interact (from traces, business flows, critical paths)? → HIGH or MEDIUM |
  | Coverage Gap | Addresses an area with zero existing test coverage? → bump up one tier |
  | Code Insight | Derived from actual implementation (spotted middleware pattern, N+1 risk, unique constraint)? → bump up one tier |

Quality gate — ask all three questions:
  1. "Would this test prevent a production incident?" → YES = HIGH priority regardless of other dimensions
  2. "Does this test exercise a real workflow or catch a real bug?" → YES = at least MEDIUM
  3. "Does this test cover a mutation that modifies child items and triggers total/amount recalculation?" → YES = HIGH priority, and prefer it for GENERATE over simple single-field update tests for the same endpoint

Assign category: bug_caught (for \`<bug_found>\` flaws from Step ${EXEC_STEP_CODE_REVIEW}) | ${TEST_CATEGORIES.join(" | ")}

${buildTestPatternGuidelines()}

**Bug-catching test insertion (from Step ${EXEC_STEP_CODE_REVIEW} findings):**
At most one \`<bug_found>\` flaw is promoted into GENERATE per run (the highest-severity one; break ties by source order). That test gets category \`bug_caught\`, CRITICAL priority, and displaces the lowest-ranked non-bug, non-protected GENERATE item. Preserve attack-surface \`security_boundary\` items for sibling destructive operations; they guard bypasses created when one destructive endpoint is newly protected but equivalent destructive siblings are not. No further justification needed — the flaw's existence IS the justification. Additional \`<bug_found>\` flaws beyond the first are placed in ADDITIONAL at highest priority.

INSERT a non-bug source-code-derived candidate into the ranked list **only if ALL three conditions are met**:
1. Priority is HIGH (it guards a critical boundary or would prevent a production incident)
2. It is specific to THIS codebase — derived from a concrete business rule, formula, or constraint found in the changed files (not a general pattern that applies to any API)
3. It is not already covered by a structural candidate in the list below

If these conditions are not met, add it to ADDITIONAL only — do NOT displace a pre-ranked GENERATE item.
**CRITICAL-tier items (category: new_endpoint) and attack-surface \`security_boundary\` items should never be displaced by non-bug candidates** — they test the actual endpoints introduced in this PR or sibling destructive endpoints that could bypass the changed auth boundary. However, bug-catching tests CAN displace them only after all lower-value non-bug slots are exhausted.

When a qualifying candidate is inserted: place it HIGH before MEDIUM before LOW; within the same priority, source-code-derived candidates go BEFORE structural ones. Re-number ranks after insertion. The top ${ctx.maxGen} ranked items become GENERATE candidates.

**Source-code validation gates:**
- **Cascade vs referential integrity**: If both a cascade-delete and a delete-blocked scenario appear for the same resource pair, keep only the one matching the source FK delete policy (ON DELETE CASCADE / cascade=True / onDelete: 'CASCADE' → keep cascade-delete; RESTRICT/PROTECT/no annotation → keep delete-blocked). Remove the inapplicable variant.
- **Unique constraints**: Unique-constraint scenarios (duplicate POST → 409) are pre-drafted for all resources. Confirm enforcement before keeping: SQL UNIQUE index, Mongoose unique: true, Prisma @unique, or explicit duplicate-check code. If the backend is Redis, schema-less, or has no explicit constraint in the changed files, move to ADDITIONAL with a note — do NOT generate.`;
}
function _execDiversityBody(_ctx) {
    return `**Bug-coverage gate (runs BEFORE dedup):**
Verify that the highest-severity \`<bug_found>\` flaw from Step ${EXEC_STEP_CODE_REVIEW} has exactly one GENERATE item with category \`bug_caught\` targeting it — meaning the test would FAIL on the current buggy code and PASS once the flaw is fixed. At most one promotion per run (per Step ${EXEC_STEP_CODE_REVIEW} cap). If the promoted flaw has no targeting \`bug_caught\` GENERATE item:
- Check ADDITIONAL for a matching test → promote it into the lowest-priority non-bug, non-CRITICAL GENERATE slot first (lowest category rank per \`crud > error_handling > workflow > data_validation > data_integrity > business_rule\`; preserve attack-surface \`security_boundary\` and internal \`new_endpoint\` items unless no lower-priority slot exists).
- If no ADDITIONAL candidate matches, create a new \`bug_caught\` test and insert it, displacing the lowest-priority non-bug, non-CRITICAL GENERATE item first; displace an attack-surface \`security_boundary\` or other CRITICAL item only when every GENERATE slot is higher priority.
A \`bug_caught\` test is NEVER considered a "duplicate" of a non-bug test during the dedup below.

Each GENERATE item must exercise a **distinct code path** — not just different input values on the same path.

For each pair of GENERATE items, ask: same HTTP method + path + step sequence + expected status? → DUPLICATE. Keep the richer item; replace the other with a test from a different path below. Move the displaced item to ADDITIONAL.

**Good diversity — aim for this mix across GENERATE slots:**
- **Happy-path**: create prerequisites → call the new endpoint → verify computed fields and child collections
- **Error-path**: trigger a distinct error status (404 for non-existent resource, 422 for invalid input, 400 for malformed request — whichever the source code handles)
- **State-variation**: same endpoint, different logic branch (empty array, remove instead of add, boundary value that triggers a guard)

Same step sequence with only payload differences (e.g. 10% vs 5% discount both returning 200) = same code path = duplicate. Different scenario names do not make duplicate tests distinct.`;
}
function _execExecuteBody(ctx) {
    return `Replace any scenario that pairs unrelated resources with one reflecting actual FK relationships in the codebase.
Use the field names and values from the \`<source_evidence>\` blocks you quoted in Step ${EXEC_STEP_ENRICH} to fill all tool call parameters. Prefer reusing Step ${EXEC_STEP_ENRICH} evidence when it already resolves a placeholder, but if a placeholder cannot be replaced with concrete values from files already read, you may read the specific schema, model, or handler file needed to resolve it. Assert response field values, not just status codes.

${buildTestQualityCriteria()}

${buildGenerationRules(ctx.isUIOnlyPR)}

**ADDITIONAL recommendations** are submitted via \`skyramp_submit_report\`. Refer to its schema for required fields. Only include recommendations that add distinct coverage beyond what was generated.

**Never mark a recommendation "blocked":** No OpenAPI spec → use source code for shapes. No traces → provide \`skyramp_start_trace_collection\` instructions. No backend trace → use the scenario pipeline.

**Critical-category minimum:** At least ${Math.min(MAX_CRITICAL_TESTS, ctx.maxGen)} of the ${ctx.maxGen} GENERATE items should be from HIGH-priority categories (security_boundary, business_rule, data_integrity, breaking_change). The pre-ranked plan below already prioritises this — only override if source-code enrichment reveals a higher-value candidate.

**Bug-catching test requirement (final gate):** Verify that the highest-severity \`<bug_found>\` flaw from Step ${EXEC_STEP_CODE_REVIEW} has a dedicated GENERATE item targeting it (test would FAIL on buggy code, PASS when fixed). At most one promotion per run. Step ${EXEC_STEP_DIVERSITY} should have already ensured this — if the promoted flaw still lacks a dedicated GENERATE test, replace the lowest-priority non-bug, non-protected GENERATE item NOW. Bug-catching tests take priority over ordinary structural coverage; preserve attack-surface \`security_boundary\` items for sibling destructive operations unless every other generated slot is higher value.`;
}
// ── PromptPlan declaration ─────────────────────────────────────────────────────
// Defines the execution-plan step structure. All five steps are non-conditional.
// startFrom: 0 produces labels "0", "1", "2", "3", "4".
//
// Labels:
//   CODE_REVIEW → "0"  (correctness analysis — dedicated bug detection step)
//   COVERAGE    → "1"  (external test coverage pre-check, always present)
//   ENRICH      → "2"  (source-code enrichment / parameter grounding)
//   DIVERSITY   → "3"  (diversity check, always present)
//   EXECUTE     → "4"  (execute plan, always present)
const _execPlan = new PromptPlan({ startFrom: 0 })
    .addPhase("execution", "", { headerLevel: "hidden", stepFormat: "bold" })
    .step("CODE_REVIEW", "Code Review — Correctness Analysis (before anything else)", _execCodeReviewBody)
    .step("COVERAGE", "External test coverage verification", _execCoverageBody)
    .step("ENRICH", "Parameter Grounding & Priority Assignment", _execEnrichBody)
    .step("DIVERSITY", (ctx) => `Diversity check (using enriched knowledge from Step ${ctx.enrichStepLabel})`, _execDiversityBody)
    .step("EXECUTE", "Execute merged plan in rank order", _execExecuteBody)
    .done();
// ── Exported step label constants ─────────────────────────────────────────────
/** "0" — Code Review: correctness analysis */
export const EXEC_STEP_CODE_REVIEW = _execPlan.labels.CODE_REVIEW; // "0"
/** "1" — External test coverage verification */
export const EXEC_STEP_COVERAGE = _execPlan.labels.COVERAGE; // "1"
/** "2" — Parameter grounding & priority assignment */
export const EXEC_STEP_ENRICH = _execPlan.labels.ENRICH; // "2"
/** "3" — Diversity check */
export const EXEC_STEP_DIVERSITY = _execPlan.labels.DIVERSITY; // "3"
/** "4" — Execute merged plan */
export const EXEC_STEP_EXECUTE = _execPlan.labels.EXECUTE; // "4"
const SERVICE_REFS = resolveServiceDetailsRef();
function prioritizeAttackSurfaceBundles(items) {
    const reordered = [];
    for (const item of items) {
        if (isAttackSurfaceSecurityBoundary(item.scenario)) {
            const firstDirectAuthIndex = reordered.findIndex(candidate => isOrdinaryDirectAuthBoundary(candidate.scenario));
            if (firstDirectAuthIndex >= 0) {
                reordered.splice(firstDirectAuthIndex, 0, item);
                continue;
            }
        }
        reordered.push(item);
    }
    return reordered;
}
export function buildExecutionPlan(scored, maxGen, topN, baseUrl, authHeaderValue, authSchemeSnippet, authTypeValue, seed, endpointCount, isUIOnlyPR, hasFrontendChanges = false, hasTraces = false, externalCoverage = new Set(), relevantExternalTestPaths = []) {
    const frontendUrl = "<frontend_url>";
    // Slot allocation:
    // - UI-only PR: all GENERATE slots are UI placeholders (no pre-ranked backend scenarios)
    // - Mixed PR:   last GENERATE slot is a UI placeholder; remaining slots are backend
    // - Backend-only PR: all GENERATE slots are backend scenarios
    const backendGenerateCount = isUIOnlyPR
        ? 0
        : hasFrontendChanges
            ? Math.max(0, maxGen - 1)
            : maxGen;
    // Filter out scenarios whose primary method + resource + test type is already covered by external tests.
    // Method-aware: an external test covering GET /orders won't block PUT /orders scenarios.
    // This is the programmatic complement to the prompt-level Step ${EXEC_STEP_COVERAGE} dedup instructions.
    const scoredAfterExternalDedup = externalCoverage.size > 0
        ? scored.filter((item) => {
            const key = externalDedupKey(item.scenario);
            if (externalCoverage.has(key)) {
                if (item.scenario.category === "bug_caught" || isAttackSurfaceSecurityBoundary(item.scenario)) {
                    logger.info(`External dedup: preserving "${item.scenario.scenarioName}" (${key}) — protected bug/attack-surface scenario requires semantic flaw coverage`);
                    return true;
                }
                logger.info(`External dedup: skipping "${item.scenario.scenarioName}" (${key}) — covered by external test`);
                return false;
            }
            return true;
        })
        : scored;
    const slotOrderedItems = prioritizeAttackSurfaceBundles(scoredAfterExternalDedup);
    const generateItems = slotOrderedItems.slice(0, Math.min(backendGenerateCount, slotOrderedItems.length));
    const rawAdditionalItems = slotOrderedItems.slice(backendGenerateCount, topN);
    // Filter additional items whose primary resource + test type already appear in GENERATE
    const generatedCoverage = new Set(generateItems.map((item) => scenarioCoverageKey(item.scenario)));
    const additionalItems = rawAdditionalItems.filter((item) => !generatedCoverage.has(scenarioCoverageKey(item.scenario)));
    const hasWorkspaceAuthType = !!authTypeValue && authTypeValue !== "none";
    // For skyramp_integration_test_generation with scenarioFile:
    // - If workspace has authType set: omit auth entirely — workspace handles Bearer prefix.
    // - If no authType: pass authHeader only (no authScheme).
    const authHeaderOnlyRef = hasWorkspaceAuthType
        ? ""
        : authHeaderValue
            ? `, authHeader: "${authHeaderValue}"`
            : `, authHeader: <check OpenAPI securitySchemes or auth middleware; "" if confirmed unauthenticated>`;
    // UI-only: all GENERATE slots are UI test placeholders (one per changed component/flow)
    const uiGenerateBlocks = isUIOnlyPR
        ? Array.from({ length: maxGen }, (_, i) => {
            const rank = i + 1;
            const zipPath = `<repositoryPath>/.skyramp/ui_test_${rank}_trace.zip`;
            return hasTraces
                ? `**#${rank} — GENERATE** | ui | workflow | new\n` +
                    `Scenario: ui-test-from-trace-${rank} (rename from the actual changed component/flow)\n` +
                    `Validates: UI interactions for a changed frontend component or flow.\n\n` +
                    `**Tool**: \`skyramp_ui_test_generation({ playwrightInput: "<discovered_trace_file_path>", outputDir: "<frontend_output_dir>" })\` — set \`outputDir\` to ${SERVICE_REFS.frontendTestDirRef}`
                : `**#${rank} — GENERATE** | ui | workflow | new\n` +
                    `Scenario: ui-test-for-changed-component-${rank} (rename from the actual changed component/flow)\n` +
                    `Validates: UI interactions for changed frontend component/flow ${rank}.\n\n` +
                    `**Tool workflow:**\n` +
                    `  1. \`browser_navigate({ url: "${frontendUrl}" })\`\n` +
                    `  2. Interact with the changed component (read the diff to identify which component changed and what interactions it supports)\n` +
                    `  3. \`browser_snapshot()\` after each key interaction\n` +
                    `  4. \`skyramp_export_zip({ outputPath: "${zipPath}" })\` — absolute path\n` +
                    `  5. \`skyramp_ui_test_generation({ playwrightInput: "${zipPath}", outputDir: "<frontend_output_dir>" })\` — set \`outputDir\` to ${SERVICE_REFS.frontendTestDirRef}\n\n` +
                    `Each item must target a distinct changed component or user flow.\n\n` +
                    `**Empty-page fallback**: If the page has no test data (empty state, no items to interact with), do NOT skip test generation. Instead: (a) write the Playwright test code directly from your code analysis — you already know the component structure, event handlers, and expected behavior from reading the diff; (b) use \`page.evaluate()\` or API calls to seed test data if possible; (c) at minimum, test that the UI renders correctly and that interactive elements exist with correct attributes. An empty page is NEVER a reason to produce zero tests.`;
        }).join("\n\n")
        : "";
    // Mixed PR: reserve the last GENERATE slot for a UI test for the changed frontend components.
    // Guard: skip when maxGen=0 (caller explicitly requested no generation)
    const uiRank = generateItems.length + 1;
    const uiPlaceholderBlock = hasFrontendChanges && !isUIOnlyPR && maxGen > 0
        ? hasTraces
            ? `**#${uiRank} — GENERATE** | ui | workflow | new\n` +
                `Scenario: ui-test-for-changed-components (rename from the actual changed component/flow)\n` +
                `Validates: UI interactions for the changed frontend components in this PR.\n\n` +
                `**Tool**: \`skyramp_ui_test_generation({ playwrightInput: "<discovered_trace_file_path>", outputDir: "<frontend_output_dir>" })\` — set \`outputDir\` to ${SERVICE_REFS.frontendTestDirRef}`
            : `**#${uiRank} — GENERATE** | ui | workflow | new\n` +
                `Scenario: ui-test-for-changed-components (rename from the actual changed component/flow)\n` +
                `Validates: UI interactions for the changed frontend components in this PR.\n\n` +
                `**Tool workflow:**\n` +
                `  1. \`browser_navigate({ url: "${frontendUrl}" })\`\n` +
                `  2. Interact with the changed component (read the diff to identify which component changed and what interactions it supports)\n` +
                `  3. \`browser_snapshot()\` after each key interaction\n` +
                `  4. \`skyramp_export_zip({ outputPath: "<repositoryPath>/.skyramp/ui_mixed_pr_trace.zip" })\` — absolute path\n` +
                `  5. \`skyramp_ui_test_generation({ playwrightInput: "<repositoryPath>/.skyramp/ui_mixed_pr_trace.zip", outputDir: "<frontend_output_dir>" })\` — set \`outputDir\` to ${SERVICE_REFS.frontendTestDirRef}\n\n` +
                `Derive scenario name and steps from the actual changed frontend files.\n\n` +
                `**Empty-page fallback**: If the page has no test data (empty state), write the Playwright test code directly from code analysis. An empty page is NEVER a reason to skip UI test generation.`
        : "";
    const generateBlocks = generateItems
        .map((item, i) => {
        const rank = i + 1;
        const s = item.scenario;
        const testType = s.testType ?? (s.steps.length === 1 ? "contract" : "integration");
        if (testType === "contract") {
            const step = s.steps[0];
            const endpointURL = `${baseUrl}${step.path}`;
            const isBodyMethod = ["POST", "PUT", "PATCH"].includes(step.method);
            const requestBodyData = step.requestBody && Object.keys(step.requestBody).length > 0
                ? `\n  Request body: ${JSON.stringify(step.requestBody)} (pass as JSON string in tool call, NOT as object)`
                : isBodyMethod
                    ? `\n  Request body: <derive from source code schemas>`
                    : "";
            const authContext = authHeaderValue
                ? `\n  authHeader: "${authHeaderValue}"${authSchemeSnippet}`
                : `\n  authHeader: <resolve from workspace or OpenAPI securitySchemes>; authScheme: <if Authorization>`;
            return (`**#${rank} — GENERATE** | ${testType} | ${s.category} | ${item.novelty}\n` +
                `${step.method} ${step.path} → ${step.expectedStatusCode}\n` +
                `Validates: ${s.description}\n\n` +
                `**Context for generation**:\n` +
                `  Endpoint URL: ${endpointURL}${requestBodyData}${authContext}\n\n` +
                `**Tool**: skyramp_contract_test_generation (see tool description for parameter structure)`);
        }
        else {
            // integration / e2e / ui — multi-step scenario pipeline
            const stepLines = s.steps
                .map((st) => {
                const chains = st.chainsFrom
                    ? ` (chains: ${Array.isArray(st.chainsFrom)
                        ? st.chainsFrom
                            .map((c) => `${c.sourceField} from step ${c.sourceStep}`)
                            .join(", ")
                        : `${st.chainsFrom.sourceField} from step ${st.chainsFrom.sourceStep}`})`
                    : "";
                const bodyHint = st.bodyMustInclude?.length
                    ? ` [required fields: ${st.bodyMustInclude.join(", ")}]`
                    : "";
                const responseHint = st.expectedResponseFields?.length
                    ? ` [assert: ${st.expectedResponseFields.join(", ")}]`
                    : "";
                const bodyData = st.requestBody && Object.keys(st.requestBody).length > 0
                    ? ` [use requestBody: ${JSON.stringify(st.requestBody)} — pass as JSON string in tool call]`
                    : "";
                return `  ${st.order}. ${st.method} ${st.path} → ${st.expectedStatusCode}: ${st.description}${chains}${bodyHint}${bodyData}${responseHint}`;
            })
                .join("\n");
            let destinationHost = "localhost";
            try {
                const parsed = new URL(baseUrl);
                destinationHost = parsed.hostname;
            }
            catch {
                /* use localhost as fallback */
            }
            const authContext = authHeaderValue
                ? `authHeader: "${authHeaderValue}"${authSchemeSnippet}`
                : "authHeader: <resolve from workspace or OpenAPI securitySchemes>; authScheme: <if Authorization>";
            const prereqNote = s.category === "new_endpoint"
                ? `\n**Prerequisite discovery**: Check for FK fields (product_id, user_id, order_id) in the endpoint's request body. If found, prepend a step to create that prerequisite resource first, then chain its primary key field into the dependent step using template variable syntax. Check the actual field name from the response body (\`id\`, \`uuid\`, \`_id\`, etc.), response header (\`Location\`), or cookie — do not assume \`id\`.`
                : "";
            const bugLine = s.bugCatchingTarget
                ? `**Bug to catch**: ${s.bugCatchingTarget}\n`
                : "";
            const fromSource = s.source === "agent-enriched"
                ? "Auth: OpenAPI securitySchemes or auth middleware"
                : "Request/response shapes: source code schemas; Auth: OpenAPI securitySchemes or auth middleware";
            return (`**#${rank} — GENERATE** | ${testType} | ${s.category} | ${item.novelty}\n` +
                `Scenario: ${s.scenarioName} (${s.steps.length} steps)\n` +
                bugLine +
                `${stepLines}\n\n` +
                `**Context for generation**:\n` +
                `  - Destination: ${destinationHost}\n` +
                `  - Base URL: ${baseUrl}\n` +
                `  - ${authContext}\n` +
                `  - From source: ${fromSource}\n\n` +
                `**Tool pipeline**:\n` +
                `  1. skyramp_batch_scenario_test_generation (see tool description for parameter structure)\n` +
                `  2. skyramp_integration_test_generation with returned scenarioFile${authHeaderOnlyRef ? ` and ${authHeaderOnlyRef.replace(/^,\s*/, "")}` : ""}\n` +
                `  **Note**: requestBody/responseBody must be JSON strings (e.g. "{\\"field\\":\\"value\\"}"), not objects.` +
                prereqNote);
        }
    })
        .join("\n\n");
    // Pre-ranked backend additional candidates — the LLM picks from these per its Budget Plan.
    const additionalLines = additionalItems
        .map((item, i) => {
        const rank = maxGen + i + 1;
        const s = item.scenario;
        const testType = s.testType ?? (s.steps.length === 1 ? "contract" : "integration");
        const target = s.steps.length === 1
            ? `${s.steps[0].method} ${s.steps[0].path} → ${s.steps[0].expectedStatusCode}`
            : `Scenario: ${s.scenarioName} (${s.steps.map((st) => `${st.method} ${st.path}`).join(" → ")})`;
        return `#${rank} [ADDITIONAL] | ${testType} | ${s.category} | ${item.novelty}\n  ${target}\n  Validates: ${s.description}`;
    })
        .join("\n\n");
    // Phase C D-1.a: UI grounding guidance — fires whenever the PR has
    // frontend changes (UI-only OR mixed). Tells the agent what to put in the
    // `reasoning` field for UI test entries. This disambiguates the "Fill in
    // placeholders from source code, then display verbatim" header that
    // analyzeChangesTool wraps around this prompt: the catalog's STRUCTURE is
    // frozen, but the `reasoning` CONTENT for UI entries should be blueprint-
    // grounded using concrete elements the agent captured via browser_blueprint.
    const uiGroundingGuidance = hasFrontendChanges ? `
**UI recommendation grounding — applies to \`testType: "ui"\` entries.** The \`reasoning\` field for \`testType: "ui"\` entries MUST contain at least three of {\`role\`, \`accessibleName\`, \`testId\`, \`stableId\`, \`logicalName\`} cited in key=value form. Entries that omit the tuple are not valid output for UI test types. The agent should call \`browser_blueprint\` on affected pages (if it hasn't already) and use the captured data.

**Field stability note for consumers:** \`testId\` and \`stableId\` are stable identifiers (from \`data-testid\` and unique \`id\` attributes respectively) — code-generation consumers can key off them. \`role\` and \`accessibleName\` are derived from ARIA and survive DOM reshuffles. \`logicalName\` is a **display handle, not a stable identifier** — it's derived from role + accessibleName + section context and drifts when the accessibleName text changes. Cite \`logicalName\` for readability in the \`reasoning\` field, but downstream consumers (test generators, scoping tools) should NOT key off it. Prefer \`testId\` > \`stableId\` > \`role + accessibleName\` > \`fingerprint\` for identity.

**Format for singular elements:**
> role=<role>, accessibleName="<Accessible Name>", testId=<test-id-or-null>, stableId=<id-or-null>, logicalName=<logical_name>

**Format for repeating elements (table rows, list items):** include \`contextText\` values from the row when the recommendation targets a specific row:
> role=<role>, accessibleName="<template>", testId=<null>, stableId=<null>, logicalName=<name>, contextText=["customer@example.com", "$129.99", "Pending"]

**Example (Edit Order form's Save button on /orders/{id}):**
> role=button, accessibleName="Save changes", testId=null, stableId=null, logicalName=save_changes_btn — verifies boundary-value clamping on discount_percent (0..100)

**Example (new-order-row recommendation on /orders):**
> role=button, accessibleName="View details for order 13", testId=null, stableId=null, logicalName=view_details_for_order_btn, contextText=["customer@example.com", "$129.99", "Pending"] — verifies new order row renders with correct customer + total + status

**Validates line — applies to \`testType: "ui"\` entries.** The \`Validates:\` line for UI entries should describe an observable behavior the test verifies — what changes on the page after the action, or what state the user can see. Ground this description in the captured blueprint when possible. Reference structural facts (an element appears, a count changes, a status text updates, a URL transitions) rather than implementation language (component names, props, internal state). The line should be readable to someone who has not seen the source diff.

**Scope clarification:** this grounding format applies **only** to \`testType: "ui"\` entries. Contract, integration, e2e, batch-scenario \`reasoning\` and \`Validates:\` fields use their existing conventions (endpoint paths, schemas, fixture chains) — do NOT reformat those. The "Fill in placeholders, then display verbatim" rule above refers to the CATALOG STRUCTURE (sections, ordering, test types); UI entries' \`reasoning\` and \`Validates:\` CONTENT follows this grounding format.

**If blueprint data isn't available** — agent skipped pre-scan, app unreachable, \`BlueprintInvariantError\`, or no candidate page covers the changed component — UI entries may fall back to source-grounded reasoning. Each such entry MUST be flagged with a leading \`[no-blueprint-data]\` marker in the \`reasoning\` field, and the failure mode must be logged in \`issuesFound\` with \`info\` severity naming the entry. Do NOT silently produce ungrounded reasoning without the marker.
` : "";
    // UI/E2E guidance — the LLM adds as many as its Budget Plan calls for.
    // Note: if a UI test already occupies a GENERATE slot (uiPlaceholderBlock), that slot
    // satisfies the UI generate count — do not add it again in ADDITIONAL.
    // Only include when there are actually frontend changes (not backend-only PRs).
    const uiGuidance = !isUIOnlyPR && hasFrontendChanges
        ? `
**UI/E2E tests (add per your Budget Plan):** If your Budget Plan requires UI/E2E items beyond what is already in your GENERATE list, append an [ADDITIONAL] entry for each. If a UI test already occupies a GENERATE slot above, that slot satisfies your UI/E2E generate count — do NOT add it again to ADDITIONAL. Tool workflow for each new item:
- **E2E**: ${hasTraces ? "Use discovered trace/recording files with `skyramp_e2e_test_generation`." : "Add to additionalRecommendations with a note that both a backend API trace (`skyramp_start_trace_collection` / `skyramp_stop_trace_collection`) and a browser Playwright recording must be collected in a live environment first. Do NOT attempt `skyramp_e2e_test_generation` without both traces present."}
- **UI**: ${hasTraces ? "Use an existing Playwright `.zip` trace with `skyramp_ui_test_generation`." : `Record a trace using \`browser_navigate\` + \`browser_snapshot\` + \`skyramp_export_zip\`, then call \`skyramp_ui_test_generation({ playwrightInput: "<zip_path>", outputDir: "<frontend_output_dir>" })\` — set \`outputDir\` to ${SERVICE_REFS.frontendTestDirRef}.`}
Derive scenario names and steps from the actual changed frontend files. If your Budget Plan calls for 0% UI/E2E, omit this entirely.`
        : "";
    const supplementNote = `\n**If your Budget Plan total exceeds the pre-ranked items listed above:** draft additional tests from source-code enrichment (Step ${EXEC_STEP_ENRICH}). For each new or changed endpoint, identify boundary or variation scenarios — formula parameters, search/filter constraints, required field validation. Only after exhausting PR-specific scenarios, add generic patterns (auth boundary → 401, non-existent ID → 404). Do NOT supplement with tests whose endpoint + test type match a GENERATE item.`;
    // ── PR / branch-diff mode: execution plan ────────────────────────────────
    const externalTestFilesList = relevantExternalTestPaths.length > 0
        ? `**Read these external test files first** (paths are relative to the \`repositoryPath\` you passed to \`skyramp_analyze_changes\` — prepend it to get the absolute path). Determine exactly which HTTP methods + paths each one covers. This is the definitive source of truth for external coverage:\n${relevantExternalTestPaths.map((p) => `- \`${p}\``).join("\n")}\n\n`
        : "";
    const _ctx = {
        externalTestFilesList,
        maxGen,
        isUIOnlyPR,
        enrichStepLabel: EXEC_STEP_ENRICH,
    };
    return `## Execution Plan
Seed: ${seed} | Endpoints: ${endpointCount} | Max: ${maxGen} generate + up to ${Math.max(topN - maxGen, 0)} additional (your Budget Plan determines the exact count)

${buildScopeAssessmentSection(topN, maxGen, isUIOnlyPR, isUIOnlyPR ? 100 : hasFrontendChanges ? undefined : 0, hasFrontendChanges)}

${_execPlan.render(_ctx)}

### GENERATE (after completing Steps ${EXEC_STEP_CODE_REVIEW}–${EXEC_STEP_EXECUTE} above) — generate exactly these items in order; add variations to ADDITIONAL instead. If Step ${EXEC_STEP_COVERAGE} converts an item to UPDATE, backfill from ADDITIONAL (priority order in Step ${EXEC_STEP_COVERAGE})

${isUIOnlyPR
        ? uiGenerateBlocks ||
            "  (no UI generate items — derive scenarios from changed frontend files)"
        : [generateBlocks, uiPlaceholderBlock].filter(Boolean).join("\n\n") ||
            "  (no pre-ranked generate items — draft your own based on endpoint analysis)"}

### ADDITIONAL (list in additionalRecommendations in this order after Step ${EXEC_STEP_ENRICH} insertion)

${additionalLines || "  (none pre-ranked)"}
${uiGroundingGuidance}
${uiGuidance}
${supplementNote}

**Honor your Budget Plan: produce exactly the total you committed to (GENERATE + ADDITIONAL). No fewer, no padding with low-value tests.**

## Recommendation Stability
- **Carry forward** previous additionalRecommendations that still apply — match by scenarioName (multi-step) or endpoint (single-endpoint). Re-derive category and priority from test content.
- **Only drop** a previous recommendation if its target endpoint was removed, its business logic changed, or it is now covered by a generated test.
- **Only add** new recommendations for code paths introduced since the last run.`;
}
