Skip to content

feat(ruff-format): add Ruff format/lint-on-edit plugin#23

Merged
kyle-sexton merged 4 commits into
mainfrom
feat/ruff-format-plugin
Jul 4, 2026
Merged

feat(ruff-format): add Ruff format/lint-on-edit plugin#23
kyle-sexton merged 4 commits into
mainfrom
feat/ruff-format-plugin

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fourth migrated plugin, continuing the format-on-edit family (markdown → shell → JS/TS → Python). On every Write/Edit of a .py/.pyi file it runs the consuming repo's own Ruff: check --fix (safe fixes only) + format, then surfaces residual diagnostics as advisory additionalContext. Always exits 0 — commit hooks / CI stay the hard gate.

Design decisions (all verified empirically against ruff 0.15.20 + fetched docs)

  • Opt-in on a governing Ruff config (.ruff.toml, ruff.toml, or pyproject.toml with [tool.ruff]), discovered by walking up from the file — mirroring Ruff's own file-anchored discovery, including ignoring a pyproject.toml without [tool.ruff]. No config → file untouched (never impose built-in defaults).
  • --unfixable F401: a just-added import is reported but never auto-deleted mid-edit (the code using it often arrives on the next edit). Verified the CLI flag extends the consumer config's own unfixable list rather than replacing it.
  • Version-aware syntax errors come from Ruff's parser natively (stabilized in Ruff v0.12), checked against target-version / the requires-python floor — this replaces the source repo's separate AST-check script, so no second Python asset is bundled.
  • --force-exclude honors consumer excludes even for the explicitly-passed path (excluded file → untouched, no advisory noise). --no-cache keeps .ruff_cache out of consumer repos on edits.
  • Binary resolution: repo's own .venv (bin/ or Scripts/) walking up from the file, else PATH; never downloaded on a per-edit hook.
  • Kill switch HOOK_RUFF_FORMAT_ENABLED; telemetry envelope contract v1.0 unchanged; hook-utils.sh is a byte-identical copy of the lib the three shipped plugins already carry.

Verification

  • ruff-format.test.sh: 43/43 black-box contract assertions (opt-in gate incl. both pyproject variants, format/fix, F401 preservation, syntax + target-version findings, exclude respect, kill switch, PATH fallback, telemetry envelope) in throwaway non-source repos — Windows Git Bash + ruff 0.15.20.
  • hook-utils.test.sh: 37/37.
  • claude plugin validate ./plugins/ruff-format and claude plugin validate --strict . both pass.
  • ShellCheck + shfmt clean.

Notes

  • With four plugins now carrying identical hook-utils.sh copies, the Rule of Three for extracting a shared lib (playbook "wait on" item) is arguably met — flagged as follow-up, not done here.
  • Medley reintegration (cutover of its in-repo python-format.sh + retiring its AST-check script) is consumer-side follow-up per the playbook, alongside the still-pending bash-lint/biome-format reintegrations.

🤖 Generated with Claude Code

https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh


Note

Low Risk
Adds an advisory, opt-in dev hook that mutates only opted-in Python files in the consumer repo; no auth, data, or core infrastructure changes in this marketplace repo.

Overview
Introduces ruff-format, the fourth format-on-edit hook in the marketplace, and registers it in marketplace.json and the root README catalog.

On Write/Edit of Python files, a PostToolUse hook runs the consuming repo's Ruff (check --fix with safe fixes only, then format), then returns residual diagnostics as advisory additionalContext while always exiting 0. Ruff runs only when a governing config exists (.ruff.toml, ruff.toml, or pyproject.toml with [tool.ruff]), discovered by walking up from the file; no config means the file is left unchanged. Notable hook behavior includes --unfixable F401, --force-exclude, --no-cache, .venv/PATH binary resolution (no on-the-fly install), kill switch HOOK_RUFF_FORMAT_ENABLED, and telemetry envelope v1.0 via a copied hook-utils.sh.

The PR ships plugin manifest, hooks.json, ruff-format.sh, README, and black-box (ruff-format.test.sh) plus hook-utils.test.sh contract tests.

