diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e0567f..0b195b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,42 @@ # Changelog -All notable changes to this documentation repository, grouped by version and release date. -Document versions of the individual guideline files are tracked separately in their headers. +All notable changes to this documentation repository, grouped into numbered revisions (`v1`, `v2`, …) +of the repository as a whole. There is no semantic version here — this repository has no manifest and +ships documents, so it uses the same `v#` scheme as the documents themselves. -## 0.4.0 — 2026-08-18 +Individual guideline documents carry their own `Document Version` header, and the skills carry +`metadata.version`; both are independent of the revisions below. + +## v5 — 2026-08-18 + +### Added +- `check_versions.py` — walks the sibling project directories, compares every `CLAUDE.md`, + `AGENTS.md` and `DESIGN_DOCUMENT*.md` copy against its master here, and reports the ones that are + behind or were edited in place. Standard library only; exits non-zero when anything needs + attention +- "Guideline copies in projects" section in `README.md` — the copies in project roots are now + **committed** with each project, with the rationale and the three rules that keep them from + drifting (never edit a copy, deviations go to `PROJECT.md`, sync commits stand alone) + +### Changed +- `README.md` step 3 of "Starting a new project": the guideline copies are committed with the + project instead of being kept out of version control, and `Claude/CLAUDE.md` is named alongside + `AGENTS.md` and the design document +- `AGENTS.md` v6 → v7: the Git section said the opposite of the new policy — "Never commit shared + documentation" is replaced by the rule that every core document is committed, must never be edited + inside a project, and is synchronised in its own commit that stays out of the project `CHANGELOG.md` +- `CLAUDE.md` added to the list of core root documents in `AGENTS.md` and in all five design + documents, which had omitted it: `DESIGN_DOCUMENT.md` v9 → v10, `DESIGN_DOCUMENT_MODULE.md` v2 → v3, + `Rust/DESIGN_DOCUMENT.md` v2 → v3, `Rust/DESIGN_DOCUMENT_LIB.md` v2 → v3, + `DESIGN_DOCUMENT_GODOT.md` v2 → v3 +- `Python/TEMPLATE.md`: the note that the shared documents are "never committed" now matches the new + policy — they stay out of the template, but the copies in a project are committed and never edited +- `Project template/CHANGELOG.md` gained a "Versioning" section: semantic versioning stays mandatory + for software projects, while documentation and other manifest-less repositories may use + single-number revisions (`v1`, `v2`, …). This repository's own changelog was converted to that + scheme and its revisions renumbered `0.1.0`–`0.4.0` → `v1`–`v4` + +## v4 — 2026-08-18 ### Added - Skill versioning: every `SKILL.md` now carries `metadata.version` — `changelog` v2, `check` v1, @@ -45,7 +78,7 @@ Document versions of the individual guideline files are tracked separately in th - Removed the misplaced lock-file rule from the Testing sections of `DESIGN_DOCUMENT_MODULE.md` (§9) and `Rust/DESIGN_DOCUMENT_LIB.md` (§8) — it already belongs to the Poetry / Distribution sections -## 0.3.0 — 2026-08-18 +## v3 — 2026-08-18 ### Added - `Zscaler/ZSCALER_CERTIFICATE.md` — full certificate setup for Node, Python, git and cargo @@ -63,12 +96,12 @@ Document versions of the individual guideline files are tracked separately in th ### Removed - `Zscaler/NODE_EXTRA_CA_CERTS.md`, superseded by `ZSCALER_CERTIFICATE.md` -## 0.2.0 — 2026-06-24 +## v2 — 2026-06-24 ### Added - `Godot/DESIGN_DOCUMENT_GODOT.md` — Godot development guidelines (v1) -## 0.1.0 — 2026-05-25 +## v1 — 2026-05-25 ### Added - `Rust/DESIGN_DOCUMENT.md` and `Rust/DESIGN_DOCUMENT_LIB.md` — Rust guidelines for applications and libraries (v1) diff --git a/Claude/AGENTS.md b/Claude/AGENTS.md index 053c8a8..193f0a6 100644 --- a/Claude/AGENTS.md +++ b/Claude/AGENTS.md @@ -1,6 +1,6 @@ # AI Agents - Project Rules -**Document Version:** v6 (independent, incremented on structural changes) +**Document Version:** v7 (independent, incremented on structural changes) Language-agnostic rules for AI assistants (Claude Code, Cursor, Copilot, etc.). @@ -34,7 +34,7 @@ Where this file and a design document disagree, **the design document wins**. - Source code, tests and detailed documentation each have their own directory — the concrete layout is in the design document - Detailed documentation belongs in `docs/`, never in the project root -- The project root holds only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT*.md`, `PROJECT.md`, `CHANGELOG.md` +- The project root holds only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT*.md`, `PROJECT.md`, `CHANGELOG.md` - Entry points follow the language convention; a project may have several - The dependency/build directory (`.venv/`, `target/`, …) is tool-managed — do not copy it, do not generate it by hand @@ -65,8 +65,9 @@ Where this file and a design document disagree, **the design document wins**. - `.gitignore` must cover at least: the dependency/build directory, tool caches, and `.env` - **Commit the lock file for applications, do not commit it for libraries** -- **Never commit shared documentation** (`AGENTS.md`, `DESIGN_DOCUMENT*.md`) — it comes from the documentation repository, not from the project -- `README.md`, `PROJECT.md` and `CHANGELOG.md` **are committed** — they are project-specific +- **Every core document is committed**, including the shared ones (`CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT*.md`). A checkout then carries the rules that applied to that code, and a fresh clone works standalone. +- **Never edit a shared document inside a project** — it is a copy. Changes belong in the documentation repository and are copied outward; project-specific deviations go in `PROJECT.md`. +- Synchronising the copies is its own commit (`docs: sync guidelines to AGENTS v7 / DESIGN_DOCUMENT v10`) and is **not** recorded in the project `CHANGELOG.md` — it is not a change to the product ### Commit messages diff --git a/Godot/DESIGN_DOCUMENT_GODOT.md b/Godot/DESIGN_DOCUMENT_GODOT.md index f9005a5..ff05945 100644 --- a/Godot/DESIGN_DOCUMENT_GODOT.md +++ b/Godot/DESIGN_DOCUMENT_GODOT.md @@ -1,6 +1,6 @@ # Godot Development Guidelines -**Document Version:** v2 +**Document Version:** v3 > **Note on Versioning:** > - This document version is independent — reused across Godot projects @@ -19,7 +19,7 @@ All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root. -The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_GODOT.md`, `PROJECT.md`, `CHANGELOG.md`. +The root directory contains only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT_GODOT.md`, `PROJECT.md`, `CHANGELOG.md`. --- diff --git a/Project template/CHANGELOG.md b/Project template/CHANGELOG.md index 6b2ac05..ddc2325 100644 --- a/Project template/CHANGELOG.md +++ b/Project template/CHANGELOG.md @@ -12,9 +12,21 @@ Each version entry uses these sections (include only those that apply): - **Removed** — removed features - **Dependencies** — added, updated, or removed dependencies +## Versioning + +Which scheme applies depends on what the repository ships: + +- **Software projects — semantic versioning** `MAJOR.MINOR.PATCH`. The project manifest + (`pyproject.toml`, `Cargo.toml`, `project.godot`) is the source of truth, and this file is updated + **before** the version there is bumped. +- **Documentation and other non-program projects — single-number revisions** `v1`, `v2`, … + A repository with no manifest has nothing to derive a semantic version from, and + MAJOR/MINOR/PATCH carries no meaning for prose. Increment by one per released revision. + +Pick one scheme per repository and do not mix them in this file. + ## Rules -- Follow semantic versioning: `MAJOR.MINOR.PATCH` - Newest version goes at the top -- Always update this file before bumping the version in the project manifest (`pyproject.toml`, `Cargo.toml`, `project.godot`) - Document changes as they are made, not all at once at release time +- Always ask before bumping the version — never increment automatically diff --git a/Python/DESIGN_DOCUMENT.md b/Python/DESIGN_DOCUMENT.md index 693aeba..9a6739e 100644 --- a/Python/DESIGN_DOCUMENT.md +++ b/Python/DESIGN_DOCUMENT.md @@ -1,6 +1,6 @@ # Python Development Guidelines -**Document Version:** v9 +**Document Version:** v10 > **Note on Versioning:** > - This document version is independent — reused across projects @@ -19,7 +19,7 @@ All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root. -The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`. +The root directory contains only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`. --- diff --git a/Python/DESIGN_DOCUMENT_MODULE.md b/Python/DESIGN_DOCUMENT_MODULE.md index 4fcfa84..a89f79d 100644 --- a/Python/DESIGN_DOCUMENT_MODULE.md +++ b/Python/DESIGN_DOCUMENT_MODULE.md @@ -1,6 +1,6 @@ # Python Library Development Guidelines -**Document Version:** v2 +**Document Version:** v3 > **Note on Versioning:** > - This document version is independent — reused across projects @@ -18,7 +18,7 @@ All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root. -The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_MODULE.md`, `PROJECT.md`, `CHANGELOG.md`. +The root directory contains only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT_MODULE.md`, `PROJECT.md`, `CHANGELOG.md`. --- diff --git a/Python/TEMPLATE.md b/Python/TEMPLATE.md index f3568f1..28104d7 100644 --- a/Python/TEMPLATE.md +++ b/Python/TEMPLATE.md @@ -26,8 +26,9 @@ template/ └── test_constants.py # Basic test ``` -`AGENTS.md` and `DESIGN_DOCUMENT.md` are **not** part of the template — they are copied -into the project root from the documentation repository and are never committed. +`CLAUDE.md`, `AGENTS.md` and `DESIGN_DOCUMENT.md` are **not** part of the template — they are copied +into the project root from the documentation repository, which stays their source of truth. The +copies themselves **are committed** with the project and are never edited in place. ## Key Features diff --git a/README.md b/README.md index 7077d01..25a86a3 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Dokumentace/ │ ├── ZSCALER_CERTIFICATE.md # Cert setup for Node, Python, git, cargo │ └── ZscalerRootCertificate-2048-SHA256.crt │ +├── check_versions.py # Reports projects whose guideline copies are behind └── CHANGELOG.md # History of this repository ``` @@ -71,9 +72,32 @@ Where the two disagree, **the design document wins**. 1. Copy `Project template/PROJECT.md` and `Project template/CHANGELOG.md` into the project root and write a `README.md` — all three are committed. 2. Build `.gitignore` from `Project template/.gitignore` plus the `.gitignore` of the matching language folder. -3. Copy `Claude/AGENTS.md` and the matching `DESIGN_DOCUMENT*.md` into the project root — these are **not** committed, they come from here. +3. Copy `Claude/CLAUDE.md`, `Claude/AGENTS.md` and the matching `DESIGN_DOCUMENT*.md` into the project root. This repository is their source of truth, but the copies **are committed** with the project — see "Guideline copies in projects" below. 4. For Python, follow `Python/TEMPLATE.md` to generate the project skeleton. +### Guideline copies in projects + +`CLAUDE.md`, `AGENTS.md` and the matching `DESIGN_DOCUMENT*.md` live in the root of every project and +are **committed there**. They are duplicated on purpose: + +- A checkout of an old commit carries the rules that applied to that code. Kept only here, the + guidelines are always "latest" and the pairing is lost. +- A fresh clone — another machine, CI, Claude Code on the web or a remote agent — has to work + standalone. `CLAUDE.md` and `AGENTS.md` are only picked up from the repository root. +- Drift becomes visible: `git log DESIGN_DOCUMENT.md` shows when a project was last synchronised. + +Three rules keep the copies from rotting: + +- **Never edit a copy inside a project.** Changes go into this repository and are copied outward. +- **Project-specific deviations belong in `PROJECT.md`**, which is project-owned and already on the + session read list. That is also where extra files to read (for example a generated `CONTEXT.md`) + are named. +- **Sync commits stand alone**, in the form + `docs: sync guidelines to AGENTS v6 / DESIGN_DOCUMENT v9`. They do not go into the project + `CHANGELOG.md` — they are not a change to the product. + +Run `check_versions.py` from this repository to list every sibling project whose copies are behind. + ### Skills The skills in `Claude/skills/` are the source of truth; the copies Claude Code actually runs live in diff --git a/Rust/DESIGN_DOCUMENT.md b/Rust/DESIGN_DOCUMENT.md index 551f12d..6194101 100644 --- a/Rust/DESIGN_DOCUMENT.md +++ b/Rust/DESIGN_DOCUMENT.md @@ -1,6 +1,6 @@ # Rust Application Development Guidelines -**Document Version:** v2 +**Document Version:** v3 > **Note on Versioning:** > - This document version is independent — reused across projects @@ -18,7 +18,7 @@ All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root. -The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`. +The root directory contains only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`. --- diff --git a/Rust/DESIGN_DOCUMENT_LIB.md b/Rust/DESIGN_DOCUMENT_LIB.md index d1d7322..a0df25b 100644 --- a/Rust/DESIGN_DOCUMENT_LIB.md +++ b/Rust/DESIGN_DOCUMENT_LIB.md @@ -1,6 +1,6 @@ # Rust Library Development Guidelines -**Document Version:** v2 +**Document Version:** v3 > **Note on Versioning:** > - This document version is independent — reused across projects @@ -20,7 +20,7 @@ All detailed documentation of features and systems belongs in the `docs/` folder The API reference is generated by `cargo doc` from doc comments — `docs/` holds the prose documentation that does not fit in doc comments. -The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_LIB.md`, `PROJECT.md`, `CHANGELOG.md`. +The root directory contains only the core documents: `README.md`, `CLAUDE.md`, `AGENTS.md`, `DESIGN_DOCUMENT_LIB.md`, `PROJECT.md`, `CHANGELOG.md`. --- diff --git a/check_versions.py b/check_versions.py new file mode 100644 index 0000000..083b4fc --- /dev/null +++ b/check_versions.py @@ -0,0 +1,204 @@ +"""Report sibling projects whose copies of the guideline documents are out of date. + +This repository is the source of truth for ``CLAUDE.md``, ``AGENTS.md`` and the +``DESIGN_DOCUMENT*.md`` family. Every project keeps a committed copy in its root, so the copies +drift as soon as a document here is bumped. This script walks the sibling directories of this +repository, reads the ``Document Version`` header of every copy it finds and reports the ones that +are behind, missing, or modified locally. + +Standard library only, so it runs without a virtual environment: + + python check_versions.py + python check_versions.py --root .. # scan a different directory + python check_versions.py --quiet # print only projects needing attention +""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +# Guideline documents owned by this repository, mapped to their master copy. +MASTERS: dict[str, str] = { + "CLAUDE.md": "Claude/CLAUDE.md", + "AGENTS.md": "Claude/AGENTS.md", + "DESIGN_DOCUMENT.md": "Python/DESIGN_DOCUMENT.md", + "DESIGN_DOCUMENT_MODULE.md": "Python/DESIGN_DOCUMENT_MODULE.md", + "DESIGN_DOCUMENT_LIB.md": "Rust/DESIGN_DOCUMENT_LIB.md", + "DESIGN_DOCUMENT_GODOT.md": "Godot/DESIGN_DOCUMENT_GODOT.md", +} + +# A Rust application uses the same file name as a Python one; the language decides which master +# applies. Detected from the project manifest. +RUST_OVERRIDES: dict[str, str] = {"DESIGN_DOCUMENT.md": "Rust/DESIGN_DOCUMENT.md"} + +VERSION_PATTERN = re.compile(r"^\*\*Document Version:\*\*\s*(v\d+)", re.MULTILINE) + +STATUS_OK = "ok" +STATUS_BEHIND = "behind" +STATUS_MODIFIED = "modified" +STATUS_UNVERSIONED = "unversioned" + + +@dataclass(frozen=True) +class Result: + """Outcome of comparing one project copy against its master.""" + + project: str + document: str + project_version: str | None + master_version: str | None + status: str + + @property + def needs_attention(self) -> bool: + return self.status != STATUS_OK + + +def read_text(path: Path) -> str: + """Read a document, tolerating the BOM some editors leave behind.""" + return path.read_text(encoding="utf-8-sig") + + +def normalise(text: str) -> str: + """Strip line-ending differences so CRLF and LF copies compare equal.""" + return text.replace("\r\n", "\n").replace("\r", "\n").rstrip("\n") + + +def read_version(path: Path) -> str | None: + """Return the ``Document Version`` declared in a guideline document, if it has one.""" + match = VERSION_PATTERN.search(read_text(path)) + return match.group(1) if match else None + + +def version_key(version: str | None) -> int: + """Order versions numerically; a missing version sorts lowest.""" + return int(version[1:]) if version else -1 + + +def is_rust_project(project: Path) -> bool: + return (project / "Cargo.toml").is_file() + + +def load_masters(repo: Path) -> dict[str, tuple[Path, str | None]]: + """Map each document name to its master path and version.""" + masters: dict[str, tuple[Path, str | None]] = {} + for name, relative in MASTERS.items(): + path = repo / relative + if path.is_file(): + masters[name] = (path, read_version(path)) + return masters + + +def check_project(project: Path, repo: Path, masters: dict[str, tuple[Path, str | None]]) -> list[Result]: + """Compare every guideline document present in one project against its master.""" + results: list[Result] = [] + rust = is_rust_project(project) + + for name, (master_path, master_version) in masters.items(): + copy = project / name + if not copy.is_file(): + continue + + if rust and name in RUST_OVERRIDES: + master_path = repo / RUST_OVERRIDES[name] + master_version = read_version(master_path) + + copy_version = read_version(copy) + + if copy_version is None or master_version is None: + status = STATUS_UNVERSIONED + elif version_key(copy_version) < version_key(master_version): + status = STATUS_BEHIND + elif normalise(read_text(copy)) != normalise(read_text(master_path)): + # Same version number but different content — someone edited the copy in place. + status = STATUS_MODIFIED + else: + status = STATUS_OK + + results.append(Result(project.name, name, copy_version, master_version, status)) + + return results + + +def find_projects(root: Path, repo: Path) -> list[Path]: + """Return candidate project directories: siblings of this repository, excluding itself.""" + return sorted( + path + for path in root.iterdir() + if path.is_dir() and path.resolve() != repo.resolve() and not path.name.startswith(".") + ) + + +def format_row(result: Result) -> str: + marker = { + STATUS_OK: "ok ", + STATUS_BEHIND: "BEHIND ", + STATUS_MODIFIED: "MODIFIED", + STATUS_UNVERSIONED: "NO VER. ", + }[result.status] + versions = f"{result.project_version or '-'} -> {result.master_version or '-'}" + return f" {marker} {result.document:<26} {versions}" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + "--root", + type=Path, + default=None, + help="directory holding the projects (default: the parent of this repository)", + ) + parser.add_argument("--quiet", action="store_true", help="list only projects that need attention") + args = parser.parse_args() + + repo = Path(__file__).resolve().parent + root = (args.root or repo.parent).resolve() + + masters = load_masters(repo) + if not masters: + print(f"No master documents found in {repo}", file=sys.stderr) + return 2 + + print(f"Master documents in {repo.name}:") + for name, (_, version) in sorted(masters.items()): + print(f" {name:<26} {version or '(no version header)'}") + print() + + stale = 0 + unmanaged: list[str] = [] + + for project in find_projects(root, repo): + results = check_project(project, repo, masters) + if not results: + unmanaged.append(project.name) + continue + + attention = [result for result in results if result.needs_attention] + stale += len(attention) + + if args.quiet and not attention: + continue + + print(f"{project.name}") + for result in results if not args.quiet else attention: + print(format_row(result)) + print() + + if unmanaged: + print(f"No guideline documents: {', '.join(unmanaged)}") + print() + + if stale: + print(f"{stale} document(s) need attention.") + return 1 + + print("All projects are up to date.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())