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:
m3tm3re
2026-02-17 19:05:45 +01:00
parent d475dde398
commit 8910413315
12 changed files with 1407 additions and 11 deletions

129
rules/languages/nix.md Normal file
View 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
View 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
View 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

View 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
```