All notable changes to git-cms are documented in this file.
1.2.1 — 2026-03-21
- Publish surface cleanup: npm packaging is now explicitly scoped with a
filesallowlist, and the README hero image now uses a stable hosted URL so release tarballs only ship runtime-relevant assets.
- Guided demo stability:
prepare-playground.shno longer deletes existing refs whenGIT_CMS_SKIP_SEED=1, sonpm run demopreserves state correctly across draft, publish, and Git inspection steps. - Release docs consistency: Removed the stale duplicate unreleased changelog section, fixed the historical
1.1.0link, and updated remaining reader-facingplaygroundwording to the canonicalsandboxname.
1.2.0 — 2026-03-21
- Reader-safe sandbox workflow: Added a seeded long-lived
playgroundcontainer workflow withsandboxaliases,GIT_CMS_REPO=/data/repo, deterministic bootstrap, and host-side smoke coverage. - Review lanes: Added speculative editorial review lanes backed by
git-warpworking sets, includingCmsServicesupport, HTTP endpoints, admin UI controls, and E2E/integration tests. - Blog and media support: Added a public companion walkthrough (
docs/GIT_CMS_COMPANION.md), reproducible browser/VHS media capture pipeline, and a contributor devcontainer.
- CLI repo root selection:
bin/git-cms.jsnow honorsGIT_CMS_REPObeforeprocess.cwd(). - Public onboarding: README, quick reference, testing guide, ADR, and getting-started docs now distinguish
demo,sandbox, and contributordevmodes explicitly. - Dependencies: Upgraded to
@git-stunts/git-cas5.3.2,@git-stunts/git-warp14.8.0,@git-stunts/vault1.0.1,@playwright/test1.58.2, andvitest4.1.0, with npm/pnpm overrides for patched transitives.
- Published draft behavior: Published articles can now accept newer draft commits while the published ref stays pinned until the next explicit publish.
- Publish semantics:
publishArticle()now only moves the published ref to the current draft tip; suppliedshavalues act as stale-write guards instead of arbitrary publish targets. - Sandbox/bootstrap robustness: Playground bootstrap now repairs incomplete seeded state instead of silently accepting any partial namespace.
- Warning noise: Suppressed the
DEP0169url.parse()warning from transitive dependencies and removed theNO_COLOR/FORCE_COLORstartup warning in wrapped entrypoints. - Upload path handling: Server upload path now uses the sanitized basename consistently when writing the temporary file and invoking asset storage.
- Version-restore UX/docs alignment: Companion and reader docs now describe restore accurately: history is always visible, but published articles must be unpublished before restore.
1.1.5 — 2026-02-14
- (Security) Git identity leakage: Removed
git config --globalfrom host-level modification in CI workflow (.github/workflows/ci.yml). Scripts now use an isolated global config file viaGIT_CONFIG_GLOBALredirected to/tmp, preventing accidental modification of host global settings if workflows are executed locally (e.g., viaact). QUICK_REFERENCE.md:revertcommand description corrected — sets state toreverted, notdraftQUICK_REFERENCE.md: state machine diagram refined to accurately showdraft→revertedtransitionQUICK_REFERENCE.md: HTTP API table uses canonicaloptionalnotation and clarifies optimistic concurrency forpublishdocs/GETTING_STARTED.md: migration walkthrough refined (separated idempotency/dry-run, clarified no-op behavior)check-doc-drift.sh: recursive search for deleted files and root-level documentation linkscheck-doc-drift.sh: improved regex for CLI and API matching to prevent substring false positives and support underscores/digitsQUICK_REFERENCE.md: state derivation rule clarified — "draft ref only" requires noStatustrailer orStatus: draftQUICK_REFERENCE.md:<40-hex>replaced with<oid>for hash-format-agnostic docsdocs/GETTING_STARTED.md: migration walkthrough clarifies no-dry-run vs idempotencycheck-doc-drift.sh: API endpoint matching uses backtick-delimited grep (prevents substring false positives)check-doc-drift.sh: deleted-file search recurses into docs subdirectoriescheck-doc-drift.sh: root GS links regex handles../relative path prefixes
1.1.4 — 2026-02-14
- Consolidate and update documentation for M1.1, M1.2, M1.3, CE2, and CE3
QUICK_REFERENCE.mdis now the canonical reference for all 9 CLI commands, 10 HTTP API endpoints, and state machinedocs/GETTING_STARTED.mdupdated with version history, migration, unpublish/revert workflowsREADME.mdupdated with missing CLI commands and choose-your-path navigationROADMAP.mdmilestone statuses updated (M1.1, M1.2, M1.3, CE2, CE3 → complete)- Root
GETTING_STARTED.mdandREPO_WALKTHROUGH.mdreplaced with redirect stubs docs/ADR.mdfile tree updated (removedREPO_WALKTHROUGH.md, addedCONTENT_ID_POLICY.mdandLAYOUT_SPEC.md)
scripts/check-doc-drift.sh— automated doc drift detection (CLI commands, HTTP endpoints, stale references)npm run check:docsscript
- Doc freshness banners now reference v1.1.4 (was v1.1.3)
check-doc-drift.sh: CLI/API regex broadened to[a-z0-9_-]+for future-proofingcheck-doc-drift.sh: CLI command matching uses backtick-delimited grep to prevent substring false positivescheck-doc-drift.sh:root_gs_linkscheck was computed but never evaluated (dead code)
1.1.3 — 2026-02-14
migrate()JSDoc now documents TOCTOU / concurrency limitation
1.1.2 — 2026-02-14
migrate()computestofrom applied migrations instead of redundantreadLayoutVersionre-read
1.1.1 — 2026-02-14
readLayoutVersionnow rejects empty/whitespace-only config values (Number('')was silently treated as version 0)writeLayoutVersionvalidates input (rejects NaN, Infinity, floats, negatives) to prevent storing invalid versionsMIGRATIONSarray and entries frozen withObject.freezefor immutability consistency
1.1.0 — 2026-02-14
- Layout Specification v1 (
docs/LAYOUT_SPEC.md): Formalizes ref namespace, state derivation rules, commit format, config keys, and migration policy (M1.3) - Migration framework (
src/lib/LayoutMigration.js):readLayoutVersion,writeLayoutVersion,pendingMigrations,migrate— forward-only, idempotent layout migrations stored incms.layout.versiongit config - CLI commands:
git-cms migrate(run pending migrations) andgit-cms layout-version(print repo + codebase versions)
-
Version History Browser (CE3): Browse prior versions of an article, preview old content, and restore a selected version as a new draft commit
CmsService.getArticleHistory()— walk parent chain to list version summaries (SHA, title, status, author, date)CmsService.readVersion()— read full content of a specific commit by SHACmsService.restoreVersion()— restore historical content as a new draft with ancestry validation and provenance trailers (restoredFromSha,restoredAt)GET /api/cms/history,GET /api/cms/show-version,POST /api/cms/restoreserver endpoints- Admin UI: collapsible history panel with lazy-fetch, version preview, and restore button
-
Content Identity Policy (M1.1): Canonical slug validation with NFKC normalization, reserved word rejection, and
CmsValidationErrorcontract (ContentIdentityPolicy.js) -
State Machine (M1.2): Explicit draft/published/unpublished/reverted states with enforced transition rules (
ContentStatePolicy.js) -
Admin UI overhaul: Split/edit/preview markdown editor (via
marked), autosave, toast notifications, skeleton loading, drag-and-drop file uploads, metadata trailer editor, keyboard shortcuts (Cmd+S,Esc), dark mode token system -
DI seam in CmsService: Optional
graphconstructor param enablesInMemoryGraphAdapterinjection for zero-subprocess tests -
In-memory test adapter: Unit tests run in ~11ms instead of hundreds of ms (no
git init/subprocess forks) -
E2E test separation: Real-git smoke tests in
test/git-e2e.test.js, excluded from defaulttest:localruns -
test:git-e2escript: Run real-git integration tests independently -
@git-stunts/alfreddependency: Resilience policy library (wired but not yet integrated) -
@git-stunts/docker-guarddependency: Docker isolation helpers -
ROADMAP.md: M0–M6 milestone plan with blocking graph
-
Formal LaTeX ADR (
docs/adr-tex-2/) -
Onboarding scripts:
setup.sh,demo.sh,quickstart.shwith interactive menus -
Dependency integrity check:
check-dependency-integrity.mjspreventsfile:path regressions
- CmsService now uses
@git-stunts/git-warpGitGraphAdapterand@git-stunts/plumbingGitRepositoryServiceinstead of raw plumbing calls - All
repo.updateRef()calls routed throughCmsService._updateRef()for DI/production dual-path listArticles()supports both plumbing (for-each-ref) and in-memory (graph.listRefs) paths- Server endpoints return structured
{ code, field }errors for validation failures - Swapped all
file:dependency paths to versioned npm ranges (PP3)
- Symlink traversal hardening in static file serving
- Slug canonicalization enforced at all API ingress points
- Admin UI API calls aligned with server contract (query params, response shapes)
- Server integration test environment stabilized for CI
- (P1) Stored XSS via markdown preview: Sanitize
marked.parse()output with DOMPurify - (P1) Unpublish atomicity: Reorder
unpublishArticleso draft ref updates before published ref deletion - (P2) XSS via slug/badge rendering: Use
textContentand DOM APIs instead ofinnerHTMLinterpolation - (P2) SRI hashes: Add
integrity+crossoriginto marked and DOMPurify CDN script tags - (P2) Null guards:
revertArticleandunpublishArticlethrowno_draftwhen draft ref is missing;_resolveArticleStatethrowsarticle_not_foundwhen both draft and published refs are missing - (P2) uploadAsset DI guard: Throw
unsupported_in_di_modewhencas/vaultare null - (P1) Path traversal in upload handler: Sanitize user-controlled
filenametopath.basename()preventing writes outside tmpDir - (P1) readVersion lineage scoping:
readVersionnow validates SHA ancestry (prevents cross-article content leakage) - (P1) readVersion published fallback:
readVersionchecks both draft and published refs (consistent withgetArticleHistory) - (P2) Trailer key casing: Use camelCase
updatedAtinunpublishArticleandrevertArticle(was lowercaseupdatedatwhich brokerenderBadgeslookups); destructure out decoded lowercase key before spreading to avoidTrailerInvalidError - (P2) XSS in
escAttr: Escape single quotes ('→') to prevent injection into single-quoted attributes - (P2) Supply-chain hardening: Vendor Open Props CSS files locally (
public/css/) instead of@importfrom unpkg, eliminating CDN dependency and SRI gap - (P2) Monkey-patch safety: E2E test restores
plumbing.executeinfinallyblock - Unknown
draftStatusinresolveEffectiveStatenow throwsunknown_statusinstead of silently falling through to draft - Removed double-canonicalization in
_resolveArticleState - Replaced sequential
readRefloop withPromise.allinlistArticlesDI path - Admin UI: fixed
removeTrailerRowredundant positional removal, FileReader error handling, autosave-while-saving guard, Escape key scoped to editor panel, drag-and-drop scoped to drop zone - Test cleanup: extracted
createTestCms()helper, converted try/catch assertions to.rejects.toMatchObject(), added guard-path tests TRANSITIONSSets nowObject.freezed to prevent mutation via.add()/.delete()- DI-mode
_updateRefnow performs manual CAS check againstoldSha - Server tests assert setup call status codes to surface silent failures
- Vitest exclude glob
test/git-e2e*→test/git-e2e**to cover future subdirectories - Admin UI: reset history panel state (versions list, preview, selection) when creating a new article to prevent stale data
- Defensive
|| {}guard ondecoded.trailersdestructuring inunpublishArticleandrevertArticle(prevents TypeError if trailers is undefined) readVersionnow returnstrailers: decoded.trailers || {}ensuring callers always receive an object- Upload handler: moved tmpDir cleanup to
finallyblock preventing temp directory leaks on failure - (P1) sendError info leak: 500 responses now return generic 'Internal server error' instead of raw
err.message(prevents leaking file paths, git subprocess details, or internal state) - (P2) readBody O(n²):
readBodynow accumulates chunks in an array and usesBuffer.concatinstead of repeated string concatenation - Admin UI:
loadArticleunconditionally resetshistoryVersionsandselectedVersionto prevent stale history state when switching articles with the panel closed - Admin UI:
selectVersionguards against out-of-order async responses (prevents stale preview flash from rapid clicks) - (P2) walkLimit divergence: Extracted
HISTORY_WALK_LIMITas a shared exported constant used by both_validateAncestryand the server's history limit clamp