Step 8's verification record: 2,707 unit tests green at close (the golden fixture suite among them, untouched — no git fixtures ever existed); the UI test targets compile; the keeper behaviors — a board beside a real .git opened, watched, and healed byte-identically — are pinned by GitAgnosticStorageTests, FolderWatcherTests, and the gitignore/relocation suites, all green. The accessibility audit could not run to a verdict in this environment (its AX sweep flagged another app's fullscreen video element — the same failure on surfaces the excision never touched); the popover audit's own screenshot shows the strip as ruled: Info, Theme, Sync. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
16 KiB
Git Excision — Plan
This document is the ruled plan for removing app-managed git from Lanework entirely. It is the second step beyond the 2026-08-07 pivot recorded in DESIGN/12-editions.md (git left the paywall and went tier-independent); this step retires the git stack itself. The safety-net successor — the foreign-change journal that would reclaim tracking of agent-borne mutations — is explicitly deferred, a later decision and not part of this milestone; the harvest step below stays in scope precisely because it keeps that option cheap.
Researched: 2026-08-07 · Ruled: 2026-08-08 — excise; execute as written · Executed: 2026-08-08 — steps 1–8 complete, each committed green (tag pre-git-excision = the resurrection point; 2,707 unit tests at close)
Why (the case, compressed)
The strategic reasoning lives in the 2026-08-07 tier-strategy discussion; the plan only summarizes it.
- The dividing line for tiers became "your board beyond your Mac" (iPhone + ops-based sync service as Pro); git carries no revenue in that structure.
- Sync no longer needs git rails: the phone was ruled a worktree client speaking semantic ops, and once an ops protocol exists the Mac and server can speak it too.
- The most natural developer usage — a board inside a project repo — was always excluded from app-managed git (nearest-
.git-wins leaves it alone); those users get history from their own workflow, forever, because the format is git-friendly by construction. - The safety-net value (history of externally delivered changes, e.g. agents) survives via a designated successor: a foreign-change journal built on the write-provenance ledger plus close-time text snapshots — protection by default on every board, no opt-in, no git states.
- What ends is the permanent design tax: every future feature answering "what does this do mid-merge, index-locked, mid-branch-switch?"
- Market evidence (research/08): nobody anywhere successfully charges for git features; the closest competitor thrives with zero git integration; the wedge is files, not git.
Reversibility
- Tag the pre-excision commit
pre-git-excision. The entire stack — ~8,150 lines inKanban/Git/, ~8,300 lines of tests — remains in history. - Dependency pins recorded here for resurrection: SwiftGitX
0.4.0, libgit21.9.2(bothgithub.com/ibrahimcetin/…, declared inproject.yml). - The seams the git stack plugged into (
HistoryProviding,commitSeam,IdentityHistoryRanker) are all nil-safe/default-native and are kept, so a future provider — journal, ops service, or even git again — re-binds without re-plumbing.
What is kept (do not over-cut)
.gitignoreseeding and parsing —BoardWriter.gitignoreSeed/seedGitignoreIfAbsent,Storage/GitignoreRules.swift. Since 2026-07-31 this is the noise-gate definition for the loose-file heal on every board, git or not; the format's git-friendliness promise also keeps it. Tests:GitignoreRulesTests, the noise-gate suite inLooseFileRelocationTests— untouched.- FSEvents watcher
.gitfiltering —FolderWatcher's.gitexclusion. Boards living inside user repos remain first-class; the watcher must keep ignoring their churn. Tests inFolderWatcherTests— untouched. - The write-provenance ledger —
EchoLedgerruns identically with the announcer as its only consumer (02-architecture.md already states this posture for no-git boards); it is the foundation of the successor journal. The macOS-gated harvest surface (HarvestedReceipt) stays; only its git consumer (CommitAttribution.swift) goes. - Native undo, whole —
Kanban/History/becomes the only substrate:HistoryProviding,NativeHistoryProvider,BoardUndoManager,CardWindowUndo, staleness re-checks, phrase vocabulary.HistoryProviderTests(968 lines) untouched. The protocol seam is retained deliberately (see Reversibility). - Duplicate-id repair — the ladder's
historyRankrung is nil-safe by design; with the ranker gone, the birth-date rung is the top rung, exactly today's no-git behavior. Only the two git-rung tests inDuplicateIdentityTestsgo. - Tier/StoreKit machinery — already dormant and structurally separate since the pivot (
Tier,ProEntitlement,ProSettingsSectionunrendered,TierTestsgit-free). Zero changes; it waits for the phone/sync split. The one git-referencing marketing string inProSettingsSectiongets rewritten whenever Pro re-surfaces, not now. - The Sync tab placeholder —
BoardSyncTabViewkeeps its claimed position in the popover; its annotation changes from "the remote half of the git story" to the future ops-based sync service. - The network-client entitlement — stays dormant; the future sync service needs it regardless.
UndoProbe— provider-agnostic diagnostic, keeps working over the native provider alone.- The agent guide mechanism —
AgentGuide.swiftstays; only its## Gitprose changes (step 6).
What is harvested before deletion
The semantic message composer becomes the change narrator. CommitMessageEngine.swift (1,419 lines, pure Foundation, a BoardModel × BoardModel × changed-paths → prose function) and its spec (CommitMessageTests.swift, 998 lines) are the designated core of the future activity feed / foreign-change journal. Harvest in place, before any deletion:
- Relocate engine +
CommitMessageComposer.swift's request/protocol types out ofKanban/Git/into a neutral home (working name:Kanban/Changes/), renamed away from commit vocabulary (e.g.ChangeNarrator). - Sever the two git ties the inventory found: move
GitChangedPath(a plain path+kind value type currently defined insideGitCommitOperation.swift) into the neutral module; replaceCommitAuthorship.foreign(GitIdentity)with a plain string identity. - Transplant the spec tests alongside; they keep the engine alive and correct until the journal work begins.
- The "previous snapshot" input becomes the caller's problem by contract (the journal will supply it from memory/snapshot; git supplied it from HEAD).
What is deleted
Kanban/Git/wholesale (minus the two harvested files): mode detection, repository wrapper, auto-committer, commit/branch/restore operations, HEAD snapshot, history walk/provider, history store, housekeeping, identity, attribution, operation stamp, commit seam type. ~6,600 lines after harvest.- UI surfaces (~1,160 lines):
BoardGitControls,BoardGitSetup,BoardGitTabView,CardHistorySection; the.gitcase ofBoardInfoTaband its switch arm; the card window'shistorySlotwiring. - Menu placeholders:
RemoteCommands(Board ▸ Pull/Push,m7-git-tagged) removed; theView ▸ Historyfuture row re-tagged to the journal successor rather than removed (the reserved slot survives the substrate). AppModelwiring (the largest single edit, ~130 mentions):makeHistoryProvidercollapses to always-native;wireGitUndo,wireBranchSwitching,bindHistoryProvider's add-git path, and the card-session staging threading (setCardSession/releaseCardSessionStaging/resumeCardSessionStaging) all go;HistoryStorecomposition leavesbeginSession.BoardStore: thecommitSeamproperty and its three call sites (willWrite,writeBracketDidClose,reloadDidLand); themakeIdentityHistoryRankerproperty.BoardRegistry: the persistedgitOperationStampfield and accessors. Safe removal: decoders ignore unknown keys, so existing registries with the field simply drop it on next write.BannerCenter:GitFailureBanner, theGitOperationenum,.gitFailure/.repositoryUnreadablecases and their rows.SessionSettleGate: theGitRestoreOperation.planintegration goes with the git restore path; the path utility it borrows fromGitHistoryWalkmoves to a neutral home; the gate itself (settle editors before wholesale operations) stays — it serves any future wholesale operation.- Dependencies: SwiftGitX and libgit2 leave
project.yml(with their rationale comment blocks, including the now-stale "never runs in the free app" note);xcodegen generate;Package.resolvedregenerates. - Tests: eleven wholesale suites (~8,285 lines minus the transplanted
CommitMessageTests):AutoCommitTests,GitUndoTests,BranchSwitchTests,HistoryStoreTests,RepositoryHygieneTests,GitIdentityTests,CardSessionCommitTests,BoardGitModeTests,BoardGitSetupTests,BoardInfoPopoverTests(git-only despite the generic name). Mixed-file edits:AppModelTests(helper + 3 branch tests),CardSessionUndoTests(2 lines),DuplicateIdentityTests(2 git-rung tests),BoardDecisionSurfaceTests(the Pro-repair-commit suite),BannerCenterTests(git banner rows),AccessibilityAuditTests(git-tab audit mentions). - Naming footguns, called out so the sweep doesn't misfire:
BoardInfoPopoverTests.swiftis entirely git (delete);InertGitTests.swiftis a Storage-layer keeper despite the name (keep, rename to say what it pins — the writer/loader's byte-level git-agnosticism);RepositoryHygieneTests.swiftis fully git despite two ".gitignore"-titled suites (delete — the kept gitignore behavior is covered byGitignoreRulesTestsandLooseFileRelocationTests).
Companion changes (same milestone, not deletions)
- Agent guide rewrite + version bump. The
## Gitsection currently documents auto-commit ("If you don't commit, Lanework Pro auto-commits your changes"). Rewrite to the new posture: boards may live in git repos; agents working a repo-resident board should commit their own changes with clear messages, stage only their own paths, never touch app state. The guide is version-gated, so the marker bumps and existing boards heal to the new text on next open. - README key features. Remove the four git bullets (git integration, auto-commit, undo-as-forward-commits/branches, card History) and the Pro-unlocks-git framing in the tiers bullet; add one line stating the format's git-friendliness promise (per-card files, stable UUIDs, byte-fidelity, seeded
.gitignore— version boards with any tools you like). - DESIGN re-rulings (each as a dated note in the doc it amends, per the corpus convention):
12-editions.md— second pivot note: git excised entirely; tier machinery stays dormant pending the phone/sync split.06-history-undo.md— retired, with one migration first: its tier-independent Undo routing section (both substrates dispatch through it) moves into13-native-undo.mdbefore the retirement note lands, so the routing rules keep a live home.13-native-undo.md— amended: the only substrate; add-git swap language removed.07-sync-collab.md— retired as written; a successor sync doc arrives with the ops-service workstream.14-git-operations.md— retired (superseded record).DESIGN/README.md— index updated: retired docs marked, the stale "— Pro tier" labels on 06/07/14 fixed.- Source-file doc comments citing 06/07/14 by number get repointed during the sweep (mostly they go with the deleted files; the survivors repoint to 13).
strategy/00-charter.md— the tier anchors re-ruled to the 2026-08-07 structure (free = complete Mac experience; Pro = iPhone + ops-based sync) once the user rules; the charter edit is the ruling's paper trail, not part of the mechanical excision.
Successors (deferred — explicitly outside this ruling)
- Foreign-change journal — deferred by the 2026-08-08 ruling ("we'll worry about reclaiming the value of tracking agent-borne mutation another time"); recorded here so the shape isn't lost: pre-images of foreign writes journaled at reconciliation (the store holds prior content in memory when the ledger flags a foreign batch), a close-time text snapshot covering the app-closed blind window,
modified-byattribution, bounded retention, a plain "previous versions / what changed" surface. Text only; attachment binaries deliberately out of scope locally. Interim safety net until then: the trash for deletes, user-rungit initon boards that warrant it, and the agent guide's commit etiquette on repo-resident boards. - Ops-based sync service — per the tier-strategy discussion: semantic ops, server-side worktree API, phone as worktree client; the harvested change narrator supplies the activity feed. Own workstream, own docs.
Sequencing
Each step leaves the build green and the unit suite passing; steps are separately committable.
- Preconditions. Settle or land the in-flight working-tree changes (the pivot's implementation is mid-flight in
BoardStoreHistory/HistoryStaleness/BoardStore); tagpre-git-excision. - Harvest. Relocate the message engine + composer types and
GitChangedPathtoKanban/Changes/; sever theGitIdentitytie; transplant the spec tests; rename to narrator vocabulary. Green. - UI peel. Remove the Git tab,
CardHistorySection, git banners,RemoteCommands; re-annotate the Sync placeholder. Green — the git engine still compiles underneath, unreferenced by UI. - Seam unbind.
AppModelwiring out (provider always native),BoardStoreseam + ranker out,SessionSettleGatecleaned,BoardRegistrystamp field out. Green —Kanban/Git/now compiles dead. - Delete.
Kanban/Git/(minus harvested), the eleven test suites, the mixed-file edits. Green. - Dependency drop. SwiftGitX/libgit2 out of
project.yml; regenerate;Package.resolvedrefreshes. Green — this is the step that proves nothing living referenced libgit2. - Companions. Agent-guide rewrite + version bump; README features; keeper renames (
InertGitTests); DESIGN re-rulings and index; grep sweep for orphaned references (gitcase-insensitive overKanban/andKanbanTests/, adjudicated against the keeper list above — filename-based sweeps misfire on the three footguns). - Verify. Full unit suite; UI suites compile (
build-for-testing) and the accessibility audit passes without the Git tab; golden fixture suite untouched (no git fixtures exist — repos were built at runtime); a fixture board inside a real user repo still opens, watches, and heals correctly (the keeper behaviors).
Size and risk
- Net deletion: roughly 9,000 lines of source and 8,000 of tests; edits in ~15 shared files. With the seams as clean as the inventory found, this is a one-to-two-session milestone.
- Biggest single risk:
AppModel's ~130-mention entanglement — step 4 is the one to do slowly, against a green step-3 baseline. - Second risk: over-cutting a keeper — the keeper list and the three naming footguns above exist to prevent exactly that; the sweep adjudicates against this document.
- Registry compatibility: dropping
gitOperationStampis decode-safe (unknown keys ignored); no migration needed. - The agent-guide bump is mandatory, not optional — shipping the old text would document machinery that no longer exists, to agents.
What this plan does not decide
The journal's design and timing, the ops-service architecture, the Pro split's final contents, and Pro pricing are all downstream workstreams with their own documents. This plan only removes the git stack, preserves its seams and keepers, harvests its one reusable engine, and keeps the paper trail honest.
Handoff
Excise app-managed git per strategy/01-git-excision.md (ruled 2026-08-08): tag pre-git-excision, then run Sequencing steps 1–8 in order, each committed green — the keeper list and the three test-file naming footguns are binding, the message engine is harvested to Kanban/Changes/ before anything is deleted, and the agent guide's Git section is rewritten with a version bump; the foreign-change journal (reclaiming tracking of agent-borne mutations) is deferred and out of scope.