garrytan / garrytan/gstack

taste profile: rejected preferences always have confidence 0 (broken `show` ranking + dead drift warning)

Open
#1,776 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.