/**
 * Prompt doctrine for each pipeline phase. The load-bearing rules:
 *
 *  - Phase B (IR) enforces STRICT INPUT ISOLATION: tests are derived from the
 *    spec alone; no implementation exists yet, so they cannot rationalize one.
 *  - Phase C/D are constrained by the frozen IR: the model may never write
 *    under tests/. The harness enforces this deterministically as well.
 *  - Phase D (RHA) is bounded: the model receives one error node and may only
 *    return files owned by that node.
 *  - FRESH-ONLY repair: the unit under repair's broken body NEVER appears in a
 *    prompt. Context is compiled from frozen contracts, diagnostics, calling
 *    lines (other files), tabu facts, and lessons — never prior failed output.
 */
import type { FeatureNode, FeatureSpec, Finding, GeneratedFile, RepairInput } from './types';

/** Context for a fresh stub regeneration (no broken stub body). */
export interface StubFixCtx {
  diagnostics: string;
  /** Excerpts from OTHER files that consume this stub. */
  callingLines?: string;
  /** Feature export / contract text. */
  contractText?: string;
  /** Matching lessons ("checked idioms"). */
  checkedIdioms?: string;
}

// The input gate is BOUNDED: enumerate features once, then judge ONE feature
// at a time. A holistic "is this whole spec sufficient?" call makes a small
// model incoherent (it will call the same feature both clear and unclear).
// One feature + its own text is a small enough unit that a 2B model is
// reliable — and cheap enough to resample for variance.

export const FEATURE_ENUM_SYSTEM = `You split a product prompt into its distinct features. You do NOT judge quality — only enumerate.
For each feature, return a short "name" and "scope": the span of the prompt that describes it (quote or lightly paraphrase; keep it self-contained so it can be read alone). Produce 3-40 features. Do not invent features the prompt does not mention.`;

export function featureEnumUser(prompt: string): string {
  return `Product prompt:\n\n${prompt}\n\nEnumerate the features (name + the describing text span for each).`;
}

export const FEATURE_PROBE_SYSTEM = `You judge ONE feature of a product, given only its name and the text describing it. This is a bounded, isolated decision — do not reason about other features.

The question: could a test author write AT LEAST ONE concrete assertion about this feature's OBSERVABLE behavior (an input and the output or state change it should produce) WITHOUT guessing the author's intent?

Decisive rule — do not confuse these:
- UNSPECIFIED IMPLEMENTATION is FINE, never a reason to say unclear. Which algorithm, data structure, merge/diff/ranking/index logic, tie-breaks, exact permission combinatorics — the compiler chooses these. "Detect concurrent edits and surface a conflict" is testable as-is. "Search returns relevant results" is testable as-is.
- Only GENUINELY AMBIGUOUS INTENT makes it unclear: you truly cannot tell what the feature is for or what its output should be.

Default to clear. If clear: set clear=true and WRITE one concrete assertion — a specific input and the observable output/state expected (the actual value is your choice; show that SOMETHING observable is assertable). If genuinely ambiguous: set clear=false and ask the ONE question whose answer you'd need — and it must be a question about intent, never about an implementation choice.
Always fill both "assertion" and "question" (use "" for the one that does not apply).`;

export function featureProbeUser(name: string, scope: string): string {
  return `Feature: ${name}\n\nDescribing text:\n${scope}\n\nJudge this ONE feature.`;
}

