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,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
|
+
```
|