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:
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