/**
 * Modular section builders for the Drift Analysis prompt,
 * mirroring the recommendationSections.ts pattern.
 */
import { AUTH_MIDDLEWARE_PATTERNS_STR } from "../../utils/workspaceAuth.js";
export function buildActionDecisionTree() {
    return `<decision_rules>
**Before the numbered checks, apply the scope gate:** can this test's service or interface boundary actually reach the changed code? If the answer is clearly no — the test targets a definitively different service, a read-only replica, a completely separate microservice, or a different protocol — assign IGNORE and stop. A failed scope gate is terminal: it does not produce a signal subject to severity comparison below. When reachability is uncertain (dynamic serializers, inherited base models, conditional field exposure), use VERIFY instead of IGNORE.

**Before working through any individual check, do a single pass over the entire diff** to record all high-signal patterns. For each matched diff line write one line: \`{pattern type} — "{diff line}" — affects {endpoint/test}\`. Build this detection list first — it is your working artifact for all tests. An action with no entry in this list is unsupported.

High-signal patterns to look for in the pre-scan:
- Route removed or renamed: \`- @app.route\`, \`- router.get\`, \`- @GetMapping\`, or paired \`-\`/\`+\` on a route decorator
- Field type/removal: \`- field: int\`/\`+ field: string\`, \`- "responseField":\`
- Status/enum change: \`- return 200\`/\`+ return 201\`, \`- status: "active"\`/\`+ status: "enabled"\`
- Root wrapper change: \`- return Response({...})\`/\`+ return Response({"data":{...},"meta":{...}})\`
- New field in serializer/view/output formatter: \`+ "newField":\` or \`+ newField =\` inside a Response or serializer
- New field in model/migration only (no serializer): \`+ newField = Column(...)\`
- Auth added/removed/changed: \`+ @require_auth\`, \`- @require_auth\`, token-type change
- Scope narrowed: \`+ requireRole\`, \`+ raise PermissionError\`, \`+ if not is_owner\`, \`+ [x for x in xs if x.owner == caller_id]\`
- Behavioral: \`+ raise ValidationError\`/\`+ HTTPException(409)\` on new \`if\`, \`+ VALID_TRANSITIONS\`, sync→async (\`- return 200\`/\`+ return 202\`), formula change (\`- total = a - b\`/\`+ total = a + tax - b\`)

Then, for each test where the changed code *is* reachable, work through the individual checks below using your pre-built detection list. Collect **all** matching signals, then assign the single highest-severity action across all matches. Severity order (highest first): **DELETE > REGENERATE > UPDATE > VERIFY > IGNORE**. **Before assigning UPDATE, REGENERATE, or DELETE, quote the specific diff line(s) that triggered it in the rationale. If you cannot point to a diff line this test's endpoint can observe, the action is IGNORE or VERIFY, not UPDATE.**

Rules:
- Collect all signals; assign the highest-severity action across them. Include all matched signals in the rationale and all matching \`updateInstructions\` — a diff that renames a path AND adds a field requires both a URL patch and a new assertion.
- DELETE when all covered endpoints no longer exist; REGENERATE when they still exist but the root response wrapper changed and essentially every assertion is now invalid. In all other cases, prefer UPDATE.
- REGENERATE when the root response wrapper changed (flat→nested, new envelope object, root key renamed) and essentially every assertion is invalid — if you can describe the fix as patching N specific paths, it is UPDATE regardless of how many paths there are.
- Prefer IGNORE over VERIFY when all changed files are unrelated to the test's endpoint. Exception: if the diff touches a shared serializer, base model, or response formatter that the test's endpoint uses, prefer VERIFY even if no route file changed.
- ADD actions belong in the next step — complete this assessment with IGNORE / VERIFY / UPDATE / REGENERATE / DELETE only.

<examples>
<example>
Diff adds one field to a serializer and renames a URL path segment:
\`\`\`
- @app.route("/users/<id>/orders")
+ @app.route("/users/<id>/purchases")
+ "total_items": len(order.items)  # inside the serializer this test hits
\`\`\`
→ **UPDATE**: serializer signal confirmed in diff (total_items added to the same serializer) + path rename. Patch the URL and add an assertion for \`total_items\`.
</example>
<example>
Diff wraps the entire response in a new envelope object:
\`\`\`
- return Response({"id": ..., "status": ..., "items": [...]})
+ return Response({"data": {"id": ..., "status": ..., "items": [...]}, "meta": {"page": 1}})
\`\`\`
→ **REGENERATE**: root wrapper changed — every existing assertion (e.g. \`response["id"]\`) is broken. Rewrite from scratch.
</example>
<example>
Diff adds a field to a model/migration only — project uses explicit serializers (DRF, FastAPI, etc.):
\`\`\`
+ sort_order = Column(Integer, nullable=True)  # in models.py
\`\`\`
No serializer or field-inclusion list changed.
→ **VERIFY**: model-only signal in a project with explicit serializers — cannot confirm from the diff whether the field is included in the serializer's \`fields\` list and therefore exposed in API responses.
</example>
<example>
Diff adds a field to a schema/model only — project has no explicit serializer layer (ORM fields passed through directly):
\`\`\`
+ sort_order: {type: 'integer', nullable: true}  # in db/schema.js
\`\`\`
No serializer file changed. No \`fields =\` or field-exclusion list exists for this resource.
→ **UPDATE**: project has no serializer gate, so new schema columns are auto-exposed in API responses. Augment the test to assert the new field is present and round-trips correctly.
</example>
<example>
Diff changes a status code in the handler:
\`\`\`
- res.status(200).json(...)
+ res.status(201).json(...)
\`\`\`
→ **UPDATE**: the test asserts \`toBe(200)\` which now fails. Patch the status assertion.
</example>
<example>
Diff adds a role gate to a route the test covers:
\`\`\`
+ if (user.role !== "owner") {
+   return res.status(403).json({ error: "forbidden_role" });
+ }
\`\`\`
→ **UPDATE**: the test's existing token now gets 403. Send a token with sufficient role and add a 403 negative assertion for the restricted role. (Authorization scope narrowed — not an auth-mechanism change; the Auth/AuthZ and Behavioral Contract checks cover this.)
</example>
<example>
Diff adds a state-transition guard:
\`\`\`
+ const VALID_TRANSITIONS = { draft: ["review"], review: ["published"] };
+ if (!VALID_TRANSITIONS[currentStatus]?.includes(newStatus)) {
+   throw new HTTPException(409, "invalid_transition");
+ }
\`\`\`
→ **UPDATE**: an integration test that previously posted \`draft→published\` directly now gets 409. Chain through the valid states (draft→review→published) and add a 409 assertion for the direct skip.
</example>
</examples>
</decision_rules>`;
}
// Retained for backwards compatibility — no longer rendered in the prompt.
// Diff signals are now inlined into each individual check function.
/** @deprecated use the individual check functions; this function is no longer part of the prompt */
export function buildBreakingChangePatterns() {
    return "";
}
export function buildCheckEndpointExistence() {
    return `Does the endpoint the test targets still exist in the codebase?

Diff signals to look for:
- Route removed: \`- @app.route("/path")\`, \`- router.get("/path")\`, \`- @GetMapping("/path")\`
- Route renamed: paired \`-\` and \`+\` on a route decorator with a different path

Actions:
- ALL endpoints the test covers were removed → DELETE (the entire test file is obsolete)
- SOME methods removed but others remain → UPDATE (remove test functions for deleted methods, keep the rest)
- Endpoint renamed → UPDATE (path substitution; supply \`renamedEndpoints\`)`;
}
export function buildCheckResponseShape() {
    return `Has the request body or response structure changed in a way that breaks the test?

Diff signals to look for:
- Field type change: \`- field: int\` / \`+ field: string\`
- Required request body field added: \`+ required: [..., "newField"]\`
- Required query param added with no default
- Response field removed: \`- "responseField":\`
- Enum value changed: \`- status: "active"\` / \`+ status: "enabled"\`
- Status code changed: \`- return 200\` / \`+ return 201\`
- Root wrapper added: \`- return Response({...})\` / \`+ return Response({"data": {...}, "meta": {...}})\`

Actions:
- Type changes, new required fields, removed asserted fields, status/enum changes → UPDATE
- Root response wrapper changed and essentially every assertion is now invalid → REGENERATE

**UPDATE vs REGENERATE — the deciding question is whether the root response wrapper changed:**
- **REGENERATE** only when a new top-level envelope object wraps the entire payload or the root key is renamed so that essentially every existing assertion must change.
- **UPDATE** for everything else. If you can describe the fix as "patch these N assertion paths", it is UPDATE regardless of how many paths there are.
- When every assertion in the file is invalid, it is REGENERATE. When you can still patch individual paths, it is UPDATE.`;
}
export function buildCheckAuthAndAuthorization() {
    return `Has the authentication or authorization for this endpoint changed?

**Authentication mechanism**

Diff signals to look for: ${AUTH_MIDDLEWARE_PATTERNS_STR}. Also: \`@requiresRole\`/\`@Protected\`, \`validateToken\`/\`checkPermission\`/\`verifyHMAC\`, imports from auth/security packages, \`- @require_auth\` (removal), token type change (Bearer → Cookie).

Actions:
- Auth added where none existed → UPDATE (test would 401/403 on every request without the new credential)
- Auth method changed (e.g. bearer→cookie) → UPDATE (test sends the wrong credential type)
- Auth removed and test asserts a 401/403 response that will no longer fire → UPDATE
- Auth removed and test does not assert on auth responses → VERIFY (endpoint may now be intentionally public)

**Authorization scope** (same credential, narrower access)

Diff signals to look for: \`+ requireRole\`, \`+ requireCreateX\`/\`requireDeleteX\`, \`+ assert_*_scope\`, \`+ ALLOWED_ROLES.includes\`/\`ASSIGNABLE_ROLES\`, \`+ if not is_owner\`, \`+ raise PermissionError\`/\`HTTPException(403)\`, \`+ [x for x in xs if x.owner == caller_id]\`, new role-carrying request header (e.g. \`X-Workspace-Role\`).

Actions:
- Role or ownership gate added → UPDATE (test's existing token may now get 403; send a sufficient-role token and add a 403 negative assertion)
- Caller-identity filtering added → UPDATE (test's token now returns a subset; adjust expectations or use an admin-scope token)

Do NOT assign IGNORE just because the auth *mechanism* is unchanged — scope narrowing breaks a token-valid test.`;
}
export function buildCheckBehavioralContract() {
    return `Has the endpoint's BEHAVIOR changed while its response shape stayed the same? A test can break even when no field was added or removed.

Diff signals and actions:
- **Validation tightened**: \`+ raise ValidationError\`/\`+ throw new ValidationError\` gated on field value, \`+ Field(pattern=...)\`/\`ge=\`/\`le=\`/\`max_length=\` on an existing field → UPDATE (fix the payload to satisfy the new constraint; add the 4xx negative case)
- **New conditional rejection / state guard**: \`+ raise HTTPException(status_code=409)\`/\`+ res.status(409)\` inside a new \`if\`, \`+ VALID_TRANSITIONS\`, \`+ allowed_states = ...\` → UPDATE (chain through valid states; assert the rejection status for the now-illegal path)
- **Sync → async**: \`- return 200 result\` / \`+ return 202 {job_id}\` → UPDATE (assert \`202\` and the job/id field; remove old result-field assertions from the immediate response)
- **Computed-field formula changed**: \`- total = a - b\` / \`+ total = a + tax - b\` on an existing asserted field → UPDATE; describe the new formula in \`updateInstructions\` and provide the recomputed expected value where inputs are known from the diff
- **Behavior gated on a request header**: old shape returned only when a version header is sent; new shape is now the default → UPDATE (migrate assertions to the new default shape, or pin the old shape by sending the version header)

**Reachability for behavioral changes:** The service/interface scope gate still applies — if the test targets a completely different service or protocol, IGNORE. However, do NOT use the absence of a route or serializer file in the diff as grounds for IGNORE. Behavioral changes (new error branches, new validation, status-code changes) are observable from any test calling the same endpoint, even when the logic lives in an internal handler, middleware, or utility file rather than the route file itself.`;
}
export function buildCheckAssignAction() {
    return `Based on the above checks, choose the action (IGNORE / VERIFY / UPDATE / REGENERATE / DELETE) and provide a 1-2 sentence rationale.

**Every action requires a specific rationale — including IGNORE:**
- UPDATE / REGENERATE / DELETE: quote the specific diff line that triggered it.
- VERIFY: name the uncertain element (e.g. "shared serializer, cannot confirm field exposure without reading the file").
- IGNORE: name the specific reason the changed code cannot reach this test's endpoint (e.g. "diff only touches \`auth/session-service.js\` — this test targets \`/api/v1/orders\` which has no session dependency"). Generic "unrelated endpoint" or "service boundary" without a diff reference is not sufficient.

- If the Additive Fields check flagged a new field with serializer signal confirmed in the diff → action is UPDATE. If the Additive Fields check returned VERIFY (model/schema only, serializer not in diff) → action remains VERIFY.
- **Service/layer scope gate is terminal:** If the changed code is clearly not reachable through the service or base URL this test targets, assign IGNORE — this overrides all other checks. When reachability is uncertain, assign VERIFY rather than IGNORE.
- **Pre-commit verification — confirm all three before finalizing UPDATE/REGENERATE/DELETE:**
  1. You can quote a specific diff line this test's endpoint observes that triggered the action.
  2. The changed code is reachable through this test's service and base URL.
  3. For REGENERATE: every assertion in the file is invalid, not just some — if you can patch N paths, it is UPDATE.
  If any check fails, downgrade to VERIFY or IGNORE.
- **For user-written (external) tests** marked \`[external]\` in the test list:
  - UPDATE is permitted — targeted edits (fix renamed URL, add assertion for new field).
  - REGENERATE and DELETE are **not permitted** — assign those actions in your recommendations but \`skyramp_actions\` will surface them as report-only findings for the developer to act on. Do NOT attempt to rewrite or delete a user-authored test file.`;
}
export function buildCheckAdditiveFields() {
    return `Even if existing assertions still pass, new response fields on a covered endpoint may need a new assertion.

Diff signals to look for:
- \`+ "newField": ...\` inside a \`Response({...})\`, \`res.json({...})\`, serializer class, or output formatter → serializer signal confirmed
- \`+ newField = serializers.XXXField()\` in a serializer used by this endpoint → serializer signal confirmed
- \`+ newField = Column(...)\` or \`+ newField:\` in a model/migration only, with no serializer change → model-only signal

Actions:
- Serializer signal confirmed → UPDATE (add an assertion for the new field)
- Model/schema only, serializer not in diff, **project has explicit serializer layer** (a separate serializer file, Pydantic schema, field allowlist, or \`fields =\` definition controls what gets exposed) → VERIFY (cannot confirm from diff alone whether the field is included)
- Model/schema only, serializer not in diff, **project has no explicit serializer gate** (no field allowlist or exclusion for this resource; ORM/model fields are passed through directly) → UPDATE (field is auto-exposed in responses; augment the test)

To determine which applies: check whether the repo has a serializer file or field-inclusion list for this resource. If none exists in the diff context, prefer UPDATE. When genuinely uncertain, prefer VERIFY.`;
}
export function buildUpdateExecutionRules() {
    return `<execution_rules>
**UPDATE means edit-in-place — never use a generation tool for UPDATE**
UPDATE instructs you to modify the existing file using the Edit tool. Do NOT call \`skyramp_contract_test_generation\`, \`skyramp_integration_test_generation\`, or any other generation tool for an UPDATE action — generation tools create a new file and will overwrite or duplicate the existing one. Only use generation tools for REGENERATE actions.

When applying UPDATE actions to existing test files, follow these rules in addition to the drift-detected changes:

**Test file ordering**
Place mutation test functions (PATCH, PUT, POST) **before** any DELETE test function targeting the same resource. DELETE removes the resource — any mutation call after it will 404. When inserting a new mutation test, place it above the DELETE function and above the DELETE call in the \`if __name__ == "__main__"\` block (or equivalent runner entrypoint).

**Happy path first**
When adding a new HTTP method (PUT, PATCH, POST) to an existing test file, always include a 2xx success assertion first. Error-path tests (404, 422) may follow, but the happy path case is required.

**All test files for a resource**
When a diff adds a new HTTP method to a resource, UPDATE covers **all** existing test files for that resource — contract, integration, and UI. Apply UPDATE to every file the analyze tool reported for that resource path; do not stop after updating the first one.

**PATCH/PUT with child collections**
Child collection arrays (e.g. \`items\`, \`products\`, \`line_items\`) drive computed totals — a test that omits them cannot catch the most common mutation bugs. When the request/response includes a child collection:
1. Include the child array with at least one item containing the FK field (e.g. \`product_id\`) and a \`quantity\` field.
2. Assert each item's FK field and \`quantity\` match the sent values.
3. Assert the top-level computed total (e.g. \`total_amount\`) equals the expected math from the items.

**REGENERATE**
Call the appropriate generation tool to replace the existing test from scratch. Use the same filename so it overwrites the old file.

**DELETE**
Assign DELETE when ALL endpoints the test covers were removed from the codebase. \`skyramp_actions\` surfaces DELETE as a report-only finding — it does not delete the file automatically. The developer must delete the obsolete test file manually. If only SOME methods were removed, use UPDATE instead — remove the test functions for deleted methods and keep the rest.

**Test data isolation**
Never use hardcoded resource IDs (e.g. \`order_id=1\`) in any test step, including GET or DELETE steps. Always create required resources via prior POST steps and chain IDs dynamically. Use timestamp-based unique names for created resources (e.g. \`"Product-\${int(time.time())}"\`) to prevent collisions across test runs.

**Assertion quality**
When adding assertions, assert response *values* (field equals expected), not just field presence or status code — match the assertion depth the test already uses for other fields.

**Enhance assertions after UPDATE**
Call \`skyramp_enhance_assertions\` with \`testFile\` set to the absolute path of the test file you just updated, \`enhanceType: "maintenance"\`, and the matching \`testType\` based on the file you are editing:
- **Integration test file** (multi-step chained requests): call with \`testType: "integration"\`
- **Contract-provider test file** (single endpoint with \`beforeAll\`/\`afterAll\` setup, provider mode): call with \`testType: "contract"\`. Skip for consumer-mode contract tests.
- **UI test file** (imports \`@playwright/test\`, uses \`page.\` calls): call with \`testType: "ui"\`

Then apply every instruction returned by the tool to the test file.
</execution_rules>`;
}
export function buildDriftOutputChecklist(existingTestCount, newEndpointCount, stateFile) {
    const finalStep = `**Final step**
After completing all assessments above, call \`skyramp_actions\` with \`stateFile: "${stateFile ?? "<stateFile>"}"\` and a \`recommendations\` entry for every test assessed. For each entry include:
- \`testFile\` (absolute path as reported by the analysis tools)
- \`action\`
- \`rationale\` — one sentence naming the diff line or pattern that triggered the action
- \`updateInstructions\` — written for the downstream LLM that will edit the file; name the specific fields, types, paths, or constraints that must change. Example: "Added stock_count: int (ge=0, default=0) to ProductBase. Test hits GET /products — assert stock_count is present and non-negative." Vague instructions produce incomplete edits — be specific.
- \`renamedEndpoints\` (for path-rename updates)

For \`[external]\` tests: include them in \`recommendations[]\` with the assessed action. \`skyramp_actions\` will apply UPDATE edits and surface REGENERATE/DELETE as report-only findings — it will never rewrite or delete a user-authored file.

Do not edit or regenerate files before calling \`skyramp_actions\`. After calling it, follow its emitted instructions to apply UPDATE edits and run REGENERATE tool calls.`;
    const existingTestSection = `**Existing tests (${existingTestCount} total)**
For each existing test:
- **IGNORE/VERIFY tests**: one line each: \`{testFile} — IGNORE\` or \`{testFile} — VERIFY\`. Rationale omitted for brevity.
- **UPDATE/REGENERATE/DELETE tests**: output the full block:
\`\`\`
Test: {testFile}
Action: {UPDATE | REGENERATE | DELETE}
Rationale: {action} because {quoted diff signal}; affects {assertion/path}
\`\`\`
Focus your analysis on tests that need action — keep reasoning for unchanged tests to a single line.`;
    const newEndpointSection = newEndpointCount > 0
        ? `**New endpoints (${newEndpointCount} detected)**
For EACH new endpoint, output one line: \`{METHOD} {path} — {recommended test types} — {1 sentence rationale}\`
Do NOT include new endpoints in the \`recommendations[]\` passed to \`skyramp_actions\` — ADD is handled separately by the test generation tools.`
        : `**New endpoints** — none detected in this diff.`;
    const sections = [existingTestSection, newEndpointSection, finalStep];
    return `<output_format>
Complete ALL of the following:

${sections.join("\n\n")}
Be brief. Decide the action for each test and apply edits immediately. Limit reasoning for IGNORE'd tests to a single line.
</output_format>`;
}
