difit 3.1.18 → 4.0.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 (94) hide show
  1. package/README.ja.md +30 -3
  2. package/README.ko.md +30 -3
  3. package/README.md +30 -3
  4. package/README.zh.md +30 -3
  5. package/dist/cli/github.d.ts +65 -0
  6. package/dist/cli/github.js +296 -0
  7. package/dist/cli/github.test.d.ts +1 -0
  8. package/dist/cli/github.test.js +341 -0
  9. package/dist/cli/index.js +26 -1
  10. package/dist/cli/index.test.js +206 -4
  11. package/dist/cli/utils.d.ts +2 -8
  12. package/dist/cli/utils.js +4 -43
  13. package/dist/cli/utils.test.js +50 -67
  14. package/dist/client/assets/{_basePickBy-DyiQWUmK.js → _basePickBy-B9N-f0iT.js} +1 -1
  15. package/dist/client/assets/{_baseUniq-DivSZEOF.js → _baseUniq-tbL7nVvN.js} +1 -1
  16. package/dist/client/assets/{arc-c0kacVOL.js → arc-BOY-7mep.js} +1 -1
  17. package/dist/client/assets/{architectureDiagram-2XIMDMQ5-ubymLNEe.js → architectureDiagram-2XIMDMQ5-59AvHaSB.js} +1 -1
  18. package/dist/client/assets/{blockDiagram-WCTKOSBZ-F9D8w4_S.js → blockDiagram-WCTKOSBZ-DXIlumQk.js} +1 -1
  19. package/dist/client/assets/{c4Diagram-IC4MRINW-JE9Kx4yQ.js → c4Diagram-IC4MRINW-BbfZ0uRn.js} +1 -1
  20. package/dist/client/assets/channel-cZXsTJxA.js +1 -0
  21. package/dist/client/assets/{chunk-4BX2VUAB-CYOCoDMc.js → chunk-4BX2VUAB-l7rcB2IW.js} +1 -1
  22. package/dist/client/assets/{chunk-55IACEB6-PRBuiJg9.js → chunk-55IACEB6-CrZL3qv9.js} +1 -1
  23. package/dist/client/assets/{chunk-FMBD7UC4-C0eJ7JsI.js → chunk-FMBD7UC4-CrKv7ndg.js} +1 -1
  24. package/dist/client/assets/{chunk-JSJVCQXG-QZotPSqo.js → chunk-JSJVCQXG-DyBDhAEM.js} +1 -1
  25. package/dist/client/assets/{chunk-KX2RTZJC-B8du3tt8.js → chunk-KX2RTZJC-By5mkZmU.js} +1 -1
  26. package/dist/client/assets/{chunk-NQ4KR5QH-B10ldi5m.js → chunk-NQ4KR5QH-C30p9xRx.js} +1 -1
  27. package/dist/client/assets/{chunk-QZHKN3VN-CpwW9rUQ.js → chunk-QZHKN3VN-DVlhR2wU.js} +1 -1
  28. package/dist/client/assets/{chunk-WL4C6EOR-DwKPHpbL.js → chunk-WL4C6EOR-Cn7a6CO3.js} +1 -1
  29. package/dist/client/assets/classDiagram-VBA2DB6C-B_coIPEy.js +1 -0
  30. package/dist/client/assets/classDiagram-v2-RAHNMMFH-B_coIPEy.js +1 -0
  31. package/dist/client/assets/clone-BjaT2HOk.js +1 -0
  32. package/dist/client/assets/{cose-bilkent-S5V4N54A-p76yal75.js → cose-bilkent-S5V4N54A-LyauIk_9.js} +1 -1
  33. package/dist/client/assets/{dagre-KLK3FWXG-CdDyed3V.js → dagre-KLK3FWXG-DRWb2KE3.js} +1 -1
  34. package/dist/client/assets/{diagram-E7M64L7V-BaC8dXuW.js → diagram-E7M64L7V-ChT6mNWK.js} +1 -1
  35. package/dist/client/assets/{diagram-IFDJBPK2-BGf8xwJI.js → diagram-IFDJBPK2-CqbTduoP.js} +1 -1
  36. package/dist/client/assets/{diagram-P4PSJMXO-D3j16gBZ.js → diagram-P4PSJMXO-Bzv5Z3ri.js} +1 -1
  37. package/dist/client/assets/{erDiagram-INFDFZHY-DFpDdocf.js → erDiagram-INFDFZHY-CvXfUZ4L.js} +1 -1
  38. package/dist/client/assets/{flowDiagram-PKNHOUZH-Cz4mb4IF.js → flowDiagram-PKNHOUZH-CxmpNUKq.js} +1 -1
  39. package/dist/client/assets/{ganttDiagram-A5KZAMGK-CNzY9ua5.js → ganttDiagram-A5KZAMGK-9LpZCsg6.js} +1 -1
  40. package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DCSxL8EQ.js → gitGraphDiagram-K3NZZRJ6-C6yZOrQJ.js} +1 -1
  41. package/dist/client/assets/{graph-BC2BV1-T.js → graph-bUZ7uHLW.js} +1 -1
  42. package/dist/client/assets/index-BLNN1bfE.js +98 -0
  43. package/dist/client/assets/index-VxkpzDXr.css +1 -0
  44. package/dist/client/assets/{infoDiagram-LFFYTUFH-BKSspZbH.js → infoDiagram-LFFYTUFH-Djdy3W21.js} +1 -1
  45. package/dist/client/assets/{ishikawaDiagram-PHBUUO56-DZ2IRYwc.js → ishikawaDiagram-PHBUUO56-oOdwCpeS.js} +1 -1
  46. package/dist/client/assets/{journeyDiagram-4ABVD52K-BrjXAkii.js → journeyDiagram-4ABVD52K-DTb_nGAw.js} +1 -1
  47. package/dist/client/assets/{kanban-definition-K7BYSVSG-B1mfOekw.js → kanban-definition-K7BYSVSG-CMtP7pHA.js} +1 -1
  48. package/dist/client/assets/{layout-CWTG02uT.js → layout-CXr5MatK.js} +1 -1
  49. package/dist/client/assets/{linear-CGgOKp1d.js → linear-pOMS9pjV.js} +1 -1
  50. package/dist/client/assets/{mermaid.core-DTPtVBG7.js → mermaid.core-DV5JJ1Ie.js} +4 -4
  51. package/dist/client/assets/{mindmap-definition-YRQLILUH-DByVRPFT.js → mindmap-definition-YRQLILUH-DN-sbonc.js} +1 -1
  52. package/dist/client/assets/{pieDiagram-SKSYHLDU-DEgvAxAy.js → pieDiagram-SKSYHLDU-tAHCkgh1.js} +1 -1
  53. package/dist/client/assets/{prism-csharp-DqTrHqwJ.js → prism-csharp-5CQ0RcEE.js} +1 -1
  54. package/dist/client/assets/{prism-elixir-DEJaM00V.js → prism-elixir-BSOTyVg2.js} +1 -1
  55. package/dist/client/assets/{prism-hcl-HvJ0aPiH.js → prism-hcl-BYvi1mtM.js} +1 -1
  56. package/dist/client/assets/{prism-java-DDUFERTh.js → prism-java-DMU2FM4X.js} +1 -1
  57. package/dist/client/assets/{prism-perl-CNA3SNC9.js → prism-perl-CpfvaEQk.js} +1 -1
  58. package/dist/client/assets/{prism-php-hBQuhE2A.js → prism-php-SC920LoD.js} +1 -1
  59. package/dist/client/assets/{prism-ruby-BKap8imy.js → prism-ruby-DZph-YiO.js} +1 -1
  60. package/dist/client/assets/{prism-solidity-DHc7LZHq.js → prism-solidity-qTLbmiAT.js} +1 -1
  61. package/dist/client/assets/{quadrantDiagram-337W2JSQ-DTtikTvc.js → quadrantDiagram-337W2JSQ-B0wODmgR.js} +1 -1
  62. package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B34R-xD0.js → requirementDiagram-Z7DCOOCP-A3aeHC06.js} +1 -1
  63. package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-Dts1ZXRC.js → sankeyDiagram-WA2Y5GQK-BWa6kZhG.js} +1 -1
  64. package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DzM3WhEY.js → sequenceDiagram-2WXFIKYE-Cx_COX9G.js} +1 -1
  65. package/dist/client/assets/{stateDiagram-RAJIS63D-B2dF8YnK.js → stateDiagram-RAJIS63D-BXGnN6rZ.js} +1 -1
  66. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-CMw3xNha.js +1 -0
  67. package/dist/client/assets/{timeline-definition-YZTLITO2-BO4OtcEm.js → timeline-definition-YZTLITO2-DbqaUm9k.js} +1 -1
  68. package/dist/client/assets/{treemap-KZPCXAKY-DaXnvVRH.js → treemap-KZPCXAKY-CfEujPCR.js} +1 -1
  69. package/dist/client/assets/{vennDiagram-LZ73GAT5-AIMhd8Js.js → vennDiagram-LZ73GAT5-CqJE8CAD.js} +1 -1
  70. package/dist/client/assets/{xychartDiagram-JWTSCODW-Ch6W1f7P.js → xychartDiagram-JWTSCODW-CfdDvzHC.js} +1 -1
  71. package/dist/client/index.html +2 -2
  72. package/dist/server/generated-file-check.js +113 -58
  73. package/dist/server/generated-file-check.test.js +2 -0
  74. package/dist/server/git-diff.d.ts +1 -0
  75. package/dist/server/git-diff.js +33 -6
  76. package/dist/server/git-diff.test.js +26 -0
  77. package/dist/server/server.d.ts +2 -0
  78. package/dist/server/server.js +107 -34
  79. package/dist/server/server.test.js +120 -0
  80. package/dist/types/diff.d.ts +73 -14
  81. package/dist/utils/commentFormatting.d.ts +4 -2
  82. package/dist/utils/commentFormatting.js +57 -19
  83. package/dist/utils/commentImports.d.ts +9 -0
  84. package/dist/utils/commentImports.js +264 -0
  85. package/dist/utils/commentImports.test.d.ts +1 -0
  86. package/dist/utils/commentImports.test.js +197 -0
  87. package/package.json +1 -1
  88. package/dist/client/assets/channel-Ca4c0q8d.js +0 -1
  89. package/dist/client/assets/classDiagram-VBA2DB6C-CJLw9sK7.js +0 -1
  90. package/dist/client/assets/classDiagram-v2-RAHNMMFH-CJLw9sK7.js +0 -1
  91. package/dist/client/assets/clone-D0mDLEir.js +0 -1
  92. package/dist/client/assets/index-DHt9OwVU.css +0 -1
  93. package/dist/client/assets/index-mE8CA51x.js +0 -95
  94. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-ReD0hBzH.js +0 -1
