
Software Clean Code Standard
- 259 installs
- 73 repo stars
- Updated July 13, 2026
- vasilyu1983/ai-agents-public
Helps with ai & agent building tasks.
About
software-clean-code-standard is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- software-clean-code-standard
- AI & Agent Building
- AI-coding skill
Software Clean Code Standard by the numbers
- 259 all-time installs (skills.sh)
- +9 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,512 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vasilyu1983/ai-agents-public --skill software-clean-code-standardAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 259 |
|---|---|
| repo stars | ★ 73 |
| Last updated | July 13, 2026 |
| Repository | vasilyu1983/ai-agents-public ↗ |
What it does
Helps with ai & agent building tasks.
Files
Clean Code Standard — Quick Reference
This skill is the authoritative clean code standard for this repository's shared skills. It defines stable rule IDs (CC-*), how to apply them in reviews, and how to extend them safely via language overlays and explicit exceptions.
Modern Best Practices (January 2026): Prefer small, reviewable changes and durable change context. Use RFC 2119 normative language consistently. Treat security-by-design and secure defaults as baseline (OWASP Top 10, NIST SSDF). Build observable systems (OpenTelemetry). For durable links and current tool choices, consult data/sources.json.
---
Quick Reference
| Task | Tool/Framework | Command | When to Use |
|---|---|---|---|
| Cite a standard | CC-* rule ID | N/A | PR review comments, design discussions, postmortems |
| Categorize feedback | CC-NAM, CC-ERR, CC-SEC, etc. | N/A | Keep feedback consistent without "style wars" |
| Add stack nuance | Language overlay | N/A | When the base rule is too generic for a language/framework |
| Allow an exception | Waiver record | N/A | When a rule must be violated with explicit risk |
| Reuse shared checklists | assets/checklists/ | N/A | When you need product-agnostic review/release checklists |
| Reuse utility patterns | references/*-utilities.md | N/A | When extracting shared auth/logging/errors/resilience/testing utilities |
When to Use This Skill
- Defining or enforcing clean code rules across teams and languages.
- Reviewing code: cite
CC-*IDs and avoid restating standards in reviews. - Building automation: map linters/CI gates to
CC-*IDs. - Resolving recurring review debates: align on rule IDs, scope, and exceptions.
When NOT to Use This Skill
- Deep security audits: Use software-security-appsec for OWASP/SAST deep dives beyond
CC-SEC-*baseline. - Review workflow mechanics: Use software-code-review for PR workflow, reviewer assignment, and feedback patterns.
- Refactoring execution: Use qa-refactoring for step-by-step refactoring patterns and quality gates.
- Architecture decisions: Use software-architecture-design for system-level tradeoffs beyond code-level rules.
Decision Tree: Base Rule vs Overlay vs Exception
Feedback needed: [What kind of guidance is this?]
├─ Universal, cross-language rule? → Add/modify `CC-*` in `references/clean-code-standard.md`
│
├─ Language/framework-specific nuance? → Add overlay entry referencing existing `CC-*`
│
└─ One-off constraint or temporary tradeoff?
├─ Timeboxed? → Add waiver with expiry + tracking issue
└─ Permanent? → Propose a new rule or revise scope/exception criteria---
Navigation
Resources
- references/clean-code-standard.md
- references/code-quality-operational-playbook.md — Legacy operational playbook (RULE-01–RULE-13)
- references/clean-code-operational-checklist.md
- references/clean-coder-operational-checklist.md
- references/code-complete-operational-checklist.md
- references/pragmatic-programmer-operational-checklist.md
- references/practice-of-programming-operational-checklist.md
- references/working-effectively-with-legacy-code-operational-checklist.md
- references/art-of-clean-code-operational-checklist.md
- references/refactoring-operational-checklist.md
- references/design-patterns-operational-checklist.md
- references/functional-programming-patterns.md — Result/Either types, pipe/compose, immutability, pure functions, railway-oriented programming, CC-* rule mapping
- references/code-complexity-metrics.md — Cyclomatic/cognitive complexity, Halstead metrics, nesting depth, tooling (ESLint, SonarQube, CodeClimate), refactoring triggers
- data/sources.json — Durable external references for review, security-by-design, and observability
- CONVENTIONS.md — Skill structure and validation conventions
- SKILL-TEMPLATE.md — Copy-paste starter for new skills
- sources-schema.json — JSON schema for
data/sources.json - skill-dependencies.json — Related-skills dependency graph
Templates
- assets/checklists/backend-api-review-checklist.md
- assets/checklists/secure-code-review-checklist.md
- assets/checklists/frontend-performance-a11y-checklist.md
- assets/checklists/mobile-release-checklist.md
- assets/checklists/ux-design-review-checklist.md
- assets/checklists/ux-research-plan-template.md
Utility Patterns
- references/auth-utilities.md
- references/error-handling.md
- references/config-validation.md
- references/resilience-utilities.md
- references/logging-utilities.md
- references/observability-utilities.md
- references/testing-utilities.md
- references/llm-utilities.md
Related Skills
- ../software-code-review/SKILL.md — Review workflow and judgment; cite
CC-*IDs - ../software-security-appsec/SKILL.md — Security deep dives beyond baseline
CC-SEC-* - ../qa-refactoring/SKILL.md — Refactoring execution patterns and quality gates
- ../software-architecture-design/SKILL.md — System-level tradeoffs and boundaries
---
Optional: AI/Automation
- Map automation findings to
CC-*IDs (linters, SAST, dependency scanning) so humans can review impact, not tooling noise. - Keep AI-assisted suggestions advisory; human reviewers approve/deny with rule citations (https://conventionalcomments.org/).
---
Trend Awareness Protocol
IMPORTANT: When users ask recommendation questions about clean code standards, linters, or code quality tools, you MUST use a web search capability (if available) to check current trends before answering. If web search is unavailable, say so and answer using data/sources.json, clearly flagging that the recommendation may be stale.
Trigger Conditions
- "What's the best linter for [language]?"
- "What should I use for [code quality/static analysis]?"
- "What's the latest in clean code practices?"
- "Current best practices for [code standards/formatting]?"
- "Is [ESLint/Prettier/Biome] still relevant in 2026?"
- "[Biome] vs [ESLint] vs [other]?"
- "Best static analysis tool for [language]?"
Required Searches
1. Search: "clean code best practices 2026" 2. Search: "[specific linter] vs alternatives 2026" 3. Search: "code quality tools trends 2026" 4. Search: "[language] linter comparison 2026"
What to Report
After searching, provide:
- Current landscape: What linters/formatters are popular NOW
- Emerging trends: New tools, standards, or patterns gaining traction
- Deprecated/declining: Tools/approaches losing relevance or support
- Recommendation: Based on fresh data, not just static knowledge
Example Topics (verify with fresh search)
- JavaScript/TypeScript linters (ESLint, Biome, oxlint)
- Formatters (Prettier, dprint, Biome)
- Python quality (Ruff, mypy, pylint)
- Go linting (golangci-lint, staticcheck)
- Rust analysis (clippy, cargo-deny)
- Code quality metrics and reporting tools
- AI-assisted code review tools
Fact-Checking
- Use web search/web fetch to verify current external facts, versions, pricing, deadlines, regulations, or platform behavior before final answers.
- Prefer primary sources; report source links and dates for volatile information.
- If web access is unavailable, state the limitation and mark guidance as unverified.
Backend API Review Checklist
PR: [Link] Reviewer: [Name] Date: YYYY-MM-DD
---
Standards (Core)
- Clean code standard (single source of truth): ../../references/clean-code-standard.md
- Review comments: if feedback is primarily about clean code, cite
CC-*IDs; do not restate the standard.
Core Review (All PRs)
Correctness
- [ ] Request/response schemas match documentation
- [ ] Business logic handles edge cases (null, empty, max values)
- [ ] Error responses use consistent format (prefer RFC 9457 Problem Details) https://www.rfc-editor.org/rfc/rfc9457
- [ ] Database transactions have proper boundaries
API Design
- [ ] RESTful conventions followed (verbs, resources, status codes)
- [ ] Idempotency key supported for mutating operations
- [ ] Pagination implemented for list endpoints
- [ ] Versioning strategy consistent with existing APIs
Security
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-01,CC-SEC-02,CC-SEC-03,CC-SEC-05,CC-SEC-08,CC-ERR-02,CC-OBS-02 - [ ] Rate limiting configured
- [ ] API threats considered (OWASP API Security Top 10) https://owasp.org/API-Security/
Performance
- Baseline
CC-*to apply (cite IDs if violated):CC-PERF-01,CC-PERF-02,CC-PERF-03 - [ ] Database queries are efficient (no N+1)
- [ ] Indexes exist for filtered/sorted fields
- [ ] Response payload size is reasonable
- [ ] Caching considered for read-heavy endpoints
Testing
- Baseline
CC-*to apply (cite IDs if violated):CC-TST-01,CC-TST-02,CC-TST-03,CC-TST-04 - [ ] Unit tests cover business logic
- [ ] Integration tests verify API contracts
- [ ] Error cases tested (400, 401, 403, 404, 500)
- [ ] Performance/load tests for critical paths
Observability
- Baseline
CC-*to apply (cite IDs if violated):CC-OBS-01,CC-OBS-02,CC-OBS-03 - [ ] Request logging with correlation ID
- [ ] Metrics for latency, throughput, errors
- [ ] Health check endpoints (liveness, readiness)
- [ ] Tracing/metrics aligned with OpenTelemetry where applicable https://opentelemetry.io/docs/
---
Decision
- [ ] APPROVE: All checks pass
- [ ] REQUEST CHANGES: Issues listed below
- [ ] BLOCK: Critical security/correctness issue
Issues Found
| Severity | Issue | Line(s) |
|---|---|---|
| P0/P1/P2/P3 | Description | #L1-L5 |
---
Optional: AI/Automation Section
Include only when using AI features or adding automation gates.
- [ ] Automation (linters/SAST/SCA) results reviewed and mapped to
CC-*IDs where possible - [ ] If any AI-generated code is included, validate APIs exist and match project conventions
- [ ] LLM integrations have explicit timeouts, cancellation, and safe fallbacks
Frontend Performance & Accessibility Checklist
Page/Component: [Name] Reviewer: [Name] Date: YYYY-MM-DD
---
Performance (Core)
Core Web Vitals
- [ ] LCP (Largest Contentful Paint) <= 2.5s
- [ ] INP (Interaction to Next Paint) <= 200ms
- [ ] CLS (Cumulative Layout Shift) <= 0.1
Bundle Size
- [ ] Performance budgets defined and tracked (JS/CSS/images/fonts) [Inference]
- [ ] Route-level code splitting used where applicable
- [ ] Images optimized (WebP/AVIF, lazy loading)
- [ ] Fonts subset and preloaded
Rendering and Runtime
- [ ] Main-thread long tasks minimized (avoid synchronous heavy work on input) [Inference]
- [ ] Critical rendering path avoids unnecessary blocking requests [Inference]
- [ ] Error boundaries or equivalent fault isolation for UI crashes
Framework-Specific (React/Next.js)
- [ ] Server Components used where they reduce client JS [Inference]
- [ ] Client Components are minimal and explicitly marked (
"use client") - [ ] Suspense boundaries used for async data and streaming
- [ ] Error boundaries used for fault isolation
---
Accessibility (Core)
WCAG 2.2 AA Compliance
- [ ] Color contrast ratio >= 4.5:1 (text), >= 3:1 (large text)
- [ ] Focus visible on all interactive elements
- [ ] Keyboard navigation works (Tab, Enter, Escape)
- [ ] Touch targets meet WCAG 2.2 SC 2.5.8 target size (24x24 CSS px; exceptions apply) https://www.w3.org/TR/WCAG22/#target-size-minimum
- [ ] Touch targets follow platform guidance where relevant (e.g., 44x44 on iOS) https://developer.apple.com/design/human-interface-guidelines/
Semantic HTML
- [ ] Proper heading hierarchy (h1, h2, h3)
- [ ] Landmarks used (main, nav, aside, footer)
- [ ] Form inputs have associated labels
- [ ] Images have alt text (or alt="" for decorative)
Screen Reader
- [ ] ARIA labels where semantic HTML insufficient
- [ ] Live regions for dynamic content
- [ ] Focus management for modals/dialogs
- [ ] Skip links for navigation
---
Testing
- [ ] Core Web Vitals measured (field + lab) with no regressions https://web.dev/vitals/
- [ ] Lighthouse budgets set with no regressions [Inference]
- [ ] axe DevTools: 0 critical/serious issues [Inference]
- [ ] Manual keyboard navigation test
- [ ] Screen reader test (VoiceOver/NVDA)
---
Optional: AI/Automation Section
Include only for AI design tools or AI features.
- [ ] AI-generated components reviewed for accessibility
- [ ] AI chat interfaces have ARIA live regions
- [ ] Loading states visible during AI processing
- [ ] Error handling for AI service failures
Mobile Release Readiness Checklist
App: [Name] Version: [X.Y.Z] Platform: iOS / Android / Both Release Date: YYYY-MM-DD
---
Standards (Core)
- Clean code standard (single source of truth): ../../references/clean-code-standard.md
- Review comments: if feedback is primarily about clean code, cite
CC-*IDs; do not restate the standard.
Pre-Release (Core)
Code Quality
- [ ] All tests passing (unit, integration, UI)
- [ ] No critical or high-severity bugs open
- [ ] Code review completed for all changes
- [ ] Static analysis clean (SwiftLint/detekt)
iOS Specific
- [ ] Privacy manifest updated (app + third-party SDKs) https://developer.apple.com/documentation/bundlereferences/privacy_manifest_files
- [ ] Required-reason APIs declared with approved justifications https://developer.apple.com/documentation/bundlereferences/privacy_manifest_files
- [ ] App Transport Security configured (or exceptions documented) https://developer.apple.com/documentation/bundlereferences/information_property_list/nsapptransportsecurity
- [ ] Minimum deployment target and device matrix are documented and tested [Inference]
Android Specific
- [ ] Target SDK meets Google Play target API requirements https://support.google.com/googleplay/android-developer/answer/11926878
- [ ] Data safety form updated
- [ ] ProGuard/R8 rules tested
- [ ] 64-bit APK/AAB included
Performance
- [ ] Cold start performance meets product SLO (measure on low-end device) [Inference]
- [ ] Memory usage within budget
- [ ] No battery drain issues
- [ ] Network calls optimized (batching, caching)
Security
- [ ] Sensitive data in Keychain/Keystore only
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-03 - [ ] Android: R8/ProGuard enabled and verified (release builds) [Inference]
- [ ] Certificate pinning used only with rotation strategy and fail-open/fail-closed decision documented [Inference]
---
App Store Submission
Assets
- [ ] App icons (all required sizes)
- [ ] Screenshots (all device sizes)
- [ ] Preview video (optional)
- [ ] App description and keywords updated
Compliance
- [ ] Privacy policy URL valid
- [ ] Terms of service URL valid
- [ ] Age rating questionnaire completed
- [ ] Export compliance documentation (if applicable)
Testing
- [ ] TestFlight/Internal testing complete
- [ ] Beta feedback addressed
- [ ] Regression test on release candidate
---
Post-Release
- [ ] Monitoring dashboards configured
- [ ] Crash reporting active (Crashlytics, Sentry)
- [ ] Analytics tracking verified
- [ ] Rollback plan documented
---
Optional: AI/Automation Section
Include only for apps shipping AI/automation features.
- [ ] iOS: Apple Foundation Models integration tested (on-device behavior, privacy expectations) https://developer.apple.com/documentation/foundationmodels
- [ ] Android: ML Kit integration tested (offline/online behavior, performance) https://developers.google.com/ml-kit
- [ ] Model size, startup impact, and memory budget verified [Inference]
- [ ] AI feature degradation is graceful (timeouts, cancel, fallback) [Inference]
Secure Code Review Checklist
PR: [Link] Reviewer: [Name] Date: YYYY-MM-DD
---
Standards (Core)
- Clean code standard (single source of truth): ../../references/clean-code-standard.md
- Review comments: if feedback is primarily about clean code, cite
CC-*IDs; do not restate the standard.
Authentication (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-06,CC-SEC-07 - [ ] Token lifetime and rotation aligned to OAuth security BCP (short-lived access tokens + rotation) https://www.rfc-editor.org/rfc/rfc9700
- [ ] Logout invalidates all sessions/tokens
Authorization (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-02 - [ ] RBAC/ABAC policies enforced consistently
- [ ] No direct object references without ownership check
- [ ] Admin actions require elevated confirmation
Input Validation (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-01,CC-SEC-08 - [ ] File uploads validated (type, size, content)
- [ ] Path traversal prevented (no user input in file paths)
Output Encoding (Core, Conditional: Web)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-08 - [ ] CSP headers configured (no 'unsafe-inline')
- [ ] CORS restricted to known origins
Cryptography (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-04,CC-SEC-03 - [ ] TLS configured per modern guidance (prefer TLS 1.3; no legacy ciphers) [Inference]
- [ ] Encryption at rest uses organization-approved algorithms and libraries (avoid bespoke crypto) [Inference]
Logging & Monitoring (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-OBS-01,CC-OBS-02,CC-OBS-03 - [ ] Authentication events logged
- [ ] Authorization failures logged
- [ ] Correlation IDs for request tracing
Dependency Security (Core)
- Baseline
CC-*to apply (cite IDs if violated):CC-SEC-05 - [ ] Dependencies from trusted sources
- [ ] Supply chain controls in place (SBOM/provenance expectations) https://slsa.dev/spec/v1.0/
---
Decision
- [ ] APPROVE: No security issues found
- [ ] REQUEST CHANGES: Security issues listed below
- [ ] BLOCK: Critical vulnerability detected
Security Issues Found
| Severity | Issue | CWE | Line(s) |
|---|---|---|---|
| Critical/High/Medium/Low | Description | CWE-XXX | #L1-L5 |
---
Optional: AI/Automation Section
Include only for AI/LLM features.
- [ ] Prompt injection mitigated (input sanitization)
- [ ] LLM output sanitized before display
- [ ] PII not sent to external LLM APIs
- [ ] Rate limiting on AI endpoints
- [ ] Fallback behavior when AI service unavailable
UX Design Review Checklist
Feature/Page: [Name] Reviewer: [Name] Date: YYYY-MM-DD
---
Usability (Core)
Nielsen Heuristics
- [ ] Visibility of system status: User knows what's happening
- [ ] Match with real world: Language users understand
- [ ] User control: Easy to undo, exit, cancel
- [ ] Consistency: Same patterns throughout
- [ ] Error prevention: Prevents mistakes before they happen
- [ ] Recognition over recall: Options visible, not memorized
- [ ] Flexibility: Shortcuts for experts
- [ ] Aesthetic design: Minimal, relevant information
- [ ] Error recovery: Clear error messages with solutions
- [ ] Help: Documentation accessible when needed
Interaction Design
- [ ] Primary action is obvious
- [ ] Loading states shown (skeletons, not spinners)
- [ ] Empty states guide user to action
- [ ] Form validation inline (on blur, not every keystroke)
- [ ] Confirmation for destructive actions
---
Accessibility (Core)
- [ ] Color contrast meets WCAG 2.2 AA (4.5:1 / 3:1)
- [ ] Touch targets meet WCAG 2.2 SC 2.5.8 target size (24x24 CSS px; exceptions apply) https://www.w3.org/TR/WCAG22/#target-size-minimum
- [ ] Touch targets follow platform guidance where relevant (e.g., 44x44 on iOS) https://developer.apple.com/design/human-interface-guidelines/
- [ ] Focus states visible
- [ ] Screen reader experience tested
- [ ] Reduced motion respected
---
Responsiveness (Core)
- [ ] Works at 320px wide without horizontal scrolling for primary flows (WCAG reflow) https://www.w3.org/TR/WCAG22/#reflow
- [ ] Breakpoints are content-driven and documented (not framework defaults) [Inference]
- [ ] Touch-friendly on mobile (no hover-only interactions)
- [ ] Content readable without horizontal scroll
---
Design System Alignment
- [ ] Uses approved components from design system
- [ ] Colors from design tokens
- [ ] Typography from design tokens
- [ ] Spacing from design tokens (4px/8px grid)
---
Performance UX
- [ ] Perceived load time optimized (skeleton screens)
- [ ] Large images lazy loaded
- [ ] Optimistic UI for user actions
- [ ] No layout shifts during load
---
Optional: AI/Automation Section
Include only for AI-powered UI features.
- [ ] AI processing status visible (tool calls, streaming)
- [ ] User can stop, undo, retry AI actions
- [ ] Confidence cues for uncertain AI outputs
- [ ] Citations/sources provided where applicable
- [ ] Graceful fallback when AI unavailable
UX Research Plan: [Study Name]
Study Type: [Usability Test | Interview | Survey | Diary Study] Researcher: [Name] Date: YYYY-MM-DD Status: Draft | In Review | Approved | In Progress | Complete
---
1. Research Questions
Primary Question
[What is the main question this research will answer?]
Secondary Questions
1. [Sub-question 1] 2. [Sub-question 2]
---
2. Background & Context
Business Context
[Why is this research important now? What decision will it inform?]
Existing Knowledge
[What do we already know? Previous research, analytics, etc.]
Assumptions to Test
- [Assumption 1]
- [Assumption 2]
---
3. Methodology
Method Selection Rationale
[Why this method? What alternatives were considered?]
Participants
- Target: [User type/segment]
- Sample size: [N participants]
- Recruitment criteria: [Include/exclude criteria]
- Recruitment method: [Panel, intercept, etc.]
- Incentive: [$X / gift card / etc.]
Tasks (for usability tests)
1. [Task 1: Description, success criteria] 2. [Task 2: Description, success criteria]
Interview Guide (for interviews)
- [Topic 1: Questions]
- [Topic 2: Questions]
---
4. Timeline
| Phase | Dates | Deliverable |
|---|---|---|
| Planning | MM/DD - MM/DD | This plan |
| Recruitment | MM/DD - MM/DD | N participants scheduled |
| Sessions | MM/DD - MM/DD | N sessions completed |
| Analysis | MM/DD - MM/DD | Findings synthesis |
| Reporting | MM/DD - MM/DD | Final report |
---
5. Success Metrics
Quantitative
- Task success rate: [Target %]
- Time on task: [Target seconds]
- SUS score: [Target score]
Qualitative
- [Theme 1 to explore]
- [Theme 2 to explore]
---
6. Deliverables
- [ ] Research plan (this document)
- [ ] Participant recruitment tracker
- [ ] Session recordings/notes
- [ ] Findings report with recommendations
- [ ] Presentation to stakeholders
---
7. Stakeholders
| Name | Role | Involvement |
|---|---|---|
| [Name] | [Role] | [Observer, reviewer, etc.] |
---
8. Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Low recruitment | Start early, broaden criteria |
| Participant no-shows | Over-recruit by 20% |
| Bias in findings | Multiple researchers in analysis |
---
Optional: AI/Automation Section
Include only if using AI in research process.
AI-Assisted Analysis
- [ ] PII redacted before AI processing
- [ ] AI insights traced back to source quotes
- [ ] Human validation of AI-generated themes
- [ ] Audit trail maintained
Tools
- [AI tool 1: Purpose]
- [AI tool 2: Purpose]
---
References (Primary Sources)
- ISO 9241-210:2019 (human-centred design): https://www.iso.org/standard/77520.html
- ISO 9241-11:2018 (usability definition): https://www.iso.org/standard/63500.html
- GDPR (EU 2016/679): https://eur-lex.europa.eu/eli/reg/2016/679/oj
Skills Conventions
Centralized patterns and conventions for Claude Code Kit skills.
---
Overview
This document defines conventions to:
1. Eliminate duplication across 50+ skills 2. Ensure consistency in structure and naming 3. Enable automation for validation and sync 4. Simplify maintenance with centralized definitions
---
Directory Structure
Standard Skill Layout
skill-name/
├── SKILL.md # Main skill file (REQUIRED)
├── README.md # AVOID: extraneous; prefer SKILL.md
├── data/
│ └── sources.json # External references (REQUIRED)
├── references/
│ ├── operational-playbook.md
│ └── [topic]-best-practices.md
└── assets/
└── template-[name].mdShared Resources
software-clean-code-standard/
├── SKILL-TEMPLATE.md # Template for new skills
├── sources-schema.json # JSON Schema for sources.json
├── skill-dependencies.json # Dependency graph for Related Skills
└── CONVENTIONS.md # This file---
Naming Conventions
Skill Names
| Pattern | Example | Use When |
|---|---|---|
domain-topic | software-backend | Primary skills |
domain-topic-focus | ai-ml-data-science | Specialized skills |
domain-topic-tool | qa-testing-playwright | Tool-specific skills |
File Names
| Type | Pattern | Example |
|---|---|---|
| Best practices | [topic]-best-practices.md | nodejs-best-practices.md |
| Patterns | [topic]-patterns.md | fullstack-patterns.md |
| Playbooks | operational-playbook.md | (standard name) |
| Templates | template-[stack]-[variant].md | template-nodejs-prisma-postgres.md |
Directory Names
- All lowercase
- Kebab-case:
qa-testing-playwright - No underscores in skill names
---
Version References
Required Format
Always include explicit versions in:
1. Frontmatter description 2. Modern Best Practices section 3. Quick Reference table 4. Decision trees
Version Notation
| Format | Example | Meaning |
|---|---|---|
| Exact | Node.js 24.11.0 | Specific version |
| Minor range | Express 5.x | Any 5.x version |
| Minimum | Python 3.14+ | 3.14 or higher |
| LTS | Node.js 24 LTS | Long-term support |
| Current | Node.js 25 Current | Latest non-LTS |
Version Sources
Centralized in /.claude/skills/tech-stack-updater/data/version-registry.json
Update versions using: /update-tech-versions
---
Section Standards
SKILL.md Required Sections
# [Skill Name] — Quick Reference ← H1 title with "Quick Reference"
[Intro paragraph]
**Modern Best Practices (Month Year)**: ... ← Always include month/year
---
## Quick Reference ← 4-column table
## When to Use This Skill ← Bullet list
## Decision Tree: [Topic] ← Text-based tree
---
## Navigation ← Three subsections
**Resources**
**Templates**
**Related Skills**
---
## Operational Playbooks ← OptionalQuick Reference Table Format
| Task | Tool/Framework | Command | When to Use |
|------|----------------|---------|-------------|All 4 columns required. Include versions in Tool/Framework column.
Decision Tree Format
Project needs: [Decision Point]
├─ Option A?
│ ├─ Sub-option → Tool v1.x (reason)
│ └─ Sub-option → Tool v2.x (reason)
│
└─ Option B?
└─ Sub-option → Tool (reason)Use ├─, │, └─ characters. Max 4 levels deep.
---
Related Skills
Auto-Generation
Related Skills sections can be auto-generated from skill-dependencies.json.
{
"skill-name": {
"primary": ["skill-1", "skill-2"],
"secondary": ["skill-3", "skill-4"],
"description_map": {
"skill-1": "Brief description of relationship"
}
}
}Manual Format
**Related Skills**
- [../related-skill/SKILL.md](../related-skill/SKILL.md) — Description- Use relative paths:
../skill-name/SKILL.md - Include 3-8 related skills
- One-line descriptions
---
Sources Schema
Required Fields
{
"metadata": {
"skill": "skill-name", // REQUIRED: kebab-case
"updated": "YYYY-MM-DD", // REQUIRED: ISO date
"total_sources": 45 // REQUIRED: count
},
"categories": {
"category_name": [
{
"name": "Resource Name", // REQUIRED
"url": "https://...", // REQUIRED: HTTPS only
"type": "documentation", // REQUIRED: from enum
"relevance": "Why useful" // REQUIRED: 10-300 chars
}
]
}
}Type Enum
documentation, tutorial, reference, tool, examples,
framework, library, guide, blog, course, book,
video, podcast, specification, announcement, researchValidation
Validate against sources-schema.json:
npx --yes -p ajv-cli@5.0.0 -p ajv-formats@3.0.1 ajv validate -c ajv-formats -s software-clean-code-standard/sources-schema.json -d skill-name/data/sources.json---
Cross-Skill Patterns
Avoiding Duplication
| Pattern | Instead Of | Use |
|---|---|---|
| Shared templates | Copy-paste templates | Reference software-clean-code-standard/SKILL-TEMPLATE.md |
| Dependency graph | Manual Related Skills | Use skill-dependencies.json |
| Version registry | Hardcoded versions | Reference version-registry.json |
| Common playbooks | Duplicate checklists | Link to shared resource |
Common Content to Centralize
1. Technology versions → version-registry.json 2. Related Skills descriptions → skill-dependencies.json 3. Sources schema → sources-schema.json 4. Skill structure → SKILL-TEMPLATE.md 5. Code utilities → references/*.md
---
Clean Code in Templates
Templates should guide users to create centralized utilities instead of duplicated code.
Utility Centralization Guides
| Utility | Guide | Location in Project |
|---|---|---|
| Auth (JWT, password) | references/auth-utilities.md | src/utils/auth.ts |
| Error handling | references/error-handling.md | src/utils/errors.ts |
| Config validation | references/config-validation.md | src/config/index.ts |
| Resilience (retry, circuit breaker) | references/resilience-utilities.md | src/utils/resilience.ts |
| Logging | references/logging-utilities.md | src/utils/logger.ts |
| Testing (fixtures, mocks) | references/testing-utilities.md | src/test/factories.ts |
| Observability (tracing, metrics) | references/observability-utilities.md | src/utils/telemetry.ts |
| LLM (tokens, streaming, costs) | references/llm-utilities.md | src/utils/llm.ts |
Template Requirements
All code templates MUST include a Centralization Guide section after project structure:
## Centralization Guide
> **Important**: Extract utilities to shared modules. Do not duplicate.
| Utility | Extract To | Reference |
|---------|------------|-----------|
| JWT, password | `src/utils/auth.ts` | [auth-utilities.md](...) |
| Errors | `src/utils/errors.ts` | [error-handling.md](...) |Code Anti-Patterns to Avoid
Templates should NOT show:
// BAD: Inline utility in service
// user.service.ts
const hashPassword = async (pw: string) => bcrypt.hash(pw, 10);
// BAD: Duplicated in another file
// admin.service.ts
const hashPassword = async (pw: string) => bcrypt.hash(pw, 10);Templates SHOULD show:
// GOOD: Import from centralized utility
import { hashPassword } from '@/utils/auth';Checklist for Template Authors
- [ ] Includes "Centralization Guide" section
- [ ] Links to
references/*.mdfor common patterns - [ ] Shows import statements, not inline implementations
- [ ] Uses path aliases (
@/utils/) for clean imports - [ ] No duplicated functions across code examples
---
Quality Checklist
Before Committing a Skill
- [ ] YAML frontmatter has
nameanddescription - [ ] Description includes key versions, under 300 chars
- [ ] Modern Best Practices section has (Month Year)
- [ ] Quick Reference table has all 4 columns
- [ ] When to Use has 3+ bullet points
- [ ] Decision tree uses correct characters
- [ ] Navigation has Resources, Templates, Related Skills
- [ ] All internal links are valid relative paths
- [ ] sources.json validates against schema
- [ ] No content duplicated from other skills
- [ ] Versions match version-registry.json
Validation Commands
# Check character limit
wc -c skill-name/SKILL.md
# Validate sources.json
npx --yes -p ajv-cli@5.0.0 -p ajv-formats@3.0.1 ajv validate -c ajv-formats -s software-clean-code-standard/sources-schema.json -d skill-name/data/sources.json
# Check for broken links
python3 - <<'PY'
import re
from pathlib import Path
skill_md = Path("skill-name/SKILL.md")
text = skill_md.read_text(encoding="utf-8")
for match in re.finditer(r"\[[^\]]*\]\(([^)]+)\)", text):
target = match.group(1)
if target.startswith(("http://", "https://", "mailto:")):
continue
target = target.split("#", 1)[0]
if not target:
continue
resolved = (skill_md.parent / target)
if not resolved.exists():
raise SystemExit(f"Broken link: {target}")
print("OK: no broken file links in SKILL.md")
PY
# Find duplicate content
rg "Modern Best Practices" */SKILL.md---
Maintenance
Adding a New Skill
1. Copy software-clean-code-standard/SKILL-TEMPLATE.md to skill-name/SKILL.md 2. Create skill-name/data/sources.json following schema 3. Add to skill-dependencies.json 4. Run /validate-skill skill-name
Updating Versions
1. Run /update-tech-versions 2. Review generated report 3. Update affected skills
Syncing to Other Kits
1. Validate: /validate-skill skill-name 2. Sync: /sync-kits skill skill-name 3. Verify: /sync-kits validate-all
---
Anti-Patterns
Avoid
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Copy-paste sections | Drift, maintenance burden | Use shared templates |
| Hardcoded versions | Outdated quickly | Reference version-registry |
| Manual Related Skills | Inconsistent, incomplete | Use skill-dependencies.json |
| Long SKILL.md | Slow loading, hard to navigate | Progressive disclosure |
| Duplicate sources | Wasted tokens | Centralize common sources |
| Mixed naming conventions | Confusion | Follow conventions strictly |
---
Migration Guide
Updating Existing Skills
1. Check structure against SKILL-TEMPLATE.md 2. Add missing sections (Modern Best Practices, Navigation) 3. Update versions from version-registry.json 4. Validate sources.json against schema 5. Add to skill-dependencies.json 6. Run validation: /validate-skill skill-name
Batch Updates
# Find skills missing Modern Best Practices
rg -L "Modern Best Practices" */SKILL.md
# Find outdated versions
rg "Node.js 2[0-3]" */SKILL.md
# Find skills not in dependencies
diff <(ls -d */ | sed 's/\///') <(jq -r '.dependencies | keys[]' software-clean-code-standard/skill-dependencies.json){
"metadata": {
"skill": "software-clean-code-standard",
"updated": "2026-01-17",
"total_sources": 36,
"description": "Authoritative references for clean code standards (CC-*), code review practices, security-by-design, observability, refactoring, testing strategy, reliability, and modern linting/SAST tooling."
},
"categories": {
"code_review_practices": [
{
"name": "Google Engineering Practices - What to look for in a code review",
"url": "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
"type": "guide",
"relevance": "Durable reviewer focus areas: complexity, tests, and relying on style guides instead of preference debates.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Google Engineering Practices - Small CLs",
"url": "https://google.github.io/eng-practices/review/developer/small-cls.html",
"type": "guide",
"relevance": "Explains why small, reviewable changes improve review quality and reduce rework.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Google Engineering Practices - Writing good CL descriptions",
"url": "https://google.github.io/eng-practices/review/developer/cl-descriptions.html",
"type": "guide",
"relevance": "Defines durable expectations for \"what\" and \"why\" so future readers can understand intent.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Google Engineering Practices - Code review speed",
"url": "https://google.github.io/eng-practices/review/reviewer/speed.html",
"type": "guide",
"relevance": "Sets expectations for review responsiveness to reduce process latency without sacrificing quality.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false
}
],
"commenting_and_feedback": [
{
"name": "Conventional Comments",
"url": "https://conventionalcomments.org/",
"type": "specification",
"relevance": "A lightweight, machine-parseable standard for review comment intent (issue/suggestion/question/nitpick).",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
}
],
"security": [
{
"name": "OWASP Top 10",
"url": "https://owasp.org/www-project-top-ten/",
"type": "reference",
"relevance": "Baseline web application security risks to consider in both coding and review standards.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "NIST SSDF (SP 800-218)",
"url": "https://csrc.nist.gov/pubs/sp/800/218/final",
"type": "specification",
"relevance": "Secure software development framework for governance, practices, and organizational adoption.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false
}
],
"observability": [
{
"name": "OpenTelemetry Documentation",
"url": "https://opentelemetry.io/docs/",
"type": "documentation",
"relevance": "Industry-standard approach to traces, metrics, and logs for observable systems.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
}
],
"normative_language": [
{
"name": "RFC 2119 - Key words for use in RFCs to Indicate Requirement Levels",
"url": "https://www.rfc-editor.org/rfc/rfc2119",
"type": "specification",
"relevance": "Defines MUST/SHOULD/MAY requirement wording for stable standards documents.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false
}
],
"github_workflow_controls": [
{
"name": "GitHub Docs - Creating a pull request template",
"url": "https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository",
"type": "documentation",
"relevance": "Repository-level PR templates improve review context consistency and reduce back-and-forth.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
},
{
"name": "GitHub Docs - About CODEOWNERS",
"url": "https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners",
"type": "documentation",
"relevance": "Routes reviews to responsible owners and helps scale review quality across teams.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
},
{
"name": "GitHub Docs - About protected branches",
"url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches",
"type": "documentation",
"relevance": "Explains branch protection controls (required reviews, status checks) for enforcement in CI.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
}
],
"refactoring_and_design": [
{
"name": "Refactoring (Martin Fowler) - Catalog",
"url": "https://refactoring.com/catalog/",
"type": "reference",
"relevance": "Canonical refactoring patterns with intent and mechanics; useful for proposing precise, low-risk refactors.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "The Twelve-Factor App",
"url": "https://12factor.net/",
"type": "reference",
"relevance": "Operational principles for cloud-native apps (config, logs, concurrency, disposability) that inform reusable templates and defaults.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "C4 Model",
"url": "https://c4model.com/",
"type": "guide",
"relevance": "A lightweight diagramming model for communicating architecture with stable levels of detail; useful for skill resources and templates.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Documenting Architecture Decisions (Michael Nygard)",
"url": "https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions",
"type": "blog",
"relevance": "Canonical ADR format and rationale; useful when skills recommend decision records for durability and traceability.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "AWS Builders' Library",
"url": "https://aws.amazon.com/builders-library/",
"type": "guide",
"relevance": "Operationally grounded essays on reliability and maintainability; good defaults for resilience and scaling patterns in templates.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
}
],
"foundational_books": [
{
"name": "Clean Code (Robert C. Martin)",
"url": "https://www.pearson.com/en-us/subject-catalog/p/clean-code-a-handbook-of-agile-software-craftsmanship/P200000006168/9780132350884",
"type": "book",
"relevance": "Foundational readability and maintainability heuristics; use to inform checklists and examples (avoid copying prose into standards).",
"update_frequency": "static",
"access": "paid",
"add_as_web_search": false
},
{
"name": "Code Complete, 2nd Edition (Steve McConnell)",
"url": "https://www.microsoftpressstore.com/store/code-complete-9780735619678",
"type": "book",
"relevance": "Construction practices: defensive coding, complexity control, and practical discipline that maps well to CC-* review feedback.",
"update_frequency": "static",
"access": "paid",
"add_as_web_search": false
},
{
"name": "The Pragmatic Programmer (20th Anniversary Edition)",
"url": "https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/",
"type": "book",
"relevance": "Durable engineering habits: refactoring, testing, automation, and designing for change; good background for skill guidance.",
"update_frequency": "static",
"access": "paid",
"add_as_web_search": false
},
{
"name": "Working Effectively with Legacy Code (Michael Feathers)",
"url": "https://www.pearson.com/en-us/subject-catalog/p/working-effectively-with-legacy-code/P200000006301/9780131177055",
"type": "book",
"relevance": "Change-safe techniques for legacy systems (seams, characterization tests, risk control); complements refactoring checklists.",
"update_frequency": "static",
"access": "paid",
"add_as_web_search": false
}
],
"testing_strategy": [
{
"name": "Google Testing Blog",
"url": "https://testing.googleblog.com/",
"type": "blog",
"relevance": "Testing strategy and maintainability practices from Google engineers; useful background for cross-skill testing guidance.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
},
{
"name": "Playwright Documentation",
"url": "https://playwright.dev/",
"type": "documentation",
"relevance": "E2E testing for web apps with reliable tooling and cross-browser support; good reference for test automation templates.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
},
{
"name": "Testing Library - Guiding Principles",
"url": "https://testing-library.com/docs/guiding-principles",
"type": "guide",
"relevance": "Principles for writing tests that resemble user behavior and avoid brittle implementation-coupled assertions.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
}
],
"observability_and_reliability": [
{
"name": "Google SRE Book",
"url": "https://sre.google/sre-book/table-of-contents/",
"type": "book",
"relevance": "SLIs/SLOs, incident response, and reliability fundamentals; good baseline for operational and observability guidance.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "OpenTelemetry Specifications",
"url": "https://opentelemetry.io/docs/specs/",
"type": "specification",
"relevance": "Semantic conventions and protocol-level specs for consistent observability across vendors and stacks.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": false
}
],
"specs_and_standards": [
{
"name": "RFC 9457 - Problem Details for HTTP APIs",
"url": "https://www.rfc-editor.org/rfc/rfc9457",
"type": "specification",
"relevance": "Standard error response format for HTTP APIs (updates RFC 7807); helps keep API templates consistent.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "OWASP Cheat Sheet Series",
"url": "https://cheatsheetseries.owasp.org/",
"type": "reference",
"relevance": "Practical security guidance (auth, session management, headers, input validation) for templates and reviews.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
}
],
"optional_ai_automation": [
{
"name": "TypeScript Native Previews",
"url": "https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/",
"type": "announcement",
"relevance": "Optional: TypeScript native tooling progress; useful when skills discuss build performance and editor/tooling ergonomics.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "OWASP Top 10 for Large Language Model Applications",
"url": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
"type": "reference",
"relevance": "Optional: threat taxonomy for LLM applications (prompt injection, data leakage, model supply chain) for AI-enabled product work.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
},
{
"name": "AI Code Review Tools 2026 (Qodo)",
"url": "https://www.qodo.ai/blog/best-ai-code-review-tools-2026/",
"type": "guide",
"relevance": "AI-assisted review tools landscape; addresses review throughput bottleneck as AI-generated code volume increases.",
"update_frequency": "annual",
"access": "free",
"add_as_web_search": true
},
{
"name": "AI SAST Tools Guide 2026 (Arnica)",
"url": "https://www.arnica.io/blog/top-6-ai-sast-tools-for-2026-the-quick-guide-to-agentic-static-application-security-testing",
"type": "guide",
"relevance": "AI-native SAST for faster, more accurate vulnerability detection; covers Cycode, Snyk Code, Semgrep AI capabilities.",
"update_frequency": "annual",
"access": "free",
"add_as_web_search": true
}
],
"modern_linting_tooling": [
{
"name": "Biome vs ESLint Comparison (Better Stack)",
"url": "https://betterstack.com/community/guides/scaling-nodejs/biome-eslint/",
"type": "guide",
"relevance": "Modern linter performance comparison; Biome is 10-25x faster than ESLint+Prettier with unified tooling.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
},
{
"name": "Oxlint - Rust-based JavaScript Linter",
"url": "https://oxc.rs/docs/guide/usage/linter.html",
"type": "documentation",
"relevance": "50-100x faster than ESLint with zero config; production-ready standalone linter from Oxc project.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
},
{
"name": "Ruff - Python Linter and Formatter",
"url": "https://docs.astral.sh/ruff/",
"type": "documentation",
"relevance": "Rust-based Python linter 10-100x faster than Flake8; drop-in replacement with unified linting and formatting.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true
},
{
"name": "Code Quality Metrics for Large Orgs 2026 (Qodo)",
"url": "https://www.qodo.ai/blog/code-quality-metrics-2026/",
"type": "guide",
"relevance": "Technical debt metrics (TDR, code churn, defect ratio), benchmarks, and AI-generated code validation costs.",
"update_frequency": "annual",
"access": "free",
"add_as_web_search": true
}
]
}
}
The Art of Clean Code Operational Checklist
Actionable guidance focused on readability, intent, and humane codebases.
Intent and Storytelling
- Lead with intent: each module/function starts from “what it does and why,” not how.
- Keep one narrative thread per function; avoid mixing setup, business rules, and orchestration in a single block.
- Prefer expressive names that match domain language; keep naming consistent across files and layers.
Simplicity and Structure
- Make the happy path straight-line; push error/edge cases to guard clauses.
- Keep functions small and single-purpose; align all statements to one abstraction level.
- Remove incidental complexity: collapse redundant layers, delete unused parameters/flags, and simplify conditionals.
- Choose the simplest data structure that fits; wrap domain concepts in value objects to encode rules once.
Clarity Over Cleverness
- Avoid “smart” tricks (implicit mutation, overloading semantics, condensed boolean logic) when a clearer version exists.
- Replace boolean soups with named predicates; extract readability helpers for compound conditions.
- Prefer composition and plain data over inheritance and global state; keep dependency graphs shallow.
Error Handling and Safety
- Validate inputs early; fail fast with actionable messages.
- Isolate external calls; standardize timeouts, retries, and error mapping in one place.
- Log decisions and failures at boundaries; include key identifiers and outcomes, not raw data dumps.
Comments and Documentation
- Comment intent, constraints, and trade-offs; delete comments that restate code.
- Co-locate short “why” notes near non-obvious decisions; keep longer rationale in adjacent docs when necessary.
- Remove dead/commented-out code promptly.
Testing for Confidence
- Tests must read like examples: clear Arrange-Act-Assert with domain-meaningful names.
- Cover happy path, edges, and failure modes; ensure tests fail without the code under test.
- Keep tests isolated and deterministic; control time, randomness, and IO with fakes.
Incremental Improvement
- When touching code, rename for clarity, split long functions, and remove duplication—avoid scope creep beyond the touched area.
- Prefer small, reversible commits; keep refactors behavior-preserving and separately labeled from features.
Authentication Utilities
Centralized patterns for password hashing, JWT, and OAuth 2.1. Updated December 2025.
---
Quick Reference
| Task | Library | Version | Notes |
|---|---|---|---|
| Password hashing | argon2 / @node-rs/argon2 | 2.x | Recommended over bcrypt |
| JWT (TypeScript) | jose | 5.x | Standards-compliant, no native deps |
| JWT (Python) | PyJWT | 2.9+ | Or python-jose for JWE |
| JWT (Go) | golang-jwt/jwt | v5 | Current standard |
| OAuth 2.1 | openid-client | 6.x | PKCE required |
---
File Structure
src/
├── utils/
│ └── auth.ts # JWT, password utilities
├── middleware/
│ └── auth.ts # Auth middleware (uses utils/auth)
└── types/
└── auth.ts # Shared types---
TypeScript/Node.js
Types (src/types/auth.ts)
export interface TokenPayload {
userId: string;
email: string;
role: 'user' | 'admin';
iat?: number;
exp?: number;
}
export interface AuthUser {
id: string;
email: string;
role: string;
}
export interface TokenPair {
accessToken: string;
refreshToken: string;
}Utilities (src/utils/auth.ts)
import { hash, verify } from '@node-rs/argon2';
import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
import { config } from '@/config';
import type { TokenPayload, TokenPair } from '@/types/auth';
// ============================================
// PASSWORD HASHING (Argon2id - recommended)
// ============================================
// Argon2id with OWASP-recommended parameters
const ARGON2_OPTIONS = {
memoryCost: 65536, // 64 MiB
timeCost: 3, // 3 iterations
parallelism: 4, // 4 threads
};
export const hashPassword = async (password: string): Promise<string> => {
return hash(password, ARGON2_OPTIONS);
};
export const verifyPassword = async (
password: string,
hashedPassword: string
): Promise<boolean> => {
try {
return await verify(hashedPassword, password);
} catch {
return false;
}
};
// ============================================
// JWT TOKENS (jose library)
// ============================================
const ACCESS_SECRET = new TextEncoder().encode(config.JWT_SECRET);
const REFRESH_SECRET = new TextEncoder().encode(config.JWT_REFRESH_SECRET);
export const generateAccessToken = async (
payload: Omit<TokenPayload, 'iat' | 'exp'>
): Promise<string> => {
return new SignJWT({ ...payload } as JWTPayload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(config.JWT_ACCESS_EXPIRE || '15m')
.sign(ACCESS_SECRET);
};
export const generateRefreshToken = async (
payload: Pick<TokenPayload, 'userId'>
): Promise<string> => {
return new SignJWT({ userId: payload.userId } as JWTPayload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(config.JWT_REFRESH_EXPIRE || '7d')
.sign(REFRESH_SECRET);
};
export const generateTokenPair = async (
payload: Omit<TokenPayload, 'iat' | 'exp'>
): Promise<TokenPair> => {
const [accessToken, refreshToken] = await Promise.all([
generateAccessToken(payload),
generateRefreshToken({ userId: payload.userId }),
]);
return { accessToken, refreshToken };
};
export const verifyAccessToken = async (token: string): Promise<TokenPayload> => {
const { payload } = await jwtVerify(token, ACCESS_SECRET);
return payload as unknown as TokenPayload;
};
export const verifyRefreshToken = async (
token: string
): Promise<Pick<TokenPayload, 'userId'>> => {
const { payload } = await jwtVerify(token, REFRESH_SECRET);
return { userId: payload.userId as string };
};
// ============================================
// TOKEN EXTRACTION
// ============================================
export const extractBearerToken = (header: string | undefined): string | null => {
if (!header?.startsWith('Bearer ')) return null;
return header.slice(7);
};Middleware (src/middleware/auth.ts)
import type { Request, Response, NextFunction } from 'express';
import { verifyAccessToken, extractBearerToken } from '@/utils/auth';
import { UnauthorizedError, ForbiddenError } from '@/utils/errors';
import type { AuthUser } from '@/types/auth';
declare global {
namespace Express {
interface Request {
user?: AuthUser;
}
}
}
export const authenticate = async (
req: Request,
_res: Response,
next: NextFunction
): Promise<void> => {
const token = extractBearerToken(req.headers.authorization);
if (!token) {
return next(new UnauthorizedError('Missing authorization token'));
}
try {
const payload = await verifyAccessToken(token);
req.user = {
id: payload.userId,
email: payload.email,
role: payload.role,
};
next();
} catch {
next(new UnauthorizedError('Invalid or expired token'));
}
};
export const requireRole = (...roles: string[]) => {
return (req: Request, _res: Response, next: NextFunction): void => {
if (!req.user) {
return next(new UnauthorizedError('Not authenticated'));
}
if (!roles.includes(req.user.role)) {
return next(new ForbiddenError('Insufficient permissions'));
}
next();
};
};---
Python (FastAPI)
Utilities (src/utils/auth.py)
from datetime import datetime, timedelta, timezone
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import jwt
from src.config import settings
# Argon2id with OWASP-recommended parameters
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
)
# ============================================
# PASSWORD HASHING (Argon2id)
# ============================================
def hash_password(password: str) -> str:
"""Hash password using Argon2id."""
return ph.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify password against hash. Returns False on mismatch."""
try:
ph.verify(hashed_password, plain_password)
return True
except VerifyMismatchError:
return False
def needs_rehash(hashed_password: str) -> bool:
"""Check if password hash needs updating to current parameters."""
return ph.check_needs_rehash(hashed_password)
# ============================================
# JWT TOKENS
# ============================================
def create_access_token(user_id: int, email: str, role: str) -> str:
"""Create short-lived access token (15 min default)."""
expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.JWT_ACCESS_EXPIRE_MINUTES
)
payload = {
"sub": str(user_id),
"email": email,
"role": role,
"exp": expire,
"iat": datetime.now(timezone.utc),
}
return jwt.encode(payload, settings.JWT_SECRET, algorithm="HS256")
def create_refresh_token(user_id: int) -> str:
"""Create long-lived refresh token (7 days default)."""
expire = datetime.now(timezone.utc) + timedelta(
days=settings.JWT_REFRESH_EXPIRE_DAYS
)
payload = {
"sub": str(user_id),
"exp": expire,
"iat": datetime.now(timezone.utc),
}
return jwt.encode(payload, settings.JWT_REFRESH_SECRET, algorithm="HS256")
def create_token_pair(user_id: int, email: str, role: str) -> dict[str, str]:
"""Create both access and refresh tokens."""
return {
"access_token": create_access_token(user_id, email, role),
"refresh_token": create_refresh_token(user_id),
}
def decode_access_token(token: str) -> dict:
"""Decode and validate access token."""
return jwt.decode(token, settings.JWT_SECRET, algorithms=["HS256"])
def decode_refresh_token(token: str) -> dict:
"""Decode and validate refresh token."""
return jwt.decode(token, settings.JWT_REFRESH_SECRET, algorithms=["HS256"])Dependencies (src/dependencies/auth.py)
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from jwt.exceptions import InvalidTokenError
from src.utils.auth import decode_access_token, verify_password, needs_rehash
from src.database import get_db
from src.repositories.user import UserRepository
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
db: Annotated[AsyncSession, Depends(get_db)],
):
"""Extract and validate user from JWT token."""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = decode_access_token(token)
user_id = payload.get("sub")
if user_id is None:
raise credentials_exception
except InvalidTokenError:
raise credentials_exception
user = await UserRepository(db).get_by_id(int(user_id))
if user is None:
raise credentials_exception
return user
def require_role(*roles: str):
"""Dependency factory for role-based access control."""
async def role_checker(user=Depends(get_current_user)):
if user.role not in roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions",
)
return user
return role_checker---
Go (Fiber)
Utilities (internal/utils/auth.go)
package utils
import (
"errors"
"time"
"github.com/alexedwards/argon2id"
"github.com/golang-jwt/jwt/v5"
)
// Argon2id params (OWASP recommended)
var argon2Params = &argon2id.Params{
Memory: 64 * 1024, // 64 MiB
Iterations: 3,
Parallelism: 4,
SaltLength: 16,
KeyLength: 32,
}
// ============================================
// PASSWORD HASHING (Argon2id)
// ============================================
func HashPassword(password string) (string, error) {
return argon2id.CreateHash(password, argon2Params)
}
func VerifyPassword(password, hash string) (bool, error) {
return argon2id.ComparePasswordAndHash(password, hash)
}
// ============================================
// JWT TOKENS
// ============================================
type TokenClaims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
type TokenPair struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func GenerateAccessToken(userID, email, role, secret string, expiry time.Duration) (string, error) {
claims := TokenClaims{
UserID: userID,
Email: email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
func GenerateRefreshToken(userID, secret string, expiry time.Duration) (string, error) {
claims := jwt.RegisteredClaims{
Subject: userID,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
IssuedAt: jwt.NewNumericDate(time.Now()),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
func GenerateTokenPair(userID, email, role, accessSecret, refreshSecret string) (*TokenPair, error) {
accessToken, err := GenerateAccessToken(userID, email, role, accessSecret, 15*time.Minute)
if err != nil {
return nil, err
}
refreshToken, err := GenerateRefreshToken(userID, refreshSecret, 7*24*time.Hour)
if err != nil {
return nil, err
}
return &TokenPair{
AccessToken: accessToken,
RefreshToken: refreshToken,
}, nil
}
func VerifyAccessToken(tokenString, secret string) (*TokenClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*TokenClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}Middleware (internal/middleware/auth.go)
package middleware
import (
"strings"
"github.com/gofiber/fiber/v2"
"myapp/internal/config"
"myapp/internal/utils"
)
func Authenticate(cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
auth := c.Get("Authorization")
if auth == "" {
return c.Status(401).JSON(fiber.Map{"error": "Missing authorization header"})
}
parts := strings.Split(auth, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
return c.Status(401).JSON(fiber.Map{"error": "Invalid authorization format"})
}
claims, err := utils.VerifyAccessToken(parts[1], cfg.JWTSecret)
if err != nil {
return c.Status(401).JSON(fiber.Map{"error": "Invalid or expired token"})
}
c.Locals("user_id", claims.UserID)
c.Locals("email", claims.Email)
c.Locals("role", claims.Role)
return c.Next()
}
}
func RequireRole(roles ...string) fiber.Handler {
return func(c *fiber.Ctx) error {
userRole, ok := c.Locals("role").(string)
if !ok {
return c.Status(401).JSON(fiber.Map{"error": "Not authenticated"})
}
for _, role := range roles {
if userRole == role {
return c.Next()
}
}
return c.Status(403).JSON(fiber.Map{"error": "Insufficient permissions"})
}
}---
OAuth 2.1 / PKCE (TypeScript)
OAuth 2.1 requires PKCE for all clients. Use openid-client for standards-compliant implementation.
PKCE Utilities (src/utils/oauth.ts)
import { randomBytes, createHash } from 'node:crypto';
export const generateCodeVerifier = (): string => {
return randomBytes(32).toString('base64url');
};
export const generateCodeChallenge = (verifier: string): string => {
return createHash('sha256').update(verifier).digest('base64url');
};
export interface PKCEPair {
codeVerifier: string;
codeChallenge: string;
}
export const generatePKCE = (): PKCEPair => {
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
return { codeVerifier, codeChallenge };
};OAuth Client Setup
import { Issuer, generators } from 'openid-client';
export const setupOAuthClient = async () => {
const issuer = await Issuer.discover('https://accounts.google.com');
const client = new issuer.Client({
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
redirect_uris: ['http://localhost:3000/auth/callback'],
response_types: ['code'],
});
return client;
};
export const getAuthorizationUrl = async (client: any) => {
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
const url = client.authorizationUrl({
scope: 'openid email profile',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state: generators.state(),
});
return { url, codeVerifier };
};---
Usage Examples
TypeScript Service
// src/services/user.service.ts
import { hashPassword, verifyPassword, generateTokenPair } from '@/utils/auth';
import { UnauthorizedError } from '@/utils/errors';
export const createUser = async (email: string, password: string) => {
const hashedPassword = await hashPassword(password);
return db.user.create({ data: { email, password: hashedPassword } });
};
export const loginUser = async (email: string, password: string) => {
const user = await db.user.findUnique({ where: { email } });
if (!user || !(await verifyPassword(password, user.password))) {
throw new UnauthorizedError('Invalid credentials');
}
return generateTokenPair({
userId: user.id,
email: user.email,
role: user.role,
});
};Python Router
# src/routers/auth.py
from src.utils.auth import hash_password, verify_password, create_token_pair
@router.post("/register")
async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
hashed = hash_password(data.password)
user = await UserRepository(db).create(email=data.email, password=hashed)
return {"id": user.id}
@router.post("/login")
async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)):
user = await UserRepository(db).get_by_email(data.email)
if not user or not verify_password(data.password, user.password):
raise HTTPException(401, "Invalid credentials")
return create_token_pair(user.id, user.email, user.role)---
Migration Guide: bcrypt → Argon2
Gradual Migration Pattern
// Support both during migration
export const verifyPasswordWithMigration = async (
password: string,
hash: string,
userId: string
): Promise<boolean> => {
// Check if it's an Argon2 hash (starts with $argon2)
if (hash.startsWith('$argon2')) {
return verifyPassword(password, hash);
}
// Legacy bcrypt hash (starts with $2a, $2b, or $2y)
const bcrypt = await import('bcrypt');
const isValid = await bcrypt.compare(password, hash);
if (isValid) {
// Rehash with Argon2 on successful login
const newHash = await hashPassword(password);
await db.user.update({
where: { id: userId },
data: { password: newHash },
});
}
return isValid;
};---
Anti-Patterns
// BAD: Using bcrypt with low cost
const hash = await bcrypt.hash(password, 10);
// BAD: Using jsonwebtoken (legacy)
import jwt from 'jsonwebtoken';
const token = jwt.sign(payload, secret);
// BAD: Duplicated auth code
// user.service.ts
const hashPwd = async (p: string) => bcrypt.hash(p, 10);
// admin.service.ts
const hashPwd = async (p: string) => bcrypt.hash(p, 10);
// GOOD: Argon2 with OWASP params
const hash = await hashPassword(password);
// GOOD: jose library for JWT
const token = await generateAccessToken(payload);
// GOOD: Import from centralized utility
import { hashPassword, generateAccessToken } from '@/utils/auth';---
Dependencies
TypeScript/Node.js
{
"@node-rs/argon2": "^2.0.0",
"jose": "^5.9.0",
"openid-client": "^6.1.0"
}Python
argon2-cffi>=23.1.0
PyJWT>=2.9.0Go
go get github.com/alexedwards/argon2id
go get github.com/golang-jwt/jwt/v5Clean Code Operational Checklist
Actionable rules distilled from Clean Code for day-to-day coding, reviews, and refactors.
Naming
- Use intention-revealing names; avoid noise words and misleading terms.
- Prefer pronounceable, searchable names; avoid encodings or Hungarian notation.
- Commands are verbs, queries are nouns; keep consistent terminology across modules.
Functions
- Keep functions small and do one thing; stay at one abstraction level per function.
- Limit parameters to three; avoid boolean flags and long parameter lists—extract objects when needed.
- Prefer clear control flow with guard clauses over deep nesting; avoid hidden side effects.
- Separate commands from queries; return data instead of mutating unexpectedly.
Comments
- Explain intent, warnings, or legal notes only; delete redundant or outdated comments.
- Remove commented-out code; version control preserves history.
Formatting and Structure
- Group related code vertically; keep closely related lines together and separate concepts with blank lines.
- Keep line length reasonable and indentation simple; one statement per line.
- Order dependencies: high-level context first, details later; minimize interleaving concerns.
Objects, Data, and Classes
- Apply Single Responsibility: one reason to change per class/module.
- Keep instance variables few and coherent; constructors establish invariants.
- Hide representation; expose behavior over data. For DTOs, keep them simple and behavior-free.
- Respect the Law of Demeter: talk to friends, not strangers; avoid train-wreck calls.
Error Handling
- Use exceptions, not error codes; avoid returning null—use exceptions, Option/Maybe, or Null Object.
- Keep try/catch blocks narrow; translate low-level exceptions to domain-specific ones.
- Do not mix error handling with normal logic; handle once, close resources deterministically.
Boundaries and External Code
- Wrap third-party APIs behind adapters; centralize translation, validation, and error mapping.
- Write contract tests against boundaries; keep external details out of domain logic.
Tests
- Apply FIRST: Fast, Independent, Repeatable, Self-validating, Timely.
- One assertion concept per test; use descriptive names and clear Arrange-Act-Assert structure.
- Tests should read like documentation and never share hidden state.
Emergent Design Rules
- A design is clean when: all tests pass, no duplication, clear intent, and minimal classes/methods.
- Refactor whenever adding behavior to preserve these properties.
Concurrency (Clean Code guidance)
- Separate concurrency concerns from business logic; isolate shared state and prefer immutability.
- Keep critical sections small; minimize locking scope and shared data.
- Use producer/consumer queues or message passing to reduce contention; test with stress and race detectors.
Smells → Refactor Triggers (selected)
- Long function, long parameter list, flag arguments, or deep nesting → extract helpers/objects and flatten control flow.
- Divergent change or shotgun surgery → split responsibilities and introduce stable abstractions.
- Feature envy or data clumps → move behavior to the data owner and create value objects.
- Dead code and duplicated code → delete or consolidate; keep single sources of truth.
Clean Code Standard
Single source of truth for clean code rules. Other skills (especially software-code-review) must reference these rules by CC-* ID instead of duplicating guidance.
This standard is language-agnostic by default. Language/framework-specific details belong in overlays that reference existing CC-* IDs.
Normative keywords use RFC 2119 (https://www.rfc-editor.org/rfc/rfc2119).
---
How Reviews Use This Standard
Review comments that are primarily about clean code MUST cite a CC-* rule ID.
Example:
issue (blocking): P1 CC-ERR-01 — Error is swallowed; caller cannot act on failure.
Use labeled comment intent to reduce ambiguity (https://conventionalcomments.org/).
---
Review Priority Defaults (P0–P3)
P0 Critical: security vulnerability, data loss, funds at risk, immediate exploit. P1 High: correctness bug, missing error handling, unsafe defaults, major operability gaps. P2 Medium: maintainability issues, performance hazards in plausible hot paths. P3 Low: minor clarity improvements, non-impactful refactors.
Teams MAY override priorities by repo, but the standard’s default MUST be preserved in the canonical catalog.
---
Rule ID Scheme
Format: CC-<CAT>-<NN>
CATis a stable category code.NNis a zero-padded number; IDs are never reused.- Deprecations MUST keep the old ID and point to a replacement ID.
Categories:
CC-NAMNamingCC-FUNFunctions/methodsCC-TYPTypes & data structuresCC-FLOWControl flowCC-ERRError handlingCC-OBSLogging & observabilityCC-PERFPerformance hygieneCC-SECSecurity hygieneCC-TSTTestsCC-DOCDocumentation
---
Rule Catalog (Core)
Naming (CC-NAM)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-NAM-01 | Names MUST be intention-revealing and domain-accurate. | MUST | P2 |
| CC-NAM-02 | Domain terms MUST be consistent across a bounded context. | MUST | P2 |
| CC-NAM-03 | Units, encoding, and time semantics MUST be explicit when relevant (e.g., milliseconds, utc). | MUST | P1 |
| CC-NAM-04 | Boolean names SHOULD read as predicates (is*, has*, can*). | SHOULD | P3 |
Functions/methods (CC-FUN)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-FUN-01 | Each function MUST have one dominant responsibility (describe in one sentence). | MUST | P2 |
| CC-FUN-02 | Side effects MUST be explicit (naming, return type, or API boundary). | MUST | P1 |
| CC-FUN-03 | Parameter lists SHOULD be small and cohesive; group related parameters into a type/object. | SHOULD | P2 |
| CC-FUN-04 | Pure logic SHOULD be separated from I/O to improve testability and local reasoning. | SHOULD | P2 |
| CC-FUN-05 | Bug-prone duplication SHOULD be eliminated by extracting a well-named helper or abstraction (avoid copy/paste divergence). | SHOULD | P2 |
Types & data structures (CC-TYP)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-TYP-01 | Invalid states SHOULD be unrepresentable via types/enums/value objects where practical. | SHOULD | P2 |
| CC-TYP-02 | Domain concepts SHOULD use domain types (not raw primitives) when mistakes are costly. | SHOULD | P2 |
| CC-TYP-03 | Internal representations MUST be encapsulated; callers MUST not depend on volatile structure. | MUST | P2 |
| CC-TYP-04 | Domain-significant literals (numbers/strings) SHOULD be named constants/enums with explicit units/meaning; avoid duplicated “magic” literals. | SHOULD | P3 |
Control flow (CC-FLOW)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-FLOW-01 | Control flow SHOULD be shallow; prefer guard clauses over deep nesting. | SHOULD | P2 |
| CC-FLOW-02 | Complex boolean conditions SHOULD be extracted into named predicates/helpers. | SHOULD | P3 |
| CC-FLOW-03 | Concurrency and shared mutable state MUST be explicit and safe (no hidden races). | MUST | P1 |
Error handling (CC-ERR)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-ERR-01 | Failures MUST be explicit and actionable; no silent failures or empty catches. | MUST | P1 |
| CC-ERR-02 | Errors MUST carry context (operation + identifiers) without leaking secrets/PII. | MUST | P1 |
| CC-ERR-03 | Retries MUST be bounded, use timeouts, and be safe for idempotency/duplication. | MUST | P0 |
| CC-ERR-04 | I/O calls MUST define timeout and cancellation behavior where supported. | MUST | P1 |
Logging & observability (CC-OBS)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-OBS-01 | Logs SHOULD be structured and include correlation identifiers where relevant. | SHOULD | P2 |
| CC-OBS-02 | Logs MUST NOT include secrets, credentials, or sensitive personal data. | MUST | P0 |
| CC-OBS-03 | Critical paths MUST be observable via logs/metrics/traces appropriate to risk. | MUST | P1 |
Performance hygiene (CC-PERF)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-PERF-01 | Work MUST be bounded (limits, pagination, backpressure) for untrusted or large inputs. | MUST | P1 |
| CC-PERF-02 | Obvious performance hazards MUST be avoided (N+1, O(n²) loops on growth paths). | MUST | P2 |
| CC-PERF-03 | Material performance changes SHOULD be supported by measurement in the target environment. | SHOULD | P2 |
Security hygiene (CC-SEC)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-SEC-01 | Untrusted inputs MUST be validated at trust boundaries (prefer allowlists). | MUST | P0 |
| CC-SEC-02 | Authorization MUST be enforced for every sensitive operation; no bypass paths. | MUST | P0 |
| CC-SEC-03 | Secrets MUST NOT be hardcoded or logged; use secret management and rotation. | MUST | P0 |
| CC-SEC-04 | Cryptography MUST use proven libraries and secure defaults; do not invent crypto. | MUST | P0 |
| CC-SEC-05 | Dependencies MUST be pinned/locked and managed for known vulnerabilities (SCA + timely remediation); prefer trusted sources and provenance where feasible. | MUST | P1 |
| CC-SEC-06 | Passwords MUST be stored using adaptive hashing (Argon2id/bcrypt/scrypt) with organization-approved parameters; never plaintext or reversible encryption. | MUST | P0 |
| CC-SEC-07 | Authentication/session tokens MUST be generated with a CSPRNG and have expiry/rotation per policy; validate tokens on every request. | MUST | P0 |
| CC-SEC-08 | Untrusted inputs MUST NOT be interpolated into interpreters (SQL, shell, HTML); use parameterized queries, escaping, and safe APIs to prevent injection. | MUST | P0 |
Tests (CC-TST)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-TST-01 | New behavior MUST be covered by tests; bug fixes MUST include regression tests. | MUST | P1 |
| CC-TST-02 | Tests MUST be deterministic and isolated; flakiness is a release risk. | MUST | P1 |
| CC-TST-03 | Tests SHOULD assert behavior and contracts, not incidental implementation details. | SHOULD | P2 |
| CC-TST-04 | Test types SHOULD match risk (unit/integration/e2e) rather than blanket thresholds. | SHOULD | P2 |
Documentation (CC-DOC)
| ID | Rule | Requirement | Default Priority |
|---|---|---|---|
| CC-DOC-01 | Public interfaces MUST document contracts (inputs, outputs, errors, side effects). | MUST | P2 |
| CC-DOC-02 | Comments SHOULD explain intent and tradeoffs (“why”), not restate code. | SHOULD | P3 |
| CC-DOC-03 | Critical systems SHOULD have operational docs (runbooks, alerts, dashboards) when relevant. | SHOULD | P2 |
| CC-DOC-04 | Commented-out code MUST NOT be committed; non-trivial TODO/FIXME notes MUST be actionable (tracking + owner). | MUST | P3 |
---
Language Overlays (Non-Duplicative)
Overlays MUST reference existing CC-* IDs and add only:
- How to enforce in the language/tooling.
- Accepted idioms and exceptions.
- Automation mapping (linters/formatters/SAST) to rule IDs.
Overlays MUST NOT restate the base rule text.
---
Exceptions And Waivers
Exceptions MUST be explicit, reviewable, and time-bounded when possible.
Minimum waiver fields:
cc_rule_idjustificationriskownercreated_atexpires_at(optional but preferred)tracking_issue(required for non-trivial waivers)
---
Governance
To add or change a rule:
1. Define the problem and failure modes. 2. Specify scope and exception criteria. 3. Provide at least one enforcement path (human review or automation). 4. Assign a default review priority (P0–P3) that is stable across teams. 5. Do not renumber; deprecate with replaced_by if needed.
---
Optional: AI / Automation
Use automation to reduce review fatigue and keep humans focused on risk and intent.
- Prefer mechanical enforcement in CI (formatters/linters/static analysis), and reference
CC-*IDs in tool output where possible. - For repositories on GitHub: use PR templates, CODEOWNERS, and protected branches for consistent review routing and gating (https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository, https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners, https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches).
The Clean Coder Operational Checklist
Professional conduct and discipline guidelines distilled from “The Clean Coder.”
Professional Commitments
- Say “no” clearly when scope, time, or risk make success unlikely; offer options instead of silent agreement.
- Do not promise what you cannot deliver; replace dates with evidence-based ranges and assumptions.
- Keep the codebase releasable: no breaking changes without tests and fixes in the same commit.
Coding Discipline
- Write tests first or immediately after; never ship untested code. Keep tests fast, isolated, and reliable.
- Refactor continually to keep code clean and simple; leave every file slightly better.
- Manage technical debt deliberately: log it, estimate it, and schedule repayment instead of hiding it.
Time and Estimation
- Break work into small tasks with clear definitions of done; estimate at that level.
- Communicate uncertainty and risks; update estimates as facts change.
- Protect slack for integration, debugging, and unexpected work; avoid heroics to mask bad plans.
Handling Pressure and Interruptions
- Do not code when exhausted or distracted; pause and resume when focus is back.
- Under pressure, reduce scope first, not quality; keep tests green and changes small.
- Avoid multitasking; finish and integrate small slices before starting new ones.
Collaboration and Communication
- Use pairing or swarm reviews on risky changes; switch roles (driver/navigator) and keep sessions focused.
- Ask for clarification early; document decisions, trade-offs, and constraints in code or adjacent notes.
- Give and receive feedback professionally—specific, respectful, and actionable.
Quality and Defect Handling
- Reproduce defects, add a failing test, fix, and keep the test; avoid cosmetic “fixes” without proof.
- Prefer prevention: add checks, assertions, and monitoring around defect-prone areas.
- Maintain CI discipline: commit in small increments; do not break the build. Fix red builds before new work.
Environment and Preparation
- Automate setup and repetitive tasks; keep tools/scripts versioned and reliable.
- Maintain a quiet, interruption-minimized environment for deep work; batch meetings and comms when possible.
- Invest in practice: katas, reading, and deliberate learning to raise baseline skill and speed safely.
Code Complete Operational Checklist
Pragmatic construction rules distilled from Code Complete for authors and reviewers.
Design and Decomposition
- Define responsibilities before coding; split by cohesion/coupling—high cohesion inside modules, low coupling across.
- Favor information hiding: expose minimal interfaces, keep volatile details behind stable abstractions.
- Separate policy from implementation; isolate IO/integration behind adapters to keep core logic testable.
- Choose simple designs that satisfy current requirements; avoid speculative generality.
API and Routine Design
- Write short, focused routines with single, clear purposes; keep a consistent abstraction level.
- Prefer clear parameter lists: ≤3–4 params; avoid control booleans—use enums or parameter objects.
- Make side effects explicit; separate queries (no state change) from commands (clear state change).
- Use assertions and pre/postconditions at boundaries; fail fast on invalid inputs.
Naming and Readability
- Names express intent and units; avoid abbreviations and noise words.
- Keep related code close; use vertical spacing to separate concepts; one statement per line.
- Replace magic numbers/strings with named constants; document units and ranges.
Defensive Programming
- Validate external inputs and configuration; sanitize before use.
- Handle errors once per layer; translate low-level exceptions into domain errors.
- Use guard clauses to handle error cases early; keep normal flow straight-line.
- Prefer immutable data or minimal mutation; encapsulate shared state.
Control Flow and Complexity
- Reduce nesting with early returns; avoid deeply nested conditionals/loops.
- Simplify conditionals: extract predicates into named helpers; avoid compound boolean soup.
- Break large loops or condition clusters into smaller routines; limit local variables per scope.
Data Structures
- Choose structures for clarity and operations (lookup, order, uniqueness); wrap raw collections in intention-revealing types when behavior matters.
- Avoid primitive obsession: create value objects with validation/invariants for domain concepts.
Comments and Documentation
- Comment “why” and constraints, not “what”; keep comments current or delete.
- Eliminate dead/commented-out code; keep docs near the code they describe.
Testing and Quality Gates
- Unit tests cover normal, boundary, and error cases; isolate external dependencies with fakes.
- Use systematic test checklists: null/empty, limits, ordering, concurrency, resource cleanup, locale/time.
- Measure and prevent defects early: code reading and static checks before tests catch many issues.
Construction Practices
- Build in increments; integrate frequently to shrink defect-finding latency.
- Refactor continuously: remove duplication, tighten names, simplify logic as you touch code.
- Prefer clarity over micro-optimizations; profile before tuning and after changes.
Code Tuning (when justified)
- Optimize last, guided by measurements; focus on hotspots, not entire modules.
- Preserve correctness and readability while tuning; re-run tests after each change.
Code Complexity Metrics
Practical guide to measuring code complexity, choosing the right metrics, setting thresholds, and using metrics to trigger refactoring. Covers cyclomatic complexity, cognitive complexity, Halstead metrics, function length, nesting depth, and tooling.
Contents
- Cyclomatic Complexity
- Cognitive Complexity
- Halstead Metrics
- Function Length and Size Metrics
- Nesting Depth
- Tooling
- Refactoring Decision Framework
- CC-Rule Mapping
- Anti-Patterns
- Cross-References
---
Cyclomatic Complexity
Definition
Cyclomatic complexity (CC) measures the number of linearly independent paths through a function. Each decision point (if, else, for, while, case, catch, &&, ||, ?:) adds 1 to the count.
CC = number_of_decision_points + 1Calculation Example
function processOrder(order: Order): string {
// CC starts at 1
if (!order.items.length) { // +1 → CC = 2
return 'empty';
}
if (order.total > 1000) { // +1 → CC = 3
if (order.customer.isPremium) { // +1 → CC = 4
return 'premium-high-value';
}
return 'high-value';
}
for (const item of order.items) { // +1 → CC = 5
if (item.quantity > 100) { // +1 → CC = 6
return 'bulk';
}
}
return order.customer.isPremium // +1 (ternary) → CC = 7
? 'premium-standard'
: 'standard';
}
// Final CC = 7Thresholds
| CC Range | Risk Level | Action |
|---|---|---|
| 1-5 | Low | Simple, easy to test |
| 6-10 | Moderate | Acceptable; consider simplification if growing |
| 11-20 | High | Refactor: extract methods, use strategy pattern |
| 21-50 | Very High | Must refactor; function is doing too much |
| 50+ | Critical | Emergency refactor; untestable, unmaintainable |
Per-Language Tools
| Language | Tool | Command |
|---|---|---|
| TypeScript/JS | ESLint complexity rule | "complexity": ["warn", 10] |
| TypeScript/JS | ts-complexity | npx ts-complexity src/ |
| Python | radon | radon cc src/ -a -s |
| Go | gocyclo | gocyclo -over 10 ./... |
| Rust | rust-code-analysis | rust-code-analysis -m ./src |
| Java/Kotlin | SonarQube, Checkstyle | Via CI integration |
Limitations of Cyclomatic Complexity
// CC = 11 but actually easy to read (flat switch)
function getStatusLabel(status: OrderStatus): string {
switch (status) {
case 'pending': return 'Pending';
case 'confirmed': return 'Confirmed';
case 'processing': return 'Processing';
case 'shipped': return 'Shipped';
case 'delivered': return 'Delivered';
case 'cancelled': return 'Cancelled';
case 'refunded': return 'Refunded';
case 'disputed': return 'Disputed';
case 'returned': return 'Returned';
case 'archived': return 'Archived';
default: return 'Unknown';
}
}
// High CC, but low cognitive load — this is a known limitation
// Cognitive complexity handles this case better---
Cognitive Complexity
Why Cognitive Complexity Is Better for Readability
Cognitive complexity (developed by SonarSource) measures how hard code is for a human to understand, not just how many paths exist. Key differences from cyclomatic:
| Feature | Cyclomatic | Cognitive |
|---|---|---|
| Flat switch/case | Each case adds 1 | Adds 1 total (not per case) |
| Nested conditions | Same weight as flat | Nesting adds extra penalty |
Shorthand syntax (?., ??) | Counts as branch | Reduced weight (less cognitive load) |
| Linear sequence of if/else | Counts each branch | Lower weight for sequential logic |
| Break in control flow | Not counted | Penalized (goto, break, continue) |
Calculation Rules
1. Increment for each:
- if, else if, else
- for, while, do-while
- catch
- switch
- Logical operators in conditions (sequences of && or ||)
- goto, break to label, continue to label
- Recursion
2. Nesting penalty: +1 for each level of nesting when incrementing
3. No increment for:
- Individual case labels in a switch
- Multiple sequential if/else (no nesting penalty for linear flow)Calculation Example
function processPayment(payment: Payment): Result {
if (payment.amount <= 0) { // +1 (if)
return Err('invalid amount');
}
if (payment.method === 'card') { // +1 (if)
if (payment.card.expired) { // +2 (if + nesting)
return Err('expired card');
}
if (payment.amount > 10000 // +2 (if + nesting)
&& !payment.customer.verified) { // +1 (logical operator)
return Err('verification required');
}
return chargeCard(payment);
} else if (payment.method === 'bank') { // +1 (else if)
return processBankTransfer(payment);
} else { // +1 (else)
return Err('unsupported method');
}
}
// Cognitive complexity = 9
// Cyclomatic complexity would be 7
// The nested conditions make this harder to read than CC suggestsThresholds
| Cognitive Complexity | Assessment | Action |
|---|---|---|
| 0-5 | Excellent | Easy to understand and test |
| 6-10 | Good | Acceptable for most functions |
| 11-15 | Concerning | Consider refactoring |
| 16-25 | High | Should refactor; extract nested logic |
| 25+ | Critical | Must refactor; too complex for reliable review |
Reducing Cognitive Complexity
// Before: cognitive complexity = 12
function handleRequest(req: Request): Response {
if (req.authenticated) {
if (req.method === 'GET') {
if (req.path.startsWith('/admin')) {
if (req.user.isAdmin) {
return handleAdminGet(req);
} else {
return forbidden();
}
} else {
return handleUserGet(req);
}
} else if (req.method === 'POST') {
if (req.body) {
return handlePost(req);
} else {
return badRequest('Missing body');
}
}
}
return unauthorized();
}
// After: cognitive complexity = 5 (guard clauses + extraction)
function handleRequest(req: Request): Response {
if (!req.authenticated) return unauthorized();
if (req.method === 'GET') return handleGet(req);
if (req.method === 'POST') return handlePost(req);
return methodNotAllowed();
}
function handleGet(req: Request): Response {
if (!req.path.startsWith('/admin')) return handleUserGet(req);
if (!req.user.isAdmin) return forbidden();
return handleAdminGet(req);
}
function handlePost(req: Request): Response {
if (!req.body) return badRequest('Missing body');
return processPost(req);
}---
Halstead Metrics
Overview
Halstead metrics measure code complexity based on operators and operands. Less commonly used than cyclomatic/cognitive, but useful for comparing implementations of the same algorithm.
| Metric | Formula | Measures |
|---|---|---|
| Vocabulary (n) | n1 + n2 | Unique operators + operands |
| Length (N) | N1 + N2 | Total operators + operands |
| Volume (V) | N * log2(n) | Information content |
| Difficulty (D) | (n1/2) * (N2/n2) | Error proneness |
| Effort (E) | D * V | Mental effort to understand |
| Bugs (B) | V / 3000 | Estimated bugs (rough) |
When Halstead Metrics Are Useful
| Use Case | Why |
|---|---|
| Comparing two implementations of same algorithm | Volume shows which is more concise |
| Estimating bug density | Effort correlates with defect probability |
| Benchmarking code generators | Measure output complexity |
For day-to-day code review, prefer cyclomatic + cognitive complexity. Halstead is more academic and harder to act on.
---
Function Length and Size Metrics
Guidelines by Language
| Language | Recommended Max Lines | Hard Limit | Source |
|---|---|---|---|
| TypeScript/JS | 20-30 lines | 50 lines | Community consensus |
| Python | 20-30 lines | 50 lines | PEP style guides |
| Go | 30-40 lines | 60 lines | Go community (slightly longer due to error handling) |
| Rust | 30-40 lines | 60 lines | Match arms can inflate length |
| Java/Kotlin | 20-30 lines | 50 lines | Clean Code (Robert Martin) |
Parameter Count
| Count | Assessment | Action |
|---|---|---|
| 0-2 | Ideal | Easy to understand and test |
| 3 | Acceptable | Consider if all params are necessary |
| 4-5 | Concerning | Group into object/struct |
| 6+ | Too many | Must refactor; extract parameter object |
// Too many parameters
function createUser(
name: string, email: string, age: number,
role: string, department: string, manager: string
): User { /* ... */ }
// Refactored: parameter object
interface CreateUserInput {
name: string;
email: string;
age: number;
role: string;
department: string;
manager: string;
}
function createUser(input: CreateUserInput): User { /* ... */ }File Length
| Lines | Assessment | Action |
|---|---|---|
| 0-200 | Good | Single responsibility, easy to navigate |
| 200-400 | Acceptable | Check for hidden concerns |
| 400-600 | Concerning | Look for extraction opportunities |
| 600+ | Too large | Must split; multiple responsibilities likely |
---
Nesting Depth
Why Nesting Matters
Each level of nesting requires the reader to maintain mental context. Beyond 3 levels, comprehension drops rapidly.
Nesting depth and cognitive load:
Depth 0: // ← Easy
Depth 1: if (...) { // ← Fine
Depth 2: for (...) { // ← Acceptable
Depth 3: if (...) { // ← Reader starts struggling
Depth 4: if (...) { // ← Very hard to follow
Depth 5: // ← UnacceptableReducing Nesting
Technique 1: Early returns (guard clauses)
// Before: depth 4
function process(user: User) {
if (user) {
if (user.active) {
if (user.permissions.includes('write')) {
return doWork(user);
}
}
}
return null;
}
// After: depth 1
function process(user: User) {
if (!user) return null;
if (!user.active) return null;
if (!user.permissions.includes('write')) return null;
return doWork(user);
}Technique 2: Extract helper functions
// Before: deep nesting in loop
for (const order of orders) {
if (order.status === 'pending') {
for (const item of order.items) {
if (item.inStock) {
// process...
}
}
}
}
// After: extracted
const pendingOrders = orders.filter(o => o.status === 'pending');
for (const order of pendingOrders) {
processInStockItems(order.items);
}Thresholds
| Max Nesting | Assessment |
|---|---|
| 1-2 | Excellent |
| 3 | Acceptable |
| 4 | Must refactor |
| 5+ | Emergency refactor |
---
Tooling
ESLint Complexity Rules
{
"rules": {
"complexity": ["warn", 10],
"max-depth": ["warn", 3],
"max-nested-callbacks": ["warn", 3],
"max-params": ["warn", 4],
"max-lines-per-function": ["warn", { "max": 50, "skipBlankLines": true, "skipComments": true }],
"max-lines": ["warn", { "max": 400, "skipBlankLines": true, "skipComments": true }]
}
}SonarQube (Cognitive Complexity)
SonarQube provides:
- Cognitive complexity per function (default threshold: 15)
- File-level complexity
- Complexity distribution charts
- Trend analysis over time
Integration: CI pipeline → SonarQube → quality gate
Quality gate example: No new code with cognitive complexity > 15CodeClimate
CodeClimate provides:
- Maintainability rating (A-F) per file
- Cognitive complexity
- Duplication detection
- Technical debt estimation in time units
Integration: GitHub PR checks → CodeClimate → inline commentsTool Comparison
| Tool | Metrics | Languages | Cost | Integration |
|---|---|---|---|---|
| ESLint (complexity) | Cyclomatic, depth, params, lines | JS/TS | Free | Editor, CI |
| SonarQube | Cognitive, cyclomatic, duplication | 30+ languages | Free (Community) / Paid | CI, PR |
| CodeClimate | Cognitive, duplication, maintainability | 15+ languages | Free (open source) / Paid | GitHub PR |
| Radon | Cyclomatic, Halstead, maintainability | Python | Free | CI |
| gocyclo | Cyclomatic | Go | Free | CI |
| rust-code-analysis | Cyclomatic, cognitive, Halstead | Rust, C++, JS | Free | CI |
| Biome | Limited complexity rules | JS/TS | Free | Editor, CI |
CI Integration Example
# GitHub Actions: fail if complexity exceeds threshold
- name: Check complexity
run: |
npx eslint src/ --rule 'complexity: [error, 15]' --no-eslintrc
if [ $? -ne 0 ]; then
echo "::error::Functions exceed complexity threshold of 15"
exit 1
fi---
Refactoring Decision Framework
When to Refactor Based on Metrics
Refactoring trigger decision:
1. Is the function's cyclomatic complexity > 10?
└─ Yes → Refactor: extract sub-functions, use strategy pattern
2. Is the cognitive complexity > 15?
└─ Yes → Refactor: reduce nesting, add guard clauses, extract helpers
3. Is the nesting depth > 3?
└─ Yes → Refactor: early returns, extract inner logic
4. Is the function longer than 50 lines?
└─ Yes → Refactor: extract cohesive blocks into named functions
5. Does the function have > 4 parameters?
└─ Yes → Refactor: introduce parameter object or builder
6. Is the file longer than 400 lines?
└─ Yes → Refactor: split into modules by responsibilityRefactoring Priority Matrix
| Metric Violation | Change Frequency | Priority |
|---|---|---|
| High complexity + frequently changed | Must fix NOW | P0 — Immediate |
| High complexity + rarely changed | Fix opportunistically | P2 — Next sprint |
| Moderate complexity + frequently changed | Fix soon | P1 — This sprint |
| Moderate complexity + rarely changed | Monitor | P3 — Backlog |
Refactoring Techniques by Metric
| Metric Problem | Technique | CC Rule |
|---|---|---|
| High cyclomatic complexity | Extract method, replace conditional with polymorphism | CC-FUNC |
| Deep nesting | Guard clauses, extract helper, invert conditions | CC-FUNC |
| Long function | Extract method, split by responsibility | CC-FUNC, CC-MOD |
| Many parameters | Introduce parameter object, builder pattern | CC-FUNC |
| High cognitive complexity | Flatten conditions, extract named predicates | CC-FUNC |
| Large file | Split into modules, move related functions | CC-MOD |
---
CC-Rule Mapping
| Metric | CC Rule | Application |
|---|---|---|
| Cyclomatic complexity | CC-FUNC | Functions should be small and single-purpose |
| Cognitive complexity | CC-FUNC | Functions should be easy to understand |
| Nesting depth | CC-FUNC | Reduce nesting with guard clauses |
| Function length | CC-FUNC | Extract when function exceeds 30-50 lines |
| Parameter count | CC-FUNC | Group related parameters |
| File length | CC-MOD | One responsibility per module |
| Duplication | CC-REF | DRY principle; extract shared logic |
| Halstead effort | CC-FUNC, CC-MOD | High effort indicates refactoring need |
Using Metrics in Code Review
PR review comment template:
> **CC-FUNC violation**: `processOrder()` has cognitive complexity 22
> (threshold: 15). The nested `if` at line 45 adds +3 due to nesting level.
>
> Suggested fix: Extract the discount calculation into a pure function
> `calculateDiscount()` and use guard clauses for validation.
>
> Reference: [code-complexity-metrics.md] § Reducing Cognitive Complexity---
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Metric-driven refactoring only | Refactoring code that works fine just to hit numbers | Metrics inform decisions; judgment matters |
| Ignoring metrics entirely | Complexity creeps up, bugs increase | Set CI quality gates; review periodically |
| One threshold for all code | Utility vs business logic have different needs | Allow higher thresholds for serialization, parsing |
| Measuring but not acting | Dashboards exist but no one looks at them | Tie metrics to PR checks; block on violations |
| Splitting to game metrics | Tiny functions that are harder to follow together | Functions should be cohesive; splitting must improve readability |
---
Cross-References
- clean-code-standard.md — CC-FUNC, CC-MOD, CC-REF rule definitions
- functional-programming-patterns.md — FP patterns that reduce complexity
- refactoring-operational-checklist.md — Refactoring techniques and triggers
- design-patterns-operational-checklist.md — When to use patterns to reduce complexity
- ../../software-code-review/SKILL.md — Code review practices with complexity references
- ../../qa-refactoring/SKILL.md — Refactoring execution patterns
Configuration Validation Utilities
Centralized patterns for environment variable loading with schema validation and secrets management.
Updated: December 2025 Node.js: 24 LTS | Python: 3.14+ | TypeScript: 5.7+
---
File Structure
src/
├── config/
│ ├── index.ts # Main config export
│ ├── schema.ts # Validation schema (Zod or Valibot)
│ └── secrets.ts # Secrets loader (optional)
└── types/
└── config.ts # Config types (if not inferred)---
TypeScript: Zod 3.24+
Dependencies
npm install zod@^3.24 dotenvSchema (src/config/schema.ts)
import { z } from 'zod';
// ============================================
// ZOD 3.24+ FEATURES
// ============================================
// 1. z.pipe() for transform chains
const portSchema = z.string()
.pipe(z.coerce.number().int().min(1).max(65535));
// 2. z.string().url() with protocol validation
const urlSchema = z.string().url().refine(
(url) => url.startsWith('https://') || url.startsWith('postgresql://'),
{ message: 'Must use HTTPS or postgresql://' }
);
// 3. z.string().datetime() for ISO 8601
const timestampSchema = z.string().datetime({ offset: true });
// ============================================
// ENVIRONMENT SCHEMA
// ============================================
export const envSchema = z.object({
// Server
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.string().pipe(z.coerce.number().int().min(1).max(65535)).default('3000'),
HOST: z.string().default('0.0.0.0'),
// Database
DATABASE_URL: z.string().url(),
// Authentication (secrets - see Secrets Management section)
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
JWT_ACCESS_EXPIRE: z.string().default('15m'),
JWT_REFRESH_SECRET: z.string().min(32).optional(),
JWT_REFRESH_EXPIRE: z.string().default('7d'),
// Redis (optional)
REDIS_URL: z.string().url().optional(),
// CORS - transform to array
CORS_ORIGINS: z.string()
.default('http://localhost:3000')
.transform((s) => s.split(',').map((o) => o.trim())),
// Rate limiting
RATE_LIMIT_WINDOW_MS: z.string().pipe(z.coerce.number().int().positive()).default('60000'),
RATE_LIMIT_MAX_REQUESTS: z.string().pipe(z.coerce.number().int().positive()).default('100'),
// Logging
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
// Feature flags (Zod 3.24+ branded types)
FEATURE_NEW_DASHBOARD: z.string()
.transform((s) => s.toLowerCase() === 'true')
.default('false'),
});
export type EnvConfig = z.infer<typeof envSchema>;Config Export (src/config/index.ts)
import 'dotenv/config';
import { envSchema, type EnvConfig } from './schema';
// Fail fast at startup
const parseResult = envSchema.safeParse(process.env);
if (!parseResult.success) {
const errors = parseResult.error.flatten().fieldErrors;
console.error('ERROR: Invalid environment variables:');
for (const [field, messages] of Object.entries(errors)) {
console.error(` ${field}: ${messages?.join(', ')}`);
}
process.exit(1);
}
export const config: EnvConfig = parseResult.data;
// Computed values
export const isDevelopment = config.NODE_ENV === 'development';
export const isProduction = config.NODE_ENV === 'production';
export const isTest = config.NODE_ENV === 'test';
// CORS_ORIGINS already transformed to array by schema
export const corsOrigins = config.CORS_ORIGINS;---
TypeScript: Valibot (Lightweight Alternative)
Valibot is a smaller alternative to Zod (~1KB vs ~12KB minified).
Dependencies
npm install valibot@^1.0 dotenvSchema (src/config/schema.ts)
import * as v from 'valibot';
// ============================================
// VALIBOT SCHEMA
// ============================================
export const envSchema = v.object({
// Server
NODE_ENV: v.optional(
v.picklist(['development', 'production', 'test']),
'development'
),
PORT: v.pipe(
v.optional(v.string(), '3000'),
v.transform((s) => parseInt(s, 10)),
v.number(),
v.integer(),
v.minValue(1),
v.maxValue(65535)
),
HOST: v.optional(v.string(), '0.0.0.0'),
// Database
DATABASE_URL: v.pipe(v.string(), v.url()),
// Authentication
JWT_SECRET: v.pipe(
v.string(),
v.minLength(32, 'JWT_SECRET must be at least 32 characters')
),
JWT_ACCESS_EXPIRE: v.optional(v.string(), '15m'),
JWT_REFRESH_SECRET: v.optional(
v.pipe(v.string(), v.minLength(32))
),
JWT_REFRESH_EXPIRE: v.optional(v.string(), '7d'),
// CORS - transform to array
CORS_ORIGINS: v.pipe(
v.optional(v.string(), 'http://localhost:3000'),
v.transform((s) => s.split(',').map((o) => o.trim()))
),
// Logging
LOG_LEVEL: v.optional(
v.picklist(['debug', 'info', 'warn', 'error']),
'info'
),
});
export type EnvConfig = v.InferOutput<typeof envSchema>;Config Export (src/config/index.ts)
import 'dotenv/config';
import * as v from 'valibot';
import { envSchema, type EnvConfig } from './schema';
const parseResult = v.safeParse(envSchema, process.env);
if (!parseResult.success) {
console.error('ERROR: Invalid environment variables:');
for (const issue of parseResult.issues) {
const path = issue.path?.map((p) => p.key).join('.') || 'unknown';
console.error(` ${path}: ${issue.message}`);
}
process.exit(1);
}
export const config: EnvConfig = parseResult.output;
export const isDevelopment = config.NODE_ENV === 'development';
export const isProduction = config.NODE_ENV === 'production';
export const isTest = config.NODE_ENV === 'test';Zod vs Valibot Comparison
| Feature | Zod 3.24+ | Valibot 1.x |
|---|---|---|
| Bundle size | ~12KB | ~1KB |
| API style | Method chaining | Function composition |
| TypeScript | Excellent | Excellent |
| Transforms | .transform() | v.transform() in pipe |
| Defaults | .default() | v.optional(schema, default) |
| Ecosystem | Larger | Growing |
Recommendation: Use Zod for complex validation, Valibot for size-sensitive apps.
---
Python: Pydantic 2.x
Dependencies
pip install pydantic>=2.10 pydantic-settings>=2.7Settings (src/config/settings.py)
from functools import lru_cache
from typing import Annotated
from pydantic import (
AnyHttpUrl,
Field,
PostgresDsn,
field_validator,
model_validator,
)
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore", # Ignore unknown env vars
)
# Server
app_env: str = Field(default="development", alias="APP_ENV")
app_host: str = Field(default="0.0.0.0", alias="APP_HOST")
app_port: Annotated[int, Field(ge=1, le=65535)] = Field(
default=8000, alias="APP_PORT"
)
# Database
database_url: PostgresDsn = Field(alias="DATABASE_URL")
# Authentication
jwt_secret: Annotated[str, Field(min_length=32)] = Field(alias="JWT_SECRET")
jwt_access_expire_minutes: int = Field(default=15, alias="JWT_ACCESS_EXPIRE_MINUTES")
jwt_refresh_secret: str | None = Field(default=None, alias="JWT_REFRESH_SECRET")
jwt_refresh_expire_days: int = Field(default=7, alias="JWT_REFRESH_EXPIRE_DAYS")
# Redis (optional)
redis_url: AnyHttpUrl | None = Field(default=None, alias="REDIS_URL")
# CORS
cors_origins: str = Field(
default="http://localhost:3000", alias="CORS_ORIGINS"
)
# Logging
log_level: str = Field(default="INFO", alias="LOG_LEVEL")
@field_validator("log_level")
@classmethod
def validate_log_level(cls, v: str) -> str:
allowed = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
upper = v.upper()
if upper not in allowed:
raise ValueError(f"log_level must be one of {allowed}")
return upper
@model_validator(mode="after")
def validate_refresh_secret(self) -> "Settings":
"""Ensure refresh secret is set in production."""
if self.app_env == "production" and not self.jwt_refresh_secret:
raise ValueError("JWT_REFRESH_SECRET required in production")
return self
@property
def cors_origins_list(self) -> list[str]:
return [origin.strip() for origin in self.cors_origins.split(",")]
@property
def is_development(self) -> bool:
return self.app_env == "development"
@property
def is_production(self) -> bool:
return self.app_env == "production"
@lru_cache
def get_settings() -> Settings:
return Settings()
settings = get_settings()Usage
from src.config.settings import settings
print(settings.app_port) # int, validated
print(settings.database_url) # PostgresDsn, validated
print(settings.is_production) # bool
print(settings.cors_origins_list) # list[str]---
Go 1.25+
Dependencies
go get github.com/caarlos0/env/v11
go get github.com/go-playground/validator/v10Config (internal/config/config.go)
package config
import (
"fmt"
"strings"
"time"
"github.com/caarlos0/env/v11"
"github.com/go-playground/validator/v10"
)
type Config struct {
// Server
Env string `env:"APP_ENV" envDefault:"development" validate:"oneof=development production test"`
Host string `env:"HOST" envDefault:"0.0.0.0"`
Port int `env:"PORT" envDefault:"3000" validate:"min=1,max=65535"`
// Database
DatabaseURL string `env:"DATABASE_URL,required" validate:"required,url"`
// JWT
JWTSecret string `env:"JWT_SECRET,required" validate:"required,min=32"`
JWTAccessExpire time.Duration `env:"JWT_ACCESS_EXPIRE" envDefault:"15m"`
JWTRefreshSecret string `env:"JWT_REFRESH_SECRET"`
JWTRefreshExpire time.Duration `env:"JWT_REFRESH_EXPIRE" envDefault:"168h"` // 7 days
// Redis
RedisURL string `env:"REDIS_URL"`
// CORS (comma-separated in env, parsed to slice)
CORSOriginsRaw string `env:"CORS_ORIGINS" envDefault:"http://localhost:3000"`
CORSOrigins []string `env:"-"` // Computed field
// Logging
LogLevel string `env:"LOG_LEVEL" envDefault:"info" validate:"oneof=debug info warn error"`
}
func Load() (*Config, error) {
cfg := &Config{}
// Parse environment variables
if err := env.Parse(cfg); err != nil {
return nil, fmt.Errorf("failed to parse env: %w", err)
}
// Transform CORS origins
cfg.CORSOrigins = parseCORSOrigins(cfg.CORSOriginsRaw)
// Validate struct
validate := validator.New()
if err := validate.Struct(cfg); err != nil {
return nil, fmt.Errorf("config validation failed: %w", err)
}
// Production-specific validation
if cfg.Env == "production" && cfg.JWTRefreshSecret == "" {
return nil, fmt.Errorf("JWT_REFRESH_SECRET required in production")
}
return cfg, nil
}
func parseCORSOrigins(raw string) []string {
parts := strings.Split(raw, ",")
origins := make([]string, 0, len(parts))
for _, part := range parts {
if trimmed := strings.TrimSpace(part); trimmed != "" {
origins = append(origins, trimmed)
}
}
return origins
}
func (c *Config) IsDevelopment() bool { return c.Env == "development" }
func (c *Config) IsProduction() bool { return c.Env == "production" }
func (c *Config) IsTest() bool { return c.Env == "test" }Usage
package main
import (
"log"
"myapp/internal/config"
)
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("Config error: %v", err)
}
log.Printf("Server: %s:%d (%s)", cfg.Host, cfg.Port, cfg.Env)
}---
Secrets Management
NEVER commit secrets to git. Use external secret managers for production.
1Password CLI (Recommended for Teams)
# Install 1Password CLI
brew install 1password-cli
# Authenticate
op signin
# Load secrets at runtime
export JWT_SECRET=$(op read "op://Vault/MyApp/JWT_SECRET")
export DATABASE_URL=$(op read "op://Vault/MyApp/DATABASE_URL")
# Or use op run for full env injection
op run --env-file=.env.1p -- npm start.env.1p (template for 1Password):
JWT_SECRET=op://Vault/MyApp/JWT_SECRET
DATABASE_URL=op://Vault/MyApp/DATABASE_URL
JWT_REFRESH_SECRET=op://Vault/MyApp/JWT_REFRESH_SECRETDoppler (Cloud-Native)
# Install Doppler CLI
brew install dopplerhq/cli/doppler
# Setup project
doppler setup
# Run with injected secrets
doppler run -- npm start
# Or export to .env (for local dev only)
doppler secrets download --no-file --format env > .envAWS Secrets Manager
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
export const loadSecrets = async (secretId: string): Promise<Record<string, string>> => {
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await client.send(command);
if (response.SecretString) {
return JSON.parse(response.SecretString);
}
throw new Error('Secret not found');
};
// Usage at startup
const secrets = await loadSecrets('myapp/production');
process.env.JWT_SECRET = secrets.JWT_SECRET;
process.env.DATABASE_URL = secrets.DATABASE_URL;HashiCorp Vault
import Vault from 'node-vault';
const vault = Vault({
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
export const loadVaultSecrets = async (path: string): Promise<Record<string, string>> => {
const result = await vault.read(path);
return result.data.data;
};Secret Rotation Pattern
// Auto-refresh secrets every hour
let cachedSecrets: Record<string, string> | null = null;
let lastRefresh = 0;
const REFRESH_INTERVAL = 60 * 60 * 1000; // 1 hour
export const getSecret = async (key: string): Promise<string> => {
const now = Date.now();
if (!cachedSecrets || now - lastRefresh > REFRESH_INTERVAL) {
cachedSecrets = await loadSecrets('myapp/production');
lastRefresh = now;
}
const value = cachedSecrets[key];
if (!value) {
throw new Error(`Secret ${key} not found`);
}
return value;
};---
Environment File Template
.env.example
# Server
NODE_ENV=development
PORT=3000
HOST=0.0.0.0
# Database (required)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# Authentication (required)
# Generate: openssl rand -base64 32
JWT_SECRET=your-super-secret-key-at-least-32-chars
JWT_ACCESS_EXPIRE=15m
JWT_REFRESH_SECRET=another-secret-key-at-least-32-chars
JWT_REFRESH_EXPIRE=7d
# Redis (optional)
REDIS_URL=redis://localhost:6379
# CORS (comma-separated)
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
# Rate limiting
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
# Logging
LOG_LEVEL=debug
# Feature flags
FEATURE_NEW_DASHBOARD=false.gitignore
# Environment files
.env
.env.local
.env.*.local
!.env.example
!.env.test---
Anti-Patterns
Non-Null Assertions on Environment Variables
This is the #1 cause of production crashes from missing config.
// BAD: Non-null assertion crashes at runtime if missing
const apiKey = process.env.OPENAI_API_KEY!; // Runtime crash if undefined
const dbUrl = process.env.DATABASE_URL!; // TypeScript trusts you, runtime doesn't
// BAD: Trusting environment without validation
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
// If either is missing, your app crashes with unhelpful error
// GOOD: Fail fast with clear message
function getRequiredEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
const apiKey = getRequiredEnv('OPENAI_API_KEY');
const dbUrl = getRequiredEnv('DATABASE_URL');
// BETTER: Zod validation at startup (recommended)
import { z } from 'zod';
const envSchema = z.object({
OPENAI_API_KEY: z.string().min(1, 'OPENAI_API_KEY is required'),
DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
// Optional with sensible default
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});
// Validate once at startup, export typed config
const parseResult = envSchema.safeParse(process.env);
if (!parseResult.success) {
console.error('ERROR: Environment validation failed:');
console.error(parseResult.error.flatten().fieldErrors);
process.exit(1);
}
export const env = parseResult.data;
// Now env.OPENAI_API_KEY is guaranteed to exist and is typed as stringWhy Non-Null Assertions Are Dangerous:
| Pattern | Behavior | Problem |
|---|---|---|
process.env.VAR! | TypeScript trusts you | Crashes at runtime if missing |
| `process.env.VAR \ | \ | ''` |
getRequiredEnv('VAR') | Throws with clear message | Good, but manual |
| Zod validation | Validates all at startup | Best - catches all issues early |
Scattered Config Access
// BAD: Raw process.env everywhere
const port = parseInt(process.env.PORT || '3000'); // No validation
const secret = process.env.JWT_SECRET; // Could be undefined
// GOOD: Centralized, validated config
import { config } from '@/config';
const port = config.PORT; // Already number, validated
const secret = config.JWT_SECRET; // Guaranteed to existSecrets in Code
// BAD: Hardcoded secrets
const JWT_SECRET = 'my-super-secret-key-123';
// BAD: Secrets in .env committed to git
// (in .env file committed to repo)
// GOOD: External secret manager
const JWT_SECRET = await getSecret('JWT_SECRET');
// GOOD: Environment variable from CI/CD
const JWT_SECRET = process.env.JWT_SECRET;No Startup Validation
// BAD: Fails at runtime when config is used
app.listen(process.env.PORT); // Could be undefined
// GOOD: Fails at startup with clear error
const parseResult = envSchema.safeParse(process.env);
if (!parseResult.success) {
console.error('Invalid config:', parseResult.error.flatten());
process.exit(1);
}---
Security Checklist
- [ ] No secrets in git - Use .env.example, not .env
- [ ] Minimum 32 chars for JWT secrets
- [ ] HTTPS only in production URLs
- [ ] Fail fast at startup on invalid config
- [ ] External secret manager for production (1Password, Doppler, Vault)
- [ ] Rotate secrets periodically (at least quarterly)
- [ ] Different secrets per environment (dev/staging/prod)
- [ ] Audit secret access in production
---