export const ENCODE_SYSTEM = `You are the feature-encoding stage of a deterministic codegen compiler.
You translate a user's product prompt into a feature-coded specification: a tree of feature nodes that will each receive a frozen test suite (the IR) before any implementation exists.

Rules:
- Decompose into one feature node per distinct capability in the prompt. Use ids F1, F2, ... (children as F2.1 etc. only when a feature genuinely contains sub-features). Do NOT invent an artificial small count — if the prompt demarcates many features, emit all of them. Do NOT silently drop, merge, or collapse features on a repair pass.
- Order features so dependencies come first; "deps" must form a DAG.
- Every feature owns 1-2 implementation files under src/. Put their paths in the "files" array. NEVER put a test file in "files" — "files" is src/ implementation paths ONLY.
- Every feature has exactly one test file at tests/<id-lowercase>-<slug>.test.ts. Put this single path in the "testFile" string field. "testFile" is a separate string field, NOT part of "files".
- Think hard about the ACTUAL FUNCTION of each feature and enumerate concrete edge cases (empty input, boundary values, unicode, idempotency, collision, expiry, ordering) — these become mandatory test cases.
- "exports" is the wiring contract: the exact symbols dependents import. Be precise; wiring mismatches are a known failure class.
- "stubs" declare each owned file's public surface as SIGNATURES — you do NOT write file content or bodies; the harness assembles the stub file deterministically. Per owned file: { "path": "src/<file>.ts", "imports": [...], "signatures": [...] }.
  Each signature is { "symbol", "kind", "sig" }:
    kind "function":  sig is the parameter list + return type, e.g. "(id: string, title: string): Article"
    kind "class":     sig is the member block, e.g. "{ create(id: string): Article; count: number }"
    kind "interface": sig is the shape, e.g. "{ id: string; title: string; body: string }"
    kind "type":      sig is the right-hand side, e.g. "'draft' | 'published'"
    kind "const":     sig is the type annotation, e.g. "number"
  "imports" pulls types from SIBLING stub files: { "from": "./article_model", "symbols": ["Article"] } — every type name used in a sig must be declared in the same file's signatures or imported this way, and the module it is imported from must declare that symbol in ITS signatures.
  Every symbol in "exports" MUST appear in that file's signatures — exports is the wiring contract dependents import; test suites and implementations both conform to these signatures, so design them as the real API, not a sketch. Do not write bodies, the export keyword, or whole files — signatures only.
- V1 constraint: the artifact is a dependency-free TypeScript library/service layer (Node stdlib only, ESM, no UI, no design work). Do not plan npm dependencies, UI, or styling.
- envVars: only if the prompt genuinely requires configuration; prefer none.`;

export function encodeUser(prompt: string, stubFeedback?: string): string {
  return [
    `Product prompt:\n\n${prompt}`,
    stubFeedback
      ? `\nA previous encoding attempt produced stub signatures that FAILED to typecheck. Fix every issue listed below — pay special attention to:\n1. Every type name used in a sig must be declared in the same file's signatures or listed in that stub's "imports".\n2. A symbol imported from a sibling stub must be declared in that sibling's signatures.\n\nCompiler errors:\n${stubFeedback}`
      : ``,
    `\nProduce the feature-coded specification.`,
  ].join('');
}

export const FEATURE_REPAIR_SYSTEM = `You perform BOUNDED repair on a feature-coded specification.
You receive the full current spec and a list of offending feature ids plus validation problems.
Return ONLY corrected FeatureNode objects for those offending ids — every other feature must be left untouched by the caller.

Rules:
- Do NOT add, remove, rename, or renumber features. Same ids. Same feature count.
- Fix only the listed mechanical problems (files under src/, testFile under tests/, stubs present per path, edgeCases non-empty, deps valid, ownership disjoint).
- Preserve descriptions, deps, and exports unless a problem explicitly requires changing them.
- Stubs are SIGNATURES ({ path, imports, signatures }), never file content or bodies — the harness assembles the files.`;

/**
 * Fresh feature repair: known-good siblings intact; offending features are
 * id/name/description skeletons only — never broken mechanical fields.
 */
export function repairFeaturesUser(
  spec: { title: string; features: unknown[] },
  offendingIds: string[],
  problems: string[],
): string {
  const offend = new Set(offendingIds);
  const features = (spec.features as Array<Record<string, unknown>>).map(f => {
    const id = String(f.id ?? '');
    if (!offend.has(id)) return f;
    return {
      id: f.id,
      name: f.name,
      description: f.description,
      parentId: f.parentId ?? null,
      deps: [],
      files: [],
      testFile: '',
      edgeCases: [],
      exports: [],
      envVars: [],
      stubs: [],
    };
  });
  return [
    `Specification context (offending features are skeletons — regenerate their mechanical fields; siblings are known-good):`,
    JSON.stringify({ title: spec.title, features }, null, 2),
    ``,
    `Repair ONLY these feature ids: ${offendingIds.join(', ')}`,
    `Problems:`,
    ...problems.map(p => `- ${p}`),
    ``,
    `Return { "features": [ ...corrected FeatureNode for each offending id... ] }.`,
  ].join('\n');
}

