diff --git a/src/index.ts b/src/index.ts index ed72131..f0a8968 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,9 @@ import { registerListProductsTool } from './tools/list-products.js' import { registerGetClaudeContextTool } from './tools/get-claude-context.js' import { registerUpdateTaskStatusTool } from './tools/update-task-status.js' import { registerUpdateTaskPlanTool } from './tools/update-task-plan.js' +import { registerLogImplementationTool } from './tools/log-implementation.js' +import { registerLogTestResultTool } from './tools/log-test-result.js' +import { registerLogCommitTool } from './tools/log-commit.js' const VERSION = '0.1.0' @@ -24,7 +27,10 @@ async function main() { registerGetClaudeContextTool(server) registerUpdateTaskStatusTool(server) registerUpdateTaskPlanTool(server) - // Log tools, create_todo and prompts in ST-707..ST-709. + registerLogImplementationTool(server) + registerLogTestResultTool(server) + registerLogCommitTool(server) + // create_todo and prompts in ST-708..ST-709. const transport = new StdioServerTransport() await server.connect(transport) diff --git a/src/tools/log-commit.ts b/src/tools/log-commit.ts new file mode 100644 index 0000000..01e86b0 --- /dev/null +++ b/src/tools/log-commit.ts @@ -0,0 +1,45 @@ +import { z } from 'zod' +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { prisma } from '../prisma.js' +import { requireWriteAccess } from '../auth.js' +import { userCanAccessStory } from '../access.js' +import { toolError, toolJson, withToolErrors } from '../errors.js' + +const inputSchema = z.object({ + story_id: z.string().min(1), + content: z.string().min(1), + commit_hash: z.string().min(1), + commit_message: z.string().min(1), + metadata: z.record(z.string(), z.unknown()).optional(), +}) + +export function registerLogCommitTool(server: McpServer) { + server.registerTool( + 'log_commit', + { + title: 'Log commit', + description: + 'Append a COMMIT entry to a story log with hash and message. ' + + 'Forbidden for demo accounts.', + inputSchema, + }, + async ({ story_id, content, commit_hash, commit_message }) => + withToolErrors(async () => { + const auth = await requireWriteAccess() + if (!(await userCanAccessStory(story_id, auth.userId))) { + return toolError(`Story ${story_id} not found or not accessible`) + } + const log = await prisma.storyLog.create({ + data: { + story_id, + type: 'COMMIT', + content, + commit_hash, + commit_message, + }, + select: { id: true, created_at: true }, + }) + return toolJson({ id: log.id, created_at: log.created_at }) + }), + ) +} diff --git a/src/tools/log-implementation.ts b/src/tools/log-implementation.ts new file mode 100644 index 0000000..f12de38 --- /dev/null +++ b/src/tools/log-implementation.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { prisma } from '../prisma.js' +import { requireWriteAccess } from '../auth.js' +import { userCanAccessStory } from '../access.js' +import { toolError, toolJson, withToolErrors } from '../errors.js' + +// metadata is accepted on input but not yet written — schema sync after +// Scrum4Me PR #2 lands adds the StoryLog.metadata JSONB column. +const inputSchema = z.object({ + story_id: z.string().min(1), + content: z.string().min(1), + metadata: z.record(z.string(), z.unknown()).optional(), +}) + +export function registerLogImplementationTool(server: McpServer) { + server.registerTool( + 'log_implementation', + { + title: 'Log implementation plan', + description: + 'Append an IMPLEMENTATION_PLAN entry to a story log. ' + + 'Forbidden for demo accounts.', + inputSchema, + }, + async ({ story_id, content }) => + withToolErrors(async () => { + const auth = await requireWriteAccess() + if (!(await userCanAccessStory(story_id, auth.userId))) { + return toolError(`Story ${story_id} not found or not accessible`) + } + const log = await prisma.storyLog.create({ + data: { + story_id, + type: 'IMPLEMENTATION_PLAN', + content, + }, + select: { id: true, created_at: true }, + }) + return toolJson({ id: log.id, created_at: log.created_at }) + }), + ) +} diff --git a/src/tools/log-test-result.ts b/src/tools/log-test-result.ts new file mode 100644 index 0000000..d6eaa05 --- /dev/null +++ b/src/tools/log-test-result.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { prisma } from '../prisma.js' +import { requireWriteAccess } from '../auth.js' +import { userCanAccessStory } from '../access.js' +import { toolError, toolJson, withToolErrors } from '../errors.js' + +const inputSchema = z.object({ + story_id: z.string().min(1), + content: z.string().min(1), + status: z.enum(['PASSED', 'FAILED']), + metadata: z.record(z.string(), z.unknown()).optional(), +}) + +export function registerLogTestResultTool(server: McpServer) { + server.registerTool( + 'log_test_result', + { + title: 'Log test result', + description: + 'Append a TEST_RESULT entry (PASSED or FAILED) to a story log. ' + + 'Forbidden for demo accounts.', + inputSchema, + }, + async ({ story_id, content, status }) => + withToolErrors(async () => { + const auth = await requireWriteAccess() + if (!(await userCanAccessStory(story_id, auth.userId))) { + return toolError(`Story ${story_id} not found or not accessible`) + } + const log = await prisma.storyLog.create({ + data: { + story_id, + type: 'TEST_RESULT', + content, + status, + }, + select: { id: true, created_at: true }, + }) + return toolJson({ id: log.id, created_at: log.created_at }) + }), + ) +}