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,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 10-db-optimize
|
|
3
|
+
description: /db-optimize — Query Optimization Workflow
|
|
4
|
+
verified: 2026-06-04
|
|
5
|
+
libraries: [sequelize, pg]
|
|
6
|
+
source: https://sequelize.org/docs/v6/
|
|
7
|
+
staleness-threshold-days: 60
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# /db-optimize — Query Optimization Workflow
|
|
11
|
+
|
|
12
|
+
Triggered by: `/db-optimize [method-name or file]`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Step 1 — Find the Slow Query
|
|
17
|
+
```bash
|
|
18
|
+
# From logs (pino + your log aggregator)
|
|
19
|
+
# Or from pg_stat_statements:
|
|
20
|
+
psql $DATABASE_URL -c "
|
|
21
|
+
SELECT query, mean_exec_time::int, calls
|
|
22
|
+
FROM pg_stat_statements
|
|
23
|
+
WHERE mean_exec_time > 200
|
|
24
|
+
ORDER BY mean_exec_time DESC LIMIT 10;
|
|
25
|
+
"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Step 2 — Run EXPLAIN ANALYZE
|
|
29
|
+
```bash
|
|
30
|
+
# Replace with the actual query being executed
|
|
31
|
+
psql $DATABASE_URL << 'SQL'
|
|
32
|
+
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
|
|
33
|
+
SELECT a.*, n.id as note_id, n.content
|
|
34
|
+
FROM applications a
|
|
35
|
+
LEFT JOIN notes n ON n.application_id = a.id
|
|
36
|
+
WHERE a.user_id = 'uuid-here' AND a.deleted_at IS NULL
|
|
37
|
+
ORDER BY a.created_at DESC LIMIT 21;
|
|
38
|
+
SQL
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Step 3 — Read the Output
|
|
42
|
+
| Output | Problem | Fix |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `Seq Scan` on large table | Missing index | Add B-tree index on filter column |
|
|
45
|
+
| `Rows Removed >> returned` | Index too broad | Add partial index |
|
|
46
|
+
| `Nested Loop` with large outer | N+1 at DB level | Add composite index or use Hash Join |
|
|
47
|
+
| `Sort` without `Index Scan` | ORDER BY without index | Add index on sort column |
|
|
48
|
+
|
|
49
|
+
## Step 4 — Add Index via Migration
|
|
50
|
+
```bash
|
|
51
|
+
npx sequelize-cli migration:generate --name add-idx-{table}-{columns}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// In the generated migration file:
|
|
56
|
+
module.exports = {
|
|
57
|
+
up: async (queryInterface) => {
|
|
58
|
+
await queryInterface.sequelize.query(
|
|
59
|
+
'CREATE INDEX CONCURRENTLY idx_{table}_{col} ON {table}({col}) WHERE deleted_at IS NULL'
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
down: async (queryInterface) => {
|
|
63
|
+
await queryInterface.sequelize.query('DROP INDEX IF EXISTS idx_{table}_{col}')
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx sequelize-cli db:migrate
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Step 5 — Also Add to Model
|
|
73
|
+
```typescript
|
|
74
|
+
// src/models/{Entity}.model.ts
|
|
75
|
+
@Table({
|
|
76
|
+
tableName: '{table}',
|
|
77
|
+
paranoid: true,
|
|
78
|
+
indexes: [
|
|
79
|
+
{ fields: ['{col}'], where: { deleted_at: null }, name: 'idx_{table}_{col}' },
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 6 — Verify
|
|
85
|
+
Re-run EXPLAIN ANALYZE — confirm `Index Scan` is used and execution time < 200ms.
|
|
86
|
+
|
|
87
|
+
## Step 7 — Attach to Commit
|
|
88
|
+
```bash
|
|
89
|
+
git add db/migrations/ src/models/
|
|
90
|
+
git commit -m "perf(db): add index idx_{table}_{col}
|
|
91
|
+
|
|
92
|
+
EXPLAIN ANALYZE before: {N}ms
|
|
93
|
+
EXPLAIN ANALYZE after: {M}ms
|
|
94
|
+
|
|
95
|
+
Query: [description]"
|
|
96
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 11-load-test
|
|
3
|
+
description: /load-test — k6 Performance Testing
|
|
4
|
+
verified: 2026-06-04
|
|
5
|
+
libraries: [k6]
|
|
6
|
+
source: https://grafana.com/docs/k6/latest/
|
|
7
|
+
staleness-threshold-days: 60
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# /load-test — k6 Performance Testing
|
|
11
|
+
|
|
12
|
+
Triggered by: `/load-test [smoke|stress|soak]`
|
|
13
|
+
Default: `smoke`
|
|
14
|
+
|
|
15
|
+
**The scripts already ship in the template** at `tests/load/{smoke,stress,soak}.js`
|
|
16
|
+
and run out of the box against `/health` (no feature needed). npm scripts wrap them.
|
|
17
|
+
k6 is a separate binary — install it first: `choco install k6` (Windows) / `brew install k6` (macOS).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Run it
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Local (server running on :3000)
|
|
25
|
+
npm run load:smoke
|
|
26
|
+
npm run load:stress
|
|
27
|
+
npm run load:soak
|
|
28
|
+
|
|
29
|
+
# Against a remote target (+ auth)
|
|
30
|
+
k6 run --env BASE_URL=https://staging.example.com --env AUTH_TOKEN=$TOKEN tests/load/smoke.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## In CI (manual)
|
|
34
|
+
|
|
35
|
+
`.github/workflows/load-test.yml` runs **only** via `workflow_dispatch` — never on
|
|
36
|
+
push or PR. Trigger it from **GitHub → Actions → Load Test → Run workflow** and pass
|
|
37
|
+
`base_url`, `test` (smoke/stress/soak), and optional `auth_token`.
|
|
38
|
+
|
|
39
|
+
## Profiles & thresholds
|
|
40
|
+
|
|
41
|
+
| Profile | Load | Thresholds |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Smoke | 10 VUs / 30s | P95 < 500ms · err < 1% |
|
|
44
|
+
| Stress | ramp → 100 VUs | P95 < 1000ms · P99 < 2000ms · err < 5% |
|
|
45
|
+
| Soak | 20 VUs, sustained (`--env DURATION=2h`) | P95 < 800ms · err < 2% |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Adapting a script to a real endpoint
|
|
50
|
+
|
|
51
|
+
Each shipped script hits `/health` and includes a **commented** authenticated
|
|
52
|
+
example. Uncomment and point it at your feature:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const headers = { Authorization: `Bearer ${__ENV.AUTH_TOKEN}`, 'Content-Type': 'application/json' }
|
|
56
|
+
const res = http.get(`${__ENV.BASE_URL}/api/v1/applications`, { headers })
|
|
57
|
+
check(res, {
|
|
58
|
+
'status 200': (r) => r.status === 200,
|
|
59
|
+
'has data field': (r) => r.json('data') !== undefined,
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Interpreting Results
|
|
64
|
+
| Metric | Healthy | Investigate |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| P95 latency | < 500ms | > 500ms |
|
|
67
|
+
| P99 latency | < 1000ms | > 1000ms |
|
|
68
|
+
| Error rate | < 1% | > 1% |
|
|
69
|
+
| Checks passed | > 99% | < 99% |
|
|
70
|
+
|
|
71
|
+
## Attach Results to PR
|
|
72
|
+
```
|
|
73
|
+
Load Test Results (smoke — 10 VUs, 30s):
|
|
74
|
+
P95: {N}ms ✅/❌
|
|
75
|
+
P99: {N}ms ✅/❌
|
|
76
|
+
Errors: {N}% ✅/❌
|
|
77
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Build context hygiene — keep the Docker context small and free of secrets.
|
|
2
|
+
# Without this, `COPY . .` in the builder stage ships node_modules, .git, the
|
|
3
|
+
# .claude harness, docs, and tests into the image layers.
|
|
4
|
+
|
|
5
|
+
# Dependencies (reinstalled inside the image via npm ci)
|
|
6
|
+
node_modules/
|
|
7
|
+
npm-debug.log*
|
|
8
|
+
|
|
9
|
+
# Build output (rebuilt inside the image)
|
|
10
|
+
dist/
|
|
11
|
+
coverage/
|
|
12
|
+
|
|
13
|
+
# Version control + CI
|
|
14
|
+
.git/
|
|
15
|
+
.gitignore
|
|
16
|
+
.github/
|
|
17
|
+
|
|
18
|
+
# Agent harness (never needed at runtime)
|
|
19
|
+
.claude/
|
|
20
|
+
|
|
21
|
+
# Docs, specs, plans
|
|
22
|
+
docs/
|
|
23
|
+
*.md
|
|
24
|
+
|
|
25
|
+
# Local env + secrets — NEVER bake these into an image
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
!.env.example
|
|
29
|
+
|
|
30
|
+
# Editor / OS
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
.DS_Store
|
|
34
|
+
Thumbs.db
|
|
35
|
+
*.swp
|
|
36
|
+
|
|
37
|
+
# Tests + load scripts
|
|
38
|
+
tests/
|
|
39
|
+
infra/
|
|
40
|
+
|
|
41
|
+
# Local compose overrides + k6 output
|
|
42
|
+
docker-compose.override.yml
|
|
43
|
+
k6-results/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
|
|
4
|
+
# Server
|
|
5
|
+
PORT=3000
|
|
6
|
+
|
|
7
|
+
# Database (PostgreSQL)
|
|
8
|
+
DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
|
|
9
|
+
DATABASE_POOL_MAX=10
|
|
10
|
+
DATABASE_POOL_MIN=2
|
|
11
|
+
|
|
12
|
+
# Redis
|
|
13
|
+
REDIS_URL=redis://localhost:6379
|
|
14
|
+
|
|
15
|
+
# JWT
|
|
16
|
+
JWT_SECRET=
|
|
17
|
+
JWT_ACCESS_EXPIRY_SECONDS=900
|
|
18
|
+
JWT_REFRESH_EXPIRY_SECONDS=604800
|
|
19
|
+
|
|
20
|
+
# CORS
|
|
21
|
+
CORS_ORIGINS=http://localhost:3000
|
|
22
|
+
|
|
23
|
+
# Logging
|
|
24
|
+
LOG_LEVEL=info
|
|
25
|
+
SERVICE_NAME=your-service-name
|
|
26
|
+
APP_VERSION=0.0.1
|
|
27
|
+
|
|
28
|
+
# Observability (OpenTelemetry)
|
|
29
|
+
# OTLP/HTTP collector base URL for traces + metrics. The exporters append /v1/traces and
|
|
30
|
+
# /v1/metrics.
|
|
31
|
+
# IMPORTANT: BLANK by default ⇒ the SDK fully no-ops (the config/ helpers — logger, withSpan,
|
|
32
|
+
# metrics — still work). This is the correct default for tests, CI, and local-without-backend.
|
|
33
|
+
# To see signals in Grafana: start the LGTM stack and set this to http://localhost:4318
|
|
34
|
+
# (see docs/design-docs/observability.md).
|
|
35
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
36
|
+
OTEL_SERVICE_NAME=your-service-name
|
|
37
|
+
# Optional extra resource attributes (e.g. version, deployment env) — standard OTel var.
|
|
38
|
+
# OTEL_RESOURCE_ATTRIBUTES=service.version=0.0.1,deployment.environment=development
|
|
39
|
+
# How often metrics are flushed to the collector (ms).
|
|
40
|
+
OTEL_METRIC_EXPORT_INTERVAL_MS=10000
|
|
41
|
+
# Logs are NOT sent over OTLP — pino writes correlated JSON to stdout and Grafana Alloy ships
|
|
42
|
+
# it to Loki. See docs/design-docs/observability.md.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# CODEOWNERS — GitHub auto-requests these reviewers based on which files changed.
|
|
2
|
+
# Order matters: the LAST matching pattern wins.
|
|
3
|
+
#
|
|
4
|
+
# Replace the placeholder team slugs with your real GitHub teams/usernames, then
|
|
5
|
+
# enable "Require review from Code Owners" in branch protection
|
|
6
|
+
# (see docs/design-docs/team-workflow.md). Until then these are inert placeholders.
|
|
7
|
+
|
|
8
|
+
# Default — any engineer on the team can review
|
|
9
|
+
* @your-team/engineers
|
|
10
|
+
|
|
11
|
+
# Security-sensitive — leads must review
|
|
12
|
+
/.claude/hooks/ @your-team/leads
|
|
13
|
+
/src/providers/auth/ @your-team/leads
|
|
14
|
+
/src/config/ @your-team/leads
|
|
15
|
+
|
|
16
|
+
# Platform / infra — platform team owns build, containers, and CI
|
|
17
|
+
/Dockerfile @your-team/platform
|
|
18
|
+
/docker-compose.yml @your-team/platform
|
|
19
|
+
/.github/workflows/ @your-team/platform
|
|
20
|
+
|
|
21
|
+
# Database changes are irreversible in prod — leads must review
|
|
22
|
+
/db/migrations/ @your-team/leads
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Dependabot keeps the (deliberately unpinned) dependency tree from drifting
|
|
2
|
+
# silently. Because /infra-setup installs "latest LTS" with no lockfile in the
|
|
3
|
+
# template, every product needs an automated way to surface updates + CVEs once
|
|
4
|
+
# its lockfile exists. Grouped to keep PR noise low.
|
|
5
|
+
version: 2
|
|
6
|
+
updates:
|
|
7
|
+
# Application dependencies (npm). Active once a product's package-lock.json exists.
|
|
8
|
+
- package-ecosystem: npm
|
|
9
|
+
directory: /
|
|
10
|
+
schedule:
|
|
11
|
+
interval: weekly
|
|
12
|
+
day: monday
|
|
13
|
+
open-pull-requests-limit: 10
|
|
14
|
+
versioning-strategy: increase
|
|
15
|
+
groups:
|
|
16
|
+
# Peer-coupled set — must move together or the decorator layer breaks.
|
|
17
|
+
sequelize:
|
|
18
|
+
patterns:
|
|
19
|
+
- sequelize
|
|
20
|
+
- sequelize-typescript
|
|
21
|
+
- sequelize-cli
|
|
22
|
+
# OpenTelemetry shares a release line; bump as one unit.
|
|
23
|
+
opentelemetry:
|
|
24
|
+
patterns:
|
|
25
|
+
- '@opentelemetry/*'
|
|
26
|
+
types:
|
|
27
|
+
patterns:
|
|
28
|
+
- '@types/*'
|
|
29
|
+
update-types:
|
|
30
|
+
- minor
|
|
31
|
+
- patch
|
|
32
|
+
dev-minor-patch:
|
|
33
|
+
dependency-type: development
|
|
34
|
+
update-types:
|
|
35
|
+
- minor
|
|
36
|
+
- patch
|
|
37
|
+
labels:
|
|
38
|
+
- dependencies
|
|
39
|
+
|
|
40
|
+
# GitHub Actions — keep pinned action versions current (security-relevant).
|
|
41
|
+
- package-ecosystem: github-actions
|
|
42
|
+
directory: /
|
|
43
|
+
schedule:
|
|
44
|
+
interval: weekly
|
|
45
|
+
day: monday
|
|
46
|
+
labels:
|
|
47
|
+
- dependencies
|
|
48
|
+
- ci
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
PR title format: <type>(<scope>): <summary>
|
|
3
|
+
type ∈ feat | fix | chore | refactor | docs | test | perf
|
|
4
|
+
Example: feat(applications): add stage-transition endpoint
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
## What does this PR do?
|
|
8
|
+
|
|
9
|
+
<!-- One sentence. Link the spec it came from. -->
|
|
10
|
+
|
|
11
|
+
- Spec: `docs/product-specs/ready/SPEC-XXX.md`
|
|
12
|
+
|
|
13
|
+
## Execution plan reference
|
|
14
|
+
|
|
15
|
+
<!-- Link the plan that drove this work. -->
|
|
16
|
+
|
|
17
|
+
- Plan: `docs/exec-plans/active/PLAN-XXX.md` (or completed/)
|
|
18
|
+
- Closes #
|
|
19
|
+
|
|
20
|
+
## Layers touched (check each, confirm its rule holds)
|
|
21
|
+
|
|
22
|
+
- [ ] **Types** — zero imports from other layers; no logic
|
|
23
|
+
- [ ] **Config** — imports only from types/zod; `env.ts` Zod-validated
|
|
24
|
+
- [ ] **Models** — `paranoid: true`; UUIDv7 ids
|
|
25
|
+
- [ ] **Repo** — `Schema.parse(raw.toJSON())` on every result; ownership (`findByIdAndUser`); cursor pagination
|
|
26
|
+
- [ ] **Service** — no `express`/`Request`/`Response` imports; business logic only
|
|
27
|
+
- [ ] **Runtime** — order is auth → validate → service → respond; canonical envelope via `ok()`/`errorHandler`
|
|
28
|
+
- [ ] **Providers** — entered via explicit interface only
|
|
29
|
+
- [ ] **Utils** — zero domain imports; **100% test coverage**
|
|
30
|
+
|
|
31
|
+
## Gate check (all green)
|
|
32
|
+
|
|
33
|
+
- [ ] `tsc --noEmit` — type-check passes
|
|
34
|
+
- [ ] `eslint src/ --max-warnings=0` — lint clean
|
|
35
|
+
- [ ] `jest` — all tests pass
|
|
36
|
+
- [ ] Coverage meets per-layer thresholds (utils 100 · services 90 · repo 80 · routes 75 · providers 70)
|
|
37
|
+
- [ ] Architecture tests pass (`jest tests/architecture/`)
|
|
38
|
+
- [ ] No file exceeds 400 lines
|
|
39
|
+
|
|
40
|
+
## Security checklist
|
|
41
|
+
|
|
42
|
+
- [ ] No `console.*` in `src/` (use `logger`)
|
|
43
|
+
- [ ] No raw `process.env` outside `src/config/env.ts`
|
|
44
|
+
- [ ] Auth required on every protected endpoint (`requireAuth` first)
|
|
45
|
+
- [ ] `userId` scoped into every query (no cross-tenant leakage)
|
|
46
|
+
- [ ] Cross-user isolation test exists (User B → 404 on User A's resource)
|
|
47
|
+
|
|
48
|
+
## ADR
|
|
49
|
+
|
|
50
|
+
- [ ] A non-obvious architectural decision was made → ADR written in `docs/design-docs/decisions/`
|
|
51
|
+
- [ ] No ADR needed (nothing non-obvious)
|
|
52
|
+
|
|
53
|
+
## Breaking changes
|
|
54
|
+
|
|
55
|
+
- [ ] None
|
|
56
|
+
- [ ] Yes — documented below (API contract, DB migration, env var, or config change)
|
|
57
|
+
|
|
58
|
+
<!-- If breaking: describe the change, the migration path, and rollback steps. -->
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# The PR gate. Runs the same deterministic checks as `npm run gate`, plus
|
|
4
|
+
# security scanning, image build/scan, SBOM, and a performance-budget gate.
|
|
5
|
+
#
|
|
6
|
+
# Template-aware: a freshly-cloned template has no `src/` yet (it is generated by
|
|
7
|
+
# /infra-setup). The `guard` job detects that and lets source-dependent jobs skip
|
|
8
|
+
# cleanly, so the template's own pushes stay green while every product built from
|
|
9
|
+
# it inherits the full pipeline. Action versions are pinned; Dependabot keeps them
|
|
10
|
+
# current (see .github/dependabot.yml).
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
branches: [main]
|
|
15
|
+
pull_request:
|
|
16
|
+
branches: [main]
|
|
17
|
+
|
|
18
|
+
# Cancel superseded runs on the same ref to save minutes.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
security-events: write # upload Trivy SARIF to GitHub code scanning
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Detect whether application code exists yet. All source-dependent jobs gate
|
|
30
|
+
# on `has_src` so the bare template doesn't fail CI before /infra-setup runs.
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
guard:
|
|
33
|
+
name: Detect project state
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
outputs:
|
|
36
|
+
has_src: ${{ steps.detect.outputs.has_src }}
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- id: detect
|
|
40
|
+
run: |
|
|
41
|
+
if [ -n "$(find src -name '*.ts' 2>/dev/null | head -1)" ]; then
|
|
42
|
+
echo "has_src=true" >> "$GITHUB_OUTPUT"
|
|
43
|
+
echo "Application code detected — running the full pipeline."
|
|
44
|
+
else
|
|
45
|
+
echo "has_src=false" >> "$GITHUB_OUTPUT"
|
|
46
|
+
echo "No src/ yet (template state) — source-dependent jobs will skip."
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Deterministic quality gate — mirrors `npm run gate` + coverage-enforced tests.
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
quality:
|
|
53
|
+
name: Lint · Typecheck · Test · Build
|
|
54
|
+
needs: guard
|
|
55
|
+
if: needs.guard.outputs.has_src == 'true'
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
services:
|
|
58
|
+
postgres:
|
|
59
|
+
image: postgres:16-alpine
|
|
60
|
+
env:
|
|
61
|
+
POSTGRES_USER: postgres
|
|
62
|
+
POSTGRES_PASSWORD: postgres
|
|
63
|
+
POSTGRES_DB: myapp_test
|
|
64
|
+
ports: ["5432:5432"]
|
|
65
|
+
options: >-
|
|
66
|
+
--health-cmd "pg_isready -U postgres"
|
|
67
|
+
--health-interval 10s --health-timeout 5s --health-retries 5
|
|
68
|
+
redis:
|
|
69
|
+
image: redis:7-alpine
|
|
70
|
+
ports: ["6379:6379"]
|
|
71
|
+
options: >-
|
|
72
|
+
--health-cmd "redis-cli ping"
|
|
73
|
+
--health-interval 10s --health-timeout 3s --health-retries 5
|
|
74
|
+
env:
|
|
75
|
+
NODE_ENV: test
|
|
76
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/myapp_test
|
|
77
|
+
REDIS_URL: redis://localhost:6379
|
|
78
|
+
JWT_SECRET: ci-test-secret-not-a-real-key-min-32-chars
|
|
79
|
+
CORS_ORIGINS: http://localhost:3000
|
|
80
|
+
# Blank endpoint ⇒ OTel SDK is a clean no-op. Never start LGTM in CI.
|
|
81
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: ""
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- uses: actions/setup-node@v4
|
|
85
|
+
with:
|
|
86
|
+
node-version: "24"
|
|
87
|
+
cache: npm
|
|
88
|
+
- name: Install dependencies
|
|
89
|
+
run: |
|
|
90
|
+
if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
91
|
+
- name: Typecheck
|
|
92
|
+
run: npm run typecheck
|
|
93
|
+
- name: Lint (0 warnings)
|
|
94
|
+
run: npm run lint
|
|
95
|
+
- name: Circular import check
|
|
96
|
+
run: npm run check:circular
|
|
97
|
+
- name: Test with coverage (thresholds enforced in jest.config.ts)
|
|
98
|
+
run: npm run test:coverage
|
|
99
|
+
- name: Build
|
|
100
|
+
run: npm run build
|
|
101
|
+
- name: OpenAPI contract is up to date
|
|
102
|
+
run: |
|
|
103
|
+
npm run openapi:export
|
|
104
|
+
# -N (intent-to-add) makes a never-committed contract count as a diff too, so this
|
|
105
|
+
# catches both drift AND "forgot to commit the generated contract" (plain `git diff`
|
|
106
|
+
# ignores untracked files and would pass silently).
|
|
107
|
+
git add -N docs/generated/openapi.json docs/generated/openapi.yaml 2>/dev/null || true
|
|
108
|
+
if ! git diff --exit-code -- docs/generated/openapi.json docs/generated/openapi.yaml; then
|
|
109
|
+
echo "::error::OpenAPI contract drifted or was never committed. Run 'npm run openapi:export' and commit docs/generated/openapi.*"
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
- name: Upload coverage
|
|
113
|
+
if: always()
|
|
114
|
+
uses: actions/upload-artifact@v4
|
|
115
|
+
with:
|
|
116
|
+
name: coverage
|
|
117
|
+
path: coverage/
|
|
118
|
+
if-no-files-found: ignore
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Secret scanning — runs ALWAYS (even on the bare template; secrets can land in
|
|
122
|
+
# any file, not just src/).
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
secret-scan:
|
|
125
|
+
name: Secret scan (gitleaks)
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
steps:
|
|
128
|
+
- uses: actions/checkout@v4
|
|
129
|
+
with:
|
|
130
|
+
fetch-depth: 0 # full history so gitleaks can scan every commit
|
|
131
|
+
# Run the gitleaks CLI from its official image rather than the marketplace
|
|
132
|
+
# action. The action calls the GitHub API (job summaries / usage checks) and
|
|
133
|
+
# crashes with an unhandled RequestError on any non-2xx response; the binary
|
|
134
|
+
# just scans git history and exits non-zero on findings — no token, no API,
|
|
135
|
+
# no action version to resolve. `git` subcommand needs the full history above.
|
|
136
|
+
- name: gitleaks
|
|
137
|
+
run: |
|
|
138
|
+
docker run --rm -v "$PWD:/repo" -w /repo \
|
|
139
|
+
ghcr.io/gitleaks/gitleaks:latest git \
|
|
140
|
+
--config .gitleaks.toml --redact --exit-code 1 .
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Dependency vulnerability audit (needs a lockfile to be meaningful).
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
audit:
|
|
146
|
+
name: Dependency audit
|
|
147
|
+
needs: guard
|
|
148
|
+
if: needs.guard.outputs.has_src == 'true'
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v4
|
|
152
|
+
- uses: actions/setup-node@v4
|
|
153
|
+
with:
|
|
154
|
+
node-version: "24"
|
|
155
|
+
cache: npm
|
|
156
|
+
- name: Install dependencies
|
|
157
|
+
run: |
|
|
158
|
+
if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
159
|
+
- name: npm audit (fail on high+)
|
|
160
|
+
run: npm run audit
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Static analysis (CodeQL) — scans the app source for security issues and
|
|
164
|
+
# uploads findings to GitHub code scanning. JS/TS needs no build step.
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
codeql:
|
|
167
|
+
name: CodeQL (JS/TS)
|
|
168
|
+
needs: guard
|
|
169
|
+
if: needs.guard.outputs.has_src == 'true'
|
|
170
|
+
runs-on: ubuntu-latest
|
|
171
|
+
permissions:
|
|
172
|
+
contents: read
|
|
173
|
+
security-events: write
|
|
174
|
+
steps:
|
|
175
|
+
- uses: actions/checkout@v4
|
|
176
|
+
- uses: github/codeql-action/init@v3
|
|
177
|
+
with:
|
|
178
|
+
languages: javascript-typescript
|
|
179
|
+
- uses: github/codeql-action/analyze@v3
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Dependency review — blocks PRs that introduce vulnerable or disallowed-license
|
|
183
|
+
# dependencies. PR-only (it diffs the base vs head dependency manifests).
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
dependency-review:
|
|
186
|
+
name: Dependency review
|
|
187
|
+
if: github.event_name == 'pull_request'
|
|
188
|
+
runs-on: ubuntu-latest
|
|
189
|
+
steps:
|
|
190
|
+
- uses: actions/checkout@v4
|
|
191
|
+
- uses: actions/dependency-review-action@v4
|
|
192
|
+
with:
|
|
193
|
+
fail-on-severity: high
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Container build + image vulnerability scan + SBOM.
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
container:
|
|
199
|
+
name: Image build · Trivy scan · SBOM
|
|
200
|
+
needs: [guard, quality]
|
|
201
|
+
if: needs.guard.outputs.has_src == 'true'
|
|
202
|
+
runs-on: ubuntu-latest
|
|
203
|
+
steps:
|
|
204
|
+
- uses: actions/checkout@v4
|
|
205
|
+
- name: Build image
|
|
206
|
+
run: docker build -t harness-app:ci .
|
|
207
|
+
# Scanners run from their official Docker images (not marketplace actions) so there is
|
|
208
|
+
# no action-version to resolve/pin. `:latest` is intentional — you want the freshest
|
|
209
|
+
# vuln DB; a team that needs reproducibility can pin an image digest here.
|
|
210
|
+
- name: Trivy image scan (fail on CRITICAL/HIGH)
|
|
211
|
+
run: |
|
|
212
|
+
docker run --rm \
|
|
213
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
214
|
+
-v "$PWD:/out" \
|
|
215
|
+
aquasec/trivy:latest image \
|
|
216
|
+
--severity CRITICAL,HIGH --ignore-unfixed \
|
|
217
|
+
--format sarif --output /out/trivy-results.sarif \
|
|
218
|
+
--exit-code 1 \
|
|
219
|
+
harness-app:ci
|
|
220
|
+
- name: Upload Trivy SARIF
|
|
221
|
+
if: always()
|
|
222
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
223
|
+
with:
|
|
224
|
+
sarif_file: trivy-results.sarif
|
|
225
|
+
- name: Generate SBOM (SPDX) with Syft
|
|
226
|
+
if: always()
|
|
227
|
+
run: |
|
|
228
|
+
docker run --rm \
|
|
229
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
230
|
+
-v "$PWD:/out" \
|
|
231
|
+
anchore/syft:latest \
|
|
232
|
+
scan harness-app:ci -o spdx-json=/out/sbom.spdx.json
|
|
233
|
+
- name: Upload SBOM
|
|
234
|
+
if: always()
|
|
235
|
+
uses: actions/upload-artifact@v4
|
|
236
|
+
with:
|
|
237
|
+
name: sbom
|
|
238
|
+
path: sbom.spdx.json
|
|
239
|
+
if-no-files-found: ignore
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# Performance budget gate — boots the app + Postgres + Redis via compose and
|
|
243
|
+
# runs the k6 smoke test (p95 < 500ms, error rate < 1%; see tests/load/smoke.js).
|
|
244
|
+
# Push-only (post-merge) to keep PRs fast; the manual load-test.yml covers
|
|
245
|
+
# stress/soak against deployed environments.
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
perf-smoke:
|
|
248
|
+
name: Perf budget (k6 smoke)
|
|
249
|
+
needs: [guard, quality]
|
|
250
|
+
if: needs.guard.outputs.has_src == 'true' && github.event_name == 'push'
|
|
251
|
+
runs-on: ubuntu-latest
|
|
252
|
+
steps:
|
|
253
|
+
- uses: actions/checkout@v4
|
|
254
|
+
- name: Boot the stack
|
|
255
|
+
env:
|
|
256
|
+
# Blank ⇒ OTel SDK no-ops. Honors "never start the LGTM backend in CI".
|
|
257
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: ""
|
|
258
|
+
run: |
|
|
259
|
+
# --wait blocks until postgres/redis are healthy so the app doesn't race the DB on boot.
|
|
260
|
+
docker compose up -d --wait postgres redis
|
|
261
|
+
# --no-deps so the lgtm/alloy observability backend is NOT started in CI.
|
|
262
|
+
docker compose up -d --no-deps --build app
|
|
263
|
+
# Wait for the app health endpoint.
|
|
264
|
+
for i in $(seq 1 30); do
|
|
265
|
+
if curl -fsS http://localhost:3000/health >/dev/null 2>&1; then echo "app up"; break; fi
|
|
266
|
+
sleep 2
|
|
267
|
+
done
|
|
268
|
+
- uses: grafana/setup-k6-action@v1
|
|
269
|
+
- name: k6 smoke (enforces thresholds)
|
|
270
|
+
env:
|
|
271
|
+
BASE_URL: http://localhost:3000
|
|
272
|
+
run: k6 run tests/load/smoke.js
|
|
273
|
+
- name: Dump app logs on failure
|
|
274
|
+
if: failure()
|
|
275
|
+
run: docker compose logs app
|
|
276
|
+
- name: Tear down
|
|
277
|
+
if: always()
|
|
278
|
+
run: docker compose down -v
|