Contributing #
Read the note in the README first: this is a single-user codebase, largely AI generated, with no stability guarantees. Contact @permadeath.com before sending an issue or a pull request.
Build #
The toolchain is pinned in rust-toolchain.toml. With rustup installed,
nothing else is needed; it will fetch the right version on first build.
cargo build
cargo install --path .
Build directories #
Several branches are usually open here at once, each in its own git worktree
under .claude/worktrees, and each builds into its own target/. Cargo's
default, which nothing in this repo changes and nothing needs to.
A target directory only grows, and there is one per worktree.
cargo-sweep takes project directories, so point it at the worktrees
as well as the main checkout:
cargo sweep --maxsize 20GiB . # in any one checkout
cargo sweep --time 7 --recursive ~/Code # anything untouched in a week
Its README calls it unmaintained and points at cargo-clean-all;
either is fine for this, which is why neither is in the tool list above.
Tests #
cargo test: no network, no session, about eight seconds.
docs/testing.md says what earns a test here, when to reach
for the mock services in tests/support/ rather than a fixture, and the two
constraints that trip people up: HOME cannot be redirected inside the test
process, and the working directory is process-wide.
./scripts/test-isolated.sh runs the same tests with a throwaway home per
binary and fails if one of them left anything there. Because HOME cannot be
redirected from inside, a test that reaches for the real configuration
directory finds it, works, and stays green -- so this is the only thing that
catches it. Bubblewrap makes the home unreachable as well as empty; without it
the script still does the checking.
Docs #
cargo docs
An alias for cargo doc --no-deps --document-private-items, defined in
.cargo/config.toml. It renders the narrative pages under docs/ and the
documentation generated from src/ into one tree at
target/doc/atgc/index.html. .cargo/config.toml also denies rustdoc
warnings, so this is where a prose page linking to an item that no longer
exists is caught. A docs/ page may not contain an untested Rust fence,
rustdoc runs no doctests for a binary crate, so it would be an example that
lies about its own status, and prek's doc-lint hook says so if you try.
Commits #
prek #
Commits here gain a Change-Id: trailer automatically, from the change-id hook in prek.toml — it is what a stacked pull request is matched to its commits by, and it can only be written without rewriting history while the commit is being made. The script is scripts/change-id-hook.sh, and it does nothing to a commit that already has an id. In a repo with no hook framework, atgc repo configure installs the same script directly as .git/hooks/commit-msg; here prek owns that file, so it is declared as a hook instead — which is what atgc repo configure --hook-config writes for you, and what atgc doctor local's change-id hook row checks.
Commits are checked by prek, configured in prek.toml. Run prek install once in your checkout to enable the hooks. That installs two hook types, pre-commit and commit-msg, because prek.toml names both in default_install_hook_types; on a prek old enough not to read that key, spell it out with prek install --hook-type pre-commit --hook-type commit-msg.
The pre-commit hooks cover whitespace and file hygiene plus cargo fmt --check and cargo clippy -D warnings, and they leave vendor/ alone. prek run --all-files checks everything without committing: it runs the pre-commit stage, so it does not re-check commit messages.
Commit messages #
Conventional Commits, checked on commit by committed and configured in committed.toml:
feat(logs): read the OAuth log back with `atgc logs oauth`
fix(resolve)!: return the repo's DID rather than the owner's
docs: record which DID a Tangled path segment holds
A type from feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert; an optional scope, usually the module; a ! before the colon for a breaking behaviour change; then a lower-case description in the imperative, no trailing period, up to 80 columns including the prefix. Body lines wrap at 80. If the change needs justification, put it in the body as prose.
The version in Cargo.toml is computed from these subjects (see below), so a subject that does not parse is a change that does not count.
Commits before 5f7d819 predate the convention and are plain prose. Nothing rewrites them and nothing needs to: the commit-msg hook only ever sees the message being written.
Commits written with an AI assistant carry a Co-Authored-By: trailer.
Versions #
Cargo.toml's version is derived from the commits that have landed on main since the last tag. It is bumped on main, by a maintainer, as its own act, not inside a pull request. Do not edit the version line in a PR; a branch that touches it will conflict with every other open branch that does, on a line where a textual merge means nothing.
One command, run from a clean main:
cargo release "$(git cliff --unreleased --bumped-version | sed 's/^v//')" --execute
git cliff reads the conventional commits since the last tag and prints the version they imply; cargo release writes it into Cargo.toml and Cargo.lock, rewrites the "current release" line under the README's lockup, makes a chore(release): commit, and creates an annotated tag. It asks for confirmation first and shows the version it computed; read that line before answering. Nothing is pushed and nothing is published; those are separate, deliberate acts. The rules live in cliff.toml and release.toml, both of which explain themselves.
The README line is generated, so do not hand-edit it: it lives between <!-- release --> markers and is replaced wholesale on each bump. Losing a marker fails the bump rather than skipping the line, and tests/release_metadata.rs fails the commit if the line and Cargo.toml ever disagree.
While the project is pre-1.0: fix and docs bump the patch, feat bumps the minor, and a breaking ! also bumps the minor rather than jumping to 1.0.0. That last one overrides a git-cliff default that would have gone straight to 1.0.0, and it is set explicitly in cliff.toml. 1.0.0 is a compatibility guarantee this project has not given, and it should be a version somebody types on purpose.
Installing the tools #
rustup and prek are the two you need to commit; cargo-deny (cargo install --locked cargo-deny) joins them the day a commit touches Cargo.lock or deny.toml, since the pre-commit hook shells out to it. The rest are only needed to run a bump:
prek install # hooks; committed is fetched by prek
cargo install --locked cargo-release # writes the version
cargo install --locked git-cliff # computes the version
git-cliff and cargo-release also ship prebuilt binaries on their GitHub releases, which is quicker than a source build; cargo-release's Linux builds lag its crates.io version by a release or two, so cargo install is the version-accurate route. committed is installed by prek itself from the pin in prek.toml, so there is nothing to do by hand.
Pull requests #
Since this project lives on Tangled, the best way to submit PRs is through atgc itself:
atgc auth login <handle|did>
atgc pr create
atgc pr create diffs the current branch against the remote's default branch and uploads the patch to your own PDS, so the branch does not need to be pushed anywhere. Use --dry-run to see what would be sent.