this repo has no description
0
fork

Configure Feed

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

fix(capture): extract tasks from stream-of-consciousness text

The parser now handles unstructured brain dumps like:
"i have to send an email to hr. i'm finally sending..."

Extracts tasks from:
- Intent phrases: "need to X", "have to X", "gotta X", "should X"
- "Going to X", "gonna X"
- "Finally X" (procrastination indicator!)

Plus existing support for:
- Bullet points and numbered lists
- Action verbs at line starts

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

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

alice df5b929e fb24d9c4

+53 -15
+53 -15
src/tools/capture.ts
··· 36 36 /** 37 37 * Parse brain dump text to extract tasks and ideas 38 38 * 39 - * Uses simple heuristics for M2: 40 - * - Lines starting with "- ", "* ", "TODO", numbers, or action verbs are tasks 41 - * - Other lines are ideas 42 - * - Strips common prefixes and cleans up text 39 + * Handles both structured (bullet points) and unstructured (stream-of-consciousness) text. 40 + * Extracts tasks based on: 41 + * - Explicit list markers (-, *, numbers, TODO) 42 + * - Intent phrases ("need to", "have to", "gotta", "should") 43 + * - Action verbs at sentence starts 43 44 */ 44 45 function parseBrainDump(text: string): { tasks: string[]; ideas: string[] } { 45 - const lines = text 46 - .split('\n') 47 - .map((line) => line.trim()) 48 - .filter((line) => line.length > 0); 49 - 50 46 const tasks: string[] = []; 51 47 const ideas: string[] = []; 52 48 53 - // Common task prefixes to strip 49 + // Common task prefixes to strip from list items 54 50 const taskPrefixes = [/^-\s+/, /^\*\s+/, /^\d+[).]\s+/, /^TODO:?\s*/i, /^TASK:?\s*/i]; 55 51 56 52 // Action verbs that indicate tasks 57 53 const actionVerbs = 58 - /^(add|create|implement|fix|update|write|build|test|review|refactor|delete|remove|setup|configure|install|deploy|investigate|research|learn|study|call|email|message|buy|order|schedule|plan|organize|clean|finish|complete|start|begin)\b/i; 54 + /^(add|create|implement|fix|update|write|build|test|review|refactor|delete|remove|setup|configure|install|deploy|investigate|research|learn|study|call|email|message|buy|order|schedule|plan|organize|clean|finish|complete|start|begin|send|check|get|make|do|prepare|submit|apply|respond|reply|contact|reach|follow|set|book|cancel|return|pick|drop)\b/i; 55 + 56 + // Intent phrases that signal tasks (captures "I need to X", "gotta X", etc.) 57 + const intentPatterns = [ 58 + /\b(?:i\s+)?(?:need|have|got|gotta|should|must|want)\s+to\s+(\w+(?:\s+\w+){0,10}?)(?:\.|,|$|(?=\s+(?:and|but|i\s|i'm|also)))/gi, 59 + /\b(?:i'm|i\s+am)\s+(?:gonna|going\s+to)\s+(\w+(?:\s+\w+){0,10}?)(?:\.|,|$|(?=\s+(?:and|but|i\s|i'm|also)))/gi, 60 + /\bfinally\s+(\w+(?:\s+\w+){0,10}?)(?:\.|,|$|(?=\s+(?:and|but|i\s|i'm|also)))/gi, 61 + ]; 62 + 63 + // First, try to extract tasks from intent phrases in unstructured text 64 + const extractedTasks = new Set<string>(); 65 + for (const pattern of intentPatterns) { 66 + let match; 67 + while ((match = pattern.exec(text)) !== null) { 68 + const task = match[1]?.trim() ?? ''; 69 + if (task.length > 2 && task.length < 200) { 70 + // Clean up the task - capitalize first letter 71 + const cleanTask = task.charAt(0).toUpperCase() + task.slice(1); 72 + extractedTasks.add(cleanTask); 73 + } 74 + } 75 + } 76 + 77 + // Add extracted tasks 78 + for (const task of extractedTasks) { 79 + tasks.push(task); 80 + } 81 + 82 + // Then process line by line for structured input 83 + const lines = text 84 + .split('\n') 85 + .map((line) => line.trim()) 86 + .filter((line) => line.length > 0); 59 87 60 88 for (const line of lines) { 61 89 let content = line; 62 90 let isTask = false; 63 91 64 - // Check for task prefixes 92 + // Check for task prefixes (bullet points, numbers) 65 93 for (const prefix of taskPrefixes) { 66 94 if (prefix.test(content)) { 67 95 content = content.replace(prefix, ''); ··· 70 98 } 71 99 } 72 100 73 - // If no prefix matched, check for action verbs 101 + // If no prefix matched, check for action verbs at line start 74 102 if (!isTask && actionVerbs.test(content)) { 75 103 isTask = true; 76 104 } ··· 80 108 81 109 if (content.length > 0) { 82 110 if (isTask) { 83 - tasks.push(content); 84 - } else { 111 + // Avoid duplicates from intent extraction 112 + if (!tasks.some((t) => t.toLowerCase() === content.toLowerCase())) { 113 + tasks.push(content); 114 + } 115 + } else if (tasks.length === 0) { 116 + // Only add as idea if we didn't extract any tasks from it 117 + // (to avoid storing the whole brain dump as an idea when we extracted tasks) 85 118 ideas.push(content); 86 119 } 87 120 } 121 + } 122 + 123 + // If we extracted tasks but the whole text was one line, don't store it as an idea 124 + if (tasks.length > 0 && ideas.length === 1 && ideas[0] === text.trim()) { 125 + ideas.length = 0; 88 126 } 89 127 90 128 return { tasks, ideas };