diff --git a/.claude/skills/lint/SKILL.md b/.claude/skills/lint/SKILL.md new file mode 100644 index 00000000..81237c7b --- /dev/null +++ b/.claude/skills/lint/SKILL.md @@ -0,0 +1,72 @@ +--- +name: lint +description: > + Run ruff lint checks on piker Python files for + docstring style (D2xx) and code convention + compliance. Checks staged files by default. +user-invocable: true +argument-hint: "[--all|--fix|--stats|paths...]" +allowed-tools: Bash(python3 *), Bash(ruff *), Read, Edit +--- + +# Ruff Lint Checker + +Run piker's ruff config against staged files, +specific paths, or the full codebase. + +## Available scripts + +- **`scripts/check.py`** — self-contained (PEP 723) + wrapper around `ruff check` that defaults to staged + files and uses the project's `ruff.toml`. + +## Usage + +```bash +# check staged Python files (default) +python3 scripts/check.py + +# check full codebase +python3 scripts/check.py --all + +# auto-fix fixable violations +python3 scripts/check.py --fix + +# preview fixes without applying +python3 scripts/check.py --diff + +# show per-rule violation counts +python3 scripts/check.py --stats + +# check specific files +python3 scripts/check.py piker/ui/_style.py +``` + +## Common violations + +| Rule | Meaning | Fixable? | +|------|---------|----------| +| D213 | summary not on 2nd line after `'''` | yes | +| D205 | no blank line after summary | no | +| D204 | no blank line after class docstring | yes | +| D209 | closing quotes not on own line | yes | +| D200 | ignored — piker always multiline | n/a | +| W291 | trailing whitespace | yes | + +## Fixing D213 (most common) + +Convert this: +```python +"""Summary on first line.""" +``` + +To piker's `'''` multiline style: +```python +''' +Summary on second line. + +''' +``` + +For D205, insert a blank line between the summary +and description paragraphs inside the docstring. diff --git a/.claude/skills/lint/scripts/check.py b/.claude/skills/lint/scripts/check.py new file mode 100755 index 00000000..d49ab370 --- /dev/null +++ b/.claude/skills/lint/scripts/check.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.12" +# dependencies = [] +# /// +''' +Ruff lint checker for piker docstring and code style. + +Checks staged files by default, or the full `piker/` +tree with `--all`. Uses the project's `ruff.toml`. + +''' +import argparse +import shutil +import subprocess +import sys + + +def get_staged_py_files() -> list[str]: + ''' + Return staged Python file paths (added/copied/modified). + + ''' + result = subprocess.run( + [ + 'git', 'diff', + '--cached', + '--name-only', + '--diff-filter=ACM', + '--', '*.py', + ], + capture_output=True, + text=True, + ) + return [ + f for f in result.stdout.strip().split('\n') + if f + ] + + +def main() -> int: + ''' + Parse args and run `ruff check` against targets. + + ''' + ap = argparse.ArgumentParser( + description=( + "Run ruff check with piker's ruff.toml config.\n" + '\n' + 'Default: checks staged .py files only.\n' + 'Use --all for the full piker/ tree.' + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + ap.add_argument( + 'paths', + nargs='*', + help='files/dirs to check (default: staged)', + ) + ap.add_argument( + '--all', '-a', + action='store_true', + dest='check_all', + help='check entire piker/ directory', + ) + ap.add_argument( + '--fix', + action='store_true', + help='auto-fix fixable violations', + ) + ap.add_argument( + '--diff', + action='store_true', + help='preview fixes as unified diff (dry-run)', + ) + ap.add_argument( + '--stats', + action='store_true', + help='show per-rule violation counts only', + ) + args = ap.parse_args() + + # determine check targets + if args.check_all: + targets: list[str] = ['piker/'] + elif args.paths: + targets = args.paths + else: + targets = get_staged_py_files() + if not targets: + print( + 'No staged Python files found.\n' + 'Use --all for full codebase ' + 'or pass file paths.' + ) + return 0 + print( + f'Checking {len(targets)} staged file(s)..\n' + ) + + # ensure ruff is available on PATH + if not shutil.which('ruff'): + print( + 'Error: `ruff` not found on PATH.\n' + 'On NixOS: add to flake.nix or ' + '`nix-shell -p ruff`\n' + 'Otherwise: `uv sync --group lint` or ' + '`pip install ruff`' + ) + return 127 + + # build ruff command + cmd: list[str] = ['ruff', 'check'] + + if args.diff: + cmd.append('--diff') + elif args.fix: + cmd.append('--fix') + + if args.stats: + cmd.append('--statistics') + + cmd.extend(targets) + + result = subprocess.run(cmd) + return result.returncode + + +if __name__ == '__main__': + sys.exit(main())