# Phase 3 Startup Guide: Memory Intelligence Implementation

**Version**: v0.15.0 Unified Memory Model
**Phase**: Phase 3 - Memory Intelligence (Week 5-6, Day 25-36)
**Duration**: 7.5 days (6 days implementation + 1.5 days QA)
**Last Updated**: 2025-11-03

---

## 📋 Quick Start for New Claude Code Session

### Prerequisites Checklist

Before starting Phase 3, verify:

```bash
# 1. Confirm you're on the correct branch
git branch --show-current
# Expected: feature/v0.15.0-unified-memory

# 2. Verify Phase 2 is complete
git log --oneline | head -3
# Expected to see:
#   01aa67f feat: implement Phase 2 Smart Memory (Memory Extraction, Linking, CLI)
#   12b16a8 docs: add Phase 2 startup guide...
#   6cddd3c docs: add Phase 2-4 execution plan...

# 3. Check all Phase 2 tests pass
./.venv/bin/pytest tests/semantic/test_memory_extractor.py \
  tests/semantic/test_memory_linker.py \
  tests/cli/test_memory_extract.py -v
# Expected: 74 passed, 1 deselected

# 4. Verify working directory is clean
git status
# Expected: "nothing to commit, working tree clean"

# 5. Check Python environment
./.venv/bin/python --version
# Expected: Python 3.11+
```

✅ **All checks passed?** → Proceed to Phase 3 execution

---

## 🎯 Phase 3 Overview

### Objectives

1. **Question-Answering System** (Agent 9)
   - Answer questions about project using memory search
   - Architecture: "Why did we switch to PostgreSQL?"
   - Patterns: "What authentication method do we use?"
   - Tasks: "What should I work on next?"
   - Confidence scoring and source tracking

2. **Summarization & Prediction** (Agent 10)
   - Generate project summaries from memories
   - Predict next likely tasks
   - Detect knowledge gaps
   - Extract tech stack and constraints

3. **Memory Graph Visualization** (Agent 11)
   - Generate relationship graphs
   - Export to Graphviz DOT, Mermaid, JSON
   - Interactive CLI commands
   - Node sizing and edge weighting

### Success Criteria

- **Implementation**: 4 files (~800 lines)
- **Tests**: 25 tests, all passing
- **Coverage**: >90% on new modules
- **Quality Score**: >90/100 (Grade A or higher)
- **Performance**: Q&A <500ms, Summary <1s, Graph <2s for 100 nodes
- **Duration**: 6 days (+ 1.5 days QA)

---

## 🚀 Execution Steps

### Step 1: Launch Agents 9, 10 & 11 in Parallel (Day 25-30, 6 days)

**Important**: Launch **all three agents in a single message** for true parallel execution.

Copy and paste this to Claude Code:

```
I want to launch Phase 3 of v0.15.0 Unified Memory Model implementation.

Please launch Agent 9 (Question-Answering), Agent 10 (Summarization & Prediction),
and Agent 11 (Memory Graph Visualization) IN PARALLEL using the Task tool.

Use the detailed prompts from the sections below:
- Agent 9 Prompt: [See "Agent 9 Detailed Prompt" section]
- Agent 10 Prompt: [See "Agent 10 Detailed Prompt" section]
- Agent 11 Prompt: [See "Agent 11 Detailed Prompt" section]

Launch all three agents in a SINGLE MESSAGE with THREE Task tool calls.
```

---

## 📝 Agent 9 Detailed Prompt

**Agent**: Question-Answering System
**Duration**: 6 days
**Depends on**: Phase 2 complete ✅

### Full Prompt for Task Tool

```markdown
# Agent 9: Memory Question-Answering System

## Context

You are implementing **question-answering** for Clauxton v0.15.0, which allows users to ask questions about their project and get answers from memory.

**Project**: Clauxton - Claude Code plugin
**Version**: v0.15.0 Unified Memory Model
**Phase**: Phase 3 (Memory Intelligence), Day 25-30
**Branch**: feature/v0.15.0-unified-memory
**Dependencies**: Phase 2 complete ✅

## Documentation to Read First

1. `docs/v0.15.0_PHASE2-4_EXECUTION_PLAN.md` (Phase 3 section, lines 221-261)
2. `CLAUDE.md` (Code style guidelines)
3. Review existing code:
   - `clauxton/core/memory.py` (Memory system)
   - `clauxton/semantic/memory_linker.py` (Relationship detection)

## Tasks

### Task 1: Implement MemoryQA (Day 25-28)

**File**: `clauxton/semantic/memory_qa.py`

Create a class that answers questions using memory search:

```python
from pathlib import Path
from typing import List, Tuple, Optional
from clauxton.core.memory import Memory, MemoryEntry
from clauxton.semantic.memory_linker import MemoryLinker
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