export const STUB_FIX_SYSTEM = `You regenerate ONE TypeScript interface stub file from diagnostics and calling evidence. This is a FRESH draw — you are NOT given the broken prior content (feeding it would resample the failed distribution).

Rules:
- Return the COMPLETE file content for this path.
- ADDITIVE-ONLY contract growth: ADD exported members when diagnostics say dependents need them — infer signatures from calling-line evidence. Never invent unrelated API surface.
- PRE-FREEZE WIDENING: when callers pass MORE arguments than declared (TS2554 M > N), widen parameters from the shown calls. If \`new C(...)\` lacks a constructor, ADD one.
- Every function/method/constructor body must be exactly: { throw new Error('stub: not implemented'); }.
- Every type referenced must be defined in this file or imported with a relative extensionless path.
- Balance all braces and parentheses; the file must parse.`;

export function stubFixUser(path: string, ctx: StubFixCtx): string {
  return [
    `File: ${path}`,
    ``,
    `Compiler errors:`,
    ctx.diagnostics,
    ctx.contractText ? `\nDeclared contract / exports for this feature:\n${ctx.contractText}` : ``,
    ctx.callingLines ? `\nCalling-line evidence (from OTHER files that consume this stub):\n${ctx.callingLines}` : ``,
    ctx.checkedIdioms ? `\n${ctx.checkedIdioms}` : ``,
    ``,
    `Produce the complete corrected stub file content. Do not echo any prior broken draft — there is none in this prompt.`,
  ].join('\n');
}

export const IR_SYSTEM = `You are the IR-generation stage of a deterministic codegen compiler.
You produce the frozen test suite for ONE feature node — but NOT as a file. The harness owns the file structure (the vitest import, the describe block, name prefixes, assembly); you return STRUCTURED PARTS: implementation imports and test cases. This scaffolding exists so structure can never be wrong; your entire job is the substance of the cases. The assembled suite is the intermediate representation: generated BEFORE any implementation exists, never edited afterward.

Rules (strict input isolation):
- Derive tests ONLY from the specification and the interface stubs. There is no implementation to read; do not imagine one and rationalize it.
- The stubs are the declared API surface — yours and your dependencies'. Call ONLY what the stubs declare, with the declared signatures. The suite is typechecked against the stubs before it may freeze.
- "imports": the implementation modules and symbols your case bodies use. "from" is the implementation file path (e.g. "src/service"); the harness normalizes it. Import ONLY symbols the stubs export. Do NOT list vitest — describe/it/expect are already in scope.
- "cases": one entry per test. "body" is ONLY the code inside the it() callback — no it(), no describe(), no imports, no markdown fences. Use expect(...) assertions. If the body uses await, the harness makes the callback async.
- "kind": "behavior" for core behavior; "edge" for the listed edge cases — cover EVERY listed edge case with at least one case; "integration" for wired-dependency paths — at least one when the feature has deps. The harness prefixes names by kind automatically.
- Tests must be deterministic: no network, no timers you don't control, no randomness without seeding.
- Each body must be small, self-contained, syntactically complete TypeScript.`;

export function irUser(spec: FeatureSpec, feature: FeatureNode, feedback?: string): string {
  const stubBlock = (f: FeatureNode) =>
    f.stubs.map(s => `--- ${s.path} (interface stub) ---\n${s.content}`).join('\n');
  return [
    // The raw product prompt is deliberately NOT re-sent here: the encoding
    // already distilled it into this feature node + stubs (strict input
    // isolation), and re-embedding a large prompt per feature blows the
    // model's context window. The feature description + edge cases carry intent.
    `Product: ${spec.title}`,
    ``,
    `Feature node ${feature.id}: ${feature.name}`,
    `Description: ${feature.description}`,
    `Owned implementation files: ${feature.files.join(', ')}`,
    `Edge cases to capture: ${feature.edgeCases.join(' | ')}`,
    `Depends on: ${feature.deps.length ? feature.deps.join(', ') : 'nothing'}`,
    ``,
    `YOUR feature's declared interface (test exactly this surface):`,
    stubBlock(feature),
    feature.deps.length
      ? `\nDependency interfaces (for integration tests — call only what these declare):\n${spec.features
          .filter(f => feature.deps.includes(f.id))
          .map(stubBlock)
          .join('\n')}`
      : ``,
    feedback
      ? `\nA previous version of this suite FAILED a deterministic gate. Fix these findings by conforming to the stubs (never by weakening coverage):\n${feedback}`
      : ``,
    ``,
    `Return the imports and cases for the frozen suite ${feature.testFile}.`,
  ].join('\n');
}

