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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/cli.js +122 -0
  4. package/package.json +47 -0
  5. package/templates/express/.claude/CLAUDE.md +114 -0
  6. package/templates/express/.claude/agents/arch-validator.md +85 -0
  7. package/templates/express/.claude/agents/db-optimizer.md +72 -0
  8. package/templates/express/.claude/agents/doc-gardener.md +67 -0
  9. package/templates/express/.claude/agents/garbage-collector.md +94 -0
  10. package/templates/express/.claude/agents/gate-checker.md +176 -0
  11. package/templates/express/.claude/agents/reviewer.md +84 -0
  12. package/templates/express/.claude/agents/security-auditor.md +82 -0
  13. package/templates/express/.claude/hooks/post-write.sh +93 -0
  14. package/templates/express/.claude/rules/api.md +149 -0
  15. package/templates/express/.claude/rules/architecture.md +78 -0
  16. package/templates/express/.claude/rules/database.md +104 -0
  17. package/templates/express/.claude/rules/jobs.md +81 -0
  18. package/templates/express/.claude/rules/security.md +92 -0
  19. package/templates/express/.claude/rules/testing.md +97 -0
  20. package/templates/express/.claude/settings.json +35 -0
  21. package/templates/express/.claude/skills/00-infra-setup/SKILL.md +479 -0
  22. package/templates/express/.claude/skills/01-write-roadmap/SKILL.md +72 -0
  23. package/templates/express/.claude/skills/02-write-spec/SKILL.md +119 -0
  24. package/templates/express/.claude/skills/03-write-plan/SKILL.md +124 -0
  25. package/templates/express/.claude/skills/04-build-layer/SKILL.md +216 -0
  26. package/templates/express/.claude/skills/05-validate-layer/SKILL.md +43 -0
  27. package/templates/express/.claude/skills/06-push-layer/SKILL.md +59 -0
  28. package/templates/express/.claude/skills/07-layer-check/SKILL.md +62 -0
  29. package/templates/express/.claude/skills/08-garbage-collect/SKILL.md +86 -0
  30. package/templates/express/.claude/skills/09-doc-garden/SKILL.md +64 -0
  31. package/templates/express/.claude/skills/10-db-optimize/SKILL.md +96 -0
  32. package/templates/express/.claude/skills/11-load-test/SKILL.md +77 -0
  33. package/templates/express/.dockerignore +43 -0
  34. package/templates/express/.env.example +42 -0
  35. package/templates/express/.github/CODEOWNERS +22 -0
  36. package/templates/express/.github/dependabot.yml +48 -0
  37. package/templates/express/.github/pull_request_template.md +58 -0
  38. package/templates/express/.github/workflows/ci.yml +278 -0
  39. package/templates/express/.github/workflows/load-test.yml +41 -0
  40. package/templates/express/.gitleaks.toml +18 -0
  41. package/templates/express/.lintstagedrc.json +3 -0
  42. package/templates/express/.nvmrc +1 -0
  43. package/templates/express/.prettierrc +8 -0
  44. package/templates/express/.sequelizerc +8 -0
  45. package/templates/express/AGENTS.md +94 -0
  46. package/templates/express/ARCHITECTURE.md +161 -0
  47. package/templates/express/Dockerfile +40 -0
  48. package/templates/express/Makefile +79 -0
  49. package/templates/express/QUICKSTART.md +131 -0
  50. package/templates/express/docker-compose.yml +114 -0
  51. package/templates/express/docs/PLANS.md +111 -0
  52. package/templates/express/docs/QUALITY_SCORE.md +66 -0
  53. package/templates/express/docs/design-docs/core-beliefs.md +89 -0
  54. package/templates/express/docs/design-docs/decisions/ADR-000-infrastructure.md +70 -0
  55. package/templates/express/docs/design-docs/decisions/ADR-001-observability.md +82 -0
  56. package/templates/express/docs/design-docs/decisions/index.md +23 -0
  57. package/templates/express/docs/design-docs/index.md +11 -0
  58. package/templates/express/docs/design-docs/observability.md +194 -0
  59. package/templates/express/docs/design-docs/team-workflow.md +64 -0
  60. package/templates/express/docs/exec-plans/active/.gitkeep +0 -0
  61. package/templates/express/docs/exec-plans/completed/.gitkeep +0 -0
  62. package/templates/express/docs/exec-plans/tech-debt-tracker.md +31 -0
  63. package/templates/express/docs/generated/.gitkeep +0 -0
  64. package/templates/express/docs/product-specs/ROADMAP.md +49 -0
  65. package/templates/express/docs/product-specs/draft/.gitkeep +0 -0
  66. package/templates/express/docs/product-specs/index.md +21 -0
  67. package/templates/express/docs/product-specs/ready/.gitkeep +0 -0
  68. package/templates/express/eslint.config.mjs +94 -0
  69. package/templates/express/gitignore +36 -0
  70. package/templates/express/infra/monitoring/alloy/config.alloy +59 -0
  71. package/templates/express/jest.config.ts +61 -0
  72. package/templates/express/package.json +45 -0
  73. package/templates/express/scripts/openapi.export.ts +77 -0
  74. package/templates/express/tests/architecture/isolation.test.ts +51 -0
  75. package/templates/express/tests/architecture/layers.test.ts +83 -0
  76. package/templates/express/tests/integration/health.test.ts +40 -0
  77. package/templates/express/tests/integration/isolation.test.template.ts +88 -0
  78. package/templates/express/tests/load/smoke.js +46 -0
  79. package/templates/express/tests/load/soak.js +45 -0
  80. package/templates/express/tests/load/stress.js +44 -0
  81. package/templates/express/tests/unit/utils/response.util.test.ts +53 -0
  82. package/templates/express/tsconfig.build.json +8 -0
  83. package/templates/express/tsconfig.json +20 -0
  84. package/templates/fastapi/.claude/CLAUDE.md +122 -0
  85. package/templates/fastapi/.claude/agents/arch-validator.md +64 -0
  86. package/templates/fastapi/.claude/agents/db-optimizer.md +59 -0
  87. package/templates/fastapi/.claude/agents/doc-gardener.md +55 -0
  88. package/templates/fastapi/.claude/agents/garbage-collector.md +74 -0
  89. package/templates/fastapi/.claude/agents/gate-checker.md +133 -0
  90. package/templates/fastapi/.claude/agents/reviewer.md +74 -0
  91. package/templates/fastapi/.claude/agents/security-auditor.md +69 -0
  92. package/templates/fastapi/.claude/hooks/post-write.sh +65 -0
  93. package/templates/fastapi/.claude/rules/api.md +119 -0
  94. package/templates/fastapi/.claude/rules/architecture.md +84 -0
  95. package/templates/fastapi/.claude/rules/database.md +153 -0
  96. package/templates/fastapi/.claude/rules/jobs.md +85 -0
  97. package/templates/fastapi/.claude/rules/security.md +102 -0
  98. package/templates/fastapi/.claude/rules/testing.md +207 -0
  99. package/templates/fastapi/.claude/settings.json +50 -0
  100. package/templates/fastapi/.claude/skills/00-infra-setup/SKILL.md +311 -0
  101. package/templates/fastapi/.claude/skills/01-write-roadmap/SKILL.md +61 -0
  102. package/templates/fastapi/.claude/skills/02-write-spec/SKILL.md +82 -0
  103. package/templates/fastapi/.claude/skills/03-write-plan/SKILL.md +83 -0
  104. package/templates/fastapi/.claude/skills/04-build-layer/SKILL.md +170 -0
  105. package/templates/fastapi/.claude/skills/05-validate-layer/SKILL.md +20 -0
  106. package/templates/fastapi/.claude/skills/06-push-layer/SKILL.md +31 -0
  107. package/templates/fastapi/.claude/skills/07-layer-check/SKILL.md +51 -0
  108. package/templates/fastapi/.claude/skills/08-garbage-collect/SKILL.md +47 -0
  109. package/templates/fastapi/.claude/skills/09-doc-garden/SKILL.md +9 -0
  110. package/templates/fastapi/.claude/skills/10-db-optimize/SKILL.md +53 -0
  111. package/templates/fastapi/.claude/skills/11-load-test/SKILL.md +81 -0
  112. package/templates/fastapi/.dockerignore +30 -0
  113. package/templates/fastapi/.github/dependabot.yml +24 -0
  114. package/templates/fastapi/AGENTS.md +87 -0
  115. package/templates/fastapi/ARCHITECTURE.md +141 -0
  116. package/templates/fastapi/Makefile +42 -0
  117. package/templates/fastapi/README.md +179 -0
  118. package/templates/fastapi/docs/PLANS.md +26 -0
  119. package/templates/fastapi/docs/QUALITY_SCORE.md +26 -0
  120. package/templates/fastapi/docs/design-docs/core-beliefs.md +46 -0
  121. package/templates/fastapi/docs/design-docs/decisions/ADR-000-infrastructure.md +79 -0
  122. package/templates/fastapi/docs/design-docs/decisions/index.md +7 -0
  123. package/templates/fastapi/docs/design-docs/index.md +9 -0
  124. package/templates/fastapi/docs/exec-plans/active/.gitkeep +0 -0
  125. package/templates/fastapi/docs/exec-plans/completed/.gitkeep +0 -0
  126. package/templates/fastapi/docs/exec-plans/tech-debt-tracker.md +19 -0
  127. package/templates/fastapi/docs/generated/.gitkeep +0 -0
  128. package/templates/fastapi/docs/product-specs/ROADMAP.md +49 -0
  129. package/templates/fastapi/docs/product-specs/draft/.gitkeep +0 -0
  130. package/templates/fastapi/docs/product-specs/index.md +18 -0
  131. package/templates/fastapi/docs/product-specs/ready/.gitkeep +0 -0
  132. package/templates/fastapi/gitignore +46 -0
  133. package/templates/fastapi/scripts/gate.sh +68 -0
  134. package/templates/nestjs/.claude/CLAUDE.md +121 -0
  135. package/templates/nestjs/.claude/agents/db-optimizer.md +51 -0
  136. package/templates/nestjs/.claude/agents/doc-gardener.md +45 -0
  137. package/templates/nestjs/.claude/agents/garbage-collector.md +50 -0
  138. package/templates/nestjs/.claude/agents/gate-checker.md +127 -0
  139. package/templates/nestjs/.claude/agents/reviewer.md +68 -0
  140. package/templates/nestjs/.claude/agents/security-auditor.md +72 -0
  141. package/templates/nestjs/.claude/hooks/post-write.sh +91 -0
  142. package/templates/nestjs/.claude/rules/api.md +87 -0
  143. package/templates/nestjs/.claude/rules/architecture.md +104 -0
  144. package/templates/nestjs/.claude/rules/database.md +111 -0
  145. package/templates/nestjs/.claude/rules/security.md +110 -0
  146. package/templates/nestjs/.claude/rules/testing.md +145 -0
  147. package/templates/nestjs/.claude/settings.json +33 -0
  148. package/templates/nestjs/.claude/skills/00-infra-setup/SKILL.md +222 -0
  149. package/templates/nestjs/.claude/skills/01-write-spec/SKILL.md +25 -0
  150. package/templates/nestjs/.claude/skills/02-write-plan/SKILL.md +18 -0
  151. package/templates/nestjs/.claude/skills/03-build-layer/SKILL.md +242 -0
  152. package/templates/nestjs/.claude/skills/04-validate-layer/SKILL.md +4 -0
  153. package/templates/nestjs/.claude/skills/05-push-layer/SKILL.md +6 -0
  154. package/templates/nestjs/.claude/skills/06-layer-check/SKILL.md +12 -0
  155. package/templates/nestjs/.claude/skills/07-garbage-collect/SKILL.md +2 -0
  156. package/templates/nestjs/.claude/skills/08-doc-garden/SKILL.md +4 -0
  157. package/templates/nestjs/.claude/skills/09-api-sync/SKILL.md +36 -0
  158. package/templates/nestjs/.claude/skills/10-load-test/SKILL.md +29 -0
  159. package/templates/nestjs/.env.example +11 -0
  160. package/templates/nestjs/AGENTS.md +88 -0
  161. package/templates/nestjs/ARCHITECTURE.md +192 -0
  162. package/templates/nestjs/docs/PLANS.md +22 -0
  163. package/templates/nestjs/docs/QUALITY_SCORE.md +27 -0
  164. package/templates/nestjs/docs/design-docs/core-beliefs.md +18 -0
  165. package/templates/nestjs/docs/design-docs/decisions/ADR-000-infrastructure.md +58 -0
  166. package/templates/nestjs/docs/design-docs/decisions/index.md +7 -0
  167. package/templates/nestjs/docs/design-docs/index.md +7 -0
  168. package/templates/nestjs/docs/exec-plans/active/.gitkeep +0 -0
  169. package/templates/nestjs/docs/exec-plans/completed/.gitkeep +0 -0
  170. package/templates/nestjs/docs/exec-plans/tech-debt-tracker.md +16 -0
  171. package/templates/nestjs/docs/generated/.gitkeep +0 -0
  172. package/templates/nestjs/docs/product-specs/draft/.gitkeep +0 -0
  173. package/templates/nestjs/docs/product-specs/index.md +7 -0
  174. package/templates/nestjs/docs/product-specs/ready/.gitkeep +0 -0
  175. package/templates/nestjs/gitignore +8 -0
  176. package/templates/nextjs/.claude/CLAUDE.md +139 -0
  177. package/templates/nextjs/.claude/agents/arch-validator.md +66 -0
  178. package/templates/nextjs/.claude/agents/contract-checker.md +78 -0
  179. package/templates/nextjs/.claude/agents/doc-gardener.md +52 -0
  180. package/templates/nextjs/.claude/agents/garbage-collector.md +61 -0
  181. package/templates/nextjs/.claude/agents/gate-checker.md +129 -0
  182. package/templates/nextjs/.claude/agents/perf-auditor.md +109 -0
  183. package/templates/nextjs/.claude/agents/reviewer.md +79 -0
  184. package/templates/nextjs/.claude/agents/security-auditor.md +50 -0
  185. package/templates/nextjs/.claude/hooks/post-write.sh +121 -0
  186. package/templates/nextjs/.claude/rules/api-contract.md +166 -0
  187. package/templates/nextjs/.claude/rules/architecture.md +99 -0
  188. package/templates/nextjs/.claude/rules/components.md +122 -0
  189. package/templates/nextjs/.claude/rules/security.md +122 -0
  190. package/templates/nextjs/.claude/rules/testing.md +187 -0
  191. package/templates/nextjs/.claude/scripts/infra-setup.sh +550 -0
  192. package/templates/nextjs/.claude/settings.json +33 -0
  193. package/templates/nextjs/.claude/skills/00-infra-setup/SKILL.md +471 -0
  194. package/templates/nextjs/.claude/skills/01-write-roadmap/SKILL.md +77 -0
  195. package/templates/nextjs/.claude/skills/02-write-spec/SKILL.md +78 -0
  196. package/templates/nextjs/.claude/skills/03-write-plan/SKILL.md +91 -0
  197. package/templates/nextjs/.claude/skills/04-build-layer/SKILL.md +141 -0
  198. package/templates/nextjs/.claude/skills/05-validate-layer/SKILL.md +13 -0
  199. package/templates/nextjs/.claude/skills/06-push-layer/SKILL.md +20 -0
  200. package/templates/nextjs/.claude/skills/07-layer-check/SKILL.md +28 -0
  201. package/templates/nextjs/.claude/skills/08-garbage-collect/SKILL.md +14 -0
  202. package/templates/nextjs/.claude/skills/09-doc-garden/SKILL.md +12 -0
  203. package/templates/nextjs/.claude/skills/10-api-sync/SKILL.md +50 -0
  204. package/templates/nextjs/.claude/skills/11-perf-budget/SKILL.md +51 -0
  205. package/templates/nextjs/.dockerignore +25 -0
  206. package/templates/nextjs/.gitattributes +10 -0
  207. package/templates/nextjs/.github/CODEOWNERS +24 -0
  208. package/templates/nextjs/.github/dependabot.yml +31 -0
  209. package/templates/nextjs/.github/pull_request_template.md +59 -0
  210. package/templates/nextjs/.github/workflows/ci.yml +143 -0
  211. package/templates/nextjs/.github/workflows/lighthouse.yml +54 -0
  212. package/templates/nextjs/.github/workflows/load-test.yml +54 -0
  213. package/templates/nextjs/.husky/pre-commit +1 -0
  214. package/templates/nextjs/.lintstagedrc.json +4 -0
  215. package/templates/nextjs/.mcp.json +8 -0
  216. package/templates/nextjs/.prettierignore +13 -0
  217. package/templates/nextjs/.prettierrc +9 -0
  218. package/templates/nextjs/AGENTS.md +88 -0
  219. package/templates/nextjs/ARCHITECTURE.md +165 -0
  220. package/templates/nextjs/Dockerfile +38 -0
  221. package/templates/nextjs/Makefile +50 -0
  222. package/templates/nextjs/QUICKSTART.md +128 -0
  223. package/templates/nextjs/docs/PLANS.md +23 -0
  224. package/templates/nextjs/docs/QUALITY_SCORE.md +27 -0
  225. package/templates/nextjs/docs/design-docs/core-beliefs.md +43 -0
  226. package/templates/nextjs/docs/design-docs/decisions/ADR-000-infrastructure.md +72 -0
  227. package/templates/nextjs/docs/design-docs/decisions/index.md +7 -0
  228. package/templates/nextjs/docs/design-docs/index.md +7 -0
  229. package/templates/nextjs/docs/design-docs/team-workflow.md +66 -0
  230. package/templates/nextjs/docs/exec-plans/active/.gitkeep +0 -0
  231. package/templates/nextjs/docs/exec-plans/completed/.gitkeep +0 -0
  232. package/templates/nextjs/docs/exec-plans/tech-debt-tracker.md +19 -0
  233. package/templates/nextjs/docs/generated/.gitkeep +0 -0
  234. package/templates/nextjs/docs/product-specs/ROADMAP.md +59 -0
  235. package/templates/nextjs/docs/product-specs/draft/.gitkeep +0 -0
  236. package/templates/nextjs/docs/product-specs/index.md +18 -0
  237. package/templates/nextjs/docs/product-specs/ready/.gitkeep +0 -0
  238. package/templates/nextjs/eslint.config.mjs +150 -0
  239. package/templates/nextjs/gitignore +35 -0
  240. package/templates/nextjs/lighthouserc.js +37 -0
