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
14 changes: 8 additions & 6 deletions .bully.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,14 @@ rules:

ruff-clean:
description: >
Python files must pass `ruff check`. Requires ruff on PATH (see
the `dev` extras in pyproject.toml). When ruff isn't installed the
rule passes with a stderr hint rather than blocking edits.
--force-exclude makes ruff respect pyproject's extend-exclude so
test fixtures under bench/fixtures/ are skipped.
Python files must pass `ruff check`. Looks for ruff on PATH first,
then falls back to `.venv/bin/ruff` for repo-local venv installs
(the common case when the hook runs from Claude Code without the
venv activated). When neither is present the rule passes with a
stderr hint rather than blocking edits. --force-exclude makes
ruff respect pyproject's extend-exclude so test fixtures under
bench/fixtures/ are skipped.
engine: script
scope: "*.py"
severity: error
script: "command -v ruff >/dev/null 2>&1 || { echo 'bully: ruff not on PATH; pip install -e .[dev]' >&2; exit 0; }; ruff check --force-exclude {file}"
script: "if command -v ruff >/dev/null 2>&1; then ruff check --force-exclude {file}; elif [ -x .venv/bin/ruff ]; then .venv/bin/ruff check --force-exclude {file}; else echo 'bully: ruff not found; pip install -e .[dev]' >&2; exit 0; fi"
4 changes: 4 additions & 0 deletions bully
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec python3 "$HERE/pipeline/pipeline.py" "$@"
15 changes: 15 additions & 0 deletions docs/rule-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ rule-id:
- Stdout on failure should list violations. The pipeline parses common formats (see [Output formats](#output-formats)).
- Each rule has a 30-second timeout.

### YAML string escapes

Double-quoted scalars process YAML escapes: `\\` becomes `\`, `\"` becomes `"`, `\n` becomes newline, `\t` becomes tab. Single-quoted scalars only process `''` as `'` (YAML spec); backslashes pass through literally.

The practical upshot for regex in `script:` values: write `"grep -nE 'console\\.log\\(' {file}"` — YAML turns each `\\` into `\`, so grep sees `console\.log\(` and matches the intended pattern. Under-escaping (`"grep -nE 'console\.log\('"`) leaves grep with `console.log(`, where `.` matches any character and the parens become a group.

For a literal backslash in the script, use single-quoted YAML (`'grep "\\"'`) or double up explicitly in double quotes.

### Minimal grep pattern

```yaml
Expand Down Expand Up @@ -221,6 +229,8 @@ Scope is right-anchored glob matching via `PurePath.match`.
| `*` | everything |
| `["*.php", "*.blade.php"]` | any file matching either glob |

`**` works on all supported Python versions because bully's matcher implements recursive globbing explicitly, not via `PurePath.match` (whose recursive-`**` support only landed in 3.13).

Scope narrowly. Broad scopes (like `"*"`) run more rules per edit and inflate the noise floor. Save `"*"` for truly cross-language rules like orchestration-label bans.

## Output formats
Expand Down Expand Up @@ -328,6 +338,11 @@ bully lint src/foo.php --print-prompt
bully lint src/foo.php --diff "$(git diff src/foo.php)"
```

Two more flags worth knowing about:

- **`bully --explain --file <path>`** — prints one line per rule in scope with its verdict: `fire`, `pass`, `skipped <reason>`, or `dispatched`. Reach for this when a rule *should* match a file but isn't firing — the output tells you whether the scope filter dropped it or the rule ran and passed. Already present; surfaced here because it's easy to miss.
- **`bully validate --execute-dry-run`** — runs every script rule against empty input and flags rules that error out at the shell or regex level. Catches the "grep: parentheses not balanced" class of bugs at config time instead of at hook time. Run it before committing a new regex rule.

Exit codes:

- `0` — pass or evaluate. Stdout is JSON.
Expand Down
Loading
Loading