Thought Space - MCP Advanced Branch-Thinking Tool

  • dist
import { BiasDetector } from './biasDetector.js'; export class AnalyticsEngine { constructor() { this.SIMILARITY_THRESHOLD = 0.7; this.WINDOW_SIZE = 5; this.biasDetector = new BiasDetector(); } async generateBranchAnalytics(branch) { const nodes = branch.nodes; if (!nodes?.length) { throw new Error('Branch contains no nodes'); } return { branchMetrics: await this.calculateBranchMetrics(branch, nodes), temporalMetrics: this.calculateTemporalMetrics(nodes), semanticMetrics: await this.calculateSemanticMetrics(nodes) }; } async calculateBranchMetrics(branch, nodes) { const totalThoughts = nodes.length; const avgConfidence = this.calculateAverageConfidence(nodes); return { totalThoughts, avgConfidence, insightDensity: this.calculateInsightDensity(branch, totalThoughts), crossRefDensity: this.calculateCrossRefDensity(branch, totalThoughts), temporalStability: this.calculateTemporalStability(nodes), biasScore: await this.calculateBiasScore(nodes) }; } calculateTemporalMetrics(nodes) { return { thoughtsPerDay: this.calculateThoughtsPerDay(nodes), confidenceTrend: this.calculateConfidenceTrend(nodes), stateTransitions: this.countStateTransitions(nodes) }; } async calculateSemanticMetrics(nodes) { const clusters = await this.identifySemanticClusters(nodes); return { clusterDensity: clusters.length / nodes.length, semanticCoverage: this.calculateSemanticCoverage(clusters), conceptDrift: this.calculateConceptDrift(nodes) }; } async identifySemanticClusters(nodes) { const clusters = []; const visited = new Set(); for (const node of nodes) { if (visited.has(node.id)) continue; const cluster = [node]; visited.add(node.id); for (const link of node.semanticLinks) { if (link.similarity > this.SIMILARITY_THRESHOLD) { const linkedNode = nodes.find(n => n.id === link.targetId); if (linkedNode && !visited.has(linkedNode.id)) { cluster.push(linkedNode); visited.add(linkedNode.id); } } } if (cluster.length > 1) { clusters.push(cluster); } } return clusters; } calculateConceptDrift(nodes) { const windowSize = Math.min(this.WINDOW_SIZE, Math.floor(nodes.length / 2)); if (windowSize === 0) return 0; const early = nodes.slice(0, windowSize); const recent = nodes.slice(-windowSize); let driftScore = 0; let comparisons = 0; early.forEach(earlyNode => { recent.forEach(recentNode => { const similarity = earlyNode.semanticLinks.find(link => link.targetId === recentNode.id)?.similarity ?? 0; driftScore += 1 - similarity; comparisons++; }); }); return comparisons > 0 ? driftScore / comparisons : 0; } calculateTemporalStability(nodes) { const stateChanges = nodes.reduce((changes, node) => { const transitions = node.stateTransitions; return changes + (transitions?.length ?? 0); }, 0); return Math.max(0, 1 - (stateChanges / (nodes.length * 2))); } calculateAverageConfidence(nodes) { return nodes.reduce((sum, node) => sum + node.confidence, 0) / nodes.length; } calculateInsightDensity(branch, totalThoughts) { return totalThoughts > 0 ? branch.insights.length / totalThoughts : 0; } calculateCrossRefDensity(branch, totalThoughts) { return totalThoughts > 0 ? branch.crossRefs.length / totalThoughts : 0; } async calculateBiasScore(nodes) { const biasAnalyses = await this.biasDetector.detectBias(nodes); if (biasAnalyses.length === 0) return 1; return 1 - (biasAnalyses.reduce((score, analysis) => score + analysis.severity, 0) / biasAnalyses.length); } calculateConfidenceTrend(nodes) { const trends = []; if (nodes.length < this.WINDOW_SIZE) return trends; for (let i = this.WINDOW_SIZE; i <= nodes.length; i++) { const window = nodes.slice(i - this.WINDOW_SIZE, i); const avg = this.calculateAverageConfidence(window); trends.push(avg); } return trends; } calculateThoughtsPerDay(nodes) { if (nodes.length < 2) return nodes.length; const firstDate = new Date(nodes[0].createdAt); const lastDate = new Date(nodes[nodes.length - 1].createdAt); const daysDiff = (lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24); return daysDiff > 0 ? nodes.length / daysDiff : nodes.length; } countStateTransitions(nodes) { return nodes.reduce((count, node) => { const transitions = node.stateTransitions; return count + (transitions?.length ?? 0); }, 0); } calculateSemanticCoverage(clusters) { const totalNodes = clusters.reduce((sum, cluster) => sum + cluster.length, 0); if (totalNodes === 0) return 0; const uniqueNodes = new Set(clusters.flatMap(cluster => cluster.map(node => node.id))).size; return uniqueNodes / totalNodes; } }