Files
ai-code-review/src/lib/roles.js
T
Jeffery 8662e8ca80
node-actions/template: CI / BUILD (pull_request) Successful in 5s
CI / TEST (Claude) (pull_request) Successful in 27s
CI / TEST (Codex) (pull_request) Successful in 31s
CI / TEST (Antigravity) (pull_request) Failing after 36s
docs(審查步驟編號): 依新順序重編步驟編號與說明
2026-07-20 09:46:12 +08:00

95 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const fs = require('fs');
const path = require('path');
// 角色提示載入:讀取 src/prompts/roles/*.md,解析 YAML frontmatter 取出角色中繼資料。
/**
* 載入指定目錄下的全部角色提示檔(`*.md`),解析各檔開頭的 YAML frontmatter 為中繼資料。
*
* 只處理副檔名為 `.md` 的檔案,並依檔名字串排序,確保輸出順序穩定。
* frontmatter 採輕量解析:僅支援位於檔案開頭、以 `---` 包夾的「鍵: 值」單行欄位
* (鍵名限英文字母與底線),值外層的一對雙引號會被去除;不支援巢狀或多行值。
*
* @param {string} rolesDir - 角色提示檔所在目錄的路徑(例如 action 內的 `src/prompts/roles`)。
* @returns {Array<{file: string, meta: Object.<string, string>, body: string, raw: string}>}
* 角色物件陣列(依檔名排序):
* - `file`:檔名(不含目錄),例如 `mage.md`。
* - `meta`frontmatter 鍵值物件(如 `name`、`side`、`focus`、`badge`、`color`、`personality`);
* 檔案無 frontmatter 時為空物件。
* - `body`:去除 frontmatter 後的 Markdown 內文;無 frontmatter 時等於全文。
* - `raw`:原始完整檔案內容。
* @throws {Error} 當 `rolesDir` 不存在、無法讀取,或個別檔案讀取失敗時,
* 由 `fs.readdirSync` / `fs.readFileSync` 直接拋出(未在函式內捕捉)。
* @remarks
* 使用情境:`src/index.js` 於審查流程步驟 5 呼叫
* `loadRoles(path.join(ctx.actionPath, 'src', 'prompts', 'roles'))` 載入全部角色,
* 再以 {@link attackersOf} / {@link defendersOf} 依 frontmatter 的 `side` 欄位
* 分出攻擊方(Mage/Assassin/Rogue/Bard/Leo/Maya)與防守方(Paladin),
* 供後續組裝各角色的 review 提示詞。
*/
function loadRoles(rolesDir) {
return fs
.readdirSync(rolesDir)
.filter((file) => file.endsWith('.md'))
.sort()
.map((file) => {
const raw = fs.readFileSync(path.join(rolesDir, file), 'utf8');
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw);
const meta = {};
if (match) {
for (const line of match[1].split(/\r?\n/)) {
const kv = /^([A-Za-z_]+):\s*(.*)$/.exec(line.trim());
if (kv) meta[kv[1]] = kv[2].replace(/^"(.*)"$/, '$1');
}
}
return {
file,
meta,
body: match ? raw.slice(match[0].length) : raw,
raw,
};
});
}
/**
* 從角色陣列中過濾出攻擊方角色(frontmatter `side: attack`)。
*
* 以嚴格相等比對 `role.meta.side === 'attack'`(大小寫敏感),
* 回傳新陣列且保留原輸入順序(即 {@link loadRoles} 的檔名排序),不修改原陣列。
*
* @param {Array<{file: string, meta: Object.<string, string>, body: string, raw: string}>} roles
* {@link loadRoles} 回傳的角色物件陣列。
* @returns {Array<{file: string, meta: Object.<string, string>, body: string, raw: string}>}
* 僅含 `meta.side === 'attack'` 的角色新陣列;無符合者回傳空陣列。
* @remarks
* 使用情境:`src/index.js` 在 `loadRoles(...)` 之後呼叫 `attackersOf(roles)`
* 取得攻擊方角色(Mage 邏輯、Assassin 安全、Rogue 效率、Bard 風格、
* Leo 可維護性、Maya 測試)以對 PR diff 發動各面向的攻擊式 review。
*/
function attackersOf(roles) {
return roles.filter((role) => role.meta.side === 'attack');
}
/**
* 從角色陣列中過濾出防守方角色(frontmatter `side: defend`)。
*
* 以嚴格相等比對 `role.meta.side === 'defend'`(大小寫敏感),
* 回傳新陣列且保留原輸入順序(即 {@link loadRoles} 的檔名排序),不修改原陣列。
*
* @param {Array<{file: string, meta: Object.<string, string>, body: string, raw: string}>} roles
* {@link loadRoles} 回傳的角色物件陣列。
* @returns {Array<{file: string, meta: Object.<string, string>, body: string, raw: string}>}
* 僅含 `meta.side === 'defend'` 的角色新陣列;無符合者回傳空陣列。
* @remarks
* 使用情境:`src/index.js` 在 `loadRoles(...)` 之後呼叫 `defendersOf(roles)`
* 取得防守方角色(現況為 Paladin`focus: verdict`)擔任裁決者,
* 依原始碼脈絡與排除事項裁定攻擊方提出的 findings 是否成立。
*/
function defendersOf(roles) {
return roles.filter((role) => role.meta.side === 'defend');
}
module.exports = { loadRoles, attackersOf, defendersOf };