Skip to content

feat(fix): add /fix — the lean single-issue lane (RFC 0022) - #798

Open
TheFJK wants to merge 5 commits into
mainfrom
feat/fix-lean-lane
Open

TheFJK wants to merge 5 commits into
mainfrom
feat/fix-lean-lane

Conversation

@TheFJK

@TheFJK TheFJK commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Adds /fix <issue#> — a lean single-issue lane, the speedup version of /turbox.

/turbox /fix
Issues per invocation up to 3 exactly 1 — refused before any claim is written
Design rungs security research → design-planner → plan-reviewer none
Implementation wave-parallel over a plan's Owns sets one agent, whole change
Review spec-compliance-reviewer + code-reviewer, per task one code-reviewer, whole diff
Fix ladder per-task, inside each wave fixRounds from the plan (default 1)
Delivery a delivery agent per issue the controller
Sequential agent rungs 8+ 2–3

A narrowing of standard mode, not a new transport: commands/fix.md passes
--auto-mode=1 --turbo --standard --fix to the same launcher, and
skills/fix-fleet/SKILL.md reuses lib/turbox-fleet.sh for worktree-add,
stage-commit, round-permitted and audit rather than forking them.

What it drops is design, not diligence. The change is still committed by
explicit path, still reviewed by an independent agent against the
phase1-reviewer-v1 contract, still validated through the canonical
uberdev_child_validate_phase1_review_result boundary, still run against the
full test suite, and still parked as a PR nobody merges.

The failure mode, answered structurally

An operator picks the lean lane because it is fast, the issue turns out to need
a decomposition, and a lane with no design rungs produces a plausible-looking
PR nobody planned.

The implementer's return vocabulary carries TOO_BIG — a first-class
answer meaning this issue wants a decomposition, run /turbox. It ends the
run with the worktree and the claim intact, and the controller is told never to
argue an agent out of it.

Circuit breakers

  • FX1 — a second issue number is refused before gh runs, and
    therefore before Step 4.5 can write a claim. A lane that discovered its
    second issue after claiming both would leave uberdev:active labels behind
    with nothing running.
  • FX2 — fixRounds, the lane's own ceiling (default 1, UBERDEV_FIX_FIX_ROUNDS),
    resolved and validated beside FX1. Deliberately not the turbox per-task
    cap, whose value stays with its SSOT and is not copied into this lane.
  • FX3 — retest_rounds, shared, because it bounds the same thing on both lanes.
  • FX4 — an unparseable implementer return, or a review result the validator
    rejects: one re-prompt, then stop or count the run UNREVIEWED.

A red suite, standing blockers, or an UNREVIEWED run each open the PR as a
draft, named in both the body and the report.

Pre-existing bugs fixed, both measured rather than inferred

/uberdev:uninstall-aliases removed nothing under Claude Code. It walked
its alias list with for SHORT in $SHORTS; under the Bash tool's /bin/zsh an
unquoted scalar does not word-split, so the loop ran once over the whole
string and every [ -e "$DEST_DIR/${SHORT}.md" ] missed. Extracted the fence
and ran it against a scratch HOME under both shells:

form bash zsh
for SHORT in $SHORTS (before) 16 of 16 removed 0 of 16 removed
here-doc reader (after) 16 of 16 16 of 16

Alias-table drift. /turbox was missing from the README alias table and
the UBERDEV_NO_AUTO_ALIAS row while the prose claimed fifteen forwarders, and
/turbox and /premerge were missing from tests/aliases.test.sh's A1 and A6
loops — so neither command's forwarder nor its allowed-tools byte-equality
was ever checked. Both loops now cover them.

Surfaces moved

All six alias surfaces. The five-surface recipe repeated across RFCs 0005,
0007, 0008, 0009 and 0010 omits skills/using-uberdev/references/configuration.md,
which docs-accuracy T6b provably enforces — following the RFC recipe lands a
red build. The correction is recorded in tests/aliases.test.sh's header.

Both chained vendor locks (vendor.json C-COVER + the RFC 0019 component
count), the run-tree policy's does_not_govern declaration, the
sdd-wave-contract §C5 cap-sweep ignore-list and its three tallies, CI wiring,
and the version bump to 0.57.0.

CLAUDE.md's bump-lane table gains a fourth row: /turbox and /fix open one
PR per issue rather than a stack, so there is no once-per-stack commit to carry
the bump — it rides whatever lands the PR. Without that row a reviewer reads a
/fix PR as an unbumped user-facing change.

Tests

tests/fix-lane.test.sh, 166 rows, wired into the ubuntu job and declared
Unix-only.

The FX-E* rows execute the launcher and the lib against a stubbed gh
and claude. The stub is not a nicety: the one row that deliberately passes
the arity gate reaches Step 4.5 and would otherwise write a live claim on
whatever repo gh repo view resolved, which nothing in the run releases.

