diff --git a/app/findings.js b/app/findings.js index d045941..82ae388 100644 --- a/app/findings.js +++ b/app/findings.js @@ -34,6 +34,12 @@ function readJSONArray(fullPath, label) { } } +function normalizeExclusions(data) { + if (Array.isArray(data)) return data; + if (data && Array.isArray(data.excluded_findings)) return data.excluded_findings; + return []; +} + /** * 讀取舊 findings(從 workspace 的 FINDINGS_PATH) */ @@ -107,7 +113,21 @@ export async function deduplicateWithAI(findings) { * 讀取排除問題檔案(從 workspace 的 EXCLUSIONS_PATH) */ export function loadExclusions(workspace) { - const exclusions = readJSONArray(path.join(workspace, EXCLUSIONS_PATH), '排除問題'); + const fullPath = path.join(workspace, EXCLUSIONS_PATH); + if (!fs.existsSync(fullPath)) { + console.log(' 排除問題檔案不存在,視為空'); + console.log(' 讀取排除問題: 0 筆'); + return []; + } + + let exclusions = []; + try { + const data = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + exclusions = normalizeExclusions(data); + } catch (e) { + console.log(` ⚠️ 讀取排除問題失敗: ${e.message},視為空`); + exclusions = []; + } console.log(` 讀取排除問題: ${exclusions.length} 筆`); return exclusions; } diff --git a/app/findings.test.js b/app/findings.test.js new file mode 100644 index 0000000..d163a2c --- /dev/null +++ b/app/findings.test.js @@ -0,0 +1,50 @@ +import { describe, it, beforeEach, afterEach } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { loadExclusions, applyExclusions } from './findings.js'; +import { EXCLUSIONS_PATH } from './config.js'; + +describe('findings exclusions', () => { + let workspace; + + beforeEach(() => { + workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-test-')); + }); + + afterEach(() => { + fs.rmSync(workspace, { recursive: true, force: true }); + }); + + it('loads excluded_findings wrapper format', () => { + const fullPath = path.join(workspace, EXCLUSIONS_PATH); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, JSON.stringify({ + excluded_findings: [ + { location: 'entrypoint.sh:180', title: 'fetch_package_versions jq overhead' }, + ], + }, null, 2)); + + const exclusions = loadExclusions(workspace); + + assert.equal(exclusions.length, 1); + assert.equal(exclusions[0].location, 'entrypoint.sh:180'); + assert.equal(exclusions[0].title, 'fetch_package_versions jq overhead'); + }); + + it('applies exclusions loaded from wrapper format', () => { + const findings = [ + { location: 'entrypoint.sh:180', role: 'Maya', suggestion: 'keep' }, + { location: 'README.md:12', role: 'Maya', suggestion: 'keep' }, + ]; + const exclusions = [ + { location: 'entrypoint.sh:180', title: 'fetch_package_versions jq overhead' }, + ]; + + const filtered = applyExclusions(findings, exclusions); + + assert.equal(filtered.length, 1); + assert.equal(filtered[0].location, 'README.md:12'); + }); +});