import fs from "node:fs/promises";
import fss from "node:fs";
import path from "node:path";
import matter from "gray-matter";
import YAML from "yaml";
import { debugLog } from "../engine/core/logger.js";
import { getProjectKnowledgeDir, getGlobalPatternsDir } from "./loader.js";
import { checkKnowledgeHealth, persistHealthChanges } from "./health_checker.js";
import { detectConflicts } from "./conflict_detector.js";
import { getContractDecision } from "../engine/templates/template_asset_contract_registry.js";
import { recordConsumptionTrace } from "../engine/templates/consumption_trace_store.js";
import { readSyncManifest, resolveAuthoritativeIdentity } from "../engine/templates/template_init_sync.js";
import { isSameOrDescendantPath } from "../engine/core/path_scope_utils.js";
import crypto from "node:crypto";
/** 跳过的目录名集合 */
const SKIP_DIRS = new Set(["node_modules", ".git", ".hg", ".svn", "__pycache__", ".DS_Store"]);
/** 索引 schema 版本号 — 增量变更时递增 */
export const INDEX_SCHEMA_VERSION = 1;
/** 监听的文件系统事件类型集合 */
const WATCH_EVENTS = new Set(["add", "change", "unlink", "unlinkDir"]);
/**
 * 知识索引管理器。
 * @description 负责扫描、解析、索引知识文件（Markdown 和 YAML），
 *              支持增量更新（文件监听）和按条件查询。
 *              同时提供健康检查和冲突检测功能。
 */
