docs+tooling: doc-freshness gate + documentation-pass policy + token-hygiene (#24)
Some checks failed
C++ Tests / test-fast (push) Successful in 2m16s
API Docs / doc-build (push) Has been skipped
Markdown link check / check (push) Successful in 43s
Mirror to Codeberg / mirror (push) Failing after 28s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / quality-gates (push) Successful in 2m10s

This commit is contained in:
2026-05-28 04:15:35 +00:00
8 changed files with 328 additions and 68 deletions

36
.claude/settings.json Normal file
View File

@@ -0,0 +1,36 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(cmake:*)",
"Bash(ctest:*)",
"Bash(nice:*)",
"Bash(./build/conformallab_tests:*)",
"Bash(./build/conformallab_cgal_tests:*)",
"Bash(bash scripts/quality/*)",
"Bash(bash scripts/*)",
"Bash(python3 scripts/*)",
"Bash(codespell:*)",
"Bash(shellcheck:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git branch:*)",
"Bash(git ls-remote:*)",
"Bash(git ls-files:*)",
"Bash(ls:*)",
"Bash(find:*)",
"Bash(grep:*)",
"Bash(rg:*)",
"Bash(wc:*)",
"Bash(curl -s*)"
],
"deny": [
"Bash(git push --force* origin main*)",
"Bash(git push -f* origin main*)",
"Bash(git reset --hard*)",
"Bash(rm -rf /*)"
]
}
}

62
.claude/token-hygiene.md Normal file
View File

@@ -0,0 +1,62 @@
# Token-Hygiene — User Guide (Tier 3: Cache-Disziplin)
Eine kurze Anleitung **für dich** (den Nutzer), wie du die Prompt-Cache-Mechanik
auf deiner Seite richtig bedienst. Claude liest diese Datei mit und **erinnert
dich aktiv**, wenn es eines der unten genannten Anti-Patterns beobachtet.
Hintergrund: Der stabile Anfang jeder Anfrage (System-Prompt + `CLAUDE.md`) wird
von Anthropic **gecacht**, mit einer **TTL von 5 Minuten**. Solange der Cache
warm ist, zahlst du diesen Teil nicht erneut. Ein Cache-Miss bedeutet: der ganze
Kontext wird uncached neu gelesen — langsamer und teurer.
---
## Die drei Regeln
### 1. Stabilen Präfix stabil halten
`CLAUDE.md` und Projekt-Config **nicht mitten in einer Arbeitssession editieren**.
Jede Änderung an der Datei bustet den Cache für **alle** folgenden Turns dieser
Session. Sammle Doku-/Config-Änderungen und mach sie in **einem eigenen
Durchgang** (so wie dieser hier), nicht verstreut zwischen Code-Arbeit.
> **Reminder-Trigger für Claude:** Wenn der Nutzer mitten in einer laufenden
> Code-/Debug-Aufgabe eine Änderung an `CLAUDE.md` oder `.claude/settings.json`
> verlangt → kurz darauf hinweisen, dass das den Cache bustet, und anbieten, es
> zu bündeln/ans Ende zu legen.
### 2. Keine Kette kurzer Wartepausen
Eine Pause **> 5 Minuten** kostet beim nächsten Turn einen Cache-Miss. Eine
*einzelne* lange Pause ist egal. Schlecht ist, viele kurze „warte mal kurz"-Turns
aneinanderzuhängen, bei denen jeder nach >5 Min den Kontext neu liest. Wenn du
auf etwas Externes wartest (CI, Deploy), lieber **einmal** lang warten als
mehrfach pollen.
> **Reminder-Trigger für Claude:** Wenn der Nutzer wiederholt kurze
> „warte/poll mal"-Anweisungen mit Lücken um die 5-Minuten-Grenze gibt → einen
> einzelnen längeren Wartezyklus (oder eine Hintergrund-/Monitor-Lösung)
> vorschlagen statt mehrfachem Pollen.
### 3. Eine Session pro Thema
Lange Multi-Themen-Sessions sind der größte versteckte Kostenfaktor (jeder Turn
schickt die ganze History neu). Nach einer abgeschlossenen Aufgabe: **neue
Session** (`/clear`) statt zum nächsten, unzusammenhängenden Thema im selben
Thread zu wechseln. Das ist eigentlich Tier 1, aber es interagiert direkt mit
dem Cache: ein frischer Thread = kleiner, vollständig gecachter Präfix.
> **Reminder-Trigger für Claude:** Wenn die Session erkennbar das Thema wechselt
> (z.B. von „Pages-Fix" zu „neues Feature implementieren") → vorschlagen, eine
> frische Session zu starten, und optional die wichtigsten Entscheidungen vorher
> in Memory/Decision-Records festhalten.
---
## Schnell-Check vor einer langen Session
- [ ] Ist das ein **einzelnes** Thema? Wenn nein → splitten.
- [ ] Stehen anstehende `CLAUDE.md`-Änderungen an? → **vorher** erledigen, dann stabil lassen.
- [ ] Erwarte ich lange Wartezeiten (CI/Build)? → Hintergrund-Job + **eine** lange Wartephase, kein Polling.
## Verwandte Hebel (nicht Cache, aber gleiche Wirkung)
Tier 1 (Session-Schnitt) und Tier 2 (Kommando-Disziplin: Output filtern, Pfad-
Scope, nicht zurücklesen) stehen als operative Regeln im Abschnitt
*„Agentic workflow patterns" → „Token hygiene"* in der `CLAUDE.md`.

