When growing the scaffold, the load-bearing decisions are:
- Reusable behavior →
crates/template-core. Both the CLI binary and the PyO3 bindings depend on this crate; growth here propagates to both consumers for free. - CLI commands →
crates/template-cli. Subcommands here become part of the composite action's surface contract — adding a subcommand means updatingaction.yml's shell snippets and lettingci/gates/action_surface.pyverify the binary still exports it. - Public Python API →
src/template_python_rust_cmd/bindings.py. Keep the wrapper thin: each function should be a near-1:1 reflection of the underlying_nativecall, with type annotations and a one-line docstring. - Native Rust boundary →
crates/template-py/src/lib.rs. PyO3 decorators belong here, not intemplate-core. - CI logic →
ci/gates/<name>.py. Never in the workflow YAML.
- New gate →
ci/gates/<name>.pyexposingdef run() -> int, registered inci.py::GATE_ORDER. - New hook →
ci/hooks/<name>.pyreading JSON from stdin, wired in.claude/settings.json. - New named build entry point → script at repo root with shebang
#!/usr/bin/env bash(route to./ci.sh) or#!/usr/bin/env -S uv run --scriptfor inline Python; add its name toci/hooks/tool_guard.py::BUILD_ENTRY_POINTSso the hook knows it's allowed to use fulluv run.
template-cliandtemplate-pynever diverge on core behavior.- There is no Python CLI shim.
template-cli[.exe]ships as a raw wheel script attemplate_python_rust_cmd-<ver>.data/scripts/; pip drops it straight into the venv'sScripts//bin/on install with no Python wrapper. Adding[project.scripts]back would re-introduce the Windowsos.execvrace fixed in #7 — don't. - The wheel always contains both deliverables (PyO3 extension AND
the raw
template-cli[.exe]wheel script).ci/build_wheel.py:: verify_artifacts()enforces this — don't bypass it. - The composite
action.ymlonly references subcommands that exist intemplate-cli --help.ci/gates/action_surface.pychecks this.
Read CLAUDE.md for the essential rules, then UPDATE.md for the change checklist, then the relevant gate's docstring for why the check exists.