class MemoryQA:
    """Answer questions about project using memory search."""

    def __init__(self, project_root: Path):
        """
        Initialize QA system.

        Args:
            project_root: Project root directory
        """
        self.project_root = project_root
        self.memory = Memory(project_root)
        self.linker = MemoryLinker(project_root)

    def answer_question(
        self,
        question: str,
        top_k: int = 5,
        min_confidence: float = 0.3
    ) -> Tuple[str, float, List[str]]:
        """
        Answer a question using memory search.

        Args:
            question: Question to answer
            top_k: Number of memories to consider
            min_confidence: Minimum confidence threshold

        Returns:
            Tuple of (answer, confidence, source_memory_ids)

        Examples:
            >>> qa = MemoryQA(Path("."))
            >>> answer, conf, sources = qa.answer_question("Why did we switch to PostgreSQL?")
            >>> print(f"Answer: {answer} (confidence: {conf:.2f})")
            Answer: We switched to PostgreSQL for better performance... (confidence: 0.85)
        """
        # 1. Search relevant memories
        relevant_memories = self._search_relevant_memories(question, top_k)

        if not relevant_memories:
            return "No relevant information found.", 0.0, []

        # 2. Rank by relevance to question
        ranked = self._rank_by_context(question, relevant_memories)

        # 3. Generate answer from top memories
        answer = self._generate_answer(question, ranked[:top_k])

        # 4. Calculate confidence
        confidence = self._calculate_confidence(ranked[:top_k], question)

        # 5. Extract source IDs
        source_ids = [mem.id for mem in ranked[:top_k]]

        return answer, confidence, source_ids

    def _search_relevant_memories(
        self,
        question: str,
        top_k: int
    ) -> List[MemoryEntry]:
        """
        Search for memories relevant to the question.

        Args:
            question: Question text
            top_k: Number of results

        Returns:
            List of relevant memories
        """
        # Use Memory.search() with TF-IDF
        results = self.memory.search(query=question, limit=top_k * 2)
        return results

    def _rank_by_context(
        self,
        question: str,
        memories: List[MemoryEntry]
    ) -> List[MemoryEntry]:
        """
        Rank memories by relevance to question.

        Uses TF-IDF cosine similarity between question and memory content.

        Args:
            question: Question text
            memories: Candidate memories

        Returns:
            Memories sorted by relevance (descending)
        """
        if not memories:
            return []

        try:
            # TF-IDF vectorization
            vectorizer = TfidfVectorizer(stop_words="english", lowercase=True)

            # Create corpus: question + all memory texts
            texts = [question] + [f"{m.title} {m.content}" for m in memories]
            vectors = vectorizer.fit_transform(texts)

            # Calculate similarity: question vs each memory
            question_vec = vectors[0:1]
            memory_vecs = vectors[1:]
            similarities = cosine_similarity(question_vec, memory_vecs)[0]

            # Sort by similarity
            ranked_indices = similarities.argsort()[::-1]  # Descending
            return [memories[i] for i in ranked_indices]

        except Exception:
            # Fallback: return as-is
            return memories

    def _generate_answer(
        self,
        question: str,
        memories: List[MemoryEntry]
    ) -> str:
        """
        Generate answer from memories.

        Simple approach: Concatenate relevant content.
        Advanced approach (TODO): Use LLM to synthesize answer.

        Args:
            question: Question text
            memories: Ranked relevant memories

        Returns:
            Answer text
        """
        if not memories:
            return "No relevant information found."

        # Simple approach: Extract most relevant content
        if len(memories) == 1:
            mem = memories[0]
            return f"{mem.title}: {mem.content}"
        else:
            # Multiple memories: Combine content
            parts = []
            for i, mem in enumerate(memories[:3], 1):  # Top 3
                parts.append(f"{i}. {mem.title}: {mem.content}")

            return "\n\n".join(parts)

    def _calculate_confidence(
        self,
        memories: List[MemoryEntry],
        question: str
    ) -> float:
        """
        Calculate confidence score for the answer.

        Factors:
        - Number of relevant memories found
        - Confidence scores of memories (if auto-extracted)
        - Keyword overlap between question and memories

        Args:
            memories: Relevant memories
            question: Question text

        Returns:
            Confidence score (0.0-1.0)
        """
        if not memories:
            return 0.0

        # Factor 1: Number of memories (more = higher confidence)
        count_score = min(len(memories) / 5.0, 1.0)  # Cap at 5 memories

        # Factor 2: Average memory confidence
        confidences = [m.confidence for m in memories if m.confidence is not None]
        avg_confidence = sum(confidences) / len(confidences) if confidences else 0.5

        # Factor 3: Keyword overlap
        question_words = set(question.lower().split())
        overlap_scores = []
        for mem in memories:
            mem_words = set(f"{mem.title} {mem.content}".lower().split())
            if mem_words:
                overlap = len(question_words & mem_words) / len(question_words)
                overlap_scores.append(overlap)

        avg_overlap = sum(overlap_scores) / len(overlap_scores) if overlap_scores else 0.0

        # Weighted average
        confidence = (
            count_score * 0.3 +
            avg_confidence * 0.4 +
            avg_overlap * 0.3
        )

        return min(confidence, 1.0)
