From 99ec5e2328a2aee9aeb0742cf8c9b824ba64fd44 Mon Sep 17 00:00:00 2001 From: Jamkris Date: Fri, 10 Apr 2026 13:18:45 +0900 Subject: [PATCH 01/32] feat: add rules directory with 89 language-specific rule files Synced from everything-claude-code. Includes: - common/ (10 files): agents, code-review, coding-style, etc. - Language-specific rules for 12 languages: cpp, csharp, dart, golang, java, kotlin, perl, php, python, rust, swift, typescript - web/ (7 files): frontend-specific rules - zh/ (11 files): Chinese translations --- rules/README.md | 111 ++++++++++++ rules/common/agents.md | 50 +++++ rules/common/code-review.md | 124 +++++++++++++ rules/common/coding-style.md | 90 +++++++++ rules/common/development-workflow.md | 44 +++++ rules/common/git-workflow.md | 24 +++ rules/common/hooks.md | 30 +++ rules/common/patterns.md | 31 ++++ rules/common/performance.md | 55 ++++++ rules/common/security.md | 29 +++ rules/common/testing.md | 57 ++++++ rules/cpp/coding-style.md | 44 +++++ rules/cpp/hooks.md | 39 ++++ rules/cpp/patterns.md | 51 ++++++ rules/cpp/security.md | 51 ++++++ rules/cpp/testing.md | 44 +++++ rules/csharp/coding-style.md | 72 ++++++++ rules/csharp/hooks.md | 25 +++ rules/csharp/patterns.md | 50 +++++ rules/csharp/security.md | 58 ++++++ rules/csharp/testing.md | 46 +++++ rules/dart/coding-style.md | 159 ++++++++++++++++ rules/dart/hooks.md | 66 +++++++ rules/dart/patterns.md | 261 +++++++++++++++++++++++++++ rules/dart/security.md | 135 ++++++++++++++ rules/dart/testing.md | 215 ++++++++++++++++++++++ rules/golang/coding-style.md | 32 ++++ rules/golang/hooks.md | 17 ++ rules/golang/patterns.md | 45 +++++ rules/golang/security.md | 34 ++++ rules/golang/testing.md | 31 ++++ rules/java/coding-style.md | 114 ++++++++++++ rules/java/hooks.md | 18 ++ rules/java/patterns.md | 146 +++++++++++++++ rules/java/security.md | 100 ++++++++++ rules/java/testing.md | 131 ++++++++++++++ rules/kotlin/coding-style.md | 86 +++++++++ rules/kotlin/hooks.md | 17 ++ rules/kotlin/patterns.md | 146 +++++++++++++++ rules/kotlin/security.md | 82 +++++++++ rules/kotlin/testing.md | 128 +++++++++++++ rules/perl/coding-style.md | 46 +++++ rules/perl/hooks.md | 22 +++ rules/perl/patterns.md | 76 ++++++++ rules/perl/security.md | 69 +++++++ rules/perl/testing.md | 54 ++++++ rules/php/coding-style.md | 40 ++++ rules/php/hooks.md | 24 +++ rules/php/patterns.md | 33 ++++ rules/php/security.md | 37 ++++ rules/php/testing.md | 39 ++++ rules/python/coding-style.md | 42 +++++ rules/python/hooks.md | 19 ++ rules/python/patterns.md | 39 ++++ rules/python/security.md | 30 +++ rules/python/testing.md | 38 ++++ rules/rust/coding-style.md | 151 ++++++++++++++++ rules/rust/hooks.md | 16 ++ rules/rust/patterns.md | 168 +++++++++++++++++ rules/rust/security.md | 141 +++++++++++++++ rules/rust/testing.md | 154 ++++++++++++++++ rules/swift/coding-style.md | 47 +++++ rules/swift/hooks.md | 20 ++ rules/swift/patterns.md | 66 +++++++ rules/swift/security.md | 33 ++++ rules/swift/testing.md | 45 +++++ rules/typescript/coding-style.md | 199 ++++++++++++++++++++ rules/typescript/hooks.md | 22 +++ rules/typescript/patterns.md | 52 ++++++ rules/typescript/security.md | 28 +++ rules/typescript/testing.md | 18 ++ rules/web/coding-style.md | 96 ++++++++++ rules/web/design-quality.md | 63 +++++++ rules/web/hooks.md | 120 ++++++++++++ rules/web/patterns.md | 79 ++++++++ rules/web/performance.md | 64 +++++++ rules/web/security.md | 57 ++++++ rules/web/testing.md | 55 ++++++ rules/zh/README.md | 108 +++++++++++ rules/zh/agents.md | 50 +++++ rules/zh/code-review.md | 124 +++++++++++++ rules/zh/coding-style.md | 48 +++++ rules/zh/development-workflow.md | 44 +++++ rules/zh/git-workflow.md | 24 +++ rules/zh/hooks.md | 30 +++ rules/zh/patterns.md | 31 ++++ rules/zh/performance.md | 55 ++++++ rules/zh/security.md | 29 +++ rules/zh/testing.md | 29 +++ 89 files changed, 5962 insertions(+) create mode 100644 rules/README.md create mode 100644 rules/common/agents.md create mode 100644 rules/common/code-review.md create mode 100644 rules/common/coding-style.md create mode 100644 rules/common/development-workflow.md create mode 100644 rules/common/git-workflow.md create mode 100644 rules/common/hooks.md create mode 100644 rules/common/patterns.md create mode 100644 rules/common/performance.md create mode 100644 rules/common/security.md create mode 100644 rules/common/testing.md create mode 100644 rules/cpp/coding-style.md create mode 100644 rules/cpp/hooks.md create mode 100644 rules/cpp/patterns.md create mode 100644 rules/cpp/security.md create mode 100644 rules/cpp/testing.md create mode 100644 rules/csharp/coding-style.md create mode 100644 rules/csharp/hooks.md create mode 100644 rules/csharp/patterns.md create mode 100644 rules/csharp/security.md create mode 100644 rules/csharp/testing.md create mode 100644 rules/dart/coding-style.md create mode 100644 rules/dart/hooks.md create mode 100644 rules/dart/patterns.md create mode 100644 rules/dart/security.md create mode 100644 rules/dart/testing.md create mode 100644 rules/golang/coding-style.md create mode 100644 rules/golang/hooks.md create mode 100644 rules/golang/patterns.md create mode 100644 rules/golang/security.md create mode 100644 rules/golang/testing.md create mode 100644 rules/java/coding-style.md create mode 100644 rules/java/hooks.md create mode 100644 rules/java/patterns.md create mode 100644 rules/java/security.md create mode 100644 rules/java/testing.md create mode 100644 rules/kotlin/coding-style.md create mode 100644 rules/kotlin/hooks.md create mode 100644 rules/kotlin/patterns.md create mode 100644 rules/kotlin/security.md create mode 100644 rules/kotlin/testing.md create mode 100644 rules/perl/coding-style.md create mode 100644 rules/perl/hooks.md create mode 100644 rules/perl/patterns.md create mode 100644 rules/perl/security.md create mode 100644 rules/perl/testing.md create mode 100644 rules/php/coding-style.md create mode 100644 rules/php/hooks.md create mode 100644 rules/php/patterns.md create mode 100644 rules/php/security.md create mode 100644 rules/php/testing.md create mode 100644 rules/python/coding-style.md create mode 100644 rules/python/hooks.md create mode 100644 rules/python/patterns.md create mode 100644 rules/python/security.md create mode 100644 rules/python/testing.md create mode 100644 rules/rust/coding-style.md create mode 100644 rules/rust/hooks.md create mode 100644 rules/rust/patterns.md create mode 100644 rules/rust/security.md create mode 100644 rules/rust/testing.md create mode 100644 rules/swift/coding-style.md create mode 100644 rules/swift/hooks.md create mode 100644 rules/swift/patterns.md create mode 100644 rules/swift/security.md create mode 100644 rules/swift/testing.md create mode 100644 rules/typescript/coding-style.md create mode 100644 rules/typescript/hooks.md create mode 100644 rules/typescript/patterns.md create mode 100644 rules/typescript/security.md create mode 100644 rules/typescript/testing.md create mode 100644 rules/web/coding-style.md create mode 100644 rules/web/design-quality.md create mode 100644 rules/web/hooks.md create mode 100644 rules/web/patterns.md create mode 100644 rules/web/performance.md create mode 100644 rules/web/security.md create mode 100644 rules/web/testing.md create mode 100644 rules/zh/README.md create mode 100644 rules/zh/agents.md create mode 100644 rules/zh/code-review.md create mode 100644 rules/zh/coding-style.md create mode 100644 rules/zh/development-workflow.md create mode 100644 rules/zh/git-workflow.md create mode 100644 rules/zh/hooks.md create mode 100644 rules/zh/patterns.md create mode 100644 rules/zh/performance.md create mode 100644 rules/zh/security.md create mode 100644 rules/zh/testing.md diff --git a/rules/README.md b/rules/README.md new file mode 100644 index 0000000..f6d9c64 --- /dev/null +++ b/rules/README.md @@ -0,0 +1,111 @@ +# Rules +## Structure + +Rules are organized into a **common** layer plus **language-specific** directories: + +``` +rules/ +├── common/ # Language-agnostic principles (always install) +│ ├── coding-style.md +│ ├── git-workflow.md +│ ├── testing.md +│ ├── performance.md +│ ├── patterns.md +│ ├── hooks.md +│ ├── agents.md +│ └── security.md +├── typescript/ # TypeScript/JavaScript specific +├── python/ # Python specific +├── golang/ # Go specific +├── web/ # Web and frontend specific +├── swift/ # Swift specific +└── php/ # PHP specific +``` + +- **common/** contains universal principles — no language-specific code examples. +- **Language directories** extend the common rules with framework-specific patterns, tools, and code examples. Each file references its common counterpart. + +## Installation + +### Option 1: Install Script (Recommended) + +```bash +# Install common + one or more language-specific rule sets +./install.sh typescript +./install.sh python +./install.sh golang +./install.sh web +./install.sh swift +./install.sh php + +# Install multiple languages at once +./install.sh typescript python +``` + +### Option 2: Manual Installation + +> **Important:** Copy entire directories — do NOT flatten with `/*`. +> Common and language-specific directories contain files with the same names. +> Flattening them into one directory causes language-specific files to overwrite +> common rules, and breaks the relative `../common/` references used by +> language-specific files. + +```bash +# Install common rules (required for all projects) +cp -r rules/common ~/.claude/rules/common + +# Install language-specific rules based on your project's tech stack +cp -r rules/typescript ~/.claude/rules/typescript +cp -r rules/python ~/.claude/rules/python +cp -r rules/golang ~/.claude/rules/golang +cp -r rules/web ~/.claude/rules/web +cp -r rules/swift ~/.claude/rules/swift +cp -r rules/php ~/.claude/rules/php + +# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only. +``` + +## Rules vs Skills + +- **Rules** define standards, conventions, and checklists that apply broadly (e.g., "80% test coverage", "no hardcoded secrets"). +- **Skills** (`skills/` directory) provide deep, actionable reference material for specific tasks (e.g., `python-patterns`, `golang-testing`). + +Language-specific rule files reference relevant skills where appropriate. Rules tell you *what* to do; skills tell you *how* to do it. + +## Adding a New Language + +To add support for a new language (e.g., `rust/`): + +1. Create a `rules/rust/` directory +2. Add files that extend the common rules: + - `coding-style.md` — formatting tools, idioms, error handling patterns + - `testing.md` — test framework, coverage tools, test organization + - `patterns.md` — language-specific design patterns + - `hooks.md` — PostToolUse hooks for formatters, linters, type checkers + - `security.md` — secret management, security scanning tools +3. Each file should start with: + ``` + > This file extends [common/xxx.md](../common/xxx.md) with specific content. + ``` +4. Reference existing skills if available, or create new ones under `skills/`. + +For non-language domains like `web/`, follow the same layered pattern when there is enough reusable domain-specific guidance to justify a standalone ruleset. + +## Rule Priority + +When language-specific rules and common rules conflict, **language-specific rules take precedence** (specific overrides general). This follows the standard layered configuration pattern (similar to CSS specificity or `.gitignore` precedence). + +- `rules/common/` defines universal defaults applicable to all projects. +- `rules/golang/`, `rules/python/`, `rules/swift/`, `rules/php/`, `rules/typescript/`, etc. override those defaults where language idioms differ. + +### Example + +`common/coding-style.md` recommends immutability as a default principle. A language-specific `golang/coding-style.md` can override this: + +> Idiomatic Go uses pointer receivers for struct mutation — see [common/coding-style.md](../common/coding-style.md) for the general principle, but Go-idiomatic mutation is preferred here. + +### Common rules with override notes + +Rules in `rules/common/` that may be overridden by language-specific files are marked with: + +> **Language note**: This rule may be overridden by language-specific rules for languages where this pattern is not idiomatic. diff --git a/rules/common/agents.md b/rules/common/agents.md new file mode 100644 index 0000000..09d6364 --- /dev/null +++ b/rules/common/agents.md @@ -0,0 +1,50 @@ +# Agent Orchestration + +## Available Agents + +Located in `~/.claude/agents/`: + +| Agent | Purpose | When to Use | +|-------|---------|-------------| +| planner | Implementation planning | Complex features, refactoring | +| architect | System design | Architectural decisions | +| tdd-guide | Test-driven development | New features, bug fixes | +| code-reviewer | Code review | After writing code | +| security-reviewer | Security analysis | Before commits | +| build-error-resolver | Fix build errors | When build fails | +| e2e-runner | E2E testing | Critical user flows | +| refactor-cleaner | Dead code cleanup | Code maintenance | +| doc-updater | Documentation | Updating docs | +| rust-reviewer | Rust code review | Rust projects | + +## Immediate Agent Usage + +No user prompt needed: +1. Complex feature requests - Use **planner** agent +2. Code just written/modified - Use **code-reviewer** agent +3. Bug fix or new feature - Use **tdd-guide** agent +4. Architectural decision - Use **architect** agent + +## Parallel Task Execution + +ALWAYS use parallel Task execution for independent operations: + +```markdown +# GOOD: Parallel execution +Launch 3 agents in parallel: +1. Agent 1: Security analysis of auth module +2. Agent 2: Performance review of cache system +3. Agent 3: Type checking of utilities + +# BAD: Sequential when unnecessary +First agent 1, then agent 2, then agent 3 +``` + +## Multi-Perspective Analysis + +For complex problems, use split role sub-agents: +- Factual reviewer +- Senior engineer +- Security expert +- Consistency reviewer +- Redundancy checker diff --git a/rules/common/code-review.md b/rules/common/code-review.md new file mode 100644 index 0000000..d79ba9b --- /dev/null +++ b/rules/common/code-review.md @@ -0,0 +1,124 @@ +# Code Review Standards + +## Purpose + +Code review ensures quality, security, and maintainability before code is merged. This rule defines when and how to conduct code reviews. + +## When to Review + +**MANDATORY review triggers:** + +- After writing or modifying code +- Before any commit to shared branches +- When security-sensitive code is changed (auth, payments, user data) +- When architectural changes are made +- Before merging pull requests + +**Pre-Review Requirements:** + +Before requesting review, ensure: + +- All automated checks (CI/CD) are passing +- Merge conflicts are resolved +- Branch is up to date with target branch + +## Review Checklist + +Before marking code complete: + +- [ ] Code is readable and well-named +- [ ] Functions are focused (<50 lines) +- [ ] Files are cohesive (<800 lines) +- [ ] No deep nesting (>4 levels) +- [ ] Errors are handled explicitly +- [ ] No hardcoded secrets or credentials +- [ ] No console.log or debug statements +- [ ] Tests exist for new functionality +- [ ] Test coverage meets 80% minimum + +## Security Review Triggers + +**STOP and use security-reviewer agent when:** + +- Authentication or authorization code +- User input handling +- Database queries +- File system operations +- External API calls +- Cryptographic operations +- Payment or financial code + +## Review Severity Levels + +| Level | Meaning | Action | +|-------|---------|--------| +| CRITICAL | Security vulnerability or data loss risk | **BLOCK** - Must fix before merge | +| HIGH | Bug or significant quality issue | **WARN** - Should fix before merge | +| MEDIUM | Maintainability concern | **INFO** - Consider fixing | +| LOW | Style or minor suggestion | **NOTE** - Optional | + +## Agent Usage + +Use these agents for code review: + +| Agent | Purpose | +|-------|---------| +| **code-reviewer** | General code quality, patterns, best practices | +| **security-reviewer** | Security vulnerabilities, OWASP Top 10 | +| **typescript-reviewer** | TypeScript/JavaScript specific issues | +| **python-reviewer** | Python specific issues | +| **go-reviewer** | Go specific issues | +| **rust-reviewer** | Rust specific issues | + +## Review Workflow + +``` +1. Run git diff to understand changes +2. Check security checklist first +3. Review code quality checklist +4. Run relevant tests +5. Verify coverage >= 80% +6. Use appropriate agent for detailed review +``` + +## Common Issues to Catch + +### Security + +- Hardcoded credentials (API keys, passwords, tokens) +- SQL injection (string concatenation in queries) +- XSS vulnerabilities (unescaped user input) +- Path traversal (unsanitized file paths) +- CSRF protection missing +- Authentication bypasses + +### Code Quality + +- Large functions (>50 lines) - split into smaller +- Large files (>800 lines) - extract modules +- Deep nesting (>4 levels) - use early returns +- Missing error handling - handle explicitly +- Mutation patterns - prefer immutable operations +- Missing tests - add test coverage + +### Performance + +- N+1 queries - use JOINs or batching +- Missing pagination - add LIMIT to queries +- Unbounded queries - add constraints +- Missing caching - cache expensive operations + +## Approval Criteria + +- **Approve**: No CRITICAL or HIGH issues +- **Warning**: Only HIGH issues (merge with caution) +- **Block**: CRITICAL issues found + +## Integration with Other Rules + +This rule works with: + +- [testing.md](testing.md) - Test coverage requirements +- [security.md](security.md) - Security checklist +- [git-workflow.md](git-workflow.md) - Commit standards +- [agents.md](agents.md) - Agent delegation diff --git a/rules/common/coding-style.md b/rules/common/coding-style.md new file mode 100644 index 0000000..e72f3f1 --- /dev/null +++ b/rules/common/coding-style.md @@ -0,0 +1,90 @@ +# Coding Style + +## Immutability (CRITICAL) + +ALWAYS create new objects, NEVER mutate existing ones: + +``` +// Pseudocode +WRONG: modify(original, field, value) → changes original in-place +CORRECT: update(original, field, value) → returns new copy with change +``` + +Rationale: Immutable data prevents hidden side effects, makes debugging easier, and enables safe concurrency. + +## Core Principles + +### KISS (Keep It Simple) + +- Prefer the simplest solution that actually works +- Avoid premature optimization +- Optimize for clarity over cleverness + +### DRY (Don't Repeat Yourself) + +- Extract repeated logic into shared functions or utilities +- Avoid copy-paste implementation drift +- Introduce abstractions when repetition is real, not speculative + +### YAGNI (You Aren't Gonna Need It) + +- Do not build features or abstractions before they are needed +- Avoid speculative generality +- Start simple, then refactor when the pressure is real + +## File Organization + +MANY SMALL FILES > FEW LARGE FILES: +- High cohesion, low coupling +- 200-400 lines typical, 800 max +- Extract utilities from large modules +- Organize by feature/domain, not by type + +## Error Handling + +ALWAYS handle errors comprehensively: +- Handle errors explicitly at every level +- Provide user-friendly error messages in UI-facing code +- Log detailed error context on the server side +- Never silently swallow errors + +## Input Validation + +ALWAYS validate at system boundaries: +- Validate all user input before processing +- Use schema-based validation where available +- Fail fast with clear error messages +- Never trust external data (API responses, user input, file content) + +## Naming Conventions + +- Variables and functions: `camelCase` with descriptive names +- Booleans: prefer `is`, `has`, `should`, or `can` prefixes +- Interfaces, types, and components: `PascalCase` +- Constants: `UPPER_SNAKE_CASE` +- Custom hooks: `camelCase` with a `use` prefix + +## Code Smells to Avoid + +### Deep Nesting + +Prefer early returns over nested conditionals once the logic starts stacking. + +### Magic Numbers + +Use named constants for meaningful thresholds, delays, and limits. + +### Long Functions + +Split large functions into focused pieces with clear responsibilities. + +## Code Quality Checklist + +Before marking work complete: +- [ ] Code is readable and well-named +- [ ] Functions are small (<50 lines) +- [ ] Files are focused (<800 lines) +- [ ] No deep nesting (>4 levels) +- [ ] Proper error handling +- [ ] No hardcoded values (use constants or config) +- [ ] No mutation (immutable patterns used) diff --git a/rules/common/development-workflow.md b/rules/common/development-workflow.md new file mode 100644 index 0000000..ae070be --- /dev/null +++ b/rules/common/development-workflow.md @@ -0,0 +1,44 @@ +# Development Workflow + +> This file extends [common/git-workflow.md](./git-workflow.md) with the full feature development process that happens before git operations. + +The Feature Implementation Workflow describes the development pipeline: research, planning, TDD, code review, and then committing to git. + +## Feature Implementation Workflow + +0. **Research & Reuse** _(mandatory before any new implementation)_ + - **GitHub code search first:** Run `gh search repos` and `gh search code` to find existing implementations, templates, and patterns before writing anything new. + - **Library docs second:** Use Context7 or primary vendor docs to confirm API behavior, package usage, and version-specific details before implementing. + - **Exa only when the first two are insufficient:** Use Exa for broader web research or discovery after GitHub search and primary docs. + - **Check package registries:** Search npm, PyPI, crates.io, and other registries before writing utility code. Prefer battle-tested libraries over hand-rolled solutions. + - **Search for adaptable implementations:** Look for open-source projects that solve 80%+ of the problem and can be forked, ported, or wrapped. + - Prefer adopting or porting a proven approach over writing net-new code when it meets the requirement. + +1. **Plan First** + - Use **planner** agent to create implementation plan + - Generate planning docs before coding: PRD, architecture, system_design, tech_doc, task_list + - Identify dependencies and risks + - Break down into phases + +2. **TDD Approach** + - Use **tdd-guide** agent + - Write tests first (RED) + - Implement to pass tests (GREEN) + - Refactor (IMPROVE) + - Verify 80%+ coverage + +3. **Code Review** + - Use **code-reviewer** agent immediately after writing code + - Address CRITICAL and HIGH issues + - Fix MEDIUM issues when possible + +4. **Commit & Push** + - Detailed commit messages + - Follow conventional commits format + - See [git-workflow.md](./git-workflow.md) for commit message format and PR process + +5. **Pre-Review Checks** + - Verify all automated checks (CI/CD) are passing + - Resolve any merge conflicts + - Ensure branch is up to date with target branch + - Only request review after these checks pass diff --git a/rules/common/git-workflow.md b/rules/common/git-workflow.md new file mode 100644 index 0000000..d57d9e2 --- /dev/null +++ b/rules/common/git-workflow.md @@ -0,0 +1,24 @@ +# Git Workflow + +## Commit Message Format +``` +: + + +``` + +Types: feat, fix, refactor, docs, test, chore, perf, ci + +Note: Attribution disabled globally via ~/.claude/settings.json. + +## Pull Request Workflow + +When creating PRs: +1. Analyze full commit history (not just latest commit) +2. Use `git diff [base-branch]...HEAD` to see all changes +3. Draft comprehensive PR summary +4. Include test plan with TODOs +5. Push with `-u` flag if new branch + +> For the full development process (planning, TDD, code review) before git operations, +> see [development-workflow.md](./development-workflow.md). diff --git a/rules/common/hooks.md b/rules/common/hooks.md new file mode 100644 index 0000000..5439408 --- /dev/null +++ b/rules/common/hooks.md @@ -0,0 +1,30 @@ +# Hooks System + +## Hook Types + +- **PreToolUse**: Before tool execution (validation, parameter modification) +- **PostToolUse**: After tool execution (auto-format, checks) +- **Stop**: When session ends (final verification) + +## Auto-Accept Permissions + +Use with caution: +- Enable for trusted, well-defined plans +- Disable for exploratory work +- Never use dangerously-skip-permissions flag +- Configure `allowedTools` in `~/.claude.json` instead + +## TodoWrite Best Practices + +Use TodoWrite tool to: +- Track progress on multi-step tasks +- Verify understanding of instructions +- Enable real-time steering +- Show granular implementation steps + +Todo list reveals: +- Out of order steps +- Missing items +- Extra unnecessary items +- Wrong granularity +- Misinterpreted requirements diff --git a/rules/common/patterns.md b/rules/common/patterns.md new file mode 100644 index 0000000..959939f --- /dev/null +++ b/rules/common/patterns.md @@ -0,0 +1,31 @@ +# Common Patterns + +## Skeleton Projects + +When implementing new functionality: +1. Search for battle-tested skeleton projects +2. Use parallel agents to evaluate options: + - Security assessment + - Extensibility analysis + - Relevance scoring + - Implementation planning +3. Clone best match as foundation +4. Iterate within proven structure + +## Design Patterns + +### Repository Pattern + +Encapsulate data access behind a consistent interface: +- Define standard operations: findAll, findById, create, update, delete +- Concrete implementations handle storage details (database, API, file, etc.) +- Business logic depends on the abstract interface, not the storage mechanism +- Enables easy swapping of data sources and simplifies testing with mocks + +### API Response Format + +Use a consistent envelope for all API responses: +- Include a success/status indicator +- Include the data payload (nullable on error) +- Include an error message field (nullable on success) +- Include metadata for paginated responses (total, page, limit) diff --git a/rules/common/performance.md b/rules/common/performance.md new file mode 100644 index 0000000..3ffff1b --- /dev/null +++ b/rules/common/performance.md @@ -0,0 +1,55 @@ +# Performance Optimization + +## Model Selection Strategy + +**Haiku 4.5** (90% of Sonnet capability, 3x cost savings): +- Lightweight agents with frequent invocation +- Pair programming and code generation +- Worker agents in multi-agent systems + +**Sonnet 4.6** (Best coding model): +- Main development work +- Orchestrating multi-agent workflows +- Complex coding tasks + +**Opus 4.5** (Deepest reasoning): +- Complex architectural decisions +- Maximum reasoning requirements +- Research and analysis tasks + +## Context Window Management + +Avoid last 20% of context window for: +- Large-scale refactoring +- Feature implementation spanning multiple files +- Debugging complex interactions + +Lower context sensitivity tasks: +- Single-file edits +- Independent utility creation +- Documentation updates +- Simple bug fixes + +## Extended Thinking + Plan Mode + +Extended thinking is enabled by default, reserving up to 31,999 tokens for internal reasoning. + +Control extended thinking via: +- **Toggle**: Option+T (macOS) / Alt+T (Windows/Linux) +- **Config**: Set `alwaysThinkingEnabled` in `~/.claude/settings.json` +- **Budget cap**: `export MAX_THINKING_TOKENS=10000` +- **Verbose mode**: Ctrl+O to see thinking output + +For complex tasks requiring deep reasoning: +1. Ensure extended thinking is enabled (on by default) +2. Enable **Plan Mode** for structured approach +3. Use multiple critique rounds for thorough analysis +4. Use split role sub-agents for diverse perspectives + +## Build Troubleshooting + +If build fails: +1. Use **build-error-resolver** agent +2. Analyze error messages +3. Fix incrementally +4. Verify after each fix diff --git a/rules/common/security.md b/rules/common/security.md new file mode 100644 index 0000000..49624c0 --- /dev/null +++ b/rules/common/security.md @@ -0,0 +1,29 @@ +# Security Guidelines + +## Mandatory Security Checks + +Before ANY commit: +- [ ] No hardcoded secrets (API keys, passwords, tokens) +- [ ] All user inputs validated +- [ ] SQL injection prevention (parameterized queries) +- [ ] XSS prevention (sanitized HTML) +- [ ] CSRF protection enabled +- [ ] Authentication/authorization verified +- [ ] Rate limiting on all endpoints +- [ ] Error messages don't leak sensitive data + +## Secret Management + +- NEVER hardcode secrets in source code +- ALWAYS use environment variables or a secret manager +- Validate that required secrets are present at startup +- Rotate any secrets that may have been exposed + +## Security Response Protocol + +If security issue found: +1. STOP immediately +2. Use **security-reviewer** agent +3. Fix CRITICAL issues before continuing +4. Rotate any exposed secrets +5. Review entire codebase for similar issues diff --git a/rules/common/testing.md b/rules/common/testing.md new file mode 100644 index 0000000..416c1c2 --- /dev/null +++ b/rules/common/testing.md @@ -0,0 +1,57 @@ +# Testing Requirements + +## Minimum Test Coverage: 80% + +Test Types (ALL required): +1. **Unit Tests** - Individual functions, utilities, components +2. **Integration Tests** - API endpoints, database operations +3. **E2E Tests** - Critical user flows (framework chosen per language) + +## Test-Driven Development + +MANDATORY workflow: +1. Write test first (RED) +2. Run test - it should FAIL +3. Write minimal implementation (GREEN) +4. Run test - it should PASS +5. Refactor (IMPROVE) +6. Verify coverage (80%+) + +## Troubleshooting Test Failures + +1. Use **tdd-guide** agent +2. Check test isolation +3. Verify mocks are correct +4. Fix implementation, not tests (unless tests are wrong) + +## Agent Support + +- **tdd-guide** - Use PROACTIVELY for new features, enforces write-tests-first + +## Test Structure (AAA Pattern) + +Prefer Arrange-Act-Assert structure for tests: + +```typescript +test('calculates similarity correctly', () => { + // Arrange + const vector1 = [1, 0, 0] + const vector2 = [0, 1, 0] + + // Act + const similarity = calculateCosineSimilarity(vector1, vector2) + + // Assert + expect(similarity).toBe(0) +}) +``` + +### Test Naming + +Use descriptive names that explain the behavior under test: + +```typescript +test('returns empty array when no markets match query', () => {}) +test('throws error when API key is missing', () => {}) +test('falls back to substring search when Redis is unavailable', () => {}) +``` diff --git a/rules/cpp/coding-style.md b/rules/cpp/coding-style.md new file mode 100644 index 0000000..3550077 --- /dev/null +++ b/rules/cpp/coding-style.md @@ -0,0 +1,44 @@ +--- +paths: + - "**/*.cpp" + - "**/*.hpp" + - "**/*.cc" + - "**/*.hh" + - "**/*.cxx" + - "**/*.h" + - "**/CMakeLists.txt" +--- +# C++ Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with C++ specific content. + +## Modern C++ (C++17/20/23) + +- Prefer **modern C++ features** over C-style constructs +- Use `auto` when the type is obvious from context +- Use `constexpr` for compile-time constants +- Use structured bindings: `auto [key, value] = map_entry;` + +## Resource Management + +- **RAII everywhere** — no manual `new`/`delete` +- Use `std::unique_ptr` for exclusive ownership +- Use `std::shared_ptr` only when shared ownership is truly needed +- Use `std::make_unique` / `std::make_shared` over raw `new` + +## Naming Conventions + +- Types/Classes: `PascalCase` +- Functions/Methods: `snake_case` or `camelCase` (follow project convention) +- Constants: `kPascalCase` or `UPPER_SNAKE_CASE` +- Namespaces: `lowercase` +- Member variables: `snake_case_` (trailing underscore) or `m_` prefix + +## Formatting + +- Use **clang-format** — no style debates +- Run `clang-format -i ` before committing + +## Reference + +See skill: `cpp-coding-standards` for comprehensive C++ coding standards and guidelines. diff --git a/rules/cpp/hooks.md b/rules/cpp/hooks.md new file mode 100644 index 0000000..4ab677a --- /dev/null +++ b/rules/cpp/hooks.md @@ -0,0 +1,39 @@ +--- +paths: + - "**/*.cpp" + - "**/*.hpp" + - "**/*.cc" + - "**/*.hh" + - "**/*.cxx" + - "**/*.h" + - "**/CMakeLists.txt" +--- +# C++ Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with C++ specific content. + +## Build Hooks + +Run these checks before committing C++ changes: + +```bash +# Format check +clang-format --dry-run --Werror src/*.cpp src/*.hpp + +# Static analysis +clang-tidy src/*.cpp -- -std=c++17 + +# Build +cmake --build build + +# Tests +ctest --test-dir build --output-on-failure +``` + +## Recommended CI Pipeline + +1. **clang-format** — formatting check +2. **clang-tidy** — static analysis +3. **cppcheck** — additional analysis +4. **cmake build** — compilation +5. **ctest** — test execution with sanitizers diff --git a/rules/cpp/patterns.md b/rules/cpp/patterns.md new file mode 100644 index 0000000..0c156e8 --- /dev/null +++ b/rules/cpp/patterns.md @@ -0,0 +1,51 @@ +--- +paths: + - "**/*.cpp" + - "**/*.hpp" + - "**/*.cc" + - "**/*.hh" + - "**/*.cxx" + - "**/*.h" + - "**/CMakeLists.txt" +--- +# C++ Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with C++ specific content. + +## RAII (Resource Acquisition Is Initialization) + +Tie resource lifetime to object lifetime: + +```cpp +class FileHandle { +public: + explicit FileHandle(const std::string& path) : file_(std::fopen(path.c_str(), "r")) {} + ~FileHandle() { if (file_) std::fclose(file_); } + FileHandle(const FileHandle&) = delete; + FileHandle& operator=(const FileHandle&) = delete; +private: + std::FILE* file_; +}; +``` + +## Rule of Five/Zero + +- **Rule of Zero**: Prefer classes that need no custom destructor, copy/move constructors, or assignments +- **Rule of Five**: If you define any of destructor/copy-ctor/copy-assign/move-ctor/move-assign, define all five + +## Value Semantics + +- Pass small/trivial types by value +- Pass large types by `const&` +- Return by value (rely on RVO/NRVO) +- Use move semantics for sink parameters + +## Error Handling + +- Use exceptions for exceptional conditions +- Use `std::optional` for values that may not exist +- Use `std::expected` (C++23) or result types for expected failures + +## Reference + +See skill: `cpp-coding-standards` for comprehensive C++ patterns and anti-patterns. diff --git a/rules/cpp/security.md b/rules/cpp/security.md new file mode 100644 index 0000000..0ee9f5f --- /dev/null +++ b/rules/cpp/security.md @@ -0,0 +1,51 @@ +--- +paths: + - "**/*.cpp" + - "**/*.hpp" + - "**/*.cc" + - "**/*.hh" + - "**/*.cxx" + - "**/*.h" + - "**/CMakeLists.txt" +--- +# C++ Security + +> This file extends [common/security.md](../common/security.md) with C++ specific content. + +## Memory Safety + +- Never use raw `new`/`delete` — use smart pointers +- Never use C-style arrays — use `std::array` or `std::vector` +- Never use `malloc`/`free` — use C++ allocation +- Avoid `reinterpret_cast` unless absolutely necessary + +## Buffer Overflows + +- Use `std::string` over `char*` +- Use `.at()` for bounds-checked access when safety matters +- Never use `strcpy`, `strcat`, `sprintf` — use `std::string` or `fmt::format` + +## Undefined Behavior + +- Always initialize variables +- Avoid signed integer overflow +- Never dereference null or dangling pointers +- Use sanitizers in CI: + ```bash + cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" .. + ``` + +## Static Analysis + +- Use **clang-tidy** for automated checks: + ```bash + clang-tidy --checks='*' src/*.cpp + ``` +- Use **cppcheck** for additional analysis: + ```bash + cppcheck --enable=all src/ + ``` + +## Reference + +See skill: `cpp-coding-standards` for detailed security guidelines. diff --git a/rules/cpp/testing.md b/rules/cpp/testing.md new file mode 100644 index 0000000..7c28355 --- /dev/null +++ b/rules/cpp/testing.md @@ -0,0 +1,44 @@ +--- +paths: + - "**/*.cpp" + - "**/*.hpp" + - "**/*.cc" + - "**/*.hh" + - "**/*.cxx" + - "**/*.h" + - "**/CMakeLists.txt" +--- +# C++ Testing + +> This file extends [common/testing.md](../common/testing.md) with C++ specific content. + +## Framework + +Use **GoogleTest** (gtest/gmock) with **CMake/CTest**. + +## Running Tests + +```bash +cmake --build build && ctest --test-dir build --output-on-failure +``` + +## Coverage + +```bash +cmake -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" .. +cmake --build . +ctest --output-on-failure +lcov --capture --directory . --output-file coverage.info +``` + +## Sanitizers + +Always run tests with sanitizers in CI: + +```bash +cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" .. +``` + +## Reference + +See skill: `cpp-testing` for detailed C++ testing patterns, TDD workflow, and GoogleTest/GMock usage. diff --git a/rules/csharp/coding-style.md b/rules/csharp/coding-style.md new file mode 100644 index 0000000..d97aaad --- /dev/null +++ b/rules/csharp/coding-style.md @@ -0,0 +1,72 @@ +--- +paths: + - "**/*.cs" + - "**/*.csx" +--- +# C# Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with C#-specific content. + +## Standards + +- Follow current .NET conventions and enable nullable reference types +- Prefer explicit access modifiers on public and internal APIs +- Keep files aligned with the primary type they define + +## Types and Models + +- Prefer `record` or `record struct` for immutable value-like models +- Use `class` for entities or types with identity and lifecycle +- Use `interface` for service boundaries and abstractions +- Avoid `dynamic` in application code; prefer generics or explicit models + +```csharp +public sealed record UserDto(Guid Id, string Email); + +public interface IUserRepository +{ + Task FindByIdAsync(Guid id, CancellationToken cancellationToken); +} +``` + +## Immutability + +- Prefer `init` setters, constructor parameters, and immutable collections for shared state +- Do not mutate input models in-place when producing updated state + +```csharp +public sealed record UserProfile(string Name, string Email); + +public static UserProfile Rename(UserProfile profile, string name) => + profile with { Name = name }; +``` + +## Async and Error Handling + +- Prefer `async`/`await` over blocking calls like `.Result` or `.Wait()` +- Pass `CancellationToken` through public async APIs +- Throw specific exceptions and log with structured properties + +```csharp +public async Task LoadOrderAsync( + Guid orderId, + CancellationToken cancellationToken) +{ + try + { + return await repository.FindAsync(orderId, cancellationToken) + ?? throw new InvalidOperationException($"Order {orderId} was not found."); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to load order {OrderId}", orderId); + throw; + } +} +``` + +## Formatting + +- Use `dotnet format` for formatting and analyzer fixes +- Keep `using` directives organized and remove unused imports +- Prefer expression-bodied members only when they stay readable diff --git a/rules/csharp/hooks.md b/rules/csharp/hooks.md new file mode 100644 index 0000000..f7a46ef --- /dev/null +++ b/rules/csharp/hooks.md @@ -0,0 +1,25 @@ +--- +paths: + - "**/*.cs" + - "**/*.csx" + - "**/*.csproj" + - "**/*.sln" + - "**/Directory.Build.props" + - "**/Directory.Build.targets" +--- +# C# Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with C#-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **dotnet format**: Auto-format edited C# files and apply analyzer fixes +- **dotnet build**: Verify the solution or project still compiles after edits +- **dotnet test --no-build**: Re-run the nearest relevant test project after behavior changes + +## Stop Hooks + +- Run a final `dotnet build` before ending a session with broad C# changes +- Warn on modified `appsettings*.json` files so secrets do not get committed diff --git a/rules/csharp/patterns.md b/rules/csharp/patterns.md new file mode 100644 index 0000000..a94aba7 --- /dev/null +++ b/rules/csharp/patterns.md @@ -0,0 +1,50 @@ +--- +paths: + - "**/*.cs" + - "**/*.csx" +--- +# C# Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with C#-specific content. + +## API Response Pattern + +```csharp +public sealed record ApiResponse( + bool Success, + T? Data = default, + string? Error = null, + object? Meta = null); +``` + +## Repository Pattern + +```csharp +public interface IRepository +{ + Task> FindAllAsync(CancellationToken cancellationToken); + Task FindByIdAsync(Guid id, CancellationToken cancellationToken); + Task CreateAsync(T entity, CancellationToken cancellationToken); + Task UpdateAsync(T entity, CancellationToken cancellationToken); + Task DeleteAsync(Guid id, CancellationToken cancellationToken); +} +``` + +## Options Pattern + +Use strongly typed options for config instead of reading raw strings throughout the codebase. + +```csharp +public sealed class PaymentsOptions +{ + public const string SectionName = "Payments"; + public required string BaseUrl { get; init; } + public required string ApiKeySecretName { get; init; } +} +``` + +## Dependency Injection + +- Depend on interfaces at service boundaries +- Keep constructors focused; if a service needs too many dependencies, split responsibilities +- Register lifetimes intentionally: singleton for stateless/shared services, scoped for request data, transient for lightweight pure workers diff --git a/rules/csharp/security.md b/rules/csharp/security.md new file mode 100644 index 0000000..9eb7c23 --- /dev/null +++ b/rules/csharp/security.md @@ -0,0 +1,58 @@ +--- +paths: + - "**/*.cs" + - "**/*.csx" + - "**/*.csproj" + - "**/appsettings*.json" +--- +# C# Security + +> This file extends [common/security.md](../common/security.md) with C#-specific content. + +## Secret Management + +- Never hardcode API keys, tokens, or connection strings in source code +- Use environment variables, user secrets for local development, and a secret manager in production +- Keep `appsettings.*.json` free of real credentials + +```csharp +// BAD +const string ApiKey = "sk-live-123"; + +// GOOD +var apiKey = builder.Configuration["OpenAI:ApiKey"] + ?? throw new InvalidOperationException("OpenAI:ApiKey is not configured."); +``` + +## SQL Injection Prevention + +- Always use parameterized queries with ADO.NET, Dapper, or EF Core +- Never concatenate user input into SQL strings +- Validate sort fields and filter operators before using dynamic query composition + +```csharp +const string sql = "SELECT * FROM Orders WHERE CustomerId = @customerId"; +await connection.QueryAsync(sql, new { customerId }); +``` + +## Input Validation + +- Validate DTOs at the application boundary +- Use data annotations, FluentValidation, or explicit guard clauses +- Reject invalid model state before running business logic + +## Authentication and Authorization + +- Prefer framework auth handlers instead of custom token parsing +- Enforce authorization policies at endpoint or handler boundaries +- Never log raw tokens, passwords, or PII + +## Error Handling + +- Return safe client-facing messages +- Log detailed exceptions with structured context server-side +- Do not expose stack traces, SQL text, or filesystem paths in API responses + +## References + +See skill: `security-review` for broader application security review checklists. diff --git a/rules/csharp/testing.md b/rules/csharp/testing.md new file mode 100644 index 0000000..a00f012 --- /dev/null +++ b/rules/csharp/testing.md @@ -0,0 +1,46 @@ +--- +paths: + - "**/*.cs" + - "**/*.csx" + - "**/*.csproj" +--- +# C# Testing + +> This file extends [common/testing.md](../common/testing.md) with C#-specific content. + +## Test Framework + +- Prefer **xUnit** for unit and integration tests +- Use **FluentAssertions** for readable assertions +- Use **Moq** or **NSubstitute** for mocking dependencies +- Use **Testcontainers** when integration tests need real infrastructure + +## Test Organization + +- Mirror `src/` structure under `tests/` +- Separate unit, integration, and end-to-end coverage clearly +- Name tests by behavior, not implementation details + +```csharp +public sealed class OrderServiceTests +{ + [Fact] + public async Task FindByIdAsync_ReturnsOrder_WhenOrderExists() + { + // Arrange + // Act + // Assert + } +} +``` + +## ASP.NET Core Integration Tests + +- Use `WebApplicationFactory` for API integration coverage +- Test auth, validation, and serialization through HTTP, not by bypassing middleware + +## Coverage + +- Target 80%+ line coverage +- Focus coverage on domain logic, validation, auth, and failure paths +- Run `dotnet test` in CI with coverage collection enabled where available diff --git a/rules/dart/coding-style.md b/rules/dart/coding-style.md new file mode 100644 index 0000000..f79c1fc --- /dev/null +++ b/rules/dart/coding-style.md @@ -0,0 +1,159 @@ +--- +paths: + - "**/*.dart" + - "**/pubspec.yaml" + - "**/analysis_options.yaml" +--- +# Dart/Flutter Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Dart and Flutter-specific content. + +## Formatting + +- **dart format** for all `.dart` files — enforced in CI (`dart format --set-exit-if-changed .`) +- Line length: 80 characters (dart format default) +- Trailing commas on multi-line argument/parameter lists to improve diffs and formatting + +## Immutability + +- Prefer `final` for local variables and `const` for compile-time constants +- Use `const` constructors wherever all fields are `final` +- Return unmodifiable collections from public APIs (`List.unmodifiable`, `Map.unmodifiable`) +- Use `copyWith()` for state mutations in immutable state classes + +```dart +// BAD +var count = 0; +List items = ['a', 'b']; + +// GOOD +final count = 0; +const items = ['a', 'b']; +``` + +## Naming + +Follow Dart conventions: +- `camelCase` for variables, parameters, and named constructors +- `PascalCase` for classes, enums, typedefs, and extensions +- `snake_case` for file names and library names +- `SCREAMING_SNAKE_CASE` for constants declared with `const` at top level +- Prefix private members with `_` +- Extension names describe the type they extend: `StringExtensions`, not `MyHelpers` + +## Null Safety + +- Avoid `!` (bang operator) — prefer `?.`, `??`, `if (x != null)`, or Dart 3 pattern matching; reserve `!` only where a null value is a programming error and crashing is the right behaviour +- Avoid `late` unless initialization is guaranteed before first use (prefer nullable or constructor init) +- Use `required` for constructor parameters that must always be provided + +```dart +// BAD — crashes at runtime if user is null +final name = user!.name; + +// GOOD — null-aware operators +final name = user?.name ?? 'Unknown'; + +// GOOD — Dart 3 pattern matching (exhaustive, compiler-checked) +final name = switch (user) { + User(:final name) => name, + null => 'Unknown', +}; + +// GOOD — early-return null guard +String getUserName(User? user) { + if (user == null) return 'Unknown'; + return user.name; // promoted to non-null after the guard +} +``` + +## Sealed Types and Pattern Matching (Dart 3+) + +Use sealed classes to model closed state hierarchies: + +```dart +sealed class AsyncState { + const AsyncState(); +} + +final class Loading extends AsyncState { + const Loading(); +} + +final class Success extends AsyncState { + const Success(this.data); + final T data; +} + +final class Failure extends AsyncState { + const Failure(this.error); + final Object error; +} +``` + +Always use exhaustive `switch` with sealed types — no default/wildcard: + +```dart +// BAD +if (state is Loading) { ... } + +// GOOD +return switch (state) { + Loading() => const CircularProgressIndicator(), + Success(:final data) => DataWidget(data), + Failure(:final error) => ErrorWidget(error.toString()), +}; +``` + +## Error Handling + +- Specify exception types in `on` clauses — never use bare `catch (e)` +- Never catch `Error` subtypes — they indicate programming bugs +- Use `Result`-style types or sealed classes for recoverable errors +- Avoid using exceptions for control flow + +```dart +// BAD +try { + await fetchUser(); +} catch (e) { + log(e.toString()); +} + +// GOOD +try { + await fetchUser(); +} on NetworkException catch (e) { + log('Network error: ${e.message}'); +} on NotFoundException { + handleNotFound(); +} +``` + +## Async / Futures + +- Always `await` Futures or explicitly call `unawaited()` to signal intentional fire-and-forget +- Never mark a function `async` if it never `await`s anything +- Use `Future.wait` / `Future.any` for concurrent operations +- Check `context.mounted` before using `BuildContext` after any `await` (Flutter 3.7+) + +```dart +// BAD — ignoring Future +fetchData(); // fire-and-forget without marking intent + +// GOOD +unawaited(fetchData()); // explicit fire-and-forget +await fetchData(); // or properly awaited +``` + +## Imports + +- Use `package:` imports throughout — never relative imports (`../`) for cross-feature or cross-layer code +- Order: `dart:` → external `package:` → internal `package:` (same package) +- No unused imports — `dart analyze` enforces this with `unused_import` + +## Code Generation + +- Generated files (`.g.dart`, `.freezed.dart`, `.gr.dart`) must be committed or gitignored consistently — pick one strategy per project +- Never manually edit generated files +- Keep generator annotations (`@JsonSerializable`, `@freezed`, `@riverpod`, etc.) on the canonical source file only diff --git a/rules/dart/hooks.md b/rules/dart/hooks.md new file mode 100644 index 0000000..d120efb --- /dev/null +++ b/rules/dart/hooks.md @@ -0,0 +1,66 @@ +--- +paths: + - "**/*.dart" + - "**/pubspec.yaml" + - "**/analysis_options.yaml" +--- +# Dart/Flutter Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Dart and Flutter-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **dart format**: Auto-format `.dart` files after edit +- **dart analyze**: Run static analysis after editing Dart files and surface warnings +- **flutter test**: Optionally run affected tests after significant changes + +## Recommended Hook Configuration + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": { "tool_name": "Edit", "file_paths": ["**/*.dart"] }, + "hooks": [ + { "type": "command", "command": "dart format $CLAUDE_FILE_PATHS" } + ] + } + ] + } +} +``` + +## Pre-commit Checks + +Run before committing Dart/Flutter changes: + +```bash +dart format --set-exit-if-changed . +dart analyze --fatal-infos +flutter test +``` + +## Useful One-liners + +```bash +# Format all Dart files +dart format . + +# Analyze and report issues +dart analyze + +# Run all tests with coverage +flutter test --coverage + +# Regenerate code-gen files +dart run build_runner build --delete-conflicting-outputs + +# Check for outdated packages +flutter pub outdated + +# Upgrade packages within constraints +flutter pub upgrade +``` diff --git a/rules/dart/patterns.md b/rules/dart/patterns.md new file mode 100644 index 0000000..94bf41b --- /dev/null +++ b/rules/dart/patterns.md @@ -0,0 +1,261 @@ +--- +paths: + - "**/*.dart" + - "**/pubspec.yaml" +--- +# Dart/Flutter Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Dart, Flutter, and common ecosystem-specific content. + +## Repository Pattern + +```dart +abstract interface class UserRepository { + Future getById(String id); + Future> getAll(); + Stream> watchAll(); + Future save(User user); + Future delete(String id); +} + +class UserRepositoryImpl implements UserRepository { + const UserRepositoryImpl(this._remote, this._local); + + final UserRemoteDataSource _remote; + final UserLocalDataSource _local; + + @override + Future getById(String id) async { + final local = await _local.getById(id); + if (local != null) return local; + final remote = await _remote.getById(id); + if (remote != null) await _local.save(remote); + return remote; + } + + @override + Future> getAll() async { + final remote = await _remote.getAll(); + for (final user in remote) { + await _local.save(user); + } + return remote; + } + + @override + Stream> watchAll() => _local.watchAll(); + + @override + Future save(User user) => _local.save(user); + + @override + Future delete(String id) async { + await _remote.delete(id); + await _local.delete(id); + } +} +``` + +## State Management: BLoC/Cubit + +```dart +// Cubit — simple state transitions +class CounterCubit extends Cubit { + CounterCubit() : super(0); + + void increment() => emit(state + 1); + void decrement() => emit(state - 1); +} + +// BLoC — event-driven +@immutable +sealed class CartEvent {} +class CartItemAdded extends CartEvent { CartItemAdded(this.item); final Item item; } +class CartItemRemoved extends CartEvent { CartItemRemoved(this.id); final String id; } +class CartCleared extends CartEvent {} + +@immutable +class CartState { + const CartState({this.items = const []}); + final List items; + CartState copyWith({List? items}) => CartState(items: items ?? this.items); +} + +class CartBloc extends Bloc { + CartBloc() : super(const CartState()) { + on((event, emit) => + emit(state.copyWith(items: [...state.items, event.item]))); + on((event, emit) => + emit(state.copyWith(items: state.items.where((i) => i.id != event.id).toList()))); + on((_, emit) => emit(const CartState())); + } +} +``` + +## State Management: Riverpod + +```dart +// Simple provider +@riverpod +Future> users(Ref ref) async { + final repo = ref.watch(userRepositoryProvider); + return repo.getAll(); +} + +// Notifier for mutable state +@riverpod +class CartNotifier extends _$CartNotifier { + @override + List build() => []; + + void add(Item item) => state = [...state, item]; + void remove(String id) => state = state.where((i) => i.id != id).toList(); + void clear() => state = []; +} + +// ConsumerWidget +class CartPage extends ConsumerWidget { + const CartPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final items = ref.watch(cartNotifierProvider); + return ListView( + children: items.map((item) => CartItemTile(item: item)).toList(), + ); + } +} +``` + +## Dependency Injection + +Constructor injection is preferred. Use `get_it` or Riverpod providers at composition root: + +```dart +// get_it registration (in a setup file) +void setupDependencies() { + final di = GetIt.instance; + di.registerSingleton(ApiClient(baseUrl: Env.apiUrl)); + di.registerSingleton( + UserRepositoryImpl(di(), di()), + ); + di.registerFactory(() => UserListViewModel(di())); +} +``` + +## ViewModel Pattern (without BLoC/Riverpod) + +```dart +class UserListViewModel extends ChangeNotifier { + UserListViewModel(this._repository); + + final UserRepository _repository; + + AsyncState> _state = const Loading(); + AsyncState> get state => _state; + + Future load() async { + _state = const Loading(); + notifyListeners(); + try { + final users = await _repository.getAll(); + _state = Success(users); + } on Exception catch (e) { + _state = Failure(e); + } + notifyListeners(); + } +} +``` + +## UseCase Pattern + +```dart +class GetUserUseCase { + const GetUserUseCase(this._repository); + final UserRepository _repository; + + Future call(String id) => _repository.getById(id); +} + +class CreateUserUseCase { + const CreateUserUseCase(this._repository, this._idGenerator); + final UserRepository _repository; + final IdGenerator _idGenerator; // injected — domain layer must not depend on uuid package directly + + Future call(CreateUserInput input) async { + // Validate, apply business rules, then persist + final user = User(id: _idGenerator.generate(), name: input.name, email: input.email); + await _repository.save(user); + } +} +``` + +## Immutable State with freezed + +```dart +@freezed +class UserState with _$UserState { + const factory UserState({ + @Default([]) List users, + @Default(false) bool isLoading, + String? errorMessage, + }) = _UserState; +} +``` + +## Clean Architecture Layer Boundaries + +``` +lib/ +├── domain/ # Pure Dart — no Flutter, no external packages +│ ├── entities/ +│ ├── repositories/ # Abstract interfaces +│ └── usecases/ +├── data/ # Implements domain interfaces +│ ├── datasources/ +│ ├── models/ # DTOs with fromJson/toJson +│ └── repositories/ +└── presentation/ # Flutter widgets + state management + ├── pages/ + ├── widgets/ + └── providers/ (or blocs/ or viewmodels/) +``` + +- Domain must not import `package:flutter` or any data-layer package +- Data layer maps DTOs to domain entities at repository boundaries +- Presentation calls use cases, not repositories directly + +## Navigation (GoRouter) + +```dart +final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/users/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return UserDetailPage(userId: id); + }, + ), + ], + // refreshListenable re-evaluates redirect whenever auth state changes + refreshListenable: GoRouterRefreshStream(authCubit.stream), + redirect: (context, state) { + final isLoggedIn = context.read().state is AuthAuthenticated; + if (!isLoggedIn && !state.matchedLocation.startsWith('/login')) { + return '/login'; + } + return null; + }, +); +``` + +## References + +See skill: `flutter-dart-code-review` for the comprehensive review checklist. +See skill: `compose-multiplatform-patterns` for Kotlin Multiplatform/Flutter interop patterns. diff --git a/rules/dart/security.md b/rules/dart/security.md new file mode 100644 index 0000000..74d9643 --- /dev/null +++ b/rules/dart/security.md @@ -0,0 +1,135 @@ +--- +paths: + - "**/*.dart" + - "**/pubspec.yaml" + - "**/AndroidManifest.xml" + - "**/Info.plist" +--- +# Dart/Flutter Security + +> This file extends [common/security.md](../common/security.md) with Dart, Flutter, and mobile-specific content. + +## Secrets Management + +- Never hardcode API keys, tokens, or credentials in Dart source +- Use `--dart-define` or `--dart-define-from-file` for compile-time config (values are not truly secret — use a backend proxy for server-side secrets) +- Use `flutter_dotenv` or equivalent, with `.env` files listed in `.gitignore` +- Store runtime secrets in platform-secure storage: `flutter_secure_storage` (Keychain on iOS, EncryptedSharedPreferences on Android) + +```dart +// BAD +const apiKey = 'sk-abc123...'; + +// GOOD — compile-time config (not secret, just configurable) +const apiKey = String.fromEnvironment('API_KEY'); + +// GOOD — runtime secret from secure storage +final token = await secureStorage.read(key: 'auth_token'); +``` + +## Network Security + +- Enforce HTTPS — no `http://` calls in production +- Configure Android `network_security_config.xml` to block cleartext traffic +- Set `NSAppTransportSecurity` in `Info.plist` to disallow arbitrary loads +- Set request timeouts on all HTTP clients — never leave defaults +- Consider certificate pinning for high-security endpoints + +```dart +// Dio with timeout and HTTPS enforcement +final dio = Dio(BaseOptions( + baseUrl: 'https://api.example.com', + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 30), +)); +``` + +## Input Validation + +- Validate and sanitize all user input before sending to API or storage +- Never pass unsanitized input to SQL queries — use parameterized queries (sqflite, drift) +- Sanitize deep link URLs before navigation — validate scheme, host, and path parameters +- Use `Uri.tryParse` and validate before navigating + +```dart +// BAD — SQL injection +await db.rawQuery("SELECT * FROM users WHERE email = '$userInput'"); + +// GOOD — parameterized +await db.query('users', where: 'email = ?', whereArgs: [userInput]); + +// BAD — unvalidated deep link +final uri = Uri.parse(incomingLink); +context.go(uri.path); // could navigate to any route + +// GOOD — validated deep link +final uri = Uri.tryParse(incomingLink); +if (uri != null && uri.host == 'myapp.com' && _allowedPaths.contains(uri.path)) { + context.go(uri.path); +} +``` + +## Data Protection + +- Store tokens, PII, and credentials only in `flutter_secure_storage` +- Never write sensitive data to `SharedPreferences` or local files in plaintext +- Clear auth state on logout: tokens, cached user data, cookies +- Use biometric authentication (`local_auth`) for sensitive operations +- Avoid logging sensitive data — no `print(token)` or `debugPrint(password)` + +## Android-Specific + +- Declare only required permissions in `AndroidManifest.xml` +- Export Android components (`Activity`, `Service`, `BroadcastReceiver`) only when necessary; add `android:exported="false"` where not needed +- Review intent filters — exported components with implicit intent filters are accessible by any app +- Use `FLAG_SECURE` for screens displaying sensitive data (prevents screenshots) + +```xml + + + + + +``` + +## iOS-Specific + +- Declare only required usage descriptions in `Info.plist` (`NSCameraUsageDescription`, etc.) +- Store secrets in Keychain — `flutter_secure_storage` uses Keychain on iOS +- Use App Transport Security (ATS) — disallow arbitrary loads +- Enable data protection entitlement for sensitive files + +## WebView Security + +- Use `webview_flutter` v4+ (`WebViewController` / `WebViewWidget`) — the legacy `WebView` widget is removed +- Disable JavaScript unless explicitly required (`JavaScriptMode.disabled`) +- Validate URLs before loading — never load arbitrary URLs from deep links +- Never expose Dart callbacks to JavaScript unless absolutely needed and carefully sandboxed +- Use `NavigationDelegate.onNavigationRequest` to intercept and validate navigation requests + +```dart +// webview_flutter v4+ API (WebViewController + WebViewWidget) +final controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.disabled) // disabled unless required + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (request) { + final uri = Uri.tryParse(request.url); + if (uri == null || uri.host != 'trusted.example.com') { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ); + +// In your widget tree: +WebViewWidget(controller: controller) +``` + +## Obfuscation and Build Security + +- Enable obfuscation in release builds: `flutter build apk --obfuscate --split-debug-info=./debug-info/` +- Keep `--split-debug-info` output out of version control (used for crash symbolication only) +- Ensure ProGuard/R8 rules don't inadvertently expose serialized classes +- Run `flutter analyze` and address all warnings before release diff --git a/rules/dart/testing.md b/rules/dart/testing.md new file mode 100644 index 0000000..bd6ff62 --- /dev/null +++ b/rules/dart/testing.md @@ -0,0 +1,215 @@ +--- +paths: + - "**/*.dart" + - "**/pubspec.yaml" + - "**/analysis_options.yaml" +--- +# Dart/Flutter Testing + +> This file extends [common/testing.md](../common/testing.md) with Dart and Flutter-specific content. + +## Test Framework + +- **flutter_test** / **dart:test** — built-in test runner +- **mockito** (with `@GenerateMocks`) or **mocktail** (no codegen) for mocking +- **bloc_test** for BLoC/Cubit unit tests +- **fake_async** for controlling time in unit tests +- **integration_test** for end-to-end device tests + +## Test Types + +| Type | Tool | Location | When to Write | +|------|------|----------|---------------| +| Unit | `dart:test` | `test/unit/` | All domain logic, state managers, repositories | +| Widget | `flutter_test` | `test/widget/` | All widgets with meaningful behavior | +| Golden | `flutter_test` | `test/golden/` | Design-critical UI components | +| Integration | `integration_test` | `integration_test/` | Critical user flows on real device/emulator | + +## Unit Tests: State Managers + +### BLoC with `bloc_test` + +```dart +group('CartBloc', () { + late CartBloc bloc; + late MockCartRepository repository; + + setUp(() { + repository = MockCartRepository(); + bloc = CartBloc(repository); + }); + + tearDown(() => bloc.close()); + + blocTest( + 'emits updated items when CartItemAdded', + build: () => bloc, + act: (b) => b.add(CartItemAdded(testItem)), + expect: () => [CartState(items: [testItem])], + ); + + blocTest( + 'emits empty cart when CartCleared', + seed: () => CartState(items: [testItem]), + build: () => bloc, + act: (b) => b.add(CartCleared()), + expect: () => [const CartState()], + ); +}); +``` + +### Riverpod with `ProviderContainer` + +```dart +test('usersProvider loads users from repository', () async { + final container = ProviderContainer( + overrides: [userRepositoryProvider.overrideWithValue(FakeUserRepository())], + ); + addTearDown(container.dispose); + + final result = await container.read(usersProvider.future); + expect(result, isNotEmpty); +}); +``` + +## Widget Tests + +```dart +testWidgets('CartPage shows item count badge', (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + cartNotifierProvider.overrideWith(() => FakeCartNotifier([testItem])), + ], + child: const MaterialApp(home: CartPage()), + ), + ); + + await tester.pump(); + expect(find.text('1'), findsOneWidget); + expect(find.byType(CartItemTile), findsOneWidget); +}); + +testWidgets('shows empty state when cart is empty', (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [cartNotifierProvider.overrideWith(() => FakeCartNotifier([]))], + child: const MaterialApp(home: CartPage()), + ), + ); + + await tester.pump(); + expect(find.text('Your cart is empty'), findsOneWidget); +}); +``` + +## Fakes Over Mocks + +Prefer hand-written fakes for complex dependencies: + +```dart +class FakeUserRepository implements UserRepository { + final _users = {}; + Object? fetchError; + + @override + Future getById(String id) async { + if (fetchError != null) throw fetchError!; + return _users[id]; + } + + @override + Future> getAll() async { + if (fetchError != null) throw fetchError!; + return _users.values.toList(); + } + + @override + Stream> watchAll() => Stream.value(_users.values.toList()); + + @override + Future save(User user) async { + _users[user.id] = user; + } + + @override + Future delete(String id) async { + _users.remove(id); + } + + void addUser(User user) => _users[user.id] = user; +} +``` + +## Async Testing + +```dart +// Use fake_async for controlling timers and Futures +test('debounce triggers after 300ms', () { + fakeAsync((async) { + final debouncer = Debouncer(delay: const Duration(milliseconds: 300)); + var callCount = 0; + debouncer.run(() => callCount++); + expect(callCount, 0); + async.elapse(const Duration(milliseconds: 200)); + expect(callCount, 0); + async.elapse(const Duration(milliseconds: 200)); + expect(callCount, 1); + }); +}); +``` + +## Golden Tests + +```dart +testWidgets('UserCard golden test', (tester) async { + await tester.pumpWidget( + MaterialApp(home: UserCard(user: testUser)), + ); + + await expectLater( + find.byType(UserCard), + matchesGoldenFile('goldens/user_card.png'), + ); +}); +``` + +Run `flutter test --update-goldens` when intentional visual changes are made. + +## Test Naming + +Use descriptive, behavior-focused names: + +```dart +test('returns null when user does not exist', () { ... }); +test('throws NotFoundException when id is empty string', () { ... }); +testWidgets('disables submit button while form is invalid', (tester) async { ... }); +``` + +## Test Organization + +``` +test/ +├── unit/ +│ ├── domain/ +│ │ └── usecases/ +│ └── data/ +│ └── repositories/ +├── widget/ +│ └── presentation/ +│ └── pages/ +└── golden/ + └── widgets/ + +integration_test/ +└── flows/ + ├── login_flow_test.dart + └── checkout_flow_test.dart +``` + +## Coverage + +- Target 80%+ line coverage for business logic (domain + state managers) +- All state transitions must have tests: loading → success, loading → error, retry +- Run `flutter test --coverage` and inspect `lcov.info` with a coverage reporter +- Coverage failures should block CI when below threshold diff --git a/rules/golang/coding-style.md b/rules/golang/coding-style.md new file mode 100644 index 0000000..d7d6c31 --- /dev/null +++ b/rules/golang/coding-style.md @@ -0,0 +1,32 @@ +--- +paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" +--- +# Go Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Go specific content. + +## Formatting + +- **gofmt** and **goimports** are mandatory — no style debates + +## Design Principles + +- Accept interfaces, return structs +- Keep interfaces small (1-3 methods) + +## Error Handling + +Always wrap errors with context: + +```go +if err != nil { + return fmt.Errorf("failed to create user: %w", err) +} +``` + +## Reference + +See skill: `golang-patterns` for comprehensive Go idioms and patterns. diff --git a/rules/golang/hooks.md b/rules/golang/hooks.md new file mode 100644 index 0000000..f05e4ad --- /dev/null +++ b/rules/golang/hooks.md @@ -0,0 +1,17 @@ +--- +paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" +--- +# Go Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Go specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **gofmt/goimports**: Auto-format `.go` files after edit +- **go vet**: Run static analysis after editing `.go` files +- **staticcheck**: Run extended static checks on modified packages diff --git a/rules/golang/patterns.md b/rules/golang/patterns.md new file mode 100644 index 0000000..ba28dba --- /dev/null +++ b/rules/golang/patterns.md @@ -0,0 +1,45 @@ +--- +paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" +--- +# Go Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Go specific content. + +## Functional Options + +```go +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { s.port = port } +} + +func NewServer(opts ...Option) *Server { + s := &Server{port: 8080} + for _, opt := range opts { + opt(s) + } + return s +} +``` + +## Small Interfaces + +Define interfaces where they are used, not where they are implemented. + +## Dependency Injection + +Use constructor functions to inject dependencies: + +```go +func NewUserService(repo UserRepository, logger Logger) *UserService { + return &UserService{repo: repo, logger: logger} +} +``` + +## Reference + +See skill: `golang-patterns` for comprehensive Go patterns including concurrency, error handling, and package organization. diff --git a/rules/golang/security.md b/rules/golang/security.md new file mode 100644 index 0000000..372b754 --- /dev/null +++ b/rules/golang/security.md @@ -0,0 +1,34 @@ +--- +paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" +--- +# Go Security + +> This file extends [common/security.md](../common/security.md) with Go specific content. + +## Secret Management + +```go +apiKey := os.Getenv("OPENAI_API_KEY") +if apiKey == "" { + log.Fatal("OPENAI_API_KEY not configured") +} +``` + +## Security Scanning + +- Use **gosec** for static security analysis: + ```bash + gosec ./... + ``` + +## Context & Timeouts + +Always use `context.Context` for timeout control: + +```go +ctx, cancel := context.WithTimeout(ctx, 5*time.Second) +defer cancel() +``` diff --git a/rules/golang/testing.md b/rules/golang/testing.md new file mode 100644 index 0000000..6b80022 --- /dev/null +++ b/rules/golang/testing.md @@ -0,0 +1,31 @@ +--- +paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" +--- +# Go Testing + +> This file extends [common/testing.md](../common/testing.md) with Go specific content. + +## Framework + +Use the standard `go test` with **table-driven tests**. + +## Race Detection + +Always run with the `-race` flag: + +```bash +go test -race ./... +``` + +## Coverage + +```bash +go test -cover ./... +``` + +## Reference + +See skill: `golang-testing` for detailed Go testing patterns and helpers. diff --git a/rules/java/coding-style.md b/rules/java/coding-style.md new file mode 100644 index 0000000..d20d5ab --- /dev/null +++ b/rules/java/coding-style.md @@ -0,0 +1,114 @@ +--- +paths: + - "**/*.java" +--- +# Java Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Java-specific content. + +## Formatting + +- **google-java-format** or **Checkstyle** (Google or Sun style) for enforcement +- One public top-level type per file +- Consistent indent: 2 or 4 spaces (match project standard) +- Member order: constants, fields, constructors, public methods, protected, private + +## Immutability + +- Prefer `record` for value types (Java 16+) +- Mark fields `final` by default — use mutable state only when required +- Return defensive copies from public APIs: `List.copyOf()`, `Map.copyOf()`, `Set.copyOf()` +- Copy-on-write: return new instances rather than mutating existing ones + +```java +// GOOD — immutable value type +public record OrderSummary(Long id, String customerName, BigDecimal total) {} + +// GOOD — final fields, no setters +public class Order { + private final Long id; + private final List items; + + public List getItems() { + return List.copyOf(items); + } +} +``` + +## Naming + +Follow standard Java conventions: +- `PascalCase` for classes, interfaces, records, enums +- `camelCase` for methods, fields, parameters, local variables +- `SCREAMING_SNAKE_CASE` for `static final` constants +- Packages: all lowercase, reverse domain (`com.example.app.service`) + +## Modern Java Features + +Use modern language features where they improve clarity: +- **Records** for DTOs and value types (Java 16+) +- **Sealed classes** for closed type hierarchies (Java 17+) +- **Pattern matching** with `instanceof` — no explicit cast (Java 16+) +- **Text blocks** for multi-line strings — SQL, JSON templates (Java 15+) +- **Switch expressions** with arrow syntax (Java 14+) +- **Pattern matching in switch** — exhaustive sealed type handling (Java 21+) + +```java +// Pattern matching instanceof +if (shape instanceof Circle c) { + return Math.PI * c.radius() * c.radius(); +} + +// Sealed type hierarchy +public sealed interface PaymentMethod permits CreditCard, BankTransfer, Wallet {} + +// Switch expression +String label = switch (status) { + case ACTIVE -> "Active"; + case SUSPENDED -> "Suspended"; + case CLOSED -> "Closed"; +}; +``` + +## Optional Usage + +- Return `Optional` from finder methods that may have no result +- Use `map()`, `flatMap()`, `orElseThrow()` — never call `get()` without `isPresent()` +- Never use `Optional` as a field type or method parameter + +```java +// GOOD +return repository.findById(id) + .map(ResponseDto::from) + .orElseThrow(() -> new OrderNotFoundException(id)); + +// BAD — Optional as parameter +public void process(Optional name) {} +``` + +## Error Handling + +- Prefer unchecked exceptions for domain errors +- Create domain-specific exceptions extending `RuntimeException` +- Avoid broad `catch (Exception e)` unless at top-level handlers +- Include context in exception messages + +```java +public class OrderNotFoundException extends RuntimeException { + public OrderNotFoundException(Long id) { + super("Order not found: id=" + id); + } +} +``` + +## Streams + +- Use streams for transformations; keep pipelines short (3-4 operations max) +- Prefer method references when readable: `.map(Order::getTotal)` +- Avoid side effects in stream operations +- For complex logic, prefer a loop over a convoluted stream pipeline + +## References + +See skill: `java-coding-standards` for full coding standards with examples. +See skill: `jpa-patterns` for JPA/Hibernate entity design patterns. diff --git a/rules/java/hooks.md b/rules/java/hooks.md new file mode 100644 index 0000000..9dd33b3 --- /dev/null +++ b/rules/java/hooks.md @@ -0,0 +1,18 @@ +--- +paths: + - "**/*.java" + - "**/pom.xml" + - "**/build.gradle" + - "**/build.gradle.kts" +--- +# Java Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Java-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **google-java-format**: Auto-format `.java` files after edit +- **checkstyle**: Run style checks after editing Java files +- **./mvnw compile** or **./gradlew compileJava**: Verify compilation after changes diff --git a/rules/java/patterns.md b/rules/java/patterns.md new file mode 100644 index 0000000..570282b --- /dev/null +++ b/rules/java/patterns.md @@ -0,0 +1,146 @@ +--- +paths: + - "**/*.java" +--- +# Java Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Java-specific content. + +## Repository Pattern + +Encapsulate data access behind an interface: + +```java +public interface OrderRepository { + Optional findById(Long id); + List findAll(); + Order save(Order order); + void deleteById(Long id); +} +``` + +Concrete implementations handle storage details (JPA, JDBC, in-memory for tests). + +## Service Layer + +Business logic in service classes; keep controllers and repositories thin: + +```java +public class OrderService { + private final OrderRepository orderRepository; + private final PaymentGateway paymentGateway; + + public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) { + this.orderRepository = orderRepository; + this.paymentGateway = paymentGateway; + } + + public OrderSummary placeOrder(CreateOrderRequest request) { + var order = Order.from(request); + paymentGateway.charge(order.total()); + var saved = orderRepository.save(order); + return OrderSummary.from(saved); + } +} +``` + +## Constructor Injection + +Always use constructor injection — never field injection: + +```java +// GOOD — constructor injection (testable, immutable) +public class NotificationService { + private final EmailSender emailSender; + + public NotificationService(EmailSender emailSender) { + this.emailSender = emailSender; + } +} + +// BAD — field injection (untestable without reflection, requires framework magic) +public class NotificationService { + @Inject // or @Autowired + private EmailSender emailSender; +} +``` + +## DTO Mapping + +Use records for DTOs. Map at service/controller boundaries: + +```java +public record OrderResponse(Long id, String customer, BigDecimal total) { + public static OrderResponse from(Order order) { + return new OrderResponse(order.getId(), order.getCustomerName(), order.getTotal()); + } +} +``` + +## Builder Pattern + +Use for objects with many optional parameters: + +```java +public class SearchCriteria { + private final String query; + private final int page; + private final int size; + private final String sortBy; + + private SearchCriteria(Builder builder) { + this.query = builder.query; + this.page = builder.page; + this.size = builder.size; + this.sortBy = builder.sortBy; + } + + public static class Builder { + private String query = ""; + private int page = 0; + private int size = 20; + private String sortBy = "id"; + + public Builder query(String query) { this.query = query; return this; } + public Builder page(int page) { this.page = page; return this; } + public Builder size(int size) { this.size = size; return this; } + public Builder sortBy(String sortBy) { this.sortBy = sortBy; return this; } + public SearchCriteria build() { return new SearchCriteria(this); } + } +} +``` + +## Sealed Types for Domain Models + +```java +public sealed interface PaymentResult permits PaymentSuccess, PaymentFailure { + record PaymentSuccess(String transactionId, BigDecimal amount) implements PaymentResult {} + record PaymentFailure(String errorCode, String message) implements PaymentResult {} +} + +// Exhaustive handling (Java 21+) +String message = switch (result) { + case PaymentSuccess s -> "Paid: " + s.transactionId(); + case PaymentFailure f -> "Failed: " + f.errorCode(); +}; +``` + +## API Response Envelope + +Consistent API responses: + +```java +public record ApiResponse(boolean success, T data, String error) { + public static ApiResponse ok(T data) { + return new ApiResponse<>(true, data, null); + } + public static ApiResponse error(String message) { + return new ApiResponse<>(false, null, message); + } +} +``` + +## References + +See skill: `springboot-patterns` for Spring Boot architecture patterns. +See skill: `jpa-patterns` for entity design and query optimization. diff --git a/rules/java/security.md b/rules/java/security.md new file mode 100644 index 0000000..31ca61b --- /dev/null +++ b/rules/java/security.md @@ -0,0 +1,100 @@ +--- +paths: + - "**/*.java" +--- +# Java Security + +> This file extends [common/security.md](../common/security.md) with Java-specific content. + +## Secrets Management + +- Never hardcode API keys, tokens, or credentials in source code +- Use environment variables: `System.getenv("API_KEY")` +- Use a secret manager (Vault, AWS Secrets Manager) for production secrets +- Keep local config files with secrets in `.gitignore` + +```java +// BAD +private static final String API_KEY = "sk-abc123..."; + +// GOOD — environment variable +String apiKey = System.getenv("PAYMENT_API_KEY"); +Objects.requireNonNull(apiKey, "PAYMENT_API_KEY must be set"); +``` + +## SQL Injection Prevention + +- Always use parameterized queries — never concatenate user input into SQL +- Use `PreparedStatement` or your framework's parameterized query API +- Validate and sanitize any input used in native queries + +```java +// BAD — SQL injection via string concatenation +Statement stmt = conn.createStatement(); +String sql = "SELECT * FROM orders WHERE name = '" + name + "'"; +stmt.executeQuery(sql); + +// GOOD — PreparedStatement with parameterized query +PreparedStatement ps = conn.prepareStatement("SELECT * FROM orders WHERE name = ?"); +ps.setString(1, name); + +// GOOD — JDBC template +jdbcTemplate.query("SELECT * FROM orders WHERE name = ?", mapper, name); +``` + +## Input Validation + +- Validate all user input at system boundaries before processing +- Use Bean Validation (`@NotNull`, `@NotBlank`, `@Size`) on DTOs when using a validation framework +- Sanitize file paths and user-provided strings before use +- Reject input that fails validation with clear error messages + +```java +// Validate manually in plain Java +public Order createOrder(String customerName, BigDecimal amount) { + if (customerName == null || customerName.isBlank()) { + throw new IllegalArgumentException("Customer name is required"); + } + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Amount must be positive"); + } + return new Order(customerName, amount); +} +``` + +## Authentication and Authorization + +- Never implement custom auth crypto — use established libraries +- Store passwords with bcrypt or Argon2, never MD5/SHA1 +- Enforce authorization checks at service boundaries +- Clear sensitive data from logs — never log passwords, tokens, or PII + +## Dependency Security + +- Run `mvn dependency:tree` or `./gradlew dependencies` to audit transitive dependencies +- Use OWASP Dependency-Check or Snyk to scan for known CVEs +- Keep dependencies updated — set up Dependabot or Renovate + +## Error Messages + +- Never expose stack traces, internal paths, or SQL errors in API responses +- Map exceptions to safe, generic client messages at handler boundaries +- Log detailed errors server-side; return generic messages to clients + +```java +// Log the detail, return a generic message +try { + return orderService.findById(id); +} catch (OrderNotFoundException ex) { + log.warn("Order not found: id={}", id); + return ApiResponse.error("Resource not found"); // generic, no internals +} catch (Exception ex) { + log.error("Unexpected error processing order id={}", id, ex); + return ApiResponse.error("Internal server error"); // never expose ex.getMessage() +} +``` + +## References + +See skill: `springboot-security` for Spring Security authentication and authorization patterns. +See skill: `security-review` for general security checklists. diff --git a/rules/java/testing.md b/rules/java/testing.md new file mode 100644 index 0000000..aa2e91f --- /dev/null +++ b/rules/java/testing.md @@ -0,0 +1,131 @@ +--- +paths: + - "**/*.java" +--- +# Java Testing + +> This file extends [common/testing.md](../common/testing.md) with Java-specific content. + +## Test Framework + +- **JUnit 5** (`@Test`, `@ParameterizedTest`, `@Nested`, `@DisplayName`) +- **AssertJ** for fluent assertions (`assertThat(result).isEqualTo(expected)`) +- **Mockito** for mocking dependencies +- **Testcontainers** for integration tests requiring databases or services + +## Test Organization + +``` +src/test/java/com/example/app/ + service/ # Unit tests for service layer + controller/ # Web layer / API tests + repository/ # Data access tests + integration/ # Cross-layer integration tests +``` + +Mirror the `src/main/java` package structure in `src/test/java`. + +## Unit Test Pattern + +```java +@ExtendWith(MockitoExtension.class) +class OrderServiceTest { + + @Mock + private OrderRepository orderRepository; + + private OrderService orderService; + + @BeforeEach + void setUp() { + orderService = new OrderService(orderRepository); + } + + @Test + @DisplayName("findById returns order when exists") + void findById_existingOrder_returnsOrder() { + var order = new Order(1L, "Alice", BigDecimal.TEN); + when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); + + var result = orderService.findById(1L); + + assertThat(result.customerName()).isEqualTo("Alice"); + verify(orderRepository).findById(1L); + } + + @Test + @DisplayName("findById throws when order not found") + void findById_missingOrder_throws() { + when(orderRepository.findById(99L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> orderService.findById(99L)) + .isInstanceOf(OrderNotFoundException.class) + .hasMessageContaining("99"); + } +} +``` + +## Parameterized Tests + +```java +@ParameterizedTest +@CsvSource({ + "100.00, 10, 90.00", + "50.00, 0, 50.00", + "200.00, 25, 150.00" +}) +@DisplayName("discount applied correctly") +void applyDiscount(BigDecimal price, int pct, BigDecimal expected) { + assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected); +} +``` + +## Integration Tests + +Use Testcontainers for real database integration: + +```java +@Testcontainers +class OrderRepositoryIT { + + @Container + static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16"); + + private OrderRepository repository; + + @BeforeEach + void setUp() { + var dataSource = new PGSimpleDataSource(); + dataSource.setUrl(postgres.getJdbcUrl()); + dataSource.setUser(postgres.getUsername()); + dataSource.setPassword(postgres.getPassword()); + repository = new JdbcOrderRepository(dataSource); + } + + @Test + void save_and_findById() { + var saved = repository.save(new Order(null, "Bob", BigDecimal.ONE)); + var found = repository.findById(saved.getId()); + assertThat(found).isPresent(); + } +} +``` + +For Spring Boot integration tests, see skill: `springboot-tdd`. + +## Test Naming + +Use descriptive names with `@DisplayName`: +- `methodName_scenario_expectedBehavior()` for method names +- `@DisplayName("human-readable description")` for reports + +## Coverage + +- Target 80%+ line coverage +- Use JaCoCo for coverage reporting +- Focus on service and domain logic — skip trivial getters/config classes + +## References + +See skill: `springboot-tdd` for Spring Boot TDD patterns with MockMvc and Testcontainers. +See skill: `java-coding-standards` for testing expectations. diff --git a/rules/kotlin/coding-style.md b/rules/kotlin/coding-style.md new file mode 100644 index 0000000..5c5ee30 --- /dev/null +++ b/rules/kotlin/coding-style.md @@ -0,0 +1,86 @@ +--- +paths: + - "**/*.kt" + - "**/*.kts" +--- +# Kotlin Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Kotlin-specific content. + +## Formatting + +- **ktlint** or **Detekt** for style enforcement +- Official Kotlin code style (`kotlin.code.style=official` in `gradle.properties`) + +## Immutability + +- Prefer `val` over `var` — default to `val` and only use `var` when mutation is required +- Use `data class` for value types; use immutable collections (`List`, `Map`, `Set`) in public APIs +- Copy-on-write for state updates: `state.copy(field = newValue)` + +## Naming + +Follow Kotlin conventions: +- `camelCase` for functions and properties +- `PascalCase` for classes, interfaces, objects, and type aliases +- `SCREAMING_SNAKE_CASE` for constants (`const val` or `@JvmStatic`) +- Prefix interfaces with behavior, not `I`: `Clickable` not `IClickable` + +## Null Safety + +- Never use `!!` — prefer `?.`, `?:`, `requireNotNull()`, or `checkNotNull()` +- Use `?.let {}` for scoped null-safe operations +- Return nullable types from functions that can legitimately have no result + +```kotlin +// BAD +val name = user!!.name + +// GOOD +val name = user?.name ?: "Unknown" +val name = requireNotNull(user) { "User must be set before accessing name" }.name +``` + +## Sealed Types + +Use sealed classes/interfaces to model closed state hierarchies: + +```kotlin +sealed interface UiState { + data object Loading : UiState + data class Success(val data: T) : UiState + data class Error(val message: String) : UiState +} +``` + +Always use exhaustive `when` with sealed types — no `else` branch. + +## Extension Functions + +Use extension functions for utility operations, but keep them discoverable: +- Place in a file named after the receiver type (`StringExt.kt`, `FlowExt.kt`) +- Keep scope limited — don't add extensions to `Any` or overly generic types + +## Scope Functions + +Use the right scope function: +- `let` — null check + transform: `user?.let { greet(it) }` +- `run` — compute a result using receiver: `service.run { fetch(config) }` +- `apply` — configure an object: `builder.apply { timeout = 30 }` +- `also` — side effects: `result.also { log(it) }` +- Avoid deep nesting of scope functions (max 2 levels) + +## Error Handling + +- Use `Result` or custom sealed types +- Use `runCatching {}` for wrapping throwable code +- Never catch `CancellationException` — always rethrow it +- Avoid `try-catch` for control flow + +```kotlin +// BAD — using exceptions for control flow +val user = try { repository.getUser(id) } catch (e: NotFoundException) { null } + +// GOOD — nullable return +val user: User? = repository.findUser(id) +``` diff --git a/rules/kotlin/hooks.md b/rules/kotlin/hooks.md new file mode 100644 index 0000000..28bb02f --- /dev/null +++ b/rules/kotlin/hooks.md @@ -0,0 +1,17 @@ +--- +paths: + - "**/*.kt" + - "**/*.kts" + - "**/build.gradle.kts" +--- +# Kotlin Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Kotlin-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **ktfmt/ktlint**: Auto-format `.kt` and `.kts` files after edit +- **detekt**: Run static analysis after editing Kotlin files +- **./gradlew build**: Verify compilation after changes diff --git a/rules/kotlin/patterns.md b/rules/kotlin/patterns.md new file mode 100644 index 0000000..1a09e6b --- /dev/null +++ b/rules/kotlin/patterns.md @@ -0,0 +1,146 @@ +--- +paths: + - "**/*.kt" + - "**/*.kts" +--- +# Kotlin Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Kotlin and Android/KMP-specific content. + +## Dependency Injection + +Prefer constructor injection. Use Koin (KMP) or Hilt (Android-only): + +```kotlin +// Koin — declare modules +val dataModule = module { + single { ItemRepositoryImpl(get(), get()) } + factory { GetItemsUseCase(get()) } + viewModelOf(::ItemListViewModel) +} + +// Hilt — annotations +@HiltViewModel +class ItemListViewModel @Inject constructor( + private val getItems: GetItemsUseCase +) : ViewModel() +``` + +## ViewModel Pattern + +Single state object, event sink, one-way data flow: + +```kotlin +data class ScreenState( + val items: List = emptyList(), + val isLoading: Boolean = false +) + +class ScreenViewModel(private val useCase: GetItemsUseCase) : ViewModel() { + private val _state = MutableStateFlow(ScreenState()) + val state = _state.asStateFlow() + + fun onEvent(event: ScreenEvent) { + when (event) { + is ScreenEvent.Load -> load() + is ScreenEvent.Delete -> delete(event.id) + } + } +} +``` + +## Repository Pattern + +- `suspend` functions return `Result` or custom error type +- `Flow` for reactive streams +- Coordinate local + remote data sources + +```kotlin +interface ItemRepository { + suspend fun getById(id: String): Result + suspend fun getAll(): Result> + fun observeAll(): Flow> +} +``` + +## UseCase Pattern + +Single responsibility, `operator fun invoke`: + +```kotlin +class GetItemUseCase(private val repository: ItemRepository) { + suspend operator fun invoke(id: String): Result { + return repository.getById(id) + } +} + +class GetItemsUseCase(private val repository: ItemRepository) { + suspend operator fun invoke(): Result> { + return repository.getAll() + } +} +``` + +## expect/actual (KMP) + +Use for platform-specific implementations: + +```kotlin +// commonMain +expect fun platformName(): String +expect class SecureStorage { + fun save(key: String, value: String) + fun get(key: String): String? +} + +// androidMain +actual fun platformName(): String = "Android" +actual class SecureStorage { + actual fun save(key: String, value: String) { /* EncryptedSharedPreferences */ } + actual fun get(key: String): String? = null /* ... */ +} + +// iosMain +actual fun platformName(): String = "iOS" +actual class SecureStorage { + actual fun save(key: String, value: String) { /* Keychain */ } + actual fun get(key: String): String? = null /* ... */ +} +``` + +## Coroutine Patterns + +- Use `viewModelScope` in ViewModels, `coroutineScope` for structured child work +- Use `stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initialValue)` for StateFlow from cold Flows +- Use `supervisorScope` when child failures should be independent + +## Builder Pattern with DSL + +```kotlin +class HttpClientConfig { + var baseUrl: String = "" + var timeout: Long = 30_000 + private val interceptors = mutableListOf() + + fun interceptor(block: () -> Interceptor) { + interceptors.add(block()) + } +} + +fun httpClient(block: HttpClientConfig.() -> Unit): HttpClient { + val config = HttpClientConfig().apply(block) + return HttpClient(config) +} + +// Usage +val client = httpClient { + baseUrl = "https://api.example.com" + timeout = 15_000 + interceptor { AuthInterceptor(tokenProvider) } +} +``` + +## References + +See skill: `kotlin-coroutines-flows` for detailed coroutine patterns. +See skill: `android-clean-architecture` for module and layer patterns. diff --git a/rules/kotlin/security.md b/rules/kotlin/security.md new file mode 100644 index 0000000..a212211 --- /dev/null +++ b/rules/kotlin/security.md @@ -0,0 +1,82 @@ +--- +paths: + - "**/*.kt" + - "**/*.kts" +--- +# Kotlin Security + +> This file extends [common/security.md](../common/security.md) with Kotlin and Android/KMP-specific content. + +## Secrets Management + +- Never hardcode API keys, tokens, or credentials in source code +- Use `local.properties` (git-ignored) for local development secrets +- Use `BuildConfig` fields generated from CI secrets for release builds +- Use `EncryptedSharedPreferences` (Android) or Keychain (iOS) for runtime secret storage + +```kotlin +// BAD +val apiKey = "sk-abc123..." + +// GOOD — from BuildConfig (generated at build time) +val apiKey = BuildConfig.API_KEY + +// GOOD — from secure storage at runtime +val token = secureStorage.get("auth_token") +``` + +## Network Security + +- Use HTTPS exclusively — configure `network_security_config.xml` to block cleartext +- Pin certificates for sensitive endpoints using OkHttp `CertificatePinner` or Ktor equivalent +- Set timeouts on all HTTP clients — never leave defaults (which may be infinite) +- Validate and sanitize all server responses before use + +```xml + + + + +``` + +## Input Validation + +- Validate all user input before processing or sending to API +- Use parameterized queries for Room/SQLDelight — never concatenate user input into SQL +- Sanitize file paths from user input to prevent path traversal + +```kotlin +// BAD — SQL injection +@Query("SELECT * FROM items WHERE name = '$input'") + +// GOOD — parameterized +@Query("SELECT * FROM items WHERE name = :input") +fun findByName(input: String): List +``` + +## Data Protection + +- Use `EncryptedSharedPreferences` for sensitive key-value data on Android +- Use `@Serializable` with explicit field names — don't leak internal property names +- Clear sensitive data from memory when no longer needed +- Use `@Keep` or ProGuard rules for serialized classes to prevent name mangling + +## Authentication + +- Store tokens in secure storage, not in plain SharedPreferences +- Implement token refresh with proper 401/403 handling +- Clear all auth state on logout (tokens, cached user data, cookies) +- Use biometric authentication (`BiometricPrompt`) for sensitive operations + +## ProGuard / R8 + +- Keep rules for all serialized models (`@Serializable`, Gson, Moshi) +- Keep rules for reflection-based libraries (Koin, Retrofit) +- Test release builds — obfuscation can break serialization silently + +## WebView Security + +- Disable JavaScript unless explicitly needed: `settings.javaScriptEnabled = false` +- Validate URLs before loading in WebView +- Never expose `@JavascriptInterface` methods that access sensitive data +- Use `WebViewClient.shouldOverrideUrlLoading()` to control navigation diff --git a/rules/kotlin/testing.md b/rules/kotlin/testing.md new file mode 100644 index 0000000..cdf9733 --- /dev/null +++ b/rules/kotlin/testing.md @@ -0,0 +1,128 @@ +--- +paths: + - "**/*.kt" + - "**/*.kts" +--- +# Kotlin Testing + +> This file extends [common/testing.md](../common/testing.md) with Kotlin and Android/KMP-specific content. + +## Test Framework + +- **kotlin.test** for multiplatform (KMP) — `@Test`, `assertEquals`, `assertTrue` +- **JUnit 4/5** for Android-specific tests +- **Turbine** for testing Flows and StateFlow +- **kotlinx-coroutines-test** for coroutine testing (`runTest`, `TestDispatcher`) + +## ViewModel Testing with Turbine + +```kotlin +@Test +fun `loading state emitted then data`() = runTest { + val repo = FakeItemRepository() + repo.addItem(testItem) + val viewModel = ItemListViewModel(GetItemsUseCase(repo)) + + viewModel.state.test { + assertEquals(ItemListState(), awaitItem()) // initial state + viewModel.onEvent(ItemListEvent.Load) + assertTrue(awaitItem().isLoading) // loading + assertEquals(listOf(testItem), awaitItem().items) // loaded + } +} +``` + +## Fakes Over Mocks + +Prefer hand-written fakes over mocking frameworks: + +```kotlin +class FakeItemRepository : ItemRepository { + private val items = mutableListOf() + var fetchError: Throwable? = null + + override suspend fun getAll(): Result> { + fetchError?.let { return Result.failure(it) } + return Result.success(items.toList()) + } + + override fun observeAll(): Flow> = flowOf(items.toList()) + + fun addItem(item: Item) { items.add(item) } +} +``` + +## Coroutine Testing + +```kotlin +@Test +fun `parallel operations complete`() = runTest { + val repo = FakeRepository() + val result = loadDashboard(repo) + advanceUntilIdle() + assertNotNull(result.items) + assertNotNull(result.stats) +} +``` + +Use `runTest` — it auto-advances virtual time and provides `TestScope`. + +## Ktor MockEngine + +```kotlin +val mockEngine = MockEngine { request -> + when (request.url.encodedPath) { + "/api/items" -> respond( + content = Json.encodeToString(testItems), + headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + ) + else -> respondError(HttpStatusCode.NotFound) + } +} + +val client = HttpClient(mockEngine) { + install(ContentNegotiation) { json() } +} +``` + +## Room/SQLDelight Testing + +- Room: Use `Room.inMemoryDatabaseBuilder()` for in-memory testing +- SQLDelight: Use `JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)` for JVM tests + +```kotlin +@Test +fun `insert and query items`() = runTest { + val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + Database.Schema.create(driver) + val db = Database(driver) + + db.itemQueries.insert("1", "Sample Item", "description") + val items = db.itemQueries.getAll().executeAsList() + assertEquals(1, items.size) +} +``` + +## Test Naming + +Use backtick-quoted descriptive names: + +```kotlin +@Test +fun `search with empty query returns all items`() = runTest { } + +@Test +fun `delete item emits updated list without deleted item`() = runTest { } +``` + +## Test Organization + +``` +src/ +├── commonTest/kotlin/ # Shared tests (ViewModel, UseCase, Repository) +├── androidUnitTest/kotlin/ # Android unit tests (JUnit) +├── androidInstrumentedTest/kotlin/ # Instrumented tests (Room, UI) +└── iosTest/kotlin/ # iOS-specific tests +``` + +Minimum test coverage: ViewModel + UseCase for every feature. diff --git a/rules/perl/coding-style.md b/rules/perl/coding-style.md new file mode 100644 index 0000000..9c7fbb2 --- /dev/null +++ b/rules/perl/coding-style.md @@ -0,0 +1,46 @@ +--- +paths: + - "**/*.pl" + - "**/*.pm" + - "**/*.t" + - "**/*.psgi" + - "**/*.cgi" +--- +# Perl Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Perl-specific content. + +## Standards + +- Always `use v5.36` (enables `strict`, `warnings`, `say`, subroutine signatures) +- Use subroutine signatures — never unpack `@_` manually +- Prefer `say` over `print` with explicit newlines + +## Immutability + +- Use **Moo** with `is => 'ro'` and `Types::Standard` for all attributes +- Never use blessed hashrefs directly — always use Moo/Moose accessors +- **OO override note**: Moo `has` attributes with `builder` or `default` are acceptable for computed read-only values + +## Formatting + +Use **perltidy** with these settings: + +``` +-i=4 # 4-space indent +-l=100 # 100 char line length +-ce # cuddled else +-bar # opening brace always right +``` + +## Linting + +Use **perlcritic** at severity 3 with themes: `core`, `pbp`, `security`. + +```bash +perlcritic --severity 3 --theme 'core || pbp || security' lib/ +``` + +## Reference + +See skill: `perl-patterns` for comprehensive modern Perl idioms and best practices. diff --git a/rules/perl/hooks.md b/rules/perl/hooks.md new file mode 100644 index 0000000..0b6daad --- /dev/null +++ b/rules/perl/hooks.md @@ -0,0 +1,22 @@ +--- +paths: + - "**/*.pl" + - "**/*.pm" + - "**/*.t" + - "**/*.psgi" + - "**/*.cgi" +--- +# Perl Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Perl-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **perltidy**: Auto-format `.pl` and `.pm` files after edit +- **perlcritic**: Run lint check after editing `.pm` files + +## Warnings + +- Warn about `print` in non-script `.pm` files — use `say` or a logging module (e.g., `Log::Any`) diff --git a/rules/perl/patterns.md b/rules/perl/patterns.md new file mode 100644 index 0000000..a2f7b4f --- /dev/null +++ b/rules/perl/patterns.md @@ -0,0 +1,76 @@ +--- +paths: + - "**/*.pl" + - "**/*.pm" + - "**/*.t" + - "**/*.psgi" + - "**/*.cgi" +--- +# Perl Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Perl-specific content. + +## Repository Pattern + +Use **DBI** or **DBIx::Class** behind an interface: + +```perl +package MyApp::Repo::User; +use Moo; + +has dbh => (is => 'ro', required => 1); + +sub find_by_id ($self, $id) { + my $sth = $self->dbh->prepare('SELECT * FROM users WHERE id = ?'); + $sth->execute($id); + return $sth->fetchrow_hashref; +} +``` + +## DTOs / Value Objects + +Use **Moo** classes with **Types::Standard** (equivalent to Python dataclasses): + +```perl +package MyApp::DTO::User; +use Moo; +use Types::Standard qw(Str Int); + +has name => (is => 'ro', isa => Str, required => 1); +has email => (is => 'ro', isa => Str, required => 1); +has age => (is => 'ro', isa => Int); +``` + +## Resource Management + +- Always use **three-arg open** with `autodie` +- Use **Path::Tiny** for file operations + +```perl +use autodie; +use Path::Tiny; + +my $content = path('config.json')->slurp_utf8; +``` + +## Module Interface + +Use `Exporter 'import'` with `@EXPORT_OK` — never `@EXPORT`: + +```perl +use Exporter 'import'; +our @EXPORT_OK = qw(parse_config validate_input); +``` + +## Dependency Management + +Use **cpanfile** + **carton** for reproducible installs: + +```bash +carton install +carton exec prove -lr t/ +``` + +## Reference + +See skill: `perl-patterns` for comprehensive modern Perl patterns and idioms. diff --git a/rules/perl/security.md b/rules/perl/security.md new file mode 100644 index 0000000..c87fefc --- /dev/null +++ b/rules/perl/security.md @@ -0,0 +1,69 @@ +--- +paths: + - "**/*.pl" + - "**/*.pm" + - "**/*.t" + - "**/*.psgi" + - "**/*.cgi" +--- +# Perl Security + +> This file extends [common/security.md](../common/security.md) with Perl-specific content. + +## Taint Mode + +- Use `-T` flag on all CGI/web-facing scripts +- Sanitize `%ENV` (`$ENV{PATH}`, `$ENV{CDPATH}`, etc.) before any external command + +## Input Validation + +- Use allowlist regex for untainting — never `/(.*)/s` +- Validate all user input with explicit patterns: + +```perl +if ($input =~ /\A([a-zA-Z0-9_-]+)\z/) { + my $clean = $1; +} +``` + +## File I/O + +- **Three-arg open only** — never two-arg open +- Prevent path traversal with `Cwd::realpath`: + +```perl +use Cwd 'realpath'; +my $safe_path = realpath($user_path); +die "Path traversal" unless $safe_path =~ m{\A/allowed/directory/}; +``` + +## Process Execution + +- Use **list-form `system()`** — never single-string form +- Use **IPC::Run3** for capturing output +- Never use backticks with variable interpolation + +```perl +system('grep', '-r', $pattern, $directory); # safe +``` + +## SQL Injection Prevention + +Always use DBI placeholders — never interpolate into SQL: + +```perl +my $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?'); +$sth->execute($email); +``` + +## Security Scanning + +Run **perlcritic** with the security theme at severity 4+: + +```bash +perlcritic --severity 4 --theme security lib/ +``` + +## Reference + +See skill: `perl-security` for comprehensive Perl security patterns, taint mode, and safe I/O. diff --git a/rules/perl/testing.md b/rules/perl/testing.md new file mode 100644 index 0000000..d451699 --- /dev/null +++ b/rules/perl/testing.md @@ -0,0 +1,54 @@ +--- +paths: + - "**/*.pl" + - "**/*.pm" + - "**/*.t" + - "**/*.psgi" + - "**/*.cgi" +--- +# Perl Testing + +> This file extends [common/testing.md](../common/testing.md) with Perl-specific content. + +## Framework + +Use **Test2::V0** for new projects (not Test::More): + +```perl +use Test2::V0; + +is($result, 42, 'answer is correct'); + +done_testing; +``` + +## Runner + +```bash +prove -l t/ # adds lib/ to @INC +prove -lr -j8 t/ # recursive, 8 parallel jobs +``` + +Always use `-l` to ensure `lib/` is on `@INC`. + +## Coverage + +Use **Devel::Cover** — target 80%+: + +```bash +cover -test +``` + +## Mocking + +- **Test::MockModule** — mock methods on existing modules +- **Test::MockObject** — create test doubles from scratch + +## Pitfalls + +- Always end test files with `done_testing` +- Never forget the `-l` flag with `prove` + +## Reference + +See skill: `perl-testing` for detailed Perl TDD patterns with Test2::V0, prove, and Devel::Cover. diff --git a/rules/php/coding-style.md b/rules/php/coding-style.md new file mode 100644 index 0000000..382ea9b --- /dev/null +++ b/rules/php/coding-style.md @@ -0,0 +1,40 @@ +--- +paths: + - "**/*.php" + - "**/composer.json" +--- +# PHP Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with PHP specific content. + +## Standards + +- Follow **PSR-12** formatting and naming conventions. +- Prefer `declare(strict_types=1);` in application code. +- Use scalar type hints, return types, and typed properties everywhere new code permits. + +## Immutability + +- Prefer immutable DTOs and value objects for data crossing service boundaries. +- Use `readonly` properties or immutable constructors for request/response payloads where possible. +- Keep arrays for simple maps; promote business-critical structures into explicit classes. + +## Formatting + +- Use **PHP-CS-Fixer** or **Laravel Pint** for formatting. +- Use **PHPStan** or **Psalm** for static analysis. +- Keep Composer scripts checked in so the same commands run locally and in CI. + +## Imports + +- Add `use` statements for all referenced classes, interfaces, and traits. +- Avoid relying on the global namespace unless the project explicitly prefers fully qualified names. + +## Error Handling + +- Throw exceptions for exceptional states; avoid returning `false`/`null` as hidden error channels in new code. +- Convert framework/request input into validated DTOs before it reaches domain logic. + +## Reference + +See skill: `backend-patterns` for broader service/repository layering guidance. diff --git a/rules/php/hooks.md b/rules/php/hooks.md new file mode 100644 index 0000000..10dd3c9 --- /dev/null +++ b/rules/php/hooks.md @@ -0,0 +1,24 @@ +--- +paths: + - "**/*.php" + - "**/composer.json" + - "**/phpstan.neon" + - "**/phpstan.neon.dist" + - "**/psalm.xml" +--- +# PHP Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with PHP specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **Pint / PHP-CS-Fixer**: Auto-format edited `.php` files. +- **PHPStan / Psalm**: Run static analysis after PHP edits in typed codebases. +- **PHPUnit / Pest**: Run targeted tests for touched files or modules when edits affect behavior. + +## Warnings + +- Warn on `var_dump`, `dd`, `dump`, or `die()` left in edited files. +- Warn when edited PHP files add raw SQL or disable CSRF/session protections. diff --git a/rules/php/patterns.md b/rules/php/patterns.md new file mode 100644 index 0000000..b914474 --- /dev/null +++ b/rules/php/patterns.md @@ -0,0 +1,33 @@ +--- +paths: + - "**/*.php" + - "**/composer.json" +--- +# PHP Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with PHP specific content. + +## Thin Controllers, Explicit Services + +- Keep controllers focused on transport: auth, validation, serialization, status codes. +- Move business rules into application/domain services that are easy to test without HTTP bootstrapping. + +## DTOs and Value Objects + +- Replace shape-heavy associative arrays with DTOs for requests, commands, and external API payloads. +- Use value objects for money, identifiers, date ranges, and other constrained concepts. + +## Dependency Injection + +- Depend on interfaces or narrow service contracts, not framework globals. +- Pass collaborators through constructors so services are testable without service-locator lookups. + +## Boundaries + +- Isolate ORM models from domain decisions when the model layer is doing more than persistence. +- Wrap third-party SDKs behind small adapters so the rest of the codebase depends on your contract, not theirs. + +## Reference + +See skill: `api-design` for endpoint conventions and response-shape guidance. +See skill: `laravel-patterns` for Laravel-specific architecture guidance. diff --git a/rules/php/security.md b/rules/php/security.md new file mode 100644 index 0000000..d559b8a --- /dev/null +++ b/rules/php/security.md @@ -0,0 +1,37 @@ +--- +paths: + - "**/*.php" + - "**/composer.lock" + - "**/composer.json" +--- +# PHP Security + +> This file extends [common/security.md](../common/security.md) with PHP specific content. + +## Input and Output + +- Validate request input at the framework boundary (`FormRequest`, Symfony Validator, or explicit DTO validation). +- Escape output in templates by default; treat raw HTML rendering as an exception that must be justified. +- Never trust query params, cookies, headers, or uploaded file metadata without validation. + +## Database Safety + +- Use prepared statements (`PDO`, Doctrine, Eloquent query builder) for all dynamic queries. +- Avoid string-building SQL in controllers/views. +- Scope ORM mass-assignment carefully and whitelist writable fields. + +## Secrets and Dependencies + +- Load secrets from environment variables or a secret manager, never from committed config files. +- Run `composer audit` in CI and review new package maintainer trust before adding dependencies. +- Pin major versions deliberately and remove abandoned packages quickly. + +## Auth and Session Safety + +- Use `password_hash()` / `password_verify()` for password storage. +- Regenerate session identifiers after authentication and privilege changes. +- Enforce CSRF protection on state-changing web requests. + +## Reference + +See skill: `laravel-security` for Laravel-specific security guidance. diff --git a/rules/php/testing.md b/rules/php/testing.md new file mode 100644 index 0000000..b069901 --- /dev/null +++ b/rules/php/testing.md @@ -0,0 +1,39 @@ +--- +paths: + - "**/*.php" + - "**/phpunit.xml" + - "**/phpunit.xml.dist" + - "**/composer.json" +--- +# PHP Testing + +> This file extends [common/testing.md](../common/testing.md) with PHP specific content. + +## Framework + +Use **PHPUnit** as the default test framework. If **Pest** is configured in the project, prefer Pest for new tests and avoid mixing frameworks. + +## Coverage + +```bash +vendor/bin/phpunit --coverage-text +# or +vendor/bin/pest --coverage +``` + +Prefer **pcov** or **Xdebug** in CI, and keep coverage thresholds in CI rather than as tribal knowledge. + +## Test Organization + +- Separate fast unit tests from framework/database integration tests. +- Use factory/builders for fixtures instead of large hand-written arrays. +- Keep HTTP/controller tests focused on transport and validation; move business rules into service-level tests. + +## Inertia + +If the project uses Inertia.js, prefer `assertInertia` with `AssertableInertia` to verify component names and props instead of raw JSON assertions. + +## Reference + +See skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop. +See skill: `laravel-tdd` for Laravel-specific testing patterns (PHPUnit and Pest). diff --git a/rules/python/coding-style.md b/rules/python/coding-style.md new file mode 100644 index 0000000..3a01ae3 --- /dev/null +++ b/rules/python/coding-style.md @@ -0,0 +1,42 @@ +--- +paths: + - "**/*.py" + - "**/*.pyi" +--- +# Python Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Python specific content. + +## Standards + +- Follow **PEP 8** conventions +- Use **type annotations** on all function signatures + +## Immutability + +Prefer immutable data structures: + +```python +from dataclasses import dataclass + +@dataclass(frozen=True) +class User: + name: str + email: str + +from typing import NamedTuple + +class Point(NamedTuple): + x: float + y: float +``` + +## Formatting + +- **black** for code formatting +- **isort** for import sorting +- **ruff** for linting + +## Reference + +See skill: `python-patterns` for comprehensive Python idioms and patterns. diff --git a/rules/python/hooks.md b/rules/python/hooks.md new file mode 100644 index 0000000..600c5ea --- /dev/null +++ b/rules/python/hooks.md @@ -0,0 +1,19 @@ +--- +paths: + - "**/*.py" + - "**/*.pyi" +--- +# Python Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Python specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **black/ruff**: Auto-format `.py` files after edit +- **mypy/pyright**: Run type checking after editing `.py` files + +## Warnings + +- Warn about `print()` statements in edited files (use `logging` module instead) diff --git a/rules/python/patterns.md b/rules/python/patterns.md new file mode 100644 index 0000000..5b7f899 --- /dev/null +++ b/rules/python/patterns.md @@ -0,0 +1,39 @@ +--- +paths: + - "**/*.py" + - "**/*.pyi" +--- +# Python Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Python specific content. + +## Protocol (Duck Typing) + +```python +from typing import Protocol + +class Repository(Protocol): + def find_by_id(self, id: str) -> dict | None: ... + def save(self, entity: dict) -> dict: ... +``` + +## Dataclasses as DTOs + +```python +from dataclasses import dataclass + +@dataclass +class CreateUserRequest: + name: str + email: str + age: int | None = None +``` + +## Context Managers & Generators + +- Use context managers (`with` statement) for resource management +- Use generators for lazy evaluation and memory-efficient iteration + +## Reference + +See skill: `python-patterns` for comprehensive patterns including decorators, concurrency, and package organization. diff --git a/rules/python/security.md b/rules/python/security.md new file mode 100644 index 0000000..e795baf --- /dev/null +++ b/rules/python/security.md @@ -0,0 +1,30 @@ +--- +paths: + - "**/*.py" + - "**/*.pyi" +--- +# Python Security + +> This file extends [common/security.md](../common/security.md) with Python specific content. + +## Secret Management + +```python +import os +from dotenv import load_dotenv + +load_dotenv() + +api_key = os.environ["OPENAI_API_KEY"] # Raises KeyError if missing +``` + +## Security Scanning + +- Use **bandit** for static security analysis: + ```bash + bandit -r src/ + ``` + +## Reference + +See skill: `django-security` for Django-specific security guidelines (if applicable). diff --git a/rules/python/testing.md b/rules/python/testing.md new file mode 100644 index 0000000..49e3f08 --- /dev/null +++ b/rules/python/testing.md @@ -0,0 +1,38 @@ +--- +paths: + - "**/*.py" + - "**/*.pyi" +--- +# Python Testing + +> This file extends [common/testing.md](../common/testing.md) with Python specific content. + +## Framework + +Use **pytest** as the testing framework. + +## Coverage + +```bash +pytest --cov=src --cov-report=term-missing +``` + +## Test Organization + +Use `pytest.mark` for test categorization: + +```python +import pytest + +@pytest.mark.unit +def test_calculate_total(): + ... + +@pytest.mark.integration +def test_database_connection(): + ... +``` + +## Reference + +See skill: `python-testing` for detailed pytest patterns and fixtures. diff --git a/rules/rust/coding-style.md b/rules/rust/coding-style.md new file mode 100644 index 0000000..cda67b5 --- /dev/null +++ b/rules/rust/coding-style.md @@ -0,0 +1,151 @@ +--- +paths: + - "**/*.rs" +--- +# Rust Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Rust-specific content. + +## Formatting + +- **rustfmt** for enforcement — always run `cargo fmt` before committing +- **clippy** for lints — `cargo clippy -- -D warnings` (treat warnings as errors) +- 4-space indent (rustfmt default) +- Max line width: 100 characters (rustfmt default) + +## Immutability + +Rust variables are immutable by default — embrace this: + +- Use `let` by default; only use `let mut` when mutation is required +- Prefer returning new values over mutating in place +- Use `Cow<'_, T>` when a function may or may not need to allocate + +```rust +use std::borrow::Cow; + +// GOOD — immutable by default, new value returned +fn normalize(input: &str) -> Cow<'_, str> { + if input.contains(' ') { + Cow::Owned(input.replace(' ', "_")) + } else { + Cow::Borrowed(input) + } +} + +// BAD — unnecessary mutation +fn normalize_bad(input: &mut String) { + *input = input.replace(' ', "_"); +} +``` + +## Naming + +Follow standard Rust conventions: +- `snake_case` for functions, methods, variables, modules, crates +- `PascalCase` (UpperCamelCase) for types, traits, enums, type parameters +- `SCREAMING_SNAKE_CASE` for constants and statics +- Lifetimes: short lowercase (`'a`, `'de`) — descriptive names for complex cases (`'input`) + +## Ownership and Borrowing + +- Borrow (`&T`) by default; take ownership only when you need to store or consume +- Never clone to satisfy the borrow checker without understanding the root cause +- Accept `&str` over `String`, `&[T]` over `Vec` in function parameters +- Use `impl Into` for constructors that need to own a `String` + +```rust +// GOOD — borrows when ownership isn't needed +fn word_count(text: &str) -> usize { + text.split_whitespace().count() +} + +// GOOD — takes ownership in constructor via Into +fn new(name: impl Into) -> Self { + Self { name: name.into() } +} + +// BAD — takes String when &str suffices +fn word_count_bad(text: String) -> usize { + text.split_whitespace().count() +} +``` + +## Error Handling + +- Use `Result` and `?` for propagation — never `unwrap()` in production code +- **Libraries**: define typed errors with `thiserror` +- **Applications**: use `anyhow` for flexible error context +- Add context with `.with_context(|| format!("failed to ..."))?` +- Reserve `unwrap()` / `expect()` for tests and truly unreachable states + +```rust +// GOOD — library error with thiserror +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error("failed to read config: {0}")] + Io(#[from] std::io::Error), + #[error("invalid config format: {0}")] + Parse(String), +} + +// GOOD — application error with anyhow +use anyhow::Context; + +fn load_config(path: &str) -> anyhow::Result { + let content = std::fs::read_to_string(path) + .with_context(|| format!("failed to read {path}"))?; + toml::from_str(&content) + .with_context(|| format!("failed to parse {path}")) +} +``` + +## Iterators Over Loops + +Prefer iterator chains for transformations; use loops for complex control flow: + +```rust +// GOOD — declarative and composable +let active_emails: Vec<&str> = users.iter() + .filter(|u| u.is_active) + .map(|u| u.email.as_str()) + .collect(); + +// GOOD — loop for complex logic with early returns +for user in &users { + if let Some(verified) = verify_email(&user.email)? { + send_welcome(&verified)?; + } +} +``` + +## Module Organization + +Organize by domain, not by type: + +```text +src/ +├── main.rs +├── lib.rs +├── auth/ # Domain module +│ ├── mod.rs +│ ├── token.rs +│ └── middleware.rs +├── orders/ # Domain module +│ ├── mod.rs +│ ├── model.rs +│ └── service.rs +└── db/ # Infrastructure + ├── mod.rs + └── pool.rs +``` + +## Visibility + +- Default to private; use `pub(crate)` for internal sharing +- Only mark `pub` what is part of the crate's public API +- Re-export public API from `lib.rs` + +## References + +See skill: `rust-patterns` for comprehensive Rust idioms and patterns. diff --git a/rules/rust/hooks.md b/rules/rust/hooks.md new file mode 100644 index 0000000..4511f1c --- /dev/null +++ b/rules/rust/hooks.md @@ -0,0 +1,16 @@ +--- +paths: + - "**/*.rs" + - "**/Cargo.toml" +--- +# Rust Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Rust-specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **cargo fmt**: Auto-format `.rs` files after edit +- **cargo clippy**: Run lint checks after editing Rust files +- **cargo check**: Verify compilation after changes (faster than `cargo build`) diff --git a/rules/rust/patterns.md b/rules/rust/patterns.md new file mode 100644 index 0000000..3d807e7 --- /dev/null +++ b/rules/rust/patterns.md @@ -0,0 +1,168 @@ +--- +paths: + - "**/*.rs" +--- +# Rust Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Rust-specific content. + +## Repository Pattern with Traits + +Encapsulate data access behind a trait: + +```rust +pub trait OrderRepository: Send + Sync { + fn find_by_id(&self, id: u64) -> Result, StorageError>; + fn find_all(&self) -> Result, StorageError>; + fn save(&self, order: &Order) -> Result; + fn delete(&self, id: u64) -> Result<(), StorageError>; +} +``` + +Concrete implementations handle storage details (Postgres, SQLite, in-memory for tests). + +## Service Layer + +Business logic in service structs; inject dependencies via constructor: + +```rust +pub struct OrderService { + repo: Box, + payment: Box, +} + +impl OrderService { + pub fn new(repo: Box, payment: Box) -> Self { + Self { repo, payment } + } + + pub fn place_order(&self, request: CreateOrderRequest) -> anyhow::Result { + let order = Order::from(request); + self.payment.charge(order.total())?; + let saved = self.repo.save(&order)?; + Ok(OrderSummary::from(saved)) + } +} +``` + +## Newtype Pattern for Type Safety + +Prevent argument mix-ups with distinct wrapper types: + +```rust +struct UserId(u64); +struct OrderId(u64); + +fn get_order(user: UserId, order: OrderId) -> anyhow::Result { + // Can't accidentally swap user and order IDs at call sites + todo!() +} +``` + +## Enum State Machines + +Model states as enums — make illegal states unrepresentable: + +```rust +enum ConnectionState { + Disconnected, + Connecting { attempt: u32 }, + Connected { session_id: String }, + Failed { reason: String, retries: u32 }, +} + +fn handle(state: &ConnectionState) { + match state { + ConnectionState::Disconnected => connect(), + ConnectionState::Connecting { attempt } if *attempt > 3 => abort(), + ConnectionState::Connecting { .. } => wait(), + ConnectionState::Connected { session_id } => use_session(session_id), + ConnectionState::Failed { retries, .. } if *retries < 5 => retry(), + ConnectionState::Failed { reason, .. } => log_failure(reason), + } +} +``` + +Always match exhaustively — no wildcard `_` for business-critical enums. + +## Builder Pattern + +Use for structs with many optional parameters: + +```rust +pub struct ServerConfig { + host: String, + port: u16, + max_connections: usize, +} + +impl ServerConfig { + pub fn builder(host: impl Into, port: u16) -> ServerConfigBuilder { + ServerConfigBuilder { + host: host.into(), + port, + max_connections: 100, + } + } +} + +pub struct ServerConfigBuilder { + host: String, + port: u16, + max_connections: usize, +} + +impl ServerConfigBuilder { + pub fn max_connections(mut self, n: usize) -> Self { + self.max_connections = n; + self + } + + pub fn build(self) -> ServerConfig { + ServerConfig { + host: self.host, + port: self.port, + max_connections: self.max_connections, + } + } +} +``` + +## Sealed Traits for Extensibility Control + +Use a private module to seal a trait, preventing external implementations: + +```rust +mod private { + pub trait Sealed {} +} + +pub trait Format: private::Sealed { + fn encode(&self, data: &[u8]) -> Vec; +} + +pub struct Json; +impl private::Sealed for Json {} +impl Format for Json { + fn encode(&self, data: &[u8]) -> Vec { todo!() } +} +``` + +## API Response Envelope + +Consistent API responses using a generic enum: + +```rust +#[derive(Debug, serde::Serialize)] +#[serde(tag = "status")] +pub enum ApiResponse { + #[serde(rename = "ok")] + Ok { data: T }, + #[serde(rename = "error")] + Error { message: String }, +} +``` + +## References + +See skill: `rust-patterns` for comprehensive patterns including ownership, traits, generics, concurrency, and async. diff --git a/rules/rust/security.md b/rules/rust/security.md new file mode 100644 index 0000000..83c0e07 --- /dev/null +++ b/rules/rust/security.md @@ -0,0 +1,141 @@ +--- +paths: + - "**/*.rs" +--- +# Rust Security + +> This file extends [common/security.md](../common/security.md) with Rust-specific content. + +## Secrets Management + +- Never hardcode API keys, tokens, or credentials in source code +- Use environment variables: `std::env::var("API_KEY")` +- Fail fast if required secrets are missing at startup +- Keep `.env` files in `.gitignore` + +```rust +// BAD +const API_KEY: &str = "sk-abc123..."; + +// GOOD — environment variable with early validation +fn load_api_key() -> anyhow::Result { + std::env::var("PAYMENT_API_KEY") + .context("PAYMENT_API_KEY must be set") +} +``` + +## SQL Injection Prevention + +- Always use parameterized queries — never format user input into SQL strings +- Use query builder or ORM (sqlx, diesel, sea-orm) with bind parameters + +```rust +// BAD — SQL injection via format string +let query = format!("SELECT * FROM users WHERE name = '{name}'"); +sqlx::query(&query).fetch_one(&pool).await?; + +// GOOD — parameterized query with sqlx +// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1 +sqlx::query("SELECT * FROM users WHERE name = $1") + .bind(&name) + .fetch_one(&pool) + .await?; +``` + +## Input Validation + +- Validate all user input at system boundaries before processing +- Use the type system to enforce invariants (newtype pattern) +- Parse, don't validate — convert unstructured data to typed structs at the boundary +- Reject invalid input with clear error messages + +```rust +// Parse, don't validate — invalid states are unrepresentable +pub struct Email(String); + +impl Email { + pub fn parse(input: &str) -> Result { + let trimmed = input.trim(); + let at_pos = trimmed.find('@') + .filter(|&p| p > 0 && p < trimmed.len() - 1) + .ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?; + let domain = &trimmed[at_pos + 1..]; + if trimmed.len() > 254 || !domain.contains('.') { + return Err(ValidationError::InvalidEmail(input.to_string())); + } + // For production use, prefer a validated email crate (e.g., `email_address`) + Ok(Self(trimmed.to_string())) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} +``` + +## Unsafe Code + +- Minimize `unsafe` blocks — prefer safe abstractions +- Every `unsafe` block must have a `// SAFETY:` comment explaining the invariant +- Never use `unsafe` to bypass the borrow checker for convenience +- Audit all `unsafe` code during review — it is a red flag without justification +- Prefer `safe` FFI wrappers around C libraries + +```rust +// GOOD — safety comment documents ALL required invariants +let widget: &Widget = { + // SAFETY: `ptr` is non-null, aligned, points to an initialized Widget, + // and no mutable references or mutations exist for its lifetime. + unsafe { &*ptr } +}; + +// BAD — no safety justification +unsafe { &*ptr } +``` + +## Dependency Security + +- Run `cargo audit` to scan for known CVEs in dependencies +- Run `cargo deny check` for license and advisory compliance +- Use `cargo tree` to audit transitive dependencies +- Keep dependencies updated — set up Dependabot or Renovate +- Minimize dependency count — evaluate before adding new crates + +```bash +# Security audit +cargo audit + +# Deny advisories, duplicate versions, and restricted licenses +cargo deny check + +# Inspect dependency tree +cargo tree +cargo tree -d # Show duplicates only +``` + +## Error Messages + +- Never expose internal paths, stack traces, or database errors in API responses +- Log detailed errors server-side; return generic messages to clients +- Use `tracing` or `log` for structured server-side logging + +```rust +// Map errors to appropriate status codes and generic messages +// (Example uses axum; adapt the response type to your framework) +match order_service.find_by_id(id) { + Ok(order) => Ok((StatusCode::OK, Json(order))), + Err(ServiceError::NotFound(_)) => { + tracing::info!(order_id = id, "order not found"); + Err((StatusCode::NOT_FOUND, "Resource not found")) + } + Err(e) => { + tracing::error!(order_id = id, error = %e, "unexpected error"); + Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")) + } +} +``` + +## References + +See skill: `rust-patterns` for unsafe code guidelines and ownership patterns. +See skill: `security-review` for general security checklists. diff --git a/rules/rust/testing.md b/rules/rust/testing.md new file mode 100644 index 0000000..dae4b67 --- /dev/null +++ b/rules/rust/testing.md @@ -0,0 +1,154 @@ +--- +paths: + - "**/*.rs" +--- +# Rust Testing + +> This file extends [common/testing.md](../common/testing.md) with Rust-specific content. + +## Test Framework + +- **`#[test]`** with `#[cfg(test)]` modules for unit tests +- **rstest** for parameterized tests and fixtures +- **proptest** for property-based testing +- **mockall** for trait-based mocking +- **`#[tokio::test]`** for async tests + +## Test Organization + +```text +my_crate/ +├── src/ +│ ├── lib.rs # Unit tests in #[cfg(test)] modules +│ ├── auth/ +│ │ └── mod.rs # #[cfg(test)] mod tests { ... } +│ └── orders/ +│ └── service.rs # #[cfg(test)] mod tests { ... } +├── tests/ # Integration tests (each file = separate binary) +│ ├── api_test.rs +│ ├── db_test.rs +│ └── common/ # Shared test utilities +│ └── mod.rs +└── benches/ # Criterion benchmarks + └── benchmark.rs +``` + +Unit tests go inside `#[cfg(test)]` modules in the same file. Integration tests go in `tests/`. + +## Unit Test Pattern + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn creates_user_with_valid_email() { + let user = User::new("Alice", "alice@example.com").unwrap(); + assert_eq!(user.name, "Alice"); + } + + #[test] + fn rejects_invalid_email() { + let result = User::new("Bob", "not-an-email"); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("invalid email")); + } +} +``` + +## Parameterized Tests + +```rust +use rstest::rstest; + +#[rstest] +#[case("hello", 5)] +#[case("", 0)] +#[case("rust", 4)] +fn test_string_length(#[case] input: &str, #[case] expected: usize) { + assert_eq!(input.len(), expected); +} +``` + +## Async Tests + +```rust +#[tokio::test] +async fn fetches_data_successfully() { + let client = TestClient::new().await; + let result = client.get("/data").await; + assert!(result.is_ok()); +} +``` + +## Mocking with mockall + +Define traits in production code; generate mocks in test modules: + +```rust +// Production trait — pub so integration tests can import it +pub trait UserRepository { + fn find_by_id(&self, id: u64) -> Option; +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::predicate::eq; + + mockall::mock! { + pub Repo {} + impl UserRepository for Repo { + fn find_by_id(&self, id: u64) -> Option; + } + } + + #[test] + fn service_returns_user_when_found() { + let mut mock = MockRepo::new(); + mock.expect_find_by_id() + .with(eq(42)) + .times(1) + .returning(|_| Some(User { id: 42, name: "Alice".into() })); + + let service = UserService::new(Box::new(mock)); + let user = service.get_user(42).unwrap(); + assert_eq!(user.name, "Alice"); + } +} +``` + +## Test Naming + +Use descriptive names that explain the scenario: +- `creates_user_with_valid_email()` +- `rejects_order_when_insufficient_stock()` +- `returns_none_when_not_found()` + +## Coverage + +- Target 80%+ line coverage +- Use **cargo-llvm-cov** for coverage reporting +- Focus on business logic — exclude generated code and FFI bindings + +```bash +cargo llvm-cov # Summary +cargo llvm-cov --html # HTML report +cargo llvm-cov --fail-under-lines 80 # Fail if below threshold +``` + +## Testing Commands + +```bash +cargo test # Run all tests +cargo test -- --nocapture # Show println output +cargo test test_name # Run tests matching pattern +cargo test --lib # Unit tests only +cargo test --test api_test # Specific integration test (tests/api_test.rs) +cargo test --doc # Doc tests only +``` + +## References + +See skill: `rust-testing` for comprehensive testing patterns including property-based testing, fixtures, and benchmarking with Criterion. diff --git a/rules/swift/coding-style.md b/rules/swift/coding-style.md new file mode 100644 index 0000000..d9fc38d --- /dev/null +++ b/rules/swift/coding-style.md @@ -0,0 +1,47 @@ +--- +paths: + - "**/*.swift" + - "**/Package.swift" +--- +# Swift Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with Swift specific content. + +## Formatting + +- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement +- `swift-format` is bundled with Xcode 16+ as an alternative + +## Immutability + +- Prefer `let` over `var` — define everything as `let` and only change to `var` if the compiler requires it +- Use `struct` with value semantics by default; use `class` only when identity or reference semantics are needed + +## Naming + +Follow [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/): + +- Clarity at the point of use — omit needless words +- Name methods and properties for their roles, not their types +- Use `static let` for constants over global constants + +## Error Handling + +Use typed throws (Swift 6+) and pattern matching: + +```swift +func load(id: String) throws(LoadError) -> Item { + guard let data = try? read(from: path) else { + throw .fileNotFound(id) + } + return try decode(data) +} +``` + +## Concurrency + +Enable Swift 6 strict concurrency checking. Prefer: + +- `Sendable` value types for data crossing isolation boundaries +- Actors for shared mutable state +- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}` diff --git a/rules/swift/hooks.md b/rules/swift/hooks.md new file mode 100644 index 0000000..0fbde36 --- /dev/null +++ b/rules/swift/hooks.md @@ -0,0 +1,20 @@ +--- +paths: + - "**/*.swift" + - "**/Package.swift" +--- +# Swift Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **SwiftFormat**: Auto-format `.swift` files after edit +- **SwiftLint**: Run lint checks after editing `.swift` files +- **swift build**: Type-check modified packages after edit + +## Warning + +Flag `print()` statements — use `os.Logger` or structured logging instead for production code. diff --git a/rules/swift/patterns.md b/rules/swift/patterns.md new file mode 100644 index 0000000..b03b0ba --- /dev/null +++ b/rules/swift/patterns.md @@ -0,0 +1,66 @@ +--- +paths: + - "**/*.swift" + - "**/Package.swift" +--- +# Swift Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with Swift specific content. + +## Protocol-Oriented Design + +Define small, focused protocols. Use protocol extensions for shared defaults: + +```swift +protocol Repository: Sendable { + associatedtype Item: Identifiable & Sendable + func find(by id: Item.ID) async throws -> Item? + func save(_ item: Item) async throws +} +``` + +## Value Types + +- Use structs for data transfer objects and models +- Use enums with associated values to model distinct states: + +```swift +enum LoadState: Sendable { + case idle + case loading + case loaded(T) + case failed(Error) +} +``` + +## Actor Pattern + +Use actors for shared mutable state instead of locks or dispatch queues: + +```swift +actor Cache { + private var storage: [Key: Value] = [:] + + func get(_ key: Key) -> Value? { storage[key] } + func set(_ key: Key, value: Value) { storage[key] = value } +} +``` + +## Dependency Injection + +Inject protocols with default parameters — production uses defaults, tests inject mocks: + +```swift +struct UserService { + private let repository: any UserRepository + + init(repository: any UserRepository = DefaultUserRepository()) { + self.repository = repository + } +} +``` + +## References + +See skill: `swift-actor-persistence` for actor-based persistence patterns. +See skill: `swift-protocol-di-testing` for protocol-based DI and testing. diff --git a/rules/swift/security.md b/rules/swift/security.md new file mode 100644 index 0000000..878503a --- /dev/null +++ b/rules/swift/security.md @@ -0,0 +1,33 @@ +--- +paths: + - "**/*.swift" + - "**/Package.swift" +--- +# Swift Security + +> This file extends [common/security.md](../common/security.md) with Swift specific content. + +## Secret Management + +- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never `UserDefaults` +- Use environment variables or `.xcconfig` files for build-time secrets +- Never hardcode secrets in source — decompilation tools extract them trivially + +```swift +let apiKey = ProcessInfo.processInfo.environment["API_KEY"] +guard let apiKey, !apiKey.isEmpty else { + fatalError("API_KEY not configured") +} +``` + +## Transport Security + +- App Transport Security (ATS) is enforced by default — do not disable it +- Use certificate pinning for critical endpoints +- Validate all server certificates + +## Input Validation + +- Sanitize all user input before display to prevent injection +- Use `URL(string:)` with validation rather than force-unwrapping +- Validate data from external sources (APIs, deep links, pasteboard) before processing diff --git a/rules/swift/testing.md b/rules/swift/testing.md new file mode 100644 index 0000000..9a1b012 --- /dev/null +++ b/rules/swift/testing.md @@ -0,0 +1,45 @@ +--- +paths: + - "**/*.swift" + - "**/Package.swift" +--- +# Swift Testing + +> This file extends [common/testing.md](../common/testing.md) with Swift specific content. + +## Framework + +Use **Swift Testing** (`import Testing`) for new tests. Use `@Test` and `#expect`: + +```swift +@Test("User creation validates email") +func userCreationValidatesEmail() throws { + #expect(throws: ValidationError.invalidEmail) { + try User(email: "not-an-email") + } +} +``` + +## Test Isolation + +Each test gets a fresh instance — set up in `init`, tear down in `deinit`. No shared mutable state between tests. + +## Parameterized Tests + +```swift +@Test("Validates formats", arguments: ["json", "xml", "csv"]) +func validatesFormat(format: String) throws { + let parser = try Parser(format: format) + #expect(parser.isValid) +} +``` + +## Coverage + +```bash +swift test --enable-code-coverage +``` + +## Reference + +See skill: `swift-protocol-di-testing` for protocol-based dependency injection and mock patterns with Swift Testing. diff --git a/rules/typescript/coding-style.md b/rules/typescript/coding-style.md new file mode 100644 index 0000000..090c0a1 --- /dev/null +++ b/rules/typescript/coding-style.md @@ -0,0 +1,199 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" +--- +# TypeScript/JavaScript Coding Style + +> This file extends [common/coding-style.md](../common/coding-style.md) with TypeScript/JavaScript specific content. + +## Types and Interfaces + +Use types to make public APIs, shared models, and component props explicit, readable, and reusable. + +### Public APIs + +- Add parameter and return types to exported functions, shared utilities, and public class methods +- Let TypeScript infer obvious local variable types +- Extract repeated inline object shapes into named types or interfaces + +```typescript +// WRONG: Exported function without explicit types +export function formatUser(user) { + return `${user.firstName} ${user.lastName}` +} + +// CORRECT: Explicit types on public APIs +interface User { + firstName: string + lastName: string +} + +export function formatUser(user: User): string { + return `${user.firstName} ${user.lastName}` +} +``` + +### Interfaces vs. Type Aliases + +- Use `interface` for object shapes that may be extended or implemented +- Use `type` for unions, intersections, tuples, mapped types, and utility types +- Prefer string literal unions over `enum` unless an `enum` is required for interoperability + +```typescript +interface User { + id: string + email: string +} + +type UserRole = 'admin' | 'member' +type UserWithRole = User & { + role: UserRole +} +``` + +### Avoid `any` + +- Avoid `any` in application code +- Use `unknown` for external or untrusted input, then narrow it safely +- Use generics when a value's type depends on the caller + +```typescript +// WRONG: any removes type safety +function getErrorMessage(error: any) { + return error.message +} + +// CORRECT: unknown forces safe narrowing +function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message + } + + return 'Unexpected error' +} +``` + +### React Props + +- Define component props with a named `interface` or `type` +- Type callback props explicitly +- Do not use `React.FC` unless there is a specific reason to do so + +```typescript +interface User { + id: string + email: string +} + +interface UserCardProps { + user: User + onSelect: (id: string) => void +} + +function UserCard({ user, onSelect }: UserCardProps) { + return +} +``` + +### JavaScript Files + +- In `.js` and `.jsx` files, use JSDoc when types improve clarity and a TypeScript migration is not practical +- Keep JSDoc aligned with runtime behavior + +```javascript +/** + * @param {{ firstName: string, lastName: string }} user + * @returns {string} + */ +export function formatUser(user) { + return `${user.firstName} ${user.lastName}` +} +``` + +## Immutability + +Use spread operator for immutable updates: + +```typescript +interface User { + id: string + name: string +} + +// WRONG: Mutation +function updateUser(user: User, name: string): User { + user.name = name // MUTATION! + return user +} + +// CORRECT: Immutability +function updateUser(user: Readonly, name: string): User { + return { + ...user, + name + } +} +``` + +## Error Handling + +Use async/await with try-catch and narrow unknown errors safely: + +```typescript +interface User { + id: string + email: string +} + +declare function riskyOperation(userId: string): Promise + +function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message + } + + return 'Unexpected error' +} + +const logger = { + error: (message: string, error: unknown) => { + // Replace with your production logger (for example, pino or winston). + } +} + +async function loadUser(userId: string): Promise { + try { + const result = await riskyOperation(userId) + return result + } catch (error: unknown) { + logger.error('Operation failed', error) + throw new Error(getErrorMessage(error)) + } +} +``` + +## Input Validation + +Use Zod for schema-based validation and infer types from the schema: + +```typescript +import { z } from 'zod' + +const userSchema = z.object({ + email: z.string().email(), + age: z.number().int().min(0).max(150) +}) + +type UserInput = z.infer + +const validated: UserInput = userSchema.parse(input) +``` + +## Console.log + +- No `console.log` statements in production code +- Use proper logging libraries instead +- See hooks for automatic detection diff --git a/rules/typescript/hooks.md b/rules/typescript/hooks.md new file mode 100644 index 0000000..cd4754b --- /dev/null +++ b/rules/typescript/hooks.md @@ -0,0 +1,22 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" +--- +# TypeScript/JavaScript Hooks + +> This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content. + +## PostToolUse Hooks + +Configure in `~/.claude/settings.json`: + +- **Prettier**: Auto-format JS/TS files after edit +- **TypeScript check**: Run `tsc` after editing `.ts`/`.tsx` files +- **console.log warning**: Warn about `console.log` in edited files + +## Stop Hooks + +- **console.log audit**: Check all modified files for `console.log` before session ends diff --git a/rules/typescript/patterns.md b/rules/typescript/patterns.md new file mode 100644 index 0000000..d50729d --- /dev/null +++ b/rules/typescript/patterns.md @@ -0,0 +1,52 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" +--- +# TypeScript/JavaScript Patterns + +> This file extends [common/patterns.md](../common/patterns.md) with TypeScript/JavaScript specific content. + +## API Response Format + +```typescript +interface ApiResponse { + success: boolean + data?: T + error?: string + meta?: { + total: number + page: number + limit: number + } +} +``` + +## Custom Hooks Pattern + +```typescript +export function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + const handler = setTimeout(() => setDebouncedValue(value), delay) + return () => clearTimeout(handler) + }, [value, delay]) + + return debouncedValue +} +``` + +## Repository Pattern + +```typescript +interface Repository { + findAll(filters?: Filters): Promise + findById(id: string): Promise + create(data: CreateDto): Promise + update(id: string, data: UpdateDto): Promise + delete(id: string): Promise +} +``` diff --git a/rules/typescript/security.md b/rules/typescript/security.md new file mode 100644 index 0000000..98ba400 --- /dev/null +++ b/rules/typescript/security.md @@ -0,0 +1,28 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" +--- +# TypeScript/JavaScript Security + +> This file extends [common/security.md](../common/security.md) with TypeScript/JavaScript specific content. + +## Secret Management + +```typescript +// NEVER: Hardcoded secrets +const apiKey = "sk-proj-xxxxx" + +// ALWAYS: Environment variables +const apiKey = process.env.OPENAI_API_KEY + +if (!apiKey) { + throw new Error('OPENAI_API_KEY not configured') +} +``` + +## Agent Support + +- Use **security-reviewer** skill for comprehensive security audits diff --git a/rules/typescript/testing.md b/rules/typescript/testing.md new file mode 100644 index 0000000..6f2f402 --- /dev/null +++ b/rules/typescript/testing.md @@ -0,0 +1,18 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" +--- +# TypeScript/JavaScript Testing + +> This file extends [common/testing.md](../common/testing.md) with TypeScript/JavaScript specific content. + +## E2E Testing + +Use **Playwright** as the E2E testing framework for critical user flows. + +## Agent Support + +- **e2e-runner** - Playwright E2E testing specialist diff --git a/rules/web/coding-style.md b/rules/web/coding-style.md new file mode 100644 index 0000000..5707164 --- /dev/null +++ b/rules/web/coding-style.md @@ -0,0 +1,96 @@ +> This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content. + +# Web Coding Style + +## File Organization + +Organize by feature or surface area, not by file type: + +```text +src/ +├── components/ +│ ├── hero/ +│ │ ├── Hero.tsx +│ │ ├── HeroVisual.tsx +│ │ └── hero.css +│ ├── scrolly-section/ +│ │ ├── ScrollySection.tsx +│ │ ├── StickyVisual.tsx +│ │ └── scrolly.css +│ └── ui/ +│ ├── Button.tsx +│ ├── SurfaceCard.tsx +│ └── AnimatedText.tsx +├── hooks/ +│ ├── useReducedMotion.ts +│ └── useScrollProgress.ts +├── lib/ +│ ├── animation.ts +│ └── color.ts +└── styles/ + ├── tokens.css + ├── typography.css + └── global.css +``` + +## CSS Custom Properties + +Define design tokens as variables. Do not hardcode palette, typography, or spacing repeatedly: + +```css +:root { + --color-surface: oklch(98% 0 0); + --color-text: oklch(18% 0 0); + --color-accent: oklch(68% 0.21 250); + + --text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem); + --text-hero: clamp(3rem, 1rem + 7vw, 8rem); + + --space-section: clamp(4rem, 3rem + 5vw, 10rem); + + --duration-fast: 150ms; + --duration-normal: 300ms; + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); +} +``` + +## Animation-Only Properties + +Prefer compositor-friendly motion: +- `transform` +- `opacity` +- `clip-path` +- `filter` (sparingly) + +Avoid animating layout-bound properties: +- `width` +- `height` +- `top` +- `left` +- `margin` +- `padding` +- `border` +- `font-size` + +## Semantic HTML First + +```html +
+ +
+
+
+

