From d40a19ae4c3d123fe57faf12b939b10bfe214dbe Mon Sep 17 00:00:00 2001 From: goodboy Date: Wed, 11 Mar 2026 20:00:22 -0400 Subject: [PATCH] Add `/lint` skill with `scripts/check.py` Add a user-invocable `/lint` skill that wraps `ruff check` with staged-file defaults and piker's `ruff.toml` config via a PEP 723 script. The `ruff.toml` (from prior commit) now enforces piker's `'''` multiline docstring convention with `D2xx` formatting rules: summary on second line (D213), closing quotes on separate line (D209), blank line between summary and description (D205), plus `W` trailing-whitespace and cherry-picked `D4xx` content checks. Deats, - `scripts/check.py`: pure-stdlib wrapper that defaults to staged `.py` files via `git diff --cached`, with `--all`, `--fix`, `--diff`, and `--stats` flags. - calls `ruff` from `$PATH` (works with nixpkgs, nvim-bundled, or pip-installed). - prints clear install guidance if `ruff` not found. - `SKILL.md`: documents usage, common violations (D213, D205, D204), and fix patterns for converting to piker's docstring style. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- .claude/skills/lint/SKILL.md | 72 +++++++++++++++ .claude/skills/lint/scripts/check.py | 130 +++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 .claude/skills/lint/SKILL.md create mode 100755 .claude/skills/lint/scripts/check.py 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())