Skip to content

feat(analyze): a shorter run that ends on next steps, and a review page that fits the browser - #61

Merged
flipvh merged 7 commits into
mainfrom
claude/pnpm-cella-analyze-format-su7ggp
Oct 25, 2026
Merged

flipvh merged 7 commits into
mainfrom
claude/pnpm-cella-analyze-format-su7ggp

Conversation

@flipvh

@flipvh flipvh commented Oct 25, 2026

Copy link
Copy Markdown
Member

Reworks the terminal output of pnpm cella analyze (and the end of pnpm cella sync) so the run is shorter and ends on what to do next. The banner, the section headers (title with the divider under it) and the analysis summary keep their original design. Along the way it fixes the bugs found while reading the analyze path, and ends with an evaluation of refactoring opportunities.

Measured on raak at ac6106a against cella main at b374861 (6 commits):

before after
analyze output 185 lines, up to 175 columns 92 lines, new lines within 80 columns
review page 164 MB, 50 diffs, the 2 drifted files and 14 of 15 pinned files without a diff 2.7 MB, 22 diffs across every section, 42 snapshot files listed
last lines dim page path, alignments: 0 of 1 handled · 1 arrive with this sync · 1 open: pnpm cella align numbered next steps: review page, pnpm cella sync, the alignment by title

Fixes

  • Review page budget. Rendering now stops at 15,000 patch lines, and a single patch over 2,000 lines is listed instead of rendered. Sections take turns, so every section gets diffs before one fills the page. The 42 drizzle snapshot diffs had produced a 164 MB page.
  • Fork commit links. In the default fileLinkMode: 'commit', rows for protected-in-fork and drifted files linked the fork's commit hashes under the upstream repo, which 404s. They now link to the fork's origin, or not at all.
  • File links. In file mode, file links now point at the analyzed commit instead of the branch tip. Files upstream deleted get no link.
  • Dry-run alignments. Analyze counted the alignments arriving with the sync as open. MergeResult.alignments.open is now a list of ids, and the arriving ones only count as open after the sync.
  • Smaller fixes:
    • The banner was 2 columns wider than its divider.
    • File lists were unsorted.
    • Text said 1 conflicts and 1 files.
    • A conflicted sync printed ✓ sync complete right after ✗ sync completed with conflicts.
    • The alignments line printed twice on a clean first sync run.
    • Generated files showed under protected but behind upstream.
    • A stale review page from an earlier sync could be listed after a commit.

Output

  • Section order. Behind, diverged, protected in fork, drifted, protected but behind upstream, then the summary and next steps.
  • File rows. A row is the path (linked), a count and a short age (3h, 2d, 3mo), in columns sized per section. The ≠ 3 hours ago basename tail and the multi-sentence hints are gone.
  • Collapsed groups:
    • Behind files collapse by folder when there are more than 15.
    • When there are more than 8 pinned files, only those missing 10 or more lines get a row.
    • Five or more protected files of one kind under one override entry become one row (backend/drizzle/ · 42 × snapshot.json 311.8k).
    • --all lists every path. --json and --list are unchanged.
  • Ignored files only upstream changed join protected but behind upstream, tagged new or deleted, with their line counts and dates. This applies on the terminal and on the review page.
  • Engine steps take one line each. The upstream commits are listed newest first, one line each with PR number and age, at most 10.
  • next steps closes analyze and every sync stop: conflicts, commit, ship and up to date. Without a terminal, each alignment row also gets its id.

Refactor

findProtectedBehindUpstream is now one list read by both the terminal and the review page. The step text moved into display.ts (formatCheckoutStep and formatUpstreamStep). There is one splitPrNumber. Output is unchanged: the golden test passes as is, and the raak run prints identical lines.

Validation

  • pnpm check, pnpm test (326 tests, 297 at the start), pnpm test-build and pnpm test:release all pass. This was run locally on Node 22; CI runs 26.
  • New tests/analyze-report.test.ts compares the full analyze file sections of a raak-shaped fixture line by line (the golden test), plus tests for links and --all.
  • New tests/next-steps.test.ts covers the printer and the align step.
  • The e2e tests now pin the analyze ending and the sync conflict and commit stops.
  • Analyze was run against raak itself. Sync endings were checked in the e2e environment only, because a real raak sync runs pnpm install and pnpm check there.

Ledger

