diff-easy 0.1.0

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 (172) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +142 -0
  3. package/dist/cli/cmux.d.ts +9 -0
  4. package/dist/cli/cmux.js +274 -0
  5. package/dist/cli/comment.d.ts +2 -0
  6. package/dist/cli/comment.js +91 -0
  7. package/dist/cli/github.d.ts +84 -0
  8. package/dist/cli/github.js +364 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +397 -0
  11. package/dist/cli/turns.d.ts +14 -0
  12. package/dist/cli/turns.js +333 -0
  13. package/dist/cli/utils.d.ts +27 -0
  14. package/dist/cli/utils.js +204 -0
  15. package/dist/client/assets/arc-CHYjdFTk.js +1 -0
  16. package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
  17. package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
  18. package/dist/client/assets/array-BifhSqXX.js +1 -0
  19. package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
  20. package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
  21. package/dist/client/assets/channel-Cv40nQW1.js +1 -0
  22. package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
  23. package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
  24. package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
  25. package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
  26. package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
  27. package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
  28. package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
  29. package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
  30. package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
  31. package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
  32. package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
  33. package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
  34. package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
  35. package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
  36. package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  37. package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
  38. package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
  39. package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
  40. package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
  41. package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
  42. package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
  43. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
  44. package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
  45. package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
  46. package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
  47. package/dist/client/assets/dagre-Bx709z4p.js +1 -0
  48. package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
  49. package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
  50. package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
  51. package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
  52. package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
  53. package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
  54. package/dist/client/assets/dist-7wVkzZne.js +1 -0
  55. package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
  56. package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
  57. package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
  58. package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
  59. package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
  60. package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
  61. package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
  62. package/dist/client/assets/index-BfVtbKTZ.css +2 -0
  63. package/dist/client/assets/index-p8zzfjdu.js +87 -0
  64. package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
  65. package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
  66. package/dist/client/assets/init-D6jRqBbL.js +1 -0
  67. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
  68. package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
  69. package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
  70. package/dist/client/assets/katex-Vhh-h91d.js +257 -0
  71. package/dist/client/assets/line-D8kNd46u.js +1 -0
  72. package/dist/client/assets/linear-jwGZSrg4.js +1 -0
  73. package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
  74. package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
  75. package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
  76. package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
  77. package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
  78. package/dist/client/assets/path-BWPyau1x.js +1 -0
  79. package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
  80. package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
  81. package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
  82. package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
  83. package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
  84. package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
  85. package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
  86. package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
  87. package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
  88. package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
  89. package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  90. package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
  91. package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
  92. package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
  93. package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
  94. package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
  95. package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
  96. package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
  97. package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
  98. package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
  99. package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
  100. package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
  101. package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
  102. package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
  103. package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
  104. package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
  105. package/dist/client/assets/src-CDAPGf2l.js +1 -0
  106. package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
  107. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
  108. package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
  109. package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
  110. package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
  111. package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
  112. package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
  113. package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
  114. package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
  115. package/dist/client/favicon.png +0 -0
  116. package/dist/client/index.html +14 -0
  117. package/dist/client/logo.png +0 -0
  118. package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
  119. package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
  120. package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
  121. package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
  122. package/dist/client/site-data/manifest.json +1 -0
  123. package/dist/client/site-data/og-image.png +0 -0
  124. package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
  125. package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
  126. package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
  127. package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
  128. package/dist/server/file-watcher.d.ts +33 -0
  129. package/dist/server/file-watcher.js +340 -0
  130. package/dist/server/generated-file-check.d.ts +17 -0
  131. package/dist/server/generated-file-check.js +280 -0
  132. package/dist/server/git-diff.d.ts +94 -0
  133. package/dist/server/git-diff.js +909 -0
  134. package/dist/server/grouping/cache.d.ts +3 -0
  135. package/dist/server/grouping/cache.js +22 -0
  136. package/dist/server/grouping/claudeCli.d.ts +10 -0
  137. package/dist/server/grouping/claudeCli.js +95 -0
  138. package/dist/server/grouping/digest.d.ts +15 -0
  139. package/dist/server/grouping/digest.js +38 -0
  140. package/dist/server/grouping/reconcile.d.ts +6 -0
  141. package/dist/server/grouping/reconcile.js +34 -0
  142. package/dist/server/grouping/types.d.ts +9 -0
  143. package/dist/server/grouping/types.js +1 -0
  144. package/dist/server/server.d.ts +37 -0
  145. package/dist/server/server.js +1283 -0
  146. package/dist/shared/grouping.d.ts +4 -0
  147. package/dist/shared/grouping.js +170 -0
  148. package/dist/types/diff.d.ts +263 -0
  149. package/dist/types/diff.js +1 -0
  150. package/dist/types/watch.d.ts +59 -0
  151. package/dist/types/watch.js +8 -0
  152. package/dist/utils/commentFormatting.d.ts +6 -0
  153. package/dist/utils/commentFormatting.js +90 -0
  154. package/dist/utils/commentImports.d.ts +11 -0
  155. package/dist/utils/commentImports.js +386 -0
  156. package/dist/utils/createId.d.ts +1 -0
  157. package/dist/utils/createId.js +5 -0
  158. package/dist/utils/diffMode.d.ts +3 -0
  159. package/dist/utils/diffMode.js +13 -0
  160. package/dist/utils/diffSelection.d.ts +13 -0
  161. package/dist/utils/diffSelection.js +39 -0
  162. package/dist/utils/editorOptions.d.ts +60 -0
  163. package/dist/utils/editorOptions.js +171 -0
  164. package/dist/utils/fileUtils.d.ts +12 -0
  165. package/dist/utils/fileUtils.js +21 -0
  166. package/dist/utils/hubConfig.d.ts +32 -0
  167. package/dist/utils/hubConfig.js +51 -0
  168. package/dist/utils/suggestionUtils.d.ts +19 -0
  169. package/dist/utils/suggestionUtils.js +31 -0
  170. package/dist/utils/turnLog.d.ts +47 -0
  171. package/dist/utils/turnLog.js +133 -0
  172. package/package.json +105 -0
