ADR 0003: Core library with thin adapters
Status
Accepted.
Context
The same workflow must run identically from a terminal, an MCP host, and pi.dev, without triplicating GitHub logic, and unit tests need to exercise real behavior without hitting the GitHub API.
Decision
All domain logic, label composition, claim-marker parsing, skill and language loading, and the five review operations, lives in core, behind one GitHubGateway interface (core/github.ts). OctokitGateway implements it for production; FakeGitHubGateway (test/fakes/fake-github.ts) implements it for tests. core/index.ts exports the model types, the gateway, and the operations (createReview, listReviews, claimReview, completeReview, enrichReview, bootstrap) as the one public surface. Each adapter, the CLI (cli/index.ts), the MCP server (mcp/server.ts), and the pi.dev extension (pi/src/extension.ts, see ADR 0006), does nothing but translate its host's calling convention into a call on one of those operations and wrap the result; the MCP and pi adapters both wrap output in the identical { content: [{ type: "text", text }] } shape. Skills and language checklists are loaded inside core and served in the composed review task returned by claim, so no adapter or host keeps its own copy of review guidance, and the state core reads and writes is entirely the GitHub state described in ADR 0001.
Consequences
One code path is exercised by all three surfaces, so a fix or a new operation lands everywhere at once. Unit tests run against the fake gateway with no network and no fixtures. Adding a fourth host means writing a thin adapter, not reimplementing claim semantics; the cost is that no adapter can express host-specific behavior without either duplicating logic locally or extending core itself.