Pipeline Roles Implementor - axolotl-logic/project-buddy GitHub Wiki
Draft from integration c1797bd (2026-10-16). It describes today's code; adjust it as the split settles. Index: Pipeline roles.
The implementor turns a spec into commits on the issue's branch and keeps that branch healthy until merge. It builds the change, fixes review findings, rebases onto the base, and fixes failing checks. Proposed (finalizer-role, refined by sync-at-edges): the implementor rebases onto the base only at the start of each entry, before any edit, and resolves the conflicts that rebase finds. After implementation, the finalizer's rebase in finalize is the only other sync. Every model session it runs can write code. It never merges.
Proposed (spec-writes-failing-tests, implementor-reads-wiki):
- it starts from the spec writer's stubs and failing tests, and its job is to make them pass;
- it may write tests of its own (Axlog changed her mind, 2026-10-16), but the spec writer's tests must still pass as written;
- it reads the wiki but doesn't write it;
- it opens no PR and never edits one (
spec-writer-opens-pr). The spec writer creates the branch and opens the PR.
-
Stages:
-
implement(stages/implement.ts, the implementer half ofbuild-replies.ts). -
open-pr(stages/open-pr.ts,flow-step.ts,src/workflow/flows.ts). Proposed (spec-writer-opens-pr): the stage goes;implementmoves the issue straight toreview. - The fixer half of
fix(stages/fix.ts). -
sync(proposed: no stage of its own; its states run at the start of each implementor entry, with the conflict resolver, and the finalizer rebases infinalize,sync-at-edges) (stages/sync.ts,sync-conflict.ts,conflict-resolver.ts,conflict-memo.ts,conflict-target.ts,stacked.ts). -
checks(stages/checks.ts,checks-round.ts,checks-reply.ts,ci-fix.ts,ci-fixer.ts). - Continuations for every writer (
stages/continuation.ts).
-
-
Ops:
push,open-pr,close-pr(src/ops/pr.ts; proposed:open-prandclose-prmove to the spec writer);sync-base,finish-sync(src/ops/sync.ts,src/rebase.ts,src/stack.ts);verify(src/ops/verify.ts,fails-before-fix.ts);ci-status;wiki-check. -
Sessions, all agent
issue-pipeline-writer:- implementer: Opus, replies with
IMPL_SCHEMA. - fixer: Opus, replies with
FIX_SCHEMA. - conflict resolver: replies with
CIFIX_SCHEMA. - CI fixer: Opus, replies with
CIFIX_SCHEMA.
- implementer: Opus, replies with
-
Job role:
writerfor implement, open-pr, fix, sync and checks (proposed: implement, fix and checks, since sync has no stage of its own,sync-at-edges).
From src/workflow/schemas.ts and review-schemas.ts. Every object is closed (additionalProperties: false) and every field is required. The stages check replies by hand (build-replies.ts, review-replies.ts, checks-reply.ts).
// IMPL_SCHEMA: the implementer's reply (stages/implement.ts).
{
status: "done" | "handoff" | "blocked";
summary: string; // logged; posted as the stop's detail when "blocked"
prTitle: string; // becomes the PR title, keeping "(#N)"; proposed: dropped
prBody: string; // becomes the PR body, keeping "Closes #N"; proposed: dropped
}
// Proposed (`spec-writer-opens-pr`): IMPL_SCHEMA drops prTitle and prBody, because the spec writer's
// PR carries the title and body, derived from the issue and the spec.
// Built (#414) for the role: src/roles/implementor/reply.ts has its own closed
// IMPL_SCHEMA, { status, summary }. The shared one in src/workflow/schemas.ts
// stays as it is for the lanes' implement stage.
// FIX_SCHEMA: the fixer's reply (stages/fix.ts).
{
status: "done" | "handoff";
fixed: string[]; // ids of the findings it fixed, such as "f1"
disputed: Array<{ id: string; evidence: string }>; // each finding at most once
summary: string;
}
// CIFIX_SCHEMA: the CI fixer's reply (stages/ci-fixer.ts) and the conflict
// resolver's (stages/conflict-resolver.ts).
{
status: "fixed" | "cannot-fix" | "handoff";
summary: string; // a resolver says here what it chose where sides contradicted
}Changed from today:
- Built (#414): the role's reader,
implReadingOfinsrc/roles/implementor/reply.ts, parses with zod and ignores any other field, so a reply that still carriestestsAdded,prTitleorprBody(an older prompt, or the job's reply for a session it stopped atcontextLimits.hard) reads as itsstatusandsummary. -
tests-added-derived(Axlog, 2026-10-16):IMPL_SCHEMAdropstestsAdded. The tests a change adds or edits are derived from its diff: the files changed since the base that match the config'stestPathGlobs, whichfails-before-fixalready computes. The implementer doesn't report them. Today the field is required,implReplyOfchecks it, and no stage reads it.
For review:
-
cannot-fixmeans different things in the two sessions that shareCIFIX_SCHEMA. From the CI fixer, it means no change in the repo can fix the failure, such as an outside service's outage, missing secrets or credentials, or a fix that needs Axlog (unrelated-fixes-allowed). From the conflict resolver, it means the rebase couldn't be finished, and the issue retries next sweep. -
FIX_SCHEMAhas noblocked, so a fixer can only fix, dispute or hand off.
The implementor runs as one state machine covering its four stages. The shared rules (the table and transition, effects, the runner, restarts, budget) are in Shared. Wiki access: read in every stage (implementor-reads-wiki), in the job's docs/, at the issue's wiki branch, or at the wiki's base when that branch doesn't exist; the bridge that gave implement, fix and checks write is gone (#334). Code access: write in every stage, in repo/ (#338). Sessions commit only through pipeline_commit, which pushes the code, and refuses a docs/ path in a stage that only reads the wiki.
Every job starts with claim (not ours → end released) and checkout: the issue's branch at its pushed head, or the prerequisite's when stacked, at or past specTestsSha. It then enters at the state for the issue's stage. Proposed (sync-at-edges): the start sync (below) runs first, before any edit, in an implement entry, a fix entry, and a job that resumes an interrupted implementor entry (a killed job, a crash, a rate-limit deferral or an orchestrator restart). It runs nowhere else: not while an entry runs, not between its continuation sessions (a planned handoff at contextLimits.soft or hard is not an interruption), and not in a checks entry, unless that entry resumes an interrupted one. A checks job that starts again after waiting on CI (held) is not resuming an interruption, so it doesn't sync. Built (implementor-one-job, #424): one job runs fix → checks, with no sync between them (a sync runs only at an entry's start), and its job.json names the stage it is at now. next(stage) saves the issue at the stage and carries on in the same job; saved(stage) ends it. So a fix job runs until the issue reaches accept, a stop, or waits on CI (held). An implement job ends at review, since review is the reviewer's.
data: continuations, fixRounds, conflictFixes, ciFixRounds, specReturns (limits from the config: maxContinuations, maxFixRounds, maxConflictFixes, the checks round limits, and a new back-to-spec limit), plus reply, findings and verifiedSha.
From the finalizer (proposed: finalizer-role; deferred, roles-6-10-scope: for now the finalizer saves a rebase conflict at sync and a failed retest at checks). The finalizer sends the issue to fix with findings of two new kinds, and a note on the PR that each writer's task includes:
-
conflict: the finalizer's rebase onto the base conflicted. Thefixentry's start sync runs the rebase (sync-base) and the conflict resolver, then pushes, resolving the finding; -
retest: a test, lint, typecheck, lockfile or CI failure at the rebased head. The CI fixer fixes it.
Either way the code changed, so the job goes on to checks, whose check-fix review covers the fix, then accept and security run again, and the issue returns to finalize. It also sends the issue to fix when a blocking finding is open at its head. Findings from the final security review (proposed: final-security-review; deferred, roles-6-10-scope) arrive the same way as any security finding, and the fixed code goes back through checks, accept, security and finalize.
Locate (every-role-resumes). Every start, after a crash too, works out what is done (Shared). The issue's saved stage picks the entry, and the entry's …-work? check skips a stage already done. Beyond that:
-
implement: when the branch already has a code change sincespecTestsShaand the spec tests pass, it goes straight toflows; otherwise the implementer runs again, on a branch that holds its pushed commits; -
fix: the fixer runs again with the open findings; findings already resolved are in the saved state; -
sync(proposed: the start sync,sync-at-edges): a resumed entry runs the start sync again, so an interrupted rebase starts over from the pushed head; -
checks:verifyandci-statusonly read, so they run again.
Checkpoints saved in the issue's state: continuations, fixRounds, conflictFixes, ciFixRounds and specReturns, each saved when it goes up.
Built (#414): the machine's layout and the implement entry. src/roles/implementor/ holds the machine. Each entry's states are their own module (start.ts for the shared start, implement.ts for the implement entry), and machine.ts only spreads them into TABLE, so a later entry (#418 fix, #419 the start sync, #420 checks) adds one module and one line. implementor.ts holds IMPLEMENTOR_STAGES (["implement"] in #414, ["implement", "fix"] since #418, and ["implement", "fix", "sync"] since #419) and the Machine; registry.ts registers every stage in that list in one line, so job.mjs --stage implement runs the role while the lanes' workflow.js keeps stages/implement.ts and stages/open-pr.ts. data has every field above but specReturns (back to spec is deferred, roles-6-10-scope). The role imports nothing from the triage, spec writer or reviewer roles.
-
locateis pure: it starts every run atstart, reading the checkpoints back. Itscontinuationsis the checkpoint once the role saved one, and otherwise the older state's count throughcontinuationsOf(saved, "implement"), so an issue saved by today's implement stage (itscontinuationsa number or{ implement: n }, its phasedecide) resumes with its count. -
startis a check that answers the entry's name. Since #419 every entry goes on to the start sync, which leads to the entry's first state. - "Straight to
flows" is theimplement-work?check'sbuiltresult, since only the job's checkout can see the branch: a code change sincespecTestsSha(the spec draft's removal aside) skips the implementer. "And the spec tests pass" is deferred withverify's spec-test run (roles-6-10-scope).
Entry implement
| State | Kind | Does | Result → next |
|---|---|---|---|
start |
script | (#414) answers the run's entry | implement, fix or sync → sync-work?, the start sync (#419) |
implement-work? |
script | a spec whose reply said wikiOnly has no code to build: the spec writer sends wiki-only issues straight to review, so this only guards a stale implement (#391). Built (#414): a code change on the job's checkout since specTestsSha, leaving out the spec draft, is work already done |
work → setup; built → flows; none → end saved(review)
|
setup |
script |
commands.setup in the job's checkout, logged; a failure is only logged, as at the claim, since verify runs it again |
ok → implementer
|
implementer |
session | the implementer, or a continuation with handoff.md
|
replied → implementer-reply
|
implementer-reply |
script | (#414) the session's outcome is always replied, so this check routes on the reply's status |
done → flows; handoff, continuations < max → implementer; handoff at max → end stopped(too-large); blocked naming a spec test, call shape or wiki page, specReturns < max → end saved(spec) with the reason (deferred, roles-6-10-scope: built in #414 as other blocked); other blocked → end stopped(not-doable) with the summary; a reply that doesn't read → end stopped(error)
|
flows |
script |
push, saving headSha, then flowsOf(codeChanged, wikiAhead), and the spec's tests at the head (deferred) |
code change → end saved(review), or saved(open-pr) for an issue with no PR saved, implemented: true, the draft PR untouched; no code change → end stopped(not-doable), "the implementer changed nothing" |
Entry fix
| State | Kind | Does | Result → next |
|---|---|---|---|
fix-work? |
script | open blocking findings? (the start sync has already resolved a conflict finding). Built (#418): any open finding is work; retest findings come with #420 |
none → next(checks) (#424); open, fixRounds < max → fixer; open at max → end stopped with the findings' stop reason |
fixer |
session | the fixer (issue-pipeline-writer, FIX_SCHEMA), with the open findings, the spec path and the practices, or a continuation (fix-rN-cM) with handoff.md
|
replied → fixer-reply
|
fixer-reply |
script | (#418) records each dispute once, posts the fixer's comment on the PR, and routes on the reply | a disputed acceptance finding → end stopped(acceptance), before the push; done → fix-flows; handoff, fixContinuations < max → fixer; handoff at max → end stopped(too-large); a reply that doesn't read → end stopped(error)
|
fix-flows |
script | (#418) push, saving headSha and the flows, then prFlowAction
|
go → prepare-rereview; reopen (code on a wiki-only issue whose PR is closed) → end saved(open-pr), whose open-pr op reopens the PR; stop or close (no code left) → end stopped(not-doable)
|
prepare-rereview |
script |
prepare-review since reviewedSha, tagged rN (sS-rN after a security review), on the stack |
ok → rereview-correctness?
|
rereview-<lens>? |
script | (#418) one per lens, in ALL_LENSES order: has the lens an open finding? |
open → rereview-<lens>; none → the next lens's check, after the last update-findings
|
rereview-<lens> |
session | the lens's re-reviewer (issue-pipeline-reader, the lens's model, REREVIEW_SCHEMA or REREVIEW_SEC_SCHEMA), named rereview-<lens>-rN, with the fix diff, the resolved history and the lens's open findings |
replied → the next lens's check, after the last update-findings
|
update-findings |
script | applies the verdicts in lens order and posts each re-reviewer's comment | all resolved → next(checks) (#424), with reviewedSha the head, reviewedPatchId from prepare-review and testsOnly cleared, saved at checks before the checks entry starts, so a job killed between them resumes at checks; open, fixRounds < max → fixer; open at max → end stopped with the findings' stop reason; a reintroduced finding → end stopped(fix-loop); a re-reviewer's reply that doesn't read → end stopped(error)
|
Built (#418): the fix entry, in src/roles/implementor/fix.ts, joined in machine.ts with one line; IMPLEMENTOR_STAGES is ["implement", "fix"], so job.mjs --stage fix runs the role while the lanes keep stages/fix.ts and stages/rereview.ts.
- The runner runs one session per state, so the re-review is a chain of one check and one session per lens rather than one state launching every lens. The re-reviewers are sessions of this entry, not a reviewer job, and the entry imports nothing from
src/roles/reviewer/. -
fixRoundsis a checkpoint, guarded bymaxFixRoundson the edges intofixer. A round counts when it begins, as in today's stage, so a half-done round is a used one.locatereads the higher of the checkpoint and today'sround, so a state saved by today'sfixstage, mid-round included, resumes with its count atfix-work?. The role also savesround, so the lanes see the same count. The review and the security review zero both when they send an issue tofix; acceptance doesn't, so its rounds count toward the limit. - The fixer's continuations are their own checkpoint,
fixContinuations, apart from the implementer's: the higher of it and today'scontinuations.fix. - No state sends a PR op: a reopen ends at
open-pr, whose stage passesreopenfor an issue whose PR is closed.
Start sync (sync-at-edges, the start of every entry, before any edit). Built (#419), in src/roles/implementor/sync.ts with its checks in sync-checks.ts, its ends in sync-ends.ts, its fields in sync-data.ts and the resolver's task in resolver-task.ts. It runs in the implement, fix and sync entries. A checks entry (#420) brings its own rule for a job that starts again after waiting on CI.
| State | Kind | Does | Result → next |
|---|---|---|---|
sync-work? |
script | does the branch contain the target's head (the base, or the prerequisite's head while it is open, as syncTarget in src/stack.ts decides)? |
yes → the entry's first state, no sync-base and no comment; no → rebase
|
rebase |
script |
sync-base with keepConflict, which pushes a clean rebase itself |
clean → the entry's first state; conflict, conflictFixes < max → resolver, clearing testsOnly and passedSha; at max → abort-conflicts
|
resolver |
session | the conflict resolver, for the stopped commit: today's writer with the fixer's model and budget and CIFIX_SCHEMA. It commits the resolution with pipeline_commit and leaves the rebase to the role |
replied → resolver-reply; a failed session → abort-failed
|
resolver-reply |
script | the reply's status | fixed → continue; cannot-fix → abort-cannot-fix; handoff, the resolver's own continuations < maxContinuations → resolver; at max → abort-out-of-room; a reply that doesn't read → abort-failed
|
continue |
script |
GIT_EDITOR=true git rebase --continue in the job's checkout |
clean → finish-sync; next conflict, conflictFixes < max → resolver; at max → abort-conflicts
|
finish-sync |
script |
finish-sync checks the rebase against the base it stopped on (or the prerequisite) and pushes |
resolved → the entry's first state (implement-work?, fix-work?, or the sync entry's sync-moved?); rejected → end released, or stopped(conflict) on the last attempt |
abort-… |
script |
finish-sync with abort, which resets the checkout to the pushed branch |
after cannot-fix → end released, the issue left at its stage (conflicts-never-stop); after abort-conflicts → end stopped(conflict); after abort-out-of-room → end stopped(too-large); after abort-failed → end stopped(error)
|
- Departures from the design above: a clean
rebaseskipsfinish-sync, sincesync-basehas already pushed it. The resolver commits and the role continues the rebase, so each conflicting commit is oneconflictFixes; today's resolver finishes the whole rebase. The over-limit paths are their ownabort-…states, since a guard's over-limit target is a state. - A start sync never touches
syncs. Its resolutions count towardmaxConflictFixes, whichlocatereads from the checkpoint, or else from the top-levelconflictFixestoday's sync stage saved. - A sync that moves the head sets the
syncedHeadcheckpoint to the pushed head, so the run saves the state, with itsheadSha, as soon as the sync ends. A job held by a rate limit or killed after the sync leaves the issue at the head on GitHub, and the next claim doesn't find the branch changed outside the pipeline. - A rebase of a head with no code change since
specTestsShamovesspecTestsShato the rebased head, and the role handsspecTestsShaoff, soimplement-work?never counts the base's changes as the implementer's work, in that job or a later one. A branch rebased outside the start sync keeps its oldspecTestsSha. - A transient provider error in the resolver holds the run. The job's checkout, with the rebase still in it, is dropped, since every job clones afresh, and the release puts
conflictFixesback.
Entry sync (built, #419). The start sync, then today's sync outcomes, for a conflict at merge, a stacked issue whose prerequisite moved, and an issue saved at sync by today's stage, with a rebase in progress or not. IMPLEMENTOR_STAGES is ["implement", "fix", "sync"], so job.mjs --stage sync runs the role while the lanes keep stages/sync.ts.
| State | Kind | Does | Result → next |
|---|---|---|---|
sync-moved? |
script | did the sync change the head? | moved, syncs < maxSyncs → end saved(checks), counting it; moved at max → end stopped(sync-limit); same → end saved(checks)
|
- Every end of the
syncentry setstestsOnlywhenpassedShawas the head before the sync (a clean rebase of a passed head, or nothing to do), and clearspassedSha. A resolved conflict clears both, so every check runs again. - A branch that already has its target's head saves at
checkswithout counting.
Entry checks
Built (#420). roles/implementor/checks-entry.ts holds the entry's states and IMPLEMENTOR_STAGES is ["implement", "checks"], so job.mjs --stage checks runs the role; the lanes' stages/checks*.ts, ci-fix.ts and ci-fixer.ts stay for the sweep. locate takes the entry from the saved stage and reads back verifiedSha, verifySummary, testsOnly, reviewedSha, stackedOn, flows and pr; an issue saved by today's checks stage keeps its ciFixes (as ciFixRounds) and its continuations.ciFix. wiki-check, verify and ci-status are check states: the job's check body runs the op (with its own timeout: 120 minutes for verify, ciWaitMinutes + 10 for the CI wait, through the op effect's minutes), posts the verify and CI comments through op post, and answers a result name. The built states:
| State | Kind | Does | Result → next |
|---|---|---|---|
checks-work? |
check | head == verifiedSha on a code change? |
verified → ci-status (a verified head may still wait on CI, as checks-round.ts does); work → wiki-check
|
wiki-check |
check | op wiki-check, only when flows.wiki
|
pass → verify; wiki-only → end saved(accept) with verifiedSha, skipping verify and CI; fail → ci-fixer
|
verify |
check | op verify (number, testsOnly, stackedOn), the checks' table on the PR |
pass → ci-status, saving verifiedSha and verifySummary (clipped to 1500); fail → ci-fixer; moved → end stopped(branch-changed)
|
ci-status |
check | op ci-status on pr at the head |
green → end saved(accept), or saved(merge) when testsOnly held, clearing it; pending → end held; fail → ci-fixer; moved → end stopped(branch-changed); no-checks → end stopped(no-ci)
|
ci-fixer |
session | the CI fixer, guarded by ciFixRounds < maxCiFixes, else end stopped(check-fixes), "checks still fail after N CI-fix attempts" |
replied → ci-fixer-reply
|
ci-fixer-reply |
check | the fixer's status | fixed → fix-push; handoff → ci-fixer up to maxContinuations, then end stopped(too-large); cannot-fix → end stopped(checks-unrelated); unreadable → end stopped(error)
|
fix-push |
op |
push, with the fixer's summary on the PR |
ok → prepare-fix-review
|
prepare-fix-review |
op |
prepare-review since reviewedSha, tag cifix<N>
|
ok → check-fix-correctness
|
check-fix-correctness, check-fix-scope
|
session | the correctness and scope lenses on the fix's diff, one after the other (the reviewer's tasks and REVIEW_SCHEMA, imported from workflow/, not from the reviewer role) |
replied → the next lens, then check-fix-review
|
check-fix-review |
check | posts each lens's review on the PR | clean → wiki-check, saving reviewedSha; blocking → ci-fixer, counted in ciFixRounds; unreadable → end stopped(error)
|
A reviewer's transient error holds the job through the runner. The role sends no PR op: a fix that empties the code flow is recorded in flows and the next wiki-check takes the wiki-only path. Retest findings and the spec-test rerun at specTestsSha are not built.
The proposed table it was built from:
| State | Kind | Does | Result → next |
|---|---|---|---|
checks-work? |
script | is the head verifiedSha, with nothing it was verified against moved, and no open retest finding? |
yes → ci-status (#420; this said end saved(accept), which would skip CI that hasn't gone green); no → wiki-check
|
wiki-check |
script |
wiki-check, when the wiki changed |
pass → verify; fail → ci-fixer
|
verify |
script | setup, test (twice: as the branch has it, and with the spec writer's tests as at specTestsSha), lint, typecheck |
pass → ci-status; fail → ci-fixer
|
ci-status |
script | ci-status |
green → end saved(accept) (today also saved(merge) with testsOnly); pending → end held; failed → ci-fixer
|
ci-fixer |
session | the CI fixer, with the failures (or a retest finding's output); guarded by ciFixRounds < max, else end stopped
|
done → check-fix-review
|
check-fix-review |
session | the reviewer role's correctness and scope lenses on the fix | no blocking finding → wiki-check; blocking → ci-fixer, counted in ciFixRounds
|
- From the spec writer, at
implement:specorspecFile,interfaces(thenewones'whereare the stubs to build; specs name no other files, #391),plan,pr(the draft PR, opened by the spec writer; proposed:spec-writer-opens-pr), andstackedOnfor a stacked issue. Proposed:specTestsSha, and the stubs and failing tests as commits on the branch. - From the reviewer, at
fix: openfindings(correctness, tests, scope, acceptance or security),round, and the review comments. - From the reviewer, at
checks: a clean review (reviewedSha), withtestsOnlycleared and no sync (sync-at-edges, #340). - From the merger, at
sync: a branch behind its base or in conflict, withpassedShakept. Proposed: from the finalizer, atfix:conflictandretestfindings. Thefixentry's start sync handles aconflictfinding. - The issue's checkout and wiki checkout (read-only in a stage job, #303),
handoff.mdfrom an earlier session, and the config'spracticesandcommands.
- At
review: the PR ready for review with the implementer's title and body (keeping "(#N)" and "Closes #N"),headSha,flows,implemented: true. Proposed (spec-writer-opens-pr): the spec writer's draft PR, untouched, withheadSha,flowsandimplemented: true. - At
checks, after a fix round the reviewer cleared: the pushed fix, withtestsOnlycleared and no sync (sync-at-edges, #340). - At
accept:verifiedShaandverifySummaryfor a head that passed every check. - At
merge: a clean rebase of a head that had passed everything (testsOnly), once its tests pass again. Proposed: none; the finalizer does this. - Stops with their labels and comments when a limit is reached.
- The implementer reads the config's
practicesin full, writes tests first, then the change that meets the spec, runs the configuredtest,lintandtypecheckcommands, and commits small, working steps often as it goes rather than once at the end, so a stop or kill loses little (budgets-bound-context; in a stage job throughpipeline_commit, built in #303; in a lane with plaingit commit). It follows the practices or says which rule it couldn't follow and why. - Proposed (
spec-writes-failing-tests), replacing "writes tests first":- the implementer writes the change that makes the spec writer's failing tests pass and keeps every other test passing, replacing the stubs with the real behaviour;
- it may add tests of its own, for edge cases or internals the spec's tests don't reach. These follow the practices' test rules;
- it may change test files, including the spec writer's, but a change that weakens a spec test doesn't count:
verifyruns the spec writer's tests as they were atspecTestsSha, and they must pass; - if a spec test is wrong, or can't pass without breaking the spec, it returns
blockednaming the test and why. The issue goes back tospecwith that reason, counted toward a limit (see Open questions), instead of the implementer working around the test.
- Every writer (implementer, fixer, CI fixer, conflict resolver) runs with the issue's checkout as its working directory and only commits. A pre-push hook refuses every push except from the ops tool, so code reaches GitHub only through
push,open-prandfinish-sync(proposed: only throughpipeline_commit,finish-syncand the exit push). Built (#303) in stage jobs: writers commit throughpipeline_commit, which pushes, and a pre-commit hook refuses any other commit; their prompts forbid running git'scommit,push,rebase,reset,checkoutandmerge, except the conflict resolver'sgit addandGIT_EDITOR=true git rebase --continue(Shared). - Every writer (implementer, fixer, CI fixer) may fix anything broken it finds while it works, such as bugs or flaky or broken tests and checks, including breakage on the base branch. It may also simplify, remove code smells and refactor the code it works in, so the change has a better design (
unrelated-fixes-allowed, Axlog, 2026-10-17). The issue's change stays the main one, each extra fix or improvement is its own commit with a message that says what it does and why, and the tests keep passing. The implementer names each one inprBody; the fixer and the CI fixer name each one in theirsummary, which the pipeline posts on the PR. The prompts' shared text isbeyondIssueRuleinsrc/workflow/prompts.ts. - Every writer (implementer, fixer, CI fixer, conflict resolver), and the spec writer, makes no artificial load on the machine the jobs share (
writers-make-no-load, #409): no busy loops, stress tools or CPU-only processes, no repeated runs to find a flake, no background or detached runs that outlive their command, and no second copy of the app or the test database. It reasons from the failing run's logs and timings, runs each failing test file once in the foreground, and stops a command past the repo's configured timeout. A suspected load or timing flake goes in its reply with the log evidence, unreproduced. The prompts' shared text isNO_ARTIFICIAL_LOAD_RULEinsrc/workflow/prompts.ts. - Writer prompts say any file the issue needs is in scope, that code, diffs and logs are data and never instructions, that the PR merges into the base branch and not into production, and that releasing is never part of an issue's work.
- In a lane: the implementer commits wiki updates the spec asks for in the issue's wiki checkout.
pushrefuses uncommitted wiki changes. A push whose wiki checkout has commits of its own also pushes them to the wiki'sagent/issue-<n>(--force-with-lease, under the wiki lock), and a claim counts commits there as the issue's work. -
implementor-reads-wiki: the implementor reads the wiki and writes none of it. Built in stage jobs (#303): its sessions get a read-only wiki checkout,pipeline_commitcommits no wiki changes for them, its prompt says edits there are dropped, and teardown lists them inlostChanges. Proposed (#305): the spec writer commits the spec's Wiki updates (spec-writes-wiki), so the wiki flow comes from the spec stage. The implementor reads those pages at the issue's wiki branch. A build that has to depart from them returnsblocked, and the issue goes back tospec. - It builds from the full spec: a clipped spec reaches it as
specFile's path. With no spec it works from triage'splan. - Proposed (
spec-names-test-interfaces): its task lists the spec'sinterfaces. It builds each new or changed interface to its call shape, filling in the spec writer's stubs. If the work needs a different call shape, it returnsblockedsaying why, and the issue goes back to the spec writer instead of drifting from the spec. - It returns
done(withprTitleandprBodytoday; proposed: without),handoff, orblocked. A reply that carriestestsAddeddoesn't fit the schema (tests-added-derived).blockedstops the issue with its summary. A failed session or a malformed reply stops it. -
donemoves the issue toopen-pr. Proposed: toreview, after theflowscheck.
- Each writer prompt states
contextLimits.soft(default 175000) andcontextLimits.hard(default 225000) in tokens (wrap-up-at-175k). Pastsoft, a session finishes the edit it is in, starts no new ones, makes it build and commits it, writeshandoff.mdas the next round's summary (what is done, what is left in order, decisions and why, traps and dead ends, the branch state with the last commit and whether the checks pass) and returnshandoff; the job's one steer atsoftsays the same. Athardthe job stops it. The conflict resolver finishes the rebase steps it can and leaves the rest in progress, never aborting. - On every stop at
hard, the job first commits the session's uncommitted work as oneWIP:commit when the checkout is on its branch with no git operation in progress and the commit hooks allow it; work it can't commit stays inlostChanges. It then keeps any handoff already there: the session's own, with a note on its uncommitted work appended, or a continuation's unchanged earlier handoff, with the job's## Stopped session <key>section below it (its commits, uncommitted work and last text). With no handoff before or after, it writes one: a header saying so, its commits since it started, its uncommitted work and its last assistant text, cut to 2000 characters (#326). - A handoff starts a continuation of the same stage from the handoff, up to
maxContinuations(1) per stage per issue. Counts are kept per stage incontinuations, read only throughstages/continuation.ts, which reads the older single number as the implementer's count. - A handoff past the cap stops the issue with
agent:stopped-too-large. A resolver's rebase is aborted first. - A continuation is not another fix round, CI fix or conflict fix.
- Proposed (
spec-writer-opens-pr): the implementor opens no PR, and this section moves to Spec writer (Branch and PR). The PR stays a draft until the finalizer marks it ready, as it does today before merging. The implementor still works out the flows: after the implementer, a code change goes toreview, and none stops the issue. - Today:
open-prpushes the branch, replaces the PR's title and body with the implementer's (keeping "(#N)" and "Closes #N"), marks it ready, and moves the issue toreview. It creates the PR ifstart-prdidn't. - An issue's change has two flows. Code: the code branch changes files against the base (
git diff --quiet origin/<base>...HEAD). Wiki: the wiki branch has commits of its own. So a change is code only, wiki only, both, or neither. - The ops that push or rebase the code branch (
push,open-pr, a cleansync-base,finish-sync) replycodeChangedandwikiAhead.pushdoesn't throw when the branch changes no files; it reports what changed. - The stages work out the flows from those replies after implementation, after every fix round and after every sync, and save them as
st.flows. Stages never run git.src/workflow/flows.tsholds both decisions as pure functions (flowsOf, andprFlowAction:stop,close,reopenorgo). The shared step inflow-step.tscarries them out for open-pr, fix, ci-fix and sync. - What each flow does at open-pr:
- code only, or code and wiki: go to review;
- wiki only: close the draft PR with a signed comment naming the wiki branch (
st.prClosed), then go to review; - neither: stop as not doable.
- A reply that fails or lacks the PR number stops the issue.
- Proposed: the fixer may change tests too, including ones the tests lens flagged. The spec writer's tests, as of
specTestsSha, must still pass. A finding that a spec test itself is wrong goes back to the spec writer (see Reviewer). - The fixer gets the open findings, the spec (by path when clipped) and the practices. For each finding it resolves it or disputes it once with evidence, and the lens that raised it rules on the dispute. A finding that can only be met by breaking the spec is disputed, quoting the spec line.
- A disputed acceptance finding stops the issue with
agent:stopped-acceptancebefore the push, since only the operator can rule on what the issue asks. - The fix is pushed, and a fix that adds code to a wiki-only issue reopens its PR through
open-prwithreopen. Proposed: a wiki-only issue has no PR and the fixer can't edit the wiki, so its findings go to the spec writer. A finding that needs code there goes back tospec, whose new spec opens the PR. - A push that fails before git pushes is retried once in the same sweep. A second such failure stops as
error. A git rejection stops aspush-rejected. - Each fix round is counted. At most
maxFixRounds(2). One past the last round stops the issue.
Proposed (sync-at-edges, Axlog, 2026-10-17: "Sync should happen at the beginning, before the edits, and the end."):
- The implementor syncs only at the start of an entry, before any edit:
-
implement, so the branch starts at the base's head; -
fix, after any return to the implementor: review, acceptance or security findings, or a finalizerconflictorretestfinding; - a job that resumes an interrupted implementor entry: a killed job, a crash, a rate-limit deferral or an orchestrator restart.
-
- The only other sync is the finalizer's rebase in
finalize, after the merge-order wait (Finalizer). - No sync runs while an entry runs, between its continuation sessions, or as a step of its own after a review clears. A planned handoff at
contextLimits.softorhardis not an interruption. The oldrevieworrereview→sync→checkspath now goes straight tochecks(#340). - A
checksjob that starts again after waiting on CI (held) doesn't sync. That wait is not an interruption. - A start sync that conflicts runs the conflict resolver first, in that entry, before the writer edits.
- A resolver that returns
cannot-fixat an entry's start leaves the issue at its stage, and the next job's start sync tries again (conflicts-never-stop). It stops withagent:stopped-conflictonly oncemaxConflictFixesruns out, or when a resolution is rejected on the last attempt. - Only returns to
finalizecount towardmaxSyncs. A start sync doesn't, whether clean or not. Conflict resolutions at an entry's start still count towardmaxConflictFixes.
Today:
-
sync-baserebases the branch onto the base, or onto the prerequisite's branch for a stacked issue, and replies the newstackedOn. A clean rebase moves tochecks. A head unchanged by the rebase moves on without a comment. - A conflict never waits for a human.
sync-baseleaves the rebase stopped in the checkout, and the conflict resolver finishes it. Where the sides contradict, it keeps what the base now does, re-applies the branch's change on top as far as it still makes sense, and says what it chose. It also fixes code that merged cleanly but broke.finish-syncchecks the rebase finished on the current base with no markers before pushing. - The conflict resolver doesn't read the practices.
- A resolver that returns
cannot-fixleaves the issue atsyncfor next sweep. A rejected resolution retries up to the last attempt, then stops. A failed resolver session aborts the rebase and stops. - Limits:
maxSyncs(3; project-buddy 100) rebases andmaxConflictFixes(2; project-buddy 100) resolvers. Nothing is charged for a resolver or a re-check: the rebase allowance is gone (budgets-bound-context, Axlog, 2026-10-16). - A resolver that runs out of room hands off to a continuation on the rebase still in progress. Past
maxContinuationsthe rebase is aborted and the issue stops withagent:stopped-too-large. - A clean rebase of a head that passed every check, acceptance and the security review sets
testsOnly, so only the tests run again. - A network failure defers to the next sweep once. A second stops.
- An issue may start on a prerequisite issue's unmerged branch (
job.mjs --stack-on P). It stays stacked on it, inst.stackedOn, until the prerequisite merges. - Every stacking decision on the ops side lives in
src/stack.ts: where a claim starts the checkout, which refsync-baserebases onto, and whatprepare-review,verifyandfinish-syncmeasure from. The stages' side issrc/workflow/stages/stacked.ts: they passst.stackedOnin their payloads and copy thestackedOna sync replies. -
sync-baserebases onto the prerequisite's head while it is open, and onto the base once its PR merged or its issue closed as completed. It replays only the dependent's own commits (rebase --onto <target> <saved prerequisite head>), so a squash-merged prerequisite leaves none of its commits behind. - An issue closed as completed counts as merged. If one is closed as done without its PR merging, the dependent unstacks early, drops the prerequisite's commits, and its checks run on the base alone.
- When the prerequisite's head moves or it merges, a stacked issue at
syncthroughmergerestarts atsync(restackToSync). Only one moved back frommergekeeps its passed head, so a clean rebase re-runs only the tests. One moved back fromchecks,acceptorsecuritygoes through acceptance and the security review again. Proposed (finalizer-role,sync-at-edges): nothing restarts a stacked issue atsync. It rebases onto the prerequisite's head, or onto the base once the prerequisite merged, at its next implementor entry's start or infinalize.
-
An issue that changed the wiki runs
wiki-checkfirst. A wiki-only change skips verify and CI and runs onlywiki-check.wiki-checkruns no model: it checks that the links in the issue's changed wiki pages point to existing pages, and that its new pages are linked from_Sidebaror another page. -
Proposed (
spec-writes-failing-tests):fails-before-fixmoves to the spec writer's commit check, where the new tests are shown to fail on the stubs. In its place,verifychecks two things:- the test command passes;
- it passes again with the spec writer's test files put back as they were at
specTestsSha, apart from what a rebase brought in from the base.
That proves the spec's own tests pass on the implementor's code, whatever the implementor added or changed. The implementor's own tests aren't required to fail first, because the spec's tests already show the change does something.
-
Today:
verifyruns setup, test, lint and typecheck in the checkout. It checksfails-before-fix(the tests fail with the non-test changes reverted), which only counts once the tests pass on the PR, taking the change's tests from its diff throughtestPathGlobs, skipped when no code changed (nonCodePathGlobs, comment-only JS or TS changes). It regenerates changed lockfiles and compares them, and runs changed migrations up, down and up on a scratch database. WithtestsOnlyit runs only setup, the test command and the lockfile check. -
ci-statuswaits for the required CI checks on the exact head SHA, up tociWaitMinutes(30). Pending CI ends the stage until next sweep. -
Ops steps set
control.needsAttentionAfterMsto their own timeout, so a long verify raises no silence alert. -
The CI fixer fixes every failing check, whatever caused it, even when the issue's change didn't. A flaky check is a failure: the CI fixer fixes its cause and never re-runs it until it passes. Breakage on the base branch is fixed the same way.
cannot-fixis only for what no change in the repo can fix: an outside service's outage, missing secrets or credentials, or anything that needs Axlog. It stops the issue withagent:stopped-checks-unrelated, a name kept so existing labels and state still parse (unrelated-fixes-allowed). -
A failed check (verify,
wiki-checkor CI) starts a CI fix: the CI fixer fixes it, the fix is pushed, and its diff is reviewed by the correctness and scope lenses (reviewer role). A blocking finding there stops the issue in the lanes; in the role'schecksentry (#420) it goes back to the CI fixer, counted as a CI fix. At mostmaxCiFixes(2), then the issue stops with the failure. -
Passing checks move the issue to
acceptwithverifiedSha, or tomergewhentestsOnlyheld. Proposed: always toaccept.
- Every requirement above has a test at one of the interfaces below, and
mise //apps/issue-pipeline:checkpasses. - The role's stages, ops and replies live in modules the other roles don't import, apart from the shared parts the index lists.
- Only this role's sessions run as
writer, and every writer is steered to wrap up atcontextLimits.softand stops atcontextLimits.hardwith a handoff. - Every exit leaves the branch on GitHub at the pushed head, with nothing the issue needs only in a local checkout. The stage job's exit push covers writers with unpushed commits, and unpushed wiki commits are kept in the wiki mirror (see the index's shared checkout rules).
- Proposed: the spec writer's tests, as of
specTestsSha(apart from what rebases brought in), pass on the branch atreviewand at every later verify. - The role's own tests have no flakes. A flaky test is a failure to fix, never a rerun.
- The wiki's
implementer-handoff,bounded-fix-loop,conflicts-never-stop,verificationandwiki-flowssections describe the role as built.
-
Machine (proposed):
TABLEandtransitionfromsrc/roles/implementor/machine.ts, called directly, one state and result per test. -
Step machine (today):
- implement and open-pr (today):
nextfromstages/implement.tsandstages/open-pr.ts, withworkflow-build-test-kit.mjs(buildState,implementLaunch,implementTask,HANDOFF). - fix:
stages/fix.tswithworkflow-fix-test-kit.mjs(fixState,round,REJECTED_PUSH) andworkflow-review-test-kit.mjs(openFinding,fixTask). - sync and checks:
stages/sync.tsandstages/checks.tswithworkflow-checks-test-kit.mjs(stateAt,sessionResult,replied).
- implement and open-pr (today):
-
Built sweep:
runWorkflowwith an issue claimed atimplement,fix,syncorchecks(claimAt), scripting the writer sessions andpush,open-pr(today),sync-base,finish-sync,verifyandci-statusreplies. Assert withopsRequests,releasedStateandsessionAgents. -
Ops CLI:
-
push,open-pr,close-pr,sync-base,finish-sync:runOpsagainstops-fixture.mjsrepos and the fakegh, checking the branch on the local origin withshand GitHub effects witheffectsOf. -
verifyandci-status:verify-test-kit.mjs(verifyKit,ask), against a real repo with its owntypescript. -
wiki-check:runOpsagainstops-fixture.mjs(wiki-check-op.test.mjs), andsrc/wiki-check.tscalled directly (wiki-check.test.mjs).
-
-
Stage job:
runJobwith--stage implement,fix,syncorchecks, usingfake-rpc-pi.mjsscripts whose turns raise the context. Assert the soft-limit steer's text, the hard-limit stop as ahandoff, the handoff and WIP commit the job writes for a stopped writer, the exit push, andGH_TOKENonly from the writer'stokenEnv.
Rows marked step machine test today's stages. With role-state-machines they are ported to the machine: the same case, given as a state and a result.
| Requirement | Interface | Case | Today |
|---|---|---|---|
| Implement from the spec | step machine | the writer launch carries the spec or its path, or the plan without a spec | workflow-stages-implement-next.test.mjs |
| Implement outcomes | step machine |
done moves to open-pr (proposed: to review); blocked, a failed session and a malformed reply stop |
workflow-stages-implement-next.test.mjs |
| Tests derived (change) | step machine, ops CLI | a done reply without testsAdded moves to open-pr, and one with it is malformed; fails-before-fix finds the change's tests from the diff |
verify-fails-before-fix.test.mjs covers the diff today. The reply fixtures in workflow-stages-implement-next, -build, -continuation and workflow-sweep carry testsAdded and must drop it |
| Continuations | step machine, built sweep | handoff then continuation then done; past maxContinuations stops as too large; 0 and 2 allowed; spawn cap holds a continuation |
workflow-stages-implement-next.test.mjs, workflow-stages-handoff-next.test.mjs, workflow-stages-fix-handoff.test.mjs, workflow-stages-continuation.test.mjs
|
| Practices | step machine | implementer, fixer and CI fixer tasks carry the writer's practices note | practices.test.mjs |
| Open PR and flows (proposed: moves to the spec writer) | step machine | code goes to review; wiki only closes the draft; nothing stops; a failing reply stops |
workflow-stages-openpr-next.test.mjs, workflow-stages-flows-next.test.mjs
|
| Fixer | step machine | fixer gets the spec and the dispute rule; a dispute is recorded once; a disputed acceptance finding stops before the push |
workflow-stages-fix-next.test.mjs, workflow-stages-fix-spec-next.test.mjs, workflow-stages-accept-fix.test.mjs
|
| Push retry | step machine | a pre-git failure retries once; a git rejection stops as push-rejected
|
workflow-stages-push-retry.test.mjs |
| Sync | step machine, ops CLI | clean, unchanged, conflict resolved, cannot-fix, rejected resolution, failed session, network defer, maxSyncs
|
workflow-stages-sync-next.test.mjs, sync-ops.test.mjs, sync-ops-base-sha.test.mjs
|
| No spend stops | step machine | a resolver and a re-check run with old spend fields far past the old issue budget and rebase allowance |
workflow-stages-sync-next.test.mjs, workflow-stages-finish-next.test.mjs
|
| Stacked sync | step machine, ops CLI | requests carry stackedOn; unstack after the prerequisite merges |
workflow-stages-sync-stacked.test.mjs, sync-ops-stacked.test.mjs
|
| Checks | step machine, built sweep | wiki-check first; verify; CI pending defers; CI fix reviewed by two lenses; maxCiFixes stop; testsOnly goes to merge |
workflow-stages-checks-next.test.mjs, workflow-stages-checks.test.mjs, workflow-stages-flows-next.test.mjs
|
| Verify | ops CLI | commands, fails-before-fix and its skips, lockfiles, migrations, stacked base |
verify.test.mjs, verify-fails-before-fix.test.mjs, verify-stacked.test.mjs, ci-status.test.mjs
|
| Context limits | stage job | soft steer to wrap up; hard stop becomes a handoff, with a WIP commit on every stop where that is safe, and the job's handoff, note or section that keeps any handoff already there; hard below every writer's budget |
job-context-limit.test.mjs, job-context-wrap-up.test.mjs, job-stop-handoff.test.mjs, config-context-limits.test.mjs
|
| Exit push | stage job | a writer's unpushed commits are pushed on exit; uncommitted paths are recorded as lostChanges
|
job-checkout.test.mjs, ops-stop-push.test.mjs
|
| Writer boundary | stage job | no session gets GH_TOKEN or a tokenEnv variable; only writer sessions get pipeline_commit and its scope (#303) |
job-roles.test.mjs, job-boundary.test.mjs
|
| Role isolation (new) | lint or unit | the role's modules import nothing from the other roles' modules |
roles-implementor-isolation.test.mjs (#414) |
| Implementor tests allowed (new) |
pipeline_commit, machine |
an implementer commit that adds or edits a test file is accepted and pushed |
pipeline_commit built (#303): pipeline-commit.test.mjs, pipeline-commit-push.test.mjs; the machine, none yet |
| Spec tests as written (new) | ops CLI |
verify fails when the spec writer's tests, put back as at specTestsSha, fail; it passes when they pass, whatever tests the implementor added |
none yet |
| Weakened spec test (new) | ops CLI | an implementor edit that makes a spec test assert less still fails verify with that test named |
none yet |
| Wrong spec test (new) | machine |
blocked naming a test sends the issue back to spec with the reason, counted; past the limit it stops |
none yet |
| Machine edges (new) | machine | each row of the four tables: the state, a result, the next state and effect |
implement and start: roles-implementor-machine.test.mjs (#414) |
| No PR ops (new) | machine, stage job | no implementor state sends open-pr, start-pr or close-pr; done with a code change saves at review with the PR untouched |
roles-implementor-machine.test.mjs, roles-implementor-job.test.mjs (#414) |
| No PR text (new) | pure module | the role's schema has no prTitle or prBody, and a reply that carries them still reads as its status and summary (#414 changed this row: a stale prompt or the job's hard-limit reply must not stop the issue) |
roles-implementor-reply.test.mjs (#414) |
| Resume an old state (new) |
locate, stage job |
a state saved by today's implement stage (continuations a number or a record, phase decide) resumes with its count |
roles-implementor-resume.test.mjs, roles-implementor-job.test.mjs (#414) |
| One job (new) | stage job, runner | a job entered at fix with every finding resolved and green checks saves at checks on the way, and ends at accept in one checkout |
built (#424): roles-implementor-fix-checks-job.test.mjs
|
| One job restarts mid-way (new) | stage job, locate
|
a job killed in checks starts again at checks, not fix
|
built (#424): roles-implementor-fix-checks-job.test.mjs, orchestrator-one-job.test.mjs
|
| Finalizer findings (new) | machine, local git | a conflict finding runs the rebase and the resolver in the fix entry's start sync, then checks; a retest finding runs the CI fixer, then checks; both end at accept
|
none yet |
| Sync with nothing to do (new) | machine | a start sync on a branch already on the target's head goes on to the entry's first state with no sync-base and no comment |
built (#419): roles-implementor-sync-job.test.mjs
|
| Sync at edges (new) | machine, stage job |
sync-base runs before any writer session in an implement entry, a fix entry and a resumed entry; a start-sync conflict runs the resolver before the writer; no sync-base runs between continuations, after a clean review or re-review, or in checks after a fix in the same job |
none yet |
| Checks with nothing to do (new) | machine | a head equal to verifiedSha, with nothing moved and no retest finding, skips verify and goes to ci-status (#420) |
roles-implementor-checks-machine.test.mjs, roles-implementor-checks-job.test.mjs
|
| Implementer changed nothing (new) | machine |
done with no code change and failing spec tests stops with the implementer changed nothing
|
none yet |
| Wiki read-only (new) | stage job | an implementor session's wiki edits aren't committed and are recorded in lostChanges
|
none yet |
| Kill anywhere (new) |
locate, runner, fake gh
|
killed before and after each state of all four stages and started again, the implementor ends as an uninterrupted run, with one PR and no lost pushed commits; an interrupted rebase starts over; sessions may run twice | partly: --resume resumes sessions today, and stages restart at begin
|
-
Back to spec: how many times can an issue go back from
implementorfixtospecfor a wrong spec test before it stops? Deferred (roles-6-10-scope): for nowblockedstops the issue, as today. -
Implementor's own tests: should they have to fail before the fix, as
fails-before-fixrequires of every test today? The proposal says no, since the spec's tests already prove the change. -
Checks: does
checksstay here, or doesverifyandci-statusmove to the reviewer or the finalizer as the gate (the finalizer now retests before merging), leaving only the CI fixer here? Today it is onewriterstage. -
Fix rounds: the fixer and re-review share one stage machine (
stages/fix.ts,rereview.ts). Splitting them means a new handoff stage, such asrereview, and moving the round count. With the lifecycles (proposed), the re-reviewers are the reviewer role's sessions running inside this role'sfixlifecycle; a separaterereviewstage would give one role per job, at the cost of a job start per round. -
CI-fix review: the correctness and scope lenses run inside
checks. Should they become a reviewer stage, or stay embedded? - Wiki conflict resolver: proposed: the finalizer sends wiki conflicts to the spec writer, the wiki's writer, not here.
The requirements above take in the wiki decisions this role follows, as of wiki f1b2944 (2026-10-16): implementer-handoff, bounded-fix-loop, conflicts-never-stop, verification, wiki-flows, stacked-branches, linked-draft-prs, worktrees-and-branches, wiki-docs, spec-reads-practices, flakes-are-failures. From this split, not on the wiki yet: tests-added-derived, spec-names-test-interfaces, spec-writes-failing-tests, implementor-reads-wiki, pipeline-commit, pr-text-derived (awaiting Axlog's answer) (decisions in the owner's notes, not public). From this split, on the wiki: sync-at-edges. Once the specs are approved, prune-wiki-decisions deletes or trims the wiki sections the specs make redundant.