fix(#3309): W020 fires on any degraded worktree scan, not just real failures
gsd-test found buildWorktreeHealthField collapsed every inspectWorktreeHealth failure reason (git_timed_out, git_list_failed, not_a_git_repo) into one UNREADABLE scope, discarding which one. The migrated checkW020 then warned unconditionally on any UNREADABLE scope — but the original (verify.cts:2202-2217) only warned on git_timed_out/git_list_failed, staying silent on not_a_git_repo (a .planning/-only fixture with no git repo at all is not a degraded scan, just the absence of one). This spuriously degraded every test fixture that isn't a real git repo. planning-snapshot.cts's worktreeHealth field now carries `reason` through instead of discarding it; checkW020 branches on it exactly like the pre-migration code did.
This commit is contained in:
@@ -13,24 +13,16 @@
|
||||
* pre-migration source still names the split-off stale-worktree site
|
||||
* 'W017' — this batch is what actually applies the W027 split).
|
||||
*
|
||||
* KNOWN GAP (found while building, reported rather than papered over — see
|
||||
* this batch's dispatch report for full detail):
|
||||
*
|
||||
* 1. W020's original THREE conditions were git_timed_out / git_list_failed /
|
||||
* a per-finding 'unverified' kind, each with its own message. The first
|
||||
* two are scan-level failures reported by `inspectWorktreeHealth`'s own
|
||||
* `reason` field ('git_timed_out' vs 'git_list_failed' vs
|
||||
* 'not_a_git_repo') — but `planning-snapshot.cts`'s
|
||||
* `buildWorktreeHealthField` discards `reason` entirely and only
|
||||
* preserves `scope: SCOPE.UNREADABLE` for ANY `!result.ok` case. This
|
||||
* rule therefore CANNOT distinguish "git timed out" from "git worktree
|
||||
* list failed outright" from the snapshot alone — both collapse to the
|
||||
* same `checkScanDegraded` branch below, which emits one reasonable
|
||||
* combined message instead of the original's two separate ones. Fixing
|
||||
* this precisely requires extending `PlanningSnapshot.worktreeHealth`
|
||||
* with the discarded `reason` field — an snapshot-field enhancement
|
||||
* outside this rule-file batch's scope, flagged here rather than guessed
|
||||
* around.
|
||||
* W020's original THREE conditions were git_timed_out / git_list_failed / a
|
||||
* per-finding 'unverified' kind, each with its own message. The first two
|
||||
* are scan-level failures reported by `inspectWorktreeHealth`'s own `reason`
|
||||
* field ('git_timed_out' vs 'git_list_failed' vs 'not_a_git_repo') —
|
||||
* `planning-snapshot.cts`'s `buildWorktreeHealthField` now carries `reason`
|
||||
* straight through on `PlanningSnapshot.worktreeHealth`, so `checkW020`
|
||||
* below reproduces the original's exact branch-per-reason messages instead
|
||||
* of collapsing them (a prior version of this file collapsed both into one
|
||||
* message AND, worse, warned on 'not_a_git_repo' too — a regression, since
|
||||
* the original silently skips a non-git cwd; see `verify.cts:2202-2217`).
|
||||
*
|
||||
* W027 restores the pre-migration active-worktree exclusion
|
||||
* (`verify.cts:2233-2242`) via `PlanningSnapshot.cwd` — see `checkW027`'s own
|
||||
@@ -60,29 +52,46 @@ type Rule = healthDiagnosticMod.Rule;
|
||||
import planningScopeMod = require('../planning-scope.cjs');
|
||||
const { SCOPE } = planningScopeMod;
|
||||
|
||||
// ─── W020 — worktree health scan itself is degraded (verify.cts:2203-2264) ─
|
||||
// ─── W020 — worktree health scan itself is degraded (verify.cts:2193-2264) ─
|
||||
//
|
||||
// ONE rule, THREE internal conditions, all the same subject ("the worktree
|
||||
// health scan itself is degraded" — design doc "Rejected alternatives" §3):
|
||||
// (a) `git worktree list` timed out, (b) `git worktree list` failed
|
||||
// outright, (c) a specific 'unverified' finding (existsSync ok, statSync
|
||||
// threw). (a) and (b) collapse to a single combined message per the
|
||||
// module-doc gap note above; (c) is a per-finding, exact port of
|
||||
// `verify.cts:2256-2263`.
|
||||
// (a) `git worktree list` timed out (verify.cts:2202-2209), (b) `git
|
||||
// worktree list` failed outright (verify.cts:2210-2217), (c) a specific
|
||||
// 'unverified' finding (existsSync ok, statSync threw,
|
||||
// verify.cts:2256-2263). A fourth `!ok` reason, 'not_a_git_repo', and a
|
||||
// thrown exception ('exception') are DELIBERATELY silent — the original's
|
||||
// `if` ladder never matches 'not_a_git_repo', and the outer try/catch around
|
||||
// the whole block is commented "git worktree not available or not a git
|
||||
// repo — skip silently".
|
||||
|
||||
function checkW020(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const { scope, reason } = snapshot.worktreeHealth;
|
||||
const degraded = scope === SCOPE.UNREADABLE;
|
||||
|
||||
// (a)+(b) — scan-level degradation. GAP: cannot distinguish timeout from
|
||||
// outright failure from `scope` alone (see module doc, gap 1).
|
||||
if (snapshot.worktreeHealth.scope === SCOPE.UNREADABLE) {
|
||||
// (a) — git worktree list timed out (verify.cts:2202-2209).
|
||||
if (degraded && reason === 'git_timed_out') {
|
||||
diagnostics.push({
|
||||
code: 'W020',
|
||||
severity: SEVERITY.WARNING,
|
||||
message:
|
||||
'Worktree health check degraded: git worktree list timed out or failed — orphan/stale worktrees could not be inspected',
|
||||
'Worktree health check degraded: git worktree list timed out after 10s — orphan/stale worktrees could not be inspected',
|
||||
remedy: adviseRemedy(
|
||||
'Run: git worktree list --porcelain to diagnose; check for .git/index.lock, a hung git process, or repository permissions',
|
||||
'Run: git worktree list --porcelain to diagnose; check for .git/index.lock or a hung git process',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// (b) — git worktree list failed outright (verify.cts:2210-2217).
|
||||
if (degraded && reason === 'git_list_failed') {
|
||||
diagnostics.push({
|
||||
code: 'W020',
|
||||
severity: SEVERITY.WARNING,
|
||||
message:
|
||||
'Worktree health check degraded: git worktree list failed — orphan/stale worktrees could not be inspected',
|
||||
remedy: adviseRemedy(
|
||||
'Run: git worktree list --porcelain to diagnose; check git repository state and permissions',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4526,13 +4526,15 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
|
||||
});
|
||||
|
||||
test('validate health warns when worktree inventory cannot be listed', () => {
|
||||
const source = read('gsd-core/bin/lib/verify.cjs');
|
||||
// Accept both hand-written dot access and the tsc-compiled bracket form
|
||||
// (ADR-457: verify.cjs is now emitted from src/verify.cts):
|
||||
// hand-written: worktreeHealth.reason === 'git_list_failed'
|
||||
// tsc-compiled: worktreeHealth['reason'] === 'git_list_failed'
|
||||
const failureBranch = source.search(/worktreeHealth(?:\.reason|\['reason'\]) === 'git_list_failed'/);
|
||||
const warning = source.indexOf("addIssue('warning', 'W020'", failureBranch);
|
||||
// Phase 11 (#3309, ADR-3180): this branch moved out of verify.cts into
|
||||
// the W020 rule (src/health-diagnostic-rules/worktree-health.cts),
|
||||
// compiled to gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs.
|
||||
// Accept both hand-written dot access and the tsc-compiled bracket form:
|
||||
// hand-written: reason === 'git_list_failed'
|
||||
// tsc-compiled: reason === 'git_list_failed' (unchanged shape either way)
|
||||
const source = read('gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs');
|
||||
const failureBranch = source.search(/reason === 'git_list_failed'/);
|
||||
const warning = source.indexOf("code: 'W020'", failureBranch);
|
||||
|
||||
assert.ok(failureBranch > 0, 'verify health should branch on git_list_failed');
|
||||
assert.ok(warning > failureBranch, 'git_list_failed should emit W020 degraded-health warning');
|
||||
|
||||
Reference in New Issue
Block a user