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,145 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.spec.ts"
4
+ - "test/**/*.spec.ts"
5
+ - "test/**/*.e2e-spec.ts"
6
+ ---
7
+
8
+ # Testing Rules — Auto-injected on test file edits
9
+
10
+ ## Coverage Thresholds (enforced in CI)
11
+
12
+ | Layer | Threshold |
13
+ |---|---|
14
+ | Services | **90%** — happy path + all error paths |
15
+ | Repositories | **80%** — queries, Zod parse, cursor pagination |
16
+ | Controllers | **75%** — via E2E: 201, 401, 422, 404 |
17
+ | Utils/Common | **100%** — every branch |
18
+
19
+ ## Service Unit Tests — Mock the Repository
20
+
21
+ ```typescript
22
+ // src/applications/application.service.spec.ts
23
+ import { Test } from '@nestjs/testing'
24
+ import { ApplicationService } from './application.service'
25
+ import { ApplicationRepository } from './application.repository'
26
+ import { NotFoundException, ConflictException } from '@nestjs/common'
27
+
28
+ describe('ApplicationService', () => {
29
+ let service: ApplicationService
30
+ let repo: jest.Mocked<ApplicationRepository>
31
+
32
+ beforeEach(async () => {
33
+ const module = await Test.createTestingModule({
34
+ providers: [
35
+ ApplicationService,
36
+ {
37
+ provide: ApplicationRepository,
38
+ useValue: {
39
+ findById: jest.fn(),
40
+ create: jest.fn(),
41
+ list: jest.fn(),
42
+ updateStage: jest.fn(),
43
+ softDelete: jest.fn(),
44
+ },
45
+ },
46
+ ],
47
+ }).compile()
48
+
49
+ service = module.get(ApplicationService)
50
+ repo = module.get(ApplicationRepository)
51
+ })
52
+
53
+ describe('findOne', () => {
54
+ it('returns application when found', async () => {
55
+ repo.findById.mockResolvedValue(mockApplication)
56
+ const result = await service.findOne('user-1', 'app-1')
57
+ expect(result).toEqual(mockApplication)
58
+ })
59
+
60
+ it('propagates NotFoundException from repository', async () => {
61
+ repo.findById.mockRejectedValue(new NotFoundException())
62
+ await expect(service.findOne('user-1', 'bad-id')).rejects.toThrow(NotFoundException)
63
+ })
64
+ })
65
+
66
+ describe('transitionStage', () => {
67
+ it('throws ConflictException on invalid transition', async () => {
68
+ repo.findById.mockResolvedValue({ ...mockApplication, stage: 'ACCEPTED' })
69
+ await expect(service.transitionStage('user-1', 'app-1', 'APPLIED'))
70
+ .rejects.toThrow(ConflictException)
71
+ })
72
+ })
73
+ })
74
+ ```
75
+
76
+ ## E2E Tests — Full HTTP Stack
77
+
78
+ ```typescript
79
+ // test/applications.e2e-spec.ts
80
+ import { Test } from '@nestjs/testing'
81
+ import * as request from 'supertest'
82
+ import { AppModule } from '../src/app.module'
83
+ import { INestApplication, ValidationPipe } from '@nestjs/common'
84
+
85
+ describe('ApplicationController (e2e)', () => {
86
+ let app: INestApplication
87
+ let token: string
88
+
89
+ beforeAll(async () => {
90
+ const module = await Test.createTestingModule({
91
+ imports: [AppModule],
92
+ }).compile()
93
+
94
+ app = module.createNestApplication()
95
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }))
96
+ await app.init()
97
+
98
+ // Register + login to get token
99
+ const res = await request(app.getHttpServer())
100
+ .post('/v1/auth/login')
101
+ .send({ email: 'test@test.com', password: 'password123' })
102
+ token = res.body.data.tokens.accessToken
103
+ })
104
+
105
+ afterAll(() => app.close())
106
+
107
+ it('POST /v1/applications — 201 with valid body', async () => {
108
+ const res = await request(app.getHttpServer())
109
+ .post('/v1/applications')
110
+ .set('Authorization', `Bearer ${token}`)
111
+ .send({ company: 'Acme', role: 'Engineer' })
112
+
113
+ expect(res.status).toBe(201)
114
+ expect(res.body.data).toHaveProperty('id')
115
+ expect(res.body.data.stage).toBe('SAVED')
116
+ })
117
+
118
+ it('POST /v1/applications — 401 without token', async () => {
119
+ const res = await request(app.getHttpServer())
120
+ .post('/v1/applications')
121
+ .send({ company: 'Acme', role: 'Engineer' })
122
+ expect(res.status).toBe(401)
123
+ })
124
+
125
+ it('POST /v1/applications — 422 with invalid body', async () => {
126
+ const res = await request(app.getHttpServer())
127
+ .post('/v1/applications')
128
+ .set('Authorization', `Bearer ${token}`)
129
+ .send({ role: 'Engineer' }) // missing company
130
+ expect(res.status).toBe(422)
131
+ })
132
+ })
133
+ ```
134
+
135
+ ## NestJS TestingModule Pattern
136
+ ```typescript
137
+ // Always use Test.createTestingModule — never instantiate classes directly
138
+ const module = await Test.createTestingModule({
139
+ providers: [
140
+ MyService,
141
+ { provide: MyRepository, useValue: mockRepo },
142
+ { provide: ConfigService, useValue: { get: jest.fn(), getOrThrow: jest.fn() } },
143
+ ],
144
+ }).compile()
145
+ ```
@@ -0,0 +1,33 @@
1
+ {
2
+ "model": "claude-sonnet-4-5",
3
+ "permissions": {
4
+ "allow": [
5
+ "Bash(npm *)",
6
+ "Bash(npx *)",
7
+ "Bash(node *)",
8
+ "Bash(git *)",
9
+ "Bash(bash *)",
10
+ "Bash(sh *)",
11
+ "Bash(cat *)",
12
+ "Bash(ls *)",
13
+ "Bash(grep *)",
14
+ "Bash(find *)",
15
+ "Bash(wc *)",
16
+ "Bash(k6 *)"
17
+ ],
18
+ "deny": []
19
+ },
20
+ "hooks": {
21
+ "PostToolUse": [
22
+ {
23
+ "matcher": "Write|Edit|MultiEdit",
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "bash .claude/hooks/post-write.sh"
28
+ }
29
+ ]
30
+ }
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,222 @@
1
+ # /infra-setup — Phase 0: Full NestJS Scaffold
2
+
3
+ Triggered by: `/infra-setup`
4
+ Run ONCE. If `package.json` exists → abort.
5
+
6
+ ---
7
+
8
+ ## Step 1 — Create NestJS App
9
+ ```bash
10
+ npm install -g @nestjs/cli
11
+ nest new . --package-manager npm --strict
12
+ ```
13
+ Select: `npm` as package manager. This creates the NestJS scaffold.
14
+
15
+ ## Step 2 — Install All Dependencies
16
+ ```bash
17
+ # Core
18
+ npm install @nestjs/config joi
19
+ npm install @nestjs/sequelize sequelize sequelize-typescript pg pg-hstore
20
+ npm install @nestjs/jwt @nestjs/passport passport passport-jwt
21
+ npm install argon2
22
+ npm install nestjs-pino pino-http pino
23
+ npm install @nestjs/throttler
24
+ npm install @nestjs/swagger
25
+ npm install @nestjs/terminus @nestjs/schedule
26
+ npm install @nestjs/bullmq bullmq ioredis
27
+ npm install class-validator class-transformer
28
+ npm install uuid zod
29
+ npm install helmet
30
+
31
+ # Dev
32
+ npm install -D @types/passport-jwt @types/sequelize @types/pg
33
+ npm install -D @types/node @types/uuid
34
+ npm install -D sequelize-cli
35
+ npm install -D pino-pretty
36
+ ```
37
+
38
+ ## Step 3 — Directory Structure
39
+ ```bash
40
+ mkdir -p \
41
+ src/common/{decorators,filters,guards,interceptors,pipes,dto} \
42
+ src/config \
43
+ src/database \
44
+ src/auth/{dto,strategies,guards} \
45
+ src/users/{dto,models} \
46
+ src/health \
47
+ src/prisma \
48
+ db/{migrations,seeders} \
49
+ docs/{product-specs/{draft,ready},exec-plans/{active,completed},design-docs/decisions,generated} \
50
+ test
51
+ ```
52
+
53
+ ## Step 4 — Configuration Files to Write
54
+
55
+ ### src/config/configuration.ts
56
+ Typed config factory returning all env vars. Validated by Joi schema.
57
+
58
+ ### src/config/config.module.ts
59
+ ```typescript
60
+ ConfigModule.forRoot({
61
+ isGlobal: true,
62
+ load: [configuration],
63
+ validationSchema: Joi.object({
64
+ NODE_ENV: Joi.string().valid('development','test','production').default('development'),
65
+ PORT: Joi.number().default(3000),
66
+ DATABASE_URL: Joi.string().uri().required(),
67
+ DATABASE_POOL_MAX: Joi.number().default(10),
68
+ DATABASE_POOL_MIN: Joi.number().default(2),
69
+ REDIS_URL: Joi.string().uri().required(),
70
+ JWT_SECRET: Joi.string().min(32).required(),
71
+ JWT_ACCESS_EXPIRY: Joi.string().default('15m'),
72
+ JWT_REFRESH_EXPIRY: Joi.string().default('7d'),
73
+ CORS_ORIGINS: Joi.string().default('http://localhost:3001'),
74
+ LOG_LEVEL: Joi.string().default('info'),
75
+ }),
76
+ })
77
+ ```
78
+
79
+ ### src/database/database.module.ts
80
+ ```typescript
81
+ SequelizeModule.forRootAsync({
82
+ imports: [ConfigModule],
83
+ inject: [ConfigService],
84
+ useFactory: (config: ConfigService) => ({
85
+ dialect: 'postgres',
86
+ uri: config.getOrThrow('DATABASE_URL'),
87
+ pool: {
88
+ max: config.get('DATABASE_POOL_MAX', 10),
89
+ min: config.get('DATABASE_POOL_MIN', 2),
90
+ acquire: 30_000,
91
+ idle: 10_000,
92
+ },
93
+ dialectOptions: {
94
+ statement_timeout: 5000,
95
+ },
96
+ autoLoadModels: true,
97
+ synchronize: false,
98
+ logging: false,
99
+ benchmark: true,
100
+ }),
101
+ })
102
+ ```
103
+
104
+ ### src/common/decorators/current-user.decorator.ts
105
+ `@CurrentUser()` — extracts user from JWT payload on request object.
106
+
107
+ ### src/common/decorators/public.decorator.ts
108
+ `@Public()` — metadata marker to skip global JWT guard.
109
+
110
+ ### src/common/decorators/roles.decorator.ts
111
+ `@Roles('ADMIN')` — metadata for RolesGuard.
112
+
113
+ ### src/common/guards/jwt-auth.guard.ts
114
+ Checks `@Public()` metadata — if present, skip. Otherwise validate JWT.
115
+
116
+ ### src/common/guards/roles.guard.ts
117
+ Reads `@Roles()` metadata from reflector, checks user.role.
118
+
119
+ ### src/common/filters/all-exceptions.filter.ts
120
+ Global exception filter — maps all exceptions to standard envelope:
121
+ `{ statusCode, message, timestamp, path, requestId }`. No stack traces in production.
122
+
123
+ ### src/common/interceptors/logging.interceptor.ts
124
+ Global interceptor — logs every request/response via nestjs-pino Logger with duration.
125
+
126
+ ### src/common/dto/pagination.dto.ts
127
+ ```typescript
128
+ export class PaginationDto {
129
+ @IsOptional() @IsString() cursor?: string
130
+ @IsOptional() @IsInt() @Min(1) @Max(100) @Transform(({ value }) => parseInt(value))
131
+ limit?: number = 20
132
+ }
133
+ ```
134
+
135
+ ### src/auth/* — Full JWT auth module
136
+ - `JwtStrategy` validates token, checks revocation in Redis, attaches user to request
137
+ - `AuthService` — register (argon2.hash), login (argon2.verify), refresh (rotate), logout (revoke)
138
+ - `AuthController` — `@Public()` on all auth routes, strict rate limit
139
+
140
+ ### src/health/health.module.ts
141
+ ```typescript
142
+ TerminusModule — /health (liveness) + /ready (DB + Redis checks)
143
+ ```
144
+
145
+ ### src/utils/uuid.util.ts
146
+ ```typescript
147
+ import { v7 as uuidv7 } from 'uuid'
148
+ export const newId = (): string => uuidv7()
149
+ ```
150
+
151
+ ### main.ts — Global setup
152
+ ```typescript
153
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true }))
154
+ app.useGlobalFilters(new AllExceptionsFilter())
155
+ app.useGlobalInterceptors(new LoggingInterceptor())
156
+ // JwtAuthGuard registered as APP_GUARD in AppModule providers
157
+ // Helmet
158
+ app.use(helmet())
159
+ // CORS
160
+ app.enableCors({ origin: config.get('CORS_ORIGINS').split(','), credentials: true })
161
+ // Swagger
162
+ const swaggerConfig = new DocumentBuilder()
163
+ .setTitle('API').setVersion('1.0').addBearerAuth().build()
164
+ SwaggerModule.setup('api/docs', app, SwaggerModule.createDocument(app, swaggerConfig))
165
+ // Listen
166
+ await app.listen(config.get('PORT', 3000))
167
+ ```
168
+
169
+ ### .sequelizerc
170
+ ```javascript
171
+ module.exports = {
172
+ 'config': 'src/database/sequelize-cli.cjs',
173
+ 'migrations-path': 'db/migrations',
174
+ 'seeders-path': 'db/seeders',
175
+ }
176
+ ```
177
+
178
+ ### src/database/sequelize-cli.cjs
179
+ Loads DATABASE_URL from .env for sequelize-cli.
180
+
181
+ ### Docker
182
+ `Dockerfile` — multi-stage: deps → builder → runner, non-root user, HEALTHCHECK.
183
+ `docker-compose.yml` — app + postgres:16 + redis:7 with healthchecks.
184
+
185
+ ### GitHub Actions
186
+ `.github/workflows/ci.yml` — lint → typecheck → test → npm audit → secret-scan.
187
+
188
+ ### Scaffold Docs
189
+ `AGENTS.md`, `ARCHITECTURE.md`, `ADR-000-infrastructure.md`, `docs/product-specs/index.md`,
190
+ `docs/exec-plans/tech-debt-tracker.md`, `docs/QUALITY_SCORE.md`, `.env.example`, `.gitignore`.
191
+
192
+ ---
193
+
194
+ ## Gate Check
195
+ ```bash
196
+ npx tsc --noEmit
197
+ npx eslint src/ --max-warnings=0
198
+ npx jest --no-coverage 2>&1 | tail -5
199
+ curl http://localhost:3000/health
200
+ curl http://localhost:3000/ready
201
+ ```
202
+
203
+ ## Commit
204
+ ```bash
205
+ git add -A
206
+ git commit -m "chore(infra): phase 0 NestJS infrastructure setup
207
+
208
+ - NestJS 11 + TypeScript 5 strict
209
+ - @nestjs/sequelize + sequelize-typescript + sequelize-cli
210
+ - Global JWT guard (opt-out with @Public())
211
+ - Global ValidationPipe, AllExceptionsFilter, LoggingInterceptor
212
+ - nestjs-pino structured logging
213
+ - @nestjs/swagger Swagger UI at /api/docs
214
+ - @nestjs/terminus health checks (/health + /ready)
215
+ - @nestjs/throttler rate limiting
216
+ - @nestjs/bullmq queue infrastructure
217
+ - Helmet security headers
218
+ - Docker multi-stage + docker-compose
219
+ - GitHub Actions CI
220
+ - ADR-000: infrastructure decisions"
221
+ git push origin main
222
+ ```
@@ -0,0 +1,25 @@
1
+ # /write-spec
2
+ 1. Get next number: ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep SPEC | sort | tail -1
3
+ 2. Write to docs/product-specs/draft/SPEC-XXX-{slug}.md
4
+
5
+ Template:
6
+ ```markdown
7
+ # SPEC-XXX — {Name}
8
+ **Status:** DRAFT
9
+ **Created:** YYYY-MM-DD
10
+ **Plan:** —
11
+
12
+ ## Problem Statement
13
+ ## What We're Building
14
+ ## Core Entities
15
+ ## API Endpoints (will map to NestJS controllers)
16
+ ## Business Rules
17
+ ## State Machines (if any)
18
+ ## Non-Functional Requirements
19
+ ## Out of Scope (v1)
20
+ ## Acceptance Criteria
21
+ - [ ] criterion
22
+ ```
23
+
24
+ 3. Update docs/product-specs/index.md
25
+ 4. Tell human: move to ready/ and change Status to READY, then run /write-plan.
@@ -0,0 +1,18 @@
1
+ # /write-plan
2
+ 1. Check docs/product-specs/ready/ — stop if empty
3
+ 2. Get next plan number
4
+ 3. Write to docs/exec-plans/active/PLAN-XXX-{slug}.md
5
+
6
+ Layer order for each NestJS feature:
7
+ | # | Layer | Gate |
8
+ |---|---|---|
9
+ | 1 | Model | paranoid, UUIDv7, indexes |
10
+ | 2 | Migration | runs clean, has down() |
11
+ | 3 | DTOs | class-validator + @ApiProperty on every field |
12
+ | 4 | Repository | Zod parse, cursor pagination, ownership, no N+1 |
13
+ | 5 | Service | no HTTP, NestJS exceptions, ≥90% coverage |
14
+ | 6 | Controller | @ApiTags, @ApiOperation, @ApiResponse, one-liner handlers |
15
+ | 7 | Module | SequelizeModule.forFeature, registered in AppModule |
16
+ | 8 | Tests | service unit (mock repo) + e2e (201, 401, 422) |
17
+
18
+ Tell human: Run /build-layer to start Layer 1.
@@ -0,0 +1,242 @@
1
+ # /build-layer — Build Next Layer from Active Plan
2
+
3
+ Triggered by: `/build-layer` (no argument)
4
+
5
+ ---
6
+
7
+ ## Step 1 — Find Active Plan and Next Layer
8
+ ```bash
9
+ PLAN=$(ls docs/exec-plans/active/*.md 2>/dev/null | head -1)
10
+ [[ -z "$PLAN" ]] && echo "No active plan. Run /write-plan first." && exit 1
11
+ cat "$PLAN"
12
+ ```
13
+ Find first `[ ]` row. If all `[x]` → run `/garbage-collect`.
14
+
15
+ ---
16
+
17
+ ## Step 2 — Load Context
18
+ Read: active plan, spec, `ARCHITECTURE.md`, path-scoped rule:
19
+ - Model → `.claude/rules/database.md` + `.claude/rules/architecture.md`
20
+ - DTOs → `.claude/rules/api.md` + `.claude/rules/architecture.md`
21
+ - Repository → `.claude/rules/database.md` + `.claude/rules/security.md`
22
+ - Service → `.claude/rules/architecture.md` + `.claude/rules/security.md`
23
+ - Controller → `.claude/rules/api.md` + `.claude/rules/security.md`
24
+ - Module → `.claude/rules/architecture.md`
25
+ - Tests → `.claude/rules/testing.md`
26
+
27
+ ---
28
+
29
+ ## Step 3 — Build the Layer
30
+
31
+ ### Model (`src/[feature]/models/[feature].model.ts`)
32
+ ```typescript
33
+ import { Table, Column, Model, DataType, Default, DeletedAt, CreatedAt, UpdatedAt, BelongsTo, ForeignKey, HasMany, Index } from 'sequelize-typescript'
34
+ import { newId } from '../../utils/uuid.util'
35
+
36
+ @Table({
37
+ tableName: 'applications',
38
+ paranoid: true, // ← always
39
+ indexes: [
40
+ { fields: ['user_id'] },
41
+ { fields: ['user_id', 'stage'] },
42
+ ],
43
+ })
44
+ export class Application extends Model {
45
+ @Default(() => newId()) // ← UUIDv7 always
46
+ @Column({ type: DataType.UUID, primaryKey: true })
47
+ id!: string
48
+
49
+ @ForeignKey(() => User)
50
+ @Column(DataType.UUID)
51
+ userId!: string
52
+
53
+ @Column(DataType.STRING(200)) company!: string
54
+ @Column(DataType.STRING(200)) role!: string
55
+ @Column({ type: DataType.ENUM(...STAGES), defaultValue: 'SAVED' }) stage!: string
56
+
57
+ @CreatedAt createdAt!: Date
58
+ @UpdatedAt updatedAt!: Date
59
+ @DeletedAt deletedAt?: Date
60
+ }
61
+ ```
62
+
63
+ ### Migration (`db/migrations/YYYYMMDDHHMMSS-create-[feature].js`)
64
+ ```javascript
65
+ module.exports = {
66
+ up: async (queryInterface, Sequelize) => {
67
+ await queryInterface.createTable('applications', {
68
+ id: { type: Sequelize.UUID, primaryKey: true },
69
+ user_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'users', key: 'id' } },
70
+ company: { type: Sequelize.STRING(200), allowNull: false },
71
+ stage: { type: Sequelize.ENUM(...stages), defaultValue: 'SAVED' },
72
+ created_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },
73
+ updated_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },
74
+ deleted_at: { type: Sequelize.DATE, allowNull: true },
75
+ })
76
+ await queryInterface.sequelize.query(
77
+ 'CREATE INDEX CONCURRENTLY idx_applications_user_id ON applications(user_id)'
78
+ )
79
+ },
80
+ down: async (queryInterface) => queryInterface.dropTable('applications'),
81
+ }
82
+ ```
83
+ Run: `npm run db:migrate`
84
+
85
+ ### DTOs (`src/[feature]/dto/`)
86
+ ```typescript
87
+ // create-application.dto.ts
88
+ import { IsString, IsOptional, MinLength, MaxLength, IsUrl } from 'class-validator'
89
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
90
+
91
+ export class CreateApplicationDto {
92
+ @IsString() @MinLength(1) @MaxLength(200)
93
+ @ApiProperty({ example: 'Acme Corp', description: 'Company name' })
94
+ company: string
95
+
96
+ @IsString() @MinLength(1) @MaxLength(200)
97
+ @ApiProperty({ example: 'Software Engineer' })
98
+ role: string
99
+
100
+ @IsOptional() @IsUrl()
101
+ @ApiPropertyOptional({ example: 'https://jobs.acme.com/123' })
102
+ postingUrl?: string
103
+ }
104
+
105
+ // application-response.dto.ts — what we return (controls serialisation)
106
+ export class ApplicationResponseDto {
107
+ @ApiProperty() id: string
108
+ @ApiProperty() company: string
109
+ @ApiProperty({ enum: ApplicationStage }) stage: ApplicationStage
110
+ @ApiProperty() createdAt: string
111
+ }
112
+ ```
113
+
114
+ ### Repository (`src/[feature]/[feature].repository.ts`)
115
+ ```typescript
116
+ @Injectable()
117
+ export class ApplicationRepository {
118
+ constructor(
119
+ @InjectModel(Application) private appModel: typeof Application,
120
+ @InjectConnection() private sequelize: Sequelize,
121
+ ) {}
122
+
123
+ async findById(id: string, userId: string): Promise<ApplicationRecord> {
124
+ const raw = await this.appModel.findOne({
125
+ where: { id, userId }, // ownership always
126
+ include: [{ model: Note }, { model: Contact }], // eager load
127
+ })
128
+ if (!raw) throw new NotFoundException(`Application ${id} not found`)
129
+ return ApplicationSchema.parse(raw.toJSON()) // Zod ALWAYS
130
+ }
131
+
132
+ async list(userId: string, cursor?: PageCursor, limit = 20): Promise<PageResult<ApplicationRecord>> {
133
+ const where: WhereOptions = { userId }
134
+ if (cursor) {
135
+ where[Op.or as symbol] = [
136
+ { createdAt: { [Op.lt]: cursor.createdAt } },
137
+ { createdAt: cursor.createdAt, id: { [Op.lt]: cursor.id } },
138
+ ]
139
+ }
140
+ const rows = await this.appModel.findAll({
141
+ where, order: [['createdAt', 'DESC'], ['id', 'DESC']], limit: limit + 1,
142
+ })
143
+ const hasMore = rows.length > limit
144
+ const items = hasMore ? rows.slice(0, limit) : rows
145
+ return {
146
+ items: items.map(r => ApplicationSchema.parse(r.toJSON())),
147
+ nextCursor: hasMore ? { id: items.at(-1)!.id, createdAt: items.at(-1)!.createdAt } : null,
148
+ hasMore,
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Service (`src/[feature]/[feature].service.ts`)
155
+ ```typescript
156
+ @Injectable()
157
+ export class ApplicationService {
158
+ private readonly logger = new Logger(ApplicationService.name)
159
+
160
+ constructor(private readonly applicationRepository: ApplicationRepository) {}
161
+
162
+ async findOne(userId: string, id: string): Promise<ApplicationRecord> {
163
+ return this.applicationRepository.findById(id, userId)
164
+ // NotFoundException thrown by repo if not found — no try/catch needed
165
+ }
166
+
167
+ async transitionStage(userId: string, id: string, to: ApplicationStage): Promise<ApplicationRecord> {
168
+ const app = await this.applicationRepository.findById(id, userId)
169
+ const validNext = VALID_TRANSITIONS[app.stage]
170
+ if (!validNext.includes(to)) {
171
+ throw new ConflictException(`Cannot transition from ${app.stage} to ${to}`)
172
+ }
173
+ const result = await this.applicationRepository.updateStage(id, to)
174
+ this.logger.log({ event: 'application.stage_changed', id, from: app.stage, to })
175
+ return result
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### Controller (`src/[feature]/[feature].controller.ts`)
181
+ ```typescript
182
+ @ApiTags('Applications')
183
+ @ApiBearerAuth()
184
+ @Controller('v1/applications')
185
+ export class ApplicationController {
186
+ constructor(private readonly appService: ApplicationService) {}
187
+
188
+ @Get(':id')
189
+ @ApiOperation({ summary: 'Get application by ID' })
190
+ @ApiOkResponse({ type: ApplicationResponseDto })
191
+ @ApiNotFoundResponse()
192
+ findOne(
193
+ @Param('id', ParseUUIDPipe) id: string,
194
+ @CurrentUser() user: JwtPayload,
195
+ ) {
196
+ return this.appService.findOne(user.sub, id)
197
+ }
198
+
199
+ @Post()
200
+ @HttpCode(HttpStatus.CREATED)
201
+ @ApiOperation({ summary: 'Create application' })
202
+ @ApiCreatedResponse({ type: ApplicationResponseDto })
203
+ create(@Body() dto: CreateApplicationDto, @CurrentUser() user: JwtPayload) {
204
+ return this.appService.createApplication(user.sub, dto)
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Module (`src/[feature]/[feature].module.ts`)
210
+ ```typescript
211
+ @Module({
212
+ imports: [SequelizeModule.forFeature([Application, Note, Contact])],
213
+ controllers: [ApplicationController],
214
+ providers: [ApplicationRepository, ApplicationService],
215
+ exports: [ApplicationService], // only if other modules need it
216
+ })
217
+ export class ApplicationModule {}
218
+ ```
219
+ Then add to `AppModule.imports`.
220
+
221
+ ### Tests
222
+ - `src/[feature]/[feature].service.spec.ts` — mock repository, test all error paths
223
+ - `test/[feature].e2e-spec.ts` — supertest full HTTP cycle
224
+
225
+ ---
226
+
227
+ ## Step 4 — Gate
228
+ Call `gate-checker` agent. Auto-fix failures. Re-run up to 3 times.
229
+
230
+ ## Step 5 — ADR if needed
231
+ `docs/design-docs/decisions/ADR-XXX-{slug}.md`
232
+
233
+ ## Step 6 — Commit
234
+ ```bash
235
+ git add -A
236
+ git commit -m "{feat|chore|test}({feature}): {description}
237
+ PLAN-XXX Layer N/Total"
238
+ git push origin main
239
+ ```
240
+
241
+ ## Step 7 — Report + Wait
242
+ Show progress, auto-fixed items, ADR. Wait for human `yes` before next layer.
@@ -0,0 +1,4 @@
1
+ # /validate-layer
2
+ Calls gate-checker agent. Read-only — no auto-fix, no commit.
3
+ Present full PASS/FAIL report.
4
+ To auto-fix: run /build-layer.
@@ -0,0 +1,6 @@
1
+ # /push-layer
2
+ 1. npx tsc --noEmit && npx eslint src/ --max-warnings=0
3
+ 2. If fails → stop
4
+ 3. git add -A
5
+ 4. git commit -m "{type}({scope}): {description from active plan}"
6
+ 5. git push origin main