diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..2172764 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1 @@ +{"includeCoAuthoredBy":false,"attribution":{"co_authored_by":false,"commit_message_footer":false}} diff --git a/.github/scripts/check-attribution.sh b/.github/scripts/check-attribution.sh deleted file mode 100755 index 7a16a25..0000000 --- a/.github/scripts/check-attribution.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -# Enforces the CONTRIBUTING.md commit policy over the full history: -# - no AI/bot attribution in any commit message, author, or committer -# - a DCO Signed-off-by trailer matching the author on every commit -set -euo pipefail - -AI_PATTERN='co-authored-by:.*(claude|copilot|chatgpt|gpt|openai|anthropic|gemini|cursor|codex|devin|aider|assistant|\[bot\])' -GENERATED_PATTERN='generated (with|by).*(claude|copilot|chatgpt|gpt|openai|anthropic|gemini|cursor|codex|devin|aider)' -BOT_IDENTITY_PATTERN='(\[bot\]|noreply@anthropic\.com|noreply@openai\.com|github-actions)' - -fail=0 - -while read -r sha; do - msg=$(git log -1 --format='%B' "$sha") - author_name=$(git log -1 --format='%an' "$sha") - author_email=$(git log -1 --format='%ae' "$sha") - committer=$(git log -1 --format='%cn <%ce>' "$sha") - committer_email=$(git log -1 --format='%ce' "$sha") - - if grep -iqE "$AI_PATTERN" <<<"$msg"; then - echo "::error::$sha: AI co-author trailer in commit message" - fail=1 - fi - if grep -iqE "$GENERATED_PATTERN" <<<"$msg"; then - echo "::error::$sha: 'Generated with/by' AI watermark in commit message" - fail=1 - fi - if grep -q '๐Ÿค–' <<<"$msg"; then - echo "::error::$sha: robot-emoji watermark in commit message" - fail=1 - fi - if grep -iqE "$BOT_IDENTITY_PATTERN" <<<"$author_name <$author_email>"; then - echo "::error::$sha: bot/vendor author identity: $author_name <$author_email>" - fail=1 - fi - if grep -iqE "$BOT_IDENTITY_PATTERN" <<<"$committer"; then - echo "::error::$sha: bot/vendor committer identity: $committer" - fail=1 - fi - # GitHub's own squash and merge commits (committer noreply@github.com) - # rewrite the author email to the merging account's address, so an exact - # sign-off==author match is impossible by construction: the sign-off was - # written before GitHub chose the author. The commits that went into the - # pull request were already checked by this script on the branch, so what - # is left to require of the merge commit is that a sign-off is present at - # all โ€” the certification, without the address it cannot control. - if [ "$committer_email" = "noreply@github.com" ]; then - if ! grep -qiE '^signed-off-by:' <<<"$msg"; then - echo "::error::$sha: merge commit carries no Signed-off-by at all" - echo " subject: $(git log -1 --format='%s' "$sha")" - fail=1 - fi - elif ! grep -qF "Signed-off-by: $author_name <$author_email>" <<<"$msg"; then - echo "::error::$sha: missing DCO Signed-off-by matching author $author_name <$author_email>" - fail=1 - fi -done < <(git rev-list HEAD) - -if [ "$fail" -ne 0 ]; then - echo "Attribution/DCO check failed. Policy: CONTRIBUTING.md." - exit 1 -fi -echo "All $(git rev-list --count HEAD) commits clean: no AI attribution, DCO present." diff --git a/.github/scripts/check-dco.sh b/.github/scripts/check-dco.sh new file mode 100755 index 0000000..93ba2d4 --- /dev/null +++ b/.github/scripts/check-dco.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# check-dco.sh โ€” require a DCO sign-off on every commit in a range. +# +# Every non-merge commit must contain a "Signed-off-by: Name " line +# whose email matches the commit AUTHOR email. Merge commits are exempt +# (the repo is squash-merge only, so there should be none anyway). +# +# Usage: +# .github/scripts/check-dco.sh .. +# .github/scripts/check-dco.sh # single commit and ancestors +# +# tests: exercise locally on a scratch branch before trusting it in CI โ€” +# git checkout -b scratch/dco +# git commit --allow-empty -m "test: signed" -s +# git commit --allow-empty -m "test: unsigned" +# .github/scripts/check-dco.sh main..HEAD # must fail on the 2nd commit +# git checkout - && git branch -D scratch/dco +set -euo pipefail + +range="${1:?usage: check-dco.sh }" + +fail=0 +count=0 +while IFS= read -r sha; do + count=$((count + 1)) + author_email="$(git log -1 --format='%ae' "$sha")" + committer_email="$(git log -1 --format='%ce' "$sha")" + + # GitHub web-flow commits (squash/merge performed by github.com itself, + # committer noreply@github.com) rewrite the author email to the merging + # account's GitHub address, so an exact sign-off==author match is + # impossible by construction. The underlying PR commits were already + # DCO-checked by this workflow's required pull_request run; for the + # resulting merge commit we require a sign-off to be present but skip + # the email match. + if [ "$committer_email" = "noreply@github.com" ]; then + if ! git log -1 --format='%B' "$sha" | grep -qi '^signed-off-by:'; then + echo "::error::Merge/squash commit ${sha} carries no Signed-off-by at all." + echo " subject: $(git log -1 --format='%s' "$sha")" + fail=1 + fi + continue + fi + + # The sign-off may sit anywhere in the message body: after a squash merge, + # GitHub concatenates commit messages, which moves trailers out of the + # strict trailer block. Match any "Signed-off-by:" line instead. + if ! git log -1 --format='%B' "$sha" \ + | grep -i '^signed-off-by:' \ + | grep -qF "<${author_email}>"; then + echo "::error::Commit ${sha} has no Signed-off-by matching its author <${author_email}>." + echo " subject: $(git log -1 --format='%s' "$sha")" + echo " fix: 'git commit --amend -s' for the last commit, or" \ + "'git rebase --signoff' for a branch, then force-push." + fail=1 + fi +done < <(git rev-list --no-merges "$range") + +echo "check-dco: inspected ${count} commit(s) in ${range}" +if [ "$fail" -ne 0 ]; then + echo "::error::DCO check failed. Every commit must be signed off (git commit -s)." \ + "See CONTRIBUTING.md ยง5 and https://developercertificate.org" + exit 1 +fi +echo "check-dco: OK" diff --git a/.github/scripts/check-no-ai-attribution.sh b/.github/scripts/check-no-ai-attribution.sh new file mode 100755 index 0000000..76eaba1 --- /dev/null +++ b/.github/scripts/check-no-ai-attribution.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# check-no-ai-attribution.sh โ€” reject AI/bot attribution in commits. +# +# Policy (one line): AI assistance is welcome; AI attribution is not. Remove +# the trailer and recommit โ€” you are the author of record. +# +# For every commit in the range this fails on any of: +# * message (subject + body + trailers, case-insensitive): +# - Co-authored-by trailers naming an AI/agent/bot vendor +# - "Generated with/by" watermarks +# - the robot emoji watermark +# - noreply.anthropic.com anywhere +# * author/committer identity: +# - *[bot]@users.noreply.github.com, *@noreply.anthropic.com, +# actions@github.com emails +# - names containing claude/copilot/devin/aider/codex/gemini +# +# Usage: +# .github/scripts/check-no-ai-attribution.sh .. +# +# tests: exercise locally on a scratch branch before trusting it in CI โ€” +# git checkout -b scratch/attribution +# git commit --allow-empty -s -m "test: clean commit" +# git commit --allow-empty -s -m "test: bad commit" \ +# -m "Co-Authored-By: Example Bot " +# .github/scripts/check-no-ai-attribution.sh main..HEAD # must fail once +# git checkout - && git branch -D scratch/attribution +set -euo pipefail + +range="${1:?usage: check-no-ai-attribution.sh }" + +POLICY="AI assistance is welcome; AI attribution is not. Remove the trailer and recommit โ€” you are the author of record." + +# Message patterns, matched case-insensitively (ERE). +msg_patterns=( + 'co-authored-by:[[:space:]]*.*\b(claude|anthropic|copilot|chatgpt|gpt|openai|cursor|devin|aider|codex|gemini|windsurf|jetbrains ai|amazon q|sweep|bot)\b' + 'generated (with|by)\b' + 'noreply\.anthropic\.com' +) + +# Identity patterns (ERE). Emails are lowercased before matching. +email_pattern='(\[bot\]@users\.noreply\.github\.com$|@noreply\.anthropic\.com$|^actions@github\.com$)' +name_pattern='\b(claude|copilot|devin|aider|codex|gemini)\b' + +fail=0 +count=0 +while IFS= read -r sha; do + count=$((count + 1)) + msg="$(git log -1 --format='%B' "$sha")" + + for pat in "${msg_patterns[@]}"; do + if printf '%s\n' "$msg" | grep -Eiq "$pat"; then + echo "::error::Commit ${sha} message matches banned pattern: ${pat}" + fail=1 + fi + done + if printf '%s\n' "$msg" | grep -Fq '๐Ÿค–'; then + echo "::error::Commit ${sha} message contains a robot-emoji watermark." + fail=1 + fi + + for role in author committer; do + if [ "$role" = author ]; then + name="$(git log -1 --format='%an' "$sha")" + email="$(git log -1 --format='%ae' "$sha")" + else + name="$(git log -1 --format='%cn' "$sha")" + email="$(git log -1 --format='%ce' "$sha")" + fi + if printf '%s\n' "$email" | tr '[:upper:]' '[:lower:]' | grep -Eq "$email_pattern"; then + echo "::error::Commit ${sha} ${role} email '${email}' is a bot/vendor identity." + fail=1 + fi + if printf '%s\n' "$name" | grep -Eiq "$name_pattern"; then + echo "::error::Commit ${sha} ${role} name '${name}' is an AI/agent identity." + fail=1 + fi + done +done < <(git rev-list "$range") + +echo "check-no-ai-attribution: inspected ${count} commit(s) in ${range}" +if [ "$fail" -ne 0 ]; then + echo "::error::${POLICY}" + exit 1 +fi +echo "check-no-ai-attribution: OK" diff --git a/.github/workflows/no-ai-attribution.yml b/.github/workflows/commit-policy.yml similarity index 54% rename from .github/workflows/no-ai-attribution.yml rename to .github/workflows/commit-policy.yml index c36f91e..b6a0bef 100644 --- a/.github/workflows/no-ai-attribution.yml +++ b/.github/workflows/commit-policy.yml @@ -1,7 +1,7 @@ # Repo policy (CONTRIBUTING.md): zero AI attribution anywhere, plus DCO # sign-off on every commit. This checks the full history โ€” the repo is small # enough that scanning everything is cheaper than getting ranges right. -name: no-ai-attribution +name: commit-policy on: push: @@ -21,8 +21,35 @@ jobs: # On PRs, check the real commits โ€” not the synthetic merge commit # GitHub fabricates (committer GitHub , no DCO). ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Check commits for AI attribution and DCO sign-off - run: bash .github/scripts/check-attribution.sh + - name: Determine the commit range + id: range + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.event.after }} + run: | + if [ "$EVENT" = "pull_request" ]; then + range="${BASE}..${HEAD}" + elif [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ] \ + || ! git cat-file -e "$BEFORE" 2>/dev/null; then + range="$AFTER" + else + range="${BEFORE}..${AFTER}" + fi + echo "Checking range: $range" + echo "range=$range" >> "$GITHUB_OUTPUT" + + - name: DCO sign-off + env: + RANGE: ${{ steps.range.outputs.range }} + run: .github/scripts/check-dco.sh "$RANGE" + + - name: No AI attribution + env: + RANGE: ${{ steps.range.outputs.range }} + run: .github/scripts/check-no-ai-attribution.sh "$RANGE" - name: Check PR body for AI attribution if: github.event_name == 'pull_request' env: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e3c0d62 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,122 @@ +# Working on launchbound + +Instructions for coding agents โ€” and useful to humans. **launchbound** is a convergence-safe autotuner for Rust GPU kernels: it searches a kernel's launch and specialization space, finds the fastest configuration, and never returns one that is convergence-unsafe. + +This file is the canonical brief; `CLAUDE.md` points here. `CONTRIBUTING.md` +is the full contributor document and wins wherever the two disagree. + +## Layout + +- `crates/launchbound-*` โ€” one crate per stage: `space` (enumerate), `prune` + (the safety gate), `build`, `bench`, `search`, `model`, `metal`, `report`, + `tui`, `runner`, `cli`. +- `corpus/` โ€” standalone kernel fixtures, deliberately **excluded** from the + workspace: they path-depend on a sibling cuda-oxide checkout and are compiled + by the prune tooling, never by `cargo test`. +- `docs/` โ€” `ARCHITECTURE.md`, `SAFETY.md`, and `LIMITATIONS.md`. **Read + LIMITATIONS before trusting a result**, and before claiming one. + +## Build and test + +```sh +just ci # fmt, clippy, test, deny, schemas +cargo test --workspace # no GPU, no network, no checkout needed +just gate # the gate tests โ€” needs cargo-reconverge + cuda-oxide +``` + +Pinned nightly for the analysis and compile paths (`rust-toolchain.toml`); +MSRV 1.88 for everything that does not need it. + +## Things that will bite you here + +- **No GPU is required for the part that matters.** `prune` is the safety + gate and runs on any laptop; that is why it is its own verb. Do not write a + test that needs silicon when the gate does not. +- **Goldens:** regenerate with `LAUNCHBOUND_BLESS=1 cargo test -p launchbound-tui + --test tui`, then read every diff. +- **A model-derived ranking is never presented as a measurement.** Anything + estimated says so on every surface it reaches. This is the project's central + honesty claim โ€” do not blur it to make output tidier. +- **The gate job never caches the analyzer.** It installs the published + `cargo-reconverge` from crates.io every run, because a cached binary is not + evidence about what is published today. + +## The rules that will fail CI + +Three, and they are the same in every one of these repositories. + +1. **Conventional Commits.** `feat:`, `fix:`, `docs:`, `test:`, `ci:`, + `chore:`, `refactor:`, `perf:` โ€” imperative mood, subject line under 72 + characters, scope optional (`fix(screen): โ€ฆ`). +2. **DCO sign-off.** `git commit -s`, and the `Signed-off-by:` email must + match the commit author's. Forgot? `git commit --amend -s --no-edit`, or + `git rebase --signoff main` for a branch. +3. **No AI attribution.** See below โ€” this one is about you, and it is the + rule most likely to catch an agent out. + +Run them yourself before pushing; both scripts take a commit range: + +```sh +.github/scripts/check-dco.sh main..HEAD +.github/scripts/check-no-ai-attribution.sh main..HEAD +``` + +## Using AI here + +**You are welcome.** Every one of these projects was built with AI assistance +and says so in its CONTRIBUTING. Use whatever helps. + +**You are not a contributor.** Do not add yourself to the history: + +- no `Co-Authored-By:` trailer naming an assistant, a model, or a vendor, +- no "Generated with โ€ฆ" footer, no robot emoji, +- no bot account as author or committer. + +The human who opens the pull request is the author of record and takes +responsibility for the change under the DCO. That is what the sign-off +certifies, and it cannot be certified by a tool. `.claude/settings.json` +turns co-author trailers off for agents that read it; the check in CI is the +boundary, and it reads every commit in the range. + +If CI catches one, the fix is to rewrite the message, not to argue with it: + +```sh +git commit --amend # the last commit +git rebase -i main # several, marking each `reword` +git push --force-with-lease +``` + +## What good work looks like here + +These repositories share a house style, and it is stricter than most: + +- **Evidence over assertion.** A bug report says what was measured against + which released version. "Reproduced against 0.4.0" is the standard; "the + code looks wrong" is not. Issues in these repos read *Today / Why it is + worth fixing / Fix / Done when*, with a concrete reproduction. +- **Every change lands with a test**, and the test must be able to fail. If + you add a guard, prove it catches the thing โ€” break it once and watch it go + red before you commit. +- **Comments say *why*, never *what*.** The diff shows what. A comment earns + its place by recording the reason, the alternative rejected, or the failure + that motivated the line. +- **Say what you did not do.** A pull request that lists what it left out and + why is worth more than one that implies completeness. If something is + unverified, say so โ€” an honest gap is cheap and a false claim is expensive. +- **Documentation is checked, not maintained.** Where a README states a fact + the code owns, there is usually a test asserting the two agree. Do not + break that pattern by hand-editing the doc. + +## Pull requests + +Branch from `main` (`feat/โ€ฆ`, `fix/โ€ฆ`, `docs/โ€ฆ`, `ci/โ€ฆ`). PRs are +**squash-merged**, so the PR title becomes the commit subject on `main` โ€” +write it as a Conventional Commit. Update `CHANGELOG.md` under +`[Unreleased]` for anything user-facing. + +Direct pushes to `main` are blocked by a ruleset; everything goes through a +pull request, including releases. + +## Releasing + +Tag `vX.Y.Z` on `main`; `release.yml` publishes the crates in dependency order via Trusted Publishing. See `docs/RELEASING.md`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a1526b0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,8 @@ +# launchbound + +See **[AGENTS.md](AGENTS.md)** โ€” one brief for every coding agent, +so the instructions cannot drift apart per tool. + +The short version: Conventional Commits, `git commit -s` for the DCO, +and **no AI attribution in the history** โ€” you are welcome to use AI +here, but the human opening the pull request is the author of record. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ab3cbd..f7055da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,14 @@ Thanks for your interest. Issues and small PRs are welcome; large features are better discussed first. -## Dev setup +> **These four projects share one contributor pattern** โ€” the same commit +> rules, the same DCO, the same AI policy, the same CI and release shape: +> [termlens](https://github.com/vyncint/termlens), +> [mossaic](https://github.com/vyncint/mossaic), +> [launchbound](https://github.com/vyncint/launchbound), +> [reconverge](https://github.com/vyncint/reconverge). Learn it once. + +## 1. Dev setup The default feature set builds and tests **without any GPU, CUDA SDK, or Metal** โ€” that is a hard project rule, and CI enforces it on plain GitHub @@ -19,53 +26,11 @@ just ci # fmt, clippy -D warnings, tests, cargo-deny, schemas wrapped `cargo build`. Everything except an actual benchmark can be developed on a laptop. -## The pin policy - -Three pins move together or not at all: the nightly in -`rust-toolchain.toml`, the `cuda-oxide` pin, and the `reconverge` pin. -`reconverge` is a rustc-driver tool โ€” it must be built by the exact rustc it -wraps โ€” and `cuda-oxide` requires the same pin. A bump is its own commit, -never mixed with a behaviour change, and re-runs the affected stage gates. -The scheduled `pins.yml` workflow reports upstream movement by opening an -issue; it never bumps anything. Current pins: nightly-2026-04-03, -cuda-oxide 50d07314, reconverge 0.1.11 (installed from crates.io). - -## Commit requirements +## 2. Project layout -Every commit needs all three of: +See [AGENTS.md](AGENTS.md#layout). -1. **DCO sign-off** โ€” `git commit -s`, with a `Signed-off-by:` trailer - matching the author. -2. **Cryptographic signature** โ€” `git commit -S` (SSH or GPG signing both - work; the signature is what "verified" means on GitHub). -3. **Conventional Commits** โ€” `feat:`, `fix:`, `docs:`, `test:`, `ci:`, - `chore:`, `refactor:`, `perf:`, plus `bench:` for a change that alters - measured timings. Scope optional, e.g. `feat(prune): โ€ฆ`. - -Treat `git commit -sS` as the only spelling. Fix a missed sign-off with -`git commit --amend -s --no-edit`, or a branch with -`git rebase --signoff main`. - -One exception, and it is GitHub's rather than ours: when a pull request is -**squash-merged through the web UI**, GitHub rewrites the squash commit's -author email to the merging account's address โ€” chosen *after* the sign-off -was written, so an exact match is impossible by construction. The check -therefore requires such a commit (committer `noreply@github.com`) to carry a -`Signed-off-by` trailer, without matching it against the author. The commits -that went into the pull request were already checked, address and all, on the -branch. Everything else in the policy โ€” no AI attribution, no bot identities โ€” -applies to merge commits unchanged. - -## AI tooling policy - -You may use whatever tools you like to write your contribution. What lands in -this repository carries **no AI attribution of any kind**: no -`Co-Authored-By` naming an AI, bot or agent; no "Generated with โ€ฆ" lines; no -robot emoji or watermarks in commits, PRs, comments or docs; no `*[bot]` or -vendor noreply authors. You sign off on your commits as your own work โ€” that -is what the DCO trailer means. CI (`no-ai-attribution.yml`) enforces this. - -## Testing policy +## 3. Testing policy - The default feature set must pass `just ci` with no GPU present. - Hardware-dependent tests live behind the `hardware` feature and @@ -76,19 +41,112 @@ is what the DCO trailer means. CI (`no-ai-attribution.yml`) enforces this. - A model-derived number is labelled `estimated` everywhere. Reporting an estimate as a measurement is a release-blocking defect. -## Releasing +## 4. Commit conventions + +We use [Conventional Commits](https://www.conventionalcommits.org/): +`feat:`, `fix:`, `docs:`, `test:`, `ci:`, `chore:`, `refactor:`, `perf:` โ€” +scope optional (`feat(prune): โ€ฆ`). Subject line: imperative mood, +โ‰ค 72 characters. + +## 5. Developer Certificate of Origin (DCO) + +Every commit must be signed off: + +```sh +git commit -s +``` + +This appends `Signed-off-by: Your Name ` and certifies you +wrote the change or otherwise have the right to submit it under the project +license โ€” the [Developer Certificate of Origin](https://developercertificate.org), +the same lightweight model the Linux kernel uses. The sign-off email must +match the commit author email; CI enforces this on every commit in a PR. -1. Bump the workspace version (Cargo.toml, one place) and CHANGELOG.md via - PR; CI must be green. -2. Tag the merge commit `vX.Y.Z` (signed) and push it. The release - workflow refuses to publish if the tag disagrees with Cargo.toml, and - publishes via crates.io Trusted Publishing (no token anywhere). -3. Move the floating action tag: `git tag -f -s v1 && git push -f origin - v1`. **This step is manual and easy to forget** โ€” `@v1` consumers keep - running the old action until it happens. Only move it to a commit that - is green on main. +**There is no CLA. DCO only.** You keep your copyright. + +Forgot to sign off? `git commit --amend -s` for the last commit, or +`git rebase --signoff main` for a whole branch, then force-push. + +One exception, and it is GitHub's rather than ours: a pull request +**squash-merged through the web UI** has its author email rewritten by GitHub +*after* the sign-off was written, so an exact match is impossible by +construction. Such a commit must carry a sign-off, but is not matched against +an author it did not choose. The commits that went into the PR were already +checked, address and all, on the branch. + +## 6. AI tooling policy + +**AI assistance is welcome here โ€” use whatever helps.** Every one of these +projects was built with it. There is an [AGENTS.md](AGENTS.md) briefing coding +agents on the layout, the commands, and the house style. + +**AI attribution is not welcome.** No `Co-Authored-By` trailer naming an +assistant, model or vendor; no "Generated with โ€ฆ" footer; no robot emoji; no +bot identity as author or committer. Whoever opens the pull request is the +author of record, takes responsibility under the DCO, and the history should +say so โ€” a tool cannot certify the DCO, which is the whole point of it. + +This is enforced, not requested: `commit-policy.yml` runs +[`check-no-ai-attribution.sh`](.github/scripts/check-no-ai-attribution.sh) and +[`check-dco.sh`](.github/scripts/check-dco.sh) over every commit in a pull +request. Run them yourself first โ€” both take a range: + +```sh +.github/scripts/check-dco.sh main..HEAD +.github/scripts/check-no-ai-attribution.sh main..HEAD +``` + +If a check fails, rewrite the message rather than arguing with it: + +```sh +git commit --amend # the last commit +git rebase -i main # several, marking each `reword` +git push --force-with-lease +``` + +`.claude/settings.json` turns co-author trailers off for agents that read +repository settings. That is a courtesy; the check in CI is the boundary. +Contributions authored *by* an autonomous account are not accepted. + +## 7. PR flow + +- Branch from `main`; name branches `feat/โ€ฆ`, `fix/โ€ฆ`, `docs/โ€ฆ`, `ci/โ€ฆ`. +- PRs are **squash-merged** โ€” keep the PR title in Conventional Commit form, + since it becomes the commit subject on `main`. Branches are deleted on merge. +- Required checks: `ci`, `msrv`, `check` and `gate`, plus `commit-policy` (DCO + attribution). All + must pass before merge; direct pushes to `main` are blocked by a ruleset. +- **Every change lands with a test, and the test must be able to fail.** If + you add a guard, break it once and watch it go red before you commit. +- **Say what you did not do.** A PR that lists what it left out and why is + worth more than one implying completeness. An honest gap is cheap; a false + claim is expensive. +- **Contributing from a fork?** Two things are normal. On your first PR the + workflows wait for a maintainer to approve them โ€” GitHub's standard + first-time-contributor safeguard, nothing you did wrong. And when + `commit-policy` fails on a fork PR it cannot post its explanatory comment + (fork PRs get a read-only token); the job log carries the full explanation, + including the offending commit and the command that fixes it. +- Review: expect actionable review within a few days. Small, focused PRs get + reviewed faster. Update `CHANGELOG.md` under `[Unreleased]` for any + user-facing change. + +## 8. Release process + +Releases are cut by maintainers only; the checklist lives in +[docs/RELEASING.md](docs/RELEASING.md). + +## 9. The pin policy + +Three pins move together or not at all: the nightly in +`rust-toolchain.toml`, the `cuda-oxide` pin, and the `reconverge` pin. +`reconverge` is a rustc-driver tool โ€” it must be built by the exact rustc it +wraps โ€” and `cuda-oxide` requires the same pin. A bump is its own commit, +never mixed with a behaviour change, and re-runs the affected stage gates. +The scheduled `pins.yml` workflow reports upstream movement by opening an +issue; it never bumps anything. Current pins: nightly-2026-04-03, +cuda-oxide 50d07314, reconverge 0.1.11 (installed from crates.io). -## License +## 10. License By contributing, you agree that your contributions will be dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) without additional diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..6d92627 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,84 @@ +# Releasing launchbound + +One page, copy-pasteable. Maintainers only. The same shape as the sibling +projects' release docs โ€” [termlens], [mossaic], [reconverge] โ€” so a +maintainer moving between them is not relearning the process. + +## Prerequisites + +- **crates.io Trusted Publishing**, linked to this repository and + `release.yml`. No token is stored anywhere; the publish job mints a + short-lived one over OIDC. +- **`v*.*.*` tags protected by a ruleset**, so only a maintainer can push one. +- Eight crates publish **in dependency order**, and `cargo publish` waits for + each to appear on the index before the next. A rate-limited run can be + resumed with `workflow_dispatch`, which skips crates already on the + registry. + +## Cutting vX.Y.Z + +```sh +# 0. Green main, and no flakes. The gate runs per PR; the hunt does not. +gh workflow run stress.yml -f iterations=100 +gh run watch # ten shards, both OSes + +# 1. Bump the version. It appears once per crate plus the workspace pins. +$EDITOR Cargo.toml # version = "X.Y.Z" +cargo check --workspace # refreshes Cargo.lock + +# 2. Move the CHANGELOG section: [Unreleased] -> [X.Y.Z] - YYYY-MM-DD, +# leaving an empty [Unreleased] above it. + +# 3. Bump every version the docs name. Two kinds go stale: the +# `action@vN` refs people copy, and "pin a number" examples a reader +# reasonably reads as current. This finds both: +grep -rEn "launchbound/action@v|[0-9]+\.[0-9]+\.[0-9]+" docs action README.md \ + | grep -v CHANGELOG + +# 4. Land it. +git switch -c release/vX.Y.Z +git commit -sam "release: vX.Y.Z" +gh pr create --fill + +# 5. Tag the squash-merged commit on main. +git switch main && git pull +git tag vX.Y.Z && git push origin vX.Y.Z +``` + +Pushing the tag runs `release.yml`, which gates, then publishes each crate in +order via Trusted Publishing. + +## After the tag + +- **The GitHub Release is created by hand**, from the CHANGELOG section: + `gh release create vX.Y.Z --title "launchbound X.Y.Z" --notes-file โ€ฆ`. + Every released version has one; do not skip it. +- **Verify what was published, not what was built.** `install.yml` installs + from crates.io into a clean directory and runs the binaries; dispatch it + once the version is live: + ```sh + gh workflow run install.yml + ``` + +## What a version number means here + +- **Breaking** (minor pre-1.0, major after): a removed or renamed public item, + a changed CLI flag, or a change to what the gate admits that a user would + have to relearn. +- **Not breaking**: new flags, new backends, a corpus addition, a report field. +- **MSRV and pinned-toolchain bumps are minor**, never patch, and never land + in the same change as a behaviour change. + +## If something fails mid-release + +- **Before publish**: fix, delete the tag (`git push --delete origin vX.Y.Z`), + re-tag. Nothing was published; the world never saw it. +- **Part-way through the eight crates**: re-run `release.yml` by dispatch. It + skips what is already on the registry. +- **After publish**: crates.io is immutable. Ship `X.Y.Z+1`. Yank only if the + release is actively harmful โ€” a yanked crate still breaks downstream + lockfiles. + +[termlens]: https://github.com/vyncint/termlens/blob/main/docs/RELEASING.md +[mossaic]: https://github.com/vyncint/mossaic/blob/main/docs/RELEASING.md +[reconverge]: https://github.com/vyncint/reconverge/blob/main/docs/RELEASING.md