difit 5.0.6 → 5.0.8

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 (97) hide show
  1. package/README.ja.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.zh.md +1 -1
  5. package/dist/cli/comment.js +56 -14
  6. package/dist/cli/comment.test.js +93 -1
  7. package/dist/cli/utils.js +1 -1
  8. package/dist/client/assets/{arc-jpxKSIAg.js → arc-Ce71GpYh.js} +1 -1
  9. package/dist/client/assets/architecture-7EHR7CIX-DJuaV-Fy.js +1 -0
  10. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B92ZxjNC.js → architectureDiagram-3BPJPVTR-Co4nMikn.js} +1 -1
  11. package/dist/client/assets/{blockDiagram-GPEHLZMM-D3ySUhR3.js → blockDiagram-GPEHLZMM-BZ8XmnOe.js} +1 -1
  12. package/dist/client/assets/{c4Diagram-AAUBKEIU-BUzdNEmx.js → c4Diagram-AAUBKEIU-Bifc6015.js} +1 -1
  13. package/dist/client/assets/channel-DCmC_NJ8.js +1 -0
  14. package/dist/client/assets/{chunk-2J33WTMH-1DQ-4Bz3.js → chunk-2J33WTMH-BKZtrrMM.js} +1 -1
  15. package/dist/client/assets/{chunk-3OPIFGDE-BQmtgKnq.js → chunk-3OPIFGDE-p1HDQKHx.js} +1 -1
  16. package/dist/client/assets/{chunk-4BX2VUAB-DFX6Qy74.js → chunk-4BX2VUAB-B1JdC880.js} +1 -1
  17. package/dist/client/assets/{chunk-55IACEB6-DtWki8a-.js → chunk-55IACEB6-BfDdrInp.js} +1 -1
  18. package/dist/client/assets/{chunk-5ZQYHXKU-uSJ_ZgCu.js → chunk-5ZQYHXKU-B0UFDeNc.js} +1 -1
  19. package/dist/client/assets/{chunk-727SXJPM-BB6xZgSD.js → chunk-727SXJPM-C1GLS2mt.js} +1 -1
  20. package/dist/client/assets/{chunk-AQP2D5EJ-B1Cm665s.js → chunk-AQP2D5EJ-CdhTjctN.js} +1 -1
  21. package/dist/client/assets/{chunk-BSJP7CBP-C15Vti01.js → chunk-BSJP7CBP-8ezJh_7H.js} +1 -1
  22. package/dist/client/assets/{chunk-CSCIHK7Q-CaTF_T3W.js → chunk-CSCIHK7Q-DNeLjTAX.js} +1 -1
  23. package/dist/client/assets/{chunk-FMBD7UC4-D1irlAGU.js → chunk-FMBD7UC4-CwtitMT8.js} +1 -1
  24. package/dist/client/assets/{chunk-KSCS5N6A-DGAjxObb.js → chunk-KSCS5N6A-DdLtmoYV.js} +1 -1
  25. package/dist/client/assets/{chunk-L5ZTLDWV-F5Tij0A9.js → chunk-L5ZTLDWV-DZrSg_8S.js} +1 -1
  26. package/dist/client/assets/{chunk-LZXEDZCA-BkUKm4Z2.js → chunk-LZXEDZCA-CPsWjeej.js} +2 -2
  27. package/dist/client/assets/{chunk-ND2GUHAM-C0rukZcz.js → chunk-ND2GUHAM-B3wT6dSk.js} +1 -1
  28. package/dist/client/assets/{chunk-NZK2D7GU-DRm_z6tq.js → chunk-NZK2D7GU-DTCItxUJ.js} +1 -1
  29. package/dist/client/assets/{chunk-O5CBEL6O-BAuwNGEL.js → chunk-O5CBEL6O-HSc7nkWp.js} +1 -1
  30. package/dist/client/assets/chunk-QZHKN3VN-Dl7i9TFZ.js +1 -0
  31. package/dist/client/assets/chunk-WU5MYG2G-H9-XOWa2.js +1 -0
  32. package/dist/client/assets/{chunk-XPW4576I-2r3neXxa.js → chunk-XPW4576I-DWA1L2SH.js} +1 -1
  33. package/dist/client/assets/classDiagram-4FO5ZUOK-CIis5MpW.js +1 -0
  34. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CIis5MpW.js +1 -0
  35. package/dist/client/assets/{cose-bilkent-S5V4N54A-BF8GqjYH.js → cose-bilkent-S5V4N54A-BA0dewjs.js} +1 -1
  36. package/dist/client/assets/{dagre-BM42HDAG-BcWO-l21.js → dagre-BM42HDAG-DQ6amw4B.js} +1 -1
  37. package/dist/client/assets/{diagram-2AECGRRQ-ChdDM46l.js → diagram-2AECGRRQ-lt3q0PPx.js} +1 -1
  38. package/dist/client/assets/{diagram-5GNKFQAL-DOPYGuMt.js → diagram-5GNKFQAL-RN_cFqeH.js} +1 -1
  39. package/dist/client/assets/{diagram-KO2AKTUF-D6gmyyoA.js → diagram-KO2AKTUF-69-WWf-i.js} +1 -1
  40. package/dist/client/assets/{diagram-LMA3HP47-C93nmtac.js → diagram-LMA3HP47-CJp_AQpE.js} +1 -1
  41. package/dist/client/assets/{diagram-OG6HWLK6-Cgm6DAM8.js → diagram-OG6HWLK6-ld8yVyEO.js} +1 -1
  42. package/dist/client/assets/{dist-C7Q1SrJ8.js → dist-MrZ1zwt-.js} +1 -1
  43. package/dist/client/assets/{erDiagram-TEJ5UH35-WzvVTKP0.js → erDiagram-TEJ5UH35-C9-9-98D.js} +1 -1
  44. package/dist/client/assets/eventmodeling-FCH6USID-sUz5BqkA.js +1 -0
  45. package/dist/client/assets/{flowDiagram-I6XJVG4X-BRAzB7RU.js → flowDiagram-I6XJVG4X-BIWAxSyl.js} +1 -1
  46. package/dist/client/assets/{ganttDiagram-6RSMTGT7-Bned6Md4.js → ganttDiagram-6RSMTGT7-DEekfrSb.js} +1 -1
  47. package/dist/client/assets/{gitGraph-WXDBUCRP-BsCF41a6.js → gitGraph-WXDBUCRP-iLmA5ZOg.js} +1 -1
  48. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-sjN-t2iw.js → gitGraphDiagram-PVQCEYII-Cp9no67W.js} +1 -1
  49. package/dist/client/assets/index-D6ilk_N-.js +83 -0
  50. package/dist/client/assets/index-DJEQpFaQ.css +2 -0
  51. package/dist/client/assets/{info-J43DQDTF-BnH-OC8Z.js → info-J43DQDTF-WGiiYO2H.js} +1 -1
  52. package/dist/client/assets/{infoDiagram-5YYISTIA-s8VJEWX-.js → infoDiagram-5YYISTIA-DSq2paHS.js} +1 -1
  53. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-Cr3iGWKj.js → ishikawaDiagram-YF4QCWOH-BRvjhcHO.js} +1 -1
  54. package/dist/client/assets/{journeyDiagram-JHISSGLW-DsSeOYHM.js → journeyDiagram-JHISSGLW-BeojzJiQ.js} +1 -1
  55. package/dist/client/assets/{kanban-definition-UN3LZRKU--hgCxMq5.js → kanban-definition-UN3LZRKU-Cxd2TSzC.js} +1 -1
  56. package/dist/client/assets/{line-BgzUTU3B.js → line-DfNX00iH.js} +1 -1
  57. package/dist/client/assets/{linear-Cj1FBal1.js → linear-BqTigxmw.js} +1 -1
  58. package/dist/client/assets/{mermaid-parser.core-oUly7wXy.js → mermaid-parser.core-B_EZyTa5.js} +2 -2
  59. package/dist/client/assets/{mermaid.core-QvJGj5O5.js → mermaid.core-Ssrtijme.js} +3 -3
  60. package/dist/client/assets/{mindmap-definition-RKZ34NQL-Cwyk4Byp.js → mindmap-definition-RKZ34NQL-CFexQB41.js} +1 -1
  61. package/dist/client/assets/{packet-YPE3B663-C559rspA.js → packet-YPE3B663-DWRftxLl.js} +1 -1
  62. package/dist/client/assets/{pie-LRSECV5Y-DZyJ170B.js → pie-LRSECV5Y-FBJQi9YS.js} +1 -1
  63. package/dist/client/assets/{pieDiagram-4H26LBE5-lGuBsE2s.js → pieDiagram-4H26LBE5-rWmojAfB.js} +1 -1
  64. package/dist/client/assets/prism-gdscript-C1gnCftx.js +1 -0
  65. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BCczJuw5.js → quadrantDiagram-W4KKPZXB-ClhgNDn7.js} +1 -1
  66. package/dist/client/assets/{radar-GUYGQ44K-Ckz6UanW.js → radar-GUYGQ44K-C948fgjI.js} +1 -1
  67. package/dist/client/assets/{requirementDiagram-4Y6WPE33-C4-ojYg3.js → requirementDiagram-4Y6WPE33-BuMMQGw0.js} +1 -1
  68. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CFpy-I_l.js → sankeyDiagram-5OEKKPKP-CEd3YUpA.js} +1 -1
  69. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DmD28UaM.js → sequenceDiagram-3UESZ5HK-DgsZJ52B.js} +1 -1
  70. package/dist/client/assets/{src-BWoEPgyG.js → src-CAgFxagX.js} +1 -1
  71. package/dist/client/assets/{stateDiagram-AJRCARHV-BNaiNMdT.js → stateDiagram-AJRCARHV-Cg0npyBC.js} +1 -1
  72. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CpV01lL-.js +1 -0
  73. package/dist/client/assets/{timeline-definition-PNZ67QCA-DQyT9Hys.js → timeline-definition-PNZ67QCA-Be8CZnBb.js} +1 -1
  74. package/dist/client/assets/{treeView-BLDUP644-BDNxHT3u.js → treeView-BLDUP644-B4fII_xt.js} +1 -1
  75. package/dist/client/assets/{treemap-LRROVOQU-mnVB3buf.js → treemap-LRROVOQU-Dy2AIK2x.js} +1 -1
  76. package/dist/client/assets/{vennDiagram-CIIHVFJN-BaDh5wWt.js → vennDiagram-CIIHVFJN-DehXkI0n.js} +1 -1
  77. package/dist/client/assets/{wardley-L42UT6IY-CZ9sEBPa.js → wardley-L42UT6IY-AqOhXLdx.js} +1 -1
  78. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-B_afE6fY.js → wardleyDiagram-YWT4CUSO-BzK61bpB.js} +1 -1
  79. package/dist/client/assets/{xychartDiagram-2RQKCTM6-6Na9BohO.js → xychartDiagram-2RQKCTM6-D0uYHNx6.js} +1 -1
  80. package/dist/client/index.html +2 -2
  81. package/dist/server/file-watcher.js +4 -1
  82. package/dist/server/git-diff.d.ts +1 -0
  83. package/dist/server/git-diff.js +79 -21
  84. package/dist/server/git-diff.test.js +110 -0
  85. package/dist/server/server.js +16 -0
  86. package/dist/server/server.test.js +56 -4
  87. package/package.json +2 -1
  88. package/dist/client/assets/architecture-7EHR7CIX-CGrQkVgf.js +0 -1
  89. package/dist/client/assets/channel-DG8kr4eK.js +0 -1
  90. package/dist/client/assets/chunk-QZHKN3VN-M1-XSu5b.js +0 -1
  91. package/dist/client/assets/chunk-WU5MYG2G-BvFjxMiG.js +0 -1
  92. package/dist/client/assets/classDiagram-4FO5ZUOK-BtF02SOc.js +0 -1
  93. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BtF02SOc.js +0 -1
  94. package/dist/client/assets/eventmodeling-FCH6USID-D2pfiIpz.js +0 -1
  95. package/dist/client/assets/index-BC8r72Kq.js +0 -82
  96. package/dist/client/assets/index-DxvaSOa7.css +0 -2
  97. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CkEmqrGj.js +0 -1
