@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
1,262 lines • 47.2 kB
JavaScript
/**
* User Interaction Handler for the Idea Honing Tool
*
* This component manages user interactions with the Idea Honing Tool,
* including command parsing, input handling, and response formatting.
*/
import { getSpecification, updateSpecification } from './specification-manager.js';
import { loadWorkflowState, createWorkflowState, updateWorkflowPhase, markSectionCompleted, addQuestion, answerQuestion, createSession, endSession, calculateOverallProgress, getSessionsForSpecification } from './workflow-state-manager.js';
import { getTasksForSpecification } from './task-memory-connector.js';
import { analyzeRepository } from './codebase-analyzer.js';
import * as fs from 'fs';
import * as path from 'path';
/**
* Command types supported by the User Interaction Handler
*/
export var CommandType;
(function (CommandType) {
CommandType["VIEW_SPEC"] = "view_spec";
CommandType["EDIT_SECTION"] = "edit_section";
CommandType["MARK_COMPLETE"] = "mark_complete";
CommandType["ADD_QUESTION"] = "add_question";
CommandType["ANSWER_QUESTION"] = "answer_question";
CommandType["CHANGE_PHASE"] = "change_phase";
CommandType["VIEW_TASKS"] = "view_tasks";
CommandType["VIEW_PROGRESS"] = "view_progress";
CommandType["HELP"] = "help";
CommandType["SUGGEST"] = "suggest";
CommandType["ANALYZE"] = "analyze";
CommandType["WORKFLOW"] = "workflow";
CommandType["EXPORT"] = "export";
})(CommandType || (CommandType = {}));
/**
* Parses a command string into a Command object
*
* @param commandStr - Command string to parse
* @returns Parsed command or null if invalid
*/
export function parseCommand(commandStr) {
try {
// Trim the command string
const trimmedCommand = commandStr.trim();
// Check if the command is empty
if (!trimmedCommand) {
return null;
}
// Split the command into parts
const parts = trimmedCommand.split(' ');
// Get the command type
const commandTypeStr = parts[0].toLowerCase();
// Map the command type string to a CommandType
let commandType;
switch (commandTypeStr) {
case 'view':
commandType = CommandType.VIEW_SPEC;
break;
case 'edit':
commandType = CommandType.EDIT_SECTION;
break;
case 'complete':
commandType = CommandType.MARK_COMPLETE;
break;
case 'question':
commandType = CommandType.ADD_QUESTION;
break;
case 'answer':
commandType = CommandType.ANSWER_QUESTION;
break;
case 'phase':
commandType = CommandType.CHANGE_PHASE;
break;
case 'tasks':
commandType = CommandType.VIEW_TASKS;
break;
case 'progress':
commandType = CommandType.VIEW_PROGRESS;
break;
case 'help':
commandType = CommandType.HELP;
break;
case 'suggest':
commandType = CommandType.SUGGEST;
break;
case 'analyze':
commandType = CommandType.ANALYZE;
break;
case 'workflow':
commandType = CommandType.WORKFLOW;
break;
case 'export':
commandType = CommandType.EXPORT;
break;
default:
return null;
}
// Parse the command parameters
const params = {};
// Different parameter parsing based on command type
switch (commandType) {
case CommandType.VIEW_SPEC:
// view <specId>
if (parts.length > 1) {
params.specId = parts[1];
}
break;
case CommandType.EDIT_SECTION:
// edit <specId> <sectionId> <content>
if (parts.length > 3) {
params.specId = parts[1];
params.sectionId = parts[2];
params.content = parts.slice(3).join(' ');
}
break;
case CommandType.MARK_COMPLETE:
// complete <specId> <sectionId>
if (parts.length > 2) {
params.specId = parts[1];
params.sectionId = parts[2];
}
break;
case CommandType.ADD_QUESTION:
// question <specId> <targetSection> <text>
if (parts.length > 3) {
params.specId = parts[1];
params.targetSection = parts[2];
params.text = parts.slice(3).join(' ');
}
break;
case CommandType.ANSWER_QUESTION:
// answer <specId> <questionId> <answer>
if (parts.length > 3) {
params.specId = parts[1];
params.questionId = parts[2];
params.answer = parts.slice(3).join(' ');
}
break;
case CommandType.CHANGE_PHASE:
// phase <specId> <phase>
if (parts.length > 2) {
params.specId = parts[1];
params.phase = parts[2];
}
break;
case CommandType.VIEW_TASKS:
// tasks <specId>
if (parts.length > 1) {
params.specId = parts[1];
}
break;
case CommandType.VIEW_PROGRESS:
// progress <specId>
if (parts.length > 1) {
params.specId = parts[1];
}
break;
case CommandType.HELP:
// help [command]
if (parts.length > 1) {
params.command = parts[1];
}
break;
case CommandType.SUGGEST:
// suggest <specId> [section|task|question]
if (parts.length > 1) {
params.specId = parts[1];
if (parts.length > 2) {
params.type = parts[2];
}
else {
params.type = 'all'; // Default to all suggestions
}
}
break;
case CommandType.ANALYZE:
// analyze <specId> [scope]
if (parts.length > 1) {
params.specId = parts[1];
if (parts.length > 2) {
params.scope = parts[2];
}
}
break;
case CommandType.WORKFLOW:
// workflow <specId> [start|resume|pause|complete]
if (parts.length > 1) {
params.specId = parts[1];
if (parts.length > 2) {
params.action = parts[2];
}
else {
params.action = 'status'; // Default to showing workflow status
}
}
break;
case CommandType.EXPORT:
// export <specId> [format]
if (parts.length > 1) {
params.specId = parts[1];
if (parts.length > 2) {
params.format = parts[2];
}
else {
params.format = 'markdown'; // Default to markdown format
}
}
break;
}
return {
type: commandType,
params
};
}
catch (error) {
console.error(`Error parsing command: ${error}`);
return null;
}
}
/**
* Executes a command
*
* @param command - Command to execute
* @returns Promise that resolves with the command response
*/
export async function executeCommand(command) {
try {
switch (command.type) {
case CommandType.VIEW_SPEC:
return await viewSpecification(command.params);
case CommandType.EDIT_SECTION:
return await editSection(command.params);
case CommandType.MARK_COMPLETE:
return await markSectionComplete(command.params);
case CommandType.ADD_QUESTION:
return await addNewQuestion(command.params);
case CommandType.ANSWER_QUESTION:
return await answerExistingQuestion(command.params);
case CommandType.CHANGE_PHASE:
return await changeWorkflowPhase(command.params);
case CommandType.VIEW_TASKS:
return await viewTasks(command.params);
case CommandType.VIEW_PROGRESS:
return await viewProgress(command.params);
case CommandType.HELP:
return getHelp(command.params);
case CommandType.SUGGEST:
return await generateSuggestions(command.params);
case CommandType.ANALYZE:
return await analyzeSpecification(command.params);
case CommandType.WORKFLOW:
return await manageWorkflow(command.params);
case CommandType.EXPORT:
return await exportSpecification(command.params);
default:
return {
success: false,
message: 'Unknown command type'
};
}
}
catch (error) {
return {
success: false,
message: `Error executing command: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Handles user input
*
* @param input - User input string
* @returns Promise that resolves with the response
*/
export async function handleUserInput(input) {
try {
// Parse the command
const command = parseCommand(input);
// Check if the command is valid
if (!command) {
return {
success: false,
message: 'Invalid command. Type "help" for a list of available commands.'
};
}
// Execute the command
return await executeCommand(command);
}
catch (error) {
return {
success: false,
message: `Error handling user input: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Views a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function viewSpecification(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Get the workflow state
const workflowState = await loadWorkflowState(params.specId);
// Create a new session
await createSession(params.specId, 'User', { action: 'view_specification' });
return {
success: true,
message: `Viewing specification: ${specification.title}`,
data: {
specification,
workflowState
}
};
}
catch (error) {
return {
success: false,
message: `Error viewing specification: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Edits a section in a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function editSection(params) {
try {
// Check if all required parameters are provided
if (!params.specId || !params.sectionId || !params.content) {
return {
success: false,
message: 'Specification ID, section ID, and content are required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Find the section
const sectionIndex = specification.sections.findIndex(s => s.id === params.sectionId);
if (sectionIndex === -1) {
return {
success: false,
message: `Section not found: ${params.sectionId}`
};
}
// Create a copy of the specification
const updatedSpecification = { ...specification };
// Update the section content
updatedSpecification.sections = [...specification.sections];
updatedSpecification.sections[sectionIndex] = {
...updatedSpecification.sections[sectionIndex],
content: params.content
};
// Update the specification
await updateSpecification(params.specId, updatedSpecification, 'User', `Updated section: ${updatedSpecification.sections[sectionIndex].title}`);
return {
success: true,
message: `Section ${params.sectionId} updated successfully`,
data: {
section: updatedSpecification.sections[sectionIndex]
}
};
}
catch (error) {
return {
success: false,
message: `Error editing section: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Marks a section as complete
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function markSectionComplete(params) {
try {
// Check if all required parameters are provided
if (!params.specId || !params.sectionId) {
return {
success: false,
message: 'Specification ID and section ID are required'
};
}
// Mark the section as completed
const workflowState = await markSectionCompleted(params.specId, params.sectionId);
// Calculate the overall progress
const progress = await calculateOverallProgress(params.specId);
return {
success: true,
message: `Section ${params.sectionId} marked as complete`,
data: {
workflowState,
progress
}
};
}
catch (error) {
return {
success: false,
message: `Error marking section as complete: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Adds a new question
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function addNewQuestion(params) {
try {
// Check if all required parameters are provided
if (!params.specId || !params.targetSection || !params.text) {
return {
success: false,
message: 'Specification ID, target section, and question text are required'
};
}
// Add the question
const workflowState = await addQuestion(params.specId, {
text: params.text,
context: 'User question',
targetSection: params.targetSection
});
return {
success: true,
message: 'Question added successfully',
data: {
workflowState
}
};
}
catch (error) {
return {
success: false,
message: `Error adding question: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Answers an existing question
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function answerExistingQuestion(params) {
try {
// Check if all required parameters are provided
if (!params.specId || !params.questionId || !params.answer) {
return {
success: false,
message: 'Specification ID, question ID, and answer are required'
};
}
// Answer the question
const workflowState = await answerQuestion(params.specId, params.questionId, params.answer);
return {
success: true,
message: 'Question answered successfully',
data: {
workflowState
}
};
}
catch (error) {
return {
success: false,
message: `Error answering question: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Changes the workflow phase
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function changeWorkflowPhase(params) {
try {
// Check if all required parameters are provided
if (!params.specId || !params.phase) {
return {
success: false,
message: 'Specification ID and phase are required'
};
}
// Validate the phase
const validPhases = ['analysis', 'drafting', 'review', 'planning'];
if (!validPhases.includes(params.phase)) {
return {
success: false,
message: `Invalid phase: ${params.phase}. Valid phases are: ${validPhases.join(', ')}`
};
}
// Update the workflow phase
const workflowState = await updateWorkflowPhase(params.specId, params.phase);
return {
success: true,
message: `Workflow phase changed to ${params.phase}`,
data: {
workflowState
}
};
}
catch (error) {
return {
success: false,
message: `Error changing workflow phase: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Views tasks for a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function viewTasks(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the tasks for the specification
const tasks = await getTasksForSpecification(params.specId);
return {
success: true,
message: `Found ${tasks.length} tasks for specification ${params.specId}`,
data: {
tasks
}
};
}
catch (error) {
return {
success: false,
message: `Error viewing tasks: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Views progress for a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function viewProgress(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the workflow state
const workflowState = await loadWorkflowState(params.specId);
if (!workflowState) {
return {
success: false,
message: `Workflow state not found for specification: ${params.specId}`
};
}
// Calculate the overall progress
const progress = await calculateOverallProgress(params.specId);
return {
success: true,
message: `Current progress: ${progress}%`,
data: {
workflowState,
progress
}
};
}
catch (error) {
return {
success: false,
message: `Error viewing progress: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Gets help information
*
* @param params - Command parameters
* @returns Response with help information
*/
function getHelp(params) {
// General help
if (!params.command) {
return {
success: true,
message: 'Available commands:',
data: {
commands: [
{ command: 'view <specId>', description: 'View a specification' },
{ command: 'edit <specId> <sectionId> <content>', description: 'Edit a section in a specification' },
{ command: 'complete <specId> <sectionId>', description: 'Mark a section as complete' },
{ command: 'question <specId> <targetSection> <text>', description: 'Add a question' },
{ command: 'answer <specId> <questionId> <answer>', description: 'Answer a question' },
{ command: 'phase <specId> <phase>', description: 'Change the workflow phase' },
{ command: 'tasks <specId>', description: 'View tasks for a specification' },
{ command: 'progress <specId>', description: 'View progress for a specification' },
{ command: 'suggest <specId> [section|task|question]', description: 'Generate suggestions' },
{ command: 'analyze <specId> [scope]', description: 'Analyze codebase for a specification' },
{ command: 'workflow <specId> [start|resume|pause|complete]', description: 'Manage workflow' },
{ command: 'export <specId> [format]', description: 'Export a specification' },
{ command: 'help [command]', description: 'Get help information' }
]
}
};
}
// Command-specific help
switch (params.command) {
case 'view':
return {
success: true,
message: 'view <specId>',
data: {
description: 'View a specification',
parameters: [
{ name: 'specId', description: 'Specification identifier' }
],
example: 'view abc123'
}
};
case 'edit':
return {
success: true,
message: 'edit <specId> <sectionId> <content>',
data: {
description: 'Edit a section in a specification',
parameters: [
{ name: 'specId', description: 'Specification identifier' },
{ name: 'sectionId', description: 'Section identifier' },
{ name: 'content', description: 'New section content' }
],
example: 'edit abc123 overview This is the new overview content.'
}
};
case 'suggest':
return {
success: true,
message: 'suggest <specId> [section|task|question]',
data: {
description: 'Generate contextual suggestions based on codebase analysis and project rules',
parameters: [
{ name: 'specId', description: 'Specification identifier' },
{ name: 'type', description: 'Type of suggestion (section, task, question, or all)' }
],
example: 'suggest abc123 section'
}
};
case 'analyze':
return {
success: true,
message: 'analyze <specId> [scope]',
data: {
description: 'Analyze the codebase for a specification',
parameters: [
{ name: 'specId', description: 'Specification identifier' },
{ name: 'scope', description: 'Optional scope to limit analysis (e.g., directory path)' }
],
example: 'analyze abc123 src/components'
}
};
case 'workflow':
return {
success: true,
message: 'workflow <specId> [start|resume|pause|complete]',
data: {
description: 'Manage workflow for a specification',
parameters: [
{ name: 'specId', description: 'Specification identifier' },
{ name: 'action', description: 'Workflow action (start, resume, pause, complete, or status)' }
],
example: 'workflow abc123 resume'
}
};
case 'export':
return {
success: true,
message: 'export <specId> [format]',
data: {
description: 'Export a specification to a specific format',
parameters: [
{ name: 'specId', description: 'Specification identifier' },
{ name: 'format', description: 'Export format (markdown, html, pdf, or json)' }
],
example: 'export abc123 html'
}
};
// Add more command-specific help as needed
default:
return {
success: false,
message: `Unknown command: ${params.command}`
};
}
}
/**
* Generates suggestions based on codebase analysis and project rules
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function generateSuggestions(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Get the workflow state
const workflowState = await loadWorkflowState(params.specId);
if (!workflowState) {
return {
success: false,
message: `Workflow state not found for specification: ${params.specId}`
};
}
// Determine the suggestion type
const type = params.type || 'all';
const validTypes = ['section', 'task', 'question', 'all'];
if (!validTypes.includes(type)) {
return {
success: false,
message: `Invalid suggestion type: ${type}. Valid types are: ${validTypes.join(', ')}`
};
}
// Generate suggestions based on type
const suggestions = {};
// Get analysis results from workflow state or perform a new analysis
let analysisResults = workflowState.analysisResults;
if (!analysisResults) {
// Perform a basic analysis if none exists
analysisResults = await analyzeRepository({
includeDirs: [],
focusKeywords: [specification.title],
maxFiles: 500
});
}
// Import project rules integrator for rule suggestions
const { generateRuleSuggestions } = await import('./project-rules-integrator.js');
// Generate section suggestions if requested
if (type === 'section' || type === 'all') {
// Get rule suggestions
const ruleSuggestions = await generateRuleSuggestions(specification);
// Map rules to sections
const sectionSuggestions = {};
for (const rule of ruleSuggestions) {
if (!sectionSuggestions[rule.sectionId]) {
sectionSuggestions[rule.sectionId] = [];
}
sectionSuggestions[rule.sectionId].push(`**${rule.description}**: ${rule.relevance}`);
}
// Add affected components to technical design section
if (analysisResults && analysisResults.affectedComponents.length > 0) {
const technicalDesignSection = specification.sections.find(s => s.title.toLowerCase().includes('technical') &&
s.title.toLowerCase().includes('design'));
if (technicalDesignSection) {
if (!sectionSuggestions[technicalDesignSection.id]) {
sectionSuggestions[technicalDesignSection.id] = [];
}
sectionSuggestions[technicalDesignSection.id].push('**Consider these components in your design:**\n' +
analysisResults.affectedComponents
.map(c => `- ${c.name}: ${c.description} (Impact: ${c.impactLevel})`)
.join('\n'));
}
}
suggestions.sections = sectionSuggestions;
}
// Generate task suggestions if requested
if (type === 'task' || type === 'all') {
const taskSuggestions = [];
// Add implementation tasks based on affected components
if (analysisResults && analysisResults.affectedComponents.length > 0) {
for (const component of analysisResults.affectedComponents) {
taskSuggestions.push(`Implement changes to ${component.name} (${component.impactLevel} impact)`);
}
}
// Add testing tasks
taskSuggestions.push('Create unit tests for new functionality');
taskSuggestions.push('Perform integration testing');
// Add documentation tasks
taskSuggestions.push('Update documentation to reflect changes');
suggestions.tasks = taskSuggestions;
}
// Generate question suggestions if requested
if (type === 'question' || type === 'all') {
const questionSuggestions = [];
// Add questions based on analysis results
if (analysisResults && analysisResults.suggestedQuestions) {
questionSuggestions.push(...analysisResults.suggestedQuestions);
}
// Add standard questions
questionSuggestions.push('Are there any performance considerations for this feature?');
questionSuggestions.push('Are there any security implications to consider?');
questionSuggestions.push('What are the acceptance criteria for this feature?');
suggestions.questions = questionSuggestions;
}
return {
success: true,
message: `Generated ${type} suggestions for specification: ${specification.title}`,
data: {
specification,
suggestions,
analysisResults
}
};
}
catch (error) {
return {
success: false,
message: `Error generating suggestions: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Analyzes the codebase for a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function analyzeSpecification(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Get the workflow state
const workflowState = await loadWorkflowState(params.specId);
if (!workflowState) {
return {
success: false,
message: `Workflow state not found for specification: ${params.specId}`
};
}
// Extract keywords from the specification title and description
const keywords = [
specification.title,
...specification.sections
.filter(s => s.title.toLowerCase().includes('overview') || s.title.toLowerCase().includes('requirement'))
.map(s => s.content)
];
// Configure the analysis
const analysisConfig = {
includeDirs: params.scope ? [params.scope] : [],
focusKeywords: keywords,
maxFiles: 1000 // Limit to prevent excessive processing
};
// Perform the analysis
console.log(`Starting codebase analysis for specification: ${specification.title}`);
console.time('Codebase analysis');
const analysisResults = await analyzeRepository(analysisConfig);
console.timeEnd('Codebase analysis');
// Update the workflow state with the analysis results
workflowState.analysisResults = analysisResults;
await updateWorkflowPhase(params.specId, 'analysis');
// Format the analysis results for visualization
const formattedResults = formatAnalysisResults(analysisResults);
return {
success: true,
message: `Codebase analysis completed for specification: ${specification.title}`,
data: {
specification,
workflowState,
analysisResults: formattedResults
}
};
}
catch (error) {
return {
success: false,
message: `Error analyzing codebase: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Formats analysis results for visualization
*
* @param results - Analysis results to format
* @returns Formatted analysis results
*/
function formatAnalysisResults(results) {
// Create a formatted version of the results
const formatted = {
summary: {
affectedComponentCount: results.affectedComponents.length,
impactedFileCount: results.impactedFiles.length,
dependencyCount: results.dependencies.length,
ruleCount: results.projectRules.length,
confidence: `${Math.round(results.analysisConfidence * 100)}%`
},
components: results.affectedComponents.map(c => ({
name: c.name,
path: c.path,
description: c.description,
impactLevel: c.impactLevel
})),
files: results.impactedFiles.map(f => ({
path: f.path,
reason: f.reason,
suggestedChanges: f.suggestedChanges || 'None'
})),
dependencies: results.dependencies.map(d => ({
source: d.source,
target: d.target,
type: d.type,
description: d.description
})),
rules: results.projectRules.map(r => ({
id: r.id,
description: r.description,
relevance: r.relevance,
sectionId: r.sectionId
})),
questions: results.suggestedQuestions
};
return formatted;
}
/**
* Manages workflow for a specification
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function manageWorkflow(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Get the workflow state
const workflowState = await loadWorkflowState(params.specId);
if (!workflowState && params.action !== 'start') {
return {
success: false,
message: `Workflow state not found for specification: ${params.specId}`
};
}
// Determine the action
const action = params.action || 'status';
const validActions = ['start', 'resume', 'pause', 'complete', 'status'];
if (!validActions.includes(action)) {
return {
success: false,
message: `Invalid workflow action: ${action}. Valid actions are: ${validActions.join(', ')}`
};
}
// Perform the action
let updatedWorkflowState = workflowState;
let message = '';
switch (action) {
case 'start':
// Create a new workflow state if it doesn't exist
if (!workflowState) {
updatedWorkflowState = await createWorkflowState(params.specId, 'analysis');
message = `Started new workflow for specification: ${specification.title}`;
}
else {
message = `Workflow already exists for specification: ${specification.title}`;
}
break;
case 'resume':
// Create a new session
await createSession(params.specId, 'User', { action: 'resume_workflow' });
message = `Resumed workflow for specification: ${specification.title}`;
break;
case 'pause':
// Get the most recent session and end it
const sessions = await getSessionsForSpecification(params.specId);
const activeSession = sessions.find(s => !s.endTime);
if (activeSession) {
await endSession(activeSession.id);
}
message = `Paused workflow for specification: ${specification.title}`;
break;
case 'complete':
// Update the workflow phase to 'planning'
updatedWorkflowState = await updateWorkflowPhase(params.specId, 'planning');
// Update the specification status to 'approved'
const updatedSpecification = { ...specification };
updatedSpecification.metadata = { ...specification.metadata, status: 'approved' };
await updateSpecification(params.specId, updatedSpecification, 'User', 'Marked specification as approved');
message = `Completed workflow for specification: ${specification.title}`;
break;
case 'status':
// Calculate the overall progress
const progress = await calculateOverallProgress(params.specId);
if (workflowState) {
message = `Current workflow status: ${workflowState.currentPhase} (${progress}% complete)`;
}
else {
message = `No workflow found for specification. Progress: ${progress}% complete`;
}
break;
}
// Get the updated specification
const updatedSpecification = await getSpecification(params.specId);
return {
success: true,
message,
data: {
specification: updatedSpecification,
workflowState: updatedWorkflowState,
progress: await calculateOverallProgress(params.specId)
}
};
}
catch (error) {
return {
success: false,
message: `Error managing workflow: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Exports a specification to a specific format
*
* @param params - Command parameters
* @returns Promise that resolves with the response
*/
async function exportSpecification(params) {
try {
// Check if the specification ID is provided
if (!params.specId) {
return {
success: false,
message: 'Specification ID is required'
};
}
// Get the specification
const specification = await getSpecification(params.specId);
// Determine the format
const format = params.format || 'markdown';
const validFormats = ['markdown', 'html', 'json', 'pdf'];
if (!validFormats.includes(format)) {
return {
success: false,
message: `Invalid export format: ${format}. Valid formats are: ${validFormats.join(', ')}`
};
}
// Export the specification
let exportedContent = '';
let contentType = '';
switch (format) {
case 'markdown':
exportedContent = exportToMarkdown(specification);
contentType = 'text/markdown';
break;
case 'html':
exportedContent = exportToHtml(specification);
contentType = 'text/html';
break;
case 'json':
exportedContent = JSON.stringify(specification, null, 2);
contentType = 'application/json';
break;
case 'pdf':
// PDF export would require additional libraries
return {
success: false,
message: 'PDF export is not yet implemented'
};
}
// Create the export directory if it doesn't exist
const exportDir = path.join(process.cwd(), 'exports');
if (!fs.existsSync(exportDir)) {
fs.mkdirSync(exportDir, { recursive: true });
}
// Save the exported content
const fileName = `${specification.id}_${format}_${Date.now()}`;
const filePath = path.join(exportDir, `${fileName}.${format === 'json' ? 'json' : format === 'html' ? 'html' : 'md'}`);
fs.writeFileSync(filePath, exportedContent, 'utf-8');
return {
success: true,
message: `Specification exported to ${format} format`,
data: {
specification,
exportedContent,
contentType,
filePath
}
};
}
catch (error) {
return {
success: false,
message: `Error exporting specification: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Exports a specification to Markdown format
*
* @param specification - Specification to export
* @returns Markdown content
*/
function exportToMarkdown(specification) {
let markdown = `# ${specification.title}\n\n`;
// Add metadata
markdown += `- **ID**: ${specification.id}\n`;
markdown += `- **Version**: ${specification.version}\n`;
markdown += `- **Status**: ${specification.metadata.status}\n`;
markdown += `- **Authors**: ${specification.metadata.authors.join(', ')}\n`;
markdown += `- **Created**: ${new Date(specification.createdAt).toLocaleDateString()}\n`;
markdown += `- **Updated**: ${new Date(specification.updatedAt).toLocaleDateString()}\n\n`;
// Add tags if present
if (specification.metadata.tags && specification.metadata.tags.length > 0) {
markdown += `**Tags**: ${specification.metadata.tags.join(', ')}\n\n`;
}
// Add sections
for (const section of specification.sections) {
markdown += `## ${section.title}\n\n${section.content}\n\n`;
}
// Add related specifications if present
if (specification.metadata.relatedSpecs && specification.metadata.relatedSpecs.length > 0) {
markdown += `## Related Specifications\n\n`;
for (const relatedSpec of specification.metadata.relatedSpecs) {
markdown += `- ${relatedSpec}\n`;
}
markdown += '\n';
}
// Add change history
if (specification.changeHistory && specification.changeHistory.length > 0) {
markdown += `## Change History\n\n`;
markdown += `| Date | Author | Description | Section |\n`;
markdown += `| ---- | ------ | ----------- | ------- |\n`;
for (const change of specification.changeHistory) {
const date = new Date(change.timestamp).toLocaleDateString();
markdown += `| ${date} | ${change.author} | ${change.description} | ${change.sectionId} |\n`;
}
markdown += '\n';
}
return markdown;
}
/**
* Exports a specification to HTML format
*
* @param specification - Specification to export
* @returns HTML content
*/
function exportToHtml(specification) {
// Convert the specification to Markdown first
const markdown = exportToMarkdown(specification);
// Simple Markdown to HTML conversion
// Note: A proper implementation would use a Markdown parser library
let html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${specification.title}</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1, h2, h3 {
color: #333;
}
table {
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
}
th {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<div class="specification">
`;
// Very basic Markdown to HTML conversion
const lines = markdown.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('# ')) {
html += ` <h1>${line.substring(2)}</h1>\n`;
}
else if (line.startsWith('## ')) {
html += ` <h2>${line.substring(3)}</h2>\n`;
}
else if (line.startsWith('### ')) {
html += ` <h3>${line.substring(4)}</h3>\n`;
}
else if (line.startsWith('- ')) {
html += ` <ul>\n <li>${line.substring(2)}</li>\n </ul>\n`;
}
else if (line.startsWith('| ')) {
// Table handling
if (i > 0 && !lines[i - 1].startsWith('| ')) {
html += ' <table>\n';
}
const cells = line.split('|').filter(cell => cell.trim() !== '');
if (i > 0 && lines[i - 1].startsWith('| ') && line.includes('---')) {
// This is a table header separator, skip it
continue;
}
html += ' <tr>\n';
for (const cell of cells) {
if (i > 0 && lines[i - 1].startsWith('| ') && !lines[i - 1].includes('---')) {
html += ` <td>${cell.trim()}</td>\n`;
}
else {
html += ` <th>${cell.trim()}</th>\n`;
}
}
html += ' </tr>\n';
if (i < lines.length - 1 && !lines[i + 1].startsWith('| ')) {
html += ' </table>\n';
}
}
else if (line.trim() === '') {
html += ' <br>\n';
}
else {
html += ` <p>${line}</p>\n`;
}
}
html += ` </div>
</body>
</html>`;
return html;
}
//# sourceMappingURL=user-interaction-handler.js.map