@atlassian-dc-mcp/bitbucket 0.25.0 → 0.26.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/CHANGELOG.md +11 -0
- package/build/__tests__/bitbucket-service.test.js +129 -16
- package/build/__tests__/bitbucket-service.test.js.map +1 -1
- package/build/bitbucket-response-mapper.js +3 -3
- package/build/bitbucket-response-mapper.js.map +1 -1
- package/build/bitbucket-service.d.ts +30 -1
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +116 -22
- package/build/bitbucket-service.js.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/build/pr-comment-mapper.d.ts +2 -0
- package/build/pr-comment-mapper.d.ts.map +1 -1
- package/build/pr-comment-mapper.js +7 -1
- package/build/pr-comment-mapper.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/bitbucket-service.test.ts +187 -16
- package/src/bitbucket-response-mapper.ts +3 -3
- package/src/bitbucket-service.ts +138 -24
- package/src/index.ts +10 -0
- package/src/pr-comment-mapper.ts +13 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -861,10 +861,8 @@ describe('BitbucketService', () => {
|
|
|
861
861
|
line: 15,
|
|
862
862
|
lineType: 'ADDED',
|
|
863
863
|
fileType: 'TO',
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
multilineStartLineType: 'ADDED',
|
|
867
|
-
multilineDestinationRange: { minimum: 10, maximum: 15 }
|
|
864
|
+
multilineMarker: { startLine: 10, startLineType: 'ADDED' },
|
|
865
|
+
multilineSpan: { dstSpanStart: 10, dstSpanEnd: 15 }
|
|
868
866
|
}
|
|
869
867
|
};
|
|
870
868
|
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
@@ -906,10 +904,8 @@ describe('BitbucketService', () => {
|
|
|
906
904
|
line: 15,
|
|
907
905
|
lineType: 'ADDED',
|
|
908
906
|
fileType: 'TO',
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
multilineStartLineType: 'ADDED',
|
|
912
|
-
multilineDestinationRange: { minimum: 10, maximum: 15 }
|
|
907
|
+
multilineMarker: { startLine: 10, startLineType: 'ADDED' },
|
|
908
|
+
multilineSpan: { dstSpanStart: 10, dstSpanEnd: 15 }
|
|
913
909
|
}
|
|
914
910
|
}
|
|
915
911
|
);
|
|
@@ -926,10 +922,8 @@ describe('BitbucketService', () => {
|
|
|
926
922
|
line: 8,
|
|
927
923
|
lineType: 'ADDED',
|
|
928
924
|
fileType: 'TO',
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
multilineStartLineType: 'CONTEXT',
|
|
932
|
-
multilineDestinationRange: { minimum: 5, maximum: 8 }
|
|
925
|
+
multilineMarker: { startLine: 5, startLineType: 'CONTEXT' },
|
|
926
|
+
multilineSpan: { dstSpanStart: 5, dstSpanEnd: 8 }
|
|
933
927
|
}
|
|
934
928
|
};
|
|
935
929
|
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
@@ -959,15 +953,133 @@ describe('BitbucketService', () => {
|
|
|
959
953
|
line: 8,
|
|
960
954
|
lineType: 'ADDED',
|
|
961
955
|
fileType: 'TO',
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
multilineStartLineType: 'CONTEXT',
|
|
965
|
-
multilineDestinationRange: { minimum: 5, maximum: 8 }
|
|
956
|
+
multilineMarker: { startLine: 5, startLineType: 'CONTEXT' },
|
|
957
|
+
multilineSpan: { dstSpanStart: 5, dstSpanEnd: 8 }
|
|
966
958
|
}
|
|
967
959
|
}
|
|
968
960
|
);
|
|
969
961
|
});
|
|
970
962
|
|
|
963
|
+
it('should normalize a reversed multiline range (startLine > line)', async () => {
|
|
964
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue({ id: 1, anchor: { path: 'src/test.js' } });
|
|
965
|
+
|
|
966
|
+
await bitbucketService.postPullRequestComment(
|
|
967
|
+
mockProjectKey,
|
|
968
|
+
mockRepositorySlug,
|
|
969
|
+
mockPullRequestId,
|
|
970
|
+
'Reversed range',
|
|
971
|
+
undefined, // parentId
|
|
972
|
+
'src/test.js', // filePath
|
|
973
|
+
15, // startLine (greater than line)
|
|
974
|
+
'ADDED', // startLineType
|
|
975
|
+
10, // line (end line, smaller)
|
|
976
|
+
'ADDED' // lineType
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
980
|
+
mockProjectKey,
|
|
981
|
+
mockPullRequestId,
|
|
982
|
+
mockRepositorySlug,
|
|
983
|
+
{
|
|
984
|
+
text: 'Reversed range',
|
|
985
|
+
anchor: {
|
|
986
|
+
path: 'src/test.js',
|
|
987
|
+
diffType: 'EFFECTIVE',
|
|
988
|
+
line: 15,
|
|
989
|
+
lineType: 'ADDED',
|
|
990
|
+
fileType: 'TO',
|
|
991
|
+
multilineMarker: { startLine: 10, startLineType: 'ADDED' },
|
|
992
|
+
multilineSpan: { dstSpanStart: 10, dstSpanEnd: 15 }
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it('should anchor a REMOVED multiline range to the source file', async () => {
|
|
999
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue({ id: 1, anchor: { path: 'src/test.js' } });
|
|
1000
|
+
|
|
1001
|
+
await bitbucketService.postPullRequestComment(
|
|
1002
|
+
mockProjectKey,
|
|
1003
|
+
mockRepositorySlug,
|
|
1004
|
+
mockPullRequestId,
|
|
1005
|
+
'Removed range',
|
|
1006
|
+
undefined, // parentId
|
|
1007
|
+
'src/test.js', // filePath
|
|
1008
|
+
2, // startLine
|
|
1009
|
+
'REMOVED', // startLineType
|
|
1010
|
+
5, // line (end line)
|
|
1011
|
+
'REMOVED' // lineType
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
1015
|
+
mockProjectKey,
|
|
1016
|
+
mockPullRequestId,
|
|
1017
|
+
mockRepositorySlug,
|
|
1018
|
+
{
|
|
1019
|
+
text: 'Removed range',
|
|
1020
|
+
anchor: {
|
|
1021
|
+
path: 'src/test.js',
|
|
1022
|
+
diffType: 'EFFECTIVE',
|
|
1023
|
+
line: 5,
|
|
1024
|
+
lineType: 'REMOVED',
|
|
1025
|
+
fileType: 'FROM',
|
|
1026
|
+
multilineMarker: { startLine: 2, startLineType: 'REMOVED' },
|
|
1027
|
+
multilineSpan: { srcSpanStart: 2, srcSpanEnd: 5 }
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('should warn when a multi-line suggestion is anchored to a single line', async () => {
|
|
1034
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue({
|
|
1035
|
+
id: 99,
|
|
1036
|
+
anchor: { path: 'src/test.js', line: 5, lineType: 'ADDED' }
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
1040
|
+
mockProjectKey,
|
|
1041
|
+
mockRepositorySlug,
|
|
1042
|
+
mockPullRequestId,
|
|
1043
|
+
'```suggestion\nconst a = 1;\nconst b = 2;\n```',
|
|
1044
|
+
undefined, // parentId
|
|
1045
|
+
'src/test.js', // filePath
|
|
1046
|
+
undefined, // startLine (single-line anchor — the bug shape)
|
|
1047
|
+
undefined, // startLineType
|
|
1048
|
+
5, // line
|
|
1049
|
+
'ADDED' // lineType
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
expect(result.success).toBe(true);
|
|
1053
|
+
expect((result.data as any).warning).toMatch(/multi-line ```suggestion/);
|
|
1054
|
+
// single-line anchor: no multiline fields sent
|
|
1055
|
+
const sentAnchor = (PullRequestsService.createComment2 as jest.Mock).mock.calls[0][3].anchor;
|
|
1056
|
+
expect(sentAnchor.multilineMarker).toBeUndefined();
|
|
1057
|
+
expect(sentAnchor.multilineSpan).toBeUndefined();
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it('should not warn when a multi-line suggestion has a proper multiline range', async () => {
|
|
1061
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue({
|
|
1062
|
+
id: 100,
|
|
1063
|
+
anchor: { path: 'src/test.js', line: 5, lineType: 'ADDED', multilineMarker: { startLine: 4, startLineType: 'ADDED' } }
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
1067
|
+
mockProjectKey,
|
|
1068
|
+
mockRepositorySlug,
|
|
1069
|
+
mockPullRequestId,
|
|
1070
|
+
'```suggestion\nconst a = 1;\nconst b = 2;\n```',
|
|
1071
|
+
undefined, // parentId
|
|
1072
|
+
'src/test.js', // filePath
|
|
1073
|
+
4, // startLine
|
|
1074
|
+
'ADDED', // startLineType
|
|
1075
|
+
5, // line
|
|
1076
|
+
'ADDED' // lineType
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
expect(result.success).toBe(true);
|
|
1080
|
+
expect((result.data as any).warning).toBeUndefined();
|
|
1081
|
+
});
|
|
1082
|
+
|
|
971
1083
|
it('should handle API errors gracefully', async () => {
|
|
972
1084
|
const mockError = new Error('API Error');
|
|
973
1085
|
(PullRequestsService.createComment2 as jest.Mock).mockRejectedValue(mockError);
|
|
@@ -2473,4 +2585,63 @@ describe('BitbucketService', () => {
|
|
|
2473
2585
|
);
|
|
2474
2586
|
});
|
|
2475
2587
|
});
|
|
2588
|
+
|
|
2589
|
+
describe('searchCode', () => {
|
|
2590
|
+
const { request: mockRequest } = require('../bitbucket-client/core/request.js');
|
|
2591
|
+
|
|
2592
|
+
it('should search code with the default page size', async () => {
|
|
2593
|
+
const mockData = { code: { count: 1, values: [{ repository: { slug: 'demo' } }] } };
|
|
2594
|
+
mockRequest.mockResolvedValue(mockData);
|
|
2595
|
+
|
|
2596
|
+
const result = await bitbucketService.searchCode('app');
|
|
2597
|
+
|
|
2598
|
+
expect(result.success).toBe(true);
|
|
2599
|
+
expect(result.data).toBe(mockData);
|
|
2600
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
2601
|
+
expect.any(Object),
|
|
2602
|
+
{
|
|
2603
|
+
method: 'POST',
|
|
2604
|
+
url: '/search/latest/search',
|
|
2605
|
+
body: {
|
|
2606
|
+
query: 'app',
|
|
2607
|
+
entities: { code: {} },
|
|
2608
|
+
limits: { primary: 25 }
|
|
2609
|
+
},
|
|
2610
|
+
mediaType: 'application/json',
|
|
2611
|
+
errors: {
|
|
2612
|
+
400: 'The search query was malformed.',
|
|
2613
|
+
401: 'The currently authenticated user is not permitted to search.',
|
|
2614
|
+
},
|
|
2615
|
+
}
|
|
2616
|
+
);
|
|
2617
|
+
});
|
|
2618
|
+
|
|
2619
|
+
it('should pass explicit primary and secondary limits', async () => {
|
|
2620
|
+
mockRequest.mockResolvedValue({ code: { count: 0, values: [] } });
|
|
2621
|
+
|
|
2622
|
+
await bitbucketService.searchCode('repo:demo TODO', 10, 5);
|
|
2623
|
+
|
|
2624
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
2625
|
+
expect.any(Object),
|
|
2626
|
+
expect.objectContaining({
|
|
2627
|
+
method: 'POST',
|
|
2628
|
+
url: '/search/latest/search',
|
|
2629
|
+
body: {
|
|
2630
|
+
query: 'repo:demo TODO',
|
|
2631
|
+
entities: { code: {} },
|
|
2632
|
+
limits: { primary: 10, secondary: 5 }
|
|
2633
|
+
},
|
|
2634
|
+
})
|
|
2635
|
+
);
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
it('should handle errors when searching', async () => {
|
|
2639
|
+
mockRequest.mockRejectedValue(new Error('API Error'));
|
|
2640
|
+
|
|
2641
|
+
const result = await bitbucketService.searchCode('app');
|
|
2642
|
+
|
|
2643
|
+
expect(result.success).toBe(false);
|
|
2644
|
+
expect(result.error).toBeDefined();
|
|
2645
|
+
});
|
|
2646
|
+
});
|
|
2476
2647
|
});
|
|
@@ -113,10 +113,10 @@ export function shapePullRequestCommentAck(comment: any): Record<string, any> {
|
|
|
113
113
|
path: comment.anchor.path,
|
|
114
114
|
...(comment.anchor.line !== undefined ? { line: comment.anchor.line } : {}),
|
|
115
115
|
...(typeof comment.anchor.lineType === 'string' ? { lineType: comment.anchor.lineType } : {}),
|
|
116
|
-
...(comment.anchor.
|
|
116
|
+
...(comment.anchor.multilineMarker
|
|
117
117
|
? {
|
|
118
|
-
startLine: comment.anchor.
|
|
119
|
-
startLineType: comment.anchor.
|
|
118
|
+
startLine: comment.anchor.multilineMarker.startLine,
|
|
119
|
+
startLineType: comment.anchor.multilineMarker.startLineType,
|
|
120
120
|
}
|
|
121
121
|
: {}),
|
|
122
122
|
},
|
package/src/bitbucket-service.ts
CHANGED
|
@@ -23,6 +23,84 @@ function resolveToken(token: string | (() => string | undefined), missingTokenMe
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
type DiffLineType = 'ADDED' | 'REMOVED' | 'CONTEXT';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build a Bitbucket DC inline comment anchor.
|
|
30
|
+
*
|
|
31
|
+
* For a multiline range, Bitbucket DC (>= 9.3.0) requires `multilineMarker` + `multilineSpan`.
|
|
32
|
+
* The legacy `multilineStartLine`/`multilineStartLineType`/`multilineAnchor`/`multilineDestinationRange`
|
|
33
|
+
* fields are silently ignored by the server, which then stores a single-line anchor — so a multiline
|
|
34
|
+
* ````suggestion` only replaces its first line on Apply. Field names verified against BB DC 9.3.2.
|
|
35
|
+
*/
|
|
36
|
+
function buildCommentAnchor(params: {
|
|
37
|
+
filePath: string;
|
|
38
|
+
line?: number;
|
|
39
|
+
lineType?: DiffLineType;
|
|
40
|
+
startLine?: number;
|
|
41
|
+
startLineType?: DiffLineType;
|
|
42
|
+
}): Record<string, any> {
|
|
43
|
+
const { filePath, line, lineType } = params;
|
|
44
|
+
const anchor: Record<string, any> = { path: filePath, diffType: 'EFFECTIVE' };
|
|
45
|
+
if (line === undefined || !lineType) {
|
|
46
|
+
return anchor;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (params.startLine === undefined) {
|
|
50
|
+
anchor.line = line;
|
|
51
|
+
anchor.lineType = lineType;
|
|
52
|
+
anchor.fileType = lineType === 'REMOVED' ? 'FROM' : 'TO';
|
|
53
|
+
return anchor;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Normalize so the marker sits on the lower line and `line` on the upper line.
|
|
57
|
+
let startLine = params.startLine;
|
|
58
|
+
let startLineType: DiffLineType = params.startLineType ?? lineType;
|
|
59
|
+
let endLine = line;
|
|
60
|
+
let endLineType: DiffLineType = lineType;
|
|
61
|
+
if (startLine > endLine) {
|
|
62
|
+
[startLine, endLine] = [endLine, startLine];
|
|
63
|
+
[startLineType, endLineType] = [endLineType, startLineType];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fileType = endLineType === 'REMOVED' ? 'FROM' : 'TO';
|
|
67
|
+
anchor.line = endLine;
|
|
68
|
+
anchor.lineType = endLineType;
|
|
69
|
+
anchor.fileType = fileType;
|
|
70
|
+
anchor.multilineMarker = { startLine, startLineType };
|
|
71
|
+
anchor.multilineSpan =
|
|
72
|
+
fileType === 'FROM'
|
|
73
|
+
? { srcSpanStart: startLine, srcSpanEnd: endLine }
|
|
74
|
+
: { dstSpanStart: startLine, dstSpanEnd: endLine };
|
|
75
|
+
return anchor;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const SUGGESTION_BLOCK_RE = /```suggestion\b[^\n]*\n([\s\S]*?)```/;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Warn when a multi-line ````suggestion` block is anchored to a single line: Bitbucket only replaces
|
|
82
|
+
* the anchored line on Apply, leaving the rest (duplicated code). Returns undefined when there is no
|
|
83
|
+
* concern. Heuristic — a 1-line anchor with a multi-line suggestion is the common failure shape.
|
|
84
|
+
*/
|
|
85
|
+
function multilineSuggestionWarning(text: string, startLine?: number): string | undefined {
|
|
86
|
+
if (startLine !== undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const match = SUGGESTION_BLOCK_RE.exec(text);
|
|
90
|
+
if (!match) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const suggestionLineCount = match[1].replace(/\n$/, '').split('\n').length;
|
|
94
|
+
if (suggestionLineCount <= 1) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return (
|
|
98
|
+
'The comment contains a multi-line ```suggestion block but is anchored to a single line. ' +
|
|
99
|
+
'On Apply, Bitbucket replaces only that one line and leaves the rest. To replace multiple lines, ' +
|
|
100
|
+
'set startLine (first replaced line) and line (last replaced line) to span the whole range.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
26
104
|
export class BitbucketService {
|
|
27
105
|
private readonly getPageSize: () => number;
|
|
28
106
|
|
|
@@ -349,27 +427,11 @@ export class BitbucketService {
|
|
|
349
427
|
|
|
350
428
|
// Add anchor for file/line comments
|
|
351
429
|
if (filePath) {
|
|
352
|
-
comment.anchor = {
|
|
353
|
-
path: filePath,
|
|
354
|
-
diffType: 'EFFECTIVE'
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Add line-specific anchor properties
|
|
358
|
-
if (line !== undefined && lineType) {
|
|
359
|
-
comment.anchor.line = line;
|
|
360
|
-
comment.anchor.lineType = lineType;
|
|
361
|
-
comment.anchor.fileType = 'TO'; // Default to destination file
|
|
362
|
-
|
|
363
|
-
if (startLine !== undefined) {
|
|
364
|
-
const resolvedStartLineType = startLineType ?? lineType;
|
|
365
|
-
comment.anchor.multilineAnchor = true;
|
|
366
|
-
comment.anchor.multilineStartLine = startLine;
|
|
367
|
-
comment.anchor.multilineStartLineType = resolvedStartLineType;
|
|
368
|
-
comment.anchor.multilineDestinationRange = { minimum: startLine, maximum: line };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
430
|
+
comment.anchor = buildCommentAnchor({ filePath, line, lineType, startLine, startLineType });
|
|
371
431
|
}
|
|
372
432
|
|
|
433
|
+
const suggestionWarning = filePath ? multilineSuggestionWarning(text, startLine) : undefined;
|
|
434
|
+
|
|
373
435
|
const result = await handleApiOperation(
|
|
374
436
|
() => PullRequestsService.createComment2(
|
|
375
437
|
projectKey,
|
|
@@ -383,10 +445,17 @@ export class BitbucketService {
|
|
|
383
445
|
if (result.success && result.data && output !== 'full') {
|
|
384
446
|
return {
|
|
385
447
|
...result,
|
|
386
|
-
data:
|
|
448
|
+
data: {
|
|
449
|
+
...shapePullRequestCommentAck(result.data),
|
|
450
|
+
...(suggestionWarning ? { warning: suggestionWarning } : {}),
|
|
451
|
+
},
|
|
387
452
|
};
|
|
388
453
|
}
|
|
389
454
|
|
|
455
|
+
if (result.success && suggestionWarning) {
|
|
456
|
+
return { ...result, warning: suggestionWarning };
|
|
457
|
+
}
|
|
458
|
+
|
|
390
459
|
return result;
|
|
391
460
|
}
|
|
392
461
|
|
|
@@ -824,6 +893,46 @@ export class BitbucketService {
|
|
|
824
893
|
return result;
|
|
825
894
|
}
|
|
826
895
|
|
|
896
|
+
/**
|
|
897
|
+
* Search code across Bitbucket using the search REST module.
|
|
898
|
+
*
|
|
899
|
+
* The query string supports Bitbucket search modifiers, e.g. `project:TEST`,
|
|
900
|
+
* `repo:projectkey/repositoryslug` (e.g. `repo:TEST/demo`), `ext:js`, so scoping to a
|
|
901
|
+
* project or repository is done inside the query text. Note: `repo:` must include the
|
|
902
|
+
* project key (`repo:KEY/slug`); a bare `repo:slug` is rejected by the server.
|
|
903
|
+
*
|
|
904
|
+
* Note: the `/rest/search` endpoint is a separate Bitbucket REST module that is not part of
|
|
905
|
+
* the generated client, so this is issued via the low-level request helper (same approach as
|
|
906
|
+
* the dashboard/inbox endpoints).
|
|
907
|
+
*
|
|
908
|
+
* @param query The search query (may include modifiers like `repo:`, `project:`, `ext:`)
|
|
909
|
+
* @param limit Optional primary result limit (defaults to the package page size)
|
|
910
|
+
* @param secondaryLimit Optional secondary limit (number of hit contexts per match)
|
|
911
|
+
* @returns Promise with the code search results
|
|
912
|
+
*/
|
|
913
|
+
async searchCode(query: string, limit?: number, secondaryLimit?: number) {
|
|
914
|
+
return handleApiOperation(
|
|
915
|
+
() => __request(OpenAPI, {
|
|
916
|
+
method: 'POST',
|
|
917
|
+
url: '/search/latest/search',
|
|
918
|
+
body: {
|
|
919
|
+
query,
|
|
920
|
+
entities: { code: {} },
|
|
921
|
+
limits: {
|
|
922
|
+
primary: limit ?? this.getPageSize(),
|
|
923
|
+
...(secondaryLimit !== undefined ? { secondary: secondaryLimit } : {}),
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
mediaType: 'application/json',
|
|
927
|
+
errors: {
|
|
928
|
+
400: 'The search query was malformed.',
|
|
929
|
+
401: 'The currently authenticated user is not permitted to search.',
|
|
930
|
+
},
|
|
931
|
+
}),
|
|
932
|
+
'Error searching code'
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
|
|
827
936
|
async validateSetup(): Promise<void> {
|
|
828
937
|
await __request(OpenAPI, {
|
|
829
938
|
method: 'GET',
|
|
@@ -911,10 +1020,10 @@ export const bitbucketToolSchemas = {
|
|
|
911
1020
|
text: z.string().describe("The comment text"),
|
|
912
1021
|
parentId: z.number().optional().describe("Parent comment ID for replies"),
|
|
913
1022
|
filePath: z.string().optional().describe("File path for file-specific comments"),
|
|
914
|
-
startLine: z.number().optional().describe("
|
|
915
|
-
startLineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the start line of a multiline
|
|
916
|
-
line: z.number().optional().describe("
|
|
917
|
-
lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the end line
|
|
1023
|
+
startLine: z.number().optional().describe("First line of a multiline range. Provide together with 'line' (the last line) to span multiple lines. REQUIRED for a multi-line ```suggestion: the anchored range (startLine..line) is exactly what 'Apply suggestion' replaces — omit it and only the single 'line' is replaced, leaving the rest. Requires Bitbucket DC >= 9.3.0 for multiline suggestions."),
|
|
1024
|
+
startLineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the start line of a multiline range. Defaults to the same value as lineType if omitted."),
|
|
1025
|
+
line: z.number().optional().describe("Single-line comments: the line. Multiline: the LAST line of the range (use startLine for the first). For a ```suggestion this is the last replaced line."),
|
|
1026
|
+
lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for 'line' (the end line, or the only line for single-line comments). Use 'ADDED'/'CONTEXT' for the new/target file, 'REMOVED' for the original/source file."),
|
|
918
1027
|
pending: z.boolean().optional().describe("If true, creates a pending (draft) comment not visible to others until the review is submitted via bitbucket_submitPullRequestReview. Only works when filePath is provided — top-level PR comments (no filePath) are always posted live."),
|
|
919
1028
|
severity: z.enum(['NORMAL', 'BLOCKER']).optional().describe("Comment severity. Use 'BLOCKER' to post the comment as a task that must be resolved before the PR can be merged. Defaults to 'NORMAL' (regular comment)."),
|
|
920
1029
|
output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
|
|
@@ -995,5 +1104,10 @@ export const bitbucketToolSchemas = {
|
|
|
995
1104
|
getInboxPullRequests: {
|
|
996
1105
|
start: z.number().optional().describe("Start number for the page (inclusive). If not passed, first page is assumed"),
|
|
997
1106
|
limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
|
|
1107
|
+
},
|
|
1108
|
+
searchCode: {
|
|
1109
|
+
query: z.string().describe("The search query. Supports Bitbucket search modifiers, e.g. 'project:TEST authenticate', 'repo:TEST/demo TODO' (the repo modifier must be 'repo:projectkey/repositoryslug'), 'ext:ts useState'. Scope to a project or repository inside the query text."),
|
|
1110
|
+
limit: z.number().optional().describe("Maximum number of matching files to return. If not passed, the package default page size is used."),
|
|
1111
|
+
secondaryLimit: z.number().optional().describe("Maximum number of hit contexts (matching code snippets) to return per file")
|
|
998
1112
|
}
|
|
999
1113
|
};
|
package/src/index.ts
CHANGED
|
@@ -218,4 +218,14 @@ server.tool(
|
|
|
218
218
|
}
|
|
219
219
|
);
|
|
220
220
|
|
|
221
|
+
server.tool(
|
|
222
|
+
"bitbucket_searchCode",
|
|
223
|
+
"Search code across Bitbucket. The query supports search modifiers like 'project:<key>', 'repo:<key>/<slug>', and 'ext:<extension>' to scope or filter results (e.g. 'project:TEST authenticate', 'repo:TEST/demo ext:js TODO'). NOTE: the 'repo:' modifier requires the project key — 'repo:projectkey/repositoryslug', not a bare slug. Returns matching files with hit contexts (snippets).",
|
|
224
|
+
bitbucketToolSchemas.searchCode,
|
|
225
|
+
async ({ query, limit, secondaryLimit }) => {
|
|
226
|
+
const result = await bitbucketService.searchCode(query, limit, secondaryLimit);
|
|
227
|
+
return formatToolResponse(result);
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
221
231
|
await connectServer(server);
|
package/src/pr-comment-mapper.ts
CHANGED
|
@@ -24,6 +24,10 @@ interface CommentAnchor {
|
|
|
24
24
|
path: string;
|
|
25
25
|
diffType: string;
|
|
26
26
|
orphaned: boolean;
|
|
27
|
+
multilineMarker?: {
|
|
28
|
+
startLine: number;
|
|
29
|
+
startLineType: string;
|
|
30
|
+
};
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
interface CommentProperties {
|
|
@@ -131,6 +135,8 @@ interface SimplifiedAnchor {
|
|
|
131
135
|
line: number;
|
|
132
136
|
path: string;
|
|
133
137
|
fileType?: string;
|
|
138
|
+
startLine?: number;
|
|
139
|
+
startLineType?: string;
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
interface SimplifiedComment {
|
|
@@ -240,7 +246,13 @@ function simplifyAnchor(anchor: CommentAnchor): SimplifiedAnchor {
|
|
|
240
246
|
return {
|
|
241
247
|
line: anchor.line,
|
|
242
248
|
path: anchor.path,
|
|
243
|
-
fileType: anchor.fileType
|
|
249
|
+
fileType: anchor.fileType,
|
|
250
|
+
...(anchor.multilineMarker
|
|
251
|
+
? {
|
|
252
|
+
startLine: anchor.multilineMarker.startLine,
|
|
253
|
+
startLineType: anchor.multilineMarker.startLineType
|
|
254
|
+
}
|
|
255
|
+
: {})
|
|
244
256
|
};
|
|
245
257
|
}
|
|
246
258
|
|