QA runs clear frontend/ before each run, taking node_modules with it. With #263 making the terminal build gate actually fire, the gate would fail on absent dependencies instead of on the code it exists to verify. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
Sprint: "Close the loop" — 2 weeks
Dates: 2026-07-23 → 2026-08-06 Source: Vikunja Correx project (id 4), 30 open tasks reviewed Theme: Real per-stage validation, warm-discovery freestyle runs, robust orchestration under failure
Goal 1 — Real per-stage validation: land the LSP gate + fix build-gate wiring
Why. Multiple freestyle QA runs hit the same wall: the build-gate only fires at the terminal stage, so 8-10 hopeful writes stack before the truth-check, and when it fails there's no budget left (#167 epic calls this the "open-loop" disease). #80 is the filed fix and its deferral condition was observed on 2026-07-18 run 4a41417b — promote now.
Tasks.
- #80 [EPIC] LSP validation gate — real per-stage gate + terminal typecheck replacement. Design finalized in the task body; break into the 5 sub-tasks listed there. Container for the work below.
- #310 ✅ LSP diagnostics runner: anchor read on server readiness, not the 750ms timer (kills phantom unresolved-import).
- #311 ✅ LSP gate: lint-class diagnostics (unused/deprecated) must not fail the run — classify by
DiagnosticTag, not severity.
- #263 ✅ Auto build-gate never fires on real freestyle scaffold — promotion attaches to no reachable stage. Blocks the terminal safety net #80 doesn't replace.
- #267 Verify build-gate actually fires + re-scope #40 (LSP obsoletes the typecheck alias) — one live run settles both. Acceptance gate for the whole goal; do last.
Exit. One live freestyle run where every write stage gets LSP diagnostics scoped to its write blast-radius, terminal build-gate fires, and a stranded-scaffold case goes to recovery instead of WorkflowFailed.
Sequencing.
- Mon: #80 sub-task 1 (cheap floor — populate the existing per-stage
static_analysisseam with file-local one-shots in the freestyle compiler). Same-day ship; de-risks everything below. - Week 1: parallel track on #263 (build-gate wiring).
- Week 2: #80 sub-tasks 2/3/4 — LSP4J wiring, pull-diagnostics, server pool, per-stage integration.
- Week 2 tail: #310, #311 (readiness anchor + DiagnosticTag classification), then #267 as the acceptance live run.
Goal 2 — Make freestyle runs start warm and converge: discovery prompts, impl decomposition, ACR
Why. The kernel executes what it's told; near-term leverage is what it's told. #260/#261 sharpen upstream (discovery + per-feature stages), #305/#306 make run N+1 actually carry forward from the event log — the two halves of the ACR thesis still unimplemented. #297 kills a budget-burning failure mode mid-discovery.
Tasks.
- #260 Freestyle SDLC: prompt-only upgrades — exhaustive discovery prompt + analyst DoD artifact (the contract the rest of the run is judged against).
- #261 Freestyle SDLC: decompose impl stages into features/sub-tasks (structural, deferred — but it's what makes per-stage LSP from Goal 1 actually bound blast-radius).
- #305 ACR: accrete task knowledge externally so discovery starts warm (model-agnostic) — warm-start half of #168's remaining follow-on.
- #306 Sticky ACR steer-away hint: fires every turn + coarse signature collapse — makes delivered concepts usable; without this ACR delivery is noise.
- #297 Analyst CoT indecision loop burns full reasoning budget, emits nothing — recovery hygiene in the stage the goal is sharpening.
Exit. A second freestyle run on a fresh repo where DoD is recorded, impl stages are feature-bounded (so Goal 1's LSP scope is real), and the discovery stage carries accreted task knowledge from a prior run on an adjacent repo.
Sequencing.
- Day 1 (parallel): #260 — files-only, doesn't block on anything.
- Week 1 mid: #297 (analyst CoT fix — same stage family #260 touches).
- Week 2: #261 (impl decomposition — depends on #260's DoD existing to decompose against), #305/#306 (ACR — pairs with the warmed discovery stage).
Goal 3 — Orchestration & recovery robustness: stop the unrecoverable kills and runaway recoveries
Why. Two failure modes currently end runs that shouldn't end: a single provider going down mid-run (#299), and the recovery stage burning its budget against a stale failure-cap it can't clear (#304). Both waste full event logs. #307 is the cheap observability floor that makes the rest debuggable.
Tasks.
- #299 Single provider death → unrecoverable session kill (
NoEligibleProvideron retry) — re-route, don't die. - #300 HealthMonitor detects provider loss ~18s too late (reactive, not gating) — gates #299's recovery path on a fast signal.
- #304 Recovery stage runs expensively then run dies on stale failure-cap — wasted work; the cap must reset on the recovery's own progress.
- #309 Recovery stage: apply same-fingerprint loop-breaker + repair-ledger — the runaway root cause; closes the loop Goal 3 started. Now lands on top of #312/#313:
recoveryTicketis USER-role in the trailing slot at highest precedence, and the loop-breaker is the agreed place to escalate a repeated tool failure out of tool-role (per-failure sites stay tool-role). - #307 Observability: no event records the assembled stage-context manifest — cheap event, makes every above failure diagnosable post-run.
Stretch (if #307 lands fast): #308 Background-process execution + monitor tool for long-running shell commands — unblocks real test gates but not load-bearing for the goals above.
Exit. A live run where provider downtime is logged + recovered around, and a recovery stage that either converges or breaks the loop with a recorded repair ledger rather than dying on a stale cap.
Sequencing.
- Week 1: #307 (manifest event — cheap, unblocks debugging of everything below).
- Week 2: #299 + #300 together (provider-death path), #304 + #309 together (recovery runaway path).
Cross-goal sequencing
| Week | Track A (validation) | Track B (freestyle content) | Track C (robustness) |
|---|---|---|---|
| 1 M | #80 sub1 — static_analysis seam populated | #260 — discovery prompts | #307 — manifest event |
| 1 W-F | #263 — build-gate wiring | #297 — analyst CoT loop | — |
| 2 M | #80 sub2/3/4 — LSP4J + server pool + per-stage | #261 — impl decomposition | #299 + #300 — provider death |
| 2 W | #80 sub4 — blast-radius filter | #305 + #306 — ACR external + sticky hint | #304 + #309 — recovery runaway |
| 2 F | #310, #311, #267 — readiness anchor + tag class + acceptance run | — | — |
Goal 1 progress — 2026-07-26
- #310 ✅ (commit
a95475be).awaitDiagnosticswaits for one push per URI then a quiescent period anchored to the last server publication (awaitAll+awaitQuiet), not a 750ms timer started atdidOpen. Kills the half-loaded-project phantom unresolved-import. - #311 ✅ (commit
f61864ff).LspDiagnostic.tags(lowercasedDiagnosticTagnames) is carried from LSP4J through the event;SessionOrchestratorGates2gates onseverity == error && !isLint. AnoUnusedLocalstsconfig promoting TS6133 to error no longer hard-fails a run that no rewrite could clear. Lint diagnostics stay recorded and visible, just non-gating. Classification by protocol tag, not a TS-code whitelist. Test:core/events/.../LspDiagnosticTest.kt. - #263 ✅ (commit
867e99d1). Two findings on trace:- The reported selection bug was already fixed by
159b3f1e(#277) —autoGateStagesis every write-declaring stage plusterminalStageId(plan), so a non-writing review terminal is gated andrunExecutionGatepromotes it to PROJECT off the realFileWrittenmanifest. The 2026-07-18 evidence was stale. - The hole that remained: any stage declaring
build_expectation: project|testszeroed the whole auto-gate set, so a plan building at stage 3 of 9 had nothing verifying the six stages written after it. A declared build now suppresses only the redundant per-writing-stage gates; the terminal floor always stays. - Left deliberately: the gate chain still short-circuits before the execution gate when the contract gate fails. The stage fails either way — cheap gates first, no COMPLETE-lie.
- The reported selection bug was already fixed by
Goal 1 remaining: #267 — the acceptance live run. Fold the unverified #312/#313 trailing-mandate check into the same run.
Precondition, handled. #191 (dependency resolution before scaffold accept) closed on 2026-07-21:
runSetupCommand runs the profile alias setup before every build gate and #40 resolves it per
toolchain. But setup is operator-declared, and the repo profile didn't declare one — so with
frontend/ cleared before each QA run, the now-firing terminal gate would run npm run build against
an absent node_modules and fail on deps instead of on the code. Added
setup = "npm --prefix frontend install" to .correx/project.toml (install, not ci — a fresh
scaffold has no lockfile). #267 needs nothing further. The general case — a workspace whose operator
declared no setup — is #314 (toolchain-default fallback).
Landed out-of-band — context message-type sweep (#312, #313)
Not in the original three goals; pulled in on 2026-07-26 because it is upstream of Goal 1's gate
verdicts and Goal 3's recovery tickets — both deliver their findings through the context builders
this touched. #312 was listed as deferred-behind-#307; that turned out to be unnecessary, placement
is statically determined by PromptRenderer + each producer's role, so no run ground truth was needed.
Rule established: the system block carries only what does not change during a run. Anything the
run mutates is a user message — a mutating system prefix defeats prompt caching, and models
under-weight system-folded content against the trailing user turn. role = which chat message type,
layer = pinning/prune eligibility; those were tangled and are now separate.
- #312 ✅ Audit — report at
docs/audits/2026-07-26-context-role-audit.md(commit514aeae7). - #313 ✅ Implementation (commit
a4f6cf05,./gradlew checkgreen). Ten entries re-roled SYSTEM→USER; trailing repair-mandate slot now emits exactly one mandate by precedence (recoveryTicket > retryFeedback > groundingFeedback > rejectionFeedback) withremainingDeltaappended, so the slot stays scarce as members were added. - Bug fixed en route: the renderer's
layer == L0clause was overriding role on four L0+USER packs —InferenceSummarizer,SemanticReviewerImpl,CapabilityGapReflectorImpl, Talkie session-naming — all four were sending a system-only request with no user turn at all.
Not verified live. Needs one freestyle run to confirm the trailing mandate lands as intended. Fold into the Goal 1 acceptance run (#267) rather than spending a separate run.
Intentionally deferred (seen, not dropped)
| ID | Title | Why out |
|---|---|---|
| #167 / #168 | Closed-loop + ACR design epics | Bodies marked IMPLEMENTED for landed slices; remaining work folded into Goal 1 (#80) and Goal 2 (#305/#306). Keep open as epic containers. |
| #193 | Frontier-parity design-review round | Design/review work, not a 2-week deliverable. Next cycle after Goals land. |
| #31 | Interactive workflow creation TUI/web-ui | Visible polish; not load-bearing for run reliability. Schedule its own sprint. |
| #265 | TUI clarification modal not dismissed on external resolve | TUI cluster; pair with the next TUI sprint. |
| #295 | TUI token usage display for router/talkie | TUI cluster. |
| #296 | TUI execution plan viewer | TUI cluster. |
| #298 | TUI output: show CoT/reasoning on artifact + tool-call turns | TUI cluster. |
| #301 | Escalate repeated scope/manifest write-block to user approval | Falls under Goal 2 once DoD lands; premature now. |
| #302 | Mid-run hard steering (drop inference, inject operator message, restart) | Bigger surface; pairs with the steering-channel design, post-Goal 3 reliability. |
| #303 | Auto-repair collapsed-argv shell calls | Nice-to-have shell hygiene. |
| #25 | Backlog (deferred/spec-level from memory) | Meta-task; verify-against-code before any sub-item is promoted. |