chore(lint): vendor anti-slop oxlint plugin and add batched cleanup pipeline

Vendor the anti-slop Oxlint plugin at tools/oxlint/anti-slop and register it
in oxlint.config.ts, with Oxlint's own rule categories disabled so ESLint
stays the general-purpose linter.

Add scripts/anti-slop.mjs (bun run deslop) mirroring the React Doctor batch
interface: next-batch, check-batch, active, release, top, file. Batch handoff
directories now double as file claims shared across clones via
~/.openchamber/maintenance-claims, so concurrent maintenance batches from
either pipeline never select the same file.

Harden both scheduled maintenance flows: stop on a dirty worktree, stop on
NO BATCH AVAILABLE, validate per package instead of workspace-wide, and pin
react-doctor to 0.9.12. The anti-slop task command documents concrete
good and bad fixes and forbids laundering types to satisfy a rule.
This commit is contained in:
Bohdan Triapitsyn
2026-08-16 15:55:08 +03:00
parent e3094ee676
commit 51aef5e316
30 changed files with 3512 additions and 33 deletions
+83 -7
View File
@@ -4,8 +4,24 @@ import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
claimedFilePaths,
printClaims,
readActiveClaims,
releaseRun,
resolveRunsDir,
runDirPath,
} from "./lib/batch-claims.mjs";
const PROJECT_NAME = "openchamber-monorepo";
const RUNS_DIR = join(process.cwd(), ".tmp", "react-doctor", "runs");
// Pinned so unattended batch runs cannot change diagnostics or output shape
// without an explicit update here.
const REACT_DOCTOR_VERSION = "0.9.12";
const PIPELINE = "rd";
// Resolved before command dispatch so every command shares one claims location.
const { runsDir: RUNS_DIR, shared: SHARED_CLAIMS } = resolveRunsDir(parseArgs(process.argv.slice(2))["claims-dir"]);
const DEFAULT_MAX_ACTIVE = 3;
const DEFAULT_CLAIM_TTL_DAYS = 3;
const PRIORITY_RULES = new Map([
["effect-needs-cleanup", 100],
@@ -88,14 +104,25 @@ function usage(exitCode = 0) {
const out = exitCode === 0 ? console.log : console.error;
out(`Usage:
bun run doctor -- next-batch [--min-issues 75] [--max-issues 120] [--max-files 4]
[--max-active ${DEFAULT_MAX_ACTIVE}] [--claim-ttl ${DEFAULT_CLAIM_TTL_DAYS}]
bun run doctor -- check-batch --run <run-id>
bun run doctor -- active [--claim-ttl ${DEFAULT_CLAIM_TTL_DAYS}]
Every command accepts --claims-dir <path> to isolate a working copy.
bun run doctor -- release --run <run-id>
bun run doctor -- file <path>
bun run doctor -- top [--limit 10]
Files selected by an active batch are excluded from later batches, so concurrent
batches never touch the same file, including batches created by the anti-slop
pipeline. Claims are shared across clones by default. A batch stays active until
it is released.
Examples:
bun run doctor -- next-batch --min-issues 75 --max-issues 120
bun run doctor -- file packages/ui/src/components/chat/ChatInput.tsx
bun run doctor -- check-batch --run 2026-05-14T12-31-44`);
bun run doctor -- check-batch --run 2026-05-14T12-31-44Z
bun run doctor -- release --run 2026-05-14T12-31-44Z`);
process.exit(exitCode);
}
@@ -132,7 +159,7 @@ function runReactDoctor() {
const output = execFileSync(
"npx",
[
"react-doctor@latest",
`react-doctor@${REACT_DOCTOR_VERSION}`,
"--project",
PROJECT_NAME,
"--json",
@@ -296,7 +323,7 @@ function selectBatch(entries, minIssues, maxIssues, maxFiles) {
}
function writeRun(runId, payload) {
const dir = join(RUNS_DIR, runId);
const dir = runDirPath(RUNS_DIR, PIPELINE, runId);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "baseline.json"), `${JSON.stringify(payload.report, null, 2)}\n`);
writeFileSync(join(dir, "batch.json"), `${JSON.stringify(payload.batch, null, 2)}\n`);
@@ -304,7 +331,7 @@ function writeRun(runId, payload) {
}
function readRun(runId) {
const dir = join(RUNS_DIR, runId);
const dir = runDirPath(RUNS_DIR, PIPELINE, runId);
const baselinePath = join(dir, "baseline.json");
const batchPath = join(dir, "batch.json");
if (!existsSync(baselinePath) || !existsSync(batchPath)) {
@@ -336,10 +363,36 @@ function commandNextBatch(args) {
const maxIssues = asPositiveInt(args["max-issues"], 120, "max-issues");
const maxFiles = asPositiveInt(args["max-files"], 4, "max-files");
if (minIssues > maxIssues) throw new Error("--min-issues cannot be greater than --max-issues.");
const maxActive = asPositiveInt(args["max-active"], DEFAULT_MAX_ACTIVE, "max-active");
const claimTtlDays = asPositiveInt(args["claim-ttl"], DEFAULT_CLAIM_TTL_DAYS, "claim-ttl");
const claims = readActiveClaims(RUNS_DIR, claimTtlDays);
if (claims.length >= maxActive) {
console.log("React Doctor Next Batch");
console.log("");
console.log("NO BATCH AVAILABLE");
console.log(`Reason: ${claims.length} active batches already exist and the limit is ${maxActive}.`);
console.log("Stop here. Do not create a branch or a pull request.");
console.log("");
printClaims(claims, PIPELINE);
return;
}
const report = runReactDoctor();
const diagnostics = allDiagnostics(report);
const claimedPaths = claimedFilePaths(claims);
const diagnostics = allDiagnostics(report).filter((diagnostic) => !claimedPaths.has(diagnostic.filePath));
const entries = sortedFileEntries(diagnostics);
if (entries.length === 0) {
console.log("React Doctor Next Batch");
console.log("");
console.log("NO BATCH AVAILABLE");
console.log("Reason: no unclaimed diagnostics remain.");
console.log("Stop here. Do not create a branch or a pull request.");
console.log("");
printClaims(claims, PIPELINE);
return;
}
const selection = selectBatch(entries, minIssues, maxIssues, maxFiles);
const runId = createRunId();
const selectedFiles = selection.selected.map(([filePath, fileDiagnostics]) => ({
@@ -348,7 +401,7 @@ function commandNextBatch(args) {
rules: summarizeRules(fileDiagnostics),
}));
const metadata = createBatchMetadata(runId, selectedFiles);
const batch = { runId, ...metadata, minIssues, maxIssues, maxFiles, selectedFiles, oversized: selection.oversized, belowTarget: selection.belowTarget, reason: selection.reason };
const batch = { runId, ...metadata, minIssues, maxIssues, maxFiles, maxActive, selectedFiles, oversized: selection.oversized, belowTarget: selection.belowTarget, reason: selection.reason };
const runDir = writeRun(runId, { report, batch });
console.log("React Doctor Next Batch");
@@ -363,6 +416,9 @@ function commandNextBatch(args) {
printReportHeader(report);
console.log("");
console.log(`Batch window: ${minIssues}-${maxIssues} diagnostics`);
console.log(`Active batches before this one: ${claims.length} of ${maxActive}`);
console.log(`Claims directory: ${RUNS_DIR} (${SHARED_CLAIMS ? "shared default" : "override"})`);
console.log(`Files excluded as claimed by active batches: ${claimedPaths.size}`);
console.log(`Selection mode: complete files only`);
console.log(`Batch total: ${selectedFiles.reduce((sum, file) => sum + file.diagnosticCount, 0)} diagnostics`);
console.log(`Oversized: ${selection.oversized ? "yes" : "no"}`);
@@ -387,6 +443,20 @@ function commandNextBatch(args) {
});
}
function commandActive(args) {
const claimTtlDays = asPositiveInt(args["claim-ttl"], DEFAULT_CLAIM_TTL_DAYS, "claim-ttl");
console.log(`Claims directory: ${RUNS_DIR} (${SHARED_CLAIMS ? "shared default" : "override"})`);
printClaims(readActiveClaims(RUNS_DIR, claimTtlDays), PIPELINE);
}
function commandRelease(args) {
const runId = args.run;
if (!runId || runId === true) throw new Error("Missing --run <run-id>.");
const dir = releaseRun(RUNS_DIR, PIPELINE, runId);
console.log(`Released batch ${runId}`);
console.log(`Removed ${dir}`);
}
function commandTop(args) {
const limit = asPositiveInt(args.limit, 10, "limit");
const report = runReactDoctor();
@@ -479,6 +549,12 @@ async function main() {
case "check-batch":
commandCheckBatch(args);
break;
case "active":
commandActive(args);
break;
case "release":
commandRelease(args);
break;
default:
throw new Error(`Unknown command: ${command}`);
}