유대선
프로젝트로
·기술 회고·3

Turning a stderr line into a tracked WorkItem (terminal fix-card → Task)

A terminal error you select in a pane now becomes a first-class WorkItem with attached evidence, built across three parallel slices that deliberately split detection, projection, and the graph-writing UI.

The smallest unit of work in a terminal session is an error scrolling past. The instinct is to fix it and move on; the loss is that the error never becomes anything you can track, assign, or cite later. This change closes that gap: select a real error in a pane and you can promote it into a tracked WorkItem with the terminal evidence attached.

It shipped on main across three parallel slices on 2026-06-17, each kept narrow on purpose so the risky part (writing graph records) landed last and alone.

LE02 — detection only (c7e6202). deriveTerminalFixCard in apps/desktop/src/terminalFixCards.ts is a pure function that maps terminal-like text to a small fix-card. It recognizes a fixed set of categories — port conflicts (EADDRINUSE, "already in use"), missing modules (TS2307, "Cannot find module"), cargo test failures, permission errors, timeout/rate-limit output, build/typecheck diagnostics, and a generic runtime fallback. Each rule emits a suggestedNextLocalAction that proposes the next local inspection step — "check which process owns port 1430, then decide" — never a command to run and never a claim of a fix. It reuses the existing terminal-selection ghost, so copying a real error now also shows Fix card: Port already in use. No auto-popup on output stream; surfacing is selection-driven to avoid noisy UI.

LE05 — projection only (4c61b8d, payload aligned in 6305560). deriveTerminalFixCardWorkItemProjection turns a fix-card into two safe inputs: a workItemCreateInput for workitem_create, and an evidenceAttachInput for workitem_attach_evidence. It threads source context — pane id, session id, transcript path, command excerpt, cwd, capture time — and stamps a heuristic confidence (low / medium) plus an explicit confidenceNote, so a guessed category never reads as proof. The body text carries "No fix was executed" literally. Critically, this slice writes nothing — no command execution, no graph records, no WorkItemSpine rewrite. It's a pure model the later UI can call.

LE06 — the UI that actually writes (344b0f8). This is where the chain reaches the graph. TerminalPane.tsx gains a Create WorkItem action on the fix-card ghost; createTerminalFixCardWorkItemFromSelection (in terminalFixCardWorkItems.ts) calls the existing Rust workitem_create then workitem_attach_evidence Tauri commands. Those Rust handlers do store.append(...) into the GraphStore — so the WorkItem and its evidence become real, persisted graph nodes with provenance and source context preserved. terminal_error was added as a first-class source in the founder and coding-ops packs.

The design lesson is the three-way split. Detection, projection, and graph-writing are three different risk classes, so they shipped as three slices with different blast radii. LE02 and LE05 are pure TypeScript with focused tests (low risk by construction). Only LE06 touches the graph — and even it executes no commands. That ordering meant the dangerous capability (persisting nodes from parsed terminal text) landed last, behind a fully-tested model, instead of being smeared across every slice.

Two honest gaps. The prompt named WorkItemSpine.tsx, but it isn't in any changed-file list — the write path goes straight through the existing workitem_create Tauri command, not the spine. And none of the three slices ran a live Tauri GUI smoke; verification was tests + typecheck + biome only (LE06: 41 files / 253 desktop tests). The manual overlay ergonomics remain unverified in the real app.