@@ -0,0 +1,341 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { parseGitHubPrUrl, parsePrCommentImportsResponse } from './github';
3
+ function createReviewComment(overrides = {}) {
4
+ return {
5
+ id: 'COMMENT_1',
6
+ body: 'Imported comment',
7
+ createdAt: '2026-03-25T09:00:00Z',
8
+ updatedAt: '2026-03-25T09:05:00Z',
9
+ author: { login: 'octocat' },
10
+ ...overrides,
11
+ };
12
+ }
13
+ function createReviewThread(overrides = {}) {
14
+ return {
15
+ id: 'THREAD_1',
16
+ isResolved: false,
17
+ isOutdated: false,
18
+ subjectType: 'LINE',
19
+ path: 'src/example.ts',
20
+ diffSide: 'RIGHT',
21
+ startDiffSide: null,
22
+ line: 12,
23
+ startLine: null,
24
+ originalLine: 11,
25
+ originalStartLine: null,
26
+ comments: {
27
+ nodes: [createReviewComment()],
28
+ },
29
+ ...overrides,
30
+ };
31
+ }
32
+ afterEach(() => {
33
+ vi.restoreAllMocks();
34
+ });
35
+ describe('CLI GitHub utils', () => {
36
+ describe('parsePrCommentImportsResponse', () => {
37
+ it('imports unresolved inline threads, sorts comments, and skips non-importable threads', () => {
38
+ const response = {
39
+ data: {
40
+ repository: {
41
+ pullRequest: {
42
+ reviewThreads: {
43
+ nodes: [
44
+ createReviewThread({
45
+ id: 'THREAD_UNRESOLVED',
46
+ diffSide: 'RIGHT',
47
+ line: 14,
48
+ comments: {
49
+ nodes: [
50
+ createReviewComment({
51
+ id: 'COMMENT_REPLY',
52
+ body: 'Second comment',
53
+ createdAt: '2026-03-25T09:10:00Z',
54
+ updatedAt: '2026-03-25T09:12:00Z',
55
+ author: { login: 'reviewer-2' },
56
+ }),
57
+ createReviewComment({
58
+ id: 'COMMENT_ROOT',
59
+ body: 'First comment',
60
+ createdAt: '2026-03-25T09:00:00Z',
61
+ updatedAt: '2026-03-25T09:05:00Z',
62
+ author: { login: 'reviewer-1' },
63
+ }),
64
+ ],
65
+ },
66
+ }),
67
+ createReviewThread({
68
+ id: 'THREAD_RESOLVED',
69
+ isResolved: true,
70
+ }),
71
+ createReviewThread({
72
+ id: 'THREAD_OUTDATED',
73
+ isOutdated: true,
74
+ }),
75
+ createReviewThread({
76
+ id: 'THREAD_FILE',
77
+ subjectType: 'FILE',
78
+ }),
79
+ ],
80
+ pageInfo: {
81
+ hasNextPage: true,
82
+ endCursor: 'CURSOR_1',
83
+ },
84
+ },
85
+ },
86
+ },
87
+ },
88
+ };
89
+ const result = parsePrCommentImportsResponse(response);
90
+ expect(result).toEqual({
91
+ commentImports: [
92
+ {
93
+ type: 'thread',
94
+ id: 'COMMENT_ROOT',
95
+ filePath: 'src/example.ts',
96
+ position: { side: 'new', line: 14 },
97
+ body: 'First comment',
98
+ author: 'reviewer-1',
99
+ createdAt: '2026-03-25T09:00:00Z',
100
+ updatedAt: '2026-03-25T09:05:00Z',
101
+ },
102
+ {
103
+ type: 'reply',
104
+ id: 'COMMENT_REPLY',
105
+ filePath: 'src/example.ts',
106
+ position: { side: 'new', line: 14 },
107
+ body: 'Second comment',
108
+ author: 'reviewer-2',
109
+ createdAt: '2026-03-25T09:10:00Z',
110
+ updatedAt: '2026-03-25T09:12:00Z',
111
+ },
112
+ ],
113
+ pageInfo: {
114
+ hasNextPage: true,
115
+ endCursor: 'CURSOR_1',
116
+ },
117
+ });
118
+ });
119
+ it('maps RIGHT and LEFT threads to new and old diff positions for single and multi-line comments', () => {
120
+ const response = {
121
+ data: {
122
+ repository: {
123
+ pullRequest: {
124
+ reviewThreads: {
125
+ nodes: [
126
+ createReviewThread({
127
+ id: 'THREAD_RIGHT_SINGLE',
128
+ diffSide: 'RIGHT',
129
+ line: 20,
130
+ startLine: 20,
131
+ startDiffSide: null,
132
+ comments: {
133
+ nodes: [createReviewComment({ id: 'COMMENT_RIGHT_SINGLE' })],
134
+ },
135
+ }),
136
+ createReviewThread({
137
+ id: 'THREAD_RIGHT_MULTI',
138
+ diffSide: 'RIGHT',
139
+ line: 24,
140
+ startLine: 21,
141
+ startDiffSide: 'RIGHT',
142
+ comments: {
143
+ nodes: [createReviewComment({ id: 'COMMENT_RIGHT_MULTI' })],
144
+ },
145
+ }),
146
+ createReviewThread({
147
+ id: 'THREAD_LEFT_SINGLE',
148
+ diffSide: 'LEFT',
149
+ line: null,
150
+ originalLine: 8,
151
+ originalStartLine: null,
152
+ startDiffSide: null,
153
+ comments: {
154
+ nodes: [createReviewComment({ id: 'COMMENT_LEFT_SINGLE' })],
155
+ },
156
+ }),
157
+ createReviewThread({
158
+ id: 'THREAD_LEFT_MULTI',
159
+ diffSide: 'LEFT',
160
+ line: null,
161
+ originalLine: 11,
162
+ originalStartLine: 7,
163
+ startDiffSide: 'LEFT',
164
+ comments: {
165
+ nodes: [createReviewComment({ id: 'COMMENT_LEFT_MULTI' })],
166
+ },
167
+ }),
168
+ ],
169
+ pageInfo: {
170
+ hasNextPage: false,
171
+ endCursor: null,
172
+ },
173
+ },
174
+ },
175
+ },
176
+ },
177
+ };
178
+ const result = parsePrCommentImportsResponse(response);
179
+ expect(result.commentImports).toEqual([
180
+ {
181
+ type: 'thread',
182
+ id: 'COMMENT_RIGHT_SINGLE',
183
+ filePath: 'src/example.ts',
184
+ position: { side: 'new', line: 20 },
185
+ body: 'Imported comment',
186
+ author: 'octocat',
187
+ createdAt: '2026-03-25T09:00:00Z',
188
+ updatedAt: '2026-03-25T09:05:00Z',
189
+ },
190
+ {
191
+ type: 'thread',
192
+ id: 'COMMENT_RIGHT_MULTI',
193
+ filePath: 'src/example.ts',
194
+ position: { side: 'new', line: { start: 21, end: 24 } },
195
+ body: 'Imported comment',
196
+ author: 'octocat',
197
+ createdAt: '2026-03-25T09:00:00Z',
198
+ updatedAt: '2026-03-25T09:05:00Z',
199
+ },
200
+ {
201
+ type: 'thread',
202
+ id: 'COMMENT_LEFT_SINGLE',
203
+ filePath: 'src/example.ts',
204
+ position: { side: 'old', line: 8 },
205
+ body: 'Imported comment',
206
+ author: 'octocat',
207
+ createdAt: '2026-03-25T09:00:00Z',
208
+ updatedAt: '2026-03-25T09:05:00Z',
209
+ },
210
+ {
211
+ type: 'thread',
212
+ id: 'COMMENT_LEFT_MULTI',
213
+ filePath: 'src/example.ts',
214
+ position: { side: 'old', line: { start: 7, end: 11 } },
215
+ body: 'Imported comment',
216
+ author: 'octocat',
217
+ createdAt: '2026-03-25T09:00:00Z',
218
+ updatedAt: '2026-03-25T09:05:00Z',
219
+ },
220
+ ]);
221
+ });
222
+ it('warns and skips threads with invalid line mapping', () => {
223
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
224
+ const response = {
225
+ data: {
226
+ repository: {
227
+ pullRequest: {
228
+ reviewThreads: {
229
+ nodes: [
230
+ createReviewThread({
231
+ id: 'THREAD_RIGHT_MISSING_LINE',
232
+ diffSide: 'RIGHT',
233
+ line: null,
234
+ }),
235
+ createReviewThread({
236
+ id: 'THREAD_LEFT_MISSING_ORIGINAL',
237
+ diffSide: 'LEFT',
238
+ line: null,
239
+ originalLine: null,
240
+ }),
241
+ createReviewThread({
242
+ id: 'THREAD_RIGHT_BAD_RANGE',
243
+ diffSide: 'RIGHT',
244
+ line: 9,
245
+ startLine: 12,
246
+ startDiffSide: 'RIGHT',
247
+ }),
248
+ createReviewThread({
249
+ id: 'THREAD_LEFT_BAD_SIDE',
250
+ diffSide: 'LEFT',
251
+ line: null,
252
+ originalLine: 9,
253
+ originalStartLine: 7,
254
+ startDiffSide: 'RIGHT',
255
+ }),
256
+ ],
257
+ pageInfo: {
258
+ hasNextPage: false,
259
+ endCursor: null,
260
+ },
261
+ },
262
+ },
263
+ },
264
+ },
265
+ };
266
+ const result = parsePrCommentImportsResponse(response);
267
+ expect(result.commentImports).toEqual([]);
268
+ expect(warnSpy).toHaveBeenCalledTimes(4);
269
+ expect(warnSpy).toHaveBeenNthCalledWith(1, 'Warning: Skipping PR review thread THREAD_RIGHT_MISSING_LINE: RIGHT thread is missing line.');
270
+ expect(warnSpy).toHaveBeenNthCalledWith(2, 'Warning: Skipping PR review thread THREAD_LEFT_MISSING_ORIGINAL: LEFT thread is missing originalLine.');
271
+ expect(warnSpy).toHaveBeenNthCalledWith(3, 'Warning: Skipping PR review thread THREAD_RIGHT_BAD_RANGE: RIGHT thread has an invalid multi-line range.');
272
+ expect(warnSpy).toHaveBeenNthCalledWith(4, 'Warning: Skipping PR review thread THREAD_LEFT_BAD_SIDE: LEFT thread has mismatched startDiffSide.');
273
+ });
274
+ });
275
+ describe('parseGitHubPrUrl', () => {
276
+ it('should parse valid GitHub PR URLs', () => {
277
+ const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/123');
278
+ expect(result).toEqual({
279
+ owner: 'owner',
280
+ repo: 'repo',
281
+ pullNumber: 123,
282
+ hostname: 'github.com',
283
+ });
284
+ });
285
+ it('should parse GitHub PR URLs with additional path segments', () => {
286
+ const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/456/files');
287
+ expect(result).toEqual({
288
+ owner: 'owner',
289
+ repo: 'repo',
290
+ pullNumber: 456,
291
+ hostname: 'github.com',
292
+ });
293
+ });
294
+ it('should parse GitHub PR URLs with query parameters', () => {
295
+ const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/789?tab=files');
296
+ expect(result).toEqual({
297
+ owner: 'owner',
298
+ repo: 'repo',
299
+ pullNumber: 789,
300
+ hostname: 'github.com',
301
+ });
302
+ });
303
+ it('should handle URLs with hyphens and underscores in owner/repo names', () => {
304
+ const result = parseGitHubPrUrl('https://github.com/owner-name/repo_name/pull/123');
305
+ expect(result).toEqual({
306
+ owner: 'owner-name',
307
+ repo: 'repo_name',
308
+ pullNumber: 123,
309
+ hostname: 'github.com',
310
+ });
311
+ });
312
+ it('should parse GitHub Enterprise PR URLs', () => {
313
+ const result1 = parseGitHubPrUrl('https://github.enterprise.com/owner/repo/pull/123');
314
+ expect(result1).toEqual({
315
+ owner: 'owner',
316
+ repo: 'repo',
317
+ pullNumber: 123,
318
+ hostname: 'github.enterprise.com',
319
+ });
320
+ const result2 = parseGitHubPrUrl('https://git.company.io/team/project/pull/456');
321
+ expect(result2).toEqual({
322
+ owner: 'team',
323
+ repo: 'project',
324
+ pullNumber: 456,
325
+ hostname: 'git.company.io',
326
+ });
327
+ });
328
+ it('should return null for invalid URLs', () => {
329
+ expect(parseGitHubPrUrl('not-a-url')).toBe(null);
330
+ expect(parseGitHubPrUrl('https://github.com/owner/repo/issues/123')).toBe(null);
331
+ expect(parseGitHubPrUrl('https://github.com/owner/repo')).toBe(null);
332
+ expect(parseGitHubPrUrl('https://github.com/owner/repo/pull/abc')).toBe(null);
333
+ });
334
+ it('should handle malformed URLs gracefully', () => {
335
+ expect(parseGitHubPrUrl('')).toBe(null);
336
+ expect(parseGitHubPrUrl('https://github.com')).toBe(null);
337
+ expect(parseGitHubPrUrl('https://github.com/owner')).toBe(null);
338
+ expect(parseGitHubPrUrl('https://github.com/owner/repo/pull')).toBe(null);
339
+ });
340
+ });
341
+ });
package/dist/cli/index.js CHANGED
@@ -6,7 +6,8 @@ import pkg from '../../package.json' with { type: 'json' };
6
6
  import { startServer } from '../server/server.js';
