Skip to main content
Glama
ReviewManager.ts7.97 kB
import { ReviewableItem, ReviewAction, ReviewStatus, ReviewSource, ItemType, Priority, ChurnConfig, } from "../types/churn.js"; import { TrackerManager } from "./TrackerManager.js"; import { FormattingUtils } from "../utils/FormattingUtils.js"; /** * ReviewManager - Core class for managing reviewable items and their workflows * * Provides ADHD-friendly interface for reviewing, editing, and managing items * that need human attention before final placement in trackers. */ export class ReviewManager { private reviewItems: Map<string, ReviewableItem> = new Map(); constructor( private config: ChurnConfig, private trackerManager: TrackerManager, ) {} /** * Flag an item for review with given confidence and metadata */ flagItemForReview( content: string, confidence: number, tracker: string, section: string, source: ReviewSource, metadata: { keywords?: string[]; urgency?: Priority; type?: ItemType; editableFields?: string[]; } = {}, ): ReviewableItem { const id = this.generateItemId(); const reviewItem: ReviewableItem = { id, content, confidence, currentSection: section, currentTracker: tracker, timestamp: new Date(), source, reviewStatus: confidence < 0.5 ? "pending" : "flagged", metadata: { keywords: metadata.keywords || [], urgency: metadata.urgency || "medium", type: metadata.type || "review", editableFields: metadata.editableFields || [ "tracker", "priority", "tags", "type", ], }, }; this.reviewItems.set(id, reviewItem); return reviewItem; } /** * Get all items that need review, optionally filtered by tracker */ getItemsNeedingReview(tracker?: string): ReviewableItem[] { const items = Array.from(this.reviewItems.values()); if (tracker) { return items.filter( (item) => item.currentTracker === tracker && (item.reviewStatus === "pending" || item.reviewStatus === "flagged"), ); } return items.filter( (item) => item.reviewStatus === "pending" || item.reviewStatus === "flagged", ); } /** * Get review status counts for dashboard indicators */ getReviewStatus(): { pending: number; flagged: number; confirmed: number; total: number; } { const items = Array.from(this.reviewItems.values()); return { pending: items.filter((item) => item.reviewStatus === "pending").length, flagged: items.filter((item) => item.reviewStatus === "flagged").length, confirmed: items.filter((item) => item.reviewStatus === "confirmed") .length, total: items.length, }; } /** * Process a review action on an item */ async processReviewAction( itemId: string, action: ReviewAction, newValues?: { tracker?: string; priority?: Priority; tags?: string[]; type?: ItemType; content?: string; }, ): Promise<boolean> { const item = this.reviewItems.get(itemId); if (!item) { throw new Error(`Review item with ID ${itemId} not found`); } switch (action) { case "accept": return this.acceptItem(item); case "edit-priority": if (newValues?.priority) { item.metadata.urgency = newValues.priority; return this.updateItemContent(item); } break; case "edit-tags": if (newValues?.tags) { item.metadata.keywords = newValues.tags; return this.updateItemContent(item); } break; case "edit-type": if (newValues?.type) { item.metadata.type = newValues.type; return this.updateItemContent(item); } break; case "move": if (newValues?.tracker) { item.currentTracker = newValues.tracker; return this.updateItemContent(item); } break; case "reject": return this.rejectItem(itemId); default: throw new Error(`Unknown review action: ${action}`); } return false; } /** * Accept an item and move it to its final tracker location */ private async acceptItem(item: ReviewableItem): Promise<boolean> { try { // Format the content for final placement const formattedContent = FormattingUtils.formatEntry( item.metadata.type, item.content, { priority: item.metadata.urgency, tag: item.metadata.keywords[0], // Use first keyword as tag if available }, ); // Write to the target tracker const success = await this.trackerManager.appendToTracker( item.currentTracker, formattedContent, ); if (success) { item.reviewStatus = "confirmed"; this.reviewItems.delete(item.id); // Remove from review queue return true; } return false; } catch (error) { console.error("Error accepting review item:", error); return false; } } /** * Update item content based on current metadata */ private updateItemContent(item: ReviewableItem): boolean { try { // Regenerate formatted content with updated metadata const formattedContent = FormattingUtils.formatEntry( item.metadata.type, item.content, { priority: item.metadata.urgency, tag: item.metadata.keywords[0], // Use first keyword as tag if available }, ); item.content = formattedContent; return true; } catch (error) { console.error("Error updating item content:", error); return false; } } /** * Reject an item and remove it from review queue */ private rejectItem(itemId: string): boolean { return this.reviewItems.delete(itemId); } /** * Get a specific reviewable item by ID */ getReviewItem(itemId: string): ReviewableItem | undefined { return this.reviewItems.get(itemId); } /** * Update the review status of an item */ updateReviewStatus(itemId: string, status: ReviewStatus): boolean { const item = this.reviewItems.get(itemId); if (item) { item.reviewStatus = status; return true; } return false; } /** * Batch process multiple review items */ async batchProcessReview( actions: Array<{ itemId: string; action: ReviewAction; newValues?: any }>, ): Promise<{ success: number; failed: number; results: Array<{ itemId: string; success: boolean; error?: string }>; }> { const results: Array<{ itemId: string; success: boolean; error?: string }> = []; let successCount = 0; let failedCount = 0; for (const { itemId, action, newValues } of actions) { try { const success = await this.processReviewAction( itemId, action, newValues, ); results.push({ itemId, success }); if (success) { successCount++; } else { failedCount++; } } catch (error) { results.push({ itemId, success: false, error: error instanceof Error ? error.message : "Unknown error", }); failedCount++; } } return { success: successCount, failed: failedCount, results, }; } /** * Clear all confirmed items from the review queue */ clearConfirmedItems(): number { const confirmedItems = Array.from(this.reviewItems.entries()).filter( ([_, item]) => item.reviewStatus === "confirmed", ); confirmedItems.forEach(([id, _]) => this.reviewItems.delete(id)); return confirmedItems.length; } /** * Generate a unique ID for review items */ private generateItemId(): string { return `review_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jgsteeler/churnflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server