import fs from "node:fs";
import path from "node:path";
import { debug } from "../core/logger.js";
import { getDesignLifecycleArtifactAliases, getDesignLifecycleArtifactPaths } from "./design_lifecycle_contract.js";
export const DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH = "docs/traceability/01-需求原型设计实现追踪矩阵.md";
export const DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH = "templates/artifacts/shared/需求原型设计实现追踪矩阵模版.md";
export const DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND = "soloforge audit-design-artifacts";
export const DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_GUIDANCE = `使用 ${DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH} 补齐 ${DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH}，让架构、详细设计、切片计划绑定 REQ/PROTO/ARCH/DD/PHASE/SLICE/AC ID 后重新运行 ${DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND}`;
const TRACEABILITY_ID_RE = /\b(REQ|PROTO|ARCH|DD|PHASE|SLICE|AC)-(?:[A-Z]+)?\d{2,}\b/g;
const TRACEABILITY_RANGE_RE = /\b(REQ|PROTO|ARCH|PHASE|SLICE|AC)-((?:[A-Z]+)?)(\d{2,})\s*[~～]\s*(?:(REQ|PROTO|ARCH|PHASE|SLICE|AC)-)?([A-Z]+)?(\d{2,})\b|\b(DD)-([A-Z]+)(\d{2,})\s*[~～]\s*(?:(DD)-)?(?:([A-Z]+))?(\d{2,})\b/g;
const PROJECT_ARTIFACT_SCAN_EXCLUDED_DIRS = new Set([
    ".claude",
    ".codex",
    ".git",
    ".soloforge",
    ".trae",
    "build",
    "coverage",
    "dist",
    "node_modules",
    "target",
    "templates",
]);
function fileExists(rootDir, relativePath) {
    return fs.existsSync(path.join(rootDir, relativePath));
}
function readText(rootDir, relativePath) {
    const absolutePath = path.join(rootDir, relativePath);
    return fs.existsSync(absolutePath) ? fs.readFileSync(absolutePath, "utf-8") : undefined;
}
function collectFiles(rootDir, matcher) {
    const results = [];
    if (!fs.existsSync(rootDir))
        return results;
    const stack = [rootDir];
    while (stack.length > 0) {
        const current = stack.pop();
        for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
            const absolutePath = path.join(current, entry.name);
            const relativePath = path.relative(rootDir, absolutePath).replace(/\\/g, "/");
            if (entry.isDirectory()) {
                if (PROJECT_ARTIFACT_SCAN_EXCLUDED_DIRS.has(entry.name))
                    continue;
                stack.push(absolutePath);
            }
            else if (matcher(relativePath)) {
                results.push(relativePath);
            }
        }
    }
    return results.sort();
}
export function extractTraceabilityIds(content) {
    const normalizedContent = content.replace(/`((?:REQ|PROTO|ARCH|DD|PHASE|SLICE|AC)-[A-Z]*\d{2,})`/g, "$1");
    const ids = [];
    for (const match of normalizedContent.matchAll(TRACEABILITY_RANGE_RE)) {
        if (match[1]) {
            const prefix = match[1];
            const startAlpha = match[2] ?? "";
            const start = Number(match[3]);
            const endPrefix = match[4] ?? prefix;
            const endAlpha = match[5] ?? startAlpha;
            const end = Number(match[6]);
            if (prefix === endPrefix && startAlpha === endAlpha && end >= start && end - start <= 200) {
                for (let current = start; current <= end; current += 1) {
                    ids.push(`${prefix}-${startAlpha}${String(current).padStart(match[3].length, "0")}`);
                }
            }
        }
        else if (match[7]) {
            const startAlpha = match[8] ?? "";
            const start = Number(match[9]);
            const endAlpha = match[11] ?? startAlpha;
            const end = Number(match[12]);
            if (startAlpha === endAlpha && end >= start && end - start <= 200) {
                for (let current = start; current <= end; current += 1) {
                    ids.push(`DD-${startAlpha}${String(current).padStart(match[9].length, "0")}`);
                }
            }
        }
    }
    ids.push(...[...normalizedContent.matchAll(TRACEABILITY_ID_RE)].map((match) => match[0]));
    return [...new Set(ids)];
}
function prefixOf(id) {
    return id.split("-", 1)[0];
}
function parseMatrixItems(content, matrixPath) {
    const lines = content.split(/\r?\n/);
    const seen = new Map();
    for (const line of lines) {
        const ids = extractTraceabilityIds(line);
        if (ids.length === 0)
            continue;
        for (const id of ids) {
            if (seen.has(id))
                continue;
            seen.set(id, {
                id,
                prefix: prefixOf(id),
                source_file: matrixPath,
                summary: line.replace(/\|/g, " ").replace(/\s+/g, " ").trim().slice(0, 160),
                linked_ids: ids.filter((candidate) => candidate !== id),
            });
        }
    }
    return [...seen.values()];
}
function addFinding(findings, code, message_zh, file, item_id) {
    const recovery = code === "TRACEABILITY_ID_NOT_IN_MATRIX" && item_id
        ? `在 ${DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH} 登记 ${item_id}，并把它绑定到对应 REQ/PROTO/ARCH/DD/PHASE/SLICE/AC；如果 ${file ?? "来源文件"} 中该 ID 是误引用，则删除或改成已登记 ID 后重新运行 ${DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND}。`
        : code === "TRACEABILITY_PHASE_ID_MISSING"
            ? `在切片计划 ${file ?? ""} 中声明 PHASE-*，并在 ${DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH} 的总矩阵中增加 PHASE 列或 PHASE 行，确保每个 SLICE-* 归属到阶段后重新运行 ${DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND}。`
            : code === "TRACEABILITY_REQUIRED_LAYER_MISSING" && message_zh.includes("PHASE")
                ? `在 ${DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH} 中补充 PHASE-* 层级，例如 PHASE-01，并把所有 SLICE-* 绑定到对应 PHASE-* 后重新运行 ${DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND}。`
                : DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_GUIDANCE;
    findings.push({
        code,
        severity: "hard_fail",
        message_zh,
        file,
        item_id,
        recovery_zh: recovery,
        recovery_command: DESIGN_IMPLEMENTATION_TRACEABILITY_RECOVERY_COMMAND,
        template_path: DESIGN_IMPLEMENTATION_TRACEABILITY_TEMPLATE_PATH,
    });
}
function detectDetailedDesignFiles(rootDir) {
    const lifecyclePaths = getDesignLifecycleArtifactPaths();
    const canonical = [
        lifecyclePaths.optional_detail_design_document,
        "docs/architecture/04-详细设计.md",
        "docs/design/04-详细设计文档.md",
    ];
    const splitCarriers = [
        lifecyclePaths.database_document,
        lifecyclePaths.api_document,
    ];
    const existing = [
        ...canonical.filter((file) => fileExists(rootDir, file)),
        ...splitCarriers.filter((file) => fileExists(rootDir, file)),
        ...collectFiles(rootDir, (file) => /(?:详细设计|detail(?:ed)?[-_ ]design)/i.test(file) && /\.md$/i.test(file)),
    ];
    return [...new Set(existing)].sort();
}
function detectSlicePlanFiles(rootDir) {
    const lifecyclePaths = getDesignLifecycleArtifactPaths();
    return [
        lifecyclePaths.slice_plan,
        ...getDesignLifecycleArtifactAliases(lifecyclePaths.slice_plan),
        ...collectFiles(rootDir, (file) => /(?:切片|slice|迭代计划|开发计划)/i.test(file) && /\.md$/i.test(file)),
    ].filter((file, index, list) => fileExists(rootDir, file) && list.indexOf(file) === index);
}
function hasAnyDesignSource(rootDir) {
    const knownSources = [
        "docs/requirements.md",
        "docs/需求文档.md",
        "docs/PRD.md",
        "docs/prototype.md",
        "docs/原型说明.md",
        "docs/architecture/01-架构设计文档.md",
        "docs/architecture/02-数据库设计文档.md",
        "docs/architecture/03-API接口规格文档.md",
    ];
    return knownSources.some((file) => fileExists(rootDir, file))
        || detectDetailedDesignFiles(rootDir).length > 0
        || detectSlicePlanFiles(rootDir).length > 0;
}
export function auditDesignImplementationTraceability(rootDir, matrixPath = DEFAULT_DESIGN_IMPLEMENTATION_TRACEABILITY_PATH) {
    const findings = [];
    const checkedFiles = [];
    const coverage = {
        REQ: 0,
        PROTO: 0,
        ARCH: 0,
        DD: 0,
        PHASE: 0,
        SLICE: 0,
        AC: 0,
    };
    const matrixContent = readText(rootDir, matrixPath);
    if (!matrixContent) {
        if (hasAnyDesignSource(rootDir)) {
            addFinding(findings, "TRACEABILITY_MATRIX_MISSING", `缺少需求/原型/设计/切片/验收追踪矩阵: ${matrixPath}`, matrixPath);
        }
        return {
            passed: findings.length === 0,
            matrix_path: matrixPath,
            matrix_exists: false,
            items: [],
            coverage,
            checked_files: [],
            hard_fail_count: findings.length,
            findings,
        };
    }
    checkedFiles.push(matrixPath);
    const items = parseMatrixItems(matrixContent, matrixPath);
    const ids = items.map((item) => item.id);
    const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
    for (const duplicate of [...new Set(duplicateIds)]) {
        addFinding(findings, "TRACEABILITY_DUPLICATE_ID", `追踪矩阵中存在重复 ID: ${duplicate}`, matrixPath, duplicate);
    }
    for (const item of items)
        coverage[item.prefix] += 1;
    for (const prefix of Object.keys(coverage)) {
        if (coverage[prefix] === 0) {
            addFinding(findings, "TRACEABILITY_REQUIRED_LAYER_MISSING", `追踪矩阵缺少 ${prefix} 层级，必须覆盖需求、原型、架构、详细设计、阶段、切片和验收`, matrixPath);
        }
    }
    const architecturePath = "docs/architecture/01-架构设计文档.md";
    const architectureContent = readText(rootDir, architecturePath);
    if (architectureContent) {
        checkedFiles.push(architecturePath);
        const archIds = extractTraceabilityIds(architectureContent);
        if (!archIds.some((id) => id.startsWith("REQ-") || id.startsWith("PROTO-"))) {
            addFinding(findings, "TRACEABILITY_ARCH_SOURCE_MISSING", "架构设计文档缺少 REQ-* 或 PROTO-* 上游来源 ID，无法证明架构来自需求和原型", architecturePath);
        }
    }
    for (const detailPath of detectDetailedDesignFiles(rootDir)) {
        const content = readText(rootDir, detailPath) ?? "";
        checkedFiles.push(detailPath);
        const detailIds = extractTraceabilityIds(content);
        if (!detailIds.some((id) => id.startsWith("ARCH-"))) {
            addFinding(findings, "TRACEABILITY_DETAIL_ARCH_MISSING", "详细设计缺少 ARCH-* 架构来源 ID，无法证明详细设计承接架构设计", detailPath);
        }
        if (!detailIds.some((id) => id.startsWith("DD-"))) {
            addFinding(findings, "TRACEABILITY_DETAIL_ID_MISSING", "详细设计缺少 DD-* 设计单元 ID，后续切片无法稳定引用", detailPath);
        }
    }
    for (const slicePath of detectSlicePlanFiles(rootDir)) {
        const content = readText(rootDir, slicePath) ?? "";
        checkedFiles.push(slicePath);
        const sliceIds = extractTraceabilityIds(content);
        if (!sliceIds.some((id) => id.startsWith("SLICE-"))) {
            addFinding(findings, "TRACEABILITY_SLICE_ID_MISSING", "切片计划缺少 SLICE-* ID，编码任务无法绑定切片", slicePath);
        }
        if (!sliceIds.some((id) => id.startsWith("PHASE-"))) {
            addFinding(findings, "TRACEABILITY_PHASE_ID_MISSING", "切片计划缺少 PHASE-* ID，无法证明切片属于哪个阶段或迭代", slicePath);
        }
        if (!sliceIds.some((id) => id.startsWith("DD-") || id.startsWith("ARCH-"))) {
            addFinding(findings, "TRACEABILITY_SLICE_DESIGN_MISSING", "切片计划缺少 DD-* 或 ARCH-* 设计来源 ID", slicePath);
        }
        if (!sliceIds.some((id) => id.startsWith("AC-"))) {
            addFinding(findings, "TRACEABILITY_SLICE_ACCEPTANCE_MISSING", "切片计划缺少 AC-* 验收 ID，无法证明验收闭环", slicePath);
        }
    }
    const matrixIds = new Set(items.map((item) => item.id));
    for (const checkedFile of checkedFiles.filter((file) => file !== matrixPath)) {
        const content = readText(rootDir, checkedFile) ?? "";
        for (const id of extractTraceabilityIds(content)) {
            if (!matrixIds.has(id)) {
                addFinding(findings, "TRACEABILITY_ID_NOT_IN_MATRIX", `文件引用的追踪 ID 未登记到矩阵: ${id}`, checkedFile, id);
            }
        }
    }
    return {
        passed: findings.length === 0,
        matrix_path: matrixPath,
        matrix_exists: true,
        items,
        coverage,
        checked_files: checkedFiles,
        hard_fail_count: findings.length,
        findings,
    };
}
function isImplementationFile(file) {
    return /\.(?:ts|tsx|js|jsx|java|kt|cs|go|py|rs|php|rb)$/.test(file)
        && !/(?:^|\/)(?:tests?|__tests__|fixtures?)\//.test(file);
}
export function extractTraceabilityBindingIds(context) {
    const explicit = context.traceability_binding
        ? [
            ...context.traceability_binding.requirement_ids,
            ...context.traceability_binding.prototype_ids,
            ...context.traceability_binding.architecture_ids,
            ...context.traceability_binding.detail_design_ids,
            ...(context.traceability_binding.phase_ids ?? []),
            ...context.traceability_binding.slice_ids,
            ...context.traceability_binding.acceptance_ids,
        ]
        : [];
    return [...new Set([
            ...explicit,
            ...extractTraceabilityIds(context.intent ?? ""),
            ...extractTraceabilityIds(context.expansion?.prompt ?? ""),
            ...extractTraceabilityIds(context.planning?.summary ?? ""),
        ])];
}
export function verifyChangedFilesAgainstDesignTraceability(rootDir, changedFiles, context) {
    const implementationFiles = changedFiles.filter(isImplementationFile);
    const bindingIds = extractTraceabilityBindingIds(context);
    const findings = [];
    if (implementationFiles.length === 0) {
        return { passed: true, hard_fail_count: 0, binding_ids: bindingIds, findings };
    }
    const report = auditDesignImplementationTraceability(rootDir);
    if (!report.matrix_exists && report.findings.length === 0) {
        return { passed: true, hard_fail_count: 0, binding_ids: bindingIds, findings };
    }
    findings.push(...report.findings);
    const requiredPrefixes = ["PHASE", "SLICE", "DD", "AC"];
    // 前置准备阶段（如 SLICE-00）在追踪矩阵中 DD/AC 标为"—"，不强制要求
    const isPreparatorySlice = bindingIds.some((id) => /^SLICE-0{1,2}0\b/.test(id));
    for (const prefix of requiredPrefixes) {
        if (isPreparatorySlice && (prefix === "DD" || prefix === "AC"))
            continue;
        if (!bindingIds.some((id) => id.startsWith(`${prefix}-`))) {
            addFinding(findings, "TRACEABILITY_CODE_BINDING_MISSING", `编码变更必须绑定 ${prefix}-* 追踪 ID，确保实现来自具体阶段、详细设计、切片和验收标准`);
        }
    }
    const knownIds = new Set(report.items.map((item) => item.id));
    for (const id of bindingIds) {
        if (knownIds.size > 0 && !knownIds.has(id)) {
            addFinding(findings, "TRACEABILITY_CODE_BINDING_UNKNOWN", `编码任务绑定了未登记的追踪 ID: ${id}`, report.matrix_path, id);
        }
    }
    return {
        passed: findings.every((finding) => finding.severity !== "hard_fail"),
        hard_fail_count: findings.filter((finding) => finding.severity === "hard_fail").length,
        binding_ids: bindingIds,
        findings,
    };
}
function determineTestCoverage(hasChangedFiles, context) {
    if (!hasChangedFiles)
        return "none";
    if (!context.verification)
        return "none";
    const checks = [
        ...context.verification.checks.build,
        ...context.verification.checks.tests,
        ...(context.verification.checks.acceptance ?? []),
    ];
    if (checks.length === 0)
        return "none";
    // "partial": 验证存在但任务未完全完成
    // （可能有重试历史或非成功状态）
    const hasFailures = context.execution?.failure_history &&
        context.execution.failure_history.length > 0;
    const notDone = context.status !== "done";
    if (hasFailures || notDone)
        return "partial";
    return "full";
}
function determineReviewStatus(context) {
    if (!context.code_review)
        return "skipped";
    const hasCritical = context.code_review.findings.some((f) => f.severity === "critical");
    return hasCritical ? "issues" : "passed";
}
/**
 * 构建
 */
