import fss from "node:fs";
import path from "node:path";
import { resolveProjectConfig } from "../../knowledge/loader.js";
import { debugLog, userError, jsonSafeError } from "../../engine/core/logger.js";
import { getDesignLifecycleArtifactPaths } from "../../engine/contracts/design_lifecycle_contract.js";
import { isSameOrDescendantPath } from "../../engine/core/path_scope_utils.js";
import { resolveProjectPath, args } from "../args.js";
import { errorMessage, safeJsonParse } from "../../engine/core/helpers.js";
import { verifyOutputAgainstSchema } from "../../engine/templates/standard_asset_contract.js";
import { hasArtifactSchema } from "../../engine/contracts/artifact_schema_registry.js";
import { scanArtifactForOmissions } from "../../engine/contracts/omission_scanner.js";
import { detectLazyPatterns } from "../../engine/contracts/lazy_pattern_detector.js";
import { ARTIFACT_ALIASES } from "../../engine/pipeline/intent_signal_extractor.js";
export { resolveProjectPath };
export async function cmdCheckWrite() {
    debugLog("CLI: 执行 cmdCheckWrite");
    try {
        // 所有适配器均通过 stdin 读取 JSON（Claude Code / Codex 统一）
        let toolInput;
        {
            const chunks = [];
            for await (const chunk of process.stdin) {
                chunks.push(chunk);
            }
            const stdin = Buffer.concat(chunks).toString().trim();
            if (stdin)
                toolInput = stdin;
        }
        // 回退: 从命令行参数读取
        if (!toolInput) {
            toolInput = args[0];
        }
        if (!toolInput) {
            process.exit(0); // 无输入，放行
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK 桥接/动态 JSON 解析，字段由运行时决定
        const parsed = safeJsonParse(toolInput);
        if (!parsed) {
            process.exit(0); // 无有效 JSON，放行
        }
        let filePath;
        let content;
        {
            // Claude Code: { tool_input: { file_path, content/new_string } } 格式
            // Codex: { input: { file_path, content/new_string } } 格式
            // 回退: 顶层 { file_path, content/new_string }
            const input = parsed.tool_input || parsed.input || parsed;
            filePath = input.file_path || input.path || "";
            content = input.content || input.new_string || "";
        }
        // 加载配置
        const projectPath = resolveProjectPath();
        const { config } = await resolveProjectConfig(projectPath);
        // 从配置中收集收窄路径（空 = 黑名单模式，不收窄）
        const allowedPaths = [
            ...(config.scope.backend ?? []),
            ...(config.scope.frontend ?? []),
            ...config.repos.flatMap((r) => r.scope ?? []),
        ].map((p) => path.resolve(projectPath, p));
        // 路径规范化
        const rawTargetFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(projectPath, filePath);
        const projectRealPath = fss.realpathSync(projectPath);
        const targetFilePath = isSameOrDescendantPath(rawTargetFilePath, projectPath, { caseSensitive: false })
            ? path.join(projectRealPath, path.relative(projectPath, rawTargetFilePath))
            : rawTargetFilePath;
        const relativeFilePath = path.relative(projectRealPath, targetFilePath).replaceAll(path.sep, "/");
        // 使用 scope_controller 统一检查：黑名单 → 收窄 → 密钥
        const { checkScope } = await import("../../engine/pipeline/scope_controller.js");
        const result = await checkScope(targetFilePath, allowedPaths, content || undefined, relativeFilePath);
        if (result.severity === "blocked") {
            const denyOutput = {
                hookSpecificOutput: {
                    hookEventName: "PreToolUse",
                    permissionDecision: "deny",
                    permissionDecisionReason: result.reason || "范围检查阻止了写入",
                },
            };
            process.stdout.write(JSON.stringify(denyOutput));
            process.exit(0);
        }
        if (result.severity === "warning") {
            jsonSafeError(JSON.stringify(result));
        }
        const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeFilePath);
        if (implementationGateReason) {
            denyPreToolUse(implementationGateReason);
            process.exit(0);
        }
        // L4: 产物结构校验
        if (content) {
            const artifactResult = evaluateArtifactStructure(relativeFilePath, content, projectPath);
            if (artifactResult.severity === "blocked") {
                denyPreToolUse(artifactResult.reason || "产物结构校验失败");
                process.exit(0);
            }
            if (artifactResult.severity === "warning") {
                jsonSafeError(artifactResult.reason || "产物结构建议");
            }
        }
        // L5: .ts 文件偷懒模式检测
        if (content && /\.ts$/.test(relativeFilePath) && !/\.d\.ts$/.test(relativeFilePath)) {
            const lazyFindings = detectLazyPatterns(content, relativeFilePath);
            const hardFails = lazyFindings.filter((f) => f.severity === "hard_fail");
            const warnings = lazyFindings.filter((f) => f.severity === "warning");
            if (hardFails.length > 0) {
                denyPreToolUse(`代码质量门禁拦截: ${hardFails.length} 个偷懒模式\n` +
                    hardFails.map((f) => `  L${f.line_number} [${f.pattern}] ${f.message}: ${f.evidence}`).join("\n"));
                process.exit(0);
            }
            if (warnings.length > 0) {
                jsonSafeError(`代码质量警告: ${warnings.length} 个待处理\n` +
                    warnings.map((f) => `  L${f.line_number} [${f.pattern}] ${f.message}`).join("\n"));
            }
        }
        process.exit(0); // 允许
    }
    catch (e) {
        userError(`SoloForge 写入检查错误: ${errorMessage(e)}`);
        process.exit(1);
    }
}
export function isImplementationSourcePath(relativeFilePath) {
    if (/^(?:docs|templates|\.soloforge|\.claude|\.codex|\.trae)\//.test(relativeFilePath))
        return false;
    if (/(^|\/)(?:package-lock|pnpm-lock|yarn.lock)$/.test(relativeFilePath))
        return false;
    return /\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|yml|yaml|properties)$/.test(relativeFilePath);
}
export function isDesignArtifactPath(relativeFilePath) {
    const lifecyclePaths = getDesignLifecycleArtifactPaths();
    if ([
        lifecyclePaths.decision_record,
        lifecyclePaths.architecture_document,
        lifecyclePaths.database_document,
        lifecyclePaths.api_document,
        lifecyclePaths.optional_detail_design_document,
        lifecyclePaths.slice_plan,
        lifecyclePaths.consistency_report,
        lifecyclePaths.traceability_matrix,
        lifecyclePaths.openapi,
    ].includes(relativeFilePath))
        return true;
    return /^docs\/architecture\//.test(relativeFilePath) ||
        /^docs\/traceability\//.test(relativeFilePath) ||
        /^docs\/api\/openapi\.(?:ya?ml|json)$/.test(relativeFilePath) ||
        /^db\/(?:migrations?|schema|changelog)\//.test(relativeFilePath) ||
        /(?:^|\/)(?:src\/main\/resources\/)?db\/(?:migrations?|schema|changelog)\//.test(relativeFilePath);
}
export function denyPreToolUse(reason) {
    process.stdout.write(JSON.stringify({
        hookSpecificOutput: {
            hookEventName: "PreToolUse",
            permissionDecision: "deny",
            permissionDecisionReason: reason,
        },
    }));
}
export async function evaluateImplementationWriteGate(projectPath, relativeFilePath) {
    // 问题六十二/七十三: Edit/Write/Bash 都必须遵守同一编码前门禁。
    // 设计资产本身允许继续修订；非设计业务实现文件在产物包复验通过前禁止写入。
    if (isDesignArtifactPath(relativeFilePath) || !isImplementationSourcePath(relativeFilePath))
        return undefined;
    const stateDir = path.join(projectPath, ".soloforge", "state");
    if (!fss.existsSync(stateDir))
        return undefined;
    const { TaskContextManager } = await import("../../engine/pipeline/task_context.js");
    const manager = new TaskContextManager(stateDir);
    const currentTask = await manager.getCurrentTask();
    const designPack = currentTask?.design_artifact_pack;
    if (currentTask && ["classifying", "clarifying", "expanding"].includes(currentTask.status)) {
        return `当前 SoloForge 任务仍处于 ${currentTask.status}，说明分类、澄清或意图膨胀未闭合。不得绕过 架构设计，不得取消/归档当前任务后改用 Bash/Edit/Write 直接写业务实现，也不得把 Bash 作为恢复路径。请先运行 soloforge next 查看阻断项；按返回的 auto_resolvable 或 recovery_command 补齐后重新 架构设计。只有 soloforge next 明确判定 current-task 陈旧且用户确认时，才可运行 sf_status action=archive_stale confirm=true；归档后仍必须重新 需求分析 → 架构设计 建立受控编码任务。`;
    }
    if (!currentTask && hasDesignArtifacts(projectPath)) {
        return "检测到项目已有架构/详细/API/数据库设计产物，但当前没有受控 SoloForge 任务。不得绕过 需求分析/架构设计 直接写业务实现，不得改用 Bash/Edit/Write 先写代码再补验证；请先运行 soloforge next 或通过 MCP 建立编码任务。";
    }
    if (currentTask && !designPack && hasDesignArtifacts(projectPath)) {
        return "检测到项目已有设计产物，但当前任务缺少 design_artifact_pack 复验状态。不得取消任务后直接写业务实现，不得改用 Bash 绕过；请先运行 soloforge audit-design-artifacts，并通过 架构设计/联调验证 绑定设计产物包。";
    }
    if (designPack && designPack.status !== "implementation_ready") {
        return `设计产物包状态为 ${designPack.status}，不得改用 Bash/Edit/Write 绕过设计复验；请先完善 docs/architecture、docs/traceability、docs/api/openapi 或 db/migration|migrations|schema|changelog 并通过复验，再写入业务实现`;
    }
    return undefined;
}
export function hasDesignArtifacts(projectPath) {
    const lifecyclePaths = getDesignLifecycleArtifactPaths();
    const candidates = [
        lifecyclePaths.decision_record,
        lifecyclePaths.architecture_document,
        lifecyclePaths.database_document,
        lifecyclePaths.api_document,
        lifecyclePaths.optional_detail_design_document,
        lifecyclePaths.slice_plan,
        lifecyclePaths.consistency_report,
        lifecyclePaths.traceability_matrix,
        lifecyclePaths.openapi,
        path.join(projectPath, "docs", "api", "openapi.yml"),
        ...lifecyclePaths.sql_roots,
    ].map((candidate) => path.join(projectPath, candidate));
    return candidates.some((candidate) => fss.existsSync(candidate));
}
// ── L4: 产物结构校验 ──
/**
 * 从文件路径推断产物类型。
 *
 * 匹配策略：
 * 1. 路径中包含 ARTIFACT_ALIASES 的 defaultName 关键词
 * 2. .soloforge/output/ 路径下的 .md 文件
 */