```

### Task 2: Add MCP Tool (Day 28)

**File**: `clauxton/mcp/server.py` (UPDATE existing file)

Add MCP tool for Claude Code integration:

```python
@server.call_tool()
async def answer_question(question: str, top_k: int = 5) -> list[types.TextContent]:
    """
    Answer a question about the project using memory search.

    Args:
        question: Question to answer
        top_k: Number of memories to consider (default: 5)

    Returns:
        Answer with confidence score and sources

    Examples:
        - "Why did we switch to PostgreSQL?"
        - "What authentication method do we use?"
        - "What should I work on next?"
    """
    try:
        project_root = Path.cwd()
        qa = MemoryQA(project_root)

        answer, confidence, sources = qa.answer_question(question, top_k)

        # Format response
        response = f"**Answer** (confidence: {confidence:.2f}):\n\n{answer}\n\n"

        if sources:
            response += f"**Sources**: {', '.join(sources)}"

        return [types.TextContent(type="text", text=response)]

    except Exception as e:
        return [types.TextContent(type="text", text=f"Error: {str(e)}")]
```

### Task 3: Write Comprehensive Tests (Day 29-30)

**File**: `tests/semantic/test_memory_qa.py`

Write 10+ tests:

```python
import pytest
from pathlib import Path
from datetime import datetime
from clauxton.semantic.memory_qa import MemoryQA
from clauxton.core.memory import Memory, MemoryEntry

def test_answer_architecture_question(tmp_path):
    """Test answering architecture question."""
    # Setup memories
    memory = Memory(tmp_path)
    now = datetime.now()

    mem1 = MemoryEntry(
        id="MEM-20251103-001",
        type="decision",
        title="Switch to PostgreSQL",
        content="We switched to PostgreSQL for better JSONB support and performance",
        category="database",
        tags=["postgresql", "database", "migration"],
        created_at=now,
        updated_at=now,
        source="manual",
    )
    memory.add(mem1)

    # Ask question
    qa = MemoryQA(tmp_path)
    answer, confidence, sources = qa.answer_question("Why did we switch to PostgreSQL?")

    # Assert
    assert "PostgreSQL" in answer
    assert confidence > 0.5
    assert "MEM-20251103-001" in sources

def test_answer_pattern_question(tmp_path):
    """Test answering pattern question."""
    memory = Memory(tmp_path)
    now = datetime.now()

    mem1 = MemoryEntry(
        id="MEM-20251103-001",
        type="knowledge",
        title="Authentication Method",
        content="We use JWT tokens for API authentication",
        category="authentication",
        tags=["jwt", "auth", "api"],
        created_at=now,
        updated_at=now,
        source="manual",
    )
    memory.add(mem1)

    qa = MemoryQA(tmp_path)
    answer, confidence, sources = qa.answer_question("What authentication method do we use?")

    assert "JWT" in answer
    assert confidence > 0.5

def test_answer_with_no_relevant_memories(tmp_path):
    """Test answering when no relevant memories exist."""
    qa = MemoryQA(tmp_path)
    answer, confidence, sources = qa.answer_question("What is the meaning of life?")

    assert "No relevant information" in answer
    assert confidence == 0.0
    assert len(sources) == 0

def test_confidence_scoring_multiple_memories(tmp_path):
    """Test confidence scoring with multiple relevant memories."""
    memory = Memory(tmp_path)
    now = datetime.now()

    # Add 3 relevant memories
    for i in range(3):
        mem = MemoryEntry(
            id=f"MEM-20251103-{i+1:03d}",
            type="knowledge",
            title=f"API Design {i}",
            content=f"We use RESTful API design with proper HTTP methods {i}",
            category="api",
            tags=["api", "rest"],
            created_at=now,
            updated_at=now,
            source="manual",
        )
        memory.add(mem)

    qa = MemoryQA(tmp_path)
    answer, confidence, sources = qa.answer_question("How do we design APIs?")

    # More memories = higher confidence
    assert confidence > 0.6
    assert len(sources) >= 3

# Add 6+ more tests for:
# - Source tracking
# - Top-k limiting
# - Ranking by relevance
# - Edge cases (empty question, very long question)
# - Performance benchmarks
```

## Quality Requirements

### 1. Code Review
- [ ] Follow code style: CLAUDE.md
- [ ] Type hints: 100%
- [ ] Docstrings: Google style
- [ ] No code smells

### 2. Performance
- [ ] answer_question() <500ms for typical questions
- [ ] Handle 1,000 memories efficiently
- [ ] Add performance benchmarks

### 3. Testing
- [ ] 10+ comprehensive tests
- [ ] Coverage >90%
- [ ] Test all question types
- [ ] Test edge cases

### 4. Lint & Type Check
- [ ] `mypy --strict clauxton/semantic/memory_qa.py`: Pass
- [ ] `ruff check clauxton tests`: Pass

## Deliverables

1. `clauxton/semantic/memory_qa.py` (~250 lines)
2. `clauxton/mcp/server.py` (updated, +50 lines)
3. `tests/semantic/test_memory_qa.py` (10+ tests)
4. Coverage report (>90%)
5. Completion report

## Expected Duration

6 days

## Success Criteria

- [ ] All tests pass
- [ ] Coverage >90%
- [ ] Type check passes
- [ ] Lint passes
- [ ] Performance targets met (<500ms)
- [ ] Can answer questions about real projects
- [ ] Documentation complete

## Important Notes

- USE MemoryLinker for relationship-aware search (optional enhancement)
- SIMPLE approach first (extract content), advanced LLM synthesis later
- ALWAYS include source IDs for transparency
- TEST with real project questions
- GENERATE a detailed completion report at the end
```

