Build the auto-commit engine
Every settled change on a git-mode board commits, debounced 2s past drag/typing churn, staged whole-root with .gitignore respected. GitCommitOperation reaches the vendored libgit2 directly (same 1.9.2 pin SwiftGitX resolves — importable, not duplicated) for signature-capable commits; add-git's config materialization is gone, identity resolves at commit time (repo-local config, else derived default) per the 2026-07-31 ruling in 06. CommitAttribution classifies per file off EchoLedger receipts: user identity on app-mediated windows, Lanework External <[email protected]> on foreign, the modified-by refinement (<slug>@agents.lanework .invalid) when every foreign file agrees, heal-marked receipts split into their own commit — window split foreign → heal → user. Edit-session granularity: ~700ms saves stay uncommitted, staging excludes open session folders (closure-resolved so mid-session moves stage around the new location), session end nudges the debounce so each session lands exactly one body commit. Flush-before-overwrite gates on known-foreign windows and commits synchronously ahead of the write; close/quit flush the pipeline via CloseFlushCoordinator's committerFlush. index.lock backs off briefly then re-debounces silently; clean tree no-ops; genuine failures ride the standing history-suspension banner and retry next debounce. Abnormal repo states (detached HEAD, merge/rebase/cherry-pick in progress) hold the engine with a 15s re-check; unborn HEAD commits "Initial board state" whole-tree; dirty tree at open catches up through the same engine. Message seam (CommitMessageComposing) ships interim — the semantic composer is the next card. Discovery diffs HEAD against an in-memory index with rename detection (git status alone never pairs a bare mv), and a failed survey reads as "could not look", never "nothing changed". 46 new tests / 8 suites, all real repositories via bundled libgit2. 2240 tests / 383 suites green; InertGitTests untouched. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -268,6 +268,26 @@ public final class EchoLedger: Sendable {
|
||||
receipts.withLock { $0[path]?.receipt }
|
||||
}
|
||||
|
||||
/// **Every receipt the ledger holds right now, with its heal mark — read, never consumed.**
|
||||
///
|
||||
/// Pro's auto-committer's one call (`GitAutoCommitter.harvest`), and it has to be a copy rather
|
||||
/// than a read at commit time for an ordering reason worth stating here: receipts are *consumed*
|
||||
/// by the landing reload that classifies them ("one write, one echo"), and the committer asks its
|
||||
/// question a debounce later — by which time the receipt for the user's own card edit is long
|
||||
/// gone, and reading the live ledger would attribute the user's own work to `Lanework External`.
|
||||
/// So the committer copies at the close of each write bracket, when a receipt describes a
|
||||
/// completed write and nothing has yet had a chance to retire it, and re-applies the satisfaction
|
||||
/// rule against disk itself (`CommitAttribution`).
|
||||
///
|
||||
/// Nothing is retired here, which is what makes this safe to call on every bracket: the
|
||||
/// announcer's consumption still decides what speaks, and the committer's copy still decides what
|
||||
/// each commit is authored by.
|
||||
func outstandingEntries() -> [String: HarvestedReceipt] {
|
||||
receipts.withLock { store in
|
||||
store.mapValues { HarvestedReceipt(receipt: $0.receipt, isHeal: $0.isHeal) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the receipt at this path is heal-marked — `false` for a path with no receipt at all,
|
||||
/// which is the same shrug every other read here gives an unknown path.
|
||||
public func isHeal(at url: URL) -> Bool {
|
||||
|
||||
Reference in New Issue
Block a user