/**
 * SET 4a — hidden harness contract. Concatenated under the hood onto the
 * encode prompt (LAST — recency wins). Corpus-published as `harnessContract`.
 */
export const HARNESS_CONTRACT_SYSTEM = `=== HARNESS CONTRACT (appended by the compiler — overrules any conflicting instruction above) ===
The artifact is a dependency-free TypeScript library (Node stdlib only, ESM). Tests execute pure functions in memory: there is NO browser, DOM, HTTP server, database, Redis, filesystem, or network at test time.
Express every capability as pure deterministic functions:
- HTTP endpoints → handler functions \`(request: {method, path, query, body, headers, session?}) => {status, headers, body}\`; assert on the returned object.
- HTML/UI → render functions returning HTML strings; assert on string structure. No computed styles, no viewport, no pixels.
- Persistence → injectable in-memory stores (Map-backed); represent schemas as typed structures plus invariant-checking functions. No SQL engines.
- Time, randomness, rate limiting → injectable clock/counter parameters; deterministic always.
Do NOT create features for anything requiring a real browser, real database, network I/O, or visual/computed-style measurement — those are out of scope for this artifact and will be handled by a later tier.
When naming edge cases, prefer these class words (they unlock verified test templates): empty input, boundary values, unicode, idempotency, roundtrip, collision, expiry, ordering, throws-on-invalid.
If any instruction above contradicts this contract, THIS CONTRACT WINS.`;

/** Concatenate user prompt + harness contract (contract LAST). */
export function composeEncodePrompt(userPrompt: string, contract = HARNESS_CONTRACT_SYSTEM): string {
  return [userPrompt, contract].join('\n\n');
}

/**
 * SET 95a — hole-only wire format. The harness has ALREADY assembled the
 * `it()` frame (case-body draws) or the signature-and-imports stub frame
 * (file-body draws); the model returns ONLY the marked hole's content, as
 * exactly one `⟨HOLE⟩ … ⟨/END⟩` block. Anything outside that block, or a
 * response that omits it, is rejected as `overreach|body` — the model has a
 * bounded work unit and no license to redo work the CPU already did.
 */
export const IR_SYSTEM_HOLE = `You fill ONE assertion hole in a frozen-IR test case. The harness has already written the \`it(...)\` frame around your answer — you do NOT write describe(), it(), imports, or any test scaffolding.

Your entire response must be ONE \`⟨HOLE⟩ … ⟨/END⟩\` block. The frame around your response is already correct; do not repeat it. Do not include \`import\`, \`describe\`, \`it\`, \`test\`, or function-declaration keywords in the body. Return the smallest correct code that satisfies the constraint.

Rules for the content inside the block:
- Call ONLY what the declared stubs export, with the declared signatures.
- Use expect(...) assertions. Assert real behavior — no existence-only checks.
- Be deterministic: no network, no uncontrolled timers, no unseeded randomness.`;

export const BUILD_SYSTEM_HOLE = `You fill the function/method bodies of ONE implementation file. The harness has already written the signatures, imports, and class/interface shells around your answer — you do NOT write signatures, imports, or declarations.

Your entire response must be ONE \`⟨HOLE⟩ … ⟨/END⟩\` block per body, in the order the holes were listed. The frame around each hole is already correct; do not repeat it. Do not include \`import\`, \`describe\`, \`it\`, \`test\`, or a function/class declaration keyword in a body. Return the smallest correct implementation that satisfies the frozen tests.

Rules for the content inside each block:
- Keep the public signature exactly as declared — you are writing the body only.
- Read the frozen tests carefully; they define the behavior. Satisfy the contract, do not game it.
- Prefer plain string scanning over regular-expression literals when practical.`;

export const IR_CASE_FIX_SYSTEM = `You regenerate individual failing test cases in a frozen-IR suite. This is BOUNDED FRESH repair: you are given case name/kind + diagnostics — NEVER the broken prior body (that would resample the failed distribution). Every other case is preserved untouched by the harness.

Rules:
- Call ONLY what the stubs declare, with the declared signatures. Fix by CONFORMING to the stub — never by weakening coverage intent.
- "body" is ONLY the code inside the it() callback: no it(), no describe, no imports, no markdown fences.
- Return exactly ONE regenerated case per listed case, in the SAME ORDER. Preserve testing intent implied by the name/kind and feature edge cases.
- If import errors are listed, return a corrected "imports" array using only symbols the stubs export. Otherwise omit "imports".`;

