<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/mahkassem/fairlead/main/assets/brand/fairlead-reversed-horizontal-dark.svg">
<img alt="Fairlead" src="https://raw.githubusercontent.com/mahkassem/fairlead/main/assets/brand/fairlead-primary-horizontal-light.svg" width="360">
</picture>
</p>
<p align="center"><strong>Guardrails and a guided path for coding agents.</strong></p>
<p align="center">
<a href="https://github.com/mahkassem/fairlead/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/mahkassem/fairlead/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/mahkassem/fairlead/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/mahkassem/fairlead?color=008F67"></a>
<a href="https://mahkassem.github.io/fairlead/docs/"><img alt="Docs" src="https://img.shields.io/badge/docs-book-0B1618"></a>
</p>
A fairlead is the fitting on a boat that keeps a line running true, so it
doesn't chafe, tangle or pull off course. Fairlead does that for an agent
working in your repository. It is one Rust binary with no project-specific
logic: everything about your repository lives in your own `fairlead.toml`.
## Why Fairlead
Join a good team and you don't start from zero. Someone tells you which module
never to touch without running the migration check first, which test lies on
Windows, and which "quick fix" broke production last spring. That knowledge is
why a new engineer is useful in week two instead of month six.
A coding agent gets none of it. It opens your repository cold every time,
reads the same code again, makes the mistake your team already paid for, and
finds out in CI that it was an old lesson nobody told it. Fairlead gives every
repository its own memory, so your agent starts where your team left off instead
of trying things like a stranger.
- **A memory for every repository.** The hard lessons from past work live with
the code, per module, where the agent meets them before it acts. A lesson is
kept short, reviewed on a date, and turned into a check when it can be, so
the memory stays true instead of growing into noise.
*Since 0.2.0:* owner rules record which tests guard which code, and a
quarantined test applies only while the evidence holds and until its date.
*Since 0.7.0:* a `[[quarantine]]` entry records a test that lies on one
platform, such as a CRLF checkout or a path with a space. There, the plan
lists it as not provable here, and `done` excuses only the failure it expects.
- **Set up once, never start cold.** The agent's first job is to learn
the repository: its modules, test runners, rules, and the commands that prove
a change is right. That goes into one checked config, so every session starts
where the last one left off.
*Since 0.1.1:* `fairlead config check` validates every layer, and
`show --origin` says where each value came from.
- **The right tool, not the nearest one.** The agent asks what applies to the
files in front of it and gets the rule, the command and the next step for
exactly those files.
*Since 0.2.0:* `fairlead plan` names the tests and checks a change can
reach, and `test --explain` says why each one is in or out.
*Since 0.13.0:* `plan --head REV` plans a past commit without checking it out.
- **Plans the change, not just the tests.** Before an edit, the agent sees
what it's about to touch, everything that depends on it, the rules and
lessons recorded there, and what has to pass before it's done. Afterwards,
what actually changed is checked against that brief.
*Since 0.6.0:* `fairlead brief <paths>` answers that before the first edit,
`fairlead done` is the gate a change passes before it's finished,
`fairlead receipt` compares what changed with the brief, and `fairlead next`
names the one step left.
*Since 0.13.0:* a brief says when it carries paths from earlier briefs of the
session, and `brief --fresh` starts the set over.
- **The right skills for the code in front of it.** A frontend change
shouldn't come with database advice. Fairlead picks the skills that apply to
what a change reaches, just as it picks tests, and checks the agent used them.
*Since 0.8.0:* `[[skills.routes]]` scopes each skill, the brief lists the
ones a change reaches, `skills sync` writes them where each agent loads
them, and `skills report` measures whether the agent used them.
- **Measured, not guessed.** Good and bad are numbers: whether the plan would
have caught real CI failures, rework, escaped defects, tokens and cost.
*Since 0.2.0:* `fairlead replay` re-plans real failures from a repository's
CI history, and the [benchmarks](https://mahkassem.github.io/fairlead/docs/benchmarks.html)
measure that recall every week. *Since 0.6.0:* `ci run --judge` names each
failing test on the default branch that the merged change's plan left out.
*Since 0.13.0:* when the config being replayed differs from the base
branch's, `replay run` reports the recall of both side by side and names
each failure that moved between hit and miss.
*Since 0.18.0:* `fairlead ci ledger` says whether each red check is the
change's own, inherited from the base branch ("red on main since abc1234"),
flaky or the platform's, with every workflow's red streak on the default
branch; `next`, `resume`, `brief` and `doctor` read it offline, and
`insights --speed` says where CI time goes: slow steps, steps that never
fail, suites faster than their plan, reruns and cancellations.
- **Fast because it remembers.** No rereading the codebase to rediscover what
was learned last week. The graph, the plan and the lessons are already there.
*Since 0.2.0:* the import graph is built without installing dependencies
and cached between runs.
- **It learns where it's blind.** When a change reaches no test, Fairlead says
so and points at the rule that's missing, so the team's knowledge grows
exactly where it was thin.
*Since 0.3.0:* `fairlead plan` lists every file no test depends on, and
replay suggests the owner rule or check path that would have caught a miss.
- **Stopped before the mistake, not after.** Your rules run as the agent
works, catching a wrong move in seconds instead of minutes later in CI.
*Since 0.4.0:* `[guard.*]` rules run in a Claude Code hook before each edit
and in a git commit hook, and `fairlead guard check` runs them on the tree.
*Since 0.7.0:* the same hooks for Codex and Gemini CLI, with
`fairlead hooks install --codex` or `--gemini`.
## Works with
- **Any test runner.** Vitest, Jest, Playwright, `node --test`, Bun, pytest,
`go test`, PHPUnit and Pest: a runner is one command in your config, so
Fairlead never needs a plugin for your stack.
- **Monorepos, precisely.** pnpm, npm, yarn and bun workspaces. A pnpm lockfile
change runs only the packages whose dependencies actually changed.
*Since 0.8.0:* in a bun project, a dependency's version bump runs only the
tests of the files that import it, and nothing when nothing does.
*Since 0.13.0:* a `plan.run_all` entry can name the kinds of change that run
everything, so a new migration goes through the graph and an edited one
runs everything. *Since 0.14.0:* a named import from a barrel depends on the
file that declares the name, so a change behind a package's `index.ts`
selects only the tests that import what it declares.
- **No install needed to read your code.** The import graph comes from source
alone, with tsconfig paths, `go.mod` and composer's autoload resolved and
parsed files cached, so a plan takes a fraction of a second.
- **Your CI, not a new one.** A GitHub Action and `ci plan --format github` for
Actions, and a JSON plan with a published schema for any other CI.
- **Proven on real projects.** Recall is replayed from the CI history of Effect,
pnpm and vitest every week, and published.
- **Private by default.** The CLI sends nothing you didn't ask for: no
telemetry, and it never phones home.
- **One binary, everywhere.** Linux (glibc or musl, as on Alpine), macOS and
Windows, installed with a shell script, PowerShell or npm.
**What it reads, and what replay has proven.** A language counts as proven
when replaying a public project's CI history judges at least 10 failures, at
high recall:
| JavaScript, TypeScript | 0.2.0 | Effect, pnpm and vitest, replayed weekly ([benchmarks](docs/src/benchmarks.md)) |
| Python | 0.5.0 | poetry: 99.2% of 122 failures |
| PHP | 0.5.0 | pterodactyl/panel and invoiceninja: 100% of 27 failures |
| Go | 0.5.0 | Reads, not yet proven: opentelemetry-go has too few failures to judge |
| Vue, Svelte, Astro components | 0.5.0 | Reads, not yet proven: no replay yet |
| Java, Kotlin (Maven, Gradle) | 0.7.0 | Reads, not yet proven: okhttp at 92.6%, most misses look flaky ([#184](https://github.com/mahkassem/fairlead/issues/184)) |
An external provider feeds the graph for any other language, and a coverage
map adds what imports can't show. Framework packs and folders of several
repositories are on the way
([#47](https://github.com/mahkassem/fairlead/issues/47)).
## What it does
This README documents Fairlead 0.20.2. Each command, by what it's for:
- **Setting a repository up:** `setup` and `guide`. *Since 0.20.0:* `setup`
writes a first config when there is none, installs the agent hooks and the
git hook, syncs the block in AGENTS.md, runs each runner's smallest test
file once, and prints a checklist in which every open item has one fix.
`setup --check` prints the list and changes nothing, and exits 1 while a
required item is open. The list is ordered for the kind of repository it
detects (new, mvp, ongoing or legacy; `--track` overrides). `guide` prints
a short guide for an agent on `setup`, `loop`, `gate`, `ci`, `upgrade`,
`troubleshoot` or `report`.
- **When Fairlead looks wrong:** `report`. *Since 0.20.2:* every refusal (a
failed gate, a hook that stops an edit, a commit the guard stops) ends by
saying how to check whether the mistake is Fairlead's. `report` then writes
a draft for the project's owner to review and post as an issue: the
release, the platform, the config's shape as counts, the setup's states and
the last refusals as numbers, with runners and checks numbered and never
named. It sends nothing, and the guide says what never goes in a report.
- **The test plan:** `plan`, `test --explain`, the import graph (`graph`) and
coverage maps (`coverage import`). *Since 0.13.0:* `graph hubs` lists the
files that link the most changes to the most tests and the barrier that
would cut the top one, and a changed barrier file selects its importers'
tests.
- **Plans in CI:** `ci plan` (by stage, with `--since-green`), `ci run` (with
`--results`, `--judge`, `--only` and `--except`), `ci report`, `ci workflow`,
which writes a staged workflow, and the GitHub Action. *Since 0.13.0:*
`ci plan --shards N` splits a plan across a matrix of N jobs, balanced by
past durations, and `ci run --shard K/N` runs one job's share.
*Since 0.15.0:* a slow suite with `all_from = "full"`, or every suite while
the project's `phase` is `early` or `testing`, runs only what a change
reaches on a pull request and runs whole on a schedule.
*Since 0.18.0:* `ci ledger` judges each failure of a run as own, inherited,
flaky or infra, `ci report --ledger` says it once, and the workflow
`ci workflow` writes runs it when a job fails.
- **Rules as the agent works:** `guard check`, the hooks `hooks install` adds,
and `doctor`. *Since 0.18.0:* a commit may not add a file named like a secret
(`.env`, `*.jks`, `*.pem` and the like) with no config at all; gitleaks
runs as a guard preset; migration numbers are checked against the base
fetched at a push (`guard check --push`); and `pr check` holds a change to
`[pr]` rules (branch name, ticket key, base, labels, body headings,
required files) in `done`, before a push and in CI. A foreign linter with
`diff = true` fails only on the findings a change adds, against the base
in a temporary worktree, and `pr check` fails a body that says "verified"
or "tests pass" with no passing receipt for the head's tree.
- **The change loop:** `brief` before an edit, or `context` for the brief with
the lessons, skills, README and history to read; `done` before it's
finished, then `receipt` and `next`; `resume` picks up where the last
session on a branch stopped. *Since 0.18.0:* `done` plans the branch from its
merge base with `[pr] base` or the default branch, runs first the cheap
steps CI history shows going red (and adds a cheap check the plan left
out, saying why), and runs as the pre-push hook (`done --pre-push`); the
receipt says where each step ran, by stage and platform, what never ran on
this tree and what can't be proved here ("unverifiable here: needs
macos"), and `receipt --pr` puts it in the pull request body.
*Since 0.19.0:* `done` runs under a budget (300 seconds): the tests
nearest the change run until it's spent, and what else the change reaches
is owed to CI, by runner and count, on the receipt.
- **Project memory:** `learn` writes a lesson, `import lessons` turns a lessons
document the team already keeps into one lesson per heading, and `lessons`
lists, reviews and checks them. The brief offers the ones a change reaches.
- **Skills:** `[[skills.routes]]` scopes each SKILL.md, and the brief lists the
skills a change reaches. `import rules` turns path-scoped rule files into
skills and routes, and refreshes a skill when its rule changes, and
`skills sync` writes each skill where Claude Code, Codex and Cursor load it.
- **Finding things:** `find` searches lessons, skills, docs headings and
declared names, nearest the brief first.
- **Agents over MCP:** `mcp` serves the plan, the brief, the receipt and the
other read-only commands as tools.
- **What agents read first:** `agents sync` keeps the loop, the always-on
lessons and the skill index in a marked block in AGENTS.md and CLAUDE.md.
- **Measuring:** `insights`, what the event log and CI's reports say about the
setup and the config changes they back; `tokens`, the tokens and cost of
each agent session; `replay`, the benchmarks, `skills eval`, which scores
skill routing on the repository's history, and `skills report`, the hit
rate of what routing offers against what agents load. *Since 0.14.0:*
`test bench` times `bun test` serial, `--parallel` and
`--parallel --no-isolate` on the repository's tests, flags order-dependent
ones, and recommends the fastest mode for `doctor --score` and
`insights --suggest`. *Since 0.18.0:* `ci ledger`, a verdict for each red
check and the default branch's red streaks, and `insights --speed`.
- **Getting started:** `init` writes a first config from what the repository
shows, and `init --blueprint` adds a starting point for a kind of project:
config, starter skills and the CI workflow. *Since 0.14.0:* on Bun 1.4.4 or
later, `init` runs `bun test` in parallel (`--parallel --no-isolate`) and
offers `bun fmt --check` on the changed files as a check. *Since 0.18.0:*
`init --profile guard` sets up only the guard, for any stack, including
those the test planner doesn't read, such as Laravel, Rails, Flutter or
Swift.
- **The config:** `config check`, `show` and `schema`.
- **Upgrading:** `migrate`, which brings the hooks, the config's version
floor and the version pins to the release you installed. *Since 0.7.0.*
The [quick start](#quick-start) shows the first few, and the
[book](https://mahkassem.github.io/fairlead/docs/) covers each one.
## Status
Pre-alpha. The latest release, v0.20.2, makes a mistake of Fairlead's easy
to report: every refusal says how to check it, and `fairlead report` drafts
a report with nothing private in it for the project's owner to send. The
release before it fixed what a first run on eight repositories got wrong,
and the one before that made a first setup one command:
`fairlead setup` applies what is local, then lists what is left with one
fix for each, ordered for the kind of repository it finds (new, mvp,
ongoing or legacy), and `fairlead guide` prints the guide an agent needs
for each step. A session and CI say so when a repository's setup has gaps.
The release before it gave the done gate a budget: it
runs the tests nearest the change until the budget is spent and records
what else the change reaches as owed to CI, so the gate fits the machine
it runs on and no suite holds it. A suite reached only through a blanket
(a `run_all` path or a wide rule) runs its smoke set, and
`insights --score` says how often plans select everything and how long the
gate takes. The release before it folded what was planned as 0.16 and
0.17 into one. Guards work on any stack from day one
(`init --profile guard`), secret file names are refused at commit with no
config, gitleaks runs as a preset, and `[pr]` rules (branch, title, ticket,
body, base, labels, files) run in `done`, at push and in CI with
`fairlead pr check`. `fairlead ci ledger` answers "is this red mine?" (own,
inherited, flaky or infra) and `insights --speed` says where test and CI
time goes. `fairlead done` plans from the merge base, runs first what CI
history shows going red, records each step's stage and platform, and a
foreign linter fails only on what a change adds. 0.15.2 made `done` read
`[[needs]]` and fixed the Stop hook; 0.15.0 left slow suites to a scheduled
run. What each earlier release added is
in the [changelog](CHANGELOG.md), and what upgrading to it asks of you is in
the [upgrade guide](https://mahkassem.github.io/fairlead/docs/migrate.html).
Recall on real CI failures, with every miss and its cause, is in the
[changelog](CHANGELOG.md) and on the
[benchmarks page](https://mahkassem.github.io/fairlead/docs/benchmarks.html).
Hooks ship for Claude Code, Codex and Gemini CLI. The [roadmap](https://mahkassem.github.io/fairlead/docs/roadmap.html)
has milestones K0 to K6, each an [issue](https://github.com/mahkassem/fairlead/issues)
with its exit criteria.
## Install
```sh
# macOS and Linux
# Windows (PowerShell)
# In a JavaScript or TypeScript project, as a dev dependency
bun add -d fairlead # or: npm i -D fairlead
# In a PHP or Laravel project, as a dev dependency
composer require --dev mahkassem/fairlead
# With Homebrew (macOS and Linux)
brew install mahkassem/tap/fairlead
# From crates.io, with Rust 1.95 or later
cargo install --locked fairlead
```
*Since 0.14.0:* the Homebrew tap, crates.io and Composer.
Then `fairlead doctor` says which binary, platform and config it would use.
*Since 0.13.0:* static musl builds for x86_64 and aarch64 Linux, which the
installers pick on a musl system such as an Alpine container image. On a
platform with no build, the npm package still installs, and `fairlead` says
how to build it from source ([Install](https://mahkassem.github.io/fairlead/docs/install.html#platforms)).
*Since 0.14.0:* the Composer package, for a PHP project without Node. A small
PHP launcher downloads the release build for the platform on its first run,
checks its sha256 before unpacking it, and caches it in `vendor/`
([Install](https://mahkassem.github.io/fairlead/docs/install.html#composer)).
*Since 0.18.0:* the npm package retries a failed download, and
`FAIRLEAD_BINARY` points it at a binary fetched some other way, such as from
a CI cache ([Install](https://mahkassem.github.io/fairlead/docs/install.html#a-binary-fetched-another-way)).
## Quick start
```sh
fairlead setup # set the repository up, then list what is still open, each with its one fix
fairlead brief src/util.ts # before an edit: what it reaches and what must pass
fairlead done # the gate a change passes before it's finished
fairlead receipt # after it: the change against its brief, and the gate's state
```
[Start here](https://mahkassem.github.io/fairlead/docs/start.html) says what
`setup` does on a new, MVP, ongoing or legacy repository, and what done looks
like on each.
## Documentation
The site is [mahkassem.github.io/fairlead](https://mahkassem.github.io/fairlead/), and [the book](https://mahkassem.github.io/fairlead/docs/) covers
install, configuration, the import graph, the test plan, plans in CI, guard
rules and hooks, the brief, the done gate, the receipt, replay and the
benchmarks. Its source is in `docs/`.
The README, the site and the book describe the latest release, and each says
which one. For an earlier release, read them at its tag, such as
[`v0.5.1`](https://github.com/mahkassem/fairlead/tree/v0.5.1). A release
can't be cut while any of them lags it: `scripts/docs_check.py` runs before the
release builds.
## Brand
The logo, its colors and how to use them are in [`assets/brand`](assets/brand).
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option. Unless you explicitly state otherwise, any contribution
intentionally submitted for inclusion in the work by you, as defined in the
Apache-2.0 license, shall be dual licensed as above, without any additional
terms or conditions.