import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import yaml from 'js-yaml'; import { warn } from './log.js'; const ROLES_DIR = path.join(fileURLToPath(import.meta.url), '..', 'prompts', 'roles'); /** * 解析單一角色 .md 檔:前置 YAML frontmatter(徽章、代表色、面向、個性等)+ 本文(審查重點)。 * 回傳合併後的角色物件:{ name, side, focus, badge, color, personality, body }。 */ export function parseRoleFile(content) { const normalized = content.replace(/\r\n/g, '\n'); const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/); if (!match) throw new Error('角色檔缺少 frontmatter'); const meta = yaml.load(match[1]) || {}; return { ...meta, body: match[2].trim() }; } let cachedRoles = null; /** * 讀取並解析所有角色 .md,結果快取於模組層級(單次程序生命週期內檔案不變)。 * 單一檔案解析失敗(壞 YAML、缺 frontmatter 等)時記錄警告並略過,不讓整個流程崩潰。 */ function readRoleFiles() { if (cachedRoles) return cachedRoles; const roles = []; for (const f of fs.readdirSync(ROLES_DIR).filter(f => f.endsWith('.md')).sort()) { try { roles.push(parseRoleFile(fs.readFileSync(path.join(ROLES_DIR, f), 'utf8'))); } catch (e) { warn(`角色檔解析失敗,已略過: ${f}(${e.message})`); } } cachedRoles = roles; return cachedRoles; } /** * 載入攻擊方角色(Step2 產生 findings 用),依檔名排序。 * 防守方(如 Paladin)不在此列,裁決邏輯由去重/誤報過濾流程承擔。 */ export function loadRoles() { return readRoleFiles().filter(r => r.side === 'attack'); } /** 依 frontmatter name 取得單一角色(不分大小寫),找不到回傳 null。 */ export function loadRole(name) { const target = String(name).toLowerCase(); return readRoleFiles().find(r => String(r.name).toLowerCase() === target) || null; } /** * 由角色定義組出攻擊方的 system prompt: * 套用其個性與審查重點本文,並要求以固定 JSON 陣列格式回傳 findings。 */ export function buildAnalysisPrompt(role) { return [ `你是 ${role.badge ? role.badge + ' ' : ''}${role.name},負責「${role.focus || '綜合'}」面向的程式碼審查(攻擊方)。`, role.personality ? `個性:${role.personality}` : '', '', role.body, '', '---', '', '請分析以下 Git Diff,只針對新增/修改處,依你的面向找出所有問題。', '回傳 JSON 陣列,每個問題格式如下:', '{', ' "level": "critical|warning|info",', ` "role": "${role.name}",`, ' "location": "檔案路徑:行號 或 檔案路徑",', ' "suggestion": "繁體中文(台灣用語)的具體修改建議"', '}', '', '等級定義:', '- critical:嚴重且應立即處理的問題', '- warning:建議修正的問題', '- info:可選的改善建議', '', '只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。', ].filter(l => l !== '').join('\n'); } export function getRoleIntro(roles) { const lines = [ '## 🤖 AI Code Review 團隊', '', '| 👤 角色 | 🎯 面向 | 🧠 個性 |', '|--------|--------|--------|', ]; for (const r of roles) { const badge = r.badge ? `${r.badge} ` : ''; lines.push(`| **${badge}${r.name}** | ${r.focus || ''} | ${r.personality || ''} |`); } return lines.join('\n'); }