Skip to content

Latest commit

 

History

History
97 lines (81 loc) · 4.47 KB

File metadata and controls

97 lines (81 loc) · 4.47 KB

CLAUDE.md

Guidance for Claude Code (and any agent) working in this repository.

This is the canonical hybrid Rust + Python template. Practices that land here propagate to every downstream consumer seeded by gh repo create --template zackees/template-python-rust-cmd. Bias toward keeping things tight and load-bearing; the leverage is high.

Essential Rules

  1. Always run gates through ./ci.sh <gate>. Never paste uv run python ci/gates/... or cargo clippy directly into a command. The ci/hooks/tool_guard.py PreToolUse hook blocks bare forms and tells you why.
  2. Reserve full uv run (without --no-project --script) for named build entry points: ./test, ./build, ci/build_wheel.py, ./publish, ./install. Everything else needs the protective flags — see ci.sh for the rationale.
  3. Every directory must have a README.md of ≥ 50 lines. Enforced by ci/hooks/readme_guard.py on every edit.
  4. Source files ≤ 1000 lines (warn) / ≤ 1500 (fail). Enforced both on every CI run (ci/gates/loc.py) and per-edit (ci/hooks/loc_guard.py). Split convention: foo.rsfoo/mod.rs + per-domain submodules with pub use re-exports in mod.rs.
  5. Logic lives in Python under ci/; YAML stays thin. Every CI step is run: ./ci.sh <gate>. No multi-line shell embedded in .github/workflows/ci.yml.
  6. build is the only fatal gate. A failing build halts the rest of the run because every downstream gate would produce noise against an uncompiled tree.

Commands

./install                # verify toolchain shape (no maturin build)
./ci.sh fmt              # one gate
./ci.sh all              # every gate, continue past failures
./ci.sh --list           # show registered gates
./test                   # cargo test + maturin develop + pytest
./lint                   # convenience: fmt + clippy + ruff

For the full design rationale see zackees/zccache#835.

Hooks vs Gates

Concern Home
Repo-state checks (git push catches it) ci/gates/*.py
Agent-intent checks (only during sessions) ci/hooks/*.py
LOC budget across the workspace ci/gates/loc.py
LOC growth on this edit ci/hooks/loc_guard.py
README presence + size ci/hooks/readme_guard.py
Bare cargo / unsafe uv run shape ci/hooks/tool_guard.py

If a rule would fire equally well on a git push from a terminal as from a Claude edit, write it as a gate. If it needs to know what tool is about to run, write it as a hook.

Repo Shape

  • crates/template-core — reusable Rust library logic
  • crates/template-cli — bare Rust binary (the packaged CLI)
  • crates/template-pyPyO3 wrapper crate
  • src/template_python_rust_cmd/ — thin Python surface, packaging glue, Python CLI shim
  • ci/ — automation (see ci/README.md)
  • action.yml, action/cleanup/action.yml — composite action contract

Working Rules

  • Grow template-core first; expose through template-cli and template-py. Don't let them diverge on core behavior.
  • The wheel exposes template_python_rust_cmd._native (PyO3) AND ships the cargo-built template-cli[.exe] as a raw wheel script at template_python_rust_cmd-<ver>.data/scripts/. Pip extracts that directly into the venv's Scripts/ / bin/ on install — no Python shim sits in front of the binary. ci/build_wheel.py::verify_artifacts() enforces both deliverables are present in the wheel.
  • When changing user-visible commands, update README.md, UPDATE.md, docs/ARCHITECTURE.md, and any affected ci/gates/<name>.py.
  • The release pipeline builds template-cli via cargo into the pinned CARGO_TARGET_DIR, then ci/build_wheel.py post-processes the maturin wheel to inject the binary at .data/scripts/ with a fresh RECORD row. There is no _bin/ staging step under the package source tree anymore; see #7 for the rationale (Windows os.execv race).

Where to ask questions