From 8910413315ecc27cdd08e99a3d557f5ee24bd1a7 Mon Sep 17 00:00:00 2001 From: m3tm3re Date: Tue, 17 Feb 2026 19:05:45 +0100 Subject: [PATCH] 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 --- .sisyphus/plans/rules-system.md | 22 +-- rules/concerns/coding-style.md | 163 ++++++++++++++++++++ rules/concerns/documentation.md | 149 ++++++++++++++++++ rules/concerns/git-workflow.md | 118 +++++++++++++++ rules/concerns/naming.md | 105 +++++++++++++ rules/concerns/project-structure.md | 82 ++++++++++ rules/concerns/testing.md | 134 +++++++++++++++++ rules/frameworks/n8n.md | 42 ++++++ rules/languages/nix.md | 129 ++++++++++++++++ rules/languages/python.md | 224 ++++++++++++++++++++++++++++ rules/languages/shell.md | 100 +++++++++++++ rules/languages/typescript.md | 150 +++++++++++++++++++ 12 files changed, 1407 insertions(+), 11 deletions(-) create mode 100644 rules/concerns/coding-style.md create mode 100644 rules/concerns/documentation.md create mode 100644 rules/concerns/git-workflow.md create mode 100644 rules/concerns/naming.md create mode 100644 rules/concerns/project-structure.md create mode 100644 rules/concerns/testing.md create mode 100644 rules/frameworks/n8n.md create mode 100644 rules/languages/nix.md create mode 100644 rules/languages/python.md create mode 100644 rules/languages/shell.md create mode 100644 rules/languages/typescript.md diff --git a/.sisyphus/plans/rules-system.md b/.sisyphus/plans/rules-system.md index 0ba6274..e50f71e 100644 --- a/.sisyphus/plans/rules-system.md +++ b/.sisyphus/plans/rules-system.md @@ -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**: - 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**: - 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. **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. **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. **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. **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**: - 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**: - 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**: - 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. **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. **Recommended Agent Profile**: `writing` diff --git a/rules/concerns/coding-style.md b/rules/concerns/coding-style.md new file mode 100644 index 0000000..c9fc4cc --- /dev/null +++ b/rules/concerns/coding-style.md @@ -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. diff --git a/rules/concerns/documentation.md b/rules/concerns/documentation.md new file mode 100644 index 0000000..c076c74 --- /dev/null +++ b/rules/concerns/documentation.md @@ -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. diff --git a/rules/concerns/git-workflow.md b/rules/concerns/git-workflow.md new file mode 100644 index 0000000..e05025e --- /dev/null +++ b/rules/concerns/git-workflow.md @@ -0,0 +1,118 @@ +# Git Workflow Rules + +## Conventional Commits + +Format: `(): ` + +### 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: `/` + +### 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 diff --git a/rules/concerns/naming.md b/rules/concerns/naming.md new file mode 100644 index 0000000..d9c8ba8 --- /dev/null +++ b/rules/concerns/naming.md @@ -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` diff --git a/rules/concerns/project-structure.md b/rules/concerns/project-structure.md new file mode 100644 index 0000000..19c0349 --- /dev/null +++ b/rules/concerns/project-structure.md @@ -0,0 +1,82 @@ +# Project Structure + +## Python + +Use src layout for all projects. Place application code in `src//`, 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 diff --git a/rules/concerns/testing.md b/rules/concerns/testing.md new file mode 100644 index 0000000..6ecc705 --- /dev/null +++ b/rules/concerns/testing.md @@ -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. diff --git a/rules/frameworks/n8n.md b/rules/frameworks/n8n.md new file mode 100644 index 0000000..07682f9 --- /dev/null +++ b/rules/frameworks/n8n.md @@ -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 diff --git a/rules/languages/nix.md b/rules/languages/nix.md new file mode 100644 index 0000000..f4b28d7 --- /dev/null +++ b/rules/languages/nix.md @@ -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 ` 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 +``` diff --git a/rules/languages/python.md b/rules/languages/python.md new file mode 100644 index 0000000..523a57b --- /dev/null +++ b/rules/languages/python.md @@ -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 +``` diff --git a/rules/languages/shell.md b/rules/languages/shell.md new file mode 100644 index 0000000..64f21e3 --- /dev/null +++ b/rules/languages/shell.md @@ -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 diff --git a/rules/languages/typescript.md b/rules/languages/typescript.md new file mode 100644 index 0000000..ee7fdd7 --- /dev/null +++ b/rules/languages/typescript.md @@ -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(); + +// 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(); +const value = map.get("key"); // number | undefined +```