feat(rules): add initial rule files for concerns, languages, and frameworks
Concerns (6 files): - coding-style.md (163 lines): patterns, anti-patterns, error handling, SOLID - naming.md (105 lines): naming conventions table per language - documentation.md (149 lines): docstrings, WHY vs WHAT, README standards - testing.md (134 lines): AAA pattern, mocking philosophy, TDD - git-workflow.md (118 lines): conventional commits, branch naming, PR format - project-structure.md (82 lines): directory layout, entry points, config placement Languages (4 files): - python.md (224 lines): uv, ruff, pyright, pytest, pydantic, idioms, anti-patterns - typescript.md (150 lines): strict mode, discriminated unions, satisfies, as const - nix.md (129 lines): flake structure, module patterns, alejandra, anti-patterns - shell.md (100 lines): set -euo pipefail, shellcheck, quoting, POSIX Frameworks (1 file): - n8n.md (42 lines): workflow design, node patterns, Error Trigger, security Context budget: 975 lines (concerns + python) < 1500 limit Refs: T6-T16 of rules-system plan
This commit is contained in:
@@ -495,7 +495,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 6. Create `rules/concerns/coding-style.md`
|
- [x] 6. Create `rules/concerns/coding-style.md`
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
- Write coding style rules: code formatting, patterns/anti-patterns, error handling, type safety, function design, DRY/SOLID
|
- Write coding style rules: code formatting, patterns/anti-patterns, error handling, type safety, function design, DRY/SOLID
|
||||||
@@ -526,7 +526,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 7. Create `rules/concerns/naming.md`
|
- [x] 7. Create `rules/concerns/naming.md`
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
- Naming conventions: files, variables, functions, classes, modules, constants
|
- Naming conventions: files, variables, functions, classes, modules, constants
|
||||||
@@ -549,7 +549,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 8. Create `rules/concerns/documentation.md`
|
- [x] 8. Create `rules/concerns/documentation.md`
|
||||||
|
|
||||||
**What to do**: When to document, docstring formats, inline comment philosophy (WHY not WHAT), README standards. Under 150 lines.
|
**What to do**: When to document, docstring formats, inline comment philosophy (WHY not WHAT), README standards. Under 150 lines.
|
||||||
**Recommended Agent Profile**: `writing`
|
**Recommended Agent Profile**: `writing`
|
||||||
@@ -560,7 +560,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 9. Create `rules/concerns/testing.md`
|
- [x] 9. Create `rules/concerns/testing.md`
|
||||||
|
|
||||||
**What to do**: Arrange-act-assert, behavior vs implementation testing, mocking philosophy, coverage, TDD. Under 200 lines.
|
**What to do**: Arrange-act-assert, behavior vs implementation testing, mocking philosophy, coverage, TDD. Under 200 lines.
|
||||||
**Recommended Agent Profile**: `writing`
|
**Recommended Agent Profile**: `writing`
|
||||||
@@ -571,7 +571,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 10. Create `rules/concerns/git-workflow.md`
|
- [x] 10. Create `rules/concerns/git-workflow.md`
|
||||||
|
|
||||||
**What to do**: Conventional commits, branch naming, PR descriptions, squash vs merge. Under 120 lines.
|
**What to do**: Conventional commits, branch naming, PR descriptions, squash vs merge. Under 120 lines.
|
||||||
**Recommended Agent Profile**: `writing`, Skills: [`git-master`]
|
**Recommended Agent Profile**: `writing`, Skills: [`git-master`]
|
||||||
@@ -582,7 +582,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 11. Create `rules/concerns/project-structure.md`
|
- [x] 11. Create `rules/concerns/project-structure.md`
|
||||||
|
|
||||||
**What to do**: Directory layout, module organization, entry points, config placement. Per-type: Python (src layout), TS (src/), Nix (modules/). Under 120 lines.
|
**What to do**: Directory layout, module organization, entry points, config placement. Per-type: Python (src layout), TS (src/), Nix (modules/). Under 120 lines.
|
||||||
**Recommended Agent Profile**: `writing`
|
**Recommended Agent Profile**: `writing`
|
||||||
@@ -593,7 +593,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 12. Create `rules/languages/python.md`
|
- [x] 12. Create `rules/languages/python.md`
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
- Deep Python patterns: `uv` (pkg mgmt), `ruff` (lint/fmt), `pyright` (types), `pytest` + `hypothesis`, Pydantic for data boundaries
|
- Deep Python patterns: `uv` (pkg mgmt), `ruff` (lint/fmt), `pyright` (types), `pytest` + `hypothesis`, Pydantic for data boundaries
|
||||||
@@ -616,7 +616,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 13. Create `rules/languages/typescript.md`
|
- [x] 13. Create `rules/languages/typescript.md`
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
- Strict mode (`strict: true`, `noUncheckedIndexedAccess`), discriminated unions, branded types, `satisfies`, `as const`
|
- Strict mode (`strict: true`, `noUncheckedIndexedAccess`), discriminated unions, branded types, `satisfies`, `as const`
|
||||||
@@ -634,7 +634,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 14. Create `rules/languages/nix.md`
|
- [x] 14. Create `rules/languages/nix.md`
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
- Flake structure, module patterns (`{ config, lib, pkgs, ... }:`), `mkIf`/`mkMerge`
|
- Flake structure, module patterns (`{ config, lib, pkgs, ... }:`), `mkIf`/`mkMerge`
|
||||||
@@ -655,7 +655,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 15. Create `rules/languages/shell.md`
|
- [x] 15. Create `rules/languages/shell.md`
|
||||||
|
|
||||||
**What to do**: `set -euo pipefail`, shellcheck, quoting, local vars, POSIX portability, `#!/usr/bin/env bash`. Under 120 lines.
|
**What to do**: `set -euo pipefail`, shellcheck, quoting, local vars, POSIX portability, `#!/usr/bin/env bash`. Under 120 lines.
|
||||||
**Recommended Agent Profile**: `writing`
|
**Recommended Agent Profile**: `writing`
|
||||||
@@ -666,7 +666,7 @@ Max Concurrent: 11 (Wave 2)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 16. Create `rules/frameworks/n8n.md`
|
- [x] 16. Create `rules/frameworks/n8n.md`
|
||||||
|
|
||||||
**What to do**: Workflow design, node patterns, naming, Error Trigger, data patterns, security. Under 120 lines.
|
**What to do**: Workflow design, node patterns, naming, Error Trigger, data patterns, security. Under 120 lines.
|
||||||
**Recommended Agent Profile**: `writing`
|
**Recommended Agent Profile**: `writing`
|
||||||
|
|||||||
163
rules/concerns/coding-style.md
Normal file
163
rules/concerns/coding-style.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Coding Style
|
||||||
|
|
||||||
|
## Critical Rules (MUST follow)
|
||||||
|
|
||||||
|
Always prioritize readability over cleverness. Never write code that requires mental gymnastics to understand.
|
||||||
|
Always fail fast and explicitly. Never silently swallow errors or hide exceptions.
|
||||||
|
Always keep functions under 20 lines. Never create monolithic functions that do multiple things.
|
||||||
|
Always validate inputs at function boundaries. Never trust external data implicitly.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
Prefer consistent indentation throughout the codebase. Never mix tabs and spaces.
|
||||||
|
Prefer meaningful variable names over short abbreviations. Never use single letters except for loop counters.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
const maxRetryAttempts = 3;
|
||||||
|
const connectionTimeout = 5000;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetryAttempts; attempt++) {
|
||||||
|
// process attempt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
const m = 3;
|
||||||
|
const t = 5000;
|
||||||
|
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
// process attempt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patterns and Anti-Patterns
|
||||||
|
|
||||||
|
Never repeat yourself. Always extract duplicated logic into reusable functions.
|
||||||
|
Prefer composition over inheritance. Never create deep inheritance hierarchies.
|
||||||
|
Always use guard clauses to reduce nesting. Never write arrow-shaped code.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
def process_user(user):
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
if not user.is_active:
|
||||||
|
return None
|
||||||
|
return user.calculate_score()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
def process_user(user):
|
||||||
|
if user:
|
||||||
|
if user.is_active:
|
||||||
|
return user.calculate_score()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Always handle specific exceptions. Never use broad catch-all exception handlers.
|
||||||
|
Always log error context, not just the error message. Never let errors vanish without trace.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
try:
|
||||||
|
data = fetch_resource(url)
|
||||||
|
return parse_data(data)
|
||||||
|
except NetworkError as e:
|
||||||
|
log_error(f"Network failed for {url}: {e}")
|
||||||
|
raise
|
||||||
|
except ParseError as e:
|
||||||
|
log_error(f"Parse failed for {url}: {e}")
|
||||||
|
return fallback_data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
try:
|
||||||
|
data = fetch_resource(url)
|
||||||
|
return parse_data(data)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Safety
|
||||||
|
|
||||||
|
Always use type annotations where supported. Never rely on implicit type coercion.
|
||||||
|
Prefer explicit type checks over duck typing for public APIs. Never assume type behavior.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
function calculateTotal(price: number, quantity: number): number {
|
||||||
|
return price * quantity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
function calculateTotal(price, quantity) {
|
||||||
|
return price * quantity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Function Design
|
||||||
|
|
||||||
|
Always write pure functions when possible. Never mutate arguments unless required.
|
||||||
|
Always limit function parameters to 3 or fewer. Never pass objects to hide parameter complexity.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
def create_user(name: str, email: str) -> User:
|
||||||
|
return User(name=name, email=email, created_at=now())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
def create_user(config: dict) -> User:
|
||||||
|
return User(
|
||||||
|
name=config['name'],
|
||||||
|
email=config['email'],
|
||||||
|
created_at=config['timestamp']
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## SOLID Principles
|
||||||
|
|
||||||
|
Never let classes depend on concrete implementations. Always depend on abstractions.
|
||||||
|
Always ensure classes are open for extension but closed for modification. Never change working code to add features.
|
||||||
|
Prefer many small interfaces over one large interface. Never force clients to depend on methods they don't use.
|
||||||
|
|
||||||
|
### Correct:
|
||||||
|
```lang
|
||||||
|
class EmailSender {
|
||||||
|
send(message: Message): void {
|
||||||
|
// implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationService {
|
||||||
|
constructor(private sender: EmailSender) {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect:
|
||||||
|
```lang
|
||||||
|
class NotificationService {
|
||||||
|
sendEmail(message: Message): void { }
|
||||||
|
sendSMS(message: Message): void { }
|
||||||
|
sendPush(message: Message): void { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules (REPEAT)
|
||||||
|
|
||||||
|
Always write self-documenting code. Never rely on comments to explain complex logic.
|
||||||
|
Always refactor when you see code smells. Never let technical debt accumulate.
|
||||||
|
Always test edge cases explicitly. Never assume happy path only behavior.
|
||||||
|
Never commit commented-out code. Always remove it or restore it.
|
||||||
149
rules/concerns/documentation.md
Normal file
149
rules/concerns/documentation.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Documentation Rules
|
||||||
|
|
||||||
|
## When to Document
|
||||||
|
|
||||||
|
**Document public APIs**. Every public function, class, method, and module needs documentation. Users need to know how to use your code.
|
||||||
|
**Document complex logic**. Algorithms, state machines, and non-obvious implementations need explanations. Future readers will thank you.
|
||||||
|
**Document business rules**. Encode domain knowledge directly in comments. Don't make anyone reverse-engineer requirements from code.
|
||||||
|
**Document trade-offs**. When you choose between alternatives, explain why. Help future maintainers understand the decision context.
|
||||||
|
**Do NOT document obvious code**. Comments like `// get user` add noise. Delete them.
|
||||||
|
|
||||||
|
## Docstring Formats
|
||||||
|
|
||||||
|
### Python (Google Style)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def calculate_price(quantity: int, unit_price: float, discount: float = 0.0) -> float:
|
||||||
|
"""Calculate total price after discount.
|
||||||
|
Args:
|
||||||
|
quantity: Number of items ordered.
|
||||||
|
unit_price: Price per item in USD.
|
||||||
|
discount: Decimal discount rate (0.0 to 1.0).
|
||||||
|
Returns:
|
||||||
|
Final price in USD.
|
||||||
|
Raises:
|
||||||
|
ValueError: If quantity is negative.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript/TypeScript (JSDoc)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* Validates user input against security rules.
|
||||||
|
* @param {string} input - Raw user input from form.
|
||||||
|
* @param {Object} rules - Validation constraints.
|
||||||
|
* @param {number} rules.maxLength - Maximum allowed length.
|
||||||
|
* @returns {boolean} True if input passes all rules.
|
||||||
|
* @throws {ValidationError} If input violates security constraints.
|
||||||
|
*/
|
||||||
|
function validateInput(input, rules) {
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bash
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Deploy application to production environment.
|
||||||
|
#
|
||||||
|
# Usage: ./deploy.sh [environment]
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# environment: Target environment (staging|production). Default: staging.
|
||||||
|
#
|
||||||
|
# Exits:
|
||||||
|
# 0 on success, 1 on deployment failure.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inline Comments: WHY Not WHAT
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
```python
|
||||||
|
# Iterate through all users
|
||||||
|
for user in users:
|
||||||
|
# Check if user is active
|
||||||
|
if user.active:
|
||||||
|
# Increment counter
|
||||||
|
count += 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
```python
|
||||||
|
# Count only active users to calculate monthly revenue
|
||||||
|
for user in users:
|
||||||
|
if user.active:
|
||||||
|
count += 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
```javascript
|
||||||
|
// Set timeout to 5000
|
||||||
|
setTimeout(() => {
|
||||||
|
// Show error message
|
||||||
|
alert('Error');
|
||||||
|
}, 5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
```javascript
|
||||||
|
// 5000ms delay prevents duplicate alerts during rapid retries
|
||||||
|
setTimeout(() => {
|
||||||
|
alert('Error');
|
||||||
|
}, 5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
```bash
|
||||||
|
# Remove temporary files
|
||||||
|
rm -rf /tmp/app/*
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
```bash
|
||||||
|
# Clear temp directory before batch import to prevent partial state
|
||||||
|
rm -rf /tmp/app/*
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule:** Describe the intent and context. Never describe what the code obviously does.
|
||||||
|
|
||||||
|
## README Standards
|
||||||
|
|
||||||
|
Every project needs a README at the top level.
|
||||||
|
|
||||||
|
**Required sections:**
|
||||||
|
1. **What it does** - One sentence summary
|
||||||
|
2. **Installation** - Setup commands
|
||||||
|
3. **Usage** - Basic example
|
||||||
|
4. **Configuration** - Environment variables and settings
|
||||||
|
5. **Contributing** - How to contribute
|
||||||
|
|
||||||
|
**Example structure:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project Name
|
||||||
|
|
||||||
|
One-line description of what this project does.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create `.env` file:
|
||||||
|
```
|
||||||
|
API_KEY=your_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keep READMEs focused**. Link to separate docs for complex topics. Don't make the README a tutorial.
|
||||||
118
rules/concerns/git-workflow.md
Normal file
118
rules/concerns/git-workflow.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# Git Workflow Rules
|
||||||
|
|
||||||
|
## Conventional Commits
|
||||||
|
|
||||||
|
Format: `<type>(<scope>): <subject>`
|
||||||
|
|
||||||
|
### Commit Types
|
||||||
|
|
||||||
|
- **feat**: New feature
|
||||||
|
- `feat(auth): add OAuth2 login flow`
|
||||||
|
- `feat(api): expose user endpoints`
|
||||||
|
|
||||||
|
- **fix**: Bug fix
|
||||||
|
- `fix(payment): resolve timeout on Stripe calls`
|
||||||
|
- `fix(ui): button not clickable on mobile`
|
||||||
|
|
||||||
|
- **refactor**: Code refactoring (no behavior change)
|
||||||
|
- `refactor(utils): extract date helpers`
|
||||||
|
- `refactor(api): simplify error handling`
|
||||||
|
|
||||||
|
- **docs**: Documentation only
|
||||||
|
- `docs(readme): update installation steps`
|
||||||
|
- `docs(api): add endpoint examples`
|
||||||
|
|
||||||
|
- **chore**: Maintenance tasks
|
||||||
|
- `chore(deps): update Node to 20`
|
||||||
|
- `chore(ci): add GitHub actions workflow`
|
||||||
|
|
||||||
|
- **test**: Tests only
|
||||||
|
- `test(auth): add unit tests for login`
|
||||||
|
- `test(e2e): add checkout flow tests`
|
||||||
|
|
||||||
|
- **style**: Formatting, no logic change
|
||||||
|
- `style: sort imports alphabetically`
|
||||||
|
|
||||||
|
### Commit Rules
|
||||||
|
|
||||||
|
- Subject max 72 chars
|
||||||
|
- Imperative mood ("add", not "added")
|
||||||
|
- No period at end
|
||||||
|
- Reference issues: `Closes #123`
|
||||||
|
|
||||||
|
## Branch Naming
|
||||||
|
|
||||||
|
Pattern: `<type>/<short-description>`
|
||||||
|
|
||||||
|
### Branch Types
|
||||||
|
|
||||||
|
- `feature/add-user-dashboard`
|
||||||
|
- `feature/enable-dark-mode`
|
||||||
|
- `fix/login-redirect-loop`
|
||||||
|
- `fix/payment-timeout-error`
|
||||||
|
- `refactor/extract-user-service`
|
||||||
|
- `refactor/simplify-auth-flow`
|
||||||
|
- `hotfix/security-vulnerability`
|
||||||
|
|
||||||
|
### Branch Rules
|
||||||
|
|
||||||
|
- Lowercase and hyphens
|
||||||
|
- Max 50 chars
|
||||||
|
- Delete after merge
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### PR Title
|
||||||
|
|
||||||
|
Follow Conventional Commit format:
|
||||||
|
- `feat: add user dashboard`
|
||||||
|
- `fix: resolve login redirect loop`
|
||||||
|
|
||||||
|
### PR Description
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## What
|
||||||
|
Brief description
|
||||||
|
|
||||||
|
## Why
|
||||||
|
Reason for change
|
||||||
|
|
||||||
|
## How
|
||||||
|
Implementation approach
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Steps performed
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] Tests pass
|
||||||
|
- [ ] Code reviewed
|
||||||
|
- [ ] Docs updated
|
||||||
|
```
|
||||||
|
|
||||||
|
## Merge Strategy
|
||||||
|
|
||||||
|
### Squash Merge
|
||||||
|
|
||||||
|
- Many small commits
|
||||||
|
- One cohesive feature
|
||||||
|
- Clean history
|
||||||
|
|
||||||
|
### Merge Commit
|
||||||
|
|
||||||
|
- Preserve commit history
|
||||||
|
- Distinct milestones
|
||||||
|
- Detailed history preferred
|
||||||
|
|
||||||
|
### When to Rebase
|
||||||
|
|
||||||
|
- Before opening PR
|
||||||
|
- Resolving conflicts
|
||||||
|
- Keeping current with main
|
||||||
|
|
||||||
|
## General Rules
|
||||||
|
|
||||||
|
- Pull latest from main before starting
|
||||||
|
- Write atomic commits
|
||||||
|
- Run tests before pushing
|
||||||
|
- Request peer review before merge
|
||||||
|
- Never force push to main/master
|
||||||
105
rules/concerns/naming.md
Normal file
105
rules/concerns/naming.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Naming Conventions
|
||||||
|
|
||||||
|
Use consistent naming across all code. Follow language-specific conventions.
|
||||||
|
|
||||||
|
## Language Reference
|
||||||
|
|
||||||
|
| Type | Python | TypeScript | Nix | Shell |
|
||||||
|
|------|--------|------------|-----|-------|
|
||||||
|
| Variables | snake_case | camelCase | camelCase | UPPER_SNAKE |
|
||||||
|
| Functions | snake_case | camelCase | camelCase | lower_case |
|
||||||
|
| Classes | PascalCase | PascalCase | - | - |
|
||||||
|
| Constants | UPPER_SNAKE | UPPER_SNAKE | camelCase | UPPER_SNAKE |
|
||||||
|
| Files | snake_case | camelCase | hyphen-case | hyphen-case |
|
||||||
|
| Modules | snake_case | camelCase | - | - |
|
||||||
|
|
||||||
|
## General Rules
|
||||||
|
|
||||||
|
**Files**: Use hyphen-case for documentation, snake_case for Python, camelCase for TypeScript. Names should describe content.
|
||||||
|
|
||||||
|
**Variables**: Use descriptive names. Avoid single letters except loop counters. No Hungarian notation.
|
||||||
|
|
||||||
|
**Functions**: Use verb-noun pattern. Name describes what it does, not how it does it.
|
||||||
|
|
||||||
|
**Classes**: Use PascalCase with descriptive nouns. Avoid abbreviations.
|
||||||
|
|
||||||
|
**Constants**: Use UPPER_SNAKE with descriptive names. Group related constants.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Python:
|
||||||
|
```python
|
||||||
|
# Variables
|
||||||
|
user_name = "alice"
|
||||||
|
is_authenticated = True
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def get_user_data(user_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class UserProfile:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
API_ENDPOINT = "https://api.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript:
|
||||||
|
```typescript
|
||||||
|
// Variables
|
||||||
|
const userName = "alice";
|
||||||
|
const isAuthenticated = true;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
function getUserData(userId: string): User {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classes
|
||||||
|
class UserProfile {
|
||||||
|
private name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
const API_ENDPOINT = "https://api.example.com";
|
||||||
|
```
|
||||||
|
|
||||||
|
Nix:
|
||||||
|
```nix
|
||||||
|
# Variables
|
||||||
|
let
|
||||||
|
userName = "alice";
|
||||||
|
isAuthenticated = true;
|
||||||
|
in
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Shell:
|
||||||
|
```bash
|
||||||
|
# Variables
|
||||||
|
USER_NAME="alice"
|
||||||
|
IS_AUTHENTICATED=true
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
get_user_data() {
|
||||||
|
echo "Getting data"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
MAX_RETRIES=3
|
||||||
|
API_ENDPOINT="https://api.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Naming
|
||||||
|
|
||||||
|
Use these patterns consistently. No exceptions.
|
||||||
|
|
||||||
|
- Skills: `hyphen-case`
|
||||||
|
- Python: `snake_case.py`
|
||||||
|
- TypeScript: `camelCase.ts` or `hyphen-case.ts`
|
||||||
|
- Nix: `hyphen-case.nix`
|
||||||
|
- Shell: `hyphen-case.sh`
|
||||||
|
- Markdown: `UPPERCASE.md` or `sentence-case.md`
|
||||||
82
rules/concerns/project-structure.md
Normal file
82
rules/concerns/project-structure.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Project Structure
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
Use src layout for all projects. Place application code in `src/<project>/`, tests in `tests/`.
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── src/myproject/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── main.py # Entry point
|
||||||
|
│ └── core/
|
||||||
|
│ └── module.py
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── test_module.py
|
||||||
|
├── pyproject.toml # Config
|
||||||
|
├── README.md
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- One module per directory file
|
||||||
|
- `__init__.py` in every package
|
||||||
|
- Entry point in `src/myproject/main.py`
|
||||||
|
- Config in root: `pyproject.toml`, `requirements.txt`
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
Use `src/` for source, `dist/` for build output.
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Entry point
|
||||||
|
│ ├── core/
|
||||||
|
│ │ └── module.ts
|
||||||
|
│ └── types.ts
|
||||||
|
├── tests/
|
||||||
|
│ └── module.test.ts
|
||||||
|
├── package.json # Config
|
||||||
|
├── tsconfig.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- One module per file
|
||||||
|
- Index exports from `src/index.ts`
|
||||||
|
- Entry point in `src/index.ts`
|
||||||
|
- Config in root: `package.json`, `tsconfig.json`
|
||||||
|
|
||||||
|
## Nix
|
||||||
|
|
||||||
|
Use `modules/` for NixOS modules, `pkgs/` for packages.
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-config/
|
||||||
|
├── modules/
|
||||||
|
│ ├── default.nix # Module list
|
||||||
|
│ └── my-service.nix
|
||||||
|
├── pkgs/
|
||||||
|
│ └── my-package/
|
||||||
|
│ └── default.nix
|
||||||
|
├── flake.nix # Entry point
|
||||||
|
├── flake.lock
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- One module per file in `modules/`
|
||||||
|
- One package per directory in `pkgs/`
|
||||||
|
- Entry point in `flake.nix`
|
||||||
|
- Config in root: `flake.nix`, shell.nix
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Use hyphen-case for directories
|
||||||
|
- Use kebab-case for file names
|
||||||
|
- Config files in project root
|
||||||
|
- Tests separate from source
|
||||||
|
- Docs in root: README.md, CHANGELOG.md
|
||||||
|
- Hidden configs: .env, .gitignore
|
||||||
134
rules/concerns/testing.md
Normal file
134
rules/concerns/testing.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Testing Rules
|
||||||
|
|
||||||
|
## Arrange-Act-Assert Pattern
|
||||||
|
|
||||||
|
Structure every test in three distinct phases:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Arrange: Set up the test data and conditions
|
||||||
|
user = User(name="Alice", role="admin")
|
||||||
|
session = create_test_session(user.id)
|
||||||
|
|
||||||
|
# Act: Execute the behavior under test
|
||||||
|
result = grant_permission(session, "read_documents")
|
||||||
|
|
||||||
|
# Assert: Verify the expected outcome
|
||||||
|
assert result.granted is True
|
||||||
|
assert result.permissions == ["read_documents"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Never mix phases. Comment each phase clearly for complex setups. Keep Act phase to one line if possible.
|
||||||
|
|
||||||
|
## Behavior vs Implementation Testing
|
||||||
|
|
||||||
|
Test behavior, not implementation details:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# GOOD: Tests the observable behavior
|
||||||
|
def test_user_can_login():
|
||||||
|
response = login("alice@example.com", "password123")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "session_token" in response.cookies
|
||||||
|
|
||||||
|
# BAD: Tests internal implementation
|
||||||
|
def test_login_sets_database_flag():
|
||||||
|
login("alice@example.com", "password123")
|
||||||
|
user = User.get(email="alice@example.com")
|
||||||
|
assert user._logged_in_flag is True # Private field
|
||||||
|
```
|
||||||
|
|
||||||
|
Focus on inputs and outputs. Test public contracts. Refactor internals freely without breaking tests.
|
||||||
|
|
||||||
|
## Mocking Philosophy
|
||||||
|
|
||||||
|
Mock external dependencies, not internal code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# GOOD: Mock external services
|
||||||
|
@patch("requests.post")
|
||||||
|
def test_sends_notification_to_slack(mock_post):
|
||||||
|
send_notification("Build complete!")
|
||||||
|
mock_post.assert_called_once_with(
|
||||||
|
"https://slack.com/api/chat.postMessage",
|
||||||
|
json={"text": "Build complete!"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# BAD: Mock internal methods
|
||||||
|
@patch("NotificationService._format_message")
|
||||||
|
def test_notification_formatting(mock_format):
|
||||||
|
# Don't mock private methods
|
||||||
|
send_notification("Build complete!")
|
||||||
|
```
|
||||||
|
|
||||||
|
Mock when:
|
||||||
|
- Dependency is slow (database, network, file system)
|
||||||
|
- Dependency is unreliable (external APIs)
|
||||||
|
- Dependency is expensive (third-party services)
|
||||||
|
|
||||||
|
Don't mock when:
|
||||||
|
- Testing the dependency itself
|
||||||
|
- The dependency is fast and stable
|
||||||
|
- The mock becomes more complex than real implementation
|
||||||
|
|
||||||
|
## Coverage Expectations
|
||||||
|
|
||||||
|
Write tests for:
|
||||||
|
- Critical business logic (aim for 90%+)
|
||||||
|
- Edge cases and error paths (aim for 80%+)
|
||||||
|
- Public APIs and contracts (aim for 100%)
|
||||||
|
|
||||||
|
Don't obsess over:
|
||||||
|
- Trivial getters/setters
|
||||||
|
- Generated code
|
||||||
|
- One-line wrappers
|
||||||
|
|
||||||
|
Coverage is a floor, not a ceiling. A test suite at 100% coverage that doesn't verify behavior is worthless.
|
||||||
|
|
||||||
|
## Test-Driven Development
|
||||||
|
|
||||||
|
Follow the red-green-refactor cycle:
|
||||||
|
1. Red: Write failing test for new behavior
|
||||||
|
2. Green: Write minimum code to pass
|
||||||
|
3. Refactor: improve code while tests stay green
|
||||||
|
|
||||||
|
Write tests first for new features. Write tests after for bug fixes. Never refactor without tests.
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
|
||||||
|
Group tests by feature or behavior, not by file structure. Name tests to describe the scenario:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TestUserAuthentication:
|
||||||
|
def test_valid_credentials_succeeds(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_invalid_credentials_fails(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_locked_account_fails(self):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
Each test should stand alone. Avoid shared state between tests. Use fixtures or setup methods to reduce duplication.
|
||||||
|
|
||||||
|
## Test Data
|
||||||
|
|
||||||
|
Use realistic test data that reflects production scenarios:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# GOOD: Realistic values
|
||||||
|
user = User(
|
||||||
|
email="alice@example.com",
|
||||||
|
name="Alice Smith",
|
||||||
|
age=28
|
||||||
|
)
|
||||||
|
|
||||||
|
# BAD: Placeholder values
|
||||||
|
user = User(
|
||||||
|
email="test@test.com",
|
||||||
|
name="Test User",
|
||||||
|
age=999
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid magic strings and numbers. Use named constants for expected values that change often.
|
||||||
42
rules/frameworks/n8n.md
Normal file
42
rules/frameworks/n8n.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# n8n Workflow Automation Rules
|
||||||
|
|
||||||
|
## Workflow Design
|
||||||
|
- Start with a clear trigger: Webhook, Schedule, or Event source
|
||||||
|
- Keep workflows under 20 nodes for maintainability
|
||||||
|
- Group related logic with sub-workflows
|
||||||
|
- Use the "Switch" node for conditional branching
|
||||||
|
- Add "Wait" nodes between rate-limited API calls
|
||||||
|
|
||||||
|
## Node Naming
|
||||||
|
- Use verb-based names: `Fetch Users`, `Transform Data`, `Send Email`
|
||||||
|
- Prefix data nodes: `Get_`, `Set_`, `Update_`
|
||||||
|
- Prefix conditionals: `Check_`, `If_`, `When_`
|
||||||
|
- Prefix actions: `Send_`, `Create_`, `Delete_`
|
||||||
|
- Add version suffix to API nodes: `API_v1_Users`
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
- Always add an Error Trigger node
|
||||||
|
- Route errors to a "Notify Failure" branch
|
||||||
|
- Log error details: `$json.error.message`, `$json.node.name`
|
||||||
|
- Send alerts on critical failures
|
||||||
|
- Add "Continue On Fail" for non-essential nodes
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
- Use "Set" nodes to normalize output structure
|
||||||
|
- Reference previous nodes: `{{ $json.field }}`
|
||||||
|
- Use "Merge" node to combine multiple data sources
|
||||||
|
- Apply "Code" node for complex transformations
|
||||||
|
- Clean data before sending to external APIs
|
||||||
|
|
||||||
|
## Credential Security
|
||||||
|
- Store all secrets in n8n credentials manager
|
||||||
|
- Never hardcode API keys or tokens
|
||||||
|
- Use environment-specific credential sets
|
||||||
|
- Rotate credentials regularly
|
||||||
|
- Limit credential scope to minimum required permissions
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Test each node independently with "Execute Node"
|
||||||
|
- Verify data structure at each step
|
||||||
|
- Mock external dependencies during development
|
||||||
|
- Log workflow execution for debugging
|
||||||
129
rules/languages/nix.md
Normal file
129
rules/languages/nix.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# Nix Code Conventions
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
- Use `alejandra` for formatting
|
||||||
|
- camelCase for variables, `PascalCase` for types
|
||||||
|
- 2 space indentation (alejandra default)
|
||||||
|
- No trailing whitespace
|
||||||
|
|
||||||
|
## Flake Structure
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
description = "Description here";
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
outputs = { self, nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in {
|
||||||
|
packages.default = pkgs.hello;
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = [ pkgs.hello ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Patterns
|
||||||
|
|
||||||
|
Standard module function signature:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
options.myService.enable = lib.mkEnableOption "my service";
|
||||||
|
config = lib.mkIf config.myService.enable {
|
||||||
|
services.myService.enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditionals and Merging
|
||||||
|
|
||||||
|
- Use `mkIf` for conditional config
|
||||||
|
- Use `mkMerge` to combine multiple config sets
|
||||||
|
- Use `mkOptionDefault` for defaults that can be overridden
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config = lib.mkMerge [
|
||||||
|
(lib.mkIf cfg.enable { ... })
|
||||||
|
(lib.mkIf cfg.extraConfig { ... })
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Patterns (AVOID)
|
||||||
|
|
||||||
|
### `with pkgs;`
|
||||||
|
Bad: Pollutes namespace, hard to trace origins
|
||||||
|
```nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
packages = with pkgs; [ vim git ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Good: Explicit references
|
||||||
|
```nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
packages = [ pkgs.vim pkgs.git ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `builtins.fetchTarball`
|
||||||
|
Use flake inputs instead. `fetchTarball` is non-reproducible.
|
||||||
|
|
||||||
|
### Impure operations
|
||||||
|
Avoid `import <nixpkgs>` in flakes. Always use inputs.
|
||||||
|
|
||||||
|
### `builtins.getAttr` / `builtins.hasAttr`
|
||||||
|
Use `lib.attrByPath` or `lib.optionalAttrs` instead.
|
||||||
|
|
||||||
|
## Home Manager Patterns
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
home.packages = with pkgs; [ ripgrep fd ];
|
||||||
|
programs.zsh.enable = true;
|
||||||
|
xdg.configFile."myapp/config".text = "...";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overlays
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
myOverlay = final: prev: {
|
||||||
|
myPackage = prev.myPackage.overrideAttrs (old: { ... });
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
nixpkgs.overlays = [ myOverlay ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Imports and References
|
||||||
|
|
||||||
|
- Use flake inputs for dependencies
|
||||||
|
- `lib` is always available in modules
|
||||||
|
- Reference packages via `pkgs.packageName`
|
||||||
|
- Use `callPackage` for complex package definitions
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
flake.nix # Entry point
|
||||||
|
modules/ # NixOS modules
|
||||||
|
services/
|
||||||
|
my-service.nix
|
||||||
|
overlays/ # Package overrides
|
||||||
|
default.nix
|
||||||
|
```
|
||||||
224
rules/languages/python.md
Normal file
224
rules/languages/python.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# Python Language Rules
|
||||||
|
|
||||||
|
## Toolchain
|
||||||
|
|
||||||
|
### Package Management (uv)
|
||||||
|
```bash
|
||||||
|
uv init my-project --package
|
||||||
|
uv add numpy pandas
|
||||||
|
uv add --dev pytest ruff pyright hypothesis
|
||||||
|
uv run python -m pytest
|
||||||
|
uv lock --upgrade-package numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting & Formatting (ruff)
|
||||||
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "W", "I", "N", "UP"]
|
||||||
|
ignore = ["E501"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Checking (pyright)
|
||||||
|
```toml
|
||||||
|
[tool.pyright]
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
reportMissingTypeStubs = true
|
||||||
|
reportUnknownMemberType = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing (pytest + hypothesis)
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
from hypothesis import given, strategies as st
|
||||||
|
|
||||||
|
@given(st.integers(), st.integers())
|
||||||
|
def test_addition_commutative(a, b):
|
||||||
|
assert a + b == b + a
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def user_data():
|
||||||
|
return {"name": "Alice", "age": 30}
|
||||||
|
|
||||||
|
def test_user_creation(user_data):
|
||||||
|
user = User(**user_data)
|
||||||
|
assert user.name == "Alice"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Validation (Pydantic)
|
||||||
|
```python
|
||||||
|
from pydantic import BaseModel, Field, validator
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
age: int = Field(ge=0, le=150)
|
||||||
|
email: str
|
||||||
|
|
||||||
|
@validator('email')
|
||||||
|
def email_must_contain_at(cls, v):
|
||||||
|
if '@' not in v:
|
||||||
|
raise ValueError('must contain @')
|
||||||
|
return v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Idioms
|
||||||
|
|
||||||
|
### Comprehensions
|
||||||
|
```python
|
||||||
|
# List comprehension
|
||||||
|
squares = [x**2 for x in range(10) if x % 2 == 0]
|
||||||
|
|
||||||
|
# Dict comprehension
|
||||||
|
word_counts = {word: text.count(word) for word in unique_words}
|
||||||
|
|
||||||
|
# Set comprehension
|
||||||
|
unique_chars = {char for char in text if char.isalpha()}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Managers
|
||||||
|
```python
|
||||||
|
# Built-in context managers
|
||||||
|
with open('file.txt', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Custom context manager
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def timer():
|
||||||
|
start = time.time()
|
||||||
|
yield
|
||||||
|
print(f"Elapsed: {time.time() - start:.2f}s")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generators
|
||||||
|
```python
|
||||||
|
def fibonacci():
|
||||||
|
a, b = 0, 1
|
||||||
|
while True:
|
||||||
|
yield a
|
||||||
|
a, b = b, a + b
|
||||||
|
|
||||||
|
def read_lines(file_path):
|
||||||
|
with open(file_path) as f:
|
||||||
|
for line in f:
|
||||||
|
yield line.strip()
|
||||||
|
```
|
||||||
|
|
||||||
|
### F-strings
|
||||||
|
```python
|
||||||
|
name = "Alice"
|
||||||
|
age = 30
|
||||||
|
|
||||||
|
# Basic interpolation
|
||||||
|
msg = f"Name: {name}, Age: {age}"
|
||||||
|
|
||||||
|
# Expression evaluation
|
||||||
|
msg = f"Next year: {age + 1}"
|
||||||
|
|
||||||
|
# Format specs
|
||||||
|
msg = f"Price: ${price:.2f}"
|
||||||
|
msg = f"Hex: {0xFF:X}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
### Bare Except
|
||||||
|
```python
|
||||||
|
# AVOID: Catches all exceptions including SystemExit
|
||||||
|
try:
|
||||||
|
risky_operation()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# USE: Catch specific exceptions
|
||||||
|
try:
|
||||||
|
risky_operation()
|
||||||
|
except ValueError as e:
|
||||||
|
log_error(e)
|
||||||
|
except KeyError as e:
|
||||||
|
log_error(e)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutable Defaults
|
||||||
|
```python
|
||||||
|
# AVOID: Default argument created once
|
||||||
|
def append_item(item, items=[]):
|
||||||
|
items.append(item)
|
||||||
|
return items
|
||||||
|
|
||||||
|
# USE: None as sentinel
|
||||||
|
def append_item(item, items=None):
|
||||||
|
if items is None:
|
||||||
|
items = []
|
||||||
|
items.append(item)
|
||||||
|
return items
|
||||||
|
```
|
||||||
|
|
||||||
|
### Global State
|
||||||
|
```python
|
||||||
|
# AVOID: Global mutable state
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
global counter
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# USE: Class-based state
|
||||||
|
class Counter:
|
||||||
|
def __init__(self):
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def increment(self):
|
||||||
|
self.count += 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Star Imports
|
||||||
|
```python
|
||||||
|
# AVOID: Pollutes namespace, unclear origins
|
||||||
|
from module import *
|
||||||
|
|
||||||
|
# USE: Explicit imports
|
||||||
|
from module import specific_function, MyClass
|
||||||
|
import module as m
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
### pyproject.toml Structure
|
||||||
|
```toml
|
||||||
|
[project]
|
||||||
|
name = "my-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"pydantic>=2.0",
|
||||||
|
"httpx>=0.25",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest", "ruff", "pyright", "hypothesis"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
```
|
||||||
|
|
||||||
|
### src Layout
|
||||||
|
```
|
||||||
|
my-project/
|
||||||
|
├── pyproject.toml
|
||||||
|
└── src/
|
||||||
|
└── my_project/
|
||||||
|
├── __init__.py
|
||||||
|
├── main.py
|
||||||
|
└── utils/
|
||||||
|
├── __init__.py
|
||||||
|
└── helpers.py
|
||||||
|
```
|
||||||
100
rules/languages/shell.md
Normal file
100
rules/languages/shell.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Shell Scripting Rules
|
||||||
|
|
||||||
|
## Shebang
|
||||||
|
|
||||||
|
Always use `#!/usr/bin/env bash` for portability. Never hardcode `/bin/bash`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Strict Mode
|
||||||
|
|
||||||
|
Enable strict mode in every script.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
```
|
||||||
|
|
||||||
|
- `-e`: Exit on error
|
||||||
|
- `-u`: Error on unset variables
|
||||||
|
- `-o pipefail`: Return exit status of last failed pipe command
|
||||||
|
|
||||||
|
## Shellcheck
|
||||||
|
|
||||||
|
Run shellcheck on all scripts before committing.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shellcheck script.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quoting
|
||||||
|
|
||||||
|
Quote all variable expansions and command substitutions. Use arrays instead of word-splitting strings.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Good
|
||||||
|
"${var}"
|
||||||
|
files=("file1.txt" "file2.txt")
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
process "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Bad
|
||||||
|
$var
|
||||||
|
files="file1.txt file2.txt"
|
||||||
|
for f in $files; do
|
||||||
|
process $f
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
Define with parentheses, use `local` for variables.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
my_function() {
|
||||||
|
local result
|
||||||
|
result=$(some_command)
|
||||||
|
echo "$result"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command Substitution
|
||||||
|
|
||||||
|
Use `$()` not backticks. Nests cleanly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Good
|
||||||
|
output=$(ls "$dir")
|
||||||
|
|
||||||
|
# Bad
|
||||||
|
output=`ls $dir`
|
||||||
|
```
|
||||||
|
|
||||||
|
## POSIX Portability
|
||||||
|
|
||||||
|
Write POSIX-compliant scripts when targeting `/bin/sh`.
|
||||||
|
|
||||||
|
- Use `[[` only for bash scripts
|
||||||
|
- Use `printf` instead of `echo -e`
|
||||||
|
- Avoid `[[`, `((`, `&>` in sh scripts
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Use `trap` for cleanup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cleanup() {
|
||||||
|
rm -f /tmp/lockfile
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
```
|
||||||
|
|
||||||
|
## Readability
|
||||||
|
|
||||||
|
- Use 2-space indentation
|
||||||
|
- Limit lines to 80 characters
|
||||||
|
- Add comments for non-obvious logic
|
||||||
|
- Separate sections with blank lines
|
||||||
150
rules/languages/typescript.md
Normal file
150
rules/languages/typescript.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# TypeScript Patterns
|
||||||
|
|
||||||
|
## Strict tsconfig
|
||||||
|
|
||||||
|
Always enable strict mode and key safety options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discriminated Unions
|
||||||
|
|
||||||
|
Use discriminated unions for exhaustive type safety:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Result =
|
||||||
|
| { success: true; data: string }
|
||||||
|
| { success: false; error: Error };
|
||||||
|
|
||||||
|
function handleResult(result: Result): string {
|
||||||
|
if (result.success) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Branded Types
|
||||||
|
|
||||||
|
Prevent type confusion with nominal branding:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type UserId = string & { readonly __brand: unique symbol };
|
||||||
|
type Email = string & { readonly __brand: unique symbol };
|
||||||
|
|
||||||
|
function createUserId(id: string): UserId {
|
||||||
|
return id as UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendEmail(email: Email, userId: UserId) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## satisfies Operator
|
||||||
|
|
||||||
|
Use `satisfies` for type-safe object literal inference:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const config = {
|
||||||
|
port: 3000,
|
||||||
|
host: "localhost",
|
||||||
|
} satisfies {
|
||||||
|
port: number;
|
||||||
|
host: string;
|
||||||
|
debug?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.port; // number
|
||||||
|
config.host; // string
|
||||||
|
```
|
||||||
|
|
||||||
|
## as const Assertions
|
||||||
|
|
||||||
|
Freeze literal types with `as const`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const routes = {
|
||||||
|
home: "/",
|
||||||
|
about: "/about",
|
||||||
|
contact: "/contact",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type Route = typeof routes[keyof typeof routes];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modern Features
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Promise.withResolvers()
|
||||||
|
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
||||||
|
|
||||||
|
// Object.groupBy()
|
||||||
|
const users = [
|
||||||
|
{ name: "Alice", role: "admin" },
|
||||||
|
{ name: "Bob", role: "user" },
|
||||||
|
];
|
||||||
|
const grouped = Object.groupBy(users, u => u.role);
|
||||||
|
|
||||||
|
// using statement for disposables
|
||||||
|
class Resource implements Disposable {
|
||||||
|
async [Symbol.asyncDispose]() {
|
||||||
|
await this.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function withResource() {
|
||||||
|
using r = new Resource();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Toolchain
|
||||||
|
|
||||||
|
Prefer modern tooling:
|
||||||
|
- Runtime: `bun` or `tsx` (no `tsc` for execution)
|
||||||
|
- Linting: `biome` (preferred) or `eslint`
|
||||||
|
- Formatting: `biome` (built-in) or `prettier`
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
Avoid these TypeScript patterns:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// NEVER use as any
|
||||||
|
const data = response as any;
|
||||||
|
|
||||||
|
// NEVER use @ts-ignore
|
||||||
|
// @ts-ignore
|
||||||
|
const value = unknownFunction();
|
||||||
|
|
||||||
|
// NEVER use ! assertion (non-null)
|
||||||
|
const element = document.querySelector("#foo")!;
|
||||||
|
|
||||||
|
// NEVER use enum (prefer union)
|
||||||
|
enum Status { Active, Inactive } // ❌
|
||||||
|
|
||||||
|
// Prefer const object or union
|
||||||
|
type Status = "Active" | "Inactive"; // ✅
|
||||||
|
const Status = { Active: "Active", Inactive: "Inactive" } as const; // ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
## Indexed Access Safety
|
||||||
|
|
||||||
|
With `noUncheckedIndexedAccess`, handle undefined:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const arr: string[] = ["a", "b"];
|
||||||
|
const item = arr[0]; // string | undefined
|
||||||
|
|
||||||
|
const item2 = arr.at(0); // string | undefined
|
||||||
|
|
||||||
|
const map = new Map<string, number>();
|
||||||
|
const value = map.get("key"); // number | undefined
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user