/**
 * Realize LLM-emit rule manifest.
 *
 * Single source of truth for every post-emit constraint enforced on
 * per-owner LLM output. Replaces the prior model where each rule was
 * authored in THREE places (a regex+correction in `per-owner-emit.ts`,
 * a prose-statement in `realize-owner.prompt.yaml`, and an ad-hoc unit
 * test fixture). That model produced drift — prompt and validator
 * statements got out of sync, fixture coverage was uneven, and adding
 * a rule meant editing three locations consistently.
 *
 * The manifest below declares each rule once. Three consumers read it:
 *
 *   1. Validator pipeline (per-owner-emit.ts) — calls `detect(...)` to
 *      find violations, calls `correction(...)` to build the surgical-
 *      retry prefix, sorts rules by `ordering` to control which fires
 *      first when multiple violations co-occur.
 *
 *   2. Prompt builder — reads `promptStatement` to render the
 *      CONSTRAINTS section of `realize-owner.prompt.yaml` (Phase 2 of
 *      the 2026-05-12-VALIDATOR-MANIFEST-AND-TEST-CORPUS proposal).
 *
 *   3. Test corpus (`engines/src/realize/__tests__/llm-output-fixtures/`)
 *      — each fixture is annotated with the rule IDs it should trigger;
 *      the pipeline harness asserts the manifest's ordering produces
 *      exactly those fires.
 *
 * Adding a new rule is one place: append a new entry to RULES.
 *
 * Why in engines/src/realize/ rather than entities/src/core/realize/:
 *   For the moment, rules are realize-internal — they're regex scans
 *   over LLM-emitted TypeScript, not the kind of bundle-able content
 *   that R12 entity modules manage. If realize rules ever need to be
 *   extended by user specs (e.g. project-specific forbidden imports),
 *   migration to the entity-module pattern would be the next step.
 */
// ─── Forbidden runtime APIs ─────────────────────────────────────────
/** Forbidden-API symbol set for Node.js target. */
const NODE_FORBIDDEN = [
    'localStorage',
    'sessionStorage',
    'window.',
    'document.',
    'navigator.',
    'location.',
    'history.',
    'alert(',
    'prompt(',
    'confirm(',
];
/** Forbidden-API symbol set for Browser target. */
const BROWSER_FORBIDDEN = ['process.env', 'require(', 'Buffer', '__dirname', '__filename', 'node:'];
/**
 * Map a target-runtime label to its forbidden-API symbol set. Exported
 * so the prompt builder can render the TARGET RUNTIME section using
 * the same source-of-truth the validator enforces.
 *
 * Targets supported:
 *   - "Node.js (backend)" (default) — browser globals are forbidden
 *   - "Browser (DOM)" — Node builtins are forbidden
 *   - Unknown / unset — treat as Node.js backend
 */