---

## 📝 Agent 10 Detailed Prompt

**Agent**: Summarization & Prediction
**Duration**: 6 days
**Depends on**: Phase 2 complete ✅
**Runs in parallel with**: Agent 9

### Full Prompt for Task Tool

```markdown
# Agent 10: Memory Summarization & Prediction

## Context

You are implementing **summarization and prediction** for Clauxton v0.15.0, which generates project summaries and predicts next tasks.

**Project**: Clauxton - Claude Code plugin
**Version**: v0.15.0 Unified Memory Model
**Phase**: Phase 3 (Memory Intelligence), Day 25-30
**Branch**: feature/v0.15.0-unified-memory
**Dependencies**: Phase 2 complete ✅

## Documentation to Read First

1. `docs/v0.15.0_PHASE2-4_EXECUTION_PLAN.md` (Phase 3 section, lines 264-311)
2. `CLAUDE.md` (Code style guidelines)
3. Review existing code:
   - `clauxton/core/memory.py` (Memory system)
   - `clauxton/semantic/memory_extractor.py` (Patterns)

## Tasks

### Task 1: Implement MemorySummarizer (Day 25-28)

**File**: `clauxton/semantic/memory_summarizer.py`

Create a class that generates summaries and predictions:

```python
from pathlib import Path
from typing import List, Dict, Optional
from datetime import datetime, timedelta
from collections import Counter
from clauxton.core.memory import Memory, MemoryEntry

