Commit guideline copies with projects and allow single-number versioning

This commit is contained in:
Jan Doubravský
2026-08-18 13:08:06 +02:00
parent 78f1f863bf
commit 950e946004
11 changed files with 300 additions and 25 deletions
+39 -6
View File
@@ -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)
+5 -4
View File
@@ -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
+2 -2
View File
@@ -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`.
---
+14 -2
View File
@@ -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
+2 -2
View File
@@ -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`.
---
+2 -2
View File
@@ -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`.
---
+3 -2
View File
@@ -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
+25 -1
View File
@@ -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
+2 -2
View File
@@ -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`.
---
+2 -2
View File
@@ -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`.
---
+204
View File
@@ -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())