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,207 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.py"
4
+ ---
5
+
6
+ # Testing Rules — Auto-injected on test file edits
7
+
8
+ ## Coverage Thresholds (enforced in CI)
9
+
10
+ | Layer | Threshold | What to test |
11
+ |---|---|---|
12
+ | `src/utils/` | **100%** | Every branch, every edge case |
13
+ | `src/services/` | **90%** | Happy path + all error paths + state machines |
14
+ | `src/repo/` | **80%** | Query results, model_validate, ownership, cursor pagination |
15
+ | `src/runtime/routers/` | **75%** | Auth required, 422 validation, 404, 422 |
16
+ | `src/providers/` | **70%** | Core functionality |
17
+
18
+ ## Required Test Patterns
19
+
20
+ ### Service Tests — error paths mandatory
21
+ ```python
22
+ import pytest
23
+ from src.services.application_service import ApplicationService
24
+ from src.types.exceptions import StageTransitionError, TerminalStageError
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_transition_stage_invalid_raises(session, user_id, app_id):
28
+ with pytest.raises(StageTransitionError):
29
+ await ApplicationService.transition_stage(session, user_id, app_id, "ACCEPTED")
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_transition_from_terminal_raises(session, user_id, accepted_app_id):
33
+ with pytest.raises(TerminalStageError):
34
+ await ApplicationService.transition_stage(session, user_id, accepted_app_id, "APPLIED")
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_ownership_enforced(session, other_user_id, app_id):
38
+ with pytest.raises(ApplicationNotFoundError):
39
+ await ApplicationService.get(session, other_user_id, app_id)
40
+ ```
41
+
42
+ ### Route Tests — test HTTP contract
43
+ ```python
44
+ @pytest.mark.asyncio
45
+ async def test_create_application_requires_auth(async_client: AsyncClient):
46
+ response = await async_client.post("/api/v1/applications", json={...})
47
+ assert response.status_code == 401
48
+
49
+ @pytest.mark.asyncio
50
+ async def test_create_application_validates_body(auth_client: AsyncClient):
51
+ response = await auth_client.post("/api/v1/applications", json={"role": "Engineer"})
52
+ assert response.status_code == 422
53
+
54
+ @pytest.mark.asyncio
55
+ async def test_create_application_returns_envelope(auth_client: AsyncClient):
56
+ response = await auth_client.post("/api/v1/applications", json=valid_body)
57
+ assert response.status_code == 201
58
+ body = response.json()
59
+ # All responses are enveloped: { ok, data, meta } — see .claude/rules/api.md
60
+ assert body["ok"] is True
61
+ assert "request_id" in body["meta"]
62
+ data = body["data"]
63
+ assert "id" in data
64
+ assert "stage" in data
65
+ ```
66
+
67
+ ### Pydantic Schema Tests
68
+ ```python
69
+ def test_create_input_rejects_missing_company():
70
+ with pytest.raises(ValidationError):
71
+ CreateApplicationInput(role="Engineer") # missing company
72
+
73
+ def test_stage_enum_rejects_invalid():
74
+ with pytest.raises(ValidationError):
75
+ ApplicationResponse(stage="PENDING", ...) # not a valid stage
76
+ ```
77
+
78
+ ### Architecture Tests
79
+ ```python
80
+ # tests/architecture/test_layers.py
81
+ import ast, pathlib
82
+
83
+ def get_imports(filepath: pathlib.Path) -> list[str]:
84
+ # NOTE: ast.Import has NO .module attribute (only ast.ImportFrom does).
85
+ # Handling them together as `node.module` raises AttributeError on any
86
+ # plain `import x` statement — the test would crash, not enforce. Split them.
87
+ tree = ast.parse(filepath.read_text())
88
+ out: list[str] = []
89
+ for node in ast.walk(tree):
90
+ if isinstance(node, ast.ImportFrom):
91
+ out.append(node.module or "")
92
+ elif isinstance(node, ast.Import):
93
+ out.extend(alias.name for alias in node.names)
94
+ return out
95
+
96
+ def test_service_does_not_import_fastapi():
97
+ services = pathlib.Path("src/services").rglob("*.py")
98
+ for f in services:
99
+ imports = get_imports(f)
100
+ assert not any("fastapi" in i for i in imports), \
101
+ f"{f} imports fastapi — services must not depend on HTTP layer"
102
+
103
+ def test_utils_has_no_domain_imports():
104
+ utils = pathlib.Path("src/utils").rglob("*.py")
105
+ domain = ["src.types", "src.config", "src.models", "src.repo", "src.services", "src.runtime"]
106
+ for f in utils:
107
+ imports = get_imports(f)
108
+ for imp in imports:
109
+ assert not any(d in imp for d in domain), \
110
+ f"{f} imports domain code: {imp}"
111
+ ```
112
+
113
+ ## conftest.py Fixtures Required
114
+
115
+ - `session` — async SQLAlchemy session (test DB, auto-rollback)
116
+ - `async_client` — httpx `AsyncClient` unauthenticated
117
+ - `auth_client` — httpx `AsyncClient` with valid Bearer token
118
+ - `user_id` — seeded test user UUID
119
+ - `login_as(user_id: UUID)` — helper that returns AsyncClient with token for specific user
120
+
121
+ ---
122
+
123
+ ## Required Integration Tests
124
+
125
+ ### Cross-User Isolation Test (CRITICAL — security boundary)
126
+
127
+ ```python
128
+ # tests/integration/test_cross_user_isolation.py
129
+ import pytest
130
+ from uuid import uuid4
131
+ from httpx import AsyncClient
132
+
133
+ @pytest.mark.asyncio
134
+ async def test_user_cannot_access_other_user_resource(
135
+ session,
136
+ login_as, # fixture from conftest.py
137
+ ):
138
+ """User B attempting to access User A's resource returns 404 (not 403)."""
139
+ user_a_id = uuid4()
140
+ user_b_id = uuid4()
141
+
142
+ # User A creates a resource
143
+ client_a = await login_as(user_a_id)
144
+ response = await client_a.post("/api/v1/applications", json={
145
+ "company": "Acme Corp",
146
+ "role": "Engineer",
147
+ })
148
+ assert response.status_code == 201
149
+ resource_id = response.json()["data"]["id"]
150
+
151
+ # User B attempts to GET User A's resource
152
+ client_b = await login_as(user_b_id)
153
+ response = await client_b.get(f"/api/v1/applications/{resource_id}")
154
+ assert response.status_code == 404 # NOT 403 (don't leak existence)
155
+ assert response.json()["error"]["code"] == "NOT_FOUND"
156
+
157
+ # User B attempts to UPDATE User A's resource
158
+ response = await client_b.patch(f"/api/v1/applications/{resource_id}", json={
159
+ "stage": "INTERVIEWING",
160
+ })
161
+ assert response.status_code == 404
162
+
163
+ # User B attempts to DELETE User A's resource
164
+ response = await client_b.delete(f"/api/v1/applications/{resource_id}")
165
+ assert response.status_code == 404
166
+ ```
167
+
168
+ **Why 404 not 403?** Returning 403 leaks the existence of the resource (user can enumerate UUIDs to discover other users' data). 404 reveals nothing.
169
+
170
+ ### GDPR Deletion Test
171
+
172
+ ```python
173
+ # tests/integration/test_gdpr_deletion.py
174
+ import pytest
175
+ from src.utils.gdpr_delete_util import gdpr_delete_user
176
+
177
+ @pytest.mark.asyncio
178
+ async def test_gdpr_delete_anonymizes_pii_keeps_audit(session, user_id):
179
+ """GDPR deletion anonymizes PII, hard-deletes sensitive rows, keeps audit logs."""
180
+ # Setup: User has applications, payments, audit logs
181
+ # (create test data here)
182
+
183
+ # Execute GDPR deletion
184
+ await gdpr_delete_user(session, user_id)
185
+ await session.commit()
186
+
187
+ # Assertions:
188
+ # 1. User PII anonymized
189
+ user = await session.get(User, user_id)
190
+ assert user.email == f"deleted-{user_id}@gdpr.local"
191
+ assert user.name == "Deleted User"
192
+ assert user.deleted_at is not None
193
+
194
+ # 2. Sensitive data hard-deleted (payments)
195
+ from src.models.payment import Payment
196
+ payments = await session.execute(
197
+ select(Payment).where(Payment.user_id == user_id)
198
+ )
199
+ assert payments.scalars().all() == []
200
+
201
+ # 3. Audit logs KEPT (compliance requirement)
202
+ from src.models.audit_log import AuditLog
203
+ logs = await session.execute(
204
+ select(AuditLog).where(AuditLog.user_id == user_id)
205
+ )
206
+ assert len(logs.scalars().all()) > 0 # audit trail preserved
207
+ ```
@@ -0,0 +1,50 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv *)",
5
+ "Bash(python *)",
6
+ "Bash(python3 *)",
7
+ "Bash(pytest *)",
8
+ "Bash(ruff *)",
9
+ "Bash(bandit *)",
10
+ "Bash(mypy *)",
11
+ "Bash(alembic *)",
12
+ "Bash(git *)",
13
+ "Bash(bash *)",
14
+ "Bash(sh *)",
15
+ "Bash(cat *)",
16
+ "Bash(ls *)",
17
+ "Bash(grep *)",
18
+ "Bash(find *)",
19
+ "Bash(wc *)",
20
+ "Bash(k6 *)",
21
+ "Bash(pre-commit *)",
22
+ "Read(//Users/shreyasss15/Projects-Clean/shreyas/voice-guard/harness-template-fastapi/**)",
23
+ "Read(//Users/shreyasss15/Projects-Clean/shreyas/voice-guard/**)",
24
+ "Bash(git add *)"
25
+ ],
26
+ "deny": [],
27
+ "additionalDirectories": [
28
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs/.claude/skills/01-write-spec",
29
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs/.claude/skills/02-write-plan",
30
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs/.claude/skills/01-write-roadmap",
31
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs/docs/product-specs",
32
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs/.claude",
33
+ "/Users/shreyasss15/Projects-Clean/shreyas/harness-template/harness-nextjs"
34
+ ]
35
+ },
36
+ "model": "claude-opus-4-8",
37
+ "hooks": {
38
+ "PostToolUse": [
39
+ {
40
+ "matcher": "Write|Edit|MultiEdit",
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "bash .claude/hooks/post-write.sh"
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ }
@@ -0,0 +1,311 @@
1
+ # /infra-setup — Phase 0 Infrastructure Setup
2
+
3
+ **Verified:** 2026-06-18 · **Staleness threshold:** 60 days
4
+ **Libraries:** FastAPI 0.115, SQLAlchemy 2.0, PyJWT 2.9, pwdlib 0.2, pytest 8, ruff 0.8, mypy 1.13, uv 0.4
5
+ **Sources:** [FastAPI](https://fastapi.tiangolo.com/), [PyJWT](https://pypi.org/project/PyJWT/), [pwdlib](https://pypi.org/project/pwdlib/), [SQLAlchemy](https://www.sqlalchemy.org/), [uv](https://docs.astral.sh/uv/)
6
+
7
+ Triggered by: `/infra-setup`
8
+ Run ONCE when starting a new project. Check `pyproject.toml` exists first — if yes, abort.
9
+
10
+ ---
11
+
12
+ ## Step 1 — Python + uv Init
13
+ ```bash
14
+ uv init --python 3.14
15
+ echo "3.14" > .python-version
16
+ ```
17
+
18
+ Create `pyproject.toml` with all dependencies:
19
+
20
+ ```toml
21
+ [project]
22
+ name = "your-service-name"
23
+ version = "0.0.1"
24
+ requires-python = ">=3.14"
25
+
26
+ # PEP 621: dependencies is an ARRAY of PEP 508 strings (NOT a Poetry-style table).
27
+ # Extras go in brackets: "uvicorn[standard]>=0.32". `uv sync` rejects the table form.
28
+ dependencies = [
29
+ "fastapi>=0.115",
30
+ "uvicorn[standard]>=0.32",
31
+ "sqlalchemy[asyncio]>=2.0",
32
+ "alembic>=1.14",
33
+ "asyncpg>=0.30",
34
+ "pydantic>=2.0",
35
+ "pydantic-settings>=2.0",
36
+ "email-validator>=2.0",
37
+ "PyJWT[crypto]>=2.9",
38
+ "pwdlib[argon2]>=0.2",
39
+ "argon2-cffi>=23.0",
40
+ "python-multipart>=0.0.9",
41
+ "celery[redis]>=5.0",
42
+ "redis>=5.0",
43
+ "httpx>=0.28",
44
+ "tenacity>=9.0",
45
+ "structlog>=24.0",
46
+ "opentelemetry-sdk>=1.0",
47
+ "opentelemetry-instrumentation-fastapi>=0.49",
48
+ "opentelemetry-instrumentation-sqlalchemy>=0.49",
49
+ "opentelemetry-exporter-otlp>=1.0",
50
+ "prometheus-fastapi-instrumentator>=7.0",
51
+ "slowapi>=0.1",
52
+ "limits>=3.0",
53
+ "uuid-utils>=0.9",
54
+ ]
55
+
56
+ # Dev tooling — PEP 735 dependency group (installed by `uv sync` by default).
57
+ [dependency-groups]
58
+ dev = [
59
+ "pytest>=8.0",
60
+ "pytest-asyncio>=0.24",
61
+ "pytest-cov>=6.0",
62
+ "factory-boy>=3.0",
63
+ "faker>=30.0",
64
+ "hypothesis>=6.0", # property-based tests
65
+ "schemathesis>=3.19,<4", # OpenAPI contract/fuzz tests (v4 moved the from_asgi loader; pin <4)
66
+ "mutmut>=3.0", # mutation testing (run on critical service modules)
67
+ "ruff>=0.8",
68
+ "bandit>=1.7", # full security scan in CI (ruff `S` is a fast subset for pre-commit)
69
+ "mypy>=1.13",
70
+ "pre-commit>=4.0",
71
+ "sqlalchemy[mypy]>=2.0",
72
+ ]
73
+
74
+ # Application (not a library) — don't try to build/install the project itself.
75
+ [tool.uv]
76
+ package = false
77
+
78
+ [tool.ruff]
79
+ line-length = 100
80
+ target-version = "py314"
81
+
82
+ [tool.ruff.lint]
83
+ # `S` = flake8-bandit fast subset (pre-commit); full `bandit -ll` runs in CI (see dev-deps).
84
+ select = ["E", "F", "I", "T20", "ANN", "S", "UP"]
85
+ # ANN101/ANN102 were REMOVED from ruff (>=0.2) — do not re-add. S101 = allow assert.
86
+ # UP046/UP047 (PEP 695 generic rewrites) are OFF: `class X(BaseModel, Generic[T])` and
87
+ # `def f(x: T)` stay valid — pydantic supports both styles and the docs use Generic[T].
88
+ # (PEP 695 `class X[T]` is allowed too; it just isn't forced.)
89
+ ignore = ["S101", "UP046", "UP047"]
90
+
91
+ [tool.ruff.lint.per-file-ignores]
92
+ "tests/**" = ["ANN", "S"]
93
+ "src/config/settings.py" = ["S105"]
94
+
95
+ [tool.mypy]
96
+ python_version = "3.14"
97
+ strict = true
98
+ ignore_missing_imports = true
99
+ plugins = ["pydantic.mypy", "sqlalchemy.ext.mypy.plugin"]
100
+
101
+ [tool.pytest.ini_options]
102
+ asyncio_mode = "auto"
103
+ testpaths = ["tests"]
104
+ # REQUIRED: the app is `package = false` (not installed), so `from src... import` in
105
+ # tests raises ModuleNotFoundError without this. (Verified by dogfood: collection failed.)
106
+ pythonpath = ["."]
107
+ # Do NOT put --cov here: a global addopts coverage gate makes EVERY partial run fail
108
+ # (e.g. `pytest tests/architecture/` → 0% coverage → fail). Coverage is invoked
109
+ # explicitly — per-layer by the gate-checker, aggregate in CI. (Verified by dogfood:
110
+ # `pytest tests/architecture/` failed only on the coverage gate, not the tests.)
111
+ addopts = "-ra -q"
112
+
113
+ [tool.coverage.run]
114
+ source = ["src"]
115
+ omit = ["src/runtime/main.py", "src/providers/telemetry.py"]
116
+
117
+ [tool.coverage.report]
118
+ # Global floor = lowest per-layer threshold (providers 70). Per-layer thresholds
119
+ # (utils 100 / services 90 / repo 80 / runtime 75 / providers 70) are enforced by the
120
+ # gate-checker agent. Keep this ≤ the lowest per-layer floor so a full `pytest` run does
121
+ # not fail spuriously when a low-threshold layer drags the aggregate down.
122
+ fail_under = 70
123
+ ```
124
+
125
+ ## Step 2 — Install Dependencies
126
+ ```bash
127
+ uv sync
128
+ ```
129
+
130
+ ## Step 3 — Directory Structure
131
+ ```bash
132
+ mkdir -p \
133
+ src/{types,config,models,repo,services,utils} \
134
+ src/runtime/{routers/v1,workers,exception_handlers} \
135
+ src/providers/auth \
136
+ tests/{architecture,unit/{services,repo,utils},integration,contract,load} \
137
+ alembic/versions \
138
+ docs/{product-specs/{draft,ready},exec-plans/{active,completed},design-docs/decisions,generated} \
139
+ infra/monitoring \
140
+ .github/workflows
141
+ ```
142
+
143
+ ## Step 4 — Environment Setup
144
+ Create `.env.example`:
145
+ ```
146
+ APP_ENV=development
147
+ PORT=8000
148
+ DATABASE_URL=postgresql+asyncpg://app:app@localhost:5432/app
149
+ DATABASE_POOL_SIZE=10
150
+ DATABASE_POOL_MIN=2
151
+ REDIS_URL=redis://localhost:6379
152
+ JWT_SECRET=
153
+ JWT_ACCESS_EXPIRY_SECONDS=900
154
+ JWT_REFRESH_EXPIRY_SECONDS=604800
155
+ CORS_ORIGINS=http://localhost:3000
156
+ LOG_LEVEL=INFO
157
+ SERVICE_NAME=your-service-name
158
+ APP_VERSION=0.0.1
159
+ OTEL_EXPORTER_OTLP_ENDPOINT=
160
+ CELERY_BROKER_URL=redis://localhost:6379/0
161
+ CELERY_RESULT_BACKEND=redis://localhost:6379/0
162
+ ```
163
+
164
+ Create `src/config/settings.py` — pydantic-settings BaseSettings, `process.exit` equivalent on failure.
165
+ Create `src/config/constants.py` — app-wide constants.
166
+ Create `src/config/timeouts.py` — `DB_QUERY_MS=5000`, `EXTERNAL_API_MS=10000`.
167
+
168
+ ## Step 5 — Providers
169
+ Create `src/providers/logger.py` — structlog JSON renderer with redaction.
170
+ Create `src/providers/database.py` — async SQLAlchemy engine + session factory + `get_session` Depends.
171
+ Create `src/providers/redis_client.py` — redis-py async client singleton.
172
+ Create `src/providers/telemetry.py` — OTel SDK init.
173
+ Create `src/providers/auth/jwt.py` — PyJWT sign/verify/decode with RS256 support.
174
+ Create `src/providers/auth/password.py` — pwdlib PasswordHash for argon2id hashing.
175
+ Create `src/providers/auth/middleware.py` — `require_auth` Depends, `AuthContext`.
176
+ - The `Authorization` header MUST be OPTIONAL in the signature (`Header(default=None)`); raise `AuthError` (→401) when missing/invalid. A required `Header(...)` makes FastAPI return **422 + a non-enveloped body** for the common no-token case — wrong status AND breaks the envelope contract. (Verified by dogfood: required Header → 422.)
177
+ Create `src/providers/rate_limit.py` — slowapi limiter with Redis store (3 tiers).
178
+
179
+ ## Step 6 — Runtime Base
180
+ Create `src/runtime/exception_handlers.py` — maps DomainError subclasses → HTTPException, emits ErrorEnvelope.
181
+ - ALSO register a `RequestValidationError` handler that returns an **enveloped** 422 (`{ok:false, error:{code:"VALIDATION_ERROR",...}, meta}`). Without it, every 422 returns FastAPI's default `{detail:[...]}`, violating "envelope always".
182
+ Create `src/runtime/middleware.py` — SecurityHeadersMiddleware (7 headers), request ID.
183
+ Create `src/runtime/routers/v1/health.py` — `/v1/health` + `/v1/ready`.
184
+ Create `src/runtime/main.py` — FastAPI app, all middleware, all routers, lifespan handler.
185
+ Create `src/runtime/response_util.py` — `ok()`, `err()` builders (import envelope types from `src.types.common`).
186
+ - MUST live in `runtime/`, NOT `utils/` — `utils` may not import domain types, and `ok()` returns `SuccessEnvelope`. Putting it in utils fails `tests/architecture/test_layers.py::test_utils_has_no_domain_imports`. (Verified by dogfood: it fails.)
187
+ Create `src/utils/cursor_util.py` — base64url encode/decode for opaque cursor pagination.
188
+ - `decode_cursor` MUST catch malformed input (`binascii.Error`, `json.JSONDecodeError`, `ValueError`, `UnicodeDecodeError`) and raise `InvalidCursorError` (→400) — NEVER let a bad `?cursor=` value 500. (Verified by dogfood: schemathesis crashed the list endpoint with `?cursor=null`.)
189
+ Create `src/utils/audit_log_util.py` — audit log writer (who, what, resource, timestamp).
190
+ Create `src/utils/gdpr_delete_util.py` — cascade GDPR deletion (anonymize PII, hard-delete sensitive, keep audit).
191
+ Create `src/utils/uuid_util.py` — `new_id()` → uuid7 (use `uuid_utils.compat.uuid7` → returns stdlib `uuid.UUID`).
192
+ Create `src/utils/retry_util.py` — tenacity retry decorator + timeout wrapper.
193
+ Create `src/types/exceptions.py` — `DomainError` base + subclasses: `NotFoundError` (404), `AuthError` (401), `InvalidCursorError` (400), `ValidationDomainError` (422).
194
+ Create `src/types/common.py` — `PageResult[T]`, `AuthContext`, `Meta`, `SuccessEnvelope[T]`, `ErrorEnvelope`.
195
+
196
+ ## Step 7 — Alembic
197
+ ```bash
198
+ uv run alembic init alembic
199
+ ```
200
+ Update `alembic/env.py` to use async engine + read `DATABASE_URL` from settings.
201
+ Update `alembic.ini` to read URL from settings.
202
+
203
+ ## Step 8 — Testing Base
204
+ Create `tests/conftest.py` — async session fixture (auto-rollback), async_client, auth_client, loginAs helper.
205
+ Create `tests/architecture/test_layers.py` — ast-based import graph structural tests.
206
+ Create `tests/integration/test_cross_user_isolation.py` — User A creates resource, User B attempts access (404 not 403).
207
+ Create `tests/integration/test_gdpr_deletion.py` — cascade deletion test (PII anonymized, audit kept).
208
+ Create `tests/contract/test_openapi.py` — schemathesis: load `/openapi.json`, fuzz every endpoint (`schemathesis.from_asgi`).
209
+ - FastAPI 0.115 emits **OpenAPI 3.1**, which schemathesis 3.x only supports experimentally. Enable it in-code: `schemathesis.experimental.OPEN_API_3_1.enable()` (or pass `--experimental=openapi-3.1` to the CLI). Without it: "Open API 3.1.0 ... not fully supported". (Verified by dogfood.)
210
+ - This catches real bugs: fuzzing `?cursor=` with garbage MUST return 400 (`InvalidCursorError`), never 500. (Dogfood found exactly this 500.)
211
+ Create `tests/load/smoke.js` — k6 smoke test scaffold (1 VU, 30s).
212
+ Create `tests/load/stress.js` — k6 stress test (ramping VUs, P95/P99 thresholds).
213
+
214
+ ## Step 9 — Makefile + gate script (single-command DX)
215
+ Create `Makefile` with daily-driver targets — every doc command goes through these:
216
+ ```make
217
+ .PHONY: bootstrap dev lint format typecheck test gate migrate openapi up down
218
+ bootstrap: ; uv sync && uv run pre-commit install # one-command setup
219
+ dev: ; uv run uvicorn src.runtime.main:app --reload
220
+ lint: ; uv run ruff check src/ tests/
221
+ format: ; uv run ruff format src/ tests/
222
+ typecheck: ; uv run mypy src/
223
+ test: ; uv run pytest
224
+ gate: ; bash scripts/gate.sh # deterministic, same checks as gate-checker agent
225
+ migrate: ; uv run alembic upgrade head
226
+ openapi: ; set -a; [ -f .env ] && . ./.env; set +a; uv run python -c "import json,sys; from src.runtime.main import app; json.dump(app.openapi(), sys.stdout)" > openapi.json
227
+ up: ; docker compose up -d
228
+ down: ; docker compose down
229
+ ```
230
+ Create `scripts/gate.sh` — runnable mirror of the gate-checker checks (file-size, print, os.environ, ruff, mypy, bandit, architecture tests, coverage). Exits non-zero on any failure so pre-commit and humans can run the SAME gate the agent runs.
231
+
232
+ ## Step 10 — Docker
233
+ Create `Dockerfile` — multi-stage (deps → runner), non-root user, HEALTHCHECK.
234
+ Create `.dockerignore` — exclude `.git`, `.venv`, `__pycache__`, `.env*`, `tests/`, `docs/`, `*.md`, `.github/` (prevents secret leaks + bloated build context).
235
+ Create `docker-compose.yml` — app + postgres:16 + redis:7 + pgbouncer (commented out).
236
+ Create `infra/monitoring/docker-compose.lgtm.yml` — local LGTM stack (Grafana + Loki + Tempo + Prometheus) wired to the app's OTLP endpoint, so "observability is not optional" is true locally too.
237
+
238
+ ## Step 11 — GitHub Actions + supply chain
239
+ Create `.github/workflows/ci.yml` with INDEPENDENT parallel jobs (security must not be gated behind lint):
240
+ - `quality`: ruff check + ruff format --check + mypy
241
+ - `test`: `pytest --cov=src --cov-report=term-missing --cov-fail-under=70` (coverage is explicit here — NOT in addopts) + schemathesis contract tests (`--experimental=openapi-3.1`)
242
+ - `security`: bandit -ll + pip-audit + gitleaks (secret scan) — runs even if `quality` fails
243
+ - `image`: build Dockerfile → Trivy scan (fail on HIGH/CRITICAL) → generate SBOM (Syft, CycloneDX) → upload artifact
244
+ Use `actions/cache` / `astral-sh/setup-uv` cache. Pin action SHAs.
245
+ Create `.github/workflows/load-test.yml` — k6 smoke on PR against ephemeral env + on staging deploy (P95/error budgets fail the job, not just report).
246
+ Create `.github/dependabot.yml` — weekly `uv`/pip + GitHub Actions + Docker update PRs.
247
+ Create `.gitleaks.toml` and add `gitleaks` + `trivy` notes; wire gitleaks as a pre-commit hook too (Step 12).
248
+
249
+ ## Step 12 — ruff + pre-commit
250
+ Create `ruff.toml` (or in pyproject.toml — already done in Step 1).
251
+ Create `.pre-commit-config.yaml` — ruff-pre-commit + mypy + gitleaks + `bash scripts/gate.sh` (so the gate runs locally before every commit).
252
+ ```bash
253
+ uv run pre-commit install
254
+ ```
255
+
256
+ ## Step 13 — Scaffold Docs
257
+ Write `ADR-000-infrastructure.md` with today's date. Include sections:
258
+ - **Auth Libraries**: PyJWT (JWT), pwdlib (password hashing)
259
+ - Reasoning: python-jose abandoned (last commit 2023, CVEs), passlib broken on Python 3.13+
260
+ - PyJWT: FastAPI official recommendation, actively maintained
261
+ - pwdlib: created by FastAPI Users maintainer, Python 3.13+ compatible
262
+ - Alternatives considered: python-jose (rejected: abandoned), passlib (rejected: broken), bcrypt directly (rejected: argon2id is OWASP recommended)
263
+ - **Task Queue**: Celery vs arq evaluation
264
+ - Celery: sync-first, beat scheduling, chord/chain primitives, larger ecosystem
265
+ - arq: async-native (by Pydantic team), Redis-backed, type-safe, simpler API
266
+ - Current choice: Celery (mature, feature-complete)
267
+ - Trade-off: async tasks require asyncio.run() wrapper in Celery
268
+ - Revisit: if beat scheduling or chord/chain not needed, consider arq for async-first simplicity
269
+ - **OpenAPI → Frontend Sync**: GET /openapi.json feeds frontend /api-sync command
270
+ - Workflow: run backend → copy /openapi.json → run /api-sync in frontend repo
271
+ - **Version Bounds**: `>=` strategy for all dependencies (no pinning)
272
+ - Lock file (uv.lock) pins transitive deps automatically
273
+ - Upper bounds cause dependency hell in libraries
274
+ - Security updates apply immediately via `uv sync --upgrade`
275
+
276
+ Write `docs/design-docs/index.md`, `decisions/index.md`.
277
+ Write `docs/product-specs/index.md`.
278
+ Write `docs/exec-plans/tech-debt-tracker.md`.
279
+
280
+ ## Gate Check
281
+ ```bash
282
+ make gate # deterministic — runs the same checks as the gate-checker agent
283
+ # (equivalently:)
284
+ uv run ruff check src/
285
+ uv run bandit -r src/ -ll
286
+ uv run mypy src/
287
+ uv run pytest tests/architecture/ -v
288
+ ```
289
+
290
+ ## Commit
291
+ ```bash
292
+ git add -A
293
+ git commit -m "chore(infra): phase 0 infrastructure setup
294
+
295
+ - FastAPI 0.115 + Python 3.14 + SQLAlchemy 2 (async)
296
+ - pydantic-settings validated config
297
+ - structlog JSON logging with redaction
298
+ - OpenTelemetry + prometheus-fastapi-instrumentator
299
+ - Celery + Redis queue
300
+ - slowapi rate limiting (Redis store)
301
+ - Health checks: /v1/health + /v1/ready
302
+ - Alembic migrations configured
303
+ - Dockerfile multi-stage, non-root user + .dockerignore
304
+ - docker-compose: app + postgres:16 + redis:7; local LGTM observability stack
305
+ - Makefile (bootstrap/dev/lint/test/gate/migrate/openapi) + scripts/gate.sh
306
+ - CI: parallel quality/test/security/image jobs (ruff, mypy, pytest, schemathesis, bandit, pip-audit, gitleaks, Trivy, SBOM)
307
+ - Dependabot (uv + actions + docker)
308
+ - Architecture structural tests (AST-based)
309
+ - ADR-000: infrastructure decisions"
310
+ git push origin main
311
+ ```
@@ -0,0 +1,61 @@
1
+ # /write-roadmap — Decompose a Product into an Ordered Spec Roadmap
2
+
3
+ **Verified:** 2026-06-19 · **Staleness threshold:** 60 days
4
+ **Libraries:** harness toolchain (uv, ruff, mypy, pytest) — process skill, no external library pins
5
+
6
+ Triggered by: `/write-roadmap` (optional arg: path to a brief file; else ask the human)
7
+
8
+ This is the altitude ABOVE specs. A real product is many specs, ordered by dependency — this
9
+ skill plans that SET. It writes ONLY `docs/product-specs/ROADMAP.md`; it NEVER writes spec files
10
+ (that is `/write-spec`'s job, which owns spec numbering).
11
+
12
+ ## Step 1 — Get the Product Brief
13
+ If invoked with a file path argument, read that file. Otherwise ask the human:
14
+ "Paste the product brief — what we're building, for whom, and the core capabilities."
15
+ Do NOT proceed without a brief.
16
+
17
+ ## Step 2 — Check for an Existing Roadmap (refresh, don't clobber)
18
+ ```bash
19
+ ls docs/product-specs/ROADMAP.md 2>/dev/null && echo "EXISTS — refresh: keep created specs"
20
+ ls docs/product-specs/{draft,ready}/ 2>/dev/null | grep "SPEC-" | sort
21
+ ```
22
+ If a roadmap exists you are REFRESHING: preserve every row that already has a real Spec ID and
23
+ its Status; only add/reorder `NOT_STARTED` rows. Never renumber a created spec.
24
+
25
+ ## Step 3 — Identify Bounded Contexts (Epics)
26
+ From the brief, list the distinct bounded contexts — each owns a cohesive set of entities and
27
+ rules (e.g. Identity, Billing, Catalog). Only the contexts the brief implies; no speculative ones.
28
+
29
+ ## Step 4 — Propose an Ordered Spec List with Dependencies
30
+ Break each epic into shippable specs (one feature each — the `/write-spec` altitude). For each
31
+ spec record: name, epic, and which specs it depends on (an entity / auth / contract another spec
32
+ must establish first). Order the whole list topologically — every spec appears after its
33
+ dependencies. Keep each spec small enough to become ONE execution plan (split if not).
34
+
35
+ ## Step 5 — Pick the Walking-Skeleton First Slice
36
+ Choose the single thinnest spec that exercises the system end-to-end — typically auth + one
37
+ entity + one persisted endpoint with a test — and that depends on nothing. Mark exactly one spec
38
+ as the skeleton. This is what the human builds first to prove every layer's path works.
39
+
40
+ ## Step 6 — Write / Refresh the Roadmap
41
+ Save to `docs/product-specs/ROADMAP.md` using the template already in that file's header
42
+ (self-documenting). Fill: Bounded Contexts, the dependency-ordered Spec Roadmap table, and the
43
+ Build Sequence Rationale.
44
+ - New specs get Spec ID `—` and Status `NOT_STARTED`.
45
+ - On refresh, keep existing Spec IDs and Statuses untouched; never renumber a created spec.
46
+ - Set the **Walking skeleton** header to the chosen first slice.
47
+
48
+ ## Step 7 — Tell the Human
49
+ ```
50
+ Roadmap written: docs/product-specs/ROADMAP.md
51
+ {N} specs across {M} epics, dependency-ordered.
52
+ Walking skeleton (build first): {skeleton spec name}
53
+
54
+ Review the roadmap. When the order looks right:
55
+ 1. Run /write-spec — it reads ROADMAP.md and authors the next NOT_STARTED spec
56
+ (starting with the walking skeleton), pre-filling its epic + dependencies.
57
+ 2. Repeat /write-spec down the list as you go.
58
+
59
+ /write-roadmap does NOT write spec files. It only plans the set. Each spec is authored
60
+ individually by /write-spec, now guided by this roadmap.
61
+ ```