This document is prescriptive. Do not introduce alternatives without an ADR
in docs/decisions/.
| Concern |
Choice |
Why |
| Language |
TypeScript 5.x |
Type safety on a JS ecosystem; agents handle it well. |
| Runtime |
Node.js 20 LTS |
Stable, modern, supported through 2026. |
| Package |
npm |
Default, no extra config required for codex/agent tools. |
| Concern |
Choice |
Why |
| HTTP |
Fastify 4.x |
Schema-validated routes, fast, plugin model. |
| Validation |
Zod |
Single source of truth for runtime + types. |
| Logger |
pino |
Bundled with Fastify; structured JSON. |
| Concern |
Choice |
Why |
| RDBMS |
PostgreSQL 15+ |
JSON columns for revisions, mature. |
| ORM |
Prisma 5.x |
Type-safe schema, migrations, agent-friendly. |
| Test DB |
postgres in Docker via testcontainers-node |
Real DB; no mock divergence. |
| Concern |
Choice |
Why |
| Framework |
oclif 4.x |
Topic/command structure, hooks, plugin model. |
| Output |
chalk + cli-table3 + boxen |
Human-friendly UI primitives. |
| Format |
--format=human (default), --format=json, --format=agent |
See docs/07-cli-spec.md. |
| Concern |
Choice |
Why |
| Frontmatter |
gray-matter |
YAML in fenced block; standard. |
| Markdown parse |
marked |
Lightweight; we don't render server-side. |
| Diff (text) |
diff (npm) |
For change previews. |
| Diff (struct) |
custom |
Walks our domain entities for impact. |
| Concern |
Choice |
Why |
| Provider |
GitHub OAuth 2.0 |
Single provider for MVP. |
| Sessions |
HTTP-only cookies |
Standard. |
| API keys |
Random 32-byte tokens, argon2id hashed |
Industry standard. |
| Hashing |
argon2 (npm) |
argon2id for passwords/API keys. |
| Concern |
Choice |
Why |
| Test runner |
Vitest |
Fast, ESM-native, TS-native. |
| Assertions |
Built-in expect |
No extra dep. |
| HTTP testing |
undici (built into node) |
Native, no extra dep. |
| E2E harness |
tests/helpers/server.ts boots Fastify on ephemeral port |
Real I/O. |
| Coverage |
c8 (Vitest built-in) |
V8 native. |
| Mutation sample |
stryker-mutator (sampled, not full run) |
Catch tautological tests. |
| Fixtures |
factory-girl-ts |
Idiomatic factories. |
| Concern |
Choice |
Why |
| Linter |
ESLint 9 (flat config) |
Standard. |
| Boundaries |
eslint-plugin-boundaries |
Enforce hexagonal layering. |
| Formatter |
Prettier 3 |
Standard. |
| Type-checker |
tsc --noEmit in CI |
Plus tsc build for output. |
| Concern |
Choice |
Why |
| Framework |
Next.js 16 (App Router) |
SSR + RSC + async request APIs + good defaults. |
| UI |
Tailwind CSS + shadcn/ui |
Fast composition. |
| Data |
Server actions + fetch |
No client state library needed for MVP. |
| Hosting |
Vercel (GitHub-linked) |
Zero-config Next.js deploys + preview URLs per branch. |
| E2E |
Playwright (chromium only) |
Honest browser-driven tests; gated behind VSPEC_GATES_SKIP_DEEP. |
The web UI is deliberately small in MVP and read-first: browse projects
and use cases, plus a fixed allowlist of writes on the management plane (project
admin) and review plane (comments). Spec-content authoring, branching, merging,
and conflict resolution happen via the CLI, not the web. See 10-web-app.md for
the scope model and write allowlist.
| Tool |
Purpose |
| SQLite |
Local dogfood and automated test default (file:.state/*.sqlite) |
| docker compose |
Optional local Postgres and production-like deploy |
| tsx |
tsx watch for local server reload |
| dotenv |
.env loading |
| husky + lint-staged |
Pre-commit hooks (lint + format staged) |
Prisma's checked-in schema uses the SQLite provider so npm start and the
restart-survival tests work on a fresh machine without a database daemon.
Production compose files override DATABASE_URL with a Postgres connection;
that deploy path is verified separately by the Goal 2 Docker gate.
| Concern |
Approach |
| API |
URL-prefixed /v1/.... Breaking changes bump. |
| CLI |
Semver. Major bumps via deprecation cycle. |
| Spec markdown |
vspec_format: 1 in frontmatter. |
- jQuery, Lodash (use native).
- Mongoose, TypeORM, Knex (we picked Prisma).
- jest (we picked Vitest).
- Yarn, pnpm (we picked npm — single tool for agent simplicity).
- Express (we picked Fastify).
- Any ORM-on-top-of-an-ORM ("repository pattern wrapping Prisma" is fine; a
generic repository library is not).
- Date libraries (use
Date + Intl APIs; dayjs only if a truly hard case arises
and an ADR documents it).
- Write an ADR in
docs/decisions/ (1-2 paragraphs is fine).
- Show that the dependency is justified relative to the alternatives above.
- Install.
- Commit with prefix
chore: add <dep> per ADR-NNN.