You picked nine actions out of the dispatch. They looked like nine unrelated bugs and were not — every one is the same shape, something reporting success while doing nothing, or doing it to the wrong target. So Phase 0 came first: a script that verifies the built artifact instead of the exit code, because eight more builds were about to be trusted and that trust had already failed twice.
It caught a real state on its very first run. All nine are implemented and pushed across three commits; nothing has been observed at runtime.
The problem
951 Chronicle records rested on two try? calls, and the
destructive path was armed — the hot file had crossed the 150-record threshold that runs it.
A lost ~100-byte sidecar made the whole archive unreachable; an undecodable archive turned
951 records into an empty array that was then written back atomically and the sidecar
rewritten to match, erasing the evidence.
Alongside it: the previous day's blueprint scoping fix had silently broken a caller
that read the cache it now clears asynchronously; four directory scanners decoded into types
where every field defaults, so they could not reject a foreign file; Chronicle aggregates
computed over 169 of 1,120 records while presenting as period summaries; one bridge import
fired ~16 @Published publishes, each making every widget re-sort its full array;
and ProjectStatus.color returned two colours that were byte-identical to
semantic ones, via aliases that shadowed SwiftUI's own members.
Approach
Verify the artifact before trusting any of the other eight
Phase 0 resolves the product from -showBuildSettings, then asserts it
exists, that its mtime advanced, and that it postdates every modified .swift
file. It stats the newest file anywhere in the bundle — measured on this machine,
the .app directory was 1.6 days stale and Info.plist 12 hours
stale on a build that was genuinely current, and this is a debug-dylib config so the 58 KB
executable is not the witness either.
Prove the check can fail before relying on it
A verification that cannot fail is the bug being fixed. All three paths were exercised: no product (Release, unbuilt), product older than modified source (temporary edit), and compiled-but-not-running — which fired naturally on the first real build.
Positive identity checks, not filename denylists
A denylist catches today's sidecars and nothing else. Where a store writes
<id>.json, requiring the decoded id to equal the file stem is invariant
by construction and free. Applied to blueprints, journeys, mockup sessions and specs.
Fix the trigger, then the multiplier
Batching the importer's task writes into one transaction cuts the publishes; memoizing the widgets cuts what each publish costs. Doing only one of the two leaves the other dominant.
Decisions made for you
The colour aliases were replaced, not deleted outright
Deleting Color.indigo as literally specified would have repointed ~95 call
sites at SwiftUI's system indigo, and replacing them with cmPurple
(#9B7BB5) would have repainted most of the Foundation editors from bright
indigo to muted lavender. That is a design decision, not a bug fix, and not one I can
verify without running the app. Instead a non-shadowing cmIndigoAccent was
introduced at the same hex, all sites migrated, then the shadowing aliases deleted.
Shadowing is gone, appearance is unchanged, and whether that bright value should join the
muted palette is still yours to decide.
Two palette collisions were left open and named
ProjectStatus is now disjoint from the semantic set — which is where the
misreading happens — but .planning still shares #D4915C with the
gold slot and .development shares #6366f1 with indigo. Closing
those needs a project palette guaranteed disjoint from everything else. Documented at the
call site rather than papered over.
Two dead blueprint APIs deleted rather than gated
loadCustomSubsystems(for:) and loadScanHistory(for:) assigned
shared caches from an arbitrary project's file and had zero callers. A public API that can
put one project's data into another's working set is a trap even when unused.
The archival pass now aborts rather than repairing
On an unreadable archive it leaves the hot file untouched and moves nothing, so records stay hot until the archive is fixed. Preferred over any automatic repair: silently rewriting a file you could not read is how the original bug worked.
The work
Phase 0 — Verify the artifact, not the exit code (a30)
9/9 done Built first on purpose — it is what proves the other eight shipped.- Create
scripts/verify-build-product.sh— resolve the product fromxcodebuild -showBuildSettings -json(BUILT_PRODUCTS_DIR+FULL_PRODUCT_NAME), never a hardcoded DerivedData hash - Record the newest mtime of ANY file inside the
.appbefore the build — not the.appdir and notInfo.plist, both of which go stale on incremental relinks - Fail on: nonzero exit; no product on disk; mtime did not advance; product older than any modified tracked
.swiftfile - Add a distinct exit code for "compiled but not live" — compare the running process start time (
pgrep -x+ps -o lstart=) against the binary mtime - Downgrade the unchanged-mtime case to a warning so a clean-tree no-op rebuild does not false-fail
- Rewrite step 4 of
Code Raven/Resources/Commands/cr-complete.mdto run the script and setbuildSucceededfrom itsRESULT:line — edit the BUNDLED copy, not~/.claude/commands/ - Add an explicit
UNVERIFIEDstate to the terminal summary's Build line (line 234) - Point
.claude/commands.json's "Verify Build" command at the script - Run the script against the current tree and show the output
Phase 1 — Close the two Chronicle archival holes (a1)
6/6 done Highest consequence. The destructive path was armed when this started.- Hole 1a:
archivedRecordCount(for:)(:177-182) must fall back to a real count whenarchive-meta.jsonis missing butarchive.jsonexists on disk - Hole 1b:
ensureArchiveLoaded(:507-517) must NOT insert intoarchiveLoadedProjectswhen the decode failed, and must surface the failure - Hole 2a:
runArchivalMaintenance(:552-590) must distinguish "file absent" from "present but undecodable" and ABORT the pass in the latter case - Hole 2b: refuse to write when
existing.count == 0butarchiveMetareports a non-zero count - Write a
.bakofarchive.jsonbefore the first overwrite of each run (the existing backup at :555-558 covers the HOT file only and never refreshes) - Build and verify the artifact with the Phase 0 script
Phase 2 — Repair the caller the blueprint fix broke (a3)
3/3 done A regression introduced by the previous day's fix.FeatureClusterGenerator.loadBlueprints(for:)(:455-461) → usestore.storedBlueprints(for:), and stop touchingloadedProjectId- Sweep every other caller that invokes
loadBlueprints(for:)then readsblueprints/customSubsystems/scanHistoryin the same synchronous scope; report findings even where no change is made - Build and verify the artifact
Phase 3 — Close the three remaining blueprint leak paths (a4)
7/7 done Same subsystem as Phase 2; done in one sitting.markAsVerified(subsystemId:projectId:)(:343-349) → resolve viastoredBlueprint(subsystemId:projectId:)loadCustomSubsystems(for:)(:426) andloadScanHistory(for:)(:547) — delete, or gate the cache assignment onloadedProjectId == projectIdloadVersionHistory(projectId:subsystemId:)(:383-389) — add the sidecar filter and empty-id rejection the other two loaders gotBlueprintBridgeImporter.processFile(_:project:)(:217-313) — delete the dead duplicate, or fix it identically- Make
BlueprintSubsystemMigration(:54-55) referenceBlueprintStore.sidecarFilenamesinstead of its own hand-copied denylist - Replace the denylist guard with a positive identity check: decoded
subsystemIdmust equal the file stem - Build and verify the artifact
Phase 4 — Give five directory scanners a way to say no (a5)
6/6 done Four of five hardened. The fifth was a deliberate no — see Out of scope.CreativeJourneyStore.swift:589—CreativeJourneydefaults everything includingidMockupSessionPersistence.swift:94—MockupSessiondefaultsidandprojectIdImplementationSpecStore.swift:67— files are<sessionId>-spec.jsonbut the stem is never checkedCLIUsageBridgeImporter.swift:61— every field defaults;guard bridge.version == 1at :120 is vacuousFlowMapScanner.swift:128— accepts any.htmlin the scanned folder- Build and verify the artifact
Phase 5 — Make Chronicle rollups archive-aware (a6)
7/7 done Aggregates were reporting 15% of history as if it were all of it.generateRollup(:295-302) — addincludeArchived, threadfullHistoryLoadedfromChronicleView.swift:658-661availableClusters(:285-290) — archive-aware, so a cluster present only in archived records is selectableChronicleView.swift:830-843—allProjectRecords/archivedCount/uniqueGroupCount, and the mismatched subtitle at :846- Decide and handle
lastPatternAnalysisDate(:979-984) andLivingGuideEvaluator.swift:84-88 - Leave
DispatchDigestGate.swift:82-84hot-only — it is correct and documented as such - Where loading the archive is too expensive, change the LABEL rather than report a partial figure silently
- Build and verify the artifact
Phase 6 — Stop the post-import render storm (a7)
7/7 done The trigger and the multiplier, fixed together.- Add batch-write APIs so each importer assigns once:
PlanTaskStore,ChronicleManager.addRecord,PlanSnapshotManager.updateSnapshot,ScratchpadManager.createNote,ToDoStore.upsertFromBridge,QuickTaskManager.createTask - Extend
beginBatchCapture()/endBatchCapture()(:52-80) to cover the@Publishedassignment, or add a parallel mechanism — today it defers disk writes only and no importer calls it - Convert
CheckinBridgeImporter(:198-250),CompletionBridgeImporter(:278, :395),SummaryBridgeImporter(:241, :347, :354, :365) - Memoize
NotesWidget(:206-230) — four uncached properties over ~310 notes, ~16 passes per render - Same treatment for
ToDosWidget(:31-39) andTasksWidget(:259-267) - Memoize
ChronicleView.baseFilteredRecords(:855-872) — currently ~15-20 full search passes per render, ~9× worse after Load full history - Build and verify the artifact
Phase 7 — Make To-Do and Quick Task writes atomic (a8)
4/4 done The only correctness item in the performance list.ToDoStore— async load,Task.detached+options: .atomicon write (:218, :236, :255)QuickTaskManager— same (:1080, :1099, :1125)- Make a decode failure on load distinguishable from an empty file in both stores
- Build and verify the artifact
Phase 8 — Delete the shadowing color aliases (a24)
5/5 done Scope changed on contact — see Decisions.- Make
ProjectStatus.color(Project.swift:152-162) token-based throughout - Delete
Color.tealandColor.indigofromColors.swift(:288, :294) and fix every call site - Fix the two-greens-side-by-side cases:
ProjectIntelligenceDashboard.swift:1512,ProgressMetricsSection.swift:441,ActiveAgentsSection.swift:122 - Report on
azure500 → cmOcean(equalscmInfoandagentRunning) and let Sarah decide - Build and verify the artifact
Manual testing
4 tasks None of this was observed running — GUI verification is blocked from the terminal.- Relaunch and confirm Chronicle still lists records and "Load full history" still appearsyou
- Generate a blueprint in a non-selected project and confirm feature clusters carry real subsystem contextyou
- Run a
/cr-checkinand confirm the overview no longer hitches while the import landsyou - Look at the sidebar and the project cards after the color change — Maintenance and Beta chips especiallyyou
Risks & edge cases
Nothing here was observed running
Every phase is compile-verified via the new script. GUI verification is blocked from the terminal on this machine, so the four manual checks above are the only real proof.
The Chronicle timeline memoization can go stale
It now recomputes on explicit triggers rather than on every render. Every input has one and the comment says so — but the failure mode of a missed input is a timeline that quietly stops tracking a filter. Worth exercising each filter once.
Stores now refuse to save after a failed load
ToDoStore and QuickTaskManager skip writes for a project whose
file failed to decode. Correct — it prevents overwriting data you could not read — but it
means edits in that state are silently not persisted until the app restarts. It logs, and
nothing surfaces in the UI.
CLIUsageBridge now rejects files missing version or type
All 933 files on disk carry both, and every writer emits them, so this should be inert. A hand-written usage bridge would now be refused rather than silently defaulted.
Out of scope
FlowMapScanner was deliberately left permissive
It has the same shape as the phantom-record bug — scan a directory, accept what
is in it — but not the same consequence, and the obvious hardening makes things worse.
There is no identity to verify; hand-authored guides are explicitly supported; and I checked
the real artifacts: requiring a <title> would drop four legitimate
mockups, and requiring a generated marker would drop every guide. The failure mode is a
stray file rendering as a gallery row with its filename as the title — visible and
self-correcting. The reasoning is recorded at the call site.
azure500 → cmOcean reported, not changed
It is byte-identical to both cmInfo and agentRunning, so an
azure project's session bar reads like a running-agent badge. Left for you: unlike the
status-enum collisions this one is a palette decision.
Final review
What this does well. Phase 0 earned its place immediately — it
reported NOT LIVE on the first real build, which is exactly the 14 August failure
caught in seconds instead of a day. The archival fixes close a live data-loss path rather than
a theoretical one. And the sweep in Phase 2 was worth more than the fix it came from: looking
for other callers of the same shape turned up two more, one of them a computed property
re-triggering a load on every render.
Where it is weakest. The Chronicle memoization is the piece I would most want to see running — it is the largest behavioural change in the batch and its failure mode is quiet. The colour migration touched 25 files mechanically; the hexes are unchanged so it should be invisible, but "should be" is doing work there. And Phase 6's batching changed the shape of two importers that write task state, which is the data you most rely on being right.
What I would adjust. If the memoization proves fiddly, the honest fallback is to keep the batching and revert the ChronicleView cache alone — the trigger fix carries most of the win and none of the staleness risk.
High on the data-integrity work — those are small, well-understood changes closing paths whose failure I could reproduce. Lower on the two big mechanical passes: the colour migration and the Chronicle memoization are both correct as written and both would fail quietly if they are not. The score is capped by the fact that nothing was seen running.