import * as fs from 'fs';
import { promises as fsp } from 'fs';
import * as path from 'path';
import { PatternRegistry } from './pattern-registry.js';
import { RuntimeRegistry, resolveNexusStateDir, } from './runtime-registry.js';
import { RagCollectionStore } from './rag-collections.js';
import { TokenSupremacyEngine } from './token-supremacy.js';
const MAX_RUNTIME_SESSION_SNAPSHOTS = 20;
export class KnowledgeFabricEngine {
    repoRoot;
    stateDir;
    tokenEngine;
    runtimeRegistry;
    ragCollections;
    patternRegistry;
    memory;
    bundleCache = new Map();
    snapshotCache = new Map();
    constructor(options = {}) {
        this.repoRoot = options.repoRoot ?? process.cwd();
        this.stateDir = path.join(options.stateRoot ?? resolveNexusStateDir(), 'knowledge-fabric');
        this.tokenEngine = options.tokenEngine ?? new TokenSupremacyEngine();
        this.runtimeRegistry = options.runtimeRegistry ?? new RuntimeRegistry(options.stateRoot);
        this.ragCollections = options.ragCollections ?? new RagCollectionStore(options.stateRoot);
        this.patternRegistry = options.patternRegistry ?? new PatternRegistry(options.stateRoot);
        this.memory = options.memory;
        // eslint-disable-next-line no-restricted-syntax -- constructor sync mkdir is startup-only, not hot path
        fs.mkdirSync(this.stateDir, { recursive: true });
    }
    async compose(input) {
        const stage = this.determineStage(input);
        const cacheKey = this.buildCacheKey(input, stage);
        const cached = this.bundleCache.get(cacheKey);
        if (cached) {
            const bundle = {
                ...cached,
                generatedAt: Date.now(),
                cache: {
                    key: cacheKey,
                    hit: true,
                    reused: true,
                },
            };
            await this.persistBundle(bundle);
            return bundle;
        }
        const bundle = await this.composeStage(input, stage, cacheKey);
        this.bundleCache.set(cacheKey, {
            ...bundle,
            cache: {
                key: cacheKey,
                hit: false,
                reused: false,
            },
        });
        await this.persistBundle(bundle);
        return bundle;
    }
    async composeStage(input, stage, cacheKey) {
        const runtimeSnapshot = input.runtimeSnapshot ?? this.runtimeRegistry.read(input.runtimeId);
        const fileRefs = (await Promise.all(input.candidateFiles.map((entry) => toFileRef(entry)))).filter((entry) => Boolean(entry));
        const readingPlan = fileRefs.length > 0 ? await this.tokenEngine.plan(input.task, fileRefs, input.fileBoosts) : undefined;
        const selectedFiles = readingPlan
            ? readingPlan.files.filter((file) => file.action !== 'skip').map((file) => file.file.path)
            : input.candidateFiles;
        const collectionSummaries = this.ragCollections.listCollections();
        const attachedCollections = collectionSummaries.filter((collection) => (collection.attachedRuntimeIds.includes(input.runtimeId)
            || collection.attachedSessionIds.includes(input.sessionId)));
        const attachedCollectionIds = attachedCollections.map((collection) => collection.collectionId);
        const ragHits = attachedCollectionIds.length === 0
            ? []
            : this.ragCollections.retrieve(input.task, {
                collectionIds: attachedCollectionIds,
                limit: stage === 'a' ? 1 : (stage === 'b' ? 3 : 6),
            });
        const selectedPatterns = this.patternRegistry.search(input.task, stage === 'a' ? 1 : (stage === 'b' ? 3 : 5));
        const shortlistPatterns = this.patternRegistry.list().slice(0, stage === 'a' ? 2 : (stage === 'b' ? 4 : 6));
        // Compute memory quality factor from quality metadata if available
        let memoryQuality = 1.0;
        if (input.memoryMatchesWithQuality && input.memoryMatchesWithQuality.length > 0) {
            const matches = input.memoryMatchesWithQuality;
            const avgConfidence = matches.reduce((s, m) => s + m.confidence, 0) / matches.length;
            const avgTrust = matches.reduce((s, m) => s + m.trust, 0) / matches.length;
            const avgEntropy = matches.reduce((s, m) => s + m.entropy, 0) / matches.length;
            memoryQuality = Math.max(0.2, avgConfidence * avgTrust * (1 - avgEntropy * 0.5));
        }
        const sourceMix = this.buildSourceMix({
            repoFiles: input.candidateFiles.length,
            memoryMatches: input.memoryMatches.length,
            memoryQuality,
            ragHits: ragHits.length,
            patternHits: selectedPatterns.length,
            runtimeSnapshot,
        });
        const tokenBudget = this.allocateTokenBudget({
            totalBudget: input.tokenBudget ?? 12_000,
            readingPlan,
            memoryMatches: input.memoryMatches,
            ragHits,
            selectedPatterns,
            runtimeSnapshot,
        });
        const recommendations = this.buildRecommendations(selectedPatterns, runtimeSnapshot, ragHits.length > 0, input.task);
        const modelTierPolicy = this.buildModelTierPolicy();
        const modelTierTrace = this.buildModelTierTrace(input, sourceMix);
        const provenance = this.buildProvenance({
            candidateFiles: input.candidateFiles,
            selectedFiles,
            memoryMatches: input.memoryMatches,
            ragHits,
            selectedPatterns,
            runtimeSnapshot,
        });
        const bundle = {
            runtimeId: input.runtimeId,
            sessionId: input.sessionId,
            task: input.task,
            generatedAt: Date.now(),
            stage,
            cache: {
                key: cacheKey,
                hit: false,
                reused: false,
            },
            sourceMix,
            tokenBudget,
            provenance,
            modelTierPolicy,
            modelTierTrace,
            repo: {
                candidateFiles: input.candidateFiles,
                selectedFiles,
                readingPlan,
            },
            memory: {
                matches: input.memoryMatches.slice(0, 8),
            },
            rag: {
                attachedCollections,
                hits: ragHits,
            },
            patterns: {
                selected: selectedPatterns,
                shortlist: shortlistPatterns,
            },
            runtime: {
                priorObjectives: runtimeSnapshot?.orchestration?.objectiveHistory?.slice(0, 8) ?? [],
                skipReasons: runtimeSnapshot?.skipReasons ?? [],
                lastToolCalls: runtimeSnapshot?.lastToolCalls ?? [],
                lastRunId: runtimeSnapshot?.latestRun?.runId,
            },
            recommendations,
            summary: this.summarizeBundle(stage, sourceMix, selectedFiles, ragHits, selectedPatterns, modelTierTrace),
        };
        return bundle;
    }
    determineStage(input) {
        if (input.stageHint)
            return input.stageHint;
        const riskClass = input.intent?.riskClass ?? 'medium';
        const complexity = input.intent?.complexity ?? 3;
        const taskType = input.intent?.taskType ?? 'feature';
        const candidateCount = input.candidateFiles.length;
        if (riskClass === 'low' && complexity <= 2 && candidateCount <= 6 && taskType === 'review') {
            return 'a';
        }
        if (riskClass === 'high'
            || complexity >= 4
            || candidateCount >= 10
            || ['bugfix', 'feature', 'refactor', 'ops', 'release', 'frontend', 'backend', 'ai', 'test'].includes(taskType)) {
            return 'c';
        }
        return 'b';
    }
    buildCacheKey(input, stage) {
        return JSON.stringify({
            runtimeId: input.runtimeId,
            sessionId: input.sessionId,
            stage,
            task: input.task,
            candidateFiles: input.candidateFiles,
            memoryMatches: input.memoryMatches.slice(0, 8),
            taskType: input.intent?.taskType,
            riskClass: input.intent?.riskClass,
            complexity: input.intent?.complexity,
        });
    }
    listCollections() {
        return this.ragCollections.listCollections();
    }
    getCollection(collectionId) {
        return this.ragCollections.getCollection(collectionId);
    }
    createCollection(input) {
        return this.ragCollections.createCollection(input);
    }
    ingestCollection(collectionId, inputs) {
        return this.ragCollections.ingestCollection(collectionId, inputs);
    }
    attachCollection(collectionId, runtimeId, sessionId) {
        return this.ragCollections.attachCollection(collectionId, runtimeId, sessionId);
    }
    detachCollection(collectionId, runtimeId, sessionId) {
        return this.ragCollections.detachCollection(collectionId, runtimeId, sessionId);
    }
    deleteCollection(collectionId) {
        return this.ragCollections.deleteCollection(collectionId);
    }
    async ensureBootstrapCollection(input) {
        const collectionSummaries = this.ragCollections.listCollections();
        const attached = collectionSummaries.filter((collection) => collection.attachedRuntimeIds.includes(input.runtimeId)
            || collection.attachedSessionIds.includes(input.sessionId));
        if (attached.some((collection) => collection.chunkCount > 0)) {
            return attached[0];
        }
        const existing = collectionSummaries.find((collection) => collection.scope === 'project'
            && collection.tags.includes('bootstrap-seeded'));
        const collection = existing ?? this.ragCollections.createCollection({
            name: 'Project bootstrap context',
            description: 'Auto-seeded project corpus for first-run orchestration grounding.',
            tags: ['bootstrap-seeded', 'project-context', 'rag'],
            scope: 'project',
        });
        const fullCollection = this.ragCollections.getCollection(collection.collectionId);
        const existingLocations = new Set((fullCollection?.sources ?? [])
            .map((source) => source.location)
            .filter((location) => Boolean(location))
            .map((location) => path.resolve(location)));
        const existingTextLabels = new Set((fullCollection?.sources ?? [])
            .filter((source) => source.kind === 'text')
            .map((source) => source.label));
        const seedCandidates = dedupePaths([
            path.join(this.repoRoot, 'README.md'),
            path.join(this.repoRoot, 'package.json'),
            ...input.candidateFiles,
        ]);
        const seedReadable = await Promise.all(seedCandidates.map((target) => isReadableBootstrapSeed(target)));
        const seedPaths = seedCandidates.filter((_, i) => seedReadable[i]).slice(0, 6);
        const ingestInputs = seedPaths
            .filter((target) => !existingLocations.has(path.resolve(target)))
            .map((target) => ({
            filePath: target,
            label: path.relative(this.repoRoot, target) || path.basename(target),
            tags: ['bootstrap-seeded', 'project-context'],
        }));
        const workspaceProfile = await buildWorkspaceProfileSeed(this.repoRoot);
        if (workspaceProfile && !existingTextLabels.has('workspace-profile')) {
            ingestInputs.unshift({
                text: workspaceProfile,
                label: 'workspace-profile',
                tags: ['bootstrap-seeded', 'project-context', 'workspace-profile'],
            });
        }
        if (ingestInputs.length > 0) {
            await this.ragCollections.ingestCollection(collection.collectionId, ingestInputs);
        }
        const attachedCollection = this.ragCollections.attachCollection(collection.collectionId, input.runtimeId, input.sessionId);
        return this.ragCollections.listCollections().find((entry) => entry.collectionId === attachedCollection.collectionId);
    }
    listPatterns() {
        return this.patternRegistry.list();
    }
    searchPatterns(query, limit = 6) {
        return this.patternRegistry.search(query, limit);
    }
    recordPatternOutcome(patternId, success) {
        this.patternRegistry.recordOutcome(patternId, success);
    }
    getSessionSnapshot(runtimeId) {
        // Serve from in-memory cache written by persistBundle — avoids disk read on hot path.
        // Falls back to disk only if the snapshot was persisted by a previous process instance.
        const cached = this.snapshotCache.get(runtimeId);
        if (cached)
            return cached;
        const target = path.join(this.stateDir, runtimeId, 'latest.json');
        // eslint-disable-next-line no-restricted-syntax -- cold-start fallback, runs at most once per runtimeId per process
        if (!fs.existsSync(target))
            return undefined;
        try {
            // eslint-disable-next-line no-restricted-syntax -- cold-start fallback, populates snapshotCache so subsequent calls are free
            const snapshot = JSON.parse(fs.readFileSync(target, 'utf8'));
            this.snapshotCache.set(runtimeId, snapshot);
            return snapshot;
        }
        catch {
            return undefined;
        }
    }
    getProvenance(runtimeId) {
        return this.getSessionSnapshot(runtimeId)?.provenance ?? { entries: [] };
    }
    getTokensBySource(runtimeId) {
        return this.runtimeRegistry.read(runtimeId)?.tokens?.bySourceClass ?? {};
    }
    getModelTiers(runtimeId) {
        const snapshot = this.getSessionSnapshot(runtimeId);
        return {
            policy: snapshot?.modelTierPolicy,
            trace: snapshot?.modelTierTrace ?? [],
        };
    }
    async persistBundle(bundle) {
        const snapshot = {
            runtimeId: bundle.runtimeId,
            sessionId: bundle.sessionId,
            generatedAt: bundle.generatedAt,
            task: bundle.task,
            stage: bundle.stage,
            cache: bundle.cache,
            sourceMix: bundle.sourceMix,
            tokenBudget: bundle.tokenBudget,
            attachedCollections: bundle.rag.attachedCollections,
            patternHits: bundle.patterns.selected.map((pattern) => ({
                patternId: pattern.patternId,
                name: pattern.name,
                score: pattern.score,
            })),
            selectedFiles: bundle.repo.selectedFiles,
            candidateFiles: bundle.repo.candidateFiles,
            recommendations: bundle.recommendations,
            modelTierPolicy: bundle.modelTierPolicy,
            modelTierTrace: bundle.modelTierTrace,
            provenance: bundle.provenance,
            summary: bundle.summary,
        };
        // Update in-memory cache immediately so getSessionSnapshot has zero disk latency.
        this.snapshotCache.set(bundle.runtimeId, snapshot);
        try {
            await this.persistBundleAsync(bundle.runtimeId, bundle.sessionId, snapshot);
        }
        catch {
            // Snapshot persistence is best effort; the in-memory cache remains authoritative in-process.
        }
    }
    async persistBundleAsync(runtimeId, sessionId, snapshot) {
        const runtimeDir = path.join(this.stateDir, runtimeId);
        await fsp.mkdir(runtimeDir, { recursive: true });
        const serialized = JSON.stringify(snapshot, null, 2);
        await Promise.all([
            fsp.writeFile(path.join(runtimeDir, 'latest.json'), serialized, 'utf8'),
            fsp.writeFile(path.join(runtimeDir, `${sessionId}.json`), serialized, 'utf8'),
        ]);
        await this.pruneRuntimeSnapshotsAsync(runtimeDir);
    }
    async pruneRuntimeSnapshotsAsync(runtimeDir) {
        let entries;
        try {
            entries = await fsp.readdir(runtimeDir);
        }
        catch {
            return;
        }
        const candidates = entries.filter((entry) => entry.endsWith('.json') && entry !== 'latest.json');
        if (candidates.length <= MAX_RUNTIME_SESSION_SNAPSHOTS)
            return;
        const withMtimes = await Promise.all(candidates.map(async (entry) => {
            try {
                const stat = await fsp.stat(path.join(runtimeDir, entry));
                return { entry, mtimeMs: stat.mtimeMs };
            }
            catch {
                return { entry, mtimeMs: 0 };
            }
        }));
        const stale = withMtimes.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_RUNTIME_SESSION_SNAPSHOTS);
        await Promise.all(stale.map(({ entry }) => fsp.unlink(path.join(runtimeDir, entry)).catch(() => { })));
    }
    buildSourceMix(input) {
        // Memory quality factor: scales memory weight by avg confidence × trust, penalizes high entropy
        const mq = input.memoryQuality ?? 1.0;
        const rawWeights = {
            repo: input.repoFiles > 0 ? 4 + Math.min(input.repoFiles, 6) : 0,
            memory: input.memoryMatches > 0 ? (3 + Math.min(input.memoryMatches, 4)) * mq : 0,
            rag: input.ragHits > 0 ? 3 + Math.min(input.ragHits, 4) : 0,
            patterns: input.patternHits > 0 ? 2 + Math.min(input.patternHits, 3) : 0,
            runtime: input.runtimeSnapshot?.executionLedger ? 2 + Math.min((input.runtimeSnapshot.lastToolCalls ?? []).length, 3) : 0,
        };
        const total = Object.values(rawWeights).reduce((sum, value) => sum + value, 0) || 1;
        const weights = Object.fromEntries(Object.entries(rawWeights).map(([key, value]) => [key, Number((value / total).toFixed(2))]));
        const dominantSource = (Object.entries(weights).sort((left, right) => right[1] - left[1])[0]?.[0] ?? 'repo');
        const reasons = [
            weights.repo > 0 ? `${Math.round(weights.repo * 100)}% repo weight from candidate-file discovery.` : 'Repo discovery contributed no files.',
            weights.memory > 0 ? `${Math.round(weights.memory * 100)}% memory weight from recalled learnings.` : 'Memory recall was low-signal.',
            weights.rag > 0 ? `${Math.round(weights.rag * 100)}% RAG weight from attached collection hits.` : 'No attached RAG collections matched.',
            weights.patterns > 0 ? `${Math.round(weights.patterns * 100)}% pattern weight from reusable orchestration cards.` : 'Pattern registry added no matching cards.',
            weights.runtime > 0 ? `${Math.round(weights.runtime * 100)}% runtime weight from prior ledgers and packet traces.` : 'No runtime history influenced this run.',
        ];
        return { dominantSource, weights, reasons };
    }
    allocateTokenBudget(input) {
        const base = {
            repo: input.readingPlan ? Math.min(input.totalBudget * 0.42, input.readingPlan.totalEstimatedTokens) : Math.round(input.totalBudget * 0.34),
            memory: Math.min(input.totalBudget * 0.16, input.memoryMatches.reduce((sum, entry) => sum + estimateTokens(entry), 0)),
            rag: Math.min(input.totalBudget * 0.2, input.ragHits.reduce((sum, entry) => sum + entry.tokens, 0)),
            patterns: Math.min(input.totalBudget * 0.12, input.selectedPatterns.reduce((sum, entry) => sum + estimateTokens(entry.summary), 0)),
            runtime: Math.min(input.totalBudget * 0.1, estimateTokens(JSON.stringify({
                objectiveHistory: input.runtimeSnapshot?.orchestration?.objectiveHistory ?? [],
                skipReasons: input.runtimeSnapshot?.skipReasons ?? [],
                lastToolCalls: input.runtimeSnapshot?.lastToolCalls ?? [],
            }))),
        };
        const normalized = normalizeBudget(base, input.totalBudget);
        const dropped = [];
        if (!input.readingPlan) {
            dropped.push({ sourceClass: 'repo', label: 'repo-plan', reason: 'no-readable-candidate-files' });
        }
        if (input.ragHits.length === 0) {
            dropped.push({ sourceClass: 'rag', label: 'rag-hits', reason: 'no-attached-collection-matches' });
        }
        if (input.selectedPatterns.length === 0) {
            dropped.push({ sourceClass: 'patterns', label: 'pattern-cards', reason: 'no-pattern-matches' });
        }
        return {
            totalBudget: input.totalBudget,
            bySource: normalized,
            byStage: {
                bootstrap: Math.round(input.totalBudget * 0.18),
                planning: Math.round(input.totalBudget * 0.24),
                mutation: Math.round(input.totalBudget * 0.3),
                verification: Math.round(input.totalBudget * 0.18),
                continuation: Math.max(0, input.totalBudget - (Math.round(input.totalBudget * 0.18)
                    + Math.round(input.totalBudget * 0.24)
                    + Math.round(input.totalBudget * 0.3)
                    + Math.round(input.totalBudget * 0.18))),
            },
            dropped,
        };
    }
    buildRecommendations(selectedPatterns, runtimeSnapshot, hasRag = false, task) {
        const recommendations = {
            skills: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedSkills)),
            workflows: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedWorkflows)),
            hooks: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedHooks)),
            automations: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedAutomations)),
            crews: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedCrews)),
            specialists: dedupeStrings(selectedPatterns.flatMap((pattern) => pattern.suggestedSpecialists)),
        };
        const taskLower = task?.toLowerCase() ?? '';
        if (recommendations.skills.length === 0) {
            if (/\b(backend|api|server|runtime|orchestrat|synapse|architect)\b/.test(taskLower)) {
                recommendations.skills.push('repo-architecture-scout');
            }
            if (/\b(research|rag|retriev|embed|document)\b/.test(taskLower)) {
                recommendations.skills.push('browser-to-mission');
            }
            if (/\b(plan|roadmap|pm|spec|ticket|feature|scope)\b/.test(taskLower)) {
                recommendations.skills.push('systematic-planning');
            }
            if (/\b(bug|fix|error|crash|broken|issue|debug)\b/.test(taskLower)) {
                recommendations.skills.push('mission-postmortem');
            }
            if (/\b(workflow|automat|pipeline|ci|deploy|release)\b/.test(taskLower)) {
                recommendations.skills.push('codex-real-workflow');
            }
        }
        if (recommendations.workflows.length === 0) {
            if (/\b(backend|api|implement|build|create|develop|extend)\b/.test(taskLower)) {
                recommendations.workflows.push('backend-execution-loop');
            }
            if (/\b(release|ship|publish|tag|version|deploy)\b/.test(taskLower)) {
                recommendations.workflows.push('release-pipeline');
            }
        }
        if (hasRag && !recommendations.workflows.includes('research-and-implement')) {
            recommendations.workflows = dedupeStrings(['research-and-implement', ...recommendations.workflows]);
        }
        if ((runtimeSnapshot?.orchestration?.repeatedFailures ?? 0) > 0 && !recommendations.automations.includes('failure-recovery-automation')) {
            recommendations.automations = dedupeStrings(['failure-recovery-automation', ...recommendations.automations]);
        }
        return recommendations;
    }
    buildModelTierPolicy() {
        return {
            lowTierStages: [
                'intent-classification',
                'candidate-file-discovery',
                'query-rewrite',
                'pattern-shortlist',
                'chunk-ranking',
                'compression-proposal',
                'eval-triage',
                'post-run-summary',
            ],
            highTierStages: [
                'orchestration-package',
                'risky-planning',
                'mutation-authority',
                'merge-apply',
                'governance-override',
                'promotion',
            ],
            escalationRules: [
                'Escalate to the high tier when riskClass is high.',
                'Escalate when pattern confidence is below 0.65.',
                'Escalate when RAG and runtime traces disagree with repo evidence.',
            ],
        };
    }
    buildModelTierTrace(input, sourceMix) {
        const highRisk = input.intent?.riskClass === 'high';
        const mixedContext = sourceMix.weights.rag > 0 && sourceMix.weights.repo > 0 && sourceMix.weights.runtime > 0;
        return [
            { stage: 'intent-classification', tier: 'low', reason: 'low-tier can classify task shape and ambiguity.', escalated: false },
            { stage: 'candidate-file-discovery', tier: 'low', reason: 'low-tier can rank likely repo hotspots.', escalated: false },
            { stage: 'pattern-shortlist', tier: 'low', reason: 'low-tier can filter reusable pattern cards.', escalated: false },
            {
                stage: 'orchestration-package',
                tier: 'high',
                reason: highRisk || mixedContext ? 'high tier selected because the run is risky or cross-source.' : 'high tier owns final package selection by policy.',
                escalated: highRisk || mixedContext,
            },
            {
                stage: 'mutation-authority',
                tier: 'high',
                reason: 'low-tier models never own mutation authority.',
                escalated: true,
            },
            {
                stage: 'merge-apply',
                tier: 'high',
                reason: 'merge/apply is guarded and stays on the high tier.',
                escalated: true,
            },
        ];
    }
    buildProvenance(input) {
        const selectedFilesSet = new Set(input.selectedFiles);
        const entries = [
            ...input.candidateFiles.map((filePath) => ({
                sourceClass: 'repo',
                id: filePath,
                label: path.relative(process.cwd(), filePath) || filePath,
                summary: selectedFilesSet.has(filePath) ? 'Selected by repo reading plan.' : 'Candidate repo file dropped by budgeting.',
                tokens: estimateTokens(filePath),
                stage: 'bootstrap',
                selected: selectedFilesSet.has(filePath),
                reason: selectedFilesSet.has(filePath) ? 'repo-reading-plan' : 'repo-budget-drop',
                score: selectedFilesSet.has(filePath) ? 10 : 3,
            })),
            ...input.memoryMatches.slice(0, 8).map((match, index) => ({
                sourceClass: 'memory',
                id: `memory_${index + 1}`,
                label: `memory-${index + 1}`,
                summary: match,
                tokens: estimateTokens(match),
                stage: 'bootstrap',
                selected: true,
                reason: 'recalled-memory',
                score: 8,
            })),
            ...input.ragHits.map((hit) => ({
                sourceClass: 'rag',
                id: hit.chunkId,
                label: `${hit.collectionName}: ${hit.label}`,
                summary: hit.text.slice(0, 240),
                tokens: hit.tokens,
                stage: 'planning',
                selected: true,
                reason: 'attached-collection-hit',
                score: hit.score,
            })),
            ...input.selectedPatterns.map((pattern) => ({
                sourceClass: 'patterns',
                id: pattern.patternId,
                label: pattern.name,
                summary: pattern.summary,
                tokens: estimateTokens(pattern.summary),
                stage: 'planning',
                selected: true,
                reason: 'pattern-shortlist',
                score: pattern.score,
            })),
        ];
        if (input.runtimeSnapshot) {
            entries.push({
                sourceClass: 'runtime',
                id: input.runtimeSnapshot.latestRun?.runId ?? input.runtimeSnapshot.runtimeId,
                label: 'runtime-trace',
                summary: `Last tool chain: ${(input.runtimeSnapshot.lastToolCalls ?? []).join(' -> ') || 'none'}`,
                tokens: estimateTokens(JSON.stringify({
                    objectiveHistory: input.runtimeSnapshot.orchestration?.objectiveHistory ?? [],
                    skipReasons: input.runtimeSnapshot.skipReasons ?? [],
                })),
                stage: 'planning',
                selected: true,
                reason: 'runtime-ledger-trace',
                score: 7,
            });
        }
        return { entries };
    }
    summarizeBundle(stage, sourceMix, selectedFiles, ragHits, patterns, modelTierTrace) {
        const lowTierStages = modelTierTrace.filter((stage) => stage.tier === 'low').map((stage) => stage.stage);
        return [
            `Stage ${stage.toUpperCase()}.`,
            `Dominant source: ${sourceMix.dominantSource}.`,
            `${selectedFiles.length} repo files retained after source-aware budgeting.`,
            ragHits.length > 0 ? `${ragHits.length} attached RAG chunk(s) grounded the plan.` : 'No RAG chunks contributed to this run.',
            patterns.length > 0 ? `${patterns.length} pattern card(s) shaped orchestration.` : 'Pattern registry added no matching cards.',
            `Low-tier stages: ${lowTierStages.join(', ') || 'none'}.`,
        ].join(' ');
    }
}
function normalizeBudget(base, totalBudget) {
    const total = Object.values(base).reduce((sum, value) => sum + value, 0) || 1;
    const normalized = Object.fromEntries(Object.entries(base).map(([key, value]) => [key, Math.round((value / total) * totalBudget)]));
    const used = Object.values(normalized).reduce((sum, value) => sum + value, 0);
    const delta = totalBudget - used;
    if (delta !== 0) {
        normalized.repo = Math.max(0, normalized.repo + delta);
    }
    return normalized;
}
async function buildWorkspaceProfileSeed(repoRoot) {
    const repoName = path.basename(repoRoot) || 'workspace';
    const packageName = await readPackageName(repoRoot);
    const topLevel = (await safeListDir(repoRoot))
        .slice(0, 10)
        .map((entry) => entry.name)
        .join(', ');
    const focusAreaCandidates = ['src/synapse', 'src/architects', 'src/dashboard', 'src/phantom', 'src/engines', 'test', 'docs'];
    const focusAreaExists = await Promise.all(focusAreaCandidates.map((relativePath) => fsp.access(path.join(repoRoot, relativePath)).then(() => true, () => false)));
    const focusAreas = focusAreaCandidates.filter((_, i) => focusAreaExists[i]).join(', ');
    return [
        `Workspace profile: ${repoName}${packageName && packageName !== repoName ? ` (package ${packageName})` : ''}.`,
        topLevel ? `Top-level entries: ${topLevel}.` : null,
        focusAreas ? `Key areas: ${focusAreas}.` : null,
    ].filter(Boolean).join(' ');
}
async function safeListDir(target) {
    try {
        const entries = await fsp.readdir(target, { withFileTypes: true });
        return entries
            .filter((entry) => !entry.name.startsWith('.'))
            .map((entry) => ({ name: entry.name }));
    }
    catch {
        return [];
    }
}
async function readPackageName(repoRoot) {
    const packageJsonPath = path.join(repoRoot, 'package.json');
    try {
        const parsed = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
        return typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim() : null;
    }
    catch {
        return null;
    }
}
async function toFileRef(filePath) {
    try {
        const stat = await fsp.stat(filePath);
        return {
            path: filePath,
            sizeBytes: stat.size,
            lastModified: stat.mtimeMs,
        };
    }
    catch {
        return undefined;
    }
}
function dedupePaths(values) {
    return [...new Set(values.map((value) => path.resolve(value)).filter(Boolean))];
}
function dedupeStrings(values) {
    return [...new Set(values.filter(Boolean))];
}
function estimateTokens(value) {
    return Math.max(1, Math.ceil(String(value || '').length / 4));
}
async function isReadableBootstrapSeed(target) {
    try {
        const stat = await fsp.stat(target);
        if (!stat.isFile() || stat.size > 96_000) {
            return false;
        }
        return /\.(md|txt|json|ya?ml|ts|tsx|js|jsx|mjs|cjs)$/i.test(target);
    }
    catch {
        return false;
    }
}
