This commit is contained in:
Jayesh Betala 2026-06-03 11:24:05 +05:30 committed by GitHub
commit 8e588a41a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 2 deletions

View File

@ -196,9 +196,14 @@ function bumpPref(list: Preference[], value: string, opposite: Preference[], act
entry.rejected_count += 1; entry.rejected_count += 1;
} }
entry.last_seen = now; entry.last_seen = now;
// Laplace-smoothed confidence // Laplace-smoothed confidence in THIS bucket's signal: an approved entry's
// confidence reflects how strongly it's approved, a rejected entry's how
// strongly it's rejected. Using approved_count for both buckets pinned every
// rejected entry to 0 (rejected entries never gain approvals), which made the
// `show` ranking meaningless and the taste-drift warning unreachable.
const total = entry.approved_count + entry.rejected_count; const total = entry.approved_count + entry.rejected_count;
entry.confidence = entry.approved_count / (total + 1); const ownCount = action === 'approved' ? entry.approved_count : entry.rejected_count;
entry.confidence = ownCount / (total + 1);
// Flag conflict if the opposite bucket has a strong entry for this value // Flag conflict if the opposite bucket has a strong entry for this value
const opp = opposite.find(p => p.value.toLowerCase() === value.toLowerCase()); const opp = opposite.find(p => p.value.toLowerCase() === value.toLowerCase());
if (opp && opp.approved_count + opp.rejected_count >= 3 && opp.confidence >= 0.6) { if (opp && opp.approved_count + opp.rejected_count >= 3 && opp.confidence >= 0.6) {

View File

@ -141,6 +141,35 @@ describe('taste-engine: Laplace-smoothed confidence', () => {
expect(rejected.rejected_count).toBe(1); expect(rejected.rejected_count).toBe(1);
expect(rejected.approved_count).toBe(0); expect(rejected.approved_count).toBe(0);
}); });
test('repeated rejections raise rejected confidence toward 1', () => {
for (let i = 0; i < 5; i++) {
run(['rejected', `variant-${i}`, '--reason', 'fonts: Comic Sans']);
}
const p = readProfile();
const pref = p.dimensions.fonts.rejected[0];
expect(pref.rejected_count).toBe(5);
// Confidence reflects this bucket's signal: 5 / (5 + 0 + 1) = 0.833.
// Pre-fix this was approved_count/(total+1) = 0/6 = 0 for every rejected entry.
expect(pref.confidence).toBeCloseTo(5 / 6, 5);
});
test('show ranks rejections by strength, not insertion order', () => {
run(['rejected', 'weak', '--reason', 'colors: beige']);
for (let i = 0; i < 4; i++) {
run(['rejected', `strong-${i}`, '--reason', 'colors: crimson']);
}
const r = run(['show']);
expect(r.status).toBe(0);
// The strongly-rejected value must rank above the weakly-rejected one even
// though it was inserted second. Pre-fix both keys were 0, so the sort was a
// no-op and "beige" (inserted first) won.
const crimsonIdx = r.stdout.indexOf('crimson');
const beigeIdx = r.stdout.indexOf('beige');
expect(crimsonIdx).toBeGreaterThanOrEqual(0);
expect(beigeIdx).toBeGreaterThanOrEqual(0);
expect(crimsonIdx).toBeLessThan(beigeIdx);
});
}); });
describe('taste-engine: decay math', () => { describe('taste-engine: decay math', () => {
@ -309,6 +338,19 @@ describe('taste-engine: taste drift conflict detection', () => {
expect(r.status).toBe(0); expect(r.status).toBe(0);
expect(r.stderr).not.toContain('taste drift'); expect(r.stderr).not.toContain('taste drift');
}); });
test('drift warning fires from real CLI rejections (no seeding)', () => {
// Build the opposite signal through the real CLI: 4 rejections take confidence
// to 4/5 = 0.8, above the 0.6 drift threshold. Pre-fix every rejected entry was
// pinned to confidence 0, so this branch was unreachable without hand-seeding.
for (let i = 0; i < 4; i++) {
run(['rejected', `variant-${i}`, '--reason', 'fonts: Comic Sans']);
}
const r = run(['approved', 'v-approve', '--reason', 'fonts: Comic Sans']);
expect(r.status).toBe(0);
expect(r.stderr).toContain('taste drift');
expect(r.stderr).toContain('Comic Sans');
});
}); });
describe('taste-engine: migration', () => { describe('taste-engine: migration', () => {