From 80da968bbde0e0056ba476df1530ecdadcbff48d Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Tue, 5 Aug 2025 23:49:18 +0900 Subject: [PATCH 1/8] feat: plan version/milestone management tools implementation Planning to implement: - get_version_milestone_list: retrieve versions/milestones for a project - add_version_milestone: create new versions/milestones - update_version_milestone: update existing versions/milestones - delete_version: delete versions From d5af63e34a2286bce71586b58346762aeb12039a Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Wed, 6 Aug 2025 07:02:57 +0900 Subject: [PATCH 2/8] feat: add get version milestone list tool --- src/tools/getVersionMilestoneList.test.ts | 81 +++++++++++++++++++++++ src/tools/getVersionMilestoneList.ts | 58 ++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 src/tools/getVersionMilestoneList.test.ts create mode 100644 src/tools/getVersionMilestoneList.ts diff --git a/src/tools/getVersionMilestoneList.test.ts b/src/tools/getVersionMilestoneList.test.ts new file mode 100644 index 0000000..fb8ebfc --- /dev/null +++ b/src/tools/getVersionMilestoneList.test.ts @@ -0,0 +1,81 @@ +import { getVersionMilestoneListTool } from './getVersionMilestoneList.js'; +import { jest, describe, it, expect } from '@jest/globals'; +import type { Backlog } from 'backlog-js'; +import { createTranslationHelper } from '../createTranslationHelper.js'; + +describe('getVersionMilestoneTool', () => { + const mockBacklog: Partial = { + getVersions: jest.fn<() => Promise>().mockResolvedValue([ + { + id: 1, + projectId: 1, + name: "wait for release", + description: "", + startDate: null, + releaseDueDate: null, + archived: false, + displayOrder: 0 + }, + { + id: 2, + projectId: 1, + name: "v1.0.0", + description: "First release", + startDate: "2025-01-01", + releaseDueDate: "2025-03-01", + archived: false, + displayOrder: 1 + }, + { + id: 3, + projectId: 1, + name: "v1.1.0", + description: "Minor update", + startDate: "2025-03-01", + releaseDueDate: "2025-05-01", + archived: false, + displayOrder: 2 + } + ]), + }; + + + const mockTranslationHelper = createTranslationHelper(); + const tool = getVersionMilestoneListTool(mockBacklog as Backlog, mockTranslationHelper); + + it('returns versions list as formatted JSON text', async () => { + const result = await tool.handler({ projectId: 123 }); + + if (!Array.isArray(result)) { + throw new Error('Unexpected non array result'); + } + + expect(result).toHaveLength(3); + expect(result[0].name).toContain('wait for release'); + expect(result[1].name).toContain('v1.0.0'); + expect(result[2].name).toContain('v1.1.0'); + }); + + it('calls backlog.getVersions with correct params when using project key', async () => { + await tool.handler({ + projectKey: 'TEST_PROJECT', + }); + + expect(mockBacklog.getVersions).toHaveBeenCalledWith('TEST_PROJECT'); + }); + + it('calls backlog.getVersions with correct params when using project ID', async () => { + await tool.handler({ + projectId: 123, + }); + + expect(mockBacklog.getVersions).toHaveBeenCalledWith(123); + }); + + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = {}; // No identifier provided + + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); + +}); diff --git a/src/tools/getVersionMilestoneList.ts b/src/tools/getVersionMilestoneList.ts new file mode 100644 index 0000000..2a164c4 --- /dev/null +++ b/src/tools/getVersionMilestoneList.ts @@ -0,0 +1,58 @@ +import { z } from 'zod'; +import { Backlog } from 'backlog-js'; +import { buildToolSchema, ToolDefinition } from '../types/tool.js'; +import { TranslationHelper } from '../createTranslationHelper.js'; +import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; +import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; + + +const getVersionMilestoneListSchema = buildToolSchema((t) => ({ + projectId: z + .number() + .optional() + .describe( + t( + 'TOOL_GET_VERSION_MILESTONE_PROJECT_ID', + 'The numeric ID of the project (e.g., 12345)' + ) + ), + projectKey: z + .string() + .optional() + .describe( + t( + 'TOOL_GET_VERSION_MILESTONE_PROJECT_KEY', + 'The key of the project (e.g., TEST_PROJECT)' + ) + ), +})); + +export const getVersionMilestoneListTool = ( + backlog: Backlog, + { t }: TranslationHelper +): ToolDefinition< + ReturnType, + (typeof VersionSchema)['shape'] +> => { + return { + name: 'get_version_milestone_list', + description: t( + 'TOOL_GET_VERSION_MILESTONE_LIST_DESCRIPTION', + 'Returns list of versions/milestones in the Backlog space' + ), + schema: z.object(getVersionMilestoneListSchema(t)), + outputSchema: VersionSchema, + importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate', 'archived'], + handler: async ({ projectId, projectKey }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + return backlog.getVersions(result.value); + }, + }; +} \ No newline at end of file From 98992cae73912b84c8576440094123a4b0bd04a0 Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Thu, 7 Aug 2025 23:29:41 +0900 Subject: [PATCH 3/8] feat(tools): add version milestone creation tool with tests --- src/tools/addVersionMilestone.test.ts | 104 ++++++++++++++++++++++++++ src/tools/addVersionMilestone.ts | 57 ++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/tools/addVersionMilestone.test.ts create mode 100644 src/tools/addVersionMilestone.ts diff --git a/src/tools/addVersionMilestone.test.ts b/src/tools/addVersionMilestone.test.ts new file mode 100644 index 0000000..25f4042 --- /dev/null +++ b/src/tools/addVersionMilestone.test.ts @@ -0,0 +1,104 @@ +import { addVersionMilestoneTool } from './addVersionMilestone.js'; +import { jest, describe, it, expect } from '@jest/globals'; +import type { Backlog } from 'backlog-js'; +import { createTranslationHelper } from '../createTranslationHelper.js'; + +describe('addVersionMilestoneTool', () => { + const mockBacklog: Partial = { + postVersions: jest.fn<() => Promise>().mockResolvedValue({ + id: 1, + projectId: 100, + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-03-31T00:00:00Z', + archived: false, + displayOrder: 1, + }), + }; + + const mockTranslationHelper = createTranslationHelper(); + const tool = addVersionMilestoneTool( + mockBacklog as Backlog, + mockTranslationHelper + ); + + it('returns created version milestone as formatted JSON text', async () => { + const result = await tool.handler({ + projectKey: 'TEST', + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-03-31T00:00:00Z', + }); + + if (Array.isArray(result)) { + throw new Error('Unexpected array result'); + } + expect(result.name).toEqual('Version 1.0.0'); + expect(result.description).toEqual('Initial release version'); + expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); + expect(result.releaseDueDate).toEqual('2023-03-31T00:00:00Z'); + }); + + it('calls backlog.postVersions with correct params when using projectKey', async () => { + const params = { + projectKey: 'TEST', + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2024-03-31T00:00:00Z', + }; + + await tool.handler(params); + + expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2024-03-31T00:00:00Z', + }); + }); + + it('calls backlog.postVersions with correct params when using projectId', async () => { + const params = { + projectId: 100, + name: 'Version 2.0.0', + description: 'Major release', + startDate: '2023-04-01T00:00:00Z', + releaseDueDate: '2023-06-30T00:00:00Z', + }; + + await tool.handler(params); + + expect(mockBacklog.postVersions).toHaveBeenCalledWith(100, { + name: 'Version 2.0.0', + description: 'Major release', + startDate: '2023-04-01T00:00:00Z', + releaseDueDate: '2023-06-30T00:00:00Z', + }); + }); + + it('calls backlog.postVersions with minimal required params', async () => { + const params = { + projectKey: 'TEST', + name: 'Quick Version', + }; + + await tool.handler(params); + + expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { + name: 'Quick Version', + }); + }); + + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = { + // projectId and projectKey are missing + name: 'Version without project', + description: 'This should fail', + }; + + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); +}); diff --git a/src/tools/addVersionMilestone.ts b/src/tools/addVersionMilestone.ts new file mode 100644 index 0000000..63803c9 --- /dev/null +++ b/src/tools/addVersionMilestone.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; +import { Backlog } from 'backlog-js'; +import { buildToolSchema, ToolDefinition } from '../types/tool.js'; +import { TranslationHelper } from '../createTranslationHelper.js'; +import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; +import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; + +const addVersionMilestoneSchema = buildToolSchema((t) => ({ + projectKey: z + .string() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_KEY', 'Project key')), + projectId: z + .number() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_ID', 'Project ID')), + name: z.string().describe(t('TOOL_ADD_VERSION_MILESTONE_NAME', 'Version name')), + description: z + .string() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Version description')), + startDate: z + .string() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_START_DATE', 'Start date of the version')), + releaseDueDate: z + .string() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_RELEASE_DUE_DATE', 'Release due date of the version')) +})); + +export const addVersionMilestoneTool = ( + backlog: Backlog, + { t }: TranslationHelper +): ToolDefinition< + ReturnType, + (typeof VersionSchema)['shape'] +> => { + return { + name: 'add_version_milestone', + description: t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Creates a new version milestone'), + schema: z.object(addVersionMilestoneSchema(t)), + outputSchema: VersionSchema, + importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate'], + handler: async ({ projectId, projectKey, ...params }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + return backlog.postVersions(result.value, params) + } + }; +} \ No newline at end of file From 3ce62233ab7a50de2ea61055aa51249da099f061 Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Fri, 8 Aug 2025 23:19:12 +0900 Subject: [PATCH 4/8] feat(tools): add update version milestone tool with tests --- src/tools/updateVersionMilestone.test.ts | 123 +++++++++++++++++++++++ src/tools/updateVersionMilestone.ts | 82 +++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/tools/updateVersionMilestone.test.ts create mode 100644 src/tools/updateVersionMilestone.ts diff --git a/src/tools/updateVersionMilestone.test.ts b/src/tools/updateVersionMilestone.test.ts new file mode 100644 index 0000000..26546ec --- /dev/null +++ b/src/tools/updateVersionMilestone.test.ts @@ -0,0 +1,123 @@ +import { updateVersionMilestoneTool } from './updateVersionMilestone.js'; +import { jest, describe, expect, it } from '@jest/globals'; +import type { Backlog } from 'backlog-js'; +import { createTranslationHelper } from '../createTranslationHelper.js'; + +describe('updateVersionMilestoneTool', () => { + const mockBacklog: Partial = { + patchVersions: jest.fn<() => Promise>().mockResolvedValue({ + id: 1, + projectId: 100, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }), + }; + + const mockTranslationHelper = createTranslationHelper(); + const tool = updateVersionMilestoneTool( + mockBacklog as Backlog, + mockTranslationHelper + ); + + it('returns updated version milestone', async () => { + const result = await tool.handler({ + projectKey: 'TEST', + projectId: 100, + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }); + + if (Array.isArray(result)) { + throw new Error('Unexpected array result'); + } + + expect(result.name).toEqual('Updated Version'); + expect(result.description).toEqual('Updated version description'); + expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); + expect(result.releaseDueDate).toEqual('2023-12-31T00:00:00Z'); + expect(result.archived).toBe(false); + }); + + it('calls backlog.patchVersions with correct params when using projectKey', async () => { + const params = { + projectKey: 'TEST', + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }; + + await tool.handler(params); + + expect(mockBacklog.patchVersions).toHaveBeenCalledWith( + 'TEST', + 1, + { + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + } + ); + }); + + it('calls backlog.pathVersions with correct params when using projectId', async () => { + const params = { + projectId: 100, + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }; + + await tool.handler(params); + + expect(mockBacklog.patchVersions).toHaveBeenCalledWith( + 100, + 1, + { + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + } + ); + }); + + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = { + // projectId and projectKey are missing + id: 1, + name: 'Version without project', + description: 'This should fail', + }; + + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); + + it('throws an error if id is not provided', async () => { + const params = { + projectKey: 'TEST', + // id is missing + name: 'Version without ID', + description: 'This should fail', + }; + + await expect(tool.handler(params as any)).rejects.toThrow( + 'Version ID is required' + ); + }); +}); diff --git a/src/tools/updateVersionMilestone.ts b/src/tools/updateVersionMilestone.ts new file mode 100644 index 0000000..3c7f930 --- /dev/null +++ b/src/tools/updateVersionMilestone.ts @@ -0,0 +1,82 @@ +import { z } from 'zod'; +import { Backlog } from 'backlog-js'; +import { buildToolSchema, ToolDefinition } from '../types/tool.js'; +import { TranslationHelper } from '../createTranslationHelper.js'; +import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; +import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; + +const updateVersionMilestoneSchema = buildToolSchema((t) => ({ + projectId: z + .number() + .optional() + .describe( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_ID', + 'The numeric ID of the project (e.g., 12345)' + ) + ), + projectKey: z + .string() + .optional() + .describe( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_KEY', + "The key of the project (e.g., 'PROJECT')" + ) + ), + id: z + .number() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_ID', 'Version ID')), + name: z + .string() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_NAME', 'Version name')), + description: z + .string() + .optional() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', 'Version description')), + startDate: z + .string() + .optional() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_START_DATE', 'Start date')), + releaseDueDate: z + .string() + .optional() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_RELEASE_DUE_DATE', 'Release due date')), + archived: z + .boolean() + .optional() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_ARCHIVED', 'Archive status of the version')), +})) + +export const updateVersionMilestoneTool = ( + backlog: Backlog, + { t }: TranslationHelper +): ToolDefinition< + ReturnType, + (typeof VersionSchema)['shape'] +> => { + return { + name: 'update_version_milestone', + description: t( + 'TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', + 'Updates an existing version milestone' + ), + schema: z.object(updateVersionMilestoneSchema(t)), + outputSchema: VersionSchema, + importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate', 'archived'], + handler: async ({ projectId, projectKey, id, ...params }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + if (!id) { + throw new Error(t('TOOL_UPDATE_VERSION_MILESTONE_ID_REQUIRED', 'Version ID is required')); + } + return backlog.patchVersions(result.value, id, params); + }, + }; +} \ No newline at end of file From 5ad532c951cf2c354d5d7acf19c013fc0f5b253d Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Sat, 9 Aug 2025 19:14:16 +0900 Subject: [PATCH 5/8] feat(tools): add delete version milestone tool with tests --- src/tools/deleteVersion.test.ts | 56 ++++++++++++++++++++++++++ src/tools/deleteVersion.ts | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/tools/deleteVersion.test.ts create mode 100644 src/tools/deleteVersion.ts diff --git a/src/tools/deleteVersion.test.ts b/src/tools/deleteVersion.test.ts new file mode 100644 index 0000000..f449563 --- /dev/null +++ b/src/tools/deleteVersion.test.ts @@ -0,0 +1,56 @@ +import { deleteVersionTool } from './deleteVersion.js'; +import { jest, describe, it, expect } from '@jest/globals'; +import type { Backlog } from 'backlog-js'; +import { createTranslationHelper } from '../createTranslationHelper.js'; + +describe('deleteVersionTool', () => { + const mockBacklog: Partial = { + deleteVersions: jest.fn<() => Promise>().mockResolvedValue({ + id: 1, + projectId: 100, + name: 'Test Version', + description: '', + startDate: null, + releaseDueDate: null, + archived: false, + displayOrder: 0, + }), + }; + + const mockTranslationHelper = createTranslationHelper(); + const tool = deleteVersionTool(mockBacklog as Backlog, mockTranslationHelper); + + it('returns deleted version information', async () => { + const result = await tool.handler({ + projectKey: 'TEST', + id: 1, + }); + + expect(result).toHaveProperty('id', 1); + expect(result).toHaveProperty('name', 'Test Version'); + }); + + it('calls backlog.deleteVersions with correct params when using project key', async () => { + await tool.handler({ + projectKey: 'TEST', + id: 1, + }); + + expect(mockBacklog.deleteVersions).toHaveBeenCalledWith('TEST', 1); + }); + + it('calls backlog.deleteVersions with correct params when using project ID', async () => { + await tool.handler({ + projectId: 100, + id: 1, + }); + + expect(mockBacklog.deleteVersions).toHaveBeenCalledWith(100, 1); + }); + + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = { id: 1 }; // No identifier provided + + await expect(tool.handler(params)).rejects.toThrowError(Error); + }); +}); diff --git a/src/tools/deleteVersion.ts b/src/tools/deleteVersion.ts new file mode 100644 index 0000000..40e8b2f --- /dev/null +++ b/src/tools/deleteVersion.ts @@ -0,0 +1,69 @@ +import { z } from 'zod'; +import { Backlog } from 'backlog-js'; +import { buildToolSchema, ToolDefinition } from '../types/tool.js'; +import { TranslationHelper } from '../createTranslationHelper.js'; +import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; +import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; + +const deleteVersionSchema = buildToolSchema((t) => ({ + projectId: z + .number() + .optional() + .describe( + t( + 'TOOL_DELETE_VERSION_PROJECT_ID', + 'The numeric ID of the project (e.g., 12345)' + ) + ), + projectKey: z + .string() + .optional() + .describe( + t( + 'TOOL_DELETE_VERSION_PROJECT_KEY', + "The key of the project (e.g., 'PROJECT')" + ) + ), + id: z + .number() + .describe( + t( + 'TOOL_DELETE_VERSION_ID', + 'The numeric ID of the version to delete (e.g., 67890)' + ) + ), +})); + +export const deleteVersionTool = ( + backlog: Backlog, + { t }: TranslationHelper +): ToolDefinition< + ReturnType, + (typeof VersionSchema)['shape'] +> => { + return { + name: 'delete_version', + description: t( + 'TOOL_DELETE_VERSION_DESCRIPTION', + 'Deletes a version from a project' + ), + schema: z.object(deleteVersionSchema(t)), + outputSchema: VersionSchema, + handler: async ({ projectId, projectKey, id }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + if (!id) { + throw new Error( + t('TOOL_DELETE_VERSION_MISSING_ID', 'Version ID is required') + ); + } + return backlog.deleteVersions(result.value, id); + }, + }; +}; From 98da32327918f87349297217091283abaa785c31 Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Sat, 9 Aug 2025 19:22:00 +0900 Subject: [PATCH 6/8] refactor(tools): apply formatting to version milestone tools. - src/tools/addVersionMilestone.test.ts - src/tools/addVersionMilestone.ts - src/tools/getVersionMilestoneList.test.ts - src/tools/getVersionMilestoneList.ts - src/tools/updateVersionMilestone.test.ts - src/tools/updateVersionMilestone.ts --- src/tools/addVersionMilestone.test.ts | 160 +++++++++--------- src/tools/addVersionMilestone.ts | 108 +++++++----- src/tools/getVersionMilestoneList.test.ts | 127 +++++++------- src/tools/getVersionMilestoneList.ts | 96 ++++++----- src/tools/updateVersionMilestone.test.ts | 192 +++++++++++----------- src/tools/updateVersionMilestone.ts | 159 ++++++++++-------- 6 files changed, 440 insertions(+), 402 deletions(-) diff --git a/src/tools/addVersionMilestone.test.ts b/src/tools/addVersionMilestone.test.ts index 25f4042..c99e4e8 100644 --- a/src/tools/addVersionMilestone.test.ts +++ b/src/tools/addVersionMilestone.test.ts @@ -4,101 +4,101 @@ import type { Backlog } from 'backlog-js'; import { createTranslationHelper } from '../createTranslationHelper.js'; describe('addVersionMilestoneTool', () => { - const mockBacklog: Partial = { - postVersions: jest.fn<() => Promise>().mockResolvedValue({ - id: 1, - projectId: 100, - name: 'Version 1.0.0', - description: 'Initial release version', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-03-31T00:00:00Z', - archived: false, - displayOrder: 1, - }), - }; - - const mockTranslationHelper = createTranslationHelper(); - const tool = addVersionMilestoneTool( - mockBacklog as Backlog, - mockTranslationHelper - ); + const mockBacklog: Partial = { + postVersions: jest.fn<() => Promise>().mockResolvedValue({ + id: 1, + projectId: 100, + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-03-31T00:00:00Z', + archived: false, + displayOrder: 1, + }), + }; - it('returns created version milestone as formatted JSON text', async () => { - const result = await tool.handler({ - projectKey: 'TEST', - name: 'Version 1.0.0', - description: 'Initial release version', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-03-31T00:00:00Z', - }); + const mockTranslationHelper = createTranslationHelper(); + const tool = addVersionMilestoneTool( + mockBacklog as Backlog, + mockTranslationHelper + ); - if (Array.isArray(result)) { - throw new Error('Unexpected array result'); - } - expect(result.name).toEqual('Version 1.0.0'); - expect(result.description).toEqual('Initial release version'); - expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); - expect(result.releaseDueDate).toEqual('2023-03-31T00:00:00Z'); + it('returns created version milestone as formatted JSON text', async () => { + const result = await tool.handler({ + projectKey: 'TEST', + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-03-31T00:00:00Z', }); - it('calls backlog.postVersions with correct params when using projectKey', async () => { - const params = { - projectKey: 'TEST', - name: 'Version 1.0.0', - description: 'Initial release version', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2024-03-31T00:00:00Z', - }; + if (Array.isArray(result)) { + throw new Error('Unexpected array result'); + } + expect(result.name).toEqual('Version 1.0.0'); + expect(result.description).toEqual('Initial release version'); + expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); + expect(result.releaseDueDate).toEqual('2023-03-31T00:00:00Z'); + }); + + it('calls backlog.postVersions with correct params when using projectKey', async () => { + const params = { + projectKey: 'TEST', + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2024-03-31T00:00:00Z', + }; - await tool.handler(params); + await tool.handler(params); - expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { - name: 'Version 1.0.0', - description: 'Initial release version', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2024-03-31T00:00:00Z', - }); + expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { + name: 'Version 1.0.0', + description: 'Initial release version', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2024-03-31T00:00:00Z', }); + }); - it('calls backlog.postVersions with correct params when using projectId', async () => { - const params = { - projectId: 100, - name: 'Version 2.0.0', - description: 'Major release', - startDate: '2023-04-01T00:00:00Z', - releaseDueDate: '2023-06-30T00:00:00Z', - }; + it('calls backlog.postVersions with correct params when using projectId', async () => { + const params = { + projectId: 100, + name: 'Version 2.0.0', + description: 'Major release', + startDate: '2023-04-01T00:00:00Z', + releaseDueDate: '2023-06-30T00:00:00Z', + }; - await tool.handler(params); + await tool.handler(params); - expect(mockBacklog.postVersions).toHaveBeenCalledWith(100, { - name: 'Version 2.0.0', - description: 'Major release', - startDate: '2023-04-01T00:00:00Z', - releaseDueDate: '2023-06-30T00:00:00Z', - }); + expect(mockBacklog.postVersions).toHaveBeenCalledWith(100, { + name: 'Version 2.0.0', + description: 'Major release', + startDate: '2023-04-01T00:00:00Z', + releaseDueDate: '2023-06-30T00:00:00Z', }); + }); - it('calls backlog.postVersions with minimal required params', async () => { - const params = { - projectKey: 'TEST', - name: 'Quick Version', - }; + it('calls backlog.postVersions with minimal required params', async () => { + const params = { + projectKey: 'TEST', + name: 'Quick Version', + }; - await tool.handler(params); + await tool.handler(params); - expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { - name: 'Quick Version', - }); + expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', { + name: 'Quick Version', }); + }); - it('throws an error if neither projectId nor projectKey is provided', async () => { - const params = { - // projectId and projectKey are missing - name: 'Version without project', - description: 'This should fail', - }; + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = { + // projectId and projectKey are missing + name: 'Version without project', + description: 'This should fail', + }; - await expect(tool.handler(params as any)).rejects.toThrow(Error); - }); + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); }); diff --git a/src/tools/addVersionMilestone.ts b/src/tools/addVersionMilestone.ts index 63803c9..3db5e86 100644 --- a/src/tools/addVersionMilestone.ts +++ b/src/tools/addVersionMilestone.ts @@ -6,52 +6,72 @@ import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; const addVersionMilestoneSchema = buildToolSchema((t) => ({ - projectKey: z - .string() - .optional() - .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_KEY', 'Project key')), - projectId: z - .number() - .optional() - .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_ID', 'Project ID')), - name: z.string().describe(t('TOOL_ADD_VERSION_MILESTONE_NAME', 'Version name')), - description: z - .string() - .optional() - .describe(t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Version description')), - startDate: z - .string() - .optional() - .describe(t('TOOL_ADD_VERSION_MILESTONE_START_DATE', 'Start date of the version')), - releaseDueDate: z - .string() - .optional() - .describe(t('TOOL_ADD_VERSION_MILESTONE_RELEASE_DUE_DATE', 'Release due date of the version')) + projectId: z + .number() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_ID', 'Project ID')), + projectKey: z + .string() + .optional() + .describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_KEY', 'Project key')), + name: z + .string() + .describe(t('TOOL_ADD_VERSION_MILESTONE_NAME', 'Version name')), + description: z + .string() + .optional() + .describe( + t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Version description') + ), + startDate: z + .string() + .optional() + .describe( + t('TOOL_ADD_VERSION_MILESTONE_START_DATE', 'Start date of the version') + ), + releaseDueDate: z + .string() + .optional() + .describe( + t( + 'TOOL_ADD_VERSION_MILESTONE_RELEASE_DUE_DATE', + 'Release due date of the version' + ) + ), })); export const addVersionMilestoneTool = ( - backlog: Backlog, - { t }: TranslationHelper + backlog: Backlog, + { t }: TranslationHelper ): ToolDefinition< - ReturnType, - (typeof VersionSchema)['shape'] + ReturnType, + (typeof VersionSchema)['shape'] > => { - return { - name: 'add_version_milestone', - description: t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Creates a new version milestone'), - schema: z.object(addVersionMilestoneSchema(t)), - outputSchema: VersionSchema, - importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate'], - handler: async ({ projectId, projectKey, ...params }) => { - const result = resolveIdOrKey( - 'project', - { id: projectId, key: projectKey }, - t - ); - if (!result.ok) { - throw result.error; - } - return backlog.postVersions(result.value, params) - } - }; -} \ No newline at end of file + return { + name: 'add_version_milestone', + description: t( + 'TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', + 'Creates a new version milestone' + ), + schema: z.object(addVersionMilestoneSchema(t)), + outputSchema: VersionSchema, + importantFields: [ + 'id', + 'name', + 'description', + 'startDate', + 'releaseDueDate', + ], + handler: async ({ projectId, projectKey, ...params }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + return backlog.postVersions(result.value, params); + }, + }; +}; diff --git a/src/tools/getVersionMilestoneList.test.ts b/src/tools/getVersionMilestoneList.test.ts index fb8ebfc..298b966 100644 --- a/src/tools/getVersionMilestoneList.test.ts +++ b/src/tools/getVersionMilestoneList.test.ts @@ -4,78 +4,79 @@ import type { Backlog } from 'backlog-js'; import { createTranslationHelper } from '../createTranslationHelper.js'; describe('getVersionMilestoneTool', () => { - const mockBacklog: Partial = { - getVersions: jest.fn<() => Promise>().mockResolvedValue([ - { - id: 1, - projectId: 1, - name: "wait for release", - description: "", - startDate: null, - releaseDueDate: null, - archived: false, - displayOrder: 0 - }, - { - id: 2, - projectId: 1, - name: "v1.0.0", - description: "First release", - startDate: "2025-01-01", - releaseDueDate: "2025-03-01", - archived: false, - displayOrder: 1 - }, - { - id: 3, - projectId: 1, - name: "v1.1.0", - description: "Minor update", - startDate: "2025-03-01", - releaseDueDate: "2025-05-01", - archived: false, - displayOrder: 2 - } - ]), - }; + const mockBacklog: Partial = { + getVersions: jest.fn<() => Promise>().mockResolvedValue([ + { + id: 1, + projectId: 1, + name: 'wait for release', + description: '', + startDate: null, + releaseDueDate: null, + archived: false, + displayOrder: 0, + }, + { + id: 2, + projectId: 1, + name: 'v1.0.0', + description: 'First release', + startDate: '2025-01-01', + releaseDueDate: '2025-03-01', + archived: false, + displayOrder: 1, + }, + { + id: 3, + projectId: 1, + name: 'v1.1.0', + description: 'Minor update', + startDate: '2025-03-01', + releaseDueDate: '2025-05-01', + archived: false, + displayOrder: 2, + }, + ]), + }; + const mockTranslationHelper = createTranslationHelper(); + const tool = getVersionMilestoneListTool( + mockBacklog as Backlog, + mockTranslationHelper + ); - const mockTranslationHelper = createTranslationHelper(); - const tool = getVersionMilestoneListTool(mockBacklog as Backlog, mockTranslationHelper); + it('returns versions list as formatted JSON text', async () => { + const result = await tool.handler({ projectId: 123 }); - it('returns versions list as formatted JSON text', async () => { - const result = await tool.handler({ projectId: 123 }); + if (!Array.isArray(result)) { + throw new Error('Unexpected non array result'); + } - if (!Array.isArray(result)) { - throw new Error('Unexpected non array result'); - } + expect(result).toHaveLength(3); + expect(result[0].name).toContain('wait for release'); + expect(result[1].name).toContain('v1.0.0'); + expect(result[2].name).toContain('v1.1.0'); + }); - expect(result).toHaveLength(3); - expect(result[0].name).toContain('wait for release'); - expect(result[1].name).toContain('v1.0.0'); - expect(result[2].name).toContain('v1.1.0'); + it('calls backlog.getVersions with correct params when using project key', async () => { + await tool.handler({ + projectKey: 'TEST_PROJECT', }); - it('calls backlog.getVersions with correct params when using project key', async () => { - await tool.handler({ - projectKey: 'TEST_PROJECT', - }); + expect(mockBacklog.getVersions).toHaveBeenCalledWith('TEST_PROJECT'); + }); - expect(mockBacklog.getVersions).toHaveBeenCalledWith('TEST_PROJECT'); + it('calls backlog.getVersions with correct params when using project ID', async () => { + await tool.handler({ + projectId: 123, }); - it('calls backlog.getVersions with correct params when using project ID', async () => { - await tool.handler({ - projectId: 123, - }); + expect(mockBacklog.getVersions).toHaveBeenCalledWith(123); + }); - expect(mockBacklog.getVersions).toHaveBeenCalledWith(123); - }); - - it('throws an error if neither projectId nor projectKey is provided', async () => { - const params = {}; // No identifier provided - - await expect(tool.handler(params as any)).rejects.toThrow(Error); - }); + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = {}; // No identifier provided -}); + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); +}); diff --git a/src/tools/getVersionMilestoneList.ts b/src/tools/getVersionMilestoneList.ts index 2a164c4..70a102b 100644 --- a/src/tools/getVersionMilestoneList.ts +++ b/src/tools/getVersionMilestoneList.ts @@ -5,54 +5,60 @@ import { TranslationHelper } from '../createTranslationHelper.js'; import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; - const getVersionMilestoneListSchema = buildToolSchema((t) => ({ - projectId: z - .number() - .optional() - .describe( - t( - 'TOOL_GET_VERSION_MILESTONE_PROJECT_ID', - 'The numeric ID of the project (e.g., 12345)' - ) - ), - projectKey: z - .string() - .optional() - .describe( - t( - 'TOOL_GET_VERSION_MILESTONE_PROJECT_KEY', - 'The key of the project (e.g., TEST_PROJECT)' - ) - ), + projectId: z + .number() + .optional() + .describe( + t( + 'TOOL_GET_VERSION_MILESTONE_PROJECT_ID', + 'The numeric ID of the project (e.g., 12345)' + ) + ), + projectKey: z + .string() + .optional() + .describe( + t( + 'TOOL_GET_VERSION_MILESTONE_PROJECT_KEY', + 'The key of the project (e.g., TEST_PROJECT)' + ) + ), })); export const getVersionMilestoneListTool = ( - backlog: Backlog, - { t }: TranslationHelper + backlog: Backlog, + { t }: TranslationHelper ): ToolDefinition< - ReturnType, - (typeof VersionSchema)['shape'] + ReturnType, + (typeof VersionSchema)['shape'] > => { - return { - name: 'get_version_milestone_list', - description: t( - 'TOOL_GET_VERSION_MILESTONE_LIST_DESCRIPTION', - 'Returns list of versions/milestones in the Backlog space' - ), - schema: z.object(getVersionMilestoneListSchema(t)), - outputSchema: VersionSchema, - importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate', 'archived'], - handler: async ({ projectId, projectKey }) => { - const result = resolveIdOrKey( - 'project', - { id: projectId, key: projectKey }, - t - ); - if (!result.ok) { - throw result.error; - } - return backlog.getVersions(result.value); - }, - }; -} \ No newline at end of file + return { + name: 'get_version_milestone_list', + description: t( + 'TOOL_GET_VERSION_MILESTONE_LIST_DESCRIPTION', + 'Returns list of versions/milestones in the Backlog space' + ), + schema: z.object(getVersionMilestoneListSchema(t)), + outputSchema: VersionSchema, + importantFields: [ + 'id', + 'name', + 'description', + 'startDate', + 'releaseDueDate', + 'archived', + ], + handler: async ({ projectId, projectKey }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + return backlog.getVersions(result.value); + }, + }; +}; diff --git a/src/tools/updateVersionMilestone.test.ts b/src/tools/updateVersionMilestone.test.ts index 26546ec..74475e1 100644 --- a/src/tools/updateVersionMilestone.test.ts +++ b/src/tools/updateVersionMilestone.test.ts @@ -4,120 +4,112 @@ import type { Backlog } from 'backlog-js'; import { createTranslationHelper } from '../createTranslationHelper.js'; describe('updateVersionMilestoneTool', () => { - const mockBacklog: Partial = { - patchVersions: jest.fn<() => Promise>().mockResolvedValue({ - id: 1, - projectId: 100, - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - }), - }; + const mockBacklog: Partial = { + patchVersions: jest.fn<() => Promise>().mockResolvedValue({ + id: 1, + projectId: 100, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }), + }; - const mockTranslationHelper = createTranslationHelper(); - const tool = updateVersionMilestoneTool( - mockBacklog as Backlog, - mockTranslationHelper - ); + const mockTranslationHelper = createTranslationHelper(); + const tool = updateVersionMilestoneTool( + mockBacklog as Backlog, + mockTranslationHelper + ); - it('returns updated version milestone', async () => { - const result = await tool.handler({ - projectKey: 'TEST', - projectId: 100, - id: 1, - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - }); + it('returns updated version milestone', async () => { + const result = await tool.handler({ + projectKey: 'TEST', + projectId: 100, + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }); - if (Array.isArray(result)) { - throw new Error('Unexpected array result'); - } + if (Array.isArray(result)) { + throw new Error('Unexpected array result'); + } - expect(result.name).toEqual('Updated Version'); - expect(result.description).toEqual('Updated version description'); - expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); - expect(result.releaseDueDate).toEqual('2023-12-31T00:00:00Z'); - expect(result.archived).toBe(false); - }); + expect(result.name).toEqual('Updated Version'); + expect(result.description).toEqual('Updated version description'); + expect(result.startDate).toEqual('2023-01-01T00:00:00Z'); + expect(result.releaseDueDate).toEqual('2023-12-31T00:00:00Z'); + expect(result.archived).toBe(false); + }); - it('calls backlog.patchVersions with correct params when using projectKey', async () => { - const params = { - projectKey: 'TEST', - id: 1, - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - }; + it('calls backlog.patchVersions with correct params when using projectKey', async () => { + const params = { + projectKey: 'TEST', + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }; - await tool.handler(params); + await tool.handler(params); - expect(mockBacklog.patchVersions).toHaveBeenCalledWith( - 'TEST', - 1, - { - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - } - ); + expect(mockBacklog.patchVersions).toHaveBeenCalledWith('TEST', 1, { + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, }); + }); - it('calls backlog.pathVersions with correct params when using projectId', async () => { - const params = { - projectId: 100, - id: 1, - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - }; + it('calls backlog.pathVersions with correct params when using projectId', async () => { + const params = { + projectId: 100, + id: 1, + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, + }; - await tool.handler(params); + await tool.handler(params); - expect(mockBacklog.patchVersions).toHaveBeenCalledWith( - 100, - 1, - { - name: 'Updated Version', - description: 'Updated version description', - startDate: '2023-01-01T00:00:00Z', - releaseDueDate: '2023-12-31T00:00:00Z', - archived: false, - } - ); + expect(mockBacklog.patchVersions).toHaveBeenCalledWith(100, 1, { + name: 'Updated Version', + description: 'Updated version description', + startDate: '2023-01-01T00:00:00Z', + releaseDueDate: '2023-12-31T00:00:00Z', + archived: false, }); + }); - it('throws an error if neither projectId nor projectKey is provided', async () => { - const params = { - // projectId and projectKey are missing - id: 1, - name: 'Version without project', - description: 'This should fail', - }; + it('throws an error if neither projectId nor projectKey is provided', async () => { + const params = { + // projectId and projectKey are missing + id: 1, + name: 'Version without project', + description: 'This should fail', + }; - await expect(tool.handler(params as any)).rejects.toThrow(Error); - }); + await expect(tool.handler(params as any)).rejects.toThrow(Error); + }); - it('throws an error if id is not provided', async () => { - const params = { - projectKey: 'TEST', - // id is missing - name: 'Version without ID', - description: 'This should fail', - }; + it('throws an error if id is not provided', async () => { + const params = { + projectKey: 'TEST', + // id is missing + name: 'Version without ID', + description: 'This should fail', + }; - await expect(tool.handler(params as any)).rejects.toThrow( - 'Version ID is required' - ); - }); + await expect(tool.handler(params as any)).rejects.toThrow( + 'Version ID is required' + ); + }); }); diff --git a/src/tools/updateVersionMilestone.ts b/src/tools/updateVersionMilestone.ts index 3c7f930..e4f6e6a 100644 --- a/src/tools/updateVersionMilestone.ts +++ b/src/tools/updateVersionMilestone.ts @@ -6,77 +6,96 @@ import { VersionSchema } from '../types/zod/backlogOutputDefinition.js'; import { resolveIdOrKey } from '../utils/resolveIdOrKey.js'; const updateVersionMilestoneSchema = buildToolSchema((t) => ({ - projectId: z - .number() - .optional() - .describe( - t( - 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_ID', - 'The numeric ID of the project (e.g., 12345)' - ) - ), - projectKey: z - .string() - .optional() - .describe( - t( - 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_KEY', - "The key of the project (e.g., 'PROJECT')" - ) - ), - id: z - .number() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_ID', 'Version ID')), - name: z - .string() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_NAME', 'Version name')), - description: z - .string() - .optional() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', 'Version description')), - startDate: z - .string() - .optional() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_START_DATE', 'Start date')), - releaseDueDate: z - .string() - .optional() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_RELEASE_DUE_DATE', 'Release due date')), - archived: z - .boolean() - .optional() - .describe(t('TOOL_UPDATE_VERSION_MILESTONE_ARCHIVED', 'Archive status of the version')), -})) + projectId: z + .number() + .optional() + .describe( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_ID', + 'The numeric ID of the project (e.g., 12345)' + ) + ), + projectKey: z + .string() + .optional() + .describe( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_PROJECT_KEY', + "The key of the project (e.g., 'PROJECT')" + ) + ), + id: z.number().describe(t('TOOL_UPDATE_VERSION_MILESTONE_ID', 'Version ID')), + name: z + .string() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_NAME', 'Version name')), + description: z + .string() + .optional() + .describe( + t('TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', 'Version description') + ), + startDate: z + .string() + .optional() + .describe(t('TOOL_UPDATE_VERSION_MILESTONE_START_DATE', 'Start date')), + releaseDueDate: z + .string() + .optional() + .describe( + t('TOOL_UPDATE_VERSION_MILESTONE_RELEASE_DUE_DATE', 'Release due date') + ), + archived: z + .boolean() + .optional() + .describe( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_ARCHIVED', + 'Archive status of the version' + ) + ), +})); export const updateVersionMilestoneTool = ( - backlog: Backlog, - { t }: TranslationHelper + backlog: Backlog, + { t }: TranslationHelper ): ToolDefinition< - ReturnType, - (typeof VersionSchema)['shape'] + ReturnType, + (typeof VersionSchema)['shape'] > => { - return { - name: 'update_version_milestone', - description: t( - 'TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', - 'Updates an existing version milestone' - ), - schema: z.object(updateVersionMilestoneSchema(t)), - outputSchema: VersionSchema, - importantFields: ['id', 'name', 'description', 'startDate', 'releaseDueDate', 'archived'], - handler: async ({ projectId, projectKey, id, ...params }) => { - const result = resolveIdOrKey( - 'project', - { id: projectId, key: projectKey }, - t - ); - if (!result.ok) { - throw result.error; - } - if (!id) { - throw new Error(t('TOOL_UPDATE_VERSION_MILESTONE_ID_REQUIRED', 'Version ID is required')); - } - return backlog.patchVersions(result.value, id, params); - }, - }; -} \ No newline at end of file + return { + name: 'update_version_milestone', + description: t( + 'TOOL_UPDATE_VERSION_MILESTONE_DESCRIPTION', + 'Updates an existing version milestone' + ), + schema: z.object(updateVersionMilestoneSchema(t)), + outputSchema: VersionSchema, + importantFields: [ + 'id', + 'name', + 'description', + 'startDate', + 'releaseDueDate', + 'archived', + ], + handler: async ({ projectId, projectKey, id, ...params }) => { + const result = resolveIdOrKey( + 'project', + { id: projectId, key: projectKey }, + t + ); + if (!result.ok) { + throw result.error; + } + if (!id) { + throw new Error( + t( + 'TOOL_UPDATE_VERSION_MILESTONE_ID_REQUIRED', + 'Version ID is required' + ) + ); + } + return backlog.patchVersions(result.value, id, params); + }, + }; +}; From 3a321be4e4c5d505c4d7a511021c0570d8c3350c Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Sat, 9 Aug 2025 19:25:33 +0900 Subject: [PATCH 7/8] feat(tools): register version milestone toolset in allTools --- src/tools/tools.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tools/tools.ts b/src/tools/tools.ts index 377fc5d..dd3b42e 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -45,6 +45,10 @@ import { updatePullRequestCommentTool } from './updatePullRequestComment.js'; import { getDocumentTool } from './getDocument.js'; import { getDocumentsTool } from './getDocuments.js'; import { getDocumentTreeTool } from './getDocumentTree.js'; +import { getVersionMilestoneListTool } from './getVersionMilestoneList.js'; +import { addVersionMilestoneTool } from './addVersionMilestone.js'; +import { updateVersionMilestoneTool } from './updateVersionMilestone.js'; +import { deleteVersionTool } from './deleteVersion.js'; export const allTools = ( backlog: Backlog, @@ -147,6 +151,17 @@ export const allTools = ( markNotificationAsReadTool(backlog, helper), ], }, + { + name: 'version_milestone', + description: 'Tools for managing version milestones in projects.', + enabled: false, + tools: [ + getVersionMilestoneListTool(backlog, helper), + addVersionMilestoneTool(backlog, helper), + updateVersionMilestoneTool(backlog, helper), + deleteVersionTool(backlog, helper), + ], + }, ], }; }; From 02d7db0db0455d67e258706a3aa61a45cf15ffba Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Sat, 9 Aug 2025 19:35:03 +0900 Subject: [PATCH 8/8] docs(progress): add version/milestone tools. --- memory-bank/progress.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 405971c..9d2edcf 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -57,6 +57,12 @@ - ✅ Adding pull request comments (`add_pull_request_comment`) - ✅ Updating pull request comments (`update_pull_request_comment`) +### Version/Milestone-related +- ✅ Retrieving version/milestone lists (`get_version_milestone_list`) +- ✅ Adding versions/milestones (`add_version_milestone`) +- ✅ Updating versions/milestones (`update_version_milestone`) +- ✅ Deleting versions (`delete_version`) + ### Watch-related - ✅ Retrieving watched item lists (`get_watching_list_items`) - ✅ Retrieving watch counts (`get_watching_list_count`) @@ -131,10 +137,6 @@ - ❌ Adding categories (`add_category`) - ❌ Updating categories (`update_category`) - ❌ Deleting categories (`delete_category`) -- ❌ Retrieving version/milestone lists (`get_version_milestone_list`) -- ❌ Adding versions/milestones (`add_version_milestone`) -- ❌ Updating versions/milestones (`update_version_milestone`) -- ❌ Deleting versions (`delete_version`) - ❌ Retrieving custom field lists (`get_custom_field_list`) - ❌ Adding custom fields (`add_custom_field`) - ❌ Updating custom fields (`update_custom_field`) @@ -218,7 +220,6 @@ This allows access to Backlog's main features from Claude, with optimizations fo 2. **Medium-term Goals** - Custom field-related features - - Version/milestone-related features - Webhook-related features - Further performance optimizations