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.
- package/RELEASES.md +273 -17
- package/bin/quilt.js +55 -9
- package/desktop/main.js +16 -2
- package/package.json +3 -2
- package/src/agent-task-workflow.js +2 -2
- package/src/agents/common.js +13 -0
- package/src/ai-vm.js +9 -1
- package/src/api/activity.js +5 -0
- package/src/api/memory-store.js +16 -9
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- package/src/api/routes/github.js +0 -13
- package/src/api/routes/relay.js +26 -3
- package/src/api/routes/session-migrate.js +7 -4
- package/src/api/routes/sessions.js +73 -1
- package/src/api/server.js +13 -7
- package/src/api/supabase-store.js +43 -4
- package/src/autowork.js +182 -16
- package/src/connection.js +36 -9
- package/src/control.js +129 -15
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/gitstate.js +38 -2
- package/src/history.js +13 -3
- package/src/mcp.js +87 -12
- package/src/merge3.js +11 -3
- package/src/now-summary.js +113 -0
- package/src/pathrules.js +12 -0
- package/src/protocol.js +10 -2
- package/src/quilt-default.js +84 -0
- package/src/quilt-history.js +106 -0
- package/src/quilt-remote.js +78 -0
- package/src/relay-commit.js +5 -0
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +151 -17
- package/src/relay-outbound.js +1 -1
- package/src/release-words.js +2 -2
- package/src/runner.js +20 -7
- package/src/seam-access.js +97 -0
- package/src/seam-claims.js +53 -0
- package/src/seam-url.js +15 -0
- package/src/server.js +1517 -86
- package/src/session-follow.js +461 -27
- package/src/session-layout3.js +410 -4
- package/src/session-migrate.js +15 -11
- package/src/session-quilt.js +1 -1
- package/src/session-seam.js +233 -20
- package/src/session.js +848 -67
- package/src/source-live/bring-in.js +283 -0
- package/src/source-live/live-block.js +504 -33
- package/src/source-live/live-square.js +16 -5
- package/src/status.js +5 -0
- package/src/ui/agent-guide.js +1 -1
- package/src/ui/app.css +85 -20
- package/src/ui/app.js +4 -1
- package/src/ui/chat.js +9 -3
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -2
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -0
- package/src/ui/home.js +0 -147
- package/src/ui/loom-model.js +15 -1
- package/src/ui/quiltview.js +213 -0
- package/src/ui/seam-model.js +251 -0
- package/src/ui/session.js +413 -31
- package/src/ui/voice-call.js +83 -14
- package/src/ui/voice-model.js +39 -0
- package/src/ui/voice.js +167 -17
- package/src/ui-server.js +80 -28
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/git.js +0 -147
- package/src/quilts/AGENTS.md +0 -1
- package/src/quilts/CLAUDE.md +0 -1
- package/src/quilts/accounts.js +0 -136
- package/src/quilts/affected.js +0 -132
- package/src/quilts/archiver.js +0 -175
- package/src/quilts/autorevert.js +0 -49
- package/src/quilts/bisect-run.js +0 -31
- package/src/quilts/blame.js +0 -103
- package/src/quilts/block.js +0 -347
- package/src/quilts/bucket.js +0 -25
- package/src/quilts/cbor.js +0 -92
- package/src/quilts/checks.js +0 -308
- package/src/quilts/claims.js +0 -144
- package/src/quilts/cli.js +0 -536
- package/src/quilts/client.js +0 -137
- package/src/quilts/conflict-format.js +0 -190
- package/src/quilts/describe.js +0 -296
- package/src/quilts/dogfood.js +0 -82
- package/src/quilts/errors.js +0 -8
- package/src/quilts/events.js +0 -171
- package/src/quilts/export-git.js +0 -130
- package/src/quilts/export-remote.js +0 -156
- package/src/quilts/export.js +0 -81
- package/src/quilts/fastmerge.js +0 -41
- package/src/quilts/hash.js +0 -44
- package/src/quilts/history.js +0 -266
- package/src/quilts/ids.js +0 -12
- package/src/quilts/import-follow.js +0 -236
- package/src/quilts/import-git.js +0 -294
- package/src/quilts/import.js +0 -196
- package/src/quilts/index.js +0 -94
- package/src/quilts/keys.js +0 -186
- package/src/quilts/limits.js +0 -67
- package/src/quilts/lines.js +0 -49
- package/src/quilts/live.js +0 -50
- package/src/quilts/merge-smart.js +0 -114
- package/src/quilts/merge.js +0 -501
- package/src/quilts/oauth.js +0 -202
- package/src/quilts/objects.js +0 -144
- package/src/quilts/pack.js +0 -118
- package/src/quilts/records.js +0 -211
- package/src/quilts/resolutions.js +0 -69
- package/src/quilts/reviews.js +0 -208
- package/src/quilts/routes.js +0 -186
- package/src/quilts/runner.js +0 -95
- package/src/quilts/server.js +0 -144
- package/src/quilts/service.js +0 -963
- package/src/quilts/setup.js +0 -41
- package/src/quilts/signing.js +0 -149
- package/src/quilts/squares.js +0 -239
- package/src/quilts/store-memory.js +0 -844
- package/src/quilts/store-rpc.js +0 -90
- package/src/quilts/store-supabase.js +0 -13
- package/src/quilts/tags.js +0 -64
- package/src/quilts/textdiff.js +0 -96
- package/src/quilts/tokens.js +0 -66
- package/src/quilts/tools.js +0 -403
- package/src/quilts/tree.js +0 -351
- package/src/quilts/webhook.js +0 -116
- package/src/quilts/window.js +0 -121
- package/src/quilts/xdiff.js +0 -435
- package/src/ui/branches.js +0 -111
package/src/quilts/xdiff.js
DELETED
|
@@ -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
|
-
}
|
package/src/ui/branches.js
DELETED
|
@@ -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
|
-
}
|