Plan · CodeRaven · 19 September 2026 · 6 phases · 38 tasks

Instruction Manager

A first-class Instructions section where every ## segment of a CLAUDE.md becomes a toggleable object in a library, so a rule can be switched off without being lost and a shared rule can be applied to several projects from one place.

38tasks
6phases
7patterns reused
0AI calls
The gist

Claude's behavior is governed by two markdown files that Code Raven can currently only read. This turns each ## section of those files into an addressable segment in a library, so segments can be enabled, disabled, reordered, and shared across projects from inside the app. Disabling a segment removes its text from the file entirely rather than commenting it out, because CLAUDE.md is injected verbatim and a commented block still costs context.

The problem

Rules cannot be selectively retired. The "Document Output Format" segment — the one producing both a .md and an .html copy of every plan plus the Documents block — applies to every session in every project, always. Turning it off means cutting the text out and keeping it somewhere.

Shared rules drift silently. CodeRaven and VentureRaven carry the same four ## sections and have already diverged. VentureRaven's H1 still reads # Code Raven - Project Instructions, and its "Plan File Management" segment is missing the "Link both files at the end of the reply" bullet and the mobile-workbench paragraph. Same failure shape as the cr-*/vr-* command divergence already on record.

Approach

Segments, library, targets

A segment is one ## section, stored in a library independent of any file. A target is a file composed into — .global for ~/.claude/CLAUDE.md, or .project(id). An assignment binds a segment to a target with an order and an enabled flag. One segment assigned to many targets is what makes a rule shared.

Managed region, by default

Code Raven owns a fenced block and nothing else. Text above or below the markers is never read, moved, or touched. The marker format mirrors DossierClaudeCodeSync:19-20; the integrity stamp reuses the CommandInstaller format at CommandInstaller.swift:65-67.

<!-- BEGIN CodeRaven Instructions — managed by Code Raven; edits inside are overwritten -->
<!-- CodeRaven v1.102.x sha256:a1b2c3d4e5f6 -->
...composed segments...
<!-- END CodeRaven Instructions -->

Full-file mode, opt-in and destructive

Code Raven owns the whole file. Reaching this mode requires a typed confirmation and a snapshot, and the UI labels it CAUTION / IRREVERSIBLE while stating that hand edits made outside the app are lost on the next compose. It exists because a fully-generated file is the cleaner model once the library holds everything, and getting there should be possible deliberately.

Drift is detected, never overwritten blind

Every write stores the sha256 of the region it wrote. Before the next write the app re-hashes what is on disk. On mismatch it refuses to write and offers two explicit choices: adopt the on-disk text back into the library, or overwrite it. This is the discipline CommandInstaller already applies to slash commands via CommandFileStatus.userModified. Drift here is expected: Claude itself edits CLAUDE.md through /init, the update-memory skill, and this project's own mirror-config rule.

Snapshots are the only undo

~/.claude is not a git repository. Before every write the current file is copied to Application Support/Code Raven/Instructions/snapshots/{target}/{ISO8601}.md, keeping the last 20, restorable from the section UI.

Getting ahead of drift, in three layers

Detecting drift after the fact is the backstop. Three layers sit in front of it.

A pinned instruction segment. The first segment in every managed region is Code Raven Managed Instructions, written by the app and non-disableable. It states that the block is composed from a library, that edits inside it are overwritten, that content outside the markers is hand-owned, and that a change to a managed rule goes through the bridge.