Reviewed by Cursor Bugbot for commit 40379b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Fourth migrated plugin: formats and lints Python (.py/.pyi) on Write|Edit
via the consuming repo's own Ruff config. Follows the biome-format model:

- Opt-in gate on a governing Ruff config (.ruff.toml, ruff.toml, or
  pyproject.toml with [tool.ruff]) — a pyproject.toml without [tool.ruff]
  does not opt in, mirroring Ruff's own discovery. No config -> untouched.
- ruff check --fix (safe fixes only) + ruff format, then a --no-fix verify
  pass whose concise diagnostics surface as advisory additionalContext.
- --unfixable F401 protects just-added imports from mid-edit auto-deletion
  (extends, not replaces, the consumer's own unfixable list — verified
  against ruff 0.15).
- Version-aware syntax errors come from Ruff's own parser (target-version /
  requires-python floor), replacing the source repo's separate AST-check
  layer.
- --force-exclude honors consumer excludes for explicitly-passed paths;
  --no-cache keeps .ruff_cache out of consumer repos.
- Binary resolved from the repo's .venv (bin/ or Scripts/) or PATH; never
  downloaded. Advisory: always exits 0. Kill switch
  HOOK_RUFF_FORMAT_ENABLED; telemetry envelope contract v1.0 unchanged.

Verified: 43/43 hook contract tests + 37/37 lib tests against ruff 0.15.20
(Windows Git Bash); claude plugin validate passes for the plugin and the
catalog (--strict).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

PR Review — feat(ruff-format): add Ruff format/lint-on-edit plugin

  • Gather context from PR description and changed files
  • Read and analyze plugin.json / hooks.json
  • Read and analyze ruff-format.sh (core hook logic)
  • Read and analyze hook-utils.sh
  • Read and analyze test files
  • Check marketplace.json and README changes
  • Synthesize findings and post review

View job run

@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 12s —— View job


PR Review — feat(ruff-format): add Ruff format/lint-on-edit plugin

  • Gather context from PR description and changed files
  • Read and analyze plugin.json / hooks.json
  • Read and analyze ruff-format.sh (core hook logic)
  • Read and analyze hook-utils.sh
  • Read and analyze test files
  • Check marketplace.json and README changes
  • Synthesize findings and post review

Summary

Strong addition that follows the existing formatter-plugin pattern faithfully. The opt-in gate, advisory-exit-0 contract, --unfixable F401 guard, and telemetry envelope are all correct. Test coverage (43 + 37 assertions) is thorough. A few findings below — one copy-paste artifact that's worth fixing before merge, plus a few minors.


Findings

1. Stale guard variable in hook-utils.sh — copy-paste artifact (minor, worth fixing)

File: plugins/ruff-format/hooks/hook-utils.sh L8–9

[[ -n "${_MDFMT_HOOK_UTILS_LOADED:-}" ]] && return 0
readonly _MDFMT_HOOK_UTILS_LOADED=1

The guard variable name _MDFMT_HOOK_UTILS_LOADED (and the comment "for the markdown-formatter plugin" on L2) are copy-paste artefacts from the original markdown-formatter. With the current architecture each hook runs in a separate bash subprocess, so the stale name causes no runtime collision — but it would become a functional bug if two plugins' hook-utils.sh files were ever sourced in the same process (the second would see the guard set by the first and silently skip loading).

The PR notes four plugins now carry identical copies and flags a shared-lib extraction as follow-up. Until that extraction lands, each copy should at least carry a distinct guard. The companion comment on L2 should also be updated.

Fix this →


2. hook-utils.sh file-level comment still says "markdown-formatter" (minor)

File: plugins/ruff-format/hooks/hook-utils.sh L2

# Minimal hook utility library for the markdown-formatter plugin.

Same copy-paste origin as finding #1 — the header says markdown-formatter not ruff-format. Cosmetic but confusing for readers of this plugin in isolation.


3. RC=1 with empty OUTPUT silently falls into the "tool break" branch (edge case, negligible risk)

