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,194 @@
1
+ # Observability — See It & Debug It
2
+
3
+ How traces, metrics, and logs flow through this service, how to look at them in Grafana, and how
4
+ to use them to debug a real problem. Read this once; it pays for itself the first time something
5
+ is slow in a way you can't reproduce locally.
6
+
7
+ ---
8
+
9
+ ## Mental model (one line)
10
+
11
+ > **Traces + metrics → OTLP → otel-lgtm collector. Logs → pino JSON (correlated by a span-reading
12
+ > mixin) → Alloy → Loki.** Helpers live in `config/` so services stay clean; the `NodeSDK` lives
13
+ > only in `providers/telemetry.ts` and boots *before* the app is imported.
14
+
15
+ ```
16
+ ┌────────────────────────────────────────────┐
17
+ │ grafana/otel-lgtm │
18
+ ┌─────────────┐ OTLP/HTTP :4318 │ Collector → Tempo (traces) │
19
+ │ │ ───────────────► │ → Prometheus (metrics) │
20
+ │ Node app │ traces+metrics │ → Loki (logs) │
21
+ │ (Express) │ │ Grafana UI :3000 → published on host :3001 │
22
+ │ │ stdout JSON └───────▲──────────────────────────────────────┘
23
+ │ pino ──────┼──► (docker logs) ─┐ │ push to Loki :3100
24
+ └─────────────┘ trace_id/span_id│ ┌───┴────────┐
25
+ └─►│ Alloy │ tails container stdout, parses pino JSON
26
+ └─────────────┘
27
+ ```
28
+
29
+ Why logs take the side road through Alloy instead of OTLP: under ESM, `@opentelemetry/instrumentation-pino`'s
30
+ OTLP log-sending does not reliably emit. So we **don't** depend on it. Correlation is done
31
+ deterministically by a pino `mixin` that reads the active span; shipping is done by Alloy tailing
32
+ stdout. Decoupled and boring — exactly what you want from logging.
33
+
34
+ ---
35
+
36
+ ## Where the code lives
37
+
38
+ | Concern | File | Notes |
39
+ |---|---|---|
40
+ | SDK boot (traces+metrics) | `src/providers/telemetry.ts` | The **only** file importing `NodeSDK`. Boot-only, excluded from coverage. |
41
+ | Boot ordering | `src/runtime/server.ts` | `await startTelemetry()` **then** `await import('./app.js')`. |
42
+ | Logger | `src/config/logger.ts` | pino + trace-correlation mixin + guarded pretty transport. |
43
+ | Spans | `src/config/tracing.ts` | `withSpan(name, attrs, fn)`. No-op when SDK off. |
44
+ | Metrics | `src/config/metrics.ts` | `increment()`, histograms. No-op when SDK off. |
45
+ | Env flags | `src/config/env.ts` | `OTEL_*`. Blank endpoint ⇒ everything no-ops. |
46
+ | Local backend | `docker-compose.yml` (`lgtm`, `alloy`) | All-in-one; nothing to configure. |
47
+ | Log shipping | `infra/monitoring/alloy/config.alloy` | stdout JSON → Loki. |
48
+
49
+ ---
50
+
51
+ ## Quickstart
52
+
53
+ ```bash
54
+ # 1. Start the backend (and the app) with telemetry wired up.
55
+ docker compose up -d
56
+
57
+ # 2. Open Grafana.
58
+ # http://localhost:3001 (user: admin password: admin)
59
+
60
+ # 3. Generate some signal.
61
+ curl http://localhost:3000/health
62
+ curl http://localhost:3000/ready
63
+ ```
64
+
65
+ > Running the app outside Docker (`npm run dev`)? Start just the backend and point the app at it:
66
+ >
67
+ > ```bash
68
+ > docker run -d --name lgtm -p 3001:3000 -p 4317:4317 -p 4318:4318 grafana/otel-lgtm
69
+ > # in .env: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
70
+ > ```
71
+ >
72
+ > Logs won't reach Loki this way (Alloy isn't running), but they print to your terminal with
73
+ > `trace_id`, and traces+metrics still flow to Grafana.
74
+
75
+ ---
76
+
77
+ ## See traces (Tempo)
78
+
79
+ 1. Grafana → **Explore** (compass icon) → data source **Tempo**.
80
+ 2. Query type **Search** → run. You'll see recent traces, one row per request (e.g. `GET /health`).
81
+ 3. Click a trace → the **span waterfall**. Express, the DB query (pg), and Redis each appear as a
82
+ span with its own duration. This is where you find *which part* of a request was slow.
83
+
84
+ What produces these spans: `getNodeAutoInstrumentations()` patches http/express/pg/ioredis
85
+ automatically. Your own service methods add spans via `withSpan(...)` (see below).
86
+
87
+ ## See metrics (Prometheus)
88
+
89
+ 1. Explore → data source **Prometheus**.
90
+ 2. Try `http_server_duration_milliseconds_count` or any custom counter you emitted via
91
+ `increment('domain.action')`. Auto-instrumentation also emits HTTP server duration histograms.
92
+
93
+ ## See logs (Loki)
94
+
95
+ 1. Explore → data source **Loki**.
96
+ 2. Query: `{service_name="myapp"}` (the label Alloy derives from the container name).
97
+ 3. Each line is the pino JSON, including `trace_id`, `span_id`, `level`, and your `event` field.
98
+ Filter by level with the `level` label, e.g. `{service_name="myapp", level="error"}`.
99
+
100
+ ---
101
+
102
+ ## Debug a slow request (the payoff)
103
+
104
+ You get a report: "some `/applications` calls take 2 seconds." You can't reproduce it. Here's the loop:
105
+
106
+ 1. **Find it in logs.** Loki: `{service_name="myapp"} | json | durationMs > 1000`. You get the
107
+ exact log line for a slow call, with its `trace_id`.
108
+ 2. **Jump to the trace.** Click the `trace_id` value → "Tempo" link (Grafana wires log↔trace via
109
+ the shared id). You land on that request's span waterfall.
110
+ 3. **Read the waterfall.** One span dominates — say a `pg` query span at 1.8s. Now you know it's the
111
+ database, not your code, not Redis, not the network.
112
+ 4. **Confirm the pattern in metrics.** Prometheus: is DB query duration elevated across the board,
113
+ or just this endpoint? That tells you "fix this query" vs "the DB is under load."
114
+ 5. **Fix and verify.** After the fix, the same Loki→Tempo→Prometheus loop confirms the slow span is
115
+ gone — no guessing, no "works on my machine."
116
+
117
+ The whole point: **logs tell you *that* it was slow, traces tell you *where*, metrics tell you *how
118
+ often*.** They're joined by `trace_id`, so you move between them in two clicks.
119
+
120
+ ---
121
+
122
+ ## Instrumenting your own code
123
+
124
+ Service methods keep the existing structured log **and** wrap their work in a span:
125
+
126
+ ```typescript
127
+ import { withSpan } from '../config/tracing.js'
128
+ import { increment } from '../config/metrics.js'
129
+ import { logger } from '../config/logger.js'
130
+
131
+ export async function createApplication(userId: string, input: CreateApplicationInput) {
132
+ const start = Date.now()
133
+ return withSpan('application.create', { userId }, async () => {
134
+ const app = await applicationRepo.insert(userId, input)
135
+ increment('application.created')
136
+ logger.info({ event: 'application.create', userId, applicationId: app.id, durationMs: Date.now() - start })
137
+ return app
138
+ })
139
+ }
140
+ ```
141
+
142
+ Because `withSpan` is active, every `logger.*` call inside it automatically carries that span's
143
+ `trace_id`/`span_id` (the mixin reads the active span). You don't thread anything by hand.
144
+
145
+ ---
146
+
147
+ ## Turning it off (tests / CI / local)
148
+
149
+ Leave **`OTEL_EXPORTER_OTLP_ENDPOINT` blank**. `startTelemetry()` returns early, no SDK starts, and
150
+ `withSpan`/`metrics`/`logger` fall back to the `@opentelemetry/api` no-op — zero overhead, no
151
+ network, no backend needed. This is the default for `npm test` and CI. **Never** start the LGTM
152
+ stack in CI.
153
+
154
+ ---
155
+
156
+ ## Production
157
+
158
+ The otel-lgtm image is for **dev/demo/testing**, not production. In prod, point
159
+ `OTEL_EXPORTER_OTLP_ENDPOINT` at your real collector (e.g. Grafana Cloud, an OTel Collector
160
+ deployment, or a vendor endpoint) and run a log agent (Alloy/Promtail/vendor) against stdout. The
161
+ app code does not change — only the endpoint and the log shipper's destination.
162
+
163
+ ---
164
+
165
+ ## Gotchas → solutions (the hard-won bits)
166
+
167
+ 1. **ESM import hoisting broke instrumentation.** A static `import` of the app loaded express/pg/redis
168
+ before the SDK started, so nothing got patched. → `server.ts` `await startTelemetry()` first,
169
+ then `await import('./app.js')`. `telemetry.ts` must not statically import any instrumented module.
170
+ 2. **Service can't import Providers** (layer rule, enforced by madge). → `withSpan`/`metrics`/`logger`
171
+ live in `config/` (every layer may import Config) and use only the `@opentelemetry/api` global.
172
+ 3. **`z.coerce.boolean('false') === true`.** → Use `z.stringbool()` (zod 4) for boolean env flags.
173
+ 4. **NodeSDK option/version skew.** → Use the plural `metricReaders` (and `logRecordProcessors` if you
174
+ ever add OTLP logs). Keep every `@opentelemetry/*` on one release line; don't bump `api` past the
175
+ SDK's peer range.
176
+ 5. **`pino-pretty` crashed the prod image.** It's a devDependency, absent after `npm ci --omit=dev`.
177
+ → `logger.ts` only uses the pretty transport if `require.resolve('pino-pretty')` succeeds; prod
178
+ logs JSON.
179
+ 6. **Logs never reached Loki under ESM.** The pino→OTLP bridge silently emitted nothing (verified by
180
+ a missing `trace_id`). → Two parts: deterministic correlation via a pino `mixin` reading the active
181
+ span, and shipping via Alloy tailing stdout JSON. Independent of auto-patching.
182
+ 7. **Coverage gates.** `telemetry.ts` is boot-only → excluded from coverage (like `server.ts`).
183
+ Unit-test the `config/` helpers against the no-op API (fast, no infra).
184
+ 8. **Port clash.** otel-lgtm's Grafana defaults to 3000 → published on host **3001** so it doesn't
185
+ collide with the app/frontend.
186
+ 9. **CI.** Leave `OTEL_EXPORTER_OTLP_ENDPOINT` blank in tests so the SDK no-ops; never start the LGTM
187
+ stack in CI. Verify against real backends only in dev.
188
+
189
+ ---
190
+
191
+ ## See also
192
+
193
+ - `docs/design-docs/decisions/ADR-001-observability.md` — *why* these choices were made.
194
+ - `ARCHITECTURE.md` — the layer rules that put the helpers in Config and the SDK in Providers.
@@ -0,0 +1,64 @@
1
+ # Team Workflow
2
+
3
+ How a 5-person team collaborates on this harness. Pairs with `.github/CODEOWNERS`
4
+ and `.github/pull_request_template.md`.
5
+
6
+ ---
7
+
8
+ ## Branching
9
+
10
+ - `main` is always deployable. No direct pushes.
11
+ - Feature branches: `<type>/<short-slug>` — e.g. `feat/application-stages`, `fix/cursor-decode`.
12
+ - One feature = one branch = one plan in `docs/exec-plans/active/`.
13
+ - Rebase on `main` before opening a PR; keep branches short-lived (< 3 days).
14
+
15
+ ## The PR Flow
16
+
17
+ 1. Build layer-by-layer with `/build-layer`; each layer is gated and committed.
18
+ 2. When the plan's layers are all checked off, run `/garbage-collect`.
19
+ 3. Open a PR — the template checklist auto-populates. Fill every box.
20
+ 4. CI must be green (lint → typecheck → test → audit → secret-scan → arch-check).
21
+ 5. Code Owners review (see below). Address feedback with new commits (don't force-push mid-review).
22
+ 6. Squash-merge to `main`. Delete the branch.
23
+
24
+ ## Branch Protection (configure on `main` in GitHub → Settings → Branches)
25
+
26
+ - [x] Require a pull request before merging
27
+ - [x] Require approvals: **1** (2 for security-sensitive paths)
28
+ - [x] Require review from Code Owners
29
+ - [x] Dismiss stale approvals when new commits are pushed
30
+ - [x] Require status checks to pass: `ci`
31
+ - [x] Require branches to be up to date before merging
32
+ - [x] Require conversation resolution before merging
33
+ - [x] Do not allow bypassing the above (applies to admins too)
34
+ - [x] Restrict who can push to matching branches (no direct pushes)
35
+
36
+ ## Approve vs. Merge
37
+
38
+ | Action | Who |
39
+ |---|---|
40
+ | Open a PR | Anyone |
41
+ | Review / approve | Any engineer not the author |
42
+ | Approve security-sensitive paths (`auth/`, `hooks/`, `config/`, `migrations/`) | A Code Owner for that path (per `CODEOWNERS`) |
43
+ | Merge to `main` | The PR author, once required approvals + CI are green |
44
+ | Merge a security-sensitive PR | A `leads` member |
45
+
46
+ Rule of thumb: **the author merges their own PR** after it's approved and green — this
47
+ keeps ownership with the person who has the most context. Exception: security-sensitive
48
+ PRs are merged by a Code Owner.
49
+
50
+ ## Required Reviews by Path
51
+
52
+ These come from `.github/CODEOWNERS`. Edit that file to swap the placeholder team
53
+ slugs for your real ones, then enable "Require review from Code Owners" in branch
54
+ protection so they're enforced:
55
+
56
+ - `/.claude/hooks/`, `/src/providers/auth/`, `/src/config/` → `@your-team/leads` (security-sensitive)
57
+ - `/Dockerfile`, `/docker-compose.yml`, `/.github/workflows/` → `@your-team/platform` (build/containers/CI)
58
+ - `/db/migrations/` → `@your-team/leads` (irreversible DB changes)
59
+ - everything else → `@your-team/engineers`
60
+
61
+ ## Hotfixes
62
+
63
+ - Branch from `main`: `fix/<slug>`. Same PR flow, but reviewers prioritise.
64
+ - A hotfix still needs CI green and one approval — no exceptions, even under pressure.
@@ -0,0 +1,31 @@
1
+ # Technical Debt Tracker
2
+
3
+ Debt is logged here immediately when created. Never ignored until it's a crisis.
4
+ Paid in small daily amounts.
5
+
6
+ ---
7
+
8
+ ## Severity
9
+
10
+ | Level | Meaning | Fix by |
11
+ |---|---|---|
12
+ | P0 | Security or data integrity risk | Immediately |
13
+ | P1 | Blocks future feature or causes recurring bugs | Within 2 sprints |
14
+ | P2 | Degrades quality or velocity | Within the quarter |
15
+ | P3 | Code smell, DX improvement | When in the area |
16
+
17
+ ---
18
+
19
+ ## Open Debt
20
+
21
+ | ID | Severity | Area | Description | Created By | Date |
22
+ |---|---|---|---|---|---|
23
+ | *(none yet)* | — | — | — | — | — |
24
+
25
+ ---
26
+
27
+ ## Resolved Debt
28
+
29
+ | ID | Description | Resolved By | Date |
30
+ |---|---|---|---|
31
+ | *(none yet)* | — | — | — |
File without changes
@@ -0,0 +1,49 @@
1
+ # Product Roadmap
2
+
3
+ **Product:** *(name)* · **Brief:** *(one-line, or link to the brief)* · **Last updated:** *(YYYY-MM-DD)*
4
+ **Walking skeleton:** *(SPEC-001 — the thinnest end-to-end slice; build this first)*
5
+
6
+ > LIVING document — the altitude ABOVE specs. `/write-roadmap` writes it from a product brief.
7
+ > `/write-spec` reads it to pick the next spec and pre-fill epic + dependency context, and flips
8
+ > a row's **Spec ID** and **Status** as specs are created. Update in place; never delete history.
9
+
10
+ ---
11
+
12
+ ## Bounded Contexts (Epics)
13
+
14
+ | Epic | Bounded Context | Responsibility |
15
+ |---|---|---|
16
+ | *(none yet)* | — | — |
17
+
18
+ ---
19
+
20
+ ## Spec Roadmap (dependency-ordered)
21
+
22
+ | Order | Spec ID | Name | Epic | Depends On | Skeleton | Status |
23
+ |---|---|---|---|---|---|---|
24
+ | *(none yet)* | — | — | — | — | — | — |
25
+
26
+ > `Spec ID` is `—` until `/write-spec` creates the file and assigns the real number.
27
+ > `Depends On` references other rows by Spec ID (or by Order if not yet numbered).
28
+ > Exactly one row carries `✅ yes` in `Skeleton`. Every spec appears after its dependencies.
29
+
30
+ ---
31
+
32
+ ## Build Sequence Rationale
33
+
34
+ 1. *(walking skeleton — why this thin slice first: proves auth + one entity + one endpoint end-to-end)*
35
+ 2. *(next slice — what it unblocks)*
36
+
37
+ ---
38
+
39
+ ## Roadmap Statuses
40
+
41
+ | Status | Meaning |
42
+ |---|---|
43
+ | NOT_STARTED | On the roadmap, no spec file yet |
44
+ | DRAFT | `/write-spec` wrote a SPEC-XXX (in `draft/`) |
45
+ | READY | Spec approved — plannable |
46
+ | PLANNED | `/write-plan` created a plan |
47
+ | IN_PROGRESS | Being built |
48
+ | SHIPPED | Complete and live |
49
+ | CANCELLED | Removed from the roadmap |
@@ -0,0 +1,21 @@
1
+ # Product Specs Index
2
+
3
+ > Whole-product decomposition (epics → dependency-ordered specs, walking skeleton) lives in
4
+ > [`ROADMAP.md`](ROADMAP.md). `/write-spec` fills this index per spec as each is created.
5
+
6
+ | ID | Name | Status | Plan | Created |
7
+ |---|---|---|---|---|
8
+ | *(no specs yet)* | — | — | — | — |
9
+
10
+ ---
11
+
12
+ ## Spec Statuses
13
+
14
+ | Status | Meaning |
15
+ |---|---|
16
+ | DRAFT | Written, not yet reviewed |
17
+ | READY | Reviewed and approved — plannable |
18
+ | PLANNED | Plan created (PLAN-XXX assigned) |
19
+ | IN_PROGRESS | Currently being built |
20
+ | SHIPPED | Feature complete and in production |
21
+ | CANCELLED | No longer being built |
@@ -0,0 +1,94 @@
1
+ import tseslint from '@typescript-eslint/eslint-plugin'
2
+ import tsparser from '@typescript-eslint/parser'
3
+ import boundaries from 'eslint-plugin-boundaries'
4
+
5
+ // Layer import boundaries — the mechanical enforcement of ARCHITECTURE.md.
6
+ // eslint-plugin-boundaries v6: the rule is `boundaries/dependencies` (the old
7
+ // `element-types` is deprecated), selectors are object-based, element patterns are
8
+ // folder globs (`src/<layer>/**`), and the TypeScript import resolver is REQUIRED so
9
+ // the project's `.js`-extension ESM specifiers resolve to their .ts files — without
10
+ // it, boundaries classifies nothing and silently enforces nothing.
11
+ const LAYER_ELEMENTS = [
12
+ { type: 'types', pattern: 'src/types/**' },
13
+ { type: 'config', pattern: 'src/config/**' },
14
+ { type: 'models', pattern: 'src/models/**' },
15
+ { type: 'repo', pattern: 'src/repo/**' },
16
+ { type: 'services', pattern: 'src/services/**' },
17
+ { type: 'runtime', pattern: 'src/runtime/**' },
18
+ { type: 'providers', pattern: 'src/providers/**' },
19
+ { type: 'utils', pattern: 'src/utils/**' },
20
+ ]
21
+
22
+ // Allowed dependencies per layer (mirrors ARCHITECTURE.md "Allowed Imports").
23
+ // `config` and `utils` are cross-cutting; `providers` is wired in by `runtime` only.
24
+ // Each layer lists itself so intra-layer imports stay legal (barrels, logger->env,
25
+ // featureFlags->redis). Anything not allowed is rejected (default: disallow).
26
+ const DEP_RULES = [
27
+ { from: { type: 'types' }, allow: { to: { type: ['types'] } } },
28
+ { from: { type: 'utils' }, allow: { to: { type: ['types', 'utils'] } } },
29
+ { from: { type: 'config' }, allow: { to: { type: ['types', 'utils', 'config'] } } },
30
+ { from: { type: 'models' }, allow: { to: { type: ['types', 'config', 'utils', 'models'] } } },
31
+ { from: { type: 'repo' }, allow: { to: { type: ['types', 'config', 'models', 'utils', 'repo'] } } },
32
+ { from: { type: 'services' }, allow: { to: { type: ['types', 'config', 'repo', 'utils', 'services'] } } },
33
+ { from: { type: 'providers' }, allow: { to: { type: ['types', 'config', 'utils', 'providers'] } } },
34
+ {
35
+ from: { type: 'runtime' },
36
+ allow: { to: { type: ['types', 'config', 'repo', 'services', 'providers', 'utils', 'runtime'] } },
37
+ },
38
+ ]
39
+
40
+ export default [
41
+ {
42
+ files: ['src/**/*.ts'],
43
+ languageOptions: {
44
+ parser: tsparser,
45
+ parserOptions: {
46
+ project: './tsconfig.json',
47
+ },
48
+ },
49
+ plugins: {
50
+ '@typescript-eslint': tseslint,
51
+ boundaries,
52
+ },
53
+ settings: {
54
+ 'boundaries/elements': LAYER_ELEMENTS,
55
+ // Required so boundaries resolves the repo's `.js`-extension ESM imports to .ts files.
56
+ 'import/resolver': { typescript: { alwaysTryTypes: true } },
57
+ },
58
+ rules: {
59
+ 'no-console': 'error',
60
+ '@typescript-eslint/no-explicit-any': 'error',
61
+ '@typescript-eslint/no-unsafe-assignment': 'error',
62
+ '@typescript-eslint/no-floating-promises': 'error',
63
+ // Custom rule: block process.env outside config
64
+ 'no-restricted-syntax': [
65
+ 'error',
66
+ {
67
+ selector: 'MemberExpression[object.name="process"][property.name="env"]',
68
+ message: 'Use env from src/config/env.ts instead of process.env directly',
69
+ },
70
+ ],
71
+ // Mechanical layer-boundary enforcement (v6 rule + object selectors).
72
+ 'boundaries/dependencies': [
73
+ 'error',
74
+ {
75
+ default: 'disallow',
76
+ rules: DEP_RULES,
77
+ },
78
+ ],
79
+ },
80
+ },
81
+ {
82
+ files: ['src/config/env.ts'],
83
+ rules: {
84
+ 'no-restricted-syntax': 'off', // env.ts is allowed to use process.env
85
+ },
86
+ },
87
+ {
88
+ files: ['tests/**/*.ts'],
89
+ rules: {
90
+ 'no-console': 'off', // tests can use console for debugging
91
+ '@typescript-eslint/no-explicit-any': 'off', // tests can use any for mocks
92
+ },
93
+ },
94
+ ]
@@ -0,0 +1,36 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+
7
+ # Environment — NEVER commit .env files
8
+ .env
9
+ .env.local
10
+ .env.*.local
11
+
12
+ # Logs
13
+ *.log
14
+ logs/
15
+
16
+ # Coverage
17
+ coverage/
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # IDE
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ *.swo
28
+
29
+ # Docker
30
+ docker-compose.override.yml
31
+
32
+ # k6 results
33
+ k6-results/
34
+
35
+ # Sequelize CLI
36
+ .sequelizerc.local
@@ -0,0 +1,59 @@
1
+ // Grafana Alloy — ship container stdout (pino JSON) to Loki inside otel-lgtm.
2
+ //
3
+ // The app writes structured JSON logs carrying trace_id / span_id (via the pino mixin in
4
+ // src/config/logger.ts). Alloy tails every container's stdout, parses the JSON to surface
5
+ // `level` and `trace_id`, and pushes to Loki. In Grafana you can then jump log ↔ trace.
6
+ //
7
+ // Traces and metrics do NOT pass through here — the app exports those straight to the
8
+ // collector over OTLP. Only logs use this path (it sidesteps the known ESM pino→OTLP bug).
9
+
10
+ discovery.docker "containers" {
11
+ host = "unix:///var/run/docker.sock"
12
+ }
13
+
14
+ // Turn the Docker container name (e.g. "/harness-app-1") into a clean `service_name` label.
15
+ discovery.relabel "containers" {
16
+ targets = discovery.docker.containers.targets
17
+
18
+ rule {
19
+ source_labels = ["__meta_docker_container_name"]
20
+ regex = "/(.*)"
21
+ target_label = "service_name"
22
+ }
23
+ rule {
24
+ source_labels = ["__meta_docker_container_name"]
25
+ regex = "/(.*)"
26
+ target_label = "container"
27
+ }
28
+ }
29
+
30
+ loki.source.docker "containers" {
31
+ host = "unix:///var/run/docker.sock"
32
+ targets = discovery.relabel.containers.output
33
+ labels = { job = "docker" }
34
+ forward_to = [loki.process.pino.receiver]
35
+ }
36
+
37
+ // Parse pino JSON so `level` becomes a label and `trace_id` is queryable.
38
+ loki.process "pino" {
39
+ stage.json {
40
+ expressions = {
41
+ level = "level",
42
+ trace_id = "trace_id",
43
+ }
44
+ }
45
+
46
+ stage.labels {
47
+ values = {
48
+ level = "",
49
+ }
50
+ }
51
+
52
+ forward_to = [loki.write.default.receiver]
53
+ }
54
+
55
+ loki.write "default" {
56
+ endpoint {
57
+ url = "http://lgtm:3100/loki/api/v1/push"
58
+ }
59
+ }
@@ -0,0 +1,61 @@
1
+ import type { Config } from 'jest'
2
+
3
+ const config: Config = {
4
+ preset: 'ts-jest/presets/default-esm',
5
+ testEnvironment: 'node',
6
+ extensionsToTreatAsEsm: ['.ts'],
7
+ moduleNameMapper: {
8
+ '^(\\.{1,2}/.*)\\.js$': '$1',
9
+ },
10
+ transform: {
11
+ '^.+\\.tsx?$': [
12
+ 'ts-jest',
13
+ {
14
+ useESM: true,
15
+ },
16
+ ],
17
+ },
18
+ testMatch: ['**/tests/**/*.test.ts'],
19
+ collectCoverageFrom: [
20
+ 'src/**/*.ts',
21
+ '!src/**/*.d.ts',
22
+ '!src/runtime/server.ts',
23
+ '!src/providers/telemetry.ts', // boot-only OTel SDK wiring, like server.ts
24
+ ],
25
+ coverageDirectory: 'coverage',
26
+ coverageReporters: ['text', 'lcov', 'html'],
27
+ coverageThreshold: {
28
+ './src/utils/': {
29
+ branches: 100,
30
+ functions: 100,
31
+ lines: 100,
32
+ statements: 100,
33
+ },
34
+ './src/services/': {
35
+ branches: 90,
36
+ functions: 90,
37
+ lines: 90,
38
+ statements: 90,
39
+ },
40
+ './src/repo/': {
41
+ branches: 80,
42
+ functions: 80,
43
+ lines: 80,
44
+ statements: 80,
45
+ },
46
+ './src/runtime/routes/': {
47
+ branches: 75,
48
+ functions: 75,
49
+ lines: 75,
50
+ statements: 75,
51
+ },
52
+ './src/providers/': {
53
+ branches: 70,
54
+ functions: 70,
55
+ lines: 70,
56
+ statements: 70,
57
+ },
58
+ },
59
+ }
60
+
61
+ export default config
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "harness-typescript-template",
3
+ "version": "0.0.1",
4
+ "description": "Express + TypeScript + Sequelize harness template",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=24",
8
+ "npm": ">=11"
9
+ },
10
+ "scripts": {
11
+ "dev": "tsx watch src/runtime/server.ts",
12
+ "build": "tsc -p tsconfig.build.json",
13
+ "start": "node dist/runtime/server.js",
14
+ "test": "jest",
15
+ "test:arch": "jest tests/architecture/",
16
+ "test:coverage": "jest --coverage",
17
+ "test:watch": "jest --watch",
18
+ "load:smoke": "k6 run tests/load/smoke.js",
19
+ "load:stress": "k6 run tests/load/stress.js",
20
+ "load:soak": "k6 run tests/load/soak.js",
21
+ "lint": "eslint src/ --max-warnings=0",
22
+ "lint:fix": "eslint src/ --fix",
23
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
24
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
25
+ "typecheck": "tsc --noEmit",
26
+ "check:circular": "madge --circular src/ --extensions ts",
27
+ "gate": "npm run typecheck && npm run lint && npm run check:circular && npm run test:arch",
28
+ "audit": "npm audit --omit=dev --audit-level=high",
29
+ "openapi:export": "tsx scripts/openapi.export.ts",
30
+ "db:migrate": "sequelize-cli db:migrate",
31
+ "db:migrate:undo": "sequelize-cli db:migrate:undo",
32
+ "db:seed": "sequelize-cli db:seed:all",
33
+ "prepare": "husky"
34
+ },
35
+ "keywords": [
36
+ "express",
37
+ "typescript",
38
+ "sequelize",
39
+ "harness"
40
+ ],
41
+ "author": "",
42
+ "license": "MIT",
43
+ "dependencies": {},
44
+ "devDependencies": {}
45
+ }