Commit guideline copies with projects and allow single-number versioning
This commit is contained in:
+39
-6
@@ -1,9 +1,42 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this documentation repository, grouped by version and release date.
|
All notable changes to this documentation repository, grouped into numbered revisions (`v1`, `v2`, …)
|
||||||
Document versions of the individual guideline files are tracked separately in their headers.
|
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
|
### Added
|
||||||
- Skill versioning: every `SKILL.md` now carries `metadata.version` — `changelog` v2, `check` v1,
|
- 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)
|
- 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
|
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
|
### Added
|
||||||
- `Zscaler/ZSCALER_CERTIFICATE.md` — full certificate setup for Node, Python, git and cargo
|
- `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
|
### Removed
|
||||||
- `Zscaler/NODE_EXTRA_CA_CERTS.md`, superseded by `ZSCALER_CERTIFICATE.md`
|
- `Zscaler/NODE_EXTRA_CA_CERTS.md`, superseded by `ZSCALER_CERTIFICATE.md`
|
||||||
|
|
||||||
## 0.2.0 — 2026-06-24
|
## v2 — 2026-06-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `Godot/DESIGN_DOCUMENT_GODOT.md` — Godot development guidelines (v1)
|
- `Godot/DESIGN_DOCUMENT_GODOT.md` — Godot development guidelines (v1)
|
||||||
|
|
||||||
## 0.1.0 — 2026-05-25
|
## v1 — 2026-05-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `Rust/DESIGN_DOCUMENT.md` and `Rust/DESIGN_DOCUMENT_LIB.md` — Rust guidelines for applications and libraries (v1)
|
- `Rust/DESIGN_DOCUMENT.md` and `Rust/DESIGN_DOCUMENT_LIB.md` — Rust guidelines for applications and libraries (v1)
|
||||||
|
|||||||
+5
-4
@@ -1,6 +1,6 @@
|
|||||||
# AI Agents - Project Rules
|
# 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.).
|
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
|
- 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
|
- 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
|
- 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
|
- 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`
|
- `.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**
|
- **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
|
- **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.
|
||||||
- `README.md`, `PROJECT.md` and `CHANGELOG.md` **are committed** — they are project-specific
|
- **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
|
### Commit messages
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Godot Development Guidelines
|
# Godot Development Guidelines
|
||||||
|
|
||||||
**Document Version:** v2
|
**Document Version:** v3
|
||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across Godot projects
|
> - 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.
|
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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,21 @@ Each version entry uses these sections (include only those that apply):
|
|||||||
- **Removed** — removed features
|
- **Removed** — removed features
|
||||||
- **Dependencies** — added, updated, or removed dependencies
|
- **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
|
## Rules
|
||||||
|
|
||||||
- Follow semantic versioning: `MAJOR.MINOR.PATCH`
|
|
||||||
- Newest version goes at the top
|
- 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
|
- Document changes as they are made, not all at once at release time
|
||||||
|
- Always ask before bumping the version — never increment automatically
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Python Development Guidelines
|
# Python Development Guidelines
|
||||||
|
|
||||||
**Document Version:** v9
|
**Document Version:** v10
|
||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across projects
|
> - 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.
|
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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Python Library Development Guidelines
|
# Python Library Development Guidelines
|
||||||
|
|
||||||
**Document Version:** v2
|
**Document Version:** v3
|
||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across projects
|
> - 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.
|
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
@@ -26,8 +26,9 @@ template/
|
|||||||
└── test_constants.py # Basic test
|
└── test_constants.py # Basic test
|
||||||
```
|
```
|
||||||
|
|
||||||
`AGENTS.md` and `DESIGN_DOCUMENT.md` are **not** part of the template — they are copied
|
`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 and are never committed.
|
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
|
## Key Features
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ Dokumentace/
|
|||||||
│ ├── ZSCALER_CERTIFICATE.md # Cert setup for Node, Python, git, cargo
|
│ ├── ZSCALER_CERTIFICATE.md # Cert setup for Node, Python, git, cargo
|
||||||
│ └── ZscalerRootCertificate-2048-SHA256.crt
|
│ └── ZscalerRootCertificate-2048-SHA256.crt
|
||||||
│
|
│
|
||||||
|
├── check_versions.py # Reports projects whose guideline copies are behind
|
||||||
└── CHANGELOG.md # History of this repository
|
└── 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.
|
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.
|
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.
|
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
|
### Skills
|
||||||
|
|
||||||
The skills in `Claude/skills/` are the source of truth; the copies Claude Code actually runs live in
|
The skills in `Claude/skills/` are the source of truth; the copies Claude Code actually runs live in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Rust Application Development Guidelines
|
# Rust Application Development Guidelines
|
||||||
|
|
||||||
**Document Version:** v2
|
**Document Version:** v3
|
||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across projects
|
> - 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.
|
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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Rust Library Development Guidelines
|
# Rust Library Development Guidelines
|
||||||
|
|
||||||
**Document Version:** v2
|
**Document Version:** v3
|
||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across projects
|
> - 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 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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
Reference in New Issue
Block a user