Compare commits

..

5 Commits

Author SHA1 Message Date
AI Review Bot 862f4e46ef chore: update ai-review findings [ai-review-bot][success]
AI / 計算版本號 (pull_request) Successful in 4s
AI / Code Review (pull_request) Successful in 10s
2026-06-16 09:03:46 +00:00
Jeffery 97888f8b22 chore(ai-review): 清空 findings
AI / 計算版本號 (pull_request) Successful in 3s
AI / Code Review (pull_request) Successful in 2m11s
2026-06-16 17:00:36 +08:00
Jeffery fa95a463f8 test(roles): 補 focus/personality 缺漏時的輸出防護測試 2026-06-16 17:00:29 +08:00
Jeffery 60001499da fix(腳色載入器): 壞角色檔改記錄警告並略過、快取解析結果並補 focus/personality 缺漏防護 2026-06-16 17:00:24 +08:00
AI Review Bot d714cf7665 chore: update ai-review findings [ai-review-bot][failure]
AI / 計算版本號 (pull_request) Successful in 4s
AI / Code Review (pull_request) Failing after 5s
2026-06-16 08:46:24 +00:00
3 changed files with 40 additions and 7 deletions
+9 -1
View File
@@ -1 +1,9 @@
[]
[
{
"level": "warning",
"role": "Bard",
"location": "app/comments.test.js:172",
"suggestion": "此處斷言使用了魔術字串 `/嚴重問題/`,就像樂譜中突然出現的無標記音符,雖能理解,卻少了點優雅與明確。建議將此字串提取為一個具名常數,或至少賦予一個描述性變數,以提升可讀性與未來維護的便利性,讓意圖更加清晰。",
"is_new": true
}
]
+20 -6
View File
@@ -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');
}
+11
View File
@@ -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/);
});
});