diff --git a/app/findings.js b/app/findings.js index 5d7d03d..392f581 100644 --- a/app/findings.js +++ b/app/findings.js @@ -272,7 +272,7 @@ export async function deduplicateWithAI(findings) { /** * 讀取排除問題檔案(從來源分支的 cloned repoDir 中的 EXCLUSIONS_PATH) */ -export function loadExclusions(workspace, repoState = null) { +export function loadExclusions(workspace, repoState = null, mirrorWorkspace = null) { const fullPath = path.join(workspace, EXCLUSIONS_PATH); if (!fs.existsSync(fullPath)) { warn(`排除問題檔案不存在,視為空: ${fullPath}`); @@ -302,6 +302,11 @@ export function loadExclusions(workspace, repoState = null) { line(`檔案資訊: bytes=${stat.size} mtime=${formatFileTime(stat.mtimeMs)} raw=${rawCount} normalized=${exclusions.length} path=${path.relative(workspace, fullPath) || fullPath}`); if (sourceFormat !== 'array') { writeCanonicalExclusions(fullPath, normalizedSource); + if (mirrorWorkspace && path.resolve(mirrorWorkspace) !== path.resolve(workspace)) { + const mirrorPath = path.join(mirrorWorkspace, EXCLUSIONS_PATH); + fs.mkdirSync(path.dirname(mirrorPath), { recursive: true }); + writeCanonicalExclusions(mirrorPath, normalizedSource); + } line(`排除問題格式已修正為頂層陣列: source=${sourceFormat} -> array`); } } catch (e) { diff --git a/app/findings.test.js b/app/findings.test.js index fc945fd..c1e2284 100644 --- a/app/findings.test.js +++ b/app/findings.test.js @@ -60,6 +60,28 @@ describe('findings exclusions', () => { assert.ok(logs.some(line => line.includes('排除問題格式已修正為頂層陣列: source=exclusions -> array'))); }); + it('mirrors repaired exclusions into the workspace root when requested', () => { + const repoRoot = path.join(workspace, 'repo'); + const mirrorRoot = path.join(workspace, 'workspace'); + const repoFullPath = path.join(repoRoot, EXCLUSIONS_PATH); + const mirrorFullPath = path.join(mirrorRoot, EXCLUSIONS_PATH); + fs.mkdirSync(path.dirname(repoFullPath), { recursive: true }); + fs.mkdirSync(path.dirname(mirrorFullPath), { recursive: true }); + fs.writeFileSync(repoFullPath, JSON.stringify({ + exclusions: [ + { location: 'README.md:12', suggestion: 'keep' }, + ], + }, null, 2)); + + const exclusions = loadExclusions(repoRoot, null, mirrorRoot); + const mirror = JSON.parse(fs.readFileSync(mirrorFullPath, 'utf8')); + + assert.equal(exclusions.length, 1); + assert.ok(Array.isArray(mirror)); + assert.equal(mirror[0].location, 'README.md:12'); + assert.equal(mirror[0].suggestion, 'keep'); + }); + it('applies exclusions loaded from wrapper format', () => { const findings = [ { location: 'entrypoint.sh:180', role: 'Maya', suggestion: 'keep' }, diff --git a/app/main.js b/app/main.js index 03cd644..3ebe32e 100644 --- a/app/main.js +++ b/app/main.js @@ -95,7 +95,7 @@ async function main() { ok(`Step3 去重完成: ${mergedFindings.length} -> ${sorted.length} 筆 (critical=${sorted.filter(f=>f.level==='critical').length} warning=${sorted.filter(f=>f.level==='warning').length} info=${sorted.filter(f=>f.level==='info').length})`); step('Step4', 'AI 排除問題過濾'); - const exclusions = loadExclusions(repoDir || WORKSPACE, repoState); + const exclusions = loadExclusions(repoDir || WORKSPACE, repoState, WORKSPACE); const ruleFiltered = applyExclusions(sorted, exclusions); const filtered = await filterFalsePositivesWithAI(ruleFiltered, exclusions); ok(`Step4 完成: findings total=${filtered.length}`);