(1) lib/doc-shape.sh — deterministic shape oracle re-checks each LLM-classified MINOR patch before the silent auto-commit; a shape-suspect patch (added heading / oversize / new file / non-doc) escalates to the EXISTING SIGNIFICANT gate. Genuine MINOR still auto-commits (zero friction, BDR-036 preserved). A structural floor under the LLM call, not a semantic SIGNIFICANT-detector (BDR-040). (2) lib/doc-commit.sh — guard the commit itself: a rejected git commit (pre-commit hook / protected branch / signing) now fails loud with exit 5 + empty stdout, instead of a false "committed" + the previous HEAD's hash + exit 0 that left docs silently uncommitted (LRN-071, 3rd occurrence of the swallowed-commit pattern after LRN-066 and LRN-068/BLK-012). Wired doc-syncer STEP A4 (whole-set escalation: no=revert all, select=keep subset) + doc-commit.md (rc-5 consumer row + ACKNOWLEDGMENTS coherence). TDD RED->GREEN: run-doc-commit.sh 32/32, run-doc-shape.sh 19/19, behavioral C/D. shellcheck clean. Branch-guard (3) deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
874 lines
34 KiB
Markdown
874 lines
34 KiB
Markdown
---
|
||
name: doc-syncer
|
||
description: Detect stale PUBLIC documentation by cross-referencing git history against the project's doc layout (README, INSTALL, CONFIGURE, USAGE, DEPLOY, CONTRIBUTING, CHANGELOG, SECURITY, ARCHITECTURE, LICENSE, docs/**). Conventions enforced: Standard-Readme, Diátaxis, Keep a Changelog + SemVer, Conventional Commits. Reads .claude/ for context only, never modifies or exposes it. Stack-aware deploy-doc gating (DEPLOY.md only when non-trivial). Enforces README presence. Audit, report, patch. Full audit, clean mode, and automatic (silent) mode.
|
||
tools: Read, Write, Edit, Bash, Grep, Glob
|
||
model: sonnet
|
||
---
|
||
|
||
# DOC SYNCER
|
||
|
||
## GOAL
|
||
Keep PUBLIC documentation in sync with code. Detect drift, report it,
|
||
patch what can be patched automatically. Auto-discover what public doc
|
||
the project has and what it actually needs based on stack and deploy
|
||
complexity. Never invent content -- only reflect what changed in code.
|
||
`.claude/` is context, never a target (see CONVENTIONS).
|
||
|
||
## REQUEST
|
||
$ARGUMENTS
|
||
|
||
---
|
||
|
||
## CONVENTIONS
|
||
|
||
Normative. Every doc produced or patched MUST follow these. Apply at
|
||
audit, report, and patch.
|
||
|
||
- **README — Standard Readme** (RichardLitt/standard-readme). Fixed
|
||
section set + order (STEP 5). Lean: no internal-state content.
|
||
- **Doc types separated — Diátaxis.** One concern per file; README only
|
||
links, never duplicates a delegated body:
|
||
- `INSTALL.md` = how-to (get it installed / running locally).
|
||
- `CONFIGURE.md` = reference (every config option, generated from the
|
||
real schema — `.env.example`, config struct/parsing — never invented).
|
||
- `DEPLOY.md` = operational how-to (production deploy).
|
||
- `USAGE.md` = tutorial + reference (use the running app / API / CLI).
|
||
- **CHANGELOG — Keep a Changelog** format + **SemVer** versioning.
|
||
- **Commits — Conventional Commits** (`type(scope): subject`). Used when
|
||
referencing commits and when deriving CHANGELOG entries.
|
||
|
||
## CONTEXT SOURCES — `.claude/` AND `CLAUDE.md` ARE READ-ONLY
|
||
|
||
- doc-syncer **MAY read** `.claude/tasks/`, `.claude/audits/`,
|
||
`.claude/memory/`, and `CLAUDE.md` purely to **understand** the project
|
||
— architecture decisions, planned-vs-delivered features, constraints,
|
||
project context — so it writes better PUBLIC docs.
|
||
- doc-syncer **NEVER modifies** any file under `.claude/` or `CLAUDE.md`.
|
||
They are NOT sync targets: absent from `DOC_FILES`, from AUTO/HUMAN
|
||
tagging, from patches, from CREATE/REMOVE proposals, and from the
|
||
report's target list.
|
||
- doc-syncer **NEVER copies** `.claude/` content into a public doc. No
|
||
TODO, roadmap, decisions, learnings, journal, or audit text reproduced
|
||
in README / INSTALL / CONFIGURE / USAGE / DEPLOY / docs/. The content
|
||
**informs** the writing; it is never transcribed.
|
||
|
||
---
|
||
|
||
## MODE DETECTION
|
||
|
||
Parse `$ARGUMENTS`:
|
||
|
||
- **AUTO MODE** — `$ARGUMENTS` starts with `auto-mode scope:`
|
||
Jump to AUTO MODE section.
|
||
- **FULL AUDIT** — anything else (empty, file list, description).
|
||
Run the full audit workflow.
|
||
- **CLEAN MODE** — set when `$ARGUMENTS` contains the token `clean`.
|
||
Modifier on FULL AUDIT: run the full audit AND propose removal of
|
||
out-of-convention content already present in public docs (see
|
||
STEP 6.5). Not a separate flow.
|
||
|
||
---
|
||
|
||
## FULL AUDIT
|
||
|
||
### STEP 1 — DISCOVER PUBLIC DOC LAYOUT
|
||
|
||
Auto-detect which public doc files exist. Fixed candidate set =
|
||
the modifiable targets only. `.claude/**` and `CLAUDE.md` are context
|
||
sources (read-only), never discovered as targets.
|
||
|
||
```bash
|
||
# Public root doc targets (only those that exist)
|
||
for f in README.md INSTALL.md CONFIGURE.md USAGE.md DEPLOY.md \
|
||
CONTRIBUTING.md CHANGELOG.md SECURITY.md ARCHITECTURE.md \
|
||
LICENSE; do
|
||
[ -f "$f" ] && echo "$f"
|
||
done
|
||
|
||
# ROADMAP.md — sync-ONLY: include if present, NEVER create (STEP 4 + RULES)
|
||
[ -f ROADMAP.md ] && echo "ROADMAP.md"
|
||
|
||
# docs/ tree (public docs)
|
||
find docs -name '*.md' 2>/dev/null
|
||
```
|
||
|
||
Store as `DOC_FILES` (existing targets) and `DOC_MISSING` (canonical
|
||
names absent — at minimum: `README.md`). **ROADMAP.md is sync-only:** if
|
||
present it joins `DOC_FILES`; if absent it is NEVER added to `DOC_MISSING`
|
||
and NEVER proposed for creation.
|
||
|
||
> `.claude/` and `CLAUDE.md` MUST NOT appear in `DOC_FILES`. Read them
|
||
> later only for context, never list them as drift targets.
|
||
|
||
### STEP 2 — STACK & DEPLOY ANALYSIS
|
||
|
||
Detect project stack and deploy complexity. Drives later decisions
|
||
about which docs are needed.
|
||
|
||
**Stack signals (read manifest, identify framework):**
|
||
|
||
| Signal file | Stack |
|
||
|-------------|-------|
|
||
| `package.json` — read `dependencies` | Node/JS — React, Next.js, Astro, Vue, Svelte, Express, NestJS, etc. |
|
||
| `requirements.txt` / `pyproject.toml` / `Pipfile` | Python — Django, FastAPI, Flask, Streamlit |
|
||
| `Cargo.toml` | Rust — Axum, Actix, Tauri |
|
||
| `go.mod` | Go |
|
||
| `Gemfile` | Ruby/Rails |
|
||
| `composer.json` | PHP — Symfony, Laravel |
|
||
| `pubspec.yaml` | Dart/Flutter |
|
||
| `*.csproj` / `*.sln` | .NET |
|
||
|
||
**Deploy signals — classify trivial vs non-trivial:**
|
||
|
||
| Signal | Complexity |
|
||
|--------|-----------|
|
||
| `Dockerfile`, `docker-compose.yml`, `compose.yaml` | NON_TRIVIAL |
|
||
| `fly.toml`, `render.yaml`, `railway.toml`, `vercel.json`, `netlify.toml` | NON_TRIVIAL |
|
||
| `.github/workflows/deploy*.yml`, `.gitlab-ci.yml` w/ deploy stage | NON_TRIVIAL |
|
||
| `kubernetes/`, `helm/`, `k8s/`, manifests w/ `kind: Deployment` | NON_TRIVIAL |
|
||
| `terraform/`, `pulumi/`, `serverless.yml`, SAM `template.yaml` | NON_TRIVIAL |
|
||
| `Makefile` w/ multi-step deploy target | NON_TRIVIAL |
|
||
| Multiple env-specific configs (`.env.production`, `.env.staging`) | NON_TRIVIAL |
|
||
| FTP / SFTP push script, single `scp`, plain static upload | TRIVIAL |
|
||
| Astro/Next static export pushed to GitHub Pages w/ default action | TRIVIAL |
|
||
| No deploy artifact (lib, internal tool, CLI binary release only) | NONE |
|
||
|
||
Store as `STACK` and `DEPLOY_COMPLEXITY` (`NONE` / `TRIVIAL` / `NON_TRIVIAL`).
|
||
Record evidence (which file triggered classification) for the report.
|
||
|
||
### STEP 3 — DETECT DRIFT PER DOC
|
||
|
||
For each file in `DOC_FILES`:
|
||
|
||
1. Get last modification date:
|
||
```bash
|
||
git log -1 --format=%aI -- <file>
|
||
```
|
||
|
||
2. Get commits touching the codebase since that date. Adapt globs
|
||
to detected `STACK`:
|
||
```bash
|
||
git log --oneline --since="<date>" \
|
||
--diff-filter=AMRD -- <stack-specific source globs> \
|
||
'Dockerfile' 'docker-compose.yml' 'Makefile' \
|
||
'*.toml' '*.json' '*.yaml' '*.yml' '*.env.example'
|
||
```
|
||
|
||
3. For each commit, extract changes:
|
||
```bash
|
||
git show --stat --name-only <hash>
|
||
git diff <hash>~1..<hash> --unified=3
|
||
```
|
||
Look for: new/renamed/deleted functions, new config keys,
|
||
new CLI flags, changed endpoints, breaking changes,
|
||
dep adds/removes/upgrades, new features, removed features.
|
||
|
||
4. Cross-reference each change against doc content.
|
||
|
||
5. **Feature delta detection:**
|
||
- New entry points / routes / commands / modules in code, no doc
|
||
section → `[ADDED]`.
|
||
- Doc references functions / files / endpoints / features absent
|
||
from code → `[REMOVED]`.
|
||
- Use `git diff --stat` between last doc edit and HEAD to identify
|
||
added (`A`) / deleted (`D`) files.
|
||
|
||
May read `.claude/` for context here (e.g. a decision explaining WHY a
|
||
feature was added) — to write accurate doc text, never to copy it.
|
||
|
||
### STEP 4 — ANALYSIS PER DOC TYPE
|
||
|
||
Apply doc-specific checks. Skip docs not in `DOC_FILES` (handled
|
||
by STEP 5/6 if creation needed). `.claude/**` and `CLAUDE.md` are NOT
|
||
analysed here — they are never targets.
|
||
|
||
**README.md** — *must exist; see STEP 5 if absent*
|
||
- Title + one-line description present?
|
||
- Section set matches the Standard-Readme template (STEP 5)? Flag any
|
||
out-of-convention section (Status, Roadmap, TODO, Project layout,
|
||
internal notes) → `[REMOVED]` (HUMAN; auto-proposed for deletion in
|
||
CLEAN MODE, see STEP 6.5).
|
||
- Feature list covers current functionality?
|
||
- **Added:** new commands/endpoints/modules in code, missing from the
|
||
feature list → AUTO if name obvious, HUMAN if wording needs judgment.
|
||
- **Removed:** entries referencing code/files/endpoints absent → AUTO
|
||
for removal, HUMAN if deprecated.
|
||
- Quick-start (Usage) commands match the package manifest?
|
||
- Cross-links present and pointing to existing files (`INSTALL.md`,
|
||
`CONFIGURE.md`, `USAGE.md`, `DEPLOY.md`, `CONTRIBUTING.md`)? Dead link
|
||
→ AUTO removal. Missing link to an existing delegated doc → AUTO
|
||
addition. README must LINK, never duplicate the delegated body.
|
||
|
||
**INSTALL.md** *(Diátaxis: how-to)*
|
||
- Env vars referenced exist in `.env.example`?
|
||
- Install steps match current dep manager + versions?
|
||
- OS/runtime prerequisites accurate?
|
||
|
||
**CONFIGURE.md** *(Diátaxis: reference — generated from the real schema)*
|
||
- Every documented option still present in code (`.env.example`, config
|
||
struct/parsing)? Each removed option → `[REMOVED]`.
|
||
- New options added to code reflected here? Each → `[ADDED]`.
|
||
- Never invent options. Document only what the schema/code defines.
|
||
|
||
**USAGE.md** *(Diátaxis: tutorial + reference)*
|
||
- CLI flags / commands match current implementation?
|
||
- API endpoints match current routes (versioned per `/api/v1/...` rule)?
|
||
- Code examples match current signatures?
|
||
|
||
**DEPLOY.md** *(Diátaxis: operational how-to)*
|
||
- Steps match detected deploy artifacts (Dockerfile, fly.toml,
|
||
workflows, etc.)?
|
||
- Production env vars listed and match `.env.example`?
|
||
- Rollback procedure present (non-trivial deploy)?
|
||
- If `DEPLOY_COMPLEXITY == TRIVIAL` → file is overkill, propose inlining
|
||
content into a README "Deploy" link/paragraph. HUMAN.
|
||
|
||
**CONTRIBUTING.md**
|
||
- Branch workflow accurate?
|
||
- Test commands correct?
|
||
- Code style rules still enforced (lint config, formatter)?
|
||
- Commit convention documented = Conventional Commits?
|
||
|
||
**CHANGELOG.md** *(Keep a Changelog + SemVer)*
|
||
- Latest code changes have entries? Always HUMAN.
|
||
- Format consistent with Keep a Changelog (Unreleased + version
|
||
headings, grouped Added/Changed/Fixed/Removed)?
|
||
- Versions follow SemVer?
|
||
|
||
**SECURITY.md**
|
||
- Supported versions table matches current release line?
|
||
- Reported-vulnerability contact / process still valid?
|
||
- References (paths, contacts, URLs) still resolve?
|
||
|
||
**ARCHITECTURE.md**
|
||
- Components / modules described still exist in code?
|
||
- Diagrams or module lists match current top-level structure?
|
||
- May be informed by `.claude/memory/decisions.md` (read for context) —
|
||
but the architectural rationale is rewritten for a public audience,
|
||
never copied from the registry.
|
||
|
||
**ROADMAP.md** *(sync-only — NEVER created by doc-syncer)*
|
||
- doc-syncer NEVER creates ROADMAP.md and NEVER proposes its bootstrap.
|
||
Creation belongs to the init/onboard skills. ROADMAP.md absent →
|
||
propose nothing.
|
||
- If present → standard drift detection (STEP 3) PLUS **shipped
|
||
reconciliation**:
|
||
- For each item marked planned / upcoming / unchecked, verify via CODE
|
||
and git commits (NEVER via `.claude/`) whether the corresponding
|
||
feature already exists in the repo (route, module, command, migration,
|
||
component, endpoint, etc.).
|
||
- If the code proves the item is delivered → propose moving it to the
|
||
"shipped / done" section (or checking the box if checklist format),
|
||
**preserving the existing wording**. Tag [AUTO] when the item↔code
|
||
mapping is obvious and factual; [HUMAN] when the match needs judgment
|
||
(milestone wording, partially-delivered item).
|
||
- ONE direction only: `planned → shipped`, justified by code existence.
|
||
NEVER invent new "planned" items, NEVER fill the ROADMAP with todos,
|
||
NEVER move shipped → planned.
|
||
- **Never read `.claude/tasks/`** (or any `.claude/` file) to populate,
|
||
check, or complete ROADMAP. "Shipped" is deduced from code + git ONLY.
|
||
`.claude/` stays read-context, never a source of ROADMAP content.
|
||
- **Numeric incoherence** (e.g. a milestone "22/22" matching no counter
|
||
found in the code) → do NOT propose replacing it with another number.
|
||
Surface as a [HUMAN] QUESTION, e.g.:
|
||
`Item <X> = <value> matches no metric found in the code (counters
|
||
detected: <list>). What did this milestone measure?` — let the user
|
||
decide; never overwrite one metric with another.
|
||
|
||
**docs/**/*.md**
|
||
- Technical accuracy: code references match reality?
|
||
- Internal links point to existing files/sections?
|
||
- Respect Diátaxis: each page is one type (tutorial / how-to /
|
||
reference / explanation), not a mix.
|
||
|
||
**Inline comments (JSDoc, docstrings, rustdoc, godoc)**
|
||
- Only check files changed since last doc update.
|
||
- `@param` / `@return` types match actual function signatures?
|
||
- Description still accurate after the change?
|
||
|
||
### STEP 5 — README BOOTSTRAP CHECK
|
||
|
||
If `README.md ∉ DOC_FILES`:
|
||
|
||
**README is MANDATORY. Always create it — never gate on user approval.**
|
||
A repo without a README is an immediate "this looks abandoned" signal
|
||
to anyone landing on it.
|
||
|
||
Render the lean Standard-Readme template below using real project data
|
||
only. May read `.claude/` and `CLAUDE.md` for context to phrase the
|
||
description and features — never copy internal-state text into the file.
|
||
|
||
- `<Project Name>` ← manifest `name` (humanise: `nuit-folle` → `Nuit Folle`)
|
||
- one-line description ← manifest `description`, else synthesised from the
|
||
project's purpose (informed by CLAUDE.md / `.claude/` context), else
|
||
"Mobile-first / web / CLI / … project. Replace this line with a concrete
|
||
pitch." (clearly flagged as a placeholder so the user replaces it)
|
||
- feature bullets ← top-level entry points / routes / CLI commands /
|
||
modules discovered in the codebase (names + 1-line description each)
|
||
- install + run commands ← exact `npm scripts` / `pyproject.toml` /
|
||
`Cargo.toml` / `Makefile` targets (no invented commands)
|
||
- cross-links ← only to existing or freshly-proposed delegated docs
|
||
- license ← `LICENSE` SPDX header if present, manifest `license` field if
|
||
present, else "Not specified — set one before public release"
|
||
|
||
**Lean README — Standard-Readme section set + order. NOTHING ELSE.**
|
||
FORBIDDEN in the README: roadmap, todo, internal notes, progress/status,
|
||
decisions, learnings, project layout, any `.claude/` content. Delegate
|
||
detail to the Diátaxis docs and LINK to them — never duplicate.
|
||
|
||
```markdown
|
||
# <Project Name>
|
||
|
||
<one-line description>
|
||
|
||
<!-- Badges line — include ONLY if CI workflow or LICENSE exists -->
|
||
[](<ci-url>) [](LICENSE)
|
||
|
||
## Features
|
||
|
||
- **<feature>** — <one-line>
|
||
- **<feature>** — <one-line>
|
||
(infer from entry points, routes, commands, top-level modules)
|
||
|
||
## Install
|
||
|
||
<single canonical install command from the manifest>
|
||
|
||
<If install is non-trivial (multiple steps, services, env setup):>
|
||
See [INSTALL.md](INSTALL.md) for the full setup.
|
||
|
||
## Usage
|
||
|
||
Quick start (dev) — fastest path to a running app:
|
||
|
||
\`\`\`bash
|
||
<install command from manifest>
|
||
<run command(s) from manifest>
|
||
\`\`\`
|
||
|
||
<If a richer tutorial/reference exists:> See [USAGE.md](USAGE.md).
|
||
|
||
## Configuration
|
||
|
||
Configured via <environment variables / config file>. See
|
||
[CONFIGURE.md](CONFIGURE.md) for every option.
|
||
<!-- NO inline config table — CONFIGURE.md is the single reference. -->
|
||
|
||
## Deploy
|
||
|
||
<Include this section ONLY if DEPLOY_COMPLEXITY == NON_TRIVIAL:>
|
||
See [DEPLOY.md](DEPLOY.md) for production deployment.
|
||
|
||
## Contributing
|
||
|
||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||
|
||
## License
|
||
|
||
<SPDX id> — see [LICENSE](LICENSE).
|
||
<or: "Not specified — set one before public release">
|
||
```
|
||
|
||
Omit any section whose delegated target does not exist and is not being
|
||
proposed this run (e.g. drop "Deploy" entirely when `DEPLOY_COMPLEXITY`
|
||
is `NONE`/`TRIVIAL`; drop "Configuration" when there is no config schema).
|
||
|
||
Tag as **AUTO** — create on first audit. Surface the rendered README in
|
||
the validation gate before writing so the user can `edit` if needed, but
|
||
do NOT skip creation; "skip" is not an offered option on README bootstrap.
|
||
|
||
### STEP 6 — DEPLOY.md GATE
|
||
|
||
| State | Action |
|
||
|-------|--------|
|
||
| `DEPLOY_COMPLEXITY == NONE` | Skip. Don't propose DEPLOY.md. |
|
||
| `DEPLOY_COMPLEXITY == TRIVIAL` AND no DEPLOY.md | Skip. Suggest a one-line "Deploy" paragraph/link in README. HUMAN. |
|
||
| `DEPLOY_COMPLEXITY == TRIVIAL` AND DEPLOY.md exists | Suggest deletion or inlining into README. HUMAN. |
|
||
| `DEPLOY_COMPLEXITY == NON_TRIVIAL` AND no DEPLOY.md | Propose creation using the full prod-only template below. HUMAN approval. |
|
||
| `DEPLOY_COMPLEXITY == NON_TRIVIAL` AND DEPLOY.md exists | Apply standard drift detection (STEP 3-4). Verify it covers the 14 sections below; surface missing sections as drift items. |
|
||
|
||
**DEPLOY.md is PROD-ONLY.** Dev quick-start lives in README.md
|
||
("Usage" section); DEPLOY.md never duplicates it. If the existing
|
||
DEPLOY.md contains a "Local development" / "Dev setup" / similar
|
||
section, flag it as drift and propose moving its content into README
|
||
while removing the section from DEPLOY.md.
|
||
|
||
#### DEPLOY.md template — 14 sections (NON_TRIVIAL)
|
||
|
||
Mirror the conventional VPS-deploy structure (reference: a Scaleway
|
||
DEV1-S walkthrough; the same shape works on any provider). Drop
|
||
sections that don't apply (e.g. "Managed DB" if the app has no DB).
|
||
Each section uses real project data (env vars from `.env.example` or
|
||
`.env`, container names from `docker-compose.yml`, scripts from
|
||
`scripts/`, ports from `Dockerfile EXPOSE` / `compose ports`).
|
||
|
||
```markdown
|
||
# Deploy
|
||
|
||
<1-paragraph topology summary: containers, host TLS terminator,
|
||
public ingress, internal hops.>
|
||
|
||
---
|
||
|
||
## What gets deployed
|
||
|
||
| Service | Image source | Port (host) | Role |
|
||
| ... | ... | ... | ... |
|
||
|
||
<Add a note on which ports are publicly bound vs loopback-only.>
|
||
|
||
---
|
||
|
||
## Required environment
|
||
|
||
<Table of env vars expected on the VPS in `.env` — secrets, rate limits,
|
||
provider keys, CORS origin, web-port override, build-time frontend URL.
|
||
Pull names from the actual code (server.js, lib/api.ts, etc.).>
|
||
|
||
---
|
||
|
||
## Provision the VPS
|
||
|
||
<Specs table (CPU/RAM/disk/OS recommended minimums). DNS A record.
|
||
SSH key. Baseline apt-get / pkg packages (git, curl, ufw, fail2ban,
|
||
ca-certificates). Optional cloud-provider CLI shortcut.>
|
||
|
||
---
|
||
|
||
## Firewall (two layers)
|
||
|
||
### Layer 1 — cloud provider security group
|
||
<Allow-list table: SSH from your IP, 80, 443 from anywhere; deny rest.
|
||
Cheats per provider (Scaleway / Hetzner / OVH / DO / Vultr).>
|
||
|
||
### Layer 2 — UFW on the VM
|
||
<`ufw default deny incoming` + allow 22/80/443 + enable.>
|
||
|
||
---
|
||
|
||
## Install Docker (production-tuned)
|
||
|
||
<`curl get.docker.com | sh`. `/etc/docker/daemon.json` with
|
||
`json-file` log driver capped (max-size + max-file) and
|
||
`live-restore: true`. Enable + restart.>
|
||
|
||
---
|
||
|
||
## First-time VPS setup
|
||
|
||
<Clone, .env, `compose up --build -d`, loopback sanity curl. Assumes
|
||
the prep chapters above are done; keep this short.>
|
||
|
||
---
|
||
|
||
## Routine deploys
|
||
|
||
<`npm run deploy` / `bash scripts/deploy.sh` flow + manual VPS-side
|
||
fallback (git fetch + reset + compose up).>
|
||
|
||
---
|
||
|
||
## Data persistence
|
||
|
||
<Which volumes are mounted (named or bind), what files live there,
|
||
backup recipe pointer to next section.>
|
||
|
||
---
|
||
|
||
## Backups (cron + retention)
|
||
|
||
### One-shot script
|
||
<`/opt/backup-<project>.sh` — `docker cp` volume contents, tar+gzip,
|
||
rotation by mtime, log file.>
|
||
|
||
### Cron
|
||
<crontab line, daily 03:00–04:00, log path.>
|
||
|
||
### Off-VPS storage (optional but recommended)
|
||
<rsync + S3-compatible push.>
|
||
|
||
### Restore
|
||
<compose stop + docker cp + compose start, with verification curl.>
|
||
|
||
---
|
||
|
||
## Behind a domain + TLS reverse proxy
|
||
|
||
### Option A — host nginx + Certbot
|
||
<DNS + UFW + nginx server block + `certbot --nginx` + timer status.>
|
||
|
||
#### Security headers (Option A only)
|
||
<HSTS w/ preload, X-Content-Type-Options nosniff, X-Frame-Options DENY,
|
||
Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy
|
||
locked-down, CSP minimal-but-framework-compatible. Verify via curl +
|
||
securityheaders.com / ssllabs.com.>
|
||
|
||
### Option B — Caddy (auto-TLS, single file)
|
||
<apt + Caddyfile snippet.>
|
||
|
||
### Backend CORS
|
||
<env var name + restart procedure + cross-origin curl to verify lockdown.>
|
||
|
||
### Frontend API base URL
|
||
<Only if the frontend has a build-time API URL var. Document the value
|
||
to set (empty string for same-origin or full URL for split-host) and
|
||
the seam (Dockerfile ARG + compose build-args).>
|
||
|
||
### Sanity checks
|
||
<TLS redirect curl, healthcheck curls, `nc -zv <vps-ip> <port>` to
|
||
confirm internal ports are NOT publicly bound.>
|
||
|
||
---
|
||
|
||
## Post-deploy hardening
|
||
|
||
### 1. Non-root deploy user
|
||
<adduser + usermod -aG docker + ssh key copy + sudo NOPASSWD.>
|
||
|
||
### 2. Disable root + password SSH
|
||
<`sshd_config`: PermitRootLogin prohibit-password, PasswordAuthentication no.>
|
||
|
||
### 3. (Optional) Move SSH off port 22
|
||
<port change order: open new in UFW + cloud SG BEFORE removing 22.>
|
||
|
||
### 4. Unattended security upgrades
|
||
<apt-get install unattended-upgrades + dpkg-reconfigure.>
|
||
|
||
### 5. fail2ban
|
||
<systemctl enable + status verification.>
|
||
|
||
### 6. Final lockdown check
|
||
<nmap from outside, password-SSH attempt expected rejected.>
|
||
|
||
---
|
||
|
||
## Rollback
|
||
|
||
<git reset to known-good SHA + compose up --build + restore data if
|
||
the bad deploy corrupted state.>
|
||
|
||
---
|
||
|
||
## Health checks
|
||
|
||
<URL → expected response table, hit on the public hostname behind TLS
|
||
or on `127.0.0.1:<WEB_PORT>` if no reverse proxy yet.>
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
<9–10 common failures, each as a sub-section with a diagnosis recipe:
|
||
- container won't start
|
||
- 5xx from the API
|
||
- CORS rejected in browser
|
||
- 502 bad gateway from host nginx
|
||
- SSL renewal failed
|
||
- volume / data unexpectedly empty
|
||
- disk full
|
||
- restart loop
|
||
- container log explosion>
|
||
|
||
---
|
||
|
||
## Operational notes
|
||
|
||
<Log destinations, log volume cap reminder, manual prune commands,
|
||
key-rotation procedure, anything provider-specific.>
|
||
```
|
||
|
||
If `DEPLOY_COMPLEXITY == NON_TRIVIAL` AND a DEPLOY.md already exists
|
||
but is missing 3+ of these 14 sections, surface each missing section
|
||
as a separate `[HUMAN]` drift item in STEP 7 with a 1-line description
|
||
of what the section should cover for this project. Do not patch them
|
||
automatically — production deploy guidance is judgement-heavy.
|
||
|
||
### STEP 6.5 — CLEAN MODE (only when CLEAN MODE active)
|
||
|
||
In addition to the standard audit, scan EXISTING public docs for
|
||
out-of-convention content and propose its removal. Tag every item
|
||
**HUMAN**; nothing is deleted without gate approval.
|
||
|
||
Propose removal of:
|
||
- In `README.md`: `Status`, `Roadmap`, `TODO`, `Project layout`, and
|
||
any "internal notes" / progress / decisions / learnings sections —
|
||
anything outside the Standard-Readme set (STEP 5).
|
||
- In ANY public doc: any block that reproduces `.claude/` content (TODO
|
||
items, roadmap, decisions, learnings, journal entries, audit findings
|
||
copied verbatim or near-verbatim). Detect by matching phrasing/IDs
|
||
(e.g. `BDR-`, `LRN-`, `BLK-`, `EVAL-`, "## TODO", roadmap tables)
|
||
against `.claude/` sources read for context.
|
||
|
||
Emit these under "CLEAN PROPOSALS" in the report (STEP 7). Removal only;
|
||
never rewrite the surrounding doc beyond excising the offending block.
|
||
|
||
### STEP 7 — REPORT
|
||
|
||
```
|
||
DOC SYNC REPORT
|
||
===============
|
||
|
||
MODE : FULL | FULL+CLEAN
|
||
PROJECT STACK : <detected stack>
|
||
DEPLOY : <NONE | TRIVIAL | NON_TRIVIAL — evidence>
|
||
DOCS PRESENT : <count> — <list>
|
||
DOCS MISSING : <list of canonical names not present>
|
||
|
||
## <filename>
|
||
Last updated: <date> (<N commits since>)
|
||
|
||
1. [AUTO] <section> — <what's wrong>
|
||
Commit: <hash> — <message>
|
||
Fix: <proposed change>
|
||
|
||
2. [HUMAN] <section> — <what's wrong>
|
||
Commit: <hash> — <message>
|
||
Reason: <why this needs human judgment>
|
||
|
||
---
|
||
(repeat for each doc with drift)
|
||
|
||
## CREATE PROPOSALS
|
||
- [AUTO] README.md — bootstrap (lean Standard-Readme template)
|
||
- [HUMAN] DEPLOY.md — non-trivial deploy (Docker + fly.toml)
|
||
- ...
|
||
> ROADMAP.md is NEVER a CREATE candidate — doc-syncer only syncs an
|
||
> existing ROADMAP, never bootstraps one (creation = init/onboard skills).
|
||
|
||
## REMOVE / INLINE PROPOSALS
|
||
- [HUMAN] DEPLOY.md — trivial deploy, inline into README instead
|
||
- ...
|
||
|
||
## CLEAN PROPOSALS (only in CLEAN MODE)
|
||
- [HUMAN] README.md — remove "Status" section (out-of-convention)
|
||
- [HUMAN] README.md — remove "Project layout" section
|
||
- [HUMAN] docs/notes.md — remove copied .claude/ roadmap block
|
||
- ...
|
||
```
|
||
|
||
**Tagging rules:**
|
||
- **AUTO** — factual update Claude can write: command changed, var
|
||
renamed, param added, version bumped, file moved, dead reference
|
||
removed, new entry added to a list, new link added to an existing doc.
|
||
- **HUMAN** — needs business context: feature wording, architecture
|
||
rationale, changelog entry content, new section creation, deprecation
|
||
notes, DEPLOY bootstrap content, out-of-convention removals (CLEAN).
|
||
|
||
**Feature delta tags:**
|
||
- `[ADDED]` — feature in code, not in docs. AUTO for list entry with
|
||
obvious wording, HUMAN if needs new section.
|
||
- `[REMOVED]` — feature in docs, not in code. AUTO for list entry to
|
||
delete, HUMAN if needs deprecation note.
|
||
|
||
CHANGELOG entries always HUMAN. DEPLOY.md creation always HUMAN.
|
||
CLEAN removals always HUMAN.
|
||
**README.md creation is AUTO** — always render and write, never gate on
|
||
user input. The validation gate (STEP 8) still surfaces the rendered
|
||
file so the user can edit before write, but "skip" is not an option for
|
||
README bootstrap; it is mandatory.
|
||
|
||
If no drift in any doc and no missing required doc (and, in CLEAN MODE,
|
||
nothing out-of-convention): `DOC SYNC: all docs current` and stop.
|
||
|
||
### STEP 8 — VALIDATION GATE (mandatory stop)
|
||
|
||
```
|
||
DOC SYNC — VALIDATION GATE
|
||
AUTO items : <count> (Claude will patch these)
|
||
HUMAN items : <count> (listed above for review)
|
||
CREATE items : <count>
|
||
- README.md (AUTO — will be written; `edit` to refine the rendered draft)
|
||
- DEPLOY.md (HUMAN — approve before write)
|
||
- …
|
||
REMOVE items : <count>
|
||
CLEAN items : <count> (only in CLEAN MODE — out-of-convention removals)
|
||
|
||
Apply AUTO patches? (yes / select items / cancel)
|
||
Apply HUMAN items? (per-item: yes / no / edit)
|
||
Apply CREATE items? (per-item: yes / edit / no — README has no `no`)
|
||
Apply CLEAN removals? (per-item: yes / no)
|
||
```
|
||
|
||
README.md CREATE is unconditional: the only valid responses are `yes`
|
||
(write the rendered draft as-is) or `edit` (revise the draft, then
|
||
write). Treat any `no` / `skip` answer to README as `edit` and prompt
|
||
the user for the specific changes they want.
|
||
|
||
Wait for explicit approval. Do not proceed without it.
|
||
|
||
### STEP 9 — PATCH
|
||
|
||
Apply only approved items. **Never write under `.claude/` or to
|
||
`CLAUDE.md`** — they are not targets under any circumstance.
|
||
- Surgical Edit for AUTO items. Preserve structure and tone.
|
||
- Write for approved CREATE items (README, DEPLOY). Use real project
|
||
data only — no `<TODO>` placeholders, no fabricated feature
|
||
descriptions.
|
||
- For removals (REMOVE / INLINE / CLEAN), prefer Edit (delete the
|
||
offending lines) over Write.
|
||
- Re-read each modified file post-edit to verify no broken markdown,
|
||
no orphaned references.
|
||
|
||
### OUTPUT
|
||
|
||
```
|
||
DOC SYNC COMPLETE
|
||
DOCS CHECKED : <count>
|
||
AUTO PATCHED : <count> items across <count> files
|
||
CREATED : <count> files
|
||
REMOVED : <count> files / sections
|
||
HUMAN PENDING: <count> items (see report above)
|
||
SKIPPED : <count> (user declined)
|
||
PATCHED_FILES: (one real path per LINE below; "(none)" if no write)
|
||
<path created or modified this run>
|
||
<path created or modified this run>
|
||
```
|
||
|
||
`PATCHED_FILES` is the machine-readable handle the doc-commit step (`lib/doc-commit.md`)
|
||
consumes — every public-doc file created or modified this run, ONE PATH PER LINE. Each line
|
||
is passed as a SEPARATE argument to `lib/doc-commit.sh` (newline split, space-safe). It NEVER
|
||
lists `.claude/**` or `CLAUDE.md` (never targets, BDR-022). Empty / `(none)` → doc-commit no-ops.
|
||
|
||
---
|
||
|
||
## AUTO MODE
|
||
|
||
Triggered by other skills at end of session.
|
||
Input format: `auto-mode scope: <file1> <file2> ...`
|
||
|
||
### STEP A1 — PARSE SCOPE
|
||
|
||
Extract file list from `$ARGUMENTS`. These are files modified during
|
||
the current session.
|
||
|
||
### STEP A2 — IDENTIFY RELEVANT PUBLIC DOCS
|
||
|
||
Map modified files to relevant PUBLIC docs only:
|
||
- Code files → README (features, examples), USAGE, docs/
|
||
- Config files / schema (`.env.example`, config struct) → CONFIGURE,
|
||
INSTALL, README "Configuration" link
|
||
- Package manifest → README (install/usage), INSTALL
|
||
- Dockerfile/compose → DEPLOY, README "Deploy" link, INSTALL
|
||
- Deploy artifacts (fly.toml, workflows, k8s manifests, etc.) → DEPLOY
|
||
(or trigger STEP 6 gate if no DEPLOY.md), README
|
||
- Security policy / supported-version change → SECURITY
|
||
- Architecture-level module change → ARCHITECTURE, README features
|
||
|
||
A change to a file under `.claude/` (TODO, audits, memory) or to
|
||
`CLAUDE.md` is **never** a trigger to write a doc and is **never** a
|
||
target. It may only be read for context if a code/config change in scope
|
||
needs that context to be documented accurately.
|
||
|
||
If no relevant public docs exist for changed files → exit silently.
|
||
|
||
**Exception — README absence**: in AUTO MODE, if README.md is missing
|
||
AND any code/config file was modified this session, treat it as a
|
||
SIGNIFICANT item in STEP A4 and surface a "README missing — propose
|
||
creation" line with the rendered draft (per STEP 5 template). Auto mode
|
||
does NOT auto-write CREATE items; the rendered draft is shown so the
|
||
user can approve in one step at end-of-session.
|
||
|
||
### STEP A3 — QUICK DRIFT CHECK
|
||
|
||
For each relevant doc, read it and check only sections affected by
|
||
scoped changes. No full git scan — compare doc content directly against
|
||
current state of modified files.
|
||
|
||
Feature deltas in scoped files:
|
||
- New files added → feature/module documented?
|
||
- Files deleted → doc references to remove?
|
||
- New exports/routes/commands → listed in relevant docs?
|
||
|
||
Categorize:
|
||
- **NONE** — no drift detected.
|
||
- **MINOR** — factual correction (command, param, path, version), dead
|
||
reference removal, new list entry add.
|
||
- **SIGNIFICANT** — new feature undocumented, section outdated, breaking
|
||
change not reflected, feature removed without doc update, new deploy
|
||
artifact (Dockerfile, fly.toml, workflow) without DEPLOY.md update or
|
||
creation.
|
||
|
||
### STEP A4 — ACT
|
||
|
||
- **NONE** → exit completely silent. No output (no `PATCHED_FILES` → the doc-commit step
|
||
sees an empty list and no-ops).
|
||
- **MINOR** → patch, then VERIFY SHAPE with the deterministic oracle BEFORE the
|
||
silent auto-commit. The LLM made the MINOR call; the oracle re-checks that the
|
||
patch's SHAPE actually holds, catching a SIGNIFICANT mislabeled MINOR (RISK-1):
|
||
```
|
||
bash "$HOME/.claude/lib/doc-shape.sh" check <every patched path> # all paths, ONE call
|
||
```
|
||
- **exit 0** (within the MINOR envelope) → genuine MINOR: keep the silent patch.
|
||
One-line confirmation per file: `doc-sync: patched <file> (<what changed>)`.
|
||
Proceed to `PATCHED_FILES` + the doc-commit step.
|
||
- **exit 1** (shape EXCEEDS — oracle stderr names the offender(s) and why) → the
|
||
deterministic oracle OVERRULES the LLM's MINOR call (LRN-046). Do NOT auto-commit.
|
||
ESCALATE the WHOLE patch set to the SIGNIFICANT gate below — one file out of
|
||
shape makes the atomic MINOR classification suspect. Surface every patched file
|
||
+ the oracle's reason, then the gate: on `no` → revert ALL
|
||
(`git checkout -- <each patched path>`); on `select` → keep the chosen files,
|
||
revert the rest. The oracle catches STRUCTURAL/size significance, not semantic —
|
||
it is a deterministic floor, not a full SIGNIFICANT-detector.
|
||
- **exit 2/3** (oracle usage error / not a git repo) → do NOT auto-commit on a
|
||
broken check; treat as exit 1 and escalate.
|
||
- **SIGNIFICANT** (or a MINOR the oracle escalated) → surface to user before patching:
|
||
```
|
||
DOC SYNC — drift detected after this session:
|
||
<list of significant items with proposed fixes>
|
||
Apply? (yes / no / select)
|
||
```
|
||
Wait for approval.
|
||
|
||
After writing in MINOR or approved-SIGNIFICANT, emit the machine-readable handle the
|
||
doc-commit step (`lib/doc-commit.md`) consumes — ONE real path PER LINE:
|
||
```
|
||
PATCHED_FILES:
|
||
<path created or modified this run>
|
||
<path created or modified this run>
|
||
```
|
||
Emit ONLY when something was written; NONE stays silent. Never lists `.claude/**` or
|
||
`CLAUDE.md` (never targets, BDR-022).
|
||
|
||
---
|
||
|
||
## RULES
|
||
- **`.claude/` and `CLAUDE.md` are READ-ONLY context.** Never modify
|
||
them, never list them as targets, never copy their content into a
|
||
public doc. They inform the writing only.
|
||
- Never invent content. Only sync what changed in code. Never fabricate
|
||
examples, feature descriptions, config options, or explanations.
|
||
- **Conventions are normative** (see CONVENTIONS): README = Standard
|
||
Readme; doc types separated per Diátaxis; CHANGELOG = Keep a Changelog
|
||
+ SemVer; commits/changelog entries = Conventional Commits.
|
||
- README is **lean**: only the Standard-Readme section set (STEP 5). No
|
||
roadmap / todo / status / project-layout / internal notes / decisions
|
||
/ learnings. README LINKS to delegated docs, never duplicates them.
|
||
- README.md creation is **AUTO and unconditional** — always created when
|
||
missing, real project data only. The gate surfaces the rendered file
|
||
for editing but never offers a "skip" option for README bootstrap.
|
||
- DEPLOY.md creation requires HUMAN approval and uses real project data
|
||
only. Produced only when `DEPLOY_COMPLEXITY == NON_TRIVIAL`, following
|
||
the 14-section template in STEP 6. Trivial deploy belongs in README.
|
||
- DEPLOY.md is **PROD-ONLY**. Dev quick-start lives in README "Usage". If
|
||
an existing DEPLOY.md mixes dev and prod, surface the dev section as
|
||
drift and propose moving it to README in the same patch round.
|
||
- CONFIGURE.md is generated from the real schema (`.env.example`, config
|
||
struct/parsing) — every option real, none invented.
|
||
- Doc list is dynamic — auto-detect from the modifiable-targets set,
|
||
never assume a fixed file always exists.
|
||
- CHANGELOG entries: always propose, never auto-write.
|
||
- ROADMAP.md is **sync-only** — never created or bootstrapped by
|
||
doc-syncer (creation belongs to init/onboard skills). Only
|
||
`planned → shipped` moves, justified by code/git existence; never
|
||
invent planned items, never fill it with todos, never move shipped
|
||
back to planned.
|
||
- ROADMAP "shipped" status is deduced from code + git ONLY — never read
|
||
`.claude/tasks/` (or any `.claude/`) to populate or check ROADMAP.
|
||
- ROADMAP numeric incoherence → surface a [HUMAN] question, never
|
||
overwrite a metric with a guessed number.
|
||
- CLEAN removals: only in CLEAN MODE, always HUMAN, removal-only.
|
||
- Inline comment updates: only for files in scope, only when the
|
||
signature actually changed.
|
||
- Preserve existing structure, formatting, tone.
|
||
- Patches minimal — change what's wrong, nothing else.
|