6
.gitignore vendored
View File

@@ -25,8 +25,10 @@ Testing/
*.user *.user
*.suo *.suo
# Claude Code worktrees # Claude Code worktrees + local/session data (but track shared project config)
.claude/ .claude/*
!.claude/settings.json
!.claude/token-hygiene.md
# Doxygen output # Doxygen output
doc/doxygen/ doc/doxygen/

151
CLAUDE.md
View File

@@ -45,6 +45,20 @@ cmake --build build -j$(nproc)
`-DWITH_CGAL=ON` automatically enables `-DWITH_VIEWER=ON`, which pulls in GLFW and requires `wayland-scanner`. Never use this in headless CI. `-DWITH_CGAL=ON` automatically enables `-DWITH_VIEWER=ON`, which pulls in GLFW and requires `wayland-scanner`. Never use this in headless CI.
### Compile-time options (v0.10.0 matrix)
The CGAL test build defaults to **PCH + Unity Build ON** (CGAL test wall-time 78 s → 55 s, CPU time 676 s → 167 s on Apple M1). Opt-in/opt-out flags — full measurements + macOS-vs-Linux notes in `doc/architecture/compile-time.md`:
| Flag | Default | Effect |
|---|---|---|
| `-DBUILD_TESTING=OFF` | ON | Skip the entire test subtree (headers-only consumers). |
| `-DCONFORMALLAB_USE_PCH=OFF` | ON | Disable precompiled headers for `conformallab_cgal_tests`. |
| `-DCMAKE_UNITY_BUILD=OFF` | ON | Disable Unity (jumbo) build. |
| `-DCONFORMALLAB_DEV_BUILD=ON` | OFF | Dev iteration: PCH on, Unity forced off (cheaper incremental rebuilds). |
| `-DCONFORMALLAB_FAST_TEST_BUILD=ON` | OFF | `-O0 -g` for tests (faster compile, slower run). |
| `-DCONFORMALLAB_USE_CCACHE=ON` | OFF | Route compiles through ccache. |
| `-DCONFORMALLAB_HEADERS_CHECK=ON` | OFF | Standalone header self-containment check target. |
### Running a single test ### Running a single test
```bash ```bash
@@ -243,87 +257,50 @@ my_map[v] = 3.14;
## CI pipeline ## CI pipeline
Two jobs in `.gitea/workflows/cpp-tests.yml`: Three jobs in `.gitea/workflows/cpp-tests.yml`:
| Job | CMake flags | Deps | Triggers on | | Job | CMake flags | Deps | Triggers on | Status |
|---|---|---|---| |---|---|---|---|---|
| `test-fast` | *(none)* | Eigen + GTest only | all branches | | `test-fast` | *(none)* | Eigen + GTest only | all branches | **active** |
| `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | pull requests only | | `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | pull requests only | **DISABLED 2026-05-26** (`if: false`) |
| `quality-gates` | *(none)* | + codespell, shellcheck | all branches (`needs: test-fast`) | **active** |
Runner: `eulernest` — self-hosted Raspberry Pi, ARM64, Ubuntu 22.04. Docker image: `git.eulernest.eu/conformallab/ci-cpp:latest`. `test-cgal` needs `test-fast` to pass first (`needs: test-fast`). Runner: `eulernest` — self-hosted Raspberry Pi, ARM64, Ubuntu 22.04. Docker image: `git.eulernest.eu/conformallab/ci-cpp:latest`. `test-cgal` and `quality-gates` both need `test-fast` to pass first (`needs: test-fast`).
Expected results: full test suite passing, 0 skipped, 0 failed. The canonical counts live in `doc/api/tests.md` — do not hardcode them anywhere else (see [`doc/release-policy.md`](doc/release-policy.md)). `quality-gates` runs four required structural gates: `license-headers.sh`, `cgal-conventions.py`, `codespell.sh`, `shellcheck.sh --strict`. Seven more gates (clang-format, cmake-format, cppcheck, sanitizers, clang-tidy, multi-compiler, reproducible-build) are local-only — see `scripts/quality/README.md`.
**`test-cgal` is gated off** (`if: false`, see the DISABLED-2026-05-26 comment block in the workflow): the `-j1` CGAL build is too memory-heavy for the 1.6 GB runner and OOMs intermittently without exposing real regressions. Consequence: the two structural sub-gates that lived inside it — `scripts/check-test-counts.sh` and `scripts/try_it.sh` — are currently **un-gated**; run them locally before tagging a release. Re-enable by restoring `if: github.event_name == 'pull_request'`.
Two other workflows are also restricted to `workflow_dispatch:` only (auto-trigger disabled 2026-05-26 while the codeberg pages-branch push is being stabilised):
- `.gitea/workflows/doxygen-pages.yml` — publishes Doxygen HTML + reviewer hub to the codeberg `pages` branch.
- `.gitea/workflows/perf-compile-time.yml` — Linux ARM64 compile-time benchmark.
Expected results: `test-fast` + `quality-gates` green on every push, 0 skipped, 0 failed. The canonical counts live in `doc/api/tests.md` — do not hardcode them anywhere else (see [`doc/release-policy.md`](doc/release-policy.md)).
## Release state ## Release state
Current release: **v0.9.0** (tag on `main`, released 2026-05-22). Current release: **v0.10.0** (tag on `main`, released 2026-05-26 — the "reviewer-ready" release).
Phases 19a complete, Phase 8b-Lite CGAL API surface complete (all 5 DCE models reachable via `<CGAL/Discrete_*.h>`), Phase 9b block-FD HyperIdeal Hessian shipped (~96× speed-up). Next planned milestones: Phase 9c (4g-polygon, genus g > 1) and Phase 9b-analytic (Schläfli identity). See `doc/release-policy.md` for the version-tag policy and `doc/roadmap/phases.md` for the phase plan. Phases 19a complete, Phase 8b-Lite CGAL API surface complete (all 5 DCE models reachable via `<CGAL/Discrete_*.h>`), Phase 9b block-FD HyperIdeal Hessian shipped (~96× speed-up). v0.10.0 added: 100 % Doxygen public-API coverage (396/396 symbols, 0 warnings), a 14-gate structural quality suite (4 required in CI), the reviewer materials package (`doc/reviewer/`), `output_uv_map` for 4 of 5 DCE entries, six new roadmap phases + three RESEARCH phases, and a six-mode compile-time workflow matrix. Numbers at release: **259/259 tests pass, 0 skipped** (23 non-CGAL + 236 CGAL). Next planned milestones: Phase 9c (4g-polygon, genus g > 1) and Phase 9b-analytic (Schläfli identity). See `doc/release-policy.md` for the version-tag policy, `CHANGELOG.md` for full release notes, and `doc/roadmap/phases.md` for the phase plan.
## Phase 8 strategic decisions (2026-05-19) ## Phase 8 CGAL-package decisions (frozen 2026-05-19)
The CGAL-package architecture was frozen on 2026-05-19. Full design: Locked architecture choices — full design in [`doc/api/cgal-package.md`](doc/api/cgal-package.md), locked-vs-flexible split in `doc/architecture/locked-vs-flexible.md`:
[`doc/api/cgal-package.md`](doc/api/cgal-package.md). Key decisions: **MIT license** (no LGPL switch) · **Named Parameters** (`CGAL::parameters::...`) · default kernel **`Simple_cartesian<double>`** · **dual-layer wrapper** (`code/include/*.hpp` = implementation, `include/CGAL/*.h` = thin wrapper, no duplication) · target design is generic **`FaceGraph + HalfedgeGraph`** but ships Surface_mesh-only · upstream CGAL submission is pre-submission-ready, not bound (12+ month horizon). Phase 8 extensions (8a.2 generic FaceGraph, 8c manuals, 8d CGAL-format tests, 8e YAML pipeline) are deferred to on-demand.
| Decision | Choice | ## Port-vs-research maintenance rule
|---|---|
| Submission to upstream CGAL | **Pre-submission-ready, not bound.** 12+ months horizon. |
| License | **MIT preserved** (no LGPL switch). |
| Mesh-type flexibility | **Generic `FaceGraph + HalfedgeGraph`** in target design; MVP starts Surface_mesh-only. |
| Parameter style | **Named Parameters** (`CGAL::parameters::...`). |
| Default kernel | **`Simple_cartesian<double>`** (status quo). |
| Backward compatibility | **Dual-layer wrapper**`code/include/*.hpp` stays as implementation, `include/CGAL/*.h` is thin wrapper. No algorithm duplication. |
| Implementation strategy | **Hybrid MVP** — minimum Phase 8 (traits + one wrapper) first, then Phase 9 in full, then Phase 8 extensions only on concrete demand. |
| Phase-8 MVP acceptance test | **Phase 9a (Inversive-Distance)** as the first new client of the new traits API. |
### Implementation sequence (committed) Before claiming something "ports X from Java", **verify empirically** — if zero matches, it is **new research** (→ `doc/roadmap/research-track.md` with citations, **not** `doc/roadmap/java-parity.md`):
```
1. Phase 7.5 Doxygen + cleanup done ✅
2. Phase 8 MVP — traits + one euclidean wrapper 35 days
3. Phase 9a — Inversive-Distance against new traits 35 days
4. Phase 9b — analytic HyperIdeal Hessian 1 week
5. Phase 9c — 4g-polygon for genus g > 1 1 week
→ port really complete, v0.9.0 release
```
Phase 8 extensions (8a.2 generic FaceGraph, 8c full Doxygen manuals, 8d
CGAL-format tests, 8e YAML pipeline) are deferred to on-demand status —
no speculative architecture for an uncertain submission.
Root-level files added at v0.7.0:
- `CITATION.cff` — machine-readable citation (Sechelmann 2016, Springborn 2020, BobenkoSpringborn 2004)
- `CONTRIBUTING.md` — short root-level pointer to `doc/contributing.md`
- `scripts/try_it.sh` — one-script quickstart: build → 209 tests → example run
- CMake install target: `cmake --install build --prefix /usr/local` → headers land in `include/conformallab/`
## Port-vs-research maintenance rule (2026-05-21 audit)
Before claiming something "ports X from Java", **verify empirically**:
```bash ```bash
find /Users/tarikmoussa/Desktop/conformallab -iname "*X*" find /Users/tarikmoussa/Desktop/conformallab -iname "*X*"
grep -r "ClassName" /Users/tarikmoussa/Desktop/conformallab/src grep -r "ClassName" /Users/tarikmoussa/Desktop/conformallab/src
``` ```
If zero matches, the work is **new research** — add it to The 2026-05-21 audit corrected four mis-labels (now fixed): `InversiveDistanceFunctional`, both HyperIdeal Hessians (FD + analytic), and the inversive-distance tutorial were all wrongly tagged "Java port" — they are research (no Java parent; Java declares `hasHessian()==false`). Details in `research-track.md`.
`doc/roadmap/research-track.md` with primary literature citations,
**not** to `doc/roadmap/java-parity.md`.
The 2026-05-21 audit found four pre-existing mis-labels:
| Item | Wrong claim | Reality |
|---|---|---|
| `InversiveDistanceFunctional` | "Java port (Luo 2004)" | No such Java class exists |
| HyperIdeal Hessian (FD) | "Phase 4a" | Research — Java has `hasHessian()==false` |
| HyperIdeal Hessian (analytic) | "Phase 9b port" | Research — derivation via Schläfli 1858 |
| Tutorial framing | "ports `InversiveDistanceFunctional.java`" | Implementation from Luo 2004 + Glickenstein 2011 |
All four are corrected as of this commit. Future contributors must
follow the empirical verification rule above before any new claim.
## Documentation map ## Documentation map
24 documents across 6 categories. Read the relevant one before reasoning from scratch 38 documents across 7 categories. Read the relevant one before reasoning from scratch
— do not hallucinate content that is already written down. — do not hallucinate content that is already written down.
### Mathematics & theory ### Mathematics & theory
@@ -348,6 +325,9 @@ follow the empirical verification rule above before any new claim.
| Key architectural decisions and their rationale | `doc/architecture/design-decisions.md` | | Key architectural decisions and their rationale | `doc/architecture/design-decisions.md` |
| Detailed comparison with geometry-central (CMU): overlap, adoption, scientific value | `doc/architecture/geometry-central-comparison.md` | | Detailed comparison with geometry-central (CMU): overlap, adoption, scientific value | `doc/architecture/geometry-central-comparison.md` |
| Phase 9a validation report (CP-Euclidean port + Luo-inversive-distance literature check) | `doc/architecture/phase-9a-validation.md` | | Phase 9a validation report (CP-Euclidean port + Luo-inversive-distance literature check) | `doc/architecture/phase-9a-validation.md` |
| Compile-time measurements + six-mode workflow matrix (macOS-vs-Linux honesty notes) | `doc/architecture/compile-time.md` |
| Required vs optional dependencies + standalone-verification recipe | `doc/architecture/dependencies.md` |
| Which 12 architecture decisions are locked vs still flexible | `doc/architecture/locked-vs-flexible.md` |
### API & extension ### API & extension
@@ -383,6 +363,18 @@ follow the empirical verification rule above before any new claim.
| Language policy, test standards, release flow | `doc/contributing.md` | | Language policy, test standards, release flow | `doc/contributing.md` |
| Versioning rules + release process + single-source-of-truth list | `doc/release-policy.md` | | Versioning rules + release process + single-source-of-truth list | `doc/release-policy.md` |
### Reviewer materials (v0.10.0)
| Question | Document |
|---|---|
| One-page reviewer briefing | `doc/reviewer/briefing.md` |
| Seven scoped reviewer questions (Q1Q7) | `doc/reviewer/questions.md` |
| Internal meeting agenda | `doc/reviewer/agenda.md` |
| Reviewer landing index | `doc/reviewer/README.md` |
| Hand-curated reviewer landing page (HTML, source-of-truth for codeberg pages) | `doc/reviewer/hub.html` |
The published hub lives at https://tmoussa.codeberg.page/ConformalLabpp/ (Doxygen index at `/doxygen.html`). See the "Codeberg pages" quirk below for how it is republished.
### geometry-central context ### geometry-central context
**geometry-central** (Keenan Crane, CMU) implements the same discrete conformal **geometry-central** (Keenan Crane, CMU) implements the same discrete conformal
@@ -393,9 +385,42 @@ The shared mathematical core (Springborn 2020) means cross-validation is meaning
Full analysis: `doc/architecture/geometry-central-comparison.md`. Full analysis: `doc/architecture/geometry-central-comparison.md`.
Optional adoption roadmap (GC-1/2/3): `doc/roadmap/phases.md` (Optional section). Optional adoption roadmap (GC-1/2/3): `doc/roadmap/phases.md` (Optional section).
## Agentic workflow patterns
Recommended loops when working in this repo. Prefer the cheapest gate that catches the class of error you just touched.
- **Add/port an algorithm**: new `.hpp` in `code/include/` → test in `code/tests/cgal/` (must include a gradient-check, see Test design patterns) → register in `code/tests/cgal/CMakeLists.txt` → build `conformallab_cgal_tests``ctest -R "^cgal\."`. Before claiming "ports X", run the empirical port-vs-research check above.
- **Inner dev loop** (fast iteration): `-DCONFORMALLAB_DEV_BUILD=ON` (PCH on, Unity off) for cheap incremental rebuilds; run a single suite via `--gtest_filter`. Switch back to the default (Unity on) for a final full build.
- **Before any commit**: run the four required gates locally — they mirror CI exactly and are seconds-cheap: `bash scripts/quality/license-headers.sh`, `python3 scripts/quality/cgal-conventions.py`, `bash scripts/quality/codespell.sh`, `bash scripts/quality/shellcheck.sh --strict`.
- **Before tagging a release**: also run the two now-un-gated structural gates (test-cgal is disabled in CI): `BUILD_DIR=build bash scripts/check-test-counts.sh` and `bash scripts/try_it.sh`. Update `CHANGELOG.md`, `CITATION.cff`, and the `doc/api/tests.md` counts (single source of truth).
- **Touching public-API headers**: rebuild Doxygen (`cmake --build build --target doc`) and re-check coverage (`bash scripts/doxygen-coverage.sh --threshold 100`); regenerate `doc/api/headers.md` via `python3 scripts/gen-headers-md.py` (or `bash scripts/regen-docs.sh`).
- **Landing to `main`** (origin is protected): branch → push to `origin` → open PR via `gh`/Gitea API → merge via API → also push `codeberg/main` directly → keep both remotes in sync.
- **Republishing the reviewer hub**: see the Codeberg `pages` quirk below — manual force-push of an orphan branch; verify the live URL with a cache-bust query.
- **Delegation**: this repo's heavy builds are slow on the ARM64 runner — when a task is genuinely parallelisable and independent, consider a background agent; otherwise handle inline. Always verify an agent's actual diff, not just its summary.
- **settings.json**: `.claude/settings.json` (committed) pre-allows the safe read/build/test/quality commands so they don't prompt, and denies destructive git on `main`. Extend the allowlist as new safe commands recur rather than re-approving each time.
### Token hygiene — session-cut (Tier 1, highest impact)
Every turn re-sends the whole conversation, so context accumulation is the largest avoidable cost.
- **One session per task.** After a task is done, start a fresh session (`/clear`) instead of pivoting to an unrelated task in the same thread — the old task's context is dead weight in every later turn.
- **Compact proactively at clean breakpoints.** Run `/compact <what matters>` when a task finishes and before the next starts, rather than waiting for auto-compaction at the limit (which you don't control).
- **Delegate read-heavy sweeps to a subagent.** An `Explore`/`Plan` subagent reads large amounts in *its* context and returns a short summary — the bulk never enters the main context. Use it for "where is X used / what depends on Y"; for a *known* path, `Read` directly (a subagent starts cold and only pays off on a large search space).
### Token hygiene — command discipline (Tier 2)
- **Scope + filter together.** Path-scope searches (`grep -rn "newton_" code/include/`, not repo-wide). Filter test output (`ctest -R "cgal.NewtonSolver" --output-on-failure`, or `--gtest_filter` for one suite) instead of dumping all 259 results.
- **Never read raw logs into context.** Redirect to a file, then `grep`/`tail` it. With `run_in_background`, read the output file selectively rather than pulling it whole.
- **Don't re-read a file you just edited.** `Edit` errors on stale state — the harness tracks it; a `Read`-back after a successful edit is wasted context.
- **Reference by line number** (`newton_solver.hpp:147`) instead of re-pasting code blocks.
Cache discipline (Tier 3) is a user-facing guide — see [`.claude/token-hygiene.md`](.claude/token-hygiene.md). **Remind the user of the relevant Tier-3 rule when you observe the matching anti-pattern** (mid-session CLAUDE.md edits, chained sub-5-minute waits, long multi-topic sessions).
## Known quirks ## Known quirks
- **No GTEST_SKIP stubs remain** (since v0.9.0): the three stale HDS-port stub files were removed because the CGAL test suite covers the same functionality with real tests. The pure-math `conformallab_tests` target now only contains active tests. - **No GTEST_SKIP stubs remain** (since v0.9.0): the three stale HDS-port stub files were removed because the CGAL test suite covers the same functionality with real tests. The pure-math `conformallab_tests` target now only contains active tests.
- **Boost is header-only**: CGAL 6.x uses only Boost headers (`Boost.Config`, `Boost.Graph`). No compiled Boost libraries are needed. `find_package(Boost REQUIRED)` only locates the include path. - **Boost is header-only**: CGAL 6.x uses only Boost headers (`Boost.Config`, `Boost.Graph`). No compiled Boost libraries are needed. `find_package(Boost REQUIRED)` only locates the include path.
- **`main` branch is protected** on `origin` (Gitea). Push to `dev`, then merge via pull request. Codeberg `main` can be pushed to directly. - **`main` branch is protected** on `origin` (Gitea). Push to `dev`, then merge via pull request. Codeberg `main` can be pushed to directly.
- **Both remotes must stay in sync**: `origin` = `git.eulernest.eu` (CI runs here), `codeberg` = `codeberg.org/TMoussa/ConformalLabpp` (public mirror). Push to both after every significant change. - **Both remotes must stay in sync**: `origin` = `git.eulernest.eu` (CI runs here), `codeberg` = `codeberg.org/TMoussa/ConformalLabpp` (public mirror, SSH). Push to both after every significant change.
- **Codeberg `pages` branch is an orphan publish target** that serves the reviewer hub + Doxygen HTML at https://tmoussa.codeberg.page/ConformalLabpp/. Its source-of-truth is `doc/reviewer/hub.html` (installed as `index.html`) + a local Doxygen build (`cmake --build build --target doc`, demoted to `/doxygen.html`). Because the auto-publish workflow (`doxygen-pages.yml`) is on `workflow_dispatch:` only, the branch is **not** refreshed on normal pushes and has been accidentally lost during force-push/merge cleanups. To republish manually: build Doxygen, copy `doc/doxygen/html/.` into a scratch dir, `mv index.html doxygen.html`, copy `hub.html``index.html`, then `git init -b pages && git commit && git push -f codeberg pages:pages`. Codeberg pages caches for ~10 min (`Cache-Control: max-age=600`) — verify with a cache-busting `?cb=$(date +%s)` query. Protect the branch in codeberg settings to prevent deletion (leave force-push allowed so CI/manual republish still works).
- **Both `main` branches are independent for the pages cycle**: `origin/main` is protected (PR-only); `codeberg/main` can be pushed directly.

View File

@@ -37,8 +37,9 @@ Phase milestone → MINOR-bump mapping (historical + planned):
| `v0.2.0` | Phase 2 (hyper-ideal geometry) | | `v0.2.0` | Phase 2 (hyper-ideal geometry) |
| `v0.7.0` | Phase 7 complete (period matrix, fundamental domain) | | `v0.7.0` | Phase 7 complete (period matrix, fundamental domain) |
| `v0.9.0` | Phase 9a + 9b + 8b-Lite | | `v0.9.0` | Phase 9a + 9b + 8b-Lite |
| `v0.10.0` | Phase 9c (4g-polygon, genus g > 1) | | `v0.10.0` | Reviewer-ready: 100 % Doxygen, quality-gate suite, reviewer materials, compile-time matrix (no new geometry phase) |
| `v0.11.0` | Phase 10a (harmonic + holomorphic 1-forms) | | `v0.11.0` | Phase 9c (4g-polygon, genus g > 1) *(planned)* |
| `v0.12.0` | Phase 10a (harmonic + holomorphic 1-forms) *(planned)* |
| `v1.0.0` | CGAL package submission-ready | | `v1.0.0` | CGAL package submission-ready |
### Post-1.0 phase (planned, ≥ `v1.0.0`) ### Post-1.0 phase (planned, ≥ `v1.0.0`)
@@ -104,6 +105,36 @@ overhead. We pay the click.
This catches the `tests.md` itself going stale even if no other doc This catches the `tests.md` itself going stale even if no other doc
hardcodes the number. hardcodes the number.
### Documentation passes
The single-source-of-truth rules above only work if the docs are actually
re-synced on a predictable schedule. A "documentation pass" is the named
ritual that does this. Two rules make it durable instead of forgotten:
**Anchor it to a trigger, never do it mid-task.**
| Trigger | What the pass covers |
|---|---|
| **Release tag** | `CHANGELOG.md`, `CITATION.cff` (version + date), `doc/api/tests.md`, CLAUDE.md "Release state" + "Documentation map" count. |
| **Feature branch, just before the PR** | Land all doc edits the branch implies as a single final `docs: sync` commit — so the doc update is part of every merge, not a separate event you can skip. |
| **Mid-code-work** | **Do not fix docs inline.** Note the drift (a TODO, a memory decision-record, or a flagged follow-up task) and fold it into the next `docs: sync`. Inline doc edits bloat the PR's scope and — for AI-assisted sessions — bust the prompt cache (see `.claude/token-hygiene.md`). |
**Make drift visible with a cheap gate.**
`scripts/quality/check-doc-freshness.sh` verifies the *string-only*
cross-file invariants in well under a second (no build):
* release version agrees across `CITATION.cff`, `CHANGELOG.md` (newest
section), and CLAUDE.md;
* release date agrees across `CITATION.cff` and `CHANGELOG.md`;
* the "N documents across …" count in CLAUDE.md's Documentation map
equals the actual `doc/**/*.md` file count.
Run it as the first step of any documentation pass. The two
build-dependent checks it deliberately skips — `scripts/check-test-counts.sh`
(needs a build dir) and `scripts/gen-headers-md.py` drift (needs Doxygen
XML) — belong to the *release* pass, not the per-branch one.
--- ---
## 3. Release process (checklist) ## 3. Release process (checklist)
@@ -137,7 +168,9 @@ The recipe that worked for v0.9.0 (after the false-start with PR #11 /
7. **Open one PR** with all prep commits. Title: 7. **Open one PR** with all prep commits. Title:
`release: vX.Y.Z (<summary>)`. Description: copy the new `release: vX.Y.Z (<summary>)`. Description: copy the new
`CHANGELOG.md` section. `CHANGELOG.md` section.
8. **Wait for CI green** — `test-fast` + `test-cgal` + `doc-build`. 8. **Wait for CI green** — `test-fast` + `quality-gates`. (`test-cgal`
is disabled since 2026-05-26 — OOM-prone on the runner; run the CGAL
suite locally instead, see step 3.)
9. **Merge** with a merge commit (the merge SHA becomes the release 9. **Merge** with a merge commit (the merge SHA becomes the release
target). target).

View File

@@ -19,6 +19,7 @@ consistency + markdown links + end-to-end smoke via `try_it.sh`).
| Script | What it checks | Wall time | Prereqs | | Script | What it checks | Wall time | Prereqs |
|---|---|---|---| |---|---|---|---|
| `license-headers.sh` | every C++ source carries `SPDX-License-Identifier: MIT` | ~1 s | `bash` | | `license-headers.sh` | every C++ source carries `SPDX-License-Identifier: MIT` | ~1 s | `bash` |
| `check-doc-freshness.sh` | string-only doc drift: version/date agree across CITATION/CHANGELOG/CLAUDE; doc-map count matches `doc/**/*.md` | <1 s | `bash` |
| `cgal-conventions.py` | CGAL-16: include-guard format, `\file` brief, namespace nesting, tag-naming, no `using namespace`, no stray `#define` | ~1 s | `python3` | | `cgal-conventions.py` | CGAL-16: include-guard format, `\file` brief, namespace nesting, tag-naming, no `using namespace`, no stray `#define` | ~1 s | `python3` |
| `clang-format.sh` | every C++ source matches `.clang-format` (dry-run by default; `--fix` to apply) | ~2 s | `clang-format` 15 | | `clang-format.sh` | every C++ source matches `.clang-format` (dry-run by default; `--fix` to apply) | ~2 s | `clang-format` 15 |
| `cmake-format.sh` | every `CMakeLists.txt` matches `.cmake-format.yaml` + passes `cmake-lint` | ~2 s | `cmake-format` (pip: cmakelang) | | `cmake-format.sh` | every `CMakeLists.txt` matches `.cmake-format.yaml` + passes `cmake-lint` | ~2 s | `cmake-format` (pip: cmakelang) |

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# scripts/quality/check-doc-freshness.sh
#
# Cheap, string-only documentation-freshness gate. Catches the cross-file
# drift that a "documentation pass" (see doc/release-policy.md) is meant to
# prevent — without building anything, so it runs in well under a second.
#
# Invariants checked:
# 1. Release version agrees across CITATION.cff, CHANGELOG.md (newest
# section) and CLAUDE.md ("Current release:" line).
# 2. Release date agrees across CITATION.cff and CHANGELOG.md.
# 3. The "N documents across ..." count in CLAUDE.md's Documentation map
# equals the actual number of doc/**/*.md files.
#
# NOT checked here (they need a build — run them as part of the release
# documentation pass, see doc/release-policy.md):
# * doc/api/tests.md counts → scripts/check-test-counts.sh (needs BUILD_DIR)
# * doc/api/headers.md regeneration → scripts/gen-headers-md.py (needs Doxygen XML)
#
# Usage:
# bash scripts/quality/check-doc-freshness.sh
#
# Exit codes:
# 0 all string invariants hold
# 1 at least one drift found
# 2 prerequisite missing (a required file is absent)
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT" || exit 2
for f in CITATION.cff CHANGELOG.md CLAUDE.md; do
if [ ! -f "$f" ]; then
echo "FAIL: required file not found: $f" >&2
exit 2
fi
done
fail=0
note() { printf ' %-4s %s\n' "$1" "$2"; }
# Strip an optional leading "v" so "v0.10.0" and "0.10.0" compare equal.
strip_v() { printf '%s' "${1#v}"; }
# ── Invariant 1 + 2: version & date triple ──────────────────────────────────
cit_ver=$(strip_v "$(grep -E '^version:' CITATION.cff | head -1 | awk '{print $2}')")
cit_date=$(grep -E '^date-released:' CITATION.cff | head -1 | awk '{print $2}')
chg_header=$(grep -m1 -E '^## \[' CHANGELOG.md)
chg_ver=$(strip_v "$(printf '%s' "$chg_header" | sed -E 's/^## \[([^]]+)\].*/\1/')")
chg_date=$(printf '%s' "$chg_header" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1)
claude_ver=$(strip_v "$(grep -m1 'Current release:' CLAUDE.md | sed -E 's/.*\*\*(v?[0-9][0-9.]*)\*\*.*/\1/')")
echo "── version / date consistency ──"
note "CIT" "CITATION.cff version=$cit_ver date=$cit_date"
note "CHG" "CHANGELOG.md version=$chg_ver date=$chg_date"
note "CLD" "CLAUDE.md version=$claude_ver"
if [ "$cit_ver" = "$chg_ver" ] && [ "$cit_ver" = "$claude_ver" ]; then
note "OK" "version agrees ($cit_ver)"
else
note "DRIFT" "version mismatch: CITATION=$cit_ver CHANGELOG=$chg_ver CLAUDE=$claude_ver"
fail=1
fi
if [ "$cit_date" = "$chg_date" ]; then
note "OK" "date agrees ($cit_date)"
else
note "DRIFT" "date mismatch: CITATION=$cit_date CHANGELOG=$chg_date"
fail=1
fi
# ── Invariant 3: documentation-map count ────────────────────────────────────
stated=$(grep -m1 -E '^[0-9]+ documents across' CLAUDE.md | awk '{print $1}')
actual=$(find doc -name '*.md' -type f | wc -l | tr -d ' ')
echo
echo "── documentation-map count ──"
note "CLD" "CLAUDE.md states: $stated documents"
note "DISK" "doc/**/*.md on disk: $actual"
if [ "$stated" = "$actual" ]; then
note "OK" "doc count agrees ($actual)"
else
note "DRIFT" "CLAUDE.md says $stated, disk has $actual — update the 'N documents across' line"
fail=1
fi
echo
if [ "$fail" -ne 0 ]; then
echo "FAIL: documentation drift detected."
echo "Recovery: run a documentation pass (see doc/release-policy.md →"
echo " 'Documentation passes'); fix the lines above, then re-run."
exit 1
fi
echo "OK: documentation strings are consistent."
echo "Reminder: at release also run scripts/check-test-counts.sh and"
echo " scripts/gen-headers-md.py (both need a build)."
exit 0

View File

@@ -35,6 +35,7 @@ FAST=0
GATES_FAST=( GATES_FAST=(
"License headers | bash scripts/quality/license-headers.sh" "License headers | bash scripts/quality/license-headers.sh"
"Doc freshness | bash scripts/quality/check-doc-freshness.sh"
"CGAL conventions | python3 scripts/quality/cgal-conventions.py" "CGAL conventions | python3 scripts/quality/cgal-conventions.py"
"clang-format drift | bash scripts/quality/clang-format.sh" "clang-format drift | bash scripts/quality/clang-format.sh"
"cmake-format/-lint | bash scripts/quality/cmake-format.sh" "cmake-format/-lint | bash scripts/quality/cmake-format.sh"