linear-implement
The core workflow: take a Linear issue, plan it, then iterate implement → validate → review until the work passes, and commit it on a dedicated branch. Everything stays local — no push, no PR.
- Triggered by:
xiv implement <issueId>(aliasli) - Also runs as a subflow inside:
linear-to-pr(behindxiv ship) andstack-build(once per stack entry) - Source:
pack/workflows/linear-implement.tsx
Task graph
What each task does
| Task | Kind | What it does |
|---|---|---|
fetch-issue | agent | Fetches the Linear issue (key, title, description, acceptance criteria, comments) via MCP into linearIssueSchema. Sets fetched: false on any auth/not-found/transport failure. |
fetch-gate | deterministic | Hard gate: throws when the issue was not actually retrieved. Without it, a fetch error's text would flow downstream and be faithfully "implemented" as if it were the issue. |
plan | agent | Produces { summary, steps[] } from the issue and its criteria. With tdd: true the plan must start with test steps. |
impl:implement | agent | Writes the code, following the plan and citing evidence per acceptance criterion. On later iterations the prompt appends the previous attempt's validation failures and review findings. |
impl:validate | agent | Runs the repo's own lint/test/typecheck commands and reports { allPassed, failingSummary }. It arbitrates on exit codes, not judgement. |
impl:review | agent | Local code review of the working-tree diff against the acceptance criteria (the judgment is the xiv-review-core skill): a verdict (approve / comment / request_changes) plus prioritized findings. |
finalize | agent | Commits all work on the branch feat/<issue-key-lowercase> (created if needed), and reports branch, head SHA, issue key/title, criteria, and a summary. Explicitly never pushes or opens a PR. |
compose-output | deterministic | Merges the finalize report with the final review's verdict and unfixed findings into the workflow result. |
Loop exit condition
The loop exits when done computes true for the current iteration:
done = validation passed AND review did not request changesValidation is the arbiter; the review is advisory. The review may only ever withhold done, never grant it:
request_changes→ not done; the findings are fed into the next implement pass.comment→ non-blocking; done if validation passed (the findings still ride along as feedback if the loop runs again for another reason).approve→ done if validation passed.- Review skipped, crashed, or unparseable → the iteration simply has no review and validation
decides alone. The review task is
continueOnFailand its verdict field has no default, so a reviewer that cannot produce a real verdict fails its task rather than fabricating one — and a failed review can never wedge the loop.
Each iteration is judged on its own outputs: the done reads are deliberately unpinned to the
current iteration, so a request_changes from iteration 1 cannot block iteration 3, and a stale
approve cannot wave a later broken attempt through.
Iteration cap
Default 3 passes of implement → validate → review, from the maxIterations input
(--max-iterations on the CLI), clamped to 1–10. On hitting the cap the loop returns the last
attempt (onMaxReached="return-last") instead of failing the run — whatever the last review still
reported becomes the result's knownGaps. A clean issue exits after one pass, so raising the cap
only costs anything for issues that actually fail a pass.
Inputs and outputs
Input (inputSchema):
| Field | Default | Meaning |
|---|---|---|
issueId | "" | The Linear issue key to implement. |
tdd | false | Force a tests-first plan and implementation order. |
skipAcceptanceReview | false | Skip impl:review entirely; validation still gates every pass. |
maxIterations | 3 | Loop cap, clamped 1–10. |
Output (finalizeSchema — the table named output, which is what subflows consume):
| Field | Meaning |
|---|---|
branch, headSha | Where the committed work lives. Branch is derived from the input issueId (feat/<key>) so stacked builds stay deterministic even if the fetched issue output is unreadable. |
issueKey, title, acceptanceCriteria | Echoed issue facts. |
summary | What was implemented. |
reviewVerdict | The final review's verdict, or null when no review ran. |
knownGaps[] | Findings the final review raised that the run shipped without fixing — { severity, title, detail, path }. Line numbers are deliberately dropped: these outlive the diff they were raised against. |
Model assignment
A balanced mixed pipeline by default (cheap tier shown; every step is overridable per-run via
XIV_*_ENGINE, and XIV_TIER=quality raises the three steps where capability matters — see
/concepts/model-tiers):
fetch-issue,impl:validate— light codex model at low effort: retrieval and running commands, no reasoning to pay for.plan— the codex planning model; tiered by effort (low → xhigh), since plan quality most determines the rest of the run.impl:implement— heavy Claude model (Sonnet 5 cheap / Opus 5 quality): sustained multi-file editing.impl:review— the codex review model at high effort: a false approval ends the loop, so findings must be trustworthy.finalize— pinned to Sonnet 5 with autonomous git permissions and a hard never-merge rule; committing is mechanical, so it never rides the tier ladder.
Failure semantics
fetch-gatefails the run hard on a bad fetch — fix Linear MCP auth and re-run.impl:reviewiscontinueOnFail; nothing else in the loop is. A crashed implement or validate task fails its attempt normally.- The heavy tasks carry 30-minute timeouts with 10-minute heartbeats.
- Runs are resumable:
fetch-issueandplanexecute once, before the loop (their reads are pinned to iteration 0), so a resumed run does not refetch or replan.