Skip to content

feat: layout spec v1 and migration framework (M1.3)#9

Merged
flyingrobots merged 8 commits intomainfrom
m1.3-migration-spec
Feb 14, 2026
Merged

feat: layout spec v1 and migration framework (M1.3)#9
flyingrobots merged 8 commits intomainfrom
m1.3-migration-spec

Conversation

@flyingrobots
Copy link
Owner

Summary

  • Layout Specification v1 (docs/LAYOUT_SPEC.md) — formalizes ref namespace, state derivation rules, commit format, config keys, and migration policy
  • Migration framework (src/lib/LayoutMigration.js) — readLayoutVersion, writeLayoutVersion, pendingMigrations, migrate with forward-only, idempotent semantics stored in cms.layout.version git config
  • CLI commandsgit-cms migrate (run pending migrations) and git-cms layout-version (print repo + codebase versions)

Test plan

  • 12 unit tests via InMemoryGraphAdapter (read/write/pending/migrate, edge cases for invalid/negative/fractional versions, idempotency, too-new guard, content preservation)
  • 2 E2E tests against real git repos (version stamp readable via git config --get, articles survive migration)
  • All 71 existing + new tests pass (npx vitest run)
  • No regressions in existing E2E tests

Formalize the repo layout as v1, add a forward-only idempotent migration
framework stored in git config, and wire up migrate/layout-version CLI
commands.
@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

Warning

Rate limit exceeded

@flyingrobots has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds a layout migration subsystem: new LayoutMigration module managing layout versions in git config, CLI commands to run/inspect migrations, a formal layout specification, package bumped to 1.1.2, and unit + end-to-end tests for migration flows.

Changes

Cohort / File(s) Summary
Changelog & Version
CHANGELOG.md, package.json
Bumped package version to 1.1.2; added changelog entries for 1.1.0/1.1.1/1.1.2 and expanded Unreleased notes.
Layout Spec
docs/LAYOUT_SPEC.md
New v1 layout specification describing ref namespaces, slug canonicalization, commit trailer codec, state derivation & transitions, invariants, config keys, and migration policy (v0→v1 and forward-compat).
Migration Core
src/lib/LayoutMigration.js
New pure module exposing CURRENT_LAYOUT_VERSION, LAYOUT_VERSION_KEY, and functions readLayoutVersion(graph), writeLayoutVersion(graph, version), pendingMigrations(currentVersion), migrate({graph, refPrefix}); validates versions, orders/applies migrations, persists version, and surfaces errors for invalid or too-new repo versions.
CLI
bin/git-cms.js
Adds migrate and layout-version commands; imports migration API; prints migration summaries or layout-version info; updates usage text.
Tests
test/layout-migration.test.js, test/git-e2e-layout-migration.test.js
Unit tests for read/write validation, pendingMigrations, migrate workflows (v0→v1, idempotence, error on newer repo); E2E test verifies git config stamping and content preservation across migration.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Invocation
    participant LM as LayoutMigration
    participant Config as Git Config
    participant Graph as CmsGraph
    participant Mig as Migration Function

    CLI->>LM: migrate({ graph, refPrefix })
    LM->>Config: readLayoutVersion(graph)
    Config-->>LM: currentVersion
    LM->>LM: pendingMigrations(currentVersion)
    alt pending migrations exist
        LM->>Mig: run migration(target, { graph, refPrefix })
        Mig->>Graph: read/process refs & commits
        Graph-->>Mig: operation results
        Mig-->>LM: migration complete
        LM->>Config: writeLayoutVersion(graph, target)
        Config-->>LM: written
    end
    LM-->>CLI: { from, to, applied }
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🔧 Versions march, a tidy line,
Config stamped where changes shine,
Migrations run — no double-take,
Old articles survive the wake,
Repo grows, precise design.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the two main additions: Layout Specification v1 and a migration framework, with the milestone reference M1.3 providing context.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of the three main components (Layout Specification, Migration framework, CLI commands) and comprehensive test coverage details.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch m1.3-migration-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@CHANGELOG.md`:
- Line 5: The changelog entry heading "## [1.1.0] — Migration & Repo Layout Spec
(M1.3)" should follow Keep a Changelog format: replace the subtitle "(M1.3)"
with an actual release date (YYYY-MM-DD) so the heading reads "## [1.1.0] —
YYYY-MM-DD", and add a corresponding link reference for "[1.1.0]" at the bottom
of the file similar to the existing "[Unreleased]" link reference (ensure the
reference URL/path matches repository/release tagging style used elsewhere).

In `@src/lib/LayoutMigration.js`:
- Around line 53-55: MIGRATIONS is currently a mutable array; freeze it and its
inner entries to prevent accidental mutation by applying Object.freeze to each
migration tuple (each [version, migrationFn] entry) and then
Object.freeze(MIGRATIONS) itself so both the outer array and its contained
arrays/functions are immutable; locate the MIGRATIONS constant and wrap its
entries with Object.freeze(...) for each tuple and finally freeze MIGRATIONS.
- Around line 23-34: The readLayoutVersion function currently treats an empty
string from graph.configGet as valid because Number('') === 0; modify
readLayoutVersion to explicitly reject empty or whitespace-only config values
(e.g. if raw === '' || raw.trim() === '') by throwing the existing
CmsValidationError with the same code/field, before converting to Number; then
continue to parse and validate numeric values as before (use Number(raw) or a
stricter integer-check like /^[0-9]+$/) so only proper non-negative integer
strings are accepted for LAYOUT_VERSION_KEY.
- Around line 43-45: writeLayoutVersion currently writes any value to
LAYOUT_VERSION_KEY via graph.configSet without validation; ensure the exported
function validates the version argument (e.g., in writeLayoutVersion) before
calling graph.configSet: require a finite integer >= 0 (no NaN, Infinity,
floats, or negatives), and if invalid throw or return a rejected Promise with a
clear message. Use the same LAYOUT_VERSION_KEY and graph.configSet call site,
and keep behavior consistent with readLayoutVersion's expectations so callers
cannot store invalid versions that would break subsequent reads.

In `@test/layout-migration.test.js`:
- Around line 29-51: Add a test that covers the empty-string edge case for
readLayoutVersion: after setting graph.configSet(LAYOUT_VERSION_KEY, ''), assert
that calling readLayoutVersion(graph) rejects with a CmsValidationError and code
'layout_version_invalid'. Place this new it(...) alongside the existing
non-integer/negative/fractional tests so the behavior when Number('') === 0 is
locked down and readLayoutVersion/LAYOUT_VERSION_KEY handling is validated.

CI Bot added 3 commits February 14, 2026 02:54
- Reject empty/whitespace-only config values in readLayoutVersion
  (Number('') === 0 was silently accepted as valid)
- Add input validation to writeLayoutVersion (reject NaN, Infinity,
  floats, negatives)
- Freeze MIGRATIONS array and entries for immutability consistency
@flyingrobots
Copy link
Owner Author

@coderabbitai review please 🥕

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

@flyingrobots: Initiating NIGHTMARE MODE review of your layout specification and migration framework. 🔥

I'll scrutinize every line with maximum pedantry: documentation clarity, API design, error handling patterns, test coverage, edge cases, naming consistency, and adherence to your own specification. No stone unturned, no mercy given.

Analyzing all 7 files now... 🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@flyingrobots
Copy link
Owner Author

@coderabbitai review please 🥕

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

@flyingrobots: NIGHTMARE MODE ACTIVATED. 🔥

Deploying maximum pedantry across all 7 files. Layout spec, migration framework, CLI integration, test coverage, error handling, naming conventions, edge cases—everything will be examined with zero tolerance for ambiguity or weakness.

Initiating ruthless review now. 🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/lib/LayoutMigration.js`:
- Around line 98-105: The final readLayoutVersion(graph) call is redundant;
compute "to" from the migration results instead of doing another async I/O.
After the loop that runs pending migrations (for ... of pending and await
migrateFn(...); await writeLayoutVersion(...); applied.push(target);), set to =
applied.length ? applied[applied.length - 1] : from and return { from, to,
applied } without calling readLayoutVersion; remove the extra
readLayoutVersion(graph) invocation.

