fix(review): group a triplet's findings title first, so arrival order never changes the rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-23 18:30:52 +02:00
commit a5fcb5e8b4
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
6 changed files with 339 additions and 37 deletions

View file

@ -637,3 +637,64 @@ test('armB — two different /code-review findings on one triplet stay two rows
});
assert.deepEqual(c.merged.map((f) => f.title).sort(), ['A lone CR does not split lines', 'EAGAIN on stdin']);
});
// ---- ORD sibling: the workflow's inlined dedup does not depend on arrival order ----
test('[ORD] sibling: workflow — conformance B with /code-review A and B, in both engine orders, gives one row of A alone and one row of both B findings', async () => {
const K = 'CODE_REVIEW_DEFECT';
const engineA = F(K, 'MINOR', { title: 'A' });
const engineB = F(K, 'MAJOR', { title: 'B' });
for (const codeFindings of [[engineA, engineB], [engineB, engineA]]) {
const name = codeFindings.map((f) => f.title).join(', ');
const c = await floorCase({
reviewerFindings: [F(K, 'MINOR', { title: 'B' })],
codeFindings,
returned: [],
verdict: 'WARN',
});
const rows = c.merged.filter((m) => triplet(m) === triplet(F(K, 'MINOR')));
assert.equal(rows.length, 2, `engine order ${name}: ${JSON.stringify(rows)}`);
const got = rows
.map((m) => ({ title: m.title, engine: m.engine ?? null, severity: m.severity }))
.sort((x, y) => x.title.localeCompare(y.title));
// A alone: the /code-review A finding survives with its own MINOR. Both B
// findings: the conformance B finding survives (a reviewer over an engine)
// at the MAJOR the /code-review B finding asserted.
assert.deepEqual(got, [
{ title: 'A', engine: 'code-review', severity: 'MINOR' },
{ title: 'B', engine: null, severity: 'MAJOR' },
], `engine order ${name}`);
}
});
test('[ORD] sibling: workflow — the title classes are placed in alphabetical order: conformance B with /code-review A, B and an untitled BLOCKER gives {A, untitled} BLOCKER kept as A and {B, B} MAJOR in all 6 engine orders', async () => {
const K = 'CODE_REVIEW_DEFECT';
const engineA = F(K, 'BLOCKER', { title: 'A' });
const engineB = F(K, 'MINOR', { title: 'B' });
const { title: _t, ...engineU } = F(K, 'BLOCKER');
// The workflow reads the reviewer findings first, so class B always arrives
// first: only the alphabetical order puts the untitled finding in A's row.
// A and the untitled finding tie on every survivor rule, so the first in the
// group's order (class A before the untitled findings) is kept.
const engineOrders = [
[engineA, engineB, engineU], [engineA, engineU, engineB], [engineB, engineA, engineU],
[engineB, engineU, engineA], [engineU, engineA, engineB], [engineU, engineB, engineA],
];
for (const codeFindings of engineOrders) {
const name = codeFindings.map((f) => f.title ?? 'U').join(', ');
const c = await floorCase({
reviewerFindings: [F(K, 'MAJOR', { title: 'B' })],
codeFindings,
returned: [],
verdict: 'WARN',
});
const rows = c.merged.filter((m) => triplet(m) === triplet(F(K, 'MINOR')));
const got = rows
.map((m) => ({ title: m.title ?? null, engine: m.engine ?? null, severity: m.severity }))
.sort((x, y) => String(x.title).localeCompare(String(y.title)));
assert.deepEqual(got, [
{ title: 'A', engine: 'code-review', severity: 'BLOCKER' },
{ title: 'B', engine: null, severity: 'MAJOR' },
], `engine order ${name}`);
}
});