heyquilt 0.3.122 → 0.3.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/RELEASES.md +273 -17
  2. package/bin/quilt.js +55 -9
  3. package/desktop/main.js +16 -2
  4. package/package.json +3 -2
  5. package/src/agent-task-workflow.js +2 -2
  6. package/src/agents/common.js +13 -0
  7. package/src/ai-vm.js +9 -1
  8. package/src/api/activity.js +5 -0
  9. package/src/api/memory-store.js +16 -9
  10. package/src/api/notify.js +134 -0
  11. package/src/api/routes/browsers.js +29 -0
  12. package/src/api/routes/github.js +0 -13
  13. package/src/api/routes/relay.js +26 -3
  14. package/src/api/routes/session-migrate.js +7 -4
  15. package/src/api/routes/sessions.js +73 -1
  16. package/src/api/server.js +13 -7
  17. package/src/api/supabase-store.js +43 -4
  18. package/src/autowork.js +182 -16
  19. package/src/connection.js +36 -9
  20. package/src/control.js +129 -15
  21. package/src/force-date.js +27 -0
  22. package/src/fsutil.js +13 -3
  23. package/src/gitstate.js +38 -2
  24. package/src/history.js +13 -3
  25. package/src/mcp.js +87 -12
  26. package/src/merge3.js +11 -3
  27. package/src/now-summary.js +113 -0
  28. package/src/pathrules.js +12 -0
  29. package/src/protocol.js +10 -2
  30. package/src/quilt-default.js +84 -0
  31. package/src/quilt-history.js +106 -0
  32. package/src/quilt-remote.js +78 -0
  33. package/src/relay-commit.js +5 -0
  34. package/src/relay-lease.js +6 -2
  35. package/src/relay-mcp.js +151 -17
  36. package/src/relay-outbound.js +1 -1
  37. package/src/release-words.js +2 -2
  38. package/src/runner.js +20 -7
  39. package/src/seam-access.js +97 -0
  40. package/src/seam-claims.js +53 -0
  41. package/src/seam-url.js +15 -0
  42. package/src/server.js +1517 -86
  43. package/src/session-follow.js +461 -27
  44. package/src/session-layout3.js +410 -4
  45. package/src/session-migrate.js +15 -11
  46. package/src/session-quilt.js +1 -1
  47. package/src/session-seam.js +233 -20
  48. package/src/session.js +848 -67
  49. package/src/source-live/bring-in.js +283 -0
  50. package/src/source-live/live-block.js +504 -33
  51. package/src/source-live/live-square.js +16 -5
  52. package/src/status.js +5 -0
  53. package/src/ui/agent-guide.js +1 -1
  54. package/src/ui/app.css +85 -20
  55. package/src/ui/app.js +4 -1
  56. package/src/ui/chat.js +9 -3
  57. package/src/ui/cloud-ai-model.js +13 -0
  58. package/src/ui/cloud-ai.js +10 -2
  59. package/src/ui/common.js +6 -2
  60. package/src/ui/fileview.js +20 -7
  61. package/src/ui/force-words.js +19 -0
  62. package/src/ui/git-rule.js +9 -0
  63. package/src/ui/home.js +0 -147
  64. package/src/ui/loom-model.js +15 -1
  65. package/src/ui/quiltview.js +213 -0
  66. package/src/ui/seam-model.js +251 -0
  67. package/src/ui/session.js +413 -31
  68. package/src/ui/voice-call.js +83 -14
  69. package/src/ui/voice-model.js +39 -0
  70. package/src/ui/voice.js +167 -17
  71. package/src/ui-server.js +80 -28
  72. package/src/update-check.js +25 -1
  73. package/src/voice.js +13 -6
  74. package/src/git.js +0 -147
  75. package/src/quilts/AGENTS.md +0 -1
  76. package/src/quilts/CLAUDE.md +0 -1
  77. package/src/quilts/accounts.js +0 -136
  78. package/src/quilts/affected.js +0 -132
  79. package/src/quilts/archiver.js +0 -175
  80. package/src/quilts/autorevert.js +0 -49
  81. package/src/quilts/bisect-run.js +0 -31
  82. package/src/quilts/blame.js +0 -103
  83. package/src/quilts/block.js +0 -347
  84. package/src/quilts/bucket.js +0 -25
  85. package/src/quilts/cbor.js +0 -92
  86. package/src/quilts/checks.js +0 -308
  87. package/src/quilts/claims.js +0 -144
  88. package/src/quilts/cli.js +0 -536
  89. package/src/quilts/client.js +0 -137
  90. package/src/quilts/conflict-format.js +0 -190
  91. package/src/quilts/describe.js +0 -296
  92. package/src/quilts/dogfood.js +0 -82
  93. package/src/quilts/errors.js +0 -8
  94. package/src/quilts/events.js +0 -171
  95. package/src/quilts/export-git.js +0 -130
  96. package/src/quilts/export-remote.js +0 -156
  97. package/src/quilts/export.js +0 -81
  98. package/src/quilts/fastmerge.js +0 -41
  99. package/src/quilts/hash.js +0 -44
  100. package/src/quilts/history.js +0 -266
  101. package/src/quilts/ids.js +0 -12
  102. package/src/quilts/import-follow.js +0 -236
  103. package/src/quilts/import-git.js +0 -294
  104. package/src/quilts/import.js +0 -196
  105. package/src/quilts/index.js +0 -94
  106. package/src/quilts/keys.js +0 -186
  107. package/src/quilts/limits.js +0 -67
  108. package/src/quilts/lines.js +0 -49
  109. package/src/quilts/live.js +0 -50
  110. package/src/quilts/merge-smart.js +0 -114
  111. package/src/quilts/merge.js +0 -501
  112. package/src/quilts/oauth.js +0 -202
  113. package/src/quilts/objects.js +0 -144
  114. package/src/quilts/pack.js +0 -118
  115. package/src/quilts/records.js +0 -211
  116. package/src/quilts/resolutions.js +0 -69
  117. package/src/quilts/reviews.js +0 -208
  118. package/src/quilts/routes.js +0 -186
  119. package/src/quilts/runner.js +0 -95
  120. package/src/quilts/server.js +0 -144
  121. package/src/quilts/service.js +0 -963
  122. package/src/quilts/setup.js +0 -41
  123. package/src/quilts/signing.js +0 -149
  124. package/src/quilts/squares.js +0 -239
  125. package/src/quilts/store-memory.js +0 -844
  126. package/src/quilts/store-rpc.js +0 -90
  127. package/src/quilts/store-supabase.js +0 -13
  128. package/src/quilts/tags.js +0 -64
  129. package/src/quilts/textdiff.js +0 -96
  130. package/src/quilts/tokens.js +0 -66
  131. package/src/quilts/tools.js +0 -403
  132. package/src/quilts/tree.js +0 -351
  133. package/src/quilts/webhook.js +0 -116
  134. package/src/quilts/window.js +0 -121
  135. package/src/quilts/xdiff.js +0 -435
  136. package/src/ui/branches.js +0 -111
