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,233 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getOctokit } from "../lib/octokit.js";
|
|
3
|
+
import { executeTool } from "../lib/response.js";
|
|
4
|
+
import { assertConfirmation } from "../lib/safety.js";
|
|
5
|
+
const REACTION_MAP = {
|
|
6
|
+
thumbs_up: "+1",
|
|
7
|
+
thumbs_down: "-1",
|
|
8
|
+
laugh: "laugh",
|
|
9
|
+
confused: "confused",
|
|
10
|
+
heart: "heart",
|
|
11
|
+
hooray: "hooray",
|
|
12
|
+
rocket: "rocket",
|
|
13
|
+
eyes: "eyes",
|
|
14
|
+
};
|
|
15
|
+
const AddCommentSchema = z.object({
|
|
16
|
+
owner: z.string(),
|
|
17
|
+
repo: z.string(),
|
|
18
|
+
issue_number: z.number(),
|
|
19
|
+
body: z.string(),
|
|
20
|
+
});
|
|
21
|
+
const UpdateCommentSchema = z.object({
|
|
22
|
+
owner: z.string(),
|
|
23
|
+
repo: z.string(),
|
|
24
|
+
comment_id: z.number(),
|
|
25
|
+
body: z.string(),
|
|
26
|
+
});
|
|
27
|
+
const DeleteCommentSchema = z.object({
|
|
28
|
+
owner: z.string(),
|
|
29
|
+
repo: z.string(),
|
|
30
|
+
comment_id: z.number(),
|
|
31
|
+
dry_run: z.boolean().optional(),
|
|
32
|
+
confirm_token: z.string().optional(),
|
|
33
|
+
expected_body_substring: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
const AddReactionSchema = z.object({
|
|
36
|
+
owner: z.string(),
|
|
37
|
+
repo: z.string(),
|
|
38
|
+
comment_id: z.number().optional(),
|
|
39
|
+
issue_number: z.number().optional(),
|
|
40
|
+
reaction: z.enum(["thumbs_up", "thumbs_down", "laugh", "confused", "heart", "hooray", "rocket", "eyes"]),
|
|
41
|
+
}).refine((data) => Boolean(data.comment_id) !== Boolean(data.issue_number), {
|
|
42
|
+
message: "Provide exactly one of comment_id or issue_number",
|
|
43
|
+
});
|
|
44
|
+
export const commentTools = [
|
|
45
|
+
{
|
|
46
|
+
definition: {
|
|
47
|
+
name: "github_add_comment",
|
|
48
|
+
description: "Add a comment to a GitHub issue or pull request",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
owner: { type: "string", description: "Repository owner" },
|
|
53
|
+
repo: { type: "string", description: "Repository name" },
|
|
54
|
+
issue_number: { type: "number", description: "Issue or PR number" },
|
|
55
|
+
body: { type: "string", description: "Comment body" },
|
|
56
|
+
},
|
|
57
|
+
required: ["owner", "repo", "issue_number", "body"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
handler: async (rawArgs) => executeTool(rawArgs, AddCommentSchema, "Failed to add comment", async (args) => {
|
|
61
|
+
const comment = await getOctokit().rest.issues.createComment({
|
|
62
|
+
owner: args.owner,
|
|
63
|
+
repo: args.repo,
|
|
64
|
+
issue_number: args.issue_number,
|
|
65
|
+
body: args.body,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
comment: {
|
|
70
|
+
id: comment.data.id,
|
|
71
|
+
body: comment.data.body,
|
|
72
|
+
user: comment.data.user?.login,
|
|
73
|
+
html_url: comment.data.html_url,
|
|
74
|
+
created_at: comment.data.created_at,
|
|
75
|
+
},
|
|
76
|
+
message: `Comment added successfully to issue/PR #${args.issue_number}`,
|
|
77
|
+
};
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
definition: {
|
|
82
|
+
name: "github_update_comment",
|
|
83
|
+
description: "Update an existing GitHub issue/PR comment",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
owner: { type: "string", description: "Repository owner" },
|
|
88
|
+
repo: { type: "string", description: "Repository name" },
|
|
89
|
+
comment_id: { type: "number", description: "Comment ID" },
|
|
90
|
+
body: { type: "string", description: "Updated comment body" },
|
|
91
|
+
},
|
|
92
|
+
required: ["owner", "repo", "comment_id", "body"],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
handler: async (rawArgs) => executeTool(rawArgs, UpdateCommentSchema, "Failed to update comment", async (args) => {
|
|
96
|
+
const comment = await getOctokit().rest.issues.updateComment({
|
|
97
|
+
owner: args.owner,
|
|
98
|
+
repo: args.repo,
|
|
99
|
+
comment_id: args.comment_id,
|
|
100
|
+
body: args.body,
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
comment: {
|
|
105
|
+
id: comment.data.id,
|
|
106
|
+
body: comment.data.body,
|
|
107
|
+
user: comment.data.user?.login,
|
|
108
|
+
html_url: comment.data.html_url,
|
|
109
|
+
updated_at: comment.data.updated_at,
|
|
110
|
+
},
|
|
111
|
+
message: `Comment #${args.comment_id} updated successfully`,
|
|
112
|
+
};
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
definition: {
|
|
117
|
+
name: "github_delete_comment",
|
|
118
|
+
description: "Delete a comment with dry-run and confirmation safeguards",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
owner: { type: "string", description: "Repository owner" },
|
|
123
|
+
repo: { type: "string", description: "Repository name" },
|
|
124
|
+
comment_id: { type: "number", description: "Comment ID to delete" },
|
|
125
|
+
dry_run: { type: "boolean", description: "Preview deletion without executing (optional, default: false)" },
|
|
126
|
+
confirm_token: { type: "string", description: "Must be \"CONFIRM\" to execute delete when dry_run=false" },
|
|
127
|
+
expected_body_substring: { type: "string", description: "Optional guard: comment must include this substring" },
|
|
128
|
+
},
|
|
129
|
+
required: ["owner", "repo", "comment_id"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
handler: async (rawArgs) => executeTool(rawArgs, DeleteCommentSchema, "Failed to delete comment", async (args) => {
|
|
133
|
+
const comment = await getOctokit().rest.issues.getComment({
|
|
134
|
+
owner: args.owner,
|
|
135
|
+
repo: args.repo,
|
|
136
|
+
comment_id: args.comment_id,
|
|
137
|
+
});
|
|
138
|
+
if (args.expected_body_substring && !comment.data.body?.includes(args.expected_body_substring)) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
dry_run: true,
|
|
142
|
+
message: "Comment body guard check failed. Deletion skipped.",
|
|
143
|
+
expected_body_substring: args.expected_body_substring,
|
|
144
|
+
current_comment_preview: comment.data.body?.slice(0, 200) ?? "",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (args.dry_run ?? false) {
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
dry_run: true,
|
|
151
|
+
action: "delete_comment",
|
|
152
|
+
target: {
|
|
153
|
+
comment_id: args.comment_id,
|
|
154
|
+
html_url: comment.data.html_url,
|
|
155
|
+
body_preview: comment.data.body?.slice(0, 200) ?? "",
|
|
156
|
+
},
|
|
157
|
+
message: "Dry run only. No deletion executed.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
assertConfirmation(args.confirm_token, "Comment deletion");
|
|
161
|
+
await getOctokit().rest.issues.deleteComment({
|
|
162
|
+
owner: args.owner,
|
|
163
|
+
repo: args.repo,
|
|
164
|
+
comment_id: args.comment_id,
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
dry_run: false,
|
|
169
|
+
message: `Comment #${args.comment_id} deleted successfully`,
|
|
170
|
+
};
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
definition: {
|
|
175
|
+
name: "github_add_reaction",
|
|
176
|
+
description: "Add a reaction to an issue/PR or to a specific comment",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
owner: { type: "string", description: "Repository owner" },
|
|
181
|
+
repo: { type: "string", description: "Repository name" },
|
|
182
|
+
comment_id: { type: "number", description: "Comment ID target (optional)" },
|
|
183
|
+
issue_number: { type: "number", description: "Issue/PR number target (optional)" },
|
|
184
|
+
reaction: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: ["thumbs_up", "thumbs_down", "laugh", "confused", "heart", "hooray", "rocket", "eyes"],
|
|
187
|
+
description: "Reaction type",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ["owner", "repo", "reaction"],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
handler: async (rawArgs) => executeTool(rawArgs, AddReactionSchema, "Failed to add reaction", async (args) => {
|
|
194
|
+
const reactionContent = REACTION_MAP[args.reaction] ?? args.reaction;
|
|
195
|
+
if (args.comment_id) {
|
|
196
|
+
const reaction = await getOctokit().rest.reactions.createForIssueComment({
|
|
197
|
+
owner: args.owner,
|
|
198
|
+
repo: args.repo,
|
|
199
|
+
comment_id: args.comment_id,
|
|
200
|
+
content: reactionContent,
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
target: `comment #${args.comment_id}`,
|
|
205
|
+
reaction: {
|
|
206
|
+
id: reaction.data.id,
|
|
207
|
+
content: reaction.data.content,
|
|
208
|
+
user: reaction.data.user?.login,
|
|
209
|
+
created_at: reaction.data.created_at,
|
|
210
|
+
},
|
|
211
|
+
message: `Reaction \"${args.reaction}\" added to comment #${args.comment_id}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const reaction = await getOctokit().rest.reactions.createForIssue({
|
|
215
|
+
owner: args.owner,
|
|
216
|
+
repo: args.repo,
|
|
217
|
+
issue_number: args.issue_number,
|
|
218
|
+
content: reactionContent,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
target: `issue/PR #${args.issue_number}`,
|
|
223
|
+
reaction: {
|
|
224
|
+
id: reaction.data.id,
|
|
225
|
+
content: reaction.data.content,
|
|
226
|
+
user: reaction.data.user?.login,
|
|
227
|
+
created_at: reaction.data.created_at,
|
|
228
|
+
},
|
|
229
|
+
message: `Reaction \"${args.reaction}\" added to issue/PR #${args.issue_number}`,
|
|
230
|
+
};
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ToolValidationError } from "../lib/errors.js";
|
|
2
|
+
import { failure } from "../lib/response.js";
|
|
3
|
+
import { branchTools } from "./branches.js";
|
|
4
|
+
import { commentTools } from "./comments.js";
|
|
5
|
+
import { issueTools } from "./issues.js";
|
|
6
|
+
import { labelTools } from "./labels.js";
|
|
7
|
+
import { pullRequestTools } from "./pull-requests.js";
|
|
8
|
+
const toolRegistry = [
|
|
9
|
+
...issueTools,
|
|
10
|
+
...pullRequestTools,
|
|
11
|
+
...commentTools,
|
|
12
|
+
...labelTools,
|
|
13
|
+
...branchTools,
|
|
14
|
+
];
|
|
15
|
+
const toolMap = new Map(toolRegistry.map((tool) => [tool.definition.name, tool.handler]));
|
|
16
|
+
export const toolDefinitions = toolRegistry.map((tool) => tool.definition);
|
|
17
|
+
export const toolCount = toolDefinitions.length;
|
|
18
|
+
export async function dispatchTool(name, args) {
|
|
19
|
+
const handler = toolMap.get(name);
|
|
20
|
+
if (!handler) {
|
|
21
|
+
return failure(new ToolValidationError(`Unknown tool: ${name}`, 404), `Unknown tool requested: ${name}`);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return await handler(args);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return failure(error, `Unexpected error while executing tool: ${name}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getOctokit } from "../lib/octokit.js";
|
|
3
|
+
import { collectPaginated, PaginationSchema, resolvePagination } from "../lib/pagination.js";
|
|
4
|
+
import { executeTool } from "../lib/response.js";
|
|
5
|
+
import { buildRepositorySearchQuery, normalizeSearchSort } from "../lib/search.js";
|
|
6
|
+
const IssueContextSchema = z.object({
|
|
7
|
+
owner: z.string(),
|
|
8
|
+
repo: z.string(),
|
|
9
|
+
issue_number: z.number(),
|
|
10
|
+
comments_page: z.number().int().min(1).optional(),
|
|
11
|
+
comments_per_page: z.number().int().min(1).max(100).optional(),
|
|
12
|
+
comments_fetch_all: z.boolean().optional(),
|
|
13
|
+
});
|
|
14
|
+
const CreateIssueSchema = z.object({
|
|
15
|
+
owner: z.string(),
|
|
16
|
+
repo: z.string(),
|
|
17
|
+
title: z.string(),
|
|
18
|
+
body: z.string().optional(),
|
|
19
|
+
labels: z.array(z.string()).optional(),
|
|
20
|
+
assignees: z.array(z.string()).optional(),
|
|
21
|
+
});
|
|
22
|
+
const UpdateIssueSchema = z.object({
|
|
23
|
+
owner: z.string(),
|
|
24
|
+
repo: z.string(),
|
|
25
|
+
issue_number: z.number(),
|
|
26
|
+
title: z.string().optional(),
|
|
27
|
+
body: z.string().optional(),
|
|
28
|
+
state: z.enum(["open", "closed"]).optional(),
|
|
29
|
+
labels: z.array(z.string()).optional(),
|
|
30
|
+
assignees: z.array(z.string()).optional(),
|
|
31
|
+
});
|
|
32
|
+
const SearchIssuesSchema = z.object({
|
|
33
|
+
owner: z.string(),
|
|
34
|
+
repo: z.string(),
|
|
35
|
+
query: z.string().optional(),
|
|
36
|
+
state: z.enum(["open", "closed", "all"]).optional(),
|
|
37
|
+
labels: z.array(z.string()).optional(),
|
|
38
|
+
qualifiers: z.array(z.string()).optional(),
|
|
39
|
+
sort: z.enum(["created", "updated", "comments", "best-match"]).optional(),
|
|
40
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
41
|
+
}).merge(PaginationSchema);
|
|
42
|
+
const ListRecentIssuesSchema = z.object({
|
|
43
|
+
owner: z.string(),
|
|
44
|
+
repo: z.string(),
|
|
45
|
+
state: z.enum(["open", "closed", "all"]).optional(),
|
|
46
|
+
sort: z.enum(["created", "updated", "comments"]).optional(),
|
|
47
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
48
|
+
}).merge(PaginationSchema);
|
|
49
|
+
async function searchIssuesWithPagination(args) {
|
|
50
|
+
const pagination = resolvePagination(args, {
|
|
51
|
+
page: 1,
|
|
52
|
+
perPage: 30,
|
|
53
|
+
fetchAll: false,
|
|
54
|
+
});
|
|
55
|
+
const q = buildRepositorySearchQuery({
|
|
56
|
+
owner: args.owner,
|
|
57
|
+
repo: args.repo,
|
|
58
|
+
kind: "issue",
|
|
59
|
+
state: args.state,
|
|
60
|
+
query: args.query,
|
|
61
|
+
labels: args.labels,
|
|
62
|
+
qualifiers: args.qualifiers,
|
|
63
|
+
});
|
|
64
|
+
const sort = normalizeSearchSort(args.sort);
|
|
65
|
+
const order = args.direction;
|
|
66
|
+
if (!pagination.fetchAll) {
|
|
67
|
+
const response = await getOctokit().rest.search.issuesAndPullRequests({
|
|
68
|
+
q,
|
|
69
|
+
sort: sort,
|
|
70
|
+
order: order,
|
|
71
|
+
page: pagination.page,
|
|
72
|
+
per_page: pagination.perPage,
|
|
73
|
+
});
|
|
74
|
+
const issues = response.data.items.filter((item) => !("pull_request" in item));
|
|
75
|
+
return {
|
|
76
|
+
query: q,
|
|
77
|
+
total_count: response.data.total_count,
|
|
78
|
+
incomplete_results: response.data.incomplete_results,
|
|
79
|
+
page: pagination.page,
|
|
80
|
+
per_page: pagination.perPage,
|
|
81
|
+
fetch_all: false,
|
|
82
|
+
count: issues.length,
|
|
83
|
+
issues: issues.map((issue) => ({
|
|
84
|
+
number: issue.number,
|
|
85
|
+
title: issue.title,
|
|
86
|
+
state: issue.state,
|
|
87
|
+
user: issue.user?.login,
|
|
88
|
+
labels: issue.labels,
|
|
89
|
+
comments: issue.comments,
|
|
90
|
+
created_at: issue.created_at,
|
|
91
|
+
updated_at: issue.updated_at,
|
|
92
|
+
html_url: issue.html_url,
|
|
93
|
+
score: issue.score,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const allItems = [];
|
|
98
|
+
let page = pagination.page;
|
|
99
|
+
let totalCount = 0;
|
|
100
|
+
let incompleteResults = false;
|
|
101
|
+
while (true) {
|
|
102
|
+
const response = await getOctokit().rest.search.issuesAndPullRequests({
|
|
103
|
+
q,
|
|
104
|
+
sort: sort,
|
|
105
|
+
order: order,
|
|
106
|
+
page,
|
|
107
|
+
per_page: pagination.perPage,
|
|
108
|
+
});
|
|
109
|
+
if (page === pagination.page) {
|
|
110
|
+
totalCount = response.data.total_count;
|
|
111
|
+
incompleteResults = response.data.incomplete_results;
|
|
112
|
+
}
|
|
113
|
+
allItems.push(...response.data.items);
|
|
114
|
+
if (response.data.items.length < pagination.perPage) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (allItems.length >= 1000) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
page += 1;
|
|
121
|
+
}
|
|
122
|
+
const issues = allItems.filter((item) => !("pull_request" in item));
|
|
123
|
+
return {
|
|
124
|
+
query: q,
|
|
125
|
+
total_count: totalCount,
|
|
126
|
+
incomplete_results: incompleteResults,
|
|
127
|
+
page: pagination.page,
|
|
128
|
+
per_page: pagination.perPage,
|
|
129
|
+
fetch_all: true,
|
|
130
|
+
pages_fetched: page - pagination.page + 1,
|
|
131
|
+
count: issues.length,
|
|
132
|
+
issues: issues.map((issue) => ({
|
|
133
|
+
number: issue.number,
|
|
134
|
+
title: issue.title,
|
|
135
|
+
state: issue.state,
|
|
136
|
+
user: issue.user?.login,
|
|
137
|
+
labels: issue.labels,
|
|
138
|
+
comments: issue.comments,
|
|
139
|
+
created_at: issue.created_at,
|
|
140
|
+
updated_at: issue.updated_at,
|
|
141
|
+
html_url: issue.html_url,
|
|
142
|
+
score: issue.score,
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export const issueTools = [
|
|
147
|
+
{
|
|
148
|
+
definition: {
|
|
149
|
+
name: "github_get_issue_context",
|
|
150
|
+
description: "Get GitHub Issue context including metadata, reactions, and paginated comments",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
owner: { type: "string", description: "Repository owner" },
|
|
155
|
+
repo: { type: "string", description: "Repository name" },
|
|
156
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
157
|
+
comments_page: { type: "number", description: "Comments page number (optional, default: 1)" },
|
|
158
|
+
comments_per_page: { type: "number", description: "Comments per page, max 100 (optional, default: 100)" },
|
|
159
|
+
comments_fetch_all: { type: "boolean", description: "Fetch all comment pages (optional, default: true)" },
|
|
160
|
+
},
|
|
161
|
+
required: ["owner", "repo", "issue_number"],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
handler: async (rawArgs) => executeTool(rawArgs, IssueContextSchema, "Failed to fetch issue context", async (args) => {
|
|
165
|
+
const issue = await getOctokit().rest.issues.get({
|
|
166
|
+
owner: args.owner,
|
|
167
|
+
repo: args.repo,
|
|
168
|
+
issue_number: args.issue_number,
|
|
169
|
+
});
|
|
170
|
+
const commentPagination = resolvePagination({
|
|
171
|
+
page: args.comments_page,
|
|
172
|
+
per_page: args.comments_per_page,
|
|
173
|
+
fetch_all: args.comments_fetch_all,
|
|
174
|
+
}, {
|
|
175
|
+
page: 1,
|
|
176
|
+
perPage: 100,
|
|
177
|
+
fetchAll: true,
|
|
178
|
+
});
|
|
179
|
+
const commentsResult = await collectPaginated(commentPagination, async (page, perPage) => {
|
|
180
|
+
const comments = await getOctokit().rest.issues.listComments({
|
|
181
|
+
owner: args.owner,
|
|
182
|
+
repo: args.repo,
|
|
183
|
+
issue_number: args.issue_number,
|
|
184
|
+
page,
|
|
185
|
+
per_page: perPage,
|
|
186
|
+
});
|
|
187
|
+
return comments.data;
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
issue: {
|
|
191
|
+
number: issue.data.number,
|
|
192
|
+
title: issue.data.title,
|
|
193
|
+
body: issue.data.body,
|
|
194
|
+
state: issue.data.state,
|
|
195
|
+
user: issue.data.user?.login,
|
|
196
|
+
assignees: issue.data.assignees?.map((assignee) => assignee.login),
|
|
197
|
+
milestone: issue.data.milestone
|
|
198
|
+
? {
|
|
199
|
+
title: issue.data.milestone.title,
|
|
200
|
+
due_on: issue.data.milestone.due_on,
|
|
201
|
+
}
|
|
202
|
+
: null,
|
|
203
|
+
labels: issue.data.labels.map((label) => (typeof label === "string" ? label : label.name)),
|
|
204
|
+
reactions: issue.data.reactions,
|
|
205
|
+
created_at: issue.data.created_at,
|
|
206
|
+
updated_at: issue.data.updated_at,
|
|
207
|
+
html_url: issue.data.html_url,
|
|
208
|
+
},
|
|
209
|
+
comments: commentsResult.items.map((comment) => ({
|
|
210
|
+
id: comment.id,
|
|
211
|
+
user: comment.user?.login,
|
|
212
|
+
body: comment.body,
|
|
213
|
+
reactions: comment.reactions,
|
|
214
|
+
created_at: comment.created_at,
|
|
215
|
+
updated_at: comment.updated_at,
|
|
216
|
+
html_url: comment.html_url,
|
|
217
|
+
})),
|
|
218
|
+
comments_pagination: {
|
|
219
|
+
page: commentsResult.page,
|
|
220
|
+
per_page: commentsResult.per_page,
|
|
221
|
+
fetch_all: commentsResult.fetch_all,
|
|
222
|
+
pages_fetched: commentsResult.pages_fetched,
|
|
223
|
+
count: commentsResult.items.length,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
definition: {
|
|
230
|
+
name: "github_create_issue",
|
|
231
|
+
description: "Create a new GitHub issue",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: "object",
|
|
234
|
+
properties: {
|
|
235
|
+
owner: { type: "string", description: "Repository owner" },
|
|
236
|
+
repo: { type: "string", description: "Repository name" },
|
|
237
|
+
title: { type: "string", description: "Issue title" },
|
|
238
|
+
body: { type: "string", description: "Issue body (optional)" },
|
|
239
|
+
labels: { type: "array", items: { type: "string" }, description: "Labels (optional)" },
|
|
240
|
+
assignees: { type: "array", items: { type: "string" }, description: "Assignees (optional)" },
|
|
241
|
+
},
|
|
242
|
+
required: ["owner", "repo", "title"],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
handler: async (rawArgs) => executeTool(rawArgs, CreateIssueSchema, "Failed to create issue", async (args) => {
|
|
246
|
+
const issue = await getOctokit().rest.issues.create({
|
|
247
|
+
owner: args.owner,
|
|
248
|
+
repo: args.repo,
|
|
249
|
+
title: args.title,
|
|
250
|
+
body: args.body,
|
|
251
|
+
labels: args.labels,
|
|
252
|
+
assignees: args.assignees,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
issue: {
|
|
257
|
+
number: issue.data.number,
|
|
258
|
+
title: issue.data.title,
|
|
259
|
+
state: issue.data.state,
|
|
260
|
+
html_url: issue.data.html_url,
|
|
261
|
+
created_at: issue.data.created_at,
|
|
262
|
+
},
|
|
263
|
+
message: `Issue #${issue.data.number} created successfully`,
|
|
264
|
+
};
|
|
265
|
+
}),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
definition: {
|
|
269
|
+
name: "github_update_issue",
|
|
270
|
+
description: "Update an existing GitHub issue",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
owner: { type: "string", description: "Repository owner" },
|
|
275
|
+
repo: { type: "string", description: "Repository name" },
|
|
276
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
277
|
+
title: { type: "string", description: "New title (optional)" },
|
|
278
|
+
body: { type: "string", description: "New body (optional)" },
|
|
279
|
+
state: { type: "string", enum: ["open", "closed"], description: "Issue state (optional)" },
|
|
280
|
+
labels: { type: "array", items: { type: "string" }, description: "New labels (optional)" },
|
|
281
|
+
assignees: { type: "array", items: { type: "string" }, description: "New assignees (optional)" },
|
|
282
|
+
},
|
|
283
|
+
required: ["owner", "repo", "issue_number"],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
handler: async (rawArgs) => executeTool(rawArgs, UpdateIssueSchema, "Failed to update issue", async (args) => {
|
|
287
|
+
const issue = await getOctokit().rest.issues.update({
|
|
288
|
+
owner: args.owner,
|
|
289
|
+
repo: args.repo,
|
|
290
|
+
issue_number: args.issue_number,
|
|
291
|
+
title: args.title,
|
|
292
|
+
body: args.body,
|
|
293
|
+
state: args.state,
|
|
294
|
+
labels: args.labels,
|
|
295
|
+
assignees: args.assignees,
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
issue: {
|
|
300
|
+
number: issue.data.number,
|
|
301
|
+
title: issue.data.title,
|
|
302
|
+
state: issue.data.state,
|
|
303
|
+
html_url: issue.data.html_url,
|
|
304
|
+
updated_at: issue.data.updated_at,
|
|
305
|
+
},
|
|
306
|
+
message: `Issue #${issue.data.number} updated successfully`,
|
|
307
|
+
};
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
definition: {
|
|
312
|
+
name: "github_search_issues",
|
|
313
|
+
description: "Search repository issues using GitHub Search API and optional qualifiers",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: {
|
|
317
|
+
owner: { type: "string", description: "Repository owner" },
|
|
318
|
+
repo: { type: "string", description: "Repository name" },
|
|
319
|
+
query: { type: "string", description: "Search text (optional)" },
|
|
320
|
+
state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional)" },
|
|
321
|
+
labels: { type: "array", items: { type: "string" }, description: "Filter by labels (optional)" },
|
|
322
|
+
qualifiers: { type: "array", items: { type: "string" }, description: "Extra GitHub search qualifiers like author:foo (optional)" },
|
|
323
|
+
sort: { type: "string", enum: ["created", "updated", "comments", "best-match"], description: "Sort strategy (optional)" },
|
|
324
|
+
direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional)" },
|
|
325
|
+
page: { type: "number", description: "Page number (optional, default: 1)" },
|
|
326
|
+
per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
|
|
327
|
+
fetch_all: { type: "boolean", description: "Fetch all pages up to GitHub search limit (optional, default: false)" },
|
|
328
|
+
},
|
|
329
|
+
required: ["owner", "repo"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
handler: async (rawArgs) => executeTool(rawArgs, SearchIssuesSchema, "Failed to search issues", async (args) => searchIssuesWithPagination(args)),
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
definition: {
|
|
336
|
+
name: "github_list_recent_issues",
|
|
337
|
+
description: "List recent repository issues with pagination support",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
type: "object",
|
|
340
|
+
properties: {
|
|
341
|
+
owner: { type: "string", description: "Repository owner" },
|
|
342
|
+
repo: { type: "string", description: "Repository name" },
|
|
343
|
+
state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional, default: open)" },
|
|
344
|
+
sort: { type: "string", enum: ["created", "updated", "comments"], description: "Sort field (optional, default: created)" },
|
|
345
|
+
direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional, default: desc)" },
|
|
346
|
+
page: { type: "number", description: "Page number (optional, default: 1)" },
|
|
347
|
+
per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
|
|
348
|
+
fetch_all: { type: "boolean", description: "Fetch all pages (optional, default: false)" },
|
|
349
|
+
},
|
|
350
|
+
required: ["owner", "repo"],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
handler: async (rawArgs) => executeTool(rawArgs, ListRecentIssuesSchema, "Failed to list recent issues", async (args) => {
|
|
354
|
+
const pagination = resolvePagination(args, {
|
|
355
|
+
page: 1,
|
|
356
|
+
perPage: 30,
|
|
357
|
+
fetchAll: false,
|
|
358
|
+
});
|
|
359
|
+
const issues = await collectPaginated(pagination, async (page, perPage) => {
|
|
360
|
+
const response = await getOctokit().rest.issues.listForRepo({
|
|
361
|
+
owner: args.owner,
|
|
362
|
+
repo: args.repo,
|
|
363
|
+
state: args.state ?? "open",
|
|
364
|
+
sort: args.sort ?? "created",
|
|
365
|
+
direction: args.direction ?? "desc",
|
|
366
|
+
page,
|
|
367
|
+
per_page: perPage,
|
|
368
|
+
});
|
|
369
|
+
return response.data.filter((issue) => !issue.pull_request);
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
count: issues.items.length,
|
|
373
|
+
issues: issues.items.map((issue) => ({
|
|
374
|
+
number: issue.number,
|
|
375
|
+
title: issue.title,
|
|
376
|
+
state: issue.state,
|
|
377
|
+
user: issue.user?.login,
|
|
378
|
+
labels: issue.labels.map((label) => (typeof label === "string" ? label : label.name)),
|
|
379
|
+
created_at: issue.created_at,
|
|
380
|
+
updated_at: issue.updated_at,
|
|
381
|
+
comments: issue.comments,
|
|
382
|
+
html_url: issue.html_url,
|
|
383
|
+
})),
|
|
384
|
+
pagination: {
|
|
385
|
+
page: issues.page,
|
|
386
|
+
per_page: issues.per_page,
|
|
387
|
+
fetch_all: issues.fetch_all,
|
|
388
|
+
pages_fetched: issues.pages_fetched,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}),
|
|
392
|
+
},
|
|
393
|
+
];
|