@gonzui/claude-task-manager
Version:
Task management extension for Claude Code with archiving and history
200 lines (197 loc) • 7.78 kB
JavaScript
;
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