Fix YAML escape miscompile and harden init/doctor flow#4
Merged
Conversation
Addresses review feedback from a real init session.
Parser (silent miscompile, highest impact):
- `_parse_scalar` processes YAML double-quoted escapes (\\, \", \n,
\t, \r, \/, \0) and single-quoted `''`. Previously stripped outer
quotes only, so `"\\."` stayed as two backslashes -- every shipped
`examples/rules/*.yml` pattern silently exited 0 on grep's
"parentheses not balanced" error.
- `filter_rules` now handles `**` recursively on all supported Python
versions via `_scope_matches` (was broken on 3.10-3.12; `PurePath.match`
only gained recursive `**` in 3.13). Matches the `_path_matches_skip`
fallback pattern for consistency.
Doctor:
- Actually compares `sys.version_info` against 3.10 instead of
printing `[OK] Python X.Y >= 3.10` unconditionally.
- Accepts skills and the evaluator agent at either the legacy path
(`~/.claude/skills/...`) or the plugin cache (`~/.claude/plugins/
cache/*/bully/*/{skills,agents}/...`).
New features:
- `bully validate --execute-dry-run` runs each script rule against
/dev/null to surface shell/regex syntax errors at config time.
- `bully` shell wrapper at repo root so plugin-installed users can
alias or symlink without needing `pip install`.
Skill + docs:
- `bully-init` Step 6 now runs trust, doctor, telemetry-dir setup,
and per-rule smoke tests before handing off.
- `docs/rule-authoring.md` documents YAML escapes, `**` compatibility,
and the `--explain` / `--execute-dry-run` flags.
Tests: 297 passing (+5 new suites covering escapes, recursive globs,
version gating, plugin paths, and dry-run execution).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's `ruff check .` flagged 3 SIM rules that the hook's dogfood `ruff-clean` missed because ruff isn't on PATH in the hook's env (graceful fallback exits 0 when the binary is missing). - SIM110: replace for-loop with `all()` in `_segment_matches` - SIM102: combine nested `if` statements with `and` in `_match_glob_segments` - SIM108: use ternary for SKILL.md vs `<name>.md` path selection in `_plugin_cache_candidates` Behaviour unchanged; 297 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous rule exited 0 when `ruff` wasn't on PATH -- which includes Claude Code's hook env (venv never activated). That meant local edits silently passed ruff checks while CI caught them. Now the rule prefers `ruff` on PATH but falls back to `.venv/bin/ruff` before the graceful "not found" exit. Smoke-tested both paths: with and without PATH containing ruff, a SIM-violating file correctly exits 1 so the hook blocks the edit. Keeps the graceful no-venv behaviour for contributors who don't have a local install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses review feedback from a real
bully-initsession. Priority-ordered:P0 — silent miscompile
_parse_scalarnow processes YAML double-quoted escapes (\\,\",\n,\t,\r,\/,\0) and single-quoted''→'. Previously it stripped outer quotes only, so"\\."stayed as two literal backslashes. Every shippedexamples/rules/*.ymlregex (react-ts, rust-cli, nextjs, django, go, fastapi, rails) exited 0 on grep'sparentheses not balancederror — silent pass.filter_ruleshandles**recursively via a new_scope_matcheshelper.PurePath.matchonly gained recursive**in Python 3.13, soapps/**/*.tswas broken on 3.10–3.12 despite docs promising otherwise.sys.version_infoagainst 3.10 instead of printing[OK]unconditionally.P1 — UX
~/.claude/plugins/cache/*/bully/*/{skills,agents}/...— plugin-installed users no longer get five bogus[FAIL]lines.bullyshell wrapper at repo root so plugin users can alias/symlink withoutpip install.bully-initStep 6 now runs trust → doctor → telemetry dir +.gitignore(gated on Step 3 answer) → per-rule smoke tests before handing off. Would have caught the parser bug above at author time.P2
bully validate --execute-dry-runruns each script rule against/dev/nullto surface shell/regex syntax errors at config time.docs/rule-authoring.mddocuments YAML escapes,**compatibility,--explain, and--execute-dry-run.Test plan
./bully doctorunder system Python 3.9 →[FAIL] Python 3.9 < 3.10 -- upgrade required./bully doctorunder venv Python 3.12 → all[OK]including plugin-path skill detection ((plugin install)annotation)./bully validate --execute-dry-runon repo's own.bully.yml→ 6 script rules clean, ast/semantic rules correctly skippedexamples/rules/react-ts.ymlconsole.logpattern now parses toconsole\.log\(instead ofconsole\\.log\\(and grep runs cleanlypip install) — confirmbully doctorpasses without legacy-path files present