import * as fs from "node:fs"; import * as path from "node:path"; export interface CandidateRule { id: string; source: "user-correction" | "failure-pattern"; status: "candidate" | "approved" | "rejected"; summary: string; condition: { projectType?: string; tool?: string; commandRegex?: string; }; action: "block" | "warn" | "preflight" | "ask"; message: string; } export function detectCorrectionCandidate(text: string): CandidateRule | null { const normalized = text.trim(); if (!normalized) return null; if (/jangan pakai go build biasa untuk wails/i.test(normalized) || /never use plain go build for wails/i.test(normalized)) { return { id: "candidate-wails-no-plain-go-build", source: "user-correction", status: "candidate", summary: "Block plain go build for Wails projects.", condition: { projectType: "wails", tool: "bash", commandRegex: "\\bgo\\s+build\\b", }, action: "block", message: "Wails project: use wails build -clean instead of plain go build.", }; } if (/jangan|never use|do not use|must not use/i.test(normalized)) { return { id: `candidate-${Buffer.from(normalized).toString("hex").slice(0, 24)}`, source: "user-correction", status: "candidate", summary: normalized, condition: {}, action: "ask", message: normalized, }; } return null; } export function approvalPrompt(rule: CandidateRule): string { return `Candidate policy rule detected: ${rule.summary}\nApprove promotion to project policy?`; } export function promoteRuleToProjectPolicy(projectRoot: string, rule: CandidateRule): string { const policyPath = path.join(projectRoot, '.pi', 'minimax-policy.json'); let policy = { version: 1, rules: [] }; try { if (fs.existsSync(policyPath)) { policy = JSON.parse(fs.readFileSync(policyPath, 'utf-8')); } } catch { policy = { version: 1, rules: [] }; } const rules = Array.isArray(policy.rules) ? policy.rules : []; if (!rules.find((entry) => entry && entry.id === rule.id)) { rules.push({ ...rule, status: 'approved' }); } const next = { ...policy, rules }; fs.mkdirSync(path.dirname(policyPath), { recursive: true }); fs.writeFileSync(policyPath, JSON.stringify(next, null, 2)); return policyPath; }