From 5f65e7762fff699e5a3efffd8da7a37c3cc70bbc Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 08:51:22 -0800 Subject: [PATCH 01/12] =?UTF-8?q?docs:=20consolidate=20and=20update=20docu?= =?UTF-8?q?mentation=20for=20M1.1=E2=80=93M1.3=20and=20CE3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 18 +++++ GETTING_STARTED.md | 74 +---------------- QUICK_REFERENCE.md | 158 +++++++++++++++++++++++++++++++------ README.md | 28 ++++++- REPO_WALKTHROUGH.md | 84 +------------------- ROADMAP.md | 10 +-- TESTING_GUIDE.md | 2 + docs/ADR.md | 3 +- docs/GETTING_STARTED.md | 49 ++++++++++++ package.json | 3 +- scripts/check-doc-drift.sh | 66 ++++++++++++++++ 11 files changed, 311 insertions(+), 184 deletions(-) create mode 100755 scripts/check-doc-drift.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa2969..22e8762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to git-cms are documented in this file. +## [1.1.4] — 2026-02-14 + +### Changed + +- Consolidate and update documentation for M1.1, M1.2, M1.3, CE2, and CE3 +- `QUICK_REFERENCE.md` is now the canonical reference for all 9 CLI commands, 10 HTTP API endpoints, and state machine +- `docs/GETTING_STARTED.md` updated with version history, migration, unpublish/revert workflows +- `README.md` updated with missing CLI commands and choose-your-path navigation +- `ROADMAP.md` milestone statuses updated (M1.1, M1.2, M1.3, CE2, CE3 → complete) +- Root `GETTING_STARTED.md` and `REPO_WALKTHROUGH.md` replaced with redirect stubs +- `docs/ADR.md` file tree updated (removed `REPO_WALKTHROUGH.md`, added `CONTENT_ID_POLICY.md` and `LAYOUT_SPEC.md`) + +### Added + +- `scripts/check-doc-drift.sh` — automated doc drift detection (CLI commands, HTTP endpoints, stale references) +- `npm run check:docs` script + ## [1.1.3] — 2026-02-14 ### Fixed @@ -102,6 +119,7 @@ All notable changes to git-cms are documented in this file. - **(P2) walkLimit divergence:** Extracted `HISTORY_WALK_LIMIT` as a shared exported constant used by both `_validateAncestry` and the server's history limit clamp [Unreleased]: https://github.com/flyingrobots/git-cms/compare/main...git-stunts +[1.1.4]: https://github.com/flyingrobots/git-cms/compare/v1.1.3...v1.1.4 [1.1.3]: https://github.com/flyingrobots/git-cms/compare/v1.1.2...v1.1.3 [1.1.2]: https://github.com/flyingrobots/git-cms/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/flyingrobots/git-cms/compare/v1.1.0...v1.1.1 diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index b2ac507..3d0d147 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -1,73 +1,3 @@ -# Getting Started with Git CMS +# Getting Started -This quick guide is the lightweight entry point. For the full Docker-focused walkthrough in the docs folder, see [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). - ---- - -## Prerequisites - -- Git -- Node.js 22+ -- Docker Desktop (recommended for safe testing) - ---- - -## Installation - -### Option A: Local CLI Install - -```bash -git clone https://github.com/flyingrobots/git-cms.git -cd git-cms -npm install -npm link -``` - -### Option B: Docker (Recommended) - -```bash -git clone https://github.com/flyingrobots/git-cms.git -cd git-cms -npm run setup -npm run dev -``` - -Open the UI at [http://localhost:4638/](http://localhost:4638/). - ---- - -## First Article - -1. Click `+ New Article`. -2. Set a slug like `my-first-post`. -3. Add title + body content. -4. Click `Save Draft`. -5. Click `Publish` when ready. - ---- - -## CLI Basics - -```bash -# Draft reads content from stdin -echo "# Hello" | git cms draft hello-world "Hello World" - -# List drafts -git cms list - -# Publish -git cms publish hello-world - -# Show article -git cms show hello-world -``` - ---- - -## Safety Notes - -- Prefer Docker workflows while learning. -- Use a dedicated test repository for local CLI experimentation. -- Avoid running low-level Git plumbing in repositories you care about. - -See [`TESTING_GUIDE.md`](TESTING_GUIDE.md) for safety and cleanup procedures. +This guide has moved to [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index e29f8d9..669c157 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -1,6 +1,8 @@ # Git CMS Quick Reference -One-page cheat sheet for Git CMS commands and concepts. +> Validated against v1.1.3 on 2026-02-14. + +One-page cheat sheet for Git CMS commands, API endpoints, and concepts. --- @@ -27,10 +29,25 @@ npm run demo # Watch a guided walkthrough | `npm test` | Run integration tests in Docker | | `npm run test:setup` | Run setup-script tests (BATS) | | `npm run test:local` | Run Vitest directly on host (advanced) | +| `npm run check:docs` | Check documentation drift against source code | --- -## CLI Commands (Inside Container) +## CLI Commands + +All 9 commands available via `node bin/git-cms.js ` or `git cms ` (if linked): + +| Command | Usage | Description | +|---------|-------|-------------| +| `draft` | `echo "body" \| git cms draft "Title"` | Create or update a draft (reads body from stdin) | +| `publish` | `git cms publish ` | Fast-forward published ref to match draft | +| `unpublish` | `git cms unpublish ` | Remove from published, keep as unpublished draft | +| `revert` | `git cms revert ` | Revert published/unpublished article back to draft state | +| `list` | `git cms list` | List all draft articles | +| `show` | `git cms show ` | Print article title and body | +| `serve` | `git cms serve` | Start HTTP API + Admin UI on port 4638 | +| `migrate` | `git cms migrate` | Run pending layout migrations (forward-only, idempotent) | +| `layout-version` | `git cms layout-version` | Print repo and codebase layout versions | ```bash # Enter container @@ -45,12 +62,24 @@ node bin/git-cms.js list # Publish a draft node bin/git-cms.js publish my-slug +# Unpublish +node bin/git-cms.js unpublish my-slug + +# Revert to draft +node bin/git-cms.js revert my-slug + # Read article content node bin/git-cms.js show my-slug # Start HTTP API + Admin UI node bin/git-cms.js serve +# Check layout version +node bin/git-cms.js layout-version + +# Run pending migrations +node bin/git-cms.js migrate + # Exit container exit ``` @@ -59,6 +88,64 @@ exit --- +## HTTP API + +All endpoints are served by `git cms serve` (default port 4638). Slugs are NFKC-normalized on the server. + +| Method | Path | Params / Body | Description | +|--------|------|---------------|-------------| +| `GET` | `/api/cms/list` | `?kind=articles\|published` | List articles by kind | +| `GET` | `/api/cms/show` | `?slug=xxx&kind=articles` | Read article content | +| `POST` | `/api/cms/snapshot` | `{ slug, title, body, trailers? }` | Create or update a draft | +| `POST` | `/api/cms/publish` | `{ slug, sha? }` | Publish a draft | +| `POST` | `/api/cms/unpublish` | `{ slug }` | Unpublish an article | +| `POST` | `/api/cms/revert` | `{ slug }` | Revert to draft state | +| `GET` | `/api/cms/history` | `?slug=xxx&limit=50` | List version history (max 200) | +| `GET` | `/api/cms/show-version` | `?slug=xxx&sha=<40-hex>` | Read a specific historical version | +| `POST` | `/api/cms/restore` | `{ slug, sha }` | Restore a historical version as new draft | +| `POST` | `/api/cms/upload` | `{ slug, filename, data }` | Upload base64-encoded asset (encrypted) | + +--- + +## State Machine + +Articles move through four states. Transitions are enforced by `ContentStatePolicy.js`. + +```text +States: draft, published, unpublished, reverted + + ┌──────────┐ + │ draft │◄──────────────────────┐ + └────┬──────┘ │ + │ publish │ revert + ▼ │ + ┌──────────┐ unpublish ┌──────┴─────┐ + │ published │───────────────►│ unpublished │ + └────┬──────┘ └──────┬──────┘ + │ publish (update) │ publish + └──────────┐ │ + ▼ │ + ┌──────────┐ │ + │ published │◄────────────┘ + └──────────┘ + + ┌──────────┐ + │ reverted │──── revert (from draft) ────► draft + └──────────┘ +``` + +**Effective state** is derived from which refs exist: +- Draft ref only → `draft` +- Both draft + published refs → `published` +- Draft ref with `Status: unpublished` trailer → `unpublished` +- Draft ref with `Status: reverted` trailer → `reverted` + +**Restoring a version creates a new commit — git-cms never rewrites history.** + +See [`docs/LAYOUT_SPEC.md`](docs/LAYOUT_SPEC.md) for the full ref namespace and state derivation rules. + +--- + ## Core Concept ### Traditional CMS @@ -89,6 +176,22 @@ The trick: content lives in commit messages while commits point at the repo's em --- +## Architecture (Lego Blocks) + +```text +git-cms + -> CmsService (orchestrator) + -> @git-stunts/plumbing (Git execution) + -> @git-stunts/trailer-codec (trailer encode/decode) + -> @git-stunts/git-warp (commit graph primitives) + -> @git-stunts/git-cas (asset chunk + manifest storage) + -> @git-stunts/vault (secret/key resolution) +``` + +This replaced older in-repo helpers (`src/lib/git.js`, `src/lib/parse.js`, `src/lib/chunks.js`, `src/lib/secrets.js`). + +--- + ## Inspecting with Git ```bash @@ -107,20 +210,6 @@ git hash-object -t tree /dev/null --- -## Architecture (Lego Blocks) - -```text -git-cms - -> CmsService (orchestrator) - -> @git-stunts/plumbing (Git execution) - -> @git-stunts/trailer-codec (trailer encode/decode) - -> @git-stunts/git-warp (commit graph primitives) - -> @git-stunts/git-cas (asset chunk + manifest storage) - -> @git-stunts/vault (secret/key resolution) -``` - ---- - ## Commit Message Shape ```text @@ -139,6 +228,19 @@ Trailers are parsed by `@git-stunts/trailer-codec`. --- +## Testing Surface + +| Test File | Coverage | +|-----------|----------| +| `test/git.test.js` | Integration tests — CRUD, publish, unpublish, revert, state machine, content identity | +| `test/chunks.test.js` | Asset encryption and chunking | +| `test/server.test.js` | HTTP API endpoints, validation, error responses | +| `test/git-e2e.test.js` | Real-git smoke tests (subprocess forks) | +| `test/setup.bats` | Setup script tests (BATS) | +| `test/run-docker.sh` | Docker test harness | + +--- + ## Troubleshooting ### Cannot find module `@git-stunts/...` @@ -166,9 +268,21 @@ Start Docker Desktop (macOS/Windows) or start Docker service on Linux. ## Docs Map -- `README.md`: overview + quick start -- `TESTING_GUIDE.md`: safe test workflow -- `docs/GETTING_STARTED.md`: full walkthrough -- `docs/ADR.md`: architecture decision record -- `scripts/README.md`: helper script docs -- `test/README.md`: test suite docs +| File | Contents | +|------|----------| +| [`README.md`](README.md) | Overview + quick start | +| [`TESTING_GUIDE.md`](TESTING_GUIDE.md) | Safe test workflow | +| [`QUICK_REFERENCE.md`](QUICK_REFERENCE.md) | This file — canonical CLI/API/state machine reference | +| [`ROADMAP.md`](ROADMAP.md) | M0–M6 milestone plan | +| [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md) | Full onboarding walkthrough | +| [`docs/ADR.md`](docs/ADR.md) | Architecture decision record | +| [`docs/CONTENT_ID_POLICY.md`](docs/CONTENT_ID_POLICY.md) | Slug validation and content identity rules | +| [`docs/LAYOUT_SPEC.md`](docs/LAYOUT_SPEC.md) | Ref namespace, layout versions, migration policy | +| [`scripts/README.md`](scripts/README.md) | Helper script docs | +| [`test/README.md`](test/README.md) | Test suite docs | + +**Removed docs** (redirect stubs remain for one release cycle): +- `GETTING_STARTED.md` (root) — moved to `docs/GETTING_STARTED.md` +- `REPO_WALKTHROUGH.md` — consolidated into this file + +**Repository:** [https://github.com/flyingrobots/git-cms](https://github.com/flyingrobots/git-cms) diff --git a/README.md b/README.md index 509da87..4885363 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ npm run dev The tests create, destroy, and manipulate Git repositories. Running low-level plumbing commands on your host filesystem is risky - a typo could affect your local Git setup. That's why we built Docker isolation into everything. -**Read more:** [TESTING_GUIDE.md](./TESTING_GUIDE.md) | [docs/GETTING_STARTED.md](./docs/GETTING_STARTED.md) | [docs/CONTENT_ID_POLICY.md](./docs/CONTENT_ID_POLICY.md) +**Read more:** [TESTING_GUIDE.md](./TESTING_GUIDE.md) | [docs/GETTING_STARTED.md](./docs/GETTING_STARTED.md) | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) | [docs/CONTENT_ID_POLICY.md](./docs/CONTENT_ID_POLICY.md) ## Features @@ -129,6 +129,32 @@ Publishing fast-forwards `refs/_blog/published/` to match the draft. git cms publish hello-world ``` +### 6. Unpublish / Revert + +```bash +# Remove from published, keep as unpublished draft +git cms unpublish hello-world + +# Revert to draft state +git cms revert hello-world +``` + +### 7. Layout Version / Migrate + +```bash +# Check repo vs codebase layout version +git cms layout-version + +# Run pending layout migrations (forward-only, idempotent) +git cms migrate +``` + +## Where to Go Next + +- **New user?** Start with [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md) +- **Operator / API user?** See [`QUICK_REFERENCE.md`](./QUICK_REFERENCE.md) for all CLI commands and HTTP endpoints +- **Architecture / rationale?** Read [`docs/ADR.md`](./docs/ADR.md) and [`docs/LAYOUT_SPEC.md`](./docs/LAYOUT_SPEC.md) + ## License Copyright © 2026 James Ross. This software is licensed under the [Apache License](./LICENSE), Version 2.0 diff --git a/REPO_WALKTHROUGH.md b/REPO_WALKTHROUGH.md index 39317b8..0506730 100644 --- a/REPO_WALKTHROUGH.md +++ b/REPO_WALKTHROUGH.md @@ -1,83 +1,3 @@ -# Git CMS Repository Walkthrough +# Repository Walkthrough -Technical orientation for the current codebase architecture. - ---- - -## 1. Core Model - -Git CMS stores article state in Git commits and refs, not SQL tables. - -- Entry point for orchestration: `src/lib/CmsService.js` -- Draft ref pattern: `{refPrefix}/articles/{slug}` -- Published ref pattern: `{refPrefix}/published/{slug}` -- Default `refPrefix`: `refs/_blog/dev` (overridable via `CMS_REF_PREFIX`) - -The service writes commit messages as content payloads and moves refs atomically with compare-and-swap semantics. - ---- - -## 2. Composition Layer - -`CmsService` composes the `@git-stunts/*` packages: - -- `@git-stunts/plumbing`: Git command execution and repository helpers -- `@git-stunts/trailer-codec`: trailer parsing/encoding -- `@git-stunts/git-warp`: commit graph primitives (`commitNode`, `showNode`) -- `@git-stunts/git-cas`: chunked asset storage and retrieval -- `@git-stunts/vault`: secret resolution for encryption keys - -This replaced older in-repo helpers such as `src/lib/git.js`, `src/lib/parse.js`, `src/lib/chunks.js`, and `src/lib/secrets.js`. - ---- - -## 3. Interfaces - -### CLI - -- File: `bin/git-cms.js` -- Commands: `draft`, `publish`, `list`, `show`, `serve` -- `draft` expects body from `stdin` - -### HTTP API - -- File: `src/server/index.js` -- Endpoints: `/api/cms/snapshot`, `/api/cms/publish`, `/api/cms/list`, `/api/cms/show`, `/api/cms/upload` -- Static UI served from `public/` - -### Admin UI - -- Files: `public/index.html`, `public/app.js` -- Uses the HTTP API for article lifecycle operations - ---- - -## 4. Testing Surface - -- Integration tests: `test/git.test.js`, `test/chunks.test.js`, `test/server.test.js` -- Setup script tests: `test/setup.bats` -- Docker test harness: `test/run-docker.sh`, `test/Dockerfile.bats` - -Key regressions covered include: - -- Ref-prefix correctness for chunk refs -- Error propagation from Git plumbing -- Symlink traversal hardening in static serving - ---- - -## 5. Operational Docs - -- Main overview: `README.md` -- Safety/testing operations: `TESTING_GUIDE.md` -- Deep architecture decisions: `docs/ADR.md` -- Detailed onboarding: `docs/GETTING_STARTED.md` -- Planning/status docs: `ROADMAP.md`, `docs/operations/` - ---- - -## 6. Repository Coordinates - -Canonical repository URL for this codebase: - -- +This content has been consolidated into [`QUICK_REFERENCE.md`](QUICK_REFERENCE.md). diff --git a/ROADMAP.md b/ROADMAP.md index 2d512f8..4fa50e1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -147,7 +147,7 @@ INF3 (UI Redesign) ideally after M1.1 ## M1 — Core Content Model + State Machine -### M1.1 — Define canonical Content IDs + paths *(ADD — MUST EXIST)* +### M1.1 — Define canonical Content IDs + paths *(complete)* - **User Story:** As a developer, I understand how content is identified and where it lives. - **Requirements:** Slug rules (charset, length, uniqueness); rename semantics; ref naming conventions; content ID immutability policy. @@ -166,7 +166,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### M1.2 — Draft/Published state machine + transitions *(ADD — MUST EXIST)* +### M1.2 — Draft/Published state machine + transitions *(complete)* - **User Story:** As a developer, the Draft→Published→Unpublished→Reverted states are explicit and deterministic. - **Requirements:** State enum; allowed transitions; revert semantics (new commit vs ref move); unpublish semantics (tombstone vs delete). @@ -185,7 +185,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### M1.3 — Migration + repo layout spec *(ADD — MUST EXIST)* +### M1.3 — Migration + repo layout spec *(complete)* - **User Story:** As a maintainer, I know where content lives and how to migrate between versions. - **Requirements:** Ref namespace spec; index structure; migration strategy; backward compatibility policy. @@ -304,7 +304,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### CE2 — Add autosave for drafts +### CE2 — Add autosave for drafts *(complete)* - **User Story:** As an author, my draft is never lost because I sneezed near a browser tab. - **Requirements:** Debounce; conflict strategy; "last saved" indicator; local vs repo save decision. @@ -324,7 +324,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### CE3 — Add version history browser +### CE3 — Add version history browser *(complete)* - **User Story:** As an editor, I can browse and diff prior versions and restore one. - **Requirements:** Map commits to content IDs; diff view; restore action creates new commit; permissions later. diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md index 0459465..862c356 100644 --- a/TESTING_GUIDE.md +++ b/TESTING_GUIDE.md @@ -195,6 +195,8 @@ docker compose run --rm test - Runs Vitest integration tests - Creates temporary Git repos in `/tmp` - Tests CRUD operations, encryption, API endpoints +- Version history browsing and restore (CE3) +- Layout migration framework (M1.3) - Cleans up after completion **Safe because:** All tests run in an isolated Docker container with temporary repos. diff --git a/docs/ADR.md b/docs/ADR.md index 43478b1..0a653ad 100644 --- a/docs/ADR.md +++ b/docs/ADR.md @@ -1741,7 +1741,8 @@ git-cms/ │ └── app.js ├── docs/ │ ├── GETTING_STARTED.md -│ ├── REPO_WALKTHROUGH.md +│ ├── CONTENT_ID_POLICY.md +│ ├── LAYOUT_SPEC.md │ └── ADR.md # This document ├── Dockerfile # Multi-stage build ├── docker-compose.yml # Dev/test orchestration diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index d118c07..6d0b3d0 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,5 +1,7 @@ # Getting Started with Git CMS +> Validated against v1.1.3 on 2026-02-14. + **⚠️ IMPORTANT: This project manipulates Git repositories at a low level. Always use Docker for testing to protect your local Git setup.** --- @@ -111,6 +113,12 @@ node bin/git-cms.js publish hello-world # Read it back node bin/git-cms.js show hello-world +# Unpublish (removes from published, keeps as unpublished draft) +node bin/git-cms.js unpublish hello-world + +# Revert to draft state +node bin/git-cms.js revert hello-world + # Exit the container exit ``` @@ -141,6 +149,47 @@ exit - Commits pointing to the "empty tree" (no files touched!) - Refs acting as pointers to "current" versions +### Step 6: Browse Version History + +Every draft save creates a new commit, so you get infinite version history for free. + +In the Admin UI: +1. Open an article you've edited multiple times +2. Expand the **History** panel on the right +3. Click any version to preview its content +4. Click **Restore** to bring back that version as a new draft + +The HTTP API provides three endpoints for version history — see [`QUICK_REFERENCE.md`](../QUICK_REFERENCE.md) for the full API table: +- `GET /api/cms/history` — list version summaries +- `GET /api/cms/show-version` — read a specific historical version +- `POST /api/cms/restore` — restore a version as a new draft + +**Restoring creates a new commit — no history is rewritten.** + +### Step 7: Run Migrations (When Upgrading) + +When upgrading git-cms to a new version, check whether your repository's layout needs migration: + +```bash +# Inside the container +docker compose exec app sh + +# Check current layout version +node bin/git-cms.js layout-version +# Repo: v0 +# Codebase: v1 + +# Run pending migrations +node bin/git-cms.js migrate +# Migrated layout: v0 → v1 (applied: 1) +``` + +Things to know about migrations: +- **Idempotent:** Safe to run multiple times — already-applied migrations are skipped +- **Forward-only:** Rollback is not supported (by design) +- **No dry-run mode:** Migrations are no-ops until structural changes exist between versions +- **Backup first:** Run `git clone --mirror ` before migrating production repos + --- ## Understanding the Safety Model diff --git a/package.json b/package.json index b774410..57bbad0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-cms", - "version": "1.1.3", + "version": "1.1.4", "description": "A serverless, database-free CMS built on Git plumbing.", "type": "module", "bin": { @@ -13,6 +13,7 @@ "quickstart": "./scripts/quickstart.sh", "setup": "./scripts/setup.sh", "check:deps": "node scripts/check-dependency-integrity.mjs", + "check:docs": "./scripts/check-doc-drift.sh", "test": "./test/run-docker.sh", "test:setup": "./test/run-setup-tests.sh", "test:local": "vitest run", diff --git a/scripts/check-doc-drift.sh b/scripts/check-doc-drift.sh new file mode 100755 index 0000000..3f58838 --- /dev/null +++ b/scripts/check-doc-drift.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# check-doc-drift.sh — verify QUICK_REFERENCE.md stays in sync with source code +# +# Checks: +# 1. All CLI commands from bin/git-cms.js appear in QUICK_REFERENCE.md +# 2. All HTTP endpoints from src/server/index.js appear in QUICK_REFERENCE.md +# 3. Deleted file names are not referenced as active links +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +QUICK_REF="$ROOT/QUICK_REFERENCE.md" +CLI_FILE="$ROOT/bin/git-cms.js" +SERVER_FILE="$ROOT/src/server/index.js" + +errors=0 + +# ── 1. CLI commands ────────────────────────────────────────────────────────── +# Extract command names from the switch/case block in bin/git-cms.js +cli_commands=$(grep -oE "case '([a-z-]+)'" "$CLI_FILE" | sed "s/case '//;s/'//") + +for cmd in $cli_commands; do + if ! grep -q "$cmd" "$QUICK_REF"; then + echo "DRIFT: CLI command '$cmd' missing from QUICK_REFERENCE.md" + errors=$((errors + 1)) + fi +done + +# ── 2. HTTP endpoints ─────────────────────────────────────────────────────── +# Extract API paths from src/server/index.js +api_paths=$(grep -oE "pathname === '/api/cms/[a-z-]+'" "$SERVER_FILE" | sed "s/pathname === '//;s/'//g" | sort -u) + +for ep in $api_paths; do + if ! grep -q "$ep" "$QUICK_REF"; then + echo "DRIFT: HTTP endpoint '$ep' missing from QUICK_REFERENCE.md" + errors=$((errors + 1)) + fi +done + +# ── 3. Deleted-file references ─────────────────────────────────────────────── +# These files have been replaced with redirect stubs. No other doc should link to them. +deleted_files=("REPO_WALKTHROUGH.md") + +for file in "${deleted_files[@]}"; do + # Search all markdown files except the stub itself, QUICK_REFERENCE (documents the removal), and CHANGELOG (historical entries) + offenders=$(grep -rl "$file" "$ROOT"/*.md "$ROOT"/docs/*.md 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) + if [ -n "$offenders" ]; then + echo "DRIFT: Deleted file '$file' still referenced in: $offenders" + errors=$((errors + 1)) + fi +done + +# Check root GETTING_STARTED.md isn't linked from docs (except from itself) +root_gs_links=$(grep -rn '\[.*\](GETTING_STARTED.md)' "$ROOT"/docs/*.md 2>/dev/null || true) +# Also check for relative links from root-level docs pointing to root GETTING_STARTED.md +# (but NOT links to docs/GETTING_STARTED.md, which is the canonical location) + +if [ "$errors" -gt 0 ]; then + echo "" + echo "FAIL: $errors doc-drift issue(s) found." + exit 1 +fi + +echo "OK: All CLI commands and HTTP endpoints documented. No stale references." From 1d7337bf3197b61da35a46aefa0829802d9cd2a8 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:27:47 -0800 Subject: [PATCH 02/12] fix: update doc freshness banners to reference v1.1.4 --- QUICK_REFERENCE.md | 2 +- docs/GETTING_STARTED.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index 669c157..e374960 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -1,6 +1,6 @@ # Git CMS Quick Reference -> Validated against v1.1.3 on 2026-02-14. +> Validated against v1.1.4 on 2026-02-14. One-page cheat sheet for Git CMS commands, API endpoints, and concepts. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 6d0b3d0..60c3f05 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,6 +1,6 @@ # Getting Started with Git CMS -> Validated against v1.1.3 on 2026-02-14. +> Validated against v1.1.4 on 2026-02-14. **⚠️ IMPORTANT: This project manipulates Git repositories at a low level. Always use Docker for testing to protect your local Git setup.** From 86fe33d7b6b460f799c58253cf0aef1d459bf65d Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:28:27 -0800 Subject: [PATCH 03/12] fix: harden check-doc-drift.sh regex and wire up dead root_gs_links check - Broaden CLI command regex to [a-z0-9_-]+ for future-proofing - Use backtick-delimited matching for CLI commands to avoid substring false positives - Broaden API path regex to [a-z0-9_/-]+ for endpoints with digits/underscores - Wire up root_gs_links check that was computed but never evaluated --- scripts/check-doc-drift.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/check-doc-drift.sh b/scripts/check-doc-drift.sh index 3f58838..95fe16a 100755 --- a/scripts/check-doc-drift.sh +++ b/scripts/check-doc-drift.sh @@ -6,6 +6,7 @@ # 1. All CLI commands from bin/git-cms.js appear in QUICK_REFERENCE.md # 2. All HTTP endpoints from src/server/index.js appear in QUICK_REFERENCE.md # 3. Deleted file names are not referenced as active links +# 4. docs/ files don't link to root GETTING_STARTED.md (canonical is docs/) # set -euo pipefail @@ -19,10 +20,10 @@ errors=0 # ── 1. CLI commands ────────────────────────────────────────────────────────── # Extract command names from the switch/case block in bin/git-cms.js -cli_commands=$(grep -oE "case '([a-z-]+)'" "$CLI_FILE" | sed "s/case '//;s/'//") +cli_commands=$(grep -oE "case '[a-z0-9_-]+'" "$CLI_FILE" | sed "s/case '//;s/'//") for cmd in $cli_commands; do - if ! grep -q "$cmd" "$QUICK_REF"; then + if ! grep -q "\`$cmd\`" "$QUICK_REF"; then echo "DRIFT: CLI command '$cmd' missing from QUICK_REFERENCE.md" errors=$((errors + 1)) fi @@ -30,7 +31,7 @@ done # ── 2. HTTP endpoints ─────────────────────────────────────────────────────── # Extract API paths from src/server/index.js -api_paths=$(grep -oE "pathname === '/api/cms/[a-z-]+'" "$SERVER_FILE" | sed "s/pathname === '//;s/'//g" | sort -u) +api_paths=$(grep -oE "pathname === '/api/cms/[a-z0-9_/-]+'" "$SERVER_FILE" | sed "s/pathname === '//;s/'//g" | sort -u) for ep in $api_paths; do if ! grep -q "$ep" "$QUICK_REF"; then @@ -52,10 +53,14 @@ for file in "${deleted_files[@]}"; do fi done -# Check root GETTING_STARTED.md isn't linked from docs (except from itself) +# ── 4. Root GETTING_STARTED.md links from docs/ ───────────────────────────── +# docs/ files should link to docs/GETTING_STARTED.md, not the root redirect stub root_gs_links=$(grep -rn '\[.*\](GETTING_STARTED.md)' "$ROOT"/docs/*.md 2>/dev/null || true) -# Also check for relative links from root-level docs pointing to root GETTING_STARTED.md -# (but NOT links to docs/GETTING_STARTED.md, which is the canonical location) +if [ -n "$root_gs_links" ]; then + echo "DRIFT: docs/ files link to root GETTING_STARTED.md instead of docs/GETTING_STARTED.md:" + echo "$root_gs_links" + errors=$((errors + 1)) +fi if [ "$errors" -gt 0 ]; then echo "" From 8a2c19839cee6075ab072ec77c4598dedcd0dba9 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:28:45 -0800 Subject: [PATCH 04/12] chore: update CHANGELOG with review round-1 fixes --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e8762..259fede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,13 @@ All notable changes to git-cms are documented in this file. - `scripts/check-doc-drift.sh` — automated doc drift detection (CLI commands, HTTP endpoints, stale references) - `npm run check:docs` script +### Fixed + +- 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-proofing +- `check-doc-drift.sh`: CLI command matching uses backtick-delimited grep to prevent substring false positives +- `check-doc-drift.sh`: `root_gs_links` check was computed but never evaluated (dead code) + ## [1.1.3] — 2026-02-14 ### Fixed From eac989561ca8ee995ba4c577572f7c3b713f72e1 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:38:40 -0800 Subject: [PATCH 05/12] =?UTF-8?q?chore:=20add=20DOC1=E2=80=93DOC3=20doc=20?= =?UTF-8?q?tooling=20backlog=20items=20to=20ROADMAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 4fa50e1..831ec23 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -591,6 +591,38 @@ INF3 (UI Redesign) ideally after M1.1 --- +## Backlog — Doc Tooling (Immediate Follow-up) + +### DOC1 — Add drift check to CI pipeline + +- **User Story:** As a maintainer, doc drift is caught automatically before merge — not manually after the fact. +- **Requirements:** Wire `npm run check:docs` into pre-push hook or CI workflow; fail the build on drift. +- **Scope:** CI config + optional git hook setup. +- **Est. Complexity:** ~30–80 LoC +- **Blocked By:** None + +--- + +### DOC2 — Add `--fix` mode to check-doc-drift.sh + +- **User Story:** As a maintainer, I can auto-regenerate the CLI and HTTP API tables in QUICK_REFERENCE.md from source instead of editing them by hand. +- **Requirements:** Parse `bin/git-cms.js` switch/case and `src/server/index.js` endpoint definitions; generate markdown tables; write them into QUICK_REFERENCE.md between sentinel comments. +- **Scope:** Script enhancement. +- **Est. Complexity:** ~100–300 LoC +- **Blocked By:** None + +--- + +### DOC3 — Markdown link checker + +- **User Story:** As a maintainer, broken internal links (`[text](path)` pointing to nonexistent files) are caught before merge. +- **Requirements:** Walk all `.md` files; resolve relative links; report broken targets; exclude external URLs. +- **Scope:** New script or integration with existing `check-doc-drift.sh`. +- **Est. Complexity:** ~80–200 LoC +- **Blocked By:** None + +--- + ## Already Published - `@git-stunts/plumbing` From 5df52fe440518b50211abc4582c38e8ad45f03e9 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:45:34 -0800 Subject: [PATCH 06/12] fix: address review round-2 feedback from CodeRabbit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix revert command description: sets state to 'reverted', not 'draft' - Fix state machine diagram: add draft→reverted, fix reverted block semantics - Clarify state derivation rule for draft-only refs - Replace <40-hex> with for hash-format-agnostic docs - Replace nonstandard { slug, sha? } with explicit optional notation - Fix migration walkthrough: clarify no-dry-run vs idempotency - Fix API endpoint substring matching in check-doc-drift.sh - Fix deleted-file search to recurse into docs subdirectories - Fix root GS links regex to handle relative path prefixes --- QUICK_REFERENCE.md | 43 +++++++++++++++++++------------------- docs/GETTING_STARTED.md | 2 +- scripts/check-doc-drift.sh | 6 +++--- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index e374960..842de49 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -42,7 +42,7 @@ All 9 commands available via `node bin/git-cms.js ` or `git cms "Title"` | Create or update a draft (reads body from stdin) | | `publish` | `git cms publish ` | Fast-forward published ref to match draft | | `unpublish` | `git cms unpublish ` | Remove from published, keep as unpublished draft | -| `revert` | `git cms revert ` | Revert published/unpublished article back to draft state | +| `revert` | `git cms revert ` | Revert article to reverted state (draft ref with `Status: reverted`) | | `list` | `git cms list` | List all draft articles | | `show` | `git cms show ` | Print article title and body | | `serve` | `git cms serve` | Start HTTP API + Admin UI on port 4638 | @@ -97,11 +97,11 @@ All endpoints are served by `git cms serve` (default port 4638). Slugs are NFKC- | `GET` | `/api/cms/list` | `?kind=articles\|published` | List articles by kind | | `GET` | `/api/cms/show` | `?slug=xxx&kind=articles` | Read article content | | `POST` | `/api/cms/snapshot` | `{ slug, title, body, trailers? }` | Create or update a draft | -| `POST` | `/api/cms/publish` | `{ slug, sha? }` | Publish a draft | +| `POST` | `/api/cms/publish` | `{ slug, sha (optional) }` | Publish a draft | | `POST` | `/api/cms/unpublish` | `{ slug }` | Unpublish an article | | `POST` | `/api/cms/revert` | `{ slug }` | Revert to draft state | | `GET` | `/api/cms/history` | `?slug=xxx&limit=50` | List version history (max 200) | -| `GET` | `/api/cms/show-version` | `?slug=xxx&sha=<40-hex>` | Read a specific historical version | +| `GET` | `/api/cms/show-version` | `?slug=xxx&sha=` | Read a specific historical version | | `POST` | `/api/cms/restore` | `{ slug, sha }` | Restore a historical version as new draft | | `POST` | `/api/cms/upload` | `{ slug, filename, data }` | Upload base64-encoded asset (encrypted) | @@ -114,28 +114,27 @@ Articles move through four states. Transitions are enforced by `ContentStatePoli ```text States: draft, published, unpublished, reverted - ┌──────────┐ - │ draft │◄──────────────────────┐ - └────┬──────┘ │ - │ publish │ revert - ▼ │ - ┌──────────┐ unpublish ┌──────┴─────┐ - │ published │───────────────►│ unpublished │ - └────┬──────┘ └──────┬──────┘ - │ publish (update) │ publish - └──────────┐ │ - ▼ │ - ┌──────────┐ │ - │ published │◄────────────┘ - └──────────┘ - - ┌──────────┐ - │ reverted │──── revert (from draft) ────► draft - └──────────┘ + revert + ┌──────────┐ ─────────────────► ┌──────────┐ + ┌───►│ draft │ │ reverted │ + │ └────┬──────┘ ◄──────────────── └──────────┘ + │ │ publish save + │ ▼ + │ ┌──────────┐ unpublish ┌─────────────┐ + │ │ published │───────────────►│ unpublished │ + │ └────┬──────┘ └──┬────────┬──┘ + │ │ publish (update) │ │ + │ └──────────┐ publish │ │ + │ ▼ │ │ │ + │ ┌──────────┐│ │ │ + │ │ published │◄──────┘ │ + │ └──────────┘ │ + │ save │ + └────────────────────────────────────────────┘ ``` **Effective state** is derived from which refs exist: -- Draft ref only → `draft` +- Draft ref only (no `Status` trailer, or `Status: draft`) → `draft` - Both draft + published refs → `published` - Draft ref with `Status: unpublished` trailer → `unpublished` - Draft ref with `Status: reverted` trailer → `reverted` diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 60c3f05..c1b3a92 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -187,7 +187,7 @@ node bin/git-cms.js migrate Things to know about migrations: - **Idempotent:** Safe to run multiple times — already-applied migrations are skipped - **Forward-only:** Rollback is not supported (by design) -- **No dry-run mode:** Migrations are no-ops until structural changes exist between versions +- **No dry-run mode:** Use `git cms layout-version` to check whether migrations are pending before running - **Backup first:** Run `git clone --mirror ` before migrating production repos --- diff --git a/scripts/check-doc-drift.sh b/scripts/check-doc-drift.sh index 95fe16a..d4066d5 100755 --- a/scripts/check-doc-drift.sh +++ b/scripts/check-doc-drift.sh @@ -34,7 +34,7 @@ done api_paths=$(grep -oE "pathname === '/api/cms/[a-z0-9_/-]+'" "$SERVER_FILE" | sed "s/pathname === '//;s/'//g" | sort -u) for ep in $api_paths; do - if ! grep -q "$ep" "$QUICK_REF"; then + if ! grep -q "\`$ep\`" "$QUICK_REF"; then echo "DRIFT: HTTP endpoint '$ep' missing from QUICK_REFERENCE.md" errors=$((errors + 1)) fi @@ -46,7 +46,7 @@ deleted_files=("REPO_WALKTHROUGH.md") for file in "${deleted_files[@]}"; do # Search all markdown files except the stub itself, QUICK_REFERENCE (documents the removal), and CHANGELOG (historical entries) - offenders=$(grep -rl "$file" "$ROOT"/*.md "$ROOT"/docs/*.md 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) + offenders=$(grep -rl --include='*.md' "$file" "$ROOT" "$ROOT"/docs 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) if [ -n "$offenders" ]; then echo "DRIFT: Deleted file '$file' still referenced in: $offenders" errors=$((errors + 1)) @@ -55,7 +55,7 @@ done # ── 4. Root GETTING_STARTED.md links from docs/ ───────────────────────────── # docs/ files should link to docs/GETTING_STARTED.md, not the root redirect stub -root_gs_links=$(grep -rn '\[.*\](GETTING_STARTED.md)' "$ROOT"/docs/*.md 2>/dev/null || true) +root_gs_links=$(grep -rnE '\[.*\]\((\.\.\/)?GETTING_STARTED\.md\)' "$ROOT"/docs/ --include='*.md' 2>/dev/null || true) if [ -n "$root_gs_links" ]; then echo "DRIFT: docs/ files link to root GETTING_STARTED.md instead of docs/GETTING_STARTED.md:" echo "$root_gs_links" From 507bc58c07434b58e3af8c7457dfc7296f5f9261 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:46:17 -0800 Subject: [PATCH 07/12] chore: bump version to 1.1.5, update CHANGELOG and doc freshness banners --- CHANGELOG.md | 14 ++++++++++++++ QUICK_REFERENCE.md | 2 +- docs/GETTING_STARTED.md | 2 +- package.json | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 259fede..c7f5338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to git-cms are documented in this file. +## [1.1.5] — 2026-02-14 + +### Fixed + +- `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` +- `QUICK_REFERENCE.md`: state machine diagram now shows draft→reverted transition and fixes reverted block semantics +- `QUICK_REFERENCE.md`: state derivation rule clarified — "draft ref only" requires no `Status` trailer or `Status: draft` +- `QUICK_REFERENCE.md`: `<40-hex>` replaced with `` for hash-format-agnostic docs +- `QUICK_REFERENCE.md`: `{ slug, sha? }` replaced with explicit `sha (optional)` notation +- `docs/GETTING_STARTED.md`: migration walkthrough clarifies no-dry-run vs idempotency +- `check-doc-drift.sh`: API endpoint matching uses backtick-delimited grep (prevents substring false positives) +- `check-doc-drift.sh`: deleted-file search recurses into docs subdirectories +- `check-doc-drift.sh`: root GS links regex handles `../` relative path prefixes + ## [1.1.4] — 2026-02-14 ### Changed diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index 842de49..b0f376b 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -1,6 +1,6 @@ # Git CMS Quick Reference -> Validated against v1.1.4 on 2026-02-14. +> Validated against v1.1.5 on 2026-02-14. One-page cheat sheet for Git CMS commands, API endpoints, and concepts. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index c1b3a92..fe96eed 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,6 +1,6 @@ # Getting Started with Git CMS -> Validated against v1.1.4 on 2026-02-14. +> Validated against v1.1.5 on 2026-02-14. **⚠️ IMPORTANT: This project manipulates Git repositories at a low level. Always use Docker for testing to protect your local Git setup.** diff --git a/package.json b/package.json index 57bbad0..217bdd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-cms", - "version": "1.1.4", + "version": "1.1.5", "description": "A serverless, database-free CMS built on Git plumbing.", "type": "module", "bin": { From 362c22554702ff4bb78d60a0a13e3567a50d9f8a Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 14 Feb 2026 21:48:29 -0800 Subject: [PATCH 08/12] =?UTF-8?q?chore:=20add=20DOC4=E2=80=93DOC6=20doc=20?= =?UTF-8?q?tooling=20backlog=20items=20to=20ROADMAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 831ec23..35ab410 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -623,6 +623,36 @@ INF3 (UI Redesign) ideally after M1.1 --- +### DOC4 — Generate state diagram from ContentStatePolicy.js + +- **User Story:** As a maintainer, the state machine diagram in QUICK_REFERENCE.md stays in sync with code automatically — no manual ASCII art updates after policy changes. +- **Requirements:** Read `TRANSITIONS` map from `ContentStatePolicy.js`; generate ASCII or Mermaid diagram; write into QUICK_REFERENCE.md between sentinel comments. +- **Scope:** Script + sentinel markers in QUICK_REFERENCE.md. +- **Est. Complexity:** ~100–250 LoC +- **Blocked By:** None + +--- + +### DOC5 — Add `test:docs` target for drift script regression tests + +- **User Story:** As a maintainer, I can verify that `check-doc-drift.sh` itself catches real drift — with intentional drift scenarios as test fixtures. +- **Requirements:** Fixture files with missing CLI commands, missing endpoints, stale references; test harness runs drift script against fixtures and asserts expected failures. +- **Scope:** Test fixtures + test runner script + `npm run test:docs` target. +- **Est. Complexity:** ~80–200 LoC +- **Blocked By:** None + +--- + +### DOC6 — Port check-doc-drift.sh to JavaScript + +- **User Story:** As a maintainer, the doc drift checker uses the same language and test patterns as the rest of the codebase — improving maintainability and cross-platform support. +- **Requirements:** Rewrite `check-doc-drift.sh` as a Node.js script; same checks (CLI commands, HTTP endpoints, stale references, root GS links); integrate with Vitest for testability. +- **Scope:** New JS script replacing shell script; update `npm run check:docs`. +- **Est. Complexity:** ~150–400 LoC +- **Blocked By:** None (DOC5 provides regression safety net) + +--- + ## Already Published - `@git-stunts/plumbing` From 7c09bbfcc1adbdef9c294d55635cc4e5293f8a9a Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 14 Feb 2026 22:36:36 -0800 Subject: [PATCH 09/12] fix(security): prevent git identity leakage in CI workflow Removed 'git config --global' from .github/workflows/ci.yml to ensure that local development environments (using tools like 'act') do not accidentally have their global git configuration modified by CI scripts. Now uses local repo configuration instead. Also updated CHANGELOG.md to reflect this security fix. --- .github/workflows/ci.yml | 12 ++++++------ CHANGELOG.md | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09a7e5d..a1b5916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,9 @@ jobs: - name: Configure Git User run: | - git config --global user.email "ci@git-cms.local" - git config --global user.name "CI Bot" - git config --global init.defaultBranch main + git config user.email "ci@git-cms.local" + git config user.name "CI Bot" + git config init.defaultBranch main - name: Run Local Integration Tests run: npm run test:local @@ -66,9 +66,9 @@ jobs: - name: Configure Git User run: | - git config --global user.email "ci@git-cms.local" - git config --global user.name "CI Bot" - git config --global init.defaultBranch main + git config user.email "ci@git-cms.local" + git config user.name "CI Bot" + git config init.defaultBranch main - name: Install Dependencies run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f5338..c925e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to git-cms are documented in this file. ### Fixed +- **(Security) Git identity leakage:** Removed `git config --global` from CI workflow (`.github/workflows/ci.yml`). Scripts now use local repository configuration to prevent accidental modification of host global settings if workflows are executed locally (e.g., via `act`). - `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` - `QUICK_REFERENCE.md`: state machine diagram now shows draft→reverted transition and fixes reverted block semantics - `QUICK_REFERENCE.md`: state derivation rule clarified — "draft ref only" requires no `Status` trailer or `Status: draft` From f29d6f5796ce632360fd926e5013783b59681a1e Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 14 Feb 2026 22:41:26 -0800 Subject: [PATCH 10/12] fix(ci): isolate git configuration using GIT_CONFIG_GLOBAL Using local 'git config' failed in CI environments where actions/checkout downloads a ZIP instead of cloning (e.g. containers without git). Switched to using 'git config --global' but redirected to a workspace-local file via GIT_CONFIG_GLOBAL to maintain host isolation while satisfying git's requirement for a configuration target. --- .github/workflows/ci.yml | 18 ++++++++++++------ CHANGELOG.md | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1b5916..79ea34e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,12 @@ jobs: - name: Configure Git User run: | - git config user.email "ci@git-cms.local" - git config user.name "CI Bot" - git config init.defaultBranch main + # Redirect global git config to a file in the workspace to avoid polluting the host system + echo "GIT_CONFIG_GLOBAL=${{ github.workspace }}/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="${{ github.workspace }}/.gitconfig_ci" + git config --global user.email "ci@git-cms.local" + git config --global user.name "CI Bot" + git config --global init.defaultBranch main - name: Run Local Integration Tests run: npm run test:local @@ -66,9 +69,12 @@ jobs: - name: Configure Git User run: | - git config user.email "ci@git-cms.local" - git config user.name "CI Bot" - git config init.defaultBranch main + # Redirect global git config to a file in the workspace to avoid polluting the host system + echo "GIT_CONFIG_GLOBAL=${{ github.workspace }}/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="${{ github.workspace }}/.gitconfig_ci" + git config --global user.email "ci@git-cms.local" + git config --global user.name "CI Bot" + git config --global init.defaultBranch main - name: Install Dependencies run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index c925e4c..2330d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to git-cms are documented in this file. ### Fixed -- **(Security) Git identity leakage:** Removed `git config --global` from CI workflow (`.github/workflows/ci.yml`). Scripts now use local repository configuration to prevent accidental modification of host global settings if workflows are executed locally (e.g., via `act`). +- **(Security) Git identity leakage:** Removed `git config --global` from host-level modification in CI workflow (`.github/workflows/ci.yml`). Scripts now use an isolated global config file via `GIT_CONFIG_GLOBAL` redirected to the workspace, preventing accidental modification of host global settings if workflows are executed locally (e.g., via `act`). - `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` - `QUICK_REFERENCE.md`: state machine diagram now shows draft→reverted transition and fixes reverted block semantics - `QUICK_REFERENCE.md`: state derivation rule clarified — "draft ref only" requires no `Status` trailer or `Status: draft` From 0d98b9357b59806549a30a788295ad62ad13db79 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 14 Feb 2026 22:43:59 -0800 Subject: [PATCH 11/12] docs: address PR review feedback - Refined QUICK_REFERENCE.md state machine diagram and derivation rules - Updated QUICK_REFERENCE.md HTTP API table with canonical optional notation - Clarified 'revert' command behavior in QUICK_REFERENCE.md - Updated GETTING_STARTED.md migration section with better bullet separation - Hardened check-doc-drift.sh recursive search and regex precision - Updated documentation validation banners to v1.1.5 - Updated CHANGELOG.md with v1.1.5 improvements --- CHANGELOG.md | 7 +++++-- QUICK_REFERENCE.md | 8 ++++---- docs/GETTING_STARTED.md | 8 ++++---- scripts/check-doc-drift.sh | 9 +++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2330d01..71a91be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,13 @@ All notable changes to git-cms are documented in this file. - **(Security) Git identity leakage:** Removed `git config --global` from host-level modification in CI workflow (`.github/workflows/ci.yml`). Scripts now use an isolated global config file via `GIT_CONFIG_GLOBAL` redirected to the workspace, preventing accidental modification of host global settings if workflows are executed locally (e.g., via `act`). - `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` -- `QUICK_REFERENCE.md`: state machine diagram now shows draft→reverted transition and fixes reverted block semantics +- `QUICK_REFERENCE.md`: state machine diagram refined to accurately show `draft`→`reverted` transition +- `QUICK_REFERENCE.md`: HTTP API table uses canonical `optional` notation and clarifies optimistic concurrency for `publish` +- `docs/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 links +- `check-doc-drift.sh`: improved regex for CLI and API matching to prevent substring false positives and support underscores/digits - `QUICK_REFERENCE.md`: state derivation rule clarified — "draft ref only" requires no `Status` trailer or `Status: draft` - `QUICK_REFERENCE.md`: `<40-hex>` replaced with `` for hash-format-agnostic docs -- `QUICK_REFERENCE.md`: `{ slug, sha? }` replaced with explicit `sha (optional)` notation - `docs/GETTING_STARTED.md`: migration walkthrough clarifies no-dry-run vs idempotency - `check-doc-drift.sh`: API endpoint matching uses backtick-delimited grep (prevents substring false positives) - `check-doc-drift.sh`: deleted-file search recurses into docs subdirectories diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index b0f376b..4d2b717 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -42,7 +42,7 @@ All 9 commands available via `node bin/git-cms.js ` or `git cms "Title"` | Create or update a draft (reads body from stdin) | | `publish` | `git cms publish ` | Fast-forward published ref to match draft | | `unpublish` | `git cms unpublish ` | Remove from published, keep as unpublished draft | -| `revert` | `git cms revert ` | Revert article to reverted state (draft ref with `Status: reverted`) | +| `revert` | `git cms revert ` | Move article to 'reverted' state (creates new draft commit with `Status: reverted` trailer) | | `list` | `git cms list` | List all draft articles | | `show` | `git cms show ` | Print article title and body | | `serve` | `git cms serve` | Start HTTP API + Admin UI on port 4638 | @@ -65,7 +65,7 @@ node bin/git-cms.js publish my-slug # Unpublish node bin/git-cms.js unpublish my-slug -# Revert to draft +# Revert to 'reverted' state node bin/git-cms.js revert my-slug # Read article content @@ -96,8 +96,8 @@ All endpoints are served by `git cms serve` (default port 4638). Slugs are NFKC- |--------|------|---------------|-------------| | `GET` | `/api/cms/list` | `?kind=articles\|published` | List articles by kind | | `GET` | `/api/cms/show` | `?slug=xxx&kind=articles` | Read article content | -| `POST` | `/api/cms/snapshot` | `{ slug, title, body, trailers? }` | Create or update a draft | -| `POST` | `/api/cms/publish` | `{ slug, sha (optional) }` | Publish a draft | +| `POST` | `/api/cms/snapshot` | `{ slug, title, body, trailers (optional) }` | Create or update a draft | +| `POST` | `/api/cms/publish` | `{ slug, sha (optional) }` | Publish a draft (omitting `sha` uses optimistic concurrency) | | `POST` | `/api/cms/unpublish` | `{ slug }` | Unpublish an article | | `POST` | `/api/cms/revert` | `{ slug }` | Revert to draft state | | `GET` | `/api/cms/history` | `?slug=xxx&limit=50` | List version history (max 200) | diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index fe96eed..d600888 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -185,10 +185,10 @@ node bin/git-cms.js migrate ``` Things to know about migrations: -- **Idempotent:** Safe to run multiple times — already-applied migrations are skipped -- **Forward-only:** Rollback is not supported (by design) -- **No dry-run mode:** Use `git cms layout-version` to check whether migrations are pending before running -- **Backup first:** Run `git clone --mirror ` before migrating production repos +- **Idempotent:** Safe to run multiple times — already-applied migrations are skipped. If no structural changes exist between repo and codebase, `node bin/git-cms.js migrate` will be a no-op. +- **Forward-only:** Rollback is not supported (by design). +- **No dry-run mode:** The tool does not provide a preview mode to simulate changes. Use `node bin/git-cms.js layout-version` to check whether a migration is pending. +- **Backup first:** Run `git clone --mirror ` before migrating production repos. --- diff --git a/scripts/check-doc-drift.sh b/scripts/check-doc-drift.sh index d4066d5..2e29e8f 100755 --- a/scripts/check-doc-drift.sh +++ b/scripts/check-doc-drift.sh @@ -45,8 +45,8 @@ done deleted_files=("REPO_WALKTHROUGH.md") for file in "${deleted_files[@]}"; do - # Search all markdown files except the stub itself, QUICK_REFERENCE (documents the removal), and CHANGELOG (historical entries) - offenders=$(grep -rl --include='*.md' "$file" "$ROOT" "$ROOT"/docs 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) + # Search all markdown files recursively except the stub itself, QUICK_REFERENCE (documents the removal), and CHANGELOG (historical entries) + offenders=$(grep -rl --include='*.md' "$file" "$ROOT" 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) if [ -n "$offenders" ]; then echo "DRIFT: Deleted file '$file' still referenced in: $offenders" errors=$((errors + 1)) @@ -54,8 +54,9 @@ for file in "${deleted_files[@]}"; do done # ── 4. Root GETTING_STARTED.md links from docs/ ───────────────────────────── -# docs/ files should link to docs/GETTING_STARTED.md, not the root redirect stub -root_gs_links=$(grep -rnE '\[.*\]\((\.\.\/)?GETTING_STARTED\.md\)' "$ROOT"/docs/ --include='*.md' 2>/dev/null || true) +# docs/ files should link to docs/GETTING_STARTED.md, not the root redirect stub. +# This search is recursive to catch subdirectories like docs/operations/ +root_gs_links=$(grep -rnE '\[.*\]\((\.\.\/)*GETTING_STARTED\.md\)' "$ROOT/docs" --include='*.md' 2>/dev/null || true) if [ -n "$root_gs_links" ]; then echo "DRIFT: docs/ files link to root GETTING_STARTED.md instead of docs/GETTING_STARTED.md:" echo "$root_gs_links" From 1f262549d8ecdb37fa9b216752a5dcc82250a605 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 14 Feb 2026 22:47:26 -0800 Subject: [PATCH 12/12] fix(ci): robust git isolation and early installation 1. Redirected GIT_CONFIG_GLOBAL to /tmp and added 'touch' to ensure writeability and prevent lock file errors. 2. Moved 'Install Git' to the top of the e2e-test job to ensure 'actions/checkout' creates a proper .git directory. --- .github/workflows/ci.yml | 28 ++++++++++++++-------------- CHANGELOG.md | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79ea34e..7d466a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,11 @@ jobs: - name: Configure Git User run: | - # Redirect global git config to a file in the workspace to avoid polluting the host system - echo "GIT_CONFIG_GLOBAL=${{ github.workspace }}/.gitconfig_ci" >> $GITHUB_ENV - export GIT_CONFIG_GLOBAL="${{ github.workspace }}/.gitconfig_ci" + # Redirect global git config to a file in /tmp to avoid polluting the host system + # and ensure we have write permissions regardless of the workspace setup. + touch /tmp/.gitconfig_ci + echo "GIT_CONFIG_GLOBAL=/tmp/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="/tmp/.gitconfig_ci" git config --global user.email "ci@git-cms.local" git config --global user.name "CI Bot" git config --global init.defaultBranch main @@ -56,22 +58,20 @@ jobs: container: image: mcr.microsoft.com/playwright:v1.57.0-jammy steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - # Git is required in the container to perform plumbing operations during E2E tests + # Git is required in the container to perform plumbing operations during E2E tests. + # Installing it BEFORE checkout ensures actions/checkout performs a real clone. - name: Install Git run: apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* + + - uses: actions/checkout@v4 - name: Configure Git User run: | - # Redirect global git config to a file in the workspace to avoid polluting the host system - echo "GIT_CONFIG_GLOBAL=${{ github.workspace }}/.gitconfig_ci" >> $GITHUB_ENV - export GIT_CONFIG_GLOBAL="${{ github.workspace }}/.gitconfig_ci" + # Redirect global git config to a file in /tmp to avoid polluting the host system + # and ensure we have write permissions regardless of the workspace setup. + touch /tmp/.gitconfig_ci + echo "GIT_CONFIG_GLOBAL=/tmp/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="/tmp/.gitconfig_ci" git config --global user.email "ci@git-cms.local" git config --global user.name "CI Bot" git config --global init.defaultBranch main diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a91be..69ec5bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to git-cms are documented in this file. ### Fixed -- **(Security) Git identity leakage:** Removed `git config --global` from host-level modification in CI workflow (`.github/workflows/ci.yml`). Scripts now use an isolated global config file via `GIT_CONFIG_GLOBAL` redirected to the workspace, preventing accidental modification of host global settings if workflows are executed locally (e.g., via `act`). +- **(Security) Git identity leakage:** Removed `git config --global` from host-level modification in CI workflow (`.github/workflows/ci.yml`). Scripts now use an isolated global config file via `GIT_CONFIG_GLOBAL` redirected to `/tmp`, preventing accidental modification of host global settings if workflows are executed locally (e.g., via `act`). - `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` - `QUICK_REFERENCE.md`: state machine diagram refined to accurately show `draft`→`reverted` transition - `QUICK_REFERENCE.md`: HTTP API table uses canonical `optional` notation and clarifies optimistic concurrency for `publish`