Build AgentGuide — versioned CLAUDE.md maintenance

The app-owned agent guide at every board root (DESIGN/08 ▸ The agent
guide): version-gated by a first-line marker (v5, superseding the
pathfinder's v4 guides on real boards), rewritten when missing or older,
byte-for-byte untouched when current or newer. A markerless CLAUDE.md is
displaced to CLAUDE.user.md when that name is free — never clobbered —
and the guide write is skipped with a log when it isn't. Symlinks,
folders, and read-only volumes are skipped in silence; the write rides
performWrite's bracket as an app-mediated Writer operation (new
WriteOperation.agentGuide), so the echo lands appMediated and the
Pro-era committer can attribute it honestly later.

Hooked at store acquire (beside the loose-file relocation, after the
watcher exists) and on every successful reload — the guide self-heals
from foreign deletion or rollback, pre-wiring 06's acknowledged undo
bounce. The refresh memo arms before each attempt and clears on a
successful write, so a failing write can't hot-loop and a foreign
deletion stays healable. First-line-only marker parsing (no Regex);
guide content is one swappable literal, finalized under the next card.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 21:56:10 -04:00
parent 3aa80db2a4
commit b3812ed928
8 changed files with 1119 additions and 2 deletions
+19 -2
View File
@@ -2362,6 +2362,21 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
/// never the Finder-renamed one it would have landed under.
case relocateLooseFile(filename: String)
/// The board-root `CLAUDE.md` being written or upgraded and the rescue move that precedes it
/// when a markerless one has to be displaced to `CLAUDE.user.md` (08-agent-integration.md The
/// agent guide; `AgentGuide`).
///
/// **No payload**, unlike every other case that names something: there is exactly one guide per
/// board, its filename is fixed, and it is not an item with a title. Its own case on
/// `.relocateLooseFile`'s reasoning, doubled this is work the *app* started on its own, for a
/// file the user did not create, does not own and may not know exists; folding it into any
/// gesture's phrasing would name an act that never happened.
///
/// One case rather than two (the move and the write) deliberately: they ride one bracket and
/// have one outcome the user could care about the board's guide is not up to date and a
/// second phrasing for "couldn't move a file you have never seen" would explain nothing.
case agentGuide
/// A Preview task-list checkbox being ticked or unticked (05-card-window.md Preview).
///
/// Its own case rather than a fold into `.rename`'s or `.style`'s neighbourhood, on the
@@ -2399,7 +2414,8 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
/// on identity for the cases with no title slot at all: `createBoard`/`createLane`/
/// `createCard` are minting a file, not reading one; `importAttachment`, `removeAttachment` and
/// `relocateLooseFile` carry a filename, which is the name the user is looking at and the only
/// one their banner should say; `listAttachments` and `renumberChildren` name no single item.
/// one their banner should say; `listAttachments`, `renumberChildren` and `agentGuide` name no
/// single item.
/// Called once, right where the operation's `readDocument` succeeds `updateIndex` itself
/// (which covers every case that funnels through it: renumber, delete, restore, style, and
/// the tail end of move/copy) and the move/copy pre-flight, before the folder travels or the
@@ -2408,7 +2424,7 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
public func withTitle(_ title: String?) -> WriteOperation {
switch self {
case .createBoard, .createLane, .createCard, .importAttachment, .listAttachments,
.removeAttachment, .renumberChildren, .relocateLooseFile:
.removeAttachment, .renumberChildren, .relocateLooseFile, .agentGuide:
self
case .move: .move(title: title)
case .reorder: .reorder(title: title)
@@ -2454,6 +2470,7 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
case let .removeAttachment(filename): "move attachment '\(filename)' to the Trash"
case .renumberChildren: "renumber children"
case let .relocateLooseFile(filename): "relocate loose file '\(filename)'"
case .agentGuide: "update the agent guide"
case let .toggleTask(title): Self.phrase("toggle a checkbox in", title)
case let .editBody(title): Self.phrase("save the body of", title)
case let .rawSource(title): Self.phrase("apply source changes to", title)