# Contributing
## Getting Started
bun install
just quickstart
`ANTHROPIC_API_KEY` is already set; `libconfig` reads it automatically.
## Core Rules
**READ-DO**: read each item, then do it. **DO-CONFIRM**: do from memory, then
confirm (Gawande, _Checklist Manifesto_ Ch. 6).
### Invariants
Architectural non-negotiables — the shape of the codebase.
- **OO+DI everywhere** — Classes accept collaborators via constructors. Factory
functions (`createXxx`) wire implementations; composition roots (CLI `bin/`
entry points) wire instances; tests inject mocks directly. No module-level
singletons or inline dependency creation. Exempt — pure stateless functions
need no DI: libskill, libui (functional DOM), libsecret (crypto), libtype
(generated protobuf).
- **No frontend frameworks** — Vanilla JS, ESM modules only, no CommonJS.
- **FIT upstream of Kata** — FIT skills and docs (`fit-*`, `websites/fit/`,
shared `libraries/`) must not reference the Kata Agent Team. Kata may
reference FIT; the dependency points one way.
- **Explain WHY, not WHEN** — Comments, logs, and durable docs state the present
contract: no spec/design/plan numbers, issue/PR references, or
experiment/obstacle labels. Provenance lives in PR bodies and git history.
`specs/`, `wiki/`, `benchmarks/`, `generated/` are exempt; rewrite, don't
port, their content. Enforced by `bun run invariants`.
### READ-DO
Entry gate — read every item before starting.
- [ ] **Understand the task.** What is it asking? Which files will I
touch, and which will I not?
- [ ] **Smallest plan.** No unrequested features, abstractions, or refactors.
- [ ] **Read the code** I'm about to change before writing.
- [ ] **Search shared libraries first.** Before writing any generic helper, scan
[libraries/README.md](libraries/README.md). Use a library if one covers
it; otherwise note that in the commit or plan.
- [ ] **Reuse libmock; inject collaborators.** Before writing a mock or fixture,
check `libraries/libmock/src/index.js` and reuse it. New src takes
injected `fs`/`proc`/`clock`/`subprocess`, not ambient globals — see
[MONOREPO.md § Ambient Dependencies](MONOREPO.md#ambient-dependencies-and-collaborator-injection).
- [ ] **Simple over easy.** Reduce complexity, don't relocate it. Three similar
lines beat a premature abstraction. Inline single-use helpers; hardcode
single-consumer configuration.
- [ ] **No defensive code.** Trust the architecture — let errors surface. No
try/catch "just to be safe," no optional chaining on data that isn't
optional.
- [ ] **Clean breaks.** Delete old code as you write new — in one commit. No
shims, aliases, fallbacks, or flags for the old path; update every call
site and remove the old interface.
### DO-CONFIRM
Exit gate — verify every item before committing.
- [ ] `bun run check` passes — format, lint, jsdoc, invariants, context.
- [ ] `bun run test` passes — new logic has tests.
- [ ] No new inline mock/fixture helpers that libmock already provides.
Touched test files import from `@forwardimpact/libmock` instead of
redefining `createMock*`, `make*`, or `stubQueries`.
- [ ] My diff only contains changes the task required — no unrequested
refactors or scope creep.
- [ ] Commit format: `type(scope): subject` (see § Git Conventions).
- [ ] If the run produced commits: branch pushed with `git push -u origin` and
PR URL captured in output. Exception: release engineer's direct-to-`main`
CI fixes.
- [ ] Outputs routed per `coordination-protocol.md`; wiki writes per
`memory-protocol.md` — prefer `fit-wiki` subcommands over hand-edits.
None of § Common mis-routings apply.
## Structure
### Monorepo layout
.claude/ # agents and skills, edited via `bunx fit-selfedit`
products/ # one directory per product — see the products list below
libraries/
lib*/ # shared libraries
services/
/ # one per service — see config/config.json
config/
config.json # service definitions
data/
synthetic/ # synthetic data DSL and artifacts
specs/
{feature}/ # specifications and plans
wiki/ # GitHub wiki — shared agent memory
design/ # design language and brand implementations
websites/ # public sites — fit/ → forwardimpact.team, kata/ → kata.team
The `products/` directory holds one directory per product:
map/
pathway/
guide/
landmark/
summit/
outpost/
gear/
kata/
Git tracks `*.example.*` templates in `config/`; live files are gitignored.
### Per-package layout
Every package follows the same on-disk shape: source under `src/`, no `.js`
or `.ts` files at the package root.
/
package.json Required
justfile Per-package task runner (optional)
src/ All source (index.js + domain subdirs)
bin/ Thin CLI entry points, one per binary
config/ Checked-in configuration (optional)
macos/ macOS app bundle (optional)
pkg/ Packaging artifacts, non-source (optional)
proto/ Protobuf source (optional)
schema/ Published schemas (optional)
starter/ Starter data installed to a consumer (optional)
supabase/ Supabase edge project (optional)
templates/ Runtime template files (optional)
test/ Test files
Subcommand handlers live under `src/commands/`, helpers under `src/lib/`.
Published `exports` point at `src/`; consumers import via subpath aliases. No
build step or root-level proxy file.
### Services — the one exception
Services keep `index.js` and `server.js` at the package root (loaded by fixed
path from `config/config.example.json`), plus `proto/`, `src/`, `test/`,
`package.json`. No `bin/`, no `src/index.js`.
### `.claude/` — agent configuration
`Edit` and `Write` are denied on `.claude/**`. Use
[`bunx fit-selfedit`](.claude/agents/x-self-improvement.md) instead.
## Pull Request Workflow
All changes go through pull requests — never push directly to `main`. Commit,
push, and open a PR before finishing; on an ephemeral runner the PR URL is the
only valid "done" signal.
**Exception:** the release engineer may push trivial CI fixes (format, lint,
lockfile drift) that `bun run check:fix` resolves directly to `main`. See
[release-engineer.md](.claude/agents/release-engineer.md).
## Git Conventions
Format: `type(scope): subject`
- **Types**: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `spec`
- **Scope**: package name (`map`, `libui`, `pathway`), or `security` for specs
- **Breaking**: add `!` after scope
`spec` = new specification documents in `specs/`.
### Releasing
Tag prefix matches the directory: `libraries/libfoo` → `libfoo@v0.1.5`,
`services/graph` → `svcgraph@v0.1.60`.
Pre-1.0 packages bump patch for any change. Post-1.0: semver (breaking=major,
feat=minor, fix/refactor=patch). The release engineer handles bumps, tags, and
publishing — see [kata-release-cut](.claude/skills/kata-release-cut).
**Composite actions** release on a separate tag space: append-only `v1.0.x`
tags on the sibling repos, with `v1` a moving major alias for external
consumers. A push to `main` mirrors each action to its sibling by subtree
split; the publish path, SHA-pin policy, and `v1`-move constraints live in
[`.github/CLAUDE.md`](.github/CLAUDE.md).
Some changes span more than one tag. A new or renamed binary, for example, is
reachable only after every tier that carries it ships. Release producer before
consumer, bottom-up, confirming each tier resolves before tagging the next:
1. the compiled binary or bundle;
2. the `fit-install.sh` installer that fetches it, co-versioned with the bundle;
3. the sibling repos' actions and workflows that run it;
4. this repo's pins to those siblings.
A consumer pinned ahead of its producer fails closed: a missing binary has no
`bunx`/`npx` fallback.
## Quality Commands
bun run check # All quality gates (run before every commit)
bun run check:fix # Auto-fix format and lint issues
bun run test # Unit tests, bun runner (local/PR loop)
bun run test:gate # The blocking gate (see Test-runner strategy)
bun run test:e2e # Playwright E2E tests
bunx fit-map validate # Validate data files
bunx fit-map validate --shacl # Validate with SHACL syntax check
### Test-runner strategy
Settled per surface. `node --test` (`bun run test:gate`) is the blocking gate:
reference-correct, since `describe`-inside-`test` is valid where bun throws
([bun#5090](https://github.com/oven-sh/bun/issues/5090)). `bun test`
(`bun run test`) is the fast, non-required loop. Never import `bun:test` — node
cannot resolve `bun:`; `scripts/check-bun-test-imports.mjs` reddens on any.
Import from `node:test` and `@forwardimpact/libmock/expect`.
## Security
Security policies apply to all contributors — human and agent.
- **Vulnerability audit** — `npm audit --audit-level=high` runs in CI and
gates publish workflows.
- **CI secret scanning** — Gitleaks runs on every push and PR — see
[check-security.yml](.github/workflows/check-security.yml).
- **GitHub Actions** — All third-party actions, including `forwardimpact/*`
siblings, are SHA-pinned on `uses:` lines. Use `Dependabot`; never
change a pin to a tag.
- **Vendored trace fixtures** — vendor byte-exact only after a security
reviewer reads the result prose in full; sensitive prose forces documented
redaction or synthesis. Widening fixture exclusions in
`.coaligned/invariants/` or any security scan requires security review.
- **Reporting** — See [SECURITY.md](SECURITY.md). Contact
`hi.security@senzilla.io`.
## Dependency Policy
- **Prefer built-ins.** Use Node built-ins over npm (`fetch` not `undici`);
consolidate overlapping packages.
- **Align versions.** Declare the same range across workspaces. Bun hoists
matched versions; don't drop a runtime dep just because it deduplicates.
- **No nested duplicates.** The same package at two major versions is
forbidden. Before a major bump, run `bun pm ls` and inspect `bun.lock` for
`invalid` markers; close the PR if dependents lack compatible ranges.
- **Audit after changes.** Run `just audit-vulnerabilities` after adding or
updating deps.
- **No `jsdom`** (its `css-tree` breaks `bun --compile`) — use `linkedom` for
parsing, `happy-dom` for browser-env tests.
### Classification
Every dependency belongs in one category. Apply in order — first match wins.
- **Always needed** — imported synchronously at load time → `dependencies`
- **Backend-specific** — selected by env var, alternatives exist →
`optionalDependencies` + dynamic `import()`
- **Feature-gated** — user opts in, core works without it →
`optionalDependencies` + dynamic `import()`
- **Build-tool** — consumers already have it → `peerDependencies`
- **Build-time only** — `bin/` scripts or codegen only → `devDependencies`
### Optional Dependency Pattern
Backend-specific and feature-gated deps use dynamic `import()` at the call site
(never at module top), wrapped in `try/catch` that throws naming the feature,
package, and install command. Never silently fall back.