difit 3.1.18 → 4.0.1
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/README.ja.md +44 -16
- package/README.ko.md +44 -16
- package/README.md +44 -16
- package/README.zh.md +44 -16
- package/dist/cli/github.d.ts +65 -0
- package/dist/cli/github.js +296 -0
- package/dist/cli/github.test.d.ts +1 -0
- package/dist/cli/github.test.js +341 -0
- package/dist/cli/index.js +42 -1
- package/dist/cli/index.test.js +330 -4
- package/dist/cli/utils.d.ts +2 -8
- package/dist/cli/utils.js +4 -43
- package/dist/cli/utils.test.js +50 -67
- package/dist/client/assets/{_basePickBy-DyiQWUmK.js → _basePickBy-ChXFkTMC.js} +1 -1
- package/dist/client/assets/{_baseUniq-DivSZEOF.js → _baseUniq-Mj_sFFQW.js} +1 -1
- package/dist/client/assets/{arc-c0kacVOL.js → arc-BMA6S9F1.js} +1 -1
- package/dist/client/assets/{architectureDiagram-2XIMDMQ5-ubymLNEe.js → architectureDiagram-2XIMDMQ5-0uiM_v5K.js} +1 -1
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-F9D8w4_S.js → blockDiagram-WCTKOSBZ-CM7ZLL6F.js} +1 -1
- package/dist/client/assets/{c4Diagram-IC4MRINW-JE9Kx4yQ.js → c4Diagram-IC4MRINW-DKtCnVwn.js} +1 -1
- package/dist/client/assets/channel-D057yzDp.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CYOCoDMc.js → chunk-4BX2VUAB-Wsl8DxEB.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-PRBuiJg9.js → chunk-55IACEB6-CHm9X5i7.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-C0eJ7JsI.js → chunk-FMBD7UC4-BSa8SHgd.js} +1 -1
- package/dist/client/assets/{chunk-JSJVCQXG-QZotPSqo.js → chunk-JSJVCQXG-Cpk76oJ3.js} +1 -1
- package/dist/client/assets/{chunk-KX2RTZJC-B8du3tt8.js → chunk-KX2RTZJC-D8YvfZVu.js} +1 -1
- package/dist/client/assets/{chunk-NQ4KR5QH-B10ldi5m.js → chunk-NQ4KR5QH-BogviJOv.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CpwW9rUQ.js → chunk-QZHKN3VN-DwLJYu26.js} +1 -1
- package/dist/client/assets/{chunk-WL4C6EOR-DwKPHpbL.js → chunk-WL4C6EOR-BFDpGxW2.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C---D4iOts.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH---D4iOts.js +1 -0
- package/dist/client/assets/clone-xSR3otEf.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-p76yal75.js → cose-bilkent-S5V4N54A-oEosZ_5y.js} +1 -1
- package/dist/client/assets/{dagre-KLK3FWXG-CdDyed3V.js → dagre-KLK3FWXG-gFld4u1H.js} +1 -1
- package/dist/client/assets/{diagram-E7M64L7V-BaC8dXuW.js → diagram-E7M64L7V-gJq3kSrf.js} +1 -1
- package/dist/client/assets/{diagram-IFDJBPK2-BGf8xwJI.js → diagram-IFDJBPK2-BsUm_q22.js} +1 -1
- package/dist/client/assets/{diagram-P4PSJMXO-D3j16gBZ.js → diagram-P4PSJMXO-juB-sfcR.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-DFpDdocf.js → erDiagram-INFDFZHY-Dn77qXAt.js} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-Cz4mb4IF.js → flowDiagram-PKNHOUZH-DtmvDYdN.js} +1 -1
- package/dist/client/assets/{ganttDiagram-A5KZAMGK-CNzY9ua5.js → ganttDiagram-A5KZAMGK-BlDaKLbQ.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DCSxL8EQ.js → gitGraphDiagram-K3NZZRJ6-DeAAeuMS.js} +1 -1
- package/dist/client/assets/{graph-BC2BV1-T.js → graph-NX9gBP47.js} +1 -1
- package/dist/client/assets/index-VxkpzDXr.css +1 -0
- package/dist/client/assets/index-kJdw4DY-.js +98 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-BKSspZbH.js → infoDiagram-LFFYTUFH-CAaX023c.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-DZ2IRYwc.js → ishikawaDiagram-PHBUUO56-CmiTQStv.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-BrjXAkii.js → journeyDiagram-4ABVD52K-B0SHC7mz.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-B1mfOekw.js → kanban-definition-K7BYSVSG-IfRdhzz7.js} +1 -1
- package/dist/client/assets/{layout-CWTG02uT.js → layout-l3OdNQhJ.js} +1 -1
- package/dist/client/assets/{linear-CGgOKp1d.js → linear-CQ0hx5Qs.js} +1 -1
- package/dist/client/assets/{mermaid.core-DTPtVBG7.js → mermaid.core-DqlPTabt.js} +4 -4
- package/dist/client/assets/{mindmap-definition-YRQLILUH-DByVRPFT.js → mindmap-definition-YRQLILUH-DIgSmG_f.js} +1 -1
- package/dist/client/assets/{pieDiagram-SKSYHLDU-DEgvAxAy.js → pieDiagram-SKSYHLDU-FzM5qoIB.js} +1 -1
- package/dist/client/assets/{prism-csharp-DqTrHqwJ.js → prism-csharp-DCfUUOUs.js} +1 -1
- package/dist/client/assets/{prism-elixir-DEJaM00V.js → prism-elixir-riuOL1mm.js} +1 -1
- package/dist/client/assets/{prism-hcl-HvJ0aPiH.js → prism-hcl-CizuX1s4.js} +1 -1
- package/dist/client/assets/{prism-java-DDUFERTh.js → prism-java-DYCKrDUh.js} +1 -1
- package/dist/client/assets/{prism-perl-CNA3SNC9.js → prism-perl-BJwBYR3Y.js} +1 -1
- package/dist/client/assets/{prism-php-hBQuhE2A.js → prism-php-BMhFuA7y.js} +1 -1
- package/dist/client/assets/{prism-ruby-BKap8imy.js → prism-ruby-Bcu0cDEh.js} +1 -1
- package/dist/client/assets/{prism-solidity-DHc7LZHq.js → prism-solidity-DDDs3w-w.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-DTtikTvc.js → quadrantDiagram-337W2JSQ-BBrApyD7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B34R-xD0.js → requirementDiagram-Z7DCOOCP-CLXiwUaA.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-Dts1ZXRC.js → sankeyDiagram-WA2Y5GQK-9Y3Ly5qe.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DzM3WhEY.js → sequenceDiagram-2WXFIKYE-DEpX1BA5.js} +1 -1
- package/dist/client/assets/{stateDiagram-RAJIS63D-B2dF8YnK.js → stateDiagram-RAJIS63D-Ck3ullwA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-X6UiDsar.js +1 -0
- package/dist/client/assets/{timeline-definition-YZTLITO2-BO4OtcEm.js → timeline-definition-YZTLITO2-CMezf3XV.js} +1 -1
- package/dist/client/assets/{treemap-KZPCXAKY-DaXnvVRH.js → treemap-KZPCXAKY-DqrcV0gQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-LZ73GAT5-AIMhd8Js.js → vennDiagram-LZ73GAT5-eQg945Fz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-Ch6W1f7P.js → xychartDiagram-JWTSCODW-_hqdXeX1.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/generated-file-check.js +113 -58
- package/dist/server/generated-file-check.test.js +2 -0
- package/dist/server/git-diff-tui.d.ts +1 -1
- package/dist/server/git-diff-tui.js +7 -5
- package/dist/server/git-diff-tui.test.d.ts +1 -0
- package/dist/server/git-diff-tui.test.js +60 -0
- package/dist/server/git-diff.d.ts +4 -1
- package/dist/server/git-diff.js +73 -9
- package/dist/server/git-diff.test.js +46 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +111 -37
- package/dist/server/server.test.js +152 -0
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/App.js +2 -2
- package/dist/types/diff.d.ts +74 -14
- package/dist/utils/commentFormatting.d.ts +4 -2
- package/dist/utils/commentFormatting.js +57 -19
- package/dist/utils/commentImports.d.ts +9 -0
- package/dist/utils/commentImports.js +264 -0
- package/dist/utils/commentImports.test.d.ts +1 -0
- package/dist/utils/commentImports.test.js +197 -0
- package/package.json +1 -1
- package/dist/client/assets/channel-Ca4c0q8d.js +0 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-CJLw9sK7.js +0 -1
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-CJLw9sK7.js +0 -1
- package/dist/client/assets/clone-D0mDLEir.js +0 -1
- package/dist/client/assets/index-DHt9OwVU.css +0 -1
- package/dist/client/assets/index-mE8CA51x.js +0 -95
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-ReD0hBzH.js +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { Comment } from '../types/diff';
|
|
1
|
+
import type { Comment, CommentThread } from '../types/diff';
|
|
2
2
|
export declare function formatCommentPrompt(file: string, line: number | number[], body: string, codeContent?: string): string;
|
|
3
3
|
export declare function formatAllCommentsPrompt(comments: Comment[]): string;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function formatCommentThreadPrompt(thread: CommentThread): string;
|
|
5
|
+
export declare function formatAllCommentThreadsPrompt(threads: CommentThread[]): string;
|
|
6
|
+
export declare function formatCommentsOutput(input: Comment[] | CommentThread[]): string;
|
|
@@ -1,38 +1,56 @@
|
|
|
1
1
|
import { hasSuggestionBlock, parseSuggestionBlocks } from './suggestionUtils.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// Check if body contains suggestion blocks
|
|
2
|
+
function getLineInfo(line) {
|
|
3
|
+
return typeof line === 'number' ? `L${line}` : `L${line[0]}-L${line[1]}`;
|
|
4
|
+
}
|
|
5
|
+
function formatCommentContent(body, codeContent) {
|
|
7
6
|
if (hasSuggestionBlock(body)) {
|
|
8
7
|
const suggestions = parseSuggestionBlocks(body);
|
|
9
8
|
if (suggestions.length > 0) {
|
|
10
|
-
let result =
|
|
11
|
-
// Walk through body preserving text between suggestion blocks
|
|
9
|
+
let result = '';
|
|
12
10
|
let lastIndex = 0;
|
|
13
11
|
for (const suggestion of suggestions) {
|
|
14
|
-
// Add text before this suggestion block
|
|
15
12
|
const textBefore = body.slice(lastIndex, suggestion.startIndex).trim();
|
|
16
13
|
if (textBefore) {
|
|
17
|
-
result +=
|
|
14
|
+
result += `${result ? '\n' : ''}${textBefore}`;
|
|
18
15
|
}
|
|
19
|
-
// Add structured ORIGINAL/SUGGESTED format
|
|
20
16
|
if (codeContent) {
|
|
21
|
-
result +=
|
|
17
|
+
result += `${result ? '\n' : ''}ORIGINAL:\n\`\`\`\n${codeContent}\n\`\`\``;
|
|
22
18
|
}
|
|
23
|
-
result +=
|
|
19
|
+
result += `${result ? '\n' : ''}SUGGESTED:\n\`\`\`\n${suggestion.suggestedCode}\n\`\`\``;
|
|
24
20
|
lastIndex = suggestion.endIndex;
|
|
25
21
|
}
|
|
26
|
-
// Add remaining text after the last suggestion block
|
|
27
22
|
const textAfter = body.slice(lastIndex).trim();
|
|
28
23
|
if (textAfter) {
|
|
29
|
-
result +=
|
|
24
|
+
result += `${result ? '\n' : ''}${textAfter}`;
|
|
30
25
|
}
|
|
31
26
|
return result;
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
return body;
|
|
30
|
+
}
|
|
31
|
+
function normalizeLegacyComment(comment) {
|
|
32
|
+
return {
|
|
33
|
+
id: comment.id,
|
|
34
|
+
file: comment.file,
|
|
35
|
+
line: comment.line,
|
|
36
|
+
side: comment.side,
|
|
37
|
+
createdAt: comment.timestamp,
|
|
38
|
+
updatedAt: comment.timestamp,
|
|
39
|
+
codeContent: comment.codeContent,
|
|
40
|
+
messages: [
|
|
41
|
+
{
|
|
42
|
+
id: comment.id,
|
|
43
|
+
body: comment.body,
|
|
44
|
+
author: comment.author,
|
|
45
|
+
createdAt: comment.timestamp,
|
|
46
|
+
updatedAt: comment.timestamp,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function formatCommentPrompt(file, line, body, codeContent) {
|
|
52
|
+
const filePath = file || '<unknown file>';
|
|
53
|
+
return `${filePath}:${getLineInfo(line)}\n${formatCommentContent(body, codeContent)}`;
|
|
36
54
|
}
|
|
37
55
|
export function formatAllCommentsPrompt(comments) {
|
|
38
56
|
if (comments.length === 0)
|
|
@@ -40,13 +58,33 @@ export function formatAllCommentsPrompt(comments) {
|
|
|
40
58
|
const prompts = comments.map((comment) => formatCommentPrompt(comment.file, comment.line, comment.body, comment.codeContent));
|
|
41
59
|
return prompts.join('\n=====\n');
|
|
42
60
|
}
|
|
43
|
-
export function
|
|
44
|
-
const
|
|
61
|
+
export function formatCommentThreadPrompt(thread) {
|
|
62
|
+
const sections = [`${thread.file || '<unknown file>'}:${getLineInfo(thread.line)}`];
|
|
63
|
+
thread.messages.forEach((message, index) => {
|
|
64
|
+
if (index === 0) {
|
|
65
|
+
sections.push(formatCommentContent(message.body, thread.codeContent));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const replyIndex = index;
|
|
69
|
+
const authorLabel = message.author?.trim() || 'Unknown';
|
|
70
|
+
sections.push(`Reply ${replyIndex} (${authorLabel})`);
|
|
71
|
+
sections.push(formatCommentContent(message.body, thread.codeContent));
|
|
72
|
+
});
|
|
73
|
+
return sections.filter(Boolean).join('\n');
|
|
74
|
+
}
|
|
75
|
+
export function formatAllCommentThreadsPrompt(threads) {
|
|
76
|
+
if (threads.length === 0)
|
|
77
|
+
return '';
|
|
78
|
+
return threads.map((thread) => formatCommentThreadPrompt(thread)).join('\n=====\n');
|
|
79
|
+
}
|
|
80
|
+
export function formatCommentsOutput(input) {
|
|
81
|
+
const threads = input.map((item) => ('messages' in item ? item : normalizeLegacyComment(item)));
|
|
82
|
+
const allPrompts = formatAllCommentThreadsPrompt(threads);
|
|
45
83
|
return [
|
|
46
84
|
'\n📝 Comments from review session:',
|
|
47
85
|
'='.repeat(50),
|
|
48
86
|
allPrompts,
|
|
49
87
|
'='.repeat(50),
|
|
50
|
-
`Total comments: ${
|
|
88
|
+
`Total comments: ${threads.length}\n`,
|
|
51
89
|
].join('\n');
|
|
52
90
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CommentImport, DiffCommentThread } from '../types/diff.js';
|
|
2
|
+
interface MergeCommentImportsResult {
|
|
3
|
+
threads: DiffCommentThread[];
|
|
4
|
+
warnings: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function parseCommentImportValue(value: string): CommentImport[];
|
|
7
|
+
export declare function serializeCommentImports(commentImports: CommentImport[]): string;
|
|
8
|
+
export declare function mergeCommentImports(existingThreads: DiffCommentThread[], commentImports: CommentImport[]): MergeCommentImportsResult;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null;
|
|
3
|
+
}
|
|
4
|
+
function isValidIsoTimestamp(value) {
|
|
5
|
+
return Number.isFinite(Date.parse(value));
|
|
6
|
+
}
|
|
7
|
+
function normalizeTimestamp(value, fieldName) {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
if (typeof value !== 'string' || value.trim().length === 0 || !isValidIsoTimestamp(value)) {
|
|
12
|
+
throw new Error(`Invalid comment import field: ${fieldName}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function normalizeLineRange(value) {
|
|
17
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (!isRecord(value)) {
|
|
21
|
+
throw new Error('Invalid comment import field: position.line');
|
|
22
|
+
}
|
|
23
|
+
const start = value.start;
|
|
24
|
+
const end = value.end;
|
|
25
|
+
if (typeof start !== 'number' ||
|
|
26
|
+
typeof end !== 'number' ||
|
|
27
|
+
!Number.isInteger(start) ||
|
|
28
|
+
!Number.isInteger(end) ||
|
|
29
|
+
start <= 0 ||
|
|
30
|
+
end <= 0 ||
|
|
31
|
+
start > end) {
|
|
32
|
+
throw new Error('Invalid comment import field: position.line');
|
|
33
|
+
}
|
|
34
|
+
return { start, end };
|
|
35
|
+
}
|
|
36
|
+
function normalizePosition(value) {
|
|
37
|
+
if (!isRecord(value)) {
|
|
38
|
+
throw new Error('Invalid comment import field: position');
|
|
39
|
+
}
|
|
40
|
+
if (value.side !== 'old' && value.side !== 'new') {
|
|
41
|
+
throw new Error('Invalid comment import field: position.side');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
side: value.side,
|
|
45
|
+
line: normalizeLineRange(value.line),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function normalizeCodeSnapshot(value) {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(value) || typeof value.content !== 'string') {
|
|
53
|
+
throw new Error('Invalid comment import field: codeSnapshot');
|
|
54
|
+
}
|
|
55
|
+
if (value.language !== undefined && typeof value.language !== 'string') {
|
|
56
|
+
throw new Error('Invalid comment import field: codeSnapshot.language');
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
content: value.content,
|
|
60
|
+
language: value.language,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function normalizeOptionalString(value, fieldName) {
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value !== 'string') {
|
|
68
|
+
throw new Error(`Invalid comment import field: ${fieldName}`);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function normalizeRequiredBody(value) {
|
|
73
|
+
if (typeof value !== 'string') {
|
|
74
|
+
throw new Error('Invalid comment import field: body');
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
if (trimmed.length === 0) {
|
|
78
|
+
throw new Error('Invalid comment import field: body');
|
|
79
|
+
}
|
|
80
|
+
return trimmed;
|
|
81
|
+
}
|
|
82
|
+
function normalizeCommentImportEntry(value) {
|
|
83
|
+
if (!isRecord(value)) {
|
|
84
|
+
throw new Error('Comment import must be an object');
|
|
85
|
+
}
|
|
86
|
+
if (value.type !== 'thread' && value.type !== 'reply') {
|
|
87
|
+
throw new Error('Invalid comment import field: type');
|
|
88
|
+
}
|
|
89
|
+
if (typeof value.filePath !== 'string' || value.filePath.trim().length === 0) {
|
|
90
|
+
throw new Error('Invalid comment import field: filePath');
|
|
91
|
+
}
|
|
92
|
+
const createdAt = normalizeTimestamp(value.createdAt, 'createdAt');
|
|
93
|
+
const updatedAt = normalizeTimestamp(value.updatedAt, 'updatedAt');
|
|
94
|
+
const normalized = {
|
|
95
|
+
type: value.type,
|
|
96
|
+
id: normalizeOptionalString(value.id, 'id'),
|
|
97
|
+
filePath: value.filePath,
|
|
98
|
+
position: normalizePosition(value.position),
|
|
99
|
+
body: normalizeRequiredBody(value.body),
|
|
100
|
+
author: normalizeOptionalString(value.author, 'author'),
|
|
101
|
+
createdAt,
|
|
102
|
+
updatedAt,
|
|
103
|
+
codeSnapshot: normalizeCodeSnapshot(value.codeSnapshot),
|
|
104
|
+
};
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
function normalizeCommentImports(input) {
|
|
108
|
+
if (Array.isArray(input)) {
|
|
109
|
+
return input.map((entry) => normalizeCommentImportEntry(entry));
|
|
110
|
+
}
|
|
111
|
+
return [normalizeCommentImportEntry(input)];
|
|
112
|
+
}
|
|
113
|
+
export function parseCommentImportValue(value) {
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(value);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new Error('Invalid --comment JSON');
|
|
120
|
+
}
|
|
121
|
+
return normalizeCommentImports(parsed);
|
|
122
|
+
}
|
|
123
|
+
function getPositionKey(position) {
|
|
124
|
+
if (typeof position.line === 'number') {
|
|
125
|
+
return `${position.side}:${position.line}`;
|
|
126
|
+
}
|
|
127
|
+
return `${position.side}:${position.line.start}-${position.line.end}`;
|
|
128
|
+
}
|
|
129
|
+
function positionsMatch(left, right) {
|
|
130
|
+
return getPositionKey(left) === getPositionKey(right);
|
|
131
|
+
}
|
|
132
|
+
function normalizeAuthor(author) {
|
|
133
|
+
return author?.trim() || '';
|
|
134
|
+
}
|
|
135
|
+
function messageMatchesImport(message, commentImport, includeId = true) {
|
|
136
|
+
if (includeId && commentImport.id && message.id === commentImport.id) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (normalizeAuthor(message.author) !== normalizeAuthor(commentImport.author)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (message.body !== commentImport.body) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (commentImport.createdAt && message.createdAt !== commentImport.createdAt) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
function rootThreadMatchesImport(thread, commentImport) {
|
|
151
|
+
if (commentImport.id && thread.id === commentImport.id) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (thread.filePath !== commentImport.filePath ||
|
|
155
|
+
!positionsMatch(thread.position, commentImport.position)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const rootMessage = thread.messages[0];
|
|
159
|
+
if (!rootMessage) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return messageMatchesImport(rootMessage, commentImport, true);
|
|
163
|
+
}
|
|
164
|
+
function toThreadTimestamp(commentImport, now) {
|
|
165
|
+
const createdAt = commentImport.createdAt ?? now;
|
|
166
|
+
const updatedAt = commentImport.updatedAt ?? createdAt;
|
|
167
|
+
return { createdAt, updatedAt };
|
|
168
|
+
}
|
|
169
|
+
function maxIsoTimestamp(left, right) {
|
|
170
|
+
return left.localeCompare(right) >= 0 ? left : right;
|
|
171
|
+
}
|
|
172
|
+
function sortByNewestThread(left, right) {
|
|
173
|
+
return right.updatedAt.localeCompare(left.updatedAt);
|
|
174
|
+
}
|
|
175
|
+
function createImportedThread(commentImport, now) {
|
|
176
|
+
const { createdAt, updatedAt } = toThreadTimestamp(commentImport, now);
|
|
177
|
+
const threadId = commentImport.id ?? crypto.randomUUID();
|
|
178
|
+
return {
|
|
179
|
+
id: threadId,
|
|
180
|
+
filePath: commentImport.filePath,
|
|
181
|
+
createdAt,
|
|
182
|
+
updatedAt,
|
|
183
|
+
position: commentImport.position,
|
|
184
|
+
codeSnapshot: commentImport.codeSnapshot,
|
|
185
|
+
messages: [
|
|
186
|
+
{
|
|
187
|
+
id: commentImport.id ?? threadId,
|
|
188
|
+
body: commentImport.body,
|
|
189
|
+
author: commentImport.author,
|
|
190
|
+
createdAt,
|
|
191
|
+
updatedAt,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function createImportedReply(commentImport, now) {
|
|
197
|
+
const { createdAt, updatedAt } = toThreadTimestamp(commentImport, now);
|
|
198
|
+
return {
|
|
199
|
+
id: commentImport.id ?? crypto.randomUUID(),
|
|
200
|
+
body: commentImport.body,
|
|
201
|
+
author: commentImport.author,
|
|
202
|
+
createdAt,
|
|
203
|
+
updatedAt,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function serializeLineRange(line) {
|
|
207
|
+
if (typeof line === 'number') {
|
|
208
|
+
return line;
|
|
209
|
+
}
|
|
210
|
+
return { start: line.start, end: line.end };
|
|
211
|
+
}
|
|
212
|
+
export function serializeCommentImports(commentImports) {
|
|
213
|
+
return JSON.stringify(commentImports.map((commentImport) => ({
|
|
214
|
+
type: commentImport.type,
|
|
215
|
+
id: commentImport.id,
|
|
216
|
+
filePath: commentImport.filePath,
|
|
217
|
+
position: {
|
|
218
|
+
side: commentImport.position.side,
|
|
219
|
+
line: serializeLineRange(commentImport.position.line),
|
|
220
|
+
},
|
|
221
|
+
body: commentImport.body,
|
|
222
|
+
author: commentImport.author,
|
|
223
|
+
createdAt: commentImport.createdAt,
|
|
224
|
+
updatedAt: commentImport.updatedAt,
|
|
225
|
+
codeSnapshot: commentImport.codeSnapshot
|
|
226
|
+
? {
|
|
227
|
+
content: commentImport.codeSnapshot.content,
|
|
228
|
+
language: commentImport.codeSnapshot.language,
|
|
229
|
+
}
|
|
230
|
+
: undefined,
|
|
231
|
+
})));
|
|
232
|
+
}
|
|
233
|
+
export function mergeCommentImports(existingThreads, commentImports) {
|
|
234
|
+
const threads = [...existingThreads];
|
|
235
|
+
const warnings = [];
|
|
236
|
+
for (const commentImport of commentImports) {
|
|
237
|
+
const now = new Date().toISOString();
|
|
238
|
+
if (commentImport.type === 'thread') {
|
|
239
|
+
const hasDuplicate = threads.some((thread) => rootThreadMatchesImport(thread, commentImport));
|
|
240
|
+
if (hasDuplicate) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
threads.push(createImportedThread(commentImport, now));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const targetThreads = threads
|
|
247
|
+
.filter((thread) => thread.filePath === commentImport.filePath &&
|
|
248
|
+
positionsMatch(thread.position, commentImport.position))
|
|
249
|
+
.sort(sortByNewestThread);
|
|
250
|
+
const targetThread = targetThreads[0];
|
|
251
|
+
if (!targetThread) {
|
|
252
|
+
warnings.push(`Skipped reply import for ${commentImport.filePath}:${getPositionKey(commentImport.position)} because no matching thread was found.`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const hasDuplicateReply = targetThread.messages.some((message) => messageMatchesImport(message, commentImport, true));
|
|
256
|
+
if (hasDuplicateReply) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const importedReply = createImportedReply(commentImport, now);
|
|
260
|
+
targetThread.messages = [...targetThread.messages, importedReply];
|
|
261
|
+
targetThread.updatedAt = maxIsoTimestamp(targetThread.updatedAt, importedReply.updatedAt);
|
|
262
|
+
}
|
|
263
|
+
return { threads, warnings };
|
|
264
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mergeCommentImports, parseCommentImportValue, serializeCommentImports, } from './commentImports';
|
|
3
|
+
function createThread({ id, filePath = 'src/example.ts', side = 'new', line = 10, body, updatedAt = '2024-01-01T00:00:00.000Z', }) {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
filePath,
|
|
7
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
8
|
+
updatedAt,
|
|
9
|
+
position: {
|
|
10
|
+
side,
|
|
11
|
+
line,
|
|
12
|
+
},
|
|
13
|
+
messages: [
|
|
14
|
+
{
|
|
15
|
+
id,
|
|
16
|
+
body,
|
|
17
|
+
author: 'User',
|
|
18
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
19
|
+
updatedAt,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
describe('commentImports', () => {
|
|
25
|
+
describe('parseCommentImportValue', () => {
|
|
26
|
+
it('parses a single thread import', () => {
|
|
27
|
+
const imports = parseCommentImportValue(JSON.stringify({
|
|
28
|
+
type: 'thread',
|
|
29
|
+
filePath: 'src/example.ts',
|
|
30
|
+
position: { side: 'new', line: 10 },
|
|
31
|
+
body: 'Review comment',
|
|
32
|
+
}));
|
|
33
|
+
expect(imports).toEqual([
|
|
34
|
+
{
|
|
35
|
+
type: 'thread',
|
|
36
|
+
filePath: 'src/example.ts',
|
|
37
|
+
position: { side: 'new', line: 10 },
|
|
38
|
+
body: 'Review comment',
|
|
39
|
+
id: undefined,
|
|
40
|
+
author: undefined,
|
|
41
|
+
createdAt: undefined,
|
|
42
|
+
updatedAt: undefined,
|
|
43
|
+
codeSnapshot: undefined,
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
it('parses an array of imports', () => {
|
|
48
|
+
const imports = parseCommentImportValue(JSON.stringify([
|
|
49
|
+
{
|
|
50
|
+
type: 'thread',
|
|
51
|
+
filePath: 'src/example.ts',
|
|
52
|
+
position: { side: 'new', line: 10 },
|
|
53
|
+
body: 'Root',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: 'reply',
|
|
57
|
+
filePath: 'src/example.ts',
|
|
58
|
+
position: { side: 'new', line: 10 },
|
|
59
|
+
body: 'Reply',
|
|
60
|
+
},
|
|
61
|
+
]));
|
|
62
|
+
expect(imports).toHaveLength(2);
|
|
63
|
+
expect(imports[0]?.type).toBe('thread');
|
|
64
|
+
expect(imports[1]?.type).toBe('reply');
|
|
65
|
+
});
|
|
66
|
+
it('rejects malformed json', () => {
|
|
67
|
+
expect(() => parseCommentImportValue('{')).toThrow('Invalid --comment JSON');
|
|
68
|
+
});
|
|
69
|
+
it('rejects invalid import shape', () => {
|
|
70
|
+
expect(() => parseCommentImportValue(JSON.stringify({
|
|
71
|
+
type: 'thread',
|
|
72
|
+
filePath: '',
|
|
73
|
+
position: { side: 'new', line: 0 },
|
|
74
|
+
body: '',
|
|
75
|
+
}))).toThrow('Invalid comment import field: filePath');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('serializeCommentImports', () => {
|
|
79
|
+
it('creates a stable payload string for hashing', () => {
|
|
80
|
+
const commentImports = [
|
|
81
|
+
{
|
|
82
|
+
type: 'thread',
|
|
83
|
+
id: 'thread-1',
|
|
84
|
+
filePath: 'src/example.ts',
|
|
85
|
+
position: { side: 'new', line: { start: 10, end: 12 } },
|
|
86
|
+
body: 'Root',
|
|
87
|
+
author: 'AI',
|
|
88
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
89
|
+
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
90
|
+
codeSnapshot: { content: 'const value = 1;' },
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
expect(serializeCommentImports(commentImports)).toBe(JSON.stringify([
|
|
94
|
+
{
|
|
95
|
+
type: 'thread',
|
|
96
|
+
id: 'thread-1',
|
|
97
|
+
filePath: 'src/example.ts',
|
|
98
|
+
position: { side: 'new', line: { start: 10, end: 12 } },
|
|
99
|
+
body: 'Root',
|
|
100
|
+
author: 'AI',
|
|
101
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
102
|
+
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
103
|
+
codeSnapshot: { content: 'const value = 1;', language: undefined },
|
|
104
|
+
},
|
|
105
|
+
]));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('mergeCommentImports', () => {
|
|
109
|
+
it('adds a new thread import', () => {
|
|
110
|
+
vi.useFakeTimers();
|
|
111
|
+
vi.setSystemTime(new Date('2024-02-01T00:00:00.000Z'));
|
|
112
|
+
const result = mergeCommentImports([], [
|
|
113
|
+
{
|
|
114
|
+
type: 'thread',
|
|
115
|
+
filePath: 'src/example.ts',
|
|
116
|
+
position: { side: 'new', line: 10 },
|
|
117
|
+
body: 'Imported thread',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
expect(result.warnings).toEqual([]);
|
|
121
|
+
expect(result.threads).toHaveLength(1);
|
|
122
|
+
expect(result.threads[0]?.messages[0]?.body).toBe('Imported thread');
|
|
123
|
+
vi.useRealTimers();
|
|
124
|
+
});
|
|
125
|
+
it('skips a duplicate thread import with the same root message', () => {
|
|
126
|
+
const existing = [createThread({ id: 'thread-1', body: 'Imported thread' })];
|
|
127
|
+
const result = mergeCommentImports(existing, [
|
|
128
|
+
{
|
|
129
|
+
type: 'thread',
|
|
130
|
+
filePath: 'src/example.ts',
|
|
131
|
+
position: { side: 'new', line: 10 },
|
|
132
|
+
body: 'Imported thread',
|
|
133
|
+
author: 'User',
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
expect(result.threads).toHaveLength(1);
|
|
137
|
+
});
|
|
138
|
+
it('adds a reply to the newest matching thread', () => {
|
|
139
|
+
const olderThread = createThread({
|
|
140
|
+
id: 'thread-1',
|
|
141
|
+
body: 'Root 1',
|
|
142
|
+
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
143
|
+
});
|
|
144
|
+
const newerThread = createThread({
|
|
145
|
+
id: 'thread-2',
|
|
146
|
+
body: 'Root 2',
|
|
147
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
148
|
+
});
|
|
149
|
+
const result = mergeCommentImports([olderThread, newerThread], [
|
|
150
|
+
{
|
|
151
|
+
type: 'reply',
|
|
152
|
+
filePath: 'src/example.ts',
|
|
153
|
+
position: { side: 'new', line: 10 },
|
|
154
|
+
body: 'Imported reply',
|
|
155
|
+
author: 'AI',
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
expect(result.threads[0]?.messages).toHaveLength(1);
|
|
159
|
+
expect(result.threads[1]?.messages).toHaveLength(2);
|
|
160
|
+
expect(result.threads[1]?.messages[1]?.body).toBe('Imported reply');
|
|
161
|
+
});
|
|
162
|
+
it('skips a duplicate reply import', () => {
|
|
163
|
+
const existing = createThread({ id: 'thread-1', body: 'Root' });
|
|
164
|
+
existing.messages.push({
|
|
165
|
+
id: 'reply-1',
|
|
166
|
+
body: 'Imported reply',
|
|
167
|
+
author: 'AI',
|
|
168
|
+
createdAt: '2024-01-02T00:00:00.000Z',
|
|
169
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
170
|
+
});
|
|
171
|
+
const result = mergeCommentImports([existing], [
|
|
172
|
+
{
|
|
173
|
+
type: 'reply',
|
|
174
|
+
id: 'reply-1',
|
|
175
|
+
filePath: 'src/example.ts',
|
|
176
|
+
position: { side: 'new', line: 10 },
|
|
177
|
+
body: 'Imported reply',
|
|
178
|
+
author: 'AI',
|
|
179
|
+
},
|
|
180
|
+
]);
|
|
181
|
+
expect(result.threads[0]?.messages).toHaveLength(2);
|
|
182
|
+
});
|
|
183
|
+
it('warns and skips reply import when no matching thread exists', () => {
|
|
184
|
+
const result = mergeCommentImports([], [
|
|
185
|
+
{
|
|
186
|
+
type: 'reply',
|
|
187
|
+
filePath: 'src/example.ts',
|
|
188
|
+
position: { side: 'new', line: 10 },
|
|
189
|
+
body: 'Imported reply',
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
expect(result.threads).toEqual([]);
|
|
193
|
+
expect(result.warnings).toHaveLength(1);
|
|
194
|
+
expect(result.warnings[0]).toContain('Skipped reply import');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{V as a,W as n}from"./mermaid.core-DTPtVBG7.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-DwKPHpbL.js";import{_ as i}from"./mermaid.core-DTPtVBG7.js";import"./chunk-FMBD7UC4-C0eJ7JsI.js";import"./chunk-JSJVCQXG-QZotPSqo.js";import"./chunk-55IACEB6-PRBuiJg9.js";import"./chunk-KX2RTZJC-B8du3tt8.js";import"./index-mE8CA51x.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-DwKPHpbL.js";import{_ as i}from"./mermaid.core-DTPtVBG7.js";import"./chunk-FMBD7UC4-C0eJ7JsI.js";import"./chunk-JSJVCQXG-QZotPSqo.js";import"./chunk-55IACEB6-PRBuiJg9.js";import"./chunk-KX2RTZJC-B8du3tt8.js";import"./index-mE8CA51x.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as r}from"./_baseUniq-DivSZEOF.js";var e=4;function a(o){return r(o,e)}export{a as c};
|