class MemorySummarizer:
    """Generate project summaries and predictions from memories."""

    def __init__(self, project_root: Path):
        """
        Initialize summarizer.

        Args:
            project_root: Project root directory
        """
        self.project_root = project_root
        self.memory = Memory(project_root)

    def summarize_project(self) -> Dict[str, any]:
        """
        Generate comprehensive project summary.

        Returns:
            Dictionary with summary sections:
            {
                "architecture_decisions": [...],
                "active_patterns": [...],
                "tech_stack": [...],
                "constraints": [...],
                "recent_changes": [...],
                "statistics": {...}
            }
        """
        memories = self.memory.list_all()

        summary = {
            "architecture_decisions": self._extract_decisions(memories),
            "active_patterns": self._extract_patterns(memories),
            "tech_stack": self._extract_tech_stack(memories),
            "constraints": self._extract_constraints(memories),
            "recent_changes": self._extract_recent_changes(memories, days=7),
            "statistics": self._calculate_statistics(memories),
        }

        return summary

    def predict_next_tasks(
        self,
        context: Optional[str] = None,
        limit: int = 5
    ) -> List[Dict[str, any]]:
        """
        Predict likely next tasks based on project state.

        Args:
            context: Optional context (e.g., "frontend", "backend")
            limit: Max number of predictions

        Returns:
            List of task predictions:
            [
                {
                    "title": "Implement user login",
                    "reason": "Authentication mentioned in 3 recent decisions",
                    "priority": "high",
                    "confidence": 0.85
                },
                ...
            ]
        """
        memories = self.memory.list_all()

        # Filter by context if provided
        if context:
            memories = [m for m in memories if context.lower() in m.content.lower()]

        # Analyze patterns and predict tasks
        predictions = []

        # 1. Look for incomplete patterns
        predictions.extend(self._predict_from_patterns(memories))

        # 2. Look for pending tasks in memories
        predictions.extend(self._predict_from_tasks(memories))

        # 3. Look for recent activity trends
        predictions.extend(self._predict_from_trends(memories))

        # Sort by confidence and limit
        predictions.sort(key=lambda x: x["confidence"], reverse=True)
        return predictions[:limit]

    def generate_knowledge_gaps(self) -> List[Dict[str, str]]:
        """
        Identify missing knowledge/decisions.

        Returns:
            List of knowledge gaps:
            [
                {
                    "category": "authentication",
                    "gap": "No documented authentication method",
                    "severity": "high"
                },
                ...
            ]
        """
        memories = self.memory.list_all()
        gaps = []

        # Check for common categories
        categories = Counter(m.category for m in memories)

        # Expected categories for a typical project
        expected = ["authentication", "api", "database", "testing", "deployment"]

        for category in expected:
            if categories.get(category, 0) == 0:
                gaps.append({
                    "category": category,
                    "gap": f"No documented {category} decisions or patterns",
                    "severity": "medium"
                })

        # Check for specific patterns
        if not any("error handling" in m.content.lower() for m in memories):
            gaps.append({
                "category": "error-handling",
                "gap": "No documented error handling strategy",
                "severity": "high"
            })

        return gaps

    def _extract_decisions(self, memories: List[MemoryEntry]) -> List[Dict[str, str]]:
        """Extract architecture decisions."""
        decisions = [m for m in memories if m.type == "decision"]
        return [
            {
                "id": m.id,
                "title": m.title,
                "content": m.content[:200],
                "date": m.created_at.strftime("%Y-%m-%d")
            }
            for m in decisions[:10]  # Top 10 most recent
        ]

    def _extract_patterns(self, memories: List[MemoryEntry]) -> List[Dict[str, str]]:
        """Extract active patterns."""
        patterns = [m for m in memories if m.type == "pattern"]
        return [
            {
                "id": m.id,
                "title": m.title,
                "category": m.category
            }
            for m in patterns[:10]
        ]

    def _extract_tech_stack(self, memories: List[MemoryEntry]) -> List[str]:
        """Extract tech stack from memories."""
        tech_keywords = [
            "python", "javascript", "typescript", "react", "vue", "angular",
            "postgresql", "mysql", "redis", "mongodb",
            "docker", "kubernetes", "aws", "gcp", "azure",
            "fastapi", "django", "flask", "express", "nextjs"
        ]

        found_tech = set()
        for mem in memories:
            text = f"{mem.title} {mem.content}".lower()
            for tech in tech_keywords:
                if tech in text:
                    found_tech.add(tech.capitalize())

        return sorted(found_tech)

    def _extract_constraints(self, memories: List[MemoryEntry]) -> List[str]:
        """Extract project constraints."""
        constraint_keywords = ["must", "should not", "cannot", "required", "constraint"]

        constraints = []
        for mem in memories:
            text = f"{mem.title} {mem.content}".lower()
            if any(keyword in text for keyword in constraint_keywords):
                constraints.append(f"{mem.title}: {mem.content[:150]}")

        return constraints[:5]

    def _extract_recent_changes(
        self,
        memories: List[MemoryEntry],
        days: int = 7
    ) -> List[Dict[str, str]]:
        """Extract recent changes."""
        cutoff = datetime.now() - timedelta(days=days)
        recent = [m for m in memories if m.created_at >= cutoff]

        recent.sort(key=lambda m: m.created_at, reverse=True)

        return [
            {
                "id": m.id,
                "title": m.title,
                "type": m.type,
                "date": m.created_at.strftime("%Y-%m-%d")
            }
            for m in recent[:10]
        ]

    def _calculate_statistics(self, memories: List[MemoryEntry]) -> Dict[str, int]:
        """Calculate memory statistics."""
        return {
            "total": len(memories),
            "by_type": dict(Counter(m.type for m in memories)),
            "by_category": dict(Counter(m.category for m in memories)),
            "with_relationships": sum(1 for m in memories if m.related_to),
        }

    def _predict_from_patterns(self, memories: List[MemoryEntry]) -> List[Dict[str, any]]:
        """Predict tasks from patterns."""
        # Implementation: Analyze patterns and suggest related tasks
        predictions = []

        # Example: If authentication pattern exists but no tests, suggest testing
        auth_patterns = [m for m in memories if "auth" in m.content.lower()]
        if auth_patterns:
            test_exists = any(m.type == "task" and "test" in m.title.lower() for m in memories)
            if not test_exists:
                predictions.append({
                    "title": "Add authentication tests",
                    "reason": f"Authentication patterns found ({len(auth_patterns)} memories) but no test tasks",
                    "priority": "high",
                    "confidence": 0.8
                })

        return predictions

    def _predict_from_tasks(self, memories: List[MemoryEntry]) -> List[Dict[str, any]]:
        """Predict tasks from existing task memories."""
        tasks = [m for m in memories if m.type == "task"]
        predictions = []

        # Look for pending tasks
        pending = [t for t in tasks if "pending" in t.tags or "todo" in t.tags]
        for task in pending[:3]:
            predictions.append({
                "title": task.title,
                "reason": "Pending task in memory",
                "priority": "medium",
                "confidence": 0.9
            })

        return predictions

    def _predict_from_trends(self, memories: List[MemoryEntry]) -> List[Dict[str, any]]:
        """Predict tasks from recent trends."""
        predictions = []

        # Analyze recent activity
        recent = sorted(memories, key=lambda m: m.created_at, reverse=True)[:20]
        categories = Counter(m.category for m in recent)

        # Most active category suggests related work
        if categories:
            top_category = categories.most_common(1)[0][0]
            predictions.append({
                "title": f"Continue {top_category} work",
                "reason": f"Recent focus on {top_category} ({categories[top_category]} memories)",
                "priority": "medium",
                "confidence": 0.7
            })

        return predictions
```

### Task 2: Add MCP Tools (Day 28)

**File**: `clauxton/mcp/server.py` (UPDATE existing file)

Add 3 MCP tools:

```python
@server.call_tool()
async def get_project_summary() -> list[types.TextContent]:
    """
    Get comprehensive project summary from memories.

    Returns:
        Summary with architecture decisions, tech stack, patterns, etc.
    """
    try:
        project_root = Path.cwd()
        summarizer = MemorySummarizer(project_root)

        summary = summarizer.summarize_project()

        # Format response
        response = "# Project Summary\n\n"

        response += "## Architecture Decisions\n"
        for dec in summary["architecture_decisions"][:5]:
            response += f"- {dec['title']} ({dec['date']})\n"

        response += "\n## Tech Stack\n"
        response += ", ".join(summary["tech_stack"])

        response += "\n\n## Recent Changes\n"
        for change in summary["recent_changes"][:5]:
            response += f"- {change['title']} ({change['type']}, {change['date']})\n"

        response += f"\n\n## Statistics\n"
        response += f"Total memories: {summary['statistics']['total']}\n"

        return [types.TextContent(type="text", text=response)]

    except Exception as e:
        return [types.TextContent(type="text", text=f"Error: {str(e)}")]

