@godrix/flow
Version:
Context-Driven Development CLI: Create structured task contexts with AI-optimized templates for efficient software development workflow
447 lines (412 loc) • 18.1 kB
JavaScript
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { createTaskContext } from './services/taskService.js';
const program = new Command();
program
.name('flow')
.description('CLI tool to create structured task contexts with templates')
.version('1.5.0');
program
.argument('<taskName>', 'Name of the task (e.g., task-1234, FEATURE_AUTH)')
.option('-t, --type <type>', 'Task type: feature, bug, improvement, research', 'feature')
.option('--no-auto-generate', 'Use traditional templates instead of auto-generated content')
.description('Create a new task context with templates')
.action(async (taskName, options) => {
try {
console.log(chalk.blue(`🚀 Creating ${options.type} task context for: ${taskName}`));
const currentDir = process.cwd();
const result = await createTaskContext(taskName, currentDir, options.type, options.autoGenerate, taskName);
if (result.success) {
console.log(chalk.green(`✅ Task context created successfully!`));
console.log(chalk.gray(`📁 Location: ${result.taskPath}`));
console.log(chalk.gray(`📄 Files created: ${result.filesCreated?.join(', ') || 'none'}`));
console.log(chalk.gray(`🏷️ Type: ${options.type}`));
}
else {
console.error(chalk.red(`❌ Error creating task context: ${result.error}`));
process.exit(1);
}
}
catch (error) {
console.error(chalk.red(`❌ Unexpected error: ${error}`));
process.exit(1);
}
});
program
.command('list')
.description('List all existing tasks')
.action(async () => {
try {
const currentDir = process.cwd();
const flowDir = path.join(currentDir, '.flow');
if (!(await fs.pathExists(flowDir))) {
console.log(chalk.yellow('⚠️ No .flow directory found. Run "flow <task-name>" to initialize.'));
return;
}
const items = await fs.readdir(flowDir);
const taskDirs = items.filter(item => {
return fs.statSync(path.join(flowDir, item)).isDirectory() && /^\d{2}_/.test(item);
});
if (taskDirs.length === 0) {
console.log(chalk.gray('📝 No tasks found. Create your first task with "flow <task-name>"'));
return;
}
console.log(chalk.blue('📋 Existing Tasks:'));
console.log(chalk.gray('─'.repeat(50)));
for (const taskDir of taskDirs.sort()) {
const taskPath = path.join(flowDir, taskDir);
const taskName = taskDir.replace(/^\d{2}_/, '');
const taskNumber = taskDir.match(/^(\d{2})_/)?.[1] || '00';
// Check if task has completion report
const completionReportPath = path.join(taskPath, 'COMPLETION_REPORT.md');
const hasCompletionReport = await fs.pathExists(completionReportPath);
// Try to determine task type from business context
const businessContextPath = path.join(taskPath, 'BUSINESS_CONTEXT.md');
let taskType = 'unknown';
if (await fs.pathExists(businessContextPath)) {
const content = await fs.readFile(businessContextPath, 'utf-8');
if (content.includes('FEATURE') || content.includes('feature'))
taskType = 'feature';
else if (content.includes('BUG') || content.includes('bug'))
taskType = 'bug';
else if (content.includes('IMPROVEMENT') || content.includes('improvement'))
taskType = 'improvement';
else if (content.includes('RESEARCH') || content.includes('research'))
taskType = 'research';
}
const status = hasCompletionReport ? chalk.green('✅ Complete') : chalk.yellow('🔄 In Progress');
const typeIcon = {
feature: '✨',
bug: '🐛',
improvement: '🔧',
research: '🔬',
unknown: '📝'
}[taskType];
console.log(`${chalk.cyan(taskNumber)} ${typeIcon} ${chalk.white(taskName)} ${status}`);
}
console.log(chalk.gray('─'.repeat(50)));
console.log(chalk.gray(`Total: ${taskDirs.length} task(s)`));
}
catch (error) {
console.error(chalk.red(`❌ Error listing tasks: ${error}`));
process.exit(1);
}
});
program
.command('validate <taskName>')
.description('Validate the structure of a specific task')
.action(async (taskName) => {
try {
const currentDir = process.cwd();
const flowDir = path.join(currentDir, '.flow');
if (!(await fs.pathExists(flowDir))) {
console.log(chalk.red('❌ No .flow directory found.'));
process.exit(1);
}
// Find task directory
const items = await fs.readdir(flowDir);
const taskDir = items.find(item => {
return fs.statSync(path.join(flowDir, item)).isDirectory() &&
item.endsWith(`_${taskName}`) &&
/^\d{2}_/.test(item);
});
if (!taskDir) {
console.log(chalk.red(`❌ Task "${taskName}" not found.`));
console.log(chalk.gray('Use "flow list" to see available tasks.'));
process.exit(1);
}
const taskPath = path.join(flowDir, taskDir);
console.log(chalk.blue(`🔍 Validating task: ${taskName}`));
console.log(chalk.gray(`📁 Path: ${taskPath}`));
console.log(chalk.gray('─'.repeat(50)));
const requiredFiles = [
'APPROACH.md',
'BUSINESS_CONTEXT.md',
'COMPLETION_REPORT.md'
];
let isValid = true;
const results = [];
for (const file of requiredFiles) {
const filePath = path.join(taskPath, file);
const exists = await fs.pathExists(filePath);
const issues = [];
if (!exists) {
issues.push('File does not exist');
isValid = false;
}
else {
// Check if file has content
const content = await fs.readFile(filePath, 'utf-8');
if (content.trim().length === 0) {
issues.push('File is empty');
isValid = false;
}
// Check for template variables
if (content.includes('{{TASK_NAME}}')) {
issues.push('Contains unresolved template variables');
isValid = false;
}
// File-specific validations
if (file === 'BUSINESS_CONTEXT.md') {
if (!content.includes('Given') || !content.includes('When') || !content.includes('Then')) {
issues.push('Missing Gherkin scenarios');
isValid = false;
}
}
if (file === 'APPROACH.md') {
if (!content.includes('##') || content.split('##').length < 3) {
issues.push('Insufficient structure (needs at least 3 sections)');
isValid = false;
}
}
}
results.push({
file,
status: issues.length === 0 ? '✅ Valid' : '❌ Issues',
issues
});
}
// Display results
for (const result of results) {
console.log(`${result.status} ${chalk.white(result.file)}`);
if (result.issues.length > 0) {
for (const issue of result.issues) {
console.log(chalk.red(` └─ ${issue}`));
}
}
}
console.log(chalk.gray('─'.repeat(50)));
if (isValid) {
console.log(chalk.green('✅ Task structure is valid!'));
}
else {
console.log(chalk.red('❌ Task structure has issues that need to be fixed.'));
}
}
catch (error) {
console.error(chalk.red(`❌ Error validating task: ${error}`));
process.exit(1);
}
});
program
.command('init')
.description('Initialize a new Flow project')
.option('-n, --name <name>', 'Project name')
.option('-m, --mission <mission>', 'Project mission statement')
.option('--agents-scoped', 'Create AGENTS.md inside .flow directory (legacy behavior)')
.action(async (options) => {
try {
console.log(chalk.blue('🚀 Initializing Flow project...'));
const currentDir = process.cwd();
// Check if Flow project already exists
const flowDir = path.join(currentDir, '.flow');
if (await fs.pathExists(flowDir)) {
console.log(chalk.yellow('⚠️ Flow project already exists!'));
console.log(chalk.gray(`📁 Directory: ${flowDir}`));
console.log(chalk.gray('Use "flow update-context" to update existing context or remove .flow directory to reinitialize.'));
return;
}
// Create .flow directory
await fs.ensureDir(flowDir);
// Create PROJECT_CONTEXT.md
const projectContextPath = path.join(flowDir, 'PROJECT_CONTEXT.md');
const currentDate = new Date().toISOString().split('T')[0];
let projectContextContent = `# Contexto do Projeto
## 🎯 Missão & Objetivos
### Missão Principal
${options.mission || '*Descreva a missão central do projeto - o problema que resolve e o valor que entrega.*'}
### Objetivos de Longo Prazo
- **Objetivo 1**: Descrição específica e mensurável
- **Objetivo 2**: Descrição específica e mensurável
- **Objetivo 3**: Descrição específica e mensurável
## 🛠️ Stack Tecnológico
### Frontend
- **Framework**: A definir
- **Styling**: A definir
- **State Management**: A definir
### Backend
- **Runtime**: A definir
- **Framework**: A definir
- **Database**: A definir
### DevOps & Infraestrutura
- **Deployment**: A definir
- **Monitoring**: A definir
- **CI/CD**: A definir
## 🏗️ Arquitetura
### Princípios Arquiteturais
- **Modularidade**: Componentes independentes e reutilizáveis
- **Escalabilidade**: Capacidade de crescer com a demanda
- **Manutenibilidade**: Código limpo e bem documentado
- **Performance**: Otimização contínua
### Padrões de Design
- **Padrão 1**: Descrição e justificativa
- **Padrão 2**: Descrição e justificativa
### Diretrizes de Desenvolvimento
- **Code Review**: Obrigatório para todas as mudanças
- **Testing**: Cobertura mínima de 80%
- **Documentation**: Documentação atualizada
## 📋 Padrões de Desenvolvimento
### Convenções de Código
- **Naming**: camelCase para variáveis, PascalCase para classes
- **Formatting**: Prettier com configuração padrão
- **Linting**: ESLint com regras estritas
### Qualidade de Código
- **Complexity**: Máximo 10 por função
- **Duplication**: Máximo 3% de código duplicado
- **Coverage**: Mínimo 80% de cobertura de testes
### Git Workflow
- **Branches**: feature/, bugfix/, hotfix/
- **Commits**: Conventional Commits
- **Pull Requests**: Obrigatórios para merge
## 🔧 Ferramentas e Configurações
### Desenvolvimento
- **IDE**: VS Code com extensões recomendadas
- **Debugging**: Configuração específica do projeto
- **Hot Reload**: Configurado para desenvolvimento local
### Monitoramento
- **Logs**: Estruturados com níveis apropriados
- **Metrics**: Coleta de métricas de performance
- **Alerts**: Configuração de alertas críticos
### CI/CD
- **Build**: Pipeline automatizado
- **Testing**: Execução automática de testes
- **Deploy**: Deploy automático em ambientes
## 📊 Métricas de Sucesso
### Técnicas
- **Performance**: Tempo de resposta < 200ms
- **Availability**: 99.9% de uptime
- **Error Rate**: < 0.1% de erros
### Negócio
- **User Satisfaction**: Score > 4.5/5
- **Adoption Rate**: Crescimento mensal de usuários
- **Feature Usage**: Taxa de utilização de funcionalidades
## 🔄 Processo de Evolução
### Atualizações de Contexto
Este documento deve ser atualizado quando:
- Novas tecnologias são adotadas
- Princípios arquiteturais mudam
- Padrões de desenvolvimento evoluem
- Métricas de sucesso são redefinidas
### Aprovação de Mudanças
- **Minor Changes**: Aprovação do tech lead
- **Major Changes**: Aprovação do time + arquiteto
- **Architectural Changes**: Aprovação do CTO + stakeholders
---
**Última Atualização**: ${currentDate}
**Próxima Revisão**: ${new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}
**Responsável**: A definir`;
await fs.writeFile(projectContextPath, projectContextContent);
// Create AGENTS.md
const agentsPath = options.agentsScoped
? path.join(flowDir, 'AGENTS.md') // Legacy: inside .flow
: path.join(currentDir, 'AGENTS.md'); // Default: project root
const agentsTemplatePath = path.join(path.dirname(new URL(import.meta.url).pathname), 'templates', 'AGENTS.md');
if (await fs.pathExists(agentsTemplatePath)) {
const agentsContent = await fs.readFile(agentsTemplatePath, 'utf-8');
await fs.writeFile(agentsPath, agentsContent);
}
else {
// Fallback: create basic AGENTS.md if template not found
const basicAgentsContent = `# 🤖 Instruções para Agentes de IA
## 🎯 Contexto do Projeto
Este é um projeto Flow que utiliza desenvolvimento orientado a contexto.
## 📁 Estrutura do Projeto
- \`.flow/\` - Diretório principal do Flow
- \`.flow/PROJECT_CONTEXT.md\` - Contexto global do projeto
- \`AGENTS.md\` - Este arquivo com instruções para IA (${options.agentsScoped ? 'dentro de .flow/' : 'na raiz do projeto'})
- \`.flow/task-*/\` - Diretórios de tasks individuais
## 🔄 Fluxo de Desenvolvimento
1. **Criar Task**: Use \`flow <task-name>\` para nova task
2. **Definir Contexto**: Preencha BUSINESS_CONTEXT.md
3. **Planejar**: Preencha APPROACH.md
4. **Implementar**: Desenvolva a solução
5. **Documentar**: Preencha COMPLETION_REPORT.md
## ⚠️ Regras Importantes
- **Isolamento**: Cada task deve ser independente
- **Referências**: Só referencie outras tasks quando necessário
- **Qualidade**: Valide sempre com \`flow validate <task-name>\`
## 🛠️ Comandos CLI Disponíveis
- \`flow <task-name>\` - Criar nova task
- \`flow list\` - Listar tasks existentes
- \`flow validate <task-name>\` - Validar estrutura da task
- \`flow init\` - Inicializar projeto Flow
- \`flow mcp\` - Iniciar servidor MCP para IA
**Última Atualização**: ${currentDate}
`;
await fs.writeFile(agentsPath, basicAgentsContent);
}
// Create .gitignore for .flow directory
const gitignorePath = path.join(flowDir, '.gitignore');
const gitignoreContent = options.agentsScoped
? `# Flow project files (agents-scoped mode)
# Keep PROJECT_CONTEXT.md and AGENTS.md in version control
!PROJECT_CONTEXT.md
!AGENTS.md
# Ignore task-specific files (they should be in individual task folders)
*.md
!PROJECT_CONTEXT.md
!AGENTS.md
`
: `# Flow project files (default mode)
# Keep PROJECT_CONTEXT.md in version control
# AGENTS.md is in project root and should be committed there
!PROJECT_CONTEXT.md
# Ignore task-specific files (they should be in individual task folders)
*.md
!PROJECT_CONTEXT.md
`;
await fs.writeFile(gitignorePath, gitignoreContent);
console.log(chalk.green('✅ Flow project initialized successfully!'));
console.log(chalk.gray(`📁 Directory: ${flowDir}`));
console.log(chalk.gray(`📄 PROJECT_CONTEXT.md: ${projectContextPath}`));
console.log(chalk.gray(`📄 AGENTS.md: ${agentsPath} ${options.agentsScoped ? '(inside .flow/)' : '(in project root)'}`));
console.log(chalk.gray(`📅 Created: ${currentDate}`));
if (options.name) {
console.log(chalk.gray(`📋 Project name: ${options.name}`));
}
if (options.mission) {
console.log(chalk.gray(`🎯 Mission: ${options.mission}`));
}
console.log(chalk.blue('\n✅ Next steps:'));
console.log(chalk.gray('1. Use "flow <task-name>" to create your first task'));
console.log(chalk.gray('2. Use "flow list" to see all tasks'));
console.log(chalk.gray('3. Use "flow validate <task-name>" to validate task structure'));
console.log(chalk.gray('4. Consult AGENTS.md for detailed AI instructions'));
if (options.agentsScoped) {
console.log(chalk.yellow('\n📝 Note: AGENTS.md was created inside .flow/ (agents-scoped mode)'));
}
else {
console.log(chalk.blue('\n📝 Note: AGENTS.md was created in project root (default mode)'));
}
}
catch (error) {
console.error(chalk.red(`❌ Error initializing Flow project: ${error}`));
process.exit(1);
}
});
program
.command('mcp')
.description('Start MCP server for AI integration')
.action(async () => {
// Import and start MCP server
const { spawn } = await import('child_process');
const mcpServerPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'mcp-server.js');
console.log(chalk.blue('🚀 Starting Flow MCP Server...'));
console.log(chalk.gray('This server exposes Flow commands to AI assistants via MCP protocol.'));
console.log(chalk.gray('Available tools: create_task, list_tasks, validate_task, get_task_info, get_project_status'));
const server = spawn('node', [mcpServerPath], {
stdio: 'inherit'
});
server.on('error', (error) => {
console.error(chalk.red(`❌ MCP Server error: ${error}`));
process.exit(1);
});
server.on('close', (code) => {
console.log(chalk.gray(`MCP Server exited with code ${code}`));
process.exit(code || 0);
});
});
program.parse();