Add list command with subtasks option and update documentation
This commit is contained in:
610
scripts/dev.js
610
scripts/dev.js
@@ -42,24 +42,43 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import readline from 'readline';
|
||||
import { program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { Anthropic } from '@anthropic-ai/sdk';
|
||||
import OpenAI from 'openai';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Load environment variables from .env file
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
// Configure Anthropic client
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
// Configure OpenAI client for Perplexity
|
||||
const perplexity = new OpenAI({
|
||||
apiKey: process.env.PERPLEXITY_API_KEY,
|
||||
baseURL: 'https://api.perplexity.ai',
|
||||
});
|
||||
|
||||
// Model configuration
|
||||
const MODEL = process.env.MODEL || 'claude-3-7-sonnet-20250219';
|
||||
const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-small-online';
|
||||
const MAX_TOKENS = parseInt(process.env.MAX_TOKENS || '4000');
|
||||
const TEMPERATURE = parseFloat(process.env.TEMPERATURE || '0.7');
|
||||
|
||||
// Set up configuration with environment variables or defaults
|
||||
const CONFIG = {
|
||||
model: process.env.MODEL || "claude-3-7-sonnet-20250219",
|
||||
maxTokens: parseInt(process.env.MAX_TOKENS || "4000"),
|
||||
temperature: parseFloat(process.env.TEMPERATURE || "0.7"),
|
||||
model: MODEL,
|
||||
maxTokens: MAX_TOKENS,
|
||||
temperature: TEMPERATURE,
|
||||
debug: process.env.DEBUG === "true",
|
||||
logLevel: process.env.LOG_LEVEL || "info",
|
||||
defaultSubtasks: parseInt(process.env.DEFAULT_SUBTASKS || "3"),
|
||||
@@ -95,10 +114,6 @@ function log(level, ...args) {
|
||||
}
|
||||
}
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
function readJSON(filepath) {
|
||||
if (!fs.existsSync(filepath)) return null;
|
||||
const content = fs.readFileSync(filepath, 'utf8');
|
||||
@@ -613,7 +628,7 @@ function setTaskStatus(tasksPath, taskIdInput, newStatus) {
|
||||
//
|
||||
// 5) list tasks
|
||||
//
|
||||
function listTasks(tasksPath) {
|
||||
function listTasks(tasksPath, statusFilter, withSubtasks = false) {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
@@ -621,114 +636,138 @@ function listTasks(tasksPath) {
|
||||
}
|
||||
|
||||
log('info', `Tasks in ${tasksPath}:`);
|
||||
data.tasks.forEach(t => {
|
||||
|
||||
// Filter tasks by status if a filter is provided
|
||||
const filteredTasks = statusFilter
|
||||
? data.tasks.filter(t => t.status === statusFilter)
|
||||
: data.tasks;
|
||||
|
||||
filteredTasks.forEach(t => {
|
||||
log('info', `- ID=${t.id}, [${t.status}] ${t.title}`);
|
||||
|
||||
// Display subtasks if requested and they exist
|
||||
if (withSubtasks && t.subtasks && t.subtasks.length > 0) {
|
||||
t.subtasks.forEach(st => {
|
||||
log('info', ` └─ ID=${t.id}.${st.id}, [${st.status || 'pending'}] ${st.title}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If no tasks match the filter, show a message
|
||||
if (filteredTasks.length === 0) {
|
||||
log('info', `No tasks found${statusFilter ? ` with status '${statusFilter}'` : ''}.`);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 6) expand task with subtasks
|
||||
//
|
||||
async function expandTask(tasksPath, taskId, numSubtasks, additionalContext = '') {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
process.exit(1);
|
||||
/**
|
||||
* Expand a task by generating subtasks
|
||||
* @param {string} taskId - The ID of the task to expand
|
||||
* @param {number} numSubtasks - The number of subtasks to generate
|
||||
* @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function expandTask(taskId, numSubtasks = CONFIG.defaultSubtasks, useResearch = false) {
|
||||
try {
|
||||
// Get the tasks
|
||||
const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
const task = tasksData.tasks.find(t => t.id === parseInt(taskId));
|
||||
|
||||
if (!task) {
|
||||
console.error(chalk.red(`Task with ID ${taskId} not found.`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the task is already completed
|
||||
if (task.status === 'completed' || task.status === 'done') {
|
||||
console.log(chalk.yellow(`Task ${taskId} is already completed. Skipping expansion.`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
}
|
||||
|
||||
// Calculate the next subtask ID
|
||||
const nextSubtaskId = task.subtasks.length > 0
|
||||
? Math.max(...task.subtasks.map(st => st.id)) + 1
|
||||
: 1;
|
||||
|
||||
// Generate subtasks
|
||||
let subtasks;
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue(`Using Perplexity AI for research-backed subtask generation...`));
|
||||
subtasks = await generateSubtasksWithPerplexity(task, numSubtasks, nextSubtaskId);
|
||||
} else {
|
||||
subtasks = await generateSubtasks(task, numSubtasks, nextSubtaskId);
|
||||
}
|
||||
|
||||
// Add the subtasks to the task
|
||||
task.subtasks = [...task.subtasks, ...subtasks];
|
||||
|
||||
// Save the updated tasks
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'tasks', 'tasks.json'),
|
||||
JSON.stringify(tasksData, null, 2)
|
||||
);
|
||||
|
||||
console.log(chalk.green(`Added ${subtasks.length} subtasks to task ${taskId}.`));
|
||||
|
||||
// Log the added subtasks
|
||||
subtasks.forEach(st => {
|
||||
console.log(chalk.cyan(` ${st.id}. ${st.title}`));
|
||||
console.log(chalk.gray(` ${st.description.substring(0, 100)}${st.description.length > 100 ? '...' : ''}`));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error expanding task:'), error);
|
||||
}
|
||||
|
||||
// Use default subtasks count from config if not specified
|
||||
numSubtasks = numSubtasks || CONFIG.defaultSubtasks;
|
||||
|
||||
const task = data.tasks.find(t => t.id === taskId);
|
||||
if (!task) {
|
||||
log('error', `Task with ID=${taskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Skip tasks that are already completed
|
||||
if (task.status === 'done' || task.status === 'completed') {
|
||||
log('info', `Skipping task ID=${taskId} "${task.title}" - task is already marked as ${task.status}.`);
|
||||
log('info', `Use set-status command to change the status if you want to modify this task.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
log('info', `Expanding task: ${task.title}`);
|
||||
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
}
|
||||
|
||||
// Calculate next subtask ID
|
||||
const nextSubtaskId = task.subtasks.length > 0
|
||||
? Math.max(...task.subtasks.map(st => st.id)) + 1
|
||||
: 1;
|
||||
|
||||
// Generate subtasks using Claude
|
||||
const subtasks = await generateSubtasks(task, numSubtasks, nextSubtaskId, additionalContext);
|
||||
|
||||
// Add new subtasks to the task
|
||||
task.subtasks = [...task.subtasks, ...subtasks];
|
||||
|
||||
// Update tasks.json
|
||||
writeJSON(tasksPath, data);
|
||||
log('info', `Added ${subtasks.length} subtasks to task ID=${taskId}.`);
|
||||
|
||||
// Print the new subtasks
|
||||
log('info', "New subtasks:");
|
||||
subtasks.forEach(st => {
|
||||
log('info', `- ${st.id}. ${st.title}`);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Expand all tasks with subtasks
|
||||
//
|
||||
async function expandAllTasks(tasksPath, numSubtasks, additionalContext = '', forceRegenerate = false) {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('info', `Expanding all ${data.tasks.length} tasks with subtasks...`);
|
||||
|
||||
let tasksExpanded = 0;
|
||||
let tasksSkipped = 0;
|
||||
let tasksCompleted = 0;
|
||||
|
||||
// Process each task sequentially to avoid overwhelming the API
|
||||
for (const task of data.tasks) {
|
||||
// Skip tasks that are already completed
|
||||
if (task.status === 'done' || task.status === 'completed') {
|
||||
log('info', `Skipping task ID=${task.id} "${task.title}" - task is already marked as ${task.status}.`);
|
||||
tasksCompleted++;
|
||||
continue;
|
||||
/**
|
||||
* Expand all tasks that are not completed
|
||||
* @param {number} numSubtasks - The number of subtasks to generate for each task
|
||||
* @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation
|
||||
* @returns {Promise<number>} - The number of tasks expanded
|
||||
*/
|
||||
async function expandAllTasks(numSubtasks = CONFIG.defaultSubtasks, useResearch = false) {
|
||||
try {
|
||||
// Get the tasks
|
||||
const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
|
||||
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
|
||||
console.error(chalk.red('No valid tasks found.'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip tasks that already have subtasks unless force regeneration is enabled
|
||||
if (!forceRegenerate && task.subtasks && task.subtasks.length > 0) {
|
||||
log('info', `Skipping task ID=${task.id} "${task.title}" - already has ${task.subtasks.length} subtasks`);
|
||||
tasksSkipped++;
|
||||
continue;
|
||||
// Filter tasks that are not completed
|
||||
const tasksToExpand = tasksData.tasks.filter(task =>
|
||||
task.status !== 'completed' && task.status !== 'done'
|
||||
);
|
||||
|
||||
if (tasksToExpand.length === 0) {
|
||||
console.log(chalk.yellow('No tasks to expand. All tasks are already completed.'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const success = await expandTask(tasksPath, task.id, numSubtasks, additionalContext);
|
||||
if (success) {
|
||||
console.log(chalk.blue(`Expanding ${tasksToExpand.length} tasks with ${numSubtasks} subtasks each...`));
|
||||
|
||||
let tasksExpanded = 0;
|
||||
|
||||
// Expand each task
|
||||
for (const task of tasksToExpand) {
|
||||
console.log(chalk.blue(`\nExpanding task ${task.id}: ${task.title}`));
|
||||
await expandTask(task.id, numSubtasks, useResearch);
|
||||
tasksExpanded++;
|
||||
}
|
||||
}
|
||||
|
||||
log('info', `Expansion complete: ${tasksExpanded} tasks expanded, ${tasksSkipped} tasks skipped (already had subtasks), ${tasksCompleted} tasks skipped (already completed).`);
|
||||
|
||||
if (tasksSkipped > 0) {
|
||||
log('info', `Tip: Use --force flag to regenerate subtasks for all tasks, including those that already have subtasks.`);
|
||||
}
|
||||
|
||||
if (tasksCompleted > 0) {
|
||||
log('info', `Note: Completed tasks are always skipped. Use set-status command to change task status if needed.`);
|
||||
|
||||
console.log(chalk.green(`\nExpanded ${tasksExpanded} tasks with ${numSubtasks} subtasks each.`));
|
||||
return tasksExpanded;
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error expanding all tasks:'), error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,135 +1000,272 @@ function parseSubtasksFromText(text, startId, expectedCount) {
|
||||
return subtasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate subtasks for a task using Perplexity AI with research capabilities
|
||||
* @param {Object} task - The task to generate subtasks for
|
||||
* @param {number} numSubtasks - The number of subtasks to generate
|
||||
* @param {number} nextSubtaskId - The ID to start assigning to subtasks
|
||||
* @returns {Promise<Array>} - The generated subtasks
|
||||
*/
|
||||
async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtaskId = 1) {
|
||||
const { title, description, details = '', subtasks = [] } = task;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numSubtasks} subtasks for task: ${title}`));
|
||||
if (subtasks.length > 0) {
|
||||
console.log(chalk.yellow(`Task already has ${subtasks.length} subtasks. Adding ${numSubtasks} more.`));
|
||||
}
|
||||
|
||||
// Get the tasks.json content for context
|
||||
let tasksData = {};
|
||||
try {
|
||||
tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow('Could not read tasks.json for context. Proceeding without it.'));
|
||||
}
|
||||
|
||||
// Get the PRD content for context if available
|
||||
let prdContent = '';
|
||||
if (tasksData.meta && tasksData.meta.source) {
|
||||
try {
|
||||
prdContent = fs.readFileSync(path.join(process.cwd(), tasksData.meta.source), 'utf8');
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(`Could not read PRD at ${tasksData.meta.source}. Proceeding without it.`));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the prompt for Perplexity/Anthropic
|
||||
const prompt = `I need to break down the following task into ${numSubtasks} detailed subtasks:
|
||||
|
||||
Task Title: ${title}
|
||||
Task Description: ${description}
|
||||
Additional Details: ${details}
|
||||
|
||||
${subtasks.length > 0 ? `Existing Subtasks:
|
||||
${subtasks.map(st => `- ${st.title}: ${st.description}`).join('\n')}` : ''}
|
||||
|
||||
${prdContent ? `Here is the Product Requirements Document for context:
|
||||
${prdContent}` : ''}
|
||||
|
||||
${tasksData.tasks ? `Here are the other tasks in the project for context:
|
||||
${JSON.stringify(tasksData.tasks.filter(t => t.id !== task.id).map(t => ({ id: t.id, title: t.title, description: t.description })), null, 2)}` : ''}
|
||||
|
||||
Please generate ${numSubtasks} subtasks. For each subtask, provide:
|
||||
1. A clear, concise title
|
||||
2. A detailed description explaining what needs to be done
|
||||
3. Dependencies (if any) - list the IDs of tasks this subtask depends on
|
||||
4. Acceptance criteria - specific conditions that must be met for the subtask to be considered complete
|
||||
|
||||
Format each subtask as follows:
|
||||
|
||||
Subtask 1: [Title]
|
||||
Description: [Detailed description]
|
||||
Dependencies: [List of task IDs, or "None" if no dependencies]
|
||||
Acceptance Criteria: [List of criteria]
|
||||
|
||||
Subtask 2: [Title]
|
||||
...
|
||||
|
||||
Research the task thoroughly and ensure the subtasks are comprehensive, specific, and actionable.`;
|
||||
|
||||
// Start loading indicator
|
||||
const loadingInterval = startLoadingIndicator('Researching and generating subtasks with AI');
|
||||
|
||||
try {
|
||||
let responseText;
|
||||
|
||||
try {
|
||||
// Try to use Perplexity first
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation...'));
|
||||
const result = await perplexity.chat.completions.create({
|
||||
model: PERPLEXITY_MODEL,
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: prompt
|
||||
}],
|
||||
temperature: TEMPERATURE,
|
||||
max_tokens: MAX_TOKENS,
|
||||
});
|
||||
|
||||
// Extract the response text
|
||||
responseText = result.choices[0].message.content;
|
||||
console.log(chalk.green('Successfully generated subtasks with Perplexity AI'));
|
||||
} catch (perplexityError) {
|
||||
console.log(chalk.yellow('Falling back to Anthropic for subtask generation...'));
|
||||
console.log(chalk.gray('Perplexity error:'), perplexityError.message);
|
||||
|
||||
// Use Anthropic as fallback
|
||||
const stream = await anthropic.messages.create({
|
||||
model: MODEL,
|
||||
max_tokens: MAX_TOKENS,
|
||||
temperature: TEMPERATURE,
|
||||
system: "You are an expert software developer and project manager. Your task is to break down software development tasks into detailed subtasks.",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: prompt
|
||||
}
|
||||
],
|
||||
stream: true
|
||||
});
|
||||
|
||||
// Process the stream
|
||||
responseText = '';
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||
responseText += chunk.delta.text;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('Successfully generated subtasks with Anthropic AI'));
|
||||
}
|
||||
|
||||
// Stop loading indicator
|
||||
stopLoadingIndicator(loadingInterval);
|
||||
|
||||
if (CONFIG.debug) {
|
||||
console.log(chalk.gray('AI Response:'));
|
||||
console.log(chalk.gray(responseText));
|
||||
}
|
||||
|
||||
// Parse the subtasks from the response text
|
||||
const subtasks = parseSubtasksFromText(responseText, nextSubtaskId, numSubtasks);
|
||||
return subtasks;
|
||||
} catch (error) {
|
||||
stopLoadingIndicator(loadingInterval);
|
||||
console.error(chalk.red('Error generating subtasks:'), error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Main CLI
|
||||
// ------------------------------------------
|
||||
(async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
async function main() {
|
||||
program
|
||||
.name('dev')
|
||||
.description('AI-driven development task management')
|
||||
.version('1.3.1');
|
||||
|
||||
const outputDir = path.resolve(process.cwd(), 'tasks');
|
||||
// Update tasksPath to be inside the tasks directory
|
||||
const tasksPath = path.resolve(outputDir, 'tasks.json');
|
||||
program
|
||||
.command('parse-prd')
|
||||
.description('Parse a PRD file and generate tasks')
|
||||
.argument('<file>', 'Path to the PRD file')
|
||||
.option('-o, --output <file>', 'Output file path', 'tasks/tasks.json')
|
||||
.option('-n, --num-tasks <number>', 'Number of tasks to generate', '10')
|
||||
.action(async (file, options) => {
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${file}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
|
||||
await parsePRD(file, outputPath, numTasks);
|
||||
});
|
||||
|
||||
const inputArg = (args.find(a => a.startsWith('--input=')) || '').split('=')[1] || 'sample-prd.txt';
|
||||
const fromArg = (args.find(a => a.startsWith('--from=')) || '').split('=')[1];
|
||||
const promptArg = (args.find(a => a.startsWith('--prompt=')) || '').split('=')[1] || '';
|
||||
const idArg = (args.find(a => a.startsWith('--id=')) || '').split('=')[1];
|
||||
const statusArg = (args.find(a => a.startsWith('--status=')) || '').split('=')[1] || '';
|
||||
const tasksCountArg = (args.find(a => a.startsWith('--tasks=')) || '').split('=')[1];
|
||||
const numTasks = tasksCountArg ? parseInt(tasksCountArg, 10) : undefined;
|
||||
const subtasksArg = (args.find(a => a.startsWith('--subtasks=')) || '').split('=')[1];
|
||||
const numSubtasks = subtasksArg ? parseInt(subtasksArg, 10) : 3; // Default to 3 subtasks if not specified
|
||||
const forceFlag = args.includes('--force'); // Check if --force flag is present
|
||||
program
|
||||
.command('update')
|
||||
.description('Update tasks based on the PRD')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
|
||||
console.log(chalk.blue(`Updating tasks from: ${tasksPath}`));
|
||||
|
||||
await updateTasks(tasksPath);
|
||||
});
|
||||
|
||||
log('info', `Executing command: ${command}`);
|
||||
|
||||
// Make sure the tasks directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
log('info', `Creating tasks directory: ${outputDir}`);
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'parse-prd':
|
||||
log('info', `Parsing PRD from ${inputArg} to generate tasks.json...`);
|
||||
if (numTasks) {
|
||||
log('info', `Limiting to ${numTasks} tasks as specified`);
|
||||
program
|
||||
.command('generate')
|
||||
.description('Generate task files from tasks.json')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-o, --output <dir>', 'Output directory', 'tasks')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const outputDir = options.output;
|
||||
|
||||
console.log(chalk.blue(`Generating task files from: ${tasksPath}`));
|
||||
console.log(chalk.blue(`Output directory: ${outputDir}`));
|
||||
|
||||
await generateTaskFiles(tasksPath, outputDir);
|
||||
});
|
||||
|
||||
program
|
||||
.command('set-status')
|
||||
.description('Set the status of a task')
|
||||
.argument('<id>', 'Task ID')
|
||||
.argument('<status>', 'New status (todo, in-progress, review, done)')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.action(async (id, status, options) => {
|
||||
const tasksPath = options.file;
|
||||
const taskId = parseInt(id, 10);
|
||||
|
||||
console.log(chalk.blue(`Setting status of task ${taskId} to: ${status}`));
|
||||
|
||||
await setTaskStatus(tasksPath, taskId, status);
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List all tasks')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-s, --status <status>', 'Filter by status')
|
||||
.option('--with-subtasks', 'Show subtasks for each task')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const statusFilter = options.status;
|
||||
const withSubtasks = options.withSubtasks || false;
|
||||
|
||||
console.log(chalk.blue(`Listing tasks from: ${tasksPath}`));
|
||||
if (statusFilter) {
|
||||
console.log(chalk.blue(`Filtering by status: ${statusFilter}`));
|
||||
}
|
||||
await parsePRD(inputArg, tasksPath, numTasks);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
if (!fromArg) {
|
||||
log('error', "Please specify --from=<id>. e.g. node dev.js update --from=3 --prompt='Changes...'");
|
||||
process.exit(1);
|
||||
if (withSubtasks) {
|
||||
console.log(chalk.blue('Including subtasks in listing'));
|
||||
}
|
||||
log('info', `Updating tasks from ID ${fromArg} based on prompt...`);
|
||||
await updateTasks(tasksPath, parseInt(fromArg, 10), promptArg);
|
||||
break;
|
||||
|
||||
await listTasks(tasksPath, statusFilter, withSubtasks);
|
||||
});
|
||||
|
||||
case 'generate':
|
||||
log('info', `Generating individual task files from ${tasksPath} to ${outputDir}...`);
|
||||
generateTaskFiles(tasksPath, outputDir);
|
||||
break;
|
||||
|
||||
case 'set-status':
|
||||
if (!idArg) {
|
||||
log('error', "Missing --id=<taskId> argument.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!statusArg) {
|
||||
log('error', "Missing --status=<newStatus> argument (e.g., done, pending, deferred, in-progress).");
|
||||
process.exit(1);
|
||||
}
|
||||
log('info', `Setting task(s) ${idArg} status to "${statusArg}"...`);
|
||||
setTaskStatus(tasksPath, idArg, statusArg);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
log('info', `Listing tasks from ${tasksPath}...`);
|
||||
listTasks(tasksPath);
|
||||
break;
|
||||
|
||||
case 'expand':
|
||||
if (args.includes('--all')) {
|
||||
// Expand all tasks
|
||||
log('info', `Expanding all tasks with ${numSubtasks} subtasks each...`);
|
||||
await expandAllTasks(tasksPath, numSubtasks, promptArg, forceFlag);
|
||||
program
|
||||
.command('expand')
|
||||
.description('Expand tasks with subtasks')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-i, --id <id>', 'Task ID to expand')
|
||||
.option('-a, --all', 'Expand all tasks')
|
||||
.option('-n, --num <number>', 'Number of subtasks to generate', CONFIG.defaultSubtasks.toString())
|
||||
.option('-r, --research', 'Use Perplexity AI for research-backed subtask generation')
|
||||
.option('--force', 'Force regeneration of subtasks for tasks that already have them')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const idArg = options.id ? parseInt(options.id, 10) : null;
|
||||
const allFlag = options.all;
|
||||
const numSubtasks = parseInt(options.num, 10);
|
||||
const forceFlag = options.force;
|
||||
const useResearch = options.research;
|
||||
|
||||
if (allFlag) {
|
||||
console.log(chalk.blue(`Expanding all tasks with ${numSubtasks} subtasks each...`));
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation'));
|
||||
}
|
||||
await expandAllTasks(numSubtasks, useResearch);
|
||||
} else if (idArg) {
|
||||
// Expand a specific task
|
||||
log('info', `Expanding task ${idArg} with ${numSubtasks} subtasks...`);
|
||||
await expandTask(tasksPath, parseInt(idArg, 10), numSubtasks, promptArg);
|
||||
console.log(chalk.blue(`Expanding task ${idArg} with ${numSubtasks} subtasks...`));
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation'));
|
||||
}
|
||||
await expandTask(idArg, numSubtasks, useResearch);
|
||||
} else {
|
||||
log('error', "Error: Please specify a task ID with --id=<id> or use --all to expand all tasks.");
|
||||
process.exit(1);
|
||||
console.error(chalk.red('Error: Please specify a task ID with --id=<id> or use --all to expand all tasks.'));
|
||||
}
|
||||
break;
|
||||
});
|
||||
|
||||
default:
|
||||
log('info', `
|
||||
Dev.js - Task Management Script
|
||||
await program.parseAsync(process.argv);
|
||||
}
|
||||
|
||||
Subcommands:
|
||||
1) parse-prd --input=some-prd.txt [--tasks=10]
|
||||
-> Creates/overwrites tasks.json with a set of tasks.
|
||||
-> Optional --tasks parameter limits the number of tasks generated.
|
||||
// ... existing code ...
|
||||
|
||||
2) update --from=5 --prompt="We changed from Slack to Discord."
|
||||
-> Regenerates tasks from ID >= 5 using the provided prompt.
|
||||
|
||||
3) generate
|
||||
-> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||
|
||||
4) set-status --id=4 --status=done
|
||||
-> Updates a single task's status to done (or pending, deferred, in-progress, etc.).
|
||||
-> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2
|
||||
|
||||
5) list
|
||||
-> Lists tasks in a brief console view (ID, title, status).
|
||||
|
||||
6) expand --id=3 --subtasks=5 [--prompt="Additional context"]
|
||||
-> Expands a task with subtasks for more detailed implementation.
|
||||
-> Use --all instead of --id to expand all tasks.
|
||||
-> Optional --subtasks parameter controls number of subtasks (default: 3).
|
||||
-> Add --force when using --all to regenerate subtasks for tasks that already have them.
|
||||
-> Note: Tasks marked as 'done' or 'completed' are always skipped.
|
||||
|
||||
Usage examples:
|
||||
node dev.js parse-prd --input=scripts/prd.txt
|
||||
node dev.js parse-prd --input=scripts/prd.txt --tasks=10
|
||||
node dev.js update --from=4 --prompt="Refactor tasks from ID 4 onward"
|
||||
node dev.js generate
|
||||
node dev.js set-status --id=3 --status=done
|
||||
node dev.js list
|
||||
node dev.js expand --id=3 --subtasks=5
|
||||
node dev.js expand --all
|
||||
node dev.js expand --all --force
|
||||
`);
|
||||
break;
|
||||
}
|
||||
})().catch(err => {
|
||||
main().catch(err => {
|
||||
log('error', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user