UNPKG

@gonzui/claude-task-manager

Version:

Task management extension for Claude Code with archiving and history

200 lines (197 loc) 7.78 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskSplitter = void 0; const fs = __importStar(require("fs-extra")); const child_process_1 = require("child_process"); class TaskSplitter { constructor(taskFile, i18n) { this.taskFile = taskFile; this.i18n = i18n; } async splitTask(count) { if (!await fs.pathExists(this.taskFile)) { throw new Error('No task.md file found'); } const content = await fs.readFile(this.taskFile, 'utf8'); const title = this.extractTitle(content); const description = this.extractDescription(content); const prompt = this.buildPrompt(title, description, count); try { const response = await this.callClaude(prompt); const subtasks = this.parseSubtasks(response); if (subtasks.length > 0) { await this.updateTaskFile(content, subtasks); } return { success: true, subtasks }; } catch (error) { return { success: false, subtasks: [], error: error instanceof Error ? error.message : 'Unknown error' }; } } extractTitle(content) { const match = content.match(/^#\s+(.+)$/m); return match ? match[1] : 'Untitled Task'; } extractDescription(content) { const descMatch = content.match(/##\s+(Description|説明)\s*\n([\s\S]*?)(?=\n##|$)/i); return descMatch ? descMatch[2].trim() : ''; } buildPrompt(title, description, count) { const countInstruction = count ? `exactly ${count}` : '3-7'; return `Analyze this task and break it down into ${countInstruction} actionable subtasks. Task Title: ${title} Description: ${description} Requirements: 1. Each subtask should be specific and actionable 2. Subtasks should be in logical order 3. Each subtask should be completable independently 4. Keep subtask descriptions concise (one line each) Output format (ONLY output the subtasks, one per line, no numbers or bullets): Subtask description 1 Subtask description 2 Subtask description 3 ...`; } async callClaude(prompt, timeoutMs = 300000) { return new Promise((resolve, reject) => { const args = ['-p']; let stdout = ''; let stderr = ''; let timedOut = false; const claudeProcess = (0, child_process_1.spawn)('claude', args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false }); // Set timeout first const timeoutId = setTimeout(() => { timedOut = true; claudeProcess.kill('SIGTERM'); reject(new Error(`Claude CLI timed out after ${timeoutMs / 1000} seconds`)); }, timeoutMs); // Handle stdin errors claudeProcess.stdin.on('error', (err) => { clearTimeout(timeoutId); if (timedOut) return; reject(new Error(`Stdin error: ${err.message}`)); }); // Write prompt to stdin try { claudeProcess.stdin.write(prompt); claudeProcess.stdin.end(); } catch (err) { clearTimeout(timeoutId); reject(new Error(`Failed to write to stdin: ${err instanceof Error ? err.message : String(err)}`)); return; } claudeProcess.stdout.on('data', (data) => { stdout += data.toString(); }); claudeProcess.stderr.on('data', (data) => { stderr += data.toString(); }); claudeProcess.on('close', (code) => { clearTimeout(timeoutId); if (timedOut) return; // Already rejected if (code === 0) { resolve(stdout.trim()); } else { // Check for API limit error if (stdout.includes('hit your limit') || stdout.includes('limit')) { reject(new Error(`Claude API limit reached. Please wait until the limit resets (usually 6pm local time).`)); } else { reject(new Error(`Claude failed with code ${code}: ${stderr || stdout}`)); } } }); claudeProcess.on('error', (error) => { clearTimeout(timeoutId); if (timedOut) return; reject(new Error(`Failed to start Claude: ${error.message}`)); }); }); } parseSubtasks(response) { const lines = response.split('\n') .map(line => line.trim()) .filter(line => line.length > 0) .filter(line => !line.startsWith('#')) .filter(line => !line.startsWith('```')) .map(line => { // Remove leading numbers, bullets, or dashes return line.replace(/^[\d\.\-\*\)]+\s*/, '').trim(); }) .filter(line => line.length > 0); return lines; } async updateTaskFile(content, subtasks) { // Find the Tasks section and replace its content const tasksSection = subtasks.map(task => `- [ ] ${task}`).join('\n'); // Look for existing Tasks section const tasksPattern = /(##\s+(Tasks|タスク)\s*\n)([\s\S]*?)(?=\n##|$)/i; const match = content.match(tasksPattern); let newContent; if (match) { // Replace existing tasks section content newContent = content.replace(tasksPattern, `$1${tasksSection}\n`); } else { // Add new Tasks section before Notes or at the end const notesPattern = /\n##\s+(Notes|メモ)/i; if (notesPattern.test(content)) { newContent = content.replace(notesPattern, `\n## Tasks\n${tasksSection}\n\n$&`); } else { newContent = content + `\n\n## Tasks\n${tasksSection}\n`; } } await fs.writeFile(this.taskFile, newContent); } } exports.TaskSplitter = TaskSplitter; //# sourceMappingURL=TaskSplitter.js.map