import path from "node:path";
import { createHash } from "node:crypto";
import { execFile as execFileCallback } from "node:child_process";
import { promisify } from "node:util";
import { ensureDir, pathExists, readJson, readText, relativeFrom, writeText } from "../utils/fs.js";
import { getRuntimeRepoGraph, getRuntimeSnapshot, persistRuntimeRepoGraph } from "../runtime/client.js";
import { classifyFileArea, deriveTaskArea, deriveTaskCategory, tokenizeTaskText } from "./task-intent.js";
import { loadCurrentFocus, loadOpenRisks } from "./memory.js";
import { loadCurrentPhase, loadLatestCheckpoint, loadLatestHandoff, loadLatestTaskPacketSet } from "./state.js";
import { loadLatestReconcileReport } from "./reconcile.js";
const execFile = promisify(execFileCallback);
const HISTORY_COMMIT_WINDOW = 40;
export async function buildRepoIntelligenceArtifacts(options) {
    const generatedAt = options.tree.timestamp;
    const repoMap = buildRepoMap(options.tree, options.view, options.index, generatedAt);
    const impactMap = buildImpactMap(options.index, generatedAt, repoMap);
    const decisionGraph = await buildDecisionGraph(options.targetRoot, options.index, generatedAt);
    const historyGraph = await buildHistoryGraph(options.targetRoot, options.index, generatedAt);
    const reviewGraph = buildReviewGraph({
        repoMap,
        impactMap,
        decisionGraph,
        historyGraph
    }, generatedAt);
    return {
        repoMap,
        symbolIndex: buildSymbolIndex(options.index, generatedAt),
        dependencyGraph: buildDependencyGraph(options.index, generatedAt),
        impactMap,
        decisionGraph,
        historyGraph,
        reviewGraph
    };
}
export async function persistRepoIntelligenceArtifacts(targetRoot, artifacts) {
    const results = await Promise.all([
        writeJsonArtifact(repoMapPath(targetRoot), artifacts.repoMap),
        writeJsonArtifact(symbolIndexPath(targetRoot), artifacts.symbolIndex),
        writeJsonArtifact(dependencyGraphPath(targetRoot), artifacts.dependencyGraph),
        writeJsonArtifact(impactMapPath(targetRoot), artifacts.impactMap),
        writeJsonArtifact(decisionGraphPath(targetRoot), artifacts.decisionGraph),
        writeJsonArtifact(historyGraphPath(targetRoot), artifacts.historyGraph),
        writeJsonArtifact(reviewGraphPath(targetRoot), artifacts.reviewGraph)
    ]);
    await persistRepoGraphAuthority(targetRoot, artifacts);
    return results;
}
export async function persistRepoIntelligenceIndexArtifacts(targetRoot, index) {
    const generatedAt = index.timestamp;
    const repoMap = await readJsonIfPresent(repoMapPath(targetRoot));
    return Promise.all([
        writeJsonArtifact(symbolIndexPath(targetRoot), buildSymbolIndex(index, generatedAt)),
        writeJsonArtifact(dependencyGraphPath(targetRoot), buildDependencyGraph(index, generatedAt)),
        writeJsonArtifact(impactMapPath(targetRoot), buildImpactMap(index, generatedAt, repoMap))
    ]);
}
export async function loadRepoIntelligenceArtifacts(targetRoot) {
    const runtimeGraph = await loadRepoGraphAuthority(targetRoot);
    if (runtimeGraph) {
        return runtimeGraph;
    }
    const [repoMap, symbolIndex, dependencyGraph, impactMap, decisionGraph, historyGraph, reviewGraph, compactContextPack, reviewContextPack, agentPack, taskPack] = await Promise.all([
        readJsonIfPresent(repoMapPath(targetRoot)),
        readJsonIfPresent(symbolIndexPath(targetRoot)),
        readJsonIfPresent(dependencyGraphPath(targetRoot)),
        readJsonIfPresent(impactMapPath(targetRoot)),
        readJsonIfPresent(decisionGraphPath(targetRoot)),
        readJsonIfPresent(historyGraphPath(targetRoot)),
        readJsonIfPresent(reviewGraphPath(targetRoot)),
        readJsonIfPresent(compactContextPackPath(targetRoot)),
        readJsonIfPresent(reviewContextPackPath(targetRoot)),
        readJsonIfPresent(agentPackPath(targetRoot)),
        readJsonIfPresent(taskPackPath(targetRoot))
    ]);
    if (!repoMap || !symbolIndex || !dependencyGraph || !impactMap || !decisionGraph || !historyGraph || !reviewGraph) {
        return null;
    }
    return {
        repoMap,
        symbolIndex,
        dependencyGraph,
        impactMap,
        decisionGraph,
        historyGraph,
        reviewGraph,
        compactContextPack,
        reviewContextPack,
        agentPack,
        taskPack
    };
}
async function persistRepoGraphAuthority(targetRoot, artifacts) {
    const snapshot = await getRuntimeSnapshot(targetRoot).catch(() => null);
    await persistRuntimeRepoGraph({
        targetRoot,
        summary: artifacts.repoMap.summary,
        sourceKind: "kiwi-control/repo-intelligence",
        sourceRevision: snapshot?.revision ?? null,
        graph: artifacts,
        artifactPath: ".agent/context/repo-map.json",
        compatibilityHash: await compatibilityHashForArtifacts(targetRoot, artifacts),
        compatibilityArtifacts: {
            repoMap: ".agent/context/repo-map.json",
            symbolIndex: ".agent/state/symbol-index.json",
            dependencyGraph: ".agent/state/dependency-graph.json",
            impactMap: ".agent/state/impact-map.json",
            decisionGraph: ".agent/state/decision-graph.json",
            historyGraph: ".agent/state/history-graph.json",
            reviewGraph: ".agent/state/review-graph.json"
        },
        ...toRuntimeNormalizedGraph(artifacts)
    });
}
async function loadRepoGraphAuthority(targetRoot) {
    const snapshot = await getRuntimeRepoGraph(targetRoot).catch(() => null);
    const graph = snapshot?.status.ready ? snapshot.graph : null;
    if (!graph || !isRepoGraphPayload(graph)) {
        return null;
    }
    const [compactContextPack, reviewContextPack, agentPack, taskPack] = await Promise.all([
        readJsonIfPresent(compactContextPackPath(targetRoot)),
        readJsonIfPresent(reviewContextPackPath(targetRoot)),
        readJsonIfPresent(agentPackPath(targetRoot)),
        readJsonIfPresent(taskPackPath(targetRoot))
    ]);
    return {
        ...graph,
        compactContextPack,
        reviewContextPack,
        agentPack,
        taskPack
    };
}
function isRepoGraphPayload(value) {
    return (value.repoMap?.artifactType === "kiwi-control/repo-map" &&
        value.symbolIndex?.artifactType === "kiwi-control/symbol-index" &&
        value.dependencyGraph?.artifactType === "kiwi-control/dependency-graph" &&
        value.impactMap?.artifactType === "kiwi-control/impact-map" &&
        value.decisionGraph?.artifactType === "kiwi-control/decision-graph" &&
        value.historyGraph?.artifactType === "kiwi-control/history-graph" &&
        value.reviewGraph?.artifactType === "kiwi-control/review-graph");
}
function toRuntimeNormalizedGraph(artifacts) {
    const nodeMap = new Map();
    const edgeMap = new Map();
    const moduleIds = new Set();
    for (const module of artifacts.repoMap.keyModules) {
        moduleIds.add(module.id);
        nodeMap.set(moduleNodeId(module.id), graphNode({
            nodeId: moduleNodeId(module.id),
            nodeKind: "module",
            moduleId: module.id,
            displayLabel: module.id,
            attributes: module
        }));
    }
    for (const relationship of artifacts.dependencyGraph.fileRelationships) {
        moduleIds.add(relationship.moduleGroup);
        const fileId = fileNodeId(relationship.file);
        nodeMap.set(fileId, graphNode({
            nodeId: fileId,
            nodeKind: "file",
            path: relationship.file,
            moduleId: relationship.moduleGroup,
            displayLabel: relationship.file,
            language: languageForFile(relationship.file),
            attributes: relationship
        }));
        addEdge(edgeMap, moduleNodeId(relationship.moduleGroup), fileId, "contains", 1, { moduleGroup: relationship.moduleGroup, file: relationship.file });
        for (const imported of relationship.imports) {
            addEdge(edgeMap, fileId, fileNodeId(imported), "imports", 1, { from: relationship.file, to: imported });
            const importedModule = artifacts.dependencyGraph.fileRelationships.find((entry) => entry.file === imported)?.moduleGroup;
            if (importedModule && importedModule !== relationship.moduleGroup) {
                addEdge(edgeMap, moduleNodeId(relationship.moduleGroup), moduleNodeId(importedModule), "imports", 0.8, {
                    fromModule: relationship.moduleGroup,
                    toModule: importedModule,
                    from: relationship.file,
                    to: imported
                });
            }
        }
        for (const callTarget of relationship.callTargets) {
            addEdge(edgeMap, fileId, fileNodeId(callTarget), "calls", 0.7, { from: relationship.file, to: callTarget });
        }
    }
    for (const symbol of artifacts.symbolIndex.symbols) {
        const symbolId = symbolNodeId(symbol.file, symbol.kind, symbol.symbol);
        nodeMap.set(symbolId, graphNode({
            nodeId: symbolId,
            nodeKind: "symbol",
            path: symbol.file,
            moduleId: symbol.moduleGroup,
            symbol: symbol.symbol,
            displayLabel: symbol.symbol,
            language: languageForFile(symbol.file),
            attributes: symbol
        }));
        addEdge(edgeMap, fileNodeId(symbol.file), symbolId, "defines", symbol.kind === "export" ? 1 : 0.7, symbol);
    }
    for (const [target, chain] of Object.entries(artifacts.impactMap.dependencyChains)) {
        for (let index = 0; index < chain.length - 1; index += 1) {
            addEdge(edgeMap, fileNodeId(chain[index] ?? target), fileNodeId(chain[index + 1] ?? target), "impacts", 1, { chain, target });
        }
    }
    for (const decision of artifacts.decisionGraph.importantDecisions) {
        const decisionId = stableNodeId("decision", decision.id);
        nodeMap.set(decisionId, graphNode({
            nodeId: decisionId,
            nodeKind: "decision",
            displayLabel: decision.label,
            attributes: decision
        }));
        for (const file of decision.relatedFiles) {
            addEdge(edgeMap, decisionId, fileNodeId(file), "decision-related-file", 0.8, { decision: decision.id, file });
        }
    }
    for (const risk of artifacts.reviewGraph.fileRisks) {
        addEdge(edgeMap, fileNodeId(risk.file), moduleNodeId(risk.moduleGroup), "review-risk", risk.score, {
            file: risk.file,
            score: risk.score,
            reasons: risk.reasons
        });
    }
    const modules = [...moduleIds].sort().map((moduleId) => ({
        moduleId,
        displayLabel: moduleId,
        summary: artifacts.repoMap.keyModules.find((entry) => entry.id === moduleId)?.entryPoints.join(", ") ?? null,
        attributes: artifacts.repoMap.keyModules.find((entry) => entry.id === moduleId) ?? { id: moduleId }
    }));
    return {
        nodes: [...nodeMap.values()],
        edges: [...edgeMap.values()],
        modules
    };
}
function graphNode(input) {
    return {
        path: null,
        moduleId: null,
        symbol: null,
        language: null,
        attributes: {},
        ...input
    };
}
function addEdge(edgeMap, fromNodeId, toNodeId, edgeKind, weight, evidence) {
    const edgeId = stableNodeId("edge", `${fromNodeId}:${edgeKind}:${toNodeId}`);
    edgeMap.set(edgeId, {
        edgeId,
        fromNodeId,
        toNodeId,
        edgeKind,
        weight,
        evidence
    });
}
function fileNodeId(file) {
    return stableNodeId("file", file);
}
function moduleNodeId(moduleId) {
    return stableNodeId("module", moduleId);
}
function symbolNodeId(file, kind, symbol) {
    return stableNodeId("symbol", `${file}:${kind}:${symbol}`);
}
function stableNodeId(kind, value) {
    return `${kind}:${value.replace(/[^A-Za-z0-9_.:/-]/g, "_")}`;
}
function languageForFile(file) {
    const ext = path.extname(file).slice(1);
    return ext || null;
}
async function compatibilityHashForArtifacts(targetRoot, artifacts) {
    const explicitAliasPath = path.join(targetRoot, ".agent", "context", "graph-aliases.json");
    const explicitAliasHash = await readText(explicitAliasPath)
        .then((value) => createHash("sha256").update(value).digest("hex"))
        .catch(() => "none");
    return `${artifacts.repoMap.generatedAt}:${artifacts.symbolIndex.totalSymbols}:${artifacts.dependencyGraph.edges.length}:${artifacts.impactMap.impactedFiles.length}:${explicitAliasHash}`;
}
export async function loadRepoIntelligenceSummary(targetRoot) {
    const [repoMap, impactMap, compactContextPack, reviewContextPack, reviewPack, agentPack, taskPack, hasSymbolIndex, hasDependencyGraph, hasDecisionGraph, hasHistoryGraph, hasReviewGraph, decisionGraph, historyGraph, reviewGraph] = await Promise.all([
        readJsonIfPresent(repoMapPath(targetRoot)),
        readJsonIfPresent(impactMapPath(targetRoot)),
        readJsonIfPresent(compactContextPackPath(targetRoot)),
        readJsonIfPresent(reviewContextPackPath(targetRoot)),
        readJsonIfPresent(path.join(targetRoot, ".agent", "context", "review-pack.json")),
        readJsonIfPresent(agentPackPath(targetRoot)),
        readJsonIfPresent(taskPackPath(targetRoot)),
        pathExists(symbolIndexPath(targetRoot)),
        pathExists(dependencyGraphPath(targetRoot)),
        pathExists(decisionGraphPath(targetRoot)),
        pathExists(historyGraphPath(targetRoot)),
        pathExists(reviewGraphPath(targetRoot)),
        readJsonIfPresent(decisionGraphPath(targetRoot)),
        readJsonIfPresent(historyGraphPath(targetRoot)),
        readJsonIfPresent(reviewGraphPath(targetRoot))
    ]);
    if (!repoMap || !impactMap || !hasSymbolIndex || !hasDependencyGraph || !hasDecisionGraph || !hasHistoryGraph || !hasReviewGraph) {
        return {
            available: false,
            generatedAt: null,
            summary: null,
            repoMapPath: null,
            symbolIndexPath: null,
            dependencyGraphPath: null,
            impactMapPath: null,
            entryPoints: [],
            keyModules: [],
            topReverseDependencyHubs: [],
            changedFiles: [],
            impactedFiles: [],
            decisionGraphPath: null,
            historyGraphPath: null,
            reviewGraphPath: null,
            importantDecisions: 0,
            hotspotFiles: 0,
            reviewRiskTargets: 0,
            agentPackAvailable: false,
            agentPackPath: null,
            agentPackSummary: null,
            taskPackAvailable: false,
            taskPackPath: null,
            taskPackTask: null,
            taskPackSummary: null,
            taskPackFiles: 0,
            compactContextPackAvailable: false,
            compactContextPackPath: null,
            compactContextPackMode: null,
            compactContextPackTask: null,
            compactContextPackSummary: null,
            compactContextPackFiles: 0,
            reviewContextPackAvailable: false,
            reviewContextPackPath: null,
            reviewContextPackTask: null,
            reviewContextPackSummary: null,
            reviewPackAvailable: false,
            reviewPackPath: null,
            reviewPackSummary: null
        };
    }
    return {
        available: true,
        generatedAt: repoMap.generatedAt,
        summary: repoMap.summary,
        repoMapPath: relativeFrom(targetRoot, repoMapPath(targetRoot)),
        symbolIndexPath: relativeFrom(targetRoot, symbolIndexPath(targetRoot)),
        dependencyGraphPath: relativeFrom(targetRoot, dependencyGraphPath(targetRoot)),
        impactMapPath: relativeFrom(targetRoot, impactMapPath(targetRoot)),
        decisionGraphPath: relativeFrom(targetRoot, decisionGraphPath(targetRoot)),
        historyGraphPath: relativeFrom(targetRoot, historyGraphPath(targetRoot)),
        reviewGraphPath: relativeFrom(targetRoot, reviewGraphPath(targetRoot)),
        entryPoints: repoMap.entryPoints,
        keyModules: repoMap.keyModules,
        topReverseDependencyHubs: repoMap.topReverseDependencyHubs,
        changedFiles: impactMap.changedFiles,
        impactedFiles: impactMap.impactedFiles,
        importantDecisions: decisionGraph?.importantDecisions.length ?? 0,
        hotspotFiles: historyGraph?.hotspotFiles.length ?? 0,
        reviewRiskTargets: reviewGraph?.reviewAttention.length ?? 0,
        agentPackAvailable: Boolean(agentPack),
        agentPackPath: agentPack ? relativeFrom(targetRoot, agentPackPath(targetRoot)) : null,
        agentPackSummary: agentPack?.summary ?? null,
        taskPackAvailable: Boolean(taskPack),
        taskPackPath: taskPack ? relativeFrom(targetRoot, taskPackPath(targetRoot)) : null,
        taskPackTask: taskPack?.task ?? null,
        taskPackSummary: taskPack?.summary ?? null,
        taskPackFiles: taskPack?.focus.files.length ?? 0,
        compactContextPackAvailable: Boolean(compactContextPack),
        compactContextPackPath: compactContextPack ? relativeFrom(targetRoot, compactContextPackPath(targetRoot)) : null,
        compactContextPackMode: compactContextPack?.mode ?? null,
        compactContextPackTask: compactContextPack?.task ?? null,
        compactContextPackSummary: compactContextPack?.summary ?? null,
        compactContextPackFiles: compactContextPack?.files.length ?? 0,
        reviewContextPackAvailable: Boolean(reviewContextPack),
        reviewContextPackPath: reviewContextPack ? relativeFrom(targetRoot, reviewContextPackPath(targetRoot)) : null,
        reviewContextPackTask: reviewContextPack?.currentTask ?? null,
        reviewContextPackSummary: reviewContextPack?.summary ?? null,
        reviewPackAvailable: Boolean(reviewPack),
        reviewPackPath: reviewPack ? ".agent/context/review-pack.json" : null,
        reviewPackSummary: reviewPack?.summary ?? null
    };
}
export function buildCompactContextPack(options) {
    const fileMap = new Map(options.index.files.map((entry) => [entry.file, entry]));
    const moduleMap = new Map();
    for (const entry of options.index.files) {
        const moduleFiles = moduleMap.get(entry.moduleGroup) ?? new Set();
        moduleFiles.add(entry.file);
        moduleMap.set(entry.moduleGroup, moduleFiles);
    }
    const impactMap = options.impactMap ?? buildImpactMap(options.index, options.index.timestamp, options.repoMap);
    const requestedFocus = uniqueStrings(options.focusTargets ?? []);
    const resolvedFocus = resolveFocusTargets(requestedFocus, fileMap, moduleMap);
    const focusFiles = resolvedFocus.focusFiles;
    const missingFocusFiles = resolvedFocus.missingFocusTargets;
    const mode = options.mode ?? (focusFiles.length > 0 ? "focus" : "overview");
    const limit = Math.max(4, Math.min(options.limit ?? 12, 24));
    const taskTokens = uniqueStrings(tokenizeTaskText(options.task ?? ""));
    const taskArea = options.task ? deriveTaskArea(options.task) : "unknown";
    const taskCategory = options.task ? deriveTaskCategory(options.task) : "general";
    const seedFiles = mode === "changed" && options.index.lastImpact.changedFiles.length > 0
        ? options.index.lastImpact.changedFiles.filter((file) => fileMap.has(file))
        : focusFiles.length > 0
            ? focusFiles
            : uniqueStrings([
                ...options.repoMap.entryPoints.slice(0, 4),
                ...options.repoMap.topReverseDependencyHubs.slice(0, 4).map((entry) => entry.file)
            ]).filter((file) => fileMap.has(file));
    const impactRanks = new Map(impactMap.rankedFiles.map((entry) => [entry.file, entry]));
    const ranking = new Map();
    const addRank = (file, score, reason, options = {}) => {
        if (!fileMap.has(file)) {
            return;
        }
        const current = ranking.get(file) ?? {
            score: 0,
            reasons: new Set(),
            matchedTerms: new Set(),
            matchedSymbols: new Set()
        };
        current.score += score;
        current.reasons.add(reason);
        if (options.matchedTerm) {
            current.matchedTerms.add(options.matchedTerm);
        }
        if (options.matchedSymbol) {
            current.matchedSymbols.add(options.matchedSymbol);
        }
        ranking.set(file, current);
    };
    for (const [file, impactRank] of impactRanks) {
        addRank(file, impactRank.score, impactRank.changed ? "impact rank (changed)" : "impact rank");
        for (const reason of impactRank.reasons) {
            addRank(file, impactRank.score, reason);
        }
    }
    for (const file of seedFiles) {
        addRank(file, 100, mode === "changed" ? "changed file" : "focus file");
    }
    for (const file of seedFiles) {
        const record = fileMap.get(file);
        if (!record) {
            continue;
        }
        for (const dependency of record.relationships) {
            addRank(dependency, 80, "direct dependency");
        }
        for (const dependent of options.index.reverseDependencies[file] ?? []) {
            addRank(dependent, 78, "reverse dependent");
        }
        for (const modulePeer of moduleMap.get(record.moduleGroup) ?? []) {
            if (modulePeer !== file) {
                addRank(modulePeer, 50, "shared module");
            }
        }
    }
    if (mode === "changed") {
        for (const impacted of options.index.lastImpact.impactedFiles) {
            const distance = options.index.lastImpact.dependencyDistances[impacted] ?? 3;
            addRank(impacted, 70 - distance, "impact chain");
        }
    }
    if (taskTokens.length > 0) {
        for (const [file, record] of fileMap) {
            const area = classifyFileArea(file);
            if (taskArea !== "unknown" && area === taskArea) {
                addRank(file, 52, "task area match");
            }
            else if (taskCategory === "docs" && area === "context") {
                addRank(file, 30, "docs context support");
            }
            else if (taskArea !== "unknown" && area !== "unknown") {
                addRank(file, 18, "cross-area support");
            }
            const pathWords = tokenizeTaskText(file.replace(/[/.]/g, " "));
            const pathMatches = taskTokens.filter((token) => pathWords.includes(token) || record.moduleGroup.toLowerCase().includes(token));
            for (const token of pathMatches.slice(0, 4)) {
                addRank(file, 62, "task term match", { matchedTerm: token });
            }
            const symbolPool = [...record.exports, ...record.localFunctions];
            for (const token of taskTokens) {
                const matchedSymbols = symbolPool.filter((symbol) => symbol.toLowerCase().includes(token));
                for (const symbol of matchedSymbols.slice(0, 3)) {
                    addRank(file, 72, "task symbol match", { matchedTerm: token, matchedSymbol: symbol });
                }
            }
        }
    }
    const selectedFiles = [...ranking.entries()]
        .sort((left, right) => {
        if (right[1].score !== left[1].score) {
            return right[1].score - left[1].score;
        }
        return left[0].localeCompare(right[0]);
    })
        .slice(0, limit)
        .map(([file]) => file);
    const files = selectedFiles.map((file) => {
        const record = fileMap.get(file);
        return {
            file,
            moduleGroup: record.moduleGroup,
            score: ranking.get(file)?.score ?? 0,
            reasons: [...(ranking.get(file)?.reasons ?? new Set())].sort((left, right) => left.localeCompare(right)),
            matchedTerms: [...(ranking.get(file)?.matchedTerms ?? new Set())].sort((left, right) => left.localeCompare(right)),
            matchedSymbols: [...(ranking.get(file)?.matchedSymbols ?? new Set())].sort((left, right) => left.localeCompare(right)),
            exports: record.exports,
            localFunctions: record.localFunctions,
            imports: record.imports,
            importedBy: record.importedBy,
            callTargets: record.importCallTargets
        };
    });
    const dependencyChains = selectedFiles
        .map((file) => ({
        file,
        chain: options.index.lastImpact.dependencyChains[file]
    }))
        .filter((entry) => Array.isArray(entry.chain) && entry.chain.length > 1)
        .sort((left, right) => left.file.localeCompare(right.file));
    const moduleIds = uniqueStrings(files.map((entry) => entry.moduleGroup));
    const moduleRanks = new Map(impactMap.rankedModules.map((entry) => [entry.id, entry]));
    const modules = options.repoMap.keyModules
        .filter((entry) => moduleIds.includes(entry.id))
        .map((entry) => ({
        id: entry.id,
        fileCount: entry.fileCount,
        entryPoints: entry.entryPoints,
        score: moduleRanks.get(entry.id)?.score ?? 0,
        topFiles: moduleRanks.get(entry.id)?.topFiles ?? []
    }))
        .sort((left, right) => right.score - left.score || right.fileCount - left.fileCount || left.id.localeCompare(right.id));
    const selectedFileSet = new Set(selectedFiles);
    const clusters = impactMap.clusters
        .filter((entry) => entry.changedFiles.some((file) => selectedFileSet.has(file))
        || entry.impactedFiles.some((file) => selectedFileSet.has(file))
        || entry.topFiles.some((file) => selectedFileSet.has(file)))
        .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
    const summary = mode === "changed"
        ? `${options.task ? `Task-focused changed-area pack for "${options.task}"` : "Focused changed-area pack"} with ${files.length} related files across ${modules.length} module groups.`
        : mode === "focus"
            ? `${options.task ? `Task-focused pack for "${options.task}" around` : "Focused repo pack for"} ${focusFiles.join(", ") || requestedFocus.join(", ") || "requested files"} with ${files.length} structurally related files.`
            : `${options.task ? `Task-focused repo pack for "${options.task}"` : "Overview repo pack"} with ${files.length} high-signal files across ${modules.length} module groups.`;
    return {
        artifactType: "kiwi-control/compact-context-pack",
        version: 1,
        generatedAt: options.index.timestamp,
        mode,
        task: options.task ?? null,
        focusFiles: seedFiles,
        missingFocusFiles,
        summary,
        files,
        dependencyChains,
        modules,
        clusters
    };
}
function buildRepoMap(tree, view, index, generatedAt) {
    return {
        artifactType: "kiwi-control/repo-map",
        version: 1,
        generatedAt,
        projectType: String(tree.projectType),
        summary: view.summary,
        languages: tree.languages,
        entryPoints: tree.entryPoints,
        keyModules: view.keyModules,
        topReverseDependencyHubs: tree.topReverseDependencyHubs,
        filesDiscovered: tree.filesDiscovered,
        importableFiles: tree.importableFiles,
        indexedFiles: index.indexedFiles
    };
}
function buildSymbolIndex(index, generatedAt) {
    const symbols = index.files
        .flatMap((record) => [
        ...record.exports.map((symbol) => ({
            symbol,
            kind: "export",
            file: record.file,
            moduleGroup: record.moduleGroup
        })),
        ...record.localFunctions.map((symbol) => ({
            symbol,
            kind: "local-function",
            file: record.file,
            moduleGroup: record.moduleGroup
        }))
    ])
        .sort((left, right) => {
        if (left.symbol !== right.symbol) {
            return left.symbol.localeCompare(right.symbol);
        }
        if (left.file !== right.file) {
            return left.file.localeCompare(right.file);
        }
        return left.kind.localeCompare(right.kind);
    });
    return {
        artifactType: "kiwi-control/symbol-index",
        version: 1,
        generatedAt,
        totalFiles: index.files.length,
        totalSymbols: symbols.length,
        files: index.files
            .map((record) => ({
            file: record.file,
            moduleGroup: record.moduleGroup,
            exports: record.exports,
            localFunctions: record.localFunctions
        }))
            .sort((left, right) => left.file.localeCompare(right.file)),
        symbols
    };
}
function buildDependencyGraph(index, generatedAt) {
    const edges = uniqueEdges(index.files.flatMap((record) => [
        ...record.imports.map((to) => ({ from: record.file, to, kind: "import" })),
        ...record.importCallTargets.map((to) => ({ from: record.file, to, kind: "call" }))
    ]));
    return {
        artifactType: "kiwi-control/dependency-graph",
        version: 1,
        generatedAt,
        nodes: index.files
            .map((record) => ({
            file: record.file,
            moduleGroup: record.moduleGroup,
            exports: record.exports.length,
            localFunctions: record.localFunctions.length,
            imports: record.imports.length,
            importedBy: record.importedBy.length
        }))
            .sort((left, right) => left.file.localeCompare(right.file)),
        edges,
        fileRelationships: index.files
            .map((record) => ({
            file: record.file,
            moduleGroup: record.moduleGroup,
            imports: record.imports,
            importedBy: record.importedBy,
            callTargets: record.importCallTargets,
            relationships: record.relationships
        }))
            .sort((left, right) => left.file.localeCompare(right.file))
    };
}
function buildImpactMap(index, generatedAt, repoMap) {
    const moduleByFile = new Map(index.files.map((record) => [record.file, record.moduleGroup]));
    const recordByFile = new Map(index.files.map((record) => [record.file, record]));
    const entryPoints = new Set(repoMap?.entryPoints ?? []);
    const changedFiles = new Set(index.lastImpact.changedFiles);
    const impactedModules = uniqueStrings([
        ...index.lastImpact.changedFiles,
        ...index.lastImpact.impactedFiles
    ].map((file) => moduleByFile.get(file) ?? "root"));
    const callTargetsByFile = new Map(index.files.map((record) => [record.file, new Set(record.importCallTargets)]));
    const rankedFiles = uniqueStrings([
        ...index.lastImpact.changedFiles,
        ...index.lastImpact.impactedFiles
    ]).map((file) => {
        const record = recordByFile.get(file);
        const distance = index.lastImpact.dependencyDistances[file] ?? null;
        const reasons = new Set();
        let score = 0;
        if (changedFiles.has(file)) {
            score += 100;
            reasons.add("changed file");
        }
        if (index.lastImpact.forwardDependencies.includes(file)) {
            score += Math.max(18, 34 - ((distance ?? 3) * 4));
            reasons.add("forward dependency");
        }
        if (index.lastImpact.reverseDependencies.includes(file)) {
            score += Math.max(22, 42 - ((distance ?? 3) * 5));
            reasons.add("reverse dependent");
        }
        if (index.lastImpact.dependencyChains[file]?.length) {
            score += 8;
            reasons.add("dependency chain");
        }
        if (entryPoints.has(file)) {
            score += 18;
            reasons.add("entry point");
        }
        if (record) {
            const importedByCount = record.importedBy.length;
            if (importedByCount > 0) {
                score += Math.min(importedByCount, 8);
                reasons.add("reverse dependency hub");
            }
            const moduleChangedCount = index.lastImpact.changedFiles.filter((changedFile) => moduleByFile.get(changedFile) === record.moduleGroup).length;
            if (moduleChangedCount > 0) {
                score += Math.min(moduleChangedCount * 10, 20);
                reasons.add("changed-area cluster");
            }
            for (const changedFile of changedFiles) {
                if (callTargetsByFile.get(changedFile)?.has(file) || callTargetsByFile.get(file)?.has(changedFile)) {
                    score += 12;
                    reasons.add("call relationship");
                    break;
                }
            }
        }
        return {
            file,
            moduleGroup: moduleByFile.get(file) ?? "root",
            score,
            reasons: [...reasons].sort((left, right) => left.localeCompare(right)),
            changed: changedFiles.has(file),
            entryPoint: entryPoints.has(file),
            importedByCount: record?.importedBy.length ?? 0,
            dependencyDistance: distance
        };
    }).sort((left, right) => right.score - left.score || left.file.localeCompare(right.file));
    const rankedModules = impactedModules.map((moduleId) => {
        const moduleFiles = rankedFiles.filter((entry) => entry.moduleGroup === moduleId);
        const changed = moduleFiles.filter((entry) => entry.changed).map((entry) => entry.file);
        const impacted = moduleFiles.filter((entry) => !entry.changed).map((entry) => entry.file);
        const moduleEntryPoints = [...entryPoints].filter((entry) => moduleByFile.get(entry) === moduleId);
        const score = moduleFiles.reduce((total, entry) => total + entry.score, 0)
            + (moduleEntryPoints.length * 12)
            + (changed.length * 10);
        return {
            id: moduleId,
            score,
            changedFiles: changed,
            impactedFiles: impacted,
            entryPoints: moduleEntryPoints,
            topFiles: moduleFiles.slice(0, 5).map((entry) => entry.file)
        };
    }).sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
    const clusterAccumulator = new Map();
    for (const fileEntry of rankedFiles) {
        const clusterId = deriveImpactClusterId(fileEntry.file);
        const cluster = clusterAccumulator.get(clusterId) ?? {
            changedFiles: new Set(),
            impactedFiles: new Set(),
            score: 0,
            topFiles: []
        };
        if (fileEntry.changed) {
            cluster.changedFiles.add(fileEntry.file);
        }
        else {
            cluster.impactedFiles.add(fileEntry.file);
        }
        cluster.score += fileEntry.score;
        cluster.topFiles.push({ file: fileEntry.file, score: fileEntry.score });
        clusterAccumulator.set(clusterId, cluster);
    }
    const clusters = [...clusterAccumulator.entries()]
        .map(([id, cluster]) => ({
        id,
        changedFiles: [...cluster.changedFiles].sort((left, right) => left.localeCompare(right)),
        impactedFiles: [...cluster.impactedFiles].sort((left, right) => left.localeCompare(right)),
        score: cluster.score,
        topFiles: cluster.topFiles
            .sort((left, right) => right.score - left.score || left.file.localeCompare(right.file))
            .slice(0, 5)
            .map((entry) => entry.file)
    }))
        .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
    return {
        artifactType: "kiwi-control/impact-map",
        version: 1,
        generatedAt,
        changedFiles: index.lastImpact.changedFiles,
        forwardDependencies: index.lastImpact.forwardDependencies,
        reverseDependencies: index.lastImpact.reverseDependencies,
        impactedFiles: index.lastImpact.impactedFiles,
        dependencyDistances: index.lastImpact.dependencyDistances,
        dependencyChains: index.lastImpact.dependencyChains,
        impactedModules,
        rankedFiles,
        rankedModules,
        clusters
    };
}
async function buildDecisionGraph(targetRoot, index, generatedAt) {
    const [currentPhase, currentFocus, openRisks, latestCheckpoint, latestHandoff, latestTaskPacketSet, latestReconcile, executionState, runtimeLifecycle, decisionLogic, workflow, executionEvents] = await Promise.all([
        loadCurrentPhase(targetRoot),
        loadCurrentFocus(targetRoot),
        loadOpenRisks(targetRoot),
        loadLatestCheckpoint(targetRoot),
        loadLatestHandoff(targetRoot),
        loadLatestTaskPacketSet(targetRoot),
        loadLatestReconcileReport(targetRoot),
        readJsonIfPresent(path.join(targetRoot, ".agent", "state", "execution-state.json")),
        readJsonIfPresent(path.join(targetRoot, ".agent", "state", "runtime-lifecycle.json")),
        readJsonIfPresent(path.join(targetRoot, ".agent", "state", "decision-logic.json")),
        readJsonIfPresent(path.join(targetRoot, ".agent", "state", "workflow.json")),
        readExecutionEvents(targetRoot)
    ]);
    const nodes = new Map();
    const edges = new Map();
    const taskNodeIds = new Map();
    const fileNodeIds = new Map();
    const moduleNodeIds = new Map();
    const recordByFile = new Map(index.files.map((record) => [record.file, record]));
    const ensureTaskNode = (task) => {
        if (!task) {
            return null;
        }
        const existing = taskNodeIds.get(task);
        if (existing) {
            return existing;
        }
        const id = `task:${task}`;
        taskNodeIds.set(task, id);
        nodes.set(id, {
            id,
            kind: "task",
            label: task,
            summary: task,
            timestamp: null,
            status: null,
            task
        });
        return id;
    };
    const ensureFileNode = (file) => {
        const existing = fileNodeIds.get(file);
        if (existing) {
            return existing;
        }
        const id = `file:${file}`;
        fileNodeIds.set(file, id);
        nodes.set(id, {
            id,
            kind: "file",
            label: file,
            summary: file,
            timestamp: null,
            status: null,
            task: null
        });
        return id;
    };
    const ensureModuleNode = (moduleGroup) => {
        const existing = moduleNodeIds.get(moduleGroup);
        if (existing) {
            return existing;
        }
        const id = `module:${moduleGroup}`;
        moduleNodeIds.set(moduleGroup, id);
        nodes.set(id, {
            id,
            kind: "module",
            label: moduleGroup,
            summary: moduleGroup,
            timestamp: null,
            status: null,
            task: null
        });
        return id;
    };
    const addEdge = (from, to, kind) => {
        edges.set(`${from}->${kind}->${to}`, { from, to, kind });
    };
    const linkNodeToFilesAndModules = (nodeId, files) => {
        for (const file of uniqueStrings(files).slice(0, 16)) {
            const fileNodeId = ensureFileNode(file);
            addEdge(nodeId, fileNodeId, "touches-file");
            const moduleGroup = recordByFile.get(file)?.moduleGroup ?? deriveImpactClusterId(file);
            const moduleNodeId = ensureModuleNode(moduleGroup);
            addEdge(nodeId, moduleNodeId, "touches-module");
        }
    };
    const addDecisionNode = (node, relatedFiles = []) => {
        nodes.set(node.id, node);
        const taskNodeId = ensureTaskNode(node.task);
        if (taskNodeId) {
            addEdge(node.id, taskNodeId, "references-task");
        }
        linkNodeToFilesAndModules(node.id, relatedFiles);
    };
    if (currentPhase) {
        addDecisionNode({
            id: `phase:${currentPhase.phaseId}`,
            kind: "phase",
            label: currentPhase.label,
            summary: currentPhase.goal,
            timestamp: currentPhase.timestamp,
            status: currentPhase.status,
            task: currentPhase.goal
        }, currentPhase.changedFilesSummary?.changedFiles ?? []);
    }
    if (latestCheckpoint) {
        addDecisionNode({
            id: `checkpoint:${latestCheckpoint.checkpointId}`,
            kind: "checkpoint",
            label: latestCheckpoint.phase,
            summary: latestCheckpoint.summary,
            timestamp: latestCheckpoint.createdAt,
            status: "recorded",
            task: latestCheckpoint.taskContext.goal
        }, [
            ...latestCheckpoint.filesTouched,
            ...latestCheckpoint.filesCreated,
            ...latestCheckpoint.filesDeleted
        ]);
    }
    if (latestHandoff) {
        addDecisionNode({
            id: `handoff:${latestHandoff.taskId}`,
            kind: "handoff",
            label: `${latestHandoff.fromRole} -> ${latestHandoff.toRole}`,
            summary: latestHandoff.summary,
            timestamp: latestHandoff.createdAt,
            status: latestHandoff.status,
            task: latestHandoff.goal
        }, latestHandoff.filesTouched);
    }
    if (latestReconcile) {
        addDecisionNode({
            id: `reconcile:${latestReconcile.dispatchId}`,
            kind: "reconcile",
            label: latestReconcile.dispatchId,
            summary: latestReconcile.recommendedNextStep,
            timestamp: latestReconcile.createdAt,
            status: latestReconcile.status,
            task: null
        });
    }
    if (currentFocus) {
        addDecisionNode({
            id: "focus:current",
            kind: "focus",
            label: currentFocus.focusOwnerRole,
            summary: currentFocus.currentFocus,
            timestamp: currentFocus.updatedAt,
            status: "active",
            task: extractTaskFromCommand(currentFocus.nextSuggestedCommand)
        }, currentFocus.nextFileToRead ? [currentFocus.nextFileToRead] : []);
    }
    if (latestTaskPacketSet) {
        addDecisionNode({
            id: `task-packet-set:${latestTaskPacketSet.createdAt}`,
            kind: "task-packet-set",
            label: latestTaskPacketSet.packetSet,
            summary: `Latest packet set with ${latestTaskPacketSet.files.length} file(s).`,
            timestamp: latestTaskPacketSet.createdAt,
            status: "ready",
            task: null
        }, latestTaskPacketSet.files);
    }
    if (executionState) {
        addDecisionNode({
            id: `execution-state:${executionState.revision}`,
            kind: "execution-event",
            label: executionState.lifecycle,
            summary: executionState.reason ?? executionState.nextCommand ?? "Execution state recorded.",
            timestamp: executionState.lastUpdatedAt,
            status: executionState.lifecycle,
            task: executionState.task
        }, flattenArtifactFiles(executionState.artifacts));
    }
    if (runtimeLifecycle) {
        addDecisionNode({
            id: `runtime-lifecycle:${runtimeLifecycle.timestamp}`,
            kind: "runtime-lifecycle",
            label: runtimeLifecycle.currentStage,
            summary: runtimeLifecycle.nextRecommendedAction ?? runtimeLifecycle.nextSuggestedCommand ?? "Runtime lifecycle recorded.",
            timestamp: runtimeLifecycle.timestamp,
            status: runtimeLifecycle.validationStatus,
            task: runtimeLifecycle.currentTask
        }, runtimeLifecycle.recentEvents.flatMap((event) => event.files).slice(0, 24));
    }
    for (const event of executionEvents.slice(0, 10)) {
        addDecisionNode({
            id: `execution-event:${event.revision}`,
            kind: "execution-event",
            label: event.type,
            summary: event.reason ?? event.nextCommand ?? event.type,
            timestamp: event.recordedAt,
            status: event.lifecycle,
            task: event.task
        }, flattenArtifactFiles(event.artifacts));
    }
    if (openRisks && openRisks.risks.length > 0) {
        openRisks.risks.slice(0, 8).forEach((risk, index) => {
            addDecisionNode({
                id: `open-risk:${index}`,
                kind: "open-risk",
                label: "open risk",
                summary: risk,
                timestamp: openRisks.updatedAt,
                status: "warn",
                task: null
            });
        });
    }
    if (decisionLogic) {
        const taskNodeId = ensureTaskNode(executionState?.task ?? runtimeLifecycle?.currentTask ?? null);
        if (taskNodeId) {
            const decisionNodeId = "focus:current";
            if (nodes.has(decisionNodeId)) {
                addEdge(decisionNodeId, taskNodeId, "continues");
            }
        }
    }
    const importantDecisions = [...nodes.values()]
        .filter((node) => !["file", "module", "task"].includes(node.kind))
        .sort((left, right) => {
        const leftTime = left.timestamp ?? "";
        const rightTime = right.timestamp ?? "";
        return rightTime.localeCompare(leftTime);
    })
        .slice(0, 12)
        .map((node) => {
        const relatedFiles = [...edges.values()]
            .filter((edge) => edge.from === node.id && edge.kind === "touches-file")
            .map((edge) => edge.to.replace(/^file:/, ""))
            .slice(0, 8);
        const relatedModules = [...edges.values()]
            .filter((edge) => edge.from === node.id && edge.kind === "touches-module")
            .map((edge) => edge.to.replace(/^module:/, ""))
            .slice(0, 6);
        return {
            id: node.id,
            kind: node.kind,
            label: node.label,
            summary: node.summary,
            status: node.status,
            task: node.task,
            relatedFiles,
            relatedModules,
            nextCommand: extractCommandFromDecisionNode(node, currentFocus, latestCheckpoint, latestHandoff, latestReconcile, executionState, workflow)
        };
    });
    return {
        artifactType: "kiwi-control/decision-graph",
        version: 1,
        generatedAt,
        currentTask: executionState?.task ?? runtimeLifecycle?.currentTask ?? null,
        nodes: [...nodes.values()].sort((left, right) => left.id.localeCompare(right.id)),
        edges: [...edges.values()].sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to) || left.kind.localeCompare(right.kind)),
        importantDecisions
    };
}
async function buildHistoryGraph(targetRoot, index, generatedAt) {
    const commits = await readRecentGitHistory(targetRoot, HISTORY_COMMIT_WINDOW);
    const recordByFile = new Map(index.files.map((record) => [record.file, record]));
    const fileTouches = new Map();
    const moduleTouches = new Map();
    const clusterTouches = new Map();
    for (const commit of commits) {
        for (const file of commit.files) {
            const moduleGroup = recordByFile.get(file)?.moduleGroup ?? deriveImpactClusterId(file);
            const fileEntry = fileTouches.get(file) ?? { touches: 0, commits: new Set(), lastTouchedAt: null };
            fileEntry.touches += 1;
            fileEntry.commits.add(commit.sha);
            fileEntry.lastTouchedAt = !fileEntry.lastTouchedAt || commit.authoredAt > fileEntry.lastTouchedAt ? commit.authoredAt : fileEntry.lastTouchedAt;
            fileTouches.set(file, fileEntry);
            const moduleEntry = moduleTouches.get(moduleGroup) ?? { touches: 0, commits: new Set(), files: new Set(), lastTouchedAt: null };
            moduleEntry.touches += 1;
            moduleEntry.commits.add(commit.sha);
            moduleEntry.files.add(file);
            moduleEntry.lastTouchedAt = !moduleEntry.lastTouchedAt || commit.authoredAt > moduleEntry.lastTouchedAt ? commit.authoredAt : moduleEntry.lastTouchedAt;
            moduleTouches.set(moduleGroup, moduleEntry);
            const clusterId = deriveImpactClusterId(file);
            const clusterEntry = clusterTouches.get(clusterId) ?? { commits: new Set(), touches: 0, files: new Set(), modules: new Set() };
            clusterEntry.commits.add(commit.sha);
            clusterEntry.touches += 1;
            clusterEntry.files.add(file);
            clusterEntry.modules.add(moduleGroup);
            clusterTouches.set(clusterId, clusterEntry);
        }
    }
    const hotspotFiles = [...fileTouches.entries()]
        .map(([file, entry]) => ({
        file,
        moduleGroup: recordByFile.get(file)?.moduleGroup ?? deriveImpactClusterId(file),
        touches: entry.touches,
        commits: entry.commits.size,
        lastTouchedAt: entry.lastTouchedAt
    }))
        .sort((left, right) => right.touches - left.touches || right.commits - left.commits || left.file.localeCompare(right.file))
        .slice(0, 24);
    const hotspotModules = [...moduleTouches.entries()]
        .map(([id, entry]) => ({
        id,
        touches: entry.touches,
        commits: entry.commits.size,
        files: [...entry.files].sort((left, right) => left.localeCompare(right)),
        lastTouchedAt: entry.lastTouchedAt
    }))
        .sort((left, right) => right.touches - left.touches || right.commits - left.commits || left.id.localeCompare(right.id))
        .slice(0, 16);
    const repeatedTouchModules = hotspotModules
        .filter((entry) => entry.touches >= 2 && entry.commits >= 2)
        .slice(0, 12);
    const changeClusters = [...clusterTouches.entries()]
        .map(([id, entry]) => ({
        id,
        commits: entry.commits.size,
        touches: entry.touches,
        files: [...entry.files].sort((left, right) => left.localeCompare(right)),
        modules: [...entry.modules].sort((left, right) => left.localeCompare(right))
    }))
        .sort((left, right) => right.touches - left.touches || right.commits - left.commits || left.id.localeCompare(right.id))
        .slice(0, 16);
    return {
        artifactType: "kiwi-control/history-graph",
        version: 1,
        generatedAt,
        commitWindow: HISTORY_COMMIT_WINDOW,
        gitHistoryAvailable: commits.length > 0,
        commitsAnalyzed: commits.length,
        recentCommits: commits.slice(0, 12).map((commit) => ({
            sha: commit.sha,
            authoredAt: commit.authoredAt,
            subject: commit.subject,
            files: commit.files,
            modules: uniqueStrings(commit.files.map((file) => recordByFile.get(file)?.moduleGroup ?? deriveImpactClusterId(file)))
        })),
        hotspotFiles,
        hotspotModules,
        repeatedTouchModules,
        changeClusters
    };
}
function buildReviewGraph(options, generatedAt) {
    const entryPoints = new Set(options.repoMap.entryPoints);
    const hubs = new Set(options.repoMap.topReverseDependencyHubs.map((entry) => entry.file));
    const decisionFileCounts = new Map();
    const failureFileCounts = new Map();
    let blockedDecisionCount = 0;
    for (const decision of options.decisionGraph.importantDecisions) {
        if (decision.status === "blocked" || decision.status === "failed") {
            blockedDecisionCount += 1;
        }
        for (const file of decision.relatedFiles) {
            decisionFileCounts.set(file, (decisionFileCounts.get(file) ?? 0) + 1);
            if (decision.status === "blocked" || decision.status === "failed") {
                failureFileCounts.set(file, (failureFileCounts.get(file) ?? 0) + 1);
            }
        }
    }
    const historyFiles = new Map(options.historyGraph.hotspotFiles.map((entry) => [entry.file, entry]));
    const impactFiles = new Map(options.impactMap.rankedFiles.map((entry) => [entry.file, entry]));
    const fileRisks = uniqueStrings([
        ...options.impactMap.rankedFiles.map((entry) => entry.file),
        ...options.historyGraph.hotspotFiles.map((entry) => entry.file),
        ...[...decisionFileCounts.keys()]
    ]).map((file) => {
        const impact = impactFiles.get(file);
        const history = historyFiles.get(file);
        const reasons = new Set();
        let score = 0;
        if (impact) {
            score += Math.min(Math.round(impact.score / 2), 90);
            for (const reason of impact.reasons.slice(0, 4)) {
                reasons.add(reason);
            }
        }
        if (history) {
            score += history.touches * 8;
            if (history.commits >= 2) {
                reasons.add("repeated touches");
            }
            reasons.add("history hotspot");
        }
        const failureCount = failureFileCounts.get(file) ?? 0;
        if (failureCount > 0) {
            score += failureCount * 18;
            reasons.add("failure-linked");
        }
        const retryCount = decisionFileCounts.get(file) ?? 0;
        if (retryCount > 1) {
            score += (retryCount - 1) * 6;
            reasons.add("repeated decision attention");
        }
        if (entryPoints.has(file)) {
            score += 12;
            reasons.add("entry point");
        }
        if (hubs.has(file)) {
            score += 10;
            reasons.add("dependency hub");
        }
        return {
            file,
            moduleGroup: impact?.moduleGroup ?? history?.moduleGroup ?? deriveImpactClusterId(file),
            score,
            reasons: [...reasons].sort((left, right) => left.localeCompare(right)),
            touches: history?.touches ?? 0,
            failures: failureCount,
            retries: retryCount > 0 ? retryCount - 1 : 0,
            entryPoint: entryPoints.has(file),
            reverseDependencyHub: hubs.has(file)
        };
    }).filter((entry) => entry.score > 0)
        .sort((left, right) => right.score - left.score || left.file.localeCompare(right.file))
        .slice(0, 24);
    const moduleRisks = options.historyGraph.hotspotModules
        .map((entry) => {
        const moduleImpact = options.impactMap.rankedModules.find((item) => item.id === entry.id);
        const failures = fileRisks.filter((file) => file.moduleGroup === entry.id).reduce((total, file) => total + file.failures, 0);
        const score = entry.touches * 10 + (moduleImpact?.score ?? 0) + failures * 15 + entry.commits * 4;
        const reasons = new Set();
        if (entry.touches >= 2)
            reasons.add("repeated touches");
        if ((moduleImpact?.changedFiles.length ?? 0) > 0)
            reasons.add("active change cluster");
        if (failures > 0)
            reasons.add("failure-linked");
        if (entry.files.some((file) => entryPoints.has(file)))
            reasons.add("contains entry point");
        return {
            id: entry.id,
            score,
            reasons: [...reasons].sort((left, right) => left.localeCompare(right)),
            touches: entry.touches,
            failures,
            entryPoints: entry.files.filter((file) => entryPoints.has(file)),
            files: entry.files.slice(0, 8)
        };
    })
        .filter((entry) => entry.score > 0)
        .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id))
        .slice(0, 16);
    const globalSignals = [];
    if (blockedDecisionCount > 0) {
        globalSignals.push({
            signal: "blocked decisions",
            detail: `${blockedDecisionCount} recent decision(s) are blocked or failed.`,
            severity: "high"
        });
    }
    if (options.historyGraph.repeatedTouchModules.length > 0) {
        globalSignals.push({
            signal: "repeated-touch modules",
            detail: `${options.historyGraph.repeatedTouchModules.length} module(s) have repeated recent touches.`,
            severity: "warn"
        });
    }
    if (options.decisionGraph.importantDecisions.some((entry) => entry.kind === "open-risk")) {
        globalSignals.push({
            signal: "open risks",
            detail: "Repo-local open risks are present in continuity state.",
            severity: "warn"
        });
    }
    const reviewAttention = [
        ...fileRisks.slice(0, 8).map((entry) => ({
            kind: "file",
            target: entry.file,
            score: entry.score,
            reasons: entry.reasons
        })),
        ...moduleRisks.slice(0, 6).map((entry) => ({
            kind: "module",
            target: entry.id,
            score: entry.score,
            reasons: entry.reasons
        })),
        ...options.decisionGraph.importantDecisions
            .filter((entry) => entry.status === "blocked" || entry.status === "failed")
            .slice(0, 4)
            .map((entry) => ({
            kind: "decision",
            target: entry.label,
            score: 90,
            reasons: [entry.status ?? "attention", entry.summary]
        }))
    ].sort((left, right) => right.score - left.score || left.target.localeCompare(right.target))
        .slice(0, 16);
    return {
        artifactType: "kiwi-control/review-graph",
        version: 1,
        generatedAt,
        status: reviewAttention.length > 0 ? "attention" : "clear",
        globalSignals,
        fileRisks,
        moduleRisks,
        reviewAttention
    };
}
export function buildReviewContextPack(options) {
    return {
        artifactType: "kiwi-control/review-context-pack",
        version: 1,
        generatedAt: new Date().toISOString(),
        summary: options.reviewGraph.reviewAttention.length > 0
            ? `Review attention is concentrated in ${options.reviewGraph.reviewAttention[0]?.target ?? "recent changes"} with ${options.reviewGraph.reviewAttention.length} ranked targets.`
            : "No elevated review targets are currently ranked.",
        currentTask: options.task ?? options.decisionGraph.currentTask,
        readFirst: [
            ".agent/state/decision-graph.json",
            ".agent/state/history-graph.json",
            ".agent/state/review-graph.json",
            ".agent/state/checkpoints/latest.json",
            ".agent/memory/current-focus.json",
            ".agent/state/execution-state.json",
            ".agent/state/execution-events.ndjson"
        ],
        decisions: options.decisionGraph.importantDecisions.slice(0, 8),
        hotspots: [
            ...options.historyGraph.hotspotFiles.slice(0, 4).map((entry) => ({
                kind: "file",
                target: entry.file,
                touches: entry.touches,
                commits: entry.commits
            })),
            ...options.historyGraph.hotspotModules.slice(0, 4).map((entry) => ({
                kind: "module",
                target: entry.id,
                touches: entry.touches,
                commits: entry.commits
            }))
        ],
        reviewAttention: options.reviewGraph.reviewAttention.slice(0, 10),
        globalSignals: options.reviewGraph.globalSignals
    };
}
export function buildAgentPack(options) {
    return {
        artifactType: "kiwi-control/agent-pack",
        version: 1,
        generatedAt: new Date().toISOString(),
        summary: `${options.repoMap.summary} Start with runtime truth, then use the task or review pack instead of the full graph when you do not need global repo exploration.`,
        readFirst: [
            ".agent/context/agent-pack.json",
            ".agent/state/ready-substrate.json",
            ".agent/state/execution-state.json",
            ".agent/state/execution-events.ndjson",
            ".agent/context/task-pack.json",
            ".agent/context/review-context-pack.json",
            ".agent/context/repo-map.json"
        ],
        sourceOfTruthNote: "Canonical workflow truth remains runtime-backed execution state and execution events. Agent packs are compact read aids, not authority.",
        repo: {
            summary: options.repoMap.summary,
            entryPoints: options.repoMap.entryPoints.slice(0, 12),
            keyModules: options.repoMap.keyModules.slice(0, 6),
            topReverseDependencyHubs: options.repoMap.topReverseDependencyHubs.slice(0, 8)
        },
        decisions: options.decisionGraph.importantDecisions.slice(0, 6),
        hotspots: [
            ...options.historyGraph.hotspotFiles.slice(0, 4).map((entry) => ({
                kind: "file",
                target: entry.file,
                touches: entry.touches,
                commits: entry.commits
            })),
            ...options.historyGraph.hotspotModules.slice(0, 4).map((entry) => ({
                kind: "module",
                target: entry.id,
                touches: entry.touches,
                commits: entry.commits
            }))
        ],
        reviewAttention: options.reviewGraph.reviewAttention.slice(0, 8),
        packPointers: {
            readySubstrate: ".agent/state/ready-substrate.json",
            repoMap: ".agent/context/repo-map.json",
            taskPack: options.compactContextPack ? ".agent/context/task-pack.json" : null,
            reviewContextPack: options.reviewContextPack ? ".agent/context/review-context-pack.json" : ".agent/context/review-context-pack.json",
            compactContextPack: options.compactContextPack ? ".agent/context/compact-context-pack.json" : null,
            decisionGraph: ".agent/state/decision-graph.json",
            historyGraph: ".agent/state/history-graph.json",
            reviewGraph: ".agent/state/review-graph.json"
        }
    };
}
export function buildTaskPack(options) {
    const currentTask = options.task ?? options.compactContextPack.task;
    const focusFiles = new Set(options.compactContextPack.files.map((entry) => entry.file));
    const relatedDecisionAttention = options.decisionGraph.importantDecisions.filter((decision) => decision.relatedFiles.some((file) => focusFiles.has(file)) || (currentTask && decision.task === currentTask));
    const reviewAttention = options.reviewGraph.reviewAttention.filter((entry) => entry.kind === "decision"
        ? true
        : focusFiles.has(entry.target) || options.compactContextPack.modules.some((module) => module.id === entry.target));
    return {
        artifactType: "kiwi-control/task-pack",
        version: 1,
        generatedAt: new Date().toISOString(),
        task: currentTask ?? null,
        summary: `${options.compactContextPack.summary} Read this first when you are implementing or editing code for the current task.`,
        readFirst: [
            ".agent/context/task-pack.json",
            ".agent/state/execution-state.json",
            ".agent/state/execution-events.ndjson",
            ".agent/memory/current-focus.json",
            ".agent/context/generated-instructions.md",
            ".agent/state/impact-map.json"
        ],
        sourceOfTruthNote: "Runtime-backed execution state remains canonical. The task pack narrows context for implementation and should be treated as a compact working set.",
        focus: {
            mode: options.compactContextPack.mode,
            focusFiles: options.compactContextPack.focusFiles,
            missingFocusFiles: options.compactContextPack.missingFocusFiles,
            files: options.compactContextPack.files,
            modules: options.compactContextPack.modules,
            dependencyChains: options.compactContextPack.dependencyChains,
            clusters: options.compactContextPack.clusters
        },
        decisions: relatedDecisionAttention.slice(0, 6),
        reviewAttention: reviewAttention.slice(0, 6)
    };
}
async function readExecutionEvents(targetRoot) {
    const filePath = path.join(targetRoot, ".agent", "state", "execution-events.ndjson");
    if (!(await pathExists(filePath))) {
        return [];
    }
    const payload = await readText(filePath);
    return payload
        .split("\n")
        .map((line) => line.trim())
        .filter(Boolean)
        .map((line) => {
        try {
            return JSON.parse(line);
        }
        catch {
            return null;
        }
    })
        .filter((entry) => Boolean(entry));
}
async function readRecentGitHistory(targetRoot, limit) {
    if (!(await pathExists(path.join(targetRoot, ".git")))) {
        return [];
    }
    try {
        const { stdout } = await execFile("git", [
            "log",
            "--date=iso-strict",
            `-n`,
            String(limit),
            "--name-only",
            "--pretty=format:__COMMIT__%n%H%n%ad%n%s"
        ], {
            cwd: targetRoot,
            encoding: "utf8"
        });
        const chunks = stdout.split("__COMMIT__").map((chunk) => chunk.trim()).filter(Boolean);
        return chunks.map((chunk) => {
            const lines = chunk.split("\n").map((line) => line.trim()).filter(Boolean);
            const [sha = "", authoredAt = "", subject = "", ...files] = lines;
            return {
                sha,
                authoredAt,
                subject,
                files: uniqueStrings(files.filter((file) => shouldIncludeHistoryFile(file)))
            };
        }).filter((commit) => commit.sha.length > 0);
    }
    catch {
        return [];
    }
}
function flattenArtifactFiles(artifacts) {
    if (!artifacts) {
        return [];
    }
    return uniqueStrings(Object.values(artifacts).flatMap((items) => items));
}
function extractTaskFromCommand(command) {
    if (!command) {
        return null;
    }
    const match = command.match(/"([^"]+)"/);
    return match?.[1] ?? null;
}
function extractCommandFromDecisionNode(node, currentFocus, latestCheckpoint, latestHandoff, latestReconcile, executionState, workflow) {
    if (node.kind === "focus") {
        return currentFocus?.nextSuggestedCommand ?? null;
    }
    if (node.kind === "checkpoint") {
        return latestCheckpoint?.nextSuggestedCommand ?? null;
    }
    if (node.kind === "handoff") {
        return latestHandoff?.nextCommand ?? null;
    }
    if (node.kind === "reconcile") {
        return latestReconcile?.recommendedNextStep ?? null;
    }
    if (node.kind === "execution-event") {
        return executionState?.nextCommand ?? workflow?.steps.find((step) => step.status === "failed")?.result.retryCommand ?? null;
    }
    return null;
}
function shouldIncludeHistoryFile(filePath) {
    const normalized = filePath.replace(/\\/g, "/");
    const lower = normalized.toLowerCase();
    if (!normalized || lower.startsWith(".agent/")) {
        return false;
    }
    if (lower.includes("/node_modules/")
        || lower.includes("/dist/")
        || lower.includes("/dist-types/")
        || lower.includes("/build/")
        || lower.includes("/.git.backup-")
        || lower.startsWith(".git.backup-")
        || lower.includes("/target/")
        || lower.includes("/coverage/")
        || lower.includes("/.next/")
        || lower.includes("/.turbo/")
        || lower.includes("/.cache/")) {
        return false;
    }
    if (lower.endsWith(".tsbuildinfo")
        || lower.endsWith(".map")
        || lower.endsWith(".log")
        || lower.includes("/._")
        || lower.startsWith("._")) {
        return false;
    }
    return true;
}
function repoMapPath(targetRoot) {
    return path.join(targetRoot, ".agent", "context", "repo-map.json");
}
function symbolIndexPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "symbol-index.json");
}
function dependencyGraphPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "dependency-graph.json");
}
function impactMapPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "impact-map.json");
}
function compactContextPackPath(targetRoot) {
    return path.join(targetRoot, ".agent", "context", "compact-context-pack.json");
}
function decisionGraphPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "decision-graph.json");
}
function historyGraphPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "history-graph.json");
}
function reviewGraphPath(targetRoot) {
    return path.join(targetRoot, ".agent", "state", "review-graph.json");
}
function reviewContextPackPath(targetRoot) {
    return path.join(targetRoot, ".agent", "context", "review-context-pack.json");
}
function agentPackPath(targetRoot) {
    return path.join(targetRoot, ".agent", "context", "agent-pack.json");
}
function taskPackPath(targetRoot) {
    return path.join(targetRoot, ".agent", "context", "task-pack.json");
}
async function readJsonIfPresent(filePath) {
    if (!(await pathExists(filePath))) {
        return null;
    }
    try {
        return await readJson(filePath);
    }
    catch {
        return null;
    }
}
async function writeJsonArtifact(filePath, value) {
    const nextContent = `${JSON.stringify(value, null, 2)}\n`;
    const exists = await pathExists(filePath);
    const currentContent = exists ? await readText(filePath) : null;
    if (currentContent === nextContent) {
        return {
            path: filePath,
            status: "unchanged",
            detail: "already up to date",
            addedLines: 0,
            removedLines: 0
        };
    }
    await ensureDir(path.dirname(filePath));
    await writeText(filePath, nextContent);
    return {
        path: filePath,
        status: exists ? "updated" : "created",
        detail: exists ? "updated repo intelligence artifact" : "created repo intelligence artifact"
    };
}
export async function persistCompactContextPack(targetRoot, compactContextPack) {
    return writeJsonArtifact(compactContextPackPath(targetRoot), compactContextPack);
}
export async function persistReviewContextPack(targetRoot, reviewContextPack) {
    return writeJsonArtifact(reviewContextPackPath(targetRoot), reviewContextPack);
}
export async function persistAgentPack(targetRoot, agentPack) {
    return writeJsonArtifact(agentPackPath(targetRoot), agentPack);
}
export async function persistTaskPack(targetRoot, taskPack) {
    return writeJsonArtifact(taskPackPath(targetRoot), taskPack);
}
function uniqueStrings(values) {
    return [...new Set(values.filter(Boolean))].sort((left, right) => left.localeCompare(right));
}
function resolveFocusTargets(requestedFocus, fileMap, moduleMap) {
    const focusFiles = new Set();
    const missingFocusTargets = [];
    for (const target of requestedFocus) {
        if (fileMap.has(target)) {
            focusFiles.add(target);
            continue;
        }
        const moduleFiles = moduleMap.get(target);
        if (moduleFiles) {
            for (const file of moduleFiles) {
                focusFiles.add(file);
            }
            continue;
        }
        const directoryMatches = [...fileMap.keys()].filter((file) => file.startsWith(`${target.replace(/\/+$/, "")}/`));
        if (directoryMatches.length > 0) {
            for (const file of directoryMatches) {
                focusFiles.add(file);
            }
            continue;
        }
        missingFocusTargets.push(target);
    }
    return {
        focusFiles: [...focusFiles].sort((left, right) => left.localeCompare(right)),
        missingFocusTargets: missingFocusTargets.sort((left, right) => left.localeCompare(right))
    };
}
function uniqueEdges(edges) {
    return [...new Map(edges
            .filter((edge) => Boolean(edge.from) && Boolean(edge.to))
            .map((edge) => [`${edge.kind}:${edge.from}->${edge.to}`, edge])).values()].sort((left, right) => {
        if (left.from !== right.from) {
            return left.from.localeCompare(right.from);
        }
        if (left.to !== right.to) {
            return left.to.localeCompare(right.to);
        }
        return left.kind.localeCompare(right.kind);
    });
}
function deriveImpactClusterId(filePath) {
    const normalized = filePath.replace(/\\/g, "/");
    const segments = normalized.split("/").filter(Boolean);
    if (segments.length <= 1) {
        return segments[0] ?? ".";
    }
    if (segments[0] === "apps" && segments[1]) {
        return segments[2] === "src" && segments[3]
            ? `apps/${segments[1]}/src/${segments[3]}`
            : `apps/${segments[1]}`;
    }
    if (segments[0] === "packages" && segments[1]) {
        return segments[2] === "src" && segments[3]
            ? `packages/${segments[1]}/src/${segments[3]}`
            : `packages/${segments[1]}`;
    }
    if (segments[0] === "src") {
        return segments[1] && !segments[1].includes(".") ? `src/${segments[1]}` : "src";
    }
    if (segments[0] === "tests") {
        return "tests";
    }
    return segments.length > 2 ? `${segments[0]}/${segments[1]}` : (segments[0] ?? ".");
}
