create-rigel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/cli.js +122 -0
- package/package.json +47 -0
- package/templates/express/.claude/CLAUDE.md +114 -0
- package/templates/express/.claude/agents/arch-validator.md +85 -0
- package/templates/express/.claude/agents/db-optimizer.md +72 -0
- package/templates/express/.claude/agents/doc-gardener.md +67 -0
- package/templates/express/.claude/agents/garbage-collector.md +94 -0
- package/templates/express/.claude/agents/gate-checker.md +176 -0
- package/templates/express/.claude/agents/reviewer.md +84 -0
- package/templates/express/.claude/agents/security-auditor.md +82 -0
- package/templates/express/.claude/hooks/post-write.sh +93 -0
- package/templates/express/.claude/rules/api.md +149 -0
- package/templates/express/.claude/rules/architecture.md +78 -0
- package/templates/express/.claude/rules/database.md +104 -0
- package/templates/express/.claude/rules/jobs.md +81 -0
- package/templates/express/.claude/rules/security.md +92 -0
- package/templates/express/.claude/rules/testing.md +97 -0
- package/templates/express/.claude/settings.json +35 -0
- package/templates/express/.claude/skills/00-infra-setup/SKILL.md +479 -0
- package/templates/express/.claude/skills/01-write-roadmap/SKILL.md +72 -0
- package/templates/express/.claude/skills/02-write-spec/SKILL.md +119 -0
- package/templates/express/.claude/skills/03-write-plan/SKILL.md +124 -0
- package/templates/express/.claude/skills/04-build-layer/SKILL.md +216 -0
- package/templates/express/.claude/skills/05-validate-layer/SKILL.md +43 -0
- package/templates/express/.claude/skills/06-push-layer/SKILL.md +59 -0
- package/templates/express/.claude/skills/07-layer-check/SKILL.md +62 -0
- package/templates/express/.claude/skills/08-garbage-collect/SKILL.md +86 -0
- package/templates/express/.claude/skills/09-doc-garden/SKILL.md +64 -0
- package/templates/express/.claude/skills/10-db-optimize/SKILL.md +96 -0
- package/templates/express/.claude/skills/11-load-test/SKILL.md +77 -0
- package/templates/express/.dockerignore +43 -0
- package/templates/express/.env.example +42 -0
- package/templates/express/.github/CODEOWNERS +22 -0
- package/templates/express/.github/dependabot.yml +48 -0
- package/templates/express/.github/pull_request_template.md +58 -0
- package/templates/express/.github/workflows/ci.yml +278 -0
- package/templates/express/.github/workflows/load-test.yml +41 -0
- package/templates/express/.gitleaks.toml +18 -0
- package/templates/express/.lintstagedrc.json +3 -0
- package/templates/express/.nvmrc +1 -0
- package/templates/express/.prettierrc +8 -0
- package/templates/express/.sequelizerc +8 -0
- package/templates/express/AGENTS.md +94 -0
- package/templates/express/ARCHITECTURE.md +161 -0
- package/templates/express/Dockerfile +40 -0
- package/templates/express/Makefile +79 -0
- package/templates/express/QUICKSTART.md +131 -0
- package/templates/express/docker-compose.yml +114 -0
- package/templates/express/docs/PLANS.md +111 -0
- package/templates/express/docs/QUALITY_SCORE.md +66 -0
- package/templates/express/docs/design-docs/core-beliefs.md +89 -0
- package/templates/express/docs/design-docs/decisions/ADR-000-infrastructure.md +70 -0
- package/templates/express/docs/design-docs/decisions/ADR-001-observability.md +82 -0
- package/templates/express/docs/design-docs/decisions/index.md +23 -0
- package/templates/express/docs/design-docs/index.md +11 -0
- package/templates/express/docs/design-docs/observability.md +194 -0
- package/templates/express/docs/design-docs/team-workflow.md +64 -0
- package/templates/express/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/tech-debt-tracker.md +31 -0
- package/templates/express/docs/generated/.gitkeep +0 -0
- package/templates/express/docs/product-specs/ROADMAP.md +49 -0
- package/templates/express/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/express/docs/product-specs/index.md +21 -0
- package/templates/express/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/express/eslint.config.mjs +94 -0
- package/templates/express/gitignore +36 -0
- package/templates/express/infra/monitoring/alloy/config.alloy +59 -0
- package/templates/express/jest.config.ts +61 -0
- package/templates/express/package.json +45 -0
- package/templates/express/scripts/openapi.export.ts +77 -0
- package/templates/express/tests/architecture/isolation.test.ts +51 -0
- package/templates/express/tests/architecture/layers.test.ts +83 -0
- package/templates/express/tests/integration/health.test.ts +40 -0
- package/templates/express/tests/integration/isolation.test.template.ts +88 -0
- package/templates/express/tests/load/smoke.js +46 -0
- package/templates/express/tests/load/soak.js +45 -0
- package/templates/express/tests/load/stress.js +44 -0
- package/templates/express/tests/unit/utils/response.util.test.ts +53 -0
- package/templates/express/tsconfig.build.json +8 -0
- package/templates/express/tsconfig.json +20 -0
- package/templates/fastapi/.claude/CLAUDE.md +122 -0
- package/templates/fastapi/.claude/agents/arch-validator.md +64 -0
- package/templates/fastapi/.claude/agents/db-optimizer.md +59 -0
- package/templates/fastapi/.claude/agents/doc-gardener.md +55 -0
- package/templates/fastapi/.claude/agents/garbage-collector.md +74 -0
- package/templates/fastapi/.claude/agents/gate-checker.md +133 -0
- package/templates/fastapi/.claude/agents/reviewer.md +74 -0
- package/templates/fastapi/.claude/agents/security-auditor.md +69 -0
- package/templates/fastapi/.claude/hooks/post-write.sh +65 -0
- package/templates/fastapi/.claude/rules/api.md +119 -0
- package/templates/fastapi/.claude/rules/architecture.md +84 -0
- package/templates/fastapi/.claude/rules/database.md +153 -0
- package/templates/fastapi/.claude/rules/jobs.md +85 -0
- package/templates/fastapi/.claude/rules/security.md +102 -0
- package/templates/fastapi/.claude/rules/testing.md +207 -0
- package/templates/fastapi/.claude/settings.json +50 -0
- package/templates/fastapi/.claude/skills/00-infra-setup/SKILL.md +311 -0
- package/templates/fastapi/.claude/skills/01-write-roadmap/SKILL.md +61 -0
- package/templates/fastapi/.claude/skills/02-write-spec/SKILL.md +82 -0
- package/templates/fastapi/.claude/skills/03-write-plan/SKILL.md +83 -0
- package/templates/fastapi/.claude/skills/04-build-layer/SKILL.md +170 -0
- package/templates/fastapi/.claude/skills/05-validate-layer/SKILL.md +20 -0
- package/templates/fastapi/.claude/skills/06-push-layer/SKILL.md +31 -0
- package/templates/fastapi/.claude/skills/07-layer-check/SKILL.md +51 -0
- package/templates/fastapi/.claude/skills/08-garbage-collect/SKILL.md +47 -0
- package/templates/fastapi/.claude/skills/09-doc-garden/SKILL.md +9 -0
- package/templates/fastapi/.claude/skills/10-db-optimize/SKILL.md +53 -0
- package/templates/fastapi/.claude/skills/11-load-test/SKILL.md +81 -0
- package/templates/fastapi/.dockerignore +30 -0
- package/templates/fastapi/.github/dependabot.yml +24 -0
- package/templates/fastapi/AGENTS.md +87 -0
- package/templates/fastapi/ARCHITECTURE.md +141 -0
- package/templates/fastapi/Makefile +42 -0
- package/templates/fastapi/README.md +179 -0
- package/templates/fastapi/docs/PLANS.md +26 -0
- package/templates/fastapi/docs/QUALITY_SCORE.md +26 -0
- package/templates/fastapi/docs/design-docs/core-beliefs.md +46 -0
- package/templates/fastapi/docs/design-docs/decisions/ADR-000-infrastructure.md +79 -0
- package/templates/fastapi/docs/design-docs/decisions/index.md +7 -0
- package/templates/fastapi/docs/design-docs/index.md +9 -0
- package/templates/fastapi/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/fastapi/docs/generated/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/ROADMAP.md +49 -0
- package/templates/fastapi/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/index.md +18 -0
- package/templates/fastapi/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/fastapi/gitignore +46 -0
- package/templates/fastapi/scripts/gate.sh +68 -0
- package/templates/nestjs/.claude/CLAUDE.md +121 -0
- package/templates/nestjs/.claude/agents/db-optimizer.md +51 -0
- package/templates/nestjs/.claude/agents/doc-gardener.md +45 -0
- package/templates/nestjs/.claude/agents/garbage-collector.md +50 -0
- package/templates/nestjs/.claude/agents/gate-checker.md +127 -0
- package/templates/nestjs/.claude/agents/reviewer.md +68 -0
- package/templates/nestjs/.claude/agents/security-auditor.md +72 -0
- package/templates/nestjs/.claude/hooks/post-write.sh +91 -0
- package/templates/nestjs/.claude/rules/api.md +87 -0
- package/templates/nestjs/.claude/rules/architecture.md +104 -0
- package/templates/nestjs/.claude/rules/database.md +111 -0
- package/templates/nestjs/.claude/rules/security.md +110 -0
- package/templates/nestjs/.claude/rules/testing.md +145 -0
- package/templates/nestjs/.claude/settings.json +33 -0
- package/templates/nestjs/.claude/skills/00-infra-setup/SKILL.md +222 -0
- package/templates/nestjs/.claude/skills/01-write-spec/SKILL.md +25 -0
- package/templates/nestjs/.claude/skills/02-write-plan/SKILL.md +18 -0
- package/templates/nestjs/.claude/skills/03-build-layer/SKILL.md +242 -0
- package/templates/nestjs/.claude/skills/04-validate-layer/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/05-push-layer/SKILL.md +6 -0
- package/templates/nestjs/.claude/skills/06-layer-check/SKILL.md +12 -0
- package/templates/nestjs/.claude/skills/07-garbage-collect/SKILL.md +2 -0
- package/templates/nestjs/.claude/skills/08-doc-garden/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/09-api-sync/SKILL.md +36 -0
- package/templates/nestjs/.claude/skills/10-load-test/SKILL.md +29 -0
- package/templates/nestjs/.env.example +11 -0
- package/templates/nestjs/AGENTS.md +88 -0
- package/templates/nestjs/ARCHITECTURE.md +192 -0
- package/templates/nestjs/docs/PLANS.md +22 -0
- package/templates/nestjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nestjs/docs/design-docs/core-beliefs.md +18 -0
- package/templates/nestjs/docs/design-docs/decisions/ADR-000-infrastructure.md +58 -0
- package/templates/nestjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nestjs/docs/design-docs/index.md +7 -0
- package/templates/nestjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/tech-debt-tracker.md +16 -0
- package/templates/nestjs/docs/generated/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/index.md +7 -0
- package/templates/nestjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nestjs/gitignore +8 -0
- package/templates/nextjs/.claude/CLAUDE.md +139 -0
- package/templates/nextjs/.claude/agents/arch-validator.md +66 -0
- package/templates/nextjs/.claude/agents/contract-checker.md +78 -0
- package/templates/nextjs/.claude/agents/doc-gardener.md +52 -0
- package/templates/nextjs/.claude/agents/garbage-collector.md +61 -0
- package/templates/nextjs/.claude/agents/gate-checker.md +129 -0
- package/templates/nextjs/.claude/agents/perf-auditor.md +109 -0
- package/templates/nextjs/.claude/agents/reviewer.md +79 -0
- package/templates/nextjs/.claude/agents/security-auditor.md +50 -0
- package/templates/nextjs/.claude/hooks/post-write.sh +121 -0
- package/templates/nextjs/.claude/rules/api-contract.md +166 -0
- package/templates/nextjs/.claude/rules/architecture.md +99 -0
- package/templates/nextjs/.claude/rules/components.md +122 -0
- package/templates/nextjs/.claude/rules/security.md +122 -0
- package/templates/nextjs/.claude/rules/testing.md +187 -0
- package/templates/nextjs/.claude/scripts/infra-setup.sh +550 -0
- package/templates/nextjs/.claude/settings.json +33 -0
- package/templates/nextjs/.claude/skills/00-infra-setup/SKILL.md +471 -0
- package/templates/nextjs/.claude/skills/01-write-roadmap/SKILL.md +77 -0
- package/templates/nextjs/.claude/skills/02-write-spec/SKILL.md +78 -0
- package/templates/nextjs/.claude/skills/03-write-plan/SKILL.md +91 -0
- package/templates/nextjs/.claude/skills/04-build-layer/SKILL.md +141 -0
- package/templates/nextjs/.claude/skills/05-validate-layer/SKILL.md +13 -0
- package/templates/nextjs/.claude/skills/06-push-layer/SKILL.md +20 -0
- package/templates/nextjs/.claude/skills/07-layer-check/SKILL.md +28 -0
- package/templates/nextjs/.claude/skills/08-garbage-collect/SKILL.md +14 -0
- package/templates/nextjs/.claude/skills/09-doc-garden/SKILL.md +12 -0
- package/templates/nextjs/.claude/skills/10-api-sync/SKILL.md +50 -0
- package/templates/nextjs/.claude/skills/11-perf-budget/SKILL.md +51 -0
- package/templates/nextjs/.dockerignore +25 -0
- package/templates/nextjs/.gitattributes +10 -0
- package/templates/nextjs/.github/CODEOWNERS +24 -0
- package/templates/nextjs/.github/dependabot.yml +31 -0
- package/templates/nextjs/.github/pull_request_template.md +59 -0
- package/templates/nextjs/.github/workflows/ci.yml +143 -0
- package/templates/nextjs/.github/workflows/lighthouse.yml +54 -0
- package/templates/nextjs/.github/workflows/load-test.yml +54 -0
- package/templates/nextjs/.husky/pre-commit +1 -0
- package/templates/nextjs/.lintstagedrc.json +4 -0
- package/templates/nextjs/.mcp.json +8 -0
- package/templates/nextjs/.prettierignore +13 -0
- package/templates/nextjs/.prettierrc +9 -0
- package/templates/nextjs/AGENTS.md +88 -0
- package/templates/nextjs/ARCHITECTURE.md +165 -0
- package/templates/nextjs/Dockerfile +38 -0
- package/templates/nextjs/Makefile +50 -0
- package/templates/nextjs/QUICKSTART.md +128 -0
- package/templates/nextjs/docs/PLANS.md +23 -0
- package/templates/nextjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nextjs/docs/design-docs/core-beliefs.md +43 -0
- package/templates/nextjs/docs/design-docs/decisions/ADR-000-infrastructure.md +72 -0
- package/templates/nextjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nextjs/docs/design-docs/index.md +7 -0
- package/templates/nextjs/docs/design-docs/team-workflow.md +66 -0
- package/templates/nextjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/nextjs/docs/generated/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/ROADMAP.md +59 -0
- package/templates/nextjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/index.md +18 -0
- package/templates/nextjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nextjs/eslint.config.mjs +150 -0
- package/templates/nextjs/gitignore +35 -0
- package/templates/nextjs/lighthouserc.js +37 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: garbage-collector
|
|
3
|
+
description: End-of-feature cleanup. Called by /garbage-collect. Scans for violations, updates quality scores, closes the plan.
|
|
4
|
+
model: claude-opus-4-8
|
|
5
|
+
tools: [Read, Write, Bash]
|
|
6
|
+
color: green
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the cleanup agent. Run after every feature is complete.
|
|
10
|
+
|
|
11
|
+
## Steps (run in order)
|
|
12
|
+
|
|
13
|
+
### 1. File Size Scan
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
find src/ -name "*.ts" | xargs wc -l | awk '$1 > 400 { print $2, $1, "lines — SPLIT REQUIRED" }' | sort -rn
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For each file over 400 lines: identify a logical split point, create sub-modules, update imports.
|
|
20
|
+
|
|
21
|
+
### 2. Layer Violation Scan
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx madge --circular src/ --extensions ts
|
|
25
|
+
# Layer-boundary enforcement lives in eslint.config.mjs (boundaries/dependencies) — a plain
|
|
26
|
+
# lint run covers it. Or just run `npm run gate` (typecheck + lint + circular + arch tests).
|
|
27
|
+
npx eslint src/ --max-warnings=0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Fix any violations found.
|
|
31
|
+
|
|
32
|
+
### 3. Stale Docs Scan
|
|
33
|
+
|
|
34
|
+
- Read `AGENTS.md` — does it match current skills and agents?
|
|
35
|
+
- Read `ARCHITECTURE.md` — does it match current layers?
|
|
36
|
+
- Read `docs/design-docs/decisions/` — are ADRs current?
|
|
37
|
+
- Update any stale content.
|
|
38
|
+
|
|
39
|
+
### 4. Update QUALITY_SCORE.md
|
|
40
|
+
|
|
41
|
+
Run coverage: `npm test -- --coverage`
|
|
42
|
+
For each domain touched in this feature, update the grade table.
|
|
43
|
+
|
|
44
|
+
Grade rubric (coverage is judged per layer against `jest.config.ts`, not a single global number):
|
|
45
|
+
|
|
46
|
+
- A: all invariants met, every per-layer coverage threshold met, zero known debt
|
|
47
|
+
- B: minor issues, per-layer thresholds met with little margin, low-priority debt logged
|
|
48
|
+
- C: layer violation OR any per-layer threshold breached OR unresolved medium debt
|
|
49
|
+
- D: multiple violations, coverage well under thresholds, active bugs
|
|
50
|
+
|
|
51
|
+
### 5. Log New Tech Debt
|
|
52
|
+
|
|
53
|
+
Any shortcut taken during this feature → add to `docs/exec-plans/tech-debt-tracker.md`
|
|
54
|
+
Format: `| DEBT-XXX | P3 | [area] | [description] | PLAN-XXX | [date] |`
|
|
55
|
+
|
|
56
|
+
### 6. Close the Plan
|
|
57
|
+
|
|
58
|
+
- Open `docs/exec-plans/active/PLAN-XXX.md`
|
|
59
|
+
- Set `Status: COMPLETE`, set `Completed:` date
|
|
60
|
+
- Verify all layer checkboxes are ticked
|
|
61
|
+
- Move file: `docs/exec-plans/active/` → `docs/exec-plans/completed/`
|
|
62
|
+
|
|
63
|
+
### 7. Mark Spec Shipped
|
|
64
|
+
|
|
65
|
+
- Open `docs/product-specs/ready/SPEC-XXX.md`
|
|
66
|
+
- Update `Status: SHIPPED`
|
|
67
|
+
- Update `docs/product-specs/index.md` table
|
|
68
|
+
|
|
69
|
+
### 8. Final Commit
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git add -A
|
|
73
|
+
git commit -m "chore: garbage collect — close PLAN-XXX
|
|
74
|
+
|
|
75
|
+
- QUALITY_SCORE.md updated
|
|
76
|
+
- [N] debt items logged
|
|
77
|
+
- PLAN-XXX moved to completed
|
|
78
|
+
- SPEC-XXX marked SHIPPED"
|
|
79
|
+
git push origin main
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 9. Report
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
GARBAGE COLLECT COMPLETE
|
|
86
|
+
|
|
87
|
+
Files split: N
|
|
88
|
+
Violations fixed: N
|
|
89
|
+
Stale docs updated: N
|
|
90
|
+
Quality scores: [domain: grade, ...]
|
|
91
|
+
Debt logged: N items
|
|
92
|
+
Plan closed: PLAN-XXX
|
|
93
|
+
Spec shipped: SPEC-XXX
|
|
94
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gate-checker
|
|
3
|
+
description: Runs the layer gate check. Called automatically by /build-layer after every layer build. Outputs PASS or FAIL with specific actionable items. Never called directly by the human.
|
|
4
|
+
model: claude-opus-4-8
|
|
5
|
+
tools: [Bash, Read]
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the gate enforcement agent. Your only job is to check the current layer against all harness standards and output a precise PASS or FAIL verdict.
|
|
10
|
+
|
|
11
|
+
You are called after a layer is built. You do NOT build code — you check it.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## How to Run
|
|
16
|
+
|
|
17
|
+
1. Read `ARCHITECTURE.md` to know the layer rules
|
|
18
|
+
2. Identify which layer was just built from the active plan
|
|
19
|
+
3. Run every check below that applies to that layer
|
|
20
|
+
4. Output the full gate report
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Universal Checks (every layer)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. File size — no file > 400 lines
|
|
28
|
+
find src/ -name "*.ts" -exec wc -l {} + | sort -rn | awk '$1 > 400 { print "FAIL:", $2, "has", $1, "lines" }'
|
|
29
|
+
|
|
30
|
+
# 2. console.log in src/ (config/ is exempt — it hosts logger.ts)
|
|
31
|
+
find src/ -name "*.ts" \
|
|
32
|
+
-not -path "src/config/*" \
|
|
33
|
+
-exec grep -Hn "console\.\(log\|error\|warn\|info\)" {} +
|
|
34
|
+
|
|
35
|
+
# 3. process.env outside config/env.ts
|
|
36
|
+
find src/ -name "*.ts" -not -path "src/config/env.ts" \
|
|
37
|
+
-exec grep -Hn "process\.env" {} +
|
|
38
|
+
|
|
39
|
+
# 4. TypeScript compilation
|
|
40
|
+
npx tsc --noEmit 2>&1
|
|
41
|
+
|
|
42
|
+
# 5. ESLint
|
|
43
|
+
npx eslint src/ --max-warnings=0 2>&1
|
|
44
|
+
|
|
45
|
+
# 6. Circular imports (madge)
|
|
46
|
+
npx madge --circular src/ --extensions ts 2>&1
|
|
47
|
+
|
|
48
|
+
# 7. Architecture structural tests
|
|
49
|
+
npx jest tests/architecture/ --no-coverage 2>&1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Layer-Specific Checks
|
|
53
|
+
|
|
54
|
+
### Types layer
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Zero imports from other layers (types may only self-reference)
|
|
58
|
+
find src/types/ -name "*.ts" -exec grep -Hn "from '\.\." {} + | grep -v "from '.*types"
|
|
59
|
+
# Zero logic (no function implementations)
|
|
60
|
+
find src/types/ -name "*.ts" -exec grep -Hn "^export function\|^export const.*=.*=>" {} +
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Config layer
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Only imports from types/zod/path
|
|
67
|
+
find src/config/ -name "*.ts" -exec grep -Hn "from '\.\." {} + | grep -Ev "types|zod|path"
|
|
68
|
+
# Zod validation present in env.ts
|
|
69
|
+
grep -n "safeParse\|process.exit" src/config/env.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Models layer
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# paranoid: true on all model @Table decorators — list models missing it
|
|
76
|
+
find src/models/ -name "*.ts" | while read -r f; do
|
|
77
|
+
if grep -q "@Table" "$f" && ! grep -q "paranoid: true" "$f"; then
|
|
78
|
+
echo "FAIL: $f has @Table without paranoid: true"
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
# UUIDv7 default on all id columns — @Default present but not newId
|
|
82
|
+
find src/models/ -name "*.ts" -exec grep -Hn "@Default" {} + | grep -v "newId"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Repo layer
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Zod parse on all returns — no bare raw or as-cast
|
|
89
|
+
find src/repo/ -name "*.ts" -exec grep -Hn "return raw as\|\.toJSON() as\|return await.*\.findAll\|return await.*\.findByPk\|return await.*\.findOne\|return await.*\.create" {} +
|
|
90
|
+
# Ownership: findByIdAndUser pattern (no findByPk without userId)
|
|
91
|
+
find src/repo/ -name "*.ts" -exec grep -Hn "findByPk\|findOne" {} + | grep -v "userId\|user_id"
|
|
92
|
+
|
|
93
|
+
# Cursor pagination check — look for offset/skip (should be empty)
|
|
94
|
+
find src/repo/ -name "*.ts" -exec grep -Hn "offset:\|\.skip(" {} +
|
|
95
|
+
|
|
96
|
+
# N+1 check — manual review: look for Model.find* inside for/forEach loops
|
|
97
|
+
find src/repo/ -name "*.ts" -exec grep -Hn "for \|forEach\|\.map(async" {} + \
|
|
98
|
+
&& echo "Manually verify: no await Model.find* calls inside the above loops"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Service layer
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# No express imports (services are HTTP-agnostic)
|
|
105
|
+
find src/services/ -name "*.ts" -exec grep -Hn "from 'express'\|require('express')" {} +
|
|
106
|
+
|
|
107
|
+
# Coverage check
|
|
108
|
+
npx jest tests/unit/services/ --coverage --coverageThreshold='{"global":{"lines":90}}' 2>&1 | tail -5
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Runtime layer
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Auth first — requireAuth before body parsing in all POST/PATCH/DELETE
|
|
115
|
+
find src/runtime/routes/ -name "*.ts" -exec grep -Hn "requireAuth" {} + | wc -l # should match protected route count
|
|
116
|
+
|
|
117
|
+
# Response envelope — ok() helper used (raw res.json should be empty)
|
|
118
|
+
find src/runtime/routes/ -name "*.ts" -exec grep -Hn "res\.json({" {} +
|
|
119
|
+
|
|
120
|
+
# Rate limit applied
|
|
121
|
+
find src/runtime/routes/ -name "*.ts" -exec grep -Hn "RateLimit\|rateLimiter" {} +
|
|
122
|
+
|
|
123
|
+
# Coverage
|
|
124
|
+
npx jest tests/integration/ --coverage 2>&1 | tail -5
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Utils layer
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Zero domain imports except type-only Types (node:, path, crypto, ../types allowed)
|
|
131
|
+
find src/utils/ -name "*.ts" -exec grep -Hn "from '\.\." {} + | grep -Ev "node:|path|crypto|/types"
|
|
132
|
+
|
|
133
|
+
# 100% coverage required
|
|
134
|
+
npx jest tests/unit/utils/ --coverage --coverageThreshold='{"global":{"lines":100,"branches":100}}' 2>&1 | tail -5
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Output Format
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
─────────────────────────────────────────
|
|
143
|
+
GATE CHECK — Layer: [name] [Attempt N]
|
|
144
|
+
─────────────────────────────────────────
|
|
145
|
+
Universal
|
|
146
|
+
✓/✗ No file > 400 lines
|
|
147
|
+
✓/✗ No console.log in src/
|
|
148
|
+
✓/✗ No process.env outside config
|
|
149
|
+
✓/✗ TypeScript compiles clean
|
|
150
|
+
✓/✗ ESLint: 0 errors
|
|
151
|
+
✓/✗ No circular imports
|
|
152
|
+
✓/✗ Architecture tests pass
|
|
153
|
+
|
|
154
|
+
Layer-Specific ([layer name])
|
|
155
|
+
✓/✗ [check description]
|
|
156
|
+
✓/✗ [check description]
|
|
157
|
+
|
|
158
|
+
Tests
|
|
159
|
+
✓/✗ Coverage: XX% (threshold: YY%)
|
|
160
|
+
✓/✗ All tests pass
|
|
161
|
+
|
|
162
|
+
Decision Doc
|
|
163
|
+
✓/✗ ADR written for [decision] / No non-obvious decisions
|
|
164
|
+
─────────────────────────────────────────
|
|
165
|
+
STATUS: ✅ PASS / ❌ FAIL — N items
|
|
166
|
+
|
|
167
|
+
[If FAIL, list each item:]
|
|
168
|
+
ITEM 1: [file:line] [problem] → [exact fix]
|
|
169
|
+
ITEM 2: ...
|
|
170
|
+
─────────────────────────────────────────
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## After FAIL Output
|
|
174
|
+
|
|
175
|
+
Do NOT ask the human what to do.
|
|
176
|
+
Return the FAIL report to the `/build-layer` skill, which will auto-fix each item and re-call you.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Full harness review before opening a PR. Checks all layers against harness standards. Use 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 engineer reviewing a PR against the harness engineering standards.
|
|
10
|
+
Run `git diff main --name-only` then `git diff main` to see all changes.
|
|
11
|
+
|
|
12
|
+
## Review Checklist
|
|
13
|
+
|
|
14
|
+
### Architecture
|
|
15
|
+
|
|
16
|
+
- [ ] No cross-layer imports (ESLint + madge)
|
|
17
|
+
- [ ] No file > 400 lines
|
|
18
|
+
- [ ] No circular imports
|
|
19
|
+
- [ ] TypeScript compiles clean
|
|
20
|
+
|
|
21
|
+
### Code Quality
|
|
22
|
+
|
|
23
|
+
- [ ] No `console.log` (use logger)
|
|
24
|
+
- [ ] No `process.env` outside `src/config/env.ts`
|
|
25
|
+
- [ ] No `as SomeType` on external/DB data
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
|
|
29
|
+
- [ ] Auth check first on every protected route
|
|
30
|
+
- [ ] Zod validation before any business logic
|
|
31
|
+
- [ ] Ownership enforced in repo layer
|
|
32
|
+
- [ ] Error responses sanitised (no stack traces)
|
|
33
|
+
- [ ] SQL: Sequelize ORM only (no interpolated raw queries)
|
|
34
|
+
|
|
35
|
+
### Database (if repo/model changed)
|
|
36
|
+
|
|
37
|
+
- [ ] All query results: `Schema.parse(raw.toJSON())`
|
|
38
|
+
- [ ] All list methods: cursor-based pagination (no offset)
|
|
39
|
+
- [ ] No N+1 (no Model calls inside loops)
|
|
40
|
+
- [ ] Multi-table writes: `sequelize.transaction()`
|
|
41
|
+
- [ ] New models: `paranoid: true`, `newId()` for UUIDs
|
|
42
|
+
- [ ] New queries: indexes defined and migrated
|
|
43
|
+
|
|
44
|
+
### API (if routes changed)
|
|
45
|
+
|
|
46
|
+
- [ ] Routes under `/api/v1/` prefix
|
|
47
|
+
- [ ] Rate limiting applied
|
|
48
|
+
- [ ] Response envelope: `ok()` / `err()` helpers
|
|
49
|
+
- [ ] `X-Request-ID` attached to response
|
|
50
|
+
|
|
51
|
+
### Jobs (if workers changed)
|
|
52
|
+
|
|
53
|
+
- [ ] Payload validated with Zod
|
|
54
|
+
- [ ] Retry config on queue definition
|
|
55
|
+
- [ ] start/complete/failed logs emitted
|
|
56
|
+
- [ ] Circuit breaker on external calls
|
|
57
|
+
|
|
58
|
+
### Tests
|
|
59
|
+
|
|
60
|
+
- [ ] Utils: 100% coverage
|
|
61
|
+
- [ ] Services: ≥ 90%
|
|
62
|
+
- [ ] Error paths tested (not just happy path)
|
|
63
|
+
- [ ] Invalid Zod inputs tested
|
|
64
|
+
|
|
65
|
+
### Docs
|
|
66
|
+
|
|
67
|
+
- [ ] ADR written for non-obvious decisions
|
|
68
|
+
- [ ] QUALITY_SCORE.md updated for affected domains
|
|
69
|
+
|
|
70
|
+
## Verdict
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
APPROVED — ready to merge.
|
|
74
|
+
|
|
75
|
+
OR
|
|
76
|
+
|
|
77
|
+
CHANGES REQUIRED:
|
|
78
|
+
|
|
79
|
+
BLOCKING:
|
|
80
|
+
1. [description + file:line + fix]
|
|
81
|
+
|
|
82
|
+
NON-BLOCKING (fix before next feature):
|
|
83
|
+
2. [description]
|
|
84
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-auditor
|
|
3
|
+
description: OWASP Top 10 security review. Run before any PR touching auth, payments, file uploads, or external data. Use with "Use the security-auditor agent to review current changes."
|
|
4
|
+
model: claude-opus-4-8
|
|
5
|
+
tools: [Read, Bash]
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a senior application security engineer. Review all recent changes against OWASP Top 10.
|
|
10
|
+
Be specific. A missed vulnerability is a production incident.
|
|
11
|
+
|
|
12
|
+
## Pre-Review Steps
|
|
13
|
+
|
|
14
|
+
1. `git diff main --name-only` → list changed files
|
|
15
|
+
2. Read each changed file carefully
|
|
16
|
+
3. Run the checklist
|
|
17
|
+
|
|
18
|
+
## OWASP Checklist
|
|
19
|
+
|
|
20
|
+
### A01 — Broken Access Control
|
|
21
|
+
|
|
22
|
+
- [ ] Every protected route: `requireAuth()` is the FIRST call
|
|
23
|
+
- [ ] Ownership check: `findByIdAndUser(id, auth.userId)` not `findById(id)`
|
|
24
|
+
- [ ] RBAC: `requirePermission()` on role-restricted actions
|
|
25
|
+
- [ ] No horizontal escalation: user A cannot access user B's data
|
|
26
|
+
|
|
27
|
+
### A02 — Cryptographic Failures
|
|
28
|
+
|
|
29
|
+
- [ ] Passwords: argon2 hash (not bcrypt, not MD5/SHA1)
|
|
30
|
+
- [ ] JWT secret: min 32 chars, from env (not hardcoded)
|
|
31
|
+
- [ ] Sensitive fields: `[REDACTED]` in logs
|
|
32
|
+
- [ ] HSTS header: present in helmet config
|
|
33
|
+
|
|
34
|
+
### A03 — Injection
|
|
35
|
+
|
|
36
|
+
- [ ] All SQL via Sequelize ORM — no string-interpolated raw queries
|
|
37
|
+
- [ ] All inputs validated with Zod before use
|
|
38
|
+
- [ ] `sequelize.query()` uses named replacements only
|
|
39
|
+
|
|
40
|
+
### A04 — Insecure Design
|
|
41
|
+
|
|
42
|
+
- [ ] Auth endpoints rate limited (10/min)
|
|
43
|
+
- [ ] Idempotency keys on mutation endpoints
|
|
44
|
+
- [ ] UUIDs (not sequential ints) on user-facing IDs
|
|
45
|
+
|
|
46
|
+
### A05 — Security Misconfiguration
|
|
47
|
+
|
|
48
|
+
- [ ] Helmet applied globally (7 headers on every response)
|
|
49
|
+
- [ ] CORS configured once centrally (no `*` on authenticated endpoints)
|
|
50
|
+
- [ ] No stack traces in error responses
|
|
51
|
+
- [ ] No debug mode in production
|
|
52
|
+
|
|
53
|
+
### A07 — Auth Failures
|
|
54
|
+
|
|
55
|
+
- [ ] Access token: max 15 min lifetime
|
|
56
|
+
- [ ] Refresh token: httpOnly cookie, 7 days, single-use (rotate on use)
|
|
57
|
+
- [ ] Token revocation: Redis revocation list checked on every verify
|
|
58
|
+
- [ ] Login error: same message for bad user vs bad password (no enumeration)
|
|
59
|
+
|
|
60
|
+
### A09 — Logging Failures
|
|
61
|
+
|
|
62
|
+
- [ ] Auth events logged: login, logout, failed auth, token refresh
|
|
63
|
+
- [ ] 401/403 logged with requestId and userId
|
|
64
|
+
- [ ] No PII in log values
|
|
65
|
+
|
|
66
|
+
## Verdict Format
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
APPROVED — all checks pass.
|
|
70
|
+
|
|
71
|
+
OR
|
|
72
|
+
|
|
73
|
+
CHANGES REQUIRED:
|
|
74
|
+
|
|
75
|
+
CRITICAL (block merge):
|
|
76
|
+
1. [file:line] [problem]
|
|
77
|
+
Fix: [exact code change]
|
|
78
|
+
|
|
79
|
+
HIGH (fix before production):
|
|
80
|
+
2. [file:line] [problem]
|
|
81
|
+
Fix: [exact code change]
|
|
82
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .claude/hooks/post-write.sh
|
|
3
|
+
# Fires after every Write/Edit/MultiEdit tool call.
|
|
4
|
+
# Claude Code passes the tool invocation as JSON on stdin; the edited file
|
|
5
|
+
# path lives at .tool_input.file_path.
|
|
6
|
+
# Checks: console.log, process.env outside config, file size, unsafe casts.
|
|
7
|
+
# Prints warnings directly — gate-checker does the hard blocking.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# Read all of stdin once (the JSON tool payload).
|
|
12
|
+
PAYLOAD=$(cat)
|
|
13
|
+
|
|
14
|
+
# Resolve the file path. Prefer the env var Claude Code may expose, else parse
|
|
15
|
+
# the JSON payload with node (always present in this project). We check the
|
|
16
|
+
# documented nested path first (.tool_input.file_path), then top-level fallbacks.
|
|
17
|
+
FILE="${CLAUDE_FILE_PATH:-}"
|
|
18
|
+
|
|
19
|
+
if [[ -z "$FILE" ]]; then
|
|
20
|
+
FILE=$(printf '%s' "$PAYLOAD" | node -e "
|
|
21
|
+
let raw = '';
|
|
22
|
+
process.stdin.on('data', (c) => (raw += c));
|
|
23
|
+
process.stdin.on('end', () => {
|
|
24
|
+
try {
|
|
25
|
+
const d = JSON.parse(raw);
|
|
26
|
+
const p =
|
|
27
|
+
(d.tool_input && (d.tool_input.file_path || d.tool_input.path)) ||
|
|
28
|
+
d.file_path ||
|
|
29
|
+
d.path ||
|
|
30
|
+
'';
|
|
31
|
+
process.stdout.write(String(p));
|
|
32
|
+
} catch {
|
|
33
|
+
process.stdout.write('');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
" 2>/dev/null || echo "")
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Nothing to check
|
|
40
|
+
[[ -z "$FILE" ]] && exit 0
|
|
41
|
+
[[ ! -f "$FILE" ]] && exit 0
|
|
42
|
+
|
|
43
|
+
# Only check TypeScript source files
|
|
44
|
+
if [[ ! "$FILE" =~ \.ts$ ]]; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Normalise to a path relative to the repo root so matching works whether the
|
|
49
|
+
# payload gives an absolute or relative path (strip everything before src/).
|
|
50
|
+
REL="$FILE"
|
|
51
|
+
if [[ "$REL" == *"/src/"* ]]; then
|
|
52
|
+
REL="src/${REL##*/src/}"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
WARNINGS=()
|
|
56
|
+
|
|
57
|
+
# 1. console.log check in src/ (config/ is exempt — it hosts logger.ts)
|
|
58
|
+
if [[ "$REL" =~ ^src/ ]] && [[ ! "$REL" =~ src/config/ ]]; then
|
|
59
|
+
if grep -qn "console\.\(log\|error\|warn\|info\)" "$FILE" 2>/dev/null; then
|
|
60
|
+
LINE=$(grep -n "console\.\(log\|error\|warn\|info\)" "$FILE" | head -1 | cut -d: -f1)
|
|
61
|
+
WARNINGS+=("⚠ [HOOK] console.log found at $REL:$LINE — use logger from src/config/logger.ts")
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# 2. process.env check — only allowed in src/config/env.ts
|
|
66
|
+
if [[ "$REL" =~ ^src/ ]] && [[ ! "$REL" =~ src/config/env\.ts ]]; then
|
|
67
|
+
if grep -qn "process\.env" "$FILE" 2>/dev/null; then
|
|
68
|
+
LINE=$(grep -n "process\.env" "$FILE" | head -1 | cut -d: -f1)
|
|
69
|
+
WARNINGS+=("⚠ [HOOK] process.env found at $REL:$LINE — import from src/config/env.ts instead")
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 3. File size check
|
|
74
|
+
LINE_COUNT=$(wc -l < "$FILE" 2>/dev/null || echo 0)
|
|
75
|
+
if [[ $LINE_COUNT -gt 400 ]]; then
|
|
76
|
+
WARNINGS+=("⚠ [HOOK] $REL has $LINE_COUNT lines — exceeds 400 line limit. Split into focused modules.")
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# 4. `as SomeType` on external data check in repo files
|
|
80
|
+
if [[ "$REL" =~ \.repo\.ts$ ]]; then
|
|
81
|
+
if grep -qn "return raw as " "$FILE" 2>/dev/null; then
|
|
82
|
+
LINE=$(grep -n "return raw as " "$FILE" | head -1 | cut -d: -f1)
|
|
83
|
+
WARNINGS+=("⚠ [HOOK] Unsafe cast at $REL:$LINE — use Schema.parse(raw.toJSON()) instead")
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Print all warnings
|
|
88
|
+
for w in "${WARNINGS[@]}"; do
|
|
89
|
+
echo "$w"
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
# Exit 0 always — hooks warn, gate-checker blocks
|
|
93
|
+
exit 0
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "src/runtime/routes/**/*.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# API Rules — Auto-injected on route file edits
|
|
7
|
+
|
|
8
|
+
## Route File Checklist (verify before every route)
|
|
9
|
+
|
|
10
|
+
### Versioning
|
|
11
|
+
```typescript
|
|
12
|
+
// ✅ All routes under version prefix
|
|
13
|
+
// File: src/runtime/routes/v1/applications.route.ts
|
|
14
|
+
router.get('/applications', ...) // mounted at /api/v1/applications
|
|
15
|
+
|
|
16
|
+
// ❌ Never create unversioned routes
|
|
17
|
+
app.get('/applications', ...) // no version = breaking changes are impossible to manage
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Required Handler Structure (every protected route)
|
|
21
|
+
```typescript
|
|
22
|
+
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
|
23
|
+
try {
|
|
24
|
+
const auth = await requireAuth(req) // 1. Auth
|
|
25
|
+
const body = CreateApplicationSchema.parse(req.body) // 2. Validate
|
|
26
|
+
const result = await applicationService.create(auth.userId, body) // 3. Service
|
|
27
|
+
return res.status(201).json(ok(result, req.requestId)) // 4. Respond
|
|
28
|
+
} catch (err) { next(err) } // 5. Error handler
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Rate Limits (pick correct tier per route)
|
|
33
|
+
```typescript
|
|
34
|
+
// Auth endpoints — strict (10/min — brute force protection)
|
|
35
|
+
router.post('/login', authRateLimit, handler)
|
|
36
|
+
router.post('/register', authRateLimit, handler)
|
|
37
|
+
|
|
38
|
+
// Public read endpoints — moderate (60/min)
|
|
39
|
+
router.get('/public-data', publicRateLimit, handler)
|
|
40
|
+
|
|
41
|
+
// Authenticated user endpoints — generous (300/min)
|
|
42
|
+
router.use(userRateLimit) // apply at router level for all user routes
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Response Envelope — Canonical Shape (always)
|
|
46
|
+
|
|
47
|
+
Every response carries a top-level `ok` discriminator so the frontend can branch
|
|
48
|
+
on one field. This shape is identical across all backends in the harness family.
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
// ✅ Success
|
|
52
|
+
{
|
|
53
|
+
"ok": true,
|
|
54
|
+
"data": { /* ... */ },
|
|
55
|
+
"meta": { "requestId": "01932b3e-4f5a-7b8c-9d0e-1f2a3b4c5d6e", "timestamp": "..." }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ✅ Error
|
|
59
|
+
{
|
|
60
|
+
"ok": false,
|
|
61
|
+
"error": {
|
|
62
|
+
"code": "VALIDATION_ERROR", // | NOT_FOUND | UNAUTHORIZED | FORBIDDEN | CONFLICT | RATE_LIMITED | INTERNAL_ERROR
|
|
63
|
+
"message": "Human-readable error message"
|
|
64
|
+
},
|
|
65
|
+
"meta": { "requestId": "01932b3e-4f5a-7b8c-9d0e-1f2a3b4c5d6e" }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ✅ Success: use ok() helper — sets ok: true + data + meta
|
|
71
|
+
res.status(200).json(ok(data, req.requestId))
|
|
72
|
+
|
|
73
|
+
// ✅ Error: use next(err) — errorHandler maps it to the canonical error shape
|
|
74
|
+
next(new NotFoundError('Application not found'))
|
|
75
|
+
|
|
76
|
+
// ❌ Never raw JSON
|
|
77
|
+
res.json({ data: result }) // missing ok + meta
|
|
78
|
+
res.json({ error: err.message }) // wrong format
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Error code enum** (the only allowed `error.code` values):
|
|
82
|
+
`VALIDATION_ERROR`, `NOT_FOUND`, `UNAUTHORIZED`, `FORBIDDEN`, `CONFLICT`, `RATE_LIMITED`, `INTERNAL_ERROR`.
|
|
83
|
+
|
|
84
|
+
**`meta` shape** (pinned by `tests/unit/utils/response.util.test.ts`): success carries
|
|
85
|
+
`{ requestId, timestamp }`; errors carry `{ requestId }` only. The timestamp asymmetry is intentional —
|
|
86
|
+
error responses stay minimal. This is identical across the harness family; do not add fields ad-hoc.
|
|
87
|
+
|
|
88
|
+
### Required Response Headers (applied by middleware — verify they're mounted)
|
|
89
|
+
```typescript
|
|
90
|
+
// In src/runtime/app.ts — these must be present
|
|
91
|
+
app.use(helmetMiddleware) // 7 security headers
|
|
92
|
+
app.use(requestIdMiddleware) // X-Request-ID propagation
|
|
93
|
+
app.use(corsMiddleware) // CORS configured centrally
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Pagination (all list endpoints)
|
|
97
|
+
|
|
98
|
+
Cursors are encoded with **base64url** (URL-safe, no padding) — standardised
|
|
99
|
+
across the harness so cursors are portable between services and safe in query strings.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// ✅ Accept cursor from query params (base64url)
|
|
103
|
+
const cursor = req.query.cursor
|
|
104
|
+
? (JSON.parse(Buffer.from(req.query.cursor as string, 'base64url').toString()) as PageCursor)
|
|
105
|
+
: undefined
|
|
106
|
+
const limit = Math.min(Number(req.query.limit) || 20, 100) // cap at 100
|
|
107
|
+
|
|
108
|
+
const result = await applicationService.list(auth.userId, { cursor, limit })
|
|
109
|
+
return res.json(ok(result, req.requestId))
|
|
110
|
+
// → { ok: true, data: { items: [...], nextCursor: 'base64url...', hasMore: true }, meta: {...} }
|
|
111
|
+
|
|
112
|
+
// ❌ Never accept raw offset/page params on user-facing endpoints
|
|
113
|
+
// ❌ Never use plain base64 — use base64url for URL safety
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Idempotency (mutation endpoints)
|
|
117
|
+
|
|
118
|
+
Mutating routes (POST/PUT/PATCH/DELETE) accept an optional `Idempotency-Key` header, handled by
|
|
119
|
+
`src/runtime/middleware/idempotency.ts` (Redis-backed). Wire it in front of the handler:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
router.post('/', idempotency, userRateLimit, async (req, res, next) => { ... })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- **First call** with a given key → handler runs; response is cached under `{userId}:{method}:{path}:{key}`.
|
|
126
|
+
- **Replay** (same key) → cached response returned with header `Idempotent-Replay: true`.
|
|
127
|
+
- **In-flight** (key still processing) → `409 CONFLICT` (`error.code: "CONFLICT"`).
|
|
128
|
+
- No key, or non-mutating method → middleware is a pass-through.
|
|
129
|
+
|
|
130
|
+
This is what the `security-auditor` A04 check ("idempotency keys on mutation endpoints") verifies.
|
|
131
|
+
|
|
132
|
+
### OpenAPI registration (every route)
|
|
133
|
+
|
|
134
|
+
The harness publishes one machine-readable contract for the frontend's `openapi-fetch` client.
|
|
135
|
+
Every route registers its Zod request/response schemas + path into the registry in
|
|
136
|
+
`src/runtime/openapi.ts`, then `npm run openapi:export` regenerates `docs/generated/openapi.{json,yaml}`.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { registry } from '../../openapi.js'
|
|
140
|
+
registry.registerPath({
|
|
141
|
+
method: 'post',
|
|
142
|
+
path: '/applications',
|
|
143
|
+
request: { body: { content: { 'application/json': { schema: CreateApplicationSchema } } } },
|
|
144
|
+
responses: { 201: { description: 'Created' /* ...envelope... */ } },
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
CI fails if the committed contract drifts from the code (the `quality` job runs `openapi:export`
|
|
149
|
+
and `git diff`). Regenerate and commit `docs/generated/openapi.*` whenever a route or schema changes.
|