30 tasks · 6 sections · 7 files · 2026-09-07

The corpus constraint reaches every prompt

Code Raven knows which of its projects a Press Raven guide feeds, and which files in those repos the exporter overwrites. That knowledge reached two views, both firing at commit time. This puts it in front of the session doing the work.

22Done
8Open
26Sites covered by one edit
7Files touched
The gist

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

Judgment call

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.

Judgment call

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.

Fixed in passing

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 CorpusContextBuilder with injectionToggleKey = "corpusContextInjectionEnabled" and an isInjectionEnabled computed property defaulting to true, mirroring DossierContextBuilder
  • Add Variant with .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 no CorpusLink
  • 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.families and .miniGame
  • Never truncate the owned-path list — an omitted file is the one that gets edited; cap only the guide list
  • Add a Project convenience overload so call sites holding a Project do not reach for .path themselves

Phase 2: The preview-sheet seam

6 tasks · 6 done One insertion covers 26 construction sites.
  • Add a corpusContextText computed property to CopyPromptPreviewSheet resolving from projectPath
  • Insert the block in effectivePrompt ahead of every other context block and immediately after the CR-PROJECT comment, 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 PromptPreviewPayload needs no change — the sheet resolves the corpus itself from projectPath
  • 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 .advisory variant that names the owned-file count rather than listing the paths
  • Rename StandupMeetingService.appendDossierContext(to:) to appendSessionContext(to:) and append the corpus block beside the dossier block
  • Read the meeting's project from the service's own projectPath property rather than threading a new parameter
  • Add the corpus block to MeetingHandoffGenerator.buildHandoffPrompt at the .constraint variant — the handoff is an implementation brief, generated once
  • Add a corpusContext field to MiraProjectContext and populate it in MiraContextBuilder.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 write only, with no Settings row, because dossierContextInjectionEnabled has none either

Verification

3 tasks · 2 done Build verified; the third item is a runtime check.
  • xcodebuild -scheme "Code Raven" -destination 'platform=macOS' build succeeds
  • scripts/verify-build-product.sh confirms the built bundle is fresh
  • CorpusContextBuilder.compactContext returns "" 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 peregrine and 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 false and 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

Known limit

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.

Edge case

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.

Edge case

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.

Cost

~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

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.

84/100

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.