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

210 lines (180 loc) 7.16 kB
/** * Module de formatage des epics * @module markdown/epic-formatter */ const path = require('path'); const fs = require('fs-extra'); const chalk = require('chalk'); const { createSlug, handleMarkdownError, markdownInstructions } = require('./utils'); const { processFeatures } = require('./feature-formatter'); /** * Génère le contenu d'un epic en markdown * @param {Object} epic - Données de l'epic * @param {string} epic.title - Titre de l'epic * @param {string} [epic.description] - Description de l'epic * @returns {string} - Contenu markdown de l'epic */ function generateEpicContent(epic) { const epicTitle = epic.title; const epicDescription = epic.description || ''; let content = `# Epic: ${epicTitle}\n\n`; content += `${markdownInstructions.epicFileInstructions}\n\n`; content += `## Description\n\n${epicDescription}\n\n`; content += `## Features\n\n`; // Les features seront ajoutées par référence, pas directement ici content += `_Les features associées se trouvent dans le dossier "features"._\n\n`; return content; } /** * Traite un epic individuel et crée le fichier markdown correspondant * @param {Object} epic - Données de l'epic * @param {string} epic.title - Titre de l'epic * @param {string} [epic.description] - Description de l'epic * @param {Array} [epic.features] - Liste des features de l'epic * @param {string} epicsDir - Chemin du répertoire des epics * @param {Map} userStoryMap - Map pour suivre les user stories * @param {Object} backlogJson - Données JSON du backlog pour référencement * @param {Array} backlogJson.epics - Liste des epics dans le backlog * @returns {Promise<void>} */ async function processEpic(epic, epicsDir, userStoryMap, backlogJson) { if (!epic?.title) { throw handleMarkdownError('Invalid epic data: title is required'); } const epicTitle = epic.title; const epicSlug = createSlug(epicTitle); try { // Créer le répertoire de l'epic const epicDir = path.join(epicsDir, epicSlug); await fs.ensureDir(epicDir); // Générer le contenu markdown const epicContent = generateEpicContent(epic); // Écrire le fichier const epicFilePath = path.join(epicDir, 'epic.md'); await fs.writeFile(epicFilePath, epicContent); console.error(chalk.green(`✓ Epic document created: ${epicFilePath} (stderr)`)); // Créer et ajouter l'entrée JSON de l'epic au backlog const epicJson = createEpicJson(epic, epicFilePath, epicSlug); backlogJson.epics.push(epicJson); // Traiter les features de cet epic await processEpicFeatures(epic, epicDir, epicTitle, userStoryMap, epicJson); return epicJson; } catch (error) { throw handleMarkdownError(`Error processing epic '${epicTitle}'`, error); } } /** * Traite les epics et crée les fichiers markdown correspondants * @param {Array} epics - Liste des epics à traiter * @param {string} backlogDir - Chemin du répertoire du backlog * @param {Map} userStoryMap - Map pour suivre les user stories * @param {Object} backlogJson - Données JSON du backlog pour référencement * @returns {Promise<void>} */ async function processEpics(epics, backlogDir, userStoryMap, backlogJson) { // Vérification simplifiée pour des epics valides if (!Array.isArray(epics) || epics.length === 0) { console.error(chalk.yellow('⚠️ No epics found, skipping epics processing (stderr)')); return; } try { // Créer le répertoire des epics const epicsDir = path.join(backlogDir, 'epics'); await fs.ensureDir(epicsDir); // Initialiser le tableau des epics dans le backlog JSON backlogJson.epics = []; // Traiter chaque epic for (const epic of epics) { await processEpic(epic, epicsDir, userStoryMap, backlogJson); } } catch (error) { throw handleMarkdownError('Error processing epics', error); } } /** * Factory function pour créer un formateur d'epics * @param {Object} options - Options de configuration * @returns {Object} - API du formateur d'epics */ function createEpicFormatter(_options = {}) { /** * Formate et écrit le contenu d'un epic dans un fichier markdown * @param {Object} epicData - Données de l'epic * @param {string} epicDirectoryPath - Chemin du répertoire où écrire l'epic * @returns {Promise<string>} - Chemin du fichier créé */ async function format(epicData, epicDirectoryPath) { if (!epicData?.title) { throw handleMarkdownError('Invalid epic data: title is required'); } if (!epicDirectoryPath) { throw handleMarkdownError('Epic directory path is required'); } try { const content = generateEpicContent(epicData); const filePath = path.join(epicDirectoryPath, 'epic.md'); await fs.writeFile(filePath, content); return filePath; } catch (error) { throw handleMarkdownError(`Failed to format epic: ${epicData.title}`, error); } } /** * Génère un slug à partir d'un titre * @param {string} title - Titre à convertir en slug * @returns {string} - Slug généré */ function generateSlug(title) { if (!title || typeof title !== 'string') throw new Error('Title is required and must be a string'); return createSlug(title); } return { format, generateSlug }; } /** * Crée un objet JSON représentant un epic * @param {Object} epic - Données de l'epic * @param {string} epicFilePath - Chemin du fichier markdown de l'epic * @param {string} epicSlug - Slug de l'epic * @returns {Object} - Objet JSON représentant l'epic */ function createEpicJson(epic, epicFilePath, epicSlug) { return { title: epic.title, description: epic.description || '', slug: epicSlug, path: `./${path.relative(process.cwd(), epicFilePath).replace(/\\/g, '/')}`, features: [] }; } /** * Traite les features d'un epic * @param {Object} epic - Données de l'epic * @param {Array} [epic.features] - Liste des features de l'epic * @param {string} epicDir - Chemin du répertoire de l'epic * @param {string} epicTitle - Titre de l'epic * @param {Map} userStoryMap - Map pour suivre les user stories * @param {Object} epicJson - Objet JSON représentant l'epic * @returns {Promise<void>} */ async function processEpicFeatures(epic, epicDir, epicTitle, userStoryMap, epicJson) { // Vérification rapide de la présence de features if (!epic?.features?.length) { console.error(chalk.yellow(`⚠️ No features found for epic '${epicTitle}' (stderr)`)); return; } await processFeatures(epic.features, epicDir, epicTitle, userStoryMap, epicJson); } module.exports = { // Factory function principale createEpicFormatter, // Fonctions utilisées par d'autres modules ou tests processEpics, processEpic, generateEpicContent, // Fonctions helper exposées pour les tests createEpicJson, processEpicFeatures };