export function forbiddenApisForRuntime(targetRuntime) {
    const t = (targetRuntime ?? 'Node.js (backend)').toLowerCase();
    if (t.includes('browser') && !t.includes('backend'))
        return BROWSER_FORBIDDEN;
    return NODE_FORBIDDEN;
}
// ─── Rules ───────────────────────────────────────────────────────────
const RULE_NO_FORBIDDEN_RUNTIME_APIS = {
    id: 'no-forbidden-runtime-apis',
    summary: 'No APIs from foreign runtimes',
    promptStatement: 'Do NOT use APIs that don\'t exist in the target runtime. The ' +
        'forbidden symbols for this run are listed in the TARGET RUNTIME ' +
        'section above. If a spec step references a forbidden API ' +
        '(e.g. "save to localStorage" with a Node.js target), emit the ' +
        'γ-stub form for THAT action explaining the spec/runtime ' +
        'mismatch — do NOT implement the forbidden API.',
    detect(fileContents, ctx) {
        const forbidden = forbiddenApisForRuntime(ctx.targetRuntime);
        const violations = new Set();
        for (const symbol of forbidden) {
            if (fileContents.includes(symbol))
                violations.add(symbol);
        }
        return Array.from(violations).slice(0, 8);
    },
    correction(violations, ctx) {
        const runtime = ctx.targetRuntime ?? 'Node.js (backend)';
        return [
            `Your previous emission used runtime APIs that are NOT available in ${runtime}:`,
            '',
            ...violations.map((v) => `  - ${v}`),
            '',
            `Re-emit the entire file WITHOUT those symbols. If a specific action's`,
            `step text references runtime-incompatible behaviour, use the γ-stub`,
            `form for THAT action explaining the spec/runtime mismatch. Other`,
            `actions in the file should still be implemented fully. Preserve`,
            `all [PRE-BAKED] snippets verbatim.`,
            '',
        ].join('\n');
    },
    ordering: 10,
};
const RULE_JS_EXTENSIONS_ON_RELATIVE_IMPORTS = {
    id: 'js-ext-on-relative-imports',
    summary: 'Relative imports require explicit `.js` (or .ts/.json/...) extension',
    promptStatement: 'Relative imports MUST end with `.js` (or `.ts`/`.json`/`.mjs`/`.mts`). ' +
        'The project uses `moduleResolution: NodeNext` which requires explicit ' +
        'extensions on relative paths. WRONG: `from \'../services/AuthService\'` ' +
        '→ CORRECT: `from \'../services/AuthService.js\'`. Bare-package imports ' +
        '(`@prisma/client`, `jsonwebtoken`) do NOT need an extension.',
    detect(fileContents) {
        const violations = new Set();
        const re = /from\s+['"](\.\.?\/[^'"]*?)['"]/g;
        let m;
        while ((m = re.exec(fileContents)) !== null) {
            const specifier = m[1];
            if (!/\.(m?[jt]sx?|json)$/.test(specifier)) {
                violations.add(specifier);
            }
        }
        return Array.from(violations).slice(0, 8);
    },
    correction(violations) {
        return [
            `Your previous emission used relative imports WITHOUT the required`,
            `\`.js\` extension. NodeNext module resolution requires explicit`,
            `extensions on relative paths:`,
            '',
            ...violations.map((v) => `  - '${v}' → '${v}.js'`),
            '',
            `Re-emit the entire file with \`.js\` appended to every relative-path`,
            `import. Bare-package imports do NOT need an extension. Preserve all`,
            `[PRE-BAKED] snippets verbatim.`,
            '',
        ].join('\n');
    },
    ordering: 20,
};
const RULE_NO_SIBLING_CLASS_PATH_IMPORTS = {
    id: 'no-sibling-class-path-imports',
    summary: 'Sibling-class imports (`./<Capital>.js`) must use the parent-directory ' +
        'path (`../<dir>/<Capital>.js`) because realize emits behaviors/ and ' +
        'services/ as separate directories',
    promptStatement: 'Sibling-class imports MUST use `../<dir>/<Name>.js`, NOT `./<Name>.js`. ' +
        'The realize pipeline emits this file into `backend/src/behaviors/`; ' +
        'service classes live at `backend/src/services/<Name>.ts` and ' +
        'controller classes at `backend/src/controllers/<Name>.ts`. WRONG: ' +
        '`from \'./GameStateService.js\'` → CORRECT: ' +
        '`from \'../services/GameStateService.js\'`. (Sibling .ai imports are ' +
        'separately forbidden — see the next rule. Subdirectory imports like ' +
        '`./types/Foo.js` are fine; only TOP-LEVEL `./<Capital>.js` is wrong.)',
    detect(fileContents) {
        const violations = new Set();
        // Match `from './<Capital><rest>.js'` with no subdirectory slash inside
        // and where the bare name (rest) does NOT end in `.ai` (the sibling-ai
        // rule handles those).
        const re = /from\s+['"](\.\/[A-Z][^'"\/]*)\.js['"]/g;
        let m;
        while ((m = re.exec(fileContents)) !== null) {
            const specifier = m[1];
            // Skip `.ai` siblings — caught by `no-sibling-ai-imports` instead.
            if (specifier.endsWith('.ai'))
                continue;
            violations.add(specifier);
        }
        return Array.from(violations).slice(0, 8);
    },
    correction(violations) {
        return [
            `Your previous emission imported sibling-class modules with the wrong path:`,
            '',
            ...violations.map((v) => `  - '${v}.js' → '../services/${v.slice(2)}.js' (or '../controllers/${v.slice(2)}.js' if it's a controller class)`),
            '',
            `The realize pipeline organises generated code by kind:`,
            `  backend/src/behaviors/<Name>.ai.ts   ← this file`,
            `  backend/src/services/<Name>.ts       ← service classes`,
            `  backend/src/controllers/<Name>.ts    ← controller classes`,
            ``,
            `So a sibling-class import from THIS file is one directory up + into`,
            `the service-or-controller dir. NOT \`./<Name>.js\`.`,
            '',
            `Re-emit the entire file with the imports corrected. Preserve all`,
            `[PRE-BAKED] snippets verbatim.`,
            '',
        ].join('\n');
    },
    ordering: 25,
};
const RULE_NO_SIBLING_AI_IMPORTS = {
    id: 'no-sibling-ai-imports',
    summary: 'Do not import sibling `.ai.ts` files',
    promptStatement: 'Do NOT import sibling `.ai.ts` files. Each `<Owner>.ai.ts` is ' +
        'self-contained — those modules export per-action async functions, ' +
        'not the entity types or service instances you might want to use. ' +
        'For cross-service calls, import from the service / controller class ' +
        'file instead (e.g. `import { AuthService } from \'../services/AuthService.js\'`). ' +
        'If a step requires logic from a sibling .ai, emit a γ-stub for THAT ' +
        'specific action explaining the cross-owner gap.',
    detect(fileContents) {
        const violations = new Set();
        const re = /from\s+['"](\.\.?\/[^'"]+\.ai)(?:\.js)?['"]/g;
        let m;
        while ((m = re.exec(fileContents)) !== null) {
            violations.add(m[1]);
        }
        return Array.from(violations).slice(0, 8);
    },
    correction(violations) {
        return [
            `Your previous emission imported sibling .ai.ts modules, which is forbidden:`,
            '',
            ...violations.map((v) => `  - ${v}`),
            '',
            `Each <Owner>.ai.ts is self-contained — those modules export per-action`,
            `functions, NOT the entity types or service instances you can use here.`,
            '',
            `Re-emit the entire file WITHOUT importing any sibling .ai modules.`,
            `For cross-service calls, import from the service / controller class file`,
            `instead (e.g. \`import { AuthService } from '../services/AuthService.js'\`).`,
            `If a step requires logic that lives in a sibling .ai, emit the γ-stub for`,
            `THAT specific action explaining the cross-owner gap. Preserve all`,
            `[PRE-BAKED] snippets verbatim.`,
            '',
        ].join('\n');
    },
    ordering: 30,
};
/**
 * The full rule set, in pipeline order. Sorted by `ordering` field.
 * The exported list is read by:
 *   - per-owner-emit.ts (validator loop)
 *   - prompt-builder (CONSTRAINTS section of realize-owner.prompt.yaml)
 *   - test corpus (fixture-to-rule expected mapping)
 */
