import { describe, expect, it } from 'vitest';
import { DocTargetSchema, DoctorConfigSchema, GarbageCollectionSchema, getConfigTier, OrchestratorSchema, requireEmbedding, TotemConfigSchema, } from './config-schema.js';
const BASE_TARGETS = [
    { glob: '**/*.md', type: 'spec', strategy: 'markdown-heading' },
];
const OPENAI_EMBEDDING = { provider: 'openai', model: 'text-embedding-3-small' };
const SHELL_ORCHESTRATOR = {
    provider: 'shell',
    command: 'echo {file}',
    defaultModel: 'test-model',
};
const GEMINI_ORCHESTRATOR = {
    provider: 'gemini',
    defaultModel: 'gemini-2.5-flash',
};
const ANTHROPIC_ORCHESTRATOR = {
    provider: 'anthropic',
    defaultModel: 'claude-sonnet-4-6',
};
describe('TotemConfigSchema', () => {
    it('accepts config without embedding (Lite tier)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
    });
    it('accepts config with embedding but no orchestrator (Standard tier)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            embedding: OPENAI_EMBEDDING,
        });
        expect(result.success).toBe(true);
    });
    it('accepts config with embedding and orchestrator (Full tier)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            embedding: OPENAI_EMBEDDING,
            orchestrator: SHELL_ORCHESTRATOR,
        });
        expect(result.success).toBe(true);
    });
    it('rejects config with no targets', () => {
        const result = TotemConfigSchema.safeParse({ targets: [] });
        expect(result.success).toBe(false);
    });
    it('defaults indexIgnorePatterns to an empty array (index-only exclusions, #1748)', () => {
        const result = TotemConfigSchema.parse({ targets: BASE_TARGETS });
        expect(result.indexIgnorePatterns).toEqual([]);
    });
    it('preserves indexIgnorePatterns independently of ignorePatterns', () => {
        const result = TotemConfigSchema.parse({
            targets: BASE_TARGETS,
            indexIgnorePatterns: ['audits/**'],
            ignorePatterns: ['dist/**'],
        });
        expect(result.indexIgnorePatterns).toEqual(['audits/**']);
        expect(result.ignorePatterns).toEqual(['dist/**']);
    });
    it('rejects absolute lanceDir (Unix)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, lanceDir: '/tmp/lancedb' });
        expect(result.success).toBe(false);
    });
    it('rejects absolute lanceDir (Windows drive)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, lanceDir: 'C:\\lancedb' });
        expect(result.success).toBe(false);
    });
    it('rejects absolute lanceDir (Windows backslash)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, lanceDir: '\\lancedb' });
        expect(result.success).toBe(false);
    });
    it('rejects absolute totemDir', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, totemDir: '/tmp/totem' });
        expect(result.success).toBe(false);
    });
    it('accepts relative lanceDir', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, lanceDir: '.lancedb' });
        expect(result.success).toBe(true);
    });
    it('accepts config with docs array', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            docs: [
                { path: 'README.md', description: 'Public README', trigger: 'post-release' },
                { path: 'docs/wiki/roadmap.md', description: 'Roadmap' },
            ],
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.docs).toHaveLength(2);
            expect(result.data.docs[1].trigger).toBe('post-release'); // default
        }
    });
    it('accepts config without docs (optional)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.docs).toBeUndefined();
        }
    });
    it('accepts strategyRoot as an optional string (mmnto-ai/totem#1710)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            strategyRoot: '../totem-strategy',
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.strategyRoot).toBe('../totem-strategy');
        }
    });
    it('treats strategyRoot as undefined when omitted', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.strategyRoot).toBeUndefined();
        }
    });
    it('rejects blank or whitespace-only strategyRoot (mmnto-ai/totem#1710 R3)', () => {
        // The resolver already trims at runtime, but the schema must fail
        // fast at config-parse time so a typo surfaces in `loadConfig` rather
        // than silently falling through to the next precedence layer.
        const blank = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            strategyRoot: '',
        });
        expect(blank.success).toBe(false);
        const whitespace = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            strategyRoot: '   ',
        });
        expect(whitespace.success).toBe(false);
    });
    it('accepts substratePath as an optional string (mmnto-ai/totem#1820)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            substratePath: '../totem-substrate',
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.substratePath).toBe('../totem-substrate');
        }
    });
    it('treats substratePath as undefined when omitted', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.substratePath).toBeUndefined();
        }
    });
    it('rejects blank or whitespace-only substratePath (mmnto-ai/totem#1820, mirrors strategyRoot)', () => {
        const blank = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            substratePath: '',
        });
        expect(blank.success).toBe(false);
        const whitespace = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            substratePath: '   ',
        });
        expect(whitespace.success).toBe(false);
    });
    // ─── ecl.cohortRepos (mmnto-ai/totem#2310) ─────────────
    it('accepts a declared ecl.cohortRepos roster (mmnto-ai/totem#2310)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            ecl: { cohortRepos: ['totem', 'totem-strategy'] },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.ecl?.cohortRepos).toEqual(['totem', 'totem-strategy']);
        }
    });
    it('accepts a single-repo roster (completeness-1, the A2.2 line)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            ecl: { cohortRepos: ['solo-repo'] },
        });
        expect(result.success).toBe(true);
    });
    it('treats ecl as undefined when omitted (the honest undeclared state)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.ecl).toBeUndefined();
        }
    });
    it('treats an ecl block without cohortRepos as undeclared (key optional)', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, ecl: {} });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.ecl?.cohortRepos).toBeUndefined();
        }
    });
    it('rejects an EMPTY ecl.cohortRepos array (config bug, not undeclared — Zod .min(1))', () => {
        // An empty roster must fail LOUD at load, never alias into the undeclared
        // gate-red arm (mmnto-ai/totem#2310; strategy#828 Tenet-4 distinction).
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            ecl: { cohortRepos: [] },
        });
        expect(result.success).toBe(false);
    });
    it('rejects a blank/empty repo name inside ecl.cohortRepos (element .min(1))', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            ecl: { cohortRepos: ['totem', ''] },
        });
        expect(result.success).toBe(false);
    });
});
// ─── Orchestrator schema ─────────────────────────────
describe('OrchestratorSchema', () => {
    it('accepts shell provider with command', () => {
        const result = OrchestratorSchema.safeParse(SHELL_ORCHESTRATOR);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.provider).toBe('shell');
        }
    });
    it('accepts gemini provider', () => {
        const result = OrchestratorSchema.safeParse(GEMINI_ORCHESTRATOR);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.provider).toBe('gemini');
        }
    });
    it('accepts anthropic provider', () => {
        const result = OrchestratorSchema.safeParse(ANTHROPIC_ORCHESTRATOR);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.provider).toBe('anthropic');
        }
    });
    it('accepts shared fields on all providers', () => {
        const geminiWithShared = {
            provider: 'gemini',
            defaultModel: 'gemini-2.5-flash',
            fallbackModel: 'gemini-2.5-pro',
            overrides: { spec: 'gemini-2.5-pro' },
            cacheTtls: { triage: 3600 },
        };
        const result = OrchestratorSchema.safeParse(geminiWithShared);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.defaultModel).toBe('gemini-2.5-flash');
            expect(result.data.fallbackModel).toBe('gemini-2.5-pro');
        }
    });
    it('accepts openai provider', () => {
        const result = OrchestratorSchema.safeParse({ provider: 'openai' });
        expect(result.success).toBe(true);
    });
    it('accepts openai provider with baseUrl', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'openai',
            baseUrl: 'http://localhost:11434/v1',
            defaultModel: 'llama3.1',
        });
        expect(result.success).toBe(true);
    });
    it('rejects unknown provider', () => {
        const result = OrchestratorSchema.safeParse({ provider: 'cohere' });
        expect(result.success).toBe(false);
    });
    it('rejects shell provider without command', () => {
        const result = OrchestratorSchema.safeParse({ provider: 'shell', defaultModel: 'test' });
        expect(result.success).toBe(false);
    });
    // ─── Caching foundation (mmnto/totem#1291 Phase 1) ─────────────
    it('accepts orchestrator config without enableContextCaching (defaults to undefined)', () => {
        const result = OrchestratorSchema.safeParse(ANTHROPIC_ORCHESTRATOR);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.enableContextCaching).toBeUndefined();
            expect(result.data.cacheTTL).toBeUndefined();
        }
    });
    it('accepts enableContextCaching: true with default cacheTTL', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            defaultModel: 'claude-sonnet-4-6',
            enableContextCaching: true,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.enableContextCaching).toBe(true);
            expect(result.data.cacheTTL).toBeUndefined();
        }
    });
    it('accepts enableContextCaching with explicit 5-minute cacheTTL', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            defaultModel: 'claude-sonnet-4-6',
            enableContextCaching: true,
            cacheTTL: 300,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.cacheTTL).toBe(300);
        }
    });
    it('accepts enableContextCaching with extended 1-hour cacheTTL', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            defaultModel: 'claude-sonnet-4-6',
            enableContextCaching: true,
            cacheTTL: 3600,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.cacheTTL).toBe(3600);
        }
    });
    it('rejects negative cacheTTL', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            enableContextCaching: true,
            cacheTTL: -1,
        });
        expect(result.success).toBe(false);
    });
    it('rejects zero cacheTTL', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            enableContextCaching: true,
            cacheTTL: 0,
        });
        expect(result.success).toBe(false);
    });
    it('rejects fractional cacheTTL (must be integer seconds)', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            enableContextCaching: true,
            cacheTTL: 300.5,
        });
        expect(result.success).toBe(false);
    });
    it('rejects unsupported cacheTTL values like 600 (only 300 and 3600 are valid)', () => {
        // mmnto/totem#1291 PR mmnto/totem#1292 review fix from CodeRabbit: cacheTTL
        // must be constrained to the literal values Anthropic supports (300 = 5m
        // default ephemeral, 3600 = 1h extended cache). Any other positive integer
        // would silently fall through to 5m at provider-invocation time, which is
        // confusing and untestable. Better to fail at config-parse time.
        //
        // Asserts the failure is specifically on the `cacheTTL` field path
        // rather than relying on the overall safeParse failing — addresses
        // a Shield AI WARN that the loose form might be a false positive if
        // some other required field were missing. Every other field in this
        // payload is valid (provider literal, enableContextCaching boolean),
        // so the error path MUST land on cacheTTL.
        const unsupported = [60, 600, 1200, 1800, 7200, 86_400];
        for (const ttl of unsupported) {
            const result = OrchestratorSchema.safeParse({
                provider: 'anthropic',
                enableContextCaching: true,
                cacheTTL: ttl,
            });
            expect(result.success, `cacheTTL: ${ttl} should be rejected`).toBe(false);
            if (!result.success) {
                const cacheTTLIssues = result.error.issues.filter((i) => i.path[0] === 'cacheTTL');
                expect(cacheTTLIssues.length, `cacheTTL: ${ttl} should produce a cacheTTL-specific error`).toBeGreaterThan(0);
            }
        }
    });
    it('coexists with the orthogonal cacheTtls (#52) field', () => {
        // enableContextCaching (prompt cache, mmnto/totem#1291) and cacheTtls (response
        // cache, #52) live at the same level but control different layers.
        // A config setting both should parse cleanly with no interaction.
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            defaultModel: 'claude-sonnet-4-6',
            cacheTtls: { triage: 3600, shield: 0 },
            enableContextCaching: true,
            cacheTTL: 300,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.cacheTtls).toEqual({ triage: 3600, shield: 0 });
            expect(result.data.enableContextCaching).toBe(true);
            expect(result.data.cacheTTL).toBe(300);
        }
    });
    it('accepts enableContextCaching on every provider variant', () => {
        const variants = [
            { provider: 'shell', command: 'echo {file}', enableContextCaching: true },
            { provider: 'anthropic', enableContextCaching: true },
            { provider: 'gemini', enableContextCaching: true },
            { provider: 'openai', enableContextCaching: true },
            { provider: 'ollama', enableContextCaching: true },
        ];
        for (const variant of variants) {
            const result = OrchestratorSchema.safeParse(variant);
            expect(result.success).toBe(true);
        }
    });
    // ─── Capability declaration (mmnto-ai/totem#2102, strategy#474 slice 3) ──
    it('accepts orchestrator config without capabilities (defaults to undefined)', () => {
        const result = OrchestratorSchema.safeParse(ANTHROPIC_ORCHESTRATOR);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.capabilities).toBeUndefined();
        }
    });
    it('accepts a declared admissionClasses capability list', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            defaultModel: 'claude-sonnet-4-6',
            capabilities: { admissionClasses: ['completion_only', 'self_grounding_agent'] },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.capabilities?.admissionClasses).toEqual([
                'completion_only',
                'self_grounding_agent',
            ]);
        }
    });
    it('accepts an empty capabilities object (no classes declared)', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            capabilities: {},
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.capabilities?.admissionClasses).toBeUndefined();
        }
    });
    it('rejects an unknown admission class, failing on the admissionClasses path', () => {
        const result = OrchestratorSchema.safeParse({
            provider: 'anthropic',
            capabilities: { admissionClasses: ['agentic_swarm'] },
        });
        expect(result.success).toBe(false);
        if (!result.success) {
            const classIssues = result.error.issues.filter((i) => i.path.includes('admissionClasses'));
            expect(classIssues.length).toBeGreaterThan(0);
        }
    });
    it('accepts capabilities on every provider variant', () => {
        const capabilities = { admissionClasses: ['self_grounding_agent'] };
        const variants = [
            { provider: 'shell', command: 'echo {file}', capabilities },
            { provider: 'anthropic', capabilities },
            { provider: 'gemini', capabilities },
            { provider: 'openai', capabilities },
            { provider: 'ollama', capabilities },
        ];
        for (const variant of variants) {
            const result = OrchestratorSchema.safeParse(variant);
            expect(result.success).toBe(true);
        }
    });
});
// ─── Backwards compatibility ─────────────────────────
describe('orchestrator backwards compatibility', () => {
    it('auto-migrates legacy config without provider to shell', () => {
        const legacyConfig = {
            targets: BASE_TARGETS,
            orchestrator: {
                command: 'gemini -e none < {file}',
                defaultModel: 'gemini-2.5-flash',
            },
        };
        const result = TotemConfigSchema.safeParse(legacyConfig);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.orchestrator.provider).toBe('shell');
        }
    });
    it('preserves all legacy fields after migration', () => {
        const legacyConfig = {
            targets: BASE_TARGETS,
            orchestrator: {
                command: 'gemini -e none < {file}',
                defaultModel: 'gemini-2.5-flash',
                fallbackModel: 'gemini-2.5-pro',
                overrides: { spec: 'gemini-2.5-pro' },
                cacheTtls: { triage: 3600 },
            },
        };
        const result = TotemConfigSchema.safeParse(legacyConfig);
        expect(result.success).toBe(true);
        if (result.success) {
            const orch = result.data.orchestrator;
            expect(orch.provider).toBe('shell');
            expect(orch.defaultModel).toBe('gemini-2.5-flash');
            expect(orch.fallbackModel).toBe('gemini-2.5-pro');
        }
    });
    it('does not inject provider when it is already present', () => {
        const explicitConfig = {
            targets: BASE_TARGETS,
            orchestrator: GEMINI_ORCHESTRATOR,
        };
        const result = TotemConfigSchema.safeParse(explicitConfig);
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.orchestrator.provider).toBe('gemini');
        }
    });
});
// ─── Config with API orchestrators ───────────────────
describe('TotemConfigSchema with API orchestrators', () => {
    it('accepts config with gemini orchestrator (Full tier)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            embedding: OPENAI_EMBEDDING,
            orchestrator: GEMINI_ORCHESTRATOR,
        });
        expect(result.success).toBe(true);
    });
    it('accepts config with anthropic orchestrator (Full tier)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            embedding: OPENAI_EMBEDDING,
            orchestrator: ANTHROPIC_ORCHESTRATOR,
        });
        expect(result.success).toBe(true);
    });
});
describe('DocTargetSchema', () => {
    it('parses valid doc target', () => {
        const result = DocTargetSchema.safeParse({
            path: 'README.md',
            description: 'Public README',
            trigger: 'post-release',
        });
        expect(result.success).toBe(true);
    });
    it('defaults trigger to post-release', () => {
        const result = DocTargetSchema.parse({
            path: 'README.md',
            description: 'Public README',
        });
        expect(result.trigger).toBe('post-release');
    });
    it('accepts on-change trigger', () => {
        const result = DocTargetSchema.safeParse({
            path: 'docs/reference/architecture.md',
            description: 'Architecture',
            trigger: 'on-change',
        });
        expect(result.success).toBe(true);
    });
    it('rejects invalid trigger', () => {
        const result = DocTargetSchema.safeParse({
            path: 'README.md',
            description: 'test',
            trigger: 'invalid',
        });
        expect(result.success).toBe(false);
    });
    it('rejects missing path', () => {
        const result = DocTargetSchema.safeParse({
            description: 'test',
        });
        expect(result.success).toBe(false);
    });
    it('rejects missing description', () => {
        const result = DocTargetSchema.safeParse({
            path: 'README.md',
        });
        expect(result.success).toBe(false);
    });
    it('parses successfully with no userFacing (backward compat)', () => {
        const result = DocTargetSchema.safeParse({
            path: 'README.md',
            description: 'Public README',
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.userFacing).toBeUndefined();
        }
    });
    it('parses successfully with userFacing: true', () => {
        const result = DocTargetSchema.safeParse({
            path: 'docs/reference/architecture.md',
            description: 'Architecture docs',
            userFacing: true,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.userFacing).toBe(true);
        }
    });
    it('parses successfully with userFacing: false', () => {
        const result = DocTargetSchema.safeParse({
            path: 'README.md',
            description: 'Internal README',
            userFacing: false,
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.userFacing).toBe(false);
        }
    });
});
describe('getConfigTier', () => {
    const base = TotemConfigSchema.parse({ targets: BASE_TARGETS });
    it('returns lite when no embedding and no orchestrator', () => {
        expect(getConfigTier(base)).toBe('lite');
    });
    it('returns standard when embedding is present but no orchestrator', () => {
        const config = { ...base, embedding: OPENAI_EMBEDDING };
        expect(getConfigTier(config)).toBe('standard');
    });
    it('returns full when both embedding and orchestrator are present', () => {
        const config = { ...base, embedding: OPENAI_EMBEDDING, orchestrator: SHELL_ORCHESTRATOR };
        expect(getConfigTier(config)).toBe('full');
    });
    it('returns lite when orchestrator is present but embedding is not', () => {
        const config = { ...base, orchestrator: SHELL_ORCHESTRATOR };
        expect(getConfigTier(config)).toBe('lite');
    });
    it('returns full for gemini API orchestrator', () => {
        const config = { ...base, embedding: OPENAI_EMBEDDING, orchestrator: GEMINI_ORCHESTRATOR };
        expect(getConfigTier(config)).toBe('full');
    });
    it('returns full for anthropic API orchestrator', () => {
        const config = { ...base, embedding: OPENAI_EMBEDDING, orchestrator: ANTHROPIC_ORCHESTRATOR };
        expect(getConfigTier(config)).toBe('full');
    });
});
describe('requireEmbedding', () => {
    const base = TotemConfigSchema.parse({ targets: BASE_TARGETS });
    it('returns embedding provider when configured', () => {
        const config = { ...base, embedding: OPENAI_EMBEDDING };
        expect(requireEmbedding(config)).toEqual(OPENAI_EMBEDDING);
    });
    it('throws when embedding is undefined', () => {
        expect(() => requireEmbedding(base)).toThrow('No embedding provider configured');
    });
    it('error message mentions Lite tier', () => {
        expect(() => requireEmbedding(base)).toThrow('Lite tier');
    });
    it('error recovery hint mentions totem init', () => {
        try {
            requireEmbedding(base);
        }
        catch (err) {
            expect(err.recoveryHint).toContain('totem init');
            return;
        }
        throw new Error('Expected requireEmbedding to throw');
    });
});
// ─── Garbage collection config ──────────────────────
// ─── Review source extensions (mmnto/totem#1527) ──────
describe('review.sourceExtensions', () => {
    it('normalizes missing dots and rejects shell-unsafe characters', () => {
        // Accepts both "ts" and ".ts", normalizes to leading-dot form.
        const ok = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: { sourceExtensions: ['ts', '.rs'] },
        });
        expect(ok.success).toBe(true);
        if (ok.success) {
            expect(ok.data.review.sourceExtensions).toEqual(['.ts', '.rs']);
        }
        // Shell-unsafe inputs rejected.
        const hazards = ['*.ts', 'ts; rm -rf /', '', 'ts js', '.ts`whoami`', '.ts"', ".ts'"];
        for (const bad of hazards) {
            const result = TotemConfigSchema.safeParse({
                targets: BASE_TARGETS,
                review: { sourceExtensions: [bad] },
            });
            expect(result.success, `extension "${bad}" should be rejected`).toBe(false);
        }
    });
    it('defaults to historical hardcoded set when field is absent', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.sourceExtensions).toEqual(['.ts', '.tsx', '.js', '.jsx']);
        }
    });
    it('defaults to historical hardcoded set when review object is empty', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {},
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.sourceExtensions).toEqual(['.ts', '.tsx', '.js', '.jsx']);
        }
    });
    it('rejects explicit empty array', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: { sourceExtensions: [] },
        });
        expect(result.success).toBe(false);
        if (!result.success) {
            const paths = result.error.issues.map((i) => i.path.join('.'));
            expect(paths.some((p) => p.includes('sourceExtensions'))).toBe(true);
        }
    });
    it('accepts compound extensions like .d.ts via internal dots', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: { sourceExtensions: ['.d.ts'] },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.sourceExtensions).toEqual(['.d.ts']);
        }
    });
    it('normalizes "ts" and ".ts" to the same stored form', () => {
        const a = TotemConfigSchema.parse({
            targets: BASE_TARGETS,
            review: { sourceExtensions: ['ts'] },
        });
        const b = TotemConfigSchema.parse({
            targets: BASE_TARGETS,
            review: { sourceExtensions: ['.ts'] },
        });
        expect(a.review.sourceExtensions).toEqual(b.review.sourceExtensions);
    });
    it('passthrough tolerates unknown future review fields', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                sourceExtensions: ['.ts'],
                futureUnknownField: { nested: 'value' },
            },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            // passthrough preserves the unknown field on the parsed object
            const asRecord = result.data.review;
            expect(asRecord['futureUnknownField']).toEqual({ nested: 'value' });
        }
    });
});
// ─── review.stage4Baseline (mmnto-ai/totem#1683) ────
describe('review.stage4Baseline', () => {
    it('validates extend and exclude as arrays of strings (happy path)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: {
                    extend: ['**/legacy/**', 'tools/scripts/**'],
                    exclude: ['**/tests/**'],
                },
            },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.stage4Baseline).toEqual({
                extend: ['**/legacy/**', 'tools/scripts/**'],
                exclude: ['**/tests/**'],
            });
        }
    });
    it('defaults extend and exclude to empty arrays when stage4Baseline is empty object', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: { stage4Baseline: {} },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.stage4Baseline).toEqual({ extend: [], exclude: [] });
        }
    });
    it('omits stage4Baseline from parsed config when not provided', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {},
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.review.stage4Baseline).toBeUndefined();
        }
    });
    it('rejects allowlist key under stage4Baseline (naming-discipline guard, mmnto-ai/totem#1683)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: {
                    allowlist: ['**/legacy/**'],
                },
            },
        });
        expect(result.success).toBe(false);
        if (!result.success) {
            const messages = result.error.issues.map((i) => i.message).join('\n');
            expect(messages).toMatch(/baseline/);
            expect(messages).toMatch(/1683/);
        }
    });
    it('rejects allowlist key even when extend is also present', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: {
                    extend: ['**/foo/**'],
                    allowlist: ['**/legacy/**'],
                },
            },
        });
        expect(result.success).toBe(false);
    });
    it('rejects extend with non-string elements', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: { extend: [42, '**/foo/**'] },
            },
        });
        expect(result.success).toBe(false);
    });
    it('rejects exclude with non-string elements', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: { exclude: [{ glob: '**/foo/**' }] },
            },
        });
        expect(result.success).toBe(false);
    });
    it('rejects unknown keys like typos (CR mmnto-ai/totem#1766 R1)', () => {
        // `exlcude` (transposed) silently passed through under the original
        // `.passthrough()` schema; now caught at parse time so misconfiguration
        // surfaces immediately instead of becoming load-bearing-but-ignored.
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: { exlcude: ['**/foo/**'] },
            },
        });
        expect(result.success).toBe(false);
    });
    it('rejects unknown keys like extends/excludes plurals (CR mmnto-ai/totem#1766 R1)', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: { extends: ['**/foo/**'], excludes: ['**/bar/**'] },
            },
        });
        expect(result.success).toBe(false);
    });
    it('continues to reject allowlist with the naming-discipline message even alongside unknown keys', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            review: {
                stage4Baseline: { allowlist: ['**/foo/**'], typo: ['**/bar/**'] },
            },
        });
        expect(result.success).toBe(false);
        if (!result.success) {
            const messages = result.error.issues.map((i) => i.message).join('\n');
            expect(messages).toMatch(/baseline/);
            expect(messages).toMatch(/1683/);
        }
    });
});
describe('GarbageCollectionSchema', () => {
    it('rejects garbage collection config with negative minAgeDays', () => {
        const result = GarbageCollectionSchema.safeParse({ minAgeDays: -1 });
        expect(result.success).toBe(false);
    });
    it('accepts garbage collection with defaults', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            garbageCollection: {},
        });
        expect(result.success).toBe(true);
        if (result.success) {
            const gc = result.data.garbageCollection;
            expect(gc.enabled).toBe(true);
            expect(gc.minAgeDays).toBe(90);
            expect(gc.exemptCategories).toEqual(['security']);
        }
    });
    it('accepts custom garbage collection config', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            garbageCollection: {
                enabled: false,
                minAgeDays: 30,
                exemptCategories: ['security', 'architecture'],
            },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            const gc = result.data.garbageCollection;
            expect(gc.enabled).toBe(false);
            expect(gc.minAgeDays).toBe(30);
            expect(gc.exemptCategories).toEqual(['security', 'architecture']);
        }
    });
    it('config without garbageCollection is valid', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.garbageCollection).toBeUndefined();
        }
    });
});
// ─── DoctorConfigSchema (mmnto-ai/totem#1483) ────────
describe('DoctorConfigSchema', () => {
    it('parses doctor config with default staleRuleWindow', () => {
        const defaulted = DoctorConfigSchema.parse({});
        expect(defaulted.staleRuleWindow).toBe(10);
    });
    it('accepts an explicit staleRuleWindow override', () => {
        const overridden = DoctorConfigSchema.parse({ staleRuleWindow: 20 });
        expect(overridden.staleRuleWindow).toBe(20);
    });
    it('rejects staleRuleWindow below 1', () => {
        const result = DoctorConfigSchema.safeParse({ staleRuleWindow: 0 });
        expect(result.success).toBe(false);
        if (!result.success) {
            expect(result.error.issues.some((issue) => issue.path.join('.') === 'staleRuleWindow' && issue.code === 'too_small')).toBe(true);
        }
    });
    it('rejects a negative staleRuleWindow', () => {
        const result = DoctorConfigSchema.safeParse({ staleRuleWindow: -1 });
        expect(result.success).toBe(false);
        if (!result.success) {
            expect(result.error.issues.some((issue) => issue.path.join('.') === 'staleRuleWindow' && issue.code === 'too_small')).toBe(true);
        }
    });
    it('TotemConfigSchema treats doctor as optional; missing value is undefined', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.doctor).toBeUndefined();
        }
    });
    it('TotemConfigSchema fills DoctorConfigSchema defaults when doctor is present but empty', () => {
        const result = TotemConfigSchema.safeParse({ targets: BASE_TARGETS, doctor: {} });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.doctor).toEqual({ staleRuleWindow: 10 });
        }
    });
    it('TotemConfigSchema accepts a doctor.staleRuleWindow override', () => {
        const result = TotemConfigSchema.safeParse({
            targets: BASE_TARGETS,
            doctor: { staleRuleWindow: 50 },
        });
        expect(result.success).toBe(true);
        if (result.success) {
            expect(result.data.doctor?.staleRuleWindow).toBe(50);
        }
    });
});
//# sourceMappingURL=config-schema.test.js.map