The message engine outlives its substrate — harvested to Kanban/Changes/ as the change narrator
Step 2 of strategy/01-git-excision.md: CommitMessageEngine and the composer seam relocate to a neutral module renamed away from commit vocabulary (ChangeNarrator, ChangeNarrationRequest, ChangeNarrating, SemanticChangeNarration, ChangeAuthorship), GitChangedPath extracts from GitCommitOperation as ChangedPath, and the one git tie severs — authorship's foreign case carries a display name, not a GitIdentity. The spec tests transplant as ChangeNarratorTests, alive until the journal work begins. 3,009 tests green. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -88,50 +88,6 @@ public struct GitRepositoryReading: Sendable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Changed paths
|
||||
|
||||
/// One path `git status` reports as differing between HEAD and the working tree.
|
||||
///
|
||||
/// Board-root-relative and file-granular, which is the unit both consumers want: staging adds or
|
||||
/// removes exactly these, and attribution asks a question per *file* (06 ▸ Interaction with external
|
||||
/// writers: "classify every observed change, per file").
|
||||
public struct GitChangedPath: Sendable, Equatable, Hashable {
|
||||
|
||||
/// The path, relative to the board root, in git's own spelling (`/` separators, no leading dot).
|
||||
public let path: String
|
||||
|
||||
/// Whether the file is **gone** from the working tree.
|
||||
///
|
||||
/// The `modified-by` rule turns on this bit — "any true deletion in the window falls back to
|
||||
/// `Lanework External` — a deletion leaves no file to stamp" — which is why the rename half
|
||||
/// below is a separate fact rather than folded in here.
|
||||
public let isDeletion: Bool
|
||||
|
||||
/// Whether this path is one end of a **rename** libgit2 paired up.
|
||||
///
|
||||
/// "**A folder move is not a deletion**: items match by id across the whole board … so a moved
|
||||
/// card attributes by its stamp like any changed file" (06). A paired departure is therefore a
|
||||
/// deletion on disk that the window must not be demoted by.
|
||||
public let isRename: Bool
|
||||
|
||||
/// Whether the path is **new in this commit** — git's own `GIT_DELTA_ADDED` (and a rename's
|
||||
/// arriving end), surfaced rather than inferred.
|
||||
///
|
||||
/// It exists for the comment verb family (01-storage-format.md § Enhanced schema): comments are
|
||||
/// window-scoped and the board snapshot never carries them, so "Comment on 'X'" and "Edit comment
|
||||
/// on 'X'" cannot be told apart by a diff of two snapshots — the only thing that distinguishes a
|
||||
/// comment folder arriving from one being rewritten is whether HEAD already had it, which is
|
||||
/// exactly the question this diff already answered.
|
||||
public let isArrival: Bool
|
||||
|
||||
public init(path: String, isDeletion: Bool, isRename: Bool, isArrival: Bool = false) {
|
||||
self.path = path
|
||||
self.isDeletion = isDeletion
|
||||
self.isRename = isRename
|
||||
self.isArrival = isArrival
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - A planned commit
|
||||
|
||||
/// **Which of 06's classes a planned commit belongs to** — carried through the libgit2 work so a
|
||||
@@ -174,7 +130,7 @@ public struct GitLandedCommit: Sendable, Equatable {
|
||||
/// two-commit split; ruled 2026-07-29: the heal's third class).
|
||||
public struct PlannedCommit: Sendable, Equatable {
|
||||
|
||||
/// Board-root-relative paths, exactly as `GitChangedPath.path` spells them.
|
||||
/// Board-root-relative paths, exactly as `ChangedPath.path` spells them.
|
||||
public let paths: [String]
|
||||
|
||||
public let message: String
|
||||
@@ -380,7 +336,7 @@ enum GitCommitOperation {
|
||||
/// but history stops advancing; surfaced per 02-architecture.md ▸ Write-failure surfacing."
|
||||
/// (Found by test rather than by reading: the failure suite went green-by-silence when discovery
|
||||
/// moved from `git_status` to staging.)
|
||||
nonisolated static func surveyChangedPaths(at boardRoot: URL) -> [GitChangedPath]? {
|
||||
nonisolated static func surveyChangedPaths(at boardRoot: URL) -> [ChangedPath]? {
|
||||
_ = startUp
|
||||
guard let repository = open(boardRoot) else { return nil }
|
||||
defer { git_repository_free(repository) }
|
||||
@@ -393,11 +349,11 @@ enum GitCommitOperation {
|
||||
/// The survey, with "could not look" folded into "nothing to do" — for the callers that have no
|
||||
/// failure channel and want the safe answer: `GitRepository.create`'s branch line, and the tests'
|
||||
/// clean-tree assertions.
|
||||
nonisolated static func changedPaths(at boardRoot: URL) -> [GitChangedPath] {
|
||||
nonisolated static func changedPaths(at boardRoot: URL) -> [ChangedPath] {
|
||||
surveyChangedPaths(at: boardRoot) ?? []
|
||||
}
|
||||
|
||||
private static func changedPaths(in repository: OpaquePointer, index: OpaquePointer) -> [GitChangedPath]? {
|
||||
private static func changedPaths(in repository: OpaquePointer, index: OpaquePointer) -> [ChangedPath]? {
|
||||
var pathspec = git_strarray()
|
||||
guard git_index_add_all(index, &pathspec, GIT_INDEX_ADD_DEFAULT.rawValue, nil, nil) == 0 else {
|
||||
return nil
|
||||
@@ -426,12 +382,12 @@ enum GitCommitOperation {
|
||||
_ = git_diff_find_similar(diff, &findOptions)
|
||||
}
|
||||
|
||||
var found: [String: GitChangedPath] = [:]
|
||||
var found: [String: ChangedPath] = [:]
|
||||
|
||||
func record(_ path: String?, isDeletion: Bool, isRename: Bool, isArrival: Bool = false) {
|
||||
guard let path, !path.isEmpty else { return }
|
||||
let existing = found[path]
|
||||
found[path] = GitChangedPath(
|
||||
found[path] = ChangedPath(
|
||||
path: path,
|
||||
// Present wins where two deltas disagree: staging asks "is it there now", and the
|
||||
// `modified-by` demotion must not fire for a file the window ends with.
|
||||
|
||||
Reference in New Issue
Block a user