@tosin2013/mcp-shrimp-task-manager
Version:
Enhanced MCP Shrimp Task Manager with comprehensive LLM integration. A task management tool built for AI Agents, emphasizing chain-of-thought, reflection, and style consistency. Features real GPT-4 ↔ MCP tools communication, comprehensive testing pipeline
299 lines • 14.3 kB
JavaScript
/**
* Implementation of the create_spec command for the Idea Honing Tool
*
* This file contains the main command execution logic for creating structured
* specification documents from raw ideas, with deep codebase analysis and
* project rule integration.
*/
import { createSpecification } from "./components/specification-manager.js";
import { analyzeRepository } from "./components/codebase-analyzer.js";
import { generateRuleSuggestions, applyRulesToSpecification } from "./components/project-rules-integrator.js";
import { storeSpecificationInTaskMemory, createTaskFromSpecification } from "./components/task-memory-connector.js";
import { createWorkflowState, createSession } from "./components/workflow-state-manager.js";
import fs from 'fs/promises';
import path from 'path';
/**
* Main function for creating a specification document
*
* @param params - Parameters for the create_spec command
* @returns Response object with content for the MCP framework
*/
/**
* Extracts keywords from text for codebase analysis
*
* @param text - Text to extract keywords from
* @returns Array of keywords
*/
function extractKeywords(text) {
// Split the text into words
const words = text.toLowerCase()
.replace(/[^\w\s]/g, ' ') // Replace punctuation with spaces
.split(/\s+/) // Split on whitespace
.filter(word => word.length > 3); // Filter out short words
// Remove common words
const commonWords = ['the', 'and', 'for', 'with', 'that', 'this', 'from', 'will', 'have', 'been'];
const filteredWords = words.filter(word => !commonWords.includes(word));
// Get unique words
const uniqueWords = [...new Set(filteredWords)];
// Return the top 5 keywords
return uniqueWords.slice(0, 5);
}
/**
* Formats the analysis result for display
*
* @param result - Analysis result to format
* @returns Formatted analysis text
*/
function formatAnalysisResult(result) {
let text = '';
// Add confidence score
text += `**Analysis Confidence**: ${Math.round(result.analysisConfidence * 100)}%\n\n`;
// Add affected components
if (result.affectedComponents && result.affectedComponents.length > 0) {
text += `### Affected Components\n\n`;
for (const component of result.affectedComponents.slice(0, 5)) {
text += `- **${component.name}** (${component.path}): ${component.description}\n`;
}
if (result.affectedComponents.length > 5) {
text += `- ... and ${result.affectedComponents.length - 5} more components\n`;
}
text += '\n';
}
// Add impacted files
if (result.impactedFiles && result.impactedFiles.length > 0) {
text += `### Impacted Files\n\n`;
for (const file of result.impactedFiles.slice(0, 5)) {
text += `- **${file.path}**: ${file.reason}\n`;
}
if (result.impactedFiles.length > 5) {
text += `- ... and ${result.impactedFiles.length - 5} more files\n`;
}
text += '\n';
}
// Add suggested questions
if (result.suggestedQuestions && result.suggestedQuestions.length > 0) {
text += `### Suggested Questions\n\n`;
for (const question of result.suggestedQuestions) {
text += `- ${question}\n`;
}
text += '\n';
}
return text;
}
export async function createSpec(params) {
try {
const { title, description, scope, template } = params;
// Analyze the codebase
let analysisResult;
let analysisText = '';
try {
// Extract keywords from the title and description
const keywords = extractKeywords(title + ' ' + description);
// Configure the analysis
const analysisConfig = {
includeDirs: scope ? [scope] : [],
focusKeywords: keywords,
maxFiles: 1000 // Limit to prevent excessive processing
};
// Perform the analysis
analysisResult = await analyzeRepository(analysisConfig);
// Generate analysis text
analysisText = formatAnalysisResult(analysisResult);
}
catch (error) {
console.error('Error during codebase analysis:', error);
analysisText = 'Codebase analysis failed. Proceeding without analysis results.';
}
// Create a new specification using the Specification Manager
let specification = await createSpecification(title, description, "AI Assistant", // Default author
template || "default-template", "default", // Default project ID
"", // Default feature ID
{
scope: scope || "Full codebase",
hasAPI: false, // Default value for conditional sections
hasDatabase: false, // Default value for conditional sections
relatedFiles: analysisResult ?
analysisResult.impactedFiles.map(file => `- ${file.path}: ${file.reason}`).join('\n') :
'No related files identified.'
});
// Apply project rules to the specification
try {
// Generate rule suggestions
const ruleSuggestions = await generateRuleSuggestions(specification);
if (ruleSuggestions.length > 0) {
// Apply rules to the specification
specification = applyRulesToSpecification(specification, ruleSuggestions);
}
}
catch (error) {
console.error('Error applying project rules:', error);
}
// Store the specification in task memory
let taskMemoryId = '';
try {
taskMemoryId = await storeSpecificationInTaskMemory(specification);
// Create an initial planning task from the specification
await createTaskFromSpecification(taskMemoryId, {
title: `Plan implementation for: ${specification.title}`,
description: `Create a detailed implementation plan for the specification: ${specification.title}. Review the specification document and break it down into manageable tasks.`,
metadata: {
type: 'planning',
priority: 'high',
estimatedEffort: 'medium'
}
});
}
catch (error) {
console.error('Error storing specification in task memory:', error);
}
// Create workflow state and session
let workflowStateId = '';
let sessionId = '';
try {
// Extract keywords from title and description for session metadata
const extractedKeywords = extractKeywords(title + ' ' + description);
// Create workflow state with analysis results
const workflowState = await createWorkflowState(specification.id, 'analysis', analysisResult);
workflowStateId = workflowState.specId;
// Create a new session
const session = await createSession(specification.id, 'AI Assistant', // Default user ID
{
source: 'create_spec',
initialKeywords: extractedKeywords,
hasAnalysisResults: !!analysisResult,
hasRuleSuggestions: false // Will be updated after counting rules
});
sessionId = session.id;
}
catch (error) {
console.error('Error creating workflow state:', error);
}
// **UPDATED: Store dev_spec.md in MCP server data directory**
try {
const devSpecContent = generateDevSpecMarkdown(specification, analysisText);
// Store in MCP server's data directory instead of current working directory
const dataDir = path.join(process.cwd(), 'data', 'specifications');
await fs.mkdir(dataDir, { recursive: true });
const devSpecPath = path.join(dataDir, `${specification.id}.md`);
await fs.writeFile(devSpecPath, devSpecContent, 'utf-8');
console.log(`Generated dev_spec.md stored at: ${devSpecPath}`);
}
catch (error) {
console.error('Error storing dev_spec.md file:', error);
}
// Count the number of rule suggestions
let ruleCount = 0;
for (const section of specification.sections) {
if (section.content.includes('### Rule Suggestions')) {
// Count the number of rule suggestions in the section
const matches = section.content.match(/- \*\*[^*]+\*\*:/g);
if (matches) {
ruleCount += matches.length;
}
}
}
// Return a success message with the specification details and analysis results
return {
content: [
{
type: "text",
text: `# Specification Created Successfully\n\n` +
`A new specification document has been created with the following details:\n\n` +
`- **Title**: ${specification.title}\n` +
`- **ID**: ${specification.id}\n` +
`- **Version**: ${specification.version}\n` +
`- **Created**: ${specification.createdAt.toISOString().split('T')[0]}\n` +
`- **Status**: ${specification.metadata.status}\n` +
`- **Storage**: Stored in MCP server data directory\n\n` +
`The specification contains the following sections:\n\n` +
`${specification.sections.map(section => `- ${section.title}`).join('\n')}\n\n` +
`## Codebase Analysis Results\n\n` +
`${analysisText}\n\n` +
`${ruleCount > 0 ?
`## Project Rules Integration\n\n` +
`${ruleCount} project rules have been integrated into the specification. ` +
`These rules provide guidance for implementation based on project standards and best practices.\n\n` :
''}` +
`${taskMemoryId ?
`## Task Memory Integration\n\n` +
`The specification has been stored in task memory with ID: ${taskMemoryId}. ` +
`An initial planning task has been created to help break down the implementation into manageable tasks.\n\n` :
''}` +
`${workflowStateId ?
`## Workflow State Management\n\n` +
`A workflow state has been created for this specification with ID: ${workflowStateId}. ` +
`This enables continuity across sessions and progress tracking. ` +
`The current workflow phase is 'analysis'.\n\n` :
''}` +
`## Specification Access\n\n` +
`- **UUID**: ${specification.id} (for \`interact_spec\` commands)\n` +
`- **File Storage**: Available via \`get_spec\` tool using UUID\n` +
`- **Interactive Management**: Use \`interact_spec\` for viewing and editing\n\n` +
`## Next Steps\n\n` +
`1. Use \`get_spec\` with UUID ${specification.id} to read the complete specification\n` +
`2. Use \`interact_spec\` with ID ${specification.id} for interactive management\n` +
`3. Use \`plan_task\` to create implementation tasks based on this specification`
},
],
};
}
catch (error) {
// Error handling
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return {
content: [
{
type: "text",
text: `Error creating specification: ${errorMessage}`,
},
],
};
}
}
/**
* Generates a complete dev_spec.md markdown file from a specification
*
* @param specification - The specification document
* @param analysisText - The codebase analysis results text
* @returns Formatted markdown content for dev_spec.md
*/
function generateDevSpecMarkdown(specification, analysisText) {
const sections = specification.sections || [];
let content = `# ${specification.title}\n\n`;
// Add metadata
content += `## Specification Metadata\n\n`;
content += `- **ID**: \`${specification.id}\`\n`;
content += `- **Version**: ${specification.version}\n`;
content += `- **Status**: ${specification.metadata?.status || 'draft'}\n`;
content += `- **Created**: ${specification.createdAt.toISOString().split('T')[0]}\n`;
content += `- **Updated**: ${specification.updatedAt.toISOString().split('T')[0]}\n`;
content += `- **Authors**: ${specification.metadata?.authors?.join(', ') || 'AI Assistant'}\n\n`;
// Add analysis results
if (analysisText && analysisText.trim()) {
content += `## Codebase Analysis\n\n`;
content += `${analysisText}\n\n`;
}
// Add all sections from the specification
for (const section of sections) {
content += `## ${section.title}\n\n`;
content += `${section.content}\n\n`;
}
// Add UUID reference for interactive commands
content += `---\n\n`;
content += `## Interactive Management\n\n`;
content += `This specification can be managed interactively using:\n\n`;
content += `\`\`\`\n`;
content += `interact_spec({\n`;
content += ` specId: "${specification.id}",\n`;
content += ` command: "view" // or "edit", "progress", "tasks", etc.\n`;
content += `})\n`;
content += `\`\`\`\n\n`;
content += `**Available Commands:**\n`;
content += `- \`view\`: Display the complete specification\n`;
content += `- \`edit <section> <content>\`: Edit a specific section\n`;
content += `- \`progress\`: Check implementation progress\n`;
content += `- \`tasks\`: View related tasks\n`;
content += `- \`help\`: Get detailed command help\n\n`;
return content;
}
//# sourceMappingURL=create-spec.js.map