Two deliberate choices about what those rows assert:

  • The refusal rows assert a gh call count of zero rather than the absence
    of an error string. That proves the guard rather than the message, and it
    cannot pass vacuously in CI, where an unstubbed run would die at gh repo view for want of auth and every negative would hold trivially.
  • The plan envelope is parsed, not grepped. Prose in the RFC claimed the
    envelope had 13 keys while the composer emitted 14; no grep over either file
    could have seen the disagreement.

Both the envelope key-count row and the audit-sink traversal guard were
mutation-checked — break the source, the row reds.

Verification

Green locally: fix-lane (166), aliases, docs-accuracy (309),
epipe-guard (93), turbox-fleet, turbox-fleet-runtime (146),
sdd-wave-contract (77), solve-run-tree-scope,
skill-renderer-awk-collision, skill-size, ci-wiring,
vendor-provenance (63).

A full re-run of the ubuntu list was still in flight when this PR was opened —
CI is the authority. The first full pass was green on all 134 fixtures except
the three reds now fixed here.

Not done

The post-merge release ritual: the v0.57.0 tag and the GitHub Release. Those
stay in the operator's foreground, after the landing commit is on main.

Closes #797

`/fix <issue#>` solves exactly one issue with one implementer, one
`code-reviewer` over the whole diff, at most one fix round, and a PR this
session pushes and parks. Two sequential agent rungs on the happy path,
three when the review comes back red — against `/turbox`'s eight or more.

A narrowing of standard mode rather than a new transport: `commands/fix.md`
passes `--auto-mode=1 --turbo --standard --fix` to the same launcher, and
`skills/fix-fleet/SKILL.md` reuses `lib/turbox-fleet.sh` for `worktree-add`,
`stage-commit`, `round-permitted` and `audit` instead of forking them.

What it drops is design — no security lens, no design-planner, no
plan-reviewer, no wave decomposition, no per-task spec-compliance. What it
keeps is diligence: the full launcher preflight and claim protocol, worktree
isolation, the explicit-path staging refusal, an independent reviewer
validated through the canonical `uberdev_child_validate_phase1_review_result`
boundary, the project's full test suite, and the park-the-PR rule.

The lane's own failure mode is answered structurally rather than hoped away:
the implementer's return vocabulary carries `TOO_BIG`, meaning "this issue
wants a decomposition — run /turbox". It ends the run with the worktree and
the claim intact, and the controller is told never to argue an agent out of
it. Without that escape, a lane with no design rungs would quietly produce a
plausible PR nobody planned.

Circuit breakers FX1-FX4. FX1 refuses a second issue number BEFORE gh runs
and therefore before Step 4.5 can write a claim — a lane that discovered its
second issue after claiming both would leave `uberdev:active` labels behind
with nothing running. FX2 is the lane's own `fixRounds` (default 1), resolved
beside FX1; it is deliberately not the turbox per-task ladder, whose value
stays with its SSOT and is not copied here. A red suite, standing blockers or
an UNREVIEWED run each open the PR as a draft.

Also fixed, both pre-existing and both measured rather than inferred:

- `/uberdev:uninstall-aliases` walked its alias list with
  `for SHORT in $SHORTS`. Under the Bash tool's /bin/zsh an unquoted scalar
  does not word-split, so the loop ran once over the whole string and the
  command removed nothing. Extracted the fence and ran it against a scratch
  HOME under both shells: the old form removed 0 of 16 under zsh and 16 of 16
  under bash; the here-doc reader removes 16 of 16 under both.
- `/turbox` was missing from the README alias table and the
  `UBERDEV_NO_AUTO_ALIAS` row while the prose claimed fifteen forwarders, and
  `/turbox` and `/premerge` were missing from `tests/aliases.test.sh`'s A1 and
  A6 loops — so neither command's forwarder nor its `allowed-tools`
  byte-equality was ever checked.

