Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97888f8b22 | |||
| fa95a463f8 | |||
| 60001499da | |||
| d714cf7665 |
+20
-6
@@ -2,6 +2,7 @@ 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');
|
||||
|
||||
@@ -17,11 +18,24 @@ export function parseRoleFile(content) {
|
||||
return { ...meta, body: match[2].trim() };
|
||||
}
|
||||
|
||||
let cachedRoles = null;
|
||||
|
||||
/**
|
||||
* 讀取並解析所有角色 .md,結果快取於模組層級(單次程序生命週期內檔案不變)。
|
||||
* 單一檔案解析失敗(壞 YAML、缺 frontmatter 等)時記錄警告並略過,不讓整個流程崩潰。
|
||||
*/
|
||||
function readRoleFiles() {
|
||||
return fs.readdirSync(ROLES_DIR)
|
||||
.filter(f => f.endsWith('.md'))
|
||||
.sort()
|
||||
.map(f => parseRoleFile(fs.readFileSync(path.join(ROLES_DIR, f), 'utf8')));
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +58,7 @@ export function loadRole(name) {
|
||||
*/
|
||||
export function buildAnalysisPrompt(role) {
|
||||
return [
|
||||
`你是 ${role.badge ? role.badge + ' ' : ''}${role.name},負責「${role.focus}」面向的程式碼審查(攻擊方)。`,
|
||||
`你是 ${role.badge ? role.badge + ' ' : ''}${role.name},負責「${role.focus || '綜合'}」面向的程式碼審查(攻擊方)。`,
|
||||
role.personality ? `個性:${role.personality}` : '',
|
||||
'',
|
||||
role.body,
|
||||
@@ -77,7 +91,7 @@ export function getRoleIntro(roles) {
|
||||
];
|
||||
for (const r of roles) {
|
||||
const badge = r.badge ? `${r.badge} ` : '';
|
||||
lines.push(`| **${badge}${r.name}** | ${r.focus} | ${r.personality} |`);
|
||||
lines.push(`| **${badge}${r.name}** | ${r.focus || ''} | ${r.personality || ''} |`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ describe('buildAnalysisPrompt', () => {
|
||||
assert.match(prompt, /審查重點:邊界與空值/);
|
||||
assert.match(prompt, /只回傳 JSON 陣列/);
|
||||
});
|
||||
|
||||
it('falls back to a default when focus is missing instead of showing undefined', () => {
|
||||
const prompt = buildAnalysisPrompt({ name: 'NoFocus', body: 'x' });
|
||||
assert.doesNotMatch(prompt, /undefined/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRoleIntro', () => {
|
||||
@@ -80,4 +85,10 @@ describe('getRoleIntro', () => {
|
||||
assert.match(intro, /🔮 Tester/);
|
||||
assert.match(intro, /logic/);
|
||||
});
|
||||
|
||||
it('renders empty cells instead of undefined when focus/personality are missing', () => {
|
||||
const intro = getRoleIntro([{ name: 'Bare' }]);
|
||||
assert.match(intro, /Bare/);
|
||||
assert.doesNotMatch(intro, /undefined/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user