repo-maintenance
Use when triaging or maintaining the eslint-custom-rules repository.
SKILL.md.claude/skills/repo-maintenance/SKILL.md
GitHubESLint Custom Rules Repository Maintenance Guide
The "Staying on Top" Ethos
Goal: 0 Outstanding Issues
If we can get to the point where there are 0 ESLint Issues in the codebase, it will become impossible for new ESLint bugs & requests to accidentally be ignored in the noise. Every new ESLint bug and request will be immediately noticed and quickly addressed.
As a result, our AI agents do a very good job of writing extensible, bug-free, performant code. We want to get to the point where every ESLint Issue that is opened is addressed right away.
Workflow Overview
This repo is operated by an autonomous maintainer (/maintainer, backed by the
deterministic toolkit scripts/maintainer.ts) — not by humans triaging labels. The
old label-driven GitHub-Action agents (research, implement, fix, bot-review) and their
trigger labels (claude-research, research-complete, claude-implement,
claude-fix, claude-address-bot-review) have been removed. The maintainer acts
on all open issues; the bug / rule-request labels only choose which subagent
handles them.
1. The maintainer loop
Run /maintainer (see .claude/commands/maintainer.md). It:
- Drains the open-issue queue bugs-before-features, oldest-first (
scripts/maintainer.ts next). - Per issue, spawns the routing subagent —
bug→fix-bug,rule-request→implement-rule(chooseAgent). Labels only choose the subagent; there is no research prerequisite and no trigger labels. - Validates each change through the repo stop-hook gate (
scripts/maintainer.ts validate: build + changed-file lint + related tests) and self-merges todevelopwith a deterministic, scope-correct conventional commit (scripts/maintainer.ts scope→fix(<rule>)for an edited rule /feat(<rule>)for a new one). - On an empty queue, promotes
develop → main(scripts/maintainer.ts release), firing semantic-release (npm publish +release-manifest.json) and notifying agora.
Triage is therefore minimal: just ensure each issue carries bug or rule-request so it routes to the right subagent. An unlabeled issue defaults to fix-bug.
2. Enforcing Implementation Standards (Stop Hooks)
The repo's own Stop Hook gates every change before it can merge — the maintainer leans on it rather than reinventing validation:
File Structure Verification (rule implementations): the agent must have created/updated ALL of:
src/index.ts(import,rulesobject, recommended config)src/rules/<rule-name>.tssrc/tests/<rule-name>.test.tsdocs/rules/<rule-name>.mdREADME.mdIf a file is missing, the Stop Hook blocks completion and forces a fix.
Quality Checks: build (
npm run build) must succeed, ESLint must pass on the changed files (scoped, not whole-repo — avoids tripping on unrelated pre-existing debt), and the tests related to the change (jest --findRelatedTests, through the same buildernpm run test:relateduses) must pass. The full suite is the CI backstop and has no local spelling; see 2b. For rule implementations the hook also prompts the agent to "Expand Tests" toward 20+ cases covering false positives/negatives.
2b. Sharing the machine with other repos (the exec-governor)
Several autonomous loops share one box, and every one of them runs jest as its gate. Jest sizes its worker fleet from the memory it can see, so two gates that start together each reserve against a machine the other has already spent and both get OOM-killed. Sampling free memory cannot prevent this — the fatal allocation spikes are shorter than any sampling interval — but a reservation can.
Set BLUMINT_GOVERNOR_CLI to the absolute path of agora's scripts/exec-governor/cli.ts. The jest steps in scripts/maintainer.ts (the validate gate) and scripts/claude-hooks/agent-check.ts (the Stop Hook) then run under governor run --profile=jest, queueing behind any peer repo's governed run instead of racing it. jest.config.js reads the resulting BLUMINT_MAX_WORKERS grant, so a governed run sizes its fleet to what it was granted rather than to installed memory.
Both sides must resolve the same pool: leave BLUMINT_GOVERNOR_STATE_DIR unset on both (it defaults to ~/.cache/blumint/exec-governor), or set it to the same path on both. A repo pointed elsewhere governs a private pool and gains nothing.
Leaving BLUMINT_GOVERNOR_CLI unset — CI, a laptop, any checkout with no agora clone beside it — runs the bare jest command, exactly as before. A configured path that has moved degrades the same way rather than failing the gate, since an unreachable governor is a fact about the machine, not a defect in the change under test. Note that a governed run buffers its child's output and replays it on exit rather than streaming live; that is the price of the reservation, and an ungoverned gate streams only up to the SIGKILL that discards the whole run. governor status shows who currently holds leases.
TSX_TSCONFIG_PATH is derived and set for you, and is not yours to touch. agora's CLI resolves functions/* through a tsconfig path alias, so started from this repo's directory it dies at module resolution — Cannot find module 'functions/src/util/assertSafe' — and the client then re-runs the tests bare. Every governed invocation therefore sets that variable to the tsconfig beside the configured CLI (<cli dir>/../../tsconfig.json), derived from BLUMINT_GOVERNOR_CLI so no machine-specific string lives in this repo. Setting it yourself points the CLI at the wrong project and reopens the silent ungoverned lane.
npm run test:related is the canonical scoped run. It governs a --findRelatedTests run over the files given, or over the branch's changed TypeScript when none are, and appends a pinned set of cheap registry guards when src/index.ts is among them. Pass files or jest flags after --, attaching a flag's value (--testTimeout=30000): the script reads a dash-prefixed token as a flag and everything else as a path, so a detached value would reach jest as a relatedness operand and run zero tests at exit 0.
The whole suite has no local spelling. npm test, npm run test, npm run test:ci and npm t are denied by the PreToolUse Bash guard, which publishes npm run test:related in their place. npm's own options are walked at both positions they sit, so quieting the run changes nothing: npm --silent test is the same denial. CI's test-report.yml, which runs npm run test:ci, is the only surface that runs all 355 suites. Path-scoped runs (npx jest src/tests/x.test.ts) are allowed and are how a suite CI named is reproduced locally.
Touching a governor variable around a jest run is denied, and is never the remedy. The family is BLUMINT_GOVERNOR_*, BLUMINT_MAX_WORKERS, BLUMINT_WORKER_BUDGET_MB, TSX_TSCONFIG_PATH and CI, in every spelling — a prefix assignment, env -u/--unset (attached or detached), a preceding unset or export. Stripping one buys an ungoverned run that sizes its fleet from installed memory rather than from a grant, which is how thirteen jest workers fill a 31 GB box shared with two other loops.
A governed run that reports FAIL after twenty minutes has hit the governor's child ceiling, not a test failure. The suites are green and the child was killed; agora's CHILD_TIMEOUT_MS has no per-profile override. Recover by splitting the operands — npm run test:related -- <subset> in batches that each fit — never by removing the governor from the command.
3. Driving a PR to clean
To drive any open PR to review-clean + CI-green — addressing CodeRabbit/human review comments and fixing failing checks autonomously, committing + pushing each cycle — run npm run pr-autopilot -- --pr=<n> (see .github/scripts/pr-autopilot.ts). This replaces the old claude-address-bot-review label trigger. A human can run it the same way.
Troubleshooting
Fixing Build/Test Failures
For a PR branch whose CI is red:
git checkout <pr-branch-name>- Run the
fix-build-testagent (it reads the failing run's suite list, then loops onnpm run build,npm run test:relatedand annpx jest <path>per suite CI named), or runnpm run pr-autopilot -- --pr=<n>which folds this in. - Commit & push.
Addressing Bot Reviews
To address bot reviews (CodeRabbit, Cursor Bugbot) on a PR, run npm run pr-autopilot -- --pr=<n> — it drains unresolved review comments and pushes fixes each cycle.
Note: If a subagent stops abruptly, resume it by sending: "It looks like you were interrupted. Please continue."