@@ -11,6 +11,7 @@ export declare class GitDiffParser {
11
11
  private resolveBaseCommitish;
12
12
  parseDiff(selection: DiffSelection, ignoreWhitespace?: boolean, contextLines?: number): Promise<DiffResponse>;
13
13
  private parseUnifiedDiff;
14
+ private splitPlainUnifiedDiff;
14
15
  private getGitattributesSourceArgs;
15
16
  private parseGitattributesGeneratedOutput;
16
17
  private getGitattributesGeneratedPaths;
@@ -113,15 +113,68 @@ export class GitDiffParser {
113
113
  parseUnifiedDiff(diffText) {
114
114
  const files = [];
115
115
  const fileBlocks = diffText.split(/^diff --git /m).slice(1);
116
- for (const fileBlock of fileBlocks) {
117
- const block = `diff --git ${fileBlock}`;
118
- const file = this.parseFileBlock(block);
116
+ if (fileBlocks.length > 0) {
117
+ for (const fileBlock of fileBlocks) {
118
+ const block = `diff --git ${fileBlock}`;
119
+ const file = this.parseFileBlock(block);
120
+ if (file) {
121
+ files.push(file);
122
+ }
123
+ }
124
+ return files;
125
+ }
126
+ for (const fileBlock of this.splitPlainUnifiedDiff(diffText)) {
127
+ const file = this.parseFileBlock(fileBlock, 'plain');
119
128
  if (file) {
120
129
  files.push(file);
121
130
  }
122
131
  }
123
132
  return files;
124
133
  }
134
+ splitPlainUnifiedDiff(diffText) {
135
+ const lines = diffText.split(/\r?\n/);
136
+ const blocks = [];
137
+ let blockStart = null;
138
+ let remainingOldLines = 0;
139
+ let remainingNewLines = 0;
140
+ for (let index = 0; index < lines.length; index++) {
141
+ const line = lines[index];
142
+ if (blockStart !== null && line.startsWith('@@')) {
143
+ const match = line.match(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
144
+ if (match) {
145
+ remainingOldLines = parseInt(match[2] ?? '1');
146
+ remainingNewLines = parseInt(match[4] ?? '1');
147
+ }
148
+ continue;
149
+ }
150
+ if (blockStart !== null && (remainingOldLines > 0 || remainingNewLines > 0)) {
151
+ if (line.startsWith('\\')) {
152
+ continue;
153
+ }
154
+ if (line.startsWith('+')) {
155
+ remainingNewLines--;
156
+ }
157
+ else if (line.startsWith('-')) {
158
+ remainingOldLines--;
159
+ }
160
+ else if (line.startsWith(' ')) {
161
+ remainingOldLines--;
162
+ remainingNewLines--;
163
+ }
164
+ continue;
165
+ }
166
+ if (line.startsWith('--- ') && lines[index + 1]?.startsWith('+++ ')) {
167
+ if (blockStart !== null) {
168
+ blocks.push(lines.slice(blockStart, index).join('\n'));
169
+ }
170
+ blockStart = index;
171
+ }
172
+ }
173
+ if (blockStart !== null) {
174
+ blocks.push(lines.slice(blockStart).join('\n'));
175
+ }
176
+ return blocks;
177
+ }
125
178
  getGitattributesSourceArgs(ref) {
126
179
  if (ref === 'working' || ref === '.') {
127
180
  return [];
@@ -179,23 +232,25 @@ export class GitDiffParser {
179
232
  }
180
233
  return files.map((file) => generatedPaths.has(file.path) ? { ...file, isGenerated: true } : file);
181
234
  }
182
- decodeGitPath(rawPath) {
235
+ decodeGitPath(rawPath, stripGitPrefix = true) {
183
236
  if (typeof rawPath !== 'string') {
184
237
  return undefined;
185
238
  }
186
- const trimmed = rawPath.startsWith('"') && rawPath.endsWith('"') ? rawPath.slice(1, -1) : rawPath;
239
+ const tabIndex = rawPath.indexOf('\t');
240
+ const pathWithoutTimestamp = tabIndex === -1 ? rawPath : rawPath.slice(0, tabIndex);
241
+ const trimmed = pathWithoutTimestamp.startsWith('"') && pathWithoutTimestamp.endsWith('"')
242
+ ? pathWithoutTimestamp.slice(1, -1)
243
+ : pathWithoutTimestamp;
187
244
  const gitPrefixes = ['a/', 'b/', 'c/', 'i/', 'w/'];
188
245
  let withoutPrefix = trimmed;
189
- for (const prefix of gitPrefixes) {
190
- if (withoutPrefix.startsWith(prefix)) {
191
- withoutPrefix = withoutPrefix.slice(prefix.length);
192
- break;
246
+ if (stripGitPrefix) {
247
+ for (const prefix of gitPrefixes) {
248
+ if (withoutPrefix.startsWith(prefix)) {
249
+ withoutPrefix = withoutPrefix.slice(prefix.length);
250
+ break;
251
+ }
193
252
  }
194
253
  }
195
- const tabIndex = withoutPrefix.indexOf('\t');
196
- if (tabIndex !== -1) {
197
- withoutPrefix = withoutPrefix.slice(0, tabIndex);
198
- }
199
254
  if (withoutPrefix === '/dev/null') {
200
255
  return undefined;
201
256
  }
@@ -261,11 +316,11 @@ export class GitDiffParser {
261
316
  }
262
317
  return Buffer.from(bytes).toString('utf8');
263
318
  }
264
- extractPathFromLine(line, prefix) {
319
+ extractPathFromLine(line, prefix, stripGitPrefix = true) {
265
320
  if (!line?.startsWith(prefix)) {
266
321
  return undefined;
267
322
  }
268
- return this.decodeGitPath(line.slice(prefix.length));
323
+ return this.decodeGitPath(line.slice(prefix.length), stripGitPrefix);
269
324
  }
270
325
  parseDiffHeaderPaths(headerLine) {
271
326
  if (!headerLine.startsWith('diff --git ')) {
@@ -304,7 +359,7 @@ export class GitDiffParser {
304
359
  newPath: this.decodeGitPath(rawNewPath),
305
360
  };
306
361
  }
307
- parseFileBlock(block) {
362
+ parseFileBlock(block, format = 'git') {
308
363
  const lines = block.split('\n');
309
364
  const headerLine = lines[0];
310
365
  const headerPaths = this.parseDiffHeaderPaths(headerLine);
@@ -312,12 +367,15 @@ export class GitDiffParser {
312
367
  const plusLine = lines.find((line) => line.startsWith('+++ '));
313
368
  const renameFromLine = lines.find((line) => line.startsWith('rename from '));
314
369
  const renameToLine = lines.find((line) => line.startsWith('rename to '));
315
- const plusPath = this.extractPathFromLine(plusLine, '+++ ');
316
- const minusPath = this.extractPathFromLine(minusLine, '--- ');
370
+ const stripGitPrefix = format !== 'plain';
371
+ const plusPath = this.extractPathFromLine(plusLine, '+++ ', stripGitPrefix);
372
+ const minusPath = this.extractPathFromLine(minusLine, '--- ', stripGitPrefix);
317
373
  const renameFromPath = this.extractPathFromLine(renameFromLine, 'rename from ');
318
374
  const renameToPath = this.extractPathFromLine(renameToLine, 'rename to ');
319
- const newPath = renameToPath ?? plusPath ?? headerPaths?.newPath;
320
- const oldPath = renameFromPath ?? minusPath ?? headerPaths?.oldPath ?? newPath;
375
+ const parsedNewPath = renameToPath ?? plusPath ?? headerPaths?.newPath;
376
+ const parsedOldPath = renameFromPath ?? minusPath ?? headerPaths?.oldPath;
377
+ const newPath = parsedNewPath ?? parsedOldPath;
378
+ const oldPath = parsedOldPath ?? newPath;
321
379
  if (!newPath) {
322
380
  return null;
323
381
  }
@@ -333,7 +391,7 @@ export class GitDiffParser {
333
391
  else if (deletedFileMode || plusLine?.includes('/dev/null')) {
334
392
  status = 'deleted';
335
393
  }
336
- else if (oldPath !== newPath) {
394
+ else if (format !== 'plain' && oldPath !== newPath) {
337
395
  status = 'renamed';
338
396
  }
339
397
  // Common properties for all files
@@ -765,6 +765,116 @@ index 0000000..1234567
765
765
  isGenerated: false,
766
766
  });
767
767
  });
768
+ it('parses plain unified diff output without git headers', () => {
769
+ const diffContent = `--- file1.txt\t2026-07-11 12:00:00
770
+ +++ file2.txt\t2026-07-11 12:00:01
771
+ @@ -1,2 +1,2 @@
772
+ unchanged
773
+ -old content
774
+ +new content`;
775
+ const result = parser.parseStdinDiff(diffContent);
776
+ expect(result).toMatchObject({
777
+ commit: 'stdin diff',
778
+ isEmpty: false,
779
+ files: [
780
+ {
781
+ path: 'file2.txt',
782
+ status: 'modified',
783
+ additions: 1,
784
+ deletions: 1,
785
+ isGenerated: false,
786
+ },
787
+ ],
788
+ });
789
+ });
790
+ it('normalizes CRLF in plain unified diff output', () => {
791
+ const diffContent = [
792
+ '--- old.txt',
793
+ '+++ new.txt',
794
+ '@@ -1 +1 @@',
795
+ '-old content',
796
+ '+new content',
797
+ ].join('\r\n');
798
+ const result = parser.parseStdinDiff(diffContent);
799
+ expect(result.files).toHaveLength(1);
800
+ expect(result.files[0]).toMatchObject({
801
+ path: 'new.txt',
802
+ chunks: [
803
+ {
804
+ lines: [
805
+ { type: 'delete', content: 'old content' },
806
+ { type: 'add', content: 'new content' },
807
+ ],
808
+ },
809
+ ],
810
+ });
811
+ });
812
+ it('parses multiple concatenated plain unified diffs', () => {
813
+ const diffContent = `--- first-old.txt
814
+ +++ first-new.txt
815
+ @@ -1 +1 @@
816
+ -before
817
+ +after
818
+ --- second-old.txt
819
+ +++ second-new.txt
820
+ @@ -1 +1,2 @@
821
+ existing
822
+ +added`;
823
+ const result = parser.parseStdinDiff(diffContent);
824
+ expect(result.files).toMatchObject([
825
+ {
826
+ path: 'first-new.txt',
827
+ status: 'modified',
828
+ additions: 1,
829
+ deletions: 1,
830
+ },
831
+ {
832
+ path: 'second-new.txt',
833
+ status: 'modified',
834
+ additions: 1,
835
+ deletions: 0,
836
+ },
837
+ ]);
838
+ });
839
+ it('does not treat header-like hunk content as another plain diff', () => {
840
+ const diffContent = `--- old.txt
841
+ +++ new.txt
842
+ @@ -1,2 +1,2 @@
843
+ --- deleted content
844
+ +++ added content`;
845
+ const result = parser.parseStdinDiff(diffContent);
846
+ expect(result.files).toHaveLength(1);
847
+ expect(result.files[0]).toMatchObject({
848
+ path: 'new.txt',
849
+ additions: 1,
850
+ deletions: 1,
851
+ });
852
+ });
853
+ it('detects added and deleted files in plain unified diffs', () => {
854
+ const diffContent = `--- /dev/null
855
+ +++ added.txt
856
+ @@ -0,0 +1 @@
857
+ +added
858
+ --- deleted.txt
859
+ +++ /dev/null
860
+ @@ -1 +0,0 @@
861
+ -deleted`;
862
+ const result = parser.parseStdinDiff(diffContent);
863
+ expect(result.files).toMatchObject([
864
+ {
865
+ path: 'added.txt',
866
+ status: 'added',
867
+ additions: 1,
868
+ deletions: 0,
869
+ },
870
+ {
871
+ path: 'deleted.txt',
872
+ status: 'deleted',
873
+ additions: 0,
874
+ deletions: 1,
875
+ },
876
+ ]);
877
+ });
768
878
  it('should handle deleted files', async () => {
769
879
  const diffContent = `diff --git a/deleted.txt b/deleted.txt
770
880
  deleted file mode 100644
@@ -567,6 +567,22 @@ export async function startServer(options) {
567
567
  res.status(400).json({ error: 'Invalid comment import data' });
568
568
  }
569
569
  });
570
+ app.delete('/api/comments/:threadId', (req, res) => {
571
+ const selection = getCommentSelectionFromQuery(req.query);
572
+ const session = getOrCreateCommentSession(selection);
573
+ const threadId = req.params.threadId;
574
+ const nextThreads = session.threads.filter((thread) => thread.id !== threadId);
575
+ if (nextThreads.length === session.threads.length) {
576
+ res.status(404).json({ error: `Thread not found: ${threadId}` });
577
+ return;
578
+ }
579
+ updateCommentSession(selection, nextThreads);
580
+ res.json({
581
+ success: true,
582
+ threadId,
583
+ version: session.version,
584
+ });
585
+ });
570
586
  app.get('/api/comments-json', (req, res) => {
571
587
  const selection = getCommentSelectionFromQuery(req.query);
572
588
  const session = getOrCreateCommentSession(selection);
@@ -191,7 +191,10 @@ describe('Server Integration Tests', () => {
191
191
  };
192
192
  it('merges concurrent agent additions instead of clobbering on a stale push', async () => {
193
193
  const port = await getAvailablePort(4966);
194
- const result = await startServer({ preferredPort: port, openBrowser: false });
194
+ const result = await startServer({
195
+ preferredPort: port,
196
+ openBrowser: false,
197
+ });
195
198
  try {
196
199
  // Browser establishes a thread; it now knows version 1.
197
200
  await postThreads(result.port, [makeThread('t1', 'src/a.ts', 10, 'human thread')]);
@@ -234,7 +237,10 @@ describe('Server Integration Tests', () => {
234
237
  });
235
238
  it('replaces (honoring deletions) when the push version matches', async () => {
236
239
  const port = await getAvailablePort(4966);
237
- const result = await startServer({ preferredPort: port, openBrowser: false });
240
+ const result = await startServer({
241
+ preferredPort: port,
242
+ openBrowser: false,
243
+ });
238
244
  try {
239
245
  await postThreads(result.port, [makeThread('t1', 'src/a.ts', 10, 'human thread')]);
240
246
  const afterFirst = await getSession(result.port);
@@ -258,7 +264,10 @@ describe('Server Integration Tests', () => {
258
264
  });
259
265
  it('bumps the version when a reply is imported (so the change is broadcast)', async () => {
260
266
  const port = await getAvailablePort(4966);
261
- const result = await startServer({ preferredPort: port, openBrowser: false });
267
+ const result = await startServer({
268
+ preferredPort: port,
269
+ openBrowser: false,
270
+ });
262
271
  try {
263
272
  await postThreads(result.port, [makeThread('t1', 'src/a.ts', 10, 'human thread')]);
264
273
  const before = await getSession(result.port);
@@ -792,6 +801,46 @@ describe('Server Integration Tests', () => {
792
801
  const threads = data.threads.filter((t) => t.filePath === 'src/dedup.ts');
793
802
  expect(threads).toHaveLength(1);
794
803
  });
804
+ it('DELETE /api/comments/:threadId removes the thread and bumps the version', async () => {
805
+ await fetch(`http://localhost:${port}/api/comment-imports`, {
806
+ method: 'POST',
807
+ headers: { 'Content-Type': 'application/json' },
808
+ body: JSON.stringify([
809
+ {
810
+ type: 'thread',
811
+ id: 'delete-me',
812
+ filePath: 'src/delete-test.ts',
813
+ position: { side: 'new', line: 3 },
814
+ body: 'Thread to delete',
815
+ },
816
+ ]),
817
+ });
818
+ const beforeResponse = await fetch(`http://localhost:${port}/api/comments-json`);
819
+ const before = (await beforeResponse.json());
820
+ expect(before.threads.some((t) => t.id === 'delete-me')).toBe(true);
821
+ const deleteResponse = await fetch(`http://localhost:${port}/api/comments/delete-me`, {
822
+ method: 'DELETE',
823
+ });
824
+ const deleteData = (await deleteResponse.json());
825
+ expect(deleteResponse.ok).toBe(true);
826
+ expect(deleteData).toMatchObject({
827
+ success: true,
828
+ threadId: 'delete-me',
829
+ });
830
+ expect(deleteData.version).toBe(before.version + 1);
831
+ const afterResponse = await fetch(`http://localhost:${port}/api/comments-json`);
832
+ const after = (await afterResponse.json());
833
+ expect(after.threads.some((t) => t.id === 'delete-me')).toBe(false);
834
+ });
835
+ it('DELETE /api/comments/:threadId returns 404 for unknown thread', async () => {
836
+ const response = await fetch(`http://localhost:${port}/api/comments/does-not-exist`, {
837
+ method: 'DELETE',
838
+ });
839
+ expect(response.status).toBe(404);
840
+ const data = (await response.json());
841
+ expect(data).toHaveProperty('error');
842
+ expect(data.error).toContain('does-not-exist');
843
+ });
795
844
  it('isolates comment sessions between different diff selections', async () => {
796
845
  const importServer = await startServer({
797
846
  selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
@@ -961,7 +1010,10 @@ describe('Server Integration Tests', () => {
961
1010
  const data = (await response.json());
962
1011
  expect(response.ok).toBe(true);
963
1012
  expect(data.specialOptions).toHaveLength(3);
964
- expect(data.specialOptions).not.toContainEqual({ value: 'merge-base', label: 'Merge Base' });
1013
+ expect(data.specialOptions).not.toContainEqual({
1014
+ value: 'merge-base',
1015
+ label: 'Merge Base',
1016
+ });
965
1017
  expect(data.branches).toEqual([{ name: 'main', current: true }]);
966
1018
  expect(data.commits).toEqual([
967
1019
  { hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "difit",
3
- "version": "5.0.6",
3
+ "version": "5.0.8",
4
4
  "description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
5
5
  "keywords": [
6
6
  "cli",
@@ -79,6 +79,7 @@
79
79
  "react-dom": "^19.1.0",
80
80
  "react-hotkeys-hook": "^5.2.4",
81
81
  "react-markdown": "^10.1.0",
82
+ "remark-breaks": "^4.0.0",
82
83
  "remark-gfm": "^4.0.1",
83
84
  "simple-git": "^3.28.0"
84
85
  },
@@ -1 +0,0 @@
1
- import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-oUly7wXy.js";export{e as createArchitectureServices};
@@ -1 +0,0 @@
1
- import{at as e,ot as t}from"./chunk-CSCIHK7Q-CaTF_T3W.js";var n=(n,r)=>t.lang.round(e.parse(n)[r]);export{n as t};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-BWoEPgyG.js";var t=class{constructor(e){this.init=e,this.records=this.init()}static{e(this,`ImperativeState`)}reset(){this.records=this.init()}};export{t};
@@ -1 +0,0 @@
1
- import{h as e,p as t}from"./src-BWoEPgyG.js";import{x as n}from"./chunk-CSCIHK7Q-CaTF_T3W.js";var r=e(e=>{let{securityLevel:r}=n(),i=t(`body`);return r===`sandbox`&&(i=t((t(`#i${e}`).node()?.contentDocument??document).body)),i.select(`#${e}`)},`selectSvgElement`);export{r as t};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-BWoEPgyG.js";import"./chunk-CSCIHK7Q-CaTF_T3W.js";import"./chunk-5ZQYHXKU-uSJ_ZgCu.js";import"./chunk-O5CBEL6O-BAuwNGEL.js";import"./chunk-FMBD7UC4-D1irlAGU.js";import"./chunk-BSJP7CBP-C15Vti01.js";import"./chunk-L5ZTLDWV-F5Tij0A9.js";import"./chunk-ND2GUHAM-C0rukZcz.js";import"./chunk-55IACEB6-DtWki8a-.js";import"./chunk-2J33WTMH-1DQ-4Bz3.js";import"./chunk-NZK2D7GU-DRm_z6tq.js";import"./chunk-3OPIFGDE-BQmtgKnq.js";import"./chunk-KSCS5N6A-DGAjxObb.js";import"./chunk-LZXEDZCA-BkUKm4Z2.js";import{i as t,n,r,t as i}from"./chunk-727SXJPM-BB6xZgSD.js";var a={parser:n,get db(){return new i},renderer:r,styles:t,init:e(e=>{e.class||={},e.class.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-BWoEPgyG.js";import"./chunk-CSCIHK7Q-CaTF_T3W.js";import"./chunk-5ZQYHXKU-uSJ_ZgCu.js";import"./chunk-O5CBEL6O-BAuwNGEL.js";import"./chunk-FMBD7UC4-D1irlAGU.js";import"./chunk-BSJP7CBP-C15Vti01.js";import"./chunk-L5ZTLDWV-F5Tij0A9.js";import"./chunk-ND2GUHAM-C0rukZcz.js";import"./chunk-55IACEB6-DtWki8a-.js";import"./chunk-2J33WTMH-1DQ-4Bz3.js";import"./chunk-NZK2D7GU-DRm_z6tq.js";import"./chunk-3OPIFGDE-BQmtgKnq.js";import"./chunk-KSCS5N6A-DGAjxObb.js";import"./chunk-LZXEDZCA-BkUKm4Z2.js";import{i as t,n,r,t as i}from"./chunk-727SXJPM-BB6xZgSD.js";var a={parser:n,get db(){return new i},renderer:r,styles:t,init:e(e=>{e.class||={},e.class.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
@@ -1 +0,0 @@
1
- import"./chunk-NNHCCRGN-DlpIbxXb.js";import{y as e}from"./mermaid-parser.core-oUly7wXy.js";export{e as createEventModelingServices};