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
26 changes: 26 additions & 0 deletions .claude/commands/check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
description: Review the current changes against this project's conventions checklist
argument-hint: "[optional: file or path to focus on]"
---

Review the changes on the current branch against the Course API conventions. Focus on
`$ARGUMENTS` if given, otherwise review everything that differs from `main`.

First run `git diff main...HEAD --stat` and `git diff main...HEAD` to see what changed.

Then check each item and report it as ✅ pass / ❌ fail / ⚠️ not applicable, with the
`file:line` for any problem:

1. **One route file per resource** — each new resource is its own `routes/<name>.js`,
mounted in `server.js` under its base path.
2. **Data access through `db/store.js`** — no route holds state directly; all reads/writes
go through store helpers.
3. **Validation & status codes** — input validated in the route; `400` on bad input,
`404` on a missing record, `201` on create.
4. **Error shape** — every error response is JSON `{ "error": "message" }`.
5. **Tests exist and pass** — new behaviour has tests in `tests/` using `node:test` +
`supertest`, with `store.reset()` in `beforeEach`. Run `npm test` and report the result.
6. **Lint clean** — run `npm run lint` and report any findings.
7. **No leftovers** — no commented-out code blocks, no secrets, no stray debug logging.

End with a short verdict: ready to commit, or the specific fixes needed first.
29 changes: 29 additions & 0 deletions .claude/hooks/eslint-fix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# PostToolUse hook: auto-fix lint on edited JS files.
# Reads the hook payload from stdin, extracts the edited file path, and runs
# `eslint --fix` on it when it is a .js file. Uses node (always present in this
# project) to parse the JSON so the hook needs no extra dependency like jq.

input=$(cat)
file=$(printf '%s' "$input" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);process.stdout.write((j.tool_input&&j.tool_input.file_path)||"")}catch{}})')

# Reject empty paths and names that begin with a dash (argv flag smuggling),
# then keep the file inside the project before handing it to eslint.
case "$file" in
''|-*) exit 0 ;;
esac

root="${CLAUDE_PROJECT_DIR:-$PWD}"
abs=$(cd "$(dirname "$file")" 2>/dev/null && printf '%s/%s' "$PWD" "$(basename "$file")")
case "$abs" in
"$root"/*) ;;
*) exit 0 ;;
esac

case "$abs" in
*.js)
npx --no-install eslint --fix -- "$abs" >/dev/null 2>&1 || true
;;
esac

exit 0
26 changes: 26 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"permissions": {
"allow": [
"mcp__docs__read_file",
"mcp__docs__read_text_file",
"mcp__docs__list_directory",
"mcp__docs__directory_tree",
"mcp__docs__search_files",
"mcp__docs__get_file_info",
"mcp__docs__list_allowed_directories"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/eslint-fix.sh\""
}
]
}
]
}
}
46 changes: 46 additions & 0 deletions .claude/skills/scaffold-route/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: scaffold-route
description: Use when adding a new resource or endpoint to this Express API — creating a new route file, a new `/something` path, or extending an existing resource. Encodes how routes, data access, validation, error responses, and tests are written in this repo.
---

# Scaffold a route the way this project does it

When asked to add a new resource or endpoint, follow these conventions exactly. They
mirror what already exists in `routes/users.js` and `routes/health.js`.

## 1. One route file per resource

- Create `routes/<resource>.js` exporting an Express router (`express.Router()`).
- Mount it in `server.js` under its base path: `app.use('/<resource>', <resource>Router);`.
- Do not add a second resource's handlers to an existing route file.

## 2. All data access goes through `db/store.js`

- Routes never hold state directly. Read and write only through helpers in `db/store.js`
(e.g. `store.listX()`, `store.getX(id)`, `store.createX(...)`, `store.updateX(...)`).
- If a needed helper doesn't exist, add it to `db/store.js` and keep the in-memory shape
consistent with `users` (numeric auto-increment `id`, `reset()` re-seeds for tests).

## 3. Validate input; use the right status codes

- Validate in the route, before touching the store.
- Return `400` on bad/missing input, `404` when a record is missing.
- Return `201` on successful creation, `200` otherwise.

## 4. Error responses are JSON `{ "error": "message" }`

Every error path responds with that exact shape, e.g.
`return res.status(404).json({ error: 'X not found' });`.

## 5. Always write tests

- Add `tests/<resource>.test.js` using Node's built-in runner (`node:test`) + `supertest`,
matching the style of `tests/users.test.js`.
- Call `store.reset()` in `test.beforeEach` so each test starts from seed data.
- Cover the happy path plus the `400` and `404` branches you added.
- Run `npm test` and confirm it passes before considering the route done.

## 6. Comment style

Short English comments above each handler describing the method, path, and behaviour —
e.g. `// GET /widgets/:id — fetch one widget, or 404 if it doesn't exist.`
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"docs": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./docs"]
}
}
}
52 changes: 52 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# NOTES — wiring Claude into this repo

## Server (MCP)

I connected a **filesystem server** (`@modelcontextprotocol/server-filesystem`) scoped to
the `docs/` folder, at project scope in `.mcp.json`. It's credential-free and gives Claude
a first-class way to read the API reference in `docs/` without me pasting it in.

The permission rule in `.claude/settings.json` allows **only the read tools** I actually
use — `read_file`, `read_text_file`, `list_directory`, `directory_tree`, `search_files`,
`get_file_info`, `list_allowed_directories` — and deliberately leaves out the server's
write tools (`write_file`, `edit_file`, `move_file`, `create_directory`). So the server can
look at the docs but never change them. I verified it end-to-end: it reports `docs/` as its
only allowed directory, lists `api.md`, and reads its contents.

## Skill

The skill `scaffold-route` captures **how a new route/resource is built in this repo**: one
route file per resource mounted in `server.js`, all data access through `db/store.js`,
`400`/`404` validation with the `{ "error": "message" }` shape, and a matching
`node:test` + `supertest` test file with `store.reset()` in `beforeEach`. The description is
worded around the concrete trigger — "adding a new resource or endpoint to this Express API,
a new route file, a new `/something` path" — so it fires when someone asks for a new route
but not on unrelated edits.

## Command

`/check` reviews the current branch's changes against the project's conventions checklist
(route-per-resource, store-only data access, status codes, error shape, tests pass, lint
clean, no leftovers). It's worth a shortcut because it's the exact pre-commit pass I'd run
every time, and it bundles `git diff`, `npm test`, and `npm run lint` into one consistent
report instead of me re-typing the checklist.

## Hook

A **PostToolUse** hook (it *reacts*, after the fact) matching `Write|Edit`. It runs
`.claude/hooks/eslint-fix.sh`, which parses the edited file path from the hook payload and
runs `eslint --fix` on it when it's a `.js` file. So every edit Claude makes to JS stays
lint-clean automatically. Verified by feeding it a file with `if (!!flag)` — the hook
rewrote it to `if (flag)`.

## Headless run

I ran a read-only task headless:

```
claude -p "List every HTTP route this API exposes ..." --allowedTools "Read" "Glob"
```

I locked it down to just `Read` and `Glob` — enough to read `server.js` and `routes/`, but
no `Bash`, `Write`, or `Edit`. With nobody watching, the worst it can do is read source
files; it can't run commands or change anything.
Loading