Repository navigation
[quality] prefixed.ts: /stage and /status exclusive replacement, two-value /remove-lifecycle, mixed-case allowlist values and the first protected offender survive mutation #538
Description
Finding
src/labels/prefixed.ts is 100 % line- and branch-covered by npm run test:coverage and by npm run test:coverage:e2e on main @ 88241f4. A single-operator mutation probe over the file (59 mutants: ===/!==, &&/||, ??/||, .some/.every, .filter/.map, .find/.findLast, .toLowerCase() removal, exclusive: true→false, new Set removal, comparison bounds) still left six non-equivalent survivors against npx vitest related src/labels/prefixed.ts --run (49 files / 722 tests):
| line | mutant | behaviour nothing asserts |
|---|---|---|
| 32 | /stage registry entry exclusive: true → false |
/stage beta on an issue carrying stage/alpha must DELETE stage/alpha before POST stage/beta. Only /lifecycle (__tests__/label/lifecycle.test.ts) and /priority (__tests__/label/priority.test.ts) have a replacement case; /stage is only ever added to a label-less issue. |
| 33 | /status registry entry exclusive: true → false |
same for /status in-review over status/in-progress |
| 136 | present = currentLabels.filter(label => labels.some(…)) → .every(…) |
/remove-lifecycle stale frozen with both labels on the issue must delete both. Every /remove-<cmd> case in the suite names a single value, so some and every agree. |
| 239 | new Map(allowed.map(value => [value.toLowerCase(), value])) → [value, value] |
An allowlist value written Frozen in the yaml must match the comment /lifecycle frozen and apply lifecycle/Frozen. Every fixture allowlist is lower-case, so the key fold never decides. |
| 266 | sameLabel: a.toLowerCase() === b.toLowerCase() → a === b.toLowerCase() |
With the yaml value Frozen and lifecycle/frozen on the issue, /lifecycle frozen must not treat the existing label as stale, and /remove-lifecycle frozen must delete it. The existing casing tests (kind.test.ts, help.test.ts) vary the issue side only, so the requested side is always already lower-case. |
| 255 | labels.find(isProtectedLabel) → .findLast(…) |
/label hold lgtm must name hold, the first protected label, in the refusal. The existing refusals each carry one protected label. |
Equivalent, not actionable: line 54 protectedPrefixes.some→.every (one prefix in the array), line 102 cmd.prefix.toLowerCase() removal (registry prefixes are lower-case and dynamic ones pass labelCommandName), line 106 stale.length > 0→>= 0 (removeLabels([]) makes no call), line 221 ?? false→|| false (the operand is boolean | undefined), lines 88/124 required: true→false (getInput is stubbed).
Recommendation
Add to __tests__/label/lifecycle.test.ts:
- an
it.eachwith/stage betaoverstage/alphaand/status in-reviewoverstatus/in-progress, asserting['DELETE <stale>', 'POST <label>'] /remove-lifecycle stale frozenwith both on the issue → twoDELETEs- a
with a mixed-case yaml valueblock (lifecycle:\n - Frozen\n):/lifecycle frozen→POST lifecycle/Frozen; the same withlifecycle/frozenalready present →POSTonly, noDELETE;/remove-lifecycle frozen→DELETE lifecycle%2Ffrozen
Add to __tests__/label/label.test.ts › protected labels: /label hold lgtm refused with label: hold is managed by its own command.
Test-only; no src/ or dist/ change. Disjoint from the open held PRs #510–#537: none touches __tests__/label/lifecycle.test.ts, __tests__/label/label.test.ts or src/labels/prefixed.ts (#512 edits __tests__/label/remove.test.ts only).
Evidence
- unit:
npm run test:coverage→ 100 / 99.75 / 100 / 100 (main @ 88241f4, local run, 163 files / 2206 tests);prefixed.ts100 % lines, no zero-hit branch - e2e:
npm run test:coverage:e2e→ 99.96 / 98.79 / 100 / 99.95 (same revision, local run);prefixed.ts100 % lines, no zero-hit branch (the two reports come from different transforms and are analysed separately) - probe: each mutant applied in turn to
src/labels/prefixed.ts,npx vitest related src/labels/prefixed.ts --run→ 722 passed for all six; with the recommended cases each mutant fails at least one test
Priority
- Impact: medium —
exclusiveis what makes/stageand/statussingle-valued; a regression there stacksstage/alphaandstage/betaon the same issue with the suite green - Effort: low
Filed by quality agent (hold-gated mode)
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 88241f4
— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88
Activity
github-actions commentedon Oct 20, 2026
Please add a kind label with
/kind feature,/kind failing-testor/kind cleanup.