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.
- package/LICENSE +22 -0
- package/README.md +142 -0
- package/dist/cli/cmux.d.ts +9 -0
- package/dist/cli/cmux.js +274 -0
- package/dist/cli/comment.d.ts +2 -0
- package/dist/cli/comment.js +91 -0
- package/dist/cli/github.d.ts +84 -0
- package/dist/cli/github.js +364 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +397 -0
- package/dist/cli/turns.d.ts +14 -0
- package/dist/cli/turns.js +333 -0
- package/dist/cli/utils.d.ts +27 -0
- package/dist/cli/utils.js +204 -0
- package/dist/client/assets/arc-CHYjdFTk.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
- package/dist/client/assets/array-BifhSqXX.js +1 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
- package/dist/client/assets/channel-Cv40nQW1.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
- package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
- package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
- package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
- package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
- package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
- package/dist/client/assets/dagre-Bx709z4p.js +1 -0
- package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
- package/dist/client/assets/dist-7wVkzZne.js +1 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
- package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/client/assets/index-BfVtbKTZ.css +2 -0
- package/dist/client/assets/index-p8zzfjdu.js +87 -0
- package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
- package/dist/client/assets/init-D6jRqBbL.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
- package/dist/client/assets/katex-Vhh-h91d.js +257 -0
- package/dist/client/assets/line-D8kNd46u.js +1 -0
- package/dist/client/assets/linear-jwGZSrg4.js +1 -0
- package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
- package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
- package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
- package/dist/client/assets/path-BWPyau1x.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
- package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
- package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
- package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
- package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
- package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
- package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
- package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
- package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
- package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
- package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
- package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
- package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
- package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
- package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
- package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
- package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
- package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
- package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
- package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
- package/dist/client/assets/src-CDAPGf2l.js +1 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/index.html +14 -0
- package/dist/client/logo.png +0 -0
- package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
- package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
- package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/manifest.json +1 -0
- package/dist/client/site-data/og-image.png +0 -0
- package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
- package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
- package/dist/server/file-watcher.d.ts +33 -0
- package/dist/server/file-watcher.js +340 -0
- package/dist/server/generated-file-check.d.ts +17 -0
- package/dist/server/generated-file-check.js +280 -0
- package/dist/server/git-diff.d.ts +94 -0
- package/dist/server/git-diff.js +909 -0
- package/dist/server/grouping/cache.d.ts +3 -0
- package/dist/server/grouping/cache.js +22 -0
- package/dist/server/grouping/claudeCli.d.ts +10 -0
- package/dist/server/grouping/claudeCli.js +95 -0
- package/dist/server/grouping/digest.d.ts +15 -0
- package/dist/server/grouping/digest.js +38 -0
- package/dist/server/grouping/reconcile.d.ts +6 -0
- package/dist/server/grouping/reconcile.js +34 -0
- package/dist/server/grouping/types.d.ts +9 -0
- package/dist/server/grouping/types.js +1 -0
- package/dist/server/server.d.ts +37 -0
- package/dist/server/server.js +1283 -0
- package/dist/shared/grouping.d.ts +4 -0
- package/dist/shared/grouping.js +170 -0
- package/dist/types/diff.d.ts +263 -0
- package/dist/types/diff.js +1 -0
- package/dist/types/watch.d.ts +59 -0
- package/dist/types/watch.js +8 -0
- package/dist/utils/commentFormatting.d.ts +6 -0
- package/dist/utils/commentFormatting.js +90 -0
- package/dist/utils/commentImports.d.ts +11 -0
- package/dist/utils/commentImports.js +386 -0
- package/dist/utils/createId.d.ts +1 -0
- package/dist/utils/createId.js +5 -0
- package/dist/utils/diffMode.d.ts +3 -0
- package/dist/utils/diffMode.js +13 -0
- package/dist/utils/diffSelection.d.ts +13 -0
- package/dist/utils/diffSelection.js +39 -0
- package/dist/utils/editorOptions.d.ts +60 -0
- package/dist/utils/editorOptions.js +171 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.js +21 -0
- package/dist/utils/hubConfig.d.ts +32 -0
- package/dist/utils/hubConfig.js +51 -0
- package/dist/utils/suggestionUtils.d.ts +19 -0
- package/dist/utils/suggestionUtils.js +31 -0
- package/dist/utils/turnLog.d.ts +47 -0
- package/dist/utils/turnLog.js +133 -0
- 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,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 {};
|