docs(ai-code-review): 補齊各模組 JSDoc、指令檔逐行註解並重建 README
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
303104bb20
commit
1378f03595
+97
-1
@@ -37,6 +37,13 @@ function readJSONArray(fullPath, label) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 將排除設定(頂層陣列、{ exclusions: [] } 或 { excluded_findings: [] })正規化為條目陣列。
|
||||
*
|
||||
* @param {Array<object>|{exclusions?: Array<object>, excluded_findings?: Array<object>}|*} data - 任意形式的排除資料來源。
|
||||
* @returns {Array<object>} 對應的排除條目陣列;無法辨識時回傳空陣列。
|
||||
* @remarks 與 detectExclusionSource 搭配,相容舊有多種 exclusions.json 結構。
|
||||
*/
|
||||
function normalizeExclusions(data) {
|
||||
if (Array.isArray(data)) return data;
|
||||
if (data && Array.isArray(data.exclusions)) return data.exclusions;
|
||||
@@ -44,6 +51,13 @@ function normalizeExclusions(data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 偵測排除資料的原始容器格式,回傳格式標籤。
|
||||
*
|
||||
* @param {Array<object>|{exclusions?: *, excluded_findings?: *}|*} data - 任意形式的排除資料來源。
|
||||
* @returns {('array'|'exclusions'|'excluded_findings'|'unknown')} 對應的格式標籤。
|
||||
* @remarks 供 loadExclusions 判斷是否需把非陣列格式改寫成標準頂層陣列。
|
||||
*/
|
||||
function detectExclusionSource(data) {
|
||||
if (Array.isArray(data)) return 'array';
|
||||
if (data && Array.isArray(data.exclusions)) return 'exclusions';
|
||||
@@ -51,19 +65,49 @@ function detectExclusionSource(data) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 以標準格式(2 空白縮排 JSON 陣列、結尾換行、UTF-8)將排除條目寫回檔案,覆蓋原內容。
|
||||
*
|
||||
* @param {string} fullPath - 目標檔案路徑;上層目錄須事先存在(本函式不建立目錄)。
|
||||
* @param {Array<object>} exclusions - 欲寫入的排除條目陣列。
|
||||
* @returns {void}
|
||||
* @throws 檔案寫入失敗(權限不足、目錄不存在等)時拋出 fs 錯誤。
|
||||
* @remarks 統一輸出格式,使 exclusions.json 永遠是可預期的頂層陣列。
|
||||
*/
|
||||
function writeCanonicalExclusions(fullPath, exclusions) {
|
||||
fs.writeFileSync(fullPath, JSON.stringify(exclusions, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 將檔案 mtime(毫秒時間戳)格式化為 ISO 字串,無效值回傳 'unknown'。
|
||||
*
|
||||
* @param {number} mtimeMs - 毫秒時間戳(通常為 fs.Stats.mtimeMs)。
|
||||
* @returns {string} ISO 8601 時間字串,或在輸入非有限數時回傳 'unknown'。
|
||||
* @remarks 僅用於診斷日誌,呈現舊 findings / exclusions 檔案的修改時間。
|
||||
*/
|
||||
function formatFileTime(mtimeMs) {
|
||||
if (!Number.isFinite(mtimeMs)) return 'unknown';
|
||||
return new Date(mtimeMs).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全取字串:字串則去頭尾空白,其餘型別(含 null/undefined/數字)一律回傳空字串。
|
||||
*
|
||||
* @param {*} value - 任意值。
|
||||
* @returns {string} 去除頭尾空白後的字串,或空字串。
|
||||
* @remarks 作為 normalizeText、toKeyText、getExclusionText 等的基礎防呆。
|
||||
*/
|
||||
function cleanText(value) {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 將文字正規化為比對用形式:NFKC、小寫、標點/符號/空白統一為單一空白後壓縮。
|
||||
*
|
||||
* @param {*} value - 任意值;非字串會先經 cleanText 轉為空字串。
|
||||
* @returns {string} 正規化後、以單一空白分隔的字串(可能為空字串)。
|
||||
* @remarks 用於 finding 與排除條目文字的雙向「包含」比對(applyExclusions、appendExclusions)。
|
||||
*/
|
||||
function normalizeText(value) {
|
||||
return cleanText(value)
|
||||
.normalize('NFKC')
|
||||
@@ -73,6 +117,14 @@ function normalizeText(value) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 將文字壓縮成無分隔符的鍵值:NFKC 後移除所有標點/符號/空白。
|
||||
*
|
||||
* @param {*} value - 任意值;非字串會先經 cleanText 轉為空字串。
|
||||
* @returns {string} 去除所有分隔符的緊湊字串(可能為空字串)。
|
||||
* @remarks 用於 normalizeExclusionEntry 的 textKey 與 fingerprint,以及群組鍵。
|
||||
* 不確定:是否刻意不轉小寫(與 normalizeText 不同),需人工確認此差異是否預期。
|
||||
*/
|
||||
function toKeyText(value) {
|
||||
return cleanText(value)
|
||||
.normalize('NFKC')
|
||||
@@ -80,6 +132,13 @@ function toKeyText(value) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 從排除條目取出代表性文字,依優先序 original_finding > title > suggestion > reason > note 取第一個非空值。
|
||||
*
|
||||
* @param {object|null|undefined} exclusion - 排除條目物件(可為 null/undefined)。
|
||||
* @returns {string} 第一個非空的代表性文字,皆空時回傳空字串。
|
||||
* @remarks 供 normalizeExclusionEntry 產生比對文字;相容多種人工撰寫的排除欄位命名。
|
||||
*/
|
||||
function getExclusionText(exclusion) {
|
||||
return cleanText(exclusion?.original_finding)
|
||||
|| cleanText(exclusion?.title)
|
||||
@@ -88,6 +147,14 @@ function getExclusionText(exclusion) {
|
||||
|| cleanText(exclusion?.note);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正規化單一排除條目,補上 filePath、text、textKey 與唯一 fingerprint,保留原始欄位。
|
||||
*
|
||||
* @param {object} exclusion - 原始排除條目(可能僅含部分欄位)。
|
||||
* @param {number} index - 條目在來源陣列中的索引;無文字可用時用於產生 fallback 指紋(entry-N)。
|
||||
* @returns {object} 合併原欄位與衍生欄位(location、filePath、role、text、textKey、fingerprint)的新物件。
|
||||
* @remarks fingerprint 以 filePath|role|textKey 組成,缺值以 '*' 或 entry-N 補位,供 dedupeExclusions 去重。
|
||||
*/
|
||||
function normalizeExclusionEntry(exclusion, index) {
|
||||
const location = cleanText(exclusion?.location);
|
||||
const filePath = location ? location.split(':')[0] : '';
|
||||
@@ -106,6 +173,13 @@ function normalizeExclusionEntry(exclusion, index) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 依 fingerprint 去除重複的排除條目,保留首次出現者並維持原順序。
|
||||
*
|
||||
* @param {Array<object>} exclusions - 已正規化(含 fingerprint)的排除條目陣列。
|
||||
* @returns {Array<object>} 去重後的排除條目陣列。
|
||||
* @remarks 須先呼叫 normalizeExclusionEntry 補上 fingerprint,否則缺指紋的條目可能被誤併。
|
||||
*/
|
||||
function dedupeExclusions(exclusions) {
|
||||
const seen = new Set();
|
||||
return exclusions.filter(exclusion => {
|
||||
@@ -115,6 +189,14 @@ function dedupeExclusions(exclusions) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 將排除條目依 textKey 分組統計,產生供 AI prompt 使用的群組摘要(含出現次數、涉及路徑與角色、樣本)。
|
||||
*
|
||||
* @param {Array<object>} exclusions - 已正規化(含 textKey、filePath、role、text、fingerprint)的排除條目。
|
||||
* @returns {Array<{text: string, count: number, paths: string[], roles: string[], samples: string[]}>}
|
||||
* 依出現次數、涉及路徑數、文字字典序排序的群組摘要陣列。
|
||||
* @remarks 每組最多保留 2 筆樣本,避免後續 prompt 過長;供 buildExclusionContext 取前 N 組組裝提示。
|
||||
*/
|
||||
function groupExclusionsForAI(exclusions) {
|
||||
const groups = new Map();
|
||||
for (const exclusion of exclusions) {
|
||||
@@ -147,6 +229,14 @@ function groupExclusionsForAI(exclusions) {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 由原始排除條目建立「已知誤報」上下文:正規化、去重、分組後,產生計數摘要與可直接嵌入 prompt 的文字。
|
||||
*
|
||||
* @param {Array<object>} exclusions - 原始(未正規化)排除條目陣列。
|
||||
* @returns {{rawCount: number, uniqueCount: number, groupCount?: number, groups: Array<object>, prompt: string}}
|
||||
* 含計數、前 12 組群組摘要與 prompt 字串;空輸入時 prompt 為空字串且不含 groupCount。
|
||||
* @remarks 供 loadExclusions 日誌與 filterFalsePositivesWithAI 組裝防守方提示使用;prompt 最多展開 12 類群組。
|
||||
*/
|
||||
function buildExclusionContext(exclusions) {
|
||||
if (exclusions.length === 0) {
|
||||
return {
|
||||
@@ -303,7 +393,13 @@ export async function resolveMissingLineNumbers(findings, diff, deps = {}) {
|
||||
return findings;
|
||||
}
|
||||
|
||||
/** 只保留 AI 需要的欄位,減少 token 用量 */
|
||||
/**
|
||||
* 將 findings 精簡為僅含 level、role、location、problem、suggestion 的物件,移除多餘欄位以節省 token。
|
||||
*
|
||||
* @param {Array<object>} findings - 完整 findings 陣列。
|
||||
* @returns {Array<{level: *, role: *, location: *, problem: *, suggestion: *}>} 精簡後的 payload 陣列。
|
||||
* @remarks 送往 LLM 前的瘦身步驟;原始欄位(如 is_new)需由呼叫端事後依鍵補回。
|
||||
*/
|
||||
function toAIPayload(findings) {
|
||||
return findings.map(({ level, role, location, problem, suggestion }) => ({ level, role, location, problem, suggestion }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user