Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# agent-template

Shared engineering principles for GenerateNU projects, in the format coding
agents actually read.

Every project writes down the same things — layer boundaries, error handling,
pagination rules, test expectations — and every project writes them slightly
differently, in a `CONTRIBUTING.md` that agents never open. This repo keeps one
copy, versioned, so a fix lands everywhere instead of in one repo.

## What's here

| Path | What it is |
| --- | --- |
| `core/core.md` | Stack-agnostic principles. Always loaded. ~120 lines. |
| `template/project-header.md` | The per-project sections you fill in: commands, architecture, stack, local conventions. |

## Install

From the root of your project:

```bash
TPL=/path/to/agent-template
cat $TPL/template/project-header.md $TPL/core/core.md > AGENTS.md
echo '@AGENTS.md' > CLAUDE.md
```

Then **fill in the TODO sections at the top of `AGENTS.md`** — commands,
architecture, stack, project conventions. The shared principles below the
divider are already done.

### Why two files

`AGENTS.md` is the cross-tool convention: Cursor and Codex read it directly.
Claude Code does *not* read `AGENTS.md` — it reads `CLAUDE.md` — so the one-line
`CLAUDE.md` imports it. One source of truth, both tools work.

If you need Claude-specific instructions, add them below the import:

```markdown
@AGENTS.md

## Contributing

Open a PR. One rule per PR, and the description must name **the agent failure it
prevents** — an actual thing that went wrong in an actual repo. If nobody can
name the failure, the rule doesn't go in. That's the only thing keeping this
file from growing to 600 lines of generic advice nobody reads.

Fixes go here, not in your project's copy. Editing the shared section in one
repo means the next project inherits the bug.

## Updating

The install is a copy, so a project doesn't pick up changes automatically. Each
generated `AGENTS.md` carries its version in an HTML comment
(`<!-- agent-template · core v0.1 -->`). To see what a project is missing:

```bash
git diff core-v0.1..main -- core/core.md
```

Apply the parts you want. Tag a new version here whenever `core.md` changes in
a way projects should know about.

## Sources

Mined from the `CLAUDE.md`, `docs/`, and `CONTRIBUTING.md` of
[toggo](https://github.com/GenerateNU/toggo),
[dearly](https://github.com/GenerateNU/dearly), and
[selfserve](https://github.com/GenerateNU/selfserve). Every rule in `core.md`
is something at least two of the three already agreed on.
119 changes: 119 additions & 0 deletions core/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Engineering Principles

<!-- agent-template · core v0.1 -->

Stack-agnostic rules that apply to every project. Stack-specific rules belong in
sibling module files; project-specific commands, paths, and architecture belong
in the project's own `AGENTS.md`. Don't add either here.

## Before you call it done

- Run the project's format, lint, typecheck, and test commands. A change isn't
done until they pass — say so plainly if they don't.
- Re-read your own diff. Delete anything you added and stopped using.
- Changed an API route? Regenerate the spec and client in the same change.
- Changed behavior the docs describe? Update the docs in the same change.

## Architecture boundaries

Layer names differ by project (handler/controller, service, repository/
transaction), the boundaries don't:

| Layer | Owns | Never contains |
| --- | --- | --- |
| Handler / Controller | HTTP in/out, parsing, validation, status codes | business logic |
| Service | business rules | HTTP types, SQL |
| Repository / Transaction | data access | business logic |

- Data flows down, errors flow up: repository → service → handler.
- Cross a boundary through an interface, not a concrete type.
- Pass dependencies explicitly through constructors. No globals, no singletons
reached via import side effects, no hidden state.

## Errors

- One error taxonomy per project. Don't invent an ad-hoc error shape per endpoint.
- Convert infrastructure errors to domain errors at the service boundary.
- Never return a raw database error, driver error, or stack trace to a client.
Log the full error server-side; return a safe message and the correct status.
- Translate known constraint violations into something a user can act on:
unique violation → "Email already exists", FK violation → "Referenced
resource not found".

## Functions and naming

- One function, one job. Prefer under ~40 lines. Extract a helper before nesting
a third conditional.
- Name by what it does, not how: `FindUserByID`, `CalculateInvoiceTotal`.
Reject `Handle`, `Process`, `DoThing`, `data`, `temp`.
- No abstraction for a single caller. No interface until there's a second
implementation or a test that needs to mock it.
- The surrounding code's idiom, comment density, and naming beat any general
preference stated here.

## No hardcoded values

Anything someone might reasonably want to change without a code review belongs
in config, constants, design tokens, or environment variables — page sizes,
timeouts, retry counts, colors, spacing, URLs, feature flags, limits.

## Dead code

- No unused imports, variables, or parameters.
- No commented-out code. Git remembers it.
- No leftover debug output — `println`, `console.log`, `.only` in tests.
- Production paths use structured logging, not print statements.

## Data access

- No unbounded lists. Every list endpoint paginates; prefer cursor/keyset over
offset, which degrades as the table grows.
- Cap page size server-side. A client asking for 10,000 rows gets the cap.
- Return only the fields the caller needs.
- No N+1. Fetch related data in one round trip; batch instead of looping.

## Secrets and input

- Secrets come from the secret manager or the environment. Never commit one,
never log one, never put one in an error message.
- Validate and parse every request payload at the edge, before it reaches
business logic. Reject unknown fields on write paths.
- Parameterize every query. Never build SQL by string concatenation.

## Tests

Cover, in rough order of what actually catches bugs:

- **Happy path** — the expected flow.
- **Error paths** — invalid input, missing resource, unauthorized.
- **Edge cases** — empty, boundary values, duplicates, malformed input,
concurrent requests.
- **Lifecycle** — create → read → update → delete → verify gone.
- **Idempotency** — calling it twice is safe.

Rules:

- Mock external services and the clock. Tests must not depend on the network,
wall-clock time, or execution order.
- A test that asserts nothing is not a test.
- When you fix a bug, add the test that would have caught it.

## Version control

- Conventional Commits: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`.
- Small, focused PRs. Split a large feature into reviewable pieces.
- The message explains *why*; the diff already shows *what*.
- Never hand-edit generated files — mocks, API clients, migration output.
Change the source and regenerate.

