Help us improve
Share bugs, ideas, or general feedback.
From bully
Bootstraps a .bully.yml config by detecting tech stack from manifest files and wiring installed linters as passthrough rules. User-driven, step-by-step setup.
npx claudepluginhub dynamik-dev/bully --plugin bullyHow this skill is triggered — by the user, by Claude, or both
Slash command
/bully:bully-initThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate a baseline `.bully.yml` by detecting the stack, wiring installed linters as passthroughs, and routing project-specific rules to the right enforcement mechanism.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Breaks plans, specs, or PRDs into thin vertical-slice issues on the project issue tracker using tracer bullets. Useful for converting high-level work into grabbable implementation tickets.
Share bugs, ideas, or general feedback.
Generate a baseline .bully.yml by detecting the stack, wiring installed linters as passthroughs, and routing project-specific rules to the right enforcement mechanism.
Bully is the cop; native linters (ruff, biome, eslint, tsc, phpstan, rubocop, golangci-lint, clippy, …) are the lawmakers. The PostToolUse hook runs on every Edit/Write, so bully is always the entry point to enforcement. Where a rule definition lives is a separate question:
.bully.yml has a passthrough (engine: script, script: "<linter> <args> {file}") that invokes it on every edit..bully.yml.This skill is user-driven. Do not silently install tools or migrate rules. Every step below is a proposal the user accepts or declines.
Read manifest files in the project root and map them to rule packs:
| Manifest | Pack candidates |
|---|---|
composer.json | php, laravel (if laravel/framework), symfony (if symfony/framework-bundle) |
package.json | node, typescript (if tsconfig.json or devDependencies.typescript), react, vue, next, svelte |
pyproject.toml / requirements.txt | python, django, fastapi, flask |
Cargo.toml | rust |
go.mod | go |
Gemfile | ruby, rails (if gem 'rails') |
Present what was detected and wait for confirmation before continuing.
Detect which lint/format/typecheck tools the project already has on PATH or declared in its manifest: ruff, biome, eslint, prettier, tsc, phpstan, pint, rubocop, rubyfmt, golangci-lint, gofmt, clippy, ast-grep, pytest, etc.
For each one, ask:
I found
<linter>configured. Add a passthrough rule so bully runs it on every Edit/Write? The linter keeps owning its own rules -- bully just enforces "pass the linter" whenever you touch a matching file.
If yes, queue a rule like:
ruff-check:
description: "Code must pass ruff check."
engine: script
scope: ["*.py"]
severity: error
script: "ruff check --quiet {file}"
Keep lint / format / typecheck as separate passthrough rules -- failure modes and messages are distinct, and bully-review telemetry stays legible per tool.
If several installed tools cover overlapping concerns, pick the superset and skip the others (wiring all three causes conflicts). Default precedence:
| Stack | Prefer | Skip (superseded) |
|---|---|---|
| Python | ruff check + ruff format | black, isort, flake8, pycodestyle, pydocstyle |
| JS/TS | biome (if configured) OR eslint + prettier | do not wire both biome and eslint/prettier together |
| Ruby | rubocop | standardrb (choose one) |
| Go | golangci-lint | individual staticcheck / revive |
If the user actively uses a superseded tool (e.g. says "we still run isort in CI"), swap in their preferred tool instead. Call out what you're skipping and why before writing.
If the detected stack has no linter installed and one is conventional (ruff for Python, biome or eslint for JS/TS, golangci-lint for Go, rubocop for Ruby, phpstan for PHP, clippy for Rust), offer it as a choice -- do not push:
You don't have a linter installed for
<stack>. Most projects use<tool>. Want me to add it to<manifest>and wire up a passthrough? Or skip and we'll handle everything in bully directly.
Installing touches package.json / pyproject.toml / composer.json / CI, so this must be an explicit user opt-in. Never install silently. If the user declines, move on -- their .bully.yml can still cover everything via ast/script/semantic rules.
For each custom rule found (CLAUDE.md/AGENTS.md guidelines, Pest arch() tests, team docs, prose rules), route it using the same four-option decision tree as bully-author:
engine: ast rule. Requires ast-grep installed.For each migration, state the enforcement-guarantee line once: "Bully still runs on every Edit/Write -- we're just deciding where the rule definition lives." Then present the chosen routing and wait for confirmation before queueing.
Before writing, ask:
error or warning)?skip: list in .bully.yml -- the key is skip, not exclude or ignore.Telemetry is on by default. Step 6 always creates .bully/ and adds it to .gitignore -- that's what enables bully-review to work later. Telemetry is never a key inside .bully.yml. If the user explicitly says they don't want it, skip the mkdir in Step 6 and tell them bully-review will report "empty log" until they create .bully/ themselves.
Seed skip: with the defaults for the detected stack(s). Merge across stacks if multiple apply (e.g. a repo with package.json and pyproject.toml).
| Stack | Default skip globs |
|---|---|
| Python | .venv/**, venv/**, **/__pycache__/**, .pytest_cache/**, .ruff_cache/**, .mypy_cache/**, build/**, dist/**, *.egg-info/** |
| Node/JS/TS | node_modules/**, dist/**, build/**, .next/**, .nuxt/**, .output/**, coverage/**, *.min.js |
| PHP | vendor/**, storage/**, bootstrap/cache/**, public/build/** |
| Go | vendor/**, bin/** |
| Rust | target/** |
| Ruby | vendor/**, tmp/**, log/**, public/assets/** |
| Universal | .git/**, .idea/**, .vscode/** (only add if present) |
Present the merged list and ask the user to add/remove before writing.
Bully ships an examples/rules/ directory -- a catalog of common rules organized by tech (react-ts, nextjs, django, fastapi, go, rails, rust-cli). These are examples, not a blessed baseline. Do not auto-extends: them. Instead, for the detected stack, open the matching examples/rules/<stack>.yml, show the user the rule list with one-line summaries, and ask which ones to seed. Copy the selected rules inline into the generated .bully.yml -- the user owns them from then on.
If the user declines all of them, write an empty rules: block and let the bully-author skill add rules as they come up.
.bully.ymlWrite to the project root. The parser expects 2-space indentation for rule IDs under rules: and 4-space indentation for each rule's fields. Scope is an inline list.
Only these keys are valid at the top level of .bully.yml:
| Key | Purpose |
|---|---|
schema_version | Optional integer, reserved for future migrations. |
extends | List of shared rule-pack paths. |
rules | Map of rule-id → rule definition. |
skip | List of globs excluded from all rules. The key is skip -- not exclude, ignore, excludes, or ignores. |
execution | Map with max_workers (int). |
Do not invent other top-level keys. In particular, there is no telemetry: key -- telemetry is enabled by the presence of the .bully/ directory at the project root, which Step 6 creates.
schema_version: 1
skip:
- "vendor/**"
- "node_modules/**"
rules:
rule-id:
description: "What the rule enforces"
engine: script # script | ast | semantic
scope: ["*.ts", "*.tsx"]
severity: error # error | warning
script: "command {file}" # script engine only
For multi-line descriptions use a folded scalar (description: >). Quote all script values with double quotes. Use {file} as the target file placeholder. Formatters use severity: warning; correctness rules use severity: error.
Never write directly to .bully.yml on init. Draft first, parse-check the draft, and only rename on pass. This prevents polluting the user's project with an invalid config if the LLM hallucinates a key (e.g. the historical telemetry: / exclude: mistakes).
# 1. Write the draft to a scratch path (use the Write tool):
# /tmp/bully-init-draft.yml <-- full proposed .bully.yml contents
#
# 2. Parse-check the draft:
BULLY=$(command -v bully 2>/dev/null || ls -d ~/.claude/plugins/cache/*/bully/*/bully 2>/dev/null | sort -V | tail -1)
"$BULLY" --validate --config /tmp/bully-init-draft.yml
Exit code 0 + no [FAIL] output = draft is valid. Nonzero exit = draft is broken: read the [FAIL] line (e.g. unknown top-level key 'telemetry'), fix the draft in place, re-check. Do not overwrite .bully.yml until --validate exits clean.
Once the draft validates:
mv /tmp/bully-init-draft.yml .bully.yml
Bully ships a bin/bully wrapper that the plugin auto-adds to $PATH, so command -v bully should resolve on any 0.8.5+ install. Older caches won't have bin/bully; resolve with this one-liner (used above):
BULLY=$(command -v bully 2>/dev/null || ls -d ~/.claude/plugins/cache/*/bully/*/bin/bully 2>/dev/null | sort -V | tail -1)
sort -V | tail -1 picks the newest cached version. If $BULLY is empty (very old install, no bin/bully), fall back to PYTHONPATH=<plugin-path>/src python3 -m bully ....
Before handing off, bring the config into a runnable state:
Trust the config so script/ast rules can execute: bully trust (fallback: PYTHONPATH=<plugin-path>/src python3 -m bully --trust --config .bully.yml).
Run bully doctor and surface any [FAIL] lines. Known false positives for plugin installs -- note them but do not try to "fix" them:
[FAIL] no PostToolUse hook invoking hook.sh found in .claude/settings.json -- the plugin loads hooks/hooks.json dynamically via the Claude Code plugin system. .claude/settings.json is not where the hook lives for plugin installs, so this FAIL is expected and harmless.0.5.0 but doctor resolves skills from 0.3.0). Doctor picks the first match in the plugin cache; stale cache directories from prior versions can shadow the current one. Either tell the user to delete old cache dirs under ~/.claude/plugins/cache/bully-marketplace/bully/ or just note it.Any [FAIL] outside that list is real -- surface it.
Telemetry directory (always, unless the user explicitly opted out in Step 3): mkdir -p .bully/ and ensure .gitignore contains .bully/ (append it if missing). This is what enables bully-review to read the rule-health log.
Smoke-test script rules. For each rule with a concrete script:, pick the first in-scope file (e.g. git ls-files | grep -E '\.(ts|tsx)$' | head -1 against the rule's scope) and run bully lint <file> --rule <rule-id>. Report each verdict. If a rule that is meant to fire on a known pattern returns pass, flag it as a likely miscompile -- surface it now, not after 40 edits.
After the verification pass, print:
.bully.yml generated.
Stack: <detected>
Extends: <packs>
Migrated: <count> rules from <sources>
Overrides: <count>
Excluded globs: <list>
Trust: <granted|failed>
Doctor: <pass|N failures>
Smoke test: <N passed, N flagged>
Tell the user: "To add project-specific rules, use /bully-author. To audit rule health later, use /bully-review."
.bully.yml: Offer overwrite, merge (append new rules only), or abort.