UNPKG

agile-planner-mcp-server

Version:

Serveur MCP pour la génération d'artefacts agiles (backlogs, features, user stories) avec IA - compatible Windsurf, Claude et Cursor

229 lines (201 loc) 8.92 kB
/** * Module de gestion des features pour l'interface CLI * @module cli/feature */ 'use strict'; const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const inquirer = require('inquirer'); const { setupTraceLog, appendToTraceLog, formatValue, startSpinner, stopSpinner, initializeApiClient } = require('./utils'); const { generateFeature } = require('../feature-generator'); const { generateFeatureMarkdown } = require('../markdown-generator'); /** * Définit les prompts pour la collecte d'informations de feature * @returns {Array} Tableau de définitions de prompts pour inquirer */ function getFeaturePrompts() { return [ { type: 'editor', name: 'featureDescription', message: 'Describe the feature you want to generate:', validate: input => input && input.length > 10 ? true : 'Please provide a detailed feature description (at least 10 characters)' }, { type: 'input', name: 'businessValue', message: 'What business value does this feature provide? (optional)', }, { type: 'number', name: 'storyCount', message: 'How many user stories should be generated?', default: 3, validate: input => input >= 3 ? true : 'The minimum number of user stories is 3' }, { type: 'input', name: 'iterationName', message: 'Name of the iteration or "next" for the next one:', default: 'next' } ]; } /** * Affiche les informations de début de génération de feature * @param {Object} info - Informations sur la feature */ function displayFeatureGenerationInfo(info) { const { epicTitle, featureDescription, storyCount } = info; if (storyCount) { console.error(chalk.blue(`Generating feature with ${storyCount} user stories...`)); console.error(chalk.blue('This might take up to 30 seconds')); console.error(chalk.blue('Please wait...')); } else { console.error(chalk.blue(`Generating feature from command arguments...`)); console.error(chalk.blue(`Epic: ${formatValue(epicTitle)}`)); console.error(chalk.blue(`Feature description: ${typeof featureDescription === 'string' ? featureDescription.substring(0, 50) + '...' : formatValue(featureDescription)}`)); } } /** * Collecte les informations de feature via prompts si nécessaire * @param {string} initialEpicName - Nom de l'epic initial * @param {string} initialFeatureDesc - Description de la feature initiale * @param {string} traceLogPath - Chemin du fichier de trace * @returns {Promise<Object>} - Objet contenant les informations de la feature */ async function collectFeatureInfo(initialEpicName, initialFeatureDesc, traceLogPath) { let epicTitle = initialEpicName; let featureDescription = initialFeatureDesc; let businessValue = 'Generated via CLI'; let storyCount = 3; let iterationName = 'next'; // Si la description de feature n'est pas fournie, demander les détails if (!featureDescription) { const answers = await inquirer.prompt(getFeaturePrompts()); // Utiliser les réponses de l'invite interactive featureDescription = answers.featureDescription; epicTitle = epicTitle || 'Default Epic'; businessValue = answers.businessValue || businessValue; storyCount = answers.storyCount || storyCount; iterationName = answers.iterationName || iterationName; displayFeatureGenerationInfo({ epicTitle, featureDescription, storyCount }); } else { displayFeatureGenerationInfo({ epicTitle, featureDescription }); } appendToTraceLog(traceLogPath, `Feature info: Epic=${epicTitle}, Description=${featureDescription}, Stories=${storyCount}`); return { epicTitle, featureDescription, businessValue, storyCount, iterationName }; } /** * Génère une feature à partir des paramètres fournis * @param {Object} featureParams - Paramètres de la feature à générer * @param {Object} client - Client API * @param {string} traceLogPath - Chemin du fichier de trace * @returns {Promise<Object>} - Résultat de la génération */ async function generateFeatureData(featureParams, client, traceLogPath) { const spinner = startSpinner(); try { // Générer la feature const featureResult = await generateFeature(featureParams, client); stopSpinner(spinner); appendToTraceLog(traceLogPath, `Feature générée avec succès: ${featureResult.feature.title}`); console.error(chalk.green(`✓ Feature "${featureResult.feature.title}" generated successfully!`)); return featureResult; } catch (error) { stopSpinner(spinner); appendToTraceLog(traceLogPath, `Erreur lors de la génération de la feature: ${error.message}`); console.error(chalk.red('Error generating feature:'), error); throw error; } } /** * Génère les fichiers Markdown pour une feature * @param {Object} featureResult - Résultat de la génération de feature * @param {string} outputPath - Chemin de sortie * @param {string} traceLogPath - Chemin du fichier de trace * @returns {Promise<Object>} - Résultat de la génération */ async function generateFeatureFiles(featureResult, outputPath, traceLogPath) { try { // Exporter les données brutes de la feature const featureDumpPath = path.join(outputPath, 'feature-last-dump.json'); console.error(chalk.blue(`Writing feature data to ${featureDumpPath} for audit and tests`)); fs.ensureDirSync(outputPath); fs.writeJSONSync(featureDumpPath, featureResult, { spaces: 2 }); // Générer les fichiers markdown await generateFeatureMarkdown(featureResult, outputPath); appendToTraceLog(traceLogPath, `Fichiers markdown générés avec succès dans ${outputPath}`); console.error(chalk.green('\n✓ All markdown files generated successfully!')); console.error(chalk.blue(`\nYou can find your generated feature files in: ${outputPath}`)); console.error(chalk.green(`✓ ${featureResult.userStories.length} user stories created`)); return { success: true, featureResult, outputPath, featureDumpPath }; } catch (error) { appendToTraceLog(traceLogPath, `Erreur lors de la génération des fichiers: ${error.message}`); console.error(chalk.red('Error generating markdown files:'), error); return { success: false, error: error.message || 'Unknown error during file generation' }; } } /** * Generate a feature with user stories using CLI * @param {string} epicName - Name of the epic (passed from yargs or inquirer) * @param {string} featureDesc - Feature description (passed from yargs or inquirer) * @param {Object} options - { outputPath } * @returns {Promise<Object>} - Result object with success/error status */ async function generateFeatureCLI(epicName, featureDesc, options = {}) { // 1. Configuration du journal de trace const traceLogPath = setupTraceLog('generateFeatureCLI', epicName, featureDesc, options); const outputPath = options.outputPath; try { // 2. Initialisation ou récupération du client API let client; if (options.client) { // Utiliser le client transmis (mode MCP ou CLI interactif) client = options.client; appendToTraceLog(traceLogPath, `Client API fourni via options: OK`); } else { // Initialiser un nouveau client (mode CLI non-interactif via yargs) const clientResult = await initializeApiClient(traceLogPath); if (!clientResult.success) return clientResult; client = clientResult.client; } // 3. Collecte des informations de la feature const featureInfo = await collectFeatureInfo(epicName, featureDesc, traceLogPath); // 4. Génération de la feature const featureParams = { featureDescription: featureInfo.featureDescription, businessValue: featureInfo.businessValue, storyCount: featureInfo.storyCount, iterationName: featureInfo.iterationName, epicName: featureInfo.epicTitle }; const featureResult = await generateFeatureData(featureParams, client, traceLogPath); // 5. Génération des fichiers markdown const effectiveOutputPath = outputPath || path.join(process.cwd(), '.agile-planner-backlog'); return generateFeatureFiles(featureResult, effectiveOutputPath, traceLogPath); } catch (error) { appendToTraceLog(traceLogPath, `Erreur dans generateFeatureCLI: ${error.message}`); console.error(chalk.red('Error during feature CLI execution:'), error); return { success: false, error: error.message || 'Unknown error' }; } } module.exports = { generateFeatureCLI, collectFeatureInfo, generateFeatureData, generateFeatureFiles };