diff --git a/README.md b/README.md index c9d9efc6..d2fed2ef 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,7 @@ Read `.action`, do exactly that, call it again. | `done` | converged | | `blocked` | needs a human; `.reason` says why | -Every rule this used to spell out is now a value crq computes. Drain-before-review is `fix` coming +Every rule this used to spell out is now a value crq computes. Fix-before-review is `fix` coming before everything else. Hold-the-head is the `hold` action, including the exception where a CodeRabbit rate-limit degrade releases the head because the queued review will fire on whatever head exists when the window opens. The wait is `.recheck_after`, derived from the account-quota window, @@ -397,12 +397,12 @@ crq feedback # current normalized findings as JSON, WITHOUT trigger crq threads # every unresolved thread, outdated included crq resolve [...] # resolve addressed review threads crq decline [...] --reason "" [--resolve] # record why a finding is declined -crq drain install # ⭐ unattended: watch every PR and fix what needs fixing -crq watch # what the drain runs: drive open PRs through crq next, one JSON +crq autofix install # ⭐ unattended: watch every PR and fix what needs fixing +crq watch # what autofix runs: drive open PRs through crq next, one JSON # line each. Fixing is ON by default (--no-dispatch observes only) -crq drain # which repositories crq may fix -crq drain off --reason "" # stop fixing there; watching and reviewing continue -crq drain on | crq drain default +crq autofix # which repositories crq may fix +crq autofix off --reason "" # stop fixing there; watching and reviewing continue +crq autofix on | crq autofix default crq hold --reason "" # persistently stop reviews for a PR crq unhold # resume reviews for a held PR crq hold # list held PRs diff --git a/cmd/crq/main.go b/cmd/crq/main.go index 72543950..2c4a4b25 100644 --- a/cmd/crq/main.go +++ b/cmd/crq/main.go @@ -64,20 +64,20 @@ func run(ctx context.Context, args []string) int { return 1 case "preflight": return preflight(ctx, args[1:]) - case "drain": + case "autofix": // A dry run is documented as a PREVIEW: it writes nothing and reads no // GitHub state. Deciding it here, before the authenticated client is // built, is what lets somebody inspect the setup before finishing it — // otherwise the one command for looking at the plan was itself another // thing to set up first. A real install still goes through the // authenticated path below. - if opts, perr := parseDrainArgs(args[1:]); perr == nil && opts.dryRun { + if opts, perr := parseAutofixArgs(args[1:]); perr == nil && opts.dryRun { cfg, cerr := crq.LoadConfig() if cerr != nil { fatal(cerr) return 1 } - plan, ierr := crq.DrainPlan(cfg, opts.agent, crq.SplitArgv(opts.agentArgs), opts.repos, true) + plan, ierr := crq.AutofixPlan(cfg, opts.agent, crq.SplitArgv(opts.agentArgs), opts.repos, true) if ierr != nil { fatal(ierr) return 1 @@ -276,17 +276,17 @@ func run(ctx context.Context, args []string) int { } printJSON(result) return 0 - case "drain": - switch sub := drainSubcommand(args[1:]); sub { + case "autofix": + switch sub := autofixSubcommand(args[1:]); sub { case "?": - fatal(fmt.Errorf("unknown drain subcommand %q (try: crq drain, crq drain on|off|default , crq drain install)", args[1])) + fatal(fmt.Errorf("unknown autofix subcommand %q (try: crq autofix, crq autofix on|off|default , crq autofix install)", args[1])) return 1 case "", "list": if err := cfg.RequireState(); err != nil { fatal(err) return 1 } - settings, derr := service.DrainSettings(ctx) + settings, derr := service.AutofixSettings(ctx) if derr != nil { fatal(derr) return 1 @@ -294,9 +294,9 @@ func run(ctx context.Context, args []string) int { printJSON(settings) return 0 case "on", "off", "default": - rest, reason, ok := parseDrainReason(args[2:]) + rest, reason, ok := parseAutofixReason(args[2:]) if !ok || len(rest) != 1 { - fatal(errors.New(`usage: crq drain on|off|default [--reason ""]`)) + fatal(errors.New(`usage: crq autofix on|off|default [--reason ""]`)) return 1 } if err := cfg.RequireState(); err != nil { @@ -304,7 +304,7 @@ func run(ctx context.Context, args []string) int { return 1 } if sub == "default" { - cleared, cerr := service.ClearDrainEnabled(ctx, rest[0]) + cleared, cerr := service.ClearAutofixEnabled(ctx, rest[0]) if cerr != nil { fatal(cerr) return 1 @@ -312,7 +312,7 @@ func run(ctx context.Context, args []string) int { printJSON(map[string]any{"repo": crq.NormalizeRepo(rest[0]), "cleared": cleared, "enabled": true, "default": true}) return 0 } - setting, serr := service.SetDrainEnabled(ctx, rest[0], sub == "on", reason) + setting, serr := service.SetAutofixEnabled(ctx, rest[0], sub == "on", reason) if serr != nil { fatal(serr) return 1 @@ -320,7 +320,7 @@ func run(ctx context.Context, args []string) int { printJSON(setting) return 0 } - opts, perr := parseDrainArgs(args[1:]) + opts, perr := parseAutofixArgs(args[1:]) if perr != nil { fatal(perr) return 1 @@ -329,7 +329,7 @@ func run(ctx context.Context, args []string) int { fatal(err) return 1 } - plan, ierr := service.InstallDrain(ctx, opts.agent, crq.SplitArgv(opts.agentArgs), opts.repos, opts.dryRun) + plan, ierr := service.InstallAutofix(ctx, opts.agent, crq.SplitArgv(opts.agentArgs), opts.repos, opts.dryRun) if ierr != nil { fatal(ierr) return 1 @@ -626,9 +626,10 @@ USAGE crq decline [...] --reason "" [--keep-open] reply on a thread to record why a finding is declined (resolves it; --keep-open leaves it open) - crq drain install [--agent ] [--dry-run] [...] - crq drain [on|off|default ] which repositories crq may fix (on by default) - install and start the unattended review drain + crq autofix install [--agent ] [--dry-run] [...] + install and start unattended autofix + crq autofix [on|off|default ] + which repositories crq may fix (on by default) crq watch [--no-dispatch] [--once] [...] [-- ] drive open PRs through crq next; --dispatch starts a session to fix the ones that need it @@ -731,7 +732,7 @@ more of the shared budget — run the daemon. Review round primitive for humans and agents. crq coordinates the review trigger, waits for real feedback on the current PR head, and emits one JSON report to stdout. -It returns unresolved findings before queueing a new round, so agents must drain +It returns unresolved findings before queueing a new round, so agents must clear current feedback before waiting for another review. Exit codes: @@ -797,17 +798,17 @@ replies contesting the decline, crq re-surfaces that reply as its own finding. Pass --keep-open to leave it unresolved anyway (an on-the-record disagreement you intend to keep working). Thread IDs come from .findings[].thread_id. `) - case "drain": - fmt.Print(`crq drain install [--agent ] [--dry-run] [...] -crq drain (which repositories crq may fix) -crq drain off [--reason ""] -crq drain on -crq drain default (back to the fleet default, which is on) - -Install and start the unattended review drain: crq watches every open PR and + case "autofix": + fmt.Print(`crq autofix install [--agent ] [--dry-run] [...] +crq autofix (which repositories crq may fix) +crq autofix off [--reason ""] +crq autofix on +crq autofix default (back to the fleet default, which is on) + +Install and start unattended autofix: crq watches every open PR and starts a fix session for the ones that need one. -Draining is ON for every repository in scope. That is the point of watching: a +Autofix is ON for every repository in scope. That is the point of watching: a pull request nobody fixes is a queue that reports work and does none. Turn it off where you do not want crq writing code — a release branch, a repository you are hand-tuning — and watching continues there regardless, so reviews still arrive @@ -841,7 +842,7 @@ line per PR per pass, and start a fix session for each PR whose action is "fix". Fixing is the default — watching a pull request nobody fixes is a queue that reports work and does none. Three things turn it off: --no-dispatch for one run, -"crq drain off " for one repository, and having no fix command configured +"crq autofix off " for one repository, and having no fix command configured at all, which observes and says so rather than refusing to start. A dispatched session runs in a worktree crq checked out at that head, with: @@ -969,7 +970,7 @@ stops blocking the round. crq resolve and crq decline both act on a thread. A review-body finding, a review-skipped notice or an outside-diff remark has none, so neither command can -touch it — and a finding that can never drain blocks every future round, leaving +touch it — and a finding that can never be cleared blocks every future round, leaving a PR whose current head no review was ever requested for. Finding IDs come from .findings[].id. They are content-derived, not GitHub node @@ -1776,18 +1777,18 @@ func configPath() string { return home + "/.config/crq/env" } -// drainArgs is `crq drain install`'s parsed command line. -type drainArgs struct { +// autofixArgs is `crq autofix install`'s parsed command line. +type autofixArgs struct { agent string agentArgs string dryRun bool repos []string } -// parseDrainArgs is shared by the pre-authentication dry-run path and the +// parseAutofixArgs is shared by the pre-authentication dry-run path and the // install itself, so the two cannot disagree about what was asked for. -func parseDrainArgs(args []string) (drainArgs, error) { - fs := flag.NewFlagSet("drain", flag.ContinueOnError) +func parseAutofixArgs(args []string) (autofixArgs, error) { + fs := flag.NewFlagSet("autofix", flag.ContinueOnError) fs.SetOutput(os.Stderr) agent := fs.String("agent", "", "fix agent to run: claude or codex (default: claude on PATH)") agentArgs := fs.String("agent-args", "", "extra flags for the agent, e.g. model and reasoning effort") @@ -1797,22 +1798,22 @@ func parseDrainArgs(args []string) (drainArgs, error) { sub, rest = rest[0], rest[1:] } if err := fs.Parse(rest); err != nil { - return drainArgs{}, err + return autofixArgs{}, err } if sub != "install" { - return drainArgs{}, errors.New("usage: crq drain install [--agent ] [--dry-run] [...]") + return autofixArgs{}, errors.New("usage: crq autofix install [--agent ] [--dry-run] [...]") } - return drainArgs{agent: *agent, agentArgs: *agentArgs, dryRun: *dryRun, repos: fs.Args()}, nil + return autofixArgs{agent: *agent, agentArgs: *agentArgs, dryRun: *dryRun, repos: fs.Args()}, nil } -// drainSubcommand names what `crq drain ...` was asked to do. An empty string +// autofixSubcommand names what `crq autofix ...` was asked to do. An empty string // means the bare command, which lists; "?" means something this command does not // have. // -// A typo must not list. `crq drain of owner/name` reading as the bare listing -// would report the repository as drained and leave it drained — an answer to a +// A typo must not list. `crq autofix of owner/name` reading as the bare listing +// would report the repository as fixable and leave it fixable — an answer to a // question nobody asked, in place of the instruction that was meant. -func drainSubcommand(args []string) string { +func autofixSubcommand(args []string) string { if len(args) == 0 { return "" } @@ -1823,10 +1824,10 @@ func drainSubcommand(args []string) string { return "?" } -// parseDrainReason splits `` from an optional --reason value. An +// parseAutofixReason splits `` from an optional --reason value. An // unrecognized flag is an error rather than a positional: a typo like --resaon // must fail loudly, not silently become the repository name. -func parseDrainReason(args []string) (rest []string, reason string, ok bool) { +func parseAutofixReason(args []string) (rest []string, reason string, ok bool) { for i := 0; i < len(args); i++ { switch arg := args[i]; { case arg == "--reason": diff --git a/examples/dispatch/README.md b/examples/dispatch/README.md index edc73b92..def6a195 100644 --- a/examples/dispatch/README.md +++ b/examples/dispatch/README.md @@ -1,13 +1,13 @@ -# Unattended review drain +# Unattended autofix `crq watch --dispatch` turns "this PR needs fixing" into a session that fixes it. ```bash -crq drain install # prompt + wrapper + service, started -crq drain install --dry-run # print what it would write first +crq autofix install # prompt + wrapper + service, started +crq autofix install --dry-run # print what it would write first # a different agent, and its own model/effort settings -crq drain install --agent codex --agent-args '-c model_reasoning_effort=high' +crq autofix install --agent codex --agent-args '-c model_reasoning_effort=high' ``` crq knows how to CALL claude and codex, and nothing about which model either @@ -19,6 +19,10 @@ agent on macOS), enables lingering so it survives a logout, and starts it. There are no files in this directory to copy: the prompt crq installs is embedded in the binary, so it cannot drift from the one documented here. +On a host that ran the pre-rename `crq drain install`, it also stops and +disables the leftover `crq-drain` service — two watchers scanning the same +fleet would race each other's dispatch claims. + ## Two rules the prompt earned the hard way **Sessions stay on a detached HEAD and push by ref.** crq's worktrees are backed @@ -45,6 +49,6 @@ somebody else actively holds stops a session now. - `$CRQ_WORKSPACE/logs///--