7
7
  import { DiffMode } from '../types/watch.js';
8
8
  import { DEFAULT_DIFF_VIEW_MODE, normalizeDiffViewMode } from '../utils/diffMode.js';
9
- import { shouldReadStdin, findUntrackedFiles, markFilesIntentToAdd, promptUser, validateDiffArguments, getPrPatch, getGitRoot, } from './utils.js';
9
+ import { shouldReadStdin, findUntrackedFiles, markFilesIntentToAdd, promptUser, parseCommentOptions, validateDiffArguments, getGitRoot, } from './utils.js';
10
+ import { getPrPatch, getPrCommentImports } from './github.js';
10
11
  function isSpecialArg(arg) {
11
12
  return arg === 'working' || arg === 'staged' || arg === '.';
12
13
  }
@@ -39,6 +40,7 @@ program
39
40
  .option('--host <host>', 'host address to bind', '')
40
41
  .option('--no-open', 'do not automatically open browser')
41
42
  .option('--mode <mode>', 'diff mode (split or unified)', normalizeDiffViewMode, DEFAULT_DIFF_VIEW_MODE)
43
+ .option('--comment <json>', 'inject initial review comments (repeatable, accepts a JSON object or array)', (value, previous = []) => [...previous, value], [])
42
44
  .option('--tui', 'use terminal UI instead of web interface')
