The embedded guide catches up on its own — creation-time parity for both apps, an open-time refresh for the phone

The card's frozen spec recommended Option C (install the agent guide at
board creation); the owner's follow-up comment extended that ruling to a
second axis — embedded guidelines should update whenever a board opens if
the on-disk version is older than Lanework's, which the Mac app already
does via BoardStore.refreshAgentGuide()/runScheduledHeals(). This card
implements both halves.

- BoardWriter.createBoard now calls AgentGuide.install(atBoardRoot:)
  right after seedGitignoreIfAbsent, so every board — Mac- or
  phone-created, since KanbanMobile.BoardIndexStore.createBoard calls
  this same method — is born with a current-version CLAUDE.md, with no
  dependency on a later open. Routed through AgentGuide.install itself
  rather than a hand-rolled write, so never-downgrade, the
  CLAUDE.user.md rescue, squatter displacement, and the EchoLedger
  heal-attribution exclusion all carry over unchanged.

- BoardSession (KanbanMobile) gains a private refreshAgentGuideOnce(),
  fired once from open() (already idempotent on the .idle phase),
  fire-and-forget through the same CoordinatedFileAccess.write bracket
  every phone write uses. Deliberately not a heal scheduler — a
  one-shot courtesy check at session open, silent on failure (logged,
  never surfaced to lastError or a banner), matching AgentGuide's own
  "nothing here is a user-facing event" posture. The type's doc comment
  now names this one exception while keeping "no heal scheduler" true.

- project.yml: lifted the KanbanMobile target's AgentGuide.swift build
  exclusion (dating to the original mobile MVP, "agents work where the
  Mac app runs") — both changes above fail to compile on the phone
  without it, since the type simply wasn't in that module. Verified
  safe: AgentGuide.swift imports only Foundation, and its one upward
  dependency touches only EchoLedger's unconditional recording API,
  never the #if os(macOS)-gated consumer surfaces.

Tests: KanbanTests/BoardWriterTests.swift gains
createBoardInstallsTheCurrentAgentGuide, calling createBoard directly
and asserting the guide lands at AgentGuide.version immediately — the
card's own Done-when, and also the phone's creation-time coverage since
it's the same call site. KanbanMobileUITests/AgentGuideUITests.swift
covers the open-time refresh itself, the one piece only reachable
end-to-end from a running KanbanMobile process (no mobile unit-test
target exists): the bundle's fixture board already carries no
CLAUDE.md, so tapping into it and polling disk proves the wiring with
no fixture changes needed.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 22:22:02 -04:00
parent 05ae703989
commit cdc91d669d
5 changed files with 162 additions and 5 deletions
+20
View File
@@ -822,6 +822,26 @@ struct BoardWriterCreateBoardTests {
let document = try FrontmatterDocument.parse(fixture.indexText("Board.kanban"))
#expect(document.rawValue(for: FrontmatterKeys.created) == document.rawValue(for: FrontmatterKeys.modified))
}
/// **Creation-time parity** (ruled 2026-08-09, extending Option C to every `createBoard` caller):
/// a board is born with a current-version `CLAUDE.md`, with no dependency on a later Mac open to
/// write the first copy the gap `AgentGuideStoreTests` doesn't cover, since every fixture there
/// hand-writes its tree and calls `store.refreshAgentGuide()` directly rather than going through
/// `createBoard`. `KanbanMobile.BoardIndexStore.createBoard` calls this exact method, so this one
/// assertion is also the phone's coverage there is no second code path to test twice.
@Test("createBoard installs a current-version agent guide, with no dependency on a later open")
func createBoardInstallsTheCurrentAgentGuide() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
let root = fixture.url("MyBoard.kanban")
try BoardWriter.createBoard(at: root, title: "My Board")
let guideText = try String(decoding: fixture.data("MyBoard.kanban/\(AgentGuide.filename)"), as: UTF8.self)
#expect(AgentGuide.installedVersion(of: guideText) == AgentGuide.version)
#expect(guideText == AgentGuide.content, "the exact shipped guide, not a hand-rolled write")
#expect(!fixture.exists("MyBoard.kanban/\(AgentGuide.userFilename)"), "nothing existed to rescue at a brand-new root")
}
}
// MARK: - Create lane / card