export function inferArtifactKindFromPath(relativeFilePath) {
    // 策略 1：通过 ARTIFACT_ALIASES 的 defaultName 关键词匹配
    for (const alias of ARTIFACT_ALIASES) {
        if (alias.patterns.some((p) => p.test(relativeFilePath))) {
            return alias.kind;
        }
    }
    // 策略 2：.soloforge/output/ 路径下的 .md 文件，从文件名提取 kind
    const outputMatch = relativeFilePath.match(/\.soloforge\/output\/\d+-(.+)\.md$/);
    if (outputMatch) {
        const slug = outputMatch[1];
        for (const alias of ARTIFACT_ALIASES) {
            const nameSlug = alias.defaultName.replace(/\s+/g, "-").toLowerCase();
            if (slug.includes(nameSlug) || nameSlug.includes(slug)) {
                return alias.kind;
            }
        }
    }
    return undefined;
}
/**
 * L4 产物结构校验 — 对写入已知产物类型的文件检查内容完整性。
 *
 * 校验策略（强化版 — 防偷懒门禁）：
 * - 基础检测：verifyOutputAgainstSchema（缺失章节/表格/字段）
 * - 增强检测：scanArtifactForOmissions（空洞章节、占位符、空表格）
 * - 任何 hard_fail finding ≥ 1 → blocked（旧阈值：≥3 且 ≥2 section missing）
 * - 任何 warning finding → warning
 * - 无 finding → pass
 */
