Essential guidance for AI assistants working in this repository.
This file is the entry point for all coding agents. Detailed rules are
split into additional documents under docs/dev/. Agents MUST read the
referenced files before making changes.
- Required Reading
- Core Rules
- Validation Workflow
- Project Context
- Design Principles
- Testing
- Documentation Maintenance
- Agent Behavior Expectations
Before modifying code, agents MUST read:
AGENTS.md(this file)- All files in
docs/dev/*.md– repository development rules docs/code_organization.md– module layout and architecture
The docs/dev/ directory contains the authoritative development guidance
for this repository. Agents must load every file in that directory before
making changes.
- NEVER run
git commit,git push,git tag, or any git commands that modify version control state - ALLOWED: read‑only git commands (
git --no-pager status,git --no-pager diff,git --no-pager log,git --no-pager show,git --no-pager blame) - ALWAYS use
git --no-pagerwhen reading git output - When suggesting branch names, prefer
{type}/{issue}-descriptor-or-two, e.g.fix/307-oriented-flips,perf/315-bench-profile, ordoc/329-branch-guidance. If an environment requires an owner/tool prefix, keep this structure after the prefix, e.g.codex/fix/307-oriented-flips. Typical types arefix,feat,perf,doc,test,refactor,ci,build,chore, andstyle. - Suggest git commands that modify version control state for the user to run manually
When using the gh CLI to view issues, PRs, or other GitHub objects:
-
ALWAYS use
--jsonwith| catto avoid pager and scope errors:gh issue view 212 --repo acgetchell/delaunay --json title,body | cat -
To extract specific fields cleanly, combine
--jsonwith--jq:gh issue view 212 --repo acgetchell/delaunay --json title,body --jq '.title + "\n" + .body' | cat
-
AVOID plain
gh issue view N— it may fail withread:projectscope errors or open a pager. -
To manage issue dependencies (Blocks / Is Blocked By), use the GitHub REST API via
gh api. The endpoint requires the internal issue ID (not the issue number).To get an issue's internal ID:
gh api repos/acgetchell/delaunay/issues/233 --jq '.id'To add a "blocked by" dependency (e.g. #254 is blocked by #233):
gh api repos/acgetchell/delaunay/issues/254/dependencies/blocked_by \ -X POST -F issue_id=<BLOCKING_ISSUE_ID>
To list existing blocked‑by dependencies:
gh api repos/acgetchell/delaunay/issues/254/dependencies/blocked_by \ --jq '[.[].number]' | cat
Note: Use
-F(not-f) forissue_idso it is sent as an integer. The API returns HTTP 422 if the dependency already exists. -
When updating issues, use explicit
comment/editcommands. For arbitrary Markdown (backticks, quotes, special characters), prefer--body-file -with a heredoc:gh issue comment 242 --repo acgetchell/delaunay --body-file - <<'EOF' ## Heading Body with `backticks`, **bold**, and apostrophes that's safe. EOF
For simple text only (no apostrophes or special characters), single‑quoted
--bodyis fine:gh issue comment 242 --repo acgetchell/delaunay --body 'Simple update text'
- NEVER use
sed,awk,perl, orpythonto modify code - ALWAYS use the patch editing mechanism provided by the agent
- Shell text tools may be used for read‑only analysis only
When generating commit messages:
- Run
git --no-pager diff --cached --stat - Use conventional commits:
<type>: <summary> - Valid types:
feat,fix,refactor,perf,docs,test,chore,style,ci,build - Include bullet‑point body describing key changes
- Present inside a code block so the user can commit manually
Commit bodies appear verbatim in CHANGELOG.md (indented by
git‑cliff's template). Write them as clean, readable prose:
- Keep the subject line concise — it becomes the changelog entry.
- The type determines the changelog section (
feat→ Added,fix→ Fixed,refactor/test/style→ Changed,perf→ Performance,docs→ Documentation,build/chore/ci→ Maintenance). - Include PR references as
(#N)in the subject — cliff auto‑links them (e.g.feat: add foo (#42)). - Avoid headings
#–###in the body — they conflict with changelog structure (##= release,###= section). Use####if a heading is truly needed. - Body text should be plain prose or simple lists. Numbered lists and sub‑items are fine but avoid deep nesting.
Breaking changes must use one of these conventional commit markers so
that git‑cliff can detect them and generate the
### ⚠️ Breaking Changes section in CHANGELOG.md:
- Bang notation:
feat!: remove deprecated API(append!after the type/scope) - Footer trailer: add
BREAKING CHANGE: <description>as a git trailer at the end of the commit body
Examples of breaking changes: removing/renaming public API items, changing default behaviour, bumping MSRV, altering serialisation formats.
After modifying files, run appropriate validators.
Common commands:
just check
just fix
just ciCodex sandbox shells may not include Homebrew on PATH. When a validation
recipe or direct tooling command needs uv, prefer:
PATH=/opt/homebrew/bin:$PATH just check
/opt/homebrew/bin/uv run pytest scripts/testsRefer to docs/dev/commands.md for full details.
For tooling-alignment work, update docs/dev/tooling-alignment.md with the
comparison and rationale before adding or changing config, workflow, or
repository-rule files.
- Language: Rust
- Project: d‑dimensional Delaunay triangulation library
- MSRV: 1.95
- Edition: 2024
- Unsafe code: forbidden (
#![forbid(unsafe_code)])
Architecture details are documented in:
docs/code_organization.md
This is a scientific d‑dimensional Delaunay triangulation library. Design decisions trade off in roughly this priority: numerical correctness → topological correctness → API stability → composability → idiomatic Rust → performance within scope. The sections below spell out what each means in practice; when in doubt, favour the invariant over the convenient edit.
- Geometric predicates (
insphere,insphere_lifted,orientation) use the three‑stage pattern fromsrc/geometry/predicates.rs: Stage 1 — provable f64 fast filter with a Shewchuk‑style errbound; Stage 2 — exact sign via Bareiss inla-stack; Stage 3 — deterministicInSphere::BOUNDARY/Orientation::DEGENERATEfallback for non‑finite inputs. A new predicate must either plug into this pattern or justify the deviation in its docs. - No f64 operation may silently lose sign information.
unwrap_or(NaN),unwrap_or(f64::INFINITY), or "returntrueon error" are anti‑patterns. - Algorithms cite their source (Shewchuk, Bowyer–Watson, Edelsbrunner,
Preparata–Shamos, …) in
REFERENCES.mdand document their conditioning behaviour. - When two predicate implementations cover the same question
(e.g.
inspherevsinsphere_distancevsinsphere_lifted), a proptest verifies they agree on the domain where all are defined.
- Every mutating operation preserves the invariants checked by
Tds::is_valid(Level 1–3) andDelaunayTriangulation::is_valid(Level 4). An operation that cannot preserve them must fail explicitly rather than leave the triangulation in an inconsistent state. - PL‑manifold invariants: facets have multiplicity 1 (boundary) or 2
(interior); ridges are linked consistently; the Euler characteristic
matches the triangulation's
TopologyGuarantee. The checks live insrc/topology/manifold.rsandsrc/topology/characteristics/. - Repair paths (
repair_delaunay_with_flips,repair_facet_oversharing,delaunayize_by_flips) bound their work via explicit budgets (max_flips,max_iterations,max_simplices_removed) and surface non‑convergence as a typed error — never by logging and proceeding.
The library exposes four validation levels, each a superset of the last:
- Level 1 — elements: individual simplices, vertices, facets are internally consistent (dimensions, UUIDs, coordinate finiteness).
- Level 2 — structure: adjacency pointers and neighbour links form a valid incidence graph; no dangling keys.
- Level 3 — topology: PL‑manifold‑with‑boundary, Euler characteristic, ridge‑link consistency.
- Level 4 — Delaunay property: every facet is locally Delaunay.
Only Level 4 requires predicate evaluation; Levels 1–3 are pure graph checks. Agents adding validation code should place it at the correct layer and avoid reaching into lower layers unnecessarily.
Degenerate configurations (cospherical, collinear, coplanar inputs) are
resolved by Simulation‑of‑Simplicity in src/geometry/sos.rs. This is
a first‑class invariant, not an implementation detail — callers can
rely on predicates returning a consistent, total ordering even on
degenerate input, and tests under tests/proptest_sos.rs enforce that.
- Error enums are
#[non_exhaustive]; public wrapper types are#[must_use]. New variants are additive. - New functionality is additive: use
crate::prelude::*(or the focusedprelude::triangulation,prelude::query, etc.) for ergonomic re‑exports; never silently rename or remove a public item. - Focused preludes are an exception to preserving accidental breadth: keep
them narrow, orthogonal, and purpose-specific, bundling only related,
non-overlapping functionality. Prefer fixing an over-broad or ambiguous
focused prelude over maintaining backwards compatibility for unrelated
re-exports. The root
crate::prelude::*remains the kitchen-sink import for new users, quick experiments, and exploratory tests. - Pre‑1.0 semver:
0.x.Yis a patch‑level additive bump,0.X.yis a minor bump that may include breaking changes. Conventional‑commit types (feat,fix,refactor, …) mirror this convention. - Publish documentation changes before bumping the crates.io version (crates.io does not allow re‑publishing docs without a version bump).
- Const‑generic
Don every core type (DelaunayTriangulation<K, U, V, D>,Tds<T, U, V, D>,Simplex<T, U, V, D>,Vertex<T, U, D>,Point<T, D>). No runtime dimension. - Per‑simplex data is stack‑allocated (
[T; D]coordinates,SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>). The triangulation's topology is stored inDenseSlotMap— heap‑backed by necessity, not by accident. - Feature flags isolate optional dependency weight. Default builds stay
dep‑minimal. Known flags:
count-allocations,bench,bench-logging,diagnostics,slow-tests.
-
#![forbid(unsafe_code)]is a hard constraint, not a guideline. -
const fnfor pure‑math helpers (sign_to_orientation,sign_to_insphere, coordinate conversions) where the inputs allow. Do not twist mutating APIs intoconst fnfor its own sake. -
Result<_, _Error>for every fallible operation. Panics are reserved for documented, debug‑only precondition violations; library code insrc/must not panic on user input. -
Borrow by default (
&T,&mut T,&[T]); return borrowed views where possible.FacetView,AdjacencyIndex, and thesimplices()/vertices()iterators are examples. -
Type and function names match the textbook vocabulary:
Triangulation,Vertex,Simplex,Facet,Ridge,InSphere,Orientation,insphere,circumcenter,circumradius. Avoid Rust‑ecosystem abstractions that obscure the math. -
Use
tracing::{debug,info,warn,error}!for committed diagnostics across production code, tests, and benchmarks, especially for library/runtime code, non-trivial test diagnostics, and debugging of numerical or topological invariants. -
eprintln!is acceptable only for short-lived local debugging while investigating a problem; remove it before landing changes. -
Never log inside hot benchmark loops or Criterion-measured closures. Emit setup/summary diagnostics outside the measured path instead.
-
Gate non-essential test/benchmark diagnostics behind feature flags. In this repository use
diagnosticsfor test diagnostics andbench-loggingfor benchmark diagnostics, e.g.:#[cfg(feature = "diagnostics")] tracing::debug!("test diagnostic"); #[cfg(feature = "bench-logging")] tracing::debug!("diagnostic message");
- Unicode math (×, ≤, ≥, ∈, Σ, ²,
2^-50, …) is welcome in doc comments — readability trumps ASCII‑only preference. - Reference literature via
REFERENCES.mdnumbered citations. - State invariants mathematically where possible (e.g.
χ(S^d) = 1 + (−1)^d) rather than prose‑only.
- Performance is a design goal but strictly subordinate to the principles above. Never trade correctness, stability, or clarity for speed; if the two conflict, re‑scope the problem rather than compromise the invariant.
- In scope: d‑dimensional Delaunay triangulations for small‑to‑medium
dimensions (typically 2 ≤ D ≤ 7), single‑threaded in‑memory
construction,
DenseSlotMap‑backed topology, Hilbert‑ordered insertion. - Out of scope: massively parallel / GPU meshing, out‑of‑core triangulations, sparse sampling, dynamic remeshing at scale. Those belong to specialised tools (CGAL, TetGen, Gmsh).
- Within scope, prefer:
- allocation‑free hot paths (
SmallBuffer, stack arrays, iterators) - Shewchuk‑style f64 fast filters with
core::hint::cold_path()on exact‑arithmetic fallbacks (seesrc/geometry/predicates.rs) const fnwhere the inputs allow- typed flip/insertion budgets rather than heuristic timeouts
- allocation‑free hot paths (
- Validate any performance claim against one of the benchmark suites in
benches/(ci_performance_suite,profiling_suite,cold_path_predicates,circumsphere_containment,topology_guarantee_construction,tds_clone) before relying on it.
- Unit tests cover known values, error paths, and dimension‑generic
correctness. Dimension‑generic tests must cover D=2 through D=5
whenever feasible; use
pasteymacros to generate per‑dimension tests (seesrc/core/simplex.rs,src/core/tds.rsfor patterns). - Proptests under
tests/proptest_*.rscover algebraic and topological invariants — round‑trips, Euler characteristic, orientation sign agreement, SoS consistency — not just "does it not panic". - Adversarial inputs (near‑boundary points, cospherical sets, degenerate simplices, large coordinates) accompany well‑conditioned inputs in both tests and benchmarks.
- When a public API has two paths for the same question (fast filter + exact fallback, or two alternative predicates), a proptest verifies they agree on the domain where all are defined.
For what tests cover and why, see Design Principles → Testing mirrors the principles above.
For detailed rules (proptest conventions, adversarial inputs, etc.), see docs/dev/testing.md.
Typical commands:
just test
just test-integration
just test-all
just examples- Never edit
CHANGELOG.mdordocs/archive/changelog/*.mdmanually - Run
just changelogto regenerate the root changelog and archive files from commits - The root
CHANGELOG.mdcontains only Unreleased + the active minor series; completed minors are archived indocs/archive/changelog/X.Y.md
The invariants in Design Principles above are authoritative; this section only lists expectations that are not already codified there.
- Prefer small, focused patches.
- Search
docs/anddocs/dev/before inventing new conventions. - When a design question is ambiguous, default to the trade‑off ordering in Design Principles (numerical correctness → topological correctness → API stability → composability → idiomatic Rust → performance).
- Keep code simple and maintainable when multiple correct solutions exist.