fix(cli): os migrate meta prints the manual change that judges an applied edit beside that edit, marked review - #21025
Conversation
…pplied edit beside it, marked review Group 2 of the human report now prints, under each run of applied edits by one conversion, a review line copying the headline of every semantic entry that names that conversion in its declared conversionIds. Pairing reads the declared links only, never the entries' prose. Group 3 keeps every entry, byte-identical, and its header count is unchanged; --json, the exit codes, --out and the loader's stderr are untouched. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
Adds the PAIR pins beside the report-order pins: the judging entry prints once under the run of edits it judges, counting them; an unjudged edit prints as before; group 3 keeps every entry with the chain's count and bytes; every link the registry declares is paired over its conversion's own fixture; and a link to an earlier step's conversion pairs across hops. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…grate-meta-pairing
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 25 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 50b14fddeee6193df970c32f733afbb5216b7fd5 && git checkout 50b14fddeee6193df970c32f733afbb5216b7fd5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2f2fa11d756f665a4c06160480c1dce15b9d67a4 c6622657f2f0d825b919efae6807645fc534fd28 && git checkout -B drift-repro 2f2fa11d756f665a4c06160480c1dce15b9d67a4 && git merge --no-ff c6622657f2f0d825b919efae6807645fc534fd28
node scripts/docs-audit/affected-docs.mjs --json 2f2fa11d756f665a4c06160480c1dce15b9d67a4
|
Contract reviewServed-tier: Inputs read: card #20620 (body and all 9 comments — triage's first grade Check-runs on the head, converged at 2026-10-01T02:37:17Z, newest run per name: 34 names, 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke opt-in), 0 failed, 0 pending. All seven required contexts are Merge state: Binding direction: triage's first grade, direction 2 — print the semantic sibling that judges an applied conversion beside the applied edit, marked review — under direction 1's ban (no notice dropped, never by prose-matching ① Derived judgmentsAccept-set: no change. File list is (a) The pairing,
(b) "⛔ No notice is dropped." The ③ loop ( (c) Unchanged surfaces, in source at the head. (d) The pins. Eight PAIR pins beside the eight existing ones, in the printer's one test file (the dev's deviation 3 — right, every pin on this printer stays in one file and reuses (e) The changeset Wrong: none found. ② Semver level
③ Boundary flagsDeviations (5), all declared, all ANSWERED:
Semantic notice, mine, for the lane: a cross-hop link (allowed by the spec test) pairs only on a run that crosses both hops; on a one-hop run the entry prints in ③ with no review line. By the data model, not this PR; no such link exists today. Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20620
Clause-②: no
Direction 2 of triage's first grade on #20620 (comment
5888087153): 「Where an applied conversion has a semantic sibling that judges it, print the sibling beside the applied edit, marked review.」 It pairs through the declaredSemanticMigration.conversionIdsonly, never through text mentions (release5916560364). Direction 1 (order) landed in PR #20691. Direction 3 (write-back) is not taken by triage, so this PR closes the card.What changes
printMigrationReport(packages/cli/src/commands/migrate/meta.ts), the human face ofos migrate meta --from N, now prints group 2 throughprintAppliedEdits. Under each run of applied edits by one conversion, it prints one review line for every semantic entry that names that conversion inconversionIds:(The review line is cut at
[…]here. It is printed in full.)[protocol N] surface → replacementheadline, which is the line group 3 prints for that entry. Group 3 still prints every entry, byte for byte, under the sameN manual change(s) require your judgment:count, so ADR-0087 D3's "never silence" holds. Thewhyandverifylines are found in group 3 under the same headline.judgesByConversionreadstodo.conversionIds, which the chain copies from the entry. Nothing matches prose.--json,--out, the exit code and the loader's stderr are untouched.--storeddoes not use this printer.The dispatch's mechanism assumptions, measured
applyMetaMigrationsbuilds each todo as{ ...s, toMajor }(packages/spec/src/migrations/chain.ts), so every todo carriesconversionIds. Onmainthe registry declares exactly two links, and both point into the same hop:flow-decision-edge-branching-first-match(18) →flow-decision-mode-inclusive-explicit(18), andtime-default-zone-refused(18) →time-default-utc-suffix-dropped(18). No authored link crosses a hop yet. The join runs over the flatapplied/todosarrays, so a cross-hop link pairs by construction. A pin proves it by adding a link from a protocol-18 entry to the protocol-17 conversionaction-execute-to-targeton a real chain result. Across steps, the 108 step conversion ids are unique (0 duplicates).todos.applyemits every edit before the next conversion starts. So a run is "this conversion's edits in this hop". HotCRM had 13 edits byflow-decision-mode-inclusive-explicit. One review line per edit would repeat a paragraph-longreplacement13 times, which is the noise this card is about. No information is lost: every edit line still names itsconversionId, and the review line counts the edits above it ("the 2 edits above", or "the edit above" for a run of one). If a conversion's edits were ever split into two runs, each run would get its own review line, so a review line never sits far from its edits.--storeddoes not useprintMigrationReport.runStoredprintsformatStoredMigrationReportfrom@objectstack/metadata-protocol, so this PR does not touch it.Evidence: real stacks through
bin/run.jsTwo small authored stacks (
export default { … }configs) were run withos migrate meta --from 17, once onorigin/mainat315888d660and once at this PR's head,c6622657f2:mode, plus a dashboardrefreshInterval;timefield defaults,09:00Zand17:30:00+00:00, plus a dashboardrefreshInterval.--outpath normalised)flow-decision-mode-inclusive-expliciteditstime-default-utc-suffix-droppededits--jsonapart fromduration--outsnapshot--json)Before this change, the decision edits printed on lines 13 and 14, and the entry that judges them printed at line 351.
Tests
The pins extend
packages/cli/src/commands/migrate/meta.report-order.test.tsrather than a new file. That file already holds the printer's helpers (run,appliedLines,noticeLines,refusalLines) and the SET pins the pairing must not break, so the pairing pins reuse them, and every pin on this printer stays in one file. The newPAIRblock adds:fixture.before, so a link the spec lane adds later is covered when it lands. Anti-vacuity: the two links named in the dispatch are asserted present;Measured at
c6622657f2:pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 src/commands/migrate/meta.report-order.test.ts: 16 passed (8 existing, 8 new).doctor-deprecation-hint-commands,meta.stored-flags,migrate-meta-strict-factories, and this file): 4 files, 41 passed.test/migrate-meta-default-range.test.ts,test/migrate-meta-engine-guidance.test.tsandtest/migrate-meta.e2e.test.tsare integration tier, so they are declared to CI. Their only group 2 assertions aretoContainchecks, which an added line cannot break.pnpm --filter @objectstack/cli typecheck: exit 0. The test file is in thetsc --noEmitprogram (--listFiles, 1 hit).Ablation. The fix was committed first (
19563f6c4e).node scripts/ablation-replace.mjsreplacedconst judges = judgesByConversion(result.todos);with an empty map (anchor x1 → x0, blob868eef7e8983→542c67cdfca2). The subject is imported relatively from source, so no build was involved. Result: 5 red (pins 1, 2, 6, 7 and 8) and 11 green. The 11 are the 8 order, count and set pins and the 3 control pins: unjudged edit, no-link stack, and group 3 intact. The restore was proven by blob868eef7e8983equal to HEAD and an emptygit diff HEAD.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, with no paths, atc6622657f2(merge base9b0de7de73), derived 62 commands. Each was run and its exit code recorded before any pipe. All 62 exited 0.--ran:62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. On the first round,check:dual-build-cjs-loadsandcheck:i18n-coverageanswered PREREQUISITE NOT MET (exit 3, no dist). They were re-run afterpnpm turbo run build --filter=!@objectstack/docs, and both are green. At reconciliation time,origin/mainwas already 3 commits ahead, and one family file (scripts/check-platform-checklist.mjs) had changed there. That family readsdocs/qa/platform-checklist/, which this diff does not touch.Lint, as a proven narrowing.
pnpm lintis a repo-wide scan owned by CI. Run atc6622657f2:isPathIgnoredandcalculateConfigForFileagainsteslint.config.mjs, inline config off).meta.tsis LINTED with 6 rules andmeta.report-order.test.tsis LINTED with 5. The changeset is in no config's population.--format jsonover those 2 files: 2 results, 0 errors, 0 warnings.eslint.config.mjsenables no type-aware linting: none of its 7parserOptionsblocks setsproject. Its only disk reads at load are two baseline JSON files this diff does not touch. So no untouched file's verdict can move.Acceptance notes
skills/objectstack-upgrade/SKILL.md:139-147describesApplied N mechanical change(s)as "one line per rewritten site". With this PR, a judged run also carries a review line. The surface is Tier H governed, and a one-line mention can ride with the group reorder that the previous ACCEPT on this card already left for that surface's next queue entry.printMigrationReportdocblock cites "242 notices for protocol 18" as the measurement behind direction 1. Today's chain prints 253. It is a dated measurement, not a claim this PR depends on, so it is left as written.docs/qa/platform-checklist/areas/cli.jsonitemcli.migrate-meta-codemodlocates planted spellings in theAppliedblock by their edit line. The added review lines do not change that.Generated by Claude Code