f107ff5 (idea-board -> profile promotion) moves to RETRO. Plan-comparison view marked
BLOCKED: no plan-candidate event exists server-side (only workflow.proposed), so there
is no wire shape to render or unit-verify until a multi-candidate planner lands.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote an auto-captured idea from the cross-session board into the curated per-repo
profile at <workspaceRoot>/.correx/project.toml as a `conventions` entry. New
IdeaPromotedEvent tombstones the idea off the board (IdeaReader treats it like a
discard) and records the promotion as a fact; the server PromoteIdea handler loads
the profile, appends the idea text (deduped via ProjectProfile.withConvention), and
writes it back. Adds ProjectProfileWriter (escape-aware TOML serialize/write) and
makes SimpleToml's reader unescape \\ and \" symmetrically with the writers
(fixes a pre-existing read/write asymmetry surfaced by the round-trip test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ab7e4be (egress activation) and 4b0024f (batch source-list approval) move to RETRO;
§D is now fully landed except the standalone web-approval client (needs network/SearXNG).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /sessions/{id}/approve-sources extracts distinct hosts from a source list
(SourceListHosts.extract) and emits one EgressHostsGrantedEvent for the session, so
the live per-session egress allowlist auto-clears subsequent web_fetches to those
hosts — approve the list once instead of per-URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ToolCallAssessmentInput gains sessionEgressHosts; SessionOrchestrator resolves it at
the assessment build site by folding EgressHostsGrantedEvents through
EgressAllowlistProjection. NetworkHostRule now enforces session-granted hosts (union
with static), replacing the emptySet() TODO — the allowlist built in 027ff1f is live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PlanStage gains a `writes` field (planner-declared workspace-relative paths/globs);
PlanDerivedManifest.deriveAllowedWrites + combine union it with the static baseline.
ExecutionPlanCompiler wires the union into StageConfig.writeManifest, enforced by the
existing ManifestContainmentRule via the same glob matcher (no parallel matcher).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pending approvals are a navigable queue (PendingQueue/PendingIdx; ↑↓/jk, count
shown), no modal stacking; resolve removes the specific gate by request id
- y/n/e keys (approve/reject/steer); T0–T2 act on one key, T3+ requires arm+confirm
("press y again"), any other key disarms; reject/steer/auto never gated
- snapshot restore now rehydrates the full pending list, not just the first
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Table-driven render assertions (37 cases over 42 protocol Type* constants), driven
through applyServer and asserted ANSI-stripped on stable defining text. Coverage
guard parses every Type* from protocol.go and fails if a kind is neither covered
nor explicitly classified non-rendering. Pins width/theme/timestamp; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
R opens a navigable overlay of recent sessions (GET /sessions: short id, status,
workflow/stage, relative age); enter resumes via POST /sessions/{id}/resume, which
replays onto the existing global /stream (no WS re-dial). Fetch/resume errors surface
in the overlay, never panic. stdlib-only (ws.Client.HTTPBase derives the base URL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renderMarkdown wraps charmbracelet/glamour (dark style, per-width memoized) and is
hooked into the router chat-turn render path; falls back to plain content on any
glamour error so the TUI never crashes or loses text. Other roles/UI untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`go 1.26` is not a fetchable/released toolchain, so released Go cannot build the
module ("toolchain not available"). 1.24.2 is the minimum the deps require
(charmbracelet/x/ansi needs >= 1.24.2) and is a real toolchain; the directive is a
floor, so newer Go still works. Bump if a specific newer release is intended.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pause narrations are tagged with a pauseKey; a per-session pending-pause set gates
the lane worker so a resolved/resumed pause is skipped instead of blending with the
current status. ApprovalDecisionResolved drops the specific request's pause;
OrchestrationResumed clears all session pauses. Replay-safe (no recorded events change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StaticFinding/StaticFindingSeverity + StaticFindingsRecordedEvent (registered + test)
- StaticAnalysisRunner over an injectable CommandRunner; parses compiler/ktlint/detekt
finding formats (lenient) into StaticFindings
- excludeStaticFindingsFromReview pure filter, live-wired into SessionOrchestrator
context assembly so static-tool findings are kept out of the reviewer's context
- static_check pipeline stage left as a documented TODO (needs an event-emitting
deterministic stage-type seam that doesn't exist yet)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- EgressHostsGrantedEvent (registered + serialization test): additive per-session
egress grants (e.g. an approved research source list)
- EgressAllowlist pure union helper + EgressAllowlistProjection (folds grants per
session); NetworkHostRule delegates to the union, preserving exact/suffix semantics
- rule not yet fed the session set live (ToolCallAssessmentInput has no sessionId);
precise TODO(wiring) left. batch fetch-approval skipped (needs approval-flow surgery)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- promote research fetch quality + content_sha256 from ToolExecutionCompletedEvent
metadata to first-class SourceFetchedEvent / LowQualityExtractionEvent (registered
+ serialization test); existing metadata write kept intact (additive)
- emitted from SandboxedToolExecutor on research-fetch tool completion, guarded on
the url/content_sha256/quality markers; reuses the extractor's LOW_QUALITY marker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PossibleContradictionFlaggedEvent + RelatedDecision (registered + serialization test)
- ArchitectContradictionChecker: L3 similarity retrieval over prior decisions,
threshold-gated, fake-testable; non-blocking (surfaces candidates only)
- live wiring left as documented TODO (needs an architect-decision emit hook +
in-session decision embedding into L3)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StageCheckpointPassed/FailedEvent (registered + serialization test): per-stage
reconciliation of produced artifacts vs the locked plan's produces-slots
- StageCheckpointReconciler (pure) + tests
- emitStageCheckpoint wired in after verifyProduces, runs regardless of its
pass/fail (verifyProduces still owns the halt); gated on an ExecutionPlanLocked
in the session log so non-plan workflows are unaffected
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- BriefEcho model + BriefEchoMismatchEvent (registered + serialization test)
- BriefEchoExtractor: parses the planner's brief_echo block and the original
brief's referenced-files/symbols/acceptance-criteria dimensions
- BriefEchoComparator: conservative diff — a dropped acceptance criterion or an
invented file path halts; dropping a file/symbol alone does not
- checkBriefEcho gate chained into the post-stage flow after groundBriefReferences;
opt-in via stageConfig.metadata["briefEcho"], fails the stage (retryable) on
divergence with re-grounding feedback
- prompt/workflow activation (planner.md emitting brief_echo; role_pipeline
metadata) left as live-QA follow-up
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standalone SceneValidator(content) -> ValidationReport: header well-formedness,
ExtResource/SubResource id resolution, node parent-path integrity. Not wired into
any pipeline (validation layer only; scene editing is a later epic). (BACKLOG I)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conservative type-declaration pattern (class/interface/struct/enum/record);
no method capture to avoid false positives. (BACKLOG I)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- EventStoreLatencyProbe: times a side-effect-free lastGlobalSequence read,
wired into HealthMonitor; HealthConfig.eventStoreWarnLatencyMs (BACKLOG A §4)
- LlamaServerHealthProbe: liveness + tokens/sec degradation trend, injectable
client (HttpLlamaLivenessClient over Ktor); deterministic unit tests
- llama probe left unregistered (TODO) — HttpClient not in monitor scope yet
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- repomap: tag prefix match requires trailing ':' so /repo can't match /repo2
(L3RepoKnowledgeRetriever filter + ProjectMemoryService write; the existing
existsByTurnIdPrefix check already included the delimiter)
- WorkspaceStateProbe fingerprint joins entries with newline to match docstring
- regression test for /repo vs /repo2 non-collision
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The repo-map turnId tag and the L3 retriever filter used 'repomap:$repoRoot' with no
delimiter, so a retriever for /repo also matched /repo2 entries (startsWith). Add a trailing
':' to the tag (no-stateKey branch) and the retriever filter so the repoRoot boundary is
explicit. Test: /repo retrieval no longer leaks /repo2 entries, still matches its own
with/without a stateKey suffix.
RETRO.md never existed, so every shipped/pending-QA item piled up in BACKLOG. Create it
(Pending live-QA + Landed sections) and move all shipped code there. Rewrite BACKLOG flat,
grouped by actionability (active epic / ready / blocked / won't-build) instead of by spec,
one line per item.
First slice of the plan-generation pipeline epic. A pure-Kotlin PlanLinter runs over the
compiled freestyle ExecutionPlan (WorkflowGraph) before it is locked, complementing
ExecutionPlanCompiler (which throws on unreachable/unknown-kind/bad-edge) with the checks
it does NOT make:
- HARD unproduced_need — a stage needs an artifact no stage produces. Real gap: only the
TOML loader checked this; the freestyle compiler did not, so such plans compiled and
failed at runtime.
- HARD trap_state — a stage with no path to the terminal (inescapable loop / dead end).
Uses terminal-reachability, NOT "any cycle", so legitimate verdict-gated review loops pass.
- SOFT stage_count / fan_out / empty_brief / duplicate_brief — scored, never blocking.
FreestyleDriver lints after compile and records PlanLintCompletedEvent (pure function of
the recorded plan → replay-safe, no env observation in v1); a hard failure rejects the plan
(ExecutionPlanRejectedEvent source="lint") before the operator is asked or the plan locks,
so a broken plan never executes. Pays off now for single-plan freestyle; becomes the
per-candidate filter when multi-candidate generation (Slice 2) lands.
Deferred to later slices: file-path grounding + symbol resolution (needs an index),
token-budget/ADR-bypass checks, and a dedicated :core:planning module.
Run operator-configured static-analysis commands (compiler/detekt/formatters) as a
deterministic harness step on the producing stage, before the LLM reviewer. A stage
declares `static_analysis = [commands]`; after it produces its artifact, each command
runs in the workspace root and the results are recorded in a StaticAnalysisCompletedEvent
(invariant #9 env observation — recorded live, folded on replay, never re-run). A
non-clean command fails the stage retryably with its output fed back verbatim (§2:
compiler/test output is ground truth), so the implementer fixes it and only static-clean
code ever reaches the reviewer. This makes §5's "reviewer context excludes static findings"
mechanical — the findings are resolved upstream, so the reviewer can't waste inference on
what detekt catches for free; no output-parsing, no context plumbing.
- StaticAnalysisCompletedEvent + StaticAnalysisFinding (core:events) + registration.
- StaticAnalysisRunner seam + ProcessStaticAnalysisRunner (whitespace-split argv, stderr
merged, timeout→nonzero exit) in core:kernel; wired (nullable) into OrchestratorEngines
and the server. Null runner / no workspace root → logged no-op, never blocks the run.
- StageConfig.staticAnalysis + `static_analysis` TOML field; runStaticAnalysis folded into
a runPostStageGates sequence (produces → grounding → echo → static analysis).
- Documented `static_analysis` on the role_pipeline implementer stage (commented; commands
are workspace-specific). The reviewer prompt half shipped in 3467826.
New brief_echo stage before the planner in role_pipeline: the model restates
the analyst brief as a structured artifact, and a deterministic gate
(checkBriefEcho, opt-in via brief_echo=true) diffs the restatement against the
original brief. On divergence — dropped requirements (Jaccard coverage < 0.34)
or hallucinated files — it emits BriefEchoMismatchEvent and fails the stage
retryably, so a misread brief never reaches plan generation.
The diff (BriefEchoDiff) is a pure function of two recorded artifacts, so it is
replay-safe and records no observation; the event fires only on mismatch, for
audit. Symbols are recorded but non-blocking in v1. Mirrors the groundBrief-
References gate. role_pipeline only; freestyle_planning wiring is a follow-up.
Runtime install (like research): copy brief_echo.json + brief_echo.md and the
[[artifacts]] block into ~/.config/correx.
Flip §A live health TUI pane to pending-live-QA (fd67a6c), register the QA
gate in §F, add docs/qa/QA-health-tui-pane.md. Note that §4 health-checks is
now feature-complete in code (3 probes + pane) — retire as a unit once QA passes.
Surface the health subsystem (llama/event-store/disk probes) in the TUI,
mirroring the session-stats pane: ClientMessage.GetHealthChecks ->
ServerMessage.HealthChecks (health.checks, reuses HealthReport) ->
StreamQueries.healthChecks via HealthInspectionService; Go OverlayHealth on
key H + palette 'health checks', pull-based fetch, per-subject status with
degraded loud. Empty/disabled -> visible fallback (no blank/lie). Go+Kotlin
golden tests pin the wire contract field-for-field. Observability spec §4/§3.
Third HealthProbe (after disk + llama): times a cheap lastGlobalSequence()
read as an event-store responsiveness heartbeat — DEGRADED on throw or when
latency exceeds eventStoreLatencyWarnMs (default 500). Read-only by design
(no probe writes into the source-of-truth log; Invariant #1). Plugs into the
existing HealthMonitor; no new events. Observability spec §4.
Also wraps a long line in LlamaServerHealthProbeTest to clear a detekt
MaxLineLength finding introduced in aaf896d.
Second HealthProbe (after disk-watermark): liveness via GET /health on the
configured llama base URL + tokens/sec degradation watch derived from recorded
InferenceCompletedEvents (windowed average vs configurable threshold, opt-in via
llamaTpsWarnBelow). Plugs into the existing HealthMonitor edge-detection loop;
no new events. Observability spec §4.
Synthesize the five 2026-06-13 design specs and session memory into a
single outstanding-work list. Header rules: resolved items move to
RETRO.md; every finished feature gets a live-QA plan before it counts as
resolved. Add docs/qa/TEMPLATE.md as the observable-evidence QA form.
OverlayIdeas (opened with I, global/cross-session) lists the idea board in the
artifact-viewer shell: ↑↓ to move, x/d to remove (sends DiscardIdea, optimistic
drop), esc to close. Pull-based via ListIdeas -> idea.list.
ListIdeas -> idea.list (StreamQueries folds the board from the log, replay-neutral)
and DiscardIdea -> append IdeaDiscardedEvent landed in the capturing session, then
reply with the refreshed board. IdeaDto on the wire.
The rubber-duck path emits a fenced {"ideas":[…]} block; Ideas.parse strips it
from the chat turn and onUserInput records an IdeaCapturedEvent each (CHAT only).
IdeaReader folds eventStore.allEvents() into the active cross-session board, and
DefaultRouterContextBuilder injects a capped "## Ideas" entry — router-only, so
workflow stages never see it. Extended SYSTEM_PROMPT option (d) to capture ideas.
IdeaCapturedEvent(ideaId, sessionId, text, timestampMs) + IdeaDiscardedEvent
(ideaId, sessionId) tombstone, registered in eventModule. The cross-session idea
board is rebuilt from these via eventStore.allEvents(); discard hides without
deleting (invariant #1).