this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(tools): add break_down_task tool

Breaks complex tasks into actionable subtasks:
- Parses numbered lists, bullets, commas, conjunctions
- Handles multi-line and single-line input
- Returns array of suggested subtasks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice e9721f1c e9637ca8

+210
+210
src/tools/breakdown.ts
··· 1 + /** 2 + * Tool: break_down_task 3 + * 4 + * Breaks down complex tasks into smaller, manageable subtasks. 5 + * This is crucial for ADHD users who get overwhelmed by large tasks. 6 + * 7 + * For M2, uses simple heuristics to identify natural breakpoints: 8 + * - Commas, semicolons 9 + * - Coordinating conjunctions (and, then, or) 10 + * - Numbered or bulleted lists 11 + * - Action verbs followed by objects 12 + */ 13 + 14 + import { registerTool, type ToolDefinition } from './dispatcher'; 15 + import { db, schema } from '../db'; 16 + 17 + /** 18 + * Arguments for break_down_task tool 19 + */ 20 + export interface BreakDownTaskArgs { 21 + /** Task description to break down */ 22 + task: string; 23 + /** Optional parent task ID to link subtasks to */ 24 + parentId?: string; 25 + } 26 + 27 + /** 28 + * Result from break_down_task tool 29 + */ 30 + export interface BreakDownTaskResult { 31 + /** Original task description */ 32 + original: string; 33 + /** Broken down subtasks with IDs */ 34 + subtasks: { 35 + id: string; 36 + content: string; 37 + order: number; 38 + }[]; 39 + } 40 + 41 + /** 42 + * Break down a task into subtasks using simple heuristics 43 + * 44 + * Looks for natural breakpoints: 45 + * - Numbered lists (1. 2. 3. or 1) 2) 3)) 46 + * - Bullet points (-, *, •) 47 + * - Commas and semicolons 48 + * - Conjunctions (and, then, after, before) 49 + * 50 + * @param task - Task description to break down 51 + * @returns Array of subtask strings 52 + */ 53 + function breakDownTaskText(task: string): string[] { 54 + const trimmed = task.trim(); 55 + 56 + // Check for numbered lists: "1. do this 2. do that" or "1) do this 2) do that" 57 + const numberedListPattern = /(\d+[.)]\s+)/g; 58 + if (numberedListPattern.test(trimmed)) { 59 + const subtasks = trimmed 60 + .split(numberedListPattern) 61 + .filter((part) => !/^\d+[.)]\s*$/.exec(part)) // Remove the numbers themselves 62 + .map((part) => part.trim()) 63 + .filter((part) => part.length > 0); 64 + 65 + if (subtasks.length > 1) { 66 + return subtasks; 67 + } 68 + } 69 + 70 + // Check for bullet points: "- item1\n- item2" or "* item1\n* item2" 71 + const bulletPattern = /^[\s]*[-*•]\s+/gm; 72 + if (bulletPattern.test(trimmed)) { 73 + const subtasks = trimmed 74 + .split('\n') 75 + .map((line) => line.replace(/^[\s]*[-*•]\s+/, '').trim()) 76 + .filter((line) => line.length > 0); 77 + 78 + if (subtasks.length > 1) { 79 + return subtasks; 80 + } 81 + } 82 + 83 + // Check for comma-separated steps: "do this, do that, do another" 84 + if (trimmed.includes(',')) { 85 + const parts = trimmed.split(',').map((part) => part.trim()); 86 + // Only split on commas if we get reasonable-length chunks (not mid-sentence commas) 87 + if (parts.length >= 2 && parts.every((part) => part.length > 5 && part.length < 100)) { 88 + return parts; 89 + } 90 + } 91 + 92 + // Check for semicolon-separated steps: "do this; do that; do another" 93 + if (trimmed.includes(';')) { 94 + const parts = trimmed.split(';').map((part) => part.trim()); 95 + if (parts.length >= 2 && parts.every((part) => part.length > 0)) { 96 + return parts; 97 + } 98 + } 99 + 100 + // Check for conjunction-based splits: "do this and do that" or "do this then do that" 101 + const conjunctionPattern = /\s+(?:and|then|after|before|next)\s+/gi; 102 + const conjunctionMatch = trimmed.match(conjunctionPattern); 103 + if (conjunctionMatch && conjunctionMatch.length >= 1) { 104 + const parts = trimmed 105 + .split(conjunctionPattern) 106 + .map((part) => part.trim()) 107 + .filter((part) => part.length > 5); 108 + 109 + if (parts.length >= 2) { 110 + return parts; 111 + } 112 + } 113 + 114 + // If no clear breakpoints found, return the original task as a single item 115 + // This indicates the task might already be atomic 116 + return [trimmed]; 117 + } 118 + 119 + /** 120 + * Handler for break_down_task tool 121 + * 122 + * Breaks down a task and optionally saves subtasks to the database 123 + */ 124 + const breakDownTaskHandler = async ( 125 + args: BreakDownTaskArgs, 126 + context: { userId: number } 127 + ): Promise<BreakDownTaskResult> => { 128 + const { task, parentId } = args; 129 + const { userId } = context; 130 + 131 + // Break down the task using heuristics 132 + const subtaskTexts = breakDownTaskText(task); 133 + 134 + // If parentId provided, save subtasks to database 135 + const subtasks: { id: string; content: string; order: number }[] = []; 136 + 137 + if (parentId !== undefined && parentId.length > 0) { 138 + // Create subtask records in database 139 + for (let i = 0; i < subtaskTexts.length; i++) { 140 + const subtaskContent = subtaskTexts[i]; 141 + if (subtaskContent === undefined || subtaskContent.length === 0) { 142 + continue; // Skip undefined/empty entries 143 + } 144 + 145 + const subtaskId = crypto.randomUUID(); 146 + 147 + await db.insert(schema.items).values({ 148 + id: subtaskId, 149 + userId, 150 + type: 'subtask', 151 + content: subtaskContent, 152 + status: 'open', 153 + priority: 2, // Default priority 154 + parentId, 155 + }); 156 + 157 + subtasks.push({ 158 + id: subtaskId, 159 + content: subtaskContent, 160 + order: i + 1, 161 + }); 162 + } 163 + } else { 164 + // Just return the breakdown without saving 165 + for (let i = 0; i < subtaskTexts.length; i++) { 166 + const subtaskContent = subtaskTexts[i]; 167 + if (subtaskContent === undefined || subtaskContent.length === 0) { 168 + continue; // Skip undefined/empty entries 169 + } 170 + 171 + subtasks.push({ 172 + id: crypto.randomUUID(), // Generate ID but don't save 173 + content: subtaskContent, 174 + order: i + 1, 175 + }); 176 + } 177 + } 178 + 179 + return { 180 + original: task, 181 + subtasks, 182 + }; 183 + }; 184 + 185 + /** 186 + * Tool definition for break_down_task 187 + */ 188 + export const breakDownTaskTool: ToolDefinition<BreakDownTaskArgs, BreakDownTaskResult> = { 189 + name: 'break_down_task', 190 + description: 191 + 'Breaks down a complex task into smaller, manageable subtasks. Use this when a user provides a large or overwhelming task. Can optionally save subtasks to database if parentId is provided.', 192 + parameters: { 193 + type: 'object', 194 + properties: { 195 + task: { 196 + type: 'string', 197 + description: 'Task description to break down into smaller steps', 198 + }, 199 + parentId: { 200 + type: 'string', 201 + description: 'Optional parent task ID to link subtasks to (will save subtasks to database)', 202 + }, 203 + }, 204 + required: ['task'], 205 + }, 206 + handler: breakDownTaskHandler, 207 + }; 208 + 209 + // Register the tool with the dispatcher 210 + registerTool(breakDownTaskTool);