You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
doctor --json dumps { checks, exitCode } verbatim: no schema identifier, no version, no overall status. A CI consumer cannot pin what it parses, and the state #458 needed a word for — usable, but something this repository relies on has stopped or cannot be verified — has no representation at all. A repository is either "no check failed" or "a check failed", and #458's repository was the first kind while capture had been dead for eight days.
Why this exists
ADR-0032 context: "--json dumps the struct verbatim: no schema identifier, no version field, no machine-readable evidence" — and §6 decides the envelope and its additive-only contract.
The CI story, PRD verbatim: "I run doctor --json, pin schema: 'commitlore_doctor.v2', branch on status, and never re-read the docs when CommitLore upgrades."
Scope
In src/commands/doctor/report.ts and the command action; docs/cli.md gains the envelope documentation (ADR-0032 consequences).
deriveStatus(checks) at one chokepoint, from non-optional checks only: any fail → failed; else any warn or non-optional skipped → degraded; else ok (PRD §5.1). This function owns the §8.3 invariant the way the reference doctor owns its honesty clamp — one tested function, not a property left emergent on each check: no report may say ok while any non-optional check is non-ok, including rows the runner synthesized from a crash.
exitCode: 1 iff some non-optional check is fail, else 0 — SPEC §10's meanings, unchanged; degraded exits 0 (PRD §7.1: this preserves RELEASE-GATE §4's fresh-clone row and init's doctor --fix step, and keeps 3 from acquiring a doctor-private meaning).
installSource per PRD §5.1, detected without spawning anything: CLAUDE_PLUGIN_ROOT set or entry under the plugin root → plugin; entry under a global node_modules prefix → npm; an _npx cache segment → npx; entry beside a .git with this package's name → source; otherwise unknown. Until a surface has a test, its detection returns unknown rather than a guess. The resolved entry path is already in cli-runtime's evidence, so the classification is itself inspectable.
The v1 compatibility contract (PRD §5.3, ADR-0032 §6): every v1 key keeps its exact name, type, and meaning; new optional fields are omitted when absent, never null; removal or repurposing requires a new schema id and a superseding ADR. New keys are camelCase, matching the surface they extend.
src/commands/init.ts consumes report.checks[].needsAttention and formatReport — it keeps compiling and behaving unchanged (v2 is a superset).
Acceptance criteria
doctor --json | jq -r .schema prints commitlore_doctor.v2 (PRD §11 "schema pinned").
v1 superset regression test: a v1-shaped reader (checks[].{id, title, status, needsAttention, detail, fix, fixed}, exitCode, and report.checks.some(c => c.status === 'fail')) decodes a v2 report, with every v1 key present at the same type and — over the same fixture — the same values as the pre-envelope output. PRD §5.3: this test "must pass forever".
Serialized JSON contains no null-valued optional: no "blockedBy": null, "skipReason": null, "selection": null.
Status table exercised: all-ok → ok; one warn → degraded; one non-optional skip → degraded; one optional skip (synthetic registry entry) → ok; one fail → failed.
Crash-synthesized rows flow through the chokepoint: a throwing check yields status: "failed", never ok (PRD §8.3).
Exit codes: ok and degraded → 0; failed → 1 (PRD §11 "exit codes", first three columns).
Fresh-clone-shaped fixture (no notes refspec, nothing pushed): warns present, status: "degraded", exit 0 — the RELEASE-GATE §4 row unbroken.
Summary invariants asserted as a property over all fixtures (PRD §11 "summary invariant").
installSource has one test per detected surface; an undetectable surface yields unknown.
docs/cli.md documents the envelope and the additive rule.
Text output still byte-identical — the envelope is a --json-only change until the text ticket.
Tests that must exist
case
what it catches
a v1 reader survives v2 (the superset regression)
ADR falsification 3 exactly: a removed key, a renamed key, or null-instead-of-omitted — the three ways a pinned consumer breaks. This is the fails-if-wrong case
degraded exits 0
the reference doctor's 0/1/2/3 exit mapping being carried over despite the ADR rejecting it — degraded → 3 would give a SPEC §10 code a doctor-private meaning (ADR falsification 4)
a crashed check cannot leave status ok
the #458 property restated: a subsystem verifiably stopped while the report says ok (ADR falsification 1) — through the crash path, where it is easiest to forget
summary counts and durationMs sum
a summary computed independently of checks[] drifting from it
Text rendering (next ticket), selection emission (filters ticket), snake_case keys (rejected, ADR-0032), any change to a v1 key, any network probe for installSource (PRD §5.1: "detected without spawning anything").
doctor --jsondumps{ checks, exitCode }verbatim: no schema identifier, no version, no overall status. A CI consumer cannot pin what it parses, and the state #458 needed a word for — usable, but something this repository relies on has stopped or cannot be verified — has no representation at all. A repository is either "no check failed" or "a check failed", and #458's repository was the first kind while capture had been dead for eight days.Why this exists
--jsondumps the struct verbatim: no schema identifier, no version field, no machine-readable evidence" — and §6 decides the envelope and its additive-only contract.status: "degraded"is that word, and#458's acceptance row requiresstatus ≠ okon a repository with stranded staged captures.doctor --json, pinschema: 'commitlore_doctor.v2', branch onstatus, and never re-read the docs when CommitLore upgrades."Scope
In
src/commands/doctor/report.tsand the command action;docs/cli.mdgains the envelope documentation (ADR-0032 consequences).deriveStatus(checks)at one chokepoint, from non-optional checks only: anyfail→failed; else anywarnor non-optionalskipped→degraded; elseok(PRD §5.1). This function owns the §8.3 invariant the way the reference doctor owns its honesty clamp — one tested function, not a property left emergent on each check: no report may sayokwhile any non-optional check is non-ok, including rows the runner synthesized from a crash.summarycounts satisfyok + warn + fail + skipped === total === checks.length;summary.durationMs === Σ checks[].durationMs(PRD §5.2, §10.2).exitCode:1iff some non-optional check isfail, else0— SPEC §10's meanings, unchanged;degradedexits0(PRD §7.1: this preserves RELEASE-GATE §4's fresh-clone row andinit'sdoctor --fixstep, and keeps3from acquiring a doctor-private meaning).installSourceper PRD §5.1, detected without spawning anything:CLAUDE_PLUGIN_ROOTset or entry under the plugin root →plugin; entry under a globalnode_modulesprefix →npm; an_npxcache segment →npx; entry beside a.gitwith this package's name →source; otherwiseunknown. Until a surface has a test, its detection returnsunknownrather than a guess. The resolved entry path is already incli-runtime's evidence, so the classification is itself inspectable.src/commands/init.tsconsumesreport.checks[].needsAttentionandformatReport— it keeps compiling and behaving unchanged (v2 is a superset).Acceptance criteria
doctor --json | jq -r .schemaprintscommitlore_doctor.v2(PRD §11 "schema pinned").checks[].{id, title, status, needsAttention, detail, fix, fixed},exitCode, andreport.checks.some(c => c.status === 'fail')) decodes a v2 report, with every v1 key present at the same type and — over the same fixture — the same values as the pre-envelope output. PRD §5.3: this test "must pass forever"."blockedBy": null,"skipReason": null,"selection": null.ok; one warn →degraded; one non-optional skip →degraded; one optional skip (synthetic registry entry) →ok; one fail →failed.status: "failed", neverok(PRD §8.3).okanddegraded→ 0;failed→ 1 (PRD §11 "exit codes", first three columns).status: "degraded", exit 0 — the RELEASE-GATE §4 row unbroken.installSourcehas one test per detected surface; an undetectable surface yieldsunknown.docs/cli.mddocuments the envelope and the additive rule.--json-only change until the text ticket.Tests that must exist
a v1 reader survives v2(the superset regression)degraded exits 0degraded → 3would give a SPEC §10 code a doctor-private meaning (ADR falsification 4)a crashed check cannot leave status okok(ADR falsification 1) — through the crash path, where it is easiest to forgetsummary counts and durationMs sumchecks[]drifting from itinstallSource: plugin / npm / npx / source / unknown(five synthesized entry-path fixtures)unknownover a guessinit still reports needsAttention findings identicallyDepends on
durationMs), A non-ok doctor check states its conclusion without the observation that produced it #465 (evidence — the entry path that makesinstallSourceinspectable), 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 (fixPlan,deriveHeadline), doctor is one 1,159-line file: model, registry, runner, thirteen checks and renderer with no seam between them #467 (report.ts).Out of scope
selectionemission (filters ticket), snake_case keys (rejected, ADR-0032), any change to a v1 key, any network probe forinstallSource(PRD §5.1: "detected without spawning anything").Traceability: PRD §5, §5.1, §7.1, §8.3, §10.2, §11; ADR-0032 §6, §7; SPEC §10; docs/RELEASE-GATE.md §4.