/** * PhaseGatePolicy — blocks phase advance when gate not passed. * * Checkpoints: pre-phase-transition, pre-write * * Logic migrated from: * - src/harness/gates.ts: canEnterPhase logic */ import type { GateContext, GateFinding } from "../findings.ts"; import type { ActiveRun, KdPhase } from "../../types.ts"; export const PHASE_GATE_POLICY_NAME = "phase-gate-policy"; const PHASE_ORDER: readonly KdPhase[] = ["discuss", "spec", "plan", "execute", "verify", "ship"]; const QUICK_PHASE_ORDER: readonly KdPhase[] = ["discuss", "plan", "execute", "verify"]; export function evaluatePhaseGatePolicy(ctx: GateContext): GateFinding[] { const findings: GateFinding[] = []; if (ctx.checkpoint === "pre-phase-transition") { findings.push(...evaluatePrePhaseTransition(ctx)); } else if (ctx.checkpoint === "pre-write") { findings.push(...evaluatePreWrite(ctx)); } return findings; } function evaluatePrePhaseTransition(ctx: GateContext): GateFinding[] { const findings: GateFinding[] = []; const { run, phaseTarget } = ctx; if (!phaseTarget) return findings; // Quick mode has relaxed gates if (run?.mode === "quick") return findings; // Check if target phase is valid const phaseOrder = run?.mode === "normal" ? PHASE_ORDER : QUICK_PHASE_ORDER; if (!phaseOrder.includes(phaseTarget)) { findings.push({ id: "PGP-001", severity: "hard-deny", policy: PHASE_GATE_POLICY_NAME, message: `阶段 ${phaseTarget} 不属于当前 Harness 模式 ${run?.mode ?? "unknown"}。`, nextAction: `可选阶段:${phaseOrder.join(", ")}`, }); return findings; } // Check required artifacts for the target phase if (run) { const missingArtifacts = getMissingArtifacts(run, phaseTarget, phaseOrder); if (missingArtifacts.length > 0) { findings.push({ id: "PGP-002", severity: "hard-deny", policy: PHASE_GATE_POLICY_NAME, message: `进入 ${phaseTarget} 阶段缺少必需产物:${missingArtifacts.join(", ")}。`, nextAction: `先完成以下产物:${missingArtifacts.join(", ")}`, }); } } return findings; } function evaluatePreWrite(ctx: GateContext): GateFinding[] { const findings: GateFinding[] = []; const { run } = ctx; // Quick mode has relaxed gates if (!run || run.mode === "quick") return findings; // If not in execute phase, source writes are already blocked by SourceWritePolicy if (run.phase !== "execute") return findings; // Check if execute gate is passed (basic validation) if (!run.gate?.passed) { findings.push({ id: "PGP-003", severity: "soft-deny", policy: PHASE_GATE_POLICY_NAME, message: "当前 execute 门禁未通过,禁止写产品代码。", nextAction: "先完成 execute 阶段的门禁检查。", }); } return findings; } // ── Utility functions (reimplemented from src/harness/gates.ts) ────────────── const PHASE_ARTIFACTS: Partial> = { spec: "SPEC.md", plan: "PLAN.md", execute: "EXECUTION.md", verify: "VERIFY.md", ship: "SHIP.md", }; function getMissingArtifacts(run: ActiveRun, targetPhase: KdPhase, phaseOrder: readonly KdPhase[]): string[] { const targetIndex = phaseOrder.indexOf(targetPhase); const missing: string[] = []; for (let i = 0; i < targetIndex; i++) { const phase = phaseOrder[i]; const artifact = PHASE_ARTIFACTS[phase]; if (artifact && !hasArtifact(run, artifact)) { missing.push(artifact); } } return missing; } function hasArtifact(run: ActiveRun, artifact: string): boolean { // Check if the artifact exists in the run's artifacts or evidence const artifacts = run.artifacts ?? {}; return artifact in artifacts; }