Uh oh!
There was an error while loading. Please reload this page.
fix(cli): withhold doctor's ✓ over a tree it never examined (#10679) - #10799
Conversation
`findMissingTests()` and `findDeprecatedUsages()` both walk `<cwd>/packages/spec/src`, a path present in this monorepo and in no application. Both returned `[]` for "that directory is not here" — the same value they return for "I walked it and found nothing wrong" — so every stock scaffold printed `✓ Test coverage` and `✓ Deprecations` about files doctor never opened, exiting 0 either way. Whether the tree was examined is now a fact in the return type (`MonorepoTreeScan`), the shape #5413 used for the installed-package ledger, so the print site cannot reach the `✓` from the unexamined arm. An unexamined tree prints an informational skip naming the reason, with the resolved directory under `--verbose`. It is deliberately not a warning: withholding a false ✓ must not manufacture a false ⚠. The adjacent `⚠ @objectstack/spec Not built` probe is gated on the workspace existing — outside the monorepo it warned about an absent package and prescribed a command that cannot succeed there. Inside it, unchanged. Two control assertions that pinned `Environment is functional but has some warnings` held only because of that phantom warning; they now assert the claim they actually make (doctor reached its summary and did not call the environment broken). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift CheckThis PR changes 1 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 906247bba51e41436ee3f16009a58dd9d46fac9c && git checkout 906247bba51e41436ee3f16009a58dd9d46fac9c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 112a8c6731ba7ffac424cdcfd3b9cf2edf800748 e34b763a0b9ecebad936f3e5b366be7ae7224d93 && git checkout -B drift-repro 112a8c6731ba7ffac424cdcfd3b9cf2edf800748 && git merge --no-ff e34b763a0b9ecebad936f3e5b366be7ae7224d93
node scripts/docs-audit/affected-docs.mjs --json 112a8c6731ba7ffac424cdcfd3b9cf2edf800748
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10679
The defect
findMissingTests()andfindDeprecatedUsages()both walkpackages/spec/srcunder the process cwd — a path that exists in this monorepo and in no application built with the framework. Both opened with the same two lines:so "that directory is not here" and "I walked it and found nothing wrong" reached the caller as the same value, and the caller's
elsebranch printed a clean bill of health about files doctor never opened. The command exits 0 either way, so "no problems found" and "I never looked" were byte-identical to every downstream reader.Doctor already refuses to do this one screen down: the ADR-0120 D5e advisory's
✓ Unique scopeis withheld unlessledgerReadingIsComplete()says the ledger half was read in full (#5412 / #5413 / #5644). These two checks escaped that discipline. This restores it — it is not a new convention.The change
Three edits, all in
packages/cli/src/commands/doctor.ts.Whether the tree was examined is now a fact in the return type, not an absence — the shape
LocalManifestSource.list()静默丢弃损坏的 ledger 条目 —— 已装应用在 boot 时消失、在控制台列表里缺席,且没有任何一条日志 #5413 used for the installed-package ledger.findMissingTests()/findDeprecatedUsages()return aMonorepoTreeScan, whosescanned: falsearm carries the directory doctor resolved and looked for. The print site cannot reach the✓from that arm even by accident.An unexamined tree prints an informational skip naming the reason — triage's first admissible shape.
-vadds the resolved directory. The step line ("Checking for missing test files…") moved inside the scanned arm too: announcing a scan that never started is the same overclaim one line earlier.The skip is deliberately not a warning. Nothing is wrong in an application that has no
packages/spec/src, and routing it throughprintWarningwould fliphasWarningsand end every healthy scaffold's report on "functional but has some warnings". Withholding a false✓must not manufacture a false⚠.The adjacent
⚠ @objectstack/spec Not builtprobe is gated on the workspace existing (in scope on the same card). It readpackages/spec/distunder the cwd with no check that the workspace it names is part of the tree, so in an application it warned about an absent package and prescribedpnpm --filter @objectstack/spec build, a command that cannot succeed there. Inside the monorepo the row is unchanged. Outside it there is no row: an application consumes@objectstack/specfromnode_modules, where "built" is not a state it can be in, and that dependency stays covered by theDependenciesrow and bycheckSpecVersionGap().Exit codes are untouched — 1 exactly when an error row exists, warnings never flip it, and the skip contributes neither. The fence on this card was not to argue an exit-code change into existence, and none is proposed.
One visible consequence, stated rather than buried: a stock scaffold with no other findings now ends on
✅ Environment is healthy and ready for development!instead of⚠️ Environment is functional but has some warnings, because the warning it used to carry was about a workspace that was never there.Reverse verification, in a real scaffold cwd
packages/create-objectstack/src/templates/blankcopied verbatim into a temp directory (itspackages/directory does not exist), then the realDoctor.run()executed from source with the process cwd set to it. The two legs differ only in the contents ofdoctor.ts; the runner imports that file by path, so it is resolved fromsrc/and no rebuild sits between the legs. The fix was committed first,git checkout origin/main -- packages/cli/src/commands/doctor.tsstood the pre-fix file up, andgit checkout HEAD -- …restored it — byte identity proved withgit hash-object(f16540ff…both sides) and a cleangit status --porcelain.Before (pre-fix
doctor.ts, blob5b640533…):After (this branch), same directory, same command:
The two real findings in that scaffold (
NODE_ENV, and a config-load warning from the uninstalled copy) survive untouched in both legs — this removes only the verdicts about a tree that was never there.With
-v, each skip names the directory it looked for:And inside the monorepo itself, nothing changed —
✓ @objectstack/spec Built, both step lines print, and the real findings (25 missing tests, the@deprecatedtag inapi/protocol.zod.ts:174) are reported exactly as before.Tests
New:
packages/cli/src/commands/doctor-unexamined-spec-tree.test.ts— 14 cases. Every end-to-end case is anchored on the user-app cwd where the tree is absent; the monorepo-shaped cwd appears only as the no-regression half, because a test proving the✓still appears when the tree was walked passes against the defect and touches nothing.It pins: both clean bills of health withheld; two skip rows present with their reason (present, not merely absent — an operator scans the name column); no step line announcing a scan that never started; the skip rendered with
ℹand neither✓nor⚠; the resolved directory named under-vonly; theNot builtrow and its unusable prescription gone in both flag modes; the exit contract unchanged. Then, from the monorepo-shaped cwd: both✓lines when the walked tree is clean, the real findings when it is not,⚠ Not builtstill firing where the workspace really exists and is unbuilt, and✓ Builtonce it has adist/.Two existing control assertions were re-adjudicated rather than re-spelled.
doctor-env-provenance.test.tsanddoctor-tenancy-posture-report.test.tseach pinnedEnvironment is functional but has some warningson a healthy control run — and held only because of the phantomNot builtwarning this PR removes. Their subject is the broken leg; the control's actual claim is that doctor reached its summary and did not call the environment broken. They now assert that, with a matcher accepting either non-error summary, which the broken leg still cannot produce.Gates
Run against
e34b763a0b, the final commit on this branch, with the workspace closure built. Derived withnode scripts/pm/dispatch-gates.mjsfrom the real change set rather than from a recalled list, and quoted by each gate's own verdict line:pnpm --filter @objectstack/cli exec vitest run—Test Files 140 passed (140) · Tests 1561 passed (1561)pnpm --filter @objectstack/cli typecheck—tsc --noEmit, exit 0check-nul-bytes: OK (scanned 6239 text file(s) … no raw ASCII control bytes)check-cross-package-test-inputs—OK: 13 package(s) read outside themselves, all declaredcheck-test-source-alias OK — 72 packages with tests scannedcheck-type-source-resolution OK — 76 packages with a tsconfig.json scannedcheck-type-check-coverage: OK — 64/77 workspace packages type-checked … 13 in the DEBT ledgercheck-engine-double-contract: OK — 371 pinned, 133 in the DEBT ledger, 2 exemptcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 264.3s, 1912 raw tsc error(s) total, none above its recorded number— the ratchet half, run afterturbo run build --filter=./packages/* --filter=./packages/*/*so it measured rather than refusedcheck:where-matcher,check:query-options-erasure,check:slot-lookup,check:objectui-changeset,check:changeset-gate-self-tests,scripts/docs-audit/check-affected-docs.mjs— all exit 0The re-measure prints one standing surplus —
@objectstack/plugin-authrecords 109 and now measures 97 — which predates this branch and belongs to no file in this diff, so it is reported rather than lowered here.Scope
#10680 is deliberately untouched. It edits the same file but is a different defect shape (a wrong prescribed codemod command), and triage's default was serial — this card first. The prescription line it owns is not in this diff.
Out-of-scope finding filed, unassigned: #10798 — three texts on the
cli.doctor-health-reportchecklist item (its EXPECTED-FAIL clause, the row enumeration naming@objectstack/spec, and the step that captures the two lines verbatim) go stale once this merges. Filed rather than folded in:docs/qa/platform-checklist/areas/cli.jsonis the hot shared file of the #10663 lane, a clause edit owes a revision bump plus a history entry, and this repo already treats checklist-text corrections as their own card. Nothing is red today —check:platform-checklistis structural and never judges whether a clause is still true.Generated by Claude Code