export function irCaseFixUser(
  spec: FeatureSpec,
  feature: FeatureNode,
  brokenCases: { name: string; kind: string; errors: string[] }[],
  importErrors: string[],
  currentImports: { from: string; symbols: string[] }[],
  checkedIdioms?: string,
): string {
  const stubBlock = (f: FeatureNode) =>
    f.stubs.map(s => `--- ${s.path} (interface stub) ---\n${s.content}`).join('\n');
  return [
    `Product: ${spec.title}`,
    `Feature node ${feature.id}: ${feature.name}`,
    `Description: ${feature.description}`,
    `Edge cases: ${feature.edgeCases.join(' | ')}`,
    ``,
    `Declared interface (conform to this EXACTLY):`,
    stubBlock(feature),
    feature.deps.length
      ? `\nDependency interfaces (call only what these declare):\n${spec.features
          .filter(f => feature.deps.includes(f.id))
          .map(stubBlock)
          .join('\n')}`
      : ``,
    currentImports.length ? `\nCurrent imports: ${JSON.stringify(currentImports)}` : ``,
    importErrors.length ? `\nImport errors (return a corrected "imports"):\n- ${importErrors.join('\n- ')}` : ``,
    checkedIdioms ? `\n${checkedIdioms}` : ``,
    ``,
    `Cases to REGENERATE from name/kind + diagnostics (prior bodies deliberately withheld):`,
    ...brokenCases.map((c, i) =>
      `[${i}] it('${c.name}') kind=${c.kind}\n  errors:\n${c.errors.map(e => '  - ' + e).join('\n')}`,
    ),
    ``,
    `Return the regenerated cases${importErrors.length ? ' and imports' : ''}.`,
  ].join('\n');
}

export const BUILD_SYSTEM = `You are the code-generation stage of a deterministic codegen compiler.
The frozen test suite you are given is the IR: a fixed contract. Your implementation must satisfy it exactly. You did not write these tests and you may not change them.

Rules:
- Implement ONLY the files owned by this feature node. Never output test files or files owned by other features.
- Your files currently contain interface stubs. Replace each stub body with a real implementation while keeping the public signatures EXACTLY as declared — dependents' frozen suites typecheck against them.
- Dependency-free TypeScript, ESM, Node stdlib only. Extensionless relative imports.
- Export exactly the wiring-contract symbols (plus internals as needed).
- Read the frozen tests carefully — they define behavior including edge cases. Satisfy the contract, do not game it.
- Prefer plain string scanning (indexOf/slice/split/startsWith) over regular-expression literals — malformed regex literals are a known failure class. Use a regex only when scanning is impractical, and keep it simple.
- Output the complete content of every owned file.`;

export function buildUser(
  spec: FeatureSpec,
  feature: FeatureNode,
  frozenTest: string,
  depContext: GeneratedFile[],
  avoid?: string,
  /** SET 36d — build-stage superbuffer (templates/lessons/sibling impls). */
  context?: string,
): string {
  return [
    `Product: ${spec.title}`,
    `Feature node ${feature.id}: ${feature.name}`,
    `Description: ${feature.description}`,
    `Files you own (output all of them): ${feature.files.join(', ')}`,
    `Wiring contract: ${feature.exports.map(e => `${e.symbol} from ${e.file}`).join('; ')}`,
    ``,
    `Declared interface (keep these public signatures exactly):`,
    ...feature.stubs.map(s => `--- ${s.path} (stub to replace) ---\n${s.content}`),
    ``,
    `FROZEN TEST SUITE (the IR — read-only contract):`,
    '```ts',
    frozenTest,
    '```',
    depContext.length ? `\nAlready-built dependency files (read-only context):` : ``,
    ...depContext.map(d => `--- ${d.path} ---\n${d.content}`),
    context
      ? `\n${context}`
      : ``,
    avoid
      ? `\nTABU — oracle facts from DISCARDED earlier attempts (you are a fresh implementation; the old code is deliberately withheld). These behaviors were observed failing and must NOT recur:\n${avoid}`
      : ``,
    ``,
    `Produce the implementation files.`,
  ].join('\n');
}

