# Show HN: A TypeScript repo where AI agents can't break the architecture

> Source: <https://github.com/lucasgodt/open-agent-ready-typescript>
> Published: 2026-07-15 13:06:07+00:00

**A reference TypeScript codebase engineered so AI agents produce good code
in it — architecture as guardrails, not as documentation.**

Most AI-assisted codebases degrade the same way: productivity jumps, and three
months later there's duplicated logic in three files, tests that assert
nothing, and an agent that breaks the code it wrote when asked to change it.
That's not a model problem — it's a workflow problem. Prose conventions don't
survive contact with agents (or with tired humans). Rules that are *machines*
do.

This repo is a small but real invoicing API (draft → send → pay, money as integer cents, a proper state machine) built to demonstrate one thesis:

Every rule that matters must be enforced by a tool the agent cannot argue with.

-
**The dependency rule is a build failure, not a diagram.**`dependency-cruiser`

fails`verify`

, the commit hook and CI on any illegal import.`src/domain`

imports*nothing*— not even node builtins. The agent doesn't need to remember the architecture; violating it is impossible to merge. -
**Mutation testing catches tests that don't test.** Coverage says a line ran.[Stryker](https://stryker-mutator.io)says the tests would*notice if the line broke*. CI breaks below 75% mutation score; the application layer currently scores 100%. -
**Tests and specs are protected from the agent.** A Claude Code`PreToolUse`

hook blocks agent edits to`tests/**`

and`specs/**`

unless a human explicitly grants an override. "Make the tests pass" can no longer be satisfied by deleting the tests (ADR 0004). -
**Commits are gated on the full verify suite.** A hook runs`typecheck + lint + deps + test`

on every`git commit`

and feeds failures back to the agent — feedback loop, not just a wall. -
**Spec-driven development is a build gate, not a suggestion.** One markdown spec per use case with Given/When/Then acceptance criteria; tests mirror them one`it()`

per criterion — and`npm run specs`

(inside`verify`

, the commit hook and CI) fails on any use case without a spec, any orphan spec, and any criterion without its mirrored test. The golden path is`npm run new -- <use-case>`

: spec + mirrored test stub + use-case stub scaffolded with matching names, workflow printed.

at the root (the`AGENTS.md`

[open standard](https://agents.md)), under 300 lines, imperative, with exact commands — plus**nested AGENTS.md** files in`src/domain`

,`src/application`

and`src/infrastructure`

carrying each layer's local invariants.`CLAUDE.md`

is one line pointing at AGENTS.md (portable across tools).records the`docs/adr/`

*why*behind decisions, so agents stop relitigating them.- Deterministic seams everywhere: time and identity are ports (
`Clock`

,`IdGenerator`

);`new Date()`

and`randomUUID()`

exist only in the composition root. Tests never sleep, never flake.

The end of [ AGENTS.md](/lucasgodt/open-agent-ready-typescript/blob/main/AGENTS.md) contains a self-contained task
(

`cancel-invoice`

) designed to verify whether *any*coding agent can work in this repo correctly: spec → tests → domain → use case → HTTP,

`verify`

green,
nothing else touched. Point your agent at it and grade the diff. That's the
whole point of this repository — it's not just an example, it's a test bench.This repo doubles as a template, and ships a [Claude Code skill](/lucasgodt/open-agent-ready-typescript/blob/main/.claude/skills/scaffold-agent-ready/SKILL.md) that does the transplant for
you: it keeps the guardrail system (configs, hooks, CI, layer structure),
removes the invoicing example, and grows your new domain spec-first inside it.
It covers three project shapes, each with its own playbook in
[ references/](/lucasgodt/open-agent-ready-typescript/blob/main/.claude/skills/scaffold-agent-ready/references):

**Backend API/service**— this repo's own structure ([backend.md](/lucasgodt/open-agent-ready-typescript/blob/main/.claude/skills/scaffold-agent-ready/references/backend.md))**Frontend app**— React/Vite with the UI as an adapter around a pure, React-free domain; mutation testing targets domain/application, never components ([frontend.md](/lucasgodt/open-agent-ready-typescript/blob/main/.claude/skills/scaffold-agent-ready/references/frontend.md))**Monorepo**— pnpm workspaces where layers become physical packages;`packages/domain`

with an empty`dependencies`

object is the loudest guardrail in the repo ([monorepo.md](/lucasgodt/open-agent-ready-typescript/blob/main/.claude/skills/scaffold-agent-ready/references/monorepo.md))

One-time install (user-wide):

```
BASE=https://raw.githubusercontent.com/lucasgodt/open-agent-ready-typescript/main/.claude/skills/scaffold-agent-ready
mkdir -p ~/.claude/skills/scaffold-agent-ready/references
curl -sL $BASE/SKILL.md -o ~/.claude/skills/scaffold-agent-ready/SKILL.md
for f in backend frontend monorepo; do
  curl -sL $BASE/references/$f.md -o ~/.claude/skills/scaffold-agent-ready/references/$f.md
done
```

Then from any empty directory, in Claude Code:

Scaffold a new agent-ready [backend | frontend | monorepo] project for [your domain]

Prefer doing it by hand? The recipe is the same one the skill follows:
clone, keep every config/hook/workflow file plus the layer skeleton, delete
`src`

domain contents + specs + tests of the example, and rebuild your domain
following the spec-first workflow in AGENTS.md. GitHub's "Use this template"
button also works for the mechanical copy.

```
npm install
npm run verify        # typecheck + lint + dependency rules + tests
npm run dev           # API on :3000
npm run mutation      # Stryker (slower)
curl -s -X POST :3000/invoices -H 'content-type: application/json' \
  -d '{"customerName":"ACME Ltda","currency":"BRL"}'
```

- The test-protection hook is Claude Code–specific; other agents need an equivalent gate. Mutation testing in CI is the tool-agnostic backstop.
- JSON-file persistence is deliberately boring and not concurrent-safe across processes (ADR 0003). Swapping in a real database is one adapter + one line in the composition root — which is precisely the demonstration.
- Mutation survivors that remain are error-message string mutants; the
contract is the error
`code`

, asserted everywhere, not the prose.

MIT
