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.
Files changed (166) hide show
  1. package/RELEASES.md +279 -17
  2. package/bin/quilt.js +62 -152
  3. package/desktop/main.js +16 -2
  4. package/package.json +3 -2
  5. package/src/account.js +0 -29
  6. package/src/agent-task-workflow.js +2 -2
  7. package/src/agents/common.js +13 -0
  8. package/src/ai-vm.js +9 -1
  9. package/src/api/activity.js +5 -0
  10. package/src/api/memory-store.js +16 -38
  11. package/src/api/notify.js +134 -0
  12. package/src/api/routes/browsers.js +29 -0
  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/routes/teams.js +0 -23
  17. package/src/api/server.js +17 -25
  18. package/src/api/supabase-store.js +43 -56
  19. package/src/autowork.js +182 -16
  20. package/src/branch-view.js +6 -131
  21. package/src/connection.js +36 -9
  22. package/src/control.js +131 -48
  23. package/src/duties.js +1 -1
  24. package/src/force-date.js +27 -0
  25. package/src/fsutil.js +13 -3
  26. package/src/github-commit.js +3 -315
  27. package/src/history.js +13 -3
  28. package/src/mcp.js +93 -167
  29. package/src/merge3.js +11 -3
  30. package/src/now-summary.js +113 -0
  31. package/src/pathrules.js +12 -0
  32. package/src/protocol.js +10 -2
  33. package/src/quilt-default.js +84 -0
  34. package/src/quilt-history.js +106 -0
  35. package/src/quilt-remote.js +78 -0
  36. package/src/relay-ask.js +3 -66
  37. package/src/relay-cluster.js +3 -5
  38. package/src/relay-lease.js +6 -2
  39. package/src/relay-mcp.js +154 -432
  40. package/src/relay-prune.js +6 -145
  41. package/src/relay-turn-sync.js +1 -1
  42. package/src/release-words.js +2 -2
  43. package/src/runner.js +20 -7
  44. package/src/seam-access.js +97 -0
  45. package/src/seam-claims.js +53 -0
  46. package/src/seam-url.js +15 -0
  47. package/src/server.js +1516 -1383
  48. package/src/session-follow.js +461 -27
  49. package/src/session-layout3.js +410 -4
  50. package/src/session-migrate.js +15 -11
  51. package/src/session-quilt.js +1 -1
  52. package/src/session-seam.js +233 -20
  53. package/src/session.js +745 -3438
  54. package/src/source-live/bring-in.js +283 -0
  55. package/src/source-live/live-block.js +504 -33
  56. package/src/source-live/live-square.js +16 -5
  57. package/src/status.js +7 -58
  58. package/src/turn-sync.js +15 -2
  59. package/src/ui/agent-guide.js +1 -1
  60. package/src/ui/app.css +85 -20
  61. package/src/ui/app.js +4 -1
  62. package/src/ui/changes.js +5 -14
  63. package/src/ui/chat.js +9 -4
  64. package/src/ui/cloud-ai-model.js +13 -0
  65. package/src/ui/cloud-ai.js +10 -2
  66. package/src/ui/common.js +6 -4
  67. package/src/ui/fileview.js +20 -7
  68. package/src/ui/force-words.js +19 -0
  69. package/src/ui/git-rule.js +9 -28
  70. package/src/ui/home.js +3 -223
  71. package/src/ui/loom-model.js +15 -1
  72. package/src/ui/quiltview.js +213 -0
  73. package/src/ui/seam-model.js +251 -0
  74. package/src/ui/session.js +377 -489
  75. package/src/ui/tree.js +2 -8
  76. package/src/ui/voice-call.js +83 -14
  77. package/src/ui/voice-model.js +39 -0
  78. package/src/ui/voice.js +167 -17
  79. package/src/ui/workspaces.js +2 -122
  80. package/src/ui-server.js +81 -66
  81. package/src/update-check.js +25 -1
  82. package/src/voice.js +13 -6
  83. package/src/api/routes/github.js +0 -393
  84. package/src/branches.js +0 -194
  85. package/src/clash.js +0 -128
  86. package/src/commit-request.js +0 -59
  87. package/src/commit.js +0 -213
  88. package/src/git-hook.js +0 -48
  89. package/src/git.js +0 -147
  90. package/src/gitignore.js +0 -49
  91. package/src/gitstate.js +0 -1033
  92. package/src/quilts/AGENTS.md +0 -1
  93. package/src/quilts/CLAUDE.md +0 -1
  94. package/src/quilts/accounts.js +0 -136
  95. package/src/quilts/affected.js +0 -132
  96. package/src/quilts/archiver.js +0 -175
  97. package/src/quilts/autorevert.js +0 -49
  98. package/src/quilts/bisect-run.js +0 -31
  99. package/src/quilts/blame.js +0 -103
  100. package/src/quilts/block.js +0 -347
  101. package/src/quilts/bucket.js +0 -25
  102. package/src/quilts/cbor.js +0 -92
  103. package/src/quilts/checks.js +0 -308
  104. package/src/quilts/claims.js +0 -144
  105. package/src/quilts/cli.js +0 -536
  106. package/src/quilts/client.js +0 -137
  107. package/src/quilts/conflict-format.js +0 -190
  108. package/src/quilts/describe.js +0 -296
  109. package/src/quilts/dogfood.js +0 -82
  110. package/src/quilts/errors.js +0 -8
  111. package/src/quilts/events.js +0 -171
  112. package/src/quilts/export-git.js +0 -130
  113. package/src/quilts/export-remote.js +0 -156
  114. package/src/quilts/export.js +0 -81
  115. package/src/quilts/fastmerge.js +0 -41
  116. package/src/quilts/hash.js +0 -44
  117. package/src/quilts/history.js +0 -266
  118. package/src/quilts/ids.js +0 -12
  119. package/src/quilts/import-follow.js +0 -236
  120. package/src/quilts/import-git.js +0 -294
  121. package/src/quilts/import.js +0 -196
  122. package/src/quilts/index.js +0 -94
  123. package/src/quilts/keys.js +0 -186
  124. package/src/quilts/limits.js +0 -67
  125. package/src/quilts/lines.js +0 -49
  126. package/src/quilts/live.js +0 -50
  127. package/src/quilts/merge-smart.js +0 -114
  128. package/src/quilts/merge.js +0 -501
  129. package/src/quilts/oauth.js +0 -202
  130. package/src/quilts/objects.js +0 -144
  131. package/src/quilts/pack.js +0 -118
  132. package/src/quilts/records.js +0 -211
  133. package/src/quilts/resolutions.js +0 -69
  134. package/src/quilts/reviews.js +0 -208
  135. package/src/quilts/routes.js +0 -186
  136. package/src/quilts/runner.js +0 -95
  137. package/src/quilts/server.js +0 -144
  138. package/src/quilts/service.js +0 -963
  139. package/src/quilts/setup.js +0 -41
  140. package/src/quilts/signing.js +0 -149
  141. package/src/quilts/squares.js +0 -239
  142. package/src/quilts/store-memory.js +0 -844
  143. package/src/quilts/store-rpc.js +0 -90
  144. package/src/quilts/store-supabase.js +0 -13
  145. package/src/quilts/tags.js +0 -64
  146. package/src/quilts/textdiff.js +0 -96
  147. package/src/quilts/tokens.js +0 -66
  148. package/src/quilts/tools.js +0 -403
  149. package/src/quilts/tree.js +0 -351
  150. package/src/quilts/webhook.js +0 -116
  151. package/src/quilts/window.js +0 -121
  152. package/src/quilts/xdiff.js +0 -435
  153. package/src/relay-ci.js +0 -111
  154. package/src/relay-clash.js +0 -214
  155. package/src/relay-commit.js +0 -918
  156. package/src/relay-filekey.js +0 -46
  157. package/src/relay-git.js +0 -274
  158. package/src/relay-githttp.js +0 -166
  159. package/src/relay-github.js +0 -109
  160. package/src/relay-outbound.js +0 -1295
  161. package/src/relay-repo-moves.js +0 -43
  162. package/src/relay-sync.js +0 -795
  163. package/src/relay-upstream.js +0 -889
  164. package/src/sync-words.js +0 -42
  165. package/src/ui/branches.js +0 -111
  166. package/src/upstream.js +0 -192
@@ -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
- }