Skip to content

fix(cli): doctor prescribes no codemod command that does not exist - #10882

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-10680-doctor-codemod-hint
Aug 21, 2026
Merged

fix(cli): doctor prescribes no codemod command that does not exist#10882
os-elon merged 2 commits into
mainfrom
claude/issue-10680-doctor-codemod-hint

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10680

The defect

After listing its hits, os doctor --scan-deprecations printed:

ℹ Found N deprecated pattern(s). Run `objectstack codemod v2-to-v3` to auto-fix.

No codemod command has ever been registered. oclif resolves this CLI by globbing dist/commands/**/*.js (package.jsonoclif.commands, pattern strategy); nothing under src/commands/ compiles to codemod, and neither bundled plugin (@oclif/plugin-help, @oclif/plugin-plugins) supplies one. Probed against the built binary on this branch:

$ node packages/cli/bin/run.js codemod v2-to-v3
› Error: command codemod:v2-to-v3 not found # exit 2
$ node packages/cli/bin/run.js codemod --help
› Error: Command codemod not found. # exit 2

content/docs/protocol/backward-compatibility.mdx:134 already recorded the automated codemod as "not yet available". The tool was the last place still claiming otherwise — and a wrong prescription costs the operator their time before they can find out, which is the same false-confidence class as the two defects this lane landed today (os datasource validate passing over drift it never read; os doctor printing over a tree it never walked, #10679).

Why the hint was removed, not repointed

The obvious repair — point it at os migrate meta — is deliberately not what landed. That would be the same defect respelled:

candidateregistered?does a v2→v3 deprecation sweep?
os codemod v2-to-v3no — exit 2, both spellings
os migrate metayes (migrate/meta.ts, --help exit 0)no. Subject is an authored stack config replayed across protocol majors. Its own header declines this rewrite: "does not silently rewrite TS config source (that AST rewrite is unsafe and lossy)". Both writes are --out JSON snapshots (:347, :394), and --from is required, so a bare invocation fails too
os lint --fixyesno — print-only by declaration: "Show what would be fixed (dry-run)"
node scripts/codemod/*.mjsexists, but not an os commandno — the two scripts there are apimethods-legacy-to-primitives and view-to-viewitem, neither covering these patterns

os migrate meta also cannot reach the subject: the scan reports on src/** TypeScript, and three of the eight DEPRECATED_PATTERNS (EnhancedObjectKernel and the two deep-import paths) are not metadata at all. The in-place AST codemod is commissioned as #9591 (v18, pm:on-hold) and unbuilt.

Nothing registered does the job, so the line says so and points at the remedy that really exists — the replacement already computed for each finding, printed under --verbose:

ℹ Found 3 deprecated pattern(s). No automated codemod ships with the CLI — apply each
finding's replacement by hand (re-run with --verbose to print them).
# with --verbose, where the replacements are already on screen:
ℹ Found 3 deprecated pattern(s). No automated codemod ships with the CLI — apply the
→ replacement shown under each finding above.

The pin closes the class, not the instance

doctor-deprecation-hint-commands.test.ts asserts that everyos … / objectstack … hint doctor can print resolves to a command file under src/commands/ — applied both to the file's printable source and to the whole rendered report. That hint was the only unresolvable one of the six distinct hints in this 2,290-line file; a future phantom prescription fails here rather than shipping.

The sweep reads printable source: whole comment lines are dropped (the print site documents the dead prescription verbatim, on purpose), never a trailing //http://localhost:3000 lives inside these strings and a greedier strip would silently delete real hints and leave the sweep passing over less than it claims. The haystack size is asserted for that reason.

The scanner itself is untouched, and its passing clauses are now pinned here so this edit cannot be what regresses them: file:line attribution, the → replacement detail under --verbose, and warns-not-gates measured as a delta against a clean src/ in the same cwd.

⛔ Nothing in #10679's landed work is touched — MonorepoTreeScan, the scoping, and the Not built gate are all as they were, and its test file is re-run green alongside this one.

Verification, at af5c564352

Ablation (subject resolves from source: the test imports ./doctor.js relative within the same package, so vitest reads doctor.ts — no dist rebuild is involved, which the red leg proves rather than assumes):

legstateresult
1fixedTest Files 2 passed (2) · Tests 26 passed (26) (with #10679's file)
2defect restored in source, no rebuildTests 5 failed | 7 passed (12) — incl. 3 end-to-end cases
3restored from the commit, byte-identical to HEADTests 12 passed (12)

The 7 that stayed green in leg 2 are the probe self-tests and the passing-clause pins — correct: the defect did not touch them.

Package: pnpm --filter @objectstack/cli typecheckTYPECHECK_EXIT=0; the full @objectstack/cli suite → Test Files 143 passed, Tests 1596 passed (the 2 failures in that run were an over-broad assertion in the new file, fixed in af5c564352).

Gate union, re-derived from the real changeset with node scripts/pm/dispatch-gates.mjs (no paths) at this head — 13 path-matched + 5 convention-triggered (new test file) + check:nul-bytes, all green:

check:changeset-gate-self-tests · check:cross-package-test-inputs · check:objectui-changeset
check:slot-lookup · check:test-source-alias · check:type-source-resolution · check:nul-bytes
check-adr-0087-registration · check-changeset-no-major · check-ci-filter-parity
check-cross-package-test-inputs · check-empty-changeset · check-plugin-teardown-shape
docs-audit/check-affected-docs · check:query-options-erasure · check:type-check-coverage
check:type-check-debt · check:engine-double-contract · check:where-matcher

Their own verdict lines, not a bare exit code:

check-nul-bytes: OK (scanned 6284 text file(s) ...; no raw ASCII control bytes).
OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured, none above its recorded number.
check-engine-double-contract: OK — 374 pinned, 133 in the DEBT ledger, 2 exempt.
✓ where-matcher conformance holds: 273 matcher(s) discovered ... none new.
✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).

Out of scope, filed

#10881packages/cli/README.md:119 still documents os codemod v2-to-v3 in a ### Code Transforms table, and :306 shows a codemod/v2-to-v3.ts node in its source-tree listing. Same claim, one surface over; filed unassigned rather than folded in, per this card's docs fence. #10881 is not addressed here.


Generated by Claude Code

…10680)
After `--scan-deprecations` reported its hits, `os doctor` printed
``Run `objectstack codemod v2-to-v3` to auto-fix``. No `codemod` command is
registered: oclif resolves this CLI by globbing `dist/commands/**`, nothing
under `src/commands/` compiles to `codemod`, and neither bundled plugin
supplies one — the prescription exits 2, `command codemod:v2-to-v3 not
found`, only after the operator has spent time on it. The docs already
recorded the automated codemod as "not yet available"; the tool was the last
place still claiming otherwise.
The hint is deliberately NOT repointed at `os migrate meta`. That command's
subject is an authored stack config replayed across protocol majors, and its
own header declines the rewrite this hint promised — "does not silently
rewrite TS config source (that AST rewrite is unsafe and lossy)" — with both
writes guarded behind `--out` JSON snapshots. It cannot touch the `src/**`
TypeScript these patterns are found in, and three of the eight
DEPRECATED_PATTERNS are not metadata at all. `os lint --fix` is print-only by
declaration. Nothing registered does the job, so the line says so and points
at the per-finding replacement it already computes, printed under `--verbose`.
The new pin closes the class rather than the instance: every `os …` /
`objectstack …` hint in `doctor.ts` must resolve to a command file under
`src/commands/`, and the whole rendered report is held to the same rule. The
scanner is untouched — file:line attribution, `→ replacement` detail and the
advisory exit 0 are pinned here so this edit cannot regress them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
The class-closing sweep read the raw file, so the print site's own comment —
which quotes the dead prescription verbatim, on purpose, as the record of why
the line reads as it does — tripped it. The rule is about the report an
operator sees, so the sweep now reads printable source: whole comment lines
dropped, never a trailing `//`, because `http://localhost:3000` lives inside
these strings and a greedier strip would delete real hints and leave the sweep
passing over less than it claims. The haystack size is asserted for the same
reason.
The ban is on the prescription, not the word: the corrected line has to be
able to say there is no codemod.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 1 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/indexing.mdx(via os doctor (command))
  • content/docs/deployment/cli.mdx(via os doctor (command))
  • content/docs/protocol/backward-compatibility.mdx(via os doctor (command))
  • content/docs/protocol/kernel/config-resolution.mdx(via os doctor (command))
  • content/docs/protocol/kernel/http-protocol.mdx(via os doctor (command))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx(via os doctor (command))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 0b4e2b73560589562ab39c80f9d578dfb770f38apackageMentionDocs.

Which tree this was computed on

This run read content/docs from 00a7ffc53fe4e56da8f00abe1ab6a9e811d3a20e — the merge of head af5c56435295b8ce9141ef0f6b281a81fe7bbf12 into base 0b4e2b73560589562ab39c80f9d578dfb770f38a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 00a7ffc53fe4e56da8f00abe1ab6a9e811d3a20e && git checkout 00a7ffc53fe4e56da8f00abe1ab6a9e811d3a20e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0b4e2b73560589562ab39c80f9d578dfb770f38a af5c56435295b8ce9141ef0f6b281a81fe7bbf12 && git checkout -B drift-repro 0b4e2b73560589562ab39c80f9d578dfb770f38a && git merge --no-ff af5c56435295b8ce9141ef0f6b281a81fe7bbf12
node scripts/docs-audit/affected-docs.mjs --json 0b4e2b73560589562ab39c80f9d578dfb770f38a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 0b4e2b73560589562ab39c80f9d578dfb770f38a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32500260335 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.99s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 29 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os doctor --scan-deprecations prescribes a codemod command that does not exist

2 participants

@os-elon@claude