taskmanager-ai
Version:
A CLI tool to manage your projects tasks with AI
229 lines (203 loc) • 8.04 kB
JavaScript
/**
* Módulo para integração com OpenAI
*
* Este módulo implementa a geração de tarefas usando a API da OpenAI.
*/
import chalk from 'chalk';
import OpenAI from 'openai';
import { simulateTasks } from './simulation.js';
/**
* Gera tarefas usando a API da OpenAI
* @param {String} projectDescription - Descrição detalhada do projeto
* @param {String} projectType - Tipo de projeto ('new' ou 'existing')
* @param {Number} taskCount - Número de tarefas a serem geradas
* @param {Object} aiConfig - Configuração da IA
* @returns {Array} Array de tarefas geradas
*/
export async function generateTasksWithOpenAI(projectDescription, projectType, taskCount, aiConfig) {
// Verificar se a chave de API está configurada
if (!aiConfig.apiKey) {
throw new Error('Chave de API da OpenAI não configurada');
}
try {
const openai = new OpenAI({ apiKey: aiConfig.apiKey });
const prompt = createPromptForTaskGeneration(projectDescription, projectType, taskCount);
const response = await openai.chat.completions.create({
model: aiConfig.model || 'gpt-4',
messages: [
{ role: 'system', content: 'Você é um assistente especializado em gerenciamento de projetos, com experiência em desenvolvimento de software e metodologias ágeis.' },
{ role: 'user', content: prompt }
],
temperature: 0.7,
max_tokens: 4096
});
const tasksJson = parseTasksFromResponse(response.choices[0].message.content);
return validateAndAdjustDependencies(tasksJson);
} catch (error) {
console.error(chalk.red(`Erro na API da OpenAI: ${error.message}`));
// Em caso de erro, retorna tarefas simuladas
return simulateTasks(projectDescription, projectType, taskCount);
}
}
/**
* Cria um prompt para geração de tarefas
* @param {String} projectDescription - Descrição do projeto
* @param {String} projectType - Tipo de projeto
* @param {Number} taskCount - Número de tarefas
* @returns {String} Prompt formatado
*/
function createPromptForTaskGeneration(projectDescription, projectType, taskCount) {
return `
Gere ${taskCount} tarefas para um ${projectType === 'new' ? 'novo' : 'existente'} projeto com a seguinte descrição:
${projectDescription}
Retorne as tarefas no formato JSON, seguindo esta estrutura:
[
{
"id": 1,
"title": "Título da tarefa",
"description": "Descrição detalhada da tarefa",
"status": "pending",
"priority": "high|medium|low",
"dependencies": [array de IDs das tarefas dependentes],
"details": "Detalhes de implementação",
"testStrategy": "Estratégia de teste",
"category": "setup|frontend|backend|database|etc",
"subtasks": [
{
"id": 1,
"title": "Título da subtarefa",
"description": "Descrição da subtarefa",
"status": "pending"
}
]
}
]
Gere tarefas lógicas e bem estruturadas que sigam boas práticas de desenvolvimento.
Se o projeto for novo, comece com tarefas de setup e configuração inicial.
Se for um projeto existente, concentre-se em tarefas de desenvolvimento, melhoria ou correção.
Regras importantes para dependências:
1. Uma tarefa só pode depender de tarefas com IDs menores que o seu próprio ID
2. As dependências devem formar um grafo acíclico direcionado (DAG)
3. Uma tarefa não pode depender de si mesma
4. As dependências devem ser lógicas e fazer sentido no contexto do projeto
5. Se uma tarefa A depende de B, e B depende de C, então A deve depender explicitamente de C também
6. Para projetos novos, a primeira tarefa (ID 1) não deve ter dependências
7. Para projetos existentes, verifique as tarefas existentes listadas na descrição do projeto
Certifique-se de que:
1. As tarefas sejam específicas e mensuráveis
2. As dependências entre tarefas sejam lógicas e respeitem as regras acima
3. As prioridades reflitam a importância real das tarefas
4. As subtarefas sejam relevantes e ajudem a quebrar tarefas complexas
5. As estratégias de teste sejam práticas e efetivas
`;
}
/**
* Analisa a resposta da OpenAI e extrai as tarefas
* @param {String} responseText - Texto da resposta da OpenAI
* @returns {Array} Array de tarefas
*/
function parseTasksFromResponse(responseText) {
try {
// Tenta extrair o JSON da resposta
const jsonMatch = responseText.match(/\[[\s\S]*\]/);
if (!jsonMatch) {
throw new Error('Não foi possível encontrar JSON na resposta');
}
const tasks = JSON.parse(jsonMatch[0]);
// Adiciona timestamps e garante que todos os campos necessários existam
const now = new Date().toISOString();
return tasks.map((task, index) => ({
id: index + 1,
title: task.title || `Tarefa ${index + 1}`,
description: task.description || '',
status: task.status || 'pending',
priority: task.priority || 'medium',
dependencies: task.dependencies || [],
details: task.details || '',
testStrategy: task.testStrategy || '',
category: task.category || 'general',
createdAt: now,
updatedAt: now,
subtasks: (task.subtasks || []).map((subtask, subIndex) => ({
id: subIndex + 1,
title: subtask.title || `Subtarefa ${subIndex + 1}`,
description: subtask.description || '',
status: subtask.status || 'pending',
createdAt: now,
updatedAt: now
}))
}));
} catch (error) {
console.error(chalk.red(`Erro ao analisar resposta da OpenAI: ${error.message}`));
throw error;
}
}
/**
* Valida e ajusta as dependências das tarefas
* @param {Array} tasks - Array de tarefas
* @returns {Array} Array de tarefas com dependências validadas
*/
export function validateAndAdjustDependencies(tasks) {
// Cria um grafo de dependências
const graph = new Map();
tasks.forEach(task => graph.set(task.id, new Set()));
// Função para verificar ciclos no grafo
function hasCycle(taskId, visited = new Set(), recursionStack = new Set()) {
visited.add(taskId);
recursionStack.add(taskId);
const dependencies = graph.get(taskId);
for (const depId of dependencies) {
if (!visited.has(depId)) {
if (hasCycle(depId, visited, recursionStack)) {
return true;
}
} else if (recursionStack.has(depId)) {
return true;
}
}
recursionStack.delete(taskId);
return false;
}
// Valida e ajusta as dependências de cada tarefa
tasks.forEach(task => {
// Filtra dependências inválidas
task.dependencies = task.dependencies.filter(depId => {
// Remove dependências para IDs maiores
if (depId >= task.id) {
console.log(chalk.yellow(`Removendo dependência inválida: Tarefa ${task.id} não pode depender da tarefa ${depId}`));
return false;
}
// Remove dependências para tarefas que não existem
if (!tasks.some(t => t.id === depId)) {
console.log(chalk.yellow(`Removendo dependência inexistente: Tarefa ${depId} não existe`));
return false;
}
return true;
});
// Adiciona dependências transitivas
const transitiveDeps = new Set();
task.dependencies.forEach(depId => {
const depTask = tasks.find(t => t.id === depId);
if (depTask) {
depTask.dependencies.forEach(transitiveDepId => {
if (transitiveDepId < task.id && transitiveDepId !== task.id) {
transitiveDeps.add(transitiveDepId);
}
});
}
});
task.dependencies = [...new Set([...task.dependencies, ...transitiveDeps])].sort((a, b) => a - b);
// Atualiza o grafo
graph.get(task.id).clear();
task.dependencies.forEach(depId => graph.get(task.id).add(depId));
});
// Verifica ciclos no grafo
tasks.forEach(task => {
if (hasCycle(task.id)) {
console.log(chalk.yellow(`Detectado ciclo de dependências na tarefa ${task.id}. Removendo dependências circulares.`));
task.dependencies = [];
graph.get(task.id).clear();
}
});
return tasks;
}