Skip to content
Merged
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
File renamed without changes.
50 changes: 50 additions & 0 deletions .claude/skills/improve-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: improve-skill
description: Use when something went wrong in a session that a skill or rule should have prevented — a correction from the user, a broken build, a convention violation, a repeated mistake. Captures the failure as a permanent fix to the responsible skill, doc, or rule.
---

# Improve Skill

## Overview

The hardening loop. Context files (skills, docs, `AGENTS.md`) are treated like code: when
they fail, they get a fix, not a workaround. Every correction the user has to make twice is
a bug in the harness, and this skill patches the harness. Over time skills converge on
bulletproof, and the always-loaded context stays small because knowledge accretes in the
right on-demand file instead of in an ever-growing CLAUDE.md.

## When invoked

Mid-session, right after the failure — while the exact wording of what went wrong is still
in context. Typical triggers: the user corrects the same thing again, a hook or CI rejects
work, a skill produced output that violated a repo convention, a doc turned out to be wrong
or stale.

## Workflow

1. **Name the failure precisely.** One sentence: what was expected, what happened instead.
If it can't be stated in one sentence, it isn't understood yet.
2. **Find the owner.** Which file *should* have prevented this?
- A task went wrong → the skill for that task.
- A convention was violated → the doc that states it (or should state it).
- It must never happen regardless of task → `AGENTS.md` (always-loaded, so the bar is
high) or better, a deterministic guardrail (git hook, lint rule, CI check).
- No owner exists → propose the smallest new home; a new skill needs a recurring task,
not a one-off.
3. **Write the smallest rule that would have prevented it.** One or two lines in the
owner's body. Prefer tightening an existing step over adding a new section. Never grow a
skill's `description` for this — descriptions are always in context and stay one line.
4. **Log it.** Append to the owner skill's `## Lessons` section (create it on first
lesson): `- YYYY-MM-DD — <failure> → <rule added>`. This keeps the audit trail of *why*
each rule exists.
5. **Confirm with the user** before writing — show the exact edit. If the same lesson is
being added a second time, the rule is wrong or unclear; rewrite it instead of
restating it.

## Do not

- Patch the symptom in the session but skip the harness fix — that's how the same failure
returns next week.
- Add generic advice a model already knows; rules must be project-specific and falsifiable.
- Grow `AGENTS.md` when an on-demand skill or doc can own the rule.
- Turn a one-off mishap into a rule; twice is a pattern, once is noise (log nothing).
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ description: Use when adding a new self-contained module under src/ — "scaffol

A module here is a folder under `src/` with a single clear purpose, a typed surface, and a
colocated test. This skill scaffolds that trio consistently so a new module matches the two
existing worked examples ([`src/feature-flags/`](../../src/feature-flags),
[`src/http/`](../../src/http)) instead of inventing its own shape.
existing worked examples ([`src/feature-flags/`](../../../src/feature-flags),
[`src/http/`](../../../src/http)) instead of inventing its own shape. Starting points for
the three files live in [`templates/`](templates) — adapt them, don't copy blindly.

## Input

Expand All @@ -35,3 +36,18 @@ existing worked examples ([`src/feature-flags/`](../../src/feature-flags),
- Create a module without a test — the test is the guardrail, not an afterthought.
- Add a dependency to scaffold a module; the toolchain stays minimal.
- Reach for a class when a function and plain data will do (KISS).

## Lessons

Failures this skill has absorbed (see the `improve-skill` skill for how entries get here;
full stories in [docs/CASE_STUDY.md](../../../docs/CASE_STUDY.md)):

- 2026-06-19 — Fake-timer test left a rejection unhandled: `await advanceTimersByTimeAsync()`
ran before a handler was attached to the promise under test → attach the handler first,
then race both with `Promise.all([...])`.
- 2026-06-19 — `toMatchObject()` silently skipped `Error.cause` (non-enumerable), so the
test passed without checking what it claimed → assert `cause` and other non-enumerable
properties directly.
- 2026-06-19 — Node-context tooling scripts (`.husky/`, `.claude/workflows/`) tripped
`no-undef` under the app's ESLint config → harness scripts stay scoped out of the app
lint config; don't "fix" them by weakening `src/` rules.
18 changes: 18 additions & 0 deletions .claude/skills/scaffold-module/templates/module.test.ts.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it, vi } from "vitest";

import { <name> } from "./<name>";

