@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
395 lines • 15.1 kB
JavaScript
/**
* Workflow State Manager for the Idea Honing Tool
*
* This component handles the persistence and management of workflow state,
* enabling continuity across sessions and tracking progress.
*/
import fsPromises from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
// Get the base directory for workflow state
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.resolve(__dirname, '../../../..');
const DATA_DIR = process.env.DATA_DIR || path.join(PROJECT_ROOT, 'data');
const WORKFLOW_STATE_DIR = path.join(DATA_DIR, 'workflow-state');
/**
* Creates or updates a workflow state
*
* @param state - Workflow state to save
* @returns Promise that resolves with the saved workflow state
*/
export async function saveWorkflowState(state) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Update the last active timestamp
state.lastActive = new Date();
// Save the workflow state
const statePath = path.join(WORKFLOW_STATE_DIR, `${state.specId}.json`);
await fsPromises.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');
return state;
}
catch (error) {
throw new Error(`Failed to save workflow state: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Loads a workflow state by specification ID
*
* @param specId - Specification identifier
* @returns Promise that resolves with the loaded workflow state, or null if not found
*/
export async function loadWorkflowState(specId) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Check if the workflow state file exists
const statePath = path.join(WORKFLOW_STATE_DIR, `${specId}.json`);
try {
await fsPromises.access(statePath);
}
catch {
return null;
}
// Load and parse the workflow state file
const stateContent = await fsPromises.readFile(statePath, 'utf-8');
const state = JSON.parse(stateContent);
// Convert string dates to Date objects
state.lastActive = new Date(state.lastActive);
return state;
}
catch (error) {
throw new Error(`Failed to load workflow state: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Creates a new workflow state
*
* @param specId - Specification identifier
* @param initialPhase - Initial workflow phase
* @param analysisResults - Optional analysis results
* @returns Promise that resolves with the created workflow state
*/
export async function createWorkflowState(specId, initialPhase = 'analysis', analysisResults) {
try {
// Create the workflow state
const state = {
specId,
currentPhase: initialPhase,
completedSections: [],
pendingQuestions: [],
analysisResults,
lastActive: new Date()
};
// Save the workflow state
return await saveWorkflowState(state);
}
catch (error) {
throw new Error(`Failed to create workflow state: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Updates the current phase of a workflow state
*
* @param specId - Specification identifier
* @param phase - New workflow phase
* @returns Promise that resolves with the updated workflow state
*/
export async function updateWorkflowPhase(specId, phase) {
try {
// Load the existing workflow state
const state = await loadWorkflowState(specId);
if (!state) {
throw new Error(`Workflow state not found for specification: ${specId}`);
}
// Update the phase
state.currentPhase = phase;
// Save the updated workflow state
return await saveWorkflowState(state);
}
catch (error) {
throw new Error(`Failed to update workflow phase: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Marks a section as completed in a workflow state
*
* @param specId - Specification identifier
* @param sectionId - Section identifier
* @returns Promise that resolves with the updated workflow state
*/
export async function markSectionCompleted(specId, sectionId) {
try {
// Load the existing workflow state
const state = await loadWorkflowState(specId);
if (!state) {
throw new Error(`Workflow state not found for specification: ${specId}`);
}
// Add the section to the completed sections if not already present
if (!state.completedSections.includes(sectionId)) {
state.completedSections.push(sectionId);
}
// Save the updated workflow state
return await saveWorkflowState(state);
}
catch (error) {
throw new Error(`Failed to mark section as completed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Adds a question to a workflow state
*
* @param specId - Specification identifier
* @param question - Question to add
* @returns Promise that resolves with the updated workflow state
*/
export async function addQuestion(specId, question) {
try {
// Load the existing workflow state
const state = await loadWorkflowState(specId);
if (!state) {
throw new Error(`Workflow state not found for specification: ${specId}`);
}
// Create the question with a unique ID
const newQuestion = {
id: `q_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
text: question.text,
context: question.context,
targetSection: question.targetSection,
answered: false
};
// Add the question to the pending questions
state.pendingQuestions.push(newQuestion);
// Save the updated workflow state
return await saveWorkflowState(state);
}
catch (error) {
throw new Error(`Failed to add question: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Answers a question in a workflow state
*
* @param specId - Specification identifier
* @param questionId - Question identifier
* @param answer - Answer to the question
* @returns Promise that resolves with the updated workflow state
*/
export async function answerQuestion(specId, questionId, answer) {
try {
// Load the existing workflow state
const state = await loadWorkflowState(specId);
if (!state) {
throw new Error(`Workflow state not found for specification: ${specId}`);
}
// Find the question
const questionIndex = state.pendingQuestions.findIndex(q => q.id === questionId);
if (questionIndex === -1) {
throw new Error(`Question not found: ${questionId}`);
}
// Update the question
state.pendingQuestions[questionIndex].answered = true;
state.pendingQuestions[questionIndex].answer = answer;
// Save the updated workflow state
return await saveWorkflowState(state);
}
catch (error) {
throw new Error(`Failed to answer question: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Creates a new session for a specification
*
* @param specId - Specification identifier
* @param userId - User identifier
* @param metadata - Session metadata
* @returns Promise that resolves with the created session information
*/
export async function createSession(specId, userId, metadata = {}) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Create the sessions directory if it doesn't exist
const sessionsDir = path.join(WORKFLOW_STATE_DIR, 'sessions');
await fsPromises.mkdir(sessionsDir, { recursive: true });
// Generate a session ID
const sessionId = `session_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// Create the session
const session = {
id: sessionId,
userId,
startTime: new Date().toISOString(),
specId,
progress: 0,
metadata
};
// Save the session
const sessionPath = path.join(sessionsDir, `${sessionId}.json`);
await fsPromises.writeFile(sessionPath, JSON.stringify(session, null, 2), 'utf-8');
return session;
}
catch (error) {
throw new Error(`Failed to create session: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Updates a session's progress
*
* @param sessionId - Session identifier
* @param progress - Session progress (0-100)
* @returns Promise that resolves with the updated session information
*/
export async function updateSessionProgress(sessionId, progress) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Create the sessions directory if it doesn't exist
const sessionsDir = path.join(WORKFLOW_STATE_DIR, 'sessions');
await fsPromises.mkdir(sessionsDir, { recursive: true });
// Load the session
const sessionPath = path.join(sessionsDir, `${sessionId}.json`);
try {
await fsPromises.access(sessionPath);
}
catch {
throw new Error(`Session not found: ${sessionId}`);
}
const sessionContent = await fsPromises.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionContent);
// Update the progress
session.progress = Math.max(0, Math.min(100, progress));
// Save the updated session
await fsPromises.writeFile(sessionPath, JSON.stringify(session, null, 2), 'utf-8');
return session;
}
catch (error) {
throw new Error(`Failed to update session progress: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Ends a session
*
* @param sessionId - Session identifier
* @returns Promise that resolves with the ended session information
*/
export async function endSession(sessionId) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Create the sessions directory if it doesn't exist
const sessionsDir = path.join(WORKFLOW_STATE_DIR, 'sessions');
await fsPromises.mkdir(sessionsDir, { recursive: true });
// Load the session
const sessionPath = path.join(sessionsDir, `${sessionId}.json`);
try {
await fsPromises.access(sessionPath);
}
catch {
throw new Error(`Session not found: ${sessionId}`);
}
const sessionContent = await fsPromises.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionContent);
// Update the end time
session.endTime = new Date().toISOString();
// Save the updated session
await fsPromises.writeFile(sessionPath, JSON.stringify(session, null, 2), 'utf-8');
return session;
}
catch (error) {
throw new Error(`Failed to end session: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Gets sessions for a specification
*
* @param specId - Specification identifier
* @returns Promise that resolves with an array of session information
*/
export async function getSessionsForSpecification(specId) {
try {
// Ensure the workflow state directory exists
await ensureWorkflowStateDirectory();
// Create the sessions directory if it doesn't exist
const sessionsDir = path.join(WORKFLOW_STATE_DIR, 'sessions');
await fsPromises.mkdir(sessionsDir, { recursive: true });
// Get all session files
const sessionFiles = await fsPromises.readdir(sessionsDir);
// Filter session files to only include .json files
const jsonSessionFiles = sessionFiles.filter(file => file.endsWith('.json'));
// Read and filter sessions
const sessions = [];
for (const file of jsonSessionFiles) {
try {
const sessionPath = path.join(sessionsDir, file);
const sessionContent = await fsPromises.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionContent);
// Check if the session is for the specified specification
if (session.specId === specId) {
sessions.push(session);
}
}
catch (error) {
console.error(`Error reading session file ${file}: ${error}`);
}
}
// Sort sessions by start time (newest first)
return sessions.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
}
catch (error) {
throw new Error(`Failed to get sessions for specification: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Calculates the overall progress for a specification
*
* @param specId - Specification identifier
* @returns Promise that resolves with the progress percentage (0-100)
*/
export async function calculateOverallProgress(specId) {
try {
// Load the workflow state
const state = await loadWorkflowState(specId);
if (!state) {
return 0;
}
// Calculate progress based on completed sections and answered questions
let progress = 0;
// Phase progress (25% per phase)
const phaseProgress = {
'analysis': 25,
'drafting': 50,
'review': 75,
'planning': 100
};
progress = phaseProgress[state.currentPhase] || 0;
// Adjust progress based on pending questions
if (state.pendingQuestions.length > 0) {
const answeredQuestions = state.pendingQuestions.filter(q => q.answered).length;
const totalQuestions = state.pendingQuestions.length;
// Reduce progress if there are unanswered questions
if (totalQuestions > 0 && answeredQuestions < totalQuestions) {
const questionProgress = answeredQuestions / totalQuestions;
progress = Math.max(0, progress - (25 * (1 - questionProgress)));
}
}
return Math.round(progress);
}
catch (error) {
throw new Error(`Failed to calculate overall progress: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Ensures the workflow state directory exists
*
* @returns Promise that resolves when the directory exists
*/
async function ensureWorkflowStateDirectory() {
try {
await fsPromises.mkdir(WORKFLOW_STATE_DIR, { recursive: true });
}
catch (error) {
throw new Error(`Failed to create workflow state directory: ${error instanceof Error ? error.message : String(error)}`);
}
}
//# sourceMappingURL=workflow-state-manager.js.map