A sanctioned path. An instruction that only says do not fails, because Claude still has to add a rule when asked. .claude/instruction-changes/*.json follows the established bridge convention, so do not edit the region has an answer to then how do I add a rule?

A hook that enforces it. HookInstaller already installs a PreToolUse hook, and approve-bridge-writes.py already singles out CLAUDE.md as a path it deliberately leaves on the normal permission path. A sibling hook matches Edit|Write|NotebookEdit and, when the edit would touch a managed region, exits 0 and emits a deny.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "This block is composed by Code Raven. Write the change to .claude/instruction-changes/ instead."
  },
  "systemMessage": "Managed instruction region — change routed through the bridge."
}

Exit 0 with JSON rather than exit 2, because exit 2 blocks deterministically and discards the reason, leaving Claude blocked without knowing the bridge exists.

One field name has to be checked against reality. A documentation pass reported the path arrives as tool_input.path. The hook already running in this repo reads tool_input.get("file_path", "") and works. The installed hook is ground truth; the new one reads both keys and prefers whichever is populated.

What this does not cover. The hook sees Claude Code tool calls and nothing else, so an external editor, a shell redirect, or the LAN sync still reach the file. /init regenerates CLAUDE.md wholesale and would be blocked rather than redirected. Instruction text is persuasion and lowers the rate without driving it to zero. Every one of those paths lands on the Phase 1 sha check, which stays the design's floor.

Shared segments and token substitution

A shared segment resolves {{PROJECT_NAME}}, {{PROJECT_PATH}}, and {{COMMAND_PREFIX}} at compose time from the target project. Unresolved tokens are left verbatim and flagged in the preview rather than silently blanked. The CodeRaven/VentureRaven diff shows project name and command prefix are the only real differences, so this is enough to make those four sections genuinely shared.

Decisions made for you

Disabling removes the text, rather than commenting it out

CLAUDE.md is injected verbatim including HTML comments, so a commented-out segment still reaches the model and still costs context. Reversibility comes from the library holding the body, not from the file retaining it.

Managed region is the default; full-file ownership is gated

Full generation is the cleaner data model but destroys any edit made outside the app, and ~/.claude has no version control to recover from. It ships as an explicitly-confirmed mode rather than the default.

The never-write-CLAUDE.md rule is narrowed, not discarded

DossierClaudeCodeSync.swift:5-10 and the developer-profile blueprint both record that the app writes to a dedicated file and never CLAUDE.md. That rule protected against automatic, generated content landing in a hand-maintained file. It is rewritten to scope it to automatic writes: the Instruction Manager writes only on explicit user action, only inside markers, only after a snapshot.

One section parser, not two

DossierHarvestService.sections(of:source:) already splits CLAUDE.md on ##. Rather than writing a second parser, the lossless version is extracted and the harvest is refactored to call it.

Composition is deterministic, with no AI call

Matching DossierProjectionBuilder.buildPackage. What the preview shows is exactly what lands on disk.

The work

Phase 1 — Model, store, parser, composer (no UI)

  • Add Models/InstructionSegment.swift with InstructionSegment, InstructionTarget, InstructionAssignment, and InstructionWriteMode, using defensive Codable with decodeIfPresent ?? default on every property
  • Add Services/InstructionSegmentParser.swift — lossless split of a markdown document into preamble plus ## sections, preserving exact body text and line ranges
  • Refactor DossierHarvestService.sections(of:source:) to delegate to InstructionSegmentParser so only one section parser exists
  • Extract the version+sha256 stamp helpers from CommandInstaller into Services/ManagedFileStamp.swift and update CommandInstaller to use it
  • Add Services/InstructionLibraryStore.swift as a @MainActor ObservableObject singleton persisting to Application Support/Code Raven/Instructions/library.json
  • Add Services/InstructionFileComposer.swift with compose(target:), write(target:), unmanage(target:), and region detection returning a .pristine / .externallyModified / .unmanaged state
  • Add Services/InstructionSnapshotStore.swift writing a pre-write copy per target and pruning to the last 20
  • Verify Phase 1 with a standalone swiftc harness covering round-trip parse, region replace, drift detection, and snapshot pruning

Phase 2 — Import and adoption

  • Add an import scan that parses the global and every project CLAUDE.md into candidate segments without writing anything
  • Add an adoption review sheet listing candidates per file with per-segment include toggles and a full preview of the resulting file
  • On confirmed adoption, populate the library, write assignments, take a snapshot, and convert the file to managed-region form
  • Detect and merge duplicate segments across files by normalized title and body so a shared rule adopts as one segment, not three

Phase 3 — Getting ahead of drift

  • Confirm the PreToolUse stdin field for the target path by testing a live hook, since the documented tool_input.path conflicts with the working tool_input.file_path in approve-bridge-writes.py
  • Confirm whether permissionDecisionReason reaches the model on a deny, and fall back to relying on the pinned instruction for redirection if it does not
  • Add a Code Raven Managed Instructions segment that the composer pins first in every managed region and refuses to disable or reorder
  • Add Services/InstructionChangeBridgeImporter.swift reading .claude/instruction-changes/*.json, following the existing bridge contract including a version field, processed/ archival, failed-{name}.json on rejection, and an ISO8601 date strategy on the decoder
  • Surface imported change proposals in the Instructions section as pending review rather than applying them
  • Add guard-managed-instructions.py matching Edit|Write|NotebookEdit, exiting 0 and emitting permissionDecision: deny with both permissionDecisionReason and systemMessage, never exit 2
  • Install the hook through HookInstaller alongside the existing two, reusing its stamp-and-merge path so settings.json is merged rather than rewritten
  • Add a settings toggle for the hook, defaulting to on, so it can be switched off without uninstalling the feature
  • Verify the hook by attempting an edit inside the region, an edit outside it, and a bridge write, confirming deny, allow, and allow respectively

Phase 4 — Instructions section UI

  • Add case instructions to AppMainView.MainContentView and a sidebar button modeled on the Developer Profile block, with a drift dot when any target is externally modified
  • Add Views/Instructions/InstructionManagerView.swift — target picker, segment list, detail pane
  • Add segment list with enable toggles, drag reordering, shared badge, and assigned-target count
  • Add a segment detail editor with title, body, notes, tags, and shared flag
  • Add a live composed preview with a diff against what is currently on disk
  • Add the drift resolution UI offering adopt-from-disk or overwrite, with the diff shown before either
  • Add snapshot history with restore
  • Add the full-file mode switch behind a typed confirmation labeled CAUTION / IRREVERSIBLE, stating that hand edits outside the app will be lost
  • Add an Unmanage action that dissolves the markers and leaves plain hand-owned markdown

Phase 5 — Shared segments across projects

  • Add token substitution at compose time for {{PROJECT_NAME}}, {{PROJECT_PATH}}, and {{COMMAND_PREFIX}}, flagging unresolved tokens in the preview
  • Add a per-project command prefix field with a derived default, surfaced in the target picker
  • Add multi-target assignment UI so one segment can be applied to several projects at once
  • Add an apply-to-all action that previews every affected file before writing any of them

Phase 6 — Surfacing and documentation

  • Extend ClaudeSetupSnapshot with per-target instruction management state and show it in ClaudeSetupSheet
  • Narrow the never-write-CLAUDE.md rule in the DossierClaudeCodeSync header comment and in .claude/context/blueprints/developer-profile.md so it reads as scoped to automatic writes
  • Add .claude/context/blueprints/instruction-manager.md and register it in blueprints/index.md
  • Verify end to end by hand: adopt the CodeRaven and VentureRaven files, mark the four shared sections shared, apply to both, and confirm the VentureRaven H1 and missing bullets are repaired

Risks & edge cases

Claude edits the managed region mid-session

Phase 3 addresses this with a pinned instruction, a bridge path, and a PreToolUse deny, which together should make it uncommon. None of the three is airtight: the hook sees only Claude Code tool calls, the instruction is persuasion, and /init would be blocked rather than redirected. The sha check stays as the floor.

The hook is a new way for the environment to feel broken

A denied write during /init reads as a failure rather than a redirect. The settings toggle exists for that reason, defaulting to on but switchable without uninstalling the feature.

The deny reason may never reach the model

There are open reports of permissionDecisionReason being discarded on a PreToolUse deny. If that holds, Claude sees a bare refusal rather than a pointer to the bridge, and the pinned instruction is the only remaining signal. Bash cannot be matched by file path at all, so a shell redirect into CLAUDE.md bypasses the hook entirely.

~/.claude is LAN-synced

LANSyncService.swift:298 syncs the whole folder between the iMac and the MacBook Pro. Writes from both machines are a genuine conflict. This plan detects it by sha mismatch and refuses; it does not merge.

Substituted text is not literally the library text

For shared segments the preview is the only accurate statement of what lands on disk. Reading the library body alone will mislead.

Full-file mode is genuinely destructive

Snapshots are the entire recovery story, and they live in Application Support, so clearing app data loses them.

Adoption is a one-way door in practice

Nothing technically prevents reverting, but once CLAUDE.md carries markers every later hand edit has to reckon with them. The Unmanage action exists so the exit is real.

Out of scope

Final review

What this does well. It solves the stated problem without inventing a new mechanism. The fenced region, the sha stamp, the drift states, the JSON store, and the deterministic composer are all patterns already running in this codebase, so the new code is mostly assembly. Making "off" mean the text actually leaves the file is the decision that gives the feature real value rather than cosmetic value. The shared-segment work is justified by a drift bug that already exists.

Where it is thin. Phase 3 moves drift from detection to prevention, which is the right direction, but it adds a hook that can deny a write and therefore a new way for the environment to feel broken. Three-way merge is still absent, so a simultaneous LAN-synced edit from the other machine remains a refusal rather than a resolution.

Adjustments worth considering. Phase 4 could ship before Phase 3 as a headless "repair the fork drift" action, delivering the concrete VentureRaven fix sooner. Cutting full-file mode entirely would remove the only irreversible path; it is kept because it was explicitly requested, gated behind a typed confirmation.

86/100

High on the data model, the reuse map, and the write mechanics, since those follow existing code closely, and raised from 82 by Phase 3 turning the weakest joint from after-the-fact detection into prevention with a sanctioned path. The remaining deduction is for hook ergonomics, which cannot be judged before it runs, and for the LAN-sync conflict case, which is detected but not solved.