From 15607b93bb0a8a0f58821b4a9834aaa081afd500 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 15 Oct 2026 18:13:44 +0200 Subject: [PATCH] chore: pin ct-conventions v0.5.0 - AGENTS.md pins ct-conventions v0.5.0 (read from a git-ignored clone in .conventions/) and cites rules by identifier; CLAUDE.md redirects to it. - References to the former Handbook become rule identifiers. - One .markdownlint.json (GEN2); CONTRIBUTING.md says where the conventions are and how to bump them. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 5 +- .markdownlint.json | 8 +- AGENTS.md | 73 ++++++++++++------- CHANGELOG.md | 8 +- CLAUDE.md | 59 +-------------- CONTRIBUTING.md | 8 ++ _typos.toml | 1 + docs/make.jl | 2 +- src/imports/ctbase.jl | 2 +- src/imports/ctmodels.jl | 2 +- test/runtests.jl | 4 +- .../environment/test_environment_contract.jl | 8 +- test/suite/shape/test_shape_contract.jl | 2 +- 13 files changed, 83 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index aa986afda..f7c2132dd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,7 @@ Manifest.toml .extras/ .save/ .devin/ -.reports/ \ No newline at end of file +.reports/ + +# ct-conventions, cloned at the tag pinned in AGENTS.md +.conventions/ diff --git a/.markdownlint.json b/.markdownlint.json index 91b0b98a3..72ef7a5c4 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,5 +1,7 @@ { - "MD046": false, "MD013": false, - "MD033": false -} \ No newline at end of file + "MD024": { "siblings_only": true }, + "MD033": false, + "MD046": false, + "MD051": false +} diff --git a/AGENTS.md b/AGENTS.md index f75fc82b3..bb3e7fdf1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,11 +12,12 @@ CTLie, CTDirect, CTSolvers, CTFlows). It does not follow the "one submodule per responsibility" layout used by sibling packages. ```text -src/imports/ # Per-dependency reexports (`@reexport import Pkg: sym`) — builds the public API -src/helpers/ # Internal helpers (routing, completion, registry, printing) -src/solve/ # `solve` dispatch: mode detection, descriptive/explicit paths -test/suite/ # Test suite: organised by functionality, not by src/ layout -docs/ # Documentation site (DocumenterVitepress) +src/imports/ # Per-dependency reexports (`@reexport import Pkg: sym`) — builds the public API +src/helpers/ # Internal helpers (routing, completion, registry, printing) +src/solve/ # `solve` dispatch: mode detection, descriptive/explicit paths +test/suite/ # Test suite: organised by functionality, not by src/ layout +docs/ # Documentation site (DocumenterVitepress) +.conventions/ # ct-conventions, cloned at the pinned tag (git-ignored) ``` There is no `ext/`: optional backends (ADNLPModels, ExaModels, AD) are armed by @@ -24,35 +25,51 @@ dependency presence, wired in `src/imports/*.jl`, not weak-dependency extensions --- -## Developer Resources +## Conventions -Design philosophy, operational rules, plan templates, and CI/CD conventions live in the -[control-toolbox Handbook](https://github.com/control-toolbox/Handbook): +The coding conventions live in +[ct-conventions](https://github.com/control-toolbox/ct-conventions), pinned below to a +SemVer release tag (`vX.Y.Z`). Read them from a local clone in `.conventions/` +(git-ignored, not a submodule). If it is missing, or `git -C .conventions describe --tags` +is not the pinned tag, (re)create it: -| Topic | Link | +```bash +rm -rf .conventions +git -c advice.detachedHead=false clone --quiet --depth 1 --branch v0.5.0 https://github.com/control-toolbox/ct-conventions.git .conventions +``` + +**Pinned version: ct-conventions v0.5.0.** Cite rules by identifier (`MOD3`, `TST1`), and +state the version you applied in plans and reports. + +Read in this order: + +| File | Content | | --- | --- | -| Code philosophy (modules, types/traits, exceptions, docstrings, testing, docs) | [`PHILOSOPHY.md`](https://raw.githubusercontent.com/control-toolbox/Handbook/refs/heads/main/PHILOSOPHY.md) | -| Operational rules (tests, coverage, docs, git) | [`RULES.md`](https://raw.githubusercontent.com/control-toolbox/Handbook/refs/heads/main/RULES.md) | -| Plan template | [`PLAN.md`](https://raw.githubusercontent.com/control-toolbox/Handbook/refs/heads/main/PLAN.md) | -| CI/CD workflows (centralized reusable workflows, label-gated triggers) | [`WORKFLOWS.md`](https://raw.githubusercontent.com/control-toolbox/Handbook/refs/heads/main/WORKFLOWS.md) | +| [`rules/general.md`](.conventions/rules/general.md) | Rules that hold everywhere — **always read** | +| [`README.md`](.conventions/README.md) | Index of the rule files, one per area (`MOD`, `TYP`, `DIM`, `EXC`, `PERF`, `DOC`, `TST`, `SITE`, `CHG`) | +| [`method.md`](.conventions/method.md) | How to work: tests, coverage, docs build, output capture | +| [`plan-template.md`](.conventions/plan-template.md) | Template for a substantial piece of work | + +How agents behave on these repositories is described in +[ct-agents](https://github.com/control-toolbox/ct-agents). The CI workflows and their +trigger labels are described in +[CTActions `docs/WORKFLOWS.md`](https://github.com/control-toolbox/CTActions/blob/main/docs/WORKFLOWS.md). --- ## Key Conventions -- **Exports a curated public API** — unlike sibling packages ("no top-level exports"), - OptimalControl is the ecosystem's DSL entry point: it re-exports selected symbols from - CTBase, CTModels, CTLie, CTDirect, CTSolvers, CTFlows. +- **English everywhere** — code, docs, commits, issues, PRs (`GEN1`). +- **Exports a curated public API** — unlike sibling packages (`MOD8`: no top-level + exports), OptimalControl is the ecosystem's DSL entry point: it re-exports selected + symbols from CTBase, CTModels, CTLie, CTDirect, CTSolvers, CTFlows. - **`@reexport import Pkg: sym`** — the one place `import` is intentional in this - ecosystem: `src/imports/*.jl` builds the exported surface this way; everywhere else, - qualified `using` only (`using Pkg: Pkg`), never bare `using Pkg`. -- **Fake types at module top-level** — never inside test functions. -- **Structured errors** — seven typed exceptions under `CTException`; pick by the - IncorrectArgument / PreconditionError / NotImplemented rule. -- **Type stability enforced** — hot paths must be `@inferred`-clean, verified with JET; - setup-path dispatch is fine. -- **1-D is a scalar** — a one-dimensional state/control/variable is a `Number`, never a - length-1 vector. -- **Plans before code** — write a plan and confirm with the user before touching files. -- **Docstrings last** — written only after all implementation steps are stable. -- **Never commit or push without explicit user approval.** + ecosystem (an exemption from `MOD3`): `src/imports/*.jl` builds the exported surface + this way; everywhere else, qualified `using` only (`using Pkg: Pkg`, `MOD4`). +- **Fake types at module top-level** — never inside test functions (`TST3`). +- **Structured errors** — seven typed exceptions; pick by the choice rule (`EXC1`). +- **Type stability enforced** — hot paths `@inferred`-clean, verified with JET (`TYP8`, `PERF1`–`PERF5`). +- **1-D is a scalar** — a one-dimensional state/control/variable is a `Number` (`DIM1`). +- **Plan before code** — a local agent proposes a plan ([`plan-template.md`](.conventions/plan-template.md)) and waits for confirmation before working on code. The process for remote agents is still to be defined. +- **Docstrings last** — written once the implementation is stable (`DOC1`–`DOC8`). +- **The human merges** — an agent never merges a pull request. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4129f2aed..b87360c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ - # Changelog All notable changes to **OptimalControl.jl** are documented here. @@ -12,6 +11,13 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 📚 Documentation +- ct-conventions pinned at 0.5.0 in `AGENTS.md` (read from a git-ignored clone in + `.conventions/`); `AGENTS.md` cites rules by identifier, `CLAUDE.md` redirects to it, and + the references to the former Handbook become rule identifiers. One `.markdownlint.json` + (`GEN2`) replaces the `markdownlint-disable MD024` comment of this changelog (`CHG2`). + +### 📚 Documentation + - **Initial guess: check a guess before solving with `build_initial_guess`** ([#965](https://github.com/control-toolbox/OptimalControl.jl/issues/965)). It builds and validates the guess against the problem. The other initial-guess helpers of CTModels (`initial_guess`, `pre_initial_guess`, `validate_initial_guess`, `initial_state`, `initial_control`, `initial_variable`, `PreInitialGuess`) stay unexported, and a test now records that choice. ### 🐛 Bug Fixes diff --git a/CLAUDE.md b/CLAUDE.md index c29c8178a..3639ba831 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,58 +1,5 @@ -# OptimalControl.jl — Agent Navigation Guide +# OptimalControl.jl — Claude Code -Quick-reference for any agent working on this repository. +All agent instructions live in `AGENTS.md`; this file only redirects to it. ---- - -## Repository Layout - -OptimalControl is the ecosystem's user-facing DSL package: a single flat module that -orchestrates and re-exports the lower-level control-toolbox packages (CTBase, CTModels, -CTLie, CTDirect, CTSolvers, CTFlows). It does not follow the "one submodule per -responsibility" layout used by sibling packages. - -```text -src/imports/ # Per-dependency reexports (`@reexport import Pkg: sym`) — builds the public API -src/helpers/ # Internal helpers (routing, completion, registry, printing) -src/solve/ # `solve` dispatch: mode detection, descriptive/explicit paths -test/suite/ # Test suite: organised by functionality, not by src/ layout -docs/ # Documentation site (DocumenterVitepress) -``` - -There is no `ext/`: optional backends (ADNLPModels, ExaModels, AD) are armed by -dependency presence, wired in `src/imports/*.jl`, not weak-dependency extensions. - ---- - -## Developer Resources - -Design philosophy, operational rules, plan templates, and CI/CD conventions live in the -[control-toolbox Handbook](https://github.com/control-toolbox/Handbook): - -| Topic | Link | -| --- | --- | -| Code philosophy (modules, types/traits, exceptions, docstrings, testing, docs) | [`PHILOSOPHY.md`](https://github.com/control-toolbox/Handbook/blob/main/PHILOSOPHY.md) | -| Operational rules (tests, coverage, docs, git) | [`RULES.md`](https://github.com/control-toolbox/Handbook/blob/main/RULES.md) | -| Plan template | [`PLAN.md`](https://github.com/control-toolbox/Handbook/blob/main/PLAN.md) | -| CI/CD workflows (centralized reusable workflows, label-gated triggers) | [`WORKFLOWS.md`](https://github.com/control-toolbox/Handbook/blob/main/WORKFLOWS.md) | - ---- - -## Key Conventions - -- **Exports a curated public API** — unlike sibling packages ("no top-level exports"), - OptimalControl is the ecosystem's DSL entry point: it re-exports selected symbols from - CTBase, CTModels, CTLie, CTDirect, CTSolvers, CTFlows. -- **`@reexport import Pkg: sym`** — the one place `import` is intentional in this - ecosystem: `src/imports/*.jl` builds the exported surface this way; everywhere else, - qualified `using` only (`using Pkg: Pkg`), never bare `using Pkg`. -- **Fake types at module top-level** — never inside test functions. -- **Structured errors** — seven typed exceptions under `CTException`; pick by the - IncorrectArgument / PreconditionError / NotImplemented rule. -- **Type stability enforced** — hot paths must be `@inferred`-clean, verified with JET; - setup-path dispatch is fine. -- **1-D is a scalar** — a one-dimensional state/control/variable is a `Number`, never a - length-1 vector. -- **Plans before code** — write a plan and confirm with the user before touching files. -- **Docstrings last** — written only after all implementation steps are stable. -- **Never commit or push without explicit user approval.** +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b2fc2d179..e01b34d31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,3 +12,11 @@ If you want to ask a question, feel free to start a discussion [here](https://gi >[!NOTE] > If you want to add an application or a package to the control-toolbox ecosystem, please follow this [set up tutorial](https://github.com/orgs/control-toolbox/discussions/65). + +--- + +## Developer Resources + +The [ct-conventions](https://github.com/control-toolbox/ct-conventions) repository (coding rules, method, plan template) is pinned to a SemVer release tag in `AGENTS.md` ("Pinned version"). Read it from a local clone in `.conventions/`, which git ignores; `AGENTS.md` gives the clone command. + +It is not a git submodule: `Pkg.add` on Julia 1.10 fails on a package whose tree contains one (`cannot get submodules without a working tree`). To bump it, change the tag in `AGENTS.md` (the "Pinned version" line and the clone command), then re-clone. diff --git a/_typos.toml b/_typos.toml index 1ca3dff01..2048292d8 100644 --- a/_typos.toml +++ b/_typos.toml @@ -20,4 +20,5 @@ extend-exclude = [ "*.json", "*.toml", "*.svg", + ".conventions/", ] diff --git a/docs/make.jl b/docs/make.jl index 11dd2b7c7..07c70278f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -48,7 +48,7 @@ using CairoMakie: CairoMakie # `import`, not `using`: keeps Makie's `plot`/`pl # prefers SVG. Both Plots.jl and CairoMakie respond to `image/png`, so PNG wins for # every figure unless it is disabled. Set once here, before any @example block runs, # so it covers every page present or future — not just the ones plotting today. See -# Handbook/VITEPRESS-DOC.md "Plot image format — SVG vs PNG". +# ct-conventions SITE12. Base.showable(::MIME"image/png", ::Plots.Plot) = false CairoMakie.activate!(; type="svg") Base.showable(::MIME"image/png", ::CairoMakie.Makie.Figure) = false diff --git a/src/imports/ctbase.jl b/src/imports/ctbase.jl index 93664788d..5180a9f76 100644 --- a/src/imports/ctbase.jl +++ b/src/imports/ctbase.jl @@ -3,7 +3,7 @@ # Since the ecosystem-wide restructuring, CTBase owns the strategy/option layer # (previously in CTSolvers) and the `Data` type vocabulary `Flow` dispatches on # (previously in CTFlows). Every import below points at the *owning* submodule, -# per the Handbook `modules.md` rule. +# per ct-conventions `MOD4`. # Generated code — `@Lie` expands to `CTBase.Traits.*` prefixes, so `CTBase` # itself must stay in scope at every call site. diff --git a/src/imports/ctmodels.jl b/src/imports/ctmodels.jl index d40af4188..e5e89324a 100644 --- a/src/imports/ctmodels.jl +++ b/src/imports/ctmodels.jl @@ -1,7 +1,7 @@ # CTModels reexports # # Re-pointed at the owning submodules (`Components`, `Models`, `Solutions`, -# `Building`, `Init`, `Serialization`), per the Handbook `modules.md` rule. +# `Building`, `Init`, `Serialization`), per ct-conventions `MOD4`. # # Two names that used to be here are gone: # - `time` — it is `Base.time`, extended (but not exported) by diff --git a/test/runtests.jl b/test/runtests.jl index 94dc9a3e2..33485fca1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,8 +30,8 @@ using .TestOptions: VERBOSE, SHOWTIMING # CUDA_FUNCTIONAL — is there a functional GPU *device*? The suite's single device # predicate — never define a local `is_cuda_on()` in a test file. # Guard device runs with `Test.@test_skip` on the `else`, never a -# silent `if CUDA_FUNCTIONAL` with no branch (see Handbook -# philosophy/testing.md §"Capability-gated tests"). +# silent `if CUDA_FUNCTIONAL` with no branch (see ct-conventions +# `TST6`, capability-gated tests skip visibly). # ON_GPU_RUNNER — turns the device tier from *skipped* into *required* on the # self-hosted GPU runners. `RUNNER_NAME` is set by the GitHub Actions # runner agent itself (no CI.yml change needed) to the runner's diff --git a/test/suite/environment/test_environment_contract.jl b/test/suite/environment/test_environment_contract.jl index 38a7577ce..572f56985 100644 --- a/test/suite/environment/test_environment_contract.jl +++ b/test/suite/environment/test_environment_contract.jl @@ -1,8 +1,8 @@ # ============================================================================ # Test-environment contract # ============================================================================ -# Central enforcement of the Handbook's capability-gated-test policy -# (philosophy/testing.md §"Capability-gated tests"). Mirrors the equivalent +# Central enforcement of the capability-gated-test policy (ct-conventions +# `TST6`). Mirrors the equivalent # file in CTSolvers (control-toolbox/CTSolvers.jl#190/#223). # # Three things are pinned here so they cannot regress silently: @@ -68,8 +68,8 @@ Recursively find, under `test/suite/` (located via `@__DIR__`, not `pwd()`), lin an `if` block directly on a CUDA-device predicate — a local `is_cuda_on()` call or a bare `CUDA.functional()` call — the anti-pattern that makes a correctly-skipped run (no device, as expected on a developer machine) and a silently-broken run (device *should* be present -but isn't) produce the same output: a green testset with zero assertions (Handbook -`philosophy/testing.md` §"Capability-gated tests", control-toolbox/CTSolvers.jl#189). +but isn't) produce the same output: a green testset with zero assertions (ct-conventions +`TST6`, control-toolbox/CTSolvers.jl#189). The sanctioned form is `if Main.TestCapabilities.CUDA_FUNCTIONAL ... else Test.@test_skip ... end`, with the device tier made *required* on the GPU runners centrally, in the testset diff --git a/test/suite/shape/test_shape_contract.jl b/test/suite/shape/test_shape_contract.jl index fd6d37794..18bb15555 100644 --- a/test/suite/shape/test_shape_contract.jl +++ b/test/suite/shape/test_shape_contract.jl @@ -22,7 +22,7 @@ # way it is solved, and nothing upstream can check that: it is precisely # the seam between CTDirect and CTFlows that OptimalControl owns. # -# The recording fakes live at module top level, per the Handbook recipe: a +# The recording fakes live at module top level, per ct-conventions `TST3`: a # closure would be recompiled per call site and the recorded types would say # more about the closure than about the coercion.