/** * GovernanceAudit — 成熟度评分系统,覆盖 6 个治理维度。 * * 每个维度扫描相关文件,报告 findings 和 top actions。 * 整体分数为所有维度分数的平均值。 * * 不依赖 LLM 评分,只检查确定性事实。 * * @see docs/AGENT_GATE_LOOP_REFACTOR_PLAN.md — "Agent Governance", "最终成熟度验收" */ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; // ── Types ────────────────────────────────────────────────────────────────────── /** 单个治理维度的名称。 */ export type DimensionName = | "PromptMaturity" | "GateCoverage" | "ToolCoverage" | "LoopSafety" | "EvidenceTrust" | "DelegationSafety"; /** 单个维度的评分结果。 */ export interface MaturityDimension { /** 维度名称。 */ name: DimensionName; /** 0-100 分。 */ score: number; /** 扫描发现的条目,pass 和 fail 各一条。 */ findings: string[]; /** 建议优先执行的改进动作。 */ topActions: string[]; } /** 审计报告整体结果。 */ export interface GovernanceAuditReport { /** 各维度评分。 */ dimensions: MaturityDimension[]; /** 6 个维度的平均分。 */ overallScore: number; /** overallScore >= threshold 时为 true。 */ passed: boolean; /** 发布门禁阈值,默认 80。 */ threshold: number; } // ── Helpers ──────────────────────────────────────────────────────────────────── /** 读取文件内容,文件不存在返回空字符串。 */ function readFile(root: string, relativePath: string): string { const absolute = join(root, relativePath); if (!existsSync(absolute)) return ""; try { return readFileSync(absolute, "utf8"); } catch { return ""; } } /** 检查文件是否存在。 */ function fileExists(root: string, relativePath: string): boolean { return existsSync(join(root, relativePath)); } /** 检查文件中是否包含指定模式。 */ function fileContains(root: string, relativePath: string, pattern: string): boolean { return readFile(root, relativePath).includes(pattern); } /** 检查 package.json 中是否存在指定 script。 */ function packageHasScript(root: string, script: string): boolean { const content = readFile(root, "package.json"); return content.includes(`"${script}"`); } interface Check { passed: boolean; finding: string; action: string; } function runChecks(checks: Check[]): { score: number; findings: string[]; topActions: string[] } { const passed = checks.filter((c) => c.passed); const failed = checks.filter((c) => !c.passed); const score = checks.length > 0 ? Math.round((passed.length / checks.length) * 100) : 0; return { score, findings: [ ...passed.map((c) => `PASS: ${c.finding}`), ...failed.map((c) => `FAIL: ${c.finding}`), ], topActions: failed.map((c) => c.action).slice(0, 3), }; } // ── Dimension: PromptMaturity ────────────────────────────────────────────────── function auditPromptMaturity(root: string): MaturityDimension { const checks: Check[] = [ { passed: fileExists(root, "src/harness/context/prompt-section-registry.ts"), finding: "PromptSectionRegistry 模块存在", action: "创建 src/harness/context/prompt-section-registry.ts,实现 PromptSectionRegistry 类", }, { passed: fileContains(root, "src/harness/context/prompt-section-registry.ts", "class PromptSectionRegistry"), finding: "PromptSectionRegistry 类已定义", action: "在 prompt-section-registry.ts 中实现 PromptSectionRegistry 类", }, { passed: fileContains(root, "src/harness/context/prompt-section-registry.ts", "traceId"), finding: "Prompt section 支持 traceId 追踪", action: "为每个 prompt section 增加 traceId 字段,确保来源可追溯", }, { passed: fileExists(root, "src/harness/context/prompt-contract.ts"), finding: "PromptContract 类型定义存在", action: "创建 src/harness/context/prompt-contract.ts,定义 PromptContract 接口", }, { passed: fileContains(root, "src/harness/context/prompt-section-registry.ts", "compile"), finding: "PromptSectionRegistry 支持 compile 输出 PromptContract", action: "在 PromptSectionRegistry 中实现 compile() 方法,输出 PromptContract", }, { passed: fileContains(root, "src/harness/context/prompt-section-registry.ts", "always"), finding: "Prompt section 支持 always 标记(不可裁剪段)", action: "为 prompt section 增加 always 标记,确保 hard deny 等关键段落不被裁剪", }, ]; const result = runChecks(checks); return { name: "PromptMaturity", ...result }; } // ── Dimension: GateCoverage ──────────────────────────────────────────────────── function auditGateCoverage(root: string): MaturityDimension { const policyFiles = [ "src/harness/gates/policies/workspace-path-policy.ts", "src/harness/gates/policies/source-write-policy.ts", "src/harness/gates/policies/phase-gate-policy.ts", "src/harness/gates/policies/tdd-policy.ts", "src/harness/gates/policies/plan-path-policy.ts", "src/harness/gates/policies/tool-gateway-policy.ts", "src/harness/gates/policies/powershell-mutation-policy.ts", "src/harness/gates/policies/verify-command-policy.ts", "src/harness/gates/policies/delegation-policy.ts", ]; const existingPolicies = policyFiles.filter((f) => fileExists(root, f)); const policyCount = existingPolicies.length; const checks: Check[] = [ { passed: fileExists(root, "src/harness/gates/policy-registry.ts"), finding: "PolicyRegistry 模块存在", action: "创建 src/harness/gates/policy-registry.ts,实现 PolicyRegistry 类", }, { passed: fileContains(root, "src/harness/gates/policy-registry.ts", "class PolicyRegistry"), finding: "PolicyRegistry 类已定义", action: "在 policy-registry.ts 中实现 PolicyRegistry 类", }, { passed: policyCount >= 8, finding: `已注册 ${policyCount} 个策略(需 >= 8)`, action: `补充缺失的 policy 模块,当前已有 ${policyCount} 个,需达到 8 个以上`, }, { passed: fileExists(root, "src/harness/gates/gate-runner.ts"), finding: "GateRunner 模块存在", action: "创建 src/harness/gates/gate-runner.ts,实现 GateRunner 类", }, { passed: fileContains(root, "src/harness/gates/gate-runner.ts", "class GateRunner"), finding: "GateRunner 类已定义", action: "在 gate-runner.ts 中实现 GateRunner 类", }, { passed: fileContains(root, "src/harness/gates/gate-runner.ts", "runGate") || fileContains(root, "src/harness/gates/gate-runner.ts", "evaluate"), finding: "GateRunner 支持执行门禁检查", action: "在 GateRunner 中实现 runGate/evaluate 方法", }, ]; const result = runChecks(checks); return { name: "GateCoverage", ...result }; } // ── Dimension: ToolCoverage ──────────────────────────────────────────────────── function auditToolCoverage(root: string): MaturityDimension { const kdToolPatterns = [ "kd_plan_status", "kd_ask_user", "kd_search", "kd_check", "kd_sdk_signature", "kd_subagent", ]; // Gate coverage via V2 event interceptor (tool_call → runGateV2) or directly defined const eventInterceptor = readFile(root, "extensions/kingdee-harness-tool-events.ts"); const toolGatewayContent = readFile(root, "src/harness/tool-gateway.ts"); const toolPipelineContent = readFile(root, "src/harness/tools/tool-pipeline.ts"); const kdToolsContent = readFile(root, "extensions/kingdee-tools.ts"); const harnessToolsContent = readFile(root, "extensions/kingdee-harness-tools.ts"); const subagentsContent = readFile(root, "extensions/kingdee-subagents.ts"); // Gate coverage: tools are protected via V2 event interceptor (tool_call → runGateV2("pre-tool")) // The event interceptor covers ALL tools including kd_* without needing per-tool ToolPipeline wrappers. const v2GateActive = eventInterceptor.includes("runGateV2") && eventInterceptor.includes("pre-tool"); const registeredKdTools = kdToolPatterns.filter((pattern) => toolGatewayContent.includes(pattern) || toolPipelineContent.includes(pattern) || (v2GateActive && ( kdToolsContent.includes(pattern) || harnessToolsContent.includes(pattern) || subagentsContent.includes(pattern) )) ); const checks: Check[] = [ { passed: fileExists(root, "src/harness/tools/tool-pipeline.ts"), finding: "ToolPipeline 模块存在", action: "创建 src/harness/tools/tool-pipeline.ts,实现 ToolPipeline 类", }, { passed: fileContains(root, "src/harness/tools/tool-pipeline.ts", "class ToolPipeline") || fileContains(root, "src/harness/tools/tool-pipeline.ts", "ToolPipeline"), finding: "ToolPipeline 已定义", action: "在 tool-pipeline.ts 中实现 ToolPipeline 接口或类", }, { passed: registeredKdTools.length >= 4, finding: `已有 ${registeredKdTools.length} 个 kd_* 工具进入 V2 pre-tool gate(${registeredKdTools.join(", ")})`, action: "确保 kd_plan_status、kd_ask_user、kd_search、kd_check 等核心工具通过 V2 pre-tool gate 事件拦截器受控", }, { passed: fileContains(root, "src/harness/tool-gateway.ts", "isWriteLikeToolCall"), finding: "写类工具判断逻辑集中", action: "在 tool-gateway.ts 中实现 isWriteLikeToolCall 集中判断", }, { passed: fileExists(root, "src/harness/tools/tool-contract.ts"), finding: "ToolResultContract 模块存在", action: "创建 src/harness/tools/tool-contract.ts,定义 ToolResultContract", }, { passed: fileContains(root, "extensions/kingdee-powershell-tool.ts", "powerShellMutationBlockReason") || fileExists(root, "src/harness/gates/policies/powershell-mutation-policy.ts"), finding: "PowerShell 变更命令拦截已实现", action: "确保 PowerShell wrapper 在执行前调用 gate check", }, ]; const result = runChecks(checks); return { name: "ToolCoverage", ...result }; } // ── Dimension: LoopSafety ───────────────────────────────────────────────────── function auditLoopSafety(root: string): MaturityDimension { const checks: Check[] = [ { passed: fileExists(root, "src/harness/loop/loop-kernel.ts"), finding: "LoopKernel 模块存在", action: "创建 src/harness/loop/loop-kernel.ts,实现 LoopKernel 类", }, { passed: fileContains(root, "src/harness/loop/loop-kernel.ts", "class LoopKernel") || fileContains(root, "src/harness/loop/loop-kernel.ts", "LoopKernel"), finding: "LoopKernel 已定义", action: "在 loop-kernel.ts 中实现 LoopKernel 类", }, { passed: fileExists(root, "src/harness/loop/stop-hook.ts"), finding: "StopHook 模块存在", action: "创建 src/harness/loop/stop-hook.ts,实现 StopHook 机制", }, { passed: fileContains(root, "src/harness/loop/stop-hook.ts", "StopHookType") || fileContains(root, "src/harness/loop/stop-hook.ts", "stopHook"), finding: "StopHook 类型已定义", action: "在 stop-hook.ts 中定义 StopHookType 枚举和评估逻辑", }, { passed: fileContains(root, "src/harness/loop/stop-hook.ts", "budget-exhausted"), finding: "StopHook 支持预算耗尽检测", action: "确保 StopHook 覆盖 budget-exhausted 场景", }, { passed: fileContains(root, "src/harness/loop/stop-hook.ts", "stuck"), finding: "StopHook 支持 stuck 检测", action: "确保 StopHook 覆盖 stuck(重复失败)场景", }, ]; const result = runChecks(checks); return { name: "LoopSafety", ...result }; } // ── Dimension: EvidenceTrust ─────────────────────────────────────────────────── function auditEvidenceTrust(root: string): MaturityDimension { const checks: Check[] = [ { passed: fileExists(root, "src/harness/evidence/evidence-store.ts"), finding: "EvidenceStore 模块存在", action: "创建 src/harness/evidence/evidence-store.ts,实现 EvidenceStore 类", }, { passed: fileContains(root, "src/harness/evidence/evidence-store.ts", "class EvidenceStore"), finding: "EvidenceStore 类已定义", action: "在 evidence-store.ts 中实现 EvidenceStore 类", }, { passed: fileExists(root, "src/harness/evidence/evidence-source.ts"), finding: "EvidenceSource 类型定义存在", action: "创建 src/harness/evidence/evidence-source.ts,定义 EvidenceSourceTrust 枚举", }, { passed: fileContains(root, "src/harness/evidence/evidence-source.ts", "EvidenceSourceTrust"), finding: "EvidenceSourceTrust 枚举已定义", action: "在 evidence-source.ts 中定义 EvidenceSourceTrust 类型", }, { passed: fileContains(root, "src/harness/evidence/evidence-source.ts", "local-command"), finding: "sourceTrust 包含 local-command 级别", action: "确保 EvidenceSourceTrust 包含 local-command、project-metadata 等信任级别", }, { passed: fileContains(root, "src/harness/evidence/evidence-store.ts", "validateForShip") || fileContains(root, "src/harness/evidence/evidence-store.ts", "validate"), finding: "EvidenceStore 支持 ship 验证", action: "在 EvidenceStore 中实现 validateForShip 方法", }, ]; const result = runChecks(checks); return { name: "EvidenceTrust", ...result }; } // ── Dimension: DelegationSafety ──────────────────────────────────────────────── function auditDelegationSafety(root: string): MaturityDimension { const checks: Check[] = [ { passed: fileExists(root, "src/harness/gates/policies/delegation-policy.ts"), finding: "DelegationPolicy 模块存在", action: "创建 src/harness/gates/policies/delegation-policy.ts,实现 DelegationPolicy", }, { passed: fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "pre-delegation") || fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "preDelegation"), finding: "DelegationPolicy 支持 pre-delegation 门禁", action: "在 DelegationPolicy 中实现 pre-delegation checkpoint 检查", }, { passed: fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "post-delegation") || fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "postDelegation"), finding: "DelegationPolicy 支持 post-delegation 门禁", action: "在 DelegationPolicy 中实现 post-delegation checkpoint 检查", }, { passed: fileContains(root, "src/harness/delegation.ts", "DelegationRole"), finding: "DelegationRole 类型已定义", action: "在 delegation.ts 中定义 DelegationRole 枚举", }, { passed: fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "evidence") || fileContains(root, "src/harness/gates/policies/delegation-policy.ts", "facts"), finding: "post-delegation 阻止子 agent 输出写入主 evidence/facts", action: "确保 post-delegation 检查阻止子 agent 输出直接成为主 evidence 或 facts", }, { passed: fileContains(root, "src/harness/delegation.ts", "ROLE_GUIDANCE") || fileContains(root, "src/harness/delegation.ts", "role"), finding: "委派角色有明确的职责边界定义", action: "为每个 DelegationRole 定义职责边界和允许操作", }, ]; const result = runChecks(checks); return { name: "DelegationSafety", ...result }; } // ── Main audit function ──────────────────────────────────────────────────────── /** * 执行治理成熟度审计。 * * 扫描项目中的 6 个治理维度,返回每个维度的评分、发现和建议动作。 * 整体分数为所有维度分数的平均值。 * * @param projectRoot - 项目根目录。 * @param threshold - 发布门禁阈值,默认 80。 * @returns 审计报告。 */ export function auditMaturity(projectRoot: string, threshold = 80): GovernanceAuditReport { const dimensions: MaturityDimension[] = [ auditPromptMaturity(projectRoot), auditGateCoverage(projectRoot), auditToolCoverage(projectRoot), auditLoopSafety(projectRoot), auditEvidenceTrust(projectRoot), auditDelegationSafety(projectRoot), ]; const overallScore = Math.round( dimensions.reduce((sum, d) => sum + d.score, 0) / dimensions.length ); // Hard gate: any dimension with a FAIL finding blocks the release const hasDimensionFailure = dimensions.some( (d) => d.findings.some((f) => f.startsWith("FAIL: ")) ); return { dimensions, overallScore, passed: overallScore >= threshold && !hasDimensionFailure, threshold, }; } // ── Report formatting ────────────────────────────────────────────────────────── /** * 格式化审计报告为可读文本。 */ export function formatGovernanceAuditReport(report: GovernanceAuditReport): string { const lines: string[] = [ `KCode Governance Audit: ${report.overallScore}/100 (${report.passed ? "PASS" : "FAIL"}, threshold=${report.threshold})`, "", ]; for (const dim of report.dimensions) { lines.push(`## ${dim.name}: ${dim.score}/100`); for (const finding of dim.findings) { lines.push(` - ${finding}`); } if (dim.topActions.length > 0) { lines.push(" Top Actions:"); for (const action of dim.topActions) { lines.push(` 1. ${action}`); } } lines.push(""); } return lines.join("\n"); } // ── CLI entry point (npx tsx governance-audit.ts) ────────────────────────────── const isMain = process.argv[1]?.endsWith("governance-audit.ts") || process.argv[1]?.endsWith("governance-audit.js"); if (isMain) { const projectRoot = join(import.meta.dirname ?? __dirname, "..", "..", ".."); const report = auditMaturity(projectRoot); console.log(formatGovernanceAuditReport(report)); process.exit(report.passed ? 0 : 1); }