@@ -1,435 +0,0 @@
1
- // A port of git's xdiff line diff (Myers with xdiff's heuristics, record cleanup, and change
2
- // compaction with the indent heuristic), so `blame` and `diff` choose the same lines git does.
3
- //
4
- // Ported from git's xdiff/xprepare.c, xdiff/xdiffi.c and xdiff-interface.c (trim_common_tail).
5
- // LibXDiff by Davide Libenzi, Copyright (C) 2003, LGPL-2.1-or-later; git's changes are under the
6
- // same terms. Structure and names follow the C so the two can be compared line by line.
7
-
8
- const XDL_KPDIS_RUN = 4
9
- const XDL_MAX_EQLIMIT = 1024
10
- const XDL_SIMSCAN_WINDOW = 100
11
- const XDL_MAX_COST_MIN = 256
12
- const XDL_HEUR_MIN_COST = 256
13
- const XDL_LINE_MAX = Number.MAX_SAFE_INTEGER
14
- const XDL_SNAKE_CNT = 20
15
- const XDL_K_HEUR = 4
16
- const DISCARD = 0; const KEEP = 1; const INVESTIGATE = 2
17
-
18
- function bogosqrt (n) { let i = 1; for (; n > 0; n = Math.floor(n / 4)) i *= 2; return i }
19
-
20
- /** git's trim_common_tail: the byte length to drop from both ends' tails (blame only). */
21
- function commonTail (a, b) {
22
- const blk = 1024
23
- let trimmed = 0; let recovered = 0
24
- const smaller = Math.min(a.length, b.length)
25
- while (blk + trimmed <= smaller &&
26
- a.compare(b, b.length - trimmed - blk, b.length - trimmed, a.length - trimmed - blk, a.length - trimmed) === 0) trimmed += blk
27
- const ap = a.length - trimmed
28
- while (recovered < trimmed) if (a[ap + recovered++] === 10) break
29
- return trimmed - recovered
30
- }
31
-
32
- /** Records of a buffer: [start, end) per line, each with its \n. */
33
- function records (buf, size) {
34
- const starts = []
35
- let s = 0
36
- while (s < size) {
37
- let e = buf.indexOf(10, s)
38
- e = e < 0 || e >= size ? size : e + 1
39
- starts.push(s, e)
40
- s = e
41
- }
42
- return starts
43
- }
44
-
45
- function prepare (buf, size, classes, counts, pass) {
46
- const r = records(buf, size)
47
- const n = r.length / 2
48
- const mph = new Int32Array(n)
49
- for (let i = 0; i < n; i++) {
50
- const key = buf.toString('latin1', r[2 * i], r[2 * i + 1])
51
- let id = classes.get(key)
52
- if (id === undefined) { id = classes.size; classes.set(key, id); counts.push(0, 0) }
53
- counts[2 * id + pass]++
54
- mph[i] = id
55
- }
56
- return { buf, r, nrec: n, mph, changed: new Uint8Array(n + 2), ref: null, nreff: 0, dstart: 0, dend: n - 1 }
57
- }
58
-
59
- function cleanMmatch (action, i, len) {
60
- let s = 0; let e = len - 1
61
- if (i - s > XDL_SIMSCAN_WINDOW) s = i - XDL_SIMSCAN_WINDOW
62
- if (e - i > XDL_SIMSCAN_WINDOW) e = i + XDL_SIMSCAN_WINDOW
63
- let r; let rdis0 = 0; let rpdis0 = 1; let rdis1 = 0; let rpdis1 = 1
64
- for (r = 1; i - r >= s; r++) {
65
- const a = action[i - r]
66
- if (a === DISCARD) rdis0++
67
- else if (a === INVESTIGATE) rpdis0++
68
- else break
69
- }
70
- if (rdis0 === 0) return false
71
- for (r = 1; i + r <= e; r++) {
72
- const a = action[i + r]
73
- if (a === DISCARD) rdis1++
74
- else if (a === INVESTIGATE) rpdis1++
75
- else break
76
- }
77
- if (rdis1 === 0) return false
78
- rdis1 += rdis0
79
- rpdis1 += rpdis0
80
- return rpdis1 * XDL_KPDIS_RUN < rpdis1 + rdis1
81
- }
82
-
83
- function trimEnds (x1, x2) {
84
- const lim = Math.min(x1.nrec, x2.nrec)
85
- let i = 0
86
- while (i < lim && x1.mph[i] === x2.mph[i]) i++
87
- x1.dstart = x2.dstart = i
88
- const lim2 = lim - i
89
- let j = 0
90
- while (j < lim2 && x1.mph[x1.nrec - 1 - j] === x2.mph[x2.nrec - 1 - j]) j++
91
- x1.dend = x1.nrec - j - 1
92
- x2.dend = x2.nrec - j - 1
93
- }
94
-
95
- function cleanupRecords (counts, x1, x2) {
96
- const off = x1.dstart
97
- const len1 = x1.dend - off + 1
98
- const len2 = x2.dend - off + 1
99
- const one = (x, len, other) => {
100
- const action = new Uint8Array(Math.max(len, 0))
101
- let mlim = bogosqrt(x.nrec)
102
- if (mlim > XDL_MAX_EQLIMIT) mlim = XDL_MAX_EQLIMIT
103
- for (let i = 0; i < len; i++) {
104
- const nm = counts[2 * x.mph[i + off] + other]
105
- action[i] = nm === 0 ? DISCARD : nm < mlim ? KEEP : INVESTIGATE
106
- }
107
- x.ref = new Int32Array(Math.max(len, 0))
108
- x.nreff = 0
109
- for (let i = 0; i < len; i++) {
110
- let a = action[i]
111
- if (a === INVESTIGATE) a = cleanMmatch(action, i, len) ? DISCARD : KEEP
112
- if (a === KEEP) x.ref[x.nreff++] = i + off
113
- else x.changed[i + off + 1] = 1
114
- }
115
- }
116
- one(x1, len1, 1)
117
- one(x2, len2, 0)
118
- }
119
-
120
- function doDiff (x1, x2) {
121
- const h1 = new Int32Array(x1.nreff); for (let i = 0; i < x1.nreff; i++) h1[i] = x1.mph[x1.ref[i]]
122
- const h2 = new Int32Array(x2.nreff); for (let i = 0; i < x2.nreff; i++) h2[i] = x2.mph[x2.ref[i]]
123
- const ndiags = x1.nreff + x2.nreff + 3
124
- const kvd = new Float64Array(2 * ndiags + 2)
125
- const F = x2.nreff + 1
126
- const B = ndiags + x2.nreff + 1
127
- let mxcost = bogosqrt(ndiags)
128
- if (mxcost < XDL_MAX_COST_MIN) mxcost = XDL_MAX_COST_MIN
129
- const snakeCnt = XDL_SNAKE_CNT; const heurMin = XDL_HEUR_MIN_COST
130
- const spl = { i1: 0, i2: 0, minLo: 0, minHi: 0 }
131
-
132
- function split (off1, lim1, off2, lim2, needMin) {
133
- const dmin = off1 - lim2; const dmax = lim1 - off2
134
- const fmid = off1 - off2; const bmid = lim1 - lim2
135
- const odd = (fmid - bmid) & 1
136
- let fmin = fmid; let fmax = fmid; let bmin = bmid; let bmax = bmid
137
- let i1, i2, prev1, best, dd, v, k, d
138
- kvd[F + fmid] = off1
139
- kvd[B + bmid] = lim1
140
- for (let ec = 1; ; ec++) {
141
- let gotSnake = false
142
- if (fmin > dmin) kvd[F + (--fmin) - 1] = -1; else ++fmin
143
- if (fmax < dmax) kvd[F + (++fmax) + 1] = -1; else --fmax
144
- for (d = fmax; d >= fmin; d -= 2) {
145
- if (kvd[F + d - 1] >= kvd[F + d + 1]) i1 = kvd[F + d - 1] + 1
146
- else i1 = kvd[F + d + 1]
147
- prev1 = i1
148
- i2 = i1 - d
149
- while (i1 < lim1 && i2 < lim2 && h1[i1] === h2[i2]) { i1++; i2++ }
150
- if (i1 - prev1 > snakeCnt) gotSnake = true
151
- kvd[F + d] = i1
152
- if (odd && bmin <= d && d <= bmax && kvd[B + d] <= i1) {
153
- spl.i1 = i1; spl.i2 = i2; spl.minLo = spl.minHi = 1
154
- return
155
- }
156
- }
157
- if (bmin > dmin) kvd[B + (--bmin) - 1] = XDL_LINE_MAX; else ++bmin
158
- if (bmax < dmax) kvd[B + (++bmax) + 1] = XDL_LINE_MAX; else --bmax
159
- for (d = bmax; d >= bmin; d -= 2) {
160
- if (kvd[B + d - 1] < kvd[B + d + 1]) i1 = kvd[B + d - 1]
161
- else i1 = kvd[B + d + 1] - 1
162
- prev1 = i1
163
- i2 = i1 - d
164
- while (i1 > off1 && i2 > off2 && h1[i1 - 1] === h2[i2 - 1]) { i1--; i2-- }
165
- if (prev1 - i1 > snakeCnt) gotSnake = true
166
- kvd[B + d] = i1
167
- if (!odd && fmin <= d && d <= fmax && i1 <= kvd[F + d]) {
168
- spl.i1 = i1; spl.i2 = i2; spl.minLo = spl.minHi = 1
169
- return
170
- }
171
- }
172
- if (needMin) continue
173
- if (gotSnake && ec > heurMin) {
174
- for (best = 0, d = fmax; d >= fmin; d -= 2) {
175
- dd = d > fmid ? d - fmid : fmid - d
176
- i1 = kvd[F + d]
177
- i2 = i1 - d
178
- v = (i1 - off1) + (i2 - off2) - dd
179
- if (v > XDL_K_HEUR * ec && v > best && off1 + snakeCnt <= i1 && i1 < lim1 && off2 + snakeCnt <= i2 && i2 < lim2) {
180
- for (k = 1; h1[i1 - k] === h2[i2 - k]; k++) {
181
- if (k === snakeCnt) { best = v; spl.i1 = i1; spl.i2 = i2; break }
182
- }
183
- }
184
- }
185
- if (best > 0) { spl.minLo = 1; spl.minHi = 0; return }
186
- for (best = 0, d = bmax; d >= bmin; d -= 2) {
187
- dd = d > bmid ? d - bmid : bmid - d
188
- i1 = kvd[B + d]
189
- i2 = i1 - d
190
- v = (lim1 - i1) + (lim2 - i2) - dd
191
- if (v > XDL_K_HEUR * ec && v > best && off1 < i1 && i1 <= lim1 - snakeCnt && off2 < i2 && i2 <= lim2 - snakeCnt) {
192
- for (k = 0; h1[i1 + k] === h2[i2 + k]; k++) {
193
- if (k === snakeCnt - 1) { best = v; spl.i1 = i1; spl.i2 = i2; break }
194
- }
195
- }
196
- }
197
- if (best > 0) { spl.minLo = 0; spl.minHi = 1; return }
198
- }
199
- if (ec >= mxcost) {
200
- let fbest = -1; let fbest1 = -1
201
- for (d = fmax; d >= fmin; d -= 2) {
202
- i1 = Math.min(kvd[F + d], lim1)
203
- i2 = i1 - d
204
- if (lim2 < i2) { i1 = lim2 + d; i2 = lim2 }
205
- if (fbest < i1 + i2) { fbest = i1 + i2; fbest1 = i1 }
206
- }
207
- let bbest = XDL_LINE_MAX; let bbest1 = XDL_LINE_MAX
208
- for (d = bmax; d >= bmin; d -= 2) {
209
- i1 = Math.max(off1, kvd[B + d])
210
- i2 = i1 - d
211
- if (i2 < off2) { i1 = off2 + d; i2 = off2 }
212
- if (i1 + i2 < bbest) { bbest = i1 + i2; bbest1 = i1 }
213
- }
214
- if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) {
215
- spl.i1 = fbest1; spl.i2 = fbest - fbest1; spl.minLo = 1; spl.minHi = 0
216
- } else {
217
- spl.i1 = bbest1; spl.i2 = bbest - bbest1; spl.minLo = 0; spl.minHi = 1
218
- }
219
- return
220
- }
221
- }
222
- }
223
-
224
- // xdl_recs_cmp, with its recursion on an explicit stack (depth can reach thousands).
225
- const stack = [[0, x1.nreff, 0, x2.nreff, 0]]
226
- while (stack.length) {
227
- let [off1, lim1, off2, lim2, needMin] = stack.pop()
228
- while (off1 < lim1 && off2 < lim2 && h1[off1] === h2[off2]) { off1++; off2++ }
229
- while (off1 < lim1 && off2 < lim2 && h1[lim1 - 1] === h2[lim2 - 1]) { lim1--; lim2-- }
230
- if (off1 === lim1) {
231
- for (; off2 < lim2; off2++) x2.changed[x2.ref[off2] + 1] = 1
232
- } else if (off2 === lim2) {
233
- for (; off1 < lim1; off1++) x1.changed[x1.ref[off1] + 1] = 1
234
- } else {
235
- spl.i1 = spl.i2 = 0
236
- split(off1, lim1, off2, lim2, needMin)
237
- const { i1, i2, minLo, minHi } = spl
238
- // Low half first, as the C recursion does (it only marks, so order matters not for the
239
- // result, but keep it for fidelity).
240
- stack.push([i1, lim1, i2, lim2, minHi])
241
- stack.push([off1, i1, off2, i2, minLo])
242
- }
243
- }
244
- }
245
-
246
- // --- change compaction (xdl_change_compact) with the indent heuristic ---
247
-
248
- const MAX_INDENT = 200
249
- const MAX_BLANKS = 20
250
- const START_OF_FILE_PENALTY = 1
251
- const END_OF_FILE_PENALTY = 21
252
- const TOTAL_BLANK_WEIGHT = -30
253
- const POST_BLANK_WEIGHT = 6
254
- const RELATIVE_INDENT_PENALTY = -4
255
- const RELATIVE_INDENT_WITH_BLANK_PENALTY = 10
256
- const RELATIVE_OUTDENT_PENALTY = 24
257
- const RELATIVE_OUTDENT_WITH_BLANK_PENALTY = 17
258
- const RELATIVE_DEDENT_PENALTY = 23
259
- const RELATIVE_DEDENT_WITH_BLANK_PENALTY = 17
260
- const INDENT_WEIGHT = 60
261
- const INDENT_HEURISTIC_MAX_SLIDING = 100
262
-
263
- function getIndent (x, i) {
264
- let ret = 0
265
- const buf = x.buf
266
- for (let p = x.r[2 * i], e = x.r[2 * i + 1]; p < e; p++) {
267
- const c = buf[p]
268
- // isspace: space, \t, \n, \v, \f, \r
269
- if (!(c === 32 || (c >= 9 && c <= 13))) return ret
270
- else if (c === 32) ret += 1
271
- else if (c === 9) ret += 8 - ret % 8
272
- if (ret >= MAX_INDENT) return MAX_INDENT
273
- }
274
- return -1
275
- }
276
-
277
- function measureSplit (x, split) {
278
- const m = { eof: 0, indent: -1, preBlank: 0, preIndent: -1, postBlank: 0, postIndent: -1 }
279
- if (split >= x.nrec) { m.eof = 1; m.indent = -1 } else m.indent = getIndent(x, split)
280
- for (let i = split - 1; i >= 0; i--) {
281
- m.preIndent = getIndent(x, i)
282
- if (m.preIndent !== -1) break
283
- m.preBlank += 1
284
- if (m.preBlank === MAX_BLANKS) { m.preIndent = 0; break }
285
- }
286
- for (let i = split + 1; i < x.nrec; i++) {
287
- m.postIndent = getIndent(x, i)
288
- if (m.postIndent !== -1) break
289
- m.postBlank += 1
290
- if (m.postBlank === MAX_BLANKS) { m.postIndent = 0; break }
291
- }
292
- return m
293
- }
294
-
295
- function scoreAddSplit (m, s) {
296
- if (m.preIndent === -1 && m.preBlank === 0) s.penalty += START_OF_FILE_PENALTY
297
- if (m.eof) s.penalty += END_OF_FILE_PENALTY
298
- const postBlank = m.indent === -1 ? 1 + m.postBlank : 0
299
- const totalBlank = m.preBlank + postBlank
300
- s.penalty += TOTAL_BLANK_WEIGHT * totalBlank
301
- s.penalty += POST_BLANK_WEIGHT * postBlank
302
- const indent = m.indent !== -1 ? m.indent : m.postIndent
303
- const anyBlanks = totalBlank !== 0
304
- s.effectiveIndent += indent
305
- if (indent === -1 || m.preIndent === -1) {
306
- // no adjustment
307
- } else if (indent > m.preIndent) {
308
- s.penalty += anyBlanks ? RELATIVE_INDENT_WITH_BLANK_PENALTY : RELATIVE_INDENT_PENALTY
309
- } else if (indent === m.preIndent) {
310
- // same level
311
- } else if (m.postIndent !== -1 && m.postIndent > indent) {
312
- s.penalty += anyBlanks ? RELATIVE_OUTDENT_WITH_BLANK_PENALTY : RELATIVE_OUTDENT_PENALTY
313
- } else {
314
- s.penalty += anyBlanks ? RELATIVE_DEDENT_WITH_BLANK_PENALTY : RELATIVE_DEDENT_PENALTY
315
- }
316
- }
317
-
318
- function scoreCmp (s1, s2) {
319
- const c = (s1.effectiveIndent > s2.effectiveIndent) - (s1.effectiveIndent < s2.effectiveIndent)
320
- return INDENT_WEIGHT * c + (s1.penalty - s2.penalty)
321
- }
322
-
323
- // `changed` is offset by one: changed[i + 1] is record i; changed[0] and changed[nrec + 1] are 0.
324
- function changeCompact (x, xo) {
325
- const ch = x.changed; const cho = xo.changed
326
- const C = (i) => ch[i + 1]
327
- const CO = (i) => cho[i + 1]
328
- const g = { start: 0, end: 0 }; const go = { start: 0, end: 0 }
329
- while (C(g.end)) g.end++
330
- while (CO(go.end)) go.end++
331
- const next = (xx, c, gg) => {
332
- if (gg.end === xx.nrec) return -1
333
- gg.start = gg.end + 1
334
- for (gg.end = gg.start; c(gg.end); gg.end++);
335
- return 0
336
- }
337
- const previous = (c, gg) => {
338
- if (gg.start === 0) return -1
339
- gg.end = gg.start - 1
340
- for (gg.start = gg.end; c(gg.start - 1); gg.start--);
341
- return 0
342
- }
343
- const slideDown = () => {
344
- if (g.end < x.nrec && x.mph[g.start] === x.mph[g.end]) {
345
- ch[g.start++ + 1] = 0
346
- ch[g.end++ + 1] = 1
347
- while (C(g.end)) g.end++
348
- return 0
349
- }
350
- return -1
351
- }
352
- const slideUp = () => {
353
- if (g.start > 0 && x.mph[g.start - 1] === x.mph[g.end - 1]) {
354
- ch[--g.start + 1] = 1
355
- ch[--g.end + 1] = 0
356
- while (C(g.start - 1)) g.start--
357
- return 0
358
- }
359
- return -1
360
- }
361
- const bug = (s) => { throw new Error('xdiff: ' + s) }
362
- for (;;) {
363
- if (g.end !== g.start) {
364
- let groupsize, earliestEnd, endMatchingOther
365
- do {
366
- groupsize = g.end - g.start
367
- endMatchingOther = -1
368
- while (!slideUp()) if (previous(CO, go)) bug('group sync broken sliding up')
369
- earliestEnd = g.end
370
- if (go.end > go.start) endMatchingOther = g.end
371
- for (;;) {
372
- if (slideDown()) break
373
- if (next(xo, CO, go)) bug('group sync broken sliding down')
374
- if (go.end > go.start) endMatchingOther = g.end
375
- }
376
- } while (groupsize !== g.end - g.start)
377
- if (g.end === earliestEnd) {
378
- // no shifting possible
379
- } else if (endMatchingOther !== -1) {
380
- while (go.end === go.start) {
381
- if (slideUp()) bug('match disappeared')
382
- if (previous(CO, go)) bug('group sync broken sliding to match')
383
- }
384
- } else {
385
- let shift = earliestEnd
386
- let bestShift = -1; let bestScore = null
387
- if (g.end - groupsize - 1 > shift) shift = g.end - groupsize - 1
388
- if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift) shift = g.end - INDENT_HEURISTIC_MAX_SLIDING
389
- for (; shift <= g.end; shift++) {
390
- const score = { effectiveIndent: 0, penalty: 0 }
391
- scoreAddSplit(measureSplit(x, shift), score)
392
- scoreAddSplit(measureSplit(x, shift - groupsize), score)
393
- if (bestShift === -1 || scoreCmp(score, bestScore) <= 0) { bestScore = score; bestShift = shift }
394
- }
395
- while (g.end > bestShift) {
396
- if (slideUp()) bug('best shift unreached')
397
- if (previous(CO, go)) bug('group sync broken sliding to blank line')
398
- }
399
- }
400
- }
401
- if (next(x, C, g)) break
402
- if (next(xo, CO, go)) bug('group sync broken moving to next group')
403
- }
404
- }
405
-
406
- /**
407
- * git's diff of two byte buffers: hunks [{ a: [start, len], b: [start, len] }] over their lines
408
- * (a final line without \n counts as different from the same line with one). `trimTail`
409
- * reproduces what git does for a zero-context diff (blame): drop the common tail in 1 KiB blocks.
410
- */
411
- export function xdiff (a, b, { trimTail = false } = {}) {
412
- let sa = a.length; let sb = b.length
413
- if (trimTail) { const t = commonTail(a, b); sa -= t; sb -= t }
414
- const classes = new Map(); const counts = []
415
- const x1 = prepare(a, sa, classes, counts, 0)
416
- const x2 = prepare(b, sb, classes, counts, 1)
417
- trimEnds(x1, x2)
418
- cleanupRecords(counts, x1, x2)
419
- doDiff(x1, x2)
420
- changeCompact(x1, x2)
421
- changeCompact(x2, x1)
422
- // xdl_build_script, forwards.
423
- const out = []
424
- const c1 = x1.changed; const c2 = x2.changed
425
- let i1 = 0; let i2 = 0
426
- while (i1 < x1.nrec || i2 < x2.nrec) {
427
- if (c1[i1 + 1] || c2[i2 + 1]) {
428
- const s1 = i1; const s2 = i2
429
- while (c1[i1 + 1]) i1++
430
- while (c2[i2 + 1]) i2++
431
- out.push({ a: [s1, i1 - s1], b: [s2, i2 - s2] })
432
- } else { i1++; i2++ }
433
- }
434
- return out
435
- }
@@ -1,111 +0,0 @@
1
- // The branch menu in the session's top bar: which branch each folder, AI
2
- // session and worktree is on, and how each stands against its upstream. Read
3
- // only: nobody switches branches from here (a folder's branch is its own, and
4
- // its AIs work on it). Commits pushed or merged elsewhere come in by
5
- // themselves; "Bring in now" asks this folder to look at once.
6
- import { esc, ago, I } from './common.js'
7
-
8
- /** One line on how a branch stands against its upstream ('' without one). */
9
- export function upstreamText (u) {
10
- if (!u || !u.name) return ''
11
- const n = (k, w) => `${k} ${w}${k === 1 ? '' : 's'}`
12
- // A failed fetch: what git last knew is stale, so never "up to date".
13
- if (u.fetchOk === false) return `can't fetch ${u.name}: ${u.fetchError || 'git fetch failed'}`
14
- const by = u.mergedBy ? `; being merged by ${u.clashMine ? 'your AI' : u.mergedBy}` : ''
15
- if (u.diverged) return `diverged from ${u.name}: ${n(u.ahead, 'commit')} here, ${n(u.behind, 'commit')} there${by}`
16
- const clash = Array.isArray(u.conflicts) ? u.conflicts.length : u.conflicts
17
- if (clash) return `${n(u.behind, 'commit')} behind ${u.name}; ${n(clash, 'file')} clash with the session's work${by}`
18
- if (u.waiting) return `${n(u.behind, 'commit')} behind ${u.name}; waiting: ${u.waiting}`
19
- if (u.behind) return `${n(u.behind, 'commit')} behind ${u.name}`
20
- if (u.ahead) return `${n(u.ahead, 'commit')} ahead of ${u.name}`
21
- return `up to date with ${u.name}`
22
- }
23
-
24
- /**
25
- * What the relay does for a branch no folder is online on: "brought in by the relay 5m ago (3
26
- * commits)", a clash it handed out, why it can't, and when it last looked; '' when it hasn't.
27
- */
28
- export function relayText (r, now = Date.now()) {
29
- if (!r || !(r.checkedAt || (r.brought && r.brought.at))) return ''
30
- const n = (k, w) => `${k} ${w}${k === 1 ? '' : 's'}`
31
- const when = (ts) => { const a = ago(ts, now); return a === 'now' ? 'just now' : /^\d+[mh]$/.test(a) ? `${a} ago` : `on ${a}` }
32
- const bits = []
33
- if (r.brought && r.brought.at) bits.push(`brought in by the relay ${when(r.brought.at)} (${n(r.brought.count, 'commit')})`)
34
- if (r.clashTask) bits.push(`a clash with ${r.upstream || 'upstream'} is with an agent`)
35
- if (r.problem) bits.push(r.problem)
36
- if (r.checkedAt) bits.push(`relay checked ${when(r.checkedAt)}`)
37
- return bits.join(' · ')
38
- }
39
-
40
- /** The agent whose own branch `name` is (quilt/<agent>/<topic>), or ''; never a protected branch's live branch or another session's. */
41
- export function agentOfBranch (name) {
42
- const m = /^quilt\/([^/]+)\/./.exec(String(name || ''))
43
- return m && m[1] !== 'live' && !m[1].startsWith('session-') ? m[1] : ''
44
- }
45
-
46
- /** Where a branch stands with GitHub, in a few words for the menu ('' when it doesn't sync). */
47
- export function syncShort (r) {
48
- const s = r && r.sync
49
- if (!s) return ''
50
- if (s.state === 'synced') return 'committed'
51
- if (s.state === 'github-pending') return 'uncommitted changes'
52
- if (s.state === 'conflict-held') return 'merge held'
53
- if (s.state === 'pr-pending') return 'commits go through a pull request'
54
- if (s.state === 'offline') return 'GitHub unreachable'
55
- return s.why || 'not syncing'
56
- }
57
-
58
- /** Whether the label should draw the eye: this folder is behind and Quilt can't bring it in by itself. */
59
- export function needsHand (u) {
60
- return !!(u && (u.diverged || u.fetchOk === false || (Array.isArray(u.conflicts) ? u.conflicts.length : u.conflicts)))
61
- }
62
-
63
- /** The menu's markup. `git`: this folder's (status.git); `branches`: status.branches; `me`: our name; `fold`: lists folded behind one line. */
64
- export function branchMenuHtml ({ git, branches = [], me = '', syncing = false, fold = false, now = Date.now() }) {
65
- const u = git && git.upstream
66
- // Folded (the git panel): the branch in one line, its upstream beside it (in full on hover), and a small Check.
67
- const compact = fold && git ? `<div class="br-here br-compact">
68
- <div class="br-line">${I.branch}<b class="br-name">${esc(git.key)}</b>${u ? `<span class="br-sub${needsHand(u) ? ' warn' : ''}" title="${esc(upstreamText(u))}">${esc(upstreamText(u))}</span>` : ''}${u ? `<button type="button" class="btn sm ghost br-sync" data-branch-sync ${syncing ? 'disabled' : ''} title="Look for new commits on ${esc(u.name || 'the upstream')}">${I.refresh}<span>${syncing ? 'Checking' : 'Check'}</span></button>` : ''}</div>
69
- ${u && Array.isArray(u.conflicts) && u.conflicts.length ? `<ul class="br-clash">${u.conflicts.slice(0, 6).map((c) => `<li><code>${esc(c.path)}</code> ${esc(c.why)}</li>`).join('')}</ul><div class="br-hint">Ask your AI to pull and resolve these: Quilt brings nothing in until they're settled.</div>` : ''}
70
- ${u && u.diverged ? '<div class="br-hint">Quilt never merges git history: ask your AI to pull or rebase, and the session takes in the result.</div>' : ''}
71
- </div>` : ''
72
- const here = compact || (git ? `<div class="br-here">
73
- <div class="br-title">This folder is on <b>${esc(git.key)}</b></div>
74
- ${u ? `<div class="br-sub${needsHand(u) ? ' warn' : ''}">${esc(upstreamText(u))}</div>` : '<div class="br-sub">No upstream: nothing to bring in.</div>'}
75
- ${u && Array.isArray(u.conflicts) && u.conflicts.length ? `<ul class="br-clash">${u.conflicts.slice(0, 6).map((c) => `<li><code>${esc(c.path)}</code> ${esc(c.why)}</li>`).join('')}</ul><div class="br-hint">Ask your AI to pull and resolve these: Quilt brings nothing in until they're settled.</div>` : ''}
76
- ${u && u.diverged ? '<div class="br-hint">Quilt never merges git history: ask your AI to pull or rebase, and the session takes in the result.</div>' : ''}
77
- ${u && u.brought && u.brought.at && !needsHand(u) ? `<div class="br-sub">Brought in ${u.brought.count ? `${u.brought.count} commit${u.brought.count === 1 ? '' : 's'}` : 'commits'} ${esc(ago(u.brought.at, now))} by itself</div>` : ''}
78
- ${u ? `<button type="button" class="btn sm ghost br-sync" data-branch-sync ${syncing ? 'disabled' : ''}>${syncing ? 'Looking…' : needsHand(u) ? 'Check again' : 'Check for new commits'}</button>` : ''}
79
- </div>` : '')
80
- // Agents' own branches (plan §2: people see them live) after everyone else's, each with its pull request and where it stands.
81
- const order = [...branches].sort((a, b) => (agentOfBranch(a.name) ? 1 : 0) - (agentOfBranch(b.name) ? 1 : 0))
82
- const row = (b) => {
83
- const who = [
84
- ...b.folders.map((f) => `<span class="br-who${needsHand(f.upstream) ? ' warn' : ''}" title="${esc(`${f.name === me ? 'Your folder' : `${f.name}'s folder`}${f.upstream ? `: ${upstreamText(f.upstream)}` : ''}`)}">${esc(f.name === me ? 'you' : f.name)}</span>`),
85
- ...b.ais.map((n) => `<span class="br-who ai" title="AI session">${esc(n)}</span>`),
86
- ...b.worktrees.map((w) => `<span class="br-who wt" title="${esc(`Worktree ${w}`)}">${w === b.name ? 'worktree' : `worktree ${esc(w)}`}</span>`)
87
- ].join('')
88
- const status = upstreamText(b.upstream)
89
- const relay = relayText(b.relay, now)
90
- const last = b.last && b.last.ts ? `${esc(b.last.author || 'someone')} · ${esc(ago(b.last.ts, now))}` : ''
91
- const tags = [b.default ? '<span class="br-tag">default</span>' : '', b.session ? '<span class="br-tag">in the session</span>' : '', b.full ? '<span class="br-tag warn" title="full: new changes aren\'t saved">full</span>' : ''].filter(Boolean).join('')
92
- const agent = agentOfBranch(b.name)
93
- const pr = b.relay && b.relay.pr && b.relay.pr.number ? b.relay.pr : null
94
- const live = syncShort(b.relay)
95
- // The agent as people know it: whoever on the branch has the name its slug was made from.
96
- const flat = (n) => String(n || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
97
- const known = agent ? [...(b.hosted || []), ...(b.folders || []).map((f) => f.name), ...(b.ais || [])].find((n) => flat(n) === agent) : ''
98
- const agentLine = agent ? [`<span>${esc(known || agent)}'s branch</span>`, live && `<span>${esc(live)}</span>`, pr && (pr.url ? `<a href="${esc(pr.url)}" target="_blank" rel="noreferrer">pull request #${pr.number}</a>` : `<span>pull request #${pr.number}</span>`)].filter(Boolean).join(' · ') : ''
99
- return `<li class="br-row${git && b.name === git.key ? ' on' : ''}${agent ? ' agent' : ''}">
100
- <div class="br-line"><span class="br-name" title="${esc(b.name)}">${esc(b.name)}</span>${tags}${who ? `<span class="br-people">${who}</span>` : ''}</div>
101
- ${agentLine ? `<div class="br-meta br-agent">${agentLine}</div>` : ''}
102
- ${status || last ? `<div class="br-meta">${[status && `<span class="${needsHand(b.upstream) ? 'warn' : ''}">${esc(status)}</span>`, last && `<span title="${esc(b.last.subject || '')}">last commit ${last}</span>`].filter(Boolean).join(' · ')}</div>` : ''}
103
- ${relay ? `<div class="br-meta br-relay${b.relay.problem ? ' warn' : ''}">${esc(relay)}</div>` : ''}
104
- </li>`
105
- }
106
- const people = order.filter((b) => !agentOfBranch(b.name)).map(row).join('')
107
- const agents = order.filter((b) => agentOfBranch(b.name)).map(row).join('')
108
- const lists = `${people ? `<div class="br-head">Branches</div><ul class="br-list">${people}</ul>` : ''}${agents ? `<div class="br-head">Agents' branches</div><ul class="br-list">${agents}</ul>` : ''}`
109
- // `fold`: the lists behind one line (the git panel, where the changes come after them).
110
- return fold && lists ? `${here}<details class="br-all"><summary>Every branch <span class="br-n">${branches.length}</span></summary>${lists}</details>` : `${here}${lists}`
111
- }