Skip to content

doctor has no answer to 'what do I fix first': findings print in check order and a root cause weighs the same as its echoes #468

Description

@MongLong0214

doctor has no answer to "what do I fix first". Findings appear in check order, a root cause and its echoes carry equal weight, and the same fix string prints once per finding that shares it. The report's first line is whichever check happens to be first, not the thing that matters.

Why this exists

Scope

Pure, exported, tested functions in src/commands/doctor/report.ts. No output change in this ticket — the envelope ticket wires fixPlan/headline into --json, the text ticket renders them.

export const computeFixPlan = (checks: readonly DoctorCheck[]): string[];
export const deriveHeadline = (args: {
  checks: readonly DoctorCheck[];
  fixPlan: readonly string[];
  status: 'ok' | 'degraded' | 'failed';   // computed by the caller; derivation lands with the envelope
}): string;

Per PRD §4:

  1. Membership: ids whose status is fail or warn and whose blockedBy is unset. (Optionality does not filter the plan — §4.1 filters on status and blockedBy only; optional gates status and exit code, §1.4.)
  2. Order: fail entries before warn entries; registry order within each tier; stable across runs.
  3. No cap — "A cap would hide findings, which is the one thing this command exists not to do." The wall-of-text control is collapse plus render-time dedup, and dedup is the renderer's job, not the plan's.
  4. Headline: Next action [<id>]: <detail> — <fix> from fixPlan[0]. Empty plan: status === "ok" → healthy wording; status === "degraded" with no actionable entry → "usable; some checks could not be verified". Never healthy wording while status is non-ok.

Acceptance criteria

  • computeFixPlan implements membership and order exactly as above, is pure, and is deterministic across runs on identical input.
  • deriveHeadline implements §4.4 including the never-healthy-while-non-ok property.
  • No cap of any size exists.
  • Nothing renders yet: text output and --json byte-identical, snapshots green.

Tests that must exist

case what it catches
dead hook runtime: the plan contains hook-runtime and not commit-msg-hook the second half of PRD §11's "collapse" row — a plan that lists both (noise) or filters the root instead of the blocked entry (loses the cause)
an independent defect stays in the plan — the dependent failing for its own reason, blockedBy unset, appears PRD §11 "independent failure survives collapse"; ADR falsification 2 — the plan burying a real finding because an unrelated dependency also failed. This is the fails-if-wrong case
fail sorts before warn, registry order breaks ties — interleaved fixture severity-blind ordering, or an unstable sort that reorders equal-tier entries between runs
twenty synthetic findings produce twenty entries a helpful cap sneaking in
empty plan + degraded: headline says usable-not-verified and empty plan + ok: healthy wording are distinct strings PRD §4.4's exact warning: a merely-unverified run reading as a clean one — the #402 shape ("a ready that is not ready") in miniature
headline of a plan whose first entry has fix: null renders without a dangling separator the crash-row case (fix: null from a contained throw) producing — null

Open questions the PRD leaves — flagged, not decided here

  1. The exact healthy and usable-not-verified headline strings are unfixed — the PRD fixes their properties (distinct; never healthy while non-ok), not their words. Pick once, snapshot, and treat as contract.
  2. Next action [<id>]: <detail> — <fix> with fix: null (a contained crash row is fail with no fix) is unspecified. Minimal reading: omit the — <fix> segment. State the choice in the PR.

Depends on

Out of scope

  • Rendering and the seenRemediations-style fix dedup (text ticket, PRD §4.3), the status derivation and envelope wiring (envelope ticket), any change to which checks warn or fail.

Traceability: PRD §4, §11; ADR-0032 §5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions