myaidev-method
Version:
Comprehensive development framework with SPARC methodology for AI-assisted software development, multi-platform publishing (WordPress, PayloadCMS, Astro, Docusaurus, Mintlify), and Coolify deployment
314 lines (270 loc) ⢠8.6 kB
JavaScript
/**
* MyAIDev Method - Task Manager
* Simple priority-based task queue with JSON persistence
* Version: 1.0.0
*/
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'fs';
import { join } from 'path';
export class TaskManager {
constructor(workingDir = process.cwd()) {
this.workingDir = workingDir;
this.myaidevDir = join(workingDir, '.myaidev-method');
this.tasksDir = join(this.myaidevDir, 'tasks');
this.initializeDirectories();
}
/**
* Initialize task directories
*/
initializeDirectories() {
if (!existsSync(this.tasksDir)) {
mkdirSync(this.tasksDir, { recursive: true });
}
}
/**
* Create a new task
* @param {string} description - Task description
* @param {Object} options - Task options
* @returns {Object} Created task object
*/
createTask(description, options = {}) {
const task = {
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
description,
type: options.type || 'development',
priority: this.validatePriority(options.priority),
status: 'queued',
assignTo: options.assignTo || null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
metadata: options.metadata || {},
dependencies: options.dependencies || [],
outputs: []
};
const taskFile = join(this.tasksDir, `${task.id}.json`);
writeFileSync(taskFile, JSON.stringify(task, null, 2), 'utf8');
console.log(`ā
Task created: ${task.id}`);
console.log(` Description: ${description}`);
console.log(` Priority: ${task.priority}`);
console.log(` Type: ${task.type}`);
return task;
}
/**
* Validate priority value (1-10)
* @param {number} priority - Priority value
* @returns {number} Validated priority (default: 5)
*/
validatePriority(priority) {
if (typeof priority !== 'number') return 5;
return Math.max(1, Math.min(10, Math.floor(priority)));
}
/**
* Get task by ID
* @param {string} taskId - Task identifier
* @returns {Object|null} Task object or null if not found
*/
getTask(taskId) {
const taskFile = join(this.tasksDir, `${taskId}.json`);
if (!existsSync(taskFile)) {
return null;
}
return JSON.parse(readFileSync(taskFile, 'utf8'));
}
/**
* Update task
* @param {string} taskId - Task identifier
* @param {Object} updates - Fields to update
* @returns {Object} Updated task object
*/
updateTask(taskId, updates) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task not found: ${taskId}`);
}
// Merge updates
Object.assign(task, updates, {
updatedAt: new Date().toISOString()
});
// Validate priority if updated
if (updates.priority !== undefined) {
task.priority = this.validatePriority(updates.priority);
}
const taskFile = join(this.tasksDir, `${taskId}.json`);
writeFileSync(taskFile, JSON.stringify(task, null, 2), 'utf8');
return task;
}
/**
* Update task status
* @param {string} taskId - Task identifier
* @param {string} status - New status (queued, running, completed, failed)
* @returns {Object} Updated task
*/
updateStatus(taskId, status) {
const validStatuses = ['queued', 'running', 'completed', 'failed', 'cancelled'];
if (!validStatuses.includes(status)) {
throw new Error(`Invalid status: ${status}. Must be one of: ${validStatuses.join(', ')}`);
}
return this.updateTask(taskId, { status });
}
/**
* Add output to task
* @param {string} taskId - Task identifier
* @param {string} output - Output file path or description
* @returns {Object} Updated task
*/
addOutput(taskId, output) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task not found: ${taskId}`);
}
task.outputs.push({
path: output,
timestamp: new Date().toISOString()
});
return this.updateTask(taskId, { outputs: task.outputs });
}
/**
* List tasks with optional filtering
* @param {Object} filter - Filter criteria
* @returns {Array} Array of tasks
*/
listTasks(filter = {}) {
if (!existsSync(this.tasksDir)) {
return [];
}
const taskFiles = readdirSync(this.tasksDir).filter(f => f.endsWith('.json'));
let tasks = taskFiles.map(file =>
JSON.parse(readFileSync(join(this.tasksDir, file), 'utf8'))
);
// Apply filters
if (filter.status) {
tasks = tasks.filter(t => t.status === filter.status);
}
if (filter.assignTo) {
tasks = tasks.filter(t => t.assignTo === filter.assignTo);
}
if (filter.type) {
tasks = tasks.filter(t => t.type === filter.type);
}
if (filter.priority) {
tasks = tasks.filter(t => t.priority === filter.priority);
}
// Sort by priority (descending) then by creation time
tasks.sort((a, b) => {
if (b.priority !== a.priority) {
return b.priority - a.priority;
}
return new Date(b.createdAt) - new Date(a.createdAt);
});
return tasks;
}
/**
* Get next task in queue (highest priority, queued status)
* @returns {Object|null} Next task or null if queue is empty
*/
getNextTask() {
const queuedTasks = this.listTasks({ status: 'queued' });
return queuedTasks.length > 0 ? queuedTasks[0] : null;
}
/**
* Delete task
* @param {string} taskId - Task identifier
* @returns {boolean} True if deleted, false if not found
*/
deleteTask(taskId) {
const taskFile = join(this.tasksDir, `${taskId}.json`);
if (existsSync(taskFile)) {
unlinkSync(taskFile);
console.log(`ā
Task deleted: ${taskId}`);
return true;
}
return false;
}
/**
* Clear all completed tasks
* @returns {number} Number of tasks cleared
*/
clearCompleted() {
const completedTasks = this.listTasks({ status: 'completed' });
let count = 0;
for (const task of completedTasks) {
if (this.deleteTask(task.id)) {
count++;
}
}
console.log(`ā
Cleared ${count} completed tasks`);
return count;
}
/**
* Get task queue summary
* @returns {Object} Queue statistics
*/
getQueueSummary() {
const allTasks = this.listTasks();
const summary = {
total: allTasks.length,
queued: allTasks.filter(t => t.status === 'queued').length,
running: allTasks.filter(t => t.status === 'running').length,
completed: allTasks.filter(t => t.status === 'completed').length,
failed: allTasks.filter(t => t.status === 'failed').length,
cancelled: allTasks.filter(t => t.status === 'cancelled').length
};
return summary;
}
/**
* Print queue summary
*/
printQueueSummary() {
const summary = this.getQueueSummary();
console.log('\nš MyAIDev Method - Task Queue Summary');
console.log('ā'.repeat(40));
console.log(`Total Tasks: ${summary.total}`);
console.log(` ā³ Queued: ${summary.queued}`);
console.log(` š Running: ${summary.running}`);
console.log(` ā
Completed: ${summary.completed}`);
console.log(` ā Failed: ${summary.failed}`);
console.log(` š« Cancelled: ${summary.cancelled}`);
console.log('');
}
/**
* Export tasks to JSON file
* @param {string} outputPath - Output file path
* @returns {string} Path to exported file
*/
exportTasks(outputPath) {
const tasks = this.listTasks();
const exportData = {
exportedAt: new Date().toISOString(),
taskCount: tasks.length,
tasks
};
writeFileSync(outputPath, JSON.stringify(exportData, null, 2), 'utf8');
console.log(`ā
Exported ${tasks.length} tasks to: ${outputPath}`);
return outputPath;
}
/**
* Import tasks from JSON file
* @param {string} inputPath - Input file path
* @returns {number} Number of tasks imported
*/
importTasks(inputPath) {
if (!existsSync(inputPath)) {
throw new Error(`Import file not found: ${inputPath}`);
}
const importData = JSON.parse(readFileSync(inputPath, 'utf8'));
const tasks = importData.tasks || [];
let count = 0;
for (const task of tasks) {
// Create new task with imported data
this.createTask(task.description, {
type: task.type,
priority: task.priority,
assignTo: task.assignTo,
metadata: task.metadata
});
count++;
}
console.log(`ā
Imported ${count} tasks`);
return count;
}
}
export default TaskManager;