A non-ok doctor check states its conclusion without the observation that produced it. The exit code a probe returned, the path that was actually executed, the counts a verdict rests on — all of it is either interpolated into the detail sentence or discarded. A machine consumer gets prose; a human pasting a bug report gets whatever the sentence happened to include.
Why this exists
The defect history of this command is a history of missing evidence:
PRD §1.3 makes evidence a rule; ADR-0032 §8.3 makes it an invariant: "No claim without evidence."
Scope
Populate evidence: Record<string, string> in every check path. Keys snake_case, values strings (PRD §1.3).
- A bounded-excerpt helper for process output: first line, hard cap 200 characters, paired
<stream>_truncated: "true" | "false" key (PRD §1.3 rule 2). The excerpt stays because hook-runtime and inject-runtime diagnose from stderr first lines — ADR-0032 explicitly rejected collapsing evidence to present/empty.
- A home-relative path helper: no evidence value carries an absolute
$HOME prefix; paths render ~/... (PRD §1.3 rule 3).
- Minimum keys per check (more are allowed; these are the floor a reviewer checks):
| check |
minimum evidence |
| cli-runtime |
entry (the resolved artifact probed — the #40 lesson), version on ok; exit_code, stderr_first_line, stderr_truncated on fail |
| notes-refspec |
remotes; missing / forced when non-empty; per-remote fetch exit_code on the could-not-verify path |
| notes-push |
remote, local_sha; remote_sha or ls_remote_stderr_first_line |
| commit-msg-hook |
hook_path, the recorded target fields (bin, node as recorded), commitlore_bin_override when set — the #49 surface |
| hook-runtime |
exit_code, stderr_first_line, stderr_truncated on non-ok |
| inject-runtime |
executable (what was actually spawned — the #149 surface), exit_code, stderr_first_line on non-ok |
| inject-version |
executable, theirs, mine — the #382 surface |
| mcp-lifecycle |
unfinished_count, last_pid, last_at on warn |
| pending-backlog |
stranded, staged_expired, oldest on warn |
| git-trailers |
git_version, parsed on fail |
| history-depth |
shallow: "true" on warn |
| index-health |
trailers, commits, last_indexed_sha, head_sha, fts |
| squash-conservation |
candidates, checked, uncheckable, lost_count on non-skip |
- Detail strings and text output stay byte-identical: evidence is a
--json surface until the text ticket adds --verbose.
Acceptance criteria
Tests that must exist
| case |
what it catches |
a hook printing 10KB of stderr yields a ≤200-char excerpt with stderr_truncated "true" |
unbounded evidence — the exact PRD §1.3.2 warning: "the bound exists so --json output size is independent of what a broken tool prints". This is the fails-if-wrong-the-way-the-PRD-warns case |
inject-runtime failure names the executable it spawned |
the #149 regression: evidence must be the configured command actually executed, never a reconstructed path |
commit-msg-hook surfaces the recorded target and any COMMITLORE_BIN override |
the #49 channel going quiet again |
inject-version rows carry theirs and mine |
the #382 skew becoming prose-only again |
paths under HOME render home-relative |
$HOME leaking into pasted bug reports (PRD §1.3.3) |
non-ok with empty evidence fails the suite (the invariant itself, exercised by a deliberately-broken test-only row) |
any future check path that reaches a verdict without recording an observation — ADR-0032 §8.3 |
Depends on
Out of scope
--verbose rendering (text ticket), the envelope, changing any status decision or detail string, the squash-conservation truncation counters (branches_seen/branches_checked belong to the performance ticket, PRD §10.3).
Traceability: PRD §1.3; ADR-0032 §8.3 and the rejected "present/empty" collapse.
A non-ok doctor check states its conclusion without the observation that produced it. The exit code a probe returned, the path that was actually executed, the counts a verdict rests on — all of it is either interpolated into the
detailsentence or discarded. A machine consumer gets prose; a human pasting a bug report gets whatever the sentence happened to include.Why this exists
The defect history of this command is a history of missing evidence:
ok. The recorded target was the observation that would have exposed it; the fix surfaced it into prose, and prose is where it still lives.okline: "doctor prints the stale path in its own ok line, so it has the value in hand and does not act on it." Values in hand belong in a field a test can assert on.okover a fetch it had just broken; the fetch exit code was the evidence.okbuilt on counting the wrong thing ("109 trailers" that were another convention's vocabulary). Counts in evidence make the claim inspectable.PRD §1.3 makes evidence a rule; ADR-0032 §8.3 makes it an invariant: "No claim without evidence."
Scope
Populate
evidence: Record<string, string>in every check path. Keys snake_case, values strings (PRD §1.3).<stream>_truncated: "true" | "false"key (PRD §1.3 rule 2). The excerpt stays because hook-runtime and inject-runtime diagnose from stderr first lines — ADR-0032 explicitly rejected collapsing evidence to present/empty.$HOMEprefix; paths render~/...(PRD §1.3 rule 3).entry(the resolved artifact probed — the #40 lesson),versionon ok;exit_code,stderr_first_line,stderr_truncatedon failremotes;missing/forcedwhen non-empty; per-remote fetchexit_codeon the could-not-verify pathremote,local_sha;remote_shaorls_remote_stderr_first_linehook_path, the recorded target fields (bin,nodeas recorded),commitlore_bin_overridewhen set — the #49 surfaceexit_code,stderr_first_line,stderr_truncatedon non-okexecutable(what was actually spawned — the #149 surface),exit_code,stderr_first_lineon non-okexecutable,theirs,mine— the #382 surfaceunfinished_count,last_pid,last_aton warnstranded,staged_expired,oldeston warngit_version,parsedon failshallow: "true"on warntrailers,commits,last_indexed_sha,head_sha,ftscandidates,checked,uncheckable,lost_counton non-skip--jsonsurface until the text ticket adds--verbose.Acceptance criteria
_truncatedkey.$HOMEprefix (walk all fixture reports withHOMEpointed at the fixture parent).--jsongains only new keys; existing doctor tests pass without edits.Tests that must exist
a hook printing 10KB of stderr yields a ≤200-char excerpt with stderr_truncated "true"inject-runtime failure names the executable it spawnedcommit-msg-hook surfaces the recorded target and any COMMITLORE_BIN overrideinject-version rows carry theirs and minepaths under HOME render home-relative$HOMEleaking into pasted bug reports (PRD §1.3.3)non-ok with empty evidence fails the suite(the invariant itself, exercised by a deliberately-broken test-only row)Depends on
error), Why doctor skipped a check exists only as prose: ten skip sites, zero machine-readable reasons #464 (skip rows are typed before evidence rules harden around them).Out of scope
--verboserendering (text ticket), the envelope, changing any status decision or detail string, the squash-conservation truncation counters (branches_seen/branches_checkedbelong to the performance ticket, PRD §10.3).Traceability: PRD §1.3; ADR-0032 §8.3 and the rejected "present/empty" collapse.