UNPKG

oneie

Version:

šŸ¤ ONE Personal Collaborative Intelligence - Creates personalized AI workspace from your me.md profile. Simple: npx oneie → edit me.md → generate personalized agents, workflows & missions. From students to enterprises, ONE adapts to your context.

478 lines (392 loc) • 16.5 kB
#!/usr/bin/env node /** * Story Management System * Manages stories within missions and orchestrates task creation */ import fs from 'fs-extra'; import path from 'path'; import yaml from 'js-yaml'; import chalk from 'chalk'; import inquirer from 'inquirer'; export class StorySystem { constructor(projectPath = process.cwd()) { this.projectPath = projectPath; this.oneDir = path.join(projectPath, '.one'); this.missionsDir = path.join(this.oneDir, 'missions'); this.storiesDir = path.join(this.oneDir, 'stories'); this.tasksDir = path.join(this.oneDir, 'tasks'); } async createStory(missionId, objective, options = {}) { console.log(chalk.blue('\nšŸ“š Creating new story...')); // Step 1: Validate mission exists const mission = await this.getMission(missionId); if (!mission) { throw new Error(`Mission not found: ${missionId}`); } // Step 2: Generate story template const storyTemplate = await this.generateStoryTemplate(objective, mission); // Step 3: Human-AI collaboration on story design const story = await this.collaborativelyDesignStory(storyTemplate, options); // Step 4: Create story file and initialize tasks await this.initializeStory(missionId, story); // Step 5: Generate initial tasks await this.generateInitialTasks(story); console.log(chalk.green(`\nāœ… Story "${story.name}" created successfully!`)); console.log(chalk.cyan(`šŸ“ Story location: .one/missions/${missionId}/stories/${story.id}.yaml`)); return story; } async generateStoryTemplate(objective, mission) { console.log(chalk.gray('🧠 Generating story template...')); const template = { name: this.generateStoryName(objective), id: this.generateStoryId(objective), objective: objective, missionId: mission.id, suggestedTasks: this.suggestTasks(objective, mission), suggestedAgents: this.suggestStoryAgents(objective, mission), estimatedDuration: this.estimateStoryDuration(objective, mission), priority: this.assessStoryPriority(objective, mission), dependencies: this.identifyStoryDependencies(objective, mission) }; return template; } generateStoryName(objective) { return objective .toLowerCase() .replace(/[^a-z0-9\s]/g, '') .replace(/\s+/g, '-') .substring(0, 40); } generateStoryId(objective) { const hash = this.hashString(objective).substring(0, 8); return `story-${hash}`; } hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16); } suggestTasks(objective, mission) { const tasks = []; const objectiveLower = objective.toLowerCase(); // Task patterns based on objective type if (objectiveLower.includes('implement') || objectiveLower.includes('build') || objectiveLower.includes('create')) { tasks.push('planning', 'design', 'implementation', 'testing', 'documentation'); } else if (objectiveLower.includes('fix') || objectiveLower.includes('debug') || objectiveLower.includes('resolve')) { tasks.push('investigation', 'diagnosis', 'solution-design', 'implementation', 'verification'); } else if (objectiveLower.includes('optimize') || objectiveLower.includes('improve') || objectiveLower.includes('enhance')) { tasks.push('analysis', 'benchmarking', 'optimization', 'testing', 'validation'); } else if (objectiveLower.includes('deploy') || objectiveLower.includes('release') || objectiveLower.includes('launch')) { tasks.push('preparation', 'deployment', 'monitoring', 'verification', 'rollback-plan'); } else { // Default task pattern tasks.push('research', 'planning', 'execution', 'review'); } // Add tech-specific tasks based on mission architecture if (mission.architecture?.techStack?.frameworks?.includes('React')) { if (objectiveLower.includes('component') || objectiveLower.includes('ui')) { tasks.push('component-design', 'styling', 'state-management'); } } if (mission.architecture?.techStack?.frameworks?.includes('Express')) { if (objectiveLower.includes('api') || objectiveLower.includes('endpoint')) { tasks.push('route-definition', 'middleware', 'error-handling'); } } return tasks; } suggestStoryAgents(objective, mission) { const agents = new Set(); const objectiveLower = objective.toLowerCase(); // Always include story coordination agents agents.add('Story Teller'); agents.add('Task Master'); // Add mission agents that are relevant to this story const relevantMissionAgents = mission.agents.filter(agent => { const agentLower = agent.toLowerCase(); // Frontend-related objectives if (objectiveLower.includes('ui') || objectiveLower.includes('component') || objectiveLower.includes('frontend')) { return agentLower.includes('react') || agentLower.includes('frontend') || agentLower.includes('ui'); } // Backend-related objectives if (objectiveLower.includes('api') || objectiveLower.includes('server') || objectiveLower.includes('backend')) { return agentLower.includes('backend') || agentLower.includes('api') || agentLower.includes('server'); } // Database-related objectives if (objectiveLower.includes('database') || objectiveLower.includes('data') || objectiveLower.includes('storage')) { return agentLower.includes('database') || agentLower.includes('data'); } // Testing-related objectives if (objectiveLower.includes('test') || objectiveLower.includes('quality')) { return agentLower.includes('test') || agentLower.includes('quality') || agentLower.includes('qa'); } // Include general development agents return agentLower.includes('developer') || agentLower.includes('specialist'); }); relevantMissionAgents.forEach(agent => agents.add(agent)); // If no specific agents matched, include some general ones from mission if (agents.size <= 2) { mission.agents.slice(0, 2).forEach(agent => agents.add(agent)); } return Array.from(agents); } estimateStoryDuration(objective, mission) { let baseDays = 3; // Default story duration const objectiveLower = objective.toLowerCase(); const complexityKeywords = ['complex', 'integration', 'migration', 'refactor', 'architecture']; const simpleKeywords = ['fix', 'update', 'small', 'minor', 'simple']; // Adjust based on objective complexity if (complexityKeywords.some(keyword => objectiveLower.includes(keyword))) { baseDays += 3; } else if (simpleKeywords.some(keyword => objectiveLower.includes(keyword))) { baseDays -= 1; } // Adjust based on mission complexity if (mission.complexity === 'high') { baseDays += 2; } else if (mission.complexity === 'low') { baseDays -= 1; } baseDays = Math.max(1, baseDays); // Minimum 1 day return baseDays <= 3 ? `${baseDays} days` : `${Math.ceil(baseDays/5)} week${baseDays > 5 ? 's' : ''}`; } assessStoryPriority(objective, mission) { const objectiveLower = objective.toLowerCase(); // High priority indicators const highPriorityKeywords = ['critical', 'urgent', 'blocker', 'security', 'bug', 'fix']; if (highPriorityKeywords.some(keyword => objectiveLower.includes(keyword))) { return 'high'; } // Low priority indicators const lowPriorityKeywords = ['nice-to-have', 'enhancement', 'optional', 'future']; if (lowPriorityKeywords.some(keyword => objectiveLower.includes(keyword))) { return 'low'; } return 'medium'; } identifyStoryDependencies(objective, mission) { // For now, return empty dependencies. In future versions, this could analyze // existing stories and identify dependencies based on objective content return []; } async collaborativelyDesignStory(template, options) { console.log(chalk.blue('\nšŸ¤ Collaborative story design...')); // Show AI analysis console.log(chalk.cyan('\nšŸ¤– AI Analysis:')); console.log(` Suggested name: ${template.name}`); console.log(` Estimated duration: ${template.estimatedDuration}`); console.log(` Priority: ${template.priority}`); console.log(` Suggested agents: ${template.suggestedAgents.join(', ')}`); console.log(` Suggested tasks: ${template.suggestedTasks.join(', ')}`); if (options.interactive !== false) { // Get human input const answers = await inquirer.prompt([ { type: 'input', name: 'name', message: 'Story name:', default: template.name }, { type: 'editor', name: 'description', message: 'Story description:', default: `# ${template.objective}\n\n[Describe what this story will accomplish and how it contributes to the mission]` }, { type: 'checkbox', name: 'agents', message: 'Select agents for this story:', choices: template.suggestedAgents, default: template.suggestedAgents.slice(0, 3) }, { type: 'list', name: 'priority', message: 'Story priority:', choices: ['high', 'medium', 'low'], default: template.priority }, { type: 'input', name: 'duration', message: 'Expected duration:', default: template.estimatedDuration } ]); // Merge human input with AI suggestions Object.assign(template, answers); template.tasks = template.suggestedTasks; // Keep AI task suggestions } else { // Non-interactive mode template.agents = template.suggestedAgents.slice(0, 3); template.tasks = template.suggestedTasks; template.duration = template.estimatedDuration; template.description = `# ${template.objective}\n\nAI-generated story to ${template.objective.toLowerCase()}.`; } console.log(chalk.green('āœ… Story design complete')); return template; } async initializeStory(missionId, story) { console.log(chalk.gray('šŸ“ Creating story structure...')); const missionDir = path.join(this.missionsDir, missionId); const storiesDir = path.join(missionDir, 'stories'); // Ensure stories directory exists await fs.ensureDir(storiesDir); // Create story data const storyData = { name: story.name, id: story.id, missionId: missionId, objective: story.objective, description: story.description, status: 'planning', priority: story.priority, created: new Date().toISOString(), agents: story.agents, tasks: story.tasks.map(taskName => ({ name: taskName, id: `task-${this.hashString(taskName).substring(0, 8)}`, status: 'pending', created: new Date().toISOString() })), duration: story.duration, dependencies: story.dependencies || [] }; // Save story file await fs.writeFile( path.join(storiesDir, `${story.id}.yaml`), yaml.dump(storyData, { indent: 2 }) ); // Create story README const readmeContent = `# ${story.name} ${story.description || `Story: ${story.objective}`} ## Objective ${story.objective} ## Agents ${story.agents.map(agent => `- ${agent}`).join('\n')} ## Tasks ${storyData.tasks.map(task => `- [ ] ${task.name} (${task.status})`).join('\n')} ## Details - Priority: ${story.priority} - Duration: ${story.duration} - Status: ${storyData.status} - Created: ${storyData.created} ## Dependencies ${story.dependencies.length > 0 ? story.dependencies.map(dep => `- ${dep}`).join('\n') : 'None'} `; await fs.writeFile(path.join(storiesDir, `${story.id}.md`), readmeContent); story.tasks = storyData.tasks; // Update with full task objects return story; } async generateInitialTasks(story) { console.log(chalk.gray('šŸ“‹ Generating initial tasks...')); // Tasks are already created in initializeStory // This method could be extended to create more detailed task specifications console.log(chalk.green(`āœ… Generated ${story.tasks.length} tasks`)); } async listStories(missionId = null) { const stories = []; if (missionId) { // List stories for a specific mission const missionDir = path.join(this.missionsDir, missionId); const storiesDir = path.join(missionDir, 'stories'); if (await fs.pathExists(storiesDir)) { const storyFiles = await fs.readdir(storiesDir); for (const file of storyFiles) { if (file.endsWith('.yaml')) { const storyPath = path.join(storiesDir, file); const storyData = yaml.load(await fs.readFile(storyPath, 'utf8')); stories.push(storyData); } } } } else { // List all stories across all missions if (await fs.pathExists(this.missionsDir)) { const missionDirs = await fs.readdir(this.missionsDir); for (const missionDir of missionDirs) { const storiesDir = path.join(this.missionsDir, missionDir, 'stories'); if (await fs.pathExists(storiesDir)) { const storyFiles = await fs.readdir(storiesDir); for (const file of storyFiles) { if (file.endsWith('.yaml')) { const storyPath = path.join(storiesDir, file); const storyData = yaml.load(await fs.readFile(storyPath, 'utf8')); stories.push(storyData); } } } } } } return stories.sort((a, b) => new Date(b.created) - new Date(a.created)); } async getStory(storyId, missionId = null) { if (missionId) { // Look in specific mission const storyPath = path.join(this.missionsDir, missionId, 'stories', `${storyId}.yaml`); if (await fs.pathExists(storyPath)) { return yaml.load(await fs.readFile(storyPath, 'utf8')); } } else { // Search across all missions const stories = await this.listStories(); return stories.find(story => story.id === storyId); } return null; } async updateStoryStatus(storyId, status, missionId = null) { const story = await this.getStory(storyId, missionId); if (!story) { throw new Error(`Story not found: ${storyId}`); } story.status = status; story.updated = new Date().toISOString(); // Find and update the story file const storyPath = missionId ? path.join(this.missionsDir, missionId, 'stories', `${storyId}.yaml`) : await this.findStoryPath(storyId); if (storyPath && await fs.pathExists(storyPath)) { await fs.writeFile(storyPath, yaml.dump(story, { indent: 2 })); return story; } throw new Error(`Could not update story file: ${storyId}`); } async findStoryPath(storyId) { const stories = await this.listStories(); const story = stories.find(s => s.id === storyId); if (story) { return path.join(this.missionsDir, story.missionId, 'stories', `${storyId}.yaml`); } return null; } async getMission(missionId) { const missionFile = path.join(this.missionsDir, missionId, 'mission.yaml'); if (await fs.pathExists(missionFile)) { return yaml.load(await fs.readFile(missionFile, 'utf8')); } return null; } } // CLI interface functions export async function createStory(missionId, objective, options = {}) { const storySystem = new StorySystem(); return await storySystem.createStory(missionId, objective, options); } export async function listStories(missionId = null) { const storySystem = new StorySystem(); return await storySystem.listStories(missionId); } export async function getStory(storyId, missionId = null) { const storySystem = new StorySystem(); return await storySystem.getStory(storyId, missionId); } export async function updateStoryStatus(storyId, status, missionId = null) { const storySystem = new StorySystem(); return await storySystem.updateStoryStatus(storyId, status, missionId); }