From 870d1d356ac960ecccf2f754a9f60f875ad69025 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Thu, 14 May 2026 17:49:40 +0200 Subject: [PATCH] feat(PBI-12): code-bindende sort_order + priority uit story/taak-orderings - Voeg parseCodeNumber-helper toe in src/lib/code.ts - create-story/create-task: sort_order = parseCodeNumber(code), sort_order-inputparam verwijderd - get-claude-context + wait-for-job: priority verwijderd uit story/taak orderBy Co-Authored-By: Claude Sonnet 4.6 --- src/lib/code.ts | 10 ++++++++++ src/tools/create-story.ts | 16 +++------------- src/tools/create-task.ts | 17 ++++------------- src/tools/get-claude-context.ts | 4 ++-- src/tools/wait-for-job.ts | 4 ++-- 5 files changed, 21 insertions(+), 30 deletions(-) create mode 100644 src/lib/code.ts diff --git a/src/lib/code.ts b/src/lib/code.ts new file mode 100644 index 0000000..6b7376d --- /dev/null +++ b/src/lib/code.ts @@ -0,0 +1,10 @@ +// Sync met Scrum4Me/lib/code.ts — bewust duplicate (geen gedeeld package) +// om de MCP-server eigenstandig te houden. +// +// Extraheert het achterste getal uit een code-string (bijv. "ST-001" → 1, +// "T-42" → 42). Gebruikt als sort_order bij create_story / create_task. +export function parseCodeNumber(code: string | null | undefined): number { + if (!code) return 0 + const m = code.match(/(\d+)$/) + return m ? Number.parseInt(m[1], 10) : 0 +} diff --git a/src/tools/create-story.ts b/src/tools/create-story.ts index 37caa59..2e419d4 100644 --- a/src/tools/create-story.ts +++ b/src/tools/create-story.ts @@ -12,6 +12,7 @@ import { prisma } from '../prisma.js' import { requireWriteAccess } from '../auth.js' import { userCanAccessProduct } from '../access.js' import { toolError, toolJson, withToolErrors } from '../errors.js' +import { parseCodeNumber } from '../lib/code.js' const STORY_AUTO_RE = /^ST-(\d+)$/ const MAX_CODE_ATTEMPTS = 3 @@ -46,7 +47,6 @@ const inputSchema = z.object({ description: z.string().max(4000).optional(), acceptance_criteria: z.string().max(4000).optional(), priority: z.number().int().min(1).max(4), - sort_order: z.number().optional(), // Optionele sprint-koppeling: bij creatie de story direct aan een sprint // hangen (status=IN_SPRINT). De sprint moet bij hetzelfde product horen. sprint_id: z.string().min(1).optional(), @@ -59,7 +59,6 @@ export async function handleCreateStory( description, acceptance_criteria, priority, - sort_order, sprint_id, }: z.infer, ) { @@ -90,19 +89,10 @@ export async function handleCreateStory( } } - let resolvedSortOrder = sort_order - if (resolvedSortOrder === undefined) { - const last = await prisma.story.findFirst({ - where: { pbi_id, priority }, - orderBy: { sort_order: 'desc' }, - select: { sort_order: true }, - }) - resolvedSortOrder = (last?.sort_order ?? 0) + 1.0 - } - let lastError: unknown for (let attempt = 0; attempt < MAX_CODE_ATTEMPTS; attempt++) { const code = await generateNextStoryCode(pbi.product_id) + const resolvedSortOrder = parseCodeNumber(code) try { const story = await prisma.story.create({ data: { @@ -146,7 +136,7 @@ export function registerCreateStoryTool(server: McpServer) { { title: 'Create story', description: - 'Add a story under an existing PBI. Optionally link it to a sprint via sprint_id — when given, the story is created with status=IN_SPRINT and the sprint must belong to the same product as the PBI; otherwise status=OPEN and the story lands in the product backlog. Sort_order auto-set to last+1 within the PBI/priority group if not provided. Forbidden for demo accounts.', + 'Add a story under an existing PBI. Optionally link it to a sprint via sprint_id — when given, the story is created with status=IN_SPRINT and the sprint must belong to the same product as the PBI; otherwise status=OPEN and the story lands in the product backlog. Sort_order is derived from the auto-generated code number. Forbidden for demo accounts.', inputSchema, }, handleCreateStory, diff --git a/src/tools/create-task.ts b/src/tools/create-task.ts index 8308146..3882220 100644 --- a/src/tools/create-task.ts +++ b/src/tools/create-task.ts @@ -10,6 +10,7 @@ import { prisma } from '../prisma.js' import { requireWriteAccess } from '../auth.js' import { userCanAccessProduct } from '../access.js' import { toolError, toolJson, withToolErrors } from '../errors.js' +import { parseCodeNumber } from '../lib/code.js' const TASK_AUTO_RE = /^T-(\d+)$/ const MAX_CODE_ATTEMPTS = 3 @@ -44,7 +45,6 @@ const inputSchema = z.object({ description: z.string().max(4000).optional(), implementation_plan: z.string().max(8000).optional(), priority: z.number().int().min(1).max(4), - sort_order: z.number().optional(), // Cross-repo override: zet expliciet de repo waarop de worker deze task // moet uitvoeren (overrides product.repo_url). Gebruik dit voor PBI's die // werk in meerdere repos coördineren — bv. PBI op Scrum4Me-product met @@ -60,10 +60,10 @@ export function registerCreateTaskTool(server: McpServer) { { title: 'Create task', description: - 'Add a task under an existing story. Inherits sprint_id from the story (denormalized). Status defaults to TO_DO. Sort_order auto-set to last+1 within the story/priority group if not provided. Optional repo_url overrides the product.repo_url for cross-repo work (e.g. tasks targeting scrum4me-mcp under a Scrum4Me PBI). Forbidden for demo accounts.', + 'Add a task under an existing story. Inherits sprint_id from the story (denormalized). Status defaults to TO_DO. Sort_order is derived from the auto-generated code number. Optional repo_url overrides the product.repo_url for cross-repo work (e.g. tasks targeting scrum4me-mcp under a Scrum4Me PBI). Forbidden for demo accounts.', inputSchema, }, - async ({ story_id, title, description, implementation_plan, priority, sort_order, repo_url }) => + async ({ story_id, title, description, implementation_plan, priority, repo_url }) => withToolErrors(async () => { const auth = await requireWriteAccess() @@ -76,19 +76,10 @@ export function registerCreateTaskTool(server: McpServer) { return toolError(`Story ${story_id} not accessible`) } - let resolvedSortOrder = sort_order - if (resolvedSortOrder === undefined) { - const last = await prisma.task.findFirst({ - where: { story_id, priority }, - orderBy: { sort_order: 'desc' }, - select: { sort_order: true }, - }) - resolvedSortOrder = (last?.sort_order ?? 0) + 1.0 - } - let lastError: unknown for (let attempt = 0; attempt < MAX_CODE_ATTEMPTS; attempt++) { const code = await generateNextTaskCode(story.product_id) + const resolvedSortOrder = parseCodeNumber(code) try { const task = await prisma.task.create({ data: { diff --git a/src/tools/get-claude-context.ts b/src/tools/get-claude-context.ts index 80f7a4c..2a94892 100644 --- a/src/tools/get-claude-context.ts +++ b/src/tools/get-claude-context.ts @@ -63,7 +63,7 @@ export function registerGetClaudeContextTool(server: McpServer) { { tasks: { some: { status: { not: 'DONE' } } } }, ], }, - orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + orderBy: [{ sort_order: 'asc' }], select: { id: true, code: true, @@ -73,7 +73,7 @@ export function registerGetClaudeContextTool(server: McpServer) { priority: true, status: true, tasks: { - orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + orderBy: [{ sort_order: 'asc' }], select: { id: true, title: true, diff --git a/src/tools/wait-for-job.ts b/src/tools/wait-for-job.ts index f3e11c0..dd322c9 100644 --- a/src/tools/wait-for-job.ts +++ b/src/tools/wait-for-job.ts @@ -606,10 +606,10 @@ export async function getFullJobContext(jobId: string) { }, tasks: { where: { status: 'TO_DO' }, - orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + orderBy: [{ sort_order: 'asc' }], }, }, - orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }], + orderBy: [{ sort_order: 'asc' }], }, }, },