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,79 @@
1
+ ---
2
+ name: reviewer
3
+ description: Full harness review before opening a PR. Run with "Use the reviewer agent to review current changes."
4
+ model: claude-opus-4-8
5
+ tools: [Read, Bash]
6
+ color: blue
7
+ ---
8
+
9
+ You are a senior frontend engineer reviewing a PR against harness standards.
10
+ Run `git diff main --name-only` then read all changed files.
11
+
12
+ ## Checklist
13
+
14
+ ### Architecture
15
+
16
+ - [ ] No cross-layer imports (ESLint + structural tests)
17
+ - [ ] No file > 400 lines
18
+ - [ ] TypeScript: 0 errors
19
+ - [ ] api.generated.ts not manually edited
20
+
21
+ ### Code Quality
22
+
23
+ - [ ] No console.log
24
+ - [ ] No process.env outside env.ts
25
+ - [ ] No direct fetch() in components/features/pages
26
+ - [ ] No <img> tags (use next/image)
27
+
28
+ ### API Contract
29
+
30
+ - [ ] api.generated.ts is current
31
+ - [ ] No hand-written types duplicating api.generated.ts
32
+ - [ ] All API calls via openapi-fetch through api-client
33
+ - [ ] Error handling in every hook (throw on error)
34
+
35
+ ### Components
36
+
37
+ - [ ] Loading + Error + Empty states on every data-fetching component
38
+ - [ ] Forms use react-hook-form + zodResolver
39
+ - [ ] 'use client' has comment explaining why
40
+ - [ ] Interactive elements are accessible (aria-label, role, focus-visible)
41
+ - [ ] Images use next/image with width/height or fill
42
+
43
+ ### Security
44
+
45
+ - [ ] Access token in memory only (not localStorage)
46
+ - [ ] No secrets in NEXT*PUBLIC*\* vars
47
+ - [ ] No dangerouslySetInnerHTML without sanitisation
48
+
49
+ ### Performance
50
+
51
+ - [ ] No unnecessary 'use client' (check each one)
52
+ - [ ] Large lists use cursor pagination (not offset)
53
+
54
+ ### Tests
55
+
56
+ - [ ] Utils: 100% coverage
57
+ - [ ] Hooks: 80% coverage (success + error)
58
+ - [ ] Components: 70% (render + interaction)
59
+ - [ ] E2E: critical paths covered
60
+
61
+ ### Docs
62
+
63
+ - [ ] ADR for non-obvious decisions
64
+ - [ ] QUALITY_SCORE.md updated
65
+
66
+ ## Verdict
67
+
68
+ ```
69
+ APPROVED — ready to merge.
70
+
71
+ OR
72
+
73
+ CHANGES REQUIRED:
74
+ BLOCKING:
75
+ 1. [file:line] [problem] → [fix]
76
+
77
+ NON-BLOCKING:
78
+ 2. [description]
79
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: security-auditor
3
+ description: Security review for Next.js frontend. Run before any auth, token, or data-exposure PR.
4
+ model: claude-opus-4-8
5
+ tools: [Read, Bash]
6
+ color: red
7
+ ---
8
+
9
+ You are a frontend security engineer. Review all changes for auth and XSS risks.
10
+
11
+ ## Checklist
12
+
13
+ ### Auth Token Handling
14
+
15
+ - [ ] Access token in React context only — NOT localStorage/sessionStorage/cookie
16
+ - [ ] Refresh token flow goes through Next.js API route (proxies to backend)
17
+ - [ ] No token logged in console
18
+ - [ ] Token cleared on logout from context
19
+
20
+ ### Environment Variables
21
+
22
+ - [ ] No secrets in `NEXT_PUBLIC_*` vars (check .env.example)
23
+ - [ ] process.env only in src/lib/env.ts
24
+ - [ ] Zod schema validates all `NEXT_PUBLIC_*` vars
25
+
26
+ ### XSS Prevention
27
+
28
+ - [ ] No dangerouslySetInnerHTML without DOMPurify sanitisation
29
+ - [ ] User-generated content rendered via JSX (auto-escaped)
30
+
31
+ ### CSP Headers
32
+
33
+ - [ ] Content-Security-Policy header in next.config.ts
34
+ - [ ] No 'unsafe-eval' or 'unsafe-inline' in script-src
35
+
36
+ ### API Client
37
+
38
+ - [ ] Auth header injected via middleware — not in individual hooks
39
+ - [ ] Error responses never expose raw backend error messages to UI
40
+
41
+ ### Next.js API Routes
42
+
43
+ - [ ] app/api/ routes only used for cookie operations and auth proxy
44
+ - [ ] No business logic in api/ routes
45
+
46
+ ## Verdict
47
+
48
+ ```
49
+ APPROVED or CHANGES REQUIRED: [specific items with fixes]
50
+ ```
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env bash
2
+ # .claude/hooks/post-write.sh
3
+ # Fires after every Write/Edit/MultiEdit on TypeScript/TSX files.
4
+ #
5
+ # Two severities:
6
+ # BLOCKERS (exit 2) — the agent MUST fix before continuing. These are correctness
7
+ # /security violations that should never land: editing the read-only generated
8
+ # contract, or putting an auth token in web storage.
9
+ # WARNINGS (exit 0) — printed for the agent to address before commit, but not fatal.
10
+ #
11
+ # Exit-code contract (Claude Code PostToolUse): a non-zero exit (2) with text on stderr
12
+ # is fed back to the agent as a required fix. Exit 0 with text on stdout is advisory.
13
+ #
14
+ # Parses the PostToolUse hook JSON from stdin with `node` (always present in a Node
15
+ # project) — no python3 dependency. Normalises the absolute path Claude Code sends
16
+ # (incl. Windows backslashes) to a repo-relative, forward-slash path so the layer
17
+ # checks below actually match.
18
+
19
+ set -euo pipefail
20
+
21
+ RAW=$(cat)
22
+
23
+ # Extract the written file path. Claude Code puts it at tool_input.file_path;
24
+ # fall back to other shapes defensively.
25
+ FILE=$(printf '%s' "$RAW" | node -e "
26
+ let d='';
27
+ process.stdin.on('data', c => d += c).on('end', () => {
28
+ try {
29
+ const j = JSON.parse(d);
30
+ const p = (j.tool_input && j.tool_input.file_path)
31
+ || (j.tool_response && j.tool_response.filePath)
32
+ || j.file_path || j.path || '';
33
+ process.stdout.write(p || '');
34
+ } catch { process.stdout.write(''); }
35
+ });
36
+ " 2>/dev/null || echo "")
37
+
38
+ [[ -z "$FILE" ]] && exit 0
39
+ [[ ! -f "$FILE" ]] && exit 0
40
+ [[ ! "$FILE" =~ \.(ts|tsx)$ ]] && exit 0
41
+
42
+ # Normalise backslashes → forward slashes for portable matching.
43
+ NORM="${FILE//\\//}"
44
+
45
+ WARNINGS=()
46
+ BLOCKERS=()
47
+
48
+ # ── BLOCKER 1: api.generated.ts is READ ONLY ───────────────────────────────
49
+ # Editing the generated contract by hand breaks the single source of truth.
50
+ if [[ "$NORM" =~ api\.generated\.ts$ ]]; then
51
+ BLOCKERS+=("🚫 [HOOK] $NORM is READ ONLY — it is regenerated by /api-sync. Revert this edit and run /api-sync against openapi.json instead.")
52
+ fi
53
+
54
+ # ── BLOCKER 2: auth tokens must never touch web storage ────────────────────
55
+ # Broadened from the old "token must be literally inside setItem(...)" grep so
56
+ # `localStorage.setItem('auth', accessToken)` (key does not contain "token") is
57
+ # caught too. Matches any setItem whose call references an auth-ish identifier.
58
+ if grep -qniE "(local|session)Storage\.setItem\([^)]*(token|jwt|auth|bearer|access|refresh|secret|credential)" "$FILE" 2>/dev/null; then
59
+ LINE=$(grep -niE "(local|session)Storage\.setItem" "$FILE" | head -1 | cut -d: -f1)
60
+ BLOCKERS+=("🚫 [HOOK] Token/credential written to web storage at $NORM:$LINE — access tokens must stay in memory (React context). Revert.")
61
+ fi
62
+
63
+ # ── WARNING 1: console.log in src/ or app/ (no structured console logging) ──
64
+ # Use the structured logger (src/lib/logger.ts) instead; raw console.log is noise.
65
+ if [[ "$NORM" =~ (^|/)src/ ]] || [[ "$NORM" =~ (^|/)app/ ]]; then
66
+ if grep -qn "console\.log\b" "$FILE" 2>/dev/null; then
67
+ LINE=$(grep -n "console\.log\b" "$FILE" | head -1 | cut -d: -f1)
68
+ WARNINGS+=("⚠ [HOOK] console.log at $NORM:$LINE — use the structured logger from src/lib/logger.ts (or remove before committing)")
69
+ fi
70
+ fi
71
+
72
+ # ── WARNING 2: process.env outside src/lib/ (env.ts validates NEXT_PUBLIC_*) ──
73
+ # NODE_ENV / NEXT_RUNTIME are build-time constants Next inlines — not flagged.
74
+ # src/lib/ is the infra boundary (env.ts, logger.ts) and may read them.
75
+ if [[ "$NORM" =~ (^|/)src/ ]] && [[ ! "$NORM" =~ (^|/)src/lib/ ]]; then
76
+ if grep -nE "process\.env" "$FILE" 2>/dev/null | grep -vqE "process\.env\.(NODE_ENV|NEXT_RUNTIME)\b"; then
77
+ LINE=$(grep -nE "process\.env" "$FILE" | grep -vE "process\.env\.(NODE_ENV|NEXT_RUNTIME)\b" | head -1 | cut -d: -f1)
78
+ WARNINGS+=("⚠ [HOOK] process.env at $NORM:$LINE — use env from src/lib/env.ts")
79
+ fi
80
+ fi
81
+
82
+ # ── WARNING 3: direct fetch() in components, features, or pages ─────────────
83
+ # Anchored so it does NOT false-positive on TanStack's refetch()/prefetchQuery()
84
+ # (fetch preceded by a letter or a dot is someone else's method, not a bare fetch).
85
+ if [[ "$NORM" =~ (^|/)(src/features|src/components|app)/ ]]; then
86
+ if grep -qnE "(^|[^A-Za-z.])fetch\(" "$FILE" 2>/dev/null; then
87
+ LINE=$(grep -nE "(^|[^A-Za-z.])fetch\(" "$FILE" | head -1 | cut -d: -f1)
88
+ WARNINGS+=("⚠ [HOOK] Direct fetch() at $NORM:$LINE — API calls must go through src/hooks/")
89
+ fi
90
+ fi
91
+
92
+ # ── WARNING 4: raw <img> tag (should use next/image) ───────────────────────
93
+ if [[ "$NORM" =~ \.tsx$ ]]; then
94
+ if grep -qn "<img " "$FILE" 2>/dev/null; then
95
+ LINE=$(grep -n "<img " "$FILE" | head -1 | cut -d: -f1)
96
+ WARNINGS+=("⚠ [HOOK] <img> at $NORM:$LINE — use next/image <Image /> component")
97
+ fi
98
+ fi
99
+
100
+ # ── WARNING 5: file size > 400 lines ───────────────────────────────────────
101
+ LINE_COUNT=$(wc -l < "$FILE" 2>/dev/null || echo 0)
102
+ if [[ "$LINE_COUNT" -gt 400 ]]; then
103
+ WARNINGS+=("⚠ [HOOK] $NORM has $LINE_COUNT lines — exceeds 400-line limit. Split into focused modules.")
104
+ fi
105
+
106
+ # Print advisory warnings to stdout (guard empty array for bash 3.2 / set -u).
107
+ if [[ ${#WARNINGS[@]} -gt 0 ]]; then
108
+ for w in "${WARNINGS[@]}"; do
109
+ echo "$w"
110
+ done
111
+ fi
112
+
113
+ # Blockers go to stderr and fail the hook so the agent must address them.
114
+ if [[ ${#BLOCKERS[@]} -gt 0 ]]; then
115
+ for b in "${BLOCKERS[@]}"; do
116
+ echo "$b" >&2
117
+ done
118
+ exit 2
119
+ fi
120
+
121
+ exit 0
@@ -0,0 +1,166 @@
1
+ ---
2
+ paths:
3
+ - "src/hooks/**/*.ts"
4
+ - "src/hooks/**/*.tsx"
5
+ - "src/lib/api-client.ts"
6
+ ---
7
+
8
+ # API Contract Rules — Auto-injected on hook and api-client edits
9
+
10
+ ## The Golden Rule
11
+ `api.generated.ts` is the contract. Every API call must flow through it.
12
+
13
+ ## Canonical Wire Contract (cross-stack — FastAPI · Express · NestJS)
14
+
15
+ This frontend talks to any of the harness backends. They MUST agree on ONE wire shape
16
+ so the client never special-cases a backend. The agreed conventions:
17
+
18
+ - **Field naming: `snake_case`** on the wire (`next_cursor_id`, `has_more`, `created_at`).
19
+ FastAPI/Pydantic emits this by default. Express/NestJS + Sequelize must emit it too
20
+ (Sequelize `underscored: true`, or a response serializer) — do NOT ship camelCase.
21
+ - **List envelope (cursor pagination):**
22
+ `{ "items": T[], "has_more": boolean, "next_cursor_id": string | null }`
23
+ Request params: `cursor_id` (opaque), `limit` (int).
24
+ - **Error envelope (every non-2xx):**
25
+ `{ "error": { "code": "SNAKE_CASE_CODE", "message": "human readable", "details"?: object } }`
26
+ (Not FastAPI's bare `{ "detail": ... }` — backends register a handler that emits this.)
27
+
28
+ **How this is enforced mechanically (not just prose):** the field names you write in
29
+ hooks are checked against `api.generated.ts` by `tsc`. If a backend deviates (e.g. emits
30
+ `nextCursorId`), `/api-sync` regenerates camelCase types and `npm run typecheck` FAILS on
31
+ `lastPage.next_cursor_id` — the drift cannot pass the gate silently. Agreement across
32
+ templates + typecheck is the guard; keep the convention above in lock-step with the
33
+ Express/NestJS/FastAPI templates.
34
+
35
+ ## Hook Pattern — Every Hook Must Follow This
36
+
37
+ ```typescript
38
+ // src/hooks/use-applications.ts
39
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
40
+ import { apiClient } from '@/lib/api-client'
41
+ import { toApiError } from '@/lib/api-error'
42
+ import type { components } from '@/types/api.generated'
43
+
44
+ type Application = components['schemas']['ApplicationResponse']
45
+ type CreateApplicationInput = components['schemas']['CreateApplicationInput']
46
+
47
+ // ─── Query Keys ─────────────────────────────────────────
48
+ // Centralise all query keys — never use raw strings in components
49
+ export const applicationKeys = {
50
+ all: () => ['applications'] as const,
51
+ lists: () => [...applicationKeys.all(), 'list'] as const,
52
+ list: (filters: Record<string, unknown>) =>
53
+ [...applicationKeys.lists(), filters] as const,
54
+ detail: (id: string) => [...applicationKeys.all(), 'detail', id] as const,
55
+ }
56
+
57
+ // ─── List ────────────────────────────────────────────────
58
+ export function useApplications(filters?: { stage?: string; limit?: number }) {
59
+ return useQuery({
60
+ queryKey: applicationKeys.list(filters ?? {}),
61
+ queryFn: async () => {
62
+ const { data, error } = await apiClient.GET('/api/v1/applications', {
63
+ params: { query: filters },
64
+ })
65
+ if (error) throw toApiError(error, 'Failed to fetch applications')
66
+ return data
67
+ },
68
+ staleTime: 60_000, // 1 min — server data doesn't change every second
69
+ })
70
+ }
71
+
72
+ // ─── Single ──────────────────────────────────────────────
73
+ export function useApplication(id: string) {
74
+ return useQuery({
75
+ queryKey: applicationKeys.detail(id),
76
+ queryFn: async () => {
77
+ const { data, error } = await apiClient.GET('/api/v1/applications/{id}', {
78
+ params: { path: { id } },
79
+ })
80
+ if (error) throw toApiError(error, `Application ${id} not found`)
81
+ return data
82
+ },
83
+ enabled: !!id, // don't fetch if no id
84
+ })
85
+ }
86
+
87
+ // ─── Mutation ────────────────────────────────────────────
88
+ export function useCreateApplication() {
89
+ const queryClient = useQueryClient()
90
+ return useMutation({
91
+ mutationFn: async (body: CreateApplicationInput) => {
92
+ const { data, error } = await apiClient.POST('/api/v1/applications', { body })
93
+ if (error) throw toApiError(error, 'Failed to create application')
94
+ return data
95
+ },
96
+ onSuccess: () => {
97
+ // Invalidate all application lists — triggers refetch
98
+ queryClient.invalidateQueries({ queryKey: applicationKeys.lists() })
99
+ },
100
+ })
101
+ }
102
+ ```
103
+
104
+ ## Error Handling in Hooks
105
+
106
+ Preserve the backend error — do NOT replace it with a hard-coded English string (that
107
+ throws away the `code`/`message` the canonical error envelope carries). Use a shared
108
+ helper that reads the envelope defensively:
109
+
110
+ ```typescript
111
+ // src/lib/api-error.ts (authored in Phase 0 glue)
112
+ export class ApiError extends Error {
113
+ constructor(public code: string, message: string, public details?: unknown) {
114
+ super(message)
115
+ }
116
+ }
117
+ // Reads the canonical { error: { code, message } } envelope; falls back gracefully
118
+ // for a bare FastAPI { detail } or an opaque error so a message ALWAYS surfaces.
119
+ export function toApiError(error: unknown, fallback = 'Request failed'): ApiError {
120
+ const e = error as { error?: { code?: string; message?: string; details?: unknown }; detail?: unknown }
121
+ if (e?.error?.message) return new ApiError(e.error.code ?? 'unknown', e.error.message, e.error.details)
122
+ if (typeof e?.detail === 'string') return new ApiError('unknown', e.detail)
123
+ return new ApiError('unknown', fallback)
124
+ }
125
+ ```
126
+
127
+ ```typescript
128
+ // ✅ Throw the real error — code + message reach the UI
129
+ if (error) throw toApiError(error, 'Failed to fetch applications')
130
+
131
+ // ✅ Handle in component via isError + error (error.message is the backend message)
132
+ const { isError, error } = useApplications()
133
+ if (isError) return <ErrorState message={error.message} />
134
+
135
+ // ❌ Never swallow errors
136
+ const { data } = await apiClient.GET(...)
137
+ // missing: if (error) throw ...
138
+
139
+ // ❌ Never discard the backend error for a hard-coded string
140
+ if (error) throw new Error('Failed to fetch applications') // loses code + real message
141
+ ```
142
+
143
+ ## staleTime Guidance
144
+ ```typescript
145
+ // User-specific, changes often: staleTime: 30_000 (30s)
146
+ // Reference data, rarely changes: staleTime: 300_000 (5min)
147
+ // Static config: staleTime: Infinity
148
+ ```
149
+
150
+ ## Pagination with Cursor
151
+ ```typescript
152
+ export function useApplications(cursor?: string) {
153
+ return useInfiniteQuery({
154
+ queryKey: applicationKeys.list({ cursor }),
155
+ queryFn: async ({ pageParam }) => {
156
+ const { data, error } = await apiClient.GET('/api/v1/applications', {
157
+ params: { query: { cursor_id: pageParam, limit: 20 } },
158
+ })
159
+ if (error) throw error
160
+ return data
161
+ },
162
+ getNextPageParam: (lastPage) => lastPage.next_cursor_id ?? undefined,
163
+ initialPageParam: undefined,
164
+ })
165
+ }
166
+ ```
@@ -0,0 +1,99 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.ts"
4
+ - "src/**/*.tsx"
5
+ - "app/**/*.tsx"
6
+ - "app/**/*.ts"
7
+ ---
8
+
9
+ # Architecture Rules — Auto-injected on every src/ and app/ file edit
10
+
11
+ ## Import Order Check — Before Writing Any Import
12
+
13
+ | Your file is in | You may import from |
14
+ |---|---|
15
+ | `src/types/` | Nothing (third-party ok: zod) |
16
+ | `src/lib/` | `src/types/` only |
17
+ | `src/hooks/` | `src/types/`, `src/lib/` |
18
+ | `src/store/` | `src/types/` only |
19
+ | `src/utils/` | Nothing (stdlib + third-party only) |
20
+ | `src/components/` | `src/types/`, `src/utils/` |
21
+ | `src/features/` | `src/types/`, `src/lib/`, `src/hooks/`, `src/store/`, `src/components/`, `src/utils/` |
22
+ | `app/` | `src/features/`, `src/components/` |
23
+
24
+ ## api.generated.ts — THE CONTRACT
25
+ ```typescript
26
+ // ✅ Import types FROM the generated file
27
+ import type { components, paths } from '@/types/api.generated'
28
+ type Application = components['schemas']['ApplicationResponse']
29
+
30
+ // ❌ NEVER manually write a type that mirrors the generated one
31
+ type Application = { // if this matches api.generated.ts → DELETE IT
32
+ id: string
33
+ stage: string
34
+ ...
35
+ }
36
+
37
+ // ❌ NEVER edit api.generated.ts
38
+ // Run /api-sync to regenerate it
39
+ ```
40
+
41
+ ## API Calls — Hooks Only
42
+ ```typescript
43
+ // ✅ Feature uses a hook
44
+ import { useApplications } from '@/hooks/use-applications'
45
+
46
+ function ApplicationList() {
47
+ const { data, isPending, error } = useApplications()
48
+ if (isPending) return <Skeleton />
49
+ if (error) return <ErrorState />
50
+ return data?.items.map(app => <ApplicationCard key={app.id} app={app} />)
51
+ }
52
+
53
+ // ❌ Direct fetch in component — FORBIDDEN
54
+ function ApplicationList() {
55
+ useEffect(() => {
56
+ fetch('/api/v1/applications') // WRONG
57
+ }, [])
58
+ }
59
+ ```
60
+
61
+ ## Server vs Client Components
62
+ ```typescript
63
+ // ✅ Server Component — default, no directive
64
+ export default async function ApplicationsPage() {
65
+ return (
66
+ <div>
67
+ <ApplicationListClient /> {/* client boundary inside */}
68
+ </div>
69
+ )
70
+ }
71
+
72
+ // ✅ Client Component — with reason comment
73
+ 'use client' // Client: uses TanStack Query (useQuery)
74
+ export function ApplicationListClient() {
75
+ const { data } = useApplications()
76
+ ...
77
+ }
78
+
79
+ // ❌ 'use client' without comment
80
+ 'use client'
81
+ export function SomeComponent() { ... } // Why? What browser API?
82
+ ```
83
+
84
+ ## No Business Logic in App Pages
85
+ ```typescript
86
+ // ✅ Page = layout + render feature
87
+ export default function ApplicationsPage() {
88
+ return <ApplicationsFeature />
89
+ }
90
+
91
+ // ❌ Business logic in page — belongs in features/
92
+ export default function ApplicationsPage() {
93
+ const [filtered, setFiltered] = useState([])
94
+ const handleStageChange = async (id, stage) => { // WRONG LAYER
95
+ await fetch(`/api/v1/applications/${id}/stage`)
96
+ ...
97
+ }
98
+ }
99
+ ```
@@ -0,0 +1,122 @@
1
+ ---
2
+ paths:
3
+ - "src/features/**/*.tsx"
4
+ - "src/components/**/*.tsx"
5
+ - "app/**/*.tsx"
6
+ ---
7
+
8
+ # Component Rules — Auto-injected on component file edits
9
+
10
+ ## Every Component Checklist
11
+
12
+ ### Loading, Error, and Empty States (ALL THREE required on data-fetching components)
13
+ ```typescript
14
+ // ✅ All 3 states handled
15
+ export function ApplicationList() {
16
+ const { data, isPending, isError, error } = useApplications()
17
+
18
+ if (isPending) return <ApplicationListSkeleton /> // skeleton > spinner
19
+ if (isError) return <ErrorState message={error.message} retry={refetch} />
20
+ if (!data?.items.length) return <EmptyState />
21
+
22
+ return data.items.map(app => <ApplicationCard key={app.id} app={app} />)
23
+ }
24
+
25
+ // ❌ Missing states — silent failure
26
+ export function ApplicationList() {
27
+ const { data } = useApplications()
28
+ return data?.items.map(app => <ApplicationCard key={app.id} app={app} />)
29
+ // undefined if loading? Error? No items? All render nothing silently.
30
+ }
31
+ ```
32
+
33
+ ### Forms — React Hook Form + Zod + shadcn/ui
34
+ ```typescript
35
+ import { useForm } from 'react-hook-form'
36
+ import { zodResolver } from '@hookform/resolvers/zod'
37
+ import { z } from 'zod'
38
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
39
+
40
+ const createApplicationSchema = z.object({
41
+ company: z.string().min(1, 'Company is required'),
42
+ role: z.string().min(1, 'Role is required'),
43
+ jobUrl: z.string().url().optional(),
44
+ })
45
+ type CreateApplicationInput = z.infer<typeof createApplicationSchema>
46
+
47
+ export function CreateApplicationForm({ onSuccess }: { onSuccess: () => void }) {
48
+ const form = useForm<CreateApplicationInput>({
49
+ resolver: zodResolver(createApplicationSchema),
50
+ defaultValues: { company: '', role: '' },
51
+ })
52
+ const { mutate, isPending } = useCreateApplication()
53
+
54
+ return (
55
+ <Form {...form}>
56
+ <form onSubmit={form.handleSubmit(data => mutate(data, { onSuccess }))}>
57
+ <FormField control={form.control} name="company" render={({ field }) => (
58
+ <FormItem>
59
+ <FormLabel>Company</FormLabel>
60
+ <FormControl><Input {...field} /></FormControl>
61
+ <FormMessage />
62
+ </FormItem>
63
+ )} />
64
+ <Button type="submit" disabled={isPending}>
65
+ {isPending ? 'Creating...' : 'Create'}
66
+ </Button>
67
+ </form>
68
+ </Form>
69
+ )
70
+ }
71
+ ```
72
+
73
+ ### Images — Always next/image
74
+ ```typescript
75
+ // ✅
76
+ import Image from 'next/image'
77
+ <Image src="/logo.png" alt="Logo" width={120} height={40} />
78
+
79
+ // ❌ Flagged by post-write hook + ESLint (next/core-web-vitals no-img-element)
80
+ <img src="/logo.png" alt="Logo" />
81
+ ```
82
+
83
+ ### Accessibility (every interactive element)
84
+ ```typescript
85
+ // ✅
86
+ <button
87
+ aria-label="Delete application"
88
+ onClick={handleDelete}
89
+ className="focus-visible:ring-2 focus-visible:ring-ring"
90
+ >
91
+ <TrashIcon aria-hidden />
92
+ </button>
93
+
94
+ // ❌
95
+ <div onClick={handleDelete}> {/* not focusable, no role */}
96
+ <TrashIcon />
97
+ </div>
98
+ ```
99
+
100
+ ### Tailwind + shadcn/ui — Use cn() for conditional classes
101
+ ```typescript
102
+ import { cn } from '@/utils/cn.util'
103
+
104
+ <div className={cn(
105
+ 'rounded-lg border p-4',
106
+ isPending && 'opacity-50 pointer-events-none',
107
+ isError && 'border-destructive',
108
+ )}>
109
+ ```
110
+
111
+ ### No Inline Event Handlers with Business Logic
112
+ ```typescript
113
+ // ✅ Handler defined, logic delegated to mutation
114
+ const { mutate: deleteApp } = useDeleteApplication()
115
+ <Button onClick={() => deleteApp(app.id)}>Delete</Button>
116
+
117
+ // ❌ Inline logic
118
+ <Button onClick={async () => {
119
+ await fetch(`/api/v1/applications/${app.id}`, { method: 'DELETE' })
120
+ router.refresh()
121
+ }}>Delete</Button>
122
+ ```