Thanks for wanting to contribute. One rule up front:
Human-authored pull requests targeting main must be raised through no-mistakes.
We require this to reduce the maintainer's burden of reviewing and merging contributions.
no-mistakes puts a local git proxy in front of your real remote.
Pushing through it runs an AI-driven review/test/lint pipeline in an isolated worktree, forwards the push upstream only after every check passes, and opens a clean PR automatically.
A GitHub Actions check (Require no-mistakes) runs on PRs targeting main and requires both the deterministic signature and a parseable structured attestation from no-mistakes v1.46.0 or newer.
The attestation must bind to the current PR head commit and report the review, test, and document steps as completed, so a stale attestation, a missing head_sha, or a skipped required step fails.
It evaluates every PR opening and body edit independently, reruns after head synchronization or reopening, and prevents a later edit from replacing an earlier pending compliance check.
GitHub Actions and Dependabot are exempt so their automation keeps working, but other contributor PRs that do not satisfy the attestation contract will not be reviewed or merged.
-
Fork the repo, then clone the parent repo or set your local
originback to the parent (git@github.com:kunchenguid/firstmate.git). -
Create a branch and make your changes.
-
Initialize the gate with your fork as the push target:
no-mistakes init --fork-url git@github.com:<you>/firstmate.git(contributing to firstmate requires no-mistakes v1.46.0+ for structured attestation; without a fork, plainno-mistakes initstill works for maintainers with push access). -
Commit your changes.
-
Push through the gate instead of pushing to
origin:git push no-mistakes
-
Run
no-mistakesto attach to the pipeline, watch findings, authorize auto-fixes, and review ask-user findings as needed. Follow the installed no-mistakes version's SKILL.md and liveaxihelp for gate mechanics. -
Once the pipeline passes, it pushes the branch to your fork and opens the PR against the parent repo for you.
See the no-mistakes quick start for the full first-run walkthrough.
- This repo is a template for running a firstmate orchestrator agent.
AGENTS.mdowns the supervisor contract, role boundary, and bundled firstmate skill triggers;CLAUDE.mdis a real@AGENTS.mdpointer to it, and.claude/skillsis a symlink to.agents/skills. - Only shared material is tracked:
AGENTS.md,README.md,CONTRIBUTING.md,.tasks.toml,.github/workflows/,bin/,.agents/skills/, andskills/..agents/skills/holds agent-loaded skills that assume a live firstmate home and carrymetadata.internal: trueso installers such as skills.sh hide them from discovery;skills/holds standalone, installer-facing public skills with no firstmate dependency (see the README's "Two-tier skill layout"). Everything personal to one captain's fleet (.env,data/,state/,config/,projects/,.no-mistakes/) is gitignored; never commit it. The root.tasks.tomlis trackedtasks-axiconfig fordata/backlog.md; compatibletasks-axiis the default backend for routine backlog mutations, with the compatibility definition owned bydocs/configuration.md("Backlog backend"). A localconfig/backlog-backend=manualopt-out forces firstmate's routine backlog updates to hand-editing and stays gitignored; validated secondmate handoffs still delegate throughtasks-axi mv. A localconfig/backendfile explicitly overrides runtime auto-detection for new task endpoints and stays gitignored; spawn-supported values aretmux,herdr(which has its own required CI lane), andzellij,orca, andcmux, which remain experimental with no dedicated real-backend CI lane, whilecodex-appis documented only indocs/codex-app-backend.md. It does not makedata/tracked. - Helper scripts in
bin/are plain bash. Each starts with a usage header comment; keep it accurate when you change behavior. Test scripts and helpers intests/are plain bash too.bin/fm-lint.shmust pass: it is the single owner of the lint definition (the shellcheck file set, config, pinned shellcheck version, pinned actionlint workflow lint, and the backend-purity check rejecting direct Beads CLI calls in corebin/scripts), and both CI and the no-mistakes pre-push gate invoke it with no arguments. Its header and--helpoutput own the exact local lint modes, file-set selection, and analysis flags. A malformed.github/workflows/*.yml, including a self-brokenci.yml, fails that local lint path before merge because a broken workflow cannot report its own breakage. It pins one exact shellcheck version and one exact actionlint version and refuses to run under any other. Print the shellcheck pin withbin/fm-lint.sh --required-versionand the actionlint pin withbin/fm-lint-workflows.sh --required-version. Usebin/fm-install-shellcheck.shandbin/fm-install-actionlint.shto install those exact builds locally; each installer's header owns its destination usage and supported platforms. - Harness-adapter ownership spans detection in
bin/fm-harness.sh, launch and hook mechanics inbin/fm-spawn.sh, spawn-time Claude workspace-trust pre-registration inbin/fm-claude-trust.sh, semantic busy sources and trust gates inbin/fm-busy-lib.sh, delivery-only rendered guards inbin/fm-composer-lib.sh, cleanup inbin/fm-teardown.sh, and facts in the skill tree rooted at.agents/skills/harness-adapters/SKILL.md; thefirstmate-coding-guidelinesskill owns the validation policy for checks that depend on those harnesses. - Changes to runtime session backends (
bin/fm-backend.sh,bin/backends/, and the scripts that dispatch through them) keep current setup and limits in the relevant backend guide and active empirical evidence indocs/verification/runtime-backends.md. docs/documentation-audiences.mdand its machine-consumed inventory own prose classification; runbin/fm-doc-audience-check.shafter documentation changes.- In Markdown, put each full sentence on its own line.
README.mdstays a concise overview plus pointers: it never carries a wall of inline detail. Route detail to the most specificdocs/file (architecture, configuration, or a backend guide) and link to it instead.
Tracked changes to firstmate itself - AGENTS.md, README.md, CONTRIBUTING.md, .tasks.toml, .github/workflows/, bin/, .agents/skills/, and skills/ - ship through the no-mistakes pipeline on a feature branch and require an explicit merge approval.
Before making any such change, load the agent-only firstmate-coding-guidelines skill (.agents/skills/firstmate-coding-guidelines/SKILL.md).
It has the knowledge-placement rules that keep AGENTS.md from regrowing after each diet pass.
There is no reliable way for bin/fm-brief.sh's scaffold to detect that a task's repo is firstmate itself, so firstmate adds this skill's load line to firstmate-repo briefs by hand.
A crewmate picking up such a brief should load the skill even if the brief predates this instruction.
When supervising live crewmates, keep firstmate's own long validation or build commands in the background so watcher wakes can still be handled.
Crewmate validation follows the installed no-mistakes version's SKILL.md and live axi help instead of duplicating gate mechanics in firstmate docs.
Firstmate's wrapper still matters: crewmates route every ask-user finding to firstmate, which applies ask-user-authority, and crewmates never pass --yes or -y because either flag bypasses that check and any required captain escalation.
docs/configuration.md owns the tracked .no-mistakes.yaml gate defaults.
The firstmate-coding-guidelines skill owns the rule that local no-mistakes Test stays intent-targeted rather than configuring commands.test.
Verify the same way the gate does: reach for bin/fm-test-run.sh with the subjects you care about rather than chaining bash tests/a.test.sh && bash tests/b.test.sh, because a list of script paths gets the same bounded concurrency as --changed.
The pipeline publishes that evidence itself, so never hand-commit .no-mistakes/ paths onto a feature branch; CI rejects them as tracked personal fleet paths.
Check and test the toolbelt before pushing:
while IFS= read -r script; do /bin/bash -n "$script" || exit; done < <(bin/fm-lint.sh --list-files) # syntax-check the shell surface fm-lint.sh will cover (changed files locally, full set in CI/on main)
bin/fm-lint.sh # lint that shell surface plus GitHub workflows via pinned actionlint; the single owner CI and the no-mistakes gate both run
bin/fm-test-run.sh tests/<subject>.test.sh # one script (primary local focus path, timed)
bin/fm-test-run.sh tests/<a>.test.sh tests/<b>.test.sh # several subjects at once: bounded automatic concurrency
bin/fm-test-run.sh --family pure-contract-unit # ordinary family-scoped local path (serial, timed)
bin/fm-test-run.sh --changed # normal changed-file-informed path with automatic bounded concurrency
bin/fm-test-run.sh --changed --jobs 1 # explicit serial override
bin/fm-test-run.sh --changed --max-wall-ms 300000 # same automatic path with a post-run five-minute result check
bin/fm-test-run.sh --proven-isolated --jobs 4 # explicit local parallel of the individually proven set
bin/fm-test-run.sh --lane portable-serial # portable serial remainder (watcher/AFK/tmux/stateful)
bin/fm-test-run.sh --list-lanes # discover exact lane names, including the current CI serial shards
bin/fm-test-run.sh --check-coverage # prove portable shards + serial + serial shards + Herdr equal the full inventory
bin/fm-test-run.sh --all # deliberate complete regression (optional local full walk; not no-mistakes Test)
bin/fm-test-isolation-proof.sh --list # proven portable parallel candidate set
bin/fm-test-isolation-proof.sh --jobs 4 --json /tmp/fm-isolation-proof.json # re-run the portable candidate proof
bin/fm-test-isolation-proof.sh --pool watcher-wake-lock --jobs 4 # re-run an admitted family proof
[ ! -L CLAUDE.md ] && cmp -s CLAUDE.md - <<'EOF'
<!-- Points Claude at AGENTS.md via import; edit AGENTS.md, not this file. -->
@AGENTS.md
EOF
[ "$(readlink .claude/skills)" = "../.agents/skills" ]
tmp=$(mktemp -d) && printf 'done: smoke\n' > "$tmp/smoke.status" && FM_STATE_OVERRIDE="$tmp" FM_SIGNAL_GRACE=1 FM_POLL=1 FM_HEARTBEAT=999999 bin/fm-watch-arm.sh # watcher re-arm smoke test (prints arm status, then an actionable signal)bin/fm-test-run.sh is the single owner of behavior-suite selection, portable CI lane composition, bounded concurrency admission, per-script timing markers, family totals, the coverage guard, and the optional JSON timing artifact.
Its header and --help own the flags, family labels, lanes, and changed-file map; this section only documents the entry points.
bin/fm-test-isolation-proof.sh remains the single owner of the portable candidate proof and reusable family proof harness; see docs/fm-test-isolation-proof.md.
Portable shard balance evidence lives in docs/fm-test-portable-shards.md.
Family selection is the ordinary local path; --all is deliberate full regression only.
CI owns broad regression across required portable parallel shards, the portable serial lane's separate-runner shards, the Herdr lane, lint, invariants, the coverage guard, and stock macOS Bash compatibility in .github/workflows/ci.yml.
Use bin/fm-test-run.sh --list-lanes for exact lane names and --help for --jobs rules and required gate-skip flags when reproducing a lane locally.
Leave the sleep 0.1 cadence in the suites' bounded condition waits alone.
Those sleeps look like recoverable overhead - fm-watch-triage.test.sh alone issues about 1,900 of them, each paying a flat ~100ms scheduler wake-up penalty on macOS - but they are not overhead added to the clock; they are how a test waits for a subject that only moves on fm-watch.sh's own one-second FM_POLL cadence.
Sampling less often does not remove that wait, it only delays detection: raising the interval to 0.5s and charging each sample proportionally measured fm-watch-triage.test.sh at 435s and 440s against 390s and 393s for the unchanged script, back to back on 2026-09-03, because each of its ~40 poll-cycle waits and ~73 process-exit waits paid up to half a second more.
Some of those loops are also catching a transient rather than waiting for a settled condition, so a coarser sample can step over the state they assert on.
Discover tests by listing tests/*.test.sh: each is a self-contained bash script named <subject>.test.sh, and its header comment describes what it covers, so pass one to bin/fm-test-run.sh to focus on a subject with canonical timing output.
Shared test helpers live in tests/lib.sh (reporters, temp roots, git fixtures), tests/fixtures.sh (fake toolchain and spawn-world builders), tests/wake-helpers.sh, and tests/secondmate-helpers.sh.
Source those instead of copying a fake toolchain into a new suite.
A fixture may shorten a production timeout to keep a failure path prompt, but never below what the real work inside that window costs on a loaded machine: a fork, an exec, a lock acquisition, a beacon publication, or a first-poll check.
Where a case's assertion is not about the timeout itself, give that window headroom over the measured loaded cost, and bound the test's own waiting with iteration-counted poll loops, which stretch under load where a wall-clock budget does not.
Tests that need a real optional backend or an explicit opt-in (real herdr/zellij/cmux smoke tests, the live Pi regression) skip themselves and print the tool or environment gate needed to enable them, so the portable suite remains safe on machines without those tools.
The Herdr backend guide owns the lane's isolation boundary, while runtime backend verification owns active empirical evidence; live harness credential tests remain opt-in.
Open an issue, or talk to me on Discord.