@dv.nghiem/flowdeck 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 +136 -0
- package/bin/flowdeck.js +108 -0
- package/dist/agents/architect.d.ts +3 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/code-explorer.d.ts +3 -0
- package/dist/agents/code-explorer.d.ts.map +1 -0
- package/dist/agents/coder.d.ts +3 -0
- package/dist/agents/coder.d.ts.map +1 -0
- package/dist/agents/debug.d.ts +4 -0
- package/dist/agents/debug.d.ts.map +1 -0
- package/dist/agents/doc-updater.d.ts +3 -0
- package/dist/agents/doc-updater.d.ts.map +1 -0
- package/dist/agents/flowdeck.d.ts +5 -0
- package/dist/agents/flowdeck.d.ts.map +1 -0
- package/dist/agents/index.d.ts +38 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/mapper.d.ts +3 -0
- package/dist/agents/mapper.d.ts.map +1 -0
- package/dist/agents/orchestrator.d.ts +10 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/performance.d.ts +4 -0
- package/dist/agents/performance.d.ts.map +1 -0
- package/dist/agents/planner.d.ts +3 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/policy-enforcer.d.ts +3 -0
- package/dist/agents/policy-enforcer.d.ts.map +1 -0
- package/dist/agents/researcher.d.ts +3 -0
- package/dist/agents/researcher.d.ts.map +1 -0
- package/dist/agents/reviewer.d.ts +3 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/risk-analyst.d.ts +3 -0
- package/dist/agents/risk-analyst.d.ts.map +1 -0
- package/dist/agents/security-auditor.d.ts +3 -0
- package/dist/agents/security-auditor.d.ts.map +1 -0
- package/dist/agents/specialist.d.ts +5 -0
- package/dist/agents/specialist.d.ts.map +1 -0
- package/dist/agents/tester.d.ts +3 -0
- package/dist/agents/tester.d.ts.map +1 -0
- package/dist/agents/types.d.ts +20 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/writer.d.ts +3 -0
- package/dist/agents/writer.d.ts.map +1 -0
- package/dist/commands/analysis/analysis.test.d.ts +2 -0
- package/dist/commands/analysis/analysis.test.d.ts.map +1 -0
- package/dist/commands/analysis/analyze-change.d.ts +148 -0
- package/dist/commands/analysis/analyze-change.d.ts.map +1 -0
- package/dist/commands/analysis/evaluate-risk.d.ts +77 -0
- package/dist/commands/analysis/evaluate-risk.d.ts.map +1 -0
- package/dist/commands/analysis/guarded-edit.d.ts +72 -0
- package/dist/commands/analysis/guarded-edit.d.ts.map +1 -0
- package/dist/commands/execution/deploy-check.d.ts +91 -0
- package/dist/commands/execution/deploy-check.d.ts.map +1 -0
- package/dist/commands/execution/fix-bug.d.ts +187 -0
- package/dist/commands/execution/fix-bug.d.ts.map +1 -0
- package/dist/commands/execution/new-feature.d.ts +171 -0
- package/dist/commands/execution/new-feature.d.ts.map +1 -0
- package/dist/commands/execution/review-code.d.ts +130 -0
- package/dist/commands/execution/review-code.d.ts.map +1 -0
- package/dist/commands/execution/write-docs.d.ts +94 -0
- package/dist/commands/execution/write-docs.d.ts.map +1 -0
- package/dist/commands/governance/approve.d.ts +80 -0
- package/dist/commands/governance/approve.d.ts.map +1 -0
- package/dist/commands/intelligence/blast-radius.d.ts +67 -0
- package/dist/commands/intelligence/blast-radius.d.ts.map +1 -0
- package/dist/commands/intelligence/impact-radar.d.ts +71 -0
- package/dist/commands/intelligence/impact-radar.d.ts.map +1 -0
- package/dist/commands/intelligence/intelligence.test.d.ts +2 -0
- package/dist/commands/intelligence/intelligence.test.d.ts.map +1 -0
- package/dist/commands/intelligence/regression-predict.d.ts +75 -0
- package/dist/commands/intelligence/regression-predict.d.ts.map +1 -0
- package/dist/commands/intelligence/review-route.d.ts +65 -0
- package/dist/commands/intelligence/review-route.d.ts.map +1 -0
- package/dist/commands/intelligence/test-gap.d.ts +73 -0
- package/dist/commands/intelligence/test-gap.d.ts.map +1 -0
- package/dist/commands/intelligence/translate-intent.d.ts +87 -0
- package/dist/commands/intelligence/translate-intent.d.ts.map +1 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts +68 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts.map +1 -0
- package/dist/commands/planning/ask.d.ts +62 -0
- package/dist/commands/planning/ask.d.ts.map +1 -0
- package/dist/commands/planning/ask.test.d.ts +2 -0
- package/dist/commands/planning/ask.test.d.ts.map +1 -0
- package/dist/commands/planning/dashboard.d.ts +30 -0
- package/dist/commands/planning/dashboard.d.ts.map +1 -0
- package/dist/commands/planning/discuss.d.ts +39 -0
- package/dist/commands/planning/discuss.d.ts.map +1 -0
- package/dist/commands/planning/plan.d.ts +67 -0
- package/dist/commands/planning/plan.d.ts.map +1 -0
- package/dist/commands/planning/roadmap.d.ts +105 -0
- package/dist/commands/planning/roadmap.d.ts.map +1 -0
- package/dist/commands/setup/doctor.d.ts +10 -0
- package/dist/commands/setup/doctor.d.ts.map +1 -0
- package/dist/commands/setup/map-codebase.d.ts +62 -0
- package/dist/commands/setup/map-codebase.d.ts.map +1 -0
- package/dist/commands/setup/new-project.d.ts +19 -0
- package/dist/commands/setup/new-project.d.ts.map +1 -0
- package/dist/commands/setup/settings.d.ts +57 -0
- package/dist/commands/setup/settings.d.ts.map +1 -0
- package/dist/commands/state/checkpoint.d.ts +27 -0
- package/dist/commands/state/checkpoint.d.ts.map +1 -0
- package/dist/commands/state/multi-repo.d.ts +63 -0
- package/dist/commands/state/multi-repo.d.ts.map +1 -0
- package/dist/commands/state/progress.d.ts +57 -0
- package/dist/commands/state/progress.d.ts.map +1 -0
- package/dist/commands/state/resume.d.ts +11 -0
- package/dist/commands/state/resume.d.ts.map +1 -0
- package/dist/commands/state/workspace-commands.d.ts +207 -0
- package/dist/commands/state/workspace-commands.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.d.ts +10 -0
- package/dist/dashboard/lib/port-finder.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.test.d.ts +2 -0
- package/dist/dashboard/lib/port-finder.test.d.ts.map +1 -0
- package/dist/dashboard/lib/state-reader.d.ts +3 -0
- package/dist/dashboard/lib/state-reader.d.ts.map +1 -0
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.mjs +13649 -0
- package/dist/dashboard/types.d.ts +72 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- package/dist/dashboard/views/index.ejs +391 -0
- package/dist/dashboard/views/partials/blockers.ejs +10 -0
- package/dist/dashboard/views/partials/header.ejs +20 -0
- package/dist/dashboard/views/partials/phase-timeline.ejs +40 -0
- package/dist/dashboard/views/partials/progress.ejs +12 -0
- package/dist/hooks/approval-hook.d.ts +13 -0
- package/dist/hooks/approval-hook.d.ts.map +1 -0
- package/dist/hooks/compaction-hook.d.ts +23 -0
- package/dist/hooks/compaction-hook.d.ts.map +1 -0
- package/dist/hooks/context-window-monitor.d.ts +21 -0
- package/dist/hooks/context-window-monitor.d.ts.map +1 -0
- package/dist/hooks/decision-trace-hook.d.ts +13 -0
- package/dist/hooks/decision-trace-hook.d.ts.map +1 -0
- package/dist/hooks/file-tracker.d.ts +29 -0
- package/dist/hooks/file-tracker.d.ts.map +1 -0
- package/dist/hooks/guard-rails.d.ts +33 -0
- package/dist/hooks/guard-rails.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/notifications.d.ts +21 -0
- package/dist/hooks/notifications.d.ts.map +1 -0
- package/dist/hooks/patch-trust.d.ts +27 -0
- package/dist/hooks/patch-trust.d.ts.map +1 -0
- package/dist/hooks/patch-trust.test.d.ts +2 -0
- package/dist/hooks/patch-trust.test.d.ts.map +1 -0
- package/dist/hooks/session-events.d.ts +8 -0
- package/dist/hooks/session-events.d.ts.map +1 -0
- package/dist/hooks/session-idle-hook.d.ts +21 -0
- package/dist/hooks/session-idle-hook.d.ts.map +1 -0
- package/dist/hooks/session-start.d.ts +10 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/shell-env-hook.d.ts +21 -0
- package/dist/hooks/shell-env-hook.d.ts.map +1 -0
- package/dist/hooks/telemetry-hook.d.ts +25 -0
- package/dist/hooks/telemetry-hook.d.ts.map +1 -0
- package/dist/hooks/todo-hook.d.ts +25 -0
- package/dist/hooks/todo-hook.d.ts.map +1 -0
- package/dist/hooks/tool-guard.d.ts +41 -0
- package/dist/hooks/tool-guard.d.ts.map +1 -0
- package/dist/hooks/tool-guard.test.d.ts +2 -0
- package/dist/hooks/tool-guard.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6639 -0
- package/dist/lib/confirmation.d.ts +20 -0
- package/dist/lib/confirmation.d.ts.map +1 -0
- package/dist/lib/impact-radar.d.ts +35 -0
- package/dist/lib/impact-radar.d.ts.map +1 -0
- package/dist/lib/signatures.d.ts +12 -0
- package/dist/lib/signatures.d.ts.map +1 -0
- package/dist/lib/timestamps.d.ts +23 -0
- package/dist/lib/timestamps.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +20 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/services/agent-performance.d.ts +29 -0
- package/dist/services/agent-performance.d.ts.map +1 -0
- package/dist/services/approval-manager.d.ts +30 -0
- package/dist/services/approval-manager.d.ts.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/model-router.d.ts +35 -0
- package/dist/services/model-router.d.ts.map +1 -0
- package/dist/services/policy-compiler.d.ts +27 -0
- package/dist/services/policy-compiler.d.ts.map +1 -0
- package/dist/services/run-trace.d.ts +34 -0
- package/dist/services/run-trace.d.ts.map +1 -0
- package/dist/services/services.test.d.ts +2 -0
- package/dist/services/services.test.d.ts.map +1 -0
- package/dist/services/telemetry.d.ts +34 -0
- package/dist/services/telemetry.d.ts.map +1 -0
- package/dist/tools/agent-dispatch.test.d.ts +2 -0
- package/dist/tools/agent-dispatch.test.d.ts.map +1 -0
- package/dist/tools/codebase-state.d.ts +4 -0
- package/dist/tools/codebase-state.d.ts.map +1 -0
- package/dist/tools/context-generator.d.ts +3 -0
- package/dist/tools/context-generator.d.ts.map +1 -0
- package/dist/tools/council.d.ts +4 -0
- package/dist/tools/council.d.ts.map +1 -0
- package/dist/tools/decision-trace.d.ts +16 -0
- package/dist/tools/decision-trace.d.ts.map +1 -0
- package/dist/tools/delegate.d.ts +4 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/failure-replay.d.ts +19 -0
- package/dist/tools/failure-replay.d.ts.map +1 -0
- package/dist/tools/failure-replay.test.d.ts +2 -0
- package/dist/tools/failure-replay.test.d.ts.map +1 -0
- package/dist/tools/hash-edit.d.ts +3 -0
- package/dist/tools/hash-edit.d.ts.map +1 -0
- package/dist/tools/planning-state-lib.d.ts +65 -0
- package/dist/tools/planning-state-lib.d.ts.map +1 -0
- package/dist/tools/planning-state.d.ts +3 -0
- package/dist/tools/planning-state.d.ts.map +1 -0
- package/dist/tools/policy-engine.d.ts +19 -0
- package/dist/tools/policy-engine.d.ts.map +1 -0
- package/dist/tools/repo-memory.d.ts +20 -0
- package/dist/tools/repo-memory.d.ts.map +1 -0
- package/dist/tools/repo-memory.test.d.ts +2 -0
- package/dist/tools/repo-memory.test.d.ts.map +1 -0
- package/dist/tools/run-parallel.d.ts +4 -0
- package/dist/tools/run-parallel.d.ts.map +1 -0
- package/dist/tools/run-pipeline.d.ts +4 -0
- package/dist/tools/run-pipeline.d.ts.map +1 -0
- package/dist/tools/volatility-map.d.ts +18 -0
- package/dist/tools/volatility-map.d.ts.map +1 -0
- package/dist/tools/volatility-map.test.d.ts +2 -0
- package/dist/tools/volatility-map.test.d.ts.map +1 -0
- package/dist/tools/workspace-state.d.ts +3 -0
- package/dist/tools/workspace-state.d.ts.map +1 -0
- package/docs/USER_GUIDE.md +20 -0
- package/docs/agents.md +562 -0
- package/docs/best-practices.md +47 -0
- package/docs/command-migration.md +175 -0
- package/docs/commands/fd-analyze-change.md +107 -0
- package/docs/commands/fd-ask.md +51 -0
- package/docs/commands/fd-checkpoint.md +10 -0
- package/docs/commands/fd-dashboard.md +11 -0
- package/docs/commands/fd-deploy-check.md +11 -0
- package/docs/commands/fd-discuss.md +28 -0
- package/docs/commands/fd-evaluate-risk.md +134 -0
- package/docs/commands/fd-fix-bug.md +24 -0
- package/docs/commands/fd-guarded-edit.md +105 -0
- package/docs/commands/fd-map-codebase.md +27 -0
- package/docs/commands/fd-multi-repo.md +63 -0
- package/docs/commands/fd-new-feature.md +25 -0
- package/docs/commands/fd-new-project.md +24 -0
- package/docs/commands/fd-plan.md +33 -0
- package/docs/commands/fd-progress.md +11 -0
- package/docs/commands/fd-resume.md +10 -0
- package/docs/commands/fd-review-code.md +29 -0
- package/docs/commands/fd-roadmap.md +10 -0
- package/docs/commands/fd-settings.md +10 -0
- package/docs/commands/fd-write-docs.md +10 -0
- package/docs/commands.md +476 -0
- package/docs/configuration.md +211 -0
- package/docs/feature-integration-architecture.md +255 -0
- package/docs/index.md +75 -0
- package/docs/installation.md +134 -0
- package/docs/intelligence.md +294 -0
- package/docs/multi-repo.md +201 -0
- package/docs/notifications.md +170 -0
- package/docs/parallel-execution.md +227 -0
- package/docs/quick-start.md +174 -0
- package/docs/rules.md +459 -0
- package/docs/skills.md +408 -0
- package/docs/workflows.md +376 -0
- package/package.json +58 -0
- package/postinstall.mjs +102 -0
- package/src/rules/README.md +37 -0
- package/src/rules/common/agent-orchestration.md +86 -0
- package/src/rules/common/coding-style.md +120 -0
- package/src/rules/common/git-workflow.md +77 -0
- package/src/rules/common/security.md +94 -0
- package/src/rules/common/testing.md +105 -0
- package/src/rules/golang/patterns.md +187 -0
- package/src/rules/java/patterns.md +204 -0
- package/src/rules/python/patterns.md +141 -0
- package/src/rules/rust/patterns.md +210 -0
- package/src/rules/typescript/patterns.md +168 -0
- package/src/skills/api-design/SKILL.md +143 -0
- package/src/skills/arch-constraint-guard/SKILL.md +61 -0
- package/src/skills/blast-radius-preview/SKILL.md +65 -0
- package/src/skills/change-impact-radar/SKILL.md +63 -0
- package/src/skills/code-review/SKILL.md +108 -0
- package/src/skills/code-tour/SKILL.md +101 -0
- package/src/skills/codebase-mapping/SKILL.md +87 -0
- package/src/skills/codebase-onboarding/SKILL.md +133 -0
- package/src/skills/confidence-aware-planning/SKILL.md +67 -0
- package/src/skills/context-load/SKILL.md +63 -0
- package/src/skills/debug-flow/SKILL.md +75 -0
- package/src/skills/decision-trace/SKILL.md +72 -0
- package/src/skills/dependency-audit/SKILL.md +126 -0
- package/src/skills/deploy-check/SKILL.md +87 -0
- package/src/skills/documentation-writer/SKILL.md +154 -0
- package/src/skills/failure-replay-engine/SKILL.md +59 -0
- package/src/skills/git-release/SKILL.md +94 -0
- package/src/skills/git-workflow/SKILL.md +177 -0
- package/src/skills/golang-patterns/SKILL.md +511 -0
- package/src/skills/human-review-routing/SKILL.md +65 -0
- package/src/skills/intent-translator/SKILL.md +57 -0
- package/src/skills/java-patterns/SKILL.md +479 -0
- package/src/skills/multi-repo/SKILL.md +187 -0
- package/src/skills/parallel-execute/SKILL.md +92 -0
- package/src/skills/patch-trust-score/SKILL.md +44 -0
- package/src/skills/performance-profiling/SKILL.md +153 -0
- package/src/skills/plan-task/SKILL.md +101 -0
- package/src/skills/python-patterns/SKILL.md +529 -0
- package/src/skills/refactor-guide/SKILL.md +117 -0
- package/src/skills/regression-prediction/SKILL.md +57 -0
- package/src/skills/repo-memory-graph/SKILL.md +49 -0
- package/src/skills/rust-patterns/SKILL.md +492 -0
- package/src/skills/security-scan/SKILL.md +91 -0
- package/src/skills/self-healing-policies/SKILL.md +76 -0
- package/src/skills/tdd-workflow/SKILL.md +126 -0
- package/src/skills/test-coverage/SKILL.md +94 -0
- package/src/skills/test-gap-detector/SKILL.md +58 -0
- package/src/skills/volatility-map/SKILL.md +52 -0
- package/src/workflows/debug-flow.md +119 -0
- package/src/workflows/deploy-check-flow.md +98 -0
- package/src/workflows/discuss-flow.md +97 -0
- package/src/workflows/execute-flow.md +233 -0
- package/src/workflows/execute-phase.md +142 -0
- package/src/workflows/fix-bug-flow.md +210 -0
- package/src/workflows/map-codebase-flow.md +92 -0
- package/src/workflows/multi-repo-flow.md +226 -0
- package/src/workflows/parallel-execution-flow.md +236 -0
- package/src/workflows/plan-flow.md +126 -0
- package/src/workflows/plan-phase.md +101 -0
- package/src/workflows/refactor-flow.md +122 -0
- package/src/workflows/review-code-flow.md +105 -0
- package/src/workflows/spec-driven-flow.md +43 -0
- package/src/workflows/write-docs-flow.md +95 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Coding Style
|
|
2
|
+
|
|
3
|
+
Language-agnostic coding conventions followed by all FlowDeck agents.
|
|
4
|
+
|
|
5
|
+
## Immutability
|
|
6
|
+
|
|
7
|
+
Always create new objects and arrays. Never mutate parameters.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// ❌ NEVER — mutating a parameter
|
|
11
|
+
function addRole(user: User, role: string): void {
|
|
12
|
+
user.roles.push(role);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ✅ ALWAYS — return new object
|
|
16
|
+
function addRole(user: User, role: string): User {
|
|
17
|
+
return { ...user, roles: [...user.roles, role] };
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## KISS / DRY / YAGNI
|
|
22
|
+
|
|
23
|
+
| Principle | Rule |
|
|
24
|
+
|-----------|------|
|
|
25
|
+
| **KISS** (Keep It Simple) | The simplest solution that works is the right solution |
|
|
26
|
+
| **DRY** (Don't Repeat Yourself) | Extract duplication only when you have 3+ identical instances |
|
|
27
|
+
| **YAGNI** (You Aren't Gonna Need It) | Don't add features for hypothetical future needs |
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// ❌ YAGNI — configurable for no reason
|
|
31
|
+
function createUser(email: string, options: {
|
|
32
|
+
hashAlgorithm?: 'bcrypt' | 'argon2'; // only ever bcrypt in practice
|
|
33
|
+
saltRounds?: number;
|
|
34
|
+
legacyCompatMode?: boolean;
|
|
35
|
+
}) { ... }
|
|
36
|
+
|
|
37
|
+
// ✅ Simple
|
|
38
|
+
function createUser(email: string, password: string): Promise<User> { ... }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## File Organization
|
|
42
|
+
|
|
43
|
+
- Many small, focused files > few large files
|
|
44
|
+
- **Typical file size**: 200-400 lines
|
|
45
|
+
- **Maximum**: 800 lines — if larger, split it
|
|
46
|
+
- **One responsibility per file**: `user-service.ts`, not `all-services.ts`
|
|
47
|
+
|
|
48
|
+
## Error Handling
|
|
49
|
+
|
|
50
|
+
Handle errors explicitly at every level. Never swallow errors silently.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// ❌ Silent catch — hides failures
|
|
54
|
+
try {
|
|
55
|
+
await saveUser(user);
|
|
56
|
+
} catch (e) {}
|
|
57
|
+
|
|
58
|
+
// ❌ Logging without rethrowing — caller doesn't know it failed
|
|
59
|
+
try {
|
|
60
|
+
await saveUser(user);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(e);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ✅ Explicit — log context, rethrow or convert to domain error
|
|
66
|
+
try {
|
|
67
|
+
await saveUser(user);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error('Failed to save user', { userId: user.id, error });
|
|
70
|
+
throw new ServiceError('USER_SAVE_FAILED', { cause: error });
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Errors propagate upward unless you have a specific reason to handle them at this level.
|
|
75
|
+
|
|
76
|
+
## Input Validation
|
|
77
|
+
|
|
78
|
+
Validate all external inputs at the system boundary:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// System boundaries where validation is required:
|
|
82
|
+
// - API endpoints (HTTP request body, query params, headers)
|
|
83
|
+
// - File uploads (type, size, content)
|
|
84
|
+
// - Environment variables (on startup)
|
|
85
|
+
// - User input from forms
|
|
86
|
+
|
|
87
|
+
// ✅ Validate at the boundary — not deep in business logic
|
|
88
|
+
router.post('/users', async (req, res) => {
|
|
89
|
+
const result = createUserSchema.safeParse(req.body);
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
return res.status(422).json({ error: result.error.flatten() });
|
|
92
|
+
}
|
|
93
|
+
const user = await userService.create(result.data);
|
|
94
|
+
res.status(201).json(user);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Naming Conventions
|
|
99
|
+
|
|
100
|
+
| Type | Convention | Example |
|
|
101
|
+
|------|-----------|---------|
|
|
102
|
+
| Variables | camelCase | `userEmail`, `isActive` |
|
|
103
|
+
| Functions | camelCase | `createUser()`, `fetchProfile()` |
|
|
104
|
+
| Types / Interfaces | PascalCase | `User`, `CreateUserInput` |
|
|
105
|
+
| Classes | PascalCase | `UserService`, `PaymentGateway` |
|
|
106
|
+
| React Components | PascalCase | `UserCard`, `LoginForm` |
|
|
107
|
+
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT` |
|
|
108
|
+
| Files | kebab-case | `user-service.ts`, `auth-middleware.ts` |
|
|
109
|
+
| Directories | kebab-case | `user-management/`, `api-routes/` |
|
|
110
|
+
|
|
111
|
+
## Code Smells to Avoid
|
|
112
|
+
|
|
113
|
+
| Smell | Threshold | Fix |
|
|
114
|
+
|-------|-----------|-----|
|
|
115
|
+
| Deep nesting | > 3 levels | Extract guard clauses or helper functions |
|
|
116
|
+
| Magic numbers | Any unlabeled number | Name the constant: `MAX_ITEMS = 100` |
|
|
117
|
+
| Long functions | > 50 lines | Extract smaller functions |
|
|
118
|
+
| Boolean parameters | Any `doThing(true)` | Use options object: `doThing({ verbose: true })` |
|
|
119
|
+
| Long argument lists | > 3 parameters | Use an options object |
|
|
120
|
+
| Implicit any | Any untyped value | Add explicit type annotation |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Git Workflow
|
|
2
|
+
|
|
3
|
+
Conventional commits, clean history, and structured PR workflow.
|
|
4
|
+
|
|
5
|
+
## Conventional Commit Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
type(scope): description
|
|
9
|
+
|
|
10
|
+
[optional body — explain the why, not the what]
|
|
11
|
+
|
|
12
|
+
[optional footer — breaking changes, issue references]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
All commits on the main branch must follow this format.
|
|
16
|
+
|
|
17
|
+
## Commit Types
|
|
18
|
+
|
|
19
|
+
| Type | Version Bump | When to Use | Example |
|
|
20
|
+
|------|-------------|-------------|---------|
|
|
21
|
+
| `feat` | minor | New feature | `feat(auth): add JWT refresh endpoint` |
|
|
22
|
+
| `fix` | patch | Bug fix | `fix(auth): correct token expiry calculation` |
|
|
23
|
+
| `perf` | patch | Performance | `perf(db): replace N+1 with single JOIN query` |
|
|
24
|
+
| `refactor` | patch | Restructure | `refactor(user): extract password validation` |
|
|
25
|
+
| `docs` | none | Documentation | `docs(api): document authentication endpoints` |
|
|
26
|
+
| `test` | none | Tests | `test(auth): add coverage for expired tokens` |
|
|
27
|
+
| `chore` | none | Maintenance | `chore(deps): update express to 4.18.2` |
|
|
28
|
+
| `ci` | none | CI/CD | `ci(github): add node 20 to test matrix` |
|
|
29
|
+
| `build` | none | Build system | `build: switch to esbuild` |
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
feat!: remove deprecated v1 authentication endpoints
|
|
35
|
+
|
|
36
|
+
BREAKING CHANGE: The /api/v1/auth/* endpoints have been removed.
|
|
37
|
+
Migrate to /api/v2/auth/* before upgrading.
|
|
38
|
+
See MIGRATION.md for details.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Branch Naming
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
feature/user-authentication — new features
|
|
45
|
+
fix/login-redirect-loop — bug fixes
|
|
46
|
+
chore/update-dependencies — maintenance
|
|
47
|
+
release/v1.2.0 — release preparation
|
|
48
|
+
hotfix/critical-null-dereference — urgent production fix
|
|
49
|
+
refactor/extract-auth-middleware — restructuring
|
|
50
|
+
docs/update-api-reference — docs only
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## PR Workflow
|
|
54
|
+
|
|
55
|
+
1. Create branch from `main` with proper naming
|
|
56
|
+
2. Make small, atomic commits with conventional messages
|
|
57
|
+
3. Rebase onto latest `main` before creating PR
|
|
58
|
+
4. Create PR with descriptive title and description
|
|
59
|
+
5. Requires at least 1 reviewer approval
|
|
60
|
+
6. All checks must pass (tests, types, lint)
|
|
61
|
+
7. Merge via squash merge for small features, regular merge for large ones
|
|
62
|
+
|
|
63
|
+
## Rebase vs Merge
|
|
64
|
+
|
|
65
|
+
| Situation | Use | Command |
|
|
66
|
+
|-----------|-----|---------|
|
|
67
|
+
| Local feature before PR | Rebase | `git rebase origin/main` |
|
|
68
|
+
| PR → main | Squash (small features) or Merge (large) | GitHub UI |
|
|
69
|
+
| Release branch | Merge with `--no-ff` | `git merge --no-ff release/v1.2.0` |
|
|
70
|
+
| **Never** | Force-push to main | — |
|
|
71
|
+
|
|
72
|
+
## Commit Hygiene
|
|
73
|
+
|
|
74
|
+
- **Atomic commits** — one logical change per commit
|
|
75
|
+
- **No WIP commits** on shared branches
|
|
76
|
+
- **Fix typos** by amending the last commit (`git commit --amend`), not a new commit
|
|
77
|
+
- **No "fix build" commits** — fix locally, amend, and force-push the feature branch
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Security Standards
|
|
2
|
+
|
|
3
|
+
Security requirements that apply to all code. These are checked before every merge and deployment.
|
|
4
|
+
|
|
5
|
+
## Pre-Commit Security Checklist
|
|
6
|
+
|
|
7
|
+
Before every commit touching auth, data access, or API routes:
|
|
8
|
+
|
|
9
|
+
- [ ] No hardcoded credentials, API keys, or secrets
|
|
10
|
+
- [ ] All database queries use parameterized inputs (no string concatenation)
|
|
11
|
+
- [ ] Input validated at all API boundaries
|
|
12
|
+
- [ ] Auth middleware present on all protected routes
|
|
13
|
+
- [ ] No passwords, tokens, or sensitive data in log statements
|
|
14
|
+
- [ ] New dependencies checked for known CVEs (`npm audit`)
|
|
15
|
+
|
|
16
|
+
## Secret Management
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// ❌ NEVER — hardcoded secret
|
|
20
|
+
const JWT_SECRET = "my-super-secret-key-abc123";
|
|
21
|
+
|
|
22
|
+
// ✅ ALWAYS — environment variable
|
|
23
|
+
const JWT_SECRET = process.env.JWT_SECRET;
|
|
24
|
+
if (!JWT_SECRET) throw new Error('JWT_SECRET environment variable is required');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Rules:
|
|
28
|
+
- Use `process.env` for all secrets
|
|
29
|
+
- Use `.env.local` (never `.env`) for local development
|
|
30
|
+
- Add `.env*` to `.gitignore` (verify it's there)
|
|
31
|
+
- Use a secret manager (AWS Secrets Manager, Vault) in production
|
|
32
|
+
- Rotate any secret that was accidentally committed
|
|
33
|
+
|
|
34
|
+
## OWASP Top 10 Quick Reference
|
|
35
|
+
|
|
36
|
+
| ID | Vulnerability | One-Line Check |
|
|
37
|
+
|----|--------------|---------------|
|
|
38
|
+
| A01 | Broken Access Control | Does every protected route check auth AND authorization? |
|
|
39
|
+
| A02 | Cryptographic Failures | Is sensitive data encrypted in transit and at rest? |
|
|
40
|
+
| A03 | Injection | Are all queries parameterized? All shell commands sanitized? |
|
|
41
|
+
| A04 | Insecure Design | Is there rate limiting? Account lockout after failed logins? |
|
|
42
|
+
| A05 | Security Misconfiguration | Is debug mode off? Are default credentials changed? |
|
|
43
|
+
| A06 | Vulnerable Components | Does `npm audit` show zero critical/high? |
|
|
44
|
+
| A07 | Auth Failures | Are JWTs validated? Sessions invalidated on logout? |
|
|
45
|
+
| A08 | Integrity Failures | Is all user input validated before processing? |
|
|
46
|
+
| A09 | Logging Failures | Are errors logged? Are passwords/tokens excluded from logs? |
|
|
47
|
+
| A10 | SSRF | Are server-side URL fetches restricted to allowlisted domains? |
|
|
48
|
+
|
|
49
|
+
## Security Response Protocol
|
|
50
|
+
|
|
51
|
+
**If a secret is accidentally committed:**
|
|
52
|
+
1. Rotate the secret immediately — assume it is compromised
|
|
53
|
+
2. Force-push or use BFG Repo Cleaner to remove from git history
|
|
54
|
+
3. Audit access logs for the period the secret was exposed
|
|
55
|
+
4. Notify your security team
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Remove secret from git history (after rotating it)
|
|
59
|
+
git filter-branch --force --index-filter \
|
|
60
|
+
'git rm --cached --ignore-unmatch path/to/secret-file' \
|
|
61
|
+
--prune-empty --tag-name-filter cat -- --all
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Input Validation Requirements
|
|
65
|
+
|
|
66
|
+
Validate at every trust boundary:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Required validations for every external input:
|
|
70
|
+
// 1. Type — is it a string? number? object?
|
|
71
|
+
// 2. Length — is it within expected bounds?
|
|
72
|
+
// 3. Format — does it match the expected pattern?
|
|
73
|
+
// 4. Range — for numbers, within acceptable range?
|
|
74
|
+
|
|
75
|
+
// ✅ Example with Zod
|
|
76
|
+
const createUserSchema = z.object({
|
|
77
|
+
email: z.string().email().max(255),
|
|
78
|
+
password: z.string().min(8).max(128),
|
|
79
|
+
role: z.enum(['user', 'admin']).default('user'),
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For security-critical fields (passwords, tokens, IDs): **reject** invalid input, do not sanitize.
|
|
84
|
+
|
|
85
|
+
## Rate Limiting Requirements
|
|
86
|
+
|
|
87
|
+
All public-facing endpoints must have rate limiting:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Minimum rate limits:
|
|
91
|
+
// - Authentication endpoints: 5 attempts per minute per IP
|
|
92
|
+
// - Public API: 100 requests per minute per user/IP
|
|
93
|
+
// - Password reset: 3 attempts per hour per email
|
|
94
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
All code must meet these testing standards before being considered done.
|
|
4
|
+
|
|
5
|
+
## Minimum Coverage
|
|
6
|
+
|
|
7
|
+
**80% line coverage** — non-negotiable.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx vitest --coverage # vitest
|
|
11
|
+
npx jest --coverage # jest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A task is not complete until coverage is ≥ 80%.
|
|
15
|
+
|
|
16
|
+
## TDD Workflow
|
|
17
|
+
|
|
18
|
+
Follow Red-Green-Refactor for all new code:
|
|
19
|
+
|
|
20
|
+
1. **Red** — write a failing test that describes the desired behavior
|
|
21
|
+
2. **Green** — write the minimum code to make it pass
|
|
22
|
+
3. **Refactor** — clean up while keeping tests green
|
|
23
|
+
4. **Commit** — commit the test + implementation together
|
|
24
|
+
|
|
25
|
+
Never write implementation before writing a failing test.
|
|
26
|
+
|
|
27
|
+
## Test Types
|
|
28
|
+
|
|
29
|
+
| Type | Purpose | Tools | When Required |
|
|
30
|
+
|------|---------|-------|--------------|
|
|
31
|
+
| Unit | Functions in isolation, mocked deps | vitest, jest | Every function with logic |
|
|
32
|
+
| Integration | API + database end-to-end | supertest, vitest | Every API route |
|
|
33
|
+
| E2E | Full user flow in browser | playwright, cypress | Critical user journeys |
|
|
34
|
+
|
|
35
|
+
## AAA Pattern
|
|
36
|
+
|
|
37
|
+
Every test uses Arrange-Act-Assert:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
describe('UserService.create', () => {
|
|
41
|
+
it('should throw ValidationError when email format is invalid', async () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
const service = new UserService(mockDb);
|
|
44
|
+
const input = { email: 'not-an-email', password: 'valid-pass' };
|
|
45
|
+
|
|
46
|
+
// Act
|
|
47
|
+
const result = service.create(input);
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
await expect(result).rejects.toThrow('ValidationError');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Test Naming
|
|
56
|
+
|
|
57
|
+
Test names describe behavior in plain language:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// ✅ Describes behavior
|
|
61
|
+
it('should return empty array when user has no orders')
|
|
62
|
+
it('should throw AuthError when token is expired')
|
|
63
|
+
it('should send confirmation email after successful registration')
|
|
64
|
+
|
|
65
|
+
// ❌ Vague
|
|
66
|
+
it('test1')
|
|
67
|
+
it('works')
|
|
68
|
+
it('handles the error case')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Format: `should [expected behavior] when [condition]`
|
|
72
|
+
|
|
73
|
+
## Test Organization
|
|
74
|
+
|
|
75
|
+
- **Co-locate** tests with source: `user-service.ts` → `user-service.test.ts`
|
|
76
|
+
- Or use a parallel `__tests__/` directory — be consistent within the project
|
|
77
|
+
- One `describe` block per module or class
|
|
78
|
+
- Group related tests with nested `describe` blocks
|
|
79
|
+
|
|
80
|
+
## What to Test
|
|
81
|
+
|
|
82
|
+
- Public interfaces and API contracts
|
|
83
|
+
- Error conditions and edge cases
|
|
84
|
+
- Auth scenarios (unauthenticated, unauthorized, correct role)
|
|
85
|
+
- Empty inputs, null, undefined
|
|
86
|
+
- Boundary values (max length, zero, negative numbers)
|
|
87
|
+
|
|
88
|
+
## What NOT to Test
|
|
89
|
+
|
|
90
|
+
- Private methods (test via public interface)
|
|
91
|
+
- Third-party library behavior
|
|
92
|
+
- Simple getters with no logic
|
|
93
|
+
- Framework internals (Express routing, ORM query builder)
|
|
94
|
+
|
|
95
|
+
## Troubleshooting Test Failures
|
|
96
|
+
|
|
97
|
+
| Situation | Action |
|
|
98
|
+
|-----------|--------|
|
|
99
|
+
| Test fails after code change | Fix the implementation |
|
|
100
|
+
| Test fails after refactor | Undo the refactor; take smaller steps |
|
|
101
|
+
| Test fails and assertion is wrong | Fix the assertion (rare — verify carefully) |
|
|
102
|
+
| Tests are flaky | Find and isolate the shared mutable state |
|
|
103
|
+
| Coverage below 80% | Add tests for uncovered branches and edge cases |
|
|
104
|
+
|
|
105
|
+
**Never change a test to make it pass unless the assertion logic itself is wrong.**
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Go Patterns
|
|
2
|
+
|
|
3
|
+
Go conventions for FlowDeck projects.
|
|
4
|
+
|
|
5
|
+
## Always Handle Errors Explicitly
|
|
6
|
+
|
|
7
|
+
Never discard error return values with `_`. Every error must be checked and either handled or propagated.
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// ❌ Silently discarding the error
|
|
11
|
+
result, _ := parseConfig(path)
|
|
12
|
+
|
|
13
|
+
// ✅ Check and handle or propagate
|
|
14
|
+
result, err := parseConfig(path)
|
|
15
|
+
if err != nil {
|
|
16
|
+
return fmt.Errorf("startup: %w", err)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Error String Style
|
|
21
|
+
|
|
22
|
+
Error strings must be lowercase and have no trailing punctuation. They are often wrapped and appear mid-sentence in logs.
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// ❌ Uppercase, trailing period
|
|
26
|
+
errors.New("Connection refused.")
|
|
27
|
+
|
|
28
|
+
// ✅ Lowercase, no trailing punctuation
|
|
29
|
+
errors.New("connection refused")
|
|
30
|
+
fmt.Errorf("user %d: record not found", id)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Interface Naming
|
|
34
|
+
|
|
35
|
+
- Single-method interfaces: use the method name with the `-er` suffix.
|
|
36
|
+
- Multi-method interfaces: use a noun that describes the role.
|
|
37
|
+
|
|
38
|
+
```go
|
|
39
|
+
// ✅ Single-method
|
|
40
|
+
type Reader interface { Read(p []byte) (int, error) }
|
|
41
|
+
type Stringer interface { String() string }
|
|
42
|
+
type Notifier interface { Notify(ctx context.Context, msg Message) error }
|
|
43
|
+
|
|
44
|
+
// ✅ Multi-method
|
|
45
|
+
type UserStore interface {
|
|
46
|
+
FindByID(ctx context.Context, id int64) (*User, error)
|
|
47
|
+
Save(ctx context.Context, u *User) error
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Exported Names Must Have Doc Comments
|
|
52
|
+
|
|
53
|
+
Every exported function, type, method, and variable requires a doc comment beginning with the name.
|
|
54
|
+
|
|
55
|
+
```go
|
|
56
|
+
// ❌ Missing doc comment
|
|
57
|
+
func ProcessOrder(o *Order) error { ... }
|
|
58
|
+
|
|
59
|
+
// ✅ Doc comment starting with the name
|
|
60
|
+
// ProcessOrder validates and persists an order, charging the associated payment method.
|
|
61
|
+
func ProcessOrder(o *Order) error { ... }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## context.Context as First Parameter
|
|
65
|
+
|
|
66
|
+
All functions that perform I/O, call external services, or could be long-running must accept `context.Context` as their first parameter.
|
|
67
|
+
|
|
68
|
+
```go
|
|
69
|
+
// ❌ No context — cannot be cancelled or carry deadlines
|
|
70
|
+
func FetchUser(id int64) (*User, error) { ... }
|
|
71
|
+
|
|
72
|
+
// ✅ context.Context first
|
|
73
|
+
func FetchUser(ctx context.Context, id int64) (*User, error) { ... }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Never panic for Expected Errors
|
|
77
|
+
|
|
78
|
+
Use `panic` only for programmer errors (invariant violations, impossible states). Use error returns for expected failure modes.
|
|
79
|
+
|
|
80
|
+
```go
|
|
81
|
+
// ❌ panic for expected failure
|
|
82
|
+
func ParseConfig(data []byte) Config {
|
|
83
|
+
var cfg Config
|
|
84
|
+
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
85
|
+
panic(err) // config may be invalid at runtime — this is expected
|
|
86
|
+
}
|
|
87
|
+
return cfg
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ✅ Return error for expected failure
|
|
91
|
+
func ParseConfig(data []byte) (Config, error) {
|
|
92
|
+
var cfg Config
|
|
93
|
+
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
94
|
+
return Config{}, fmt.Errorf("parse config: %w", err)
|
|
95
|
+
}
|
|
96
|
+
return cfg, nil
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Table-Driven Tests
|
|
101
|
+
|
|
102
|
+
Any function with more than one meaningful input/output combination requires a table-driven test using `t.Run`.
|
|
103
|
+
|
|
104
|
+
```go
|
|
105
|
+
func TestDivide(t *testing.T) {
|
|
106
|
+
cases := []struct {
|
|
107
|
+
name string
|
|
108
|
+
a, b float64
|
|
109
|
+
want float64
|
|
110
|
+
wantErr bool
|
|
111
|
+
}{
|
|
112
|
+
{name: "simple division", a: 10, b: 2, want: 5},
|
|
113
|
+
{name: "divide by zero", a: 5, b: 0, wantErr: true},
|
|
114
|
+
{name: "negative divisor", a: 9, b: -3, want: -3},
|
|
115
|
+
}
|
|
116
|
+
for _, tc := range cases {
|
|
117
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
118
|
+
got, err := Divide(tc.a, tc.b)
|
|
119
|
+
if (err != nil) != tc.wantErr {
|
|
120
|
+
t.Fatalf("Divide(%v, %v) err = %v, wantErr %v", tc.a, tc.b, err, tc.wantErr)
|
|
121
|
+
}
|
|
122
|
+
if !tc.wantErr && got != tc.want {
|
|
123
|
+
t.Errorf("Divide(%v, %v) = %v, want %v", tc.a, tc.b, got, tc.want)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Static Analysis
|
|
131
|
+
|
|
132
|
+
All code must pass:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
go vet ./...
|
|
136
|
+
staticcheck ./...
|
|
137
|
+
golangci-lint run
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Run these in CI before merge. Do not suppress linter warnings without a comment explaining why.
|
|
141
|
+
|
|
142
|
+
## Avoid Global State
|
|
143
|
+
|
|
144
|
+
Prefer dependency injection via struct fields over package-level variables. Global state makes testing and concurrent use difficult.
|
|
145
|
+
|
|
146
|
+
```go
|
|
147
|
+
// ❌ Package-level global
|
|
148
|
+
var defaultClient = &http.Client{Timeout: 10 * time.Second}
|
|
149
|
+
|
|
150
|
+
func Fetch(url string) ([]byte, error) {
|
|
151
|
+
resp, err := defaultClient.Get(url)
|
|
152
|
+
...
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ✅ Injected via struct
|
|
156
|
+
type APIClient struct {
|
|
157
|
+
http *http.Client
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
func NewAPIClient(http *http.Client) *APIClient {
|
|
161
|
+
return &APIClient{http: http}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func (c *APIClient) Fetch(ctx context.Context, url string) ([]byte, error) {
|
|
165
|
+
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
166
|
+
resp, err := c.http.Do(req)
|
|
167
|
+
...
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Never Start Goroutines in init() or Package-Level Vars
|
|
172
|
+
|
|
173
|
+
Goroutines started at package init time cannot be cancelled, cannot propagate errors, and make test isolation impossible.
|
|
174
|
+
|
|
175
|
+
```go
|
|
176
|
+
// ❌ Goroutine in init
|
|
177
|
+
func init() {
|
|
178
|
+
go backgroundWorker() // leaks, cannot cancel, runs in every test
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ✅ Start goroutines from an explicit lifecycle method
|
|
182
|
+
type Worker struct { done chan struct{} }
|
|
183
|
+
|
|
184
|
+
func (w *Worker) Start(ctx context.Context) {
|
|
185
|
+
go w.run(ctx)
|
|
186
|
+
}
|
|
187
|
+
```
|