...

+
+
+
...
+``` + +Do not reach for generic wrapper `div` stacks when a semantic element exists. + +## Naming + +- Components: PascalCase (`ScrollySection`, `SurfaceCard`) +- Hooks: `use` prefix (`useReducedMotion`) +- CSS classes: kebab-case or utility classes +- Animation timelines: camelCase with intent (`heroRevealTl`) diff --git a/rules/web/design-quality.md b/rules/web/design-quality.md new file mode 100644 index 0000000..22c63c3 --- /dev/null +++ b/rules/web/design-quality.md @@ -0,0 +1,63 @@ +> This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance. + +# Web Design Quality Standards + +## Anti-Template Policy + +Do not ship generic template-looking UI. Frontend output should look intentional, opinionated, and specific to the product. + +### Banned Patterns + +- Default card grids with uniform spacing and no hierarchy +- Stock hero section with centered headline, gradient blob, and generic CTA +- Unmodified library defaults passed off as finished design +- Flat layouts with no layering, depth, or motion +- Uniform radius, spacing, and shadows across every component +- Safe gray-on-white styling with one decorative accent color +- Dashboard-by-numbers layouts with sidebar + cards + charts and no point of view +- Default font stacks used without a deliberate reason + +### Required Qualities + +Every meaningful frontend surface should demonstrate at least four of these: + +1. Clear hierarchy through scale contrast +2. Intentional rhythm in spacing, not uniform padding everywhere +3. Depth or layering through overlap, shadows, surfaces, or motion +4. Typography with character and a real pairing strategy +5. Color used semantically, not just decoratively +6. Hover, focus, and active states that feel designed +7. Grid-breaking editorial or bento composition where appropriate +8. Texture, grain, or atmosphere when it fits the visual direction +9. Motion that clarifies flow instead of distracting from it +10. Data visualization treated as part of the design system, not an afterthought + +## Before Writing Frontend Code + +1. Pick a specific style direction. Avoid vague defaults like "clean minimal". +2. Define a palette intentionally. +3. Choose typography deliberately. +4. Gather at least a small set of real references. +5. Use ECC design/frontend skills where relevant. + +## Worthwhile Style Directions + +- Editorial / magazine +- Neo-brutalism +- Glassmorphism with real depth +- Dark luxury or light luxury with disciplined contrast +- Bento layouts +- Scrollytelling +- 3D integration +- Swiss / International +- Retro-futurism + +Do not default to dark mode automatically. Choose the visual direction the product actually wants. + +## Component Checklist + +- [ ] Does it avoid looking like a default Tailwind or shadcn template? +- [ ] Does it have intentional hover/focus/active states? +- [ ] Does it use hierarchy rather than uniform emphasis? +- [ ] Would this look believable in a real product screenshot? +- [ ] If it supports both themes, do both light and dark feel intentional? diff --git a/rules/web/hooks.md b/rules/web/hooks.md new file mode 100644 index 0000000..22f8c27 --- /dev/null +++ b/rules/web/hooks.md @@ -0,0 +1,120 @@ +> This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations. + +# Web Hooks + +## Recommended PostToolUse Hooks + +Prefer project-local tooling. Do not wire hooks to remote one-off package execution. + +### Format on Save + +Use the project's existing formatter entrypoint after edits: + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "command": "pnpm prettier --write \"$FILE_PATH\"", + "description": "Format edited frontend files" + } + ] + } +} +``` + +Equivalent local commands via `yarn prettier` or `npm exec prettier --` are fine when they use repo-owned dependencies. + +### Lint Check + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "command": "pnpm eslint --fix \"$FILE_PATH\"", + "description": "Run ESLint on edited frontend files" + } + ] + } +} +``` + +### Type Check + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "command": "pnpm tsc --noEmit --pretty false", + "description": "Type-check after frontend edits" + } + ] + } +} +``` + +### CSS Lint + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "command": "pnpm stylelint --fix \"$FILE_PATH\"", + "description": "Lint edited stylesheets" + } + ] + } +} +``` + +## PreToolUse Hooks + +### Guard File Size + +Block oversized writes from tool input content, not from a file that may not exist yet: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Write", + "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller modules');process.exit(2)}console.log(d)})\"", + "description": "Block writes that exceed 800 lines" + } + ] + } +} +``` + +## Stop Hooks + +### Final Build Verification + +```json +{ + "hooks": { + "Stop": [ + { + "command": "pnpm build", + "description": "Verify the production build at session end" + } + ] + } +} +``` + +## Ordering + +Recommended order: +1. format +2. lint +3. type check +4. build verification diff --git a/rules/web/patterns.md b/rules/web/patterns.md new file mode 100644 index 0000000..ccec368 --- /dev/null +++ b/rules/web/patterns.md @@ -0,0 +1,79 @@ +> This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns. + +# Web Patterns + +## Component Composition + +### Compound Components + +Use compound components when related UI shares state and interaction semantics: + +```tsx + + + Overview + Settings + + ... + ... + +``` + +- Parent owns state +- Children consume via context +- Prefer this over prop drilling for complex widgets + +### Render Props / Slots + +- Use render props or slot patterns when behavior is shared but markup must vary +- Keep keyboard handling, ARIA, and focus logic in the headless layer + +### Container / Presentational Split + +- Container components own data loading and side effects +- Presentational components receive props and render UI +- Presentational components should stay pure + +## State Management + +Treat these separately: + +| Concern | Tooling | +|---------|---------| +| Server state | TanStack Query, SWR, tRPC | +| Client state | Zustand, Jotai, signals | +| URL state | search params, route segments | +| Form state | React Hook Form or equivalent | + +- Do not duplicate server state into client stores +- Derive values instead of storing redundant computed state + +## URL As State + +Persist shareable state in the URL: +- filters +- sort order +- pagination +- active tab +- search query + +## Data Fetching + +### Stale-While-Revalidate + +- Return cached data immediately +- Revalidate in the background +- Prefer existing libraries instead of rolling this by hand + +### Optimistic Updates + +- Snapshot current state +- Apply optimistic update +- Roll back on failure +- Emit visible error feedback when rolling back + +### Parallel Loading + +- Fetch independent data in parallel +- Avoid parent-child request waterfalls +- Prefetch likely next routes or states when justified diff --git a/rules/web/performance.md b/rules/web/performance.md new file mode 100644 index 0000000..b720280 --- /dev/null +++ b/rules/web/performance.md @@ -0,0 +1,64 @@ +> This file extends [common/performance.md](../common/performance.md) with web-specific performance content. + +# Web Performance Rules + +## Core Web Vitals Targets + +| Metric | Target | +|--------|--------| +| LCP | < 2.5s | +| INP | < 200ms | +| CLS | < 0.1 | +| FCP | < 1.5s | +| TBT | < 200ms | + +## Bundle Budget + +| Page Type | JS Budget (gzipped) | CSS Budget | +|-----------|---------------------|------------| +| Landing page | < 150kb | < 30kb | +| App page | < 300kb | < 50kb | +| Microsite | < 80kb | < 15kb | + +## Loading Strategy + +1. Inline critical above-the-fold CSS where justified +2. Preload the hero image and primary font only +3. Defer non-critical CSS or JS +4. Dynamically import heavy libraries + +```js +const gsapModule = await import('gsap'); +const { ScrollTrigger } = await import('gsap/ScrollTrigger'); +``` + +## Image Optimization + +- Explicit `width` and `height` +- `loading="eager"` plus `fetchpriority="high"` for hero media only +- `loading="lazy"` for below-the-fold assets +- Prefer AVIF or WebP with fallbacks +- Never ship source images far beyond rendered size + +## Font Loading + +- Max two font families unless there is a clear exception +- `font-display: swap` +- Subset where possible +- Preload only the truly critical weight/style + +## Animation Performance + +- Animate compositor-friendly properties only +- Use `will-change` narrowly and remove it when done +- Prefer CSS for simple transitions +- Use `requestAnimationFrame` or established animation libraries for JS motion +- Avoid scroll handler churn; use IntersectionObserver or well-behaved libraries + +## Performance Checklist + +- [ ] All images have explicit dimensions +- [ ] No accidental render-blocking resources +- [ ] No layout shifts from dynamic content +- [ ] Motion stays on compositor-friendly properties +- [ ] Third-party scripts load async/defer and only when needed diff --git a/rules/web/security.md b/rules/web/security.md new file mode 100644 index 0000000..b44278c --- /dev/null +++ b/rules/web/security.md @@ -0,0 +1,57 @@ +> This file extends [common/security.md](../common/security.md) with web-specific security content. + +# Web Security Rules + +## Content Security Policy + +Always configure a production CSP. + +### Nonce-Based CSP + +Use a per-request nonce for scripts instead of `'unsafe-inline'`. + +```text +Content-Security-Policy: + default-src 'self'; + script-src 'self' 'nonce-{RANDOM}' https://cdn.jsdelivr.net; + style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; + img-src 'self' data: https:; + font-src 'self' https://fonts.gstatic.com; + connect-src 'self' https://*.example.com; + frame-src 'none'; + object-src 'none'; + base-uri 'self'; +``` + +Adjust origins to the project. Do not cargo-cult this block unchanged. + +## XSS Prevention + +- Never inject unsanitized HTML +- Avoid `innerHTML` / `dangerouslySetInnerHTML` unless sanitized first +- Escape dynamic template values +- Sanitize user HTML with a vetted local sanitizer when absolutely necessary + +## Third-Party Scripts + +- Load asynchronously +- Use SRI when serving from a CDN +- Audit quarterly +- Prefer self-hosting for critical dependencies when practical + +## HTTPS and Headers + +```text +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +Referrer-Policy: strict-origin-when-cross-origin +Permissions-Policy: camera=(), microphone=(), geolocation=() +``` + +## Forms + +- CSRF protection on state-changing forms +- Rate limiting on submission endpoints +- Validate client and server side +- Prefer honeypots or light anti-abuse controls over heavy-handed CAPTCHA defaults diff --git a/rules/web/testing.md b/rules/web/testing.md new file mode 100644 index 0000000..6bf5812 --- /dev/null +++ b/rules/web/testing.md @@ -0,0 +1,55 @@ +> This file extends [common/testing.md](../common/testing.md) with web-specific testing content. + +# Web Testing Rules + +## Priority Order + +### 1. Visual Regression + +- Screenshot key breakpoints: 320, 768, 1024, 1440 +- Test hero sections, scrollytelling sections, and meaningful states +- Use Playwright screenshots for visual-heavy work +- If both themes exist, test both + +### 2. Accessibility + +- Run automated accessibility checks +- Test keyboard navigation +- Verify reduced-motion behavior +- Verify color contrast + +### 3. Performance + +- Run Lighthouse or equivalent against meaningful pages +- Keep CWV targets from [performance.md](performance.md) + +### 4. Cross-Browser + +- Minimum: Chrome, Firefox, Safari +- Test scrolling, motion, and fallback behavior + +### 5. Responsive + +- Test 320, 375, 768, 1024, 1440, 1920 +- Verify no overflow +- Verify touch interactions + +## E2E Shape + +```ts +import { test, expect } from '@playwright/test'; + +test('landing hero loads', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toBeVisible(); +}); +``` + +- Avoid flaky timeout-based assertions +- Prefer deterministic waits + +## Unit Tests + +- Test utilities, data transforms, and custom hooks +- For highly visual components, visual regression often carries more signal than brittle markup assertions +- Visual regression supplements coverage targets; it does not replace them diff --git a/rules/zh/README.md b/rules/zh/README.md new file mode 100644 index 0000000..35baaf6 --- /dev/null +++ b/rules/zh/README.md @@ -0,0 +1,108 @@ +# 规则 + +## 结构 + +规则按**通用**层和**语言特定**目录组织: + +``` +rules/ +├── common/ # 语言无关的原则(始终安装) +│ ├── coding-style.md +│ ├── git-workflow.md +│ ├── testing.md +│ ├── performance.md +│ ├── patterns.md +│ ├── hooks.md +│ ├── agents.md +│ ├── security.md +│ ├── code-review.md +│ └── development-workflow.md +├── zh/ # 中文翻译版本 +│ ├── coding-style.md +│ ├── git-workflow.md +│ ├── testing.md +│ ├── performance.md +│ ├── patterns.md +│ ├── hooks.md +│ ├── agents.md +│ ├── security.md +│ ├── code-review.md +│ └── development-workflow.md +├── typescript/ # TypeScript/JavaScript 特定 +├── python/ # Python 特定 +├── golang/ # Go 特定 +├── swift/ # Swift 特定 +└── php/ # PHP 特定 +``` + +- **common/** 包含通用原则 — 无语言特定的代码示例。 +- **zh/** 包含 common 目录的中文翻译版本。 +- **语言目录** 扩展通用规则,包含框架特定的模式、工具和代码示例。每个文件引用其对应的通用版本。 + +## 安装 + +### 选项 1:安装脚本(推荐) + +```bash +# 安装通用 + 一个或多个语言特定的规则集 +./install.sh typescript +./install.sh python +./install.sh golang +./install.sh swift +./install.sh php + +# 同时安装多种语言 +./install.sh typescript python +``` + +### 选项 2:手动安装 + +> **重要提示:** 复制整个目录 — 不要使用 `/*` 展开。 +> 通用和语言特定目录包含同名文件。 +> 将它们展开到一个目录会导致语言特定文件覆盖通用规则, +> 并破坏语言特定文件使用的 `../common/` 相对引用。 + +```bash +# 创建目标目录 +mkdir -p ~/.claude/rules + +# 安装通用规则(所有项目必需) +cp -r rules/common ~/.claude/rules/common + +# 安装中文翻译版本(可选) +cp -r rules/zh ~/.claude/rules/zh + +# 根据项目技术栈安装语言特定规则 +cp -r rules/typescript ~/.claude/rules/typescript +cp -r rules/python ~/.claude/rules/python +cp -r rules/golang ~/.claude/rules/golang +cp -r rules/swift ~/.claude/rules/swift +cp -r rules/php ~/.claude/rules/php +``` + +## 规则 vs 技能 + +- **规则** 定义广泛适用的标准、约定和检查清单(如"80% 测试覆盖率"、"禁止硬编码密钥")。 +- **技能**(`skills/` 目录)为特定任务提供深入、可操作的参考材料(如 `python-patterns`、`golang-testing`)。 + +语言特定的规则文件在适当的地方引用相关技能。规则告诉你*做什么*;技能告诉你*怎么做*。 + +## 规则优先级 + +当语言特定规则与通用规则冲突时,**语言特定规则优先**(特定覆盖通用)。这遵循标准的分层配置模式(类似于 CSS 特异性或 `.gitignore` 优先级)。 + +- `rules/common/` 定义适用于所有项目的通用默认值。 +- `rules/golang/`、`rules/python/`、`rules/swift/`、`rules/php/`、`rules/typescript/` 等在语言习惯不同时覆盖这些默认值。 +- `rules/zh/` 是通用规则的中文翻译,与英文版本内容一致。 + +### 示例 + +`common/coding-style.md` 推荐不可变性作为默认原则。语言特定的 `golang/coding-style.md` 可以覆盖这一点: + +> 惯用的 Go 使用指针接收器进行结构体变更 — 参见 [common/coding-style.md](../common/coding-style.md) 了解通用原则,但这里首选符合 Go 习惯的变更方式。 + +### 带覆盖说明的通用规则 + +`rules/common/` 中可能被语言特定文件覆盖的规则会被标记: + +> **语言说明**:此规则可能会被语言特定规则覆盖;对于某些语言,该模式可能并不符合惯用写法。 diff --git a/rules/zh/agents.md b/rules/zh/agents.md new file mode 100644 index 0000000..3723d4b --- /dev/null +++ b/rules/zh/agents.md @@ -0,0 +1,50 @@ +# 代理编排 + +## 可用代理 + +位于 `~/.claude/agents/`: + +| 代理 | 用途 | 何时使用 | +|-------|---------|------------| +| planner | 实现规划 | 复杂功能、重构 | +| architect | 系统设计 | 架构决策 | +| tdd-guide | 测试驱动开发 | 新功能、bug 修复 | +| code-reviewer | 代码审查 | 编写代码后 | +| security-reviewer | 安全分析 | 提交前 | +| build-error-resolver | 修复构建错误 | 构建失败时 | +| e2e-runner | E2E 测试 | 关键用户流程 | +| refactor-cleaner | 死代码清理 | 代码维护 | +| doc-updater | 文档 | 更新文档 | +| rust-reviewer | Rust 代码审查 | Rust 项目 | + +## 立即使用代理 + +无需用户提示: +1. 复杂功能请求 - 使用 **planner** 代理 +2. 刚编写/修改的代码 - 使用 **code-reviewer** 代理 +3. Bug 修复或新功能 - 使用 **tdd-guide** 代理 +4. 架构决策 - 使用 **architect** 代理 + +## 并行任务执行 + +对独立操作始终使用并行 Task 执行: + +```markdown +# 好:并行执行 +同时启动 3 个代理: +1. 代理 1:认证模块安全分析 +2. 代理 2:缓存系统性能审查 +3. 代理 3:工具类型检查 + +# 坏:不必要的顺序 +先代理 1,然后代理 2,然后代理 3 +``` + +## 多视角分析 + +对于复杂问题,使用分角色子代理: +- 事实审查者 +- 高级工程师 +- 安全专家 +- 一致性审查者 +- 冗余检查者 diff --git a/rules/zh/code-review.md b/rules/zh/code-review.md new file mode 100644 index 0000000..bb4c228 --- /dev/null +++ b/rules/zh/code-review.md @@ -0,0 +1,124 @@ +# 代码审查标准 + +## 目的 + +代码审查确保代码合并前的质量、安全性和可维护性。此规则定义何时以及如何进行代码审查。 + +## 何时审查 + +**强制审查触发条件:** + +- 编写或修改代码后 +- 提交到共享分支之前 +- 更改安全敏感代码时(认证、支付、用户数据) +- 进行架构更改时 +- 合并 pull request 之前 + +**审查前要求:** + +在请求审查之前,确保: + +- 所有自动化检查(CI/CD)已通过 +- 合并冲突已解决 +- 分支已与目标分支同步 + +## 审查检查清单 + +在标记代码完成之前: + +- [ ] 代码可读且命名良好 +- [ ] 函数聚焦(<50 行) +- [ ] 文件内聚(<800 行) +- [ ] 无深层嵌套(>4 层) +- [ ] 错误显式处理 +- [ ] 无硬编码密钥或凭据 +- [ ] 无 console.log 或调试语句 +- [ ] 新功能有测试 +- [ ] 测试覆盖率满足 80% 最低要求 + +## 安全审查触发条件 + +**停止并使用 security-reviewer 代理当:** + +- 认证或授权代码 +- 用户输入处理 +- 数据库查询 +- 文件系统操作 +- 外部 API 调用 +- 加密操作 +- 支付或金融代码 + +## 审查严重级别 + +| 级别 | 含义 | 行动 | +|-------|---------|--------| +| CRITICAL(关键) | 安全漏洞或数据丢失风险 | **阻止** - 合并前必须修复 | +| HIGH(高) | Bug 或重大质量问题 | **警告** - 合并前应修复 | +| MEDIUM(中) | 可维护性问题 | **信息** - 考虑修复 | +| LOW(低) | 风格或次要建议 | **注意** - 可选 | + +## 代理使用 + +使用这些代理进行代码审查: + +| 代理 | 用途 | +|-------|--------| +| **code-reviewer** | 通用代码质量、模式、最佳实践 | +| **security-reviewer** | 安全漏洞、OWASP Top 10 | +| **typescript-reviewer** | TypeScript/JavaScript 特定问题 | +| **python-reviewer** | Python 特定问题 | +| **go-reviewer** | Go 特定问题 | +| **rust-reviewer** | Rust 特定问题 | + +## 审查工作流 + +``` +1. 运行 git diff 了解更改 +2. 先检查安全检查清单 +3. 审查代码质量检查清单 +4. 运行相关测试 +5. 验证覆盖率 >= 80% +6. 使用适当的代理进行详细审查 +``` + +## 常见问题捕获 + +### 安全 + +- 硬编码凭据(API 密钥、密码、令牌) +- SQL 注入(查询中的字符串拼接) +- XSS 漏洞(未转义的用户输入) +- 路径遍历(未净化的文件路径) +- CSRF 保护缺失 +- 认证绕过 + +### 代码质量 + +- 大函数(>50 行)- 拆分为更小的 +- 大文件(>800 行)- 提取模块 +- 深层嵌套(>4 层)- 使用提前返回 +- 缺少错误处理 - 显式处理 +- 变更模式 - 优先使用不可变操作 +- 缺少测试 - 添加测试覆盖 + +### 性能 + +- N+1 查询 - 使用 JOIN 或批处理 +- 缺少分页 - 给查询添加 LIMIT +- 无界查询 - 添加约束 +- 缺少缓存 - 缓存昂贵操作 + +## 批准标准 + +- **批准**:无关键或高优先级问题 +- **警告**:仅有高优先级问题(谨慎合并) +- **阻止**:发现关键问题 + +## 与其他规则的集成 + +此规则与以下规则配合: + +- [testing.md](testing.md) - 测试覆盖率要求 +- [security.md](security.md) - 安全检查清单 +- [git-workflow.md](git-workflow.md) - 提交标准 +- [agents.md](agents.md) - 代理委托 diff --git a/rules/zh/coding-style.md b/rules/zh/coding-style.md new file mode 100644 index 0000000..e20a609 --- /dev/null +++ b/rules/zh/coding-style.md @@ -0,0 +1,48 @@ +# 编码风格 + +## 不可变性(关键) + +始终创建新对象,永远不要修改现有对象: + +``` +// 伪代码 +错误: modify(original, field, value) → 就地修改 original +正确: update(original, field, value) → 返回带有更改的新副本 +``` + +原理:不可变数据防止隐藏的副作用,使调试更容易,并启用安全的并发。 + +## 文件组织 + +多个小文件 > 少量大文件: +- 高内聚,低耦合 +- 典型 200-400 行,最多 800 行 +- 从大模块中提取工具函数 +- 按功能/领域组织,而非按类型 + +## 错误处理 + +始终全面处理错误: +- 在每一层显式处理错误 +- 在面向 UI 的代码中提供用户友好的错误消息 +- 在服务器端记录详细的错误上下文 +- 永远不要静默吞掉错误 + +## 输入验证 + +始终在系统边界验证: +- 处理前验证所有用户输入 +- 在可用的情况下使用基于模式的验证 +- 快速失败并给出清晰的错误消息 +- 永远不要信任外部数据(API 响应、用户输入、文件内容) + +## 代码质量检查清单 + +在标记工作完成前: +- [ ] 代码可读且命名良好 +- [ ] 函数很小(<50 行) +- [ ] 文件聚焦(<800 行) +- [ ] 没有深层嵌套(>4 层) +- [ ] 正确的错误处理 +- [ ] 没有硬编码值(使用常量或配置) +- [ ] 没有变更(使用不可变模式) diff --git a/rules/zh/development-workflow.md b/rules/zh/development-workflow.md new file mode 100644 index 0000000..f0aa978 --- /dev/null +++ b/rules/zh/development-workflow.md @@ -0,0 +1,44 @@ +# 开发工作流 + +> 此文件扩展 [common/git-workflow.md](./git-workflow.md),包含 git 操作之前的完整功能开发流程。 + +功能实现工作流描述了开发管道:研究、规划、TDD、代码审查,然后提交到 git。 + +## 功能实现工作流 + +0. **研究与重用** _(任何新实现前必需)_ + - **GitHub 代码搜索优先:** 在编写任何新代码之前,运行 `gh search repos` 和 `gh search code` 查找现有实现、模板和模式。 + - **库文档其次:** 使用 Context7 或主要供应商文档确认 API 行为、包使用和版本特定细节。 + - **仅当前两者不足时使用 Exa:** 在 GitHub 搜索和主要文档之后,使用 Exa 进行更广泛的网络研究或发现。 + - **检查包注册表:** 在编写工具代码之前搜索 npm、PyPI、crates.io 和其他注册表。首选久经考验的库而非手工编写的解决方案。 + - **搜索可适配的实现:** 寻找解决问题 80%+ 且可以分支、移植或包装的开源项目。 + - 当满足需求时,优先采用或移植经验证的方法而非从头编写新代码。 + +1. **先规划** + - 使用 **planner** 代理创建实现计划 + - 编码前生成规划文档:PRD、架构、系统设计、技术文档、任务列表 + - 识别依赖和风险 + - 分解为阶段 + +2. **TDD 方法** + - 使用 **tdd-guide** 代理 + - 先写测试(RED) + - 实现以通过测试(GREEN) + - 重构(IMPROVE) + - 验证 80%+ 覆盖率 + +3. **代码审查** + - 编写代码后立即使用 **code-reviewer** 代理 + - 解决关键和高优先级问题 + - 尽可能修复中优先级问题 + +4. **提交与推送** + - 详细的提交消息 + - 遵循约定式提交格式 + - 参见 [git-workflow.md](./git-workflow.md) 了解提交消息格式和 PR 流程 + +5. **审查前检查** + - 验证所有自动化检查(CI/CD)已通过 + - 解决任何合并冲突 + - 确保分支已与目标分支同步 + - 仅在这些检查通过后请求审查 diff --git a/rules/zh/git-workflow.md b/rules/zh/git-workflow.md new file mode 100644 index 0000000..50a4217 --- /dev/null +++ b/rules/zh/git-workflow.md @@ -0,0 +1,24 @@ +# Git 工作流 + +## 提交消息格式 +``` +<类型>: <描述> + +<可选正文> +``` + +类型:feat, fix, refactor, docs, test, chore, perf, ci + +注意:通过 ~/.claude/settings.json 全局禁用归属。 + +## Pull Request 工作流 + +创建 PR 时: +1. 分析完整提交历史(不仅是最新提交) +2. 使用 `git diff [base-branch]...HEAD` 查看所有更改 +3. 起草全面的 PR 摘要 +4. 包含带有 TODO 的测试计划 +5. 如果是新分支,使用 `-u` 标志推送 + +> 对于 git 操作之前的完整开发流程(规划、TDD、代码审查), +> 参见 [development-workflow.md](./development-workflow.md)。 diff --git a/rules/zh/hooks.md b/rules/zh/hooks.md new file mode 100644 index 0000000..e66383b --- /dev/null +++ b/rules/zh/hooks.md @@ -0,0 +1,30 @@ +# 钩子系统 + +## 钩子类型 + +- **PreToolUse**:工具执行前(验证、参数修改) +- **PostToolUse**:工具执行后(自动格式化、检查) +- **Stop**:会话结束时(最终验证) + +## 自动接受权限 + +谨慎使用: +- 为可信、定义明确的计划启用 +- 探索性工作时禁用 +- 永远不要使用 dangerously-skip-permissions 标志 +- 改为在 `~/.claude.json` 中配置 `allowedTools` + +## TodoWrite 最佳实践 + +使用 TodoWrite 工具: +- 跟踪多步骤任务的进度 +- 验证对指令的理解 +- 启用实时引导 +- 显示细粒度的实现步骤 + +待办列表揭示: +- 顺序错误的步骤 +- 缺失的项目 +- 多余的不必要项目 +- 错误的粒度 +- 误解的需求 diff --git a/rules/zh/patterns.md b/rules/zh/patterns.md new file mode 100644 index 0000000..e39f245 --- /dev/null +++ b/rules/zh/patterns.md @@ -0,0 +1,31 @@ +# 常用模式 + +## 骨架项目 + +实现新功能时: +1. 搜索久经考验的骨架项目 +2. 使用并行代理评估选项: + - 安全性评估 + - 可扩展性分析 + - 相关性评分 + - 实现规划 +3. 克隆最佳匹配作为基础 +4. 在经验证的结构内迭代 + +## 设计模式 + +### 仓储模式 + +将数据访问封装在一致的接口后面: +- 定义标准操作:findAll、findById、create、update、delete +- 具体实现处理存储细节(数据库、API、文件等) +- 业务逻辑依赖抽象接口,而非存储机制 +- 便于轻松切换数据源,并简化使用模拟的测试 + +### API 响应格式 + +对所有 API 响应使用一致的信封: +- 包含成功/状态指示器 +- 包含数据负载(错误时可为空) +- 包含错误消息字段(成功时可为空) +- 包含分页响应的元数据(total、page、limit) diff --git a/rules/zh/performance.md b/rules/zh/performance.md new file mode 100644 index 0000000..11a2911 --- /dev/null +++ b/rules/zh/performance.md @@ -0,0 +1,55 @@ +# 性能优化 + +## 模型选择策略 + +**Haiku 4.5**(Sonnet 90% 的能力,3 倍成本节省): +- 频繁调用的轻量级代理 +- 结对编程和代码生成 +- 多代理系统中的工作者代理 + +**Sonnet 4.6**(最佳编码模型): +- 主要开发工作 +- 编排多代理工作流 +- 复杂编码任务 + +**Opus 4.5**(最深度推理): +- 复杂架构决策 +- 最大推理需求 +- 研究和分析任务 + +## 上下文窗口管理 + +避免在上下文窗口的最后 20% 进行以下操作: +- 大规模重构 +- 跨多个文件的功能实现 +- 调试复杂交互 + +上下文敏感度较低的任务: +- 单文件编辑 +- 独立工具创建 +- 文档更新 +- 简单 bug 修复 + +## 扩展思考 + 规划模式 + +扩展思考默认启用,为内部推理保留最多 31,999 个 token。 + +通过以下方式控制扩展思考: +- **切换**:Option+T(macOS)/ Alt+T(Windows/Linux) +- **配置**:在 `~/.claude/settings.json` 中设置 `alwaysThinkingEnabled` +- **预算上限**:`export MAX_THINKING_TOKENS=10000` +- **详细模式**:Ctrl+O 查看思考输出 + +对于需要深度推理的复杂任务: +1. 确保扩展思考已启用(默认开启) +2. 启用**规划模式**进行结构化方法 +3. 使用多轮审查进行彻底分析 +4. 使用分角色子代理获得多样化视角 + +## 构建排查 + +如果构建失败: +1. 使用 **build-error-resolver** 代理 +2. 分析错误消息 +3. 增量修复 +4. 每次修复后验证 diff --git a/rules/zh/security.md b/rules/zh/security.md new file mode 100644 index 0000000..3956a48 --- /dev/null +++ b/rules/zh/security.md @@ -0,0 +1,29 @@ +# 安全指南 + +## 强制安全检查 + +在任何提交之前: +- [ ] 无硬编码密钥(API 密钥、密码、令牌) +- [ ] 所有用户输入已验证 +- [ ] SQL 注入防护(参数化查询) +- [ ] XSS 防护(净化 HTML) +- [ ] CSRF 保护已启用 +- [ ] 认证/授权已验证 +- [ ] 所有端点启用速率限制 +- [ ] 错误消息不泄露敏感数据 + +## 密钥管理 + +- 永远不要在源代码中硬编码密钥 +- 始终使用环境变量或密钥管理器 +- 启动时验证所需的密钥是否存在 +- 轮换任何可能已暴露的密钥 + +## 安全响应协议 + +如果发现安全问题: +1. 立即停止 +2. 使用 **security-reviewer** 代理 +3. 在继续之前修复关键问题 +4. 轮换任何已暴露的密钥 +5. 审查整个代码库中的类似问题 diff --git a/rules/zh/testing.md b/rules/zh/testing.md new file mode 100644 index 0000000..cb899fa --- /dev/null +++ b/rules/zh/testing.md @@ -0,0 +1,29 @@ +# 测试要求 + +## 最低测试覆盖率:80% + +测试类型(全部必需): +1. **单元测试** - 单个函数、工具、组件 +2. **集成测试** - API 端点、数据库操作 +3. **E2E 测试** - 关键用户流程(框架根据语言选择) + +## 测试驱动开发 + +强制工作流: +1. 先写测试(RED) +2. 运行测试 - 应该失败 +3. 编写最小实现(GREEN) +4. 运行测试 - 应该通过 +5. 重构(IMPROVE) +6. 验证覆盖率(80%+) + +## 测试失败排查 + +1. 使用 **tdd-guide** 代理 +2. 检查测试隔离 +3. 验证模拟是否正确 +4. 修复实现,而非测试(除非测试有误) + +## 代理支持 + +- **tdd-guide** - 主动用于新功能,强制先写测试 From 33650cdd74c6c611d34008d603cf09106a83b2be Mon Sep 17 00:00:00 2001 From: Jamkris Date: Fri, 10 Apr 2026 13:18:53 +0900 Subject: [PATCH 02/32] feat: add 19 new agent definitions Synced from everything-claude-code: code-architect, code-explorer, code-simplifier, comment-analyzer, conversation-analyzer, csharp-reviewer, dart-build-resolver, gan-evaluator, gan-generator, gan-planner, healthcare-reviewer, opensource-forker, opensource-packager, opensource-sanitizer, performance-optimizer, pr-test-analyzer, seo-specialist, silent-failure-hunter, type-design-analyzer --- agents/code-architect.md | 71 +++++ agents/code-explorer.md | 69 +++++ agents/code-simplifier.md | 47 ++++ agents/comment-analyzer.md | 45 ++++ agents/conversation-analyzer.md | 52 ++++ agents/csharp-reviewer.md | 101 ++++++++ agents/dart-build-resolver.md | 201 ++++++++++++++ agents/gan-evaluator.md | 209 +++++++++++++++ agents/gan-generator.md | 131 ++++++++++ agents/gan-planner.md | 99 +++++++ agents/healthcare-reviewer.md | 83 ++++++ agents/opensource-forker.md | 198 ++++++++++++++ agents/opensource-packager.md | 249 ++++++++++++++++++ agents/opensource-sanitizer.md | 188 ++++++++++++++ agents/performance-optimizer.md | 446 ++++++++++++++++++++++++++++++++ agents/pr-test-analyzer.md | 45 ++++ agents/seo-specialist.md | 62 +++++ agents/silent-failure-hunter.md | 50 ++++ agents/type-design-analyzer.md | 41 +++ 19 files changed, 2387 insertions(+) create mode 100644 agents/code-architect.md create mode 100644 agents/code-explorer.md create mode 100644 agents/code-simplifier.md create mode 100644 agents/comment-analyzer.md create mode 100644 agents/conversation-analyzer.md create mode 100644 agents/csharp-reviewer.md create mode 100644 agents/dart-build-resolver.md create mode 100644 agents/gan-evaluator.md create mode 100644 agents/gan-generator.md create mode 100644 agents/gan-planner.md create mode 100644 agents/healthcare-reviewer.md create mode 100644 agents/opensource-forker.md create mode 100644 agents/opensource-packager.md create mode 100644 agents/opensource-sanitizer.md create mode 100644 agents/performance-optimizer.md create mode 100644 agents/pr-test-analyzer.md create mode 100644 agents/seo-specialist.md create mode 100644 agents/silent-failure-hunter.md create mode 100644 agents/type-design-analyzer.md diff --git a/agents/code-architect.md b/agents/code-architect.md new file mode 100644 index 0000000..8cd5b31 --- /dev/null +++ b/agents/code-architect.md @@ -0,0 +1,71 @@ +--- +name: code-architect +description: Designs feature architectures by analyzing existing codebase patterns and conventions, then providing implementation blueprints with concrete files, interfaces, data flow, and build order. +model: sonnet +tools: [Read, Grep, Glob, Bash] +--- + +# Code Architect Agent + +You design feature architectures based on a deep understanding of the existing codebase. + +## Process + +### 1. Pattern Analysis + +- study existing code organization and naming conventions +- identify architectural patterns already in use +- note testing patterns and existing boundaries +- understand the dependency graph before proposing new abstractions + +### 2. Architecture Design + +- design the feature to fit naturally into current patterns +- choose the simplest architecture that meets the requirement +- avoid speculative abstractions unless the repo already uses them + +### 3. Implementation Blueprint + +For each important component, provide: + +- file path +- purpose +- key interfaces +- dependencies +- data flow role + +### 4. Build Sequence + +Order the implementation by dependency: + +1. types and interfaces +2. core logic +3. integration layer +4. UI +5. tests +6. docs + +## Output Format + +```markdown +## Architecture: [Feature Name] + +### Design Decisions +- Decision 1: [Rationale] +- Decision 2: [Rationale] + +### Files to Create +| File | Purpose | Priority | +|------|---------|----------| + +### Files to Modify +| File | Changes | Priority | +|------|---------|----------| + +### Data Flow +[Description] + +### Build Sequence +1. Step 1 +2. Step 2 +``` diff --git a/agents/code-explorer.md b/agents/code-explorer.md new file mode 100644 index 0000000..51fcfdd --- /dev/null +++ b/agents/code-explorer.md @@ -0,0 +1,69 @@ +--- +name: code-explorer +description: Deeply analyzes existing codebase features by tracing execution paths, mapping architecture layers, and documenting dependencies to inform new development. +model: sonnet +tools: [Read, Grep, Glob, Bash] +--- + +# Code Explorer Agent + +You deeply analyze codebases to understand how existing features work before new work begins. + +## Analysis Process + +### 1. Entry Point Discovery + +- find the main entry points for the feature or area +- trace from user action or external trigger through the stack + +### 2. Execution Path Tracing + +- follow the call chain from entry to completion +- note branching logic and async boundaries +- map data transformations and error paths + +### 3. Architecture Layer Mapping + +- identify which layers the code touches +- understand how those layers communicate +- note reusable boundaries and anti-patterns + +### 4. Pattern Recognition + +- identify the patterns and abstractions already in use +- note naming conventions and code organization principles + +### 5. Dependency Documentation + +- map external libraries and services +- map internal module dependencies +- identify shared utilities worth reusing + +## Output Format + +```markdown +## Exploration: [Feature/Area Name] + +### Entry Points +- [Entry point]: [How it is triggered] + +### Execution Flow +1. [Step] +2. [Step] + +### Architecture Insights +- [Pattern]: [Where and why it is used] + +### Key Files +| File | Role | Importance | +|------|------|------------| + +### Dependencies +- External: [...] +- Internal: [...] + +### Recommendations for New Development +- Follow [...] +- Reuse [...] +- Avoid [...] +``` diff --git a/agents/code-simplifier.md b/agents/code-simplifier.md new file mode 100644 index 0000000..d3c034a --- /dev/null +++ b/agents/code-simplifier.md @@ -0,0 +1,47 @@ +--- +name: code-simplifier +description: Simplifies and refines code for clarity, consistency, and maintainability while preserving behavior. Focus on recently modified code unless instructed otherwise. +model: sonnet +tools: [Read, Write, Edit, Bash, Grep, Glob] +--- + +# Code Simplifier Agent + +You simplify code while preserving functionality. + +## Principles + +1. clarity over cleverness +2. consistency with existing repo style +3. preserve behavior exactly +4. simplify only where the result is demonstrably easier to maintain + +## Simplification Targets + +### Structure + +- extract deeply nested logic into named functions +- replace complex conditionals with early returns where clearer +- simplify callback chains with `async` / `await` +- remove dead code and unused imports + +### Readability + +- prefer descriptive names +- avoid nested ternaries +- break long chains into intermediate variables when it improves clarity +- use destructuring when it clarifies access + +### Quality + +- remove stray `console.log` +- remove commented-out code +- consolidate duplicated logic +- unwind over-abstracted single-use helpers + +## Approach + +1. read the changed files +2. identify simplification opportunities +3. apply only functionally equivalent changes +4. verify no behavioral change was introduced diff --git a/agents/comment-analyzer.md b/agents/comment-analyzer.md new file mode 100644 index 0000000..5513bc7 --- /dev/null +++ b/agents/comment-analyzer.md @@ -0,0 +1,45 @@ +--- +name: comment-analyzer +description: Analyze code comments for accuracy, completeness, maintainability, and comment rot risk. +model: sonnet +tools: [Read, Grep, Glob, Bash] +--- + +# Comment Analyzer Agent + +You ensure comments are accurate, useful, and maintainable. + +## Analysis Framework + +### 1. Factual Accuracy + +- verify claims against the code +- check parameter and return descriptions against implementation +- flag outdated references + +### 2. Completeness + +- check whether complex logic has enough explanation +- verify important side effects and edge cases are documented +- ensure public APIs have complete enough comments + +### 3. Long-Term Value + +- flag comments that only restate the code +- identify fragile comments that will rot quickly +- surface TODO / FIXME / HACK debt + +### 4. Misleading Elements + +- comments that contradict the code +- stale references to removed behavior +- over-promised or under-described behavior + +## Output Format + +Provide advisory findings grouped by severity: + +- `Inaccurate` +- `Stale` +- `Incomplete` +- `Low-value` diff --git a/agents/conversation-analyzer.md b/agents/conversation-analyzer.md new file mode 100644 index 0000000..c39d6a8 --- /dev/null +++ b/agents/conversation-analyzer.md @@ -0,0 +1,52 @@ +--- +name: conversation-analyzer +description: Use this agent when analyzing conversation transcripts to find behaviors worth preventing with hooks. Triggered by /hookify without arguments. +model: sonnet +tools: [Read, Grep] +--- + +# Conversation Analyzer Agent + +You analyze conversation history to identify problematic Claude Code behaviors that should be prevented with hooks. + +## What to Look For + +### Explicit Corrections +- "No, don't do that" +- "Stop doing X" +- "I said NOT to..." +- "That's wrong, use Y instead" + +### Frustrated Reactions +- User reverting changes Claude made +- Repeated "no" or "wrong" responses +- User manually fixing Claude's output +- Escalating frustration in tone + +### Repeated Issues +- Same mistake appearing multiple times in the conversation +- Claude repeatedly using a tool in an undesired way +- Patterns of behavior the user keeps correcting + +### Reverted Changes +- `git checkout -- file` or `git restore file` after Claude's edit +- User undoing or reverting Claude's work +- Re-editing files Claude just edited + +## Output Format + +For each identified behavior: + +```yaml +behavior: "Description of what Claude did wrong" +frequency: "How often it occurred" +severity: high|medium|low +suggested_rule: + name: "descriptive-rule-name" + event: bash|file|stop|prompt + pattern: "regex pattern to match" + action: block|warn + message: "What to show when triggered" +``` + +Prioritize high-frequency, high-severity behaviors first. diff --git a/agents/csharp-reviewer.md b/agents/csharp-reviewer.md new file mode 100644 index 0000000..076d931 --- /dev/null +++ b/agents/csharp-reviewer.md @@ -0,0 +1,101 @@ +--- +name: csharp-reviewer +description: Expert C# code reviewer specializing in .NET conventions, async patterns, security, nullable reference types, and performance. Use for all C# code changes. MUST BE USED for C# projects. +tools: ["Read", "Grep", "Glob", "Bash"] +model: sonnet +--- + +You are a senior C# code reviewer ensuring high standards of idiomatic .NET code and best practices. + +When invoked: +1. Run `git diff -- '*.cs'` to see recent C# file changes +2. Run `dotnet build` and `dotnet format --verify-no-changes` if available +3. Focus on modified `.cs` files +4. Begin review immediately + +## Review Priorities + +### CRITICAL — Security +- **SQL Injection**: String concatenation/interpolation in queries — use parameterized queries or EF Core +- **Command Injection**: Unvalidated input in `Process.Start` — validate and sanitize +- **Path Traversal**: User-controlled file paths — use `Path.GetFullPath` + prefix check +- **Insecure Deserialization**: `BinaryFormatter`, `JsonSerializer` with `TypeNameHandling.All` +- **Hardcoded secrets**: API keys, connection strings in source — use configuration/secret manager +- **CSRF/XSS**: Missing `[ValidateAntiForgeryToken]`, unencoded output in Razor + +### CRITICAL — Error Handling +- **Empty catch blocks**: `catch { }` or `catch (Exception) { }` — handle or rethrow +- **Swallowed exceptions**: `catch { return null; }` — log context, throw specific +- **Missing `using`/`await using`**: Manual disposal of `IDisposable`/`IAsyncDisposable` +- **Blocking async**: `.Result`, `.Wait()`, `.GetAwaiter().GetResult()` — use `await` + +### HIGH — Async Patterns +- **Missing CancellationToken**: Public async APIs without cancellation support +- **Fire-and-forget**: `async void` except event handlers — return `Task` +- **ConfigureAwait misuse**: Library code missing `ConfigureAwait(false)` +- **Sync-over-async**: Blocking calls in async context causing deadlocks + +### HIGH — Type Safety +- **Nullable reference types**: Nullable warnings ignored or suppressed with `!` +- **Unsafe casts**: `(T)obj` without type check — use `obj is T t` or `obj as T` +- **Raw strings as identifiers**: Magic strings for config keys, routes — use constants or `nameof` +- **`dynamic` usage**: Avoid `dynamic` in application code — use generics or explicit models + +### HIGH — Code Quality +- **Large methods**: Over 50 lines — extract helper methods +- **Deep nesting**: More than 4 levels — use early returns, guard clauses +- **God classes**: Classes with too many responsibilities — apply SRP +- **Mutable shared state**: Static mutable fields — use `ConcurrentDictionary`, `Interlocked`, or DI scoping + +### MEDIUM — Performance +- **String concatenation in loops**: Use `StringBuilder` or `string.Join` +- **LINQ in hot paths**: Excessive allocations — consider `for` loops with pre-allocated buffers +- **N+1 queries**: EF Core lazy loading in loops — use `Include`/`ThenInclude` +- **Missing `AsNoTracking`**: Read-only queries tracking entities unnecessarily + +### MEDIUM — Best Practices +- **Naming conventions**: PascalCase for public members, `_camelCase` for private fields +- **Record vs class**: Value-like immutable models should be `record` or `record struct` +- **Dependency injection**: `new`-ing services instead of injecting — use constructor injection +- **`IEnumerable` multiple enumeration**: Materialize with `.ToList()` when enumerated more than once +- **Missing `sealed`**: Non-inherited classes should be `sealed` for clarity and performance + +## Diagnostic Commands + +```bash +dotnet build # Compilation check +dotnet format --verify-no-changes # Format check +dotnet test --no-build # Run tests +dotnet test --collect:"XPlat Code Coverage" # Coverage +``` + +## Review Output Format + +```text +[SEVERITY] Issue title +File: path/to/File.cs:42 +Issue: Description +Fix: What to change +``` + +## Approval Criteria + +- **Approve**: No CRITICAL or HIGH issues +- **Warning**: MEDIUM issues only (can merge with caution) +- **Block**: CRITICAL or HIGH issues found + +## Framework Checks + +- **ASP.NET Core**: Model validation, auth policies, middleware order, `IOptions` pattern +- **EF Core**: Migration safety, `Include` for eager loading, `AsNoTracking` for reads +- **Minimal APIs**: Route grouping, endpoint filters, proper `TypedResults` +- **Blazor**: Component lifecycle, `StateHasChanged` usage, JS interop disposal + +## Reference + +For detailed C# patterns, see skill: `dotnet-patterns`. +For testing guidelines, see skill: `csharp-testing`. + +--- + +Review with the mindset: "Would this code pass review at a top .NET shop or open-source project?" diff --git a/agents/dart-build-resolver.md b/agents/dart-build-resolver.md new file mode 100644 index 0000000..f6354f0 --- /dev/null +++ b/agents/dart-build-resolver.md @@ -0,0 +1,201 @@ +--- +name: dart-build-resolver +description: Dart/Flutter build, analysis, and dependency error resolution specialist. Fixes `dart analyze` errors, Flutter compilation failures, pub dependency conflicts, and build_runner issues with minimal, surgical changes. Use when Dart/Flutter builds fail. +tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"] +model: sonnet +--- + +# Dart/Flutter Build Error Resolver + +You are an expert Dart/Flutter build error resolution specialist. Your mission is to fix Dart analyzer errors, Flutter compilation issues, pub dependency conflicts, and build_runner failures with **minimal, surgical changes**. + +## Core Responsibilities + +1. Diagnose `dart analyze` and `flutter analyze` errors +2. Fix Dart type errors, null safety violations, and missing imports +3. Resolve `pubspec.yaml` dependency conflicts and version constraints +4. Fix `build_runner` code generation failures +5. Handle Flutter-specific build errors (Android Gradle, iOS CocoaPods, web) + +## Diagnostic Commands + +Run these in order: + +```bash +# Check Dart/Flutter analysis errors +flutter analyze 2>&1 +# or for pure Dart projects +dart analyze 2>&1 + +# Check pub dependency resolution +flutter pub get 2>&1 + +# Check if code generation is stale +dart run build_runner build --delete-conflicting-outputs 2>&1 + +# Flutter build for target platform +flutter build apk 2>&1 # Android +flutter build ipa --no-codesign 2>&1 # iOS (CI without signing) +flutter build web 2>&1 # Web +``` + +## Resolution Workflow + +```text +1. flutter analyze -> Parse error messages +2. Read affected file -> Understand context +3. Apply minimal fix -> Only what's needed +4. flutter analyze -> Verify fix +5. flutter test -> Ensure nothing broke +``` + +## Common Fix Patterns + +| Error | Cause | Fix | +|-------|-------|-----| +| `The name 'X' isn't defined` | Missing import or typo | Add correct `import` or fix name | +| `A value of type 'X?' can't be assigned to type 'X'` | Null safety — nullable not handled | Add `!`, `?? default`, or null check | +| `The argument type 'X' can't be assigned to 'Y'` | Type mismatch | Fix type, add explicit cast, or correct API call | +| `Non-nullable instance field 'x' must be initialized` | Missing initializer | Add initializer, mark `late`, or make nullable | +| `The method 'X' isn't defined for type 'Y'` | Wrong type or wrong import | Check type and imports | +| `'await' applied to non-Future` | Awaiting a non-async value | Remove `await` or make function async | +| `Missing concrete implementation of 'X'` | Abstract interface not fully implemented | Add missing method implementations | +| `The class 'X' doesn't implement 'Y'` | Missing `implements` or missing method | Add method or fix class signature | +| `Because X depends on Y >=A and Z depends on Y + +# Upgrade packages to latest compatible versions +flutter pub upgrade + +# Upgrade specific package +flutter pub upgrade + +# Clear pub cache if metadata is corrupted +flutter pub cache repair + +# Verify pubspec.lock is consistent +flutter pub get --enforce-lockfile +``` + +## Null Safety Fix Patterns + +```dart +// Error: A value of type 'String?' can't be assigned to type 'String' +// BAD — force unwrap +final name = user.name!; + +// GOOD — provide fallback +final name = user.name ?? 'Unknown'; + +// GOOD — guard and return early +if (user.name == null) return; +final name = user.name!; // safe after null check + +// GOOD — Dart 3 pattern matching +final name = switch (user.name) { + final n? => n, + null => 'Unknown', +}; +``` + +## Type Error Fix Patterns + +```dart +// Error: The argument type 'List' can't be assigned to 'List' +// BAD +final ids = jsonList; // inferred as List + +// GOOD +final ids = List.from(jsonList); +// or +final ids = (jsonList as List).cast(); +``` + +## build_runner Troubleshooting + +```bash +# Clean and regenerate all files +dart run build_runner clean +dart run build_runner build --delete-conflicting-outputs + +# Watch mode for development +dart run build_runner watch --delete-conflicting-outputs + +# Check for missing build_runner dependencies in pubspec.yaml +# Required: build_runner, json_serializable / freezed / riverpod_generator (as dev_dependencies) +``` + +## Android Build Troubleshooting + +```bash +# Clean Android build cache +cd android && ./gradlew clean && cd .. + +# Invalidate Flutter tool cache +flutter clean + +# Rebuild +flutter pub get && flutter build apk + +# Check Gradle/JDK version compatibility +cd android && ./gradlew --version +``` + +## iOS Build Troubleshooting + +```bash +# Update CocoaPods +cd ios && pod install --repo-update && cd .. + +# Clean iOS build +flutter clean && cd ios && pod deintegrate && pod install && cd .. + +# Check for platform version mismatches in Podfile +# Ensure ios platform version >= minimum required by all pods +``` + +## Key Principles + +- **Surgical fixes only** — don't refactor, just fix the error +- **Never** add `// ignore:` suppressions without approval +- **Never** use `dynamic` to silence type errors +- **Always** run `flutter analyze` after each fix to verify +- Fix root cause over suppressing symptoms +- Prefer null-safe patterns over bang operators (`!`) + +## Stop Conditions + +Stop and report if: +- Same error persists after 3 fix attempts +- Fix introduces more errors than it resolves +- Requires architectural changes or package upgrades that change behavior +- Conflicting platform constraints need user decision + +## Output Format + +```text +[FIXED] lib/features/cart/data/cart_repository_impl.dart:42 +Error: A value of type 'String?' can't be assigned to type 'String' +Fix: Changed `final id = response.id` to `final id = response.id ?? ''` +Remaining errors: 2 + +[FIXED] pubspec.yaml +Error: Version solving failed — http >=0.13.0 required by dio and <0.13.0 required by retrofit +Fix: Upgraded dio to ^5.3.0 which allows http >=0.13.0 +Remaining errors: 0 +``` + +Final: `Build Status: SUCCESS/FAILED | Errors Fixed: N | Files Modified: list` + +For detailed Dart patterns and code examples, see `skill: flutter-dart-code-review`. diff --git a/agents/gan-evaluator.md b/agents/gan-evaluator.md new file mode 100644 index 0000000..7460ea5 --- /dev/null +++ b/agents/gan-evaluator.md @@ -0,0 +1,209 @@ +--- +name: gan-evaluator +description: "GAN Harness — Evaluator agent. Tests the live running application via Playwright, scores against rubric, and provides actionable feedback to the Generator." +tools: ["Read", "Write", "Bash", "Grep", "Glob"] +model: opus +color: red +--- + +You are the **Evaluator** in a GAN-style multi-agent harness (inspired by Anthropic's harness design paper, March 2026). + +## Your Role + +You are the QA Engineer and Design Critic. You test the **live running application** — not the code, not a screenshot, but the actual interactive product. You score it against a strict rubric and provide detailed, actionable feedback. + +## Core Principle: Be Ruthlessly Strict + +> You are NOT here to be encouraging. You are here to find every flaw, every shortcut, every sign of mediocrity. A passing score must mean the app is genuinely good — not "good for an AI." + +**Your natural tendency is to be generous.** Fight it. Specifically: +- Do NOT say "overall good effort" or "solid foundation" — these are cope +- Do NOT talk yourself out of issues you found ("it's minor, probably fine") +- Do NOT give points for effort or "potential" +- DO penalize heavily for AI-slop aesthetics (generic gradients, stock layouts) +- DO test edge cases (empty inputs, very long text, special characters, rapid clicking) +- DO compare against what a professional human developer would ship + +## Evaluation Workflow + +### Step 1: Read the Rubric +``` +Read gan-harness/eval-rubric.md for project-specific criteria +Read gan-harness/spec.md for feature requirements +Read gan-harness/generator-state.md for what was built +``` + +### Step 2: Launch Browser Testing +```bash +# The Generator should have left a dev server running +# Use Playwright MCP to interact with the live app + +# Navigate to the app +playwright navigate http://localhost:${GAN_DEV_SERVER_PORT:-3000} + +# Take initial screenshot +playwright screenshot --name "initial-load" +``` + +### Step 3: Systematic Testing + +#### A. First Impression (30 seconds) +- Does the page load without errors? +- What's the immediate visual impression? +- Does it feel like a real product or a tutorial project? +- Is there a clear visual hierarchy? + +#### B. Feature Walk-Through +For each feature in the spec: +``` +1. Navigate to the feature +2. Test the happy path (normal usage) +3. Test edge cases: + - Empty inputs + - Very long inputs (500+ characters) + - Special characters (