File: plugins/ruff-format/hooks/ruff-format.sh L207

if [[ $RC -eq 1 && -n "$OUTPUT" ]]; then
  ...
fi

# falls through to "skipped" when RC=1 and OUTPUT is empty

A Ruff exit 1 with empty --quiet output shouldn't be reachable in normal operation. But if it were, the hook emits status=skipped and a "no diagnostics; tool break" message, which misrepresents what happened. Consider adding an || [[ $RC -eq 1 ]] guard or a brief comment explaining that this branch also covers that degenerate case. Not a blocking issue — just a future-reader trap.


4. No extension filter in hooks.json matcher (design note, consistent with siblings)

File: plugins/ruff-format/hooks/hooks.json L5

"matcher": "Write|Edit"

The hook fires on every Write|Edit (any file), then bails out in bash when the extension isn't .py/.pyi. This is the established pattern across all four plugins in this repo, so it is intentional and consistent — just worth noting for any future schema update that adds path-pattern matchers, at which point the siblings can all benefit from tighter hook dispatch.


5. RUFF_COMMON includes --quiet on the verify pass (clarification note)

File: plugins/ruff-format/hooks/ruff-format.sh L183, L198

RUFF_COMMON=(--force-exclude --no-cache --quiet)
# ...
OUTPUT=$(cd "$RUN_DIR" && "$RUFF_BIN" check --no-fix --output-format concise "${RUFF_COMMON[@]}" "$RUFF_ARG" 2>&1)

--quiet in the verify pass suppresses the summary line and rule counts, keeping OUTPUT to concise per-diagnostic lines. This is correct behavior. The 2>&1 redirection captures stderr (e.g., config parse errors) on the same channel. No issue — just confirming this is intentional and correct.


What's Done Well

  • Opt-in gate is correct. The pyproject.toml check uses grep -qE '^[[:space:]]*\[tool\.ruff(\]|[.]) which correctly matches [tool.ruff] and [tool.ruff.lint] sub-tables while rejecting [tool.ruffxtension]. The walk stops at repo root and filesystem root without risk of escaping the tree.

  • --unfixable F401 design. Documented clearly (header + README + test case 4c), verified to extend rather than replace the consumer's own unfixable list, and covered by an explicit test asserting the import is preserved and reported.

  • build_data_json fallback. The fixed empty-shape fallback '{"tool":"","file":"","findings":[]}' instead of a string interpolation of $TOOL/$FILE_REL is the right call — those variables could contain quotes or backslashes and corrupt the JSON envelope.

  • Fire-and-forget telemetry. The background subshell with >/dev/null 2>&1 cleanly prevents the sink from inheriting fd1 and causing a C1 fd1-inheritance blocker. The fail-open guards (sink unset, jq absent, relative path with no anchor) are all present and tested.

  • Windows path normalization. The cygpath -lm normalization in FILE_REL computation handles the Windows short-name / drive-form problem. The POSIX host gate in hook::normalize_path correctly avoids collapsing real POSIX single-letter top dirs.

  • Test quality. ruff-format.test.sh exercises the full behavioral surface — config discovery (both pyproject variants), format/fix, F401 preservation, syntax + target-version findings, exclude respect, kill switch, PATH fallback, telemetry envelope. Running from an unrelated $UNRELATED cwd correctly isolates CWD assumptions. The wait_for_sink poll pattern avoids fixed sleeps.


Verdict

Two items worth addressing before merge: finding #1 (rename _MDFMT_HOOK_UTILS_LOADED_RUFF_FMT_HOOK_UTILS_LOADED) and the accompanying stale comment (finding #2). Everything else is either correct design or a negligible edge case. The core hook logic, opt-in gate, telemetry, and test coverage are all solid.

cursor[bot]
cursor Bot approved these changes Jul 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: faad5b17b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/ruff-format/hooks/hooks.json

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Comment thread plugins/ruff-format/hooks/hook-utils.sh
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Already fixed — ruff-format.sh is committed as mode 100755 (see HEAD git ls-tree plugins/ruff-format/hooks/ruff-format.sh). The Codex finding was from an earlier revision.

