diff --git a/plugins/ruff-format/.claude-plugin/plugin.json b/plugins/ruff-format/.claude-plugin/plugin.json index d6cce7c54..6798e7352 100644 --- a/plugins/ruff-format/.claude-plugin/plugin.json +++ b/plugins/ruff-format/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/ruff-format/CHANGELOG.md b/plugins/ruff-format/CHANGELOG.md index 87b70b890..63e4dbdc6 100644 --- a/plugins/ruff-format/CHANGELOG.md +++ b/plugins/ruff-format/CHANGELOG.md @@ -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 diff --git a/plugins/ruff-format/README.md b/plugins/ruff-format/README.md index 9954851d7..d04d6181c 100644 --- a/plugins/ruff-format/README.md +++ b/plugins/ruff-format/README.md @@ -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. diff --git a/plugins/ruff-format/hooks/ruff-format.sh b/plugins/ruff-format/hooks/ruff-format.sh index 3bf438c99..c7c4fc61e 100755 --- a/plugins/ruff-format/hooks/ruff-format.sh +++ b/plugins/ruff-format/hooks/ruff-format.sh @@ -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 diff --git a/plugins/ruff-format/hooks/ruff-format.test.sh b/plugins/ruff-format/hooks/ruff-format.test.sh index aeb24d699..9b59077eb 100755 --- a/plugins/ruff-format/hooks/ruff-format.test.sh +++ b/plugins/ruff-format/hooks/ruff-format.test.sh @@ -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"