## Refactoring

Preserve behavior unless told otherwise. Reduce complexity, improve names,
remove duplication, enforce the boundaries above. Don't expand into unrelated
files along the way.

## When you're unsure

- An existing pattern in this codebase beats the general advice here.
- If two existing patterns conflict, ask rather than silently picking one.
- State any assumption you had to make when you summarize the change.
63 changes: 63 additions & 0 deletions template/project-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# <Project Name>

<!-- agent-template · core v0.1 -->
<!-- Fill in every section below. Delete the TODO comments as you go.
Do not delete a section because you don't have the answer yet —
an empty Commands block is a bug, not a style choice. -->

## Commands

<!-- TODO: the exact commands, copy-pasteable, with the task runner you use
(just / task / make / npm). This is the single highest-value section in
this file. If an agent has to guess how to run your tests, it will guess
wrong. Include anything with a non-obvious flag or a known failure mode. -->

| Task | Command |
| --- | --- |
| Install deps | `` |
| Start backend | `` |
| Start frontend | `` |
| Run tests | `` |
| Lint | `` |
| Format | `` |
| Typecheck | `` |
| Create migration | `` |
| Apply migrations | `` |
| Regenerate API spec/client | `` |
| Regenerate mocks | `` |

<!-- TODO: note any command that hangs, needs a running container, or must not
be run against production. Example:
- `just test-be` needs the DB up first: `just up-db`
- Never run `just migrate-*-prod` without review. -->

## Architecture

<!-- TODO: ~10 lines. Only what an agent can't infer by reading the tree.
Where each layer lives, what the non-obvious boundaries are, and any
place the code deviates from the principles below (and why). -->

```
<!-- TODO: directory tree, backend and frontend, one line of purpose each -->
```

## Stack

<!-- TODO: one line per major choice, enough that an agent doesn't reach for
the wrong library. Language + framework + ORM + migration tool + auth +
server state + styling + secret manager. -->

## Project conventions

<!-- TODO: rules specific to THIS repo that override or extend the principles
below. Design token names, error constructors, test helpers, generated
directories that must never be hand-edited. Delete this section if there
genuinely aren't any yet — but there usually are. -->

---

<!-- Everything below is shared across all GenerateNU projects.
Source: github.com/GenerateNU/agent-template
Don't edit it here; open a PR against the template so every project
gets the fix. Project-specific rules go in the sections above. -->