feat(biome-format): add Biome format/lint-on-edit plugin#19
Conversation
Third plugin in the marketplace, mirroring the bash-lint structural template. A PostToolUse hook runs `biome check --write` on edits to JS/TS/JSX/JSON files, surfacing residual findings as advisory context (always exits 0) and using the consuming repo's own biome.json. - Opt-in on a biome.json governing the edited file (conservative gate, mirroring bash-lint's .editorconfig opt-in); absent config leaves the file untouched rather than imposing Biome's built-in defaults. - Biome is CWD-anchored for config discovery (verified against current Biome docs), so the hook cd's to the topmost governing config dir. - Binary resolved from the repo's node_modules/.bin or PATH; never npx (no surprise downloads on a per-edit hook). - --reporter=github yields compact one-line findings (the gcc-format analog) with a clean repo-relative path; respects the config's own ignore rules for explicitly-edited files; tool breaks are skipped. - hook-utils.sh is byte-identical to the existing copies. Registered in marketplace.json and the README catalog. Both test suites green and validated with `claude plugin validate --strict`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU
|
Claude finished @kyle-sexton's task in 2m 39s —— View job Code Review —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04cb3eb5e5
ℹ️ 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".
…floor Address review on PR #19: - build_data_json fallback no longer interpolates TOOL/FILE_REL (a path with a quote/backslash could corrupt the envelope); emit a fixed empty-shape object instead. The path is near-unreachable — it fires only if `jq -n` itself fails, and hook::emit_telemetry already drops the envelope when jq is absent — so dropping the values is harmless and strictly safer than emitting malformed JSON. - README documents Biome 2.x as recommended (tested against 2.5.1), since --reporter=github / --error-on-warnings may be absent on much older releases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU
|
Claude finished @kyle-sexton's task in 1m 55s —— View job Code Review —
|
|
Thanks for the thorough review. Dispositions: #1 — #2 & #3 — #4 — Minimum Biome version (fixed in d6fcf9f). Added to the README Requirements: Biome 2.x recommended (tested against 2.5.1), noting that #5 — All lanes green and both test suites pass after the fixes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6fcf9f6a2
ℹ️ 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".
Address Codex P2 finding on PR #19: the opt-in walk treated the hidden .biome.json / .biome.jsonc names as configs, but Biome only loads those from 2.4 onward (verified against the 2.4.0 changelog). On an older Biome the gate would fire while Biome's own discovery ignored the dotted file and reformatted the edit with built-in defaults — the exact silent wrong-config reformat the opt-in exists to prevent. Gate only on biome.json / biome.jsonc, the names every flag-supporting Biome discovers, keeping the gate in lockstep with discovery across versions. Dotted-config support is deferred behind a future Biome>=2.4 probe. Adds a regression test (hidden .biome.json alone must not opt in) and updates the README/header to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU
|
Claude finished @kyle-sexton's task in 2m 18s —— View job Code Review —
|
Address Codex P2 finding on PR #19: Biome reads BIOME_CONFIG_PATH as a config-path override, so a stray user- or project-level export would make the hook format the edit with an unrelated config even though the opt-in gated on the repo's own biome.json (verified empirically against Biome 2.5.1 — the external config won in both directory and file form). Run the biome command with `env -u BIOME_CONFIG_PATH` so the discovered CONFIG_DIR config stays authoritative. This is consistent with the in-tree opt-in the plugin is built on: an out-of-tree config has no in-tree biome.json, so the gate skips anyway. Adds a regression test (repo single-quote config beats an external double-quote config pointed at by BIOME_CONFIG_PATH). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWt8nQzh8BAoZXRZRt2ARU
|
Claude finished @kyle-sexton's task in 1m 41s —— View job Code Review —
|
| # | Finding | Status |
|---|---|---|
| 1 | build_data_json printf fallback JSON injection |
✅ Fixed in d6fcf9f |
| 2 | hook-utils.sh stale "markdown-formatter" header |
✅ Intentional — byte-identical invariant |
| 3 | _MDFMT_ load guard variable |
✅ Intentional — byte-identical invariant |
| 4 | No minimum Biome version documented | ✅ Fixed in d6fcf9f |
| 5 | MultiEdit not in matcher |
✅ Intentional — baseline parity with bash-lint |
| Codex P2a | Dotted config names gated without version check | ✅ Fixed in 557fbb8 (canonical names only) |
| Codex P2b | BIOME_CONFIG_PATH could override discovered config |
✅ Fixed in 25e5a0c |
All five original findings and both Codex findings are resolved or intentionally left as documented quirks. The hook logic, test coverage, cross-platform handling, telemetry, and manifest compliance remain solid throughout.
This PR is ready to merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25e5a0ce97
ℹ️ 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".
|
|
||
| # Resolve repo root early — used to bound the biome-config opt-in walk and to | ||
| # compute the schema-required repo-relative path in data.file. | ||
| REPO_ROOT="$(hook::repo_root "$(dirname "$FILE")")" |
There was a problem hiding this comment.
Use CLAUDE_PROJECT_DIR when Git root is unavailable
When the edited project is not a Git worktree, hook::repo_root falls back to dirname "$FILE", so root becomes the edited file's own directory. In that setup (for example /proj/biome.json, /proj/node_modules/.bin/biome, and editing /proj/src/a.ts under CLAUDE_PROJECT_DIR=/proj), both upward walks stop at src and the hook silently reports skipped instead of using the parent Biome config/install. Falling back to CLAUDE_PROJECT_DIR or continuing the walk when git rev-parse fails would keep non-Git Claude projects working.
Useful? React with 👍 / 👎.
…er (#20) ## What Follow-up to #19. Propagates two cross-plugin items I flagged when shipping `biome-format`: **1. Telemetry fallback hardening (the real change).** `bash-lint` and `markdown-formatter` carried the same `build_data_json` jq-failure fallback that `biome-format` had before review hardened it: `printf '{"tool":"%s","file":"%s","findings":[]}' "$TOOL" "$FILE_REL"`. A path containing a quote or backslash could corrupt the telemetry envelope. Both now emit a fixed empty-shape object instead. The path is near-unreachable (fires only if `jq -n` fails, and `hook::emit_telemetry` already drops the envelope when `jq` is absent), so dropping the values is harmless and strictly safer than risking malformed JSON. All three plugins' `build_data_json` are now consistent. Patch-bumped so consumers pull the change via `claude plugin update`: `bash-lint` 0.1.0 → 0.1.1, `markdown-formatter` 0.1.1 → 0.1.2. **2. `MultiEdit` matcher — investigated, no change (moot).** Codex flagged that `Write|Edit` omits `MultiEdit`. Verified against the current [tools reference](https://code.claude.com/docs/en/tools-reference): the only file-modification tools are **`Edit`, `Write`, `NotebookEdit`** — `MultiEdit` is gone (merged into `Edit`), and the permission-format table confirms `Edit(...)` applies to "Edit, Write, NotebookEdit". So `Write|Edit` is already complete; `NotebookEdit` only touches `.ipynb`, which none of these plugins' extensions match. No matcher change warranted. ## Verification `shellcheck`, `typos`, `editorconfig-checker`, `comment-hygiene` all pass on the changed files. Both test suites green against real tools (`bash-lint` 31, `markdown-formatter` 41). `claude plugin validate --strict` passes for both plugins and the catalog. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds biome-format, the third plugin in this marketplace — a PostToolUse hook that runs Biome's
check --writeon edits to JS/TS/JSX/JSON files, auto-applying safe fixes + formatting + import sorting and surfacing residual findings to Claude as advisory context. It uses the consuming repo's ownbiome.jsonand ships no rules.Built to the bash-lint structural template:
.claude-plugin/plugin.json,hooks/hooks.json,hooks/biome-format.sh, byte-identicalhooks/hook-utils.sh, self-contained*.test.sh, and aREADME.md.Key design decisions
biome.json(or.jsonc/dotted) governs the edited file — the conservative opt-in mirroring bash-lint's.editorconfiggate. A repo without Biome config is left untouched rather than rewritten to Biome's built-in defaults.cds to the topmost governing config dir (the root config's directory).npx. The Biome binary is resolved from the repo'snode_modules/.bin/biomeorPATH; never downloaded on a per-edit hook.--reporter=githubyields one-line-per-diagnostic output (the gcc-format analog) with a clean repo-relative path. Residual findings surface viaadditionalContext; the hook always exits 0.files.includesignores even for an explicitly-edited path — verified empirically; the hook treats that as a silent skip (no nagging).Verification
All local CI lanes pass: shellcheck, typos, editorconfig-checker, markdownlint, comment-hygiene, exec-bit (3 scripts
100755,hook-utils.sh100644), LF + final newline,hook-utils.shbyte-identical to the existing copies.claude plugin validate --strictpasses for both the plugin and the catalog. Both test suites green (hook-utils 37, black-box 36) against real Biome 2.5.1, plus a runtime wiring smoke invoking the exacthooks.jsoncommand with${CLAUDE_PLUGIN_ROOT}set and a real envelope on stdin.Registered in
.claude-plugin/marketplace.jsonand the root README catalog.🤖 Generated with Claude Code