export const RHA_SYSTEM = `You are the bounded-repair stage of the Recursive Healing Algorithm (RHA).
A deterministic sweep localized a failure to one feature node. You produce a FRESH implementation for that node's owned files from the frozen contract alone — the broken prior implementation is deliberately withheld (feeding it resamples the failed distribution).

Rules:
- You may ONLY return files from the owned-files list. Never tests (the IR is frozen), never other features' files.
- Implement from the frozen test suite + declared stubs + findings/tabu facts. Do not echo any prior broken draft.
- The frozen tests define correct behavior. Make the code satisfy them; do not work around them.
- Output the complete content of every owned file you change (whole file, not a diff).`;

export function rhaUser(input: RepairInput): string {
  const f = (x: Finding) => `- [${x.bugClass}] ${x.file}${x.line ? ':' + x.line : ''} — ${x.message}`;
  // Tabu: error messages only — never prior code.
  const tabu = input.findings
    .map(x => `- ${x.testName ? x.testName + ': ' : ''}${x.message.split('\n')[0].slice(0, 180)}`)
    .join('\n');
  return [
    `Product: ${input.spec.title}`,
    `Error node: feature ${input.feature.id} (${input.feature.name}), repair attempt ${input.attempt}.`,
    `Owned files (your write boundary): ${input.feature.files.join(', ')}`,
    ``,
    `Localized findings on this node:`,
    ...input.findings.map(f),
    input.parallelInstances.length
      ? `\nParallel instances of the same bug class elsewhere in the tree:\n${input.parallelInstances.map(f).join('\n')}`
      : ``,
    ``,
    `Supergit lineage of this node (decision trail):`,
    ...input.lineage.map(l => `  ${l}`),
    ``,
    `Failure output (oracle facts only):`,
    '```',
    input.failureOutput.slice(0, 6000),
    '```',
    ``,
    `Declared interface stubs (contract to implement — replace stub bodies with real code):`,
    ...input.feature.stubs.map(s => `--- ${s.path} ---\n${s.content}`),
    ``,
    `FROZEN TEST SUITE (read-only contract):`,
    '```ts',
    input.frozenTest,
    '```',
    // SET 80 — REPAIR-IN-PLACE vs REGENERATE, decided by the caller.
    // Historically this prompt ALWAYS withheld the current implementation
    // ("fresh from frozen contract") to prevent anchoring on a poisoned
    // basin. That is correct for an entropy reset, and wrong for a targeted
    // repair: it forced the model to re-derive everything that already
    // worked. When the caller supplies `ownedFiles`, the node is shown and
    // the instruction becomes repair-in-place; when it does not, the old
    // rewrite-from-contract behavior is unchanged.
    input.ownedFiles.length
      ? `\nCURRENT IMPLEMENTATION — this is the node you are repairing (edit it; preserve what already satisfies the contract):`
      : ``,
    ...input.ownedFiles.map(f => `--- ${f.path} ---\n${f.content}`),
    input.dependencyContext.length ? `\nDependency files (read-only context):` : ``,
    ...input.dependencyContext.map(d => `--- ${d.path} ---\n${d.content}`),
    tabu ? `\nTABU — oracle facts from discarded attempts (error messages only; old code withheld):\n${tabu}` : ``,
    ``,
    input.ownedFiles.length
      ? `Return the COMPLETE repaired file(s). Keep every exported signature. Change only what the findings require.`
      : `Produce the implementation files fresh from the frozen contract.`,
  ].join('\n');
}

/**
 * SET 37f — ideation entropy. ONE bounded concept draw per feature at build
 * (high t). Output is stored as the `concept` superbuffer page and consumed
 * as CONTEXT only — never trusted as code; rails unchanged.
 */
export const CONCEPT_SYSTEM = `You are sketching how you would ship ONE feature — expansive ideation, realistic contraction.
Rules:
- Respond with exactly THREE sentences: (1) approach, (2) pitfalls / edge risks, (3) key data shapes.
- Do NOT emit implementation code, test code, TypeScript, or fenced code blocks.
- Do NOT invent APIs beyond what the spec page states.
- Stay under ~120 words. This page is CONTEXT for later draws, never executed.`;

export function conceptUser(specPage: string, feature: { id: string; name: string }): string {
  return [
    `Feature ${feature.id}: ${feature.name}`,
    ``,
    `SPEC PAGE (literal encoded feature text — denote, do not invent past it):`,
    specPage,
    ``,
    `In 3 sentences: approach, pitfalls, data shapes. No code.`,
  ].join('\n');
}
