forgehive 0.6.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/README.md +631 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6265 -0
- package/dist/confirm.d.ts +1 -0
- package/dist/confirm.js +15 -0
- package/dist/fulfillment-catalog.d.ts +10 -0
- package/dist/fulfillment-catalog.js +119 -0
- package/dist/lookup-table.d.ts +16 -0
- package/dist/lookup-table.js +128 -0
- package/dist/rollback.d.ts +1 -0
- package/dist/rollback.js +9 -0
- package/dist/scanner.d.ts +2 -0
- package/dist/scanner.js +87 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/update.d.ts +7 -0
- package/dist/update.js +42 -0
- package/dist/writer.d.ts +3 -0
- package/dist/writer.js +63 -0
- package/forgehive/agents/eli.yaml +8 -0
- package/forgehive/agents/kai.yaml +8 -0
- package/forgehive/agents/nora.yaml +8 -0
- package/forgehive/agents/remy.yaml +8 -0
- package/forgehive/agents/sam.yaml +8 -0
- package/forgehive/agents/suki.yaml +8 -0
- package/forgehive/agents/vera.yaml +24 -0
- package/forgehive/agents/viktor.yaml +8 -0
- package/forgehive/commands/fh-hotfix.md +16 -0
- package/forgehive/commands/fh-review.md +16 -0
- package/forgehive/commands/fh-ship.md +18 -0
- package/forgehive/commands/fh-start-task.md +13 -0
- package/forgehive/hooks/guardrails.sh +17 -0
- package/forgehive/party/defaults.yaml +21 -0
- package/forgehive/skills/INDEX.yaml +64 -0
- package/forgehive/skills/expert/.gitkeep +0 -0
- package/forgehive/skills/expert/api-design.md +77 -0
- package/forgehive/skills/expert/auth-security.md +80 -0
- package/forgehive/skills/expert/clean-architecture.md +83 -0
- package/forgehive/skills/expert/code-review.md +81 -0
- package/forgehive/skills/expert/database-patterns.md +81 -0
- package/forgehive/skills/expert/error-handling.md +90 -0
- package/forgehive/skills/expert/gdpr-compliance.md +64 -0
- package/forgehive/skills/expert/git-conventions.md +84 -0
- package/forgehive/skills/expert/monorepo-patterns.md +91 -0
- package/forgehive/skills/expert/observability.md +98 -0
- package/forgehive/skills/expert/owasp-top10.md +153 -0
- package/forgehive/skills/expert/performance-patterns.md +79 -0
- package/forgehive/skills/expert/security-checklist.md +70 -0
- package/forgehive/skills/expert/testing-strategies.md +74 -0
- package/forgehive/skills/expert/typescript-patterns.md +62 -0
- package/forgehive/skills/templates/.gitkeep +0 -0
- package/forgehive/templates/claude-md.block.md +62 -0
- package/package.json +30 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
You are running the ForgeHive ship workflow. Follow all steps before declaring ready to merge.
|
|
2
|
+
|
|
3
|
+
## Pre-Ship Checklist
|
|
4
|
+
|
|
5
|
+
1. **Tests** — run the test suite from `package.json` scripts
|
|
6
|
+
- If tests fail: stop here, fix before proceeding
|
|
7
|
+
2. **Diff summary** — `git diff main...HEAD --stat`
|
|
8
|
+
- Show the user what changed
|
|
9
|
+
3. **Uncommitted work** — `git status`
|
|
10
|
+
- Commit or stash before proceeding
|
|
11
|
+
4. **Code quality scan**
|
|
12
|
+
- Any `console.log` / `debugger` / TODO in the diff?
|
|
13
|
+
- Any commented-out code?
|
|
14
|
+
- Any hardcoded secrets or API keys?
|
|
15
|
+
5. **PR draft**
|
|
16
|
+
- Title: under 70 chars, describes the change (not "fix stuff")
|
|
17
|
+
- Body: 2-3 bullets of what changed and why
|
|
18
|
+
6. **Ask**: "Soll ich den PR erstellen?" — only create if confirmed
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
You are starting a new development task using the ForgeHive workflow.
|
|
2
|
+
|
|
3
|
+
## Pre-Task Checklist
|
|
4
|
+
|
|
5
|
+
1. Ask the user: **"Was baust du heute?"** (or "What are you building today?")
|
|
6
|
+
2. Read `.forgehive/capabilities.yaml` — understand the available stack
|
|
7
|
+
3. Check `.forgehive/memory/MEMORY.md` — load relevant project context
|
|
8
|
+
4. Run `fh scan --check` to verify the codebase snapshot is current
|
|
9
|
+
5. Create a feature branch:
|
|
10
|
+
- Convention from `git-conventions` skill: `feat/<short-description>`, `fix/<issue>`, `chore/<task>`
|
|
11
|
+
- Run: `git checkout -b <branch-name>`
|
|
12
|
+
6. Summarize: what you're building, which files are likely to change, which capabilities apply
|
|
13
|
+
7. Check `.forgehive/skills/INDEX.yaml` for relevant expert skills
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ForgeHive Guardrails — PreToolUse hook for Bash
|
|
3
|
+
# Receives tool input as JSON on stdin. Exit 1 to block, exit 0 to allow.
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
6
|
+
COMMAND=$(node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).command||'')}catch{process.stdout.write('')}})" 2>/dev/null <<< "$INPUT" || echo "")
|
|
7
|
+
|
|
8
|
+
DANGEROUS=("git push --force" "git push -f " "git reset --hard" "rm -rf /" "DROP TABLE" "DROP DATABASE")
|
|
9
|
+
|
|
10
|
+
for PATTERN in "${DANGEROUS[@]}"; do
|
|
11
|
+
if echo "$COMMAND" | grep -qi "$PATTERN"; then
|
|
12
|
+
echo "🛡 ForgeHive Guardrail: '$PATTERN' geblockt. Erkläre die Absicht und versuche erneut."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
done
|
|
16
|
+
|
|
17
|
+
exit 0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
sets:
|
|
2
|
+
design:
|
|
3
|
+
agents: [suki, viktor]
|
|
4
|
+
trigger: "/design-party"
|
|
5
|
+
description: "UX + Architektur parallel"
|
|
6
|
+
build:
|
|
7
|
+
agents: [viktor, kai, sam]
|
|
8
|
+
trigger: "/party"
|
|
9
|
+
description: "Architektur + Engineering + QA"
|
|
10
|
+
review:
|
|
11
|
+
agents: [kai, sam, eli]
|
|
12
|
+
trigger: "/review-party"
|
|
13
|
+
description: "Code Review + QA + Doku"
|
|
14
|
+
full:
|
|
15
|
+
agents: [nora, eli, remy, suki, viktor, kai, sam]
|
|
16
|
+
trigger: "/full-party"
|
|
17
|
+
description: "Alle Agenten"
|
|
18
|
+
security:
|
|
19
|
+
agents: [vera, sam]
|
|
20
|
+
trigger: "/security-party"
|
|
21
|
+
description: "Security Review + QA"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ForgeHive Skills Index
|
|
2
|
+
# Claude: load only the skills relevant to your current task.
|
|
3
|
+
# Check 'when' field to decide which skills to load.
|
|
4
|
+
|
|
5
|
+
skills:
|
|
6
|
+
typescript-patterns:
|
|
7
|
+
file: expert/typescript-patterns.md
|
|
8
|
+
tags: [typescript, types, generics, decorators, utility-types]
|
|
9
|
+
when: "TypeScript types, generics, utility types, decorators"
|
|
10
|
+
|
|
11
|
+
testing-strategies:
|
|
12
|
+
file: expert/testing-strategies.md
|
|
13
|
+
tags: [testing, tdd, mocking, assertions, coverage, vitest, jest]
|
|
14
|
+
when: "Writing tests, TDD, mocking dependencies, test design"
|
|
15
|
+
|
|
16
|
+
api-design:
|
|
17
|
+
file: expert/api-design.md
|
|
18
|
+
tags: [api, rest, graphql, openapi, versioning, endpoints]
|
|
19
|
+
when: "Designing API endpoints, REST conventions, OpenAPI specs"
|
|
20
|
+
|
|
21
|
+
git-conventions:
|
|
22
|
+
file: expert/git-conventions.md
|
|
23
|
+
tags: [git, commits, branches, pr, conventional-commits, merge]
|
|
24
|
+
when: "Git workflow, commit messages, branch naming, PR process"
|
|
25
|
+
|
|
26
|
+
error-handling:
|
|
27
|
+
file: expert/error-handling.md
|
|
28
|
+
tags: [errors, exceptions, result-types, retry, fallbacks, typescript]
|
|
29
|
+
when: "Error handling, exceptions, Result/Either types, retry logic"
|
|
30
|
+
|
|
31
|
+
security-checklist:
|
|
32
|
+
file: expert/security-checklist.md
|
|
33
|
+
tags: [security, auth, xss, sql-injection, owasp, jwt, csrf]
|
|
34
|
+
when: "Security review, authentication, authorization, input validation"
|
|
35
|
+
|
|
36
|
+
performance-patterns:
|
|
37
|
+
file: expert/performance-patterns.md
|
|
38
|
+
tags: [performance, caching, profiling, optimization, lazy-loading, memoization]
|
|
39
|
+
when: "Performance optimization, caching strategies, profiling"
|
|
40
|
+
|
|
41
|
+
code-review:
|
|
42
|
+
file: expert/code-review.md
|
|
43
|
+
tags: [review, quality, feedback, pr-review, checklist]
|
|
44
|
+
when: "Code review, PR feedback, quality assessment"
|
|
45
|
+
|
|
46
|
+
clean-architecture:
|
|
47
|
+
file: expert/clean-architecture.md
|
|
48
|
+
tags: [architecture, domain, use-cases, clean-arch, ddd, layers]
|
|
49
|
+
when: "Architecture design, layer separation, domain modeling, DDD"
|
|
50
|
+
|
|
51
|
+
database-patterns:
|
|
52
|
+
file: expert/database-patterns.md
|
|
53
|
+
tags: [database, sql, migrations, indexing, orm, postgres, prisma]
|
|
54
|
+
when: "Database design, migrations, query optimization, indexing"
|
|
55
|
+
|
|
56
|
+
monorepo-patterns:
|
|
57
|
+
file: expert/monorepo-patterns.md
|
|
58
|
+
tags: [monorepo, workspaces, turborepo, nx, packages, pnpm]
|
|
59
|
+
when: "Monorepo setup, workspace management, build orchestration"
|
|
60
|
+
|
|
61
|
+
observability:
|
|
62
|
+
file: expert/observability.md
|
|
63
|
+
tags: [logging, metrics, tracing, opentelemetry, monitoring, datadog, sentry]
|
|
64
|
+
when: "Structured logging, metrics (RED/USE), distributed tracing"
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# API Design
|
|
2
|
+
|
|
3
|
+
## REST Resource Design
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
GET /users → list users (paginated)
|
|
7
|
+
POST /users → create user
|
|
8
|
+
GET /users/:id → get user
|
|
9
|
+
PATCH /users/:id → partial update
|
|
10
|
+
DELETE /users/:id → delete
|
|
11
|
+
|
|
12
|
+
POST /users/:id/activate → actions as sub-resources
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Naming:** plural nouns for collections, snake_case in JSON, kebab-case in URLs.
|
|
16
|
+
|
|
17
|
+
## Request / Response Shape
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Response envelope
|
|
21
|
+
interface ApiResponse<T> {
|
|
22
|
+
data: T;
|
|
23
|
+
meta?: { total: number; page: number; limit: number };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Error envelope — always consistent
|
|
27
|
+
interface ApiError {
|
|
28
|
+
error: { code: string; message: string; field?: string };
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Status Codes
|
|
33
|
+
|
|
34
|
+
| Code | When |
|
|
35
|
+
|---|---|
|
|
36
|
+
| 200 | Successful GET, PATCH |
|
|
37
|
+
| 201 | Successful POST (resource created) |
|
|
38
|
+
| 204 | Successful DELETE (no body) |
|
|
39
|
+
| 400 | Validation error (include field) |
|
|
40
|
+
| 401 | Not authenticated |
|
|
41
|
+
| 403 | Authenticated but not authorized |
|
|
42
|
+
| 404 | Resource not found |
|
|
43
|
+
| 409 | Conflict (duplicate, stale version) |
|
|
44
|
+
| 422 | Semantically invalid (passes schema, fails business rules) |
|
|
45
|
+
| 429 | Rate limited |
|
|
46
|
+
| 500 | Server error (never leak internals) |
|
|
47
|
+
|
|
48
|
+
## Versioning
|
|
49
|
+
|
|
50
|
+
Prefix: `/v1/users` — never break existing clients, deprecate with sunset headers.
|
|
51
|
+
|
|
52
|
+
## Pagination
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
GET /users?page=2&limit=20
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
"data": [...],
|
|
59
|
+
"meta": { "total": 143, "page": 2, "limit": 20, "hasNext": true }
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Validation
|
|
64
|
+
|
|
65
|
+
- Validate at the boundary (route handler) — not deep in business logic
|
|
66
|
+
- Return all validation errors at once, not just the first
|
|
67
|
+
- Use JSON Schema or Zod for input validation
|
|
68
|
+
|
|
69
|
+
## Anti-Patterns
|
|
70
|
+
|
|
71
|
+
| Avoid | Use instead |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Verbs in URLs (`/getUser`) | Nouns + HTTP method |
|
|
74
|
+
| `200` for errors | Correct 4xx/5xx |
|
|
75
|
+
| Leaking stack traces | Structured error codes |
|
|
76
|
+
| Inconsistent field naming | Single convention (snake_case) |
|
|
77
|
+
| Boolean flags in body | Semantic sub-resources or PATCH |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Authentication & Session Security
|
|
2
|
+
|
|
3
|
+
## Secure Password Handling
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import bcrypt from "bcrypt";
|
|
7
|
+
|
|
8
|
+
const SALT_ROUNDS = 12;
|
|
9
|
+
|
|
10
|
+
export async function hashPassword(plain: string): Promise<string> {
|
|
11
|
+
return bcrypt.hash(plain, SALT_ROUNDS);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function verifyPassword(plain: string, hash: string): Promise<boolean> {
|
|
15
|
+
return bcrypt.compare(plain, hash);
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## JWT Best Practices
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import jwt from "jsonwebtoken";
|
|
23
|
+
|
|
24
|
+
const JWT_SECRET = process.env.JWT_SECRET;
|
|
25
|
+
if (!JWT_SECRET || JWT_SECRET.length < 32) {
|
|
26
|
+
throw new Error("JWT_SECRET must be at least 32 characters");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function signToken(userId: string): string {
|
|
30
|
+
return jwt.sign({ userId }, JWT_SECRET!, {
|
|
31
|
+
expiresIn: "15m",
|
|
32
|
+
issuer: "myapp",
|
|
33
|
+
audience: "myapp-client",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function verifyToken(token: string): { userId: string } {
|
|
38
|
+
return jwt.verify(token, JWT_SECRET!, {
|
|
39
|
+
issuer: "myapp",
|
|
40
|
+
audience: "myapp-client",
|
|
41
|
+
}) as { userId: string };
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Session Security
|
|
46
|
+
|
|
47
|
+
- Use `httpOnly` and `secure` cookie flags
|
|
48
|
+
- Set `SameSite=Strict` or `SameSite=Lax`
|
|
49
|
+
- Rotate session tokens after privilege escalation
|
|
50
|
+
- Implement absolute session timeout (e.g., 8 hours)
|
|
51
|
+
- Implement idle timeout (e.g., 30 minutes)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
app.use(session({
|
|
55
|
+
secret: process.env.SESSION_SECRET!,
|
|
56
|
+
resave: false,
|
|
57
|
+
saveUninitialized: false,
|
|
58
|
+
cookie: {
|
|
59
|
+
httpOnly: true,
|
|
60
|
+
secure: process.env.NODE_ENV === "production",
|
|
61
|
+
sameSite: "strict",
|
|
62
|
+
maxAge: 8 * 60 * 60 * 1000, // 8 hours
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Multi-Factor Authentication Checklist
|
|
68
|
+
|
|
69
|
+
- [ ] TOTP (Time-based One-Time Password) support
|
|
70
|
+
- [ ] Recovery codes generated and stored (hashed)
|
|
71
|
+
- [ ] Rate limit MFA attempts
|
|
72
|
+
- [ ] MFA bypass audit logging
|
|
73
|
+
|
|
74
|
+
## Security Checklist
|
|
75
|
+
|
|
76
|
+
- [ ] Passwords hashed with bcrypt/argon2 (not MD5/SHA1)
|
|
77
|
+
- [ ] JWT uses short expiry + refresh token pattern
|
|
78
|
+
- [ ] Session tokens regenerated on login
|
|
79
|
+
- [ ] Brute force protection (rate limiting + account lockout)
|
|
80
|
+
- [ ] Secure password reset flow (time-limited tokens)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Clean Architecture
|
|
2
|
+
|
|
3
|
+
## Core Principle: Dependency Direction
|
|
4
|
+
|
|
5
|
+
Dependencies point inward. Business logic doesn't know about databases, HTTP, or frameworks.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
[HTTP / CLI] → [Use Cases] → [Domain]
|
|
9
|
+
[DB / APIs] → [Use Cases]
|
|
10
|
+
↑
|
|
11
|
+
Interfaces defined here,
|
|
12
|
+
implemented at outer layer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Layer Responsibilities
|
|
16
|
+
|
|
17
|
+
**Domain** — pure business logic, no I/O:
|
|
18
|
+
```typescript
|
|
19
|
+
class Order {
|
|
20
|
+
addItem(product: Product, qty: number): void {
|
|
21
|
+
if (qty <= 0) throw new InvalidQuantityError(qty);
|
|
22
|
+
this.items.push({ product, qty });
|
|
23
|
+
}
|
|
24
|
+
get total(): Money { return this.items.reduce(/* ... */); }
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Use Cases** — orchestrate domain + I/O via interfaces:
|
|
29
|
+
```typescript
|
|
30
|
+
class PlaceOrderUseCase {
|
|
31
|
+
constructor(
|
|
32
|
+
private readonly orders: OrderRepository, // interface
|
|
33
|
+
private readonly payments: PaymentGateway, // interface
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
async execute(cmd: PlaceOrderCommand): Promise<OrderId> {
|
|
37
|
+
const order = Order.create(cmd);
|
|
38
|
+
const payment = await this.payments.charge(order.total);
|
|
39
|
+
order.markPaid(payment.id);
|
|
40
|
+
return this.orders.save(order);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Infrastructure** — concrete implementations (DB, HTTP, external APIs).
|
|
46
|
+
|
|
47
|
+
## File Structure
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
domain/ ← no external imports
|
|
52
|
+
order.ts
|
|
53
|
+
user.ts
|
|
54
|
+
use-cases/ ← imports domain only
|
|
55
|
+
place-order.ts
|
|
56
|
+
infrastructure/ ← implements use-case interfaces
|
|
57
|
+
postgres-orders.ts
|
|
58
|
+
stripe-payments.ts
|
|
59
|
+
http/ ← thin: parse, call use case, respond
|
|
60
|
+
orders-controller.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Boundaries
|
|
64
|
+
|
|
65
|
+
Each boundary should answer:
|
|
66
|
+
- **What does it do?** (single responsibility)
|
|
67
|
+
- **How do you call it?** (clear interface)
|
|
68
|
+
- **What does it depend on?** (explicit, inward)
|
|
69
|
+
|
|
70
|
+
## When to Separate
|
|
71
|
+
|
|
72
|
+
Three modules that **change together** should probably be one module.
|
|
73
|
+
One module that **changes for different reasons** should be split.
|
|
74
|
+
|
|
75
|
+
## Anti-Patterns
|
|
76
|
+
|
|
77
|
+
| Avoid | Why |
|
|
78
|
+
|---|---|
|
|
79
|
+
| Business logic in controllers | Hard to test, hard to reuse |
|
|
80
|
+
| Direct DB calls from domain | Domain coupled to infrastructure |
|
|
81
|
+
| God service with 30 methods | No clear responsibility |
|
|
82
|
+
| Anemic domain (only getters/setters) | Logic lives scattered in services |
|
|
83
|
+
| Circular dependencies | Signals wrong boundary |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Code Review
|
|
2
|
+
|
|
3
|
+
## Reviewer Mindset
|
|
4
|
+
|
|
5
|
+
Your job: find issues the author missed, not rewrite their code. Be direct about problems, respectful about everything else.
|
|
6
|
+
|
|
7
|
+
## What to Check (Priority Order)
|
|
8
|
+
|
|
9
|
+
### 1. Correctness
|
|
10
|
+
- Does it do what it claims?
|
|
11
|
+
- Are edge cases handled (null, empty, concurrent)?
|
|
12
|
+
- Are errors handled correctly?
|
|
13
|
+
- Will this break in production?
|
|
14
|
+
|
|
15
|
+
### 2. Tests
|
|
16
|
+
- Do tests cover the behavior, not just the happy path?
|
|
17
|
+
- Would these tests catch a regression?
|
|
18
|
+
- Are new behaviors tested?
|
|
19
|
+
|
|
20
|
+
### 3. Design
|
|
21
|
+
- Is the change too large? Should it be split?
|
|
22
|
+
- Does it follow existing patterns?
|
|
23
|
+
- Are there simpler approaches?
|
|
24
|
+
- Are new abstractions justified?
|
|
25
|
+
|
|
26
|
+
### 4. Security
|
|
27
|
+
- User input validated?
|
|
28
|
+
- Secrets/PII handled correctly?
|
|
29
|
+
- Authorization checked?
|
|
30
|
+
|
|
31
|
+
### 5. Performance
|
|
32
|
+
- N+1 queries?
|
|
33
|
+
- Unbounded operations on large data?
|
|
34
|
+
- Unnecessary blocking calls?
|
|
35
|
+
|
|
36
|
+
## Comment Format
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
// Blocking — must fix before merge
|
|
40
|
+
[BUG] This will NPE when user has no address — add null check at line 42
|
|
41
|
+
|
|
42
|
+
// Important — should fix
|
|
43
|
+
[DESIGN] This 200-line function is hard to test — consider extracting validateAddress()
|
|
44
|
+
|
|
45
|
+
// Minor — optional improvement
|
|
46
|
+
[NIT] Variable name `d` unclear — consider `durationMs`
|
|
47
|
+
|
|
48
|
+
// Question — not blocking
|
|
49
|
+
[Q] Why stringify here instead of using the json field directly?
|
|
50
|
+
|
|
51
|
+
// Positive — acknowledge good work
|
|
52
|
+
[+] Nice — using Result type here instead of throwing makes the caller explicit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Self-Review Before Submitting
|
|
56
|
+
|
|
57
|
+
Run through this before opening a PR:
|
|
58
|
+
- [ ] Read your own diff top to bottom
|
|
59
|
+
- [ ] Does the PR description explain WHY, not just what?
|
|
60
|
+
- [ ] Are there debug logs, TODOs, commented-out code to clean up?
|
|
61
|
+
- [ ] Is the PR size reviewable? (< 400 lines is the sweet spot)
|
|
62
|
+
- [ ] Do all tests pass locally?
|
|
63
|
+
|
|
64
|
+
## PR Size Guide
|
|
65
|
+
|
|
66
|
+
| Lines | Status |
|
|
67
|
+
|---|---|
|
|
68
|
+
| < 100 | Ideal |
|
|
69
|
+
| 100–400 | Acceptable |
|
|
70
|
+
| 400–800 | Ask to split |
|
|
71
|
+
| > 800 | Split — no exceptions |
|
|
72
|
+
|
|
73
|
+
## Anti-Patterns
|
|
74
|
+
|
|
75
|
+
| Avoid | Why |
|
|
76
|
+
|---|---|
|
|
77
|
+
| "LGTM" without reading | Wastes the review |
|
|
78
|
+
| Bikeshedding on formatting | Use a linter |
|
|
79
|
+
| Rewriting working code | Not your style, their code |
|
|
80
|
+
| Vague comments ("fix this") | Be specific |
|
|
81
|
+
| Approving to avoid conflict | Technical debt |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Database Patterns
|
|
2
|
+
|
|
3
|
+
## Schema Design
|
|
4
|
+
|
|
5
|
+
**Naming conventions:**
|
|
6
|
+
- Tables: `snake_case`, plural (`users`, `order_items`)
|
|
7
|
+
- Columns: `snake_case` (`created_at`, `user_id`)
|
|
8
|
+
- Primary keys: `id` (UUID or serial)
|
|
9
|
+
- Foreign keys: `<table_singular>_id` (`user_id`, `order_id`)
|
|
10
|
+
- Booleans: `is_` or `has_` prefix (`is_active`, `has_verified_email`)
|
|
11
|
+
|
|
12
|
+
**Always include:**
|
|
13
|
+
```sql
|
|
14
|
+
CREATE TABLE users (
|
|
15
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
16
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
17
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Migrations
|
|
22
|
+
|
|
23
|
+
- One logical change per migration file
|
|
24
|
+
- Migrations are **append-only** — never edit a committed migration
|
|
25
|
+
- Always test `UP` and `DOWN` before merging
|
|
26
|
+
- Non-destructive first: add nullable column → backfill → add NOT NULL constraint
|
|
27
|
+
|
|
28
|
+
```sql
|
|
29
|
+
-- ✅ Safe migration for adding NOT NULL column
|
|
30
|
+
ALTER TABLE users ADD COLUMN locale TEXT; -- 1. add nullable
|
|
31
|
+
UPDATE users SET locale = 'en' WHERE locale IS NULL; -- 2. backfill
|
|
32
|
+
ALTER TABLE users ALTER COLUMN locale SET NOT NULL; -- 3. constrain
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Indexing Strategy
|
|
36
|
+
|
|
37
|
+
```sql
|
|
38
|
+
-- Foreign keys (always index)
|
|
39
|
+
CREATE INDEX idx_orders_user_id ON orders(user_id);
|
|
40
|
+
|
|
41
|
+
-- Common query patterns
|
|
42
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
43
|
+
|
|
44
|
+
-- Composite: put most selective column first
|
|
45
|
+
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
|
|
46
|
+
|
|
47
|
+
-- Partial index for common filtered queries
|
|
48
|
+
CREATE INDEX idx_active_users ON users(email) WHERE is_active = true;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Query Patterns
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Batch load instead of N+1
|
|
55
|
+
const userIds = posts.map(p => p.authorId);
|
|
56
|
+
const users = await db.users.findMany({ where: { id: { in: userIds } } });
|
|
57
|
+
|
|
58
|
+
// Cursor pagination (stable, works with inserts)
|
|
59
|
+
const posts = await db.posts.findMany({
|
|
60
|
+
where: { id: { lt: cursor } },
|
|
61
|
+
orderBy: { id: "desc" },
|
|
62
|
+
take: 20,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Use transactions for multi-step mutations
|
|
66
|
+
await db.$transaction(async (tx) => {
|
|
67
|
+
await tx.orders.create({ data: order });
|
|
68
|
+
await tx.inventory.decrement({ where: { id: item.id }, by: qty });
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Anti-Patterns
|
|
73
|
+
|
|
74
|
+
| Avoid | Why |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `SELECT *` | Pulls unused columns, breaks on schema change |
|
|
77
|
+
| No migrations | Schema drift between environments |
|
|
78
|
+
| Storing arrays as comma-separated strings | Use proper array column or join table |
|
|
79
|
+
| Soft-delete without index | Full table scan on active queries |
|
|
80
|
+
| Logic in triggers | Invisible side effects |
|
|
81
|
+
| ORM for complex reports | Write SQL — it's fine |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
## Core Principle: Errors Are Values
|
|
4
|
+
|
|
5
|
+
Handle errors explicitly at call sites. Don't let them propagate silently or get swallowed.
|
|
6
|
+
|
|
7
|
+
## Error Types
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Domain errors — expected, recoverable
|
|
11
|
+
class UserNotFoundError extends Error {
|
|
12
|
+
readonly code = "USER_NOT_FOUND";
|
|
13
|
+
constructor(readonly userId: string) {
|
|
14
|
+
super(`User ${userId} not found`);
|
|
15
|
+
this.name = "UserNotFoundError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Infrastructure errors — unexpected, potentially fatal
|
|
20
|
+
// → Let them propagate up to the boundary handler
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Result Pattern (for fallible operations)
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
type Result<T, E extends Error = Error> =
|
|
27
|
+
| { ok: true; value: T }
|
|
28
|
+
| { ok: false; error: E };
|
|
29
|
+
|
|
30
|
+
function parseConfig(raw: unknown): Result<Config, ConfigError> {
|
|
31
|
+
if (!isValidConfig(raw)) return { ok: false, error: new ConfigError("invalid") };
|
|
32
|
+
return { ok: true, value: raw as Config };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Usage — explicit at call site
|
|
36
|
+
const result = parseConfig(input);
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
log.error("bad config", result.error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
use(result.value);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Boundary Handlers
|
|
45
|
+
|
|
46
|
+
Catch at the outermost layer only:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// HTTP handler
|
|
50
|
+
app.use((err, req, res, next) => {
|
|
51
|
+
if (err instanceof DomainError) {
|
|
52
|
+
res.status(err.httpStatus).json({ error: { code: err.code, message: err.message } });
|
|
53
|
+
} else {
|
|
54
|
+
log.error("unexpected", err);
|
|
55
|
+
res.status(500).json({ error: { code: "INTERNAL", message: "Internal error" } });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Async Error Handling
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Always handle rejected promises
|
|
64
|
+
const data = await fetchUser(id).catch(err => {
|
|
65
|
+
if (err instanceof NotFoundError) return null;
|
|
66
|
+
throw err; // re-throw unexpected errors
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Top-level: never ignore unhandled rejections
|
|
70
|
+
process.on("unhandledRejection", (err) => {
|
|
71
|
+
log.error("unhandled rejection", err);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Logging
|
|
77
|
+
|
|
78
|
+
- Log at the boundary, not at every layer (avoid duplicate logs)
|
|
79
|
+
- Include context: `log.error("fetch failed", { userId, err })`
|
|
80
|
+
- Never log secrets or PII
|
|
81
|
+
|
|
82
|
+
## Anti-Patterns
|
|
83
|
+
|
|
84
|
+
| Avoid | Use instead |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Empty `catch {}` | At minimum log the error |
|
|
87
|
+
| `catch (e: any)` | `catch (e: unknown)` then narrow |
|
|
88
|
+
| Throwing strings | Throw Error instances |
|
|
89
|
+
| Catching everything | Only catch what you can handle |
|
|
90
|
+
| Error codes as numbers | String codes (searchable) |
|