contributing
how to work in this repository. every command below is real and is in
package.json. ci runs the same checks, so a green machine means a green
pull request.
contributing with a coding agent is welcome, and most of this repository was
built that way. the rules are the same, with a few more that matter more for an
agent. see agent-guidelines.md.
1. set up
you need bun, docker, and git.
bun install # also installs the commit-msg hook
bun run db:up # starts postgres in docker
bun run db:migrate # applies the schema
copy .env.example to .env and fill it in. the file explains each value.
tripwire starts without most of them. a missing credential turns one feature
off. it never makes tripwire accept something it cannot check.
bun run dev # web, api, worker, and a tunnel
2. before you push
run these four. ci runs the same four, in this order.
bun run check # biome: format and lint
bun run typecheck # every package
bun run check:boundaries # the dependency arrows
bun test # unit and integration tests
bun run check --write fixes most format problems for you.
run them before you PUSH, not before you open a pull request. a broken commit in the history is harder to remove than a broken branch.
the strict type suite
bun run lint:slop:changed # the changed lines only
bun run lint:slop # everything
this is a separate, custom rule set. it is strict on purpose.
any is banned. so is a cast that only silences the compiler.
these are the same mistake: as any, as unknown as T, and casting to
Record<string, unknown> to get past an error.
when a type does not fit, it is telling you something true. read it.
if you cannot type something honestly, leave the real type, add a comment naming what is unknown, and ask.
3. the dependency arrows
this is the one rule the build enforces for you.
contracts ← everything (imports nothing but zod)
utils ← everything except contracts
forge ← the adapters, worker (interface and types only)
core ← worker only (pure: no i/o, no db, no network)
db ← worker, api, web
auth ← web, api
forge-github ← worker, api
forge-opengit ← worker, api
ui ← web
three laws:
- apps import packages. packages never import an app.
- nothing imports
coreexceptworker. - an adapter never imports another adapter.
scripts/check-boundaries.ts fails the build on a wrong-direction import. if
your work does not fit an existing package, stop and ask. do not add a new
top-level package without a DECISIONS.md entry.
why core is pure
core holds the rules. it does no i/o at all. the worker passes effects in.
a rule is therefore a plain function over a fixed input. its test needs no database and no network.
4. naming
- files are kebab-case:
event-list.tsx,account-age.ts - components are pascalcase. hooks start with
use- - constants are screaming_snake_case
- types are pascalcase. a props type ends in
Props - a rule id is kebab-case with a version:
some-rule@1 - database columns are snake_case
- add a barrel
index.tsat three or more exports - never re-export from a file that is not a barrel
imports are absolute. use #/ inside apps/web and @tripwire/* across
packages. never write ../../...
5. commits
a commit message needs a type. a task id is optional.
fix: correct the timeout
MDN-42 fix: correct the timeout
MDN-42 fix(api): correct the timeout
allowed types: build, chore, ci, design, docs, feat, fix,
perf, refactor, revert, style, test.
the header has a limit of 100 characters. the commit-msg hook checks the
message. bun install installs the hook. --no-verify skips it.
put the task id in a pull request title too. that closes the task automatically.
write the why, not the what
the diff shows what changed. a message should say why. name the behaviour that was wrong, and what it does now.
6. tests
run one package:
cd packages/core && bun test
three kinds of test live here.
| kind | where | needs |
|---|---|---|
| unit | next to the source | nothing |
| integration | *.integration.test.ts |
postgres |
| live e2e | scripts/e2e/ |
real credentials |
the live e2e drives real pull requests on a real repository. it is a pre-release tool, not a per-pull-request check. ci does not run it.
bun run test --list # the github scenarios
bun run test --only <name> # one of them
the github harness leaves a pull request open when you pass --keep. the
interactive run asks before it closes anything.
the open-git harness never closes a pull request, and has no way to. open-git has no closed-pull-request page, so a closed one cannot be read.
bun run scripts/e2e/opengit.ts --list
a test must be able to fail
a test that passes when the code is broken is worse than no test. delete a line of the code under test and check that the test goes red.
7. what a good change looks like
- it does one thing
- it explains why in the commit message
- it adds a test that fails without it
- it passes the four checks in section 2
- it says out loud what it decided on your behalf
if you find a second problem while fixing the first, write it down and fix it separately. do not widen a change because you are already in the file.
fail closed
tripwire gates other people's code. when tripwire cannot check something, it must say so and hold. it must never pass a change it did not examine. a rule that cannot read what it needs SKIPS. it does not guess, and it does not report a pass.
8. a ui change needs proof
a pull request that changes the interface must include a before screenshot and an after screenshot.
record a video instead for anything with more than one step: a flow, an animation, an empty state filling up, an error appearing.
a screenshot of the after alone proves nothing, because a reviewer cannot see what changed. two images make the change reviewable in seconds.
9. keep a pull request small
a pull request should do one thing. a very large diff almost always means one change grew into several.
split it. each part should stand alone and pass every check alone.
- a refactor is its own pull request. never mix it with a behaviour change.
- a rename is its own pull request.
- a new dependency is its own pull request, with a
DECISIONS.mdentry. - a bug you found on the way is its own pull request.
if you cannot describe the pull request in one sentence without saying "and", it is two pull requests.
10. asking for a review
open a pull request against main. put the task id in the title.
in the description, answer three questions:
- what was wrong
- what you changed
- what you decided that a reader might disagree with
the third matters most. a reviewer can read a diff. a reviewer cannot read your reasoning.