In `@test/layout-migration.test.js`:
- Around line 53-59: Add a new test in test/layout-migration.test.js mirroring
the empty-string case to cover a whitespace-only string: call
graph.configSet(LAYOUT_VERSION_KEY, '   ') and assert that
readLayoutVersion(graph) rejects with CmsValidationError and code
'layout_version_invalid'; this ensures the raw.trim() === '' guard in
LayoutMigration.js (the readLayoutVersion logic) is exercised for
whitespace-only input.

CI Bot added 2 commits February 14, 2026 06:38
- Compute `to` from applied migrations array instead of re-reading
  config (eliminates unnecessary async I/O round-trip)
- Add whitespace-only string test to lock down trim() guard behavior
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/lib/LayoutMigration.js`:
- Around line 85-106: Add a one-line comment above the migrate function noting a
TOCTOU race: reading layout version via readLayoutVersion and then applying
migrations (pendingMigrations -> migrateFn) followed by writeLayoutVersion is
not protected by a lock, so concurrent migrate() invocations could both run the
same migrations; state that current migrations are idempotent/harmless but
future migrations with side effects must be made idempotent or protected by
external locking (or add locking around readLayoutVersion/writeLayoutVersion) to
avoid double-application.

In `@test/layout-migration.test.js`:
- Around line 70-100: Add an edge-case test for Infinity to the
writeLayoutVersion suite: create an InMemoryGraphAdapter and call
writeLayoutVersion(graph, Infinity) and assert it rejects with a
CmsValidationError having code 'layout_version_invalid' (same shape as the
existing negative/fractional/NaN tests), targeting the writeLayoutVersion
behavior to ensure Infinity is rejected.

CI Bot added 2 commits February 14, 2026 07:34
- Add concurrency caveat to migrate() JSDoc
- Add writeLayoutVersion(Infinity) rejection test
@flyingrobots flyingrobots merged commit ee1fcaa into main Feb 14, 2026
1 check passed
@flyingrobots flyingrobots deleted the m1.3-migration-spec branch February 14, 2026 15:34
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.

1 participant