// Sync/pure module: assert inputs -> outputs directly.
// Async/I-O module: inject a fake and use vi.useFakeTimers().
//
// Fake-timer + rejection pitfalls (see Lessons in SKILL.md):
// - Attach the rejection handler BEFORE advancing time:
// await Promise.all([expect(promise).rejects.toThrow(...), vi.advanceTimersByTimeAsync(ms)]);
// - Error.cause is non-enumerable: toMatchObject() silently skips it.
// Assert it directly (try/catch or `.cause` property check).

describe("<name>", () => {
it("does what the spec says", () => {
expect(<name>({})).toEqual(/* ... */);
});
});
8 changes: 8 additions & 0 deletions .claude/skills/scaffold-module/templates/module.ts.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { <Name>Options, <Name>Result } from "./types";

// Pure where possible; inject I/O boundaries (fetch, clock, storage) as
// parameters so the module is testable without touching globals.

export function <name>(options: <Name>Options): <Name>Result {
// ...
}
17 changes: 17 additions & 0 deletions .claude/skills/scaffold-module/templates/types.ts.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Public types for <name>: options, results, typed errors.
// Make illegal states unrepresentable — prefer unions over booleans.

export type <Name>Options = {
// ...
};

export type <Name>Result = {
// ...
};

export class <Name>Error extends Error {
constructor(message: string, options?: { cause?: unknown }) {
super(message, options);
this.name = "<Name>Error";
}
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ For agentic work, use the Superpowers sub-skills (`superpowers:executing-plans`,
[docs/GIT_HOOKS.md](docs/GIT_HOOKS.md).
- Run lint + typecheck before staging.
- Never commit automatically — only on explicit user authorization. See
[.claude/skills/commit.md](.claude/skills/commit.md).
[.claude/skills/commit/SKILL.md](.claude/skills/commit/SKILL.md).
33 changes: 6 additions & 27 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
# CLAUDE.md — Project Context for Claude Code

## Source of truth

All AI agent rules, conventions and standards live in [AGENTS.md](AGENTS.md).
**Read it first** — it covers the tech stack, principles (DRY/KISS/SOLID with
judgment), the spec-driven workflow, and commit/PR rules. This file intentionally
stays thin to avoid duplicating that source of truth.

## Auto-loaded context

A `SessionStart` hook ([.claude/hooks/session-start.sh](.claude/hooks/session-start.sh))
primes branch and workflow context at the start of every session.

## Commands, skills, subagents & workflows

- **Commands** ([.claude/commands/](.claude/commands)): `new-spec` — scaffold a spec + plan pair.
- **Skills** ([.claude/skills/](.claude/skills)): `commit`, `pr-description`, `safe-rollout`,
`scaffold-module` (scaffold a new `src/` module).
- **Subagents** ([.claude/agents/](.claude/agents)): `write-test`, `review-standards` — dispatch
with the `Agent` tool; run independent work in parallel.
- **Workflows** ([.claude/workflows/](.claude/workflows)): `parallel-review` (multi-agent review
+ skeptic), `generate-tests` (parallel test backfill).

## Workflow

Brainstorm → spec → implementation plan → implement, on the
[Superpowers](https://github.com/obra/superpowers) workflow. Specs and plans live
in [specs/](specs). Layout: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
All rules, conventions and standards live in [AGENTS.md](AGENTS.md) — read it first; it
indexes the docs to load on demand. This file stays minimal on purpose: skills, subagents
and workflows under [.claude/](.claude) announce themselves through their own descriptions,
and a `SessionStart` hook ([.claude/hooks/session-start.sh](.claude/hooks/session-start.sh))
primes branch + workflow context. When a session goes wrong in a way a skill should have
prevented, run the `improve-skill` skill — context files are code and failures get fixes.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The biggest lever for good AI output is the context you give it, not the prompt.
- **[`CLAUDE.md`](CLAUDE.md)** — the Claude Code entry point. Deliberately thin: it redirects to `AGENTS.md` so the rules live in one place instead of being duplicated per tool. A `SessionStart` hook in [`.claude/`](.claude) primes branch and workflow context automatically.
- **[`docs/`](docs)** — the standards `AGENTS.md` points to (architecture, git hooks, development setup, React guidelines).
- **[`.claude/commands/`](.claude/commands)** — slash commands for repeatable ops; `new-spec` scaffolds a spec + implementation-plan pair from the house templates.
- **[`.claude/skills/`](.claude/skills)** — reusable, plain-English skills for recurring tasks (commit, PR description, safe rollout, scaffold a module) with the safety rules baked in.
- **[`.claude/skills/`](.claude/skills)** — reusable, plain-English skills for recurring tasks (commit, PR description, safe rollout, scaffold a module) with the safety rules baked in. Each skill is a folder (`<name>/SKILL.md` plus colocated resources, e.g. `scaffold-module/templates/`), and skills carry a `## Lessons` changelog of the failures they've absorbed — see below.
- **[`.claude/agents/`](.claude/agents)** — focused subagents (a test writer, a standards reviewer) that run independently and in parallel.
- **[`.claude/workflows/`](.claude/workflows)** — `parallel-review`: a runnable,
multi-agent orchestration script — three reviewers (correctness, security,
Expand All @@ -46,6 +46,27 @@ The biggest lever for good AI output is the context you give it, not the prompt.
message or branch name doesn't conform, `post-checkout` warns on a bad branch
name, `pre-push` runs the test suite.

### Small context, self-hardening skills

Two rules govern all of the above:

**Keep the always-loaded context minimal.** `CLAUDE.md` is a handful of lines and
`AGENTS.md` stays under a page, because every word in them is paid for in every single
session. Models already know TypeScript and React; the context documents only what they
can't know — this project's conventions, decisions, and workflow. Everything else loads on
demand: docs through the index in `AGENTS.md`, skills through their own trigger
descriptions (a skill costs one description line until it's actually needed).

**Treat context files like code: failures get fixes.** When a session goes wrong in a way
a skill or doc should have prevented, the
[`improve-skill`](.claude/skills/improve-skill/SKILL.md) skill patches the responsible
file with the smallest rule that would have prevented the failure, and logs it in that
skill's `## Lessons` section — date, failure, rule. Skills converge on bulletproof instead
of the same correction being repeated across sessions, and the audit trail shows *why*
every rule exists (see the Lessons in
[`scaffold-module`](.claude/skills/scaffold-module/SKILL.md), grown out of the bugs in the
[case study](docs/CASE_STUDY.md)).

## Running the working example

Two worked examples live in [`src/`](src), each taking the same spec →
Expand Down Expand Up @@ -93,7 +114,8 @@ measured by what users feel, backed by tests, not by how clever the code looks.
| [`docs/`](docs) | Standards referenced by `AGENTS.md` |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | How the repo is laid out — worked examples, docs, and agent tooling |
| [`.claude/commands/`](.claude/commands) | Slash commands — `new-spec` scaffolds a spec + plan pair |
| [`.claude/skills/`](.claude/skills) | Reusable task skills with safety rails (incl. `scaffold-module`) |
| [`.claude/skills/`](.claude/skills) | Reusable task skills with safety rails, each with a `## Lessons` changelog (incl. `scaffold-module` + templates) |
| [`.claude/skills/improve-skill/`](.claude/skills/improve-skill) | The hardening loop — session failures become permanent skill fixes |
| [`.claude/agents/`](.claude/agents) | Independent, parallelizable subagents (`write-test`, `review-standards`) |
| [`.claude/`](.claude) | Permissions + session-start hook |
| [`.husky/`](.husky) | Real git hooks matching `docs/GIT_HOOKS.md` — not just documentation |
Expand Down
9 changes: 7 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ of problem, so the workflow is shown on more than one shape of code:
| [`src/http/`](../src/http) | async, real I/O boundary | a `fetchWithRetry` wrapper tested with an injected `fetch` + fake timers |

A module is a folder with `types.ts`, the implementation, and a colocated `*.test.ts`. New
modules follow the same shape — see the [`scaffold-module`](../.claude/skills/scaffold-module.md) skill.
modules follow the same shape — see the [`scaffold-module`](../.claude/skills/scaffold-module/SKILL.md) skill.

## Context, in version control

Expand All @@ -30,7 +30,7 @@ The rules an agent needs are files, not tribal knowledge:
| Path | Kind | Runs | Example |
|------|------|------|---------|
| [`commands/`](../.claude/commands) | slash command | on demand, in your session | `new-spec` scaffolds a spec + plan pair |
| [`skills/`](../.claude/skills) | reusable skill | when its trigger matches | `commit`, `pr-description`, `safe-rollout`, `scaffold-module` |
| [`skills/`](../.claude/skills) | reusable skill | when its trigger matches | `commit`, `pr-description`, `safe-rollout`, `scaffold-module`, `improve-skill` |
| [`agents/`](../.claude/agents) | subagent | dispatched via the `Agent` tool, in parallel | `write-test`, `review-standards` |
| [`workflows/`](../.claude/workflows) | orchestration script | as a multi-agent run | `parallel-review`, `generate-tests` |
| `settings.json` | config | always | scoped permission allowlist + session-start hook |
Expand All @@ -41,6 +41,11 @@ The rules an agent needs are files, not tribal knowledge:
its own tool permissions). A *workflow* is a script that orchestrates several subagents across
phases — `parallel-review` and `generate-tests` are the two worked examples.

Each skill is a folder — `SKILL.md` plus colocated resources (e.g.
`scaffold-module/templates/`). Skills carry a `## Lessons` changelog; the `improve-skill`
skill is the loop that appends to it: a session failure becomes the smallest permanent rule
that would have prevented it.

## Guardrails

`.husky/` git hooks and `.github/workflows/ci.yml` enforce the same four checks — `pnpm lint`,
Expand Down
26 changes: 9 additions & 17 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
# Development Setup & Tooling

## Quick reference

- **Principles:** DRY/KISS/SOLID held with judgment; earn your abstractions.
- **Folder structure:** prefer folders over loose modules; keep a component's
children and tests local to it.
- **Code style:** function declarations, no `any`, prefer `type` over `interface`.
- **Git:** `{username}/{TICKET}-{name}` branches; squash-merge PRs.

## EditorConfig

`.editorconfig` keeps formatting consistent across editors:

- UTF-8, LF line endings, final newline inserted, trailing whitespace trimmed.
- 2-space indentation (tabs in Makefiles).
- TypeScript/JavaScript: 2 spaces, double quotes.
- Markdown: no trailing-whitespace trim, no line-length limit.
Project-specific setup only; principles and workflow live in [AGENTS.md](../AGENTS.md).
Formatting rules are enforced by [.editorconfig](../.editorconfig) and Prettier — read
those files rather than a prose copy here.

## Tooling

| Tool | Role |
|------|------|
| pnpm | package manager (workspaces/monorepo) |
| pnpm | package manager — never npm/yarn (`packageManager` is pinned) |
| ESLint + Prettier | linting and formatting |
| TypeScript | type checking (`pnpm typecheck`) |
| Vitest | unit/component tests |
Expand All @@ -36,3 +23,8 @@ pnpm lint:fix # lint and auto-fix
pnpm typecheck # type check
pnpm test # run tests
```

## Code style (project choices)

- Function declarations, no `any`, prefer `type` over `interface`.
- Git: `{username}/{TICKET}-{name}` branches; squash-merge PRs.
49 changes: 11 additions & 38 deletions docs/REACT_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# React Development Guidelines

Standards for React 19 / Next.js (App Router) work in this project.

## Core philosophy

**DRY, KISS and SOLID — held with judgment, not dogma.** I avoid premature
abstraction: a wrong abstraction costs more than a little duplication, so I let a
pattern prove itself before extracting it. I apply SOLID's *spirit* — single
responsibility, composition over inheritance — rather than its OOP-era ceremony.
The goal is clarity and changeability, not acronym compliance.
Standards for React 19 / Next.js (App Router) work in this project. This doc holds only
what a model can't infer: our conventions and the choices we've made. General React/Next.js
knowledge is deliberately absent — the model already has it. Principles (DRY/KISS/SOLID
with judgment) live in [AGENTS.md](../AGENTS.md), not here.

## Folder structure

Expand All @@ -34,36 +29,14 @@ This keeps boundaries clear and makes search predictable.
4. Assets (images, SVGs, fonts)
5. Styles (Tailwind classes inline — no style imports)

## Component template

```tsx
// 1. Library imports
import { useState } from "react";

// 2. Local imports
import { formatPrice } from "@/lib/format";
import type { Product } from "./types";

// 3. Component — function declaration, typed props, no `any`
export default function ProductCard({ product }: { product: Product }) {
// derive during render; don't store computed values in state
const price = formatPrice(product.cents);
return <article>{price}</article>;
}
```

## Performance rules
## Project conventions

- Function declarations for components; typed props; no `any`.
- Derive state during render instead of syncing it in `useEffect`; don't store what you
can compute.
- No barrel imports — direct paths keep bundles lean.
- `Promise.all()` for independent async work — never sequential `await`s.
- No barrel imports — import direct paths to keep bundles lean.
- `next/dynamic` for heavy components not needed on first paint.
- With the React Compiler enabled, skip manual `useMemo`/`useCallback` unless
profiling proves a need.
- Derive state during render instead of syncing it in `useEffect`.

## Server vs client (App Router)

- Components render on the server by default; add `"use client"` only when you
need client hooks or browser APIs.
- Never touch `window`/`document`/`localStorage` during init — use `useEffect`.
- **React Compiler is enabled:** skip manual `useMemo`/`useCallback` unless profiling
proves a need.
- Treat server actions as public endpoints: authenticate them.
Loading