⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
src/signals/local-branch.ts reads the same scorePreview.warnings string array through two different
regexes for two different purposes, and the regexes have silently drifted apart.
The blocker classifier (decides which warnings count as real score blockers):
const scoreBlockers = [
...rewardRisk.scoreBlockers,
...scorePreview.warnings.filter((warning) => /not registered|no active|exceeds|credibility|token gate|confirmed ineligible/i.test(warning)),
...preflight.findings.filter((finding) => finding.severity !== "info").map((finding) => finding.title),
];
The finding-severity classifier, applied to the exact same scorePreview.warnings array elsewhere in
the same function, building the localFindings list:
...scorePreview.warnings
.filter((warning) => !/branch eligibility/i.test(warning))
.map((warning) => ({
code: "score_preview_warning",
severity: /not registered|no active|exceeds|credibility/i.test(warning) ? ("warning" as const) : ("info" as const),
title: "Private preview warning",
detail: warning,
})),
The severity regex (/not registered|no active|exceeds|credibility/i) is missing two alternatives the
blocker regex already has: token gate and confirmed ineligible. A warning string containing either
of those phrases is correctly treated as a real blocker for scoreBlockers (the first snippet), but the
IDENTICAL warning text is classified as severity: "info" (not "warning") when the same array is
mapped into localFindings (the second snippet) — an understated severity badge for a warning the
codebase's own blocker logic already treats as blocking.
This is a display/labeling divergence only: branchQualityBlockersFor and the PR-packet warning list
both already filter score_preview_warning findings by code, independent of severity — so this does
not change any merge/close/blocker behavior. It only means a "token gate" or "confirmed ineligible"
warning shows up with the wrong (understated) severity badge to anyone reading localFindings directly.
Requirements
- The severity classifier for
score_preview_warning findings must recognize the exact same set of
phrases the blocker classifier already recognizes, so the two can never silently drift apart again.
Extract the pattern into ONE shared regex constant (e.g. a module-level
SCORE_PREVIEW_BLOCKING_WARNING_PATTERN) and use that single constant at both call sites, rather than
independently editing the severity regex's alternatives to match today's blocker regex (which would
leave the same two-copies-that-can-drift shape in place for a future edit).
- Must not change which warnings are excluded from
localFindings entirely (the
!/branch eligibility/i.test(warning) filter) — only which of the surviving warnings get "warning"
vs "info" severity.
- Must not change
scoreBlockers's own behavior or set of matched phrases.
Deliverables
All three Deliverables are required in the same PR.
Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ patch coverage on every changed line and branch under
src/**. src/signals/local-branch.ts is inside src/**. The new test(s) must exercise the real
localFindings-building code path (not just test a regex constant in isolation) so the fix is verified
against the actual finding-severity output.
Expected Outcome
scoreBlockers and the score_preview_warning severity classification in localFindings are driven by
one shared pattern instead of two independently-maintained regexes, so a "token gate" or "confirmed
ineligible" warning is consistently labeled severity: "warning" everywhere it's surfaced, and a future
addition to the blocker pattern can no longer silently miss the severity classifier (or vice versa).
Links & Resources
src/signals/local-branch.ts — the blocker-filter regex (around line 359) and the severity-classifier
regex (around line 899), both reading the same scorePreview.warnings array.
Context
src/signals/local-branch.tsreads the samescorePreview.warningsstring array through two differentregexes for two different purposes, and the regexes have silently drifted apart.
The blocker classifier (decides which warnings count as real score blockers):
The finding-severity classifier, applied to the exact same
scorePreview.warningsarray elsewhere inthe same function, building the
localFindingslist:The severity regex (
/not registered|no active|exceeds|credibility/i) is missing two alternatives theblocker regex already has:
token gateandconfirmed ineligible. A warning string containing eitherof those phrases is correctly treated as a real blocker for
scoreBlockers(the first snippet), but theIDENTICAL warning text is classified as
severity: "info"(not"warning") when the same array ismapped into
localFindings(the second snippet) — an understated severity badge for a warning thecodebase's own blocker logic already treats as blocking.
This is a display/labeling divergence only:
branchQualityBlockersForand the PR-packet warning listboth already filter
score_preview_warningfindings bycode, independent ofseverity— so this doesnot change any merge/close/blocker behavior. It only means a "token gate" or "confirmed ineligible"
warning shows up with the wrong (understated) severity badge to anyone reading
localFindingsdirectly.Requirements
score_preview_warningfindings must recognize the exact same set ofphrases the blocker classifier already recognizes, so the two can never silently drift apart again.
Extract the pattern into ONE shared regex constant (e.g. a module-level
SCORE_PREVIEW_BLOCKING_WARNING_PATTERN) and use that single constant at both call sites, rather thanindependently editing the severity regex's alternatives to match today's blocker regex (which would
leave the same two-copies-that-can-drift shape in place for a future edit).
localFindingsentirely (the!/branch eligibility/i.test(warning)filter) — only which of the surviving warnings get"warning"vs
"info"severity.scoreBlockers's own behavior or set of matched phrases.Deliverables
inline blocker-filter regex and the inline severity-classifier regex in
src/signals/local-branch.ts, so both call sites reference the identical pattern."token gate"(and, separately, one containing"confirmed ineligible") is classified withseverity: "warning"in thelocalFindingsoutput,verified by a new test — today it incorrectly comes back
"info".(
"not registered","no active","exceeds","credibility") still classifies asseverity: "warning"after the consolidation, and a warning matching none of the phrases stillclassifies as
"info".All three Deliverables are required in the same PR.
Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ patch coverage on every changed line and branch under
src/**.src/signals/local-branch.tsis insidesrc/**. The new test(s) must exercise the reallocalFindings-building code path (not just test a regex constant in isolation) so the fix is verifiedagainst the actual finding-severity output.
Expected Outcome
scoreBlockersand thescore_preview_warningseverity classification inlocalFindingsare driven byone shared pattern instead of two independently-maintained regexes, so a "token gate" or "confirmed
ineligible" warning is consistently labeled
severity: "warning"everywhere it's surfaced, and a futureaddition to the blocker pattern can no longer silently miss the severity classifier (or vice versa).
Links & Resources
src/signals/local-branch.ts— the blocker-filter regex (around line 359) and the severity-classifierregex (around line 899), both reading the same
scorePreview.warningsarray.