/** * PlanPathPolicy — blocks writes to paths not in PLAN.md. * * Checkpoints: pre-write * * Logic migrated from: * - src/harness/path-policy.ts: planWriteBlockReason, flagshipWriteBlockReason */ import type { GateContext, GateFinding } from "../findings.ts"; import type { ActiveRun } from "../../types.ts"; import { readArtifact } from "../../artifacts.ts"; export const PLAN_PATH_POLICY_NAME = "plan-path-policy"; const SOURCE_EXTENSIONS = new Set([ ".java", ".kt", ".kts", ".xml", ".properties", ".yml", ".yaml", ".sql", ".ksql", ".cs", ".py", ]); export function evaluatePlanPathPolicy(ctx: GateContext): GateFinding[] { const findings: GateFinding[] = []; const { path, run, cwd, payload } = ctx; if (!run) return findings; if (ctx.checkpoint === "pre-phase-transition") { if (ctx.phaseTarget === "execute" && (run?.profile?.product ?? run?.product) === "flagship") { const plan = readArtifact(cwd, run, "plan") ?? ""; const flagshipProblem = flagshipPlanBlockReason(cwd, run, plan); if (flagshipProblem) { findings.push({ id: "PPP-003", severity: "hard-deny", policy: PLAN_PATH_POLICY_NAME, message: flagshipProblem, nextAction: "旗舰版产品必须先在 PLAN.md 中规划 code/ 目录下的源码目标路径。", }); } } return findings; } if (!path || !isSourceLikePath(path)) return findings; // Normalize path const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path); // Skip .pi/ paths if (normalized.startsWith(".pi/")) return findings; // Check flagship write block if ((run?.profile?.product ?? run?.product) === "flagship") { const flagshipReason = flagshipWriteBlockReason(run, normalized, cwd); if (flagshipReason) { findings.push({ id: "PPP-002", severity: "hard-deny", policy: PLAN_PATH_POLICY_NAME, message: flagshipReason, nextAction: "旗舰版产品必须将源码放在 code/ 目录下。", }); return findings; } } // Check plan write block (only in execute phase) if (run?.phase === "execute") { const plan = typeof payload === "string" ? payload : ""; const planReason = planWriteBlockReason(normalized, plan); if (planReason) { findings.push({ id: "PPP-001", severity: "soft-deny", policy: PLAN_PATH_POLICY_NAME, message: planReason, nextAction: "先在 PLAN.md 中声明要修改的文件路径,再执行写入。", }); } } return findings; } // ── Utility functions (reimplemented from src/harness/path-policy.ts) ──────── function flagshipWriteBlockReason(run: ActiveRun, normalizedPath: string, cwd?: string): string | undefined { if ((run?.profile?.product ?? run?.product) !== "flagship") return undefined; if (!cwd || !hasWorkspaceCodeDir(cwd)) return undefined; if (!normalizedPath.startsWith("code/")) { return `旗舰版产品源码必须写入 code/ 目录,当前路径 ${normalizedPath} 不符合要求。`; } return undefined; } function planWriteBlockReason(normalizedPath: string, plan: string): string | undefined { if (!plan) return undefined; if (planMentionsPath(plan, normalizedPath)) return undefined; return `路径 ${normalizedPath} 未在 PLAN.md 中声明,禁止写入。`; } function planMentionsPath(plan: string, normalizedPath: string): boolean { const normalizedPlan = normalizeRelativePath(plan); const escaped = escapeRegExp(normalizedPath); return new RegExp(`(?:^|[\\s\`"'(])${escaped}(?:[\\s\`"')]|$)`, "i").test(normalizedPlan); } function hasWorkspaceCodeDir(cwd: string): boolean { try { const { existsSync } = require("node:fs"); const { join } = require("node:path"); return existsSync(join(cwd, "code")); } catch { return false; } } function isSourceLikePath(path: string): boolean { const normalized = normalizeRelativePath(path); const lastSegment = normalized.split("/").at(-1) ?? normalized; const dotIndex = lastSegment.lastIndexOf("."); if (dotIndex < 0) return false; return SOURCE_EXTENSIONS.has(lastSegment.slice(dotIndex).toLowerCase()); } function normalizeRelativePath(path: string): string { return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, ""); } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined, plan: string): string | undefined { if ((run?.profile?.product ?? run?.product) !== "flagship") return undefined; if (hasWorkspaceCodeDir(cwd)) { if (/(?:^|[\s\`"'(])code[\\/][a-zA-Z0-9_\-\.\/]+/i.test(plan)) return undefined; return "不能进入 execute:星空旗舰版 PLAN.md 必须先记录当前项目 code/ 下的实际目标路径;不要按固定模块规则猜路径。"; } if (planMentionsDiscoveredSourcePath(plan)) return undefined; return "不能进入 execute:PLAN.md 必须先记录已检查当前项目结构,并写明实际源码根或目标文件路径;当前项目没有 code/ 时更不能猜路径。"; } function planMentionsDiscoveredSourcePath(plan: string): boolean { return /(?:^|[\s`"'(])(?:src[\\/]main[\\/]java|src[\\/]main[\\/]resources|[^`\n]*[\\/](?:src[\\/]main[\\/]java|src[\\/]main[\\/]resources)|[^`\n]*\.(?:java|xml|properties|yml|yaml|sql|ksql|py))(?:[\s`"')]|$)/i.test( plan, ); } import { isAbsolute, relative } from "node:path";