taste profile: rejected preferences always have confidence 0 (broken `show` ranking + dead drift warning)
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## 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:
```ts
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:
1. **`show` display** prints `conf 0.00` for every rejected preference (`bin/gstack-taste-update` cmdShow).
2. **`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.
3. **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 (19770ea8)
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
- open issues / PRs: `taste`, `in:title taste`, `confidence rejected taste` → no match
- recently closed PRs touching `bin/gstack-taste-update` → only the originating feature PR #1040 (v1.3.0.0); file untouched since
- no open PR touches `bin/gstack-taste-update`
## Candidate fix shape
In `bumpPref()`, compute confidence from this bucket's own count:
```ts
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.
Contributor guide
Research direction
Start in bin/gstack-taste-update, focusing on bumpPref() and cmdShow; review test/taste-engine.test.ts for existing drift-warning coverage. Add regression coverage for rejected confidence, show ranking, and the warning triggered by real CLI rejections, then verify that rejection strength determines confidence and ranking.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100