Observed problem
In bin/gstack-taste-update, every preference in a rejected bucket is permanently stored with confidence: 0. This makes the show output useless for rejections and silently disables the taste-drift conflict warning.
Root cause
bumpPref() computes confidence as approval rate for both buckets:
const total = entry.approved_count + entry.rejected_count;
entry.confidence = entry.approved_count / (total + 1);
A rejected-bucket entry only ever gets rejected_count incremented (the approved and rejected lists are disjoint — a value approved goes in approved, rejected goes in rejected). So approved_count stays 0 and confidence = 0 / (rejected_count + 1) = 0 no matter how many times the value is rejected.
Downstream effects:
show display prints conf 0.00 for every rejected preference (bin/gstack-taste-update cmdShow).
show ranking sorts rejections by confidence * rejected_count, which is 0 for all of them, so .slice(0, 3) returns insertion order instead of the strongest rejections.
- Taste-drift warning at
if (opp && opp.approved_count + opp.rejected_count >= 3 && opp.confidence >= 0.6) is unreachable through real CLI use, because a rejected entry's confidence can never exceed 0. (The existing test test/taste-engine.test.ts only passes it by hand-seeding confidence: 0.8 via writeProfile, with a comment acknowledging the natural value is 0/5.)
Current behavior on upstream main (19770ea)
Reproduced with a temp state dir + git repo:
$ gstack-taste-update rejected v1 --reason "colors: crimson" # x3
$ gstack-taste-update rejected vx --reason "colors: beige" # x1
$ gstack-taste-update show
[colors]
rejected:
crimson — conf 0.00 (+0/-3)
beige — conf 0.00 (+0/-1)
Both confidences are 0, and the profile JSON stores "confidence": 0 for both. A value rejected 3 times ranks no differently from one rejected once.
Expected behavior
A rejected preference's confidence should reflect rejection strength (symmetric with the approved bucket): rejected_count / (total + 1). After the fix, crimson (3 rejections) → conf 0.75, beige (1 rejection) → conf 0.50, the show ranking orders by strength, and the drift warning fires naturally after enough real rejections.
Duplicate searches performed
Candidate fix shape
In bumpPref(), compute confidence from this bucket's own count:
const total = entry.approved_count + entry.rejected_count;
const ownCount = action === 'approved' ? entry.approved_count : entry.rejected_count;
entry.confidence = ownCount / (total + 1);
This leaves approved-bucket confidence identical (for approved entries total === approved_count) and only fixes the rejected case. Add regression coverage for rejected confidence, the show ranking, and the drift warning firing from real CLI rejections.
Observed problem
In
bin/gstack-taste-update, every preference in arejectedbucket is permanently stored withconfidence: 0. This makes theshowoutput useless for rejections and silently disables the taste-drift conflict warning.Root cause
bumpPref()computes confidence as approval rate for both buckets:A rejected-bucket entry only ever gets
rejected_countincremented (the approved and rejected lists are disjoint — a value approved goes inapproved, rejected goes inrejected). Soapproved_countstays 0 andconfidence = 0 / (rejected_count + 1) = 0no matter how many times the value is rejected.Downstream effects:
showdisplay printsconf 0.00for every rejected preference (bin/gstack-taste-updatecmdShow).showranking sorts rejections byconfidence * rejected_count, which is0for all of them, so.slice(0, 3)returns insertion order instead of the strongest rejections.if (opp && opp.approved_count + opp.rejected_count >= 3 && opp.confidence >= 0.6)is unreachable through real CLI use, because a rejected entry'sconfidencecan never exceed 0. (The existing testtest/taste-engine.test.tsonly passes it by hand-seedingconfidence: 0.8viawriteProfile, with a comment acknowledging the natural value is0/5.)Current behavior on upstream main (19770ea)
Reproduced with a temp state dir + git repo:
Both confidences are 0, and the profile JSON stores
"confidence": 0for both. A value rejected 3 times ranks no differently from one rejected once.Expected behavior
A rejected preference's confidence should reflect rejection strength (symmetric with the approved bucket):
rejected_count / (total + 1). After the fix,crimson(3 rejections) → conf 0.75,beige(1 rejection) → conf 0.50, theshowranking orders by strength, and the drift warning fires naturally after enough real rejections.Duplicate searches performed
taste,in:title taste,confidence rejected taste→ no matchbin/gstack-taste-update→ only the originating feature PR feat(v1.3.0.0): open agents learnings + cross-model benchmark skill #1040 (v1.3.0.0); file untouched sincebin/gstack-taste-updateCandidate fix shape
In
bumpPref(), compute confidence from this bucket's own count:This leaves approved-bucket confidence identical (for approved entries
total === approved_count) and only fixes the rejected case. Add regression coverage for rejected confidence, theshowranking, and the drift warning firing from real CLI rejections.