A new CorpusContextBuilder turns a project's Press Raven corpus link
into a prompt block naming the guide that feeds it, the files
export-machine.py --write overwrites, and where a content change goes
instead. It rides the prompts Code Raven hands to Claude Code, the expert-meeting
turns, and Mira's always-on project context.
It mirrors DossierContextBuilder exactly, down to the two gates that
make it safe against a live app: it returns an empty string when the project backs no
guide, and one defaults key disables every site at once.
The problem
CorpusIndexService resolves every guide to its working folder, and
CorpusOwnedPathGuard can say whether a given file is written by the
exporter. Both reach exactly two views — the badge in QuickCommitSheet
and the one on a chronicle record — and both fire after the edit already
exists.
Zero prompts carried any of it. A Claude Code session launched into Peregrine
started without knowing that twelve declared outputs under
PeregrineCore/**/Resources are overwritten wholesale by the next export.
Red String's bank was authored in a mockup's JavaScript, copied into the app's
Resources to get the build going, and only declared in the corpus that afternoon;
Target Practice spent longer in the same state before 1 September.
The exporter cannot rescue this on its own. --verify computes drift
symmetrically but only ever prints --write as the remedy, so a hand edit
in a game repo is reported as “the corpus is ahead” and then overwritten.
Approach
One builder, mirroring the dossier's
DossierContextBuilder shipped in September, rides every expert, Mira
and Foundation prompt, and returns "" when it has nothing to say so
callers prepend unconditionally. Copying that shape means the ~40 projects with no
guide behind them see no change at all, and one defaults key turns the
whole thing off without a rebuild.
The preview sheet is the funnel, so it is the only seam for handoffs
CopyPromptPreviewSheet is built from 26 sites and already carries
projectPath. Every NotePlanningPromptBuilder call site —
notes, plan continuations, meeting handoffs, the Mira handoff report, chronicle
continuations, and the two that launch a terminal through
GlobalCaptureCoordinator — arrives through it. One insertion into
effectivePrompt covers all of them, and the block is visible and
editable before the copy.
Three variants, split on cost as much as content
A prompt handed to Claude Code to do work is sent once and needs the full owned-path
list, because the file it is about to edit has to appear in it (~400 tokens). A
conversational surface rebuilds its prompt every turn — five experts across ten rounds
is fifty injections — and takes .advisory, which names the owned-file
count instead of listing paths (~170 tokens). .authoring adds the declared
mini-game roster and the declare-before-you-mock rules, for the mockup pass.
The whole path list, never truncated
Twelve long paths fold on their shared directory prefix so the list reads as one header line plus twelve short rows. Folding rather than truncating is deliberate: the omitted file would be exactly the one that then gets edited.
Decisions made for you
No parameter on NotePlanningPromptBuilder
The plan called for one. Auditing the eight call sites showed every one of them funnels into the preview sheet, so a second injection point would have printed the block twice. The edit was written and then reverted. A containment check on the header marker guards the case anyway, for a prompt whose details were pasted from an earlier one.
No Settings row for the toggle
dossierContextInjectionEnabled has no UI either — it is a
defaults write escape hatch rather than a preference. Adding a row for
one and not the other would read as an inconsistency. Reversible by adding both.
DeveloperBriefingView passed neither path nor id
The audit of all 26 construction sites found one live site supplying no
projectPath, so its prompts could not resolve a corpus block — or a
CR-PROJECT comment. Now supplies both.
The work
Phase 1: The builder
7 tasks · 7 done Mirrors DossierContextBuilder, including its two gates. Lands independently.- Create
CorpusContextBuilderwithinjectionToggleKey = "corpusContextInjectionEnabled"and anisInjectionEnabledcomputed property defaulting to true, mirroringDossierContextBuilder - Add
Variantwith.constraint(owned paths and the prohibition) and.authoring(adds the declared mini-game roster and the declare-before-you-mock rule) - Implement
compactContext(forProjectPath:variant:)returning""when injection is disabled or the project resolves to noCorpusLink - Fold the owned-path list by longest common directory prefix so twelve paths render as one prefix line plus twelve short rows rather than twelve long ones
- Annotate each owned path with the families or mini game it carries, from
CorpusMachineOutput.familiesand.miniGame - Never truncate the owned-path list — an omitted file is the one that gets edited; cap only the guide list
- Add a
Projectconvenience overload so call sites holding aProjectdo not reach for.paththemselves
Phase 2: The preview-sheet seam
6 tasks · 6 done One insertion covers 26 construction sites.- Add a
corpusContextTextcomputed property toCopyPromptPreviewSheetresolving fromprojectPath - Insert the block in
effectivePromptahead of every other context block and immediately after theCR-PROJECTcomment, so a session reads the prohibition before a page of chronicle context - Confirm the block is present in the editable prompt text the sheet displays, not appended invisibly at copy time
- Verify
PromptPreviewPayloadneeds no change — the sheet resolves the corpus itself fromprojectPath - Audit all 26 construction sites for a missing
projectPath, and supply it where absent - Guard against a doubled block by skipping injection when the prompt already carries the header marker
Phase 3: Meetings and Mira
6 tasks · 6 done Per-turn surfaces, so a briefer block.- Add an
.advisoryvariant that names the owned-file count rather than listing the paths - Rename
StandupMeetingService.appendDossierContext(to:)toappendSessionContext(to:)and append the corpus block beside the dossier block - Read the meeting's project from the service's own
projectPathproperty rather than threading a new parameter - Add the corpus block to
MeetingHandoffGenerator.buildHandoffPromptat the.constraintvariant — the handoff is an implementation brief, generated once - Add a
corpusContextfield toMiraProjectContextand populate it inMiraContextBuilder.build(for:threadLastOpenedAt:) - Render the field in
renderFull()above Active plans, and carry it through the token-budget trim rather than dropping it
Phase 4: Consistency with the dossier's escape hatch
1 tasks · 1 done A decision, not a change.- Confirm the toggle matches the dossier convention —
defaults writeonly, with no Settings row, becausedossierContextInjectionEnabledhas none either
Verification
3 tasks · 2 done Build verified; the third item is a runtime check.xcodebuild -scheme "Code Raven" -destination 'platform=macOS' buildsucceedsscripts/verify-build-product.shconfirms the built bundle is freshCorpusContextBuilder.compactContextreturns""for CodeRaven itself and for VentureRaven
Manual Testing
7 tasks Yours — the app has to be relaunched first.- Open Copy for Claude on a note in Peregrine and confirm the corpus block names guide
peregrineand lists the twelve owned paths you - Open Copy for Claude on a note in CodeRaven and confirm no corpus block appears you
- Confirm the block sits ahead of chronicle and Foundation context in the composed prompt you
- Start an expert meeting in Peregrine and confirm the corpus block reaches the expert turn alongside the dossier block you
- Ask Mira in Peregrine about editing a bank file and confirm she names the corpus as the owner you
- Run
defaults write com.lochlustra.Code-Raven corpusContextInjectionEnabled -bool falseand confirm every surface returns to its previous text you - Confirm prompt length on a short Peregrine task is still workable with the block attached you
Risks & edge cases
This is advisory, and cannot prevent an edit
Prompt text lowers the odds that a session edits target-practice.json;
it does not stop one. Prevention lives at the two ends — a corpus-side declaration
validator gating the exporter, and the path guard consulted earlier than the commit
sheet. Neither is in this plan.
Terminal sessions get nothing
Most Peregrine work starts from a terminal rather than from Code Raven, and those
sessions never touch any of these seams. The answer is to write the same block to
<project>/.claude/corpus-context.md on index refresh; it is out of
scope here because it writes files into project repos.
Six guides default to the corpus folder
PressRaven-Corpus resolves as the working folder for every guide whose
category supplies a default, so its block names guides the session is not working on.
Those guides declare no machine target, so no owned-path list appears and the block
degrades to the guide names and the remedy — correct, but wordier than it needs to be.
~400 tokens on every corpus-linked handoff
Short task prompts in Peregrine now carry a block roughly as long as the task. The
.advisory split keeps meetings and Mira at ~170, but nothing trims the
handoff block, and nothing should — the list is the point.
Out of scope
- Mockups. Threading a project through the six builders in
DesignMockupsPromptBuildertouches Design Studio, which is active parallel work. The.authoringvariant is built and rendered here, so the second pass is a wiring job only. - The terminal gap. Writing the block to
.claude/corpus-context.mdon index refresh. Deferred deliberately: it writes files into project repos and deserves its own decision. - Enforcement. The corpus-side declaration validator that would
gate
export-machine.py --writeis separate work in the Press Raven repo. - The What's New entry.
WhatsNewTracker.currentVersionis 1.99 and this is not a release; writing a highlight now would mean inventing a version and retiring v1.99's ranks early.
Final review
What it does well. Every input already existed as indexed data, so nothing new is authored to make it work. The preview-sheet seam turned out to cover more than expected — all eight note-builder call sites funnel through it — which collapsed Phase 3 from eight tasks to six and removed a whole class of duplication bug. The audit that established this also found a live site passing no project at all.
Where it is weakest. It is advisory, and the plan says so rather than implying otherwise. The terminal gap is the honest hole: most Peregrine work does not start in Code Raven, so the constraint reaches the minority of sessions. And the block is long relative to a short task prompt.
What I would adjust if pushed. Close the terminal gap next, ahead of
the mockup pass. A file at .claude/corpus-context.md referenced from the
project's CLAUDE.md would reach every session in the repo, which is a bigger
win than the remaining five prompt builders.
High on the seams and the builder: the pattern is copied from one that shipped last week, every call site was traced rather than assumed, the build is green and the prefix folding is unit-tested against the real Peregrine path set. Lower because the block's runtime text has been verified by reimplementing the data path in Python against the real corpus rather than by reading it out of the running app, and because the terminal gap leaves the constraint reaching a minority of the sessions that matter.