export const RULES = [
    RULE_NO_FORBIDDEN_RUNTIME_APIS,
    RULE_JS_EXTENSIONS_ON_RELATIVE_IMPORTS,
    RULE_NO_SIBLING_CLASS_PATH_IMPORTS,
    RULE_NO_SIBLING_AI_IMPORTS,
].sort((a, b) => a.ordering - b.ordering);
/** Helper: get a single rule by id. Throws if the id doesn't exist
 *  (catches typos in test fixtures + downstream consumers). */
export function getRule(id) {
    const rule = RULES.find((r) => r.id === id);
    if (!rule) {
        const known = RULES.map((r) => r.id).join(', ');
        throw new Error(`Unknown realize rule id: '${id}'. Known: ${known}.`);
    }
    return rule;
}
/** Helper: filter RULES to the ones that apply for a given context. */
export function rulesApplicableTo(ctx) {
    return RULES.filter((r) => !r.appliesWhen || r.appliesWhen(ctx));
}
/** Helper: render the CONSTRAINTS section of the prompt by joining
 *  each rule's promptStatement. */
export function renderConstraintsForPrompt(ctx) {
    const applicable = rulesApplicableTo(ctx);
    if (applicable.length === 0)
        return '(no constraints declared)';
    return applicable
        .map((rule, i) => `${i + 1}. ${rule.promptStatement}`)
        .join('\n\n');
}
// Note: the LanguageModelV3 import is currently unused but kept for
// future rules that may need to consult the model object (e.g. for
// model-specific constraints). Remove if no such rules emerge.
void null;
//# sourceMappingURL=realize-rules.js.map