export class KnowledgeIndexManager {
    config;
    index;
    watcher = null;
    rebuildTimer = null;
    watchingStarted = false;
    watchEnabled;
    closed = false;
    rebuildInProgress = false;
    filePathToEntryMap = new Map();
    /**
     * 创建知识索引管理器实例。
     * @param config - 项目配置对象
     * @param options - 索引构建选项
     */
    constructor(config, options = {}) {
        this.config = config;
        this.watchEnabled = options.watch ?? true;
        this.index = { version: INDEX_SCHEMA_VERSION, built_at: "", global_entries: [], project_entries: [] };
    }
    /**
     * 构建知识索引。
     * @description 扫描全局和项目知识目录，解析所有知识文件并建立索引。
     *              首次构建时启动文件监听。
     */
    async build() {
        if (this.rebuildInProgress) {
            debugLog("索引管理器: build 正在进行中，跳过重复调用");
            return;
        }
        this.rebuildInProgress = true;
        try {
            debugLog("索引管理器: 开始构建知识索引...");
            this.index.built_at = new Date().toISOString();
            const globalEntries = await this.scanDirectory(getGlobalPatternsDir());
            const projectEntries = await this.scanDirectory(getProjectKnowledgeDir(this.config));
            // 合同门禁: 过滤内部资产泄漏到用户项目的情况
            this.index.global_entries = await this.filterByContractGate(globalEntries);
            this.index.project_entries = await this.filterByContractGate(projectEntries);
            this.rebuildEntryMap();
            if (!this.watchingStarted && this.watchEnabled) {
                await this.startWatching();
                this.watchingStarted = true;
            }
            debugLog(`索引管理器: 索引构建完成 — 全局 ${globalEntries.length} 条，项目 ${projectEntries.length} 条。`);
            // 自动执行一次健康检查（不阻塞调用方）
            this.checkHealth().then((issues) => {
                if (issues.length > 0) {
                    debugLog(`索引管理器: 自动健康检查完成 — 发现 ${issues.length} 个问题。`);
                }
            }).catch(() => {
                // 健康检查失败不影响索引构建成功
            });
        }
        finally {
            this.rebuildInProgress = false;
        }
    }
    /**
     * 关闭索引管理器，停止文件监听并清理资源。
     */
    async close() {
        this.closed = true;
        if (this.rebuildTimer) {
            clearTimeout(this.rebuildTimer);
            this.rebuildTimer = null;
        }
        if (this.watcher) {
            await this.watcher.close();
            this.watcher = null;
        }
        debugLog("索引管理器: 已关闭。");
    }
    /**
     * 计算文件 body 的 SHA256（排除 frontmatter），用于防篡改校验。
     * 健康检查会修改 frontmatter（status 字段），不应视为篡改。
     */
    bodyHash(content, filePath) {
        // YAML 文件: 移除健康检查会修改的 status/updated_at 行
        if (filePath && (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);
        }
        // Markdown: 排除 frontmatter 后哈希 body
        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);
    }
    /**
     * 合同门禁过滤 — 使用同步清单区分同步资产与用户自建文件。
     * 同步资产: 验证 hash 一致性和合同注册，不通过则拒绝。
     * 用户自建文件: 通过 user schema 放行，不以 "unregistered path = pass" 绕过。
     */
    async filterByContractGate(entries) {
        const filtered = [];
        const blocked = [];
        // 加载同步清单，区分同步资产与用户自建文件
        const projectDir = getProjectKnowledgeDir(this.config);
        const projectRoot = path.dirname(path.dirname(projectDir));
        const manifest = readSyncManifest(projectRoot);
        const manifestMap = manifest?.entries ?? {};
        if (Object.keys(manifestMap).length > 0) {
            debugLog(`知识索引: 加载同步清单 ${Object.keys(manifestMap).length} 条，项目根: ${projectRoot}`);
        }
        for (const entry of entries) {
            if (!entry.file_path) {
                filtered.push(entry);
                continue;
            }
            const normalized = entry.file_path.replace(/\\/g, "/");
            // 从绝对路径提取 knowledge/ 下的相对路径用于清单匹配
            const knowledgeRelPath = this.toKnowledgeRelPath(normalized);
            // 检查同步清单：有条目 = 同步资产，无条目 = 用户自建
            const syncEntry = knowledgeRelPath ? manifestMap[knowledgeRelPath] : undefined;
            if (syncEntry) {
                // 同步资产: 从权威注册表按目标 relativePath 反向解析身份
                // 不信任 manifest 中用户可编辑的 source_contract_path/source_asset_id
                if (!knowledgeRelPath) {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: normalized, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "同步条目无法解析 knowledge 相对路径", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[身份] ${entry.name} — 同步条目无法解析 knowledge 相对路径`);
                    continue;
                }
                const authId = resolveAuthoritativeIdentity(knowledgeRelPath);
                if (!authId) {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: normalized, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "同步条目无权威目标映射", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[身份] ${entry.name} — 无权威目标映射: ${knowledgeRelPath}`);
                    continue;
                }
                // manifest source_contract_path 与权威映射不一致
                if (syncEntry.source_contract_path !== authId.source_contract_path) {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `manifest source_contract_path 篡改: manifest=${syncEntry.source_contract_path} authoritative=${authId.source_contract_path}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[身份] ${entry.name} — source_contract_path 不一致`);
                    continue;
                }
                const decision = getContractDecision(authId.source_contract_path);
                // manifest source_asset_id 与权威合同不一致
                const authAssetId = decision.contract?.asset_id ?? "";
                if (syncEntry.source_asset_id !== authAssetId) {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `manifest source_asset_id 篡改: manifest=${syncEntry.source_asset_id} authoritative=${authAssetId}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[身份] ${entry.name} — source_asset_id 不一致`);
                    continue;
                }
                if (decision.decision === "unregistered") {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "同步资产在权威注册表中 unregistered", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[身份] ${entry.name} — 权威注册表 unregistered`);
                    continue;
                }
                if (decision.decision === "denied") {
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `内部资产: ${decision.contract?.asset_visibility ?? "denied"}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[内部] ${entry.name} — 同步资产被合同拒绝`);
                    continue;
                }
                // hash 比对: 权威来源 hash（不信任 manifest source_hash）
                let fileContent;
                try {
                    fileContent = await fs.readFile(normalized, "utf-8");
                }
                catch { /* 文件不存在时跳过 hash 比对 */ }
                if (fileContent !== undefined) {
                    const currentHash = this.bodyHash(fileContent, normalized);
                    if (currentHash !== authId.source_hash) {
                        recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: `同步资产 body hash 不一致（权威来源验证）: authoritative=${authId.source_hash} actual=${currentHash}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                        blocked.push(`[篡改] ${entry.name} — body hash 不一致: authoritative=${authId.source_hash} actual=${currentHash}`);
                        continue;
                    }
                }
                // 检查技术栈适用性
                if (decision.contract?.applicable_tech_stack && decision.contract.applicable_tech_stack.length > 0) {
                    const projectStack = this.getProjectTechStack();
                    const matches = decision.contract.applicable_tech_stack.some(s => projectStack.some(ps => ps.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(ps.toLowerCase())));
                    if (!matches) {
                        recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `技术栈不适配: 资产需 ${decision.contract.applicable_tech_stack.join(",")}，项目为 ${projectStack.join(",")}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                        blocked.push(`[技术栈] ${entry.name} — 资产需 ${decision.contract.applicable_tech_stack.join(",")}，项目为 ${projectStack.join(",")}`);
                        continue;
                    }
                }
                recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: authId.source_contract_path, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "同步资产通过权威来源身份验证", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString(), declared_consumer: decision.contract?.mainline_consumer });
                filtered.push(entry);
            }
            else {
                // manifest 无条目: 检查是否命中权威注册模板路径
                const templateRelPath = this.toTemplateRelPath(normalized);
                if (templateRelPath) {
                    // 文件路径命中内置注册资产 — 即使 manifest 缺失也不得降级为用户自建
                    const decision = getContractDecision(templateRelPath);
                    if (decision.decision === "denied") {
                        recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: templateRelPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `内部资产（manifest 缺失）: ${decision.contract?.asset_visibility ?? "denied"}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                        blocked.push(`[内部] ${entry.name} — 资产可见性不允许索引`);
                        continue;
                    }
                    if (decision.decision === "unregistered") {
                        // toTemplateRelPath 已验证非 unregistered，此分支理论上不可达
                        recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: normalized, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "用户自建文件", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                        filtered.push(entry);
                        continue;
                    }
                    // 已注册内置资产但 manifest 缺失 → 必须校验权威身份和 body hash
                    if (knowledgeRelPath) {
                        const authId = resolveAuthoritativeIdentity(knowledgeRelPath);
                        if (authId) {
                            // 权威 hash 校验（不依赖 manifest）
                            let hashFileContent;
                            try {
                                hashFileContent = await fs.readFile(normalized, "utf-8");
                            }
                            catch { /* 文件不存在时跳过 hash 校验 */ }
                            if (hashFileContent !== undefined) {
                                const currentHash = this.bodyHash(hashFileContent, normalized);
                                if (currentHash !== authId.source_hash) {
                                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: templateRelPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: `已注册内置资产 manifest 缺失且 body hash 不一致: authoritative=${authId.source_hash} actual=${currentHash}`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                                    blocked.push(`[篡改] ${entry.name} — manifest 缺失且 body hash 不一致`);
                                    continue;
                                }
                            }
                            // 技术栈适用性
                            if (decision.contract?.applicable_tech_stack && decision.contract.applicable_tech_stack.length > 0) {
                                const projectStack = this.getProjectTechStack();
                                const matches = decision.contract.applicable_tech_stack.some(s => projectStack.some(ps => ps.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(ps.toLowerCase())));
                                if (!matches) {
                                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: templateRelPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: `技术栈不适配（manifest 缺失）`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                                    blocked.push(`[技术栈] ${entry.name} — 资产需 ${decision.contract.applicable_tech_stack.join(",")}`);
                                    continue;
                                }
                            }
                            recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: templateRelPath, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "已注册内置资产通过权威 hash 校验（manifest 缺失）", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString(), declared_consumer: decision.contract?.mainline_consumer });
                            filtered.push(entry);
                            continue;
                        }
                    }
                    // 已注册但权威来源文件缺失 → fail-closed: 必须阻断
                    if (templateRelPath) {
                        recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: templateRelPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision"], matched_reason: `已注册内置资产 ${templateRelPath} 权威来源文件缺失`, task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                        blocked.push(`[权威缺失] ${entry.name} — 已注册模板 ${templateRelPath} 权威来源不存在`);
                        continue;
                    }
                    // knowledgeRelPath 也为 null → fail-closed: 无法确认权威身份时阻断
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: normalized, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision"], matched_reason: "无法解析权威身份，fail-closed 阻断", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    blocked.push(`[权威未知] ${entry.name} — 无法解析权威身份，安全阻断`);
                }
                else {
                    // 完全不在知识目录映射范围内 → 用户自建文件
                    recordConsumptionTrace({ consumer: "knowledge_index", asset_id: entry.name, asset_path: normalized, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "entry_type"], matched_reason: "用户自建文件", task_id_or_route: "filterByContractGate", consumed_at: new Date().toISOString() });
                    filtered.push(entry);
                }
            }
        }
        if (blocked.length > 0) {
            debugLog(`知识索引: 合同门禁过滤 ${blocked.length} 条不合规资产:`);
            for (const b of blocked)
                debugLog(`  ${b}`);
        }
        return filtered;
    }
    /** 从绝对路径提取 .soloforge/knowledge/ 下的相对路径，用于清单匹配 */
    toKnowledgeRelPath(absPath) {
        const marker = "/.soloforge/knowledge/";
        const idx = absPath.indexOf(marker);
        if (idx < 0)
            return null;
        return absPath.substring(idx + marker.length);
    }
    /**
     * 将绝对路径转换为模板相对路径。
     * 仅当路径能映射到已注册的内置模板资产时才返回 templateRelPath。
     * 用户自建文件（不在注册表中）返回 null，由调用方放行。
     */
    toTemplateRelPath(absPath) {
        const globalDir = getGlobalPatternsDir().replace(/\\/g, "/");
        // 全局模式目录下的文件: 映射为 templates/patterns/...
        if (absPath.startsWith(globalDir)) {
            const rel = absPath.substring(globalDir.length + 1);
            return `templates/patterns/${rel}`;
        }
        // 项目知识目录下的文件: 映射为 templates/... 后检查注册表
        // 使用与 toKnowledgeRelPath 相同的精确标记，避免误匹配其他 /knowledge/ 段
        const knowledgeMarker = "/.soloforge/knowledge/";
        const knowledgeIdx = absPath.indexOf(knowledgeMarker);
        if (knowledgeIdx >= 0) {
            const rel = absPath.substring(knowledgeIdx + knowledgeMarker.length); // domain/... or procedures/... etc.
            const templatePath = `templates/${rel}`;
            // 检查该路径是否在权威合同注册表中（即来自内置模板同步）
            const decision = getContractDecision(templatePath);
            if (decision.decision !== "unregistered") {
                // 已注册的内置同步资产 → 返回模板路径以便执行门禁
                return templatePath;
            }
            // 未注册 → 用户自建文件，不映射为模板路径
            return null;
        }
        return null;
    }
    getProjectTechStack() {
        const stack = [];
        const b = this.config.tech_stack?.backend;
        const f = this.config.tech_stack?.frontend;
        if (b?.lang)
            stack.push(b.lang.toLowerCase());
        if (b?.framework)
            stack.push(b.framework.toLowerCase());
        if (f?.lang)
            stack.push(f.lang.toLowerCase());
        if (f?.framework)
            stack.push(f.framework.toLowerCase());
        return stack;
    }
    /**
     * 启动文件监听（内部方法）。
     * @description 使用 chokidar 监听全局和项目知识目录的文件变更，
     *              变更后 300ms 防抖执行增量更新。
     */
    async startWatching() {
        if (!this.watchEnabled || this.closed)
            return;
        const dirs = [];
        const globalDir = getGlobalPatternsDir();
        const projectDir = getProjectKnowledgeDir(this.config);
        if (fss.existsSync(globalDir))
            dirs.push(globalDir);
        if (fss.existsSync(projectDir))
            dirs.push(projectDir);
        if (dirs.length === 0)
            return;
        // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- 动态导入需要 typeof import() 类型声明
        let chokidar;
        try {
            chokidar = await import("chokidar");
        }
        catch {
            debugLog("索引管理器: chokidar 未安装，跳过文件监听。");
            return;
        }
        if (this.closed || !this.watchEnabled)
            return;
        if (this.watcher) {
            await this.watcher.close();
        }
        this.watcher = chokidar.watch(dirs, {
            ignoreInitial: true,
            persistent: true,
            awaitWriteFinish: { stabilityThreshold: 500 },
        });
        this.watcher.on("error", (error) => {
            this.watchEnabled = false;
            const failedWatcher = this.watcher;
            this.watcher = null;
            void failedWatcher?.close().catch((e) => { debugLog(`索引管理器: 关闭监听器失败 — ${String(e)}`); });
            const err = error;
            debugLog(`索引管理器: 文件监听失败，已降级为手动重建模式 — ${err.code ?? "UNKNOWN"} ${err.message ?? ""}`.trim());
        });
        this.watcher.on("all", (event, filePath) => {
            if (!WATCH_EVENTS.has(event))
                return;
            if (this.rebuildInProgress)
                return;
            if (this.rebuildTimer)
                clearTimeout(this.rebuildTimer);
            this.rebuildTimer = setTimeout(() => {
                this.applyIncrementalUpdate(event, filePath).catch((e) => {
                    debugLog("索引管理器: 增量更新失败 —", e);
                });
                this.rebuildTimer = null;
            }, 300);
        });
        debugLog("索引管理器: 文件监听已启动。");
    }
    /**
     * 重建文件路径到条目的映射（内部方法）。
     */
    rebuildEntryMap() {
        this.filePathToEntryMap.clear();
        for (const entry of [...this.index.project_entries, ...this.index.global_entries]) {
            this.filePathToEntryMap.set(entry.file_path, entry);
        }
    }
    /**
     * 应用增量更新（内部方法）。
     * @description 根据文件系统事件对索引进行增量更新：
     *              删除事件移除条目，新增/修改事件重新解析并更新条目。
     * @param event - 文件系统事件类型
     * @param filePath - 变更的文件路径
     */
    async applyIncrementalUpdate(event, filePath) {
        if (!/\.(md|ya?ml)$/.test(filePath))
            return;
        this.index.built_at = new Date().toISOString();
        const globalDir = getGlobalPatternsDir();
        const isGlobal = isSameOrDescendantPath(filePath, globalDir);
        const entries = isGlobal ? this.index.global_entries : this.index.project_entries;
        if (event === "unlink" || event === "unlinkDir") {
            const idx = entries.findIndex((e) => e.file_path === filePath);
            if (idx !== -1)
                entries.splice(idx, 1);
            this.filePathToEntryMap.delete(filePath);
            debugLog(`索引管理器: 文件已删除，移除索引条目 — ${filePath}`);
            return;
        }
        try {
            const newEntries = await this.parseFile(filePath);
            // 增量更新也必须通过合同门禁
            const filtered = await this.filterByContractGate(newEntries);
            for (const newEntry of filtered) {
                const existingIdx = entries.findIndex((e) => e.file_path === filePath);
                if (existingIdx !== -1) {
                    entries[existingIdx] = newEntry;
                }
                else {
                    entries.push(newEntry);
                }
                this.filePathToEntryMap.set(filePath, newEntry);
            }
            debugLog(`索引管理器: 文件变更已更新 — ${filePath}`);
        }
        catch {
            // 跳过解析失败的文件
            debugLog(`索引管理器: 解析文件失败 — ${filePath}`);
        }
    }
    /**
     * 解析单个知识文件（内部方法）。
     * @description 根据文件扩展名选择解析器：Markdown 使用 gray-matter 解析 frontmatter，
     *              YAML 使用 YAML.parse 解析。
     * @param filePath - 文件路径
     * @returns 解析后的知识条目数组
     */
    async parseFile(filePath) {
        const entries = [];
        try {
            if (filePath.endsWith(".md")) {
                const raw = await fs.readFile(filePath, "utf-8");
                const { data, content } = matter(raw);
                entries.push(this.parseEntry(data, filePath, content));
            }
            else if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
                const content = await fs.readFile(filePath, "utf-8");
                const data = YAML.parse(content);
                entries.push(this.parseEntry(data, filePath));
            }
        }
        catch {
            // 跳过格式异常的文件
        }
        return entries;
    }
    /**
     * 按条件查询知识条目。
     * @description 支持按 scope、products、type 过滤，以及按 keywords 关键词匹配和排序。
     *              只返回 status 为 active 的条目，默认限制返回 5 条。
     * @param filters - 查询过滤条件
     * @param filters.scope - 作用域过滤列表
     * @param filters.products - 产品过滤列表
     * @param filters.type - 条目类型过滤
     * @param filters.keywords - 关键词列表（用于模糊匹配 name 和 when 字段）
     * @param filters.limit - 最大返回条数，默认 5
     * @returns 匹配的知识条目数组
     */
    query(filters) {
        debugLog("知识索引: 查询知识条目");
        const all = [...this.index.project_entries, ...this.index.global_entries];
        const scopeSet = new Set(filters.scope ?? []);
        const productSet = new Set(filters.products ?? []);
        const results = all
            .filter((e) => e.status === "active")
            .filter((e) => {
            if (!filters.scope?.length)
                return true;
            return e.scope.some((s) => scopeSet.has(s) || s === "shared" || s === "*");
        })
            .filter((e) => {
            if (!filters.products?.length)
                return true;
            return e.products.some((p) => productSet.has(p) || p === "*");
        })
            .filter((e) => {
            if (!filters.type)
                return true;
            return e.type === filters.type;
        })
            .filter((e) => {
            if (!filters.route)
                return true;
            if (!e.routes || e.routes.length === 0)
                return true;
            // 只过滤声明了标准 IntentRoute 名称的条目
            const standardRoutes = ["skip_chat", "direct_answer", "artifact_generation",
                "source_extraction", "code_change", "analysis", "review", "acceptance",
                "planning", "operation"];
            const hasStandardRoutes = e.routes.some((r) => standardRoutes.includes(r) || r === "*");
            if (!hasStandardRoutes)
                return true;
            return e.routes.includes(filters.route) || e.routes.includes("*");
        })
            .filter((e) => {
            if (!filters.keywords?.length)
                return true;
            const score = this.keywordScore(e, filters.keywords);
            return score > 0;
        })
            .filter((e) => {
            if (!filters.stages?.length)
                return true;
            if (!e.stages)
                return true;
            if (e.stages.includes("*"))
                return true;
            return e.stages.some(s => filters.stages.includes(s));
        });
        if (filters.keywords?.length) {
            results.sort((a, b) => {
                const scoreA = this.keywordScore(a, filters.keywords);
                const scoreB = this.keywordScore(b, filters.keywords);
                return scoreB - scoreA;
            });
        }
        return results.slice(0, filters.limit ?? 5);
    }
    /**
     * 标记知识条目为已使用。
     * @description 增加指定条目的 usage_count 并更新 last_used_at 时间戳。
     * @param entryIds - 要标记的条目 ID 列表
     */
    markUsed(entryIds) {
        const idSet = new Set(entryIds);
        const now = new Date().toISOString();
        for (const entry of [...this.index.project_entries, ...this.index.global_entries]) {
            if (idSet.has(entry.id)) {
                entry.usage_count = (entry.usage_count ?? 0) + 1;
                entry.last_used_at = now;
            }
        }
        debugLog(`索引管理器: 已标记 ${entryIds.length} 条知识为已使用。`);
    }
    /**
     * 根据 ID 获取知识条目。
     * @param id - 条目 ID
     * @returns 匹配的知识条目，未找到则返回 undefined
     */
    getEntry(id) {
        debugLog("知识索引: 获取条目 — " + id);
        return (this.index.project_entries.find((e) => e.id === id) ||
            this.index.global_entries.find((e) => e.id === id));
    }
    /**
     * 获取所有知识条目（按来源分组）。
     * @returns 包含全局和项目知识条目的对象
     */
    getAllEntries() {
        debugLog("知识索引: 获取所有条目");
        return {
            global: this.index.global_entries,
            project: this.index.project_entries,
        };
    }
    /**
     * 在内存中添加知识条目。
     * @param entry - 要添加的知识条目
     */
    async addEntryInMemory(entry) {
        debugLog("知识索引: 内存添加条目 — " + entry.name);
        this.index.project_entries.push(entry);
        this.filePathToEntryMap.set(entry.file_path, entry);
    }
    /**
     * 重新加载知识索引。
     * @description 完全重建索引，重新扫描所有知识文件。
     */
    async reload() {
        debugLog("索引管理器: 重新加载索引...");
        await this.build();
    }
    /**
     * 执行知识健康检查和冲突检测。
     * @description 检查所有知识条目的健康状态（过期、零使用），
     *              对活跃项目条目执行冲突检测，并将状态变更持久化到磁盘。
     * @returns 包含健康问题和冲突的数组
     */
    async checkHealth() {
        debugLog("索引管理器: 执行健康检查...");
        const all = [...this.index.project_entries, ...this.index.global_entries];
        const issues = checkKnowledgeHealth(all);
        const activeProject = this.index.project_entries.filter((e) => e.status === "active");
        issues.push(...detectConflicts(activeProject));
        await persistHealthChanges(issues);
        debugLog(`索引管理器: 健康检查完成 — 发现 ${issues.length} 个问题。`);
        return issues;
    }
    /**
     * 计算关键词匹配分数（内部方法）。
     * @description 在条目的 name 和 when 字段中搜索关键词，返回匹配数。
     * @param entry - 知识条目
     * @param keywords - 关键词列表
     * @returns 匹配的关键词数量
     */
    keywordScore(entry, keywords) {
        const text = `${entry.name} ${entry.when}`.toLowerCase();
        return keywords.filter((kw) => {
            const lowerKw = kw.toLowerCase();
            // 空格分词匹配（英文）
            const spacedText = ` ${text} `;
            if (spacedText.includes(` ${lowerKw} `) || text.startsWith(`${lowerKw} `) || text.endsWith(` ${lowerKw}`)) {
                return true;
            }
            // 中文无空格场景：直接子串匹配
            if (text.includes(lowerKw)) {
                return true;
            }
            return false;
        }).length;
    }
    /**
     * 扫描目录中的知识文件（内部方法）。
     * @description 递归遍历目录，解析所有 .md 和 .yaml/.yml 文件。
     * @param dir - 要扫描的目录路径
     * @returns 解析后的知识条目数组
     */
    async scanDirectory(dir) {
        const entries = [];
        try {
            await fs.access(dir);
        }
        catch {
            return entries;
        }
        const files = await this.walkFiles(dir);
        for (const file of files) {
            const parsed = await this.parseFile(file);
            entries.push(...parsed);
        }
        return entries;
    }
    /**
     * 将原始数据解析为知识条目（内部方法）。
     * @description 从 frontmatter/解析数据中提取字段，缺失字段使用默认值。
     * @param data - 解析后的 frontmatter 或 YAML 数据
     * @param filePath - 文件路径
     * @param body - Markdown 正文内容（可选）
     * @returns 解析后的知识条目
     */
    parseEntry(data, filePath, body) {
        return {
            id: typeof data.id === "string" ? data.id : typeof data.name === "string" ? data.name : path.basename(filePath, path.extname(filePath)),
            file_path: filePath,
            name: typeof data.name === "string" ? data.name : path.basename(filePath, path.extname(filePath)),
            type: (typeof data.type === "string" ? data.type : typeof data.asset_kind === "string" ? data.asset_kind : this.inferType(filePath)),
            when: typeof data.when === "string" ? data.when : Array.isArray(data.primary_triggers) ? data.primary_triggers.join(", ") : typeof data.primary_triggers === "string" ? data.primary_triggers : "",
            body: body || undefined,
            scope: Array.isArray(data.scope) ? data.scope : ["shared"],
            products: Array.isArray(data.products) ? data.products : ["*"],
            tech_stack: typeof data.tech_stack === "string" ? data.tech_stack : undefined,
            last_used_at: typeof data.last_used_at === "string" ? data.last_used_at : undefined,
            created_at: typeof data.created_at === "string" ? data.created_at : new Date().toISOString().split("T")[0],
            updated_at: typeof data.updated_at === "string" ? data.updated_at : new Date().toISOString().split("T")[0],
            usage_count: typeof data.usage_count === "number" ? data.usage_count : 0,
            confidence: typeof data.confidence === "number" ? data.confidence : 1.0,
            routes: Array.isArray(data.routes) ? data.routes : typeof data.routes === "string" ? [data.routes] : ["*"],
            status: (typeof data.status === "string" ? data.status : typeof data.lifecycle_status === "string" ? data.lifecycle_status : filePath.includes("/gates/") ? "active" : "draft"),
            stages: Array.isArray(data.stages)
                ? data.stages.filter((s) => typeof s === "string")
                : typeof data.stages === "string"
                    ? data.stages.split(",").map((s) => s.trim()).filter(Boolean)
                    : undefined,
        };
    }
    /**
     * 根据文件路径推断知识条目类型（内部方法）。
     * @description 根据路径中包含的目录名推断条目类型。
     * @param filePath - 文件路径
     * @returns 推断出的知识条目类型
     */
    inferType(filePath) {
        const parts = filePath.split(path.sep);
        if (parts.includes("patterns"))
            return "pattern";
        if (parts.includes("procedures"))
            return "procedure";
        if (parts.includes("domain"))
            return "domain";
        if (parts.includes("acceptance_templates"))
            return "acceptance_template";
        if (parts.includes("governance"))
            return "governance_rule";
        if (parts.includes("review_rules"))
            return "review_rule";
        if (parts.includes("artifacts"))
            return "artifact_template";
        if (parts.includes("checklists"))
            return "checklist";
        if (parts.includes("gates"))
            return "governance_rule";
        if (parts.includes("internal") && parts.includes("rules"))
            return "governance_rule";
        return "pattern";
    }
    /**
     * 导出能力注册表的治理规则为 review_rule 条目，用于知识审计。
     * @description 将能力治理规则转换为可在知识索引中查询的条目格式。
     * @param caps - 能力规则数组
     * @param caps.policy_id - 策略 ID
     * @param caps.name - 策略名称
     * @param caps.state - 策略状态
     * @param caps.owner_module - 所属模块
     * @param caps.evidence - 证据描述
     * @param caps.promotion_conditions - 晋级条件列表
     * @param caps.demotion_conditions - 降级条件列表
     * @param caps.evidence_requirements - 证据要求列表
     * @param caps.reason - 原因说明
     * @returns 格式化后的治理规则条目数组
     */
    exportGovernanceRules(caps) {
        debugLog("知识索引: 导出治理规则 — 条数: " + caps.length);
        return caps.map((cap) => ({
            name: `${cap.policy_id}: ${cap.name}`,
            type: "governance_rule",
            scope: cap.owner_module,
            content: [
                `# ${cap.policy_id}: ${cap.name}`,
                ``,
                `状态: ${cap.state}`,
                `模块: ${cap.owner_module}`,
                `证据: ${cap.evidence}`,
                ``,
                `## 晋级条件`,
                ...cap.promotion_conditions.map((c) => `- ${c}`),
                ``,
                `## 降级条件`,
                ...cap.demotion_conditions.map((c) => `- ${c}`),
                ``,
                `## 证据要求`,
                ...cap.evidence_requirements.map((e) => `- ${e}`),
                ``,
                `## 原因`,
                cap.reason,
            ].join("\n"),
        }));
    }
    /**
     * 递归遍历目录获取文件列表（内部方法）。
     * @description 递归遍历目录，收集所有 .md 和 .yaml/.yml 文件路径。
     *              自动跳过 SKIP_DIRS 中定义的系统目录。
     * @param dir - 要遍历的目录
     * @param maxDepth - 最大递归深度，默认 10
     * @param currentDepth - 当前递归深度
     * @returns 文件路径数组
     */
    async walkFiles(dir, maxDepth = 10, currentDepth = 0) {
        if (currentDepth >= maxDepth)
            return [];
        const results = [];
        let dirEntries;
        try {
            dirEntries = await fs.readdir(dir, { withFileTypes: true });
        }
        catch {
            return results;
        }
        for (const entry of dirEntries) {
            if (SKIP_DIRS.has(entry.name))
                continue;
            const fullPath = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                results.push(...(await this.walkFiles(fullPath, maxDepth, currentDepth + 1)));
            }
            else if (/\.(md|ya?ml)$/.test(entry.name)) {
                results.push(fullPath);
            }
        }
        return results;
    }
}
//# sourceMappingURL=index_manager.js.map