@server.call_tool()
async def suggest_next_tasks(limit: int = 5) -> list[types.TextContent]:
    """
    Get suggested next tasks based on project state.

    Args:
        limit: Max number of suggestions (default: 5)

    Returns:
        List of task suggestions with reasons
    """
    try:
        project_root = Path.cwd()
        summarizer = MemorySummarizer(project_root)

        predictions = summarizer.predict_next_tasks(limit=limit)

        response = "# Suggested Next Tasks\n\n"
        for i, pred in enumerate(predictions, 1):
            response += f"{i}. **{pred['title']}** (Priority: {pred['priority']})\n"
            response += f"   - Reason: {pred['reason']}\n"
            response += f"   - Confidence: {pred['confidence']:.2f}\n\n"

        return [types.TextContent(type="text", text=response)]

    except Exception as e:
        return [types.TextContent(type="text", text=f"Error: {str(e)}")]

@server.call_tool()
async def detect_knowledge_gaps() -> list[types.TextContent]:
    """
    Detect missing knowledge or decisions in project.

    Returns:
        List of knowledge gaps with severity
    """
    try:
        project_root = Path.cwd()
        summarizer = MemorySummarizer(project_root)

        gaps = summarizer.generate_knowledge_gaps()

        if not gaps:
            return [types.TextContent(type="text", text="No knowledge gaps detected.")]

        response = "# Knowledge Gaps\n\n"
        for gap in gaps:
            severity_emoji = "🔴" if gap["severity"] == "high" else "🟡"
            response += f"{severity_emoji} **{gap['category'].capitalize()}**\n"
            response += f"   {gap['gap']}\n\n"

        return [types.TextContent(type="text", text=response)]

    except Exception as e:
        return [types.TextContent(type="text", text=f"Error: {str(e)}")]
```

### Task 3: Write Comprehensive Tests (Day 29-30)

**File**: `tests/semantic/test_memory_summarizer.py`

Write 10+ tests covering all functionality.

## Quality Requirements

Same as Agent 9 (Code review, Performance, Testing, Lint).

## Deliverables

1. `clauxton/semantic/memory_summarizer.py` (~300 lines)
2. `clauxton/mcp/server.py` (updated, +100 lines)
3. `tests/semantic/test_memory_summarizer.py` (10+ tests)
4. Coverage report (>90%)
5. Completion report

## Expected Duration

6 days

## Success Criteria

- [ ] All tests pass
- [ ] Coverage >90%
- [ ] Type check passes
- [ ] Lint passes
- [ ] Performance targets met (<1s for summary)
- [ ] Generates useful summaries and predictions
- [ ] Documentation complete
```

---

## 📝 Agent 11 Detailed Prompt

**Agent**: Memory Graph Visualization
**Duration**: 6 days
**Depends on**: Phase 2 complete ✅ (for relationships)
**Runs in parallel with**: Agent 9, Agent 10

### Full Prompt for Task Tool

```markdown
# Agent 11: Memory Graph Visualization

## Context

You are implementing **graph visualization** for Clauxton v0.15.0, which generates visual representations of memory relationships.

**Project**: Clauxton - Claude Code plugin
**Version**: v0.15.0 Unified Memory Model
**Phase**: Phase 3 (Memory Intelligence), Day 25-30
**Branch**: feature/v0.15.0-unified-memory
**Dependencies**: Phase 2 complete ✅ (MemoryLinker provides relationships)

## Documentation to Read First

1. `docs/v0.15.0_PHASE2-4_EXECUTION_PLAN.md` (Phase 3 section, lines 314-366)
2. `CLAUDE.md` (Code style guidelines)
3. Review existing code:
   - `clauxton/core/memory.py` (Memory system with relationships)
   - `clauxton/semantic/memory_linker.py` (Relationship detection)

## Tasks

### Task 1: Implement MemoryGraph (Day 25-28)

**File**: `clauxton/visualization/memory_graph.py`

Create a class that generates graph visualizations:

```python
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from clauxton.core.memory import Memory, MemoryEntry
import json

