RFC 015: Hatch-like Tooling (Project Lifecycle CLI)¶
Status: Planned Created: 2025-12-23
Summary¶
Introduce a first-class, batteries-included project lifecycle CLI—similar in spirit to Python’s Hatch—for:
- Versioning:
incan version <major|minor|patch|alpha|beta|rc|dev>(with optional--dry-run) - Project scaffolding:
incan init(in-place) andincan new <name>(new directory) - Matrix testing (tox/nox-style):
incan test --matrix ...with declarative environments - Additional “hatch-like” ergonomics where it fits Incan’s workflow (format/lint/release/build/publish).
This RFC defines the CLI surface, the project metadata format, and the implementation boundaries so we don’t bake policy into ad-hoc scripts.
Motivation¶
Incan is a compiler + runtime ecosystem, but day-to-day developer experience is heavily shaped by tooling:
- Starting a new project should be one command.
- Bumping versions should be correct and consistent across the compiler, generated projects, and any package metadata.
- Running tests should support repeatable environments and matrix execution, without forcing users to learn Cargo internals.
- Release workflows should be scriptable and standard across projects.
Python’s Hatch demonstrates that a single tool can cover the project lifecycle. This RFC adapts the useful parts to Incan.
Goals¶
- Provide an ergonomic, consistent, and scriptable CLI for common workflows:
init,new,version,test,fmt,lint,build, (future:publish)
- Define a single source of truth for project metadata (name, version, dependencies, entrypoint).
- Enable matrix testing via a declarative config.
- Keep generated projects deterministic and reproducible.
- Avoid “magic”: every generated file is readable and intended to be edited.
Non-Goals (initial)¶
- Implement a public package registry client (publish/install) in this RFC (can be a follow-up RFC).
- Replace Cargo for Rust-level dependency resolution (we can orchestrate Cargo, not reinvent it).
- Provide virtualenv-style isolation identical to Python (we’ll use explicit env configs and reproducible commands instead).
Terminology¶
- Project: An Incan repository containing Incan sources and metadata.
- Environment: A named test/build configuration (toolchain, flags, features, timeouts).
- Matrix: Running an environment set across multiple dimensions (e.g., debug/release, features on/off).
Project Metadata¶
Add incan.toml at repo root (similar to pyproject.toml), as the canonical metadata source.
Example:
[project]
name = "hello_incan"
version = "0.1.0-alpha.1"
entrypoint = "src/main.incn"
[project.dependencies]
# Rust interop dependencies (RFC 013)
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
[tool.incan]
formatter = { line_length = 100 }
[tool.incan.test]
timeout_secs = 5
[[tool.incan.env]]
name = "unit"
command = ["incan", "test"]
[[tool.incan.env]]
name = "smoke"
command = ["incan", "smoke-test"]
Notes:
versionis SemVer-compatible with pre-release tags.- Rust dependencies integrate with RFC 013 rules.
- Environments are explicit; no implicit “dev shell” assumptions.
CLI Design¶
incan new <name>¶
Create a new directory containing a minimal Incan project scaffold:
incan.tomlsrc/main.incn(hello world)README.md.gitignore
Flags:
--bin/--lib(default:--bin)--dir <path>(default:./<name>)--force(overwrite)
incan init¶
Initialize incan.toml (and src/main.incn) in the current directory.
Flags:
--force(overwrite metadata)--detect(attempt to infer: entrypoint, existing version strings, etc.)
incan version <bump>¶
Update the project version in incan.toml and any derived files that must match.
Bumps:
major,minor,patchalpha,beta,rc,dev
Rules:
major/minor/patchoperate on the release core and clear pre-release unless--keep-prerelease.alpha/beta/rc/dev:- If no prerelease exists, append
-<tag>.1 - If same prerelease exists, increment numeric suffix
- If different prerelease exists, switch tag and reset to
.1
- If no prerelease exists, append
Flags:
--dry-run--set <version>(explicit override)--keep-prerelease--message <msg>(for future integration with changelog/commit tooling)
Output should print:
- old version
- new version
- modified files
incan test¶
Default test runner entrypoint.
Behavior:
- If
tool.incan.envexists, runningincan testexecutes the default environment (orunit). - Otherwise, runs Incan’s standard checks:
cargo testfor compiler/runtime crates andincan testfor Incan-level tests (if present).
Flags:
--env <name>(select an env)--matrix <expr>(see below)--list-envs--timeout <secs>--jobs <n>(parallel env execution)--fail-fast
Matrix execution¶
Matrix is defined either in incan.toml or via CLI:
Config:
[tool.incan.matrix]
axes = { profile = ["debug", "release"], features = ["default", "json"] }
[[tool.incan.matrix.include]]
profile = "release"
features = "json"
env = "unit"
CLI expression (initial, simple):
--matrix profile=debug,release features=default,json
Execution strategy:
- Expand combinations
- Run each as a named environment with injected variables:
INCAN_PROFILE,INCAN_FEATURES,INCAN_TIMEOUT
Additional Commands (Recommended)¶
These exist today in Makefiles across many repos. This RFC explicitly prefers CLI-native equivalents so projects do not need Make as a dependency.
incan fmt/incan fmt --checkincan lint(clippy-like checks for compiler + emitted code)incan smoke-test(build + tests + examples + benchmark smoke-check, mirroring current repo conventions)incan doctor(environment diagnostics: toolchain version, cargo, PATH, permissions)
Implementation Plan¶
Phase 1: Metadata + scaffolding¶
- Add
incan.tomlparsing (serde + toml) - Implement
incan new,incan init - Teach codegen/project generation to consult
incan.tomlwhen present
Phase 2: Version command¶
- Implement SemVer parsing + bump logic
- Apply updates to
incan.toml - Optional: update any secondary manifests (only if this repo’s policy requires it)
Phase 3: Environment + matrix runner¶
- Parse env definitions
- Implement
incan test --env - Implement
incan test --matrix - Add
--jobs+--fail-fast
Phase 4: Polish + docs¶
- Add guide pages:
docs/tooling/project_lifecycle.md - Provide “new project” tutorial for Python users
- Add examples of matrix testing
Alternatives Considered¶
- Rely solely on Makefile targets: simple but inconsistent across repos, hard to compose and introspect; also adds an extra tool dependency we don’t need.
- Embed everything in Cargo: good for Rust, but Incan’s source-of-truth isn’t Cargo.toml; also doesn’t cover project scaffolding or Incan-centric metadata.
- Adopt an existing tool (justfile, cargo-make): helps execution but doesn’t solve metadata/version semantics.
Open Questions¶
- Should
incan.tomlbe mandatory for new projects, or optional? - Should
incan versionalso update the compiler crate versions (workspace crates), or only project metadata? - How do we want to represent “dev” versions (e.g.,
-dev.1vs-dev+<sha>)? - Do we need a lockfile for
incan.tomldependencies beyond Cargo.lock (likely no, but clarify)?
Checklist¶
- [ ] Define
incan.tomlschema and document it - [ ] Implement
incan newandincan init - [ ] Implement
incan versionbump logic - [ ] Implement env runner and matrix expansion
- [ ] Docs + examples