@@ -0,0 +1,386 @@
1
+ import { createId } from './createId.js';
2
+ function isRecord(value) {
3
+ return typeof value === 'object' && value !== null;
4
+ }
5
+ function isValidIsoTimestamp(value) {
6
+ return Number.isFinite(Date.parse(value));
7
+ }
8
+ function normalizeTimestamp(value, fieldName) {
9
+ if (value === undefined) {
10
+ return undefined;
11
+ }
12
+ if (typeof value !== 'string' || value.trim().length === 0 || !isValidIsoTimestamp(value)) {
13
+ throw new Error(`Invalid comment import field: ${fieldName}`);
14
+ }
15
+ return value;
16
+ }
17
+ function normalizeLineRange(value) {
18
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
19
+ return value;
20
+ }
21
+ if (!isRecord(value)) {
22
+ throw new Error('Invalid comment import field: position.line');
23
+ }
24
+ const start = value.start;
25
+ const end = value.end;
26
+ if (typeof start !== 'number' ||
27
+ typeof end !== 'number' ||
28
+ !Number.isInteger(start) ||
29
+ !Number.isInteger(end) ||
30
+ start <= 0 ||
31
+ end <= 0 ||
32
+ start > end) {
33
+ throw new Error('Invalid comment import field: position.line');
34
+ }
35
+ return { start, end };
36
+ }
37
+ function normalizePosition(value) {
38
+ if (!isRecord(value)) {
39
+ throw new Error('Invalid comment import field: position');
40
+ }
41
+ if (value.side !== 'old' && value.side !== 'new') {
42
+ throw new Error('Invalid comment import field: position.side');
43
+ }
44
+ return {
45
+ side: value.side,
46
+ line: normalizeLineRange(value.line),
47
+ };
48
+ }
49
+ function normalizeCodeSnapshot(value) {
50
+ if (value === undefined) {
51
+ return undefined;
52
+ }
53
+ if (!isRecord(value) || typeof value.content !== 'string') {
54
+ throw new Error('Invalid comment import field: codeSnapshot');
55
+ }
56
+ if (value.language !== undefined && typeof value.language !== 'string') {
57
+ throw new Error('Invalid comment import field: codeSnapshot.language');
58
+ }
59
+ return {
60
+ content: value.content,
61
+ language: value.language,
62
+ };
63
+ }
64
+ function normalizeOptionalString(value, fieldName) {
65
+ if (value === undefined) {
66
+ return undefined;
67
+ }
68
+ if (typeof value !== 'string') {
69
+ throw new Error(`Invalid comment import field: ${fieldName}`);
70
+ }
71
+ return value;
72
+ }
73
+ function normalizeRequiredBody(value) {
74
+ if (typeof value !== 'string') {
75
+ throw new Error('Invalid comment import field: body');
76
+ }
77
+ const trimmed = value.trim();
78
+ if (trimmed.length === 0) {
79
+ throw new Error('Invalid comment import field: body');
80
+ }
81
+ return trimmed;
82
+ }
83
+ function normalizeCommentImportEntry(value) {
84
+ if (!isRecord(value)) {
85
+ throw new Error('Comment import must be an object');
86
+ }
87
+ if (value.type !== 'thread' && value.type !== 'reply') {
88
+ throw new Error('Invalid comment import field: type');
89
+ }
90
+ if (typeof value.filePath !== 'string' || value.filePath.trim().length === 0) {
91
+ throw new Error('Invalid comment import field: filePath');
92
+ }
93
+ const createdAt = normalizeTimestamp(value.createdAt, 'createdAt');
94
+ const updatedAt = normalizeTimestamp(value.updatedAt, 'updatedAt');
95
+ const normalized = {
96
+ type: value.type,
97
+ id: normalizeOptionalString(value.id, 'id'),
98
+ filePath: value.filePath,
99
+ position: normalizePosition(value.position),
100
+ body: normalizeRequiredBody(value.body),
101
+ author: normalizeOptionalString(value.author, 'author'),
102
+ createdAt,
103
+ updatedAt,
104
+ codeSnapshot: normalizeCodeSnapshot(value.codeSnapshot),
105
+ };
106
+ return normalized;
107
+ }
108
+ export function normalizeCommentImports(input) {
109
+ if (Array.isArray(input)) {
110
+ return input.map((entry) => normalizeCommentImportEntry(entry));
111
+ }
112
+ return [normalizeCommentImportEntry(input)];
113
+ }
114
+ export function parseCommentImportValue(value) {
115
+ let parsed;
116
+ try {
117
+ parsed = JSON.parse(value);
118
+ }
119
+ catch {
120
+ throw new Error('Invalid --comment JSON');
121
+ }
122
+ return normalizeCommentImports(parsed);
123
+ }
124
+ function getPositionKey(position) {
125
+ if (typeof position.line === 'number') {
126
+ return `${position.side}:${position.line}`;
127
+ }
128
+ return `${position.side}:${position.line.start}-${position.line.end}`;
129
+ }
130
+ function positionsMatch(left, right) {
131
+ return getPositionKey(left) === getPositionKey(right);
132
+ }
133
+ function normalizeAuthor(author) {
134
+ return author?.trim() || '';
135
+ }
136
+ function messageMatchesImport(message, commentImport, includeId = true) {
137
+ if (includeId && commentImport.id && message.id === commentImport.id) {
138
+ return true;
139
+ }
140
+ if (normalizeAuthor(message.author) !== normalizeAuthor(commentImport.author)) {
141
+ return false;
142
+ }
143
+ if (message.body !== commentImport.body) {
144
+ return false;
145
+ }
146
+ if (commentImport.createdAt && message.createdAt !== commentImport.createdAt) {
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ function rootThreadMatchesImport(thread, commentImport) {
152
+ if (commentImport.id && thread.id === commentImport.id) {
153
+ return true;
154
+ }
155
+ if (thread.filePath !== commentImport.filePath ||
156
+ !positionsMatch(thread.position, commentImport.position)) {
157
+ return false;
158
+ }
159
+ const rootMessage = thread.messages[0];
160
+ if (!rootMessage) {
161
+ return false;
162
+ }
163
+ return messageMatchesImport(rootMessage, commentImport, true);
164
+ }
165
+ function toThreadTimestamp(commentImport, now) {
166
+ const createdAt = commentImport.createdAt ?? now;
167
+ const updatedAt = commentImport.updatedAt ?? createdAt;
168
+ return { createdAt, updatedAt };
169
+ }
170
+ function maxIsoTimestamp(left, right) {
171
+ return left.localeCompare(right) >= 0 ? left : right;
172
+ }
173
+ function sortByNewestThread(left, right) {
174
+ return right.updatedAt.localeCompare(left.updatedAt);
175
+ }
176
+ function createImportedThread(commentImport, now) {
177
+ const { createdAt, updatedAt } = toThreadTimestamp(commentImport, now);
178
+ const threadId = commentImport.id ?? createId();
179
+ return {
180
+ id: threadId,
181
+ filePath: commentImport.filePath,
182
+ createdAt,
183
+ updatedAt,
184
+ position: commentImport.position,
185
+ codeSnapshot: commentImport.codeSnapshot,
186
+ messages: [
187
+ {
188
+ id: commentImport.id ?? threadId,
189
+ body: commentImport.body,
190
+ author: commentImport.author,
191
+ createdAt,
192
+ updatedAt,
193
+ },
194
+ ],
195
+ };
196
+ }
197
+ function createImportedReply(commentImport, now) {
198
+ const { createdAt, updatedAt } = toThreadTimestamp(commentImport, now);
199
+ return {
200
+ id: commentImport.id ?? createId(),
201
+ body: commentImport.body,
202
+ author: commentImport.author,
203
+ createdAt,
204
+ updatedAt,
205
+ };
206
+ }
207
+ function cloneLineRange(line) {
208
+ if (typeof line === 'number') {
209
+ return line;
210
+ }
211
+ return {
212
+ start: line.start,
213
+ end: line.end,
214
+ };
215
+ }
216
+ function clonePosition(position) {
217
+ return {
218
+ side: position.side,
219
+ line: cloneLineRange(position.line),
220
+ };
221
+ }
222
+ function cloneCodeSnapshot(snapshot) {
223
+ if (!snapshot) {
224
+ return undefined;
225
+ }
226
+ return {
227
+ content: snapshot.content,
228
+ language: snapshot.language,
229
+ };
230
+ }
231
+ function cloneMessage(message) {
232
+ return {
233
+ id: message.id,
234
+ body: message.body,
235
+ author: message.author,
236
+ createdAt: message.createdAt,
237
+ updatedAt: message.updatedAt,
238
+ };
239
+ }
240
+ function cloneThread(thread) {
241
+ return {
242
+ id: thread.id,
243
+ filePath: thread.filePath,
244
+ createdAt: thread.createdAt,
245
+ updatedAt: thread.updatedAt,
246
+ position: clonePosition(thread.position),
247
+ codeSnapshot: cloneCodeSnapshot(thread.codeSnapshot),
248
+ messages: thread.messages.map(cloneMessage),
249
+ };
250
+ }
251
+ function messagesMatch(left, right) {
252
+ if (left.id === right.id) {
253
+ return true;
254
+ }
255
+ if (normalizeAuthor(left.author) !== normalizeAuthor(right.author)) {
256
+ return false;
257
+ }
258
+ return left.body === right.body && left.createdAt === right.createdAt;
259
+ }
260
+ function threadsMatch(left, right) {
261
+ if (left.id === right.id) {
262
+ return true;
263
+ }
264
+ if (left.filePath !== right.filePath || !positionsMatch(left.position, right.position)) {
265
+ return false;
266
+ }
267
+ const leftRoot = left.messages[0];
268
+ const rightRoot = right.messages[0];
269
+ if (!leftRoot || !rightRoot) {
270
+ return false;
271
+ }
272
+ return messagesMatch(leftRoot, rightRoot);
273
+ }
274
+ function sortReplies(left, right) {
275
+ const createdAtOrder = left.createdAt.localeCompare(right.createdAt);
276
+ if (createdAtOrder !== 0) {
277
+ return createdAtOrder;
278
+ }
279
+ return left.id.localeCompare(right.id);
280
+ }
281
+ function pickNewerMessage(existingMessage, incomingMessage) {
282
+ if (incomingMessage.updatedAt.localeCompare(existingMessage.updatedAt) >= 0) {
283
+ return cloneMessage(incomingMessage);
284
+ }
285
+ return cloneMessage(existingMessage);
286
+ }
287
+ function mergeThread(existingThread, incomingThread) {
288
+ const mergedMessages = existingThread.messages.map(cloneMessage);
289
+ for (const incomingMessage of incomingThread.messages) {
290
+ const matchIndex = mergedMessages.findIndex((message) => messagesMatch(message, incomingMessage));
291
+ if (matchIndex >= 0) {
292
+ mergedMessages[matchIndex] = pickNewerMessage(mergedMessages[matchIndex], incomingMessage);
293
+ continue;
294
+ }
295
+ mergedMessages.push(cloneMessage(incomingMessage));
296
+ }
297
+ const [rootMessage, ...replyMessages] = mergedMessages;
298
+ const orderedMessages = rootMessage
299
+ ? [rootMessage, ...replyMessages.sort(sortReplies)]
300
+ : replyMessages.sort(sortReplies);
301
+ const updatedAt = orderedMessages.reduce((latest, message) => maxIsoTimestamp(latest, message.updatedAt), maxIsoTimestamp(existingThread.updatedAt, incomingThread.updatedAt));
302
+ return {
303
+ ...cloneThread(existingThread),
304
+ createdAt: existingThread.createdAt.localeCompare(incomingThread.createdAt) <= 0
305
+ ? existingThread.createdAt
306
+ : incomingThread.createdAt,
307
+ updatedAt,
308
+ position: clonePosition(existingThread.position),
309
+ codeSnapshot: cloneCodeSnapshot(incomingThread.codeSnapshot ?? existingThread.codeSnapshot),
310
+ messages: orderedMessages,
311
+ };
312
+ }
313
+ export function mergeCommentThreads(existingThreads, incomingThreads) {
314
+ const threads = existingThreads.map(cloneThread);
315
+ for (const incomingThread of incomingThreads) {
316
+ const existingIndex = threads.findIndex((thread) => threadsMatch(thread, incomingThread));
317
+ if (existingIndex < 0) {
318
+ threads.push(cloneThread(incomingThread));
319
+ continue;
320
+ }
321
+ threads[existingIndex] = mergeThread(threads[existingIndex], incomingThread);
322
+ }
323
+ return { threads, warnings: [] };
324
+ }
325
+ function serializeLineRange(line) {
326
+ if (typeof line === 'number') {
327
+ return line;
328
+ }
329
+ return { start: line.start, end: line.end };
330
+ }
331
+ export function serializeCommentImports(commentImports) {
332
+ return JSON.stringify(commentImports.map((commentImport) => ({
333
+ type: commentImport.type,
334
+ id: commentImport.id,
335
+ filePath: commentImport.filePath,
336
+ position: {
337
+ side: commentImport.position.side,
338
+ line: serializeLineRange(commentImport.position.line),
339
+ },
340
+ body: commentImport.body,
341
+ author: commentImport.author,
342
+ createdAt: commentImport.createdAt,
343
+ updatedAt: commentImport.updatedAt,
344
+ codeSnapshot: commentImport.codeSnapshot
345
+ ? {
346
+ content: commentImport.codeSnapshot.content,
347
+ language: commentImport.codeSnapshot.language,
348
+ }
349
+ : undefined,
350
+ })));
351
+ }
352
+ export function mergeCommentImports(existingThreads, commentImports) {
353
+ // Clone (don't shallow-copy) so appending a reply doesn't mutate the caller's
354
+ // thread objects in place — otherwise the server can't detect the change and
355
+ // skips the commentsChanged broadcast, leaving open browsers stale.
356
+ const threads = existingThreads.map(cloneThread);
357
+ const warnings = [];
358
+ for (const commentImport of commentImports) {
359
+ const now = new Date().toISOString();
360
+ if (commentImport.type === 'thread') {
361
+ const hasDuplicate = threads.some((thread) => rootThreadMatchesImport(thread, commentImport));
362
+ if (hasDuplicate) {
363
+ continue;
364
+ }
365
+ threads.push(createImportedThread(commentImport, now));
366
+ continue;
367
+ }
368
+ const targetThreads = threads
369
+ .filter((thread) => thread.filePath === commentImport.filePath &&
370
+ positionsMatch(thread.position, commentImport.position))
371
+ .sort(sortByNewestThread);
372
+ const targetThread = targetThreads[0];
373
+ if (!targetThread) {
374
+ warnings.push(`Skipped reply import for ${commentImport.filePath}:${getPositionKey(commentImport.position)} because no matching thread was found.`);
375
+ continue;
376
+ }
377
+ const hasDuplicateReply = targetThread.messages.some((message) => messageMatchesImport(message, commentImport, true));
378
+ if (hasDuplicateReply) {
379
+ continue;
380
+ }
381
+ const importedReply = createImportedReply(commentImport, now);
382
+ targetThread.messages = [...targetThread.messages, importedReply];
383
+ targetThread.updatedAt = maxIsoTimestamp(targetThread.updatedAt, importedReply.updatedAt);
384
+ }
385
+ return { threads, warnings };
386
+ }
@@ -0,0 +1 @@
1
+ export declare function createId(): string;
@@ -0,0 +1,5 @@
1
+ const ID_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789';
2
+ const ID_LENGTH = 16;
3
+ export function createId() {
4
+ return Array.from({ length: ID_LENGTH }, () => ID_ALPHABET[Math.floor(Math.random() * ID_ALPHABET.length)]).join('');
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { DiffViewMode } from '../types/diff.js';
2
+ export declare const DEFAULT_DIFF_VIEW_MODE: DiffViewMode;
3
+ export declare function normalizeDiffViewMode(mode?: string | null): DiffViewMode;
@@ -0,0 +1,13 @@
1
+ export const DEFAULT_DIFF_VIEW_MODE = 'split';
2
+ export function normalizeDiffViewMode(mode) {
3
+ switch (mode) {
4
+ case 'split':
5
+ case 'side-by-side':
6
+ return 'split';
7
+ case 'unified':
8
+ case 'inline':
9
+ return 'unified';
10
+ default:
11
+ return DEFAULT_DIFF_VIEW_MODE;
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { BaseMode, DiffSelection } from '../types/diff.js';
2
+ export declare function normalizeBaseMode(baseMode?: BaseMode): BaseMode;
3
+ export declare function createDiffSelection(baseCommitish: string, targetCommitish: string, baseMode?: BaseMode): DiffSelection;
4
+ export declare function diffSelectionsEqual(left: DiffSelection | null | undefined, right: DiffSelection | null | undefined): boolean;
5
+ export declare function getDiffSelectionKey(selection: DiffSelection): string;
6
+ /**
7
+ * Working-tree sentinel targets whose diff is *mutable* — it depends on the
8
+ * current working tree / index, not just the ref. Re-selecting one must refetch
9
+ * (an identical selection can now have different content), unlike an immutable
10
+ * commit range where an unchanged selection can be safely skipped.
11
+ */
12
+ export declare function isWorkingTreeTarget(targetCommitish: string): boolean;
13
+ export declare function getMergeBaseTargetRef(targetCommitish: string): string;
@@ -0,0 +1,39 @@
1
+ export function normalizeBaseMode(baseMode) {
2
+ return baseMode ?? 'direct';
3
+ }
4
+ export function createDiffSelection(baseCommitish, targetCommitish, baseMode) {
5
+ if (normalizeBaseMode(baseMode) === 'merge-base') {
6
+ return {
7
+ baseCommitish,
8
+ targetCommitish,
9
+ baseMode: 'merge-base',
10
+ };
11
+ }
12
+ return {
13
+ baseCommitish,
14
+ targetCommitish,
15
+ };
16
+ }
17
+ export function diffSelectionsEqual(left, right) {
18
+ return (left?.baseCommitish === right?.baseCommitish &&
19
+ left?.targetCommitish === right?.targetCommitish &&
20
+ normalizeBaseMode(left?.baseMode) === normalizeBaseMode(right?.baseMode));
21
+ }
22
+ export function getDiffSelectionKey(selection) {
23
+ return `${selection.baseCommitish}:${selection.targetCommitish}:${normalizeBaseMode(selection.baseMode)}`;
24
+ }
25
+ /**
26
+ * Working-tree sentinel targets whose diff is *mutable* — it depends on the
27
+ * current working tree / index, not just the ref. Re-selecting one must refetch
28
+ * (an identical selection can now have different content), unlike an immutable
29
+ * commit range where an unchanged selection can be safely skipped.
30
+ */
31
+ export function isWorkingTreeTarget(targetCommitish) {
32
+ return targetCommitish === '.' || targetCommitish === 'staged' || targetCommitish === 'working';
33
+ }
34
+ export function getMergeBaseTargetRef(targetCommitish) {
35
+ if (targetCommitish === '.' || targetCommitish === 'staged' || targetCommitish === 'working') {
36
+ return 'HEAD';
37
+ }
38
+ return targetCommitish;
39
+ }
@@ -0,0 +1,60 @@
1
+ export type EditorOptionId = 'vscode' | 'cursor' | 'zed' | 'textmate' | 'bbedit' | 'emacs' | 'macvim' | 'sublime' | 'nova' | 'custom' | 'none';
2
+ interface EditorOption {
3
+ readonly id: EditorOptionId;
4
+ readonly label: string;
5
+ /**
6
+ * Executable to run. Empty string for the `custom` placeholder (filled in by
7
+ * the user) and for `none` (open-in-editor disabled).
8
+ */
9
+ readonly command: string;
10
+ /**
11
+ * Whitespace-separated argument template. Supports the `%file` and `%line`
12
+ * placeholders. Parsed just before spawning so quoted segments survive:
13
+ * e.g. `'-l %line "%file"'`.
14
+ */
15
+ readonly argsTemplate: string;
16
+ }
17
+ export declare const CUSTOM_EDITOR_ID: EditorOptionId;
18
+ export declare const NONE_EDITOR_ID: EditorOptionId;
19
+ export declare const DEFAULT_EDITOR_ID: EditorOptionId;
20
+ export declare const EDITOR_OPTIONS: readonly [EditorOption, ...EditorOption[]];
21
+ export declare const DEFAULT_EDITOR_OPTION: EditorOption;
22
+ /**
23
+ * Case-insensitive lookup by id. Used mostly for the `DIFIT_EDITOR` / `EDITOR`
24
+ * environment-variable fallback on the server; the browser UI passes the full
25
+ * `{id, command, argsTemplate}` shape so it does not rely on this helper.
26
+ */
27
+ export declare const resolveEditorOption: (input?: string) => EditorOption;
28
+ /**
29
+ * Tokenise a user-supplied arguments template string into individual CLI
30
+ * arguments. Supports single and double quoted segments so paths and flags
31
+ * containing spaces can be preserved.
32
+ *
33
+ * Inside double-quoted segments `\"` decodes to a literal `"` and `\\` to a
34
+ * literal `\`, mirroring common shell intuition. Other `\x` sequences pass
35
+ * through untouched so Lisp/regex fragments like `\s` or `\$` stay intact.
36
+ * Single-quoted segments are fully literal. The template is handed to
37
+ * `spawn` as argv directly – no shell is involved.
38
+ */
39
+ export declare const parseEditorArgsTemplate: (template: string | undefined | null) => string[];
40
+ interface EditorSpawnSpec {
41
+ readonly command: string;
42
+ readonly args: readonly string[];
43
+ }
44
+ interface BuildEditorSpawnSpecInput {
45
+ readonly command: string;
46
+ readonly argsTemplate: string;
47
+ readonly filePath: string;
48
+ readonly lineNumber: number | null;
49
+ }
50
+ /**
51
+ * Build the final `{ command, args }` tuple to hand to `child_process.spawn`.
52
+ * Returns `null` when the command is empty so callers can surface a clear
53
+ * "not configured" error instead of spawning an empty process.
54
+ *
55
+ * `%file` and `%line` are substituted as-is inside each token, so composite
56
+ * tokens such as `"%file:%line"` become `"/abs/file.ts:42"`. When no line
57
+ * number is available the substitution falls back to `1`.
58
+ */
59
+ export declare const buildEditorSpawnSpec: (input: BuildEditorSpawnSpecInput) => EditorSpawnSpec | null;
60
+ export {};