Skip to content
This repository was archived by the owner on Jul 8, 2026. It is now read-only.

Commit dd6aa83

Browse files
web-flowclaude
andcommitted
feat: add support for replying to existing epic comments
The epics-create-comment tool now accepts an optional replyToCommentId parameter to create nested comment replies, mirroring the existing stories-create-comment pattern. Uses the dedicated createEpicCommentComment API endpoint for threaded replies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a6e1025 commit dd6aa83

3 files changed

Lines changed: 60 additions & 3 deletions

File tree

src/client/shortcut.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
33
import { basename } from "node:path";
44
import type {
55
ShortcutClient as BaseClient,
6+
CreateCommentComment,
67
CreateDoc,
78
CreateEpic,
89
CreateEpicComment,
@@ -460,6 +461,23 @@ export class ShortcutClientWrapper {
460461
return epicComment;
461462
}
462463

464+
async createEpicCommentComment(
465+
epicPublicId: number,
466+
commentPublicId: number,
467+
params: CreateCommentComment,
468+
): Promise<ThreadedComment> {
469+
const response = await this.client.createEpicCommentComment(
470+
epicPublicId,
471+
commentPublicId,
472+
params,
473+
);
474+
const epicComment = response?.data ?? null;
475+
476+
if (!epicComment) throw new Error(`Failed to create the comment reply: ${response.status}`);
477+
478+
return epicComment;
479+
}
480+
463481
async createEpic(params: CreateEpic): Promise<Epic> {
464482
const response = await this.client.createEpic(params);
465483
const epic = response?.data ?? null;

src/tools/epics.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,38 @@ describe("EpicTools", () => {
488488
}),
489489
).toThrow("Epic comment text is required");
490490
});
491+
492+
test("should create a reply to an existing comment", async () => {
493+
const createEpicCommentCommentMock = mock(
494+
async (_epicId: number, _commentId: number, _params: { text: string }) => ({
495+
id: 200,
496+
text: "Reply comment",
497+
app_url: "https://app.shortcut.com/test/epic/1/comments/200",
498+
}),
499+
);
500+
const mockClient = createMockClient({
501+
getEpic: getEpicMock,
502+
createEpicComment: createEpicCommentMock,
503+
createEpicCommentComment: createEpicCommentCommentMock,
504+
});
505+
const epicTools = new EpicTools(mockClient);
506+
const result = await epicTools.createEpicComment({
507+
epicPublicId: 1,
508+
replyToCommentId: 100,
509+
text: "Reply comment",
510+
});
511+
512+
expect(getTextContent(result)).toBe(
513+
"Created comment on epic 1. Comment URL: https://app.shortcut.com/test/epic/1/comments/200.",
514+
);
515+
expect(createEpicCommentMock).not.toHaveBeenCalled();
516+
expect(createEpicCommentCommentMock).toHaveBeenCalledTimes(1);
517+
expect(createEpicCommentCommentMock.mock.calls?.[0]?.[0]).toBe(1);
518+
expect(createEpicCommentCommentMock.mock.calls?.[0]?.[1]).toBe(100);
519+
expect(createEpicCommentCommentMock.mock.calls?.[0]?.[2]).toMatchObject({
520+
text: "Reply comment",
521+
});
522+
});
491523
});
492524

493525
describe("deleteEpic method", () => {

src/tools/epics.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export class EpicTools extends BaseTools {
9898
"Add a comment to an epic.",
9999
{
100100
epicPublicId: z.number().positive().describe("Epic ID"),
101+
replyToCommentId: z.number().positive().optional().describe("Comment ID to reply to"),
101102
text: z.string().min(1).describe("Comment text"),
102103
},
103104
async (params) => await tools.createEpicComment(params),
@@ -208,9 +209,11 @@ export class EpicTools extends BaseTools {
208209

209210
async createEpicComment({
210211
epicPublicId,
212+
replyToCommentId,
211213
text,
212214
}: {
213215
epicPublicId: number;
216+
replyToCommentId?: number;
214217
text: string;
215218
}): Promise<CallToolResult> {
216219
if (!epicPublicId) throw new Error("Epic public ID is required");
@@ -220,9 +223,13 @@ export class EpicTools extends BaseTools {
220223
if (!epic)
221224
throw new Error(`Failed to retrieve Shortcut epic with public ID: ${epicPublicId}`);
222225

223-
const epicComment = await this.client.createEpicComment(epicPublicId, {
224-
text,
225-
});
226+
const epicComment = replyToCommentId
227+
? await this.client.createEpicCommentComment(epicPublicId, replyToCommentId, {
228+
text,
229+
})
230+
: await this.client.createEpicComment(epicPublicId, {
231+
text,
232+
});
226233

227234
return this.toResult(
228235
`Created comment on epic ${epicPublicId}. Comment URL: ${epicComment.app_url}.`,

0 commit comments

Comments
 (0)