Skip to content

Commit 19563f6

Browse files
committed
feat(cli): os migrate meta prints the semantic entry that judges an applied 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>
1 parent 315888d commit 19563f6

1 file changed

Lines changed: 72 additions & 10 deletions

File tree

  • packages/cli/src/commands/migrate

‎packages/cli/src/commands/migrate/meta.ts‎

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
MIGRATION_MAJORS,
1414
MIGRATION_SUPPORT_FLOOR,
1515
type MigrationChainResult,
16+
type MigrationTodo,
1617
} from '@objectstack/spec/migrations';
1718
import { PROTOCOL_MAJOR, PROTOCOL_VERSION } from '@objectstack/spec/kernel';
1819
import { FILE_REFERENCE_TYPES, REFERENCE_VALUE_TYPES, STRUCTURED_JSON_TYPES } from '@objectstack/spec/data';
@@ -295,13 +296,79 @@ function printSchemaVerdict(report: MigrationReport): void {
295296
console.log('');
296297
}
297298

299+
/**
300+
* The semantic entries that judge each conversion, keyed by conversion id —
301+
* read off the entries' declared `conversionIds` (`SemanticMigration`), in the
302+
* order the chain reports the entries.
303+
*
304+
* ⛔ Declared links only. An entry's prose naming a conversion id is not a
305+
* link: prose also names incidental analogues, so the spec lane authors a link
306+
* only after reading both sides, and this printer pairs nothing it was not
307+
* told to.
308+
*
309+
* The join runs over the whole chain result, not hop by hop: a link may name a
310+
* conversion an EARLIER step replays than the entry's own, and both halves sit
311+
* in the flat `applied` / `todos` arrays whichever hop produced them.
312+
*/
313+
function judgesByConversion(todos: readonly MigrationTodo[]): ReadonlyMap<string, readonly MigrationTodo[]> {
314+
const judges = new Map<string, MigrationTodo[]>();
315+
for (const todo of todos) {
316+
for (const conversionId of todo.conversionIds ?? []) {
317+
const list = judges.get(conversionId);
318+
if (!list) judges.set(conversionId, [todo]);
319+
else if (!list.includes(todo)) list.push(todo);
320+
}
321+
}
322+
return judges;
323+
}
324+
325+
/**
326+
* Group ② — every applied edit, and beside the edits a semantic entry judges,
327+
* that entry's headline marked **review**.
328+
*
329+
* The edit lines are the chain's, one per edit, unchanged. A review line is
330+
* printed once under each RUN of consecutive edits by one conversion — the
331+
* chain replays one conversion at a time, so a conversion's edits arrive
332+
* together — and counts the edits above it that it judges. Once per run rather
333+
* than once per edit: a real upgrade wrote 13 edits by one judged conversion,
334+
* and repeating a paragraph-long judgment 13 times is the noise this report is
335+
* ordered to cut. Nothing is lost by it — every edit line still names its
336+
* conversion, and the review line says how many of the lines above it covers.
337+
*
338+
* The review line is a COPY of the entry's `[protocol N] surface → replacement`
339+
* headline, not the entry: ③ still prints that entry in full, in its place, so
340+
* its `why` and `verify` are found there by the same headline, and ③'s count
341+
* and lines do not change (ADR-0087 D3, "never silence").
342+
*/
343+
function printAppliedEdits(result: MigrationChainResult): void {
344+
const judges = judgesByConversion(result.todos);
345+
console.log(chalk.bold(` Applied ${result.applied.length} mechanical change(s):`));
346+
let run = 0;
347+
for (const [i, a] of result.applied.entries()) {
348+
console.log(` • ${a.path}: ${chalk.red(a.from)} → ${chalk.green(a.to)} ${chalk.dim(`(${a.conversionId})`)}`);
349+
run += 1;
350+
if (result.applied[i + 1]?.conversionId === a.conversionId) continue; // the run goes on
351+
const subject = run === 1 ? 'the edit above' : `the ${run} edits above`;
352+
for (const judge of judges.get(a.conversionId) ?? []) {
353+
console.log(
354+
` ${chalk.yellow('↳ review')} ${subject} against the manual change `
355+
+ `[protocol ${judge.toMajor}] ${judge.surface} → ${judge.replacement}`,
356+
);
357+
}
358+
run = 0;
359+
}
360+
console.log('');
361+
}
362+
298363
/**
299364
* The human report of an authored-source run, in the order an upgrader acts on
300365
* it, each group under one header line that counts it (ADR-0087 D3):
301366
*
302367
* ① the VERDICT and the REFUSALS — whether the migrated stack parses and, when
303368
* it does not, every refusal left after the chain: what still blocks it;
304-
* ② the APPLIED mechanical edits — the diff the chain has already made;
369+
* ② the APPLIED mechanical edits — the diff the chain has already made, with
370+
* the semantic entry that judges an edit printed beside it, marked review
371+
* (see {@link printAppliedEdits});
305372
* ③ the SEMANTIC notices — every semantic entry of every hop crossed.
306373
*
307374
* ## Why this order
@@ -320,7 +387,8 @@ function printSchemaVerdict(report: MigrationReport): void {
320387
* silence": an entry may leave ③ only on a structured, stack-derived proof that
321388
* its surface is absent, and matching the prose of `surface` against the stack
322389
* is not one. So the groups MOVE and nothing else does: every line ② and ③
323-
* printed before is printed after, byte-identical and in chain order.
390+
* printed before is printed after, byte-identical and in chain order. The one
391+
* addition is ②'s review lines, each a copy of an entry ③ still prints.
324392
* `--json` is untouched — its keys, its values and the order of its arrays.
325393
*/
326394
export function printMigrationReport(report: MigrationReport): void {
@@ -352,14 +420,8 @@ export function printMigrationReport(report: MigrationReport): void {
352420
return;
353421
}
354422

355-
// ② The mechanical rewrites (auto-applied).
356-
if (result.applied.length > 0) {
357-
console.log(chalk.bold(` Applied ${result.applied.length} mechanical change(s):`));
358-
for (const a of result.applied) {
359-
console.log(` • ${a.path}: ${chalk.red(a.from)} → ${chalk.green(a.to)} ${chalk.dim(`(${a.conversionId})`)}`);
360-
}
361-
console.log('');
362-
}
423+
// ② The mechanical rewrites (auto-applied), a judged edit's judge beside it.
424+
if (result.applied.length > 0) printAppliedEdits(result);
363425

364426
// Per-hop checkpoints.
365427
if (report.step) {

0 commit comments

Comments
 (0)