/**
 * 用户项目知识合同。
 *
 * 内置 templates/** 解决 SoloForge 自带资产的同步与可见性；
 * 用户项目 .soloforge/knowledge/** 则需要独立承载: 允许用户沉淀项目特有规则，
 * 同时保证它们能被索引、选中、注入或审计，而不是静态文件。
 */
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import matter from "gray-matter";
import YAML from "yaml";
import { getProjectKnowledgeDir } from "../../knowledge/loader.js";
import { readSyncManifest, resolveAuthoritativeIdentity, computeAuthoritativeHash, } from "../templates/template_init_sync.js";
import { getContractDecision } from "../templates/template_asset_contract_registry.js";
import { extractTraceabilityIds } from "./traceability.js";
import { getConsumptionTraces } from "../templates/consumption_trace_store.js";
import { safeMatchIndex } from "../core/helpers.js";
function listKnowledgeFiles(dir) {
    if (!fs.existsSync(dir))
        return [];
    const out = [];
    const stack = [dir];
    while (stack.length > 0) {
        const current = stack.pop();
        for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
            if (["node_modules", ".git", ".DS_Store"].includes(entry.name))
                continue;
            const full = path.join(current, entry.name);
            if (entry.isDirectory())
                stack.push(full);
            else if (/\.(md|ya?ml)$/i.test(entry.name))
                out.push(full);
        }
    }
    return out.sort();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK 桥接/动态 JSON 解析，字段由运行时决定
function parseKnowledgeFile(filePath) {
    try {
        const raw = fs.readFileSync(filePath, "utf-8");
        if (/\.md$/i.test(filePath)) {
            const parsed = matter(raw);
            return { data: parsed.data ?? {}, body: parsed.content ?? "", hasFrontmatter: raw.trimStart().startsWith("---") };
        }
        const data = YAML.parse(raw) ?? {};
        return { data, body: raw, hasFrontmatter: Object.keys(data).length > 0 };
    }
    catch {
        return { data: {}, body: "", hasFrontmatter: false };
    }
}
function bodyHash(filePath) {
    try {
        const content = fs.readFileSync(filePath, "utf-8");
        if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
            const stripped = content.split("\n")
                .filter((line) => !/^(status|updated_at)\s*:/.test(line.trim()))
                .join("\n");
            return crypto.createHash("sha256").update(stripped).digest("hex").substring(0, 16);
        }
        const bodyStart = content.indexOf("---", content.indexOf("---") + 3);
        const body = bodyStart >= 0 ? content.substring(bodyStart + 3).trimStart() : content;
        return crypto.createHash("sha256").update(body).digest("hex").substring(0, 16);
    }
    catch {
        return null;
    }
}
function normalizeList(value) {
    if (Array.isArray(value))
        return value.map(String).filter(Boolean);
    if (typeof value === "string" && value.trim())
        return [value.trim()];
    return [];
}
function normalizeLifecycleStages(value) {
    const allowed = new Set([
        "requirements_intake",
        "prototype_extraction",
        "architecture_design",
        "detail_design",
        "slice_planning",
        "implementation",
        "testing",
        "acceptance",
        "delivery",
    ]);
    return normalizeList(value).filter((item) => allowed.has(item));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK 桥接/动态 JSON 解析，字段由运行时决定
function inferConsumption(relPath, data, hardRule) {
    const kind = String(data.asset_kind ?? data.type ?? "").toLowerCase();
    const runtimeVisibility = String(data.runtime_visibility ?? "").toLowerCase();
    const enforcementLevel = String(data.enforcement?.level ?? data.enforcement_level ?? "").toLowerCase();
    if (kind === "audit_only" || runtimeVisibility === "audit_only" || enforcementLevel === "audit_only")
        return "audit_only";
    if (hardRule)
        return "hard_rule";
    if (relPath.includes("review_rules/") || kind === "review_rule")
        return "review_rule";
    if (relPath.includes("acceptance_templates/") || kind === "acceptance_template")
        return "acceptance_template";
    if (runtimeVisibility === "injectable")
        return "injectable";
    if (normalizeList(data.routes).length > 0 || normalizeList(data.primary_triggers).length > 0)
        return "injectable";
    return "index_only";
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK 桥接/动态 JSON 解析，字段由运行时决定
function hasHardRuleMarker(relPath, data, body, hasFrontmatter) {
    if (!hasFrontmatter)
        return false;
    const kind = String(data.asset_kind ?? data.type ?? "").toLowerCase();
    const priority = String(data.priority ?? "").toUpperCase();
    const enforcementLevel = String(data.enforcement?.level ?? data.enforcement_level ?? "").toLowerCase();
    return kind === "hard_rule"
        || priority === "P0"
        || priority === "P1"
        || enforcementLevel === "hard_rule"
        || (relPath.includes("rules/") && /hard[_ -]?fail|必须|不得|禁止/.test(body));
}
function classifyIdentity(projectPath, relPath, filePath) {
    const manifest = readSyncManifest(projectPath);
    const syncEntry = manifest?.entries?.[relPath];
    const authoritative = resolveAuthoritativeIdentity(relPath);
    if (syncEntry && authoritative) {
        const authoritativeHash = computeAuthoritativeHash(authoritative.source_contract_path);
        const currentBodyHash = bodyHash(filePath);
        if (syncEntry.source_contract_path !== authoritative.source_contract_path)
            return "conflict";
        if (authoritativeHash && currentBodyHash && currentBodyHash !== authoritativeHash)
            return "user_modified_builtin";
        const decision = getContractDecision(authoritative.source_contract_path);
        if (decision.contract?.lifecycle_status === "deprecated")
            return "deprecated_builtin";
        return "builtin_synced";
    }
    if (authoritative) {
        const decision = getContractDecision(authoritative.source_contract_path);
        if (decision.decision === "denied")
            return "conflict";
        return "user_modified_builtin";
    }
    return "user_authored";
}
function analyzeAsset(projectPath, knowledgeDir, filePath) {
    const relPath = path.relative(knowledgeDir, filePath).replace(/\\/g, "/");
    const parsed = parseKnowledgeFile(filePath);
    const data = parsed.data;
    const hardRule = hasHardRuleMarker(relPath, data, parsed.body, parsed.hasFrontmatter);
    const routes = normalizeList(data.routes);
    const primaryTriggers = normalizeList(data.primary_triggers);
    const techStack = normalizeList(data.tech_stack);
    const domains = normalizeList(data.domains ?? data.scope);
    const lifecycleStages = normalizeLifecycleStages(data.lifecycle_stages ?? data.lifecycle_stage ?? data.stages);
    const pathPatterns = normalizeList(data.path_patterns ?? data.paths ?? data.file_patterns);
    const businessDomains = normalizeList(data.business_domains ?? data.business_domain ?? data.domains);
    const traceabilityIds = [
        ...normalizeList(data.traceability_ids),
        ...extractTraceabilityIds(parsed.body),
    ].filter((id, index, list) => list.indexOf(id) === index);
    const verificationCommands = normalizeList(data.verification_commands ?? data.verify_commands);
    const recoveryCommand = String(data.recovery_command ?? data.recovery ?? "").trim() || undefined;
    const conflictGroup = String(data.conflict_group ?? data.rule_key ?? "").trim() || undefined;
    const ruleValue = String(data.rule_value ?? data.decision_value ?? data.format ?? "").trim() || undefined;
    const conflictsWith = normalizeList(data.conflicts_with);
    const consumption = inferConsumption(relPath, data, hardRule);
    const identity = classifyIdentity(projectPath, relPath, filePath);
    const findings = [];
    if (!parsed.hasFrontmatter) {
        findings.push({
            severity: "warning",
            code: "PROJECT_KNOWLEDGE_FRONTMATTER_MISSING",
            message_zh: "用户项目知识缺少 frontmatter，只能作为 index_only 背景资料，不能作为 hard rule 强执行。",
        });
    }
    if (hardRule && routes.length === 0 && normalizeList(data.primary_triggers).length === 0) {
        findings.push({
            severity: "hard_fail",
            code: "PROJECT_KNOWLEDGE_HARD_RULE_UNROUTED",
            message_zh: "hard rule 缺少 routes 或 primary_triggers，无法被 SoloForge 稳定选中和消费。",
        });
    }
    if (identity === "conflict") {
        findings.push({
            severity: "hard_fail",
            code: "PROJECT_KNOWLEDGE_IDENTITY_CONFLICT",
            message_zh: "项目知识与内置同步资产身份冲突，必须人工确认保留、合并或迁移。",
        });
    }
    if (identity === "user_modified_builtin") {
        findings.push({
            severity: "advisory",
            code: "PROJECT_KNOWLEDGE_USER_MODIFIED_BUILTIN",
            message_zh: "用户修改过内置同步资产，sync-templates 不得自动覆盖；升级时应输出合并建议。",
        });
    }
    if (consumption === "index_only" && hardRule) {
        findings.push({
            severity: "hard_fail",
            code: "PROJECT_KNOWLEDGE_HARD_RULE_INDEX_ONLY",
            message_zh: "hard rule 被判定为 index_only，必须补齐触发条件或降级为 advisory。",
        });
    }
    return {
        id: String(data.id ?? data.name ?? relPath),
        name: String(data.name ?? path.basename(filePath, path.extname(filePath))),
        rel_path: relPath,
        abs_path: filePath,
        identity,
        consumption,
        routes,
        primary_triggers: primaryTriggers,
        tech_stack: techStack,
        domains,
        lifecycle_stages: lifecycleStages,
        path_patterns: pathPatterns,
        business_domains: businessDomains,
        traceability_ids: traceabilityIds,
        verification_commands: verificationCommands,
        recovery_command: recoveryCommand,
        conflict_group: conflictGroup,
        rule_value: ruleValue,
        conflicts_with: conflictsWith,
        has_frontmatter: parsed.hasFrontmatter,
        hard_rule: hardRule,
        injectable: consumption === "injectable" || consumption === "hard_rule",
        audit_only: consumption === "audit_only",
        evidence: {
            created_by: identity === "user_authored" ? "user_project" : "soloforge_builtin",
            synced_by: identity === "builtin_synced" ? "sync-templates" : undefined,
            indexed_by: "audit-project-knowledge",
        },
        findings,
    };
}
const SEMANTIC_CONFLICT_GROUPS = [
    {
        group: "api_response_format",
        patterns: [/api|接口|响应|response/i],
        values: [
            { value: "ApiResponse", positive_patterns: [/统一(?:响应|返回)[^。\n]{0,24}\bApiResponse\b/i, /(?:必须|统一|采用|使用|标准为)[^。\n]{0,16}\bApiResponse\b/i] },
            { value: "Result", positive_patterns: [/统一(?:响应|返回)[^。\n]{0,24}\bResult\b/i, /(?:必须|统一|采用|使用|标准为)[^。\n]{0,16}\bResult\b/i] },
            { value: "ResponseEntity", positive_patterns: [/统一(?:响应|返回)[^。\n]{0,24}\bResponseEntity\b/i, /(?:必须|统一|采用|使用|标准为)[^。\n]{0,16}\bResponseEntity\b/i] },
        ],
    },
    {
        group: "pagination_format",
        patterns: [/分页|pagination|page/i],
        values: [
            { value: "page_size", positive_patterns: [/分页(?:参数|请求|接口)[^。\n]{0,32}(?:统一|采用|使用|标准为)[^。\n]{0,24}(?:pageSize|page_size|page\/size|page\s*\+\s*size)/i] },
            { value: "limit_offset", positive_patterns: [/分页(?:参数|请求|接口)[^。\n]{0,32}(?:统一|采用|使用|标准为)[^。\n]{0,24}(?:limit\/offset|limit\s*\+\s*offset)/i] },
        ],
    },
    {
        group: "logging_fields",
        patterns: [/日志|log/i],
        values: [
            { value: "trace_id", positive_patterns: [/(?:日志|链路|请求)(?:字段|ID)?[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:traceId|trace_id)/i] },
            { value: "request_id", positive_patterns: [/(?:日志|链路|请求)(?:字段|ID)?[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:requestId|request_id)/i] },
        ],
    },
    {
        group: "database_naming",
        patterns: [/数据库|表名|字段|database|table/i],
        values: [
            { value: "snake_case", positive_patterns: [/(?:数据库|表名|字段|table|column)[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:snake_case|下划线)/i] },
            { value: "camelCase", positive_patterns: [/(?:数据库|表名|字段|table|column)[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:camelCase|驼峰)/i] },
        ],
    },
    {
        group: "permission_model",
        patterns: [/权限|permission|auth|租户/i],
        values: [
            { value: "rbac", positive_patterns: [/(?:权限|授权|permission|auth)[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:RBAC|角色)/i] },
            { value: "abac", positive_patterns: [/(?:权限|授权|permission|auth)[^。\n]{0,24}(?:统一|采用|使用|标准为)[^。\n]{0,16}(?:ABAC|属性)/i] },
        ],
    },
];
function inferSemanticConflict(asset) {
    const text = `${asset.name} ${asset.rel_path} ${asset.primary_triggers.join(" ")} ${asset.domains.join(" ")}`;
    for (const candidate of SEMANTIC_CONFLICT_GROUPS) {
        if (!candidate.patterns.some((pattern) => pattern.test(text)))
            continue;
        const body = fs.existsSync(asset.abs_path) ? fs.readFileSync(asset.abs_path, "utf-8") : text;
        const matchedValue = candidate.values.find((item) => item.positive_patterns.some((pattern) => {
            for (const match of body.matchAll(new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`))) {
                const context = body.slice(Math.max(0, safeMatchIndex(match) - 16), Math.min(body.length, safeMatchIndex(match) + match[0].length + 16));
                if (/例如|如\s|比如|示例|不得|不要|禁止|不应|不能|避免/.test(context))
                    continue;
                return true;
            }
            return false;
        }));
        if (matchedValue)
            return { group: candidate.group, value: matchedValue.value };
    }
    return null;
}
function detectProjectKnowledgeConflicts(assets) {
    const byIdOrPath = new Map();
    for (const asset of assets) {
        byIdOrPath.set(asset.id, asset);
        byIdOrPath.set(asset.rel_path, asset);
    }
    for (const asset of assets) {
        for (const target of asset.conflicts_with) {
            const other = byIdOrPath.get(target);
            if (!other)
                continue;
            asset.findings.push({
                severity: "hard_fail",
                code: "PROJECT_KNOWLEDGE_RULE_CONFLICT",
                message_zh: `项目规则声明与 ${other.rel_path} 冲突，必须由用户确认取舍或合并。`,
            });
        }
    }
    const grouped = new Map();
    const explicitAssets = new Set();
    for (const asset of assets) {
        const hasExplicitDecision = Boolean(asset.conflict_group && asset.rule_value);
        const semantic = hasExplicitDecision
            ? { group: asset.conflict_group, value: asset.rule_value }
            : inferSemanticConflict(asset);
        if (!semantic)
            continue;
        if (hasExplicitDecision)
            explicitAssets.add(asset);
        const list = grouped.get(semantic.group) ?? [];
        if (!asset.conflict_group)
            asset.conflict_group = semantic.group;
        if (!asset.rule_value)
            asset.rule_value = semantic.value;
        list.push(asset);
        grouped.set(semantic.group, list);
    }
    for (const [group, list] of grouped) {
        const values = new Set(list.map((asset) => asset.rule_value));
        if (values.size <= 1)
            continue;
        const allExplicit = list.every((asset) => explicitAssets.has(asset));
        const inferredUserHardRuleConflict = !allExplicit && list.every((asset) => asset.hard_rule
            && asset.identity !== "builtin_synced"
            && asset.identity !== "deprecated_builtin");
        for (const asset of list) {
            const shouldHardFail = allExplicit || inferredUserHardRuleConflict;
            asset.findings.push({
                severity: shouldHardFail ? "hard_fail" : "warning",
                code: shouldHardFail ? "PROJECT_KNOWLEDGE_RULE_CONFLICT" : "PROJECT_KNOWLEDGE_INFERRED_RULE_CONFLICT",
                message_zh: shouldHardFail
                    ? `项目规则组 ${group} 存在多个互斥取值（${Array.from(values).join(" / ")}），必须由用户确认统一口径。`
                    : `项目规则组 ${group} 存在推断出的疑似互斥取值（${Array.from(values).join(" / ")}），请用 conflict_group/rule_value 明确项目口径；推断结果不得作为 hard_fail。`,
            });
        }
    }
}
export function auditProjectKnowledge(projectPath, config) {
    const knowledgeDir = config && config._projectPath
        ? getProjectKnowledgeDir(config)
        : path.join(projectPath, ".soloforge", "knowledge");
    const assets = listKnowledgeFiles(knowledgeDir).map((file) => analyzeAsset(projectPath, knowledgeDir, file));
    detectProjectKnowledgeConflicts(assets);
    const traces = getConsumptionTraces()
        .filter((trace) => trace.asset_path || trace.asset_id)
        .filter((trace) => assets.some((asset) => asset.id === trace.asset_id || asset.rel_path === trace.asset_path))
        .sort((a, b) => b.consumed_at.localeCompare(a.consumed_at));
    for (const asset of assets) {
        const latest = traces.find((trace) => trace.asset_id === asset.id || trace.asset_path === asset.rel_path);
        if (!latest)
            continue;
        asset.evidence.last_consumed_task = latest.task_id_or_route;
        asset.evidence.last_consumed_at = latest.consumed_at;
        asset.evidence.last_consumption_action = latest.action;
        asset.evidence.last_consumption_consumer = latest.consumer;
        if (latest.action === "selected")
            asset.evidence.selected_by = latest.consumer;
        if (latest.action === "injected")
            asset.evidence.injected_by = latest.consumer;
        if (latest.action === "reviewed")
            asset.evidence.reviewed_by = latest.consumer;
        if (latest.action === "verified")
            asset.evidence.verified_by = latest.consumer;
        if (latest.action === "delivery_blocked")
            asset.evidence.delivery_blocked_by = latest.consumer;
    }
    const hardFailCount = assets.reduce((sum, asset) => sum + asset.findings.filter((f) => f.severity === "hard_fail").length, 0);
    const warningCount = assets.reduce((sum, asset) => sum + asset.findings.filter((f) => f.severity === "warning").length, 0);
    const unconsumed = assets.filter((asset) => asset.consumption === "index_only" && asset.identity === "user_authored").length;
    const recommendedNextSteps = [];
    if (hardFailCount > 0)
        recommendedNextSteps.push("先修复 hard rule 缺少触发条件或身份冲突的项目知识。");
    if (unconsumed > 0)
        recommendedNextSteps.push("为需要参与执行的项目规则补充 routes、primary_triggers 或 enforcement.level。");
    if (assets.length === 0)
        recommendedNextSteps.push("当前项目暂无自定义知识；可按需新增 .soloforge/knowledge/rules 或 domain 规则。");
    return {
        project_path: projectPath,
        total: assets.length,
        user_authored: assets.filter((a) => a.identity === "user_authored").length,
        builtin_synced: assets.filter((a) => a.identity === "builtin_synced").length,
        user_modified_builtin: assets.filter((a) => a.identity === "user_modified_builtin").length,
        deprecated_builtin: assets.filter((a) => a.identity === "deprecated_builtin").length,
        conflict: assets.filter((a) => a.identity === "conflict").length,
        hard_rule: assets.filter((a) => a.hard_rule).length,
        injectable: assets.filter((a) => a.injectable).length,
        index_only: assets.filter((a) => a.consumption === "index_only").length,
        unconsumed,
        hard_fail_count: hardFailCount,
        warning_count: warningCount,
        assets,
        recommended_next_steps: recommendedNextSteps,
        recent_consumption: traces.slice(0, 20).map((trace) => ({
            asset_id: trace.asset_id,
            rel_path: trace.asset_path,
            consumer: trace.consumer,
            action: trace.action,
            lifecycle_stage: trace.lifecycle_stage,
            task_id_or_route: trace.task_id_or_route,
            consumed_at: trace.consumed_at,
            decision: trace.contract_decision,
        })),
    };
}
/**
 * @deprecated 仅供旧测试和兼容调用使用。生产入口必须消费 selectProjectKnowledgeForTaskWithReasons
 * 或 evaluateLifecycleKnowledgeDecision 返回的 SelectionDecision。
 */
export function selectProjectKnowledgeForTask(report, input) {
    return selectProjectKnowledgeForTaskWithReasons(report, input).selected.map((decision) => decision.asset);
}
export function selectProjectKnowledgeForTaskWithReasons(report, input) {
    const haystack = `${input.intent} ${input.route ?? ""}`.toLowerCase();
    const stack = (input.tech_stack ?? []).map((s) => s.toLowerCase());
    const changedFiles = (input.changed_files ?? []).map((file) => file.replace(/\\/g, "/").toLowerCase());
    const businessDomains = (input.business_domains ?? []).map((domain) => domain.toLowerCase());
    const traceabilityIds = new Set(input.traceability_ids ?? extractTraceabilityIds(input.intent));
    const decisions = report.assets.map((asset) => {
        const matchedSignals = [];
        const excludedReasons = [];
        let score = 0;
        if (asset.identity === "conflict")
            excludedReasons.push("资产身份冲突，需用户确认");
        if (asset.findings.some((finding) => finding.severity === "hard_fail"))
            excludedReasons.push("资产存在 hard_fail，需先修复");
        if (asset.consumption === "index_only")
            excludedReasons.push("index_only 仅可索引查询，不进入 prompt 或 hard rule 执行");
        if (asset.consumption === "audit_only")
            excludedReasons.push("audit_only 仅用于审计，不进入 prompt 注入");
        const techMatches = asset.tech_stack.length === 0
            || asset.tech_stack.some((t) => stack.some((s) => s.includes(t.toLowerCase()) || t.toLowerCase().includes(s)));
        if (techMatches && asset.tech_stack.length > 0) {
            score += 2;
            matchedSignals.push(`技术栈匹配: ${asset.tech_stack.join(",")}`);
        }
        else if (!techMatches) {
            excludedReasons.push(`技术栈不匹配: 规则=${asset.tech_stack.join(",")} 任务=${stack.join(",") || "未提供"}`);
        }
        const routeMatches = asset.routes.length === 0 || (input.route ? asset.routes.includes(input.route) : false);
        if (routeMatches && input.route && asset.routes.length > 0) {
            score += 3;
            matchedSignals.push(`路由匹配: ${input.route}`);
        }
        else if (!routeMatches) {
            excludedReasons.push(`路由不匹配: 规则=${asset.routes.join(",")} 任务=${input.route ?? "未提供"}`);
        }
        if (input.lifecycle_stage && asset.lifecycle_stages.length > 0) {
            if (asset.lifecycle_stages.includes(input.lifecycle_stage)) {
                score += 4;
                matchedSignals.push(`生命周期阶段匹配: ${input.lifecycle_stage}`);
            }
            else {
                excludedReasons.push(`生命周期阶段不匹配: 规则=${asset.lifecycle_stages.join(",")} 任务=${input.lifecycle_stage}`);
            }
        }
        const primaryTriggerMatches = asset.primary_triggers.filter((token) => token && haystack.includes(token.toLowerCase()));
        if (primaryTriggerMatches.length > 0) {
            score += primaryTriggerMatches.length * 2;
            matchedSignals.push(`主触发词匹配: ${primaryTriggerMatches.slice(0, 5).join(",")}`);
        }
        const secondaryTriggerTokens = [...asset.domains, ...asset.business_domains, asset.name, asset.rel_path];
        const secondaryTriggerMatches = secondaryTriggerTokens.filter((token) => token && haystack.includes(token.toLowerCase()));
        if (secondaryTriggerMatches.length > 0) {
            score += secondaryTriggerMatches.length;
            matchedSignals.push(`辅助信号匹配: ${secondaryTriggerMatches.slice(0, 5).join(",")}`);
        }
        if (asset.hard_rule && asset.primary_triggers.length > 0 && primaryTriggerMatches.length === 0) {
            excludedReasons.push(`hard rule 触发词不匹配: 规则=${asset.primary_triggers.join(",")} 任务未命中`);
        }
        const fileMatches = asset.path_patterns.filter((pattern) => {
            const normalized = pattern.toLowerCase().replace(/\*\*/g, "").replace(/\*/g, "");
            return normalized && changedFiles.some((file) => file.includes(normalized));
        });
        if (fileMatches.length > 0) {
            score += fileMatches.length * 3;
            matchedSignals.push(`文件路径匹配: ${fileMatches.join(",")}`);
        }
        const businessMatches = asset.business_domains.filter((domain) => businessDomains.includes(domain.toLowerCase()) || haystack.includes(domain.toLowerCase()));
        if (businessMatches.length > 0) {
            score += businessMatches.length * 2;
            matchedSignals.push(`业务域匹配: ${businessMatches.join(",")}`);
        }
        const traceMatches = asset.traceability_ids.filter((id) => traceabilityIds.has(id));
        if (traceMatches.length > 0) {
            score += traceMatches.length * 4;
            matchedSignals.push(`追踪 ID 匹配: ${traceMatches.join(",")}`);
        }
        if (asset.hard_rule)
            score += 3;
        if (asset.injectable)
            score += 1;
        const blocked = asset.identity === "conflict" || asset.findings.some((finding) => finding.severity === "hard_fail");
        const eligibleForSelection = !blocked
            && asset.consumption !== "index_only"
            && asset.consumption !== "audit_only"
            && techMatches
            && routeMatches
            && (!asset.hard_rule || asset.primary_triggers.length === 0 || primaryTriggerMatches.length > 0)
            && (!input.lifecycle_stage || asset.lifecycle_stages.length === 0 || asset.lifecycle_stages.includes(input.lifecycle_stage))
            && (score > 0 || asset.hard_rule);
        const status = blocked ? "blocked" : eligibleForSelection ? "selected" : "not_selected";
        const reason = status === "selected"
            ? `选中原因: ${matchedSignals.join("；") || "hard rule 与当前任务匹配"}`
            : `未选中原因: ${excludedReasons.join("；") || "未命中当前任务意图、阶段、路径或业务域"}`;
        return {
            asset,
            status,
            selected: status === "selected",
            score,
            reason_zh: reason,
            matched_signals: matchedSignals,
            excluded_reasons: excludedReasons,
            lifecycle_stage: input.lifecycle_stage,
            enforcement: asset.hard_rule ? "hard_rule" : asset.audit_only ? "audit_only" : asset.injectable ? "injectable" : asset.consumption === "index_only" ? "index_only" : "advisory",
            user_visible: {
                must_fix: blocked || asset.hard_rule,
                why_selected: reason,
                how_to_fix: asset.recovery_command ? `运行 ${asset.recovery_command}` : blocked ? "运行 soloforge audit-project-knowledge 并按 finding 修复" : undefined,
                verification_command: asset.verification_commands[0] ?? asset.recovery_command,
            },
        };
    });
    const selected = decisions
        .filter((decision) => decision.status === "selected")
        .sort((a, b) => b.score - a.score)
        .slice(0, input.limit ?? 8);
    const selectedIds = new Set(selected.map((decision) => decision.asset.id));
    const limitedDecisions = decisions.map((decision) => decision.status === "selected" && !selectedIds.has(decision.asset.id)
        ? { ...decision, status: "not_selected", selected: false, excluded_reasons: [...decision.excluded_reasons, "注入预算裁剪"], reason_zh: "未选中原因: 注入预算裁剪" }
        : decision);
    return {
        selected,
        decisions: limitedDecisions,
        blocked: limitedDecisions.filter((decision) => decision.status === "blocked"),
        not_selected: limitedDecisions.filter((decision) => decision.status === "not_selected"),
    };
}
//# sourceMappingURL=project_knowledge_contract.js.map