43
45
  .option('--pr <url>', 'GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123)')
44
46
  .option('--clean', 'start with a clean slate by clearing all existing comments')
@@ -48,6 +50,16 @@ program
48
50
  try {
49
51
  let stdinDiff;
50
52
  let stdinReviewLabel = 'diff from stdin';
53
+ let manualCommentImports = [];
54
+ let commentImports = [];
55
+ try {
56
+ manualCommentImports = parseCommentOptions(options.comment);
57
+ commentImports = manualCommentImports;
58
+ }
59
+ catch (error) {
60
+ console.error(`Error: ${error instanceof Error ? error.message : 'Invalid --comment value'}`);
61
+ process.exit(1);
62
+ }
51
63
  if (options.pr) {
52
64
  if (commitish !== 'HEAD' || compareWith) {
53
65
  console.error('Error: --pr option cannot be used with positional arguments');
@@ -65,6 +77,13 @@ program
65
77
  console.error(`Error resolving PR: ${error instanceof Error ? error.message : 'Unknown error'}`);
66
78
  process.exit(1);
67
79
  }
80
+ try {
81
+ const prCommentImports = await getPrCommentImports(options.pr);
82
+ commentImports = [...prCommentImports, ...manualCommentImports];
83
+ }
84
+ catch (error) {
85
+ console.warn(`Warning: Failed to load PR review comments: ${error instanceof Error ? error.message : 'Unknown error'}`);
86
+ }
68
87
  }
69
88
  else {
70
89
  // Check if we should read from stdin
@@ -93,6 +112,7 @@ program
93
112
  mode: options.mode,
94
113
  clearComments: options.clean,
95
114
  keepAlive: options.keepAlive,
115
+ ...(commentImports.length > 0 ? { commentImports } : {}),
96
116
  });
97
117
  console.log(`\n🚀 difit server started on ${url}`);
98
118
  console.log(`📋 Reviewing: ${stdinReviewLabel}`);
@@ -136,6 +156,10 @@ program
136
156
  await handleUntrackedFiles(git, options.includeUntracked);
137
157
  }
138
158
  if (options.tui) {
159
+ if (commentImports.length > 0) {
160
+ console.error('Error: --comment option cannot be used with --tui');
161
+ process.exit(1);
162
+ }
139
163
  // Check if we're in a TTY environment
140
164
  if (!process.stdin.isTTY) {
141
165
  console.error('Error: TUI mode requires an interactive terminal (TTY).');
@@ -169,6 +193,7 @@ program
169
193
  keepAlive: options.keepAlive,
170
194
  diffMode: determineDiffMode(targetCommitish, compareWith),
171
195
  repoPath,
196
+ ...(commentImports.length > 0 ? { commentImports } : {}),
172
197
  });
173
198
  console.log(`\n🚀 difit server started on ${url}`);
174
199
  console.log(`📋 Reviewing: ${targetCommitish}`);