class MemoryGraph:
    """Generate graph visualizations of memory relationships."""

    def __init__(self, project_root: Path):
        """
        Initialize graph generator.

        Args:
            project_root: Project root directory
        """
        self.project_root = project_root
        self.memory = Memory(project_root)

    def generate_graph_data(
        self,
        memory_type: Optional[str] = None,
        max_nodes: int = 100
    ) -> Dict[str, List]:
        """
        Generate graph data structure.

        Args:
            memory_type: Filter by type (knowledge, decision, etc.)
            max_nodes: Maximum number of nodes

        Returns:
            Dictionary with nodes and edges:
            {
                "nodes": [
                    {"id": "MEM-001", "type": "knowledge", "title": "...", "size": 10},
                    ...
                ],
                "edges": [
                    {"source": "MEM-001", "target": "MEM-002", "weight": 0.85},
                    ...
                ]
            }
        """
        # Get memories
        memories = self.memory.list_all()

        if memory_type:
            memories = [m for m in memories if m.type == memory_type]

        # Limit nodes
        if len(memories) > max_nodes:
            # Sort by importance (number of relationships)
            memories.sort(key=lambda m: len(m.related_to or []), reverse=True)
            memories = memories[:max_nodes]

        # Generate nodes
        nodes = self._generate_nodes(memories)

        # Generate edges from relationships
        edges = self._generate_edges(memories)

        return {
            "nodes": nodes,
            "edges": edges,
            "metadata": {
                "total_nodes": len(nodes),
                "total_edges": len(edges),
                "memory_type": memory_type or "all"
            }
        }

    def export_to_dot(
        self,
        output_file: Path,
        memory_type: Optional[str] = None
    ) -> None:
        """
        Export graph to Graphviz DOT format.

        Args:
            output_file: Output file path (.dot)
            memory_type: Filter by type

        Example output:
            digraph memory_graph {
                "MEM-001" [label="API Design", shape=box, color=blue];
                "MEM-002" [label="Database Schema", shape=box, color=green];
                "MEM-001" -> "MEM-002" [weight=0.85];
            }
        """
        graph_data = self.generate_graph_data(memory_type)

        dot_content = "digraph memory_graph {\n"
        dot_content += "  rankdir=LR;\n"
        dot_content += "  node [shape=box, style=rounded];\n\n"

        # Add nodes
        for node in graph_data["nodes"]:
            color = self._get_node_color(node["type"])
            size = node["size"]
            label = node["title"][:30]  # Limit length

            dot_content += f'  "{node["id"]}" [label="{label}", color={color}, penwidth={size/5}];\n'

        dot_content += "\n"

        # Add edges
        for edge in graph_data["edges"]:
            weight = edge["weight"]
            penwidth = max(1, weight * 3)  # Scale weight to line width

            dot_content += f'  "{edge["source"]}" -> "{edge["target"]}" [penwidth={penwidth:.1f}];\n'

        dot_content += "}\n"

        output_file.write_text(dot_content)

    def export_to_mermaid(
        self,
        output_file: Path,
        memory_type: Optional[str] = None
    ) -> None:
        """
        Export graph to Mermaid diagram format.

        Args:
            output_file: Output file path (.md)
            memory_type: Filter by type

        Example output:
            ```mermaid
            graph LR
                MEM001[API Design] --> MEM002[Database Schema]
                MEM002 --> MEM003[Auth Pattern]
            ```
        """
        graph_data = self.generate_graph_data(memory_type)

        mermaid_content = "```mermaid\ngraph LR\n"

        # Create node ID map (remove hyphens for Mermaid)
        id_map = {node["id"]: node["id"].replace("-", "") for node in graph_data["nodes"]}

        # Add nodes (optional, edges will create them)
        for node in graph_data["nodes"]:
            mermaid_id = id_map[node["id"]]
            label = node["title"][:20]
            mermaid_content += f"    {mermaid_id}[\"{label}\"]\n"

        mermaid_content += "\n"

        # Add edges
        for edge in graph_data["edges"]:
            source_id = id_map[edge["source"]]
            target_id = id_map[edge["target"]]

            mermaid_content += f"    {source_id} --> {target_id}\n"

        mermaid_content += "```\n"

        output_file.write_text(mermaid_content)

    def export_to_json(
        self,
        output_file: Path,
        memory_type: Optional[str] = None
    ) -> None:
        """
        Export graph to JSON format.

        Args:
            output_file: Output file path (.json)
            memory_type: Filter by type
        """
        graph_data = self.generate_graph_data(memory_type)

        with open(output_file, "w") as f:
            json.dump(graph_data, f, indent=2)

    def _generate_nodes(self, memories: List[MemoryEntry]) -> List[Dict]:
        """Generate node data."""
        nodes = []

        for mem in memories:
            # Calculate node size based on relationships
            num_relationships = len(mem.related_to or [])
            size = min(5 + num_relationships * 2, 20)  # Size 5-20

            nodes.append({
                "id": mem.id,
                "type": mem.type,
                "title": mem.title,
                "category": mem.category,
                "size": size,
                "tags": mem.tags
            })

        return nodes

    def _generate_edges(self, memories: List[MemoryEntry]) -> List[Dict]:
        """Generate edge data from relationships."""
        edges = []
        memory_ids = {m.id for m in memories}

        for mem in memories:
            if not mem.related_to:
                continue

            for related_id in mem.related_to:
                # Only add edge if target exists in filtered memories
                if related_id in memory_ids:
                    edges.append({
                        "source": mem.id,
                        "target": related_id,
                        "weight": 0.8  # Default weight (could use similarity from linker)
                    })

        return edges

    def _get_node_color(self, memory_type: str) -> str:
        """Get color for node based on type."""
        colors = {
            "knowledge": "blue",
            "decision": "green",
            "code": "orange",
            "task": "red",
            "pattern": "purple"
        }
        return colors.get(memory_type, "gray")
