UNPKG

@pimzino/claude-code-spec-workflow

Version:

Automated workflows for Claude Code. Includes spec-driven development (Requirements → Design → Tasks → Implementation) with intelligent task execution, optional steering documents and streamlined bug fix workflow (Report → Analyze → Fix → Verify). We have

264 lines (250 loc) 10.4 kB
"use strict"; /** * Task command generation utilities * Parses tasks.md files and generates individual command files */ 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.parseTasksFromMarkdown = parseTasksFromMarkdown; exports.generateTaskCommand = generateTaskCommand; const path = __importStar(require("path")); const file_cache_1 = require("./file-cache"); /** * Parse tasks from a tasks.md markdown file * Handles various formats agents might produce: * - [ ] 1. Task description * - [ ] 2.1 Subtask description * - Details * - _Requirements: 1.1, 2.2_ * - _Leverage: existing component X_ */ function parseTasksFromMarkdown(content) { const tasks = []; const lines = content.split('\n'); let currentTask = null; let isCollectingTaskContent = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); // Match task lines with flexible format: // Supports: "- [ ] 1. Task", "- [] 1 Task", "- [ ] 1.1. Task", etc. // Also handles various spacing and punctuation const taskMatch = trimmedLine.match(/^-\s*\[\s*\]\s*([0-9]+(?:\.[0-9]+)*)\s*\.?\s*(.+)$/); if (taskMatch) { // If we have a previous task, save it if (currentTask) { tasks.push(currentTask); } // Start new task const taskId = taskMatch[1]; const taskDescription = taskMatch[2].trim(); currentTask = { id: taskId, description: taskDescription }; isCollectingTaskContent = true; } // If we're in a task, look for metadata anywhere in the task block else if (currentTask && isCollectingTaskContent) { // Check if this line starts a new task section (to stop collecting) if (trimmedLine.match(/^-\s*\[\s*\]\s*[0-9]/)) { // This is the start of a new task, process it in the next iteration i--; isCollectingTaskContent = false; continue; } // Check for _Requirements: anywhere in the line const requirementsMatch = line.match(/_Requirements:\s*(.+?)(?:_|$)/); if (requirementsMatch) { currentTask.requirements = requirementsMatch[1].trim(); } // Check for _Leverage: anywhere in the line const leverageMatch = line.match(/_Leverage:\s*(.+?)(?:_|$)/); if (leverageMatch) { currentTask.leverage = leverageMatch[1].trim(); } // Stop collecting if we hit an empty line followed by non-indented content if (trimmedLine === '' && i + 1 < lines.length) { const nextLine = lines[i + 1]; if (nextLine.length > 0 && nextLine[0] !== ' ' && nextLine[0] !== '\t' && !nextLine.startsWith(' -')) { isCollectingTaskContent = false; } } } } // Don't forget the last task if (currentTask) { tasks.push(currentTask); } // Log parsing results for debugging console.log(`Parsed ${tasks.length} tasks from markdown`); if (tasks.length === 0 && content.trim().length > 0) { console.log('Warning: No tasks found. Tasks must follow this exact format:'); console.log(' - [ ] 1. Task description'); console.log(' - [ ] 2.1 Subtask description'); console.log(' - Additional details'); console.log(' - _Requirements: 1.1, 2.2_'); console.log(' - _Leverage: path/to/file.ts_'); console.log(''); console.log('Content preview:'); console.log(content.substring(0, 500) + (content.length > 500 ? '...' : '')); } return tasks; } /** * Load steering context content */ function loadSteeringContext(projectPath) { const steeringDir = path.join(projectPath, '.claude', 'steering'); if (!(0, file_cache_1.cachedFileExists)(steeringDir)) { return '## Steering Documents Context\n\nNo steering documents found.'; } const steeringFiles = [ { name: 'product.md', title: 'Product Context' }, { name: 'tech.md', title: 'Technology Context' }, { name: 'structure.md', title: 'Structure Context' } ]; const sections = []; let hasContent = false; for (const file of steeringFiles) { const filePath = path.join(steeringDir, file.name); if ((0, file_cache_1.cachedFileExists)(filePath)) { const content = (0, file_cache_1.getCachedFileContent)(filePath); if (content && content.trim()) { sections.push(`### ${file.title}\n${content.trim()}`); hasContent = true; } } } if (!hasContent) { return '## Steering Documents Context\n\nNo steering documents found or all are empty.'; } return `## Steering Documents Context (Pre-loaded)\n\n${sections.join('\n\n---\n\n')}\n\n**Note**: Steering documents have been pre-loaded. Do not use get-content to fetch them again.`; } /** * Load spec context content (requirements and design only) */ function loadSpecContext(specName, projectPath) { const specDir = path.join(projectPath, '.claude', 'specs', specName); if (!(0, file_cache_1.cachedFileExists)(specDir)) { return `## Specification Context\n\nNo specification found for: ${specName}`; } // Only load requirements and design, not tasks const specFiles = [ { name: 'requirements.md', title: 'Requirements' }, { name: 'design.md', title: 'Design' } ]; const sections = []; let hasContent = false; for (const file of specFiles) { const filePath = path.join(specDir, file.name); if ((0, file_cache_1.cachedFileExists)(filePath)) { const content = (0, file_cache_1.getCachedFileContent)(filePath); if (content && content.trim()) { sections.push(`### ${file.title}\n${content.trim()}`); hasContent = true; } } } if (!hasContent) { return `## Specification Context\n\nNo specification documents found for: ${specName}`; } return `## Specification Context (Pre-loaded): ${specName}\n\n${sections.join('\n\n---\n\n')}\n\n**Note**: Specification documents have been pre-loaded. Do not use get-content to fetch them again.`; } /** * Generate a command file for a specific task */ async function generateTaskCommand(commandsDir, specName, task) { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const pathModule = await Promise.resolve().then(() => __importStar(require('path'))); const commandFile = pathModule.join(commandsDir, `task-${task.id}.md`); // Determine project path from commandsDir (commandsDir is typically .claude/commands/{specName}) const projectPath = pathModule.resolve(commandsDir, '../../..'); // Load actual content const steeringContext = loadSteeringContext(projectPath); const specContext = loadSpecContext(specName, projectPath); let content = `# ${specName} - Task ${task.id} Execute task ${task.id} for the ${specName} specification. ## Task Description ${task.description} `; // Add Code Reuse section if leverage info exists if (task.leverage) { content += `## Code Reuse **Leverage existing code**: ${task.leverage} `; } // Add Requirements section if requirements exist if (task.requirements) { content += `## Requirements Reference **Requirements**: ${task.requirements} `; } content += `## Usage \`\`\` /Task:${task.id}-${specName} \`\`\` ## Instructions Execute with @spec-task-executor agent the following task: "${task.description}" \`\`\` Use the @spec-task-executor agent to implement task ${task.id}: "${task.description}" for the ${specName} specification and include all the below context. # Steering Context ${steeringContext} # Specification Context ${specContext} ## Task Details - Task ID: ${task.id} - Description: ${task.description}${task.leverage ? ` - Leverage: ${task.leverage}` : ''}${task.requirements ? ` - Requirements: ${task.requirements}` : ''} ## Instructions - Implement ONLY task ${task.id}: "${task.description}" - Follow all project conventions and leverage existing code - Mark the task as complete using: claude-code-spec-workflow get-tasks ${specName} ${task.id} --mode complete - Provide a completion summary \`\`\` ## Task Completion When the task is complete, mark it as done: \`\`\`bash claude-code-spec-workflow get-tasks ${specName} ${task.id} --mode complete \`\`\` ## Next Steps After task completion, you can: - Execute the next task using /${specName}-task-[next-id] - Check overall progress with /spec-status ${specName} `; await fs.writeFile(commandFile, content, 'utf8'); } //# sourceMappingURL=task-generator.js.map