cursor[bot]
cursor Bot approved these changes Jul 4, 2026
The membership check in hook::read_file_path compared lexically
normalized paths, so an in-project symlink pointing outside
CLAUDE_PROJECT_DIR passed the prefix check while Ruff's writes landed
outside the workspace (Cursor security review on PR #23, MEDIUM).

Canonicalize both sides of the comparison via realpath/readlink -f
before normalizing: escaping file and directory symlinks are now
rejected, and a project root reached through a symlink (e.g. macOS
/tmp) no longer causes false rejections. Hosts with no resolver fall
back to the historical lexical comparison rather than silently
disabling the hook.

Adds six guard tests (lexical accept/reject plus four symlink cases,
SKIP-passing where the host cannot create symlinks). Both scripts also
pick up this repo's canonical shfmt style (.editorconfig: 2-space,
no switch-case indent); the copies in the other plugins are synced in
a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLwWqDP9dvakPSnnqRxwW4
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 34s —— View job


PR Review — feat(ruff-format): add Ruff format/lint-on-edit plugin

  • Gather context from PR description and changed files
  • Review hook-utils.sh (guard variable, symlink fix)
  • Review ruff-format.sh (core logic, new commits)
  • Review test files
  • Check marketplace.json and README changes
  • Verify prior findings addressed
  • Synthesize and post final review

What changed since the last review

Three commits total:

  • faad5b1 — original feature (reviewed previously)
  • c27ccc1 — set executable bit on ruff-format.sh, ruff-format.test.sh, hook-utils.test.sh
  • 16ec6b0 — add hook::physical_path() to hook-utils.sh and use it in hook::read_file_path(); add 6 symlink test cases to hook-utils.test.sh

File modes confirmed via git cat-file:

100644  hook-utils.sh       (sourced, not executed — correct)
100755  hook-utils.test.sh  ✓
100644  hooks.json          (config — correct)
100755  ruff-format.sh      ✓
100755  ruff-format.test.sh ✓

Status of prior findings

# Finding Status
1 Stale guard var _MDFMT_HOOK_UTILS_LOADED in hook-utils.sh L8-9 Still open
2 Stale comment "markdown-formatter plugin" in hook-utils.sh L2 Still open
3 RC=1 + empty OUTPUT falls into misleading "tool break" branch Still open (minor, acknowledged)
Codex ruff-format.sh committed with mode 100644 (not executable) Fixed in c27ccc1
Cursor Symlink escape defeats project-containment guard Fixed in 16ec6b0

New commit review: 16ec6b0 — symlink fix

hook::physical_path() (lines 55–64):

hook::physical_path() {
  local resolved
  if resolved=$(realpath -- "$1" 2>/dev/null) || resolved=$(readlink -f -- "$1" 2>/dev/null); then
    if [[ -n "$resolved" ]]; then
      printf '%s' "$resolved"
      return
    fi
  fi
  printf '%s' "$1"
}

Implementation is correct. The OR short-circuit means the fallback to readlink -f only runs when realpath is absent or fails, and the inner [[ -n "$resolved" ]] guard catches the hypothetical zero-exit/empty-output edge case. The documented degradation (fall back to lexical comparison on hosts with no resolver) is the right call for an advisory hook. No issues.

hook::read_file_path() (lines 72–90):

norm_file=$(hook::normalize_path "$(hook::physical_path "$file")")
norm_project=$(hook::normalize_path "$(hook::physical_path "${CLAUDE_PROJECT_DIR}")")

Both sides of the membership comparison are now canonicalized before normalization. This correctly blocks:

  • A symlinked file inside the project pointing to a real path outside it
  • A symlinked directory inside the project that contains the target file
  • A project root reached via a symlink (macOS /tmp) no longer causing false rejections

The comment block (lines 46–54, 66–70) explains the design clearly. ✓

New tests in hook-utils.test.sh (Test 12, lines 344–435):

