Skip to content

[finding][devx] cli.doctor-deprecation-scan calls os migrate meta "the codemod that actually exists" — it is a chain replayer that prints edits and writes no source #10418

Description

@os-zhuang

Found while rewriting the sibling item cli.migrate-meta-codemod for #9733 (PR #10412). Filed unassigned; deliberately not fixed in that PR — see the scope note below.

The finding

docs/qa/platform-checklist/areas/cli.json, item cli.doctor-deprecation-scan (added 2026-08-20, revision 1, P2), describes os migrate meta twice as a codemod:

  • in an acceptance clause: "The real metadata codemod is os migrate meta (cli.migrate-meta-codemod)"
  • in source: "sibling items … and cli.migrate-meta-codemod (the codemod that actually exists)"

os migrate meta is not a codemod. It declares exactly from · to · step · out · stored · database-url · apply · yes · force · type · json — no --write/--fix/in-place flag — and on the authored-source arm its only file write is the --out JSON snapshot (both writeFileSync calls are if (flags.out)-guarded). packages/cli/src/commands/migrate/meta.ts states it in its own header:

The command does not silently rewrite TS config source (that AST rewrite is unsafe and lossy); --out writes the canonicalized stack as a JSON snapshot

The in-place AST codemod is commissioned as #9591 for v18 and has not been built.

Why this is worth a card rather than a typo fix

The load-bearing assertion of that item is unaffected and correct: os doctor's remediation hint prescribes objectstack codemod v2-to-v3, which is registered nowhere, and the item records that as an expected-fail. The contrast it draws — unlike that command, os migrate meta is real — is also true. Only the word "codemod" is wrong.

It matters because PR #10412 rewrites the sibling item's title to say os migrate meta "rewrites no source file". Once that lands, the same file asserts both things, and a reader hitting the cli.doctor-deprecation-scan clause first gets the same wrong idea the #9733 card exists to remove — that there is a working metadata codemod to be pointed at.

Why #10412 did not fix it in passing

It fails the bounded in-place exemption on two of the four conditions:

  1. Not the same defect class. The QA checklist item cli.migrate-meta-codemod is active P1 for a capability that does not exist — it asserts os migrate meta rewrites authored sources #9733 residue is an active P1 whose title and acceptance clauses assert a capability that does not exist. This is a loose descriptor in another item's parenthetical, where the surrounding assertion is true.
  2. The correct form is not mechanically pinned. Replacing "codemod" means choosing how that item wants to characterise os migrate meta — a framing call belonging to that item's author, not a substitution fixed by evidence.

It also needs its own revision bump and history entry (README lifecycle), in an item one day old.

Suggested dispositions (triage's call)

  • A — reword both descriptors to what the command is (e.g. "the metadata migration command that does exist, os migrate meta — which lists mechanical edits rather than applying them"), bump cli.doctor-deprecation-scan to revision 2 with a history entry. Keeps the contrast the clause is drawing while dropping the false noun.
  • B — leave it. The item's own verdict does not depend on the word, and the sibling item now states the contract precisely. Costs nothing, and leaves the contradiction inside one file for the next reader to re-derive.

Refs: docs/qa/platform-checklist/areas/cli.json (cli.doctor-deprecation-scan) · packages/cli/src/commands/migrate/meta.ts · #9591 (the v18 codemod, pm:on-hold) · #9733 / #10412 (where this surfaced).


Generated by Claude Code

Activity

  1. added theissue type on Aug 21, 2026
  2. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    ContributorAuthor

    First-touch grading (triage seat): promoted to pm:queue, Task, disposition A — reword the two descriptors to what os migrate meta is (lists mechanical edits, writes no source), bump cli.doctor-deprecation-scan to revision 2 with a history entry. B (leave a contradiction inside one file the runner reads) fails the anti-confusion purpose the checklist exists for, at the cost of a two-line edit. Premise gate: land after PR #10412 so the wording aligns with the sibling item's final text. Co-location note: #10416 edits the same checklist area — fold or serialize at dispatch. Size/model suggestion: S, sonnet.


    Generated by Claude Code

  3. claude commented on Aug 21, 2026

    @claude
    Contributor
    {
      "issue": 10418,
      "status": "done",
      "branch": "claude/issue-10418-migrate-meta-not-a-codemod",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10829",
      "premise_still_valid": true,
      "summary": "Corrected the two descriptors in docs/qa/platform-checklist/areas/cli.json item cli.doctor-deprecation-scan that called `os migrate meta` a codemod, taking triage's disposition A. The acceptance clause now reads \"The remediation path that DOES exist is `os migrate meta` ... but it is not a codemod: it replays the migration chain and PRINTS the mechanical edits for the author to apply by hand\", citing the flag surface, the two if (flags.out)-guarded writes and the 'unsafe and lossy' header, and naming #9591 (v18, pm:on-hold) as the commissioned-but-unbuilt in-place codemod; the source descriptor now reads \"cli.migrate-meta-codemod (`os migrate meta` -- the remediation path that does exist; its revision 3 asserts the command LISTS mechanical edits and rewrites no source file. The id keeps its `-codemod` spelling only because ids are immutable ... it reads as a forward reference to #9591)\", and #9591 was added to `source` so that reference resolves. revision 1 -> 2 with a history entry per the directory README's change lifecycle. The item's load-bearing assertion is untouched and still an expected-fail (doctor.ts:2149 prescribes `objectstack codemod v2-to-v3`, registered nowhere); the clause additionally now warns that a fix which merely repoints the hint at `os migrate meta` must promise a LIST, not an auto-fix. All four premise facts re-derived at base f4e5d916d6 and all four HELD -- nothing had moved. cli.migrate-meta-codemod was read and left alone (its revision-3 text is already correct); meta.ts untouched. The claim has NOT spread under the word 'codemod' -- but the idea has, into four docs-site pages, filed as #10831.",
      "tests": "All at final commit 862b32b7b8 (git rev-parse --short HEAD), every exit code captured BEFORE any pipe (cmd > file 2>&1; ec=$?), and each verdict quoted from the gate's own output line, never from $?.\n\n`pnpm check:platform-checklist` is TWO halves and was run as two separate commands, before AND after the edit:\n  BEFORE: node scripts/checklist-select.mjs --self-test = 0 | node scripts/check-platform-checklist.mjs = 0\n  AFTER : node scripts/checklist-select.mjs --self-test = 0 -> '\\u2713 checklist-select self-test: 17 cases pass.'\n          node scripts/check-platform-checklist.mjs      = 0 -> 'check-platform-checklist: OK -- 15 areas, 205 items (205 active); coverage: 30 kinds mapped, 0 waived; traps: 19 documented, 19 in use (extractor control: 22 assertions).'\n  The before-run matters: it proves the revision/history check was green on both sides, so the AFTER green is about my edit rather than a pre-existing red I happened to leave alone.\n\n\\u26a0\\ufe0f This gate is NOT wired into CI, by maintainer decision recorded at .github/workflows/lint.yml:954-960 and restated in docs/qa/platform-checklist/README.md ('Operating cadence -- when to run this (it is NOT in CI)'). The local green above is the ONLY signal this change will ever get. No CI job re-runs it on PR #10829, so a green Lint & Repo Gates on that PR says nothing whatsoever about the checklist.\n\nnode scripts/pm/dispatch-gates.mjs (no paths passed -- it derived the change set itself: '1 path(s) vs merge base f4e5d916d of origin/main and HEAD; committed 1, working tree 0, untracked 0'), exit 0, named exactly two families:\n  pnpm check:doc-authoring                                     = 0 -> '\\u2713 doc authoring guard: 389 files clean -- no bare metadata literals.'\n  pnpm --filter @objectstack/lint run check:doc-formula-expressions = 0 -> '\\u2713 check:doc-formula-expressions: 22 record-scoped formula example(s) across 416 files / 1443 TS blocks judged clean by @objectstack/formula.' + '\\u2713 self-test: 30 cases passed'\n  plus pnpm check:nul-bytes (standing, any edit)               = 0 -> 'check-nul-bytes: OK (scanned 6252 text file(s) -- 6252 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'\n  and a targeted self-scan of the edited file: grep -naP '[\\\\x00-\\\\x08\\\\x0b\\\\x0c\\\\x0e-\\\\x1f\\\\x7f]' exit 1 (no match).\n  The script echoed the script name in every run, so no zero-match silent green.\n\n\\u26a0\\ufe0f check:doc-formula-expressions FAILED twice first, and both failures were environmental, not mine: ERR_MODULE_NOT_FOUND 'typescript' (no pnpm install in the fresh worktree), then ERR_MODULE_NOT_FOUND @objectstack/formula/dist/index.mjs (dependency closure not built). Fixed by pnpm install then pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build, both through scripts/pm/os-verify-lock.sh -c (VERDICT command-exit 0, held 7s / 143s, waited 0s / 143s). Recording it because an unbuilt closure reads exactly like a real red.\n\nEdit landed on disk, verified by anchored counts rather than by the editor's exit code: the two removed strings now occur exactly once each and BOTH occurrences are inside the new revision-2 history entry (proved by loading the JSON and testing each field -- 'FOUND in field: history' for both, and in no other field); the four injected anchors each occur exactly once; git diff --stat = 1 file changed, 6 insertions(+), 4 deletions(-); the file re-parses as JSON with revision 2, history revisions [1, 2], 6 source entries.\n\nNO ABLATION APPLIES. This is a prose correction inside a JSON ledger -- there is no guard under test whose deletion would flip a check red, so there is nothing to mutate and nothing to rebuild. Stating that rather than filling the slot with a ceremonial run.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #10831 (domain:devx, unassigned, no pm:queue): four docs-site pages attribute an in-place source rewrite to `os migrate meta --from` -- flows.mdx:245 and widget-contract.mdx:316 (which also name the mutually-exclusive --from/--stored pair, meta.ts:195-198 `exclusive: ['stored']`, and say 'automatically'), query-syntax.mdx:860 and queries.mdx:388 -- while upgrading.mdx:145 states the opposite in bold and every generated reference table already carries #9529's corrected sentence. Includes meta.ts:81's own docblock ('this command rewrites an author's source') contradicting its header 74 lines below, and a scope warning that v17.mdx:2475 ('--stored rewrites sys_metadata rows in place') is CORRECT and must survive any search-and-replace."
      ]
    }

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions