feat(ai-review 對話收斂): 讀 PR review 留言判斷解決狀態並收斂 findings #45

Merged
admin merged 69 commits from develop into master 2026-06-23 08:30:28 +00:00
Showing only changes of commit 9e3f7afee2 - Show all commits
+43
View File
@@ -32,6 +32,18 @@ describe('parseBotReviewComment', () => {
assert.equal(f.suggestion, '移除硬編碼密鑰');
});
it('falls back to 問題 content when 建議 is absent', () => {
const body = '**審查員**Maya\n**問題**:缺少邊界測試';
const f = parseBotReviewComment(body);
assert.equal(f.problem, '缺少邊界測試');
assert.equal(f.suggestion, '缺少邊界測試');
});
it('defaults level to warning when 嚴重等級/等級 is missing', () => {
const body = '**審查員**Maya\n**問題**p\n**建議**s';
assert.equal(parseBotReviewComment(body).level, 'warning');
});
it('returns null for free-form human comments', () => {
assert.equal(parseBotReviewComment('我覺得這段可以再想想'), null);
assert.equal(parseBotReviewComment(''), null);
@@ -93,6 +105,23 @@ describe('judgeConversationsResolved', () => {
assert.deepEqual(verdicts, [{ idx: 0, resolved: false }]);
});
it('filters out AI results missing idx or resolved and keeps valid ones', async () => {
const items = [{ idx: 0 }, { idx: 1 }];
const chatFn = async () => [{ resolved: true }, { idx: 1, resolved: true }, { idx: 0 }];
const verdicts = await judgeConversationsResolved(items, chatFn);
assert.deepEqual(verdicts, [
{ idx: 0, resolved: false }, // {idx:0} 缺 resolved → 視為 false;缺 idx 的整筆被過濾
{ idx: 1, resolved: true },
]);
});
it('propagates errors thrown by chatFn to the caller', async () => {
await assert.rejects(
() => judgeConversationsResolved([{ idx: 0 }], async () => { throw new Error('LLM down'); }),
/LLM down/,
);
});
it('returns [] for no items', async () => {
assert.deepEqual(await judgeConversationsResolved([]), []);
});
@@ -134,6 +163,20 @@ describe('reconcileConversations', () => {
assert.equal(result.carriedFindings.length, 2);
});
it('treats all conversations as unresolved when the judge throws', async () => {
const resolvedIds = [];
const deps = baseDeps();
deps.judge = async () => { throw new Error('judge boom'); };
deps.resolveComment = async (id) => { resolvedIds.push(id); return { ok: true }; };
const result = await reconcileConversations(deps);
assert.deepEqual(resolvedIds, []); // 無任何對話被 resolve
assert.equal(result.resolvedCount, 0);
assert.equal(result.carriedFindings.length, 2); // a.js + b.js 皆加回(c.js 已解決略過)
assert.deepEqual(result.carriedFindings.map(f => f.suggestion).sort(), ['fix one', 'fix two']);
});
it('returns empty result and does not throw when listing comments fails', async () => {
const result = await reconcileConversations({ listComments: async () => { throw new Error('boom'); } });
assert.deepEqual(result, { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, unresolvedCount: 0 });