@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
462 lines • 19.8 kB
JavaScript
/**
* Specification Manager for the Idea Honing Tool
*
* This component handles the creation, storage, and retrieval of specification documents.
*/
import { v4 as uuidv4 } from 'uuid';
import { saveSpecification, loadSpecification, createBackup, saveChangeRecord, listSpecifications } from '../utils/file-system.js';
import { loadTemplate, createSectionsFromTemplate, renderTemplate } from './template-engine.js';
import { buildSearchIndex, indexSpecification, searchSpecifications as searchSpecsInIndex } from '../utils/search-indexer.js';
/**
* Creates a new specification document
*
* @param title - Title of the specification
* @param description - Description of the feature or idea
* @param author - Author of the specification
* @param templateName - Name of the template to use (defaults to 'default-template')
* @param projectId - Optional project identifier
* @param featureId - Optional feature identifier
* @param additionalData - Optional additional data for template rendering
* @returns Promise that resolves with the created specification
*/
export async function createSpecification(title, description, author, templateName = 'default-template', projectId = 'default', featureId = '', additionalData = {}) {
try {
// Generate a unique ID for the specification
const specId = uuidv4();
// Set the current date
const now = new Date();
// Load the template
const template = await loadTemplate(templateName);
// Prepare data for template rendering
const data = {
title,
description,
author,
currentDate: now.toISOString().split('T')[0],
projectId,
featureId,
relatedFiles: '',
...additionalData
};
// Create sections from the template
const sections = createSectionsFromTemplate(template, data);
// Create the metadata
const metadata = {
authors: [author],
status: 'draft',
tags: [],
relatedSpecs: []
};
// Create the specification document
const specification = {
id: specId,
title,
sections,
metadata,
version: 1,
changeHistory: [],
projectId,
featureId: featureId || specId,
createdAt: now,
updatedAt: now
};
// Create the initial change record
const initialChange = {
timestamp: now,
author,
description: 'Initial creation',
sectionId: 'all',
newContent: renderTemplate(template, data)
};
// Add the initial change to the history
specification.changeHistory.push(initialChange);
// Save the specification
await saveSpecification(specification);
// Save the change record
await saveChangeRecord(specId, initialChange);
// Index the specification for search
try {
await indexSpecification(specification);
}
catch (error) {
console.error(`Failed to index specification: ${error}`);
// Non-blocking error - continue even if indexing fails
}
return specification;
}
catch (error) {
throw new Error(`Failed to create specification: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Retrieves a specification document by ID
*
* @param specId - Specification identifier
* @param projectId - Optional project identifier
* @returns Promise that resolves with the retrieved specification
*/
export async function getSpecification(specId, projectId) {
try {
return await loadSpecification(specId, projectId);
}
catch (error) {
throw new Error(`Failed to retrieve specification: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Updates a specification document
*
* @param specId - Specification identifier
* @param updates - Updates to apply to the specification
* @param author - Author of the changes
* @param changeDescription - Description of the changes
* @returns Promise that resolves with the updated specification
*/
export async function updateSpecification(specId, updates, author, changeDescription) {
try {
// Load the existing specification
const specification = await loadSpecification(specId, updates.projectId);
// Create a backup of the current version
await createBackup(specification);
// Track changes to sections
const sectionChanges = [];
// Update the specification
if (updates.title) {
specification.title = updates.title;
}
if (updates.metadata) {
// Merge metadata
specification.metadata = {
...specification.metadata,
...updates.metadata
};
// Add the author if not already present
if (!specification.metadata.authors.includes(author)) {
specification.metadata.authors.push(author);
}
}
if (updates.sections) {
// Process each updated section
for (const updatedSection of updates.sections) {
// Find the existing section
const existingSection = specification.sections.find(s => s.id === updatedSection.id);
if (existingSection) {
// Record the change if the content has changed
if (existingSection.content !== updatedSection.content) {
sectionChanges.push({
timestamp: new Date(),
author,
description: changeDescription,
sectionId: updatedSection.id,
previousContent: existingSection.content,
newContent: updatedSection.content
});
}
// Update the section
existingSection.title = updatedSection.title || existingSection.title;
existingSection.content = updatedSection.content || existingSection.content;
existingSection.stakeholder = updatedSection.stakeholder || existingSection.stakeholder;
existingSection.order = updatedSection.order ?? existingSection.order;
}
else {
// Add a new section
specification.sections.push(updatedSection);
// Record the addition
sectionChanges.push({
timestamp: new Date(),
author,
description: `Added section: ${updatedSection.title}`,
sectionId: updatedSection.id,
newContent: updatedSection.content
});
}
}
}
// Increment the version
specification.version += 1;
// Update the timestamp
specification.updatedAt = new Date();
// Add the changes to the history
specification.changeHistory.push(...sectionChanges);
// Save the updated specification
await saveSpecification(specification);
// Save each change record
for (const change of sectionChanges) {
await saveChangeRecord(specId, change);
}
// Update the search index
try {
await indexSpecification(specification);
}
catch (error) {
console.error(`Failed to update search index: ${error}`);
// Non-blocking error - continue even if indexing fails
}
return specification;
}
catch (error) {
throw new Error(`Failed to update specification: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Lists all specifications, optionally filtered by project
*
* @param projectId - Optional project identifier to filter by
* @returns Promise that resolves with an array of specification metadata
*/
export async function listAllSpecifications(projectId) {
try {
return await listSpecifications(projectId);
}
catch (error) {
throw new Error(`Failed to list specifications: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Searches for specifications by keyword and filters
*
* @param query - Search query
* @param filters - Optional search filters
* @returns Promise that resolves with an array of search results
*/
export async function searchSpecifications(query, filters) {
try {
// Use the search indexer to find matching specifications
return await searchSpecsInIndex(query, filters);
}
catch (error) {
console.error(`Error using search index: ${error}`);
// Fallback to basic search if the index fails
try {
// Get all specifications
const allSpecs = await listSpecifications(filters?.projectId);
const results = [];
// Process each specification
for (const specId of allSpecs) {
try {
// Load the specification
const spec = await loadSpecification(specId);
// Apply filters
if (filters) {
if (filters.projectId && spec.projectId !== filters.projectId) {
continue;
}
if (filters.featureId && spec.featureId !== filters.featureId) {
continue;
}
if (filters.status && spec.metadata.status !== filters.status) {
continue;
}
if (filters.author && !spec.metadata.authors.includes(filters.author)) {
continue;
}
if (filters.tag && !spec.metadata.tags.includes(filters.tag)) {
continue;
}
if (filters.startDate && new Date(spec.createdAt) < new Date(filters.startDate)) {
continue;
}
if (filters.endDate && new Date(spec.createdAt) > new Date(filters.endDate)) {
continue;
}
}
// Skip if query is empty and no filters are applied
if (!query && !filters) {
continue;
}
// Calculate relevance score
let relevance = 0;
const matchingSections = [];
const matchingKeywords = [];
if (query) {
// Normalize the query
const normalizedQuery = query.toLowerCase();
// Check title match
if (spec.title.toLowerCase().includes(normalizedQuery)) {
relevance += 0.5;
}
// Check section titles and content
for (const section of spec.sections) {
if (section.title.toLowerCase().includes(normalizedQuery)) {
relevance += 0.2;
matchingSections.push(section.title);
}
if (section.content.toLowerCase().includes(normalizedQuery)) {
relevance += 0.1;
if (!matchingSections.includes(section.title)) {
matchingSections.push(section.title);
}
}
}
// Check tags
for (const tag of spec.metadata.tags) {
if (tag.toLowerCase().includes(normalizedQuery)) {
relevance += 0.1;
matchingKeywords.push(tag);
}
}
}
else {
// If no query but filters match, give a base relevance
relevance = 0.5;
}
// Add to results if relevant
if (relevance > 0) {
// Find overview section for description
const overviewSection = spec.sections.find(s => s.title.toLowerCase().includes('overview'));
results.push({
id: spec.id,
title: spec.title,
description: overviewSection ?
overviewSection.content.substring(0, 200) + '...' :
'No description available',
projectId: spec.projectId,
featureId: spec.featureId,
status: spec.metadata.status,
relevance,
matchingSections,
matchingKeywords
});
}
}
catch (error) {
console.error(`Error processing specification ${specId}:`, error);
}
}
// Sort by relevance
results.sort((a, b) => b.relevance - a.relevance);
return results;
}
catch (fallbackError) {
throw new Error(`Failed to search specifications: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
}
}
}
/**
* Resolves conflicts between different versions of a specification
*
* @param specId - Specification identifier
* @param conflictingVersions - Array of conflicting versions
* @param resolutionStrategy - Strategy for resolving conflicts ('latest', 'merge', or 'manual')
* @param manualResolution - Manual resolution data (required if strategy is 'manual')
* @param author - Author of the resolution
* @returns Promise that resolves with the resolved specification
*/
/**
* Rebuilds the search index for all specifications
*
* @returns Promise that resolves when the index is rebuilt
*/
export async function rebuildSearchIndex() {
try {
// Build the search index
await buildSearchIndex();
console.log('Search index rebuilt successfully');
}
catch (error) {
throw new Error(`Failed to rebuild search index: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Resolves conflicts between different versions of a specification
*
* @param specId - Specification identifier
* @param conflictingVersions - Array of conflicting versions
* @param resolutionStrategy - Strategy for resolving conflicts ('latest', 'merge', or 'manual')
* @param manualResolution - Manual resolution data (required if strategy is 'manual')
* @param author - Author of the resolution
* @returns Promise that resolves with the resolved specification
*/
export async function resolveConflicts(specId, conflictingVersions, resolutionStrategy, manualResolution, author = 'system') {
try {
let resolvedSpec;
if (resolutionStrategy === 'latest') {
// Use the version with the latest timestamp
resolvedSpec = conflictingVersions.reduce((latest, current) => {
return current.updatedAt > latest.updatedAt ? current : latest;
}, conflictingVersions[0]);
}
else if (resolutionStrategy === 'merge') {
// Start with the latest version
const latestVersion = conflictingVersions.reduce((latest, current) => {
return current.updatedAt > latest.updatedAt ? current : latest;
}, conflictingVersions[0]);
// Create a new specification based on the latest version
resolvedSpec = { ...latestVersion };
// Merge sections from all versions
const mergedSections = {};
// Process each version
for (const version of conflictingVersions) {
for (const section of version.sections) {
// If this section doesn't exist yet, or this version is newer, use this section
if (!mergedSections[section.id] || version.updatedAt > resolvedSpec.updatedAt) {
mergedSections[section.id] = section;
}
}
}
// Convert the merged sections back to an array
resolvedSpec.sections = Object.values(mergedSections);
// Merge metadata
const allAuthors = new Set();
const allTags = new Set();
const allRelatedSpecs = new Set();
for (const version of conflictingVersions) {
version.metadata.authors.forEach(author => allAuthors.add(author));
version.metadata.tags.forEach(tag => allTags.add(tag));
version.metadata.relatedSpecs.forEach(spec => allRelatedSpecs.add(spec));
}
resolvedSpec.metadata = {
authors: Array.from(allAuthors),
status: resolvedSpec.metadata.status,
tags: Array.from(allTags),
relatedSpecs: Array.from(allRelatedSpecs)
};
}
else if (resolutionStrategy === 'manual' && manualResolution) {
// Use the manual resolution
const latestVersion = conflictingVersions.reduce((latest, current) => {
return current.updatedAt > latest.updatedAt ? current : latest;
}, conflictingVersions[0]);
// Create a new specification based on the latest version
resolvedSpec = { ...latestVersion };
// Apply manual resolution
if (manualResolution.title) {
resolvedSpec.title = manualResolution.title;
}
if (manualResolution.sections) {
resolvedSpec.sections = manualResolution.sections;
}
if (manualResolution.metadata) {
resolvedSpec.metadata = {
...resolvedSpec.metadata,
...manualResolution.metadata
};
}
}
else {
throw new Error('Invalid resolution strategy or missing manual resolution data');
}
// Increment the version
resolvedSpec.version += 1;
// Update the timestamp
resolvedSpec.updatedAt = new Date();
// Add a change record for the conflict resolution
const changeRecord = {
timestamp: new Date(),
author,
description: `Resolved conflicts using ${resolutionStrategy} strategy`,
sectionId: 'all',
newContent: 'Conflict resolution'
};
resolvedSpec.changeHistory.push(changeRecord);
// Save the resolved specification
await saveSpecification(resolvedSpec);
// Save the change record
await saveChangeRecord(specId, changeRecord);
return resolvedSpec;
}
catch (error) {
throw new Error(`Failed to resolve conflicts: ${error instanceof Error ? error.message : String(error)}`);
}
}
//# sourceMappingURL=specification-manager.js.map