1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! The compaction merge (ARCH §2.6, §2.7, §5.5) — the one merge left in
//! the system now that merge-back is gone (§2.6).
//!
//! A compactor forks off a **checkpoint commit** `C` (the dispatching
//! branch's tip at dispatch) and rewrites only what existed at `C`:
//! deleting superseded transcript entries, landing a new summary, and
//! nominating superseded work products for deletion. The live agent keeps
//! stepping past `C`, and its commits since `C` only *append* new
//! sequence filenames (transcript immutability, §2.3) — so the two write
//! sets are disjoint and the merge is conflict-free by construction. The
//! agent's own executor lands it `--no-ff` at a step boundary; the merge
//! commit *is* the context rebuild point (§5.5).
//!
//! **The one theoretical overlap — live-branch-wins.** A compactor may
//! nominate a *work product* the live agent has rewritten since `C`. That
//! is the sole conflict class: transcript entries never collide (the live
//! branch only appends new filenames) and a fresh `summary/<NNN>.md`
//! never collides (its seq is past every prior summary, §2.7). The
//! overlap surfaces as a git modify/delete conflict, and the executor
//! resolves it **live-branch-wins**: it stages the working-tree state
//! (git leaves the live agent's version in the worktree on a
//! modify/delete), which drops the compactor's deletion. A dropped
//! deletion is lost compaction, never lost work — the same worst case the
//! deletion-only toolset already guarantees (§2.7).
//!
//! `git add -A` after a `--no-ff --no-commit` merge realizes this in one
//! move: a clean merge is already fully staged (the add is a no-op), and
//! a modify/delete conflict leaves the live version in the worktree,
//! which the add stages — resolving the conflict by keeping ours.
//!
//! **Filtered to the compaction product** (§2.6, §2.7). A compactor is an
//! ordinary child, so its branch also grew its *own* context since `C`:
//! the `goal.md` and `soul.md` its dispatch commit rewrote, and the
//! transcript entries under `messages/**` its step loop appended. None of
//! that is the dispatching branch's context — it is the compactor's
//! private dialog, whose record is its own ref. What the merge is
//! specified to land is exactly what the two-tool toolset produces
//! (§2.7): the new `summary/<NNN>.md` and the nominated deletions. So the
//! staged merge is filtered before it commits — every path the merge
//! would *add or rewrite* outside `summary/` is restored to the
//! dispatching branch's own version, while deletions (the whole point of
//! compaction) pass untouched. This is the §2.6 work-product transfer's
//! principle in mirror image: that channel admits work products and
//! excludes branch-scoped context; this one admits the branch's own
//! context product and excludes the compactor's private dialog.
use Error;
use crateGitRunner;
use crateworkspace;
use Path;
/// The compaction product's sole *addition* surface (ARCH §2.7): the
/// summary `write_summary` writes. A git exclude pathspec — the same
/// filtering mechanism the work-product transfer uses (§2.6).
const SUMMARY_EXCLUDE: &str = ":(exclude)summary";
/// Outcome of a compaction merge attempt against the dispatching branch.
/// Land the compactor branch `compactor_id` into the dispatching branch
/// checked out at `parent_worktree` (ARCH §2.6). The checkout's `HEAD`
/// *is* the dispatching branch (§2.3), so the merge base derives from
/// ancestry and no branch name is passed. `--no-ff --no-commit` sets the
/// merge up, `git add -A` resolves any work-product modify/delete overlap
/// live-branch-wins (module docs), and the commit lands the two-parent
/// merge — the §5.5 rebuild point.
///
/// A compactor whose ref is already an ancestor of `HEAD` (nothing to
/// land) leaves no `MERGE_HEAD`; that is [`MergeOutcome::NoOp`], not an
/// error. A merge that fails to even begin (a bad ref) surfaces loudly.
/// Drop the compactor's private dialog from the staged merge (module
/// docs, §2.6/§2.7). The comparison is the staged merge result against
/// `HEAD` — still the dispatching branch's own tip under `--no-commit` —
/// so its three classes name themselves: a path **added** outside
/// `summary/` is a compactor transcript entry, a path **rewritten**
/// outside `summary/` is its `goal.md`/`soul.md` (or a filename collision
/// git resolved into a conflict), and a path **deleted** is the
/// compaction the merge exists to land. Both non-deletion classes are
/// restored to the dispatching branch's version — an addition by removal,
/// a rewrite by checkout — leaving only the summary and the deletions.
/// Paths the staged merge would land outside `summary/` under diff class
/// `filter` (`A` added, `M` rewritten), relative to the dispatching
/// branch's tip. `--no-renames` keeps the classes exhaustive: an
/// add/delete pair must not collapse into an `R` that escapes both.
/// Run `args` extended with `paths`, or nothing at all when `paths` is
/// empty — the general path with empty inputs (`docs/PRINCIPLES.md`), not
/// a special case: git would read a pathspec-less `rm` as an error.
/// True iff a merge is in progress in `parent_worktree` — i.e. `MERGE_HEAD`
/// resolves. Derived from git state, never a stored flag: `git rev-parse
/// --verify -q MERGE_HEAD` prints the sha and exits zero when a merge is
/// underway, and exits non-zero (captured as `Err`) when none is.