# Kind Where What Status
L1 problem sync.ts runSync + commitSyncMerge A clean first sync run printed the alignments line twice: once after the merge (with the arrived count) and again after the commit. fixed: one align step under next steps
L2 note local env Node 22 here, engines asks for 26.x. The suite passes on 22 (297 tests at the start); CI runs 26. note
L3 problem findProtectedBehind Protected but behind upstream listed generated files (sdk/gen/openapi.json, types.gen.ts), while the upstream-only report already leaves generated output out: the fork regenerates them from its own sources. fixed
L4 problem printSummary, analyze sections renamed and deleted files are counted in the summary data but never shown: no summary row, no list. open: the summary keeps its original design by request
L5 problem enrichChangeInfo, analyzeRefs Ignored files only upstream changed had no change date and no line count, so the terminal could not size or date them. fixed
L6 opportunity sync.ts shipSyncBranch, merge-engine.ts The fork's GitHub URL was read from origin inline in the ship path; the analyze links need it too. fixed: readOriginGitHubUrl
L7 note --json upstreamChangedLines is now also set for upstreamOnly files. Same keys, more values. note
L8 problem section headers A one-file section said · 1 files. fixed
L9 problem runAnalyze + main Analyze ended with two blank lines: its own and the one main prints after every service. fixed
L10 problem upstream step A --ref <sha> run printed --ref b374861b9 @ b374861b9. fixed: main @ b374861b9 · --ref
L11 note short shas getShortSha uses git's auto length (9 in raak), commit rows use 7, as before. note
L12 problem formatMergeInProgressDetail, printSyncComplete 1 conflicts to resolve in IDE, 1 conflicts: no singular. fixed
L13 problem printSyncComplete A sync that stopped on conflicts printed ✓ sync complete right after ✗ sync completed with conflicts. fixed: ⚠ sync merge staged with conflicts
L14 problem runSyncCommand The conflict stop opened with two blank lines (the package step's and the cycle's). fixed
L15 problem analyze next steps When upstream only added an alignment (no synced files), there was nothing telling you a sync would record it. The sync step now keys off the upstream commit count (MergeResult.upstreamCommitCount), not file counts. fixed
L16 note sync endings Checked through the e2e environment (conflict stop, commit stop), not a real raak sync: that runs pnpm install and pnpm check in raak. note
L17 problem sync commit stop With nothing to review in its merge, the commit stop fell back to a sync-review.html an earlier sync cycle left. fixed: a sync with nothing to review removes the page; only a resuming rerun looks on disk
L18 note upstream step The old remote configured step printed the upstream URL; the compact upstream line leaves it out to stay within 80 columns. open: say if you want it back, e.g. only when it is not cellajs/cella
L19 opportunity packages, contributions, audit, stats These services still print steps with spinnerSuccess(message, detail) (detail on its own line, then a blank line), so a sync run mixes two step styles. open: outside analyze and sync's own output
L20 opportunity printSyncComplete → printProtectedConflicts Sync's list of protected files it kept still uses the old prose rows and the 150-character hint. It could reuse the analyze rows. open: sync's lists were out of scope
L21 note --json --scope protected JSON still includes generated and managed files flagged upstreamChanged; the terminal leaves them out. JSON is raw flags, so consumers can filter. note
L22 follow-up cella repo cella/alignments/README.md (line 40) quotes the old alignments: … · 3 open line as what every sync ends with. cella/skills/cella-sync/SKILL.md quotes "protected but behind upstream", which keeps its title. open: a docs PR in cella after this release

Refactor evaluation (after the implementation)

Act only where one behavior had to change in more than one place, or where two copies already disagree.