export function evaluateArtifactStructure(relativeFilePath, content, projectPath) {
    // 仅对 .md 文件执行校验
    if (!relativeFilePath.endsWith(".md"))
        return { severity: "pass" };
    // 内容过短（< 100 字符）跳过校验，可能是初始写入
    if (content.length < 100)
        return { severity: "pass" };
    const kind = inferArtifactKindFromPath(relativeFilePath);
    if (!kind || !hasArtifactSchema(kind))
        return { severity: "pass" };
    // ── 增强检测：遗漏扫描（空洞章节 + 占位符 + 空表格） ──
    const omissionResult = scanArtifactForOmissions(kind, content);
    if (!omissionResult.passed) {
        // 有 hard_fail → blocked
        const hardFails = omissionResult.findings.filter((f) => f.severity === "hard_fail");
        const reasons = hardFails.map((f) => f.message).join("；");
        return {
            severity: "blocked",
            reason: `产物遗漏扫描失败（${kind}）：${reasons}`,
        };
    }
    // ── 基础检测：verifyOutputAgainstSchema（字段级/附属产物级） ──
    const result = verifyOutputAgainstSchema(kind, content, projectPath);
    if (result.passed) {
        // 基础和增强都通过 → 检查是否有 warning 级 findings
        const warnings = omissionResult.findings.filter((f) => f.severity === "warning");
        if (warnings.length > 0) {
            const reasons = warnings.map((f) => f.message).join("；");
            return {
                severity: "warning",
                reason: `产物结构建议（${kind}）：${reasons}`,
            };
        }
        return { severity: "pass" };
    }
    // 基础检测未通过（字段缺失等） → warning
    return {
        severity: "warning",
        reason: `产物结构建议（${kind}）：${result.messages.join("; ")}`,
    };
}
//# sourceMappingURL=check_write.js.map