create-rigel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/cli.js +122 -0
- package/package.json +47 -0
- package/templates/express/.claude/CLAUDE.md +114 -0
- package/templates/express/.claude/agents/arch-validator.md +85 -0
- package/templates/express/.claude/agents/db-optimizer.md +72 -0
- package/templates/express/.claude/agents/doc-gardener.md +67 -0
- package/templates/express/.claude/agents/garbage-collector.md +94 -0
- package/templates/express/.claude/agents/gate-checker.md +176 -0
- package/templates/express/.claude/agents/reviewer.md +84 -0
- package/templates/express/.claude/agents/security-auditor.md +82 -0
- package/templates/express/.claude/hooks/post-write.sh +93 -0
- package/templates/express/.claude/rules/api.md +149 -0
- package/templates/express/.claude/rules/architecture.md +78 -0
- package/templates/express/.claude/rules/database.md +104 -0
- package/templates/express/.claude/rules/jobs.md +81 -0
- package/templates/express/.claude/rules/security.md +92 -0
- package/templates/express/.claude/rules/testing.md +97 -0
- package/templates/express/.claude/settings.json +35 -0
- package/templates/express/.claude/skills/00-infra-setup/SKILL.md +479 -0
- package/templates/express/.claude/skills/01-write-roadmap/SKILL.md +72 -0
- package/templates/express/.claude/skills/02-write-spec/SKILL.md +119 -0
- package/templates/express/.claude/skills/03-write-plan/SKILL.md +124 -0
- package/templates/express/.claude/skills/04-build-layer/SKILL.md +216 -0
- package/templates/express/.claude/skills/05-validate-layer/SKILL.md +43 -0
- package/templates/express/.claude/skills/06-push-layer/SKILL.md +59 -0
- package/templates/express/.claude/skills/07-layer-check/SKILL.md +62 -0
- package/templates/express/.claude/skills/08-garbage-collect/SKILL.md +86 -0
- package/templates/express/.claude/skills/09-doc-garden/SKILL.md +64 -0
- package/templates/express/.claude/skills/10-db-optimize/SKILL.md +96 -0
- package/templates/express/.claude/skills/11-load-test/SKILL.md +77 -0
- package/templates/express/.dockerignore +43 -0
- package/templates/express/.env.example +42 -0
- package/templates/express/.github/CODEOWNERS +22 -0
- package/templates/express/.github/dependabot.yml +48 -0
- package/templates/express/.github/pull_request_template.md +58 -0
- package/templates/express/.github/workflows/ci.yml +278 -0
- package/templates/express/.github/workflows/load-test.yml +41 -0
- package/templates/express/.gitleaks.toml +18 -0
- package/templates/express/.lintstagedrc.json +3 -0
- package/templates/express/.nvmrc +1 -0
- package/templates/express/.prettierrc +8 -0
- package/templates/express/.sequelizerc +8 -0
- package/templates/express/AGENTS.md +94 -0
- package/templates/express/ARCHITECTURE.md +161 -0
- package/templates/express/Dockerfile +40 -0
- package/templates/express/Makefile +79 -0
- package/templates/express/QUICKSTART.md +131 -0
- package/templates/express/docker-compose.yml +114 -0
- package/templates/express/docs/PLANS.md +111 -0
- package/templates/express/docs/QUALITY_SCORE.md +66 -0
- package/templates/express/docs/design-docs/core-beliefs.md +89 -0
- package/templates/express/docs/design-docs/decisions/ADR-000-infrastructure.md +70 -0
- package/templates/express/docs/design-docs/decisions/ADR-001-observability.md +82 -0
- package/templates/express/docs/design-docs/decisions/index.md +23 -0
- package/templates/express/docs/design-docs/index.md +11 -0
- package/templates/express/docs/design-docs/observability.md +194 -0
- package/templates/express/docs/design-docs/team-workflow.md +64 -0
- package/templates/express/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/tech-debt-tracker.md +31 -0
- package/templates/express/docs/generated/.gitkeep +0 -0
- package/templates/express/docs/product-specs/ROADMAP.md +49 -0
- package/templates/express/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/express/docs/product-specs/index.md +21 -0
- package/templates/express/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/express/eslint.config.mjs +94 -0
- package/templates/express/gitignore +36 -0
- package/templates/express/infra/monitoring/alloy/config.alloy +59 -0
- package/templates/express/jest.config.ts +61 -0
- package/templates/express/package.json +45 -0
- package/templates/express/scripts/openapi.export.ts +77 -0
- package/templates/express/tests/architecture/isolation.test.ts +51 -0
- package/templates/express/tests/architecture/layers.test.ts +83 -0
- package/templates/express/tests/integration/health.test.ts +40 -0
- package/templates/express/tests/integration/isolation.test.template.ts +88 -0
- package/templates/express/tests/load/smoke.js +46 -0
- package/templates/express/tests/load/soak.js +45 -0
- package/templates/express/tests/load/stress.js +44 -0
- package/templates/express/tests/unit/utils/response.util.test.ts +53 -0
- package/templates/express/tsconfig.build.json +8 -0
- package/templates/express/tsconfig.json +20 -0
- package/templates/fastapi/.claude/CLAUDE.md +122 -0
- package/templates/fastapi/.claude/agents/arch-validator.md +64 -0
- package/templates/fastapi/.claude/agents/db-optimizer.md +59 -0
- package/templates/fastapi/.claude/agents/doc-gardener.md +55 -0
- package/templates/fastapi/.claude/agents/garbage-collector.md +74 -0
- package/templates/fastapi/.claude/agents/gate-checker.md +133 -0
- package/templates/fastapi/.claude/agents/reviewer.md +74 -0
- package/templates/fastapi/.claude/agents/security-auditor.md +69 -0
- package/templates/fastapi/.claude/hooks/post-write.sh +65 -0
- package/templates/fastapi/.claude/rules/api.md +119 -0
- package/templates/fastapi/.claude/rules/architecture.md +84 -0
- package/templates/fastapi/.claude/rules/database.md +153 -0
- package/templates/fastapi/.claude/rules/jobs.md +85 -0
- package/templates/fastapi/.claude/rules/security.md +102 -0
- package/templates/fastapi/.claude/rules/testing.md +207 -0
- package/templates/fastapi/.claude/settings.json +50 -0
- package/templates/fastapi/.claude/skills/00-infra-setup/SKILL.md +311 -0
- package/templates/fastapi/.claude/skills/01-write-roadmap/SKILL.md +61 -0
- package/templates/fastapi/.claude/skills/02-write-spec/SKILL.md +82 -0
- package/templates/fastapi/.claude/skills/03-write-plan/SKILL.md +83 -0
- package/templates/fastapi/.claude/skills/04-build-layer/SKILL.md +170 -0
- package/templates/fastapi/.claude/skills/05-validate-layer/SKILL.md +20 -0
- package/templates/fastapi/.claude/skills/06-push-layer/SKILL.md +31 -0
- package/templates/fastapi/.claude/skills/07-layer-check/SKILL.md +51 -0
- package/templates/fastapi/.claude/skills/08-garbage-collect/SKILL.md +47 -0
- package/templates/fastapi/.claude/skills/09-doc-garden/SKILL.md +9 -0
- package/templates/fastapi/.claude/skills/10-db-optimize/SKILL.md +53 -0
- package/templates/fastapi/.claude/skills/11-load-test/SKILL.md +81 -0
- package/templates/fastapi/.dockerignore +30 -0
- package/templates/fastapi/.github/dependabot.yml +24 -0
- package/templates/fastapi/AGENTS.md +87 -0
- package/templates/fastapi/ARCHITECTURE.md +141 -0
- package/templates/fastapi/Makefile +42 -0
- package/templates/fastapi/README.md +179 -0
- package/templates/fastapi/docs/PLANS.md +26 -0
- package/templates/fastapi/docs/QUALITY_SCORE.md +26 -0
- package/templates/fastapi/docs/design-docs/core-beliefs.md +46 -0
- package/templates/fastapi/docs/design-docs/decisions/ADR-000-infrastructure.md +79 -0
- package/templates/fastapi/docs/design-docs/decisions/index.md +7 -0
- package/templates/fastapi/docs/design-docs/index.md +9 -0
- package/templates/fastapi/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/fastapi/docs/generated/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/ROADMAP.md +49 -0
- package/templates/fastapi/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/index.md +18 -0
- package/templates/fastapi/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/fastapi/gitignore +46 -0
- package/templates/fastapi/scripts/gate.sh +68 -0
- package/templates/nestjs/.claude/CLAUDE.md +121 -0
- package/templates/nestjs/.claude/agents/db-optimizer.md +51 -0
- package/templates/nestjs/.claude/agents/doc-gardener.md +45 -0
- package/templates/nestjs/.claude/agents/garbage-collector.md +50 -0
- package/templates/nestjs/.claude/agents/gate-checker.md +127 -0
- package/templates/nestjs/.claude/agents/reviewer.md +68 -0
- package/templates/nestjs/.claude/agents/security-auditor.md +72 -0
- package/templates/nestjs/.claude/hooks/post-write.sh +91 -0
- package/templates/nestjs/.claude/rules/api.md +87 -0
- package/templates/nestjs/.claude/rules/architecture.md +104 -0
- package/templates/nestjs/.claude/rules/database.md +111 -0
- package/templates/nestjs/.claude/rules/security.md +110 -0
- package/templates/nestjs/.claude/rules/testing.md +145 -0
- package/templates/nestjs/.claude/settings.json +33 -0
- package/templates/nestjs/.claude/skills/00-infra-setup/SKILL.md +222 -0
- package/templates/nestjs/.claude/skills/01-write-spec/SKILL.md +25 -0
- package/templates/nestjs/.claude/skills/02-write-plan/SKILL.md +18 -0
- package/templates/nestjs/.claude/skills/03-build-layer/SKILL.md +242 -0
- package/templates/nestjs/.claude/skills/04-validate-layer/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/05-push-layer/SKILL.md +6 -0
- package/templates/nestjs/.claude/skills/06-layer-check/SKILL.md +12 -0
- package/templates/nestjs/.claude/skills/07-garbage-collect/SKILL.md +2 -0
- package/templates/nestjs/.claude/skills/08-doc-garden/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/09-api-sync/SKILL.md +36 -0
- package/templates/nestjs/.claude/skills/10-load-test/SKILL.md +29 -0
- package/templates/nestjs/.env.example +11 -0
- package/templates/nestjs/AGENTS.md +88 -0
- package/templates/nestjs/ARCHITECTURE.md +192 -0
- package/templates/nestjs/docs/PLANS.md +22 -0
- package/templates/nestjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nestjs/docs/design-docs/core-beliefs.md +18 -0
- package/templates/nestjs/docs/design-docs/decisions/ADR-000-infrastructure.md +58 -0
- package/templates/nestjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nestjs/docs/design-docs/index.md +7 -0
- package/templates/nestjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/tech-debt-tracker.md +16 -0
- package/templates/nestjs/docs/generated/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/index.md +7 -0
- package/templates/nestjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nestjs/gitignore +8 -0
- package/templates/nextjs/.claude/CLAUDE.md +139 -0
- package/templates/nextjs/.claude/agents/arch-validator.md +66 -0
- package/templates/nextjs/.claude/agents/contract-checker.md +78 -0
- package/templates/nextjs/.claude/agents/doc-gardener.md +52 -0
- package/templates/nextjs/.claude/agents/garbage-collector.md +61 -0
- package/templates/nextjs/.claude/agents/gate-checker.md +129 -0
- package/templates/nextjs/.claude/agents/perf-auditor.md +109 -0
- package/templates/nextjs/.claude/agents/reviewer.md +79 -0
- package/templates/nextjs/.claude/agents/security-auditor.md +50 -0
- package/templates/nextjs/.claude/hooks/post-write.sh +121 -0
- package/templates/nextjs/.claude/rules/api-contract.md +166 -0
- package/templates/nextjs/.claude/rules/architecture.md +99 -0
- package/templates/nextjs/.claude/rules/components.md +122 -0
- package/templates/nextjs/.claude/rules/security.md +122 -0
- package/templates/nextjs/.claude/rules/testing.md +187 -0
- package/templates/nextjs/.claude/scripts/infra-setup.sh +550 -0
- package/templates/nextjs/.claude/settings.json +33 -0
- package/templates/nextjs/.claude/skills/00-infra-setup/SKILL.md +471 -0
- package/templates/nextjs/.claude/skills/01-write-roadmap/SKILL.md +77 -0
- package/templates/nextjs/.claude/skills/02-write-spec/SKILL.md +78 -0
- package/templates/nextjs/.claude/skills/03-write-plan/SKILL.md +91 -0
- package/templates/nextjs/.claude/skills/04-build-layer/SKILL.md +141 -0
- package/templates/nextjs/.claude/skills/05-validate-layer/SKILL.md +13 -0
- package/templates/nextjs/.claude/skills/06-push-layer/SKILL.md +20 -0
- package/templates/nextjs/.claude/skills/07-layer-check/SKILL.md +28 -0
- package/templates/nextjs/.claude/skills/08-garbage-collect/SKILL.md +14 -0
- package/templates/nextjs/.claude/skills/09-doc-garden/SKILL.md +12 -0
- package/templates/nextjs/.claude/skills/10-api-sync/SKILL.md +50 -0
- package/templates/nextjs/.claude/skills/11-perf-budget/SKILL.md +51 -0
- package/templates/nextjs/.dockerignore +25 -0
- package/templates/nextjs/.gitattributes +10 -0
- package/templates/nextjs/.github/CODEOWNERS +24 -0
- package/templates/nextjs/.github/dependabot.yml +31 -0
- package/templates/nextjs/.github/pull_request_template.md +59 -0
- package/templates/nextjs/.github/workflows/ci.yml +143 -0
- package/templates/nextjs/.github/workflows/lighthouse.yml +54 -0
- package/templates/nextjs/.github/workflows/load-test.yml +54 -0
- package/templates/nextjs/.husky/pre-commit +1 -0
- package/templates/nextjs/.lintstagedrc.json +4 -0
- package/templates/nextjs/.mcp.json +8 -0
- package/templates/nextjs/.prettierignore +13 -0
- package/templates/nextjs/.prettierrc +9 -0
- package/templates/nextjs/AGENTS.md +88 -0
- package/templates/nextjs/ARCHITECTURE.md +165 -0
- package/templates/nextjs/Dockerfile +38 -0
- package/templates/nextjs/Makefile +50 -0
- package/templates/nextjs/QUICKSTART.md +128 -0
- package/templates/nextjs/docs/PLANS.md +23 -0
- package/templates/nextjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nextjs/docs/design-docs/core-beliefs.md +43 -0
- package/templates/nextjs/docs/design-docs/decisions/ADR-000-infrastructure.md +72 -0
- package/templates/nextjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nextjs/docs/design-docs/index.md +7 -0
- package/templates/nextjs/docs/design-docs/team-workflow.md +66 -0
- package/templates/nextjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/nextjs/docs/generated/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/ROADMAP.md +59 -0
- package/templates/nextjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/index.md +18 -0
- package/templates/nextjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nextjs/eslint.config.mjs +150 -0
- package/templates/nextjs/gitignore +35 -0
- package/templates/nextjs/lighthouserc.js +37 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# /infra-setup — Phase 0 Infrastructure Setup
|
|
2
|
+
|
|
3
|
+
> **Verified:** 2026-06-18 · **Staleness threshold:** 60 days
|
|
4
|
+
> **Sources:** create-next-app (https://nextjs.org/docs/app/api-reference/cli/create-next-app) ·
|
|
5
|
+
> shadcn/ui (https://ui.shadcn.com/docs/cli) · TanStack Query (https://tanstack.com/query) ·
|
|
6
|
+
> ESLint flat config (https://eslint.org/docs/latest/use/configure/configuration-files)
|
|
7
|
+
> If older than the threshold, fetch current docs via `ctx7` (resolve library → fetch docs),
|
|
8
|
+
> or via `WebFetch`/`WebSearch` if ctx7 is unavailable, **before** following the commands.
|
|
9
|
+
|
|
10
|
+
Triggered by: `/infra-setup`
|
|
11
|
+
Run ONCE. Check if `package.json` exists first — if yes, abort (already set up).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## How to run this skill (two phases)
|
|
16
|
+
|
|
17
|
+
**Phase A — scaffold the Next.js app (Step 1 below).** `create-next-app` refuses to run
|
|
18
|
+
in a non-empty directory, and this harness ships files (`.claude/`, `.github/`, `docs/`,
|
|
19
|
+
configs…) that it treats as conflicts. So Step 1 **parks the harness files aside**, runs
|
|
20
|
+
the plain `npx create-next-app@latest .` into the now-empty root, then **restores** them.
|
|
21
|
+
Run this block directly — it cannot be a script, because any script under `.claude/`
|
|
22
|
+
would be parked away mid-run.
|
|
23
|
+
|
|
24
|
+
**Phase B — deterministic setup (Steps 2–4 + scripts + husky).** Once the app exists,
|
|
25
|
+
run the checked-in script — identical every time:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bash .claude/scripts/infra-setup.sh
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then **you** author the harness glue (Step 5) and config edits (Steps 6–8), which need
|
|
32
|
+
judgement and cannot be scripted. The Steps below document what runs (so they can be
|
|
33
|
+
audited/updated when the Verified date is stale) and what you must hand-author.
|
|
34
|
+
|
|
35
|
+
> If a command fails on a tool version change, fix it, re-run, and bump the `Verified:`
|
|
36
|
+
> date above — do not silently hand-run divergent commands.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Cardinal Scaffolding Rule
|
|
41
|
+
|
|
42
|
+
**Never hand-write boilerplate the CLI can generate.**
|
|
43
|
+
`create-next-app` and `npx shadcn` own the app shell, root layout, base pages, and UI
|
|
44
|
+
components. Dependencies install at their **latest LTS** — no version pins (bare
|
|
45
|
+
`npm install <pkg>`). Hand-authoring is restricted to **harness glue with no generator**
|
|
46
|
+
(see Step 5). If a generator exists for an artifact, run it and edit its output rather
|
|
47
|
+
than writing the file from scratch.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Step 1 — Create Next.js App (park → scaffold → restore)
|
|
52
|
+
|
|
53
|
+
Run this block **directly** (not via a script — `.claude/` gets parked). It moves the
|
|
54
|
+
harness files aside, runs the plain `create-next-app` in the empty root, then restores
|
|
55
|
+
the harness on top of the generated app (our files win on the one overlap, `.gitignore`).
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Abort if already set up
|
|
59
|
+
[ -f package.json ] && { echo "package.json exists — infra already run. Aborting."; exit 1; }
|
|
60
|
+
|
|
61
|
+
# 1. Park every harness file create-next-app would treat as a conflict.
|
|
62
|
+
# CRITICAL: park to a dir OUTSIDE the project. create-next-app refuses to run if the
|
|
63
|
+
# cwd contains ANYTHING except a known-safe allowlist (.git is fine) — a `.harness-hold`
|
|
64
|
+
# SUBDIR would itself be flagged as a conflict and abort the scaffold. So park to an
|
|
65
|
+
# external temp dir, leaving only .git behind.
|
|
66
|
+
# eslint.config.mjs is parked too — we restore OUR version on top so it wins over
|
|
67
|
+
# the one create-next-app generates (it carries the layer-boundary rules).
|
|
68
|
+
HOLD=$(mktemp -d)
|
|
69
|
+
for f in .claude .github .husky docs \
|
|
70
|
+
.prettierrc .prettierignore .lintstagedrc.json .gitattributes .gitignore .mcp.json \
|
|
71
|
+
eslint.config.mjs Dockerfile .dockerignore Makefile \
|
|
72
|
+
lighthouserc.js AGENTS.md ARCHITECTURE.md QUICKSTART.md; do
|
|
73
|
+
[ -e "$f" ] && mv "$f" "$HOLD"/
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
# 2. Scaffold into the now-empty root (plain, non-interactive)
|
|
77
|
+
npx create-next-app@latest . \
|
|
78
|
+
--typescript --tailwind --eslint --app --src-dir \
|
|
79
|
+
--import-alias "@/*" --turbopack --use-npm --yes
|
|
80
|
+
|
|
81
|
+
# 3. Restore the harness on top (clobber: our .gitignore/configs win over generated)
|
|
82
|
+
cp -a "$HOLD"/. ./
|
|
83
|
+
rm -rf "$HOLD"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> If `create-next-app` lists a NEW conflicting file not in the park list above, add it to
|
|
87
|
+
> the `for f in …` list and re-run. `.git` is never parked (create-next-app tolerates it).
|
|
88
|
+
|
|
89
|
+
## Step 2 — Install All Dependencies
|
|
90
|
+
> Steps 2–4 + package.json scripts + husky are performed by `bash .claude/scripts/infra-setup.sh`.
|
|
91
|
+
> The detail below documents what that script runs.
|
|
92
|
+
```bash
|
|
93
|
+
# Server state + API contract
|
|
94
|
+
npm install @tanstack/react-query @tanstack/react-query-devtools
|
|
95
|
+
npm install openapi-fetch
|
|
96
|
+
npm install -D openapi-typescript
|
|
97
|
+
|
|
98
|
+
# Client state
|
|
99
|
+
npm install zustand
|
|
100
|
+
|
|
101
|
+
# Forms + validation
|
|
102
|
+
npm install react-hook-form @hookform/resolvers zod
|
|
103
|
+
|
|
104
|
+
# shadcn/ui CLI
|
|
105
|
+
npm install -D shadcn
|
|
106
|
+
|
|
107
|
+
# Testing
|
|
108
|
+
npm install -D vitest @vitejs/plugin-react jsdom
|
|
109
|
+
npm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom
|
|
110
|
+
npm install -D msw playwright @playwright/test
|
|
111
|
+
npm install -D @vitest/coverage-v8
|
|
112
|
+
|
|
113
|
+
# Performance
|
|
114
|
+
npm install -D @next/bundle-analyzer
|
|
115
|
+
|
|
116
|
+
# Linting (the committed eslint.config.mjs uses Next's native flat config — no FlatCompat)
|
|
117
|
+
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-import
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Step 3 — Initialise shadcn/ui
|
|
121
|
+
```bash
|
|
122
|
+
npx shadcn init
|
|
123
|
+
# Select: Default style, CSS variables for colors
|
|
124
|
+
# This creates components.json and src/components/ui/
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Add base components:
|
|
128
|
+
```bash
|
|
129
|
+
npx shadcn add button input label form card dialog toast badge skeleton
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Step 4 — Directory Structure
|
|
133
|
+
```bash
|
|
134
|
+
mkdir -p \
|
|
135
|
+
src/types \
|
|
136
|
+
src/lib \
|
|
137
|
+
src/hooks \
|
|
138
|
+
src/store \
|
|
139
|
+
src/features \
|
|
140
|
+
src/components/shared \
|
|
141
|
+
src/utils \
|
|
142
|
+
tests/unit/{hooks,features,components,utils} \
|
|
143
|
+
tests/e2e \
|
|
144
|
+
tests/visual \
|
|
145
|
+
tests/architecture \
|
|
146
|
+
tests/mocks \
|
|
147
|
+
docs/{product-specs/{draft,ready},exec-plans/{active,completed},design-docs/decisions,generated}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Step 5 — Create Harness Glue (only files with no CLI generator)
|
|
151
|
+
|
|
152
|
+
Per the Cardinal Scaffolding Rule, Claude authors **only** the files below — none of
|
|
153
|
+
these can be produced by `create-next-app` or `npx shadcn`. The root layout, base
|
|
154
|
+
page, and all UI components come from the generators (Step 1 + Step 3); do **not**
|
|
155
|
+
re-create them. Auth/dashboard route groups and their pages are **feature-build
|
|
156
|
+
concerns**, not Phase 0 — they are created later by `/build-layer`, not here.
|
|
157
|
+
|
|
158
|
+
### src/lib/env.ts
|
|
159
|
+
Zod-validated NEXT_PUBLIC_* env vars. `process.env` ONLY here.
|
|
160
|
+
Schema includes: `NEXT_PUBLIC_API_URL`.
|
|
161
|
+
On invalid: `console.error` + `throw new Error` (not process.exit — browser context).
|
|
162
|
+
|
|
163
|
+
### src/lib/api-client.ts
|
|
164
|
+
openapi-fetch `createClient<paths>` instance.
|
|
165
|
+
Auth middleware reads token from React context.
|
|
166
|
+
Base URL from `env.NEXT_PUBLIC_API_URL`.
|
|
167
|
+
Import type `paths` from `@/types/api.generated` (will exist after /api-sync).
|
|
168
|
+
**REQUIRED: pass a lazy `fetch`** so MSW can intercept in tests — openapi-fetch binds
|
|
169
|
+
`globalThis.fetch` at client-creation time (before MSW patches it in `beforeAll`), so a
|
|
170
|
+
client created at import time would capture the un-patched fetch and every hook test
|
|
171
|
+
would hit the real network (flaky `ECONNREFUSED`). Wrap it:
|
|
172
|
+
```typescript
|
|
173
|
+
export const apiClient = createClient<paths>({
|
|
174
|
+
baseUrl: env.NEXT_PUBLIC_API_URL,
|
|
175
|
+
fetch: (...args) => globalThis.fetch(...args), // lazy — lets MSW intercept in tests
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### src/lib/constants.ts
|
|
180
|
+
App-wide constants. No process.env.
|
|
181
|
+
|
|
182
|
+
### Script-written glue (do NOT hand-author — `infra-setup.sh` Step 7 writes these)
|
|
183
|
+
- `src/lib/logger.ts` — zero-dep structured JSON logger (the only sanctioned logging path)
|
|
184
|
+
- `src/lib/api-error.ts` — `toApiError`/`ApiError` (normalises the canonical error envelope)
|
|
185
|
+
- `src/instrumentation.ts` — Next.js boot/observability seam
|
|
186
|
+
- `src/app/global-error.tsx` — root error boundary that reports via the logger
|
|
187
|
+
- `.env.example`, the per-layer `vitest.config.ts`, `playwright.config.ts`, `tests/setup.ts`,
|
|
188
|
+
`tests/architecture/layers.test.ts`, `tests/e2e/isolation.spec.ts` (+ `helpers/auth.ts`),
|
|
189
|
+
and `tests/load/{smoke,stress,soak}.js`
|
|
190
|
+
|
|
191
|
+
### src/utils/cn.util.ts
|
|
192
|
+
`clsx` + `tailwind-merge` class merger. 100% tested.
|
|
193
|
+
(Note: `npx shadcn init` may also generate a `cn` helper in `src/lib/utils.ts` —
|
|
194
|
+
if so, re-export from there instead of duplicating.)
|
|
195
|
+
|
|
196
|
+
### src/utils/format-date.util.ts
|
|
197
|
+
Date formatting helpers. 100% tested.
|
|
198
|
+
|
|
199
|
+
### src/utils/parse-error.util.ts
|
|
200
|
+
Safely extract error message from unknown. 100% tested.
|
|
201
|
+
|
|
202
|
+
### src/store/ui-store.ts
|
|
203
|
+
Zustand store for UI-only state: `sidebarOpen`, `activeModal`.
|
|
204
|
+
|
|
205
|
+
### src/types/domain.types.ts
|
|
206
|
+
Placeholder — frontend-only types added here as features are built.
|
|
207
|
+
|
|
208
|
+
### src/types/api.generated.ts
|
|
209
|
+
Placeholder with comment: `// READ ONLY — regenerate with: npm run api:sync`
|
|
210
|
+
Actual content generated by /api-sync after OpenAPI spec is available.
|
|
211
|
+
|
|
212
|
+
### tests/mocks/server.ts + tests/mocks/handlers.ts
|
|
213
|
+
MSW server setup for Vitest.
|
|
214
|
+
|
|
215
|
+
### tests/utils/create-wrapper.tsx
|
|
216
|
+
React QueryClient wrapper for renderHook tests.
|
|
217
|
+
|
|
218
|
+
### tests/architecture/layers.test.ts — WRITTEN BY THE SCRIPT (do not hand-author)
|
|
219
|
+
Dependency-free import-graph structural tests (Node `fs` only). **`.claude/scripts/infra-setup.sh`
|
|
220
|
+
(Step 6) writes this file**, so the gate is deterministic, not improvised — the gate
|
|
221
|
+
(`tests/architecture/`) and CI both depend on this exact file. Shown here for reference:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { describe, it, expect } from 'vitest'
|
|
225
|
+
import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs'
|
|
226
|
+
import { join } from 'node:path'
|
|
227
|
+
|
|
228
|
+
// Layer import rules. A layer must NOT import from any of its "forbidden" layers.
|
|
229
|
+
// Aliases are matched as `@/<layer>` and bare `app/` imports.
|
|
230
|
+
const RULES: Record<string, string[]> = {
|
|
231
|
+
'src/types': ['@/lib', '@/hooks', '@/features', '@/store', '@/components'],
|
|
232
|
+
'src/lib': ['@/hooks', '@/features', '@/components'],
|
|
233
|
+
'src/store': ['@/hooks', '@/features', '@/components'],
|
|
234
|
+
'src/hooks': ['@/features', '@/components'],
|
|
235
|
+
'src/features': ['app/'],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function walk(dir: string): string[] {
|
|
239
|
+
if (!existsSync(dir)) return []
|
|
240
|
+
return readdirSync(dir).flatMap((name) => {
|
|
241
|
+
const full = join(dir, name)
|
|
242
|
+
if (statSync(full).isDirectory()) return walk(full)
|
|
243
|
+
return /\.(ts|tsx)$/.test(full) && !full.endsWith('.d.ts') ? [full] : []
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const IMPORT_RE = /(?:import|from)\s+['"]([^'"]+)['"]/g
|
|
248
|
+
|
|
249
|
+
function importsOf(file: string): string[] {
|
|
250
|
+
const src = readFileSync(file, 'utf8')
|
|
251
|
+
const out: string[] = []
|
|
252
|
+
for (const m of src.matchAll(IMPORT_RE)) out.push(m[1])
|
|
253
|
+
return out
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
describe('layer import rules', () => {
|
|
257
|
+
for (const [layer, forbidden] of Object.entries(RULES)) {
|
|
258
|
+
it(`${layer} does not import from ${forbidden.join(', ')}`, () => {
|
|
259
|
+
const violations: string[] = []
|
|
260
|
+
for (const file of walk(layer)) {
|
|
261
|
+
for (const imp of importsOf(file)) {
|
|
262
|
+
const hit = forbidden.some((f) => {
|
|
263
|
+
const base = f.endsWith('/') ? f.slice(0, -1) : f
|
|
264
|
+
return imp === base || imp.startsWith(base + '/')
|
|
265
|
+
})
|
|
266
|
+
if (hit) violations.push(`${file} → ${imp}`)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
expect(violations, violations.join('\n')).toHaveLength(0)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('app pages contain no business logic', () => {
|
|
275
|
+
it('page.tsx files import features, not hooks directly', () => {
|
|
276
|
+
const pages = walk('app').filter((f) => f.endsWith('page.tsx'))
|
|
277
|
+
const violations = pages.filter((f) =>
|
|
278
|
+
importsOf(f).some((imp) => imp.startsWith('@/hooks')),
|
|
279
|
+
)
|
|
280
|
+
expect(violations, violations.join('\n')).toHaveLength(0)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### src/components/providers.tsx
|
|
286
|
+
`'use client'` Providers component: QueryClientProvider (+ devtools), toast provider.
|
|
287
|
+
This holds the client-side context that the generated root layout cannot.
|
|
288
|
+
|
|
289
|
+
### Wire providers into the generated layout (edit, not rewrite)
|
|
290
|
+
`app/layout.tsx` is generated by `create-next-app`. **Edit** it to wrap `{children}`
|
|
291
|
+
in `<Providers>` from `src/components/providers.tsx`. Keep the generated font setup and
|
|
292
|
+
metadata; do not rewrite the file from scratch.
|
|
293
|
+
|
|
294
|
+
## Step 6 — Config Files
|
|
295
|
+
|
|
296
|
+
`create-next-app` already generated `next.config.ts` and `tsconfig.json`. **Edit**
|
|
297
|
+
`next.config.ts` to add the settings below — do not recreate it.
|
|
298
|
+
|
|
299
|
+
`vitest.config.ts`, `playwright.config.ts`, and `tests/setup.ts` are **written by
|
|
300
|
+
`.claude/scripts/infra-setup.sh` (Step 5)** — do not author them by hand. The
|
|
301
|
+
`vitest.config.ts` it writes carries the **per-layer coverage thresholds** (utils 100 ·
|
|
302
|
+
hooks 80 · features 70 · components 70) that match `.claude/rules/testing.md`; they are
|
|
303
|
+
real, machine-enforced gates, not prose. Shown below for reference.
|
|
304
|
+
|
|
305
|
+
`eslint.config.mjs` is **committed by the harness** and restored on top of the generated
|
|
306
|
+
one (Step 1 park-and-restore) — it carries the layer-import boundaries. Do not hand-edit
|
|
307
|
+
boundaries into the generated file; edit the committed `eslint.config.mjs` instead.
|
|
308
|
+
|
|
309
|
+
### next.config.ts (base is SCRIPT-WRITTEN; you only add version-gated extras)
|
|
310
|
+
`infra-setup.sh` (Step 5) overwrites the generated `next.config.ts` with a base that has
|
|
311
|
+
`output: 'standalone'` (required by the Dockerfile) + security headers. **Only** add
|
|
312
|
+
`reactCompiler`/`typedRoutes` here if you've confirmed the installed Next version supports
|
|
313
|
+
them (they're version-sensitive — `reactCompiler` may need `babel-plugin-react-compiler`,
|
|
314
|
+
and `typedRoutes` moved out of `experimental`). Full reference shape:
|
|
315
|
+
```typescript
|
|
316
|
+
import type { NextConfig } from 'next'
|
|
317
|
+
const nextConfig: NextConfig = {
|
|
318
|
+
output: 'standalone', // minimal server bundle for the Docker runner stage (script writes this)
|
|
319
|
+
reactCompiler: true, // OPTIONAL — only if the installed Next version supports it
|
|
320
|
+
experimental: {
|
|
321
|
+
typedRoutes: true, // OPTIONAL — note: top-level `typedRoutes` in newer Next
|
|
322
|
+
},
|
|
323
|
+
// Security headers
|
|
324
|
+
async headers() {
|
|
325
|
+
return [{
|
|
326
|
+
source: '/(.*)',
|
|
327
|
+
headers: [
|
|
328
|
+
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
329
|
+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
330
|
+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
331
|
+
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
|
|
332
|
+
],
|
|
333
|
+
}]
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
export default nextConfig
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### vitest.config.ts
|
|
340
|
+
```typescript
|
|
341
|
+
import { defineConfig } from 'vitest/config'
|
|
342
|
+
import react from '@vitejs/plugin-react'
|
|
343
|
+
import path from 'path'
|
|
344
|
+
|
|
345
|
+
export default defineConfig({
|
|
346
|
+
plugins: [react()],
|
|
347
|
+
test: {
|
|
348
|
+
environment: 'jsdom',
|
|
349
|
+
globals: true,
|
|
350
|
+
setupFiles: ['./tests/setup.ts'],
|
|
351
|
+
coverage: {
|
|
352
|
+
provider: 'v8',
|
|
353
|
+
include: ['src/**/*.{ts,tsx}'],
|
|
354
|
+
exclude: ['src/types/api.generated.ts', 'src/components/ui/**'],
|
|
355
|
+
thresholds: { lines: 70, functions: 70, branches: 65 },
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
|
|
359
|
+
})
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### playwright.config.ts
|
|
363
|
+
```typescript
|
|
364
|
+
import { defineConfig, devices } from '@playwright/test'
|
|
365
|
+
export default defineConfig({
|
|
366
|
+
testDir: './tests/e2e',
|
|
367
|
+
fullyParallel: true,
|
|
368
|
+
forbidOnly: !!process.env.CI,
|
|
369
|
+
retries: process.env.CI ? 2 : 0,
|
|
370
|
+
use: {
|
|
371
|
+
baseURL: 'http://localhost:3000',
|
|
372
|
+
trace: 'on-first-retry',
|
|
373
|
+
},
|
|
374
|
+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
375
|
+
webServer: {
|
|
376
|
+
command: 'npm run dev',
|
|
377
|
+
url: 'http://localhost:3000',
|
|
378
|
+
reuseExistingServer: !process.env.CI,
|
|
379
|
+
},
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### tests/setup.ts
|
|
384
|
+
```typescript
|
|
385
|
+
import '@testing-library/jest-dom/vitest'
|
|
386
|
+
import { beforeAll, afterAll, afterEach } from 'vitest'
|
|
387
|
+
import { server } from './mocks/server'
|
|
388
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }))
|
|
389
|
+
afterEach(() => server.resetHandlers())
|
|
390
|
+
afterAll(() => server.close())
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### package.json scripts (the script in `.claude/scripts/infra-setup.sh` adds these)
|
|
394
|
+
`dev` / `build` / `start` keep create-next-app's values; the harness adds the rest.
|
|
395
|
+
Note `lint` uses **`eslint .`** (flat config), NOT the deprecated `next lint`, and `gate`
|
|
396
|
+
is the single command `/push-layer` and `/validate-layer` run.
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"scripts": {
|
|
400
|
+
"dev": "next dev",
|
|
401
|
+
"build": "next build",
|
|
402
|
+
"start": "next start",
|
|
403
|
+
"api:sync": "openapi-typescript openapi.json -o src/types/api.generated.ts",
|
|
404
|
+
"test": "vitest",
|
|
405
|
+
"test:ui": "vitest --ui",
|
|
406
|
+
"test:coverage": "vitest run --coverage",
|
|
407
|
+
"test:e2e": "playwright test",
|
|
408
|
+
"test:visual": "playwright test tests/visual/",
|
|
409
|
+
"typecheck": "tsc --noEmit",
|
|
410
|
+
"lint": "eslint .",
|
|
411
|
+
"lint:fix": "eslint . --fix",
|
|
412
|
+
"format": "prettier --write .",
|
|
413
|
+
"format:check": "prettier --check .",
|
|
414
|
+
"gate": "npm run typecheck && npm run lint && npm run format:check && npm run test:coverage",
|
|
415
|
+
"analyze": "ANALYZE=true next build",
|
|
416
|
+
"prepare": "husky"
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Committed dev-experience tooling (already in the template — no authoring needed)
|
|
422
|
+
These files ship with the template; the script installs their deps and activates husky:
|
|
423
|
+
- `.prettierrc` + `.prettierignore` — Prettier (with `prettier-plugin-tailwindcss`)
|
|
424
|
+
- `.lintstagedrc.json` + `.husky/pre-commit` — pre-commit runs `lint-staged`
|
|
425
|
+
(eslint --fix + prettier on staged files)
|
|
426
|
+
- `.github/workflows/ci.yml` — typecheck · lint · format · test · build on PR
|
|
427
|
+
- `.github/workflows/lighthouse.yml` + `lighthouserc.js` — Core Web Vitals budget
|
|
428
|
+
- `docs/design-docs/team-workflow.md` — branch protection + review policy
|
|
429
|
+
|
|
430
|
+
### .env.example
|
|
431
|
+
```
|
|
432
|
+
NEXT_PUBLIC_API_URL=http://localhost:8000
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Step 7 — ESLint Config (committed `eslint.config.mjs` — nothing to author)
|
|
436
|
+
The harness ships `eslint.config.mjs` (restored over the generated one in Step 1). It
|
|
437
|
+
already enforces, mechanically:
|
|
438
|
+
- `no-restricted-syntax` blocking `process.env` outside `src/lib/` and direct `fetch()`
|
|
439
|
+
outside the hooks layer
|
|
440
|
+
- `no-restricted-imports` enforcing the full layer-import matrix (and "no `@/hooks` in
|
|
441
|
+
pages")
|
|
442
|
+
- `no-console` at warning level
|
|
443
|
+
- `eslint-config-prettier` LAST so Prettier owns formatting
|
|
444
|
+
To change a boundary, edit the committed `eslint.config.mjs` — do NOT add rules to a
|
|
445
|
+
generated file that the next scaffold would overwrite.
|
|
446
|
+
|
|
447
|
+
## Step 8 — Write ADR-000
|
|
448
|
+
`docs/design-docs/decisions/ADR-000-infrastructure.md`
|
|
449
|
+
Document stack choices: Next.js, openapi-typescript, TanStack Query, Zustand, Vitest.
|
|
450
|
+
|
|
451
|
+
## Gate Check
|
|
452
|
+
```bash
|
|
453
|
+
npm run gate # typecheck + lint (flat config) + format:check + test:coverage (incl. tests/architecture/)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Commit
|
|
457
|
+
```bash
|
|
458
|
+
git add -A
|
|
459
|
+
git commit -m "chore(infra): phase 0 infrastructure setup
|
|
460
|
+
|
|
461
|
+
- Next.js + React + TypeScript strict (latest LTS, no version pins)
|
|
462
|
+
- TanStack Query + openapi-fetch (API contract layer)
|
|
463
|
+
- Zustand (UI state)
|
|
464
|
+
- shadcn/ui + Tailwind CSS
|
|
465
|
+
- Vitest + Playwright + MSW
|
|
466
|
+
- Security headers in next.config.ts
|
|
467
|
+
- React Compiler enabled
|
|
468
|
+
- Architecture structural tests
|
|
469
|
+
- ADR-000: infrastructure decisions"
|
|
470
|
+
git push origin main
|
|
471
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# /write-roadmap — Decompose a Product Frontend into an Ordered Spec Roadmap
|
|
2
|
+
|
|
3
|
+
Triggered by: `/write-roadmap` (optional arg: path to the product brief; else ask the human)
|
|
4
|
+
|
|
5
|
+
This is the altitude ABOVE specs. A real product frontend is many screens/flows across several
|
|
6
|
+
feature areas and personas — this skill plans that SET, ordered by dependency, and picks a
|
|
7
|
+
walking-skeleton first slice. It writes ONLY `docs/product-specs/ROADMAP.md`; it NEVER writes
|
|
8
|
+
spec files (that is `/write-spec`'s job, which owns spec numbering).
|
|
9
|
+
|
|
10
|
+
**Two inputs:** the same product **brief** the backend used, and the backend **`openapi.json`**
|
|
11
|
+
(the real contract). The brief says *what/for whom/which personas*; the OpenAPI says *what is
|
|
12
|
+
actually buildable*. The frontend roadmap is NOT a mirror of the backend roadmap — it
|
|
13
|
+
re-decomposes the product along **frontend axes** (screens, flows, personas, real-time).
|
|
14
|
+
|
|
15
|
+
## Step 1 — Gather Brief + Contract
|
|
16
|
+
Read the product brief (arg path, or ask the human to paste it). Confirm the backend contract is
|
|
17
|
+
present:
|
|
18
|
+
```bash
|
|
19
|
+
ls openapi.json src/types/api.generated.ts 2>/dev/null
|
|
20
|
+
```
|
|
21
|
+
If `openapi.json` exists but `api.generated.ts` doesn't (or may be stale), tell the human to run
|
|
22
|
+
`/api-sync` first so the roadmap can reference real endpoints. Do not proceed without a brief.
|
|
23
|
+
|
|
24
|
+
## Step 2 — Check for an Existing Roadmap (refresh, don't clobber)
|
|
25
|
+
```bash
|
|
26
|
+
ls docs/product-specs/ROADMAP.md 2>/dev/null && echo "EXISTS — refresh: keep created specs"
|
|
27
|
+
ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep "SPEC-" | sort
|
|
28
|
+
```
|
|
29
|
+
If a roadmap exists you are REFRESHING: preserve every row that already has a real Spec ID and
|
|
30
|
+
its Status; only add/reorder `NOT_STARTED` rows. Never renumber a created spec.
|
|
31
|
+
|
|
32
|
+
## Step 3 — Identify Feature Areas & Personas
|
|
33
|
+
From the brief + the OpenAPI tags, group the UI into a SMALL set of **feature areas** (screen
|
|
34
|
+
clusters), and note the **personas** (e.g. agent, supervisor, admin) — different personas are
|
|
35
|
+
often different surfaces over the same API. Two truths that make this NOT a 1:1 map of endpoints:
|
|
36
|
+
- One screen composes MANY endpoints (a dashboard pulls several domains + a live feed).
|
|
37
|
+
- Many endpoints have NO UI (machine-to-machine: ingestion, orchestration, internal jobs).
|
|
38
|
+
So the OpenAPI is the *contract*, not the roadmap — you decide the screens.
|
|
39
|
+
|
|
40
|
+
## Step 4 — Order Specs by Dependency + Map to Backend
|
|
41
|
+
Break each feature area into shippable frontend specs (one coherent screen/flow each). For every
|
|
42
|
+
spec record: name, feature area, the **backend SPEC/domain it `Implements`** (consumes), and which
|
|
43
|
+
*frontend* specs it depends on. Honor the unforgiving frontend order:
|
|
44
|
+
- **App shell + auth + role routing + design-system primitives come first** — everything depends on them.
|
|
45
|
+
- A **shared real-time client** (WebSocket/Socket.IO) is a dependency of every live screen — build it once, early.
|
|
46
|
+
- A frontend spec is only *buildable* when its `Implements` backend spec is **SHIPPED** — order accordingly.
|
|
47
|
+
Order the whole list topologically. Keep each spec small enough to become ONE execution plan.
|
|
48
|
+
|
|
49
|
+
## Step 5 — Pick the Walking-Skeleton First Slice
|
|
50
|
+
Choose the single thinnest spec that exercises the whole stack end-to-end — typically **login +
|
|
51
|
+
app shell + one auth-gated route that fetches via the generated client through a TanStack Query
|
|
52
|
+
hook and renders** (for a real-time product, also open the WS and render one live event). It must
|
|
53
|
+
depend on nothing. Mark exactly one spec as the skeleton. Build it first to prove routing + auth +
|
|
54
|
+
openapi-fetch + TanStack Query (+ the WS client) all wire up before any feature screen.
|
|
55
|
+
|
|
56
|
+
## Step 6 — Write / Refresh the Roadmap
|
|
57
|
+
Save to `docs/product-specs/ROADMAP.md` using the template in that file's header (self-documenting).
|
|
58
|
+
Fill: Feature Areas & Personas, the dependency-ordered Spec Roadmap table (with the `Implements`
|
|
59
|
+
column), and the Build Sequence Rationale.
|
|
60
|
+
- New specs get Spec ID `—` and Status `NOT_STARTED`.
|
|
61
|
+
- On refresh, keep existing Spec IDs/Statuses untouched; never renumber a created spec.
|
|
62
|
+
- Set the **Walking skeleton** header to the chosen first slice.
|
|
63
|
+
|
|
64
|
+
## Step 7 — Tell the Human
|
|
65
|
+
```
|
|
66
|
+
Roadmap written: docs/product-specs/ROADMAP.md
|
|
67
|
+
{N} frontend specs across {M} feature areas / {P} personas, dependency-ordered.
|
|
68
|
+
Walking skeleton (build first): {skeleton spec name}
|
|
69
|
+
|
|
70
|
+
Review the roadmap. When the order looks right:
|
|
71
|
+
1. Run /api-sync (ensure api.generated.ts matches the backend).
|
|
72
|
+
2. Run /write-spec — it reads ROADMAP.md and authors the next NOT_STARTED spec whose
|
|
73
|
+
`Implements` backend spec is SHIPPED (starting with the walking skeleton).
|
|
74
|
+
3. /write-plan → /build-layer down the list as backend capabilities ship.
|
|
75
|
+
|
|
76
|
+
/write-roadmap does NOT write spec files. It only plans the set.
|
|
77
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# /write-spec — Write a Product Spec
|
|
2
|
+
|
|
3
|
+
Triggered by: `/write-spec`
|
|
4
|
+
|
|
5
|
+
## Step 1 — Consult the Roadmap (if present)
|
|
6
|
+
```bash
|
|
7
|
+
ls docs/product-specs/ROADMAP.md 2>/dev/null
|
|
8
|
+
```
|
|
9
|
+
If it exists, read it. Pick the first row with Status `NOT_STARTED` — the **walking skeleton**
|
|
10
|
+
if nothing is built yet, otherwise the next dependency-unblocked row **whose `Implements`
|
|
11
|
+
(backend SPEC) is SHIPPED** (don't build UI for an endpoint the backend hasn't shipped). Use
|
|
12
|
+
that row's Name, Feature Area, Implements, and Depends-On to pre-fill the spec. If no roadmap
|
|
13
|
+
exists, proceed ad hoc (a single standalone spec).
|
|
14
|
+
|
|
15
|
+
## Step 2 — Get Next Spec Number
|
|
16
|
+
```bash
|
|
17
|
+
ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep "SPEC-" | sort | tail -1
|
|
18
|
+
```
|
|
19
|
+
(`ROADMAP.md` is CAPS, so this `grep SPEC-` is unaffected.)
|
|
20
|
+
|
|
21
|
+
## Step 3 — Write the Spec
|
|
22
|
+
Save to: `docs/product-specs/draft/SPEC-XXX-{slug}.md`
|
|
23
|
+
|
|
24
|
+
```markdown
|
|
25
|
+
# SPEC-XXX — {Feature Name}
|
|
26
|
+
|
|
27
|
+
**Status:** DRAFT
|
|
28
|
+
**Created:** YYYY-MM-DD
|
|
29
|
+
**Plan:** —
|
|
30
|
+
**Feature area:** — <!-- from ROADMAP.md row, if roadmap-driven -->
|
|
31
|
+
**Implements (backend SPEC):** — <!-- backend spec/domain this UI consumes -->
|
|
32
|
+
**Depends on:** — <!-- other frontend specs, from ROADMAP.md -->
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Problem Statement
|
|
37
|
+
|
|
38
|
+
## What We're Building
|
|
39
|
+
|
|
40
|
+
## Screens / Views
|
|
41
|
+
| Screen | Route | Description |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
|
|
44
|
+
## User Flows
|
|
45
|
+
{numbered steps for each key flow}
|
|
46
|
+
|
|
47
|
+
## API Endpoints Used
|
|
48
|
+
{list endpoints from the backend this feature consumes}
|
|
49
|
+
{These determine what will be in the hooks layer}
|
|
50
|
+
|
|
51
|
+
## Business Rules (Frontend)
|
|
52
|
+
{Validation rules, state machine UI, conditional rendering}
|
|
53
|
+
|
|
54
|
+
## Non-Functional Requirements
|
|
55
|
+
{Performance targets, accessibility requirements, responsive breakpoints}
|
|
56
|
+
|
|
57
|
+
## Out of Scope (v1)
|
|
58
|
+
|
|
59
|
+
## Acceptance Criteria
|
|
60
|
+
- [ ] {testable, specific}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Step 4 — Update Index + Roadmap
|
|
64
|
+
Add to `docs/product-specs/index.md`: `| SPEC-XXX | {Name} | DRAFT | — | YYYY-MM-DD |`
|
|
65
|
+
If this spec came from a `ROADMAP.md` row, flip that row: `Spec ID: — → SPEC-XXX` and
|
|
66
|
+
`Status: NOT_STARTED → DRAFT` (mirrors how `/write-plan` updates the spec + index together).
|
|
67
|
+
|
|
68
|
+
## Step 5 — Tell the Human
|
|
69
|
+
```
|
|
70
|
+
Spec written: docs/product-specs/draft/SPEC-XXX-{slug}.md
|
|
71
|
+
|
|
72
|
+
Review it. When satisfied:
|
|
73
|
+
1. Move to: docs/product-specs/ready/SPEC-XXX-{slug}.md
|
|
74
|
+
2. Change Status: DRAFT → READY
|
|
75
|
+
3. Run /write-plan
|
|
76
|
+
|
|
77
|
+
Claude will not create a plan until the spec is in ready/.
|
|
78
|
+
```
|