export function buildTraceabilityChain(context) {
    debug("可追溯性", "buildTraceabilityChain() 开始, task_id=${context.task_id}, changed_files=${changedFiles.length}");
    const changedFiles = context.execution?.changed_files ?? [];
    const hasPlanning = !!context.planning?.sub_tasks;
    // 步骤 1: Extract sub-tasks
    let subTasks;
    if (hasPlanning && context.planning) {
        debug("可追溯性", "有规划信息, ${context.planning.sub_tasks.length} 个子任务");
        subTasks = context.planning.sub_tasks.map((st) => ({
            id: st.id,
            title: st.title,
            changed_files: [],
            test_coverage: "none",
            review_status: "skipped",
            verified: false,
        }));
    }
    else {
        debug("可追溯性", "无规划信息, 创建合成子任务");
        subTasks = [
            {
                id: `${context.task_id}-main`,
                title: context.intent,
                changed_files: [],
                test_coverage: "none",
                review_status: "skipped",
                verified: context.status === "done",
            },
        ];
    }
    // 步骤 2: Map code changes to sub-tasks
    if (changedFiles.length > 0) {
        debug("可追溯性", "开始映射 ${changedFiles.length} 个变更文件到子任务");
        if (hasPlanning) {
            const hasOutputPath = subTasks.some((st) => {
                const planTask = context.planning?.sub_tasks.find((t) => t.id === st.id);
                return !!planTask?.output_path;
            });
            if (hasOutputPath) {
                for (const file of changedFiles) {
                    let matched = false;
                    for (const st of subTasks) {
                        const planTask = context.planning?.sub_tasks.find((t) => t.id === st.id);
                        if (planTask?.output_path && file.startsWith(planTask.output_path)) {
                            st.changed_files.push(file);
                            matched = true;
                            break;
                        }
                    }
                    if (!matched) {
                        // 将未匹配的文件分配给第一个子任务
                        subTasks[0].changed_files.push(file);
                    }
                }
            }
            else {
                // 未定义 output_path: 将所有文件分配给第一个子任务
                subTasks[0].changed_files.push(...changedFiles);
            }
        }
        else {
            // 单个合成子任务
            subTasks[0].changed_files.push(...changedFiles);
        }
    }
    // 步骤 3 & 4: 确定每个子任务的 test_coverage 和 review_status
    for (const st of subTasks) {
        st.test_coverage = determineTestCoverage(st.changed_files.length > 0, context);
        st.review_status = determineReviewStatus(context);
        if (st.test_coverage === "full" && st.review_status === "passed") {
            st.verified = true;
        }
    }
    // 步骤 5: Calculate coverage
    const gaps = [];
    for (const st of subTasks) {
        const missing = [];
        if (st.test_coverage === "none") {
            missing.push("测试覆盖");
        }
        if (st.review_status === "skipped") {
            missing.push("代码审查");
        }
        if (st.changed_files.length === 0) {
            missing.push("代码变更");
        }
        if (missing.length > 0) {
            gaps.push(`子任务 '${st.title}' 缺少 ${missing.join("、")}`);
        }
    }
    const covered = subTasks.filter((st) => st.test_coverage !== "none" && st.review_status !== "skipped").length;
    // 步骤 6: Generate summary
    const totalSubTasks = subTasks.length;
    const gapCount = gaps.length;
    let summary;
    if (gapCount === 0) {
        summary = `共 ${totalSubTasks} 个子任务，全部已完整追溯`;
    }
    else {
        summary = `共 ${totalSubTasks} 个子任务，${totalSubTasks - gapCount} 个已完整追溯，${gapCount} 个存在追溯缺口`;
    }
    debug("可追溯性", "完成, 覆盖=${covered}/${totalSubTasks}, 缺口=${gapCount}");
    return {
        task_id: context.task_id,
        intent: context.intent,
        sub_tasks: subTasks,
        coverage: {
            total_subtasks: totalSubTasks,
            covered,
            gaps,
        },
        summary,
    };
}
//# sourceMappingURL=traceability.js.map