# Candidate Measured Decision
R1 One review model The terminal section and the review page both joined findProtectedBehind with ignoredUpstreamChanges (two copies of one membership rule). --json keeps its own scope filter. done, light: findProtectedBehindUpstream serves both. A full model object is not worth it yet.
R2 Split display.ts 541 lines, down from 591: the analyze sections moved to services/analyze-report.ts. not needed
R3 One table of section names Titles repeat between terminal and page, but none disagree. no action
R4 Printers return lines The golden test needed only the existing captureInfo helper; formatRows already returns lines. no action
R5 Structured steps The engine styled the checkout and upstream text while display.ts laid out the commit rows. done: formatCheckoutStep, formatUpstreamStep in display.ts, now tested
R6 One alignments reader Four readers: the engine's range read, readOpenAlignments, alignmentsStep, readPrBodyAlignments. Each reads a different ref at a different moment; what they share is one readAlignment call. no action; revisit if a fifth appears
R7 PR number parsing Two copies of the (#NNNN) pattern. done: splitPrNumber in utils/git.ts
R8 Limits in one place Each limit is a named constant next to its only user. no action

Generated by Claude Code

…ns take turns

A raak analyze wrote a 164 MB page: 42 drizzle snapshot diffs (287k patch
lines) took 48 of the 50 diff slots, so the drifted files and 14 of 15
pinned files with absent upstream lines got no diff at all. The 50-file
cap assumed ~85 KB per diff; a rendered patch line costs ~0.6 KB.

The page now renders at most 15,000 patch lines and lists a single patch
over 2,000 lines instead of rendering it. Sections take one file each per
turn, so every section gets diffs before one fills the page. The terminal
line says how many files are listed without a diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
…nner fits its divider

A dry run counted the alignments arriving with the sync as open and said
`1 open: pnpm cella align`, but `pnpm cella align` lists nothing until the
sync lands. MergeResult.alignments.open is now the list of open ids: in
analyze the fork's pending ones, in sync those plus the arrivals. The line
reads `0 of 1 handled · 1 arrives with this sync`.

The banner padded for `⧈ cella cli v0.4.2` but printed `⧈ cella cli ·
v0.4.2`, two columns past its 60-column divider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
…, --all

The analyze report keeps its banner, section headers and summary, and
changes what sits under the headers:

- Sections go from what syncs on its own to what needs you: behind,
  diverged, protected in fork, drifted, protected but behind upstream.
- A file row is its path, linked, with a count and a short age (3h, 2d,
  3mo) in columns sized per section. The `≠ 3 hours ago basename` tail
  that repeated every file name is gone, and so are the multi-sentence
  hints; one line stays where it names a command.
- Over 15 behind files collapse to one row per folder, with the names
  that fit, added (+) and deleted (−) first. Over 8 pinned files keep
  the ones missing 10 or more upstream lines and count the rest. Five
  or more protected files of one kind under one override entry become
  one row: `backend/drizzle/ · 42 × snapshot.json  311.8k`.
- Protected but behind upstream takes in ignored files only upstream
  changed (tagged new or deleted, with their line count and date), on
  the terminal and on the review page. Generated files are left out.
- `--all` lists every path.
- Engine steps take one line each: `✓ checkout`, `✓ upstream` with the
  newest commits under it (one line each, PR number, age, at most 10),
  `✓ analyzed`.

Links: fork-side rows (protected in fork, drifted) link fork commits to
the fork's GitHub repo instead of 404ing under upstream's, file links
point at the analyzed commit instead of the branch tip, and files
upstream deleted get no link. Lists are sorted by path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
… command and the alignments

The last lines of a run were the ones you act on, and they printed dim and
unlabeled: the review page path, and `alignments: 0 of 1 handled · 1
arrive with this sync · 1 open: pnpm cella align`. Both commands now close
with a `next steps` section, under the same header and divider as every
other section, numbered in the order you take them:

  next steps
  ────────────────────────────────────────────────────────────

    1 review   file:///…/.git/cella-diff/analyze-review.html
               22 diffs in 3 sections · 42 listed without a diff
    2 sync     pnpm cella sync
               commits on a fresh branch, a rerun opens the PR · 36 update, 2 merge
    3 align    1 alignment arrives with this sync
               ○ Migration notes become alignments · manual · not sync-breaking
               after the sync: pnpm cella align

The review URL prints in full, so a terminal without OSC 8 links still
makes it clickable. Alignments are named with their kind and reach,
linked to their README, at most five; without a terminal each row also
gets its id for `align --show`. The sync step is there whenever upstream
has new commits, even when only an alignment changed.

Sync ends the same way at each stop: conflicts (resolve, finish, review,
align), the commit (review, triage, align, ship) and the shipped PR
(merge, align). The alignments line no longer prints twice on a clean
first run. Also: `1 conflict` in the singular, and a conflicted merge no
longer prints `✓ sync complete` right after `✗ sync completed with
conflicts`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
…nc left

The commit stop lists the review page of the merge it commits. When the
merge had nothing to review, it fell back to whatever sync-review.html was
on disk, which could be a page from an earlier sync cycle. A sync run with
nothing to review now removes that page, and only a rerun that resumes a
merge looks for the page on disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
…splay, one PR number parser

The post-implementation review of the output rework found three places
where one behavior lived in two:

- The terminal section and the review page each joined the protected
  files both sides changed with the ignored files only upstream changed.
  `findProtectedBehindUpstream` is that list, read by both.
- The engine styled the `checkout` and `upstream` step text while
  display.ts laid out the commit rows under it. `formatCheckoutStep` and
  `formatUpstreamStep` now live in display.ts, with tests.
- alignments.ts and display.ts each parsed the `(#1519)` a squash merge
  adds to a subject. `splitPrNumber` in utils/git.ts serves both.

Output is unchanged: the analyze golden test passes as is, and a raak run
prints the same lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WC3pgMiDH9vv2KNbQwxKfx
@flipvh
flipvh merged commit 689fcd2 into main Oct 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants