Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/ruff-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "ruff-format",
"version": "0.4.3",
"version": "0.4.4",
"description": "Auto-format and lint Python on edit via Ruff, only when a Ruff config governs the repo — using the consuming repo's own Ruff config.",
"author": {
"name": "Melodic Software",
Expand Down
13 changes: 13 additions & 0 deletions plugins/ruff-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
All notable changes to the `ruff-format` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.4.4]

### Documented

- Documented a known limitation of the pyproject.toml opt-in gate: the
line-anchored `[tool.ruff]` header check does not recognize the equivalent
TOML inline-table form (`[tool]` + `ruff = { ... }`), which Ruff itself
honors. Such a repo is treated as un-configured and the hook skips — fails
safe (a missed opt-in, never a wrong edit). Added a test case locking in the
documented (skip) behavior. No gate logic change: a robust fix needs real
TOML parsing, which is out of scope for this hook (see the hook's inline
comment for the full rationale).

## [0.4.3]

### Fixed
Expand Down
6 changes: 5 additions & 1 deletion plugins/ruff-format/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ own and runs only when your repo has opted into Ruff.
same discovery Ruff itself uses (a `pyproject.toml` without `[tool.ruff]` is
ignored, exactly as Ruff ignores it). A repo without a Ruff config is left
untouched rather than rewritten to Ruff's built-in defaults, so the plugin
never imposes a style you did not choose.
never imposes a style you did not choose. **Known limitation:** a
`pyproject.toml` that expresses this as a bare `[tool]` header with an inline
table (`[tool]` + `ruff = { ... }`) is not recognized, even though Ruff
itself honors that form — such a repo is treated as un-configured and the
hook skips (fails safe: a missed opt-in, never a wrong edit).
- **Fix + format on edit.** `ruff check --fix` applies safe fixes (never
`--unsafe-fixes`) and `ruff format` formats in place. Residual diagnostics
are reported but not auto-applied.
Expand Down
11 changes: 11 additions & 0 deletions plugins/ruff-format/hooks/ruff-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ root="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || root=""
# pyproject.toml). A pyproject.toml counts only when it carries a [tool.ruff]
# section (or a [tool.ruff.*] subtable) — Ruff skips it for discovery otherwise.
# Absence of any config is the opt-out: the file is left untouched.
#
# Known limitation: this line-anchored grep only recognizes the `[tool.ruff]`
# header form. TOML also allows the equivalent config as an inline table under
# a bare `[tool]` header (`[tool]` + `ruff = { ... }`), which Ruff itself does
# honor — a repo using that form is treated as un-configured here and the gate
# skips it. Fails safe (a missed opt-in, never a wrong edit); left undetected
# rather than heuristically matched because a robust check needs real TOML
# parsing (inline tables span lines, nest arbitrarily, and the `ruff` key can
# appear in any order under `[tool]`), and a multi-pass grep risks a false
# positive from an unrelated `ruff = "..."` key in a different table or a
# commented-out line.
CONFIG_FOUND=""
dir="$FILE_DIR_POSIX"
while [[ -n "$dir" ]]; do
Expand Down
16 changes: 16 additions & 0 deletions plugins/ruff-format/hooks/ruff-format.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ RC=$?
if [[ $RC -eq 0 ]]; then ok "pyproject with [tool.ruff] -> exit 0"; else fail "pyproject opt-in exit $RC"; fi
if grep -q 'x = 1' "$REPO_PT/opt.py"; then ok "pyproject with [tool.ruff] -> file formatted"; else fail "pyproject opt-in -> not formatted: $(cat "$REPO_PT/opt.py")"; fi

# --- Case 1d: pyproject.toml with [tool] inline-table form does NOT opt in ---
# Known limitation (documented in the hook and README): the line-anchored gate
# only recognizes the `[tool.ruff]` header form. A bare `[tool]` header with
# `ruff = { ... }` as an inline table is equivalent TOML that Ruff itself does
# honor, but the gate does not detect it — fails safe (missed opt-in, not a
# wrong edit) rather than heuristically pattern-matching it.
REPO_PI="$WORK/pyproject-inline"
new_ruff_repo "$REPO_PI" NO_CONFIG
printf '[project]\nname = "t"\n\n[tool]\nruff = { line-length = 88 }\n' >"$REPO_PI/pyproject.toml"
printf 'x=1\n' >"$REPO_PI/inline.py"
BEFORE_PI="$(cat "$REPO_PI/inline.py")"
OUT=$(run_hook "$REPO_PI/inline.py")
RC=$?
if [[ $RC -eq 0 && -z "$OUT" ]]; then ok "pyproject with [tool] inline-table ruff -> exit 0, silent (known limitation)"; else fail "inline-table pyproject opted in (rc=$RC out=$OUT)"; fi
if [[ "$(cat "$REPO_PI/inline.py")" == "$BEFORE_PI" ]]; then ok "pyproject with [tool] inline-table ruff -> file left untouched"; else fail "inline-table pyproject -> file was rewritten"; fi

# --- Case 2: gate ON + clean file -> exit 0, empty stdout --------------------
REPO="$WORK/consumer"
new_ruff_repo "$REPO"
Expand Down
Loading