```

### Task 2: Add CLI Command (Day 28)

**File**: `clauxton/cli/memory.py` (UPDATE existing file)

Add CLI command for graph generation:

```python
@memory.command("graph")
@click.option("--output", "-o", help="Output file path")
@click.option("--format", "-f", type=click.Choice(["dot", "mermaid", "json"]), default="mermaid", help="Output format")
@click.option("--type", "memory_type", help="Filter by memory type")
@click.option("--max-nodes", type=int, default=100, help="Maximum number of nodes")
def generate_graph(output, format, memory_type, max_nodes):
    """
    Generate memory relationship graph.

    Examples:
        clauxton memory graph --format mermaid
        clauxton memory graph --output graph.dot --format dot
        clauxton memory graph --type knowledge --format json
    """
    from clauxton.visualization.memory_graph import MemoryGraph

    project_root = Path.cwd()

    try:
        graph = MemoryGraph(project_root)

        # Generate output filename if not provided
        if not output:
            ext = "md" if format == "mermaid" else format
            output = f"memory_graph.{ext}"

        output_path = Path(output)

        # Export based on format
        if format == "dot":
            graph.export_to_dot(output_path, memory_type)
        elif format == "mermaid":
            graph.export_to_mermaid(output_path, memory_type)
        elif format == "json":
            graph.export_to_json(output_path, memory_type)

        click.echo(f"✓ Graph exported to {output_path}")

        # Show statistics
        graph_data = graph.generate_graph_data(memory_type, max_nodes)
        click.echo(f"  Nodes: {len(graph_data['nodes'])}")
        click.echo(f"  Edges: {len(graph_data['edges'])}")

    except Exception as e:
        click.echo(f"Error: {str(e)}", err=True)
        raise click.Abort()
```

### Task 3: Write Comprehensive Tests (Day 29-30)

**File**: `tests/visualization/test_memory_graph.py`

Write 5+ tests covering all export formats.

## Quality Requirements

Same as Agent 9 & 10.

## Deliverables

1. `clauxton/visualization/memory_graph.py` (~200 lines)
2. `clauxton/cli/memory.py` (updated, +50 lines)
3. `tests/visualization/test_memory_graph.py` (5+ tests)
4. Coverage report (>85%)
5. Completion report

## Expected Duration

6 days

## Success Criteria

- [ ] All tests pass
- [ ] Coverage >85%
- [ ] Type check passes
- [ ] Lint passes
- [ ] Performance targets met (<2s for 100 nodes)
- [ ] Generates valid DOT/Mermaid/JSON
- [ ] Documentation complete
```

---

## 🎯 Step 2: Wait for Agents 9, 10 & 11 Completion (6 days)

Monitor progress:
- Check completion reports from all three agents
- Verify all tests pass
- Review code quality

Expected completion: Day 30

---

## 🎯 Step 3: Quality Review (Day 31-32, 1.5 days)

After all 3 agents complete, launch Review Agent:

```
Use improved Quality Review Prompt (from docs/QUALITY_REVIEW_PROMPTS.md, Prompt 1)

Context:
- Phase: Phase 3 - Memory Intelligence
- Deliverables: 4 files (~800 lines), 25 tests
- Review all Phase 3 files

Generate 4 reports:
1. Executive Summary
2. Quality Review
3. Detailed Findings
4. Improvement Tasks
```

---

## 🎯 Step 4: Execute Improvements (If needed, 1-2 days)

If Review Agent identifies issues:
- Launch improvement SubAgents **in parallel**
- Each SubAgent fixes one issue
- Re-run tests after fixes

---

## ✅ Phase 3 Completion Criteria

- [ ] All 3 agents completed
- [ ] 25+ tests passing
- [ ] Coverage >90%
- [ ] Quality Score >90/100
- [ ] All improvements completed
- [ ] Documentation updated

---

## 🚨 Troubleshooting

### Issue: Agent dependencies not met

**Solution**: Verify Phase 2 tests pass:
```bash
./.venv/bin/pytest tests/semantic/test_memory_extractor.py \
  tests/semantic/test_memory_linker.py -v
```

### Issue: Tests failing

**Solution**: Check test environment:
```bash
./.venv/bin/pytest --version
./.venv/bin/python -c "import sklearn; print(sklearn.__version__)"
```

---

## 📚 Reference Documents

- **Phase 2-4 Full Plan**: `docs/v0.15.0_PHASE2-4_EXECUTION_PLAN.md`
- **Implementation Plan**: `docs/v0.15.0_IMPLEMENTATION_PLAN.md`
- **Quality Prompts**: `docs/QUALITY_REVIEW_PROMPTS.md`
- **Code Style**: `CLAUDE.md`

---

## 🎉 Success!

After Phase 3 completion:
1. Commit all changes
2. Create completion summary
3. Proceed to Phase 4 (UX Polish)

---

**Last Updated**: 2025-11-03
**Status**: ✅ Ready to Execute
**Next Action**: Copy prompts to Claude Code and launch Agents 9, 10 & 11 in parallel
