Architecture
High-level component diagram
┌──────────────────────────────────┐
│ fairway CLI │
│ (cmd/fairway) │
└──────────────┬───────────────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌───────────┐ ┌──────────────┐
│ config │ │ state │ │ dashboard │
│ (TOML) │ │ (machine) │ │ (HTTP + SSE) │
└────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
│ ┌─────────▼─────────┐ │
└─────────────►│ store │◄─────────────┘
│ (SQLite + Go │
│ migrations) │
└─────────┬─────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌────────┐
│ git │ │ session │ │ report │
│worktree│ │ tracker │ │ render │
└────────┘ └─────────┘ └────────┘
Package layout
cmd/fairway/ — CLI entrypoint. Thin: parses args via cobra, dispatches to internal/*. No business logic.
internal/config/ — TOML loader and validator. Knows nothing about the DB; consumed by other packages.
internal/store/ — SQLite schema, migrations (embedded via //go:embed), low-level queries. Exposes typed methods like ClaimTask, RecordEvidence, Snapshot. Holds the only *sql.DB instance. Threads project_id through every read and write — callers never pass it.
internal/state/ — state machine. Pure logic. Takes a config and a transition request; returns valid/invalid plus the row to write into history. No DB access.
internal/session/ — session lifecycle. PID detection, tmux pane detection (via tmux display -p), heartbeats.
internal/git/ — git shellouts for worktree setup, branch creation, status, last-commit lookup. Uses os/exec; no libgit2.
internal/report/ — status / health / timing / dispatch report generators. Reads from store; renders text or JSON.
internal/coordinator/ — composed preflight/status/tick logic. Calls config,
store, git, session, and report packages; does not mutate tasks automatically.
internal/packet/ — context, bugfix, and watcher packet rendering. Produces
Markdown or JSON artifacts from task/config inputs.
internal/dashboard/ — HTTP server, HTML templates, SSE. Embeds assets/ (HTMX, CSS) via //go:embed. Read-only views over the store. Supports both single-project and multi-project (ATTACH DATABASE) data sources via a swappable view layer.
internal/registry/ — reads / writes ~/.fairway/registry.toml. Used by fairway register, fairway projects, and the multi-project dashboard. The only fairway code that touches paths outside the current project.
Data flow: fairway claim T-042
- CLI parses args, resolves role (worktree path → config), loads config.
- Opens the store via
internal/store. - Calls
state.Validate(currentStatus, target, config.States). - On valid: store opens
BEGIN IMMEDIATE, performs a guardedUPDATE task_state ... WHERE claimant IS NULL, insertstask_state_history, then commits. A losing concurrent claim returnsErrAlreadyClaimed. - CLI prints confirmation.
- Dashboard SSE pollers (1Hz) pick up the new history row within ~1s and push it to connected clients.
Data flow: fairway merge-ready T-042
- CLI resolves the task, configured base branch, and review routes.
internal/gitcomputes changed files for the task ref against the base ref.internal/storechecks configured gates: evidence rows, handoff rows, and approved review rows for all matched review routes.internal/gitverifies the ref is based on the configured base branch and the working tree is clean.- CLI prints a merge-ready summary or the missing gate(s).
Data flow: fairway dashboard
- CLI loads config + store.
- Starts HTTP server on
[dashboard] listen. - Routes:
GET /— lanes strip + backlog (server-rendered).GET /tasks/:id— task detail.GET /partials/backlog— HTMX partial.GET /events— SSE stream of history merges.
- Each request opens a short-lived read transaction.
Concurrency model
- One
*sql.DBper process. - WAL mode enabled at open (
PRAGMA journal_mode=WAL). - All writes wrapped in transactions.
- Claim uses
BEGIN IMMEDIATEplus a guarded update so two claimers cannot both win on SQLite. - The dashboard is fully read-only against the store; it never holds locks across requests.
Build & distribution
go build ./cmd/fairway→ single static binary.- Pure Go, no CGO (via
modernc.org/sqlite) — cross-compilation is trivial. - Targets:
darwin/amd64,darwin/arm64,linux/amd64,linux/arm64. goreleaserproduces archives + checksums per tag.- Homebrew tap once v0.1 stabilizes.
What lives where — quick reference
| Concern | Package |
|---|---|
| TOML parsing | internal/config |
| State transition validation | internal/state |
| Task DB writes | internal/store |
| Migration runner | internal/store/migrations |
| tmux / PID detection | internal/session |
| Worktree shellouts | internal/git |
| Coordinator preflight/status/tick | internal/coordinator |
| Context, bugfix, and watcher packet rendering | internal/packet |
| HTML templates, SSE | internal/dashboard |
| Text / JSON report rendering | internal/report |
Project registry (~/.fairway/registry.toml) | internal/registry |
| CLI command wiring | cmd/fairway |