From 80da968bbde0e0056ba476df1530ecdadcbff48d Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Tue, 5 Aug 2025 23:49:18 +0900 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 From dbabc36069dfea4cd82380d24dc989b57495355d Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Thu, 21 Aug 2025 06:48:55 +0900 Subject: [PATCH 09/11] refactor(tools): move version milestone tool to issue. --- src/tools/tools.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/tools/tools.ts b/src/tools/tools.ts index dd3b42e..68d055f 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -100,6 +100,10 @@ export const allTools = ( getResolutionsTool(backlog, helper), getWatchingListItemsTool(backlog, helper), getWatchingListCountTool(backlog, helper), + getVersionMilestoneListTool(backlog, helper), + addVersionMilestoneTool(backlog, helper), + updateVersionMilestoneTool(backlog, helper), + deleteVersionTool(backlog, helper), ], }, { @@ -151,17 +155,6 @@ 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 57e9d76aa14dd70a3ab9358ecad0cb85b3759a72 Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Thu, 21 Aug 2025 06:51:05 +0900 Subject: [PATCH 10/11] fix(tools): remove unnecessary id verification checks. - id parameters are already specified as required in the schema. --- src/tools/updateVersionMilestone.test.ts | 13 ------------- src/tools/updateVersionMilestone.ts | 8 -------- 2 files changed, 21 deletions(-) diff --git a/src/tools/updateVersionMilestone.test.ts b/src/tools/updateVersionMilestone.test.ts index 74475e1..7cbe88a 100644 --- a/src/tools/updateVersionMilestone.test.ts +++ b/src/tools/updateVersionMilestone.test.ts @@ -99,17 +99,4 @@ describe('updateVersionMilestoneTool', () => { 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 index e4f6e6a..b2e5c69 100644 --- a/src/tools/updateVersionMilestone.ts +++ b/src/tools/updateVersionMilestone.ts @@ -87,14 +87,6 @@ export const updateVersionMilestoneTool = ( 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 0ca6d50ea3f0b75386de2a8209c1f17a37364eff Mon Sep 17 00:00:00 2001 From: HasutoSasaki Date: Sun, 24 Aug 2025 09:06:29 +0900 Subject: [PATCH 11/11] docs(readme): add version milestone tool to project tool list. --- README.ja.md | 3 ++- README.md | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.ja.md b/README.ja.md index 4d69719..74fe079 100644 --- a/README.ja.md +++ b/README.ja.md @@ -12,6 +12,7 @@ Backlog API とやり取りするための Model Context Protocol(MCP)サー - プロジェクトツール(作成、読み取り、更新、削除) - 課題とコメントの追跡(作成、更新、削除、一覧表示) +- 発生バージョン/マイルストーンの管理(作成、読み取り、更新、削除) - Wikiページサポート - Gitリポジトリとプルリクエストツール - 通知ツール @@ -105,7 +106,7 @@ docker pull ghcr.io/nulab/backlog-mcp-server:latest |-----------------|--------------------------------------------------------------------------------------| | `space` | Backlogスペース設定と一般情報を管理するためのツール | | `project` | プロジェクト、カテゴリ、カスタムフィールド、課題タイプを管理するためのツール | -| `issue` | 課題とそのコメントを管理するためのツール | +| `issue` | 課題とそのコメント、発生バージョン/マイルストーンを管理するためのツール | | `wiki` | Wikiページを管理するためのツール | | `git` | Gitリポジトリとプルリクエストを管理するためのツール | | `notifications` | ユーザー通知を管理するためのツール | diff --git a/README.md b/README.md index f9a1c3d..278a240 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A Model Context Protocol (MCP) server for interacting with the Backlog API. This - Project tools (create, read, update, delete) - Issue tracking and comments (create, update, delete, list) +- Version/Milestone management (create, read, update, delete) - Wiki page support - Git repository and pull request tools - Notification tools @@ -105,7 +106,7 @@ The following toolsets are available (enabled by default when `"all"` is used): |-----------------|--------------------------------------------------------------------------------------| | `space` | Tools for managing Backlog space settings and general information | | `project` | Tools for managing projects, categories, custom fields, and issue types | -| `issue` | Tools for managing issues and their comments | +| `issue` | Tools for managing issues and their comments, version milestones | | `wiki` | Tools for managing wiki pages | | `git` | Tools for managing Git repositories and pull requests | | `notifications` | Tools for managing user notifications | @@ -184,6 +185,10 @@ Tools for managing issues, their comments, and related items like priorities, ca - `get_resolutions`: Returns list of issue resolutions. - `get_watching_list_items`: Returns list of watching items for a user. - `get_watching_list_count`: Returns count of watching items for a user. +- `get_version_milestone_list`: Returns list of version milestones for a project. +- `add_version_milestone`: Creates a new version milestone for a project. +- `update_version_milestone`: Updates an existing version milestone. +- `delete_version_milestone`: Deletes a version milestone. ### Toolset: `wiki` Tools for managing wiki pages.