Six guard cases, all correct:

  • In-project file accepted ✓
  • Prefix-sibling rejected ✓
  • Escaping symlink (inside → outside) rejected ✓
  • In-project symlink accepted without path mangling ✓
  • Symlinked-directory escape rejected ✓
  • Symlinked project root resolves correctly ✓

The SKIP pattern using try_symlink (which checks [[ -L ]] after ln -s) is the right way to handle Windows hosts without Developer Mode — it avoids counting a silent copy as a tested real symlink. ✓


Still outstanding: stale guard variable (finding #1 from previous review)

File: plugins/ruff-format/hooks/hook-utils.sh L2, L8–9

# Minimal hook utility library for the markdown-formatter plugin.   ← L2
...
[[ -n "${_MDFMT_HOOK_UTILS_LOADED:-}" ]] && return 0               ← L8
readonly _MDFMT_HOOK_UTILS_LOADED=1                                  ← L9

These are copy-paste artefacts from the original markdown-formatter. With the current architecture (each hook in its own subprocess) there is no runtime collision today — but it becomes a functional bug the moment two plugins' hook-utils.sh copies are sourced in the same process: the second copy sees the guard set by the first and returns early without loading its own functions.

The comment on L2 ("for the markdown-formatter plugin") is also misleading for readers of this plugin in isolation.

Fix this →


Summary

The two fix commits address the real issues — executable bit and symlink escape — both correctly. The symlink fix is well-implemented and the new test cases are solid. The one remaining pre-merge item is still finding #1/2: rename _MDFMT_HOOK_UTILS_LOADED_RUFF_FMT_HOOK_UTILS_LOADED and update the L2 comment. Everything else is in good shape.

cursor[bot]
cursor Bot approved these changes Jul 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16ec6b0334

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/ruff-format/hooks/ruff-format.sh Outdated
A consumer config may set unsafe-fixes = true, aimed at interactive
runs; without the explicit flag the hook's fix pass inherited it and
applied rewrites Ruff labels as possibly not intent-preserving (e.g.
E712 x == True -> x) on every edit, violating the documented
safe-fixes-only contract (Cursor review on PR #23, P2). Same principle
as --no-fix on the verify pass: edit-time behavior is pinned by flag,
not left to config the consumer aimed at another workflow.

Adds a contract test: with unsafe-fixes = true, the E712 comparison
survives the hook and surfaces as an advisory finding instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLwWqDP9dvakPSnnqRxwW4
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 16s —— View job


PR Review — feat(ruff-format): add Ruff format/lint-on-edit plugin

  • Gather context from PR description and prior reviews
  • Read hook-utils.sh (check stale guard variable status)
  • Read ruff-format.sh (verify --no-unsafe-fixes implementation)
  • Synthesize findings and post final review

What changed in the latest commit (40379b2)

ruff-format.sh line 192--no-unsafe-fixes added to the fix pass:

(cd "$RUN_DIR" && "$RUFF_BIN" check --fix --no-unsafe-fixes --unfixable F401 "${RUFF_COMMON[@]}" "$RUFF_ARG") >/dev/null 2>&1 || true

The accompanying comment (lines 185–191) is the right call — it explains why this flag is explicit rather than assumed: a consumer config that sets unsafe-fixes = true for interactive use would otherwise promote the unattended edit-time hook to an unsafe rewriter. That's the precise distinction that justifies spelling out the flag even though it's technically the default for most installs. Implementation and rationale are both correct. ✓


Status of all outstanding findings

# Finding Status
1 Stale guard var _MDFMT_HOOK_UTILS_LOADED in hook-utils.sh L8–9 Still open
2 Stale comment "markdown-formatter plugin" in hook-utils.sh L2 Still open
3 RC=1 + empty OUTPUT falls into "tool break" branch (minor) Still open (acknowledged)
Codex executable bit on ruff-format.sh Fixed in c27ccc1
Cursor Symlink escape in hook::read_file_path Fixed in 16ec6b0
Codex P2 --no-unsafe-fixes missing from fix pass Fixed in 40379b2

Still outstanding: stale guard variable (findings #1/#2)

File: plugins/ruff-format/hooks/hook-utils.sh L2, L8–9

# Minimal hook utility library for the markdown-formatter plugin.   ← L2
...
[[ -n "${_MDFMT_HOOK_UTILS_LOADED:-}" ]] && return 0               ← L8
readonly _MDFMT_HOOK_UTILS_LOADED=1                                  ← L9

These copy-paste artefacts from the markdown-formatter are the sole remaining blocker. As noted in the prior review: with each hook running in its own subprocess the stale name causes no collision today, but it becomes a functional bug if two plugins' hook-utils.sh files are ever sourced in the same process — the second sees the guard already set and returns without loading its own functions. Each copy needs a distinct guard until the shared-lib extraction lands.

Fix this →


Summary

The 40379b2 commit correctly addresses the Codex P2 finding — --no-unsafe-fixes is in the right position, the comment explains the non-obvious reason for it, and the implementation is solid. Three of the four prior findings are now resolved. The one remaining pre-merge item is still the stale guard variable and header comment in hook-utils.sh (L2, L8–9): rename _MDFMT_HOOK_UTILS_LOADED_RUFF_FMT_HOOK_UTILS_LOADED and update the L2 comment to say "ruff-format plugin". Everything else is in good shape.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: medium. Cursor Bugbot completed successfully with no findings on the latest commit; prior review threads are resolved. Approved; no reviewers assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@kyle-sexton
kyle-sexton merged commit 03eb80a into main Jul 4, 2026
20 checks passed
@kyle-sexton
kyle-sexton deleted the feat/ruff-format-plugin branch July 4, 2026 06:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40379b2dda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# Ruff's parser flags syntax not yet valid on the configured Python floor)
# arrive on the same channel. Exit 0 = clean, 1 = findings, 2 = Ruff itself
# failed (bad config, internal error).
OUTPUT=$(cd "$RUN_DIR" && "$RUFF_BIN" check --no-fix --output-format concise "${RUFF_COMMON[@]}" "$RUFF_ARG" 2>&1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore redirected Ruff output for findings

When the consuming environment sets RUFF_OUTPUT_FILE, this reporter no longer receives diagnostics on stdout/stderr: ruff check --help documents --output-file as also controlled by [env: RUFF_OUTPUT_FILE=]. In that case Ruff exits 1 with findings but OUTPUT is empty, so the hook mislabels normal lint findings as a tool break and returns no additionalContext findings; unset that env var for the hook or force the reporter output back to stdout.

Useful? React with 👍 / 👎.

kyle-sexton added a commit that referenced this pull request Jul 4, 2026
## Summary

Four plugins carry byte-identical `hooks/hook-utils.sh` copies — the
playbook's Rule-of-Three trigger. Per the decision reached with
fresh-docs research (plugins-reference + plugin-dependencies, fetched
2026-07-03), the shared lib becomes an **authoring-time SSOT with plain
runtime copies**:

- `lib/hook-utils.sh` is the canonical source;
`scripts/sync-hook-utils.sh` propagates it into every carrying plugin
(`sync` / `--check` / `--check-bump <ref>` modes).
- New `hook-utils-sync` CI lane (wired into `ci-status`): fails on copy
drift, and on PRs fails when the lib changed without a version bump in
every carrying plugin — the plugin `version` is the update cache key, so
an unbumped plugin never delivers the change.
- Runtime is untouched: each installed plugin stays self-contained under
cache isolation; no cross-plugin coupling; one-plugin install UX
unchanged.
- Rejected/deferred alternatives are recorded in the playbook with
citations and revisit triggers: a dependency plugin is **not viable**
(no documented mechanism exposes a dependency's install path to another
plugin's hook; cache dirs are version- and SHA-suffixed by design), and
marketplace-internal symlinks are **deferred** (skipped for
`--plugin-dir`/local installs; Windows-fragile).

All four plugin versions bumped (patch): the shared header no longer
claims markdown-formatter heritage and the sourcing guard drops the
`_MDFMT` prefix.

**Stacked on #23** (`feat/ruff-format-plugin`) — do not merge into #23;
retarget to `main` after #23 merges.

## Verification

- `scripts/sync-hook-utils.sh --check` passes; all copies byte-identical
to source (sha1 `e207414`).
- Bump gate exercised in all three paths: lib-unchanged → pass;
changed+bumped → pass; changed+stale → exit 1 naming the manifest.
- All four `hook-utils.test.sh` suites pass (37/37 ×3, 43/43).
- `claude plugin validate` ×4 and `--strict` catalog validation pass;
shellcheck, markdownlint, actionlint, editorconfig-checker clean
locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 15, 2026
## Summary

Restores the eval/test-coverage attrition identified by the salvage
sweep (tier-3: items #13, #15#23 plus the two flagged cosmetic-severity
items), recreated genericized and validated against
`plugins/skill-quality/reference/evals.schema.json`. Per-plugin version
bumps + CHANGELOG entries.

| Plugin | Version | Restored coverage |
|---|---|---|
| songwriting | 0.4.0 | All 13 medley behavioral evals mapped onto the
multi-skill split (workflow 3, diagnosis 3, rhyme 2, song-form 2,
co-write 2, object-writing 1); zero drops |
| ai-briefing | 0.4.0 | 6 engine evals + 3 synthetic fixtures via the
audience-defaults seam; the legacy Grok-preload case re-derived as an
unreachable-RSS visible-degrade scenario (the flag never shipped; CI
contract bans the token) |
| event-storming | 0.4.0 | Offline board-export eval (id 8) + 74-line
fixture for `--discover-bcs` — disjoint from the live-Miro-required
eval, reconciliation noted inline |
| codebase-audit | 0.3.0 | Scope-boundary eval: decline
settings/MCP/hooks claim-extraction, route to
`/claude-config-audit:settings-audit` |
| discovery | 0.5.0 | Research floor-scaling ("floors are not targets")
+ broad-topic doubled-minimums evals, vendor-neutral |
| source-control | 0.2.0 | Readiness security-gate eval + genericized
fixture, mixed-actor (bot-fix-now vs human-pause) eval, three worktree
evals (dry-run report-only, invalid-name rejection, batched-gh status +
graceful degrade) |
| docs-hygiene | 0.4.0 | Two self-contained fixture-backed cases
(compress classification-table, declutter opt-out/section-exemption) —
fixtures empirically verified against `detect.sh`; rename-references
"add an eval case" clauses |
| review-toolkit | 0.6.0 | code-review-fanout evals 6 → 20:
dedup/severity-derivation, fix-pass safety fence (correctness never
routed to /simplify), run-everything null-reconciliation +
priority-ordering; 2 medley cases skipped (fixed-roster counts
deliberately generalized away), their surviving assertions folded in |

## Verification

- All 8 plugins pass `claude plugin validate`;
`scripts/validate-plugin-contracts.mjs` passes (1311 files)
- Every evals.json jq-parses and validates against the schema
(ajv/check-jsonschema)
- markdownlint-cli2 0 errors on new fixtures + CHANGELOGs; coupling grep
(medley/consumer refs) clean
- Rebased onto main post-#192; per-plugin validate re-run green after
rebase

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_011xHkkNc7CR98L8Xz9Mu7ZA

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…e IV, flip pre-build (#939)

Dated Phase-4 note: operator ruling (2026-07-22) to move at maximum
speed within the predicate's intent. Calendar gates stay binding;
sanctioned levers are front-loaded seeding (genuine items only),
parallel Phase IV ignition (gate already met), and pre-built Phase III
flip machinery. Records same-day watch activity: drain PRs #23/#24/#26 +
Dependabot #25 merged in kyle-sexton/autonomy-demo-scratch, queue
reseeded (scratch#27–#31), predicate completions=6.

## Related

- kyle-sexton/autonomy-demo-scratch#27, #28, #29, #30, #31. No linked
issue — status-note documentation; nothing in this repo closes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant