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
JavaScript
/**
* 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);
}