Surfaces moved: all SIX alias surfaces (the five-surface recipe repeated
across RFCs 0005/0007/0008/0009/0010 omits `references/configuration.md`,
which docs-accuracy T6b enforces — the correction is recorded in
`tests/aliases.test.sh`'s header); both chained vendor locks; the run-tree
policy's `does_not_govern` declaration; the sdd-wave-contract §C5 cap-sweep
ignore-list and its three tallies; CLAUDE.md's bump-lane table, which gains a
fourth row so a reviewer does not read a `/fix` PR as an unbumped
user-facing change; CI wiring; and the version bump to 0.57.0.

`tests/fix-lane.test.sh` is 166 rows. The FX-E rows EXECUTE the launcher and
the lib against a stubbed `gh` and `claude` — without the stub, the one row
that deliberately passes the arity gate reaches the claim protocol and writes
a live claim nothing releases. The refusal rows assert a gh call COUNT of
zero rather than the absence of a string, so they cannot pass vacuously in
CI, and the plan envelope is PARSED rather than grepped: prose in the RFC
claimed 13 keys while the composer emitted 14, and no grep over either file
could have seen the disagreement.

Closes #797
@coderabbitai

coderabbitai Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5dfa417a-c9ac-4638-bf54-01b9d03afb57


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Fixture added 4 commits August 24, 2026 12:23
…mment

Two findings from the review of #798, both real.

`grep -c` exits 1 when it matches nothing, and matching nothing is the
SUCCESS path for the Phase 3a blocker count: a clean review is zero blockers.
Run as the last command of a fence it would report the good outcome as a
failed probe. Switched to awk, which exits 0 either way, and added the
regression row — the same hazard the test harness's own gh-call counter hit.

The FX2 guard comment still described the pre-fix pattern. It claimed the glob
rejects "the literal 0" and named `-ge`; the shipped pattern is
`''|*[!0-9]*` and the test is `-lt`. Behaviour was always correct — the
numeric gate catches 0 — but a comment that misattributes which gate does the
work is exactly what gets the "redundant" one deleted later.
`printf | grep -q` is an early-exiting reader under pipefail: grep leaves on
the first match and printf takes EPIPE, so the pipeline status is the writer's
death rather than the match. tests/epipe-guard.test.sh forbids the shape
corpus-wide and reds on the file that adds one.

Caught by the full-suite re-run, not by the earlier targeted pass — epipe-guard
was green when I ran it, and the FX-E4 envelope rows that introduced the pipes
landed afterwards.
…le cap

Global CLAUDE.md: "Comment the why, three lines max ... Rationale that needs
more room goes in an RFC, an ADR or the issue, named here in one line."

Measured over the added lines: five blocks in lib/solve-launcher.sh ran 6 to
11 lines (47 comment lines in over-cap blocks). Every one had RFC 0022 as its
long form already, so the cut moves the reader one hop rather than losing the
reasoning: section 2 for why --fix requires --standard, 3.1 for why FX1 sits
ahead of gh, 3.6 for the lane's own cap, 4 for the third marker pair and the
narrow envelope.

Now 3 blocks at the cap (ruler plus three lines). Re-ran the fixtures that
read this file's bytes rather than assuming a comment edit is inert: the
sdd-wave-contract C5 cap sweep still totals 43 hit lines, and fix-lane,
turbox-fleet and solve-claim are green.
…ound

Three lenses over the diff proposed 16 cuts. The ones applied here, plus one
that turned out to be a defect rather than a simplification.

THE DEFECT. `$(( X + 0 ))` was canonicalising the FX2 rounds knob, and bash
arithmetic reads a leading zero as base 8: `UBERDEV_FIX_FIX_ROUNDS=010`
silently resolved to a cap of 8, and `08`/`09` are fatal arithmetic errors
rather than the lane's own diagnostic. The suite could not see it because it
only exercised `007`, where octal and decimal agree.

Replaced with a parameter-expansion strip that runs BEFORE validation, which
also collapses three operations into one gate: stripping makes `0` and `00`
become the empty string, which the existing glob already refuses, so the
separate `-lt 1` test and the arithmetic both go away. Verified over the full
input set in bash AND zsh: '' 0 00 000 -1 abc 1.5 +2 1e3 refused; 007->7
010->10 08->8 09->9 0100->100. The new rows red on the old form.

FX-E3 was also asserting `fixRounds` equals 1 while passing 1 — the same value
`${UBERDEV_FIX_FIX_ROUNDS:-1}` produces on its own, so the row held even if the
launcher ignored the env var. Now a non-default value.

CUTS, each verified against the owner that keeps the coverage:

- FX-S9's eight greps of the composer SOURCE: FX-E4 parses the same facts out
  of the JSON the launcher actually printed, which is strictly better evidence.
  Mutation-checked — smuggling `riskIssueCount` into the envelope still reds.
- the alias-row byte-equality check: tests/aliases.test.sh A6 owns it, and this
  PR added `fix` to its loop.
- the RFC-number uniqueness check: docs-accuracy already fails on `uniq -d`
  over every docs/rfc/NNNN-*.md.
- the CHANGELOG's FX1-FX4 table: a third copy of one RFC 0022 and the skill
  both own and test-lock. A user reading release notes does not operate
  breakers.
- two more over-cap comment blocks, and a pure back-reference comment.

SKILL.md was 488 lines against a hard 500-line ceiling. Moved the orientation
section to references/rationale.md — the pattern the sibling lane already
uses — leaving a pointer: 468 lines, and the fixture now reads SKILL.md plus
its references/ so a rule the skill still states is still found.

DECLINED: moving FX1 below the ISSUE_NUMS parse to save one python3 spawn. The
placement ahead of the first gh call IS the guarantee, it is line-number
locked, and one subprocess is not worth loosening it.

The sdd-wave-contract C5 tallies move 43/21 -> 40/18: the trims removed three
cap-sweep hit lines. Re-ran every fixture that reads these files' bytes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(fix): add /fix — a lean single-issue lane, the speedup version of /turbox

1 participant