Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 862f4e46ef | |||
| 97888f8b22 | |||
| fa95a463f8 | |||
| 60001499da |
@@ -1,30 +1,9 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"level": "critical",
|
|
||||||
"role": "Mage",
|
|
||||||
"location": "app/roles.js:30",
|
|
||||||
"suggestion": "在 `parseRoleFile` 函式中,`yaml.load(match[1])` 若遇到格式錯誤的 YAML 內容,會拋出未捕捉的例外,導致應用程式崩潰。應加入 `try-catch` 區塊來處理此潛在錯誤,確保即使角色定義檔有誤,系統也能穩定運行,例如回傳一個錯誤物件或記錄錯誤並跳過該檔案。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "critical",
|
|
||||||
"role": "Rogue",
|
|
||||||
"location": "app/roles.js:30",
|
|
||||||
"suggestion": "「loadRole」函式每次被呼叫時,都會重新讀取並解析所有角色檔案。這會造成不必要的同步檔案 I/O 與 CPU 浪費,尤其當此函式被頻繁呼叫時,會嚴重阻塞事件迴圈。建議將「readRoleFiles()」的結果快取起來,讓「loadRole」直接從記憶體中查詢,避免重複讀取磁碟。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"level": "warning",
|
"level": "warning",
|
||||||
"role": "Mage",
|
"role": "Bard",
|
||||||
"location": "app/roles.js:60",
|
"location": "app/comments.test.js:172",
|
||||||
"suggestion": "在 `buildAnalysisPrompt` 函式中,`role.focus` 屬性被直接用於字串模板。若角色定義檔中缺少 `focus` 欄位,此處將會顯示為 `負責「undefined」面向`,導致生成的提示語義不完整。建議在引用前檢查 `role.focus` 是否存在,或提供一個預設值,例如:`負責「${role.focus || '未定義'}」面向`。",
|
"suggestion": "此處斷言使用了魔術字串 `/嚴重問題/`,就像樂譜中突然出現的無標記音符,雖能理解,卻少了點優雅與明確。建議將此字串提取為一個具名常數,或至少賦予一個描述性變數,以提升可讀性與未來維護的便利性,讓意圖更加清晰。",
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Mage",
|
|
||||||
"location": "app/roles.js:77",
|
|
||||||
"suggestion": "在 `getRoleIntro` 函式中,`r.focus` 和 `r.personality` 屬性被直接用於生成 Markdown 表格。若角色定義檔中缺少這些欄位,表格中將會顯示 `undefined`,影響可讀性與呈現品質。建議在引用前檢查這些屬性是否存在,並提供一個空字串或預設值,例如:`| **${badge}${r.name}** | ${r.focus || ''} | ${r.personality || ''} |`。",
|
|
||||||
"is_new": true
|
"is_new": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+20
-6
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
import { warn } from './log.js';
|
||||||
|
|
||||||
const ROLES_DIR = path.join(fileURLToPath(import.meta.url), '..', 'prompts', 'roles');
|
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() };
|
return { ...meta, body: match[2].trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cachedRoles = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 讀取並解析所有角色 .md,結果快取於模組層級(單次程序生命週期內檔案不變)。
|
||||||
|
* 單一檔案解析失敗(壞 YAML、缺 frontmatter 等)時記錄警告並略過,不讓整個流程崩潰。
|
||||||
|
*/
|
||||||
function readRoleFiles() {
|
function readRoleFiles() {
|
||||||
return fs.readdirSync(ROLES_DIR)
|
if (cachedRoles) return cachedRoles;
|
||||||
.filter(f => f.endsWith('.md'))
|
const roles = [];
|
||||||
.sort()
|
for (const f of fs.readdirSync(ROLES_DIR).filter(f => f.endsWith('.md')).sort()) {
|
||||||
.map(f => parseRoleFile(fs.readFileSync(path.join(ROLES_DIR, f), 'utf8')));
|
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) {
|
export function buildAnalysisPrompt(role) {
|
||||||
return [
|
return [
|
||||||
`你是 ${role.badge ? role.badge + ' ' : ''}${role.name},負責「${role.focus}」面向的程式碼審查(攻擊方)。`,
|
`你是 ${role.badge ? role.badge + ' ' : ''}${role.name},負責「${role.focus || '綜合'}」面向的程式碼審查(攻擊方)。`,
|
||||||
role.personality ? `個性:${role.personality}` : '',
|
role.personality ? `個性:${role.personality}` : '',
|
||||||
'',
|
'',
|
||||||
role.body,
|
role.body,
|
||||||
@@ -77,7 +91,7 @@ export function getRoleIntro(roles) {
|
|||||||
];
|
];
|
||||||
for (const r of roles) {
|
for (const r of roles) {
|
||||||
const badge = r.badge ? `${r.badge} ` : '';
|
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');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ describe('buildAnalysisPrompt', () => {
|
|||||||
assert.match(prompt, /審查重點:邊界與空值/);
|
assert.match(prompt, /審查重點:邊界與空值/);
|
||||||
assert.match(prompt, /只回傳 JSON 陣列/);
|
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', () => {
|
describe('getRoleIntro', () => {
|
||||||
@@ -80,4 +85,10 @@ describe('getRoleIntro', () => {
|
|||||||
assert.match(intro, /🔮 Tester/);
|
assert.match(intro, /🔮 Tester/);
|
||||||
assert.match(intro, /logic/);
|
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