feat(CF-314): Add planning_mode_required field and smart planning mode support

Adds DB column, TypeScript types, MCP tool schemas, and CRUD handlers
for planning_mode_required (NULL=auto-detect, true=always, false=never).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-02-04 20:30:36 +02:00
parent baec42810c
commit 6cbb5ce6cb
5 changed files with 29 additions and 10 deletions

View File

@@ -0,0 +1,3 @@
-- CF-314: Add planning_mode_required flag for smart planning mode auto-detection
-- NULL = auto-detect (scoring algorithm), true = always plan, false = never plan
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS planning_mode_required BOOLEAN DEFAULT NULL;

View File

@@ -120,6 +120,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
type: a.type, type: a.type,
priority: a.priority, priority: a.priority,
description: a.description, description: a.description,
planning_mode_required: a.planning_mode_required,
}); });
break; break;
case 'task_list': case 'task_list':
@@ -144,6 +145,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
priority: a.priority, priority: a.priority,
type: a.type, type: a.type,
title: a.title, title: a.title,
planning_mode_required: a.planning_mode_required,
}); });
break; break;
case 'task_investigate': case 'task_investigate':

View File

@@ -63,6 +63,7 @@ interface TaskAddArgs {
type?: string; type?: string;
priority?: string; priority?: string;
description?: string; description?: string;
planning_mode_required?: boolean | null;
} }
interface TaskListArgs { interface TaskListArgs {
@@ -79,13 +80,14 @@ interface TaskUpdateArgs {
priority?: string; priority?: string;
type?: string; type?: string;
title?: string; title?: string;
planning_mode_required?: boolean | null;
} }
/** /**
* Create a new task * Create a new task
*/ */
export async function taskAdd(args: TaskAddArgs): Promise<string> { export async function taskAdd(args: TaskAddArgs): Promise<string> {
const { title, project = 'Unknown', type = 'task', priority = 'P2', description = '' } = args; const { title, project = 'Unknown', type = 'task', priority = 'P2', description = '', planning_mode_required } = args;
// Get project key // Get project key
const projectKey = await getProjectKey(project); const projectKey = await getProjectKey(project);
@@ -151,17 +153,19 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
const session_id = getSessionId(); const session_id = getSessionId();
// Insert task with session_id // Insert task with session_id
const planningValue = planning_mode_required !== undefined ? planning_mode_required : null;
if (embeddingValue) { if (embeddingValue) {
await execute( await execute(
`INSERT INTO tasks (id, project, title, description, type, status, priority, session_id, embedding) `INSERT INTO tasks (id, project, title, description, type, status, priority, session_id, embedding, planning_mode_required)
VALUES ($1, $2, $3, $4, $5, 'open', $6, $7, $8)`, VALUES ($1, $2, $3, $4, $5, 'open', $6, $7, $8, $9)`,
[taskId, projectKey, title, description, type, priority, session_id, embeddingValue] [taskId, projectKey, title, description, type, priority, session_id, embeddingValue, planningValue]
); );
} else { } else {
await execute( await execute(
`INSERT INTO tasks (id, project, title, description, type, status, priority, session_id) `INSERT INTO tasks (id, project, title, description, type, status, priority, session_id, planning_mode_required)
VALUES ($1, $2, $3, $4, $5, 'open', $6, $7)`, VALUES ($1, $2, $3, $4, $5, 'open', $6, $7, $8)`,
[taskId, projectKey, title, description, type, priority, session_id] [taskId, projectKey, title, description, type, priority, session_id, planningValue]
); );
} }
@@ -326,7 +330,7 @@ export async function taskList(args: TaskListArgs): Promise<string> {
*/ */
export async function taskShow(id: string): Promise<string> { export async function taskShow(id: string): Promise<string> {
const task = await queryOne<Task & { session_id?: string }>( const task = await queryOne<Task & { session_id?: string }>(
`SELECT id, project, title, description, type, status, priority, session_id, `SELECT id, project, title, description, type, status, priority, session_id, planning_mode_required,
to_char(created_at, 'YYYY-MM-DD HH24:MI') as created, to_char(created_at, 'YYYY-MM-DD HH24:MI') as created,
to_char(updated_at, 'YYYY-MM-DD HH24:MI') as updated, to_char(updated_at, 'YYYY-MM-DD HH24:MI') as updated,
to_char(completed_at, 'YYYY-MM-DD HH24:MI') as completed to_char(completed_at, 'YYYY-MM-DD HH24:MI') as completed
@@ -355,6 +359,9 @@ export async function taskShow(id: string): Promise<string> {
output += `**Created in session:** ${task.session_id}\n`; output += `**Created in session:** ${task.session_id}\n`;
} }
const planningLabel = task.planning_mode_required === true ? 'required' : task.planning_mode_required === false ? 'skipped' : 'auto-detect';
output += `**Planning:** ${planningLabel}\n`;
if (task.description) { if (task.description) {
output += `\n**Description:**\n${task.description}\n`; output += `\n**Description:**\n${task.description}\n`;
} }
@@ -476,7 +483,7 @@ export async function taskClose(id: string): Promise<string> {
* Update a task * Update a task
*/ */
export async function taskUpdate(args: TaskUpdateArgs): Promise<string> { export async function taskUpdate(args: TaskUpdateArgs): Promise<string> {
const { id, status, priority, type, title } = args; const { id, status, priority, type, title, planning_mode_required } = args;
// Get current values for activity tracking // Get current values for activity tracking
const task = await queryOne<{ status: string }>(`SELECT status FROM tasks WHERE id = $1`, [id]); const task = await queryOne<{ status: string }>(`SELECT status FROM tasks WHERE id = $1`, [id]);
@@ -507,6 +514,10 @@ export async function taskUpdate(args: TaskUpdateArgs): Promise<string> {
updates.push(`title = $${paramIndex++}`); updates.push(`title = $${paramIndex++}`);
params.push(title); params.push(title);
} }
if (planning_mode_required !== undefined) {
updates.push(`planning_mode_required = $${paramIndex++}`);
params.push(planning_mode_required);
}
if (updates.length === 0) { if (updates.length === 0) {
return 'No updates specified'; return 'No updates specified';

View File

@@ -13,6 +13,7 @@ export const toolDefinitions = [
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Task type (default: task)' }, type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Task type (default: task)' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Priority level (default: P2)' }, priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Priority level (default: P2)' },
description: { type: 'string', description: 'Optional description' }, description: { type: 'string', description: 'Optional description' },
planning_mode_required: { type: 'boolean', description: 'Override planning mode: true=always plan, false=never plan, omit=auto-detect' },
}, },
required: ['title'], required: ['title'],
}, },
@@ -24,7 +25,7 @@ export const toolDefinitions = [
type: 'object', type: 'object',
properties: { properties: {
project: { type: 'string', description: 'Filter by project key' }, project: { type: 'string', description: 'Filter by project key' },
status: { type: 'string', enum: ['open', 'in_progress', 'testing', 'blocked', 'completed'], description: 'Filter by status. Omit to show all non-completed tasks.' }, status: { type: 'string', enum: ['open', 'in_progress', 'testing', 'blocked', 'completed'], description: 'Filter by exact status value. IMPORTANT: "open" only matches status=open, NOT in_progress/blocked/testing. Omit this parameter entirely to show ALL non-completed tasks.' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Filter by type' }, type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Filter by type' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Filter by priority' }, priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Filter by priority' },
limit: { type: 'number', description: 'Max results (default: 20)' }, limit: { type: 'number', description: 'Max results (default: 20)' },
@@ -64,6 +65,7 @@ export const toolDefinitions = [
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'New priority' }, priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'New priority' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'New type' }, type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'New type' },
title: { type: 'string', description: 'New title' }, title: { type: 'string', description: 'New title' },
planning_mode_required: { type: 'boolean', description: 'Override planning mode: true=always plan, false=never plan, null=auto-detect' },
}, },
required: ['id'], required: ['id'],
}, },

View File

@@ -10,6 +10,7 @@ export interface Task {
priority: 'P0' | 'P1' | 'P2' | 'P3'; priority: 'P0' | 'P1' | 'P2' | 'P3';
version_id?: string; version_id?: string;
epic_id?: string; epic_id?: string;
planning_mode_required?: boolean | null;
created_at: Date; created_at: Date;
updated_at: Date; updated_at: Date;
completed_at?: Date; completed_at?: Date;