heyquilt 0.3.122 → 0.3.140
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 +279 -17
- package/bin/quilt.js +62 -152
- package/desktop/main.js +16 -2
- package/package.json +3 -2
- package/src/account.js +0 -29
- 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 -38
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- 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/routes/teams.js +0 -23
- package/src/api/server.js +17 -25
- package/src/api/supabase-store.js +43 -56
- package/src/autowork.js +182 -16
- package/src/branch-view.js +6 -131
- package/src/connection.js +36 -9
- package/src/control.js +131 -48
- package/src/duties.js +1 -1
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/github-commit.js +3 -315
- package/src/history.js +13 -3
- package/src/mcp.js +93 -167
- 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-ask.js +3 -66
- package/src/relay-cluster.js +3 -5
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +154 -432
- package/src/relay-prune.js +6 -145
- package/src/relay-turn-sync.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 +1516 -1383
- 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 +745 -3438
- 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 +7 -58
- package/src/turn-sync.js +15 -2
- 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/changes.js +5 -14
- package/src/ui/chat.js +9 -4
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -4
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -28
- package/src/ui/home.js +3 -223
- 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 +377 -489
- package/src/ui/tree.js +2 -8
- 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/workspaces.js +2 -122
- package/src/ui-server.js +81 -66
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/api/routes/github.js +0 -393
- package/src/branches.js +0 -194
- package/src/clash.js +0 -128
- package/src/commit-request.js +0 -59
- package/src/commit.js +0 -213
- package/src/git-hook.js +0 -48
- package/src/git.js +0 -147
- package/src/gitignore.js +0 -49
- package/src/gitstate.js +0 -1033
- 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/relay-ci.js +0 -111
- package/src/relay-clash.js +0 -214
- package/src/relay-commit.js +0 -918
- package/src/relay-filekey.js +0 -46
- package/src/relay-git.js +0 -274
- package/src/relay-githttp.js +0 -166
- package/src/relay-github.js +0 -109
- package/src/relay-outbound.js +0 -1295
- package/src/relay-repo-moves.js +0 -43
- package/src/relay-sync.js +0 -795
- package/src/relay-upstream.js +0 -889
- package/src/sync-words.js +0 -42
- package/src/ui/branches.js +0 -111
- package/src/upstream.js +0 -192
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/relay-ci.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// CI on GitHub becomes work on the board: checks failing on a branch the session follows open one task,
|
|
2
|
-
// "CI failing on <branch>", for the member whose commit it is (when they are in the session); a newer commit
|
|
3
|
-
// failing too is noted on it; checks passing again move it to Done. One task per branch (its id comes from the
|
|
4
|
-
// branch), so a run reported twice never adds a second.
|
|
5
|
-
import crypto from 'node:crypto'
|
|
6
|
-
import { readTasks, addTask, updateTask } from './tasks.js'
|
|
7
|
-
import { addComment } from './task-comments.js'
|
|
8
|
-
import { githubWriter } from './github-commit.js'
|
|
9
|
-
import { credentialsFor } from './relay-commit.js'
|
|
10
|
-
import { upstreamsOf, parseRepo } from './relay-upstream.js'
|
|
11
|
-
import { memberForAuthor, everyone } from './relay-clash.js'
|
|
12
|
-
import { runGit } from './relay-git.js'
|
|
13
|
-
import { RELAY_BY } from './branches.js'
|
|
14
|
-
|
|
15
|
-
const ORIGIN = 'relay-publish'
|
|
16
|
-
// What GitHub calls a check suite that failed (check_suite.conclusion).
|
|
17
|
-
const FAILED = new Set(['failure', 'timed_out', 'action_required', 'startup_failure'])
|
|
18
|
-
|
|
19
|
-
/** The task id for CI on `branch` of this session: the same every time. */
|
|
20
|
-
export const ciTaskId = (room, branch) => crypto.createHash('sha256').update(`ci\0${room.name}\0${branch}`).digest('hex').slice(0, 16)
|
|
21
|
-
|
|
22
|
-
/** Who made commit `sha` (from the relay's mirror): the member it maps to, or ''. */
|
|
23
|
-
async function authorOf (room, rec, sha) {
|
|
24
|
-
if (!room.mirrors) return ''
|
|
25
|
-
try {
|
|
26
|
-
const repo = parseRepo(`https://${rec.repo}`)
|
|
27
|
-
const dir = (await room.mirrors.ensure(repo, { url: room.gitRemoteUrl(repo) })).dir
|
|
28
|
-
const r = await runGit(dir, ['log', '-1', '--format=%an%x1f%ae', sha])
|
|
29
|
-
if (!r.ok) return ''
|
|
30
|
-
const [name, email] = r.out.trim().split('\x1f')
|
|
31
|
-
// By their noreply address, else by name: a member on record, or one connected now.
|
|
32
|
-
const online = room.names ? [...room.names.values()] : []
|
|
33
|
-
return memberForAuthor(room, { name, email }) || everyone(room).find((m) => m.name === name)?.name || online.find((n) => n === name) || ''
|
|
34
|
-
} catch { return '' }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A check suite on `repo` finished at commit `sha` of `branch` with `conclusion`. When the session follows that
|
|
39
|
-
* branch: failing opens (or reopens, or notes) its CI task; passing moves an open one to Done. Returns what it did
|
|
40
|
-
* ('opened', 'noted', 'closed') or null. Never throws.
|
|
41
|
-
*/
|
|
42
|
-
export function noteCiResult (room, result, opts = {}) {
|
|
43
|
-
// One at a time per branch, in the order GitHub's results arrive: a failure takes longer to note (GitHub is asked
|
|
44
|
-
// which checks failed), and a pass right after it must not be noted first.
|
|
45
|
-
if (!room.ciQueue) room.ciQueue = new Map()
|
|
46
|
-
const k = String(result && result.branch)
|
|
47
|
-
const next = (room.ciQueue.get(k) || Promise.resolve()).then(() => noteCiResultNow(room, result, opts))
|
|
48
|
-
room.ciQueue.set(k, next.catch(() => null))
|
|
49
|
-
return next
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Whether commit `older` is an ancestor of `newer` in the relay's mirror of `rec`'s repository (false when it can't say). */
|
|
53
|
-
async function before (room, rec, older, newer) {
|
|
54
|
-
try {
|
|
55
|
-
const repo = parseRepo(`https://${rec.repo}`)
|
|
56
|
-
const dir = (await room.mirrors.ensure(repo, { url: room.gitRemoteUrl(repo) })).dir
|
|
57
|
-
return (await runGit(dir, ['merge-base', '--is-ancestor', older, newer])).ok
|
|
58
|
-
} catch { return false }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function noteCiResultNow (room, { repo, sha, branch, conclusion }, { fetch = globalThis.fetch, log = () => {} } = {}) {
|
|
62
|
-
try {
|
|
63
|
-
const want = String(repo).toLowerCase().replace(/^github\.com\//, '')
|
|
64
|
-
const rec = Object.values(upstreamsOf(room)).find((r) => r && r.repo && r.ref === branch && String(r.repo).toLowerCase().replace(/^github\.com\//, '') === want)
|
|
65
|
-
if (!rec || !/^[0-9a-f]{40}$/.test(sha || '')) return null
|
|
66
|
-
const failed = FAILED.has(String(conclusion))
|
|
67
|
-
if (!failed && conclusion !== 'success') return null
|
|
68
|
-
// A failure on an older commit than one that passed since (GitHub's results can come out of order) is old news.
|
|
69
|
-
if (!room.ciPassed) room.ciPassed = new Map()
|
|
70
|
-
const passed = room.ciPassed.get(branch)
|
|
71
|
-
if (!failed) room.ciPassed.set(branch, sha)
|
|
72
|
-
else if (passed && passed !== sha && room.mirrors && await before(room, rec, sha, passed)) return null
|
|
73
|
-
const map = room.doc.getMap('tasks')
|
|
74
|
-
const id = ciTaskId(room, branch)
|
|
75
|
-
const task = readTasks(map).find((t) => t.id === id)
|
|
76
|
-
const open = task && !task.archived && task.column !== 'done'
|
|
77
|
-
const comment = (text) => addComment(room.doc, room.doc.getMap('taskComments'), readTasks(map), { taskId: id, by: RELAY_BY, text }, ORIGIN)
|
|
78
|
-
if (!failed) {
|
|
79
|
-
if (!open) return null
|
|
80
|
-
comment(`CI passes again on ${branch} at ${sha.slice(0, 7)}.`)
|
|
81
|
-
updateTask(room.doc, map, { id, column: 'done' }, ORIGIN)
|
|
82
|
-
return 'closed'
|
|
83
|
-
}
|
|
84
|
-
// Which checks failed, when GitHub says (a failing suite is enough to act on).
|
|
85
|
-
let names = []
|
|
86
|
-
try {
|
|
87
|
-
const r = parseRepo(`https://${rec.repo}`)
|
|
88
|
-
const c = await githubWriter({ fetch, token: await credentialsFor(room, r), owner: r.owner, name: r.name }).checks(sha)
|
|
89
|
-
names = c.failing || []
|
|
90
|
-
} catch (err) { log(`[${room.name}] ${branch}: could not read which checks failed on ${sha.slice(0, 7)}: ${err.message}`) }
|
|
91
|
-
const what = names.length ? names.slice(0, 5).join(', ') : 'checks'
|
|
92
|
-
// The relay shut down while GitHub was asked: the room notes it when it is back (server.js runCi).
|
|
93
|
-
if (room.destroyed) return null
|
|
94
|
-
const url = `https://${rec.repo}/commit/${sha}`
|
|
95
|
-
if (open) {
|
|
96
|
-
comment(`Still failing at ${sha.slice(0, 7)}: ${what}. ${url}`)
|
|
97
|
-
return 'noted'
|
|
98
|
-
}
|
|
99
|
-
const who = await authorOf(room, rec, sha)
|
|
100
|
-
if (room.destroyed) return null
|
|
101
|
-
const title = `CI failing on ${branch}: ${what}`
|
|
102
|
-
if (task) updateTask(room.doc, map, { id, title, column: 'todo', archived: false, ...(who ? { assignee: who } : {}) }, ORIGIN)
|
|
103
|
-
else addTask(room.doc, map, { id, title, by: RELAY_BY, assignee: who, column: 'todo' }, ORIGIN)
|
|
104
|
-
comment(`GitHub's checks failed on ${branch} at ${sha.slice(0, 7)}: ${what}. ${url}${who ? ` The commit is ${who}'s.` : ''}`)
|
|
105
|
-
log(`[${room.name}] ${branch}: CI failing at ${sha.slice(0, 7)} (${what}): task ${id}${who ? ` for ${who}` : ''}`)
|
|
106
|
-
return 'opened'
|
|
107
|
-
} catch (err) {
|
|
108
|
-
log(`[${room.name}] ${branch}: could not note CI on ${String(sha).slice(0, 7)}: ${err.message}`)
|
|
109
|
-
return null
|
|
110
|
-
}
|
|
111
|
-
}
|