UNPKG

coaia-spiral

Version:

Creative-Oriented AI Assistant Memory - MCP server with structural tension charts and advancing spiral patterns, based on principles by Robert Fritz

1,059 lines (1,002 loc) • 86.4 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { promises as fs } from 'fs'; import { existsSync } from 'fs'; import path from 'path'; import { LLM_GUIDANCE } from "./generated-llm-guidance.js"; import { fileURLToPath } from 'url'; import minimist from 'minimist'; import { isAbsolute } from 'path'; // Parse args and handle paths safely const argv = minimist(process.argv.slice(2)); // Handle help command if (argv.help || argv.h) { console.log(` 🧠 COAIA Spiral - Creative-Oriented AI Assistant Memory System v2.4.0 Based on Robert Fritz's Structural Tension methodology Enhanced with .coaia project organization DESCRIPTION: MCP server that extends knowledge graphs with structural tension charts for creative-oriented spiral memory management. Supports advancing patterns, telescoping charts, and natural language interaction for AI assistants. Project Organization: • .coaia directories for project-specific structural tension charts • Organized chart collections (active, completed, archived) • Chart templates and patterns for common goals • Project-local vs global chart management USAGE: coaia-spiral [OPTIONS] npx coaia-spiral [OPTIONS] OPTIONS: --memory-path PATH Custom path for memory storage (default: ./memory.jsonl) --help, -h Show this help message CORE FEATURES: šŸ“Š Structural Tension Charts • Create charts with desired outcomes, current reality, and action steps • Automatic due date distribution for strategic timing • Progress tracking and completion monitoring šŸ”­ Telescoping Support • Break down action steps into detailed sub-charts • Proper due date inheritance from parent steps • Navigate between overview and details seamlessly šŸ“ˆ Advancing Patterns • Completed actions flow into current reality automatically • Success builds momentum for continued advancement • Prevents oscillating patterns through structural awareness MCP TOOLS AVAILABLE: Chart Management (Common Workflow): • list_active_charts - START HERE: See all charts and their progress • add_action_step - Add strategic actions to existing charts • telescope_action_step - Break down action steps into detailed sub-charts • update_action_progress - Track progress without completing actions • mark_action_complete - Complete actions & update reality • update_current_reality - Add observations directly to current reality • create_structural_tension_chart - Create new chart with outcome & reality Project Organization (.coaia directories): • initialize_coaia_project - Set up .coaia directory for organized charts • list_coaia_projects - Show project status and chart organization • move_chart_to_completed - Archive completed charts as learning examples Chart Analysis (Advanced): • get_chart_progress - Detailed progress (redundant after list_active_charts) • open_nodes - Inspect specific chart components by exact name • read_graph - Dump all data (rarely needed) Knowledge Graph (Traditional): • create_entities - Add entities (people, concepts, events) • create_relations - Connect entities with relationships • add_observations - Record information about entities • search_nodes - Search across all stored information • read_graph - Export complete graph structure EXAMPLE USAGE: # Start with custom memory path coaia-spiral --memory-path /path/to/my-charts.jsonl # Use in Claude Desktop (add to claude_desktop_config.json): { "mcpServers": { "my-spiral-project": { "command": "npx", "args": ["-y", "coaia-spiral", "--memory-path", "./charts.jsonl"] } } } # Initialize project-local chart organization mkdir my-project && cd my-project git init # or npm init, etc. (any project marker) # Then use initialize_coaia_project tool in your AI assistant PROJECT ORGANIZATION PATTERNS: Project Structure: my-project/ ā”œā”€ā”€ .coaia/ │ ā”œā”€ā”€ active-charts.jsonl # Current structural tension charts │ ā”œā”€ā”€ completed-charts.jsonl # Archived successful charts │ └── templates/ │ └── common-goals.jsonl # Reusable patterns └── src/ Benefits: • Separate active from completed charts • Learn from successful patterns • Project-specific goal organization • Templates for common desired outcomes NATURAL LANGUAGE PATTERNS: Creating Charts: "I want to create a mobile app in 3 months" "My desired outcome is to establish a morning routine" Progress Tracking: "I completed the research phase yesterday" "Show me progress on my Python learning goal" Telescoping: "Break down the Django tutorial step further" "I need more detail on the deployment action" CREATIVE ORIENTATION PRINCIPLES: āœ… Focus on Creation (not problem-solving): • "I want to create..." vs "I need to fix..." • "My desired outcome..." vs "The problem is..." āœ… Structural Tension Awareness: • Always pair desired outcomes with current reality • Honest assessment creates productive tension • Action steps are strategic secondary action we choose todo to achive the primary goal āœ… Advancing Patterns: • Success builds on success • Completed actions become part of current reality • Momentum creates natural progression toward goals PHILOSOPHY: COAIA Memory recognizes that structure determines behavior. By organizing memory around structural tension rather than problem-solving patterns, it naturally forms a structure that advances and helps build, not just the life you want, but the technologies to supports it's manifestation (hopefully!). CREDITS: • Author: J.Guillaume D.-Isabelle <jgi@jgwill.com> • Methodology: Robert Fritz - https://robertfritz.com • Foundation: Shane Holloman (original mcp-knowledge-graph) • License: MIT For more information, see: CLAUDE.md in the package directory `); process.exit(0); } let memoryPath = argv['memory-path']; // If a custom path is provided, ensure it's absolute if (memoryPath && !isAbsolute(memoryPath)) { memoryPath = path.resolve(process.cwd(), memoryPath); } // Define the path to the JSONL file const __dirname = path.dirname(fileURLToPath(import.meta.url)); // COAIA Project Detection - look for common project markers function findProjectRoot(startDir = process.cwd()) { const projectMarkers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod']; let currentDir = startDir; const maxDepth = 5; for (let i = 0; i < maxDepth; i++) { // Check for project markers for (const marker of projectMarkers) { if (existsSync(path.join(currentDir, marker))) { return currentDir; } } // Move up one directory const parentDir = path.dirname(currentDir); if (parentDir === currentDir) { // Reached root directory break; } currentDir = parentDir; } return null; } // COAIA-specific storage management for structural tension charts with flexible contexts // Priority: 1) Configured --memory-path, 2) Project .coaia directory, 3) Default function getCoaiaStoragePath(context) { const filename = context ? `charts-${context}.jsonl` : 'charts.jsonl'; // FIRST PRIORITY: Use configured --memory-path (AIM philosophy) if (memoryPath) { // If memoryPath points to a directory, use it as base for chart files if (memoryPath.endsWith('/') || !memoryPath.endsWith('.jsonl')) { const dir = memoryPath.endsWith('/') ? memoryPath : memoryPath + '/'; return path.join(dir, filename); } // If memoryPath is a specific file, use it for default context or create contextual version if (context) { const dir = path.dirname(memoryPath); const name = path.basename(memoryPath, '.jsonl'); return path.join(dir, `${name}-${context}.jsonl`); } return memoryPath; } // SECOND PRIORITY: Check for project .coaia directory const projectRoot = findProjectRoot(); if (projectRoot) { const coaiaDir = path.join(projectRoot, '.coaia'); if (existsSync(coaiaDir)) { return path.join(coaiaDir, filename); } } // THIRD PRIORITY: Default fallback return path.join(__dirname, 'memory.jsonl'); } // Use the custom path or default to the installation directory const MEMORY_FILE_PATH = getCoaiaStoragePath(); // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph class KnowledgeGraphManager { async loadGraph() { try { const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); return lines.reduce((graph, line) => { const item = JSON.parse(line); if (item.type === "entity") graph.entities.push(item); if (item.type === "relation") graph.relations.push(item); return graph; }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && error.code === "ENOENT") { return { entities: [], relations: [] }; } throw error; } } // Helper function to extract current reality from user context // Maintains structural tension by requiring explicit assessment extractCurrentRealityFromContext(userInput, actionStepTitle) { // Common patterns that indicate current reality assessment const realityPatterns = [ /(?:currently|right now|at present|today)\s+(.{10,})/i, /(?:i am|we are|the situation is)\s+(.{10,})/i, /(?:i have|we have|there is|there are)\s+(.{10,})/i, /(?:my current|our current|the current)\s+(.{10,})/i ]; for (const pattern of realityPatterns) { const match = userInput.match(pattern); if (match && match[1]) { return match[1].trim(); } } // If no explicit current reality found, require assessment return null; } // Helper method to determine if we should use COAIA context-aware persistence shouldUseCoaiaPersistence() { // If --memory-path is configured, always use COAIA persistence for context support if (memoryPath) { return true; } // Otherwise, check for project .coaia directory const projectRoot = findProjectRoot(); return projectRoot !== null && existsSync(path.join(projectRoot, '.coaia')); } // Helper method to save graph using appropriate persistence method async saveGraphAppropriate(graph, context) { if (this.shouldUseCoaiaPersistence()) { await this.saveCoaiaGraph(graph, context); } else { await this.saveGraph(graph); } } // COAIA context-aware methods (similar to AIM approach but for structural tension charts) async loadCoaiaGraph(context) { const filePath = getCoaiaStoragePath(context); try { const data = await fs.readFile(filePath, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== "" && !line.startsWith("#")); return lines.reduce((graph, line) => { const item = JSON.parse(line); if (item.type === "entity") graph.entities.push(item); if (item.type === "relation") graph.relations.push(item); return graph; }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && error.code === "ENOENT") { return { entities: [], relations: [] }; } throw error; } } async saveCoaiaGraph(graph, context) { const filePath = getCoaiaStoragePath(context); const lines = [ ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; // Ensure directory exists await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, lines.join("\n")); } // Legacy saveGraph method for backward compatibility async saveGraph(graph) { const lines = [ ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n")); } async createEntities(entities) { const graph = this.shouldUseCoaiaPersistence() ? await this.loadCoaiaGraph() : await this.loadGraph(); const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)); graph.entities.push(...newEntities); await this.saveGraphAppropriate(graph); return newEntities; } async createRelations(relations) { const graph = this.shouldUseCoaiaPersistence() ? await this.loadCoaiaGraph() : await this.loadGraph(); const newRelations = relations.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from && existingRelation.to === r.to && existingRelation.relationType === r.relationType)); graph.relations.push(...newRelations); await this.saveGraphAppropriate(graph); return newRelations; } async addObservations(observations) { const graph = await this.loadGraph(); const results = observations.map(o => { const entity = graph.entities.find(e => e.name === o.entityName); if (!entity) { throw new Error(`Entity with name ${o.entityName} not found`); } const newObservations = o.contents.filter(content => !entity.observations.includes(content)); entity.observations.push(...newObservations); return { entityName: o.entityName, addedObservations: newObservations }; }); await this.saveGraph(graph); return results; } async deleteEntities(entityNames) { const graph = await this.loadGraph(); graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); await this.saveGraph(graph); } async deleteObservations(deletions) { const graph = await this.loadGraph(); deletions.forEach(d => { const entity = graph.entities.find(e => e.name === d.entityName); if (entity) { entity.observations = entity.observations.filter(o => !d.observations.includes(o)); } }); await this.saveGraph(graph); } async deleteRelations(relations) { const graph = await this.loadGraph(); graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from && r.to === delRelation.to && r.relationType === delRelation.relationType)); await this.saveGraph(graph); } async readGraph() { return this.loadGraph(); } // Very basic search function async searchNodes(query) { const graph = await this.loadGraph(); // Filter entities const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) || e.entityType.toLowerCase().includes(query.toLowerCase()) || e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); const filteredGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async openNodes(names) { const graph = await this.loadGraph(); // Filter entities const filteredEntities = graph.entities.filter(e => names.includes(e.name)); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); const filteredGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } // COAIA-specific methods for structural tension charts and creative processes async createStructuralTensionChart(desiredOutcome, currentReality, dueDate, actionSteps) { // Educational validation for creative orientation const problemSolvingWords = ['fix', 'solve', 'eliminate', 'prevent', 'stop', 'avoid', 'reduce', 'remove']; const detectedProblemWords = problemSolvingWords.filter(word => desiredOutcome.toLowerCase().includes(word)); if (detectedProblemWords.length > 0) { throw new Error(`🌊 CREATIVE ORIENTATION REQUIRED Desired Outcome: "${desiredOutcome}" āŒ **Problem**: Contains problem-solving language: "${detectedProblemWords.join(', ')}" šŸ“š **Principle**: Structural Tension Charts use creative orientation - focus on what you want to CREATE, not what you want to eliminate. šŸŽÆ **Reframe Your Outcome**: Instead of elimination → Creation focus āœ… **Examples**: - Instead of: "Fix communication problems" - Use: "Establish clear, effective communication practices" - Instead of: "Reduce website loading time" - Use: "Achieve fast, responsive website performance" **Why This Matters**: Problem-solving creates oscillating patterns. Creative orientation creates advancing patterns toward desired outcomes. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } // Educational validation for current reality const readinessWords = ['ready to', 'prepared to', 'all set', 'ready for', 'set to']; const detectedReadinessWords = readinessWords.filter(phrase => currentReality.toLowerCase().includes(phrase)); if (detectedReadinessWords.length > 0) { throw new Error(`🌊 DELAYED RESOLUTION PRINCIPLE VIOLATION Current Reality: "${currentReality}" āŒ **Problem**: Contains readiness assumptions: "${detectedReadinessWords.join(', ')}" šŸ“š **Principle**: "Tolerate discrepancy, tension, and delayed resolution" - Robert Fritz šŸŽÆ **What's Needed**: Factual assessment of your actual current state (not readiness or preparation). āœ… **Examples**: - Instead of: "Ready to learn Python" - Use: "Never programmed before, interested in web development" - Instead of: "Prepared to start the project" - Use: "Have project requirements, no code written yet" **Why This Matters**: Readiness assumptions prematurely resolve the structural tension needed for creative advancement. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } const chartId = `chart_${Date.now()}`; const timestamp = new Date().toISOString(); // Create chart, desired outcome, and current reality entities const entities = [ { name: `${chartId}_chart`, entityType: 'structural_tension_chart', observations: [`Chart created on ${timestamp}`], metadata: { chartId, dueDate, level: 0, createdAt: timestamp, updatedAt: timestamp } }, { name: `${chartId}_desired_outcome`, entityType: 'desired_outcome', observations: [desiredOutcome], metadata: { chartId, dueDate, createdAt: timestamp, updatedAt: timestamp } }, { name: `${chartId}_current_reality`, entityType: 'current_reality', observations: [currentReality], metadata: { chartId, createdAt: timestamp, updatedAt: timestamp } } ]; // Add action steps if provided if (actionSteps && actionSteps.length > 0) { const stepDueDates = this.distributeActionStepDates(new Date(), new Date(dueDate), actionSteps.length); actionSteps.forEach((step, index) => { entities.push({ name: `${chartId}_action_${index + 1}`, entityType: 'action_step', observations: [step], metadata: { chartId, dueDate: stepDueDates[index].toISOString(), completionStatus: false, createdAt: timestamp, updatedAt: timestamp } }); }); } // Create relations const relations = [ { from: `${chartId}_chart`, to: `${chartId}_desired_outcome`, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: `${chartId}_chart`, to: `${chartId}_current_reality`, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: `${chartId}_current_reality`, to: `${chartId}_desired_outcome`, relationType: 'creates_tension_with', metadata: { createdAt: timestamp } } ]; // Add action step relations if (actionSteps && actionSteps.length > 0) { actionSteps.forEach((_, index) => { const actionName = `${chartId}_action_${index + 1}`; relations.push({ from: `${chartId}_chart`, to: actionName, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: actionName, to: `${chartId}_desired_outcome`, relationType: 'advances_toward', metadata: { createdAt: timestamp } }); }); } // Save to graph await this.createEntities(entities); await this.createRelations(relations); return { chartId, entities, relations }; } async telescopeActionStep(actionStepName, newCurrentReality, initialActionSteps) { const graph = this.shouldUseCoaiaPersistence() ? await this.loadCoaiaGraph() : await this.loadGraph(); const actionStep = graph.entities.find(e => e.name === actionStepName && e.entityType === 'action_step'); if (!actionStep || !actionStep.metadata?.chartId) { throw new Error(`Action step ${actionStepName} not found or not properly configured`); } const parentChartId = actionStep.metadata.chartId; const inheritedDueDate = actionStep.metadata.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); const desiredOutcome = actionStep.observations[0]; // Use the action step description as the new desired outcome const result = await this.createStructuralTensionChart(desiredOutcome, newCurrentReality, inheritedDueDate, initialActionSteps); // Update the new chart's metadata to reflect telescoping relationship const newChart = this.shouldUseCoaiaPersistence() ? await this.loadCoaiaGraph() : await this.loadGraph(); const chartEntity = newChart.entities.find(e => e.name === `${result.chartId}_chart`); if (chartEntity && chartEntity.metadata) { chartEntity.metadata.parentChart = parentChartId; chartEntity.metadata.parentActionStep = actionStepName; chartEntity.metadata.level = (actionStep.metadata.level || 0) + 1; chartEntity.metadata.updatedAt = new Date().toISOString(); } await this.saveGraphAppropriate(newChart); return { chartId: result.chartId, parentChart: parentChartId }; } async markActionStepComplete(actionStepName) { const graph = this.shouldUseCoaiaPersistence() ? await this.loadCoaiaGraph() : await this.loadGraph(); // An "action step" can be a 'desired_outcome' of a sub-chart, or a simple 'action_step' entity. const actionStep = graph.entities.find(e => e.name === actionStepName && (e.entityType === 'action_step' || e.entityType === 'desired_outcome')); if (!actionStep) { throw new Error(`Action step ${actionStepName} not found`); } const chartId = actionStep.metadata?.chartId; if (!chartId) { throw new Error(`Chart ID not found for action step ${actionStepName}`); } // Mark the action step itself as complete if (actionStep.metadata) { actionStep.metadata.completionStatus = true; actionStep.metadata.updatedAt = new Date().toISOString(); } // Also mark the parent chart entity as complete const chartEntity = graph.entities.find(e => e.name === `${chartId}_chart`); if (chartEntity && chartEntity.metadata) { chartEntity.metadata.completionStatus = true; chartEntity.metadata.updatedAt = new Date().toISOString(); } // Structural tension principle: completed action steps flow into the CURRENT REALITY // of the PARENT chart, advancing the overall structure. const parentChartId = chartEntity?.metadata?.parentChart; if (parentChartId) { const parentCurrentReality = graph.entities.find(e => e.name === `${parentChartId}_current_reality` && e.entityType === 'current_reality'); if (parentCurrentReality) { const completionMessage = `Completed: ${actionStep.observations[0]}`; if (!parentCurrentReality.observations.includes(completionMessage)) { parentCurrentReality.observations.push(completionMessage); if (parentCurrentReality.metadata) { parentCurrentReality.metadata.updatedAt = new Date().toISOString(); } } } } await this.saveGraphAppropriate(graph); } async getChartProgress(chartId) { const graph = await this.loadGraph(); const actionSteps = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId); const completedActions = actionSteps.filter(e => e.metadata?.completionStatus === true).length; const totalActions = actionSteps.length; const progress = totalActions > 0 ? completedActions / totalActions : 0; // Find next incomplete action step with earliest due date const incompleteActions = actionSteps .filter(e => e.metadata?.completionStatus !== true) .sort((a, b) => { const dateA = new Date(a.metadata?.dueDate || '').getTime(); const dateB = new Date(b.metadata?.dueDate || '').getTime(); return dateA - dateB; }); const chart = graph.entities.find(e => e.name === `${chartId}_chart`); return { chartId, progress, completedActions, totalActions, nextAction: incompleteActions[0]?.name, dueDate: chart?.metadata?.dueDate }; } distributeActionStepDates(startDate, endDate, stepCount) { const totalTime = endDate.getTime() - startDate.getTime(); const stepInterval = totalTime / (stepCount + 1); // +1 to leave space before final due date const dates = []; for (let i = 1; i <= stepCount; i++) { dates.push(new Date(startDate.getTime() + (stepInterval * i))); } return dates; } async listActiveCharts() { const graph = await this.loadGraph(); const charts = graph.entities.filter(e => e.entityType === 'structural_tension_chart'); const chartSummaries = await Promise.all(charts.map(async (chart) => { const chartId = chart.metadata?.chartId || chart.name.replace('_chart', ''); const progress = await this.getChartProgress(chartId); // Get desired outcome const desiredOutcome = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome'); return { chartId, desiredOutcome: desiredOutcome?.observations[0] || 'Unknown outcome', dueDate: chart.metadata?.dueDate, progress: progress.progress, completedActions: progress.completedActions, totalActions: progress.totalActions, level: chart.metadata?.level || 0, parentChart: chart.metadata?.parentChart }; })); return chartSummaries.sort((a, b) => { // Sort by level first (master charts first), then by due date if (a.level !== b.level) return a.level - b.level; const dateA = new Date(a.dueDate || '').getTime(); const dateB = new Date(b.dueDate || '').getTime(); return dateA - dateB; }); } async updateActionProgress(actionStepName, progressObservation, updateCurrentReality) { const graph = await this.loadGraph(); const actionStep = graph.entities.find(e => e.name === actionStepName && (e.entityType === 'action_step' || e.entityType === 'desired_outcome')); if (!actionStep) { throw new Error(`Action step ${actionStepName} not found`); } // Add progress observation to action step actionStep.observations.push(progressObservation); if (actionStep.metadata) { actionStep.metadata.updatedAt = new Date().toISOString(); } // Optionally update current reality with progress if (updateCurrentReality && actionStep.metadata?.chartId) { const chartEntity = graph.entities.find(e => e.name === `${actionStep.metadata.chartId}_chart`); const parentChartId = chartEntity?.metadata?.parentChart; const targetChartId = parentChartId || actionStep.metadata.chartId; const currentReality = graph.entities.find(e => e.name === `${targetChartId}_current_reality` && e.entityType === 'current_reality'); if (currentReality) { // Progress observations flow into current reality, changing the structural dynamic const progressMessage = `Progress on ${actionStep.observations[0]}: ${progressObservation}`; if (!currentReality.observations.includes(progressMessage)) { currentReality.observations.push(progressMessage); if (currentReality.metadata) { currentReality.metadata.updatedAt = new Date().toISOString(); } } } } await this.saveGraph(graph); } async updateCurrentReality(chartId, newObservations) { const graph = await this.loadGraph(); const currentReality = graph.entities.find(e => e.name === `${chartId}_current_reality` && e.entityType === 'current_reality'); if (!currentReality) { throw new Error(`Chart ${chartId} not found or missing current reality`); } // Add new observations to current reality const uniqueObservations = newObservations.filter(obs => !currentReality.observations.includes(obs)); currentReality.observations.push(...uniqueObservations); if (currentReality.metadata) { currentReality.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(graph); } async updateDesiredOutcome(chartId, newDesiredOutcome) { const graph = await this.loadGraph(); const desiredOutcomeEntity = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome'); if (!desiredOutcomeEntity) { throw new Error(`Chart ${chartId} desired outcome not found`); } // Replace the first observation (which is the desired outcome text) desiredOutcomeEntity.observations[0] = newDesiredOutcome; if (desiredOutcomeEntity.metadata) { desiredOutcomeEntity.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(graph); } async updateActionStepTitle(actionStepName, newTitle) { const graph = await this.loadGraph(); const actionStepEntity = graph.entities.find(e => e.name === actionStepName); if (!actionStepEntity) { throw new Error(`Action step ${actionStepName} not found`); } // Replace the first observation (which is the action step title) actionStepEntity.observations[0] = newTitle; if (actionStepEntity.metadata) { actionStepEntity.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(graph); } async addActionStep(parentChartId, actionStepTitle, dueDate, currentReality) { const graph = await this.loadGraph(); const parentChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === parentChartId); if (!parentChart) { throw new Error(`Parent chart ${parentChartId} not found`); } // Get parent chart's due date for auto-distribution const parentDueDate = parentChart.metadata?.dueDate; if (!parentDueDate) { throw new Error(`Parent chart ${parentChartId} has no due date`); } // Calculate due date for action step if not provided let actionStepDueDate = dueDate; if (!actionStepDueDate) { // Distribute between now and parent due date (simple midpoint for now) const now = new Date(); const parentEnd = new Date(parentDueDate); const midpoint = new Date(now.getTime() + (parentEnd.getTime() - now.getTime()) / 2); actionStepDueDate = midpoint.toISOString(); } // Require current reality assessment - no defaults that prematurely resolve tension if (!currentReality) { throw new Error(`🌊 DELAYED RESOLUTION PRINCIPLE VIOLATION Action step: "${actionStepTitle}" āŒ **Problem**: Current reality assessment missing šŸ“š **Principle**: "Tolerate discrepancy, tension, and delayed resolution" - Robert Fritz šŸŽÆ **What's Needed**: Honest assessment of your actual current state relative to this action step. āœ… **Examples**: - "Never used Django, completed Python basics" - "Built one API, struggling with authentication" - "Read 3 chapters, concepts still unclear" āŒ **Avoid**: "Ready to begin", "Prepared to start", "All set to..." **Why This Matters**: Premature resolution destroys the structural tension that generates creative advancement. The system NEEDS honest current reality to create productive tension. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } const actionCurrentReality = currentReality; // Create telescoped structural tension chart const telescopedChart = await this.createStructuralTensionChart(actionStepTitle, actionCurrentReality, actionStepDueDate); // Update the telescoped chart's metadata to show parent relationship const updatedGraph = await this.loadGraph(); const telescopedChartEntity = updatedGraph.entities.find(e => e.name === `${telescopedChart.chartId}_chart`); if (telescopedChartEntity && telescopedChartEntity.metadata) { telescopedChartEntity.metadata.parentChart = parentChartId; telescopedChartEntity.metadata.level = (parentChart.metadata?.level || 0) + 1; telescopedChartEntity.metadata.updatedAt = new Date().toISOString(); } // Create relationship: telescoped chart advances toward parent's desired outcome const parentDesiredOutcome = updatedGraph.entities.find(e => e.name === `${parentChartId}_desired_outcome` && e.entityType === 'desired_outcome'); if (parentDesiredOutcome) { const timestamp = new Date().toISOString(); await this.createRelations([{ from: `${telescopedChart.chartId}_desired_outcome`, to: parentDesiredOutcome.name, relationType: 'advances_toward', metadata: { createdAt: timestamp } }]); } await this.saveGraph(updatedGraph); return { chartId: telescopedChart.chartId, actionStepName: `${telescopedChart.chartId}_desired_outcome` }; } // Enhanced method for LLMs to telescope with intelligent current reality extraction async telescopeActionStepWithContext(parentChartId, actionStepTitle, userContext, currentReality, dueDate) { // If current reality not provided, try to extract from context let finalCurrentReality = currentReality; if (!finalCurrentReality) { finalCurrentReality = this.extractCurrentRealityFromContext(userContext, actionStepTitle) ?? undefined; } // If still no current reality, provide guidance while maintaining tension if (!finalCurrentReality) { throw new Error(`Current reality assessment needed for "${actionStepTitle}". ` + `Please assess your actual current state relative to this action step. ` + `Example: "I have never used Django before" or "I completed the basics but haven't built a real project" ` + `rather than assuming readiness. Structural tension requires honest current reality assessment.`); } // Proceed with telescoping using the assessed current reality return this.addActionStep(parentChartId, actionStepTitle, finalCurrentReality, dueDate); } async removeActionStep(parentChartId, actionStepName) { const graph = await this.loadGraph(); // Find the action step (which is actually a telescoped chart's desired outcome) const actionStepEntity = graph.entities.find(e => e.name === actionStepName); if (!actionStepEntity || !actionStepEntity.metadata?.chartId) { throw new Error(`Action step ${actionStepName} not found`); } const telescopedChartId = actionStepEntity.metadata.chartId; // Verify it belongs to the parent chart const telescopedChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === telescopedChartId && e.metadata?.parentChart === parentChartId); if (!telescopedChart) { throw new Error(`Action step ${actionStepName} does not belong to chart ${parentChartId}`); } // Remove all entities belonging to the telescoped chart const entitiesToRemove = graph.entities .filter(e => e.metadata?.chartId === telescopedChartId) .map(e => e.name); await this.deleteEntities(entitiesToRemove); } // COAIA Project Organization - Enhanced chart management for .coaia directories async initializeCoaiaProject() { // Determine the target directory based on configuration priority (same as getCoaiaStoragePath) let coaiaDir; if (memoryPath) { // If --memory-path is configured, use it as the base directory coaiaDir = memoryPath.endsWith('/') ? memoryPath : (memoryPath.endsWith('.jsonl') ? path.dirname(memoryPath) : memoryPath); } else { // Fallback to project root detection const projectRoot = findProjectRoot(); if (!projectRoot) { throw new Error('No project detected and no --memory-path configured. Either run from within a project directory (must contain .git, package.json, etc.) or provide --memory-path'); } coaiaDir = path.join(projectRoot, '.coaia'); } // Create directory structure await fs.mkdir(coaiaDir, { recursive: true }); await fs.mkdir(path.join(coaiaDir, 'templates'), { recursive: true }); // Initialize default chart file (master database equivalent) const defaultChartFile = path.join(coaiaDir, 'charts.jsonl'); if (!existsSync(defaultChartFile)) { await fs.writeFile(defaultChartFile, '', 'utf-8'); } // Create sample context files to demonstrate the concept const sampleContexts = ['work', 'personal', 'learning']; for (const context of sampleContexts) { const contextFile = path.join(coaiaDir, `charts-${context}.jsonl`); if (!existsSync(contextFile)) { await fs.writeFile(contextFile, `# COAIA Charts for ${context} context\n`, 'utf-8'); } } // Create template files const templatePath = path.join(coaiaDir, 'templates', 'common-goals.txt'); const templateContent = [ '# COAIA Memory - Structural Tension Chart Templates', '# Use different contexts for different areas of life/work', '# Examples: charts-work.jsonl, charts-personal.jsonl, charts-health.jsonl', '', '## Context Usage:', '# Default/Master: charts.jsonl (no context specified)', '# Work context: charts-work.jsonl (context: "work")', '# Personal context: charts-personal.jsonl (context: "personal")', '# Health context: charts-health.jsonl (context: "health")', '# Learning context: charts-learning.jsonl (context: "learning")', '', '## Template Patterns:', '# Focus on what you want to CREATE (not solve or fix)', '# Action steps ARE structural tension charts (can be telescoped)', '# Current reality must be factual, not "ready to begin"' ].join('\n'); await fs.writeFile(templatePath, templateContent); const structure = { location: coaiaDir, files: { 'charts.jsonl': 'Master/default chart database', 'charts-work.jsonl': 'Work-related structural tension charts', 'charts-personal.jsonl': 'Personal goals and projects', 'charts-learning.jsonl': 'Learning and skill development charts', 'templates/common-goals.txt': 'Usage patterns and methodology guidance' } }; return { message: `āœ… COAIA project initialized at ${coaiaDir}. Multiple chart contexts available (default, work, personal, learning). Create additional contexts as needed.`, structure }; } async listCoaiaProjects() { const result = { global_charts: 0, project_contexts: undefined }; // Show configured memory path if set if (memoryPath) { result.configured_path = memoryPath; result.message = `Using configured memory path: ${memoryPath}`; } // Check charts in configured/detected COAIA directory const coaiaDir = memoryPath ? (memoryPath.endsWith('/') ? memoryPath : (memoryPath.endsWith('.jsonl') ? path.dirname(memoryPath) : memoryPath)) : (() => { const projectRoot = findProjectRoot(); return projectRoot ? path.join(projectRoot, '.coaia') : null; })(); if (coaiaDir && existsSync(coaiaDir)) { result.current_project = coaiaDir; // Find all chart files in COAIA directory try { const files = await fs.readdir(coaiaDir); const chartFiles = files.filter(file => file.endsWith('.jsonl') && file.startsWith('charts')); const contexts = {}; let totalProjectCharts = 0; for (const file of chartFiles) { const filePath = path.join(coaiaDir, file); const contextName = file === 'charts.jsonl' ? 'default' : file.replace('charts-', '').replace('.jsonl', ''); try { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n').filter(line => line.trim() && !line.startsWith('#')); const chartCount = lines.filter(line => { try { const item = JSON.parse(line); return item.type === 'entity' && item.entityType === 'structural_tension_chart'; } catch { return false; } }).length; contexts[contextName] = { file: file, path: filePath, charts: chartCount }; totalProjectCharts += chartCount; } catch { // File not readable, skip contexts[contextName] = { file: file, path: filePath, charts: 0 }; } } result.project_contexts = contexts; result.total_project_charts = totalProjectCharts; result.coaia_structure = { directory: coaiaDir, available_contexts: Object.keys(contexts).sort(), templates: path.join(coaiaDir, 'templates') }; } catch { // Directory not readable result.project_contexts = {}; } } return result; } // Context-aware structural tension chart methods async createStructuralTensionChartInContext(desiredOutcome, currentReality, dueDate, context, actionSteps) { // Use the existing validation logic const problemSolvingWords = ['fix', 'solve', 'eliminate', 'prevent', 'stop', 'avoid', 'reduce', 'remove']; const detectedProblemWords = problemSolvingWords.filter(word => desiredOutcome.toLowerCase().includes(word)); if (detectedProblemWords.length > 0) { throw new Error(`🌊 CREATIVE ORIENTATION REQUIRED Desired Outcome: "${desiredOutcome}" āŒ **Problem**: Contains problem-solving language: "${detectedProblemWords.join(', ')}" šŸ“š **Principle**: Structural Tension Charts use creative orientation - focus on what you want to CREATE, not what you want to eliminate. šŸŽÆ **Reframe Your Outcome**: Instead of elimination → Creation focus āœ… **Examples**: - Instead of: "Fix communication problems" - Use: "Establish clear, effective communication practices" **Why This Matters**: Problem-solving creates oscillating patterns. Creative orientation creates advancing patterns toward desired outcomes.`); } // Create chart using COAIA context system const graph = await this.loadCoaiaGraph(context); const chartId = `chart_${Date.now()}`; // Create chart entities const entities = [ { name: `${chartId}_chart`, entityType: 'structural_tension_chart', observations: [`Chart created on ${new Date().toISOString()}`], metadata: { chartId, dueDate, level: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), context: context || 'default' } }, { name: `${chartId}_desired_outcome`, entityType: 'desired_outcome', observations: [desiredOutcome], metadata: { chartId, dueDate, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } }, { name: `${chartId}_cur