Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ Manifest.toml
.extras/
.save/
.devin/
.reports/
.reports/

# ct-conventions, cloned at the tag pinned in AGENTS.md
.conventions/
8 changes: 5 additions & 3 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"MD046": false,
"MD013": false,
"MD033": false
}
"MD024": { "siblings_only": true },
"MD033": false,
"MD046": false,
"MD051": false
}
73 changes: 45 additions & 28 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,64 @@ 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
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.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<!-- markdownlint-disable MD024 -->
# Changelog

All notable changes to **OptimalControl.jl** are documented here.
Expand All @@ -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
Expand Down
59 changes: 3 additions & 56 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ extend-exclude = [
"*.json",
"*.toml",
"*.svg",
".conventions/",
]
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/imports/ctbase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/imports/ctmodels.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions test/suite/environment/test_environment_contract.jl
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/suite/shape/test_shape_contract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading