| name | fix-department |
| description | Drive a department or gem all the way to 100% conformance using per-cop validation and generated corpus reports, never manual README/docs edits. |
| allowed-tools | Bash(*), Read, Write, Edit, Grep, Glob |
Fix Department (Codex)
This skill targets one department or one gem (for example Naming,
rubocop-performance, rubocop-rspec, or rubocop-rails) and drives it all
the way to 100% conformance.
docs/corpus.md is an autogenerated corpus report. The conformance/corpus
section in README.md is also autogenerated. Never edit those generated
sections directly. Other non-generated README.md content may still be edited
normally when appropriate. Treat the generated scorecard rows as read-only and
refresh them only through the CI corpus-oracle workflow/PR. Local dry-run
checks are fine, but never run a local generator/script that writes README.md
or docs/corpus.md.
Final stop condition:
- The target shows 100.0% / 0 FP / 0 FN in the generated corpus reports
(
README.md, docs/corpus.md, and related artifacts), never via manual edits.
- Linux CI/corpus oracle agrees with the local result.
Per-cop
check_cop.py --rerun results are intermediate count-based gates only.
They are necessary, but they are NOT sufficient to end /fix-department.
When the corpus oracle has concrete FP/FN examples for a cop, use
check_cop.py --rerun to verify before treating that cop as done locally.
Run fix work from a dedicated git worktree by default.
Persistence
Default behavior for this skill is to keep iterating until the target is
actually done or truly blocked. Do not stop just because a batch was committed,
landed onto main, or pushed.
$fix-department --loop makes this explicit:
- Keep looping back to Phase 1 after each accepted cop fix, integration step,
or checkpoint.
- Do not ask non-blocking clarifying questions. Make the reasonable default
assumption and continue.
- Only stop for:
- target complete (
README.md / docs/corpus.md CI-owned scorecard at 100%)
- a real blocker or risky ambiguity that could damage unrelated work
- an explicit user interrupt, redirect, or wrap-up request
- If the user asks for an intermediate action such as commit / land / push,
treat that as a sub-step inside the loop, then resume the fix workflow.
Turn-Boundary Contract
Codex skill activation is turn-scoped, but /fix-department --loop should be
treated as a standing task across the thread.
- If thread history shows an active
/fix-department --loop run and the user
asks for status, commit, land, cherry-pick, rebase, push, or verification,
treat that request as a sub-step inside the same loop.
- After satisfying that sub-step, resume the interrupted phase or Phase 1 in
the same turn unless the user explicitly paused, stopped, or redirected the
run.
- Do not treat a successful commit, cherry-pick, rebase, or report as the end
of the loop by itself.
- If you truly cannot resume in the same turn, say that the loop is still
active and tell the user to run
$fix-continue --loop rather than silently
ending the workflow.
Workflow
Phase 0: Assess
-
Start with the scoreboard:
python3 .agents/skills/fix-department/scripts/gem_progress.py --summary
The summary treats cops covered by the synthetic benchmark as covered, but
synthetic-only divergence is called out in the gem deep-dive rather than the
summary row alone.
-
Determine the target:
- If the user specified a core
rubocop department (for example Naming,
Layout, Lint, Metrics, Style), use the owning gem only for triage,
but treat the department row in README.md / docs/corpus.md as the
read-only scorecard.
- If the user specified a plugin gem (
rubocop-performance,
rubocop-rspec, rubocop-rails, etc.), the gem is both the triage scope
and the final scorecard.
-
If no gem/department is specified, pick one with:
- Few diverging cops
- No uncovered cops (preferred) -
Untest means no corpus or synthetic coverage
- High adoption value
-
Run deep-dive for the selected gem:
python3 .agents/skills/fix-department/scripts/gem_progress.py --gem <gem-name>
Also inspect the current checked-in scorecard row for the target in
README.md and docs/corpus.md. Never patch the generated conformance
section by hand. That row, not the per-cop rerun output, is the lagging
scorecard that must eventually be refreshed to 100% by the report
CI corpus-oracle workflow/PR.
-
Bootstrap the environment before reducers/tests/check-cop:
git submodule update --init --recursive
Phase 1: Plan Batch
Select up to 4 cops for the next batch:
- FP-only cops first (fastest safety wins)
- Then mixed FP/FN cops with highest FP
- FN-only cops last
- Skip complex Layout alignment cops until necessary
Establish ground truth first — before reading cop source or running reducers,
run these commands in parallel for each selected cop:
git log --oneline -5 -- src/cop/<dept>/<cop_name>.rs
python3 scripts/check_cop.py Department/CopName --verbose
Compare the latest cop commit date against the corpus oracle run date (shown in
check_cop.py output). If the oracle ran AFTER the latest fix, its FP/FN
numbers are the real remaining gap — not stale data from before the fix. This
tells you exactly what still needs fixing before you spend time reading source
or running reducers. Do NOT read the full cop source (which may have hundreds
of lines of investigation comments) until you know the current numbers and
which repos are diverging.
Investigate each selected cop:
python3 scripts/check_cop.py Department/CopName --examples --fp-only --limit 10
python3 scripts/check_cop.py Department/CopName --examples --fn-only --limit 10
check_cop.py prefers embedded snippets from corpus-results.json. If that
is insufficient, fetch full source files directly from GitHub using repo info
from bench/corpus/manifest.jsonl:
gh api repos/OWNER/REPO/contents/PATH?ref=SHA --jq '.content' | base64 -d
Synthetic-only cops (zero corpus activity): If check_cop.py shows no results, the cop
only has data in the synthetic corpus. Investigate using:
python3 -c "import json; d=json.loads(open('bench/synthetic/synthetic-results.json').read()); [print(c) for c in d['by_cop'] if c['cop']=='Department/CopName']"
The synthetic corpus files at bench/synthetic/project/ are handcrafted trigger patterns - read
the relevant source file to understand the expected behavior. Run python3 bench/synthetic/run_synthetic.py --verbose
to re-verify after fixing.
Reduce up to 3 examples per cop (corpus cops only - not applicable to synthetic-only cops):
python3 scripts/reduce_mismatch.py Department/CopName repo_id filepath:line
python3 scripts/reduce_mismatch.py Department/CopName repo_id filepath:line --type fn
Read reduced repros from /tmp/nitrocop-reduce/ and capture root-cause hypotheses.
Phase 2: Fix Loop (Per Cop, TDD)
-
Read:
src/cop/<dept>/<cop_name>.rs
vendor/rubocop*/spec/rubocop/cop/<dept>/<cop_name>_spec.rb
- Existing investigation comments in the cop source ("Known false positives", "reverted")
-
Add failing tests first:
-
Implement the fix in src/cop/<dept>/<cop_name>.rs.
-
Re-run targeted tests and ensure they pass.
Important: Fixture tests bypass config (Include patterns, Enabled status).
A passing test does NOT guarantee the binary fires on real files. If fixing
an FN, also verify the binary detects the pattern — see AGENTS.md Key
Constraints for Prism block body shape gotchas (BeginNode vs StatementsNode,
itblock/numblock).
-
Verify with the right acceptance gate for cops changed in this loop:
python3 scripts/check_cop.py Department/CopName --verbose --rerun
python3 bench/synthetic/run_synthetic.py --verbose
Corpus validation is the intermediate acceptance gate for corpus-backed cops.
For synthetic-only cops, rerun the synthetic benchmark and inspect that cop's
entry in bench/synthetic/synthetic-results.json; check_cop.py has no
signal there. Unit tests passing is necessary but not sufficient.
Use --rerun only for cops whose behavior may have changed (cop source or
related parsing/config logic). For untouched corpus cops, prefer artifact
mode (python3 scripts/check_cop.py Department/CopName --verbose) when the
latest corpus oracle run is current.
Do not stop here. Exact-match per-cop reruns can still leave the target
department/gem below 100% once the corpus reports are regenerated.
-
Handle regressions:
-
Document ALL investigation outcomes as /// comments on the cop's struct in its
source file. This is mandatory for EVERY cop in the batch — including:
- Cops that were fixed but have remaining FP/FN (document what was fixed and why
the remaining gaps exist)
- Cops that were investigated but need no code fix (e.g., FPs caused by encoding
differences, file-drop noise, config artifacts)
- Cops that were deferred (document why and what a fix would need)
- Cops with FN from
standard:disable handling or other config edge cases (these are still bugs to fix, not acceptable differences)
This prevents future investigators from repeating the same analysis. Do not
consider a cop "done" until it has an investigation comment, even if the fix itself
is already committed. Use:
Phase 3: Batch Verification
After all cops in the batch are fixed:
cargo fmt
cargo clippy --release -- -D warnings
cargo test --release
python3 scripts/corpus_smoke_test.py --binary target/release/nitrocop
Run the corpus smoke test once per batch, not after every cop. It is the cheap
systemic guard for file discovery, config/plugin loading, directive handling,
and other cross-cop regressions that per-cop check_cop.py reruns will not catch.
It does not require vendor/corpus/.
Re-check each fixed cop with the right acceptance gate:
python3 scripts/check_cop.py Department/CopName --verbose --rerun
python3 bench/synthetic/run_synthetic.py --verbose
Use check_cop.py for corpus-backed cops and run_synthetic.py for synthetic-only cops.
Refresh department/gem status:
python3 .agents/skills/fix-department/scripts/gem_progress.py --gem <gem-name>
If the user explicitly asks for a corpus-report refresh, use the real
generation path rather than editing files by hand:
cargo run --release --bin bench_nitrocop -- conform
bench_nitrocop -- conform refreshes bench/conform.json and bench/results.md
only. It does NOT update README.md or docs/corpus.md.
When the generated conformance section in README.md or docs/corpus.md needs
to move, do not run local generators against those files. README.md and
docs/corpus.md are CI-owned artifacts and may only be updated by the CI
corpus-oracle workflow/PR. Local dry-run comparisons are allowed only if they
do not write those files. Never patch that generated content directly.
/fix-department is not done until the generated reports show the target at
100.0% / 0 FP / 0 FN.
After a CI corpus-report refresh, inspect the target row in README.md and
docs/corpus.md:
- If the row is still below 100%, loop back to Phase 1 even if the modified cops
passed
check_cop.py --rerun.
- If the row is 100% locally but Linux CI/corpus oracle is not yet green or
disagrees, treat that as a parity bug and keep investigating. Do not declare
the department/gem complete yet.
- If local and Linux CI both show 100%, the target is done.
If diverging cops remain, loop back to Phase 1.
Phase 4: Report
Report:
- Department/gem targeted
- Cops fixed with FP/FN deltas
- Cops deferred/reverted and why
- Verification status:
local
fmt / clippy / test
per-cop check-cop / run_synthetic
corpus-report generation status
README.md / docs/corpus.md target row after CI refresh
Linux CI parity status (confirmed / pending / diverged)
Phase 5: Integrate Back to Main (Default)
Do not leave retained progress only in a worktree branch.
-
Commit all progress worth keeping in the worktree:
- Accepted cop fixes: one commit per cop (preferred).
- Useful investigation artifacts retained in repo (for example, reverted-attempt notes): separate commit.
-
Integrate those commit(s) into main immediately (unless the user explicitly says not to).
If working in a worktree, cherry-pick from the worktree branch:
git checkout main
git cherry-pick <sha1> [<sha2> ...]
If already on main (worktree not used), commits are already there — just verify.
-
Verify integration on main:
git log --oneline -n 10
git status --short --branch
-
Report exactly what was integrated (commit SHA(s) and short subjects).
-
If there is truly no repo-retained progress, explicitly report that no commit was made.
-
In --loop mode, after integration/reporting, return to Phase 1 unless the
target is done or the user explicitly stopped the run.
This includes turns where the user asked only for an intermediate git action
such as "commit to main", "rebase", or "push".
Notes
- Prefer a dedicated git worktree for code-editing runs of this skill, but worktree isolation
may silently fail. Always verify your working directory with
git rev-parse --show-toplevel.
If not in a worktree, continue working on main — do not block on this.
- New worktree bootstrap (run before reducers/tests/check-cop):
- Initialize submodules:
git submodule update --init --recursive
- Ensure
bench/corpus/vendor/bundle/ exists for the active Ruby version before any corpus-backed validation or smoke runs.
- To inspect corpus source files, fetch them from GitHub using
bench/corpus/manifest.jsonl:
gh api repos/OWNER/REPO/contents/PATH?ref=SHA --jq '.content' | base64 -d
- Parallel-agent activity is common; expect unrelated local changes in the working tree.
- Do not revert or include unrelated files in your commit; stage only files for the cop(s) you are fixing.
- Treat unrelated modified files as off-limits: do not edit them unless the user explicitly asks.
- Do not pause or block on unrelated working-tree changes; continue your task and leave those files untouched.
- Commit each cop fix separately for safe cherry-picks, then integrate into
main before ending the run.
- Never use
git stash or git stash pop.
- Do not run nitrocop or rubocop directly — not on corpus repos, not on ad-hoc files in
/tmp/, not anywhere outside the test fixtures. Running nitrocop on arbitrary paths fails
("No lockfile found") and wastes tokens. The ONLY ways to verify cop behavior are:
- Unit tests: add patterns to
offense.rb / no_offense.rb fixtures and run
cargo test --lib -- <cop_name_snake>.
- Corpus validation:
check_cop.py --rerun for aggregate count regression on
modified cops; check_cop.py --verbose (artifact mode) for untouched cops.
- Synthetic corpus:
python3 bench/synthetic/run_synthetic.py --verbose for synthetic-only cops.
Never create test Ruby files outside the fixture directories.
docs/corpus.md is autogenerated, and the conformance/corpus section in
README.md is autogenerated. Do not update either file locally, even via
generator scripts; only the CI corpus-oracle workflow/PR may change them.
cargo run --release --bin bench_nitrocop -- conform updates
bench/conform.json and bench/results.md; it does not regenerate
the generated conformance section in README.md or docs/corpus.md.
check_cop.py is a count-only cop-level gate, not the completion gate. Never declare
/fix-department done while generated README.md / docs/corpus.md still
show the target below 100%, or while Linux CI parity is still unconfirmed.
- Use local corpus files under
vendor/corpus/ when available, but fetch them selectively rather than assuming a full local corpus checkout.
Arguments
$fix-department — show scoreboard and choose a gem
$fix-department Naming — complete the Naming department and keep going until its generated corpus-report rows hit 100%
$fix-department Layout --loop — persistent run: keep fixing, integrating, and resuming until the department is actually done or truly blocked
$fix-department rubocop-performance — focus that gem
$fix-department rubocop-rspec — focus that gem
$fix-department rubocop-rails — focus that gem
$fix-department --input /path/to/corpus-results.json — use local corpus data