From 83a49d80cc05498502dc8756b648c8619af58340 Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Mon, 24 Aug 2026 23:50:26 -0700 Subject: [PATCH 1/5] docs: add a contributor and coding-agent guide Records the process the user docs do not carry: the build-policy defaults that must not be relaxed to get a build through, the dependency update order, and the rename-script invariant that the template workflow enforces. Claude Code reads CLAUDE.md rather than AGENTS.md, so the symlink points it at the same file. --- AGENTS.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 84 insertions(+) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5939fa4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,83 @@ +# AGENTS + +Contributor and coding-agent guide for this repository. + +## Scope + +This repository is a template. Every change lands in every project generated from it, so prefer the +narrower change and leave project-specific decisions to the generated repository. + +Documentation is split by what it serves: + +- `README.md`: the entry point. What the template provides, and how to instantiate it. +- `docs/development.md`: the workflow reference. Sanitizers, presets, the lock file, formatting and + linting, coverage, documentation, build policy, and IDE setup. +- `SECURITY.md`: the vulnerability reporting process. +- `AGENTS.md`: the contributor process the user docs do not need. + +Do not restate `docs/development.md` here. Link to the section instead. + +## Commands + +`make help` lists every target. The `Makefile` is the human entry point; `CMakePresets.json` is the +public build interface, and the workflow presets are what CI runs. + +- `make debug` and `make release` build and test through the matching workflow preset. +- `make sanitize`, `make sanitize-asan`, and `make sanitize-ubsan` build instrumented trees. +- `make coverage` and `make coverage-report`, which enforces a line floor of `COVERAGE_FAIL_UNDER`, + 100 by default. +- `make lint` runs clang-tidy, cmake-lint, ruff, and markdownlint. `make format` rewrites in place + and `make format-check` fails instead. +- `make lock` regenerates `conan.lock`; `make lock-check` is the CI guard. +- `make docs` generates the Doxygen HTML. + +Run `make format`, then the workflow preset matching the change, then `make lint` before commit. +`make bootstrap` installs Conan dependencies for debug and release; sanitizer trees need +`make bootstrap-sanitize` first. + +## Build Policy + +The defaults differ from a stock CMake project, and the differences are deliberate. Read +[Build policy](docs/development.md#build-policy) before changing a flag. + +- Warnings are errors in every preset. Fix the warning; do not configure with + `-DWARNINGS_AS_ERRORS=OFF` to get a build through. +- Hardening and, for `release` only, link-time optimization are on by default. Both cover + first-party targets alone, because dependency binaries come from the Conan cache and are not + rebuilt. Do not describe either as a process-wide guarantee. +- Sanitizer and coverage builds omit fortification on purpose. Fortify conflicts with the ASan + interceptors, and coverage builds run at `-O0` where glibc fortification warns. +- The template does not replace the system allocator, and the reasons are recorded under + [Allocators](docs/development.md#allocators). Adding one is a measured decision for a generated + project, not a template change. + +## Dependencies + +Update in this order: edit the pins in `conanfile.py`, run `make lock`, run the workflow that +covers the change, then commit `conanfile.py` and `conan.lock` together. A lock regenerated without +a pin change is a no-op commit; drop it rather than committing churn. + +`scripts/check_settings_subset.py` verifies that an installed Conan `settings_user.yml` covers the +sanitizer values this project needs. It parses only Conan's documented two-level structure and uses +the standard library alone, because PyYAML is not available everywhere it runs. Unrecognized input +fails the check and expects a manual merge. + +## Template Invariants + +- `scripts/rename.py` is the instantiation path, and `.github/workflows/template.yml` renames the + template in CI, then builds, tests, and runs the result. A change that adds a file carrying the + project name, the namespace, an include path, or a GitHub link must teach the rename script about + it in the same change, or that workflow fails. +- The rename script uses only the Python standard library. Keep it that way: it runs before any + dependency is installed. +- C++23 is the language floor (`cxx_std_23`), and the CMake floor is stated at the top of + `CMakeLists.txt` with the feature that sets it. Raise either only with the reason recorded there. + +## Definition of Done + +- `make format && make lint` is clean, and the workflow preset covering the change passes. +- Coverage stays at the floor. Exclude genuinely unreachable defensive code rather than lowering + `COVERAGE_FAIL_UNDER`. +- Documentation changed in the same commit when the change touches presets, make targets, flags, + the rename script, or the dependency set. +- Commits follow Conventional Commits, and work reaches `main` through a pull request. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 1dcb25461ffb99f73d1fd1b6d9667db694e3cc50 Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Tue, 25 Aug 2026 09:43:17 -0700 Subject: [PATCH 2/5] fix(docs): keep agent guide valid after rename --- AGENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5939fa4..7088a68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,11 +64,11 @@ fails the check and expects a manual merge. ## Template Invariants -- `scripts/rename.py` is the instantiation path, and `.github/workflows/template.yml` renames the - template in CI, then builds, tests, and runs the result. A change that adds a file carrying the - project name, the namespace, an include path, or a GitHub link must teach the rename script about - it in the same change, or that workflow fails. -- The rename script uses only the Python standard library. Keep it that way: it runs before any +- The rename utility is the instantiation path, and the template workflow renames the repository in + CI, then builds, tests, and runs the result. A change that adds a file carrying the project name, + the namespace, an include path, or a GitHub link must teach the rename utility about it in the + same change, or that workflow fails. +- The rename utility uses only the Python standard library. Keep it that way: it runs before any dependency is installed. - C++23 is the language floor (`cxx_std_23`), and the CMake floor is stated at the top of `CMakeLists.txt` with the feature that sets it. Raise either only with the reason recorded there. @@ -79,5 +79,5 @@ fails the check and expects a manual merge. - Coverage stays at the floor. Exclude genuinely unreachable defensive code rather than lowering `COVERAGE_FAIL_UNDER`. - Documentation changed in the same commit when the change touches presets, make targets, flags, - the rename script, or the dependency set. + the rename utility, or the dependency set. - Commits follow Conventional Commits, and work reaches `main` through a pull request. From f5c6c179f6c136982cc8de2799ed6f783a9400e2 Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Tue, 25 Aug 2026 09:49:35 -0700 Subject: [PATCH 3/5] fix(ci): skip documentation symlink in format checks --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ac55284..8419e8e 100644 --- a/Makefile +++ b/Makefile @@ -58,10 +58,10 @@ SANITIZE_STAMPS := \ FORMAT_SOURCES = $(shell find include src tests -type f \( -name '*.hpp' -o -name '*.cpp' \)) CMAKE_FORMAT_SOURCES = CMakeLists.txt TIDY_SOURCES = $(shell find src tests -type f -name '*.cpp') -# Tracked Markdown/JSON/YAML only; conan.lock is Conan-generated, so its formatting is -# Conan's, not prettier's. -PRETTIER_SOURCES = $(shell git ls-files '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock') -MARKDOWN_SOURCES = $(shell git ls-files '*.md') +# Tracked Markdown/JSON/YAML only; conan.lock is Conan-generated, and CLAUDE.md is a symlink to +# AGENTS.md. Format and lint the target once instead of passing the symlink explicitly to tools. +PRETTIER_SOURCES = $(shell git ls-files '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock' ':!CLAUDE.md') +MARKDOWN_SOURCES = $(shell git ls-files '*.md' ':!CLAUDE.md') PYTHON_SOURCES = scripts/ conanfile.py define require-tool From aa452618642f176700b7acb2ca024e8e283f3afa Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Tue, 25 Aug 2026 09:54:37 -0700 Subject: [PATCH 4/5] fix(ci): filter tracked symlinks generically --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8419e8e..aca5838 100644 --- a/Makefile +++ b/Makefile @@ -58,10 +58,10 @@ SANITIZE_STAMPS := \ FORMAT_SOURCES = $(shell find include src tests -type f \( -name '*.hpp' -o -name '*.cpp' \)) CMAKE_FORMAT_SOURCES = CMakeLists.txt TIDY_SOURCES = $(shell find src tests -type f -name '*.cpp') -# Tracked Markdown/JSON/YAML only; conan.lock is Conan-generated, and CLAUDE.md is a symlink to -# AGENTS.md. Format and lint the target once instead of passing the symlink explicitly to tools. -PRETTIER_SOURCES = $(shell git ls-files '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock' ':!CLAUDE.md') -MARKDOWN_SOURCES = $(shell git ls-files '*.md' ':!CLAUDE.md') +# Content formatters and linters operate on tracked regular files. Git records symlinks with mode +# 120000; their targets are already checked independently, and Prettier rejects explicit symlinks. +PRETTIER_SOURCES = $(shell git ls-files -s '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock' | awk '$$1 != "120000" {sub(/^[^\t]*\t/, ""); print}') +MARKDOWN_SOURCES = $(shell git ls-files -s '*.md' | awk '$$1 != "120000" {sub(/^[^\t]*\t/, ""); print}') PYTHON_SOURCES = scripts/ conanfile.py define require-tool From 286dfc81f88f7c94b79d4652dc413dad8bdbb83f Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Tue, 25 Aug 2026 20:43:26 -0700 Subject: [PATCH 5/5] fix(ci): drop the symlink so the format lists need no filtering CLAUDE.md becomes a regular file importing AGENTS.md, the alternative the Claude Code documentation gives for the symlink. That removes the only tracked symlink, so PRETTIER_SOURCES and MARKDOWN_SOURCES go back to a plain git ls-files. The import also survives a Windows checkout. Git without symlink support writes the link target as file content, which would leave a generated project with a CLAUDE.md containing the string AGENTS.md. --- CLAUDE.md | 4 +++- Makefile | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) mode change 120000 => 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3f07d74 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code + +@AGENTS.md diff --git a/Makefile b/Makefile index aca5838..ac55284 100644 --- a/Makefile +++ b/Makefile @@ -58,10 +58,10 @@ SANITIZE_STAMPS := \ FORMAT_SOURCES = $(shell find include src tests -type f \( -name '*.hpp' -o -name '*.cpp' \)) CMAKE_FORMAT_SOURCES = CMakeLists.txt TIDY_SOURCES = $(shell find src tests -type f -name '*.cpp') -# Content formatters and linters operate on tracked regular files. Git records symlinks with mode -# 120000; their targets are already checked independently, and Prettier rejects explicit symlinks. -PRETTIER_SOURCES = $(shell git ls-files -s '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock' | awk '$$1 != "120000" {sub(/^[^\t]*\t/, ""); print}') -MARKDOWN_SOURCES = $(shell git ls-files -s '*.md' | awk '$$1 != "120000" {sub(/^[^\t]*\t/, ""); print}') +# Tracked Markdown/JSON/YAML only; conan.lock is Conan-generated, so its formatting is +# Conan's, not prettier's. +PRETTIER_SOURCES = $(shell git ls-files '*.md' '*.json' '*.yml' '*.yaml' ':!conan.lock') +MARKDOWN_SOURCES = $(shell git ls-files '*.md') PYTHON_SOURCES = scripts/ conanfile.py define require-tool