issue-scribe-mcp 1.2.0 → 1.3.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/.github/workflows/ci.yml +29 -0
- package/README.md +110 -5
- package/README_EN.md +108 -3
- package/dist/index.js +7 -1689
- package/dist/lib/env.js +9 -0
- package/dist/lib/errors.js +46 -0
- package/dist/lib/octokit.js +13 -0
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/response.js +32 -0
- package/dist/lib/safety.js +12 -0
- package/dist/lib/search.js +32 -0
- package/dist/lib/version.js +14 -0
- package/dist/tools/branches.js +249 -0
- package/dist/tools/comments.js +233 -0
- package/dist/tools/index.js +29 -0
- package/dist/tools/issues.js +393 -0
- package/dist/tools/labels.js +204 -0
- package/dist/tools/pull-requests.js +724 -0
- package/dist/tools/types.js +1 -0
- package/package.json +3 -3
- package/src/index.ts +7 -1869
- package/src/lib/env.ts +15 -0
- package/src/lib/errors.ts +64 -0
- package/src/lib/octokit.ts +17 -0
- package/src/lib/pagination.ts +72 -0
- package/src/lib/response.ts +42 -0
- package/src/lib/safety.ts +25 -0
- package/src/lib/search.ts +52 -0
- package/src/lib/version.ts +19 -0
- package/src/tools/branches.ts +287 -0
- package/src/tools/comments.ts +272 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/issues.ts +452 -0
- package/src/tools/labels.ts +239 -0
- package/src/tools/pull-requests.ts +838 -0
- package/src/tools/types.ts +18 -0
- package/test/safety.test.mjs +28 -0
- package/test/search.test.mjs +28 -0
- package/test/version-and-metadata.test.mjs +32 -0
- package/test-local.sh +1 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { getOctokit } from "../lib/octokit.js";
|
|
4
|
+
import { collectPaginated, PaginationSchema, resolvePagination } from "../lib/pagination.js";
|
|
5
|
+
import { executeTool } from "../lib/response.js";
|
|
6
|
+
import { buildRepositorySearchQuery, normalizeSearchSort } from "../lib/search.js";
|
|
7
|
+
import type { ToolRegistration } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const IssueContextSchema = z.object({
|
|
10
|
+
owner: z.string(),
|
|
11
|
+
repo: z.string(),
|
|
12
|
+
issue_number: z.number(),
|
|
13
|
+
comments_page: z.number().int().min(1).optional(),
|
|
14
|
+
comments_per_page: z.number().int().min(1).max(100).optional(),
|
|
15
|
+
comments_fetch_all: z.boolean().optional(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const CreateIssueSchema = z.object({
|
|
19
|
+
owner: z.string(),
|
|
20
|
+
repo: z.string(),
|
|
21
|
+
title: z.string(),
|
|
22
|
+
body: z.string().optional(),
|
|
23
|
+
labels: z.array(z.string()).optional(),
|
|
24
|
+
assignees: z.array(z.string()).optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const UpdateIssueSchema = z.object({
|
|
28
|
+
owner: z.string(),
|
|
29
|
+
repo: z.string(),
|
|
30
|
+
issue_number: z.number(),
|
|
31
|
+
title: z.string().optional(),
|
|
32
|
+
body: z.string().optional(),
|
|
33
|
+
state: z.enum(["open", "closed"]).optional(),
|
|
34
|
+
labels: z.array(z.string()).optional(),
|
|
35
|
+
assignees: z.array(z.string()).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const SearchIssuesSchema = z.object({
|
|
39
|
+
owner: z.string(),
|
|
40
|
+
repo: z.string(),
|
|
41
|
+
query: z.string().optional(),
|
|
42
|
+
state: z.enum(["open", "closed", "all"]).optional(),
|
|
43
|
+
labels: z.array(z.string()).optional(),
|
|
44
|
+
qualifiers: z.array(z.string()).optional(),
|
|
45
|
+
sort: z.enum(["created", "updated", "comments", "best-match"]).optional(),
|
|
46
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
47
|
+
}).merge(PaginationSchema);
|
|
48
|
+
|
|
49
|
+
const ListRecentIssuesSchema = z.object({
|
|
50
|
+
owner: z.string(),
|
|
51
|
+
repo: z.string(),
|
|
52
|
+
state: z.enum(["open", "closed", "all"]).optional(),
|
|
53
|
+
sort: z.enum(["created", "updated", "comments"]).optional(),
|
|
54
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
55
|
+
}).merge(PaginationSchema);
|
|
56
|
+
|
|
57
|
+
async function searchIssuesWithPagination(args: z.infer<typeof SearchIssuesSchema>) {
|
|
58
|
+
const pagination = resolvePagination(args, {
|
|
59
|
+
page: 1,
|
|
60
|
+
perPage: 30,
|
|
61
|
+
fetchAll: false,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const q = buildRepositorySearchQuery({
|
|
65
|
+
owner: args.owner,
|
|
66
|
+
repo: args.repo,
|
|
67
|
+
kind: "issue",
|
|
68
|
+
state: args.state,
|
|
69
|
+
query: args.query,
|
|
70
|
+
labels: args.labels,
|
|
71
|
+
qualifiers: args.qualifiers,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const sort = normalizeSearchSort(args.sort);
|
|
75
|
+
const order = args.direction;
|
|
76
|
+
|
|
77
|
+
if (!pagination.fetchAll) {
|
|
78
|
+
const response = await getOctokit().rest.search.issuesAndPullRequests({
|
|
79
|
+
q,
|
|
80
|
+
sort: sort as any,
|
|
81
|
+
order: order as any,
|
|
82
|
+
page: pagination.page,
|
|
83
|
+
per_page: pagination.perPage,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const issues = response.data.items.filter((item) => !("pull_request" in item));
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
query: q,
|
|
90
|
+
total_count: response.data.total_count,
|
|
91
|
+
incomplete_results: response.data.incomplete_results,
|
|
92
|
+
page: pagination.page,
|
|
93
|
+
per_page: pagination.perPage,
|
|
94
|
+
fetch_all: false,
|
|
95
|
+
count: issues.length,
|
|
96
|
+
issues: issues.map((issue) => ({
|
|
97
|
+
number: issue.number,
|
|
98
|
+
title: issue.title,
|
|
99
|
+
state: issue.state,
|
|
100
|
+
user: issue.user?.login,
|
|
101
|
+
labels: issue.labels,
|
|
102
|
+
comments: issue.comments,
|
|
103
|
+
created_at: issue.created_at,
|
|
104
|
+
updated_at: issue.updated_at,
|
|
105
|
+
html_url: issue.html_url,
|
|
106
|
+
score: issue.score,
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allItems: any[] = [];
|
|
112
|
+
let page = pagination.page;
|
|
113
|
+
let totalCount = 0;
|
|
114
|
+
let incompleteResults = false;
|
|
115
|
+
|
|
116
|
+
while (true) {
|
|
117
|
+
const response = await getOctokit().rest.search.issuesAndPullRequests({
|
|
118
|
+
q,
|
|
119
|
+
sort: sort as any,
|
|
120
|
+
order: order as any,
|
|
121
|
+
page,
|
|
122
|
+
per_page: pagination.perPage,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (page === pagination.page) {
|
|
126
|
+
totalCount = response.data.total_count;
|
|
127
|
+
incompleteResults = response.data.incomplete_results;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
allItems.push(...response.data.items);
|
|
131
|
+
|
|
132
|
+
if (response.data.items.length < pagination.perPage) {
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (allItems.length >= 1000) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
page += 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const issues = allItems.filter((item) => !("pull_request" in item));
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
query: q,
|
|
147
|
+
total_count: totalCount,
|
|
148
|
+
incomplete_results: incompleteResults,
|
|
149
|
+
page: pagination.page,
|
|
150
|
+
per_page: pagination.perPage,
|
|
151
|
+
fetch_all: true,
|
|
152
|
+
pages_fetched: page - pagination.page + 1,
|
|
153
|
+
count: issues.length,
|
|
154
|
+
issues: issues.map((issue) => ({
|
|
155
|
+
number: issue.number,
|
|
156
|
+
title: issue.title,
|
|
157
|
+
state: issue.state,
|
|
158
|
+
user: issue.user?.login,
|
|
159
|
+
labels: issue.labels,
|
|
160
|
+
comments: issue.comments,
|
|
161
|
+
created_at: issue.created_at,
|
|
162
|
+
updated_at: issue.updated_at,
|
|
163
|
+
html_url: issue.html_url,
|
|
164
|
+
score: issue.score,
|
|
165
|
+
})),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const issueTools: ToolRegistration[] = [
|
|
170
|
+
{
|
|
171
|
+
definition: {
|
|
172
|
+
name: "github_get_issue_context",
|
|
173
|
+
description: "Get GitHub Issue context including metadata, reactions, and paginated comments",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
owner: { type: "string", description: "Repository owner" },
|
|
178
|
+
repo: { type: "string", description: "Repository name" },
|
|
179
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
180
|
+
comments_page: { type: "number", description: "Comments page number (optional, default: 1)" },
|
|
181
|
+
comments_per_page: { type: "number", description: "Comments per page, max 100 (optional, default: 100)" },
|
|
182
|
+
comments_fetch_all: { type: "boolean", description: "Fetch all comment pages (optional, default: true)" },
|
|
183
|
+
},
|
|
184
|
+
required: ["owner", "repo", "issue_number"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
handler: async (rawArgs) => executeTool(
|
|
188
|
+
rawArgs,
|
|
189
|
+
IssueContextSchema,
|
|
190
|
+
"Failed to fetch issue context",
|
|
191
|
+
async (args) => {
|
|
192
|
+
const issue = await getOctokit().rest.issues.get({
|
|
193
|
+
owner: args.owner,
|
|
194
|
+
repo: args.repo,
|
|
195
|
+
issue_number: args.issue_number,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const commentPagination = resolvePagination(
|
|
199
|
+
{
|
|
200
|
+
page: args.comments_page,
|
|
201
|
+
per_page: args.comments_per_page,
|
|
202
|
+
fetch_all: args.comments_fetch_all,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
page: 1,
|
|
206
|
+
perPage: 100,
|
|
207
|
+
fetchAll: true,
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const commentsResult = await collectPaginated(commentPagination, async (page, perPage) => {
|
|
212
|
+
const comments = await getOctokit().rest.issues.listComments({
|
|
213
|
+
owner: args.owner,
|
|
214
|
+
repo: args.repo,
|
|
215
|
+
issue_number: args.issue_number,
|
|
216
|
+
page,
|
|
217
|
+
per_page: perPage,
|
|
218
|
+
});
|
|
219
|
+
return comments.data;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
issue: {
|
|
224
|
+
number: issue.data.number,
|
|
225
|
+
title: issue.data.title,
|
|
226
|
+
body: issue.data.body,
|
|
227
|
+
state: issue.data.state,
|
|
228
|
+
user: issue.data.user?.login,
|
|
229
|
+
assignees: issue.data.assignees?.map((assignee) => assignee.login),
|
|
230
|
+
milestone: issue.data.milestone
|
|
231
|
+
? {
|
|
232
|
+
title: issue.data.milestone.title,
|
|
233
|
+
due_on: issue.data.milestone.due_on,
|
|
234
|
+
}
|
|
235
|
+
: null,
|
|
236
|
+
labels: issue.data.labels.map((label) => (typeof label === "string" ? label : label.name)),
|
|
237
|
+
reactions: issue.data.reactions,
|
|
238
|
+
created_at: issue.data.created_at,
|
|
239
|
+
updated_at: issue.data.updated_at,
|
|
240
|
+
html_url: issue.data.html_url,
|
|
241
|
+
},
|
|
242
|
+
comments: commentsResult.items.map((comment) => ({
|
|
243
|
+
id: comment.id,
|
|
244
|
+
user: comment.user?.login,
|
|
245
|
+
body: comment.body,
|
|
246
|
+
reactions: comment.reactions,
|
|
247
|
+
created_at: comment.created_at,
|
|
248
|
+
updated_at: comment.updated_at,
|
|
249
|
+
html_url: comment.html_url,
|
|
250
|
+
})),
|
|
251
|
+
comments_pagination: {
|
|
252
|
+
page: commentsResult.page,
|
|
253
|
+
per_page: commentsResult.per_page,
|
|
254
|
+
fetch_all: commentsResult.fetch_all,
|
|
255
|
+
pages_fetched: commentsResult.pages_fetched,
|
|
256
|
+
count: commentsResult.items.length,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
),
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
definition: {
|
|
264
|
+
name: "github_create_issue",
|
|
265
|
+
description: "Create a new GitHub issue",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
owner: { type: "string", description: "Repository owner" },
|
|
270
|
+
repo: { type: "string", description: "Repository name" },
|
|
271
|
+
title: { type: "string", description: "Issue title" },
|
|
272
|
+
body: { type: "string", description: "Issue body (optional)" },
|
|
273
|
+
labels: { type: "array", items: { type: "string" }, description: "Labels (optional)" },
|
|
274
|
+
assignees: { type: "array", items: { type: "string" }, description: "Assignees (optional)" },
|
|
275
|
+
},
|
|
276
|
+
required: ["owner", "repo", "title"],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
handler: async (rawArgs) => executeTool(
|
|
280
|
+
rawArgs,
|
|
281
|
+
CreateIssueSchema,
|
|
282
|
+
"Failed to create issue",
|
|
283
|
+
async (args) => {
|
|
284
|
+
const issue = await getOctokit().rest.issues.create({
|
|
285
|
+
owner: args.owner,
|
|
286
|
+
repo: args.repo,
|
|
287
|
+
title: args.title,
|
|
288
|
+
body: args.body,
|
|
289
|
+
labels: args.labels,
|
|
290
|
+
assignees: args.assignees,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
issue: {
|
|
296
|
+
number: issue.data.number,
|
|
297
|
+
title: issue.data.title,
|
|
298
|
+
state: issue.data.state,
|
|
299
|
+
html_url: issue.data.html_url,
|
|
300
|
+
created_at: issue.data.created_at,
|
|
301
|
+
},
|
|
302
|
+
message: `Issue #${issue.data.number} created successfully`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
definition: {
|
|
309
|
+
name: "github_update_issue",
|
|
310
|
+
description: "Update an existing GitHub issue",
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
owner: { type: "string", description: "Repository owner" },
|
|
315
|
+
repo: { type: "string", description: "Repository name" },
|
|
316
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
317
|
+
title: { type: "string", description: "New title (optional)" },
|
|
318
|
+
body: { type: "string", description: "New body (optional)" },
|
|
319
|
+
state: { type: "string", enum: ["open", "closed"], description: "Issue state (optional)" },
|
|
320
|
+
labels: { type: "array", items: { type: "string" }, description: "New labels (optional)" },
|
|
321
|
+
assignees: { type: "array", items: { type: "string" }, description: "New assignees (optional)" },
|
|
322
|
+
},
|
|
323
|
+
required: ["owner", "repo", "issue_number"],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
handler: async (rawArgs) => executeTool(
|
|
327
|
+
rawArgs,
|
|
328
|
+
UpdateIssueSchema,
|
|
329
|
+
"Failed to update issue",
|
|
330
|
+
async (args) => {
|
|
331
|
+
const issue = await getOctokit().rest.issues.update({
|
|
332
|
+
owner: args.owner,
|
|
333
|
+
repo: args.repo,
|
|
334
|
+
issue_number: args.issue_number,
|
|
335
|
+
title: args.title,
|
|
336
|
+
body: args.body,
|
|
337
|
+
state: args.state,
|
|
338
|
+
labels: args.labels,
|
|
339
|
+
assignees: args.assignees,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
success: true,
|
|
344
|
+
issue: {
|
|
345
|
+
number: issue.data.number,
|
|
346
|
+
title: issue.data.title,
|
|
347
|
+
state: issue.data.state,
|
|
348
|
+
html_url: issue.data.html_url,
|
|
349
|
+
updated_at: issue.data.updated_at,
|
|
350
|
+
},
|
|
351
|
+
message: `Issue #${issue.data.number} updated successfully`,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
),
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
definition: {
|
|
358
|
+
name: "github_search_issues",
|
|
359
|
+
description: "Search repository issues using GitHub Search API and optional qualifiers",
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: "object",
|
|
362
|
+
properties: {
|
|
363
|
+
owner: { type: "string", description: "Repository owner" },
|
|
364
|
+
repo: { type: "string", description: "Repository name" },
|
|
365
|
+
query: { type: "string", description: "Search text (optional)" },
|
|
366
|
+
state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional)" },
|
|
367
|
+
labels: { type: "array", items: { type: "string" }, description: "Filter by labels (optional)" },
|
|
368
|
+
qualifiers: { type: "array", items: { type: "string" }, description: "Extra GitHub search qualifiers like author:foo (optional)" },
|
|
369
|
+
sort: { type: "string", enum: ["created", "updated", "comments", "best-match"], description: "Sort strategy (optional)" },
|
|
370
|
+
direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional)" },
|
|
371
|
+
page: { type: "number", description: "Page number (optional, default: 1)" },
|
|
372
|
+
per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
|
|
373
|
+
fetch_all: { type: "boolean", description: "Fetch all pages up to GitHub search limit (optional, default: false)" },
|
|
374
|
+
},
|
|
375
|
+
required: ["owner", "repo"],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
handler: async (rawArgs) => executeTool(
|
|
379
|
+
rawArgs,
|
|
380
|
+
SearchIssuesSchema,
|
|
381
|
+
"Failed to search issues",
|
|
382
|
+
async (args) => searchIssuesWithPagination(args)
|
|
383
|
+
),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
definition: {
|
|
387
|
+
name: "github_list_recent_issues",
|
|
388
|
+
description: "List recent repository issues with pagination support",
|
|
389
|
+
inputSchema: {
|
|
390
|
+
type: "object",
|
|
391
|
+
properties: {
|
|
392
|
+
owner: { type: "string", description: "Repository owner" },
|
|
393
|
+
repo: { type: "string", description: "Repository name" },
|
|
394
|
+
state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional, default: open)" },
|
|
395
|
+
sort: { type: "string", enum: ["created", "updated", "comments"], description: "Sort field (optional, default: created)" },
|
|
396
|
+
direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional, default: desc)" },
|
|
397
|
+
page: { type: "number", description: "Page number (optional, default: 1)" },
|
|
398
|
+
per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
|
|
399
|
+
fetch_all: { type: "boolean", description: "Fetch all pages (optional, default: false)" },
|
|
400
|
+
},
|
|
401
|
+
required: ["owner", "repo"],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
handler: async (rawArgs) => executeTool(
|
|
405
|
+
rawArgs,
|
|
406
|
+
ListRecentIssuesSchema,
|
|
407
|
+
"Failed to list recent issues",
|
|
408
|
+
async (args) => {
|
|
409
|
+
const pagination = resolvePagination(args, {
|
|
410
|
+
page: 1,
|
|
411
|
+
perPage: 30,
|
|
412
|
+
fetchAll: false,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const issues = await collectPaginated(pagination, async (page, perPage) => {
|
|
416
|
+
const response = await getOctokit().rest.issues.listForRepo({
|
|
417
|
+
owner: args.owner,
|
|
418
|
+
repo: args.repo,
|
|
419
|
+
state: args.state ?? "open",
|
|
420
|
+
sort: args.sort ?? "created",
|
|
421
|
+
direction: args.direction ?? "desc",
|
|
422
|
+
page,
|
|
423
|
+
per_page: perPage,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
return response.data.filter((issue) => !issue.pull_request);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
count: issues.items.length,
|
|
431
|
+
issues: issues.items.map((issue) => ({
|
|
432
|
+
number: issue.number,
|
|
433
|
+
title: issue.title,
|
|
434
|
+
state: issue.state,
|
|
435
|
+
user: issue.user?.login,
|
|
436
|
+
labels: issue.labels.map((label) => (typeof label === "string" ? label : label.name)),
|
|
437
|
+
created_at: issue.created_at,
|
|
438
|
+
updated_at: issue.updated_at,
|
|
439
|
+
comments: issue.comments,
|
|
440
|
+
html_url: issue.html_url,
|
|
441
|
+
})),
|
|
442
|
+
pagination: {
|
|
443
|
+
page: issues.page,
|
|
444
|
+
per_page: issues.per_page,
|
|
445
|
+
fetch_all: issues.fetch_all,
|
|
446
|
+
pages_fetched: issues.pages_fetched,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
),
|
|
451
|
+
},
|
|
452
|
+
];
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { getOctokit } from "../lib/octokit.js";
|
|
4
|
+
import { collectPaginated, PaginationSchema, resolvePagination } from "../lib/pagination.js";
|
|
5
|
+
import { executeTool } from "../lib/response.js";
|
|
6
|
+
import { assertConfirmation } from "../lib/safety.js";
|
|
7
|
+
import type { ToolRegistration } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const CreateLabelSchema = z.object({
|
|
10
|
+
owner: z.string(),
|
|
11
|
+
repo: z.string(),
|
|
12
|
+
name: z.string(),
|
|
13
|
+
color: z.string(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const UpdateLabelSchema = z.object({
|
|
18
|
+
owner: z.string(),
|
|
19
|
+
repo: z.string(),
|
|
20
|
+
name: z.string(),
|
|
21
|
+
new_name: z.string().optional(),
|
|
22
|
+
color: z.string().optional(),
|
|
23
|
+
description: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const DeleteLabelSchema = z.object({
|
|
27
|
+
owner: z.string(),
|
|
28
|
+
repo: z.string(),
|
|
29
|
+
name: z.string(),
|
|
30
|
+
dry_run: z.boolean().optional(),
|
|
31
|
+
confirm_token: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const ListLabelsSchema = z.object({
|
|
35
|
+
owner: z.string(),
|
|
36
|
+
repo: z.string(),
|
|
37
|
+
}).merge(PaginationSchema);
|
|
38
|
+
|
|
39
|
+
export const labelTools: ToolRegistration[] = [
|
|
40
|
+
{
|
|
41
|
+
definition: {
|
|
42
|
+
name: "github_create_label",
|
|
43
|
+
description: "Create a new repository label",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
owner: { type: "string", description: "Repository owner" },
|
|
48
|
+
repo: { type: "string", description: "Repository name" },
|
|
49
|
+
name: { type: "string", description: "Label name" },
|
|
50
|
+
color: { type: "string", description: "Hex color without #" },
|
|
51
|
+
description: { type: "string", description: "Label description (optional)" },
|
|
52
|
+
},
|
|
53
|
+
required: ["owner", "repo", "name", "color"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
handler: async (rawArgs) => executeTool(
|
|
57
|
+
rawArgs,
|
|
58
|
+
CreateLabelSchema,
|
|
59
|
+
"Failed to create label",
|
|
60
|
+
async (args) => {
|
|
61
|
+
const label = await getOctokit().rest.issues.createLabel({
|
|
62
|
+
owner: args.owner,
|
|
63
|
+
repo: args.repo,
|
|
64
|
+
name: args.name,
|
|
65
|
+
color: args.color,
|
|
66
|
+
description: args.description,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
label: {
|
|
72
|
+
name: label.data.name,
|
|
73
|
+
color: label.data.color,
|
|
74
|
+
description: label.data.description,
|
|
75
|
+
url: label.data.url,
|
|
76
|
+
},
|
|
77
|
+
message: `Label \"${args.name}\" created successfully`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
definition: {
|
|
84
|
+
name: "github_update_label",
|
|
85
|
+
description: "Update an existing repository label",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
owner: { type: "string", description: "Repository owner" },
|
|
90
|
+
repo: { type: "string", description: "Repository name" },
|
|
91
|
+
name: { type: "string", description: "Current label name" },
|
|
92
|
+
new_name: { type: "string", description: "New name (optional)" },
|
|
93
|
+
color: { type: "string", description: "New hex color without # (optional)" },
|
|
94
|
+
description: { type: "string", description: "New description (optional)" },
|
|
95
|
+
},
|
|
96
|
+
required: ["owner", "repo", "name"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
handler: async (rawArgs) => executeTool(
|
|
100
|
+
rawArgs,
|
|
101
|
+
UpdateLabelSchema,
|
|
102
|
+
"Failed to update label",
|
|
103
|
+
async (args) => {
|
|
104
|
+
const label = await getOctokit().rest.issues.updateLabel({
|
|
105
|
+
owner: args.owner,
|
|
106
|
+
repo: args.repo,
|
|
107
|
+
name: args.name,
|
|
108
|
+
new_name: args.new_name,
|
|
109
|
+
color: args.color,
|
|
110
|
+
description: args.description,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
label: {
|
|
116
|
+
name: label.data.name,
|
|
117
|
+
color: label.data.color,
|
|
118
|
+
description: label.data.description,
|
|
119
|
+
url: label.data.url,
|
|
120
|
+
},
|
|
121
|
+
message: `Label \"${args.name}\" updated successfully`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
definition: {
|
|
128
|
+
name: "github_delete_label",
|
|
129
|
+
description: "Delete a repository label with dry-run and confirmation safeguards",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
owner: { type: "string", description: "Repository owner" },
|
|
134
|
+
repo: { type: "string", description: "Repository name" },
|
|
135
|
+
name: { type: "string", description: "Label name to delete" },
|
|
136
|
+
dry_run: { type: "boolean", description: "Preview deletion without executing (optional, default: false)" },
|
|
137
|
+
confirm_token: { type: "string", description: "Must be \"CONFIRM\" to execute delete when dry_run=false" },
|
|
138
|
+
},
|
|
139
|
+
required: ["owner", "repo", "name"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
handler: async (rawArgs) => executeTool(
|
|
143
|
+
rawArgs,
|
|
144
|
+
DeleteLabelSchema,
|
|
145
|
+
"Failed to delete label",
|
|
146
|
+
async (args) => {
|
|
147
|
+
const label = await getOctokit().rest.issues.getLabel({
|
|
148
|
+
owner: args.owner,
|
|
149
|
+
repo: args.repo,
|
|
150
|
+
name: args.name,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (args.dry_run ?? false) {
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
dry_run: true,
|
|
157
|
+
action: "delete_label",
|
|
158
|
+
target: {
|
|
159
|
+
name: label.data.name,
|
|
160
|
+
color: label.data.color,
|
|
161
|
+
description: label.data.description,
|
|
162
|
+
},
|
|
163
|
+
message: "Dry run only. No deletion executed.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
assertConfirmation(args.confirm_token, "Label deletion");
|
|
168
|
+
|
|
169
|
+
await getOctokit().rest.issues.deleteLabel({
|
|
170
|
+
owner: args.owner,
|
|
171
|
+
repo: args.repo,
|
|
172
|
+
name: args.name,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
dry_run: false,
|
|
178
|
+
message: `Label \"${args.name}\" deleted successfully from ${args.owner}/${args.repo}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
definition: {
|
|
185
|
+
name: "github_list_labels",
|
|
186
|
+
description: "List repository labels with pagination",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: "object",
|
|
189
|
+
properties: {
|
|
190
|
+
owner: { type: "string", description: "Repository owner" },
|
|
191
|
+
repo: { type: "string", description: "Repository name" },
|
|
192
|
+
page: { type: "number", description: "Page number (optional, default: 1)" },
|
|
193
|
+
per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
|
|
194
|
+
fetch_all: { type: "boolean", description: "Fetch all pages (optional, default: false)" },
|
|
195
|
+
},
|
|
196
|
+
required: ["owner", "repo"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
handler: async (rawArgs) => executeTool(
|
|
200
|
+
rawArgs,
|
|
201
|
+
ListLabelsSchema,
|
|
202
|
+
"Failed to list labels",
|
|
203
|
+
async (args) => {
|
|
204
|
+
const pagination = resolvePagination(args, {
|
|
205
|
+
page: 1,
|
|
206
|
+
perPage: 30,
|
|
207
|
+
fetchAll: false,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const labels = await collectPaginated(pagination, async (page, perPage) => {
|
|
211
|
+
const response = await getOctokit().rest.issues.listLabelsForRepo({
|
|
212
|
+
owner: args.owner,
|
|
213
|
+
repo: args.repo,
|
|
214
|
+
page,
|
|
215
|
+
per_page: perPage,
|
|
216
|
+
});
|
|
217
|
+
return response.data;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
count: labels.items.length,
|
|
223
|
+
labels: labels.items.map((label) => ({
|
|
224
|
+
name: label.name,
|
|
225
|
+
color: label.color,
|
|
226
|
+
description: label.description,
|
|
227
|
+
url: label.url,
|
|
228
|
+
})),
|
|
229
|
+
pagination: {
|
|
230
|
+
page: labels.page,
|
|
231
|
+
per_page: labels.per_page,
|
|
232
|
+
fetch_all: labels.fetch_all,
|
|
233
|
+
pages_fetched: labels.pages_fetched,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
];
|