mcp-booster
Version:
Servidor MCP com CoConuT (Continuous Chain of Thought) para uso com Cursor IDE - Pacote Global NPM
1,104 lines (1,071 loc) • 61 kB
JavaScript
"use strict";
/**
* Booster_Steps - Ferramenta para formatação de steps de tarefas usando template de card
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BoosterStepsService = void 0;
const logger_1 = require("./logger");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const express_1 = __importDefault(require("express"));
const open_1 = __importDefault(require("open"));
/**
* Servidor web para edição interativa de steps
*/
class WebEditorServer {
constructor(steps, params) {
this.server = null;
this.port = 5555;
this.resolve = null;
this.closeTimer = null;
this.app = (0, express_1.default)();
this.logger = logger_1.Logger.getInstance();
this.steps = [...steps]; // Clone para evitar mutação
this.originalParams = params;
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
this.app.use(express_1.default.json({ limit: '10mb' }));
this.app.use(express_1.default.urlencoded({ extended: true, limit: '10mb' }));
}
setupRoutes() {
// Rota principal - interface de edição
this.app.get('/', (req, res) => {
const html = this.generateEditHTML();
res.send(html);
});
// Rota para processar atualizações
this.app.post('/update', (req, res) => {
try {
this.logger.debug('Received update request', {
bodyExists: !!req.body,
stepsExists: !!(req.body && req.body.steps),
contentType: req.headers['content-type']
});
if (!req.body) {
throw new Error('Request body is empty - check Content-Type header');
}
const updatedSteps = this.processUpdates(req.body);
this.steps = updatedSteps;
// Agendar fechamento automático do servidor em 5 segundos
this.scheduleServerClose(5000);
const confirmationHtml = this.generateConfirmationHTML();
res.send(confirmationHtml);
}
catch (error) {
this.logger.error('Error processing updates', {
error: error.message,
body: req.body,
headers: req.headers
});
res.status(400).json({ error: error.message });
}
});
// Rota para fechar servidor manualmente (simplificada)
this.app.get('/close', (req, res) => {
res.json({ message: 'Server closing...' });
// Fechar servidor imediatamente
setTimeout(() => {
this.stopServer();
if (this.resolve) {
this.resolve(this.steps);
}
}, 500);
});
}
async startServer() {
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.server = this.app.listen(this.port, () => {
this.logger.info(`Web editor server started on port ${this.port}`);
this.openBrowser();
});
this.server.on('error', (error) => {
this.logger.error('Server error', { error });
reject(error);
});
});
}
async openBrowser() {
try {
await (0, open_1.default)(`http://localhost:${this.port}`);
this.logger.info('Browser opened automatically');
}
catch (error) {
this.logger.warn('Failed to open browser automatically', { error });
this.logger.info(`Please open your browser and navigate to: http://localhost:${this.port}`);
}
}
scheduleServerClose(delayMs) {
// Limpar timer anterior se existir
if (this.closeTimer) {
clearTimeout(this.closeTimer);
}
this.logger.info(`Server close scheduled in ${delayMs}ms`);
this.closeTimer = setTimeout(() => {
this.logger.info('Auto-closing server after scheduled delay');
this.stopServer();
if (this.resolve) {
this.resolve(this.steps);
}
}, delayMs);
}
stopServer() {
// Limpar timer se existir
if (this.closeTimer) {
clearTimeout(this.closeTimer);
this.closeTimer = null;
}
if (this.server) {
this.server.close(() => {
this.logger.info('Web editor server stopped');
});
this.server = null;
}
}
processUpdates(body) {
if (!body.steps || !Array.isArray(body.steps)) {
throw new Error('Invalid steps data received');
}
// Validar e processar cada step
const updatedSteps = body.steps.map((stepData, index) => {
// Validação básica dos campos obrigatórios para AI
if (!stepData.id || !stepData.title || !stepData.technicalContext || !stepData.implementationGoal) {
throw new Error(`Step ${index + 1}: Missing required fields (id, title, technicalContext, implementationGoal)`);
}
if (!stepData.complexity) {
throw new Error(`Step ${index + 1}: Complexity is required`);
}
if (!stepData.targetFiles || !Array.isArray(stepData.targetFiles) || stepData.targetFiles.length === 0) {
throw new Error(`Step ${index + 1}: Target files are required`);
}
if (!stepData.technicalRequirements || !Array.isArray(stepData.technicalRequirements) || stepData.technicalRequirements.length === 0) {
throw new Error(`Step ${index + 1}: Technical requirements are required`);
}
if (!stepData.verificationSteps || !Array.isArray(stepData.verificationSteps) || stepData.verificationSteps.length === 0) {
throw new Error(`Step ${index + 1}: Verification steps are required`);
}
// Construir step atualizado com nova estrutura AI-focada
const updatedStep = {
id: stepData.id.trim(),
title: stepData.title.trim(),
technicalContext: stepData.technicalContext.trim(),
implementationGoal: stepData.implementationGoal.trim(),
complexity: stepData.complexity,
// Arrays obrigatórios
targetFiles: stepData.targetFiles?.filter((file) => file.trim()) || [],
referencePaths: stepData.referencePaths?.filter((path) => path.trim()) || [],
codePatterns: stepData.codePatterns?.filter((pattern) => pattern.trim()) || [],
technicalRequirements: stepData.technicalRequirements?.filter((req) => req.trim()) || [],
shellCommands: stepData.shellCommands?.filter((cmd) => cmd.trim()) || [],
installationSteps: stepData.installationSteps?.filter((step) => step.trim()) || [],
verificationSteps: stepData.verificationSteps?.filter((step) => step.trim()) || [],
codeDependencies: stepData.codeDependencies?.filter((dep) => dep.trim()) || [],
serviceDependencies: stepData.serviceDependencies?.filter((dep) => dep.trim()) || [],
// Campos editáveis pelo usuário (consolidado)
userNotes: stepData.userNotes?.trim(),
// Campos editáveis pela AI
aiNotes: stepData.aiNotes?.trim(),
// Metadados técnicos
estimatedLines: stepData.estimatedLines ? parseInt(stepData.estimatedLines) : undefined,
testingStrategy: stepData.testingStrategy?.trim(),
rollbackPlan: stepData.rollbackPlan?.trim(),
// Contexto adicional
relatedIssues: stepData.relatedIssues?.filter((issue) => issue.trim()) || [],
apiEndpoints: stepData.apiEndpoints?.filter((endpoint) => endpoint.trim()) || [],
databaseChanges: stepData.databaseChanges?.filter((change) => change.trim()) || [],
environmentVars: stepData.environmentVars?.filter((env) => env.trim()) || [],
securityConsiderations: stepData.securityConsiderations?.filter((sec) => sec.trim()) || [],
performanceConsiderations: stepData.performanceConsiderations?.filter((perf) => perf.trim()) || [],
accessibilityNotes: stepData.accessibilityNotes?.filter((acc) => acc.trim()) || [],
monitoringAndLogs: stepData.monitoringAndLogs?.filter((log) => log.trim()) || []
};
return updatedStep;
});
this.logger.info('Steps updated successfully', { totalSteps: updatedSteps.length });
return updatedSteps;
}
generateEditHTML() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 LLM Booster - Steps Editor - ${this.originalParams.title}</title>
<link href="https://fonts.googleapis.com/css2?family=Geist+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--background: #0B192B;
--surface: #1F2A3D;
--text-primary: #E0E6ED;
--text-secondary: #8FA6BC;
--accent-primary: #3A8FB7;
--accent-secondary:rgb(215, 145, 75);
--destructive: #dc3545;
--border: rgba(255, 255, 255, 0.1);
--success: #28a745;
}
body {
font-family: 'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, var(--background) 0%, #0a1521 100%);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: var(--surface);
border-radius: 12px;
overflow: hidden;
margin-top: 20px;
margin-bottom: 20px;
border: 1px solid var(--border);
}
.header {
background: var(--surface);
color: var(--text-primary);
padding: 32px;
text-align: center;
border-bottom: 1px solid var(--border);
}
.header h1 {
margin: 0;
font-size: 2rem;
font-weight: 600;
color: var(--text-primary);
}
.header p {
margin: 12px 0 0 0;
font-size: 1rem;
font-weight: 400;
color: var(--text-secondary);
}
.header-controls {
margin-top: 16px;
display: flex;
gap: 8px;
justify-content: center;
}
.control-btn {
background: var(--background);
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 400;
transition: all 0.2s ease;
}
.control-btn:hover {
color: var(--text-primary);
border-color: var(--accent-primary);
}
.content {
padding: 40px;
background: var(--background);
}
.steps-info {
background: var(--background);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 24px;
text-align: center;
color: var(--text-secondary);
font-size: 0.9rem;
}
.steps-info strong {
color: var(--text-primary);
}
.step-card {
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 12px;
background: var(--surface);
overflow: hidden;
transition: all 0.3s ease;
}
.step-card:hover {
border-color: var(--text-secondary);
}
.step-header {
background: var(--background);
color: var(--text-primary);
padding: 16px 20px;
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
}
.step-card.expanded .step-header {
border-bottom: 1px solid var(--border);
}
.step-header:hover {
background: var(--surface);
}
.step-header h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.step-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
color: var(--text-secondary);
}
.step-arrow {
font-size: 1rem;
transition: transform 0.2s ease;
color: var(--text-secondary);
}
.step-card.expanded .step-arrow {
transform: rotate(180deg);
}
.step-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
background: var(--surface);
}
.step-card.expanded .step-body {
max-height: none;
padding: 24px;
}
.form-group {
margin-bottom: 24px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text-primary);
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 6px;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.9rem;
box-sizing: border-box;
background: var(--background);
color: var(--text-primary);
transition: all 0.2s ease;
font-family: 'Geist Sans', sans-serif;
}
.form-group input:focus, .form-group textarea:focus, .form-group select:focus {
outline: none;
border-color: var(--accent-primary);
}
.form-group textarea {
resize: vertical;
min-height: 120px;
line-height: 1.6;
}
.array-field {
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px;
background: var(--background);
margin-top: 8px;
}
.array-field h4 {
margin: 0 0 12px 0;
color: var(--text-primary);
font-size: 0.85rem;
font-weight: 500;
}
.array-item {
display: flex;
margin-bottom: 12px;
align-items: center;
gap: 12px;
}
.array-item input {
flex: 1;
}
.array-item button {
background: var(--destructive);
color: var(--text-primary);
border: none;
padding: 12px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
}
.array-item button:hover {
background: #c82333;
}
.add-button {
background: var(--accent-primary);
color: var(--text-primary);
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 400;
margin-top: 12px;
transition: all 0.2s ease;
}
.add-button:hover {
background: var(--accent-secondary);
}
.submit-button {
background: var(--accent-primary);
color: var(--text-primary);
border: none;
padding: 16px 32px;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
width: 100%;
margin-top: 32px;
transition: all 0.2s ease;
}
.submit-button:hover {
background: var(--accent-secondary);
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.row {
grid-template-columns: 1fr;
}
}
.col {
flex: 1;
}
.required {
color: var(--accent-secondary);
font-weight: 600;
}
.optional-section {
background: var(--background);
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px;
margin-top: 16px;
}
.optional-section h3 {
margin: 0 0 16px 0;
color: var(--text-primary);
font-size: 1rem;
font-weight: 500;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 LLM Booster - Steps Editor</h1>
<p>⚡ AI-structured development for developers - Edit your task breakdown and boost productivity</p>
<div class="header-controls">
<button type="button" class="control-btn" onclick="expandAllSteps()">📖 Expand All</button>
<button type="button" class="control-btn" onclick="collapseAllSteps()">📄 Collapse All</button>
</div>
</div>
<div class="content">
<div class="steps-info">
📋 <strong>${this.steps.length} steps</strong> loaded • Click step headers to expand/collapse • All steps are collapsed by default
</div>
<form id="stepsForm">
${this.steps.map((step, index) => this.generateStepFormHTML(step, index)).join('')}
<button type="submit" class="submit-button">
🚀 Save Changes & Boost Productivity
</button>
</form>
</div>
</div>
<script>
function addArrayItem(containerId, fieldName) {
const container = document.getElementById(containerId);
const itemsContainer = container.querySelector('.array-items');
const newItem = document.createElement('div');
newItem.className = 'array-item';
newItem.innerHTML = \`
<input type="text" name="\${fieldName}" placeholder="Type here...">
<button type="button" onclick="this.parentElement.remove()">Remove</button>
\`;
itemsContainer.appendChild(newItem);
}
function toggleStep(index) {
const stepCard = document.querySelector(\`[data-step-index="\${index}"]\`);
const isExpanded = stepCard.classList.contains('expanded');
if (isExpanded) {
stepCard.classList.remove('expanded');
} else {
// Opcional: fechar outros steps abertos (uncomment para comportamento de accordion exclusivo)
// document.querySelectorAll('.step-card.expanded').forEach(card => {
// if (card !== stepCard) card.classList.remove('expanded');
// });
stepCard.classList.add('expanded');
}
}
function expandAllSteps() {
document.querySelectorAll('.step-card').forEach(card => {
card.classList.add('expanded');
});
}
function collapseAllSteps() {
document.querySelectorAll('.step-card').forEach(card => {
card.classList.remove('expanded');
});
}
document.getElementById('stepsForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const steps = [];
// Processar dados do formulário
const stepIndices = new Set();
for (let [key, value] of formData.entries()) {
const match = key.match(/^step(\\d+)_(.+)$/);
if (match) {
stepIndices.add(parseInt(match[1]));
}
}
stepIndices.forEach(index => {
const step = {
id: formData.get(\`step\${index}_id\`) || '',
title: formData.get(\`step\${index}_title\`) || '',
technicalContext: formData.get(\`step\${index}_technicalContext\`) || '',
implementationGoal: formData.get(\`step\${index}_implementationGoal\`) || '',
complexity: formData.get(\`step\${index}_complexity\`) || 'medium',
// Arrays obrigatórios
targetFiles: formData.getAll(\`step\${index}_targetFiles\`).filter(v => v.trim()),
referencePaths: formData.getAll(\`step\${index}_referencePaths\`).filter(v => v.trim()),
codePatterns: formData.getAll(\`step\${index}_codePatterns\`).filter(v => v.trim()),
technicalRequirements: formData.getAll(\`step\${index}_technicalRequirements\`).filter(v => v.trim()),
shellCommands: formData.getAll(\`step\${index}_shellCommands\`).filter(v => v.trim()),
installationSteps: formData.getAll(\`step\${index}_installationSteps\`).filter(v => v.trim()),
verificationSteps: formData.getAll(\`step\${index}_verificationSteps\`).filter(v => v.trim()),
codeDependencies: formData.getAll(\`step\${index}_codeDependencies\`).filter(v => v.trim()),
serviceDependencies: formData.getAll(\`step\${index}_serviceDependencies\`).filter(v => v.trim()),
// Campos editáveis pelo usuário
userNotes: formData.get(\`step\${index}_userNotes\`) || '',
specialInstructions: formData.get(\`step\${index}_specialInstructions\`) || '',
projectSpecificContext: formData.get(\`step\${index}_projectSpecificContext\`) || '',
implementationPreferences: formData.get(\`step\${index}_implementationPreferences\`) || '',
warningsAndCaveats: formData.get(\`step\${index}_warningsAndCaveats\`) || '',
businessRationale: formData.get(\`step\${index}_businessRationale\`) || '',
// Metadados técnicos
estimatedLines: formData.get(\`step\${index}_estimatedLines\`) ? parseInt(formData.get(\`step\${index}_estimatedLines\`)) : undefined,
testingStrategy: formData.get(\`step\${index}_testingStrategy\`) || '',
rollbackPlan: formData.get(\`step\${index}_rollbackPlan\`) || '',
// Contexto adicional
relatedIssues: formData.getAll(\`step\${index}_relatedIssues\`).filter(v => v.trim()),
apiEndpoints: formData.getAll(\`step\${index}_apiEndpoints\`).filter(v => v.trim()),
databaseChanges: formData.getAll(\`step\${index}_databaseChanges\`).filter(v => v.trim()),
environmentVars: formData.getAll(\`step\${index}_environmentVars\`).filter(v => v.trim()),
securityConsiderations: formData.getAll(\`step\${index}_securityConsiderations\`).filter(v => v.trim()),
performanceConsiderations: formData.getAll(\`step\${index}_performanceConsiderations\`).filter(v => v.trim()),
accessibilityNotes: formData.getAll(\`step\${index}_accessibilityNotes\`).filter(v => v.trim()),
monitoringAndLogs: formData.getAll(\`step\${index}_monitoringAndLogs\`).filter(v => v.trim())
};
steps.push(step);
});
try {
const response = await fetch('/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ steps })
});
if (response.ok) {
document.body.innerHTML = await response.text();
} else {
const error = await response.json();
alert('Save error: ' + error.error);
}
} catch (error) {
alert('Connection error: ' + error.message);
}
});
</script>
</body>
</html>
`;
}
generateStepFormHTML(step, index) {
return `
<div class="step-card" data-step-index="${index}">
<div class="step-header" onclick="toggleStep(${index})">
<h3>🤖 Step ${index + 1}: ${step.title}</h3>
<div class="step-toggle">
<span>${step.complexity || 'medium'} complexity</span>
<span class="step-arrow">▼</span>
</div>
</div>
<div class="step-body">
<!-- === BASIC IDENTIFICATION === -->
<div class="ai-section">
<h3>🏷️ Basic Identification</h3>
<div class="row">
<div class="col">
<div class="form-group">
<label>ID <span class="required">*</span></label>
<input type="text" name="step${index}_id" value="${step.id}" required>
</div>
</div>
<div class="col">
<div class="form-group">
<label>Complexity <span class="required">*</span></label>
<select name="step${index}_complexity" required>
<option value="low" ${step.complexity === 'low' ? 'selected' : ''}>Low - Simple tasks</option>
<option value="medium" ${step.complexity === 'medium' ? 'selected' : ''}>Medium - Requires knowledge</option>
<option value="high" ${step.complexity === 'high' ? 'selected' : ''}>High - Complex</option>
<option value="expert" ${step.complexity === 'expert' ? 'selected' : ''}>Expert - Very advanced</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label>Technical Title <span class="required">*</span></label>
<input type="text" name="step${index}_title" value="${step.title}" required placeholder="ex: Implement JWT authentication in backend">
</div>
</div>
<!-- === AI INFORMATION === -->
<div class="ai-section">
<h3>🤖 AI Information</h3>
<div class="form-group">
<label>Technical Context <span class="required">*</span></label>
<textarea name="step${index}_technicalContext" required placeholder="Describe the detailed technical context...">${step.technicalContext || ''}</textarea>
</div>
<div class="form-group">
<label>Implementation Goal <span class="required">*</span></label>
<textarea name="step${index}_implementationGoal" required placeholder="Specific implementation objective...">${step.implementationGoal || ''}</textarea>
</div>
${this.generateArrayFieldHTML(`step${index}_targetFiles`, 'Target Files', step.targetFiles || [], true)}
${this.generateArrayFieldHTML(`step${index}_referencePaths`, 'Reference Paths', step.referencePaths || [])}
${this.generateArrayFieldHTML(`step${index}_codePatterns`, 'Code Patterns', step.codePatterns || [])}
${this.generateArrayFieldHTML(`step${index}_technicalRequirements`, 'Technical Requirements', step.technicalRequirements || [], true)}
${this.generateArrayFieldHTML(`step${index}_shellCommands`, 'Terminal Commands', step.shellCommands || [])}
${this.generateArrayFieldHTML(`step${index}_installationSteps`, 'Installation Steps', step.installationSteps || [])}
${this.generateArrayFieldHTML(`step${index}_verificationSteps`, 'Verification Steps', step.verificationSteps || [], true)}
${this.generateArrayFieldHTML(`step${index}_codeDependencies`, 'Code Dependencies', step.codeDependencies || [])}
${this.generateArrayFieldHTML(`step${index}_serviceDependencies`, 'Service Dependencies', step.serviceDependencies || [])}
</div>
<!-- === AI-EDITABLE INFORMATION === -->
<div class="ai-section">
<h3>🤖 AI Information</h3>
<div class="form-group">
<label>AI Notes</label>
<textarea name="step${index}_aiNotes" placeholder="AI specific notes for this task...">${step.aiNotes || ''}</textarea>
</div>
</div>
<!-- === USER-EDITABLE INFORMATION === -->
<div class="user-section">
<h3>👤 User Information</h3>
<div class="form-group">
<label><strong>⚠️ IMPORTANT: User Notes</strong></label>
<textarea name="step${index}_userNotes" placeholder="CONSOLIDATED USER NOTES: Include all your instructions, project context, implementation preferences, warnings, business rationale and any other relevant information for this task. This is the ONLY field for user input - make it comprehensive!" rows="8">${step.userNotes || ''}</textarea>
<small style="color: var(--text-secondary); margin-top: 4px; display: block;">
💡 This field consolidates all user input. Include: special instructions for AI, project-specific context, implementation preferences, warnings/caveats, business rationale, and any other relevant information.
</small>
</div>
</div>
<!-- === TECHNICAL METADATA === -->
<div class="technical-section">
<h3>⚙️ Technical Metadata</h3>
<div class="row">
<div class="col">
<div class="form-group">
<label>Estimated Lines of Code</label>
<input type="number" name="step${index}_estimatedLines" value="${step.estimatedLines || ''}" placeholder="ex: 50">
</div>
</div>
</div>
<div class="form-group">
<label>Testing Strategy</label>
<textarea name="step${index}_testingStrategy" placeholder="How to test this implementation...">${step.testingStrategy || ''}</textarea>
</div>
<div class="form-group">
<label>Rollback Plan</label>
<textarea name="step${index}_rollbackPlan" placeholder="How to revert if needed...">${step.rollbackPlan || ''}</textarea>
</div>
</div>
<!-- === ADDITIONAL CONTEXT === -->
<div class="optional-section">
<h3>📋 Additional Context</h3>
${this.generateArrayFieldHTML(`step${index}_relatedIssues`, 'Related Issues', step.relatedIssues || [])}
${this.generateArrayFieldHTML(`step${index}_apiEndpoints`, 'API Endpoints', step.apiEndpoints || [])}
${this.generateArrayFieldHTML(`step${index}_databaseChanges`, 'Database Changes', step.databaseChanges || [])}
${this.generateArrayFieldHTML(`step${index}_environmentVars`, 'Environment Variables', step.environmentVars || [])}
${this.generateArrayFieldHTML(`step${index}_securityConsiderations`, 'Security Considerations', step.securityConsiderations || [])}
${this.generateArrayFieldHTML(`step${index}_performanceConsiderations`, 'Performance Considerations', step.performanceConsiderations || [])}
${this.generateArrayFieldHTML(`step${index}_accessibilityNotes`, 'Accessibility Notes', step.accessibilityNotes || [])}
${this.generateArrayFieldHTML(`step${index}_monitoringAndLogs`, 'Monitoring and Logs', step.monitoringAndLogs || [])}
</div>
</div>
</div>
`;
}
generateArrayFieldHTML(fieldName, label, items, required = false) {
const containerId = `${fieldName}_container`;
const requiredSpan = required ? '<span class="required">*</span>' : '';
return `
<div class="form-group">
<div class="array-field" id="${containerId}">
<h4>${label} ${requiredSpan}</h4>
<div class="array-items">
${items.map(item => `
<div class="array-item">
<input type="text" name="${fieldName}" value="${item}" placeholder="Type here...">
<button type="button" onclick="this.parentElement.remove()">Remove</button>
</div>
`).join('')}
${items.length === 0 && required ? `
<div class="array-item">
<input type="text" name="${fieldName}" placeholder="Type here..." required>
<button type="button" onclick="this.parentElement.remove()">Remove</button>
</div>
` : ''}
</div>
<button type="button" class="add-button" onclick="addArrayItem('${containerId}', '${fieldName}')">
➕ Add Item
</button>
</div>
</div>
`;
}
generateConfirmationHTML() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 LLM Booster - Changes Saved</title>
<link href="https://fonts.googleapis.com/css2?family=Geist+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--background: #0B192B;
--surface: #1F2A3D;
--text-primary: #E0E6ED;
--text-secondary: #8FA6BC;
--accent-primary: #3A8FB7;
--success: #28a745;
--border: rgba(255, 255, 255, 0.1);
}
body {
font-family: 'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, var(--background) 0%, #0a1521 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 40px;
text-align: center;
max-width: 480px;
width: 100%;
}
.success-icon {
font-size: 3rem;
margin-bottom: 20px;
}
h1 {
color: var(--text-primary);
margin-bottom: 12px;
font-weight: 600;
font-size: 1.5rem;
}
.steps-count {
background: var(--background);
border: 1px solid var(--border);
padding: 16px;
border-radius: 8px;
margin: 20px 0;
color: var(--text-primary);
font-size: 1rem;
}
.close-info {
color: var(--text-secondary);
font-size: 0.9rem;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="success-icon">✅</div>
<h1>Changes Saved Successfully</h1>
<div class="steps-count">
${this.steps.length} task cards processed and saved
</div>
<div class="close-info">
You can now close this page
</div>
</div>
</body>
</html>
`;
}
}
class BoosterStepsService {
constructor() {
this.logger = logger_1.Logger.getInstance();
}
/**
* Processa os steps fornecidos pelo modelo usando interface web para edição
*/
async processTask(params) {
try {
this.logger.info('Processing task cards with web editor', {
taskDescription: params.taskDescription.substring(0, 100) + '...',
totalSteps: params.steps.length
});
// Validar os steps fornecidos
this.validateSteps(params.steps);
// Iniciar servidor web para edição
this.logger.info('Starting web editor server for step editing...');
const webServer = new WebEditorServer(params.steps, params);
// Aguardar modificações do usuário via interface web
const editedSteps = await webServer.startServer();
this.logger.info('Web editor completed, processing final steps', {
originalSteps: params.steps.length,
editedSteps: editedSteps.length
});
// Validar os steps editados
this.validateSteps(editedSteps);
// Gerar resumo
const summaryParams = { ...params, steps: editedSteps };
const summary = this.generateSummary(summaryParams);
// Gerar recomendações
const recommendations = this.generateRecommendations(editedSteps);
// Identificar fatores de risco
const riskFactors = this.identifyRiskFactors(editedSteps);
// Criar resposta
const response = {
taskTitle: this.extractTaskTitle(params.taskDescription),
totalSteps: editedSteps.length,
steps: editedSteps,
summary,
recommendations,
riskFactors
};
// Salvar arquivo (projectPath é obrigatório)
const savedFilePath = await this.saveStepCards(params.projectPath, params.title, response, params.taskDescription);
response.savedFilePath = savedFilePath;
this.logger.info('Task cards processing completed', {
totalSteps: editedSteps.length,
savedFilePath
});
return response;
}
catch (error) {
this.logger.error('Error processing task cards', { error });
throw new Error(`Failed to process task: ${error.message}`);
}
}
/**
* Valida os steps fornecidos pelo modelo
*/
validateSteps(steps) {
if (!steps || steps.length === 0) {
throw new Error("At least one step must be provided");
}
steps.forEach((step, index) => {
if (!step.id || !step.title || !step.technicalContext || !step.implementationGoal) {
throw new Error(`Step ${index + 1}: Missing required fields (id, title, technicalContext, implementationGoal)`);
}
if (!step.complexity) {
throw new Error(`Step ${index + 1}: Complexity is required`);
}
if (!step.targetFiles || step.targetFiles.length === 0) {
throw new Error(`Step ${index + 1}: Target files are required`);
}
if (!step.technicalRequirements || step.technicalRequirements.length === 0) {
throw new Error(`Step ${index + 1}: Technical requirements are required`);
}
if (!step.verificationSteps || step.verificationSteps.length === 0) {
throw new Error(`Step ${index + 1}: Verification steps are required`);
}
});
}
/**
* Gera um resumo do plano baseado nos steps fornecidos
*/
generateSummary(params) {
const taskTitle = this.extractTaskTitle(params.taskDescription);
const totalSteps = params.steps.length;
// Analisar complexidades dos steps
const complexities = [...new Set(params.steps.map(s => s.complexity).filter(Boolean))];
const targetFilesCount = params.steps.reduce((acc, step) => acc + step.targetFiles.length, 0);
let summary = `Plano para "${taskTitle}" dividido em ${totalSteps} cards técnicos focados em AI.`;
if (complexities.length > 0) {
summary += ` Complexidades: ${complexities.join(', ')}.`;
}
summary += ` Total de ${targetFilesCount} arquivos alvo identificados.`;
summary += ` Cada card contém especificações técnicas detalhadas, comandos, verificações e contexto específico para implementação por AI.`;
return summary;
}
/**
* Gera recomendações baseadas nos steps focados em AI
*/
generateRecommendations(steps) {
const recommendations = [];
recommendations.push('Execute os cards seguindo a complexidade técnica definida');
recommendations.push('Revise os requisitos técnicos antes de iniciar cada implementação');
recommendations.push('Execute os passos de verificação para validar a implementação');
// Recomendações baseadas em dependências de código
const stepsWithCodeDeps = steps.filter(s => s.codeDependencies && s.codeDependencies.length > 0);
if (stepsWithCodeDeps.length > 0) {
recommendations.push('Verifique as dependências de código antes de iniciar a implementação');
}
// Recomendações baseadas em dependências de serviços
const stepsWithServiceDeps = steps.filter(s => s.serviceDependencies && s.serviceDependencies.length > 0);
if (stepsWithServiceDeps.length > 0) {
recommendations.push('Confirme que os serviços dependentes estão disponíveis');
}
// Recomendações baseadas em comandos shell
const stepsWithCommands = steps.filter(s => s.shellCommands && s.shellCommands.length > 0);
if (stepsWithCommands.length > 0) {
recommendations.push('Execute os comandos shell na ordem especificada');
}
// Recomendações baseadas em considerações de segurança
const stepsWithSecurity = steps.filter(s => s.securityConsiderations && s.securityConsiderations.length > 0);
if (stepsWithSecurity.length > 0) {
recommendations.push('Revise as considerações de segurança antes da implementação');
}
// Recomendações baseadas em instruções especiais do usuário
const stepsWithInstructions = steps.filter(s => s.userNotes && s.userNotes.trim() !== '');
if (stepsWithInstructions.length > 0) {
recommendations.push('Siga as instruções especiais fornecidas pelo usuário');
}
return recommendations;
}
/**
* Identifica fatores de risco baseados nos steps focados em AI
*/
identifyRiskFactors(steps) {
const risks = [];
// Risco por número de steps
if (steps.length > 10) {
risks.push('Grande número de cards pode aumentar a complexidade de implementação');
}
// Risco por complexidade técnica
const expertSteps = steps.filter(s => s.complexity === 'expert').length;
const highComplexitySteps = steps.filter(s => s.complexity === 'high' || s.complexity === 'expert').length;
if (expertSteps > steps.length * 0.3) {
risks.push('Muitos steps de complexidade expert podem requerer conhecimento especializado');
}
if (highComplexitySteps > steps.length * 0.7) {
risks.push('Alta proporção de steps complexos pode aumentar o tempo de implementação');
}
// Risco por dependências de código
const totalCodeDeps = steps.reduce((acc, step) => acc + (step.codeDependencies ? step.codeDependencies.length : 0), 0);
if (totalCodeDeps > steps.length * 2) {
risks.push('Muitas dependências de código podem causar problemas de compatibilidade');
}
// Risco por dependências de serviços
const totalServiceDeps = steps.reduce((acc, step) => acc + (step.serviceDependencies ? step.serviceDependencies.length : 0), 0);
if (totalServiceDeps > steps.length) {
risks.push('Dependências de serviços externos podem causar bloqueios');
}
// Risco por comandos shell complexos
const stepsWithManyCommands = steps.filter(s => s.shellCommands && s.shellCommands.length > 5);
if (stepsWithManyCommands.length > 0) {
risks.push('Steps with many shell commands may be prone to errors');
}
// Risco por falta de verificação
const stepsWithoutVerification = steps.filter(s => !s.verificationSteps || s.verificationSteps.length === 0);
if (stepsWithoutVerification.length > 0) {
risks.push('Steps sem passos de verificação podem ter problemas não detectados');
}
// Risco por avisos do usuário
const stepsWithWarnings = steps.filter(s => s.userNotes && s.userNotes.includes('warning') || s.userNotes && s.userNotes.includes('cuidado') || s.userNotes && s.userNotes.includes('atenção'));
if (stepsWithWarnings.length > 0) {
risks.push('Alguns steps possuem avisos especiais que requerem atenção');
}
return risks;
}
/**
* Extrai um título da descrição da tarefa
*/
extractTaskTitle(description) {
// Pegar as primeiras palavras ou até o primeiro ponto
const firstSentence = description.split('.')[0];
if (firstSentence.length <= 60) {
return firstSentence;
}
// Se muito longo, pegar as primeiras 60 caracteres
return description.substring(0, 60) + '...';
}
/**
* Sanitiza o título para criar um nome de arquivo seguro
*/
sanitizeFilename(title) {
// Remover caracteres não permitidos em nomes de arquivo
let sanitized = title
.replace(/[<>:"/\\|?*]/g, '') // Caracteres proibidos no Windows
.replace(/[^\w\s-_().]/g, '') // Manter apenas caracteres alfanuméricos, espaços, hífens, underscore e parênteses
.replace(/\s+/g, '-') // Substituir espaços por hífens
.replace(/-+/g, '-') // M