# 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`][sweep] takes project directories, so point it at the worktrees as well as the main checkout: ```bash 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`][clean-all]; either is fine for this, which is why neither is in the tool list above. [sweep]: https://github.com/holmgr/cargo-sweep [clean-all]: https://github.com/dnlmlr/cargo-clean-all ## Tests `cargo test`: no network, no session, about eight seconds. [docs/testing.md](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](https://prek.j178.dev/), 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](https://www.conventionalcommits.org/en/v1.0.0/), checked on commit by [`committed`](https://github.com/crate-ci/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`: ```bash 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 `` 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: ```bash 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](https://tangled.org), the best way to submit PRs is through `atgc` itself: ```bash atgc auth login 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.