@@ -0,0 +1,479 @@
1
+ ---
2
+ name: 00-infra-setup
3
+ description: /infra-setup — Phase 0 Infrastructure Setup
4
+ verified: 2026-06-19
5
+ libraries: [express, sequelize, sequelize-typescript, pg, zod, pino, jose, argon2, bullmq, ioredis, helmet, cors, express-rate-limit, "@asteasolutions/zod-to-openapi", "eslint-plugin-boundaries", "@opentelemetry/sdk-node", "@opentelemetry/auto-instrumentations-node", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/exporter-metrics-otlp-http", "@opentelemetry/sdk-metrics", "@opentelemetry/api"]
6
+ source: https://www.npmjs.com
7
+ staleness-threshold-days: 60
8
+ ---
9
+
10
+ # /infra-setup — Phase 0 Infrastructure Setup
11
+
12
+ Triggered by: `/infra-setup`
13
+ Run ONCE when starting a new project. Never run again.
14
+
15
+ If `src/` directory already exists → abort and tell the human "Infra already set up".
16
+
17
+ ---
18
+
19
+ ## What Gets Built
20
+
21
+ Run each step in order. Gate-check after all steps complete.
22
+
23
+ ### Step 1 — Install All Dependencies
24
+
25
+ **Note:** Config files (`package.json`, `tsconfig.json`, `jest.config.ts`, `docker-compose.yml`, `Dockerfile`, `.dockerignore`, `.env.example`, `eslint.config.mjs`, `.prettierrc`, `.sequelizerc`, `.lintstagedrc.json`, `.nvmrc`) already exist in the template. So do the committed CI/automation files (`.github/workflows/ci.yml`, `.github/workflows/load-test.yml`, `.github/dependabot.yml`) and the OpenAPI exporter (`scripts/openapi.export.ts`). No need to create any of these.
26
+
27
+ Run npm install to get all dependencies:
28
+ ```bash
29
+ # Production dependencies (no version pinning — installs latest LTS)
30
+ npm install \
31
+ express \
32
+ sequelize@6 \
33
+ sequelize-typescript \
34
+ pg \
35
+ pg-hstore \
36
+ zod \
37
+ pino \
38
+ jose \
39
+ argon2 \
40
+ helmet \
41
+ cors \
42
+ express-rate-limit \
43
+ rate-limit-redis \
44
+ bullmq \
45
+ ioredis \
46
+ uuid \
47
+ compression \
48
+ dotenv \
49
+ reflect-metadata \
50
+ @asteasolutions/zod-to-openapi \
51
+ @opentelemetry/api \
52
+ @opentelemetry/sdk-node \
53
+ @opentelemetry/auto-instrumentations-node \
54
+ @opentelemetry/exporter-trace-otlp-http \
55
+ @opentelemetry/exporter-metrics-otlp-http \
56
+ @opentelemetry/sdk-metrics
57
+
58
+ # OTel note: keep every @opentelemetry/* package on ONE release line (the experimental
59
+ # packages share a 0.2xx version; the SDK pulls the matching stable trace/metrics cores).
60
+ # Do NOT bump @opentelemetry/api past the SDK's peer range. Logs are NOT sent over OTLP —
61
+ # they ship as pino JSON via Grafana Alloy (see docs/design-docs/observability.md), so no
62
+ # logs exporter / sdk-logs is installed.
63
+
64
+ # Sequelize is PINNED to v6 (sequelize@6) on purpose: sequelize-typescript v2 only supports
65
+ # Sequelize 6. Without the pin, "latest" could resolve Sequelize 7 at setup time and break the
66
+ # decorator models. Dependabot keeps the sequelize / sequelize-typescript / sequelize-cli trio
67
+ # moving together (see .github/dependabot.yml).
68
+
69
+ # Dev dependencies
70
+ npm install -D \
71
+ typescript@5 \
72
+ tsx \
73
+ ts-jest \
74
+ jest \
75
+ supertest \
76
+ @types/express \
77
+ @types/node \
78
+ @types/cors \
79
+ @types/compression \
80
+ @types/supertest \
81
+ @types/uuid \
82
+ @types/jest \
83
+ eslint \
84
+ @typescript-eslint/eslint-plugin \
85
+ @typescript-eslint/parser \
86
+ eslint-plugin-boundaries \
87
+ eslint-import-resolver-typescript \
88
+ prettier \
89
+ eslint-config-prettier \
90
+ madge \
91
+ husky \
92
+ lint-staged \
93
+ pino-pretty \
94
+ yaml \
95
+ sequelize-cli
96
+ ```
97
+
98
+ > `eslint-plugin-boundaries` powers the mechanical layer-import enforcement in `eslint.config.mjs`
99
+ > (replaces the old structural-test-only check). `eslint-import-resolver-typescript` is required with
100
+ > it — boundaries must resolve the project's `.js`-extension ESM imports to .ts files, or it classifies
101
+ > nothing and enforces nothing. **`typescript` is pinned to 5.x**: TypeScript 6.0 changed default
102
+ > `@types` auto-inclusion and breaks the template's `tsconfig` (node globals go unresolved across
103
+ > `src/`); pin to 5.x and revisit (add `"types": ["node"]`) when migrating to TS 6.
104
+ > `@asteasolutions/zod-to-openapi` + `yaml` back the
105
+ > committed `scripts/openapi.export.ts` contract generator. `http-graceful-shutdown` is intentionally
106
+ > **not** installed — `server.ts` hand-rolls SIGTERM/SIGINT shutdown (Step 5), so the dep is redundant.
107
+
108
+ ### Step 2 — Directory Structure
109
+ ```bash
110
+ mkdir -p \
111
+ src/{types,config,models,repo,services,runtime/{routes/v1,middleware,workers},providers/auth,utils} \
112
+ tests/{architecture,unit/{services,repo,utils},integration,load} \
113
+ db/{migrations,seeders} \
114
+ docs/{product-specs/{draft,ready},exec-plans/{active,completed},design-docs/decisions,generated} \
115
+ infra/monitoring \
116
+ .github/workflows
117
+ ```
118
+
119
+ ### Step 3 — Environment & Config Files
120
+
121
+ Create `src/config/env.ts` — Zod-validated environment variables, `process.exit(1)` on validation failure.
122
+ Create `src/config/constants.ts` — app-wide constants (VALID_TRANSITIONS, etc.).
123
+ Create `src/config/timeouts.ts` — `DB_QUERY_MS=5000`, `EXTERNAL_API_MS=10000`, `REDIS_MS=1000`.
124
+ Create `src/config/database.ts` — Sequelize instance with pool config + timeouts. Do **not** pass
125
+ `models: [...]` here — that imports the models layer into config (a boundary violation). Register
126
+ models in `src/models/index.ts` via `sequelize.addModels([...])` (import `reflect-metadata` first
127
+ there): `models → config` is the only legal registration edge — neither config nor runtime may
128
+ import the models layer.
129
+ Create `src/config/logger.ts` — pino + trace-correlation mixin + guarded pretty transport.
130
+ Create `src/config/tracing.ts` — `withSpan()` helper (uses only `@opentelemetry/api`).
131
+ Create `src/config/metrics.ts` — counter/histogram helpers (uses only `@opentelemetry/api`).
132
+
133
+ > **Why logger/tracing/metrics live in Config, not Providers:** the layer rules let *every*
134
+ > domain layer import Config but forbid Service/Repo from importing Providers. These three helpers
135
+ > use only the `@opentelemetry/api` global, which is a safe no-op until the SDK starts — so they
136
+ > belong in Config. The `NodeSDK` itself stays in `providers/telemetry.ts` (Step 4).
137
+
138
+ **`src/config/env.ts`** — the OTEL block (append to the existing schema). Use `z.stringbool()`
139
+ for boolean flags — **never** `z.coerce.boolean()` (it turns the string `"false"` into `true`):
140
+ ```typescript
141
+ // ...inside the zod schema object...
142
+ OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url().optional(), // blank/unset ⇒ SDK no-ops
143
+ OTEL_SERVICE_NAME: z.string().default('app'),
144
+ OTEL_METRIC_EXPORT_INTERVAL_MS: z.coerce.number().int().positive().default(10000),
145
+ APP_VERSION: z.string().default('0.0.1'),
146
+ // example boolean flag done right:
147
+ // OTEL_DEBUG: z.stringbool().default(false),
148
+ ```
149
+
150
+ **`src/config/logger.ts`**:
151
+ ```typescript
152
+ import pino from 'pino'
153
+ import { trace } from '@opentelemetry/api'
154
+ import { createRequire } from 'node:module'
155
+ import { env } from './env.js'
156
+
157
+ const require = createRequire(import.meta.url)
158
+ const tryResolve = (m: string): boolean => { try { require.resolve(m); return true } catch { return false } }
159
+ // pino-pretty is a devDependency — absent after `npm ci --omit=dev`. Guard so prod logs JSON.
160
+ const usePretty = env.NODE_ENV !== 'production' && tryResolve('pino-pretty')
161
+
162
+ export const logger = pino({
163
+ level: env.LOG_LEVEL,
164
+ // Deterministic trace↔log correlation, independent of auto-instrumentation patching.
165
+ mixin() {
166
+ const span = trace.getActiveSpan()
167
+ if (!span) return {}
168
+ const { traceId, spanId } = span.spanContext()
169
+ return { trace_id: traceId, span_id: spanId }
170
+ },
171
+ ...(usePretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),
172
+ })
173
+ ```
174
+
175
+ **`src/config/tracing.ts`**:
176
+ ```typescript
177
+ import { trace, SpanStatusCode, type Attributes } from '@opentelemetry/api'
178
+
179
+ const tracer = trace.getTracer('app')
180
+
181
+ /** Wrap a unit of work in a span. No-op (zero overhead) when no SDK is started. */
182
+ export async function withSpan<T>(name: string, attrs: Attributes, fn: () => Promise<T>): Promise<T> {
183
+ return tracer.startActiveSpan(name, async (span) => {
184
+ span.setAttributes(attrs)
185
+ try {
186
+ return await fn()
187
+ } catch (err) {
188
+ span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message })
189
+ span.recordException(err as Error)
190
+ throw err
191
+ } finally {
192
+ span.end()
193
+ }
194
+ })
195
+ }
196
+ ```
197
+
198
+ **`src/config/metrics.ts`**:
199
+ ```typescript
200
+ import { metrics, type Attributes } from '@opentelemetry/api'
201
+
202
+ const meter = metrics.getMeter('app')
203
+ // Lazily created instruments; all are no-ops until an SDK MeterProvider is registered.
204
+ const counters = new Map<string, ReturnType<typeof meter.createCounter>>()
205
+
206
+ export function increment(name: string, attrs: Attributes = {}, by = 1): void {
207
+ let c = counters.get(name)
208
+ if (!c) { c = meter.createCounter(name); counters.set(name, c) }
209
+ c.add(by, attrs)
210
+ }
211
+
212
+ export const httpRequestDuration = meter.createHistogram('http.server.duration', { unit: 'ms' })
213
+ ```
214
+
215
+ ### Step 4 — Providers
216
+
217
+ > The logger now lives in `src/config/logger.ts` (Step 3), **not** here — services may import
218
+ > Config but not Providers.
219
+
220
+ Create `src/providers/redis.ts` — ioredis singleton with retry.
221
+ Create `src/providers/telemetry.ts` — OTel `NodeSDK` init (boot-only, template below).
222
+ Create `src/providers/auth/jwt.ts` — jose sign + verify + revocation.
223
+ Create `src/providers/auth/middleware.ts` — requireAuth.
224
+ Create `src/providers/auth/rbac.ts` — requirePermission.
225
+ Create `src/providers/featureFlags.ts` — Redis-backed flag reader with static defaults
226
+ (makes the "feature flags" provider referenced in `ARCHITECTURE.md` real, not aspirational).
227
+
228
+ **`src/providers/featureFlags.ts`** — reads a Redis hash override, falls back to a static default
229
+ map in `config/constants.ts`. Uses Redis (a sibling provider) + Config only — never `process.env`
230
+ (that rule belongs to `env.ts`), so it stays layer-clean:
231
+ ```typescript
232
+ import { redis } from './redis.js'
233
+ import { FEATURE_FLAG_DEFAULTS } from '../config/constants.js'
234
+
235
+ /** True when the flag is enabled. Redis override wins; otherwise the compiled-in default. */
236
+ export async function isEnabled(flag: keyof typeof FEATURE_FLAG_DEFAULTS): Promise<boolean> {
237
+ const override = await redis.hget('feature_flags', flag)
238
+ if (override !== null) return override === 'true'
239
+ return FEATURE_FLAG_DEFAULTS[flag] ?? false
240
+ }
241
+ ```
242
+ Add `export const FEATURE_FLAG_DEFAULTS = {} as const` to `config/constants.ts` (products add flags).
243
+
244
+ **`src/providers/telemetry.ts`** — boot-only. It must **not** statically import any instrumented
245
+ module (express/pg/ioredis/pino/the logger); doing so would load them before `sdk.start()` patches
246
+ them. Gated on `OTEL_EXPORTER_OTLP_ENDPOINT` so it is a clean no-op in tests/CI:
247
+ ```typescript
248
+ import { NodeSDK } from '@opentelemetry/sdk-node'
249
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
250
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
251
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
252
+ import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
253
+ import { env } from '../config/env.js'
254
+
255
+ let sdk: NodeSDK | undefined
256
+
257
+ export async function startTelemetry(): Promise<void> {
258
+ const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT
259
+ if (!endpoint) return // no-op when unset (tests, CI, local-without-backend)
260
+
261
+ // service.name / service.version are picked up automatically from the standard
262
+ // OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES env vars (set in .env / docker-compose) —
263
+ // no manual Resource needed, so no @opentelemetry/resources / semantic-conventions imports.
264
+ sdk = new NodeSDK({
265
+ // Traces → collector. Note the plural option names and the /v1/* path suffixes.
266
+ traceExporter: new OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),
267
+ metricReaders: [
268
+ new PeriodicExportingMetricReader({
269
+ exporter: new OTLPMetricExporter({ url: `${endpoint}/v1/metrics` }),
270
+ exportIntervalMillis: env.OTEL_METRIC_EXPORT_INTERVAL_MS,
271
+ }),
272
+ ],
273
+ instrumentations: [getNodeAutoInstrumentations()],
274
+ })
275
+ await sdk.start()
276
+ }
277
+
278
+ export async function shutdownTelemetry(): Promise<void> {
279
+ await sdk?.shutdown()
280
+ }
281
+ ```
282
+
283
+ ### Step 5 — Runtime Layer
284
+ Create `src/runtime/middleware/helmet.ts` — all 7 headers.
285
+ Create `src/runtime/middleware/cors.ts` — allowlist from env.
286
+ Create `src/runtime/middleware/rateLimiter.ts` — 3 tiers with Redis store.
287
+ Create `src/runtime/middleware/requestId.ts` — X-Request-ID propagation.
288
+ Create `src/runtime/middleware/errorHandler.ts` — sanitised errors + logging.
289
+ Create `src/runtime/middleware/idempotency.ts` — Redis-backed idempotency for mutations
290
+ (makes the `security-auditor` A04 "idempotency keys on mutation endpoints" check real).
291
+ Create `src/runtime/openapi.ts` — the OpenAPI registry that `scripts/openapi.export.ts` reads.
292
+ Create `src/runtime/routes/v1/health.route.ts` — /health + /ready (DB ping + Redis ping).
293
+ Create `src/runtime/app.ts` — Express app with all middleware mounted.
294
+ Create `src/runtime/server.ts` — **boot telemetry first**, then listen + SIGTERM graceful shutdown.
295
+
296
+ **`src/runtime/middleware/idempotency.ts`** — apply to mutating routes. On the first request with an
297
+ `Idempotency-Key` header it stores the response keyed by `{userId}:{method}:{path}:{key}`; a replay
298
+ returns the cached response with `Idempotent-Replay: true`; a still-in-flight key returns `409 CONFLICT`.
299
+ Skips non-mutating methods and requests with no key. Keep it ≤ 400 lines; back it with the `redis` provider.
300
+
301
+ **`src/runtime/openapi.ts`** — the contract registry. Routes register their Zod schemas + paths here so
302
+ `npm run openapi:export` emits `docs/generated/openapi.{json,yaml}` (the frontend's `openapi-fetch`
303
+ source of truth). Call `extendZodWithOpenApi(z)` once before any schema uses `.openapi(...)`:
304
+ ```typescript
305
+ import { OpenAPIRegistry, extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'
306
+ import { z } from 'zod'
307
+
308
+ extendZodWithOpenApi(z) // enables `.openapi()` metadata on Zod schemas (call once, at import time)
309
+
310
+ export const registry = new OpenAPIRegistry()
311
+ // Each route registers itself, e.g.:
312
+ // registry.registerPath({ method: 'post', path: '/applications', request: {...}, responses: {...} })
313
+ // `scripts/openapi.export.ts` imports this `registry` and generates the document.
314
+ ```
315
+
316
+ **`src/runtime/server.ts`** — the ESM ordering fix. Auto-instrumentation can only patch a library
317
+ if the SDK starts **before** that library is first loaded. `server.ts` statically imports *only*
318
+ telemetry, starts it, then dynamically imports the app (which pulls in express/pg/redis/pino):
319
+ ```typescript
320
+ import { startTelemetry, shutdownTelemetry } from '../providers/telemetry.js'
321
+ import { env } from '../config/env.js'
322
+ import { logger } from '../config/logger.js'
323
+
324
+ async function main(): Promise<void> {
325
+ await startTelemetry() // 1. patch instrumented libs before they load
326
+ const { app } = await import('./app.js') // 2. dynamic import AFTER sdk.start()
327
+
328
+ const server = app.listen(env.PORT, () => logger.info({ event: 'server.start', port: env.PORT }))
329
+
330
+ const shutdown = async (signal: string): Promise<void> => {
331
+ logger.info({ event: 'server.shutdown', signal })
332
+ server.close()
333
+ await shutdownTelemetry() // flush spans/metrics before exit
334
+ process.exit(0)
335
+ }
336
+ process.on('SIGTERM', () => void shutdown('SIGTERM'))
337
+ process.on('SIGINT', () => void shutdown('SIGINT'))
338
+ }
339
+
340
+ void main()
341
+ ```
342
+
343
+ > **Why dynamic import (not an `--import` flag):** the instrumented libs (express/pg/ioredis/pino)
344
+ > are CommonJS, so the SDK's require-hook patches them with no ESM loader hook needed — *as long as*
345
+ > `startTelemetry()` runs before the first `require`. The `await import('./app.js')` guarantees that
346
+ > ordering and works identically under `tsx` (dev) and `node dist/...` (prod), so no NODE_OPTIONS or
347
+ > start-command divergence. `dev`/`start` scripts in `package.json` stay unchanged.
348
+ Create `src/utils/response.util.ts` — `ok()` and `err()` envelope helpers.
349
+ Create `src/utils/uuid.util.ts` — `newId()` → uuidv7.
350
+
351
+ ### Step 6 — Testing Scaffolds
352
+
353
+ **Note:** These already exist in the template — do NOT recreate them:
354
+ - `jest.config.ts` (per-layer coverage thresholds)
355
+ - `tests/architecture/layers.test.ts` (working layer-boundary structural tests)
356
+ - `tests/unit/utils/response.util.test.ts` (utils-layer example — pins the envelope contract)
357
+ - `tests/integration/health.test.ts` (route example using supertest)
358
+ - `tests/integration/isolation.test.template.ts` (copy per feature for cross-user isolation)
359
+
360
+ Create `tests/integration/setup.ts` — test DB lifecycle hooks (beforeAll/afterAll, truncate between tests, auth-token + createUser helpers referenced by the isolation template).
361
+
362
+ ### Step 7 — GitHub Actions CI
363
+
364
+ **Do NOT create `ci.yml` — it already ships, committed, in the template.** `.github/workflows/ci.yml`
365
+ is a real, pinned, product-agnostic pipeline (not generated prose). It guards on whether `src/` exists,
366
+ so it stayed green on the bare template and switches on automatically the moment this step's code lands.
367
+
368
+ What it runs once `src/` exists:
369
+ - **quality**: `typecheck` → `lint` (boundaries-enforced) → `check:circular` → `test:coverage`
370
+ (thresholds from `jest.config.ts`) → `build` → OpenAPI drift check (`openapi:export` + `git diff`)
371
+ - **secret-scan**: gitleaks (runs always, full history)
372
+ - **audit**: `npm audit --audit-level=high`
373
+ - **container**: Docker build → Trivy image scan (CRITICAL/HIGH) → SBOM (SPDX)
374
+ - **perf-smoke**: boots compose + runs `k6 smoke` against the p95/error budget (push-to-main only)
375
+
376
+ Also already shipped: `.github/workflows/load-test.yml` (manual k6 stress/soak), `.github/dependabot.yml`
377
+ (grouped npm + actions updates), `.github/pull_request_template.md`, `.github/CODEOWNERS`. **infra-setup
378
+ generates no workflow files.**
379
+
380
+ > Verify, don't author: after this step, push a branch and confirm the `quality` job goes from skipped
381
+ > to green. If a check fails, fix the source — never weaken the committed `ci.yml`.
382
+
383
+ > **Observability in CI:** leave `OTEL_EXPORTER_OTLP_ENDPOINT` **unset** in the test job so the
384
+ > SDK no-ops — never start the LGTM stack in CI. The `config/` helpers are exercised against the
385
+ > `@opentelemetry/api` no-op (fast, no infra). Backends are only run locally in dev.
386
+
387
+ ### Step 8 — Git Hooks
388
+ ```bash
389
+ npm run prepare # init husky
390
+ # pre-commit: lint-staged + a local secret scan (gitleaks runs only if installed — the CI
391
+ # secret-scan job is the hard gate; this is a fast local pre-flight, never a blocker if absent).
392
+ npx husky add .husky/pre-commit 'npx lint-staged && (command -v gitleaks >/dev/null && gitleaks protect --staged --redact || true)'
393
+ npx husky add .husky/pre-push "npm test"
394
+ ```
395
+
396
+ **Note:** `.lintstagedrc.json` already exists in template (ESLint + Prettier on staged TS files).
397
+
398
+ ### Step 9 — Write ADRs
399
+ Create `docs/design-docs/decisions/ADR-000-infrastructure.md`:
400
+ - Document stack choices: why Express, why Sequelize, why jose (not jsonwebtoken), why argon2 (not bcrypt), why BullMQ
401
+ - Document architecture choices: why layered architecture, why Zod at repo boundary, why cursor pagination
402
+
403
+ `docs/design-docs/decisions/ADR-001-observability.md` and `docs/design-docs/observability.md`
404
+ already ship in the template — do **not** recreate them. They document the telemetry decisions
405
+ (otel-lgtm backend, logs-via-Alloy, helpers-in-Config) and the see-it/debug-it walkthrough.
406
+
407
+ ---
408
+
409
+ ## Gate Check
410
+
411
+ Run these checks to verify infra setup:
412
+
413
+ ```bash
414
+ # 0. The one-command deterministic gate (typecheck + lint + circular + arch tests).
415
+ # This is the same gate humans and CI run — prefer it over the individual steps below.
416
+ npm run gate
417
+
418
+ # 0b. OpenAPI contract generates cleanly (writes docs/generated/openapi.{json,yaml}).
419
+ npm run openapi:export
420
+
421
+ # 1. TypeScript compiles clean
422
+ npx tsc --noEmit
423
+
424
+ # 2. ESLint passes with zero warnings (now includes boundaries/dependencies layer checks)
425
+ npx eslint src/ --max-warnings=0
426
+
427
+ # 3. Architecture tests pass
428
+ npx jest tests/architecture/ --no-coverage
429
+
430
+ # 4. Health endpoint responds 200
431
+ npm run dev &
432
+ sleep 3
433
+ curl -f http://localhost:3000/health || echo "FAIL: health check"
434
+ pkill -f "node.*tsx"
435
+
436
+ # 5. No file exceeds 400 lines
437
+ find src/ -name "*.ts" -exec wc -l {} + | awk '$1 > 400 { print "FAIL:", $2, "has", $1, "lines" }'
438
+
439
+ # 6. Telemetry is a clean no-op when the endpoint is unset (the CI/test default).
440
+ # With OTEL_EXPORTER_OTLP_ENDPOINT blank, the app must boot and /health must still pass.
441
+ OTEL_EXPORTER_OTLP_ENDPOINT= npm run dev &
442
+ sleep 3
443
+ curl -f http://localhost:3000/health || echo "FAIL: telemetry no-op boot"
444
+ pkill -f "node.*tsx"
445
+
446
+ # 7. (optional, dev only) End-to-end telemetry — see docs/design-docs/observability.md:
447
+ # docker compose up -d lgtm alloy → set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 →
448
+ # hit /health a few times → confirm a Tempo trace + Prometheus metrics + correlated Loki logs
449
+ # in Grafana at http://localhost:3001 (admin/admin).
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Commit
455
+
456
+ ```bash
457
+ git init
458
+ git add -A
459
+ git commit -m "chore(infra): phase 0 infrastructure setup
460
+
461
+ - Express + TypeScript + Sequelize (latest LTS)
462
+ - Security middleware: helmet, cors, rate-limit (Redis store), idempotency keys (mutations)
463
+ - Feature-flags provider (Redis-backed, static defaults in config)
464
+ - Pino structured logging (config/logger.ts) with trace-correlation mixin
465
+ - OpenTelemetry: traces + metrics via NodeSDK (boot-first), withSpan/metrics helpers in config/
466
+ - Observability backend: grafana/otel-lgtm + Alloy log shipping (docker-compose)
467
+ - BullMQ + ioredis for background jobs
468
+ - Health checks: /health + /ready (DB + Redis ping)
469
+ - Graceful SIGTERM shutdown (flushes telemetry)
470
+ - OpenAPI contract export (zod-to-openapi) → docs/generated/openapi.{json,yaml}
471
+ - Layer-import enforcement: eslint-plugin-boundaries + structural tests + madge
472
+ - Docker multi-stage build + .dockerignore + docker-compose (app + postgres + redis + lgtm + alloy)
473
+ - Git hooks: pre-commit (lint-staged + gitleaks), pre-push (test)
474
+ - ADR-000: infrastructure decisions; ADR-001: observability decisions
475
+
476
+ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
477
+
478
+ git push origin main
479
+ ```
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: 01-write-roadmap
3
+ description: /write-roadmap — Decompose a Product into an Ordered Spec Roadmap
4
+ verified: 2026-06-19
5
+ libraries: []
6
+ source: docs/product-specs/ROADMAP.md
7
+ note: Process skill — no library dependencies, no freshness check needed.
8
+ ---
9
+
10
+ # /write-roadmap — Decompose a Product into an Ordered Spec Roadmap
11
+
12
+ Triggered by: `/write-roadmap` (optional arg: path to a brief file; else ask the human)
13
+
14
+ This is the altitude ABOVE specs. A real product is many specs, ordered by dependency — this
15
+ skill plans that SET. It writes ONLY `docs/product-specs/ROADMAP.md`; it NEVER writes spec files
16
+ (that is `/write-spec`'s job, which owns spec numbering).
17
+
18
+ ---
19
+
20
+ ## Step 1 — Get the Product Brief
21
+ If invoked with a file path argument, read that file. Otherwise ask the human:
22
+ "Paste the product brief — what we're building, for whom, and the core capabilities."
23
+ Do NOT proceed without a brief.
24
+
25
+ ## Step 2 — Check for an Existing Roadmap (refresh, don't clobber)
26
+ ```bash
27
+ ls docs/product-specs/ROADMAP.md 2>/dev/null && echo "EXISTS — refresh: keep created specs"
28
+ ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep "SPEC-" | sort
29
+ ```
30
+ If a roadmap exists you are REFRESHING: preserve every row that already has a real Spec ID and
31
+ its Status; only add/reorder `NOT_STARTED` rows. Never renumber a created spec.
32
+
33
+ ## Step 3 — Identify Bounded Contexts (Epics)
34
+ From the brief, list the distinct domains / feature areas — each owns a cohesive set of entities
35
+ and rules (e.g. Identity, Billing, Catalog). Only the contexts the brief implies; no speculative
36
+ ones. In Express terms a context maps to a slice of `src/models` + `src/services` + a route group.
37
+
38
+ ## Step 4 — Propose an Ordered Spec List with Dependencies
39
+ Break each epic into shippable specs (one feature each — the `/write-spec` altitude). For each
40
+ spec record: name, epic, and which specs it depends on (an entity / auth / contract another spec
41
+ must establish first). Order the whole list topologically — every spec appears after its
42
+ dependencies. Keep each spec small enough to become ONE execution plan (split if not).
43
+
44
+ ## Step 5 — Pick the Walking-Skeleton First Slice
45
+ Choose the single thinnest spec that exercises every layer end-to-end and depends on nothing —
46
+ in Express terms: **auth + one Zod-validated entity + one Sequelize model + one persisted Express
47
+ endpoint + one integration test**. Mark exactly one spec as the skeleton. This is what the human
48
+ builds first to prove every layer's path (Types → Config → Models → Repo → Service → Runtime → Tests)
49
+ works before the rest is layered on.
50
+
51
+ ## Step 6 — Write / Refresh the Roadmap
52
+ Save to `docs/product-specs/ROADMAP.md` using the template already in that file's header
53
+ (self-documenting). Fill: Bounded Contexts, the dependency-ordered Spec Roadmap table, and the
54
+ Build Sequence Rationale.
55
+ - New specs get Spec ID `—` and Status `NOT_STARTED`.
56
+ - On refresh, keep existing Spec IDs and Statuses untouched; never renumber a created spec.
57
+ - Set the **Walking skeleton** header to the chosen first slice.
58
+
59
+ ## Step 7 — Tell the Human
60
+ ```
61
+ Roadmap written: docs/product-specs/ROADMAP.md
62
+ {N} specs across {M} epics, dependency-ordered.
63
+ Walking skeleton (build first): {skeleton spec name}
64
+
65
+ Review the roadmap. When the order looks right:
66
+ 1. Run /write-spec — it reads ROADMAP.md and authors the next NOT_STARTED spec
67
+ (starting with the walking skeleton), pre-filling its epic + dependencies.
68
+ 2. Repeat /write-spec down the list as you go.
69
+
70
+ /write-roadmap does NOT write spec files. It only plans the set. Each spec is authored
71
+ individually by /write-spec, now guided by this roadmap.
72
+ ```
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: 02-write-spec
3
+ description: /write-spec — Write a Product Spec
4
+ verified: 2026-06-19
5
+ libraries: []
6
+ source: docs/PLANS.md
7
+ note: Process skill — no library dependencies, no freshness check needed.
8
+ ---
9
+
10
+ # /write-spec — Write a Product Spec
11
+
12
+ Triggered by: `/write-spec`
13
+
14
+ ---
15
+
16
+ ## Step 1 — Consult the Roadmap (if present)
17
+ ```bash
18
+ ls docs/product-specs/ROADMAP.md 2>/dev/null
19
+ ```
20
+ If it exists, read it. Pick the first row with Status `NOT_STARTED` — the **walking skeleton** if
21
+ nothing is created yet, otherwise the next dependency-unblocked row (every spec in its `Depends On`
22
+ is already DRAFT or later). Use that row's Name, Epic, and Depends-On to pre-fill the spec's title
23
+ and the `**Epic:**` / `**Depends on:**` fields. If no roadmap exists, proceed ad hoc (a single
24
+ standalone spec).
25
+
26
+ ## Step 2 — Get Next Spec Number
27
+ ```bash
28
+ ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep "SPEC-" | sort | tail -1
29
+ ```
30
+ Increment by 1. If none exist, start at SPEC-001. (`ROADMAP.md` is CAPS, so the `grep SPEC-` never matches it.)
31
+
32
+ ## Step 3 — Gather Intent
33
+ If the roadmap pre-filled the title / epic / dependencies, confirm them. Otherwise ask the human
34
+ ONE question if their description is too vague:
35
+ "What problem does this solve, and who experiences it?"
36
+ Do not ask for technical details — you'll derive those.
37
+
38
+ ## Step 4 — Write the Spec
39
+
40
+ Save to: `docs/product-specs/draft/SPEC-XXX-{slug}.md`
41
+
42
+ ```markdown
43
+ # SPEC-XXX — {Feature Name}
44
+
45
+ **Status:** DRAFT
46
+ **Created:** YYYY-MM-DD
47
+ **Plan:** — (assigned when READY)
48
+ **Epic:** — <!-- from ROADMAP.md row, if roadmap-driven -->
49
+ **Depends on:** — <!-- from ROADMAP.md row, if roadmap-driven -->
50
+
51
+ ---
52
+
53
+ ## Problem Statement
54
+ {1-3 sentences: what problem, who has it, why it matters}
55
+
56
+ ---
57
+
58
+ ## What We're Building
59
+ {2-4 sentences: the solution at a high level}
60
+
61
+ ---
62
+
63
+ ## Core Entities
64
+ | Entity | Purpose |
65
+ |---|---|
66
+ | {Entity} | {one line — becomes a Sequelize model + Zod schema} |
67
+
68
+ ---
69
+
70
+ ## API Endpoints
71
+ {List all endpoints: METHOD /path — description}
72
+
73
+ ---
74
+
75
+ ## Business Rules
76
+ {Numbered list of rules the system must enforce}
77
+
78
+ ---
79
+
80
+ ## State Machine (if applicable)
81
+ {ASCII diagram of state transitions}
82
+ {Transition rules}
83
+
84
+ ---
85
+
86
+ ## Non-Functional Requirements
87
+ {Performance, rate limits, auth requirements, coverage targets}
88
+
89
+ ---
90
+
91
+ ## Out of Scope (v1)
92
+ {What is explicitly NOT being built}
93
+
94
+ ---
95
+
96
+ ## Acceptance Criteria
97
+ - [ ] {Testable, specific criterion}
98
+ - [ ] {Each criterion maps to a test}
99
+ ```
100
+
101
+ ## Step 5 — Update Index + Roadmap
102
+ Add row to `docs/product-specs/index.md`:
103
+ `| SPEC-XXX | {Name} | DRAFT | — | YYYY-MM-DD |`
104
+
105
+ If this spec came from a `ROADMAP.md` row, flip that row in place: `Spec ID: — → SPEC-XXX` and
106
+ `Status: NOT_STARTED → DRAFT` (mirrors how `/write-plan` updates the spec + index together).
107
+
108
+ ## Step 6 — Tell the Human
109
+ ```
110
+ Spec written: docs/product-specs/draft/SPEC-XXX-{slug}.md
111
+ Status: DRAFT
112
+
113
+ Review it. When satisfied:
114
+ 1. Move to: docs/product-specs/ready/SPEC-XXX-{slug}.md
115
+ 2. Change Status: DRAFT → READY
116
+ 3. Run /write-plan
117
+
118
+ Claude will not create a plan until the spec is in ready/.
119
+ ```