feat: Add Skills Library and Session Insights (v2.3)

## Skills Library
- Browse and install 36+ community skills from Anthropic & Superpowers
- Local skill templates (Rails, React, testing)
- Create custom project-specific skills
- Beautiful UI with search and filtering
- /claude-os-skills command for CLI management

## Session Insights
- Parse Claude Code native .jsonl session files
- Extract patterns, decisions, blockers automatically
- Track tool usage and file changes
- Session summary generation for LLM processing

## Documentation
- Updated README with v2.3 features
- Updated API_REFERENCE with new endpoints
- Added SKILLS_GUIDE.md and SESSIONS_GUIDE.md
- Updated GitHub Pages (docs/index.html)
- Reorganized docs (internal_docs for private)

## Bug Fixes
- Fixed httpx client closure in community skill install
- Fixed frontend request format for skill install

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Bob Roberts 2025-12-11 12:56:49 -06:00
parent 8908f2b03b
commit a744080e65
37 changed files with 11294 additions and 45 deletions

BIN
.coverage

Binary file not shown.

16
.gitignore vendored
View file

@ -96,16 +96,8 @@ frontend/e2e/screenshots/
frontend/playwright-report/
frontend/test-results/
# Docs - ignore all except README-linked files
docs/
!docs/API_REFERENCE.md
!docs/HYBRID_INDEXING_DESIGN.md
!docs/REAL_TIME_LEARNING_GUIDE.md
!docs/SELF_LEARNING_SYSTEM.md
!docs/guides/
docs/guides/*
!docs/guides/REALTIME_KANBAN_GUIDE.md
!docs/guides/VISUAL_GUIDE.md
# Internal docs (not for public repo)
internal_docs/
# Export files (contain project-specific data)
exports/
@ -113,3 +105,7 @@ exports/
# Marketing materials (not for distribution)
brochure.html
presentation.html
# Private directory (not for public repo)
our_stuff/
.coverage

136
README.md
View file

@ -36,11 +36,35 @@
---
## 🆕 What's New in v2.2
## 🆕 What's New in v2.3
> **Latest Release: December 2025**
### ✨ Beautiful New Installer
### 🎯 Skills Library & Community Skills
Browse and install **36+ skills** from the community with one click!
```bash
/claude-os-skills # List all skills
/claude-os-skills install pdf # Install from community
```
**New Features:**
- 📚 **Local Templates** - Pre-built skills for Rails, React, testing workflows
- 🌐 **Community Skills** - Install from Anthropic Official (16) & Superpowers (20)
- 🔧 **Custom Skills** - Create and share project-specific skills
- 🎨 **Beautiful UI** - Browse, search, and install via web interface
### 📊 Session Insights
Parse Claude Code sessions and extract insights automatically!
**New Features:**
- 🔍 **Session Parser** - Reads Claude Code's native `.jsonl` session files
- 💡 **Insight Extraction** - Extracts patterns, decisions, blockers
- 📈 **Analytics Ready** - Track tool usage and file changes across sessions
### ✨ Beautiful New Installer (v2.2)
The installer got a major upgrade with **Charm CLI (gum)** support!
@ -66,6 +90,7 @@ The installer got a major upgrade with **Charm CLI (gum)** support!
| Version | Highlights |
|---------|------------|
| **v2.3** | Skills library, community skills, session insights |
| **v2.2** | Gum CLI support, safety features, lite model default |
| **v2.1** | Unified installer, OpenAI provider support |
| **v2.0** | Hybrid tree-sitter indexing, real-time Kanban board |
@ -109,8 +134,10 @@ You work with Claude Code on a feature, close the terminal, come back tomorrow..
### Key Features
✅ **Lightning-Fast Indexing** - NEW! Tree-sitter hybrid indexing: 10,000 files in 30 seconds (vs 3-5 hours)
✅ **Real-Time Kanban Board** - NEW! Auto-syncing task visualization for agent-os specs (updates within 3 seconds)
✅ **Skills Library** - NEW! Browse & install skills from Anthropic and community repos
✅ **Session Insights** - NEW! Parse Claude Code sessions and extract patterns automatically
✅ **Lightning-Fast Indexing** - Tree-sitter hybrid indexing: 10,000 files in 30 seconds
✅ **Real-Time Kanban Board** - Auto-syncing task visualization for agent-os specs
✅ **One-Command Project Init** - `/claude-os-init` and you're done
✅ **Automatic Context Loading** - Starts every session with relevant memories
✅ **Session Management** - Track work, save progress, resume later
@ -424,13 +451,19 @@ All these work in any initialized project:
- **`/claude-os-list`** - List all knowledge bases
- **`/claude-os-session [action]`** - Manage development sessions
- **`/claude-os-triggers`** - Manage trigger phrases
- **`/claude-os-skills [action]`** - Manage skills (list, install, create)
### Available Skills
**Global Skills (always available):**
- **`initialize-project`** - Analyze codebase and generate standards
- **`remember-this`** - Auto-save when you say "remember this:"
- **`memory`** - Simple memory management
**Community Skills (install via `/claude-os-skills`):**
- **36+ skills** from Anthropic Official and Superpowers repos
- PDF/XLSX manipulation, frontend design, TDD, debugging, code review, and more
---
## 🤖 Agent-OS: Spec-Driven Development (Optional)
@ -544,6 +577,82 @@ Agent-OS agents deeply integrate with Claude OS:
---
## 🎯 Skills Library
**Browse, install, and manage Claude Code skills with ease!**
### What Are Skills?
Skills are reusable instruction sets that teach Claude specific capabilities. They can include:
- Coding patterns and best practices
- Tool usage workflows
- Domain-specific knowledge
- Development methodologies
### Skill Types
**Global Skills** (`~/.claude/skills/`)
- Available in ALL projects
- Core skills: `memory`, `remember-this`, `initialize-project`
**Project Skills** (`{project}/.claude/skills/`)
- Available only in that project
- Installed from templates or custom created
**Community Skills** (fetched from GitHub)
- **Anthropic Official** - 16 skills from `anthropics/skills`
- **Superpowers** - 20 skills from `obra/superpowers`
### Using the Skills Command
```bash
# List all installed skills
/claude-os-skills
# Browse local templates
/claude-os-skills templates
# Install a template to your project
/claude-os-skills install rails-backend
# Create a custom skill
/claude-os-skills create
# View skill details
/claude-os-skills view <name>
# Delete a project skill
/claude-os-skills delete <name>
```
### Community Skills (via Web UI)
1. Open the web UI at http://localhost:5173
2. Select your project
3. Click the **Skills** tab
4. Click **Install Template**
5. Switch to **Community Skills** tab
6. Browse skills from Anthropic Official and Superpowers
7. Click **Install** on any skill
### Featured Community Skills
**From Anthropic Official:**
- `pdf` - Create, edit, and analyze PDF documents
- `xlsx` - Spreadsheet manipulation with formulas
- `frontend-design` - Production-grade UI components
- `mcp-builder` - Create MCP servers
- `doc-coauthoring` - Collaborative documentation
**From Superpowers:**
- `test-driven-development` - TDD workflow
- `systematic-debugging` - Four-phase debugging framework
- `code-review` - Rigorous code review process
- `git-worktrees` - Isolated development branches
- `brainstorming` - Structured ideation process
---
## 🎯 Spec Tracking & Kanban Board
**NEW: Real-time auto-syncing Kanban board for Agent-OS specs!**
@ -722,11 +831,17 @@ claude-os/
│ ├── commands/ # Slash commands (symlinked to ~/.claude/)
│ │ ├── claude-os-init.md
│ │ ├── claude-os-search.md
│ │ ├── claude-os-skills.md # NEW: Skills management
│ │ └── ...
│ ├── skills/ # Skills (symlinked to ~/.claude/)
│ ├── skills/ # Global skills (symlinked to ~/.claude/)
│ │ ├── initialize-project/
│ │ ├── remember-this/
│ │ └── memory/
│ ├── skill-library/ # NEW: Local skill templates
│ │ ├── general/ # General purpose skills
│ │ ├── rails/ # Ruby on Rails skills
│ │ ├── react/ # React/TypeScript skills
│ │ └── testing/ # Testing frameworks
│ └── project-files/ # Files created during /claude-os-init
│ ├── CLAUDE.md.template
│ └── .claude-os/
@ -1036,7 +1151,8 @@ ollama list | grep llama3.1
claude-os/
├── templates/ # Shared templates system
│ ├── commands/ # Slash commands
│ ├── skills/ # Skills
│ ├── skills/ # Global skills
│ ├── skill-library/ # Local skill templates (NEW)
│ └── project-files/ # Files created during init
├── cli/ # CLI tools
│ └── claude-os-consolidate.sh
@ -1044,13 +1160,19 @@ claude-os/
│ ├── core/ # Core modules
│ │ ├── sqlite_manager.py
│ │ ├── rag_engine.py
│ │ ├── skill_manager.py # NEW: Skills management
│ │ ├── session_parser.py # NEW: Session parsing
│ │ ├── insight_extractor.py # NEW: Insight extraction
│ │ └── ...
│ └── db/ # Database schemas
├── frontend/ # React UI (Vite)
│ ├── src/
│ │ ├── components/
│ │ │ ├── SkillsManagement.tsx # NEW: Skills UI
│ │ │ └── ...
│ │ └── pages/
│ └── public/
│ └── assets/
│ └── claude-os-hero.png
├── mcp_server/ # MCP Server (HTTP)
│ └── server.py # FastAPI + MCP endpoints
├── data/ # SQLite database

View file

@ -0,0 +1,312 @@
"""
Insight Extractor for Claude Code session transcripts.
Uses LLM to extract valuable insights (decisions, patterns, solutions, blockers).
"""
import json
import logging
from dataclasses import dataclass
from typing import List, Dict, Any, Optional
import httpx
from app.core.config import Config
logger = logging.getLogger(__name__)
@dataclass
class Insight:
"""An extracted insight from a session."""
type: str # "decision", "pattern", "solution", or "blocker"
title: str
content: str
confidence: float # 0.0 to 1.0
metadata: Optional[Dict[str, Any]] = None
# Extraction prompt template
EXTRACTION_PROMPT = """Analyze this Claude Code session transcript and extract valuable insights.
Look for:
1. **Decisions**: Technical choices made with reasoning (e.g., "Decided to use SQLite instead of PostgreSQL because...")
2. **Patterns**: Reusable approaches or code patterns discovered (e.g., "Session files use JSONL format with entry types...")
3. **Solutions**: Bug fixes, error resolutions, implementation approaches (e.g., "Fixed authentication error by updating token refresh logic...")
4. **Blockers**: Problems encountered, whether resolved or not (e.g., "Package installation failed due to Python version mismatch...")
For each insight:
- Provide a clear title (5-10 words)
- Write detailed content (2-4 sentences with specific technical details)
- Rate confidence (0.0-1.0) based on how explicit/important it is
- Categorize by type
**Only extract HIGH VALUE insights worth remembering for future sessions.**
Skip routine operations (ls, git status, simple file reads, etc.).
Focus on decisions, learnings, and technical discoveries.
Session transcript:
{transcript}
Return valid JSON only (no markdown, no extra text):
{{
"insights": [
{{"type": "decision|pattern|solution|blocker", "title": "Clear descriptive title", "content": "Detailed explanation with technical specifics...", "confidence": 0.85}}
]
}}
"""
class InsightExtractor:
"""Extract insights from session transcripts using LLM."""
def __init__(self, ollama_base_url: Optional[str] = None, model: Optional[str] = None):
"""
Initialize insight extractor.
Args:
ollama_base_url: Ollama API base URL (defaults to Config.OLLAMA_HOST)
model: LLM model to use (defaults to Config.OLLAMA_MODEL)
"""
self.ollama_base_url = ollama_base_url or Config.OLLAMA_HOST
self.model = model or Config.get_active_llm_model()
logger.info(f"Initialized InsightExtractor with model: {self.model}")
async def extract(
self,
session_summary: str,
insight_types: Optional[List[str]] = None
) -> List[Insight]:
"""
Extract insights from session summary using LLM.
Args:
session_summary: Condensed session transcript
insight_types: Filter to specific types (defaults to all types)
Returns:
List of Insight objects
"""
if insight_types is None:
insight_types = ["decision", "pattern", "solution", "blocker"]
logger.info(f"Extracting insights from session summary ({len(session_summary)} chars)")
# Build prompt
prompt = EXTRACTION_PROMPT.format(transcript=session_summary)
# Call Ollama
try:
insights = await self._call_ollama(prompt)
# Filter by requested types
filtered_insights = [
i for i in insights
if i.type in insight_types
]
logger.info(f"Extracted {len(filtered_insights)} insights (filtered from {len(insights)})")
return filtered_insights
except Exception as e:
logger.error(f"Failed to extract insights: {e}")
return []
async def _call_ollama(self, prompt: str) -> List[Insight]:
"""
Call Ollama API to extract insights.
Args:
prompt: Extraction prompt with session transcript
Returns:
List of Insight objects
"""
url = f"{self.ollama_base_url}/api/generate"
payload = {
"model": self.model,
"prompt": prompt,
"stream": False,
"format": "json", # Request JSON response
"options": {
"temperature": 0.3, # Lower temperature for more focused extraction
"num_predict": 2048, # Allow longer responses for multiple insights
"top_k": 40,
"top_p": 0.9
}
}
try:
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(url, json=payload)
response.raise_for_status()
result = response.json()
response_text = result.get("response", "")
# Parse JSON response
insights_data = self._parse_llm_response(response_text)
# Convert to Insight objects
insights = []
for item in insights_data.get("insights", []):
try:
insight = Insight(
type=item.get("type", "pattern"),
title=item.get("title", "Untitled"),
content=item.get("content", ""),
confidence=float(item.get("confidence", 0.5))
)
insights.append(insight)
except (ValueError, KeyError) as e:
logger.warning(f"Failed to parse insight item: {e}")
continue
return insights
except httpx.HTTPError as e:
logger.error(f"Ollama API error: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error calling Ollama: {e}")
raise
def _parse_llm_response(self, response_text: str) -> Dict[str, Any]:
"""
Parse LLM response JSON.
Handles cases where LLM might include markdown code blocks or extra text.
Args:
response_text: Raw LLM response
Returns:
Parsed JSON dict
"""
# Clean up response if it has markdown code blocks
cleaned_text = response_text.strip()
# Remove markdown code blocks if present
if cleaned_text.startswith("```json"):
cleaned_text = cleaned_text[7:] # Remove ```json
elif cleaned_text.startswith("```"):
cleaned_text = cleaned_text[3:] # Remove ```
if cleaned_text.endswith("```"):
cleaned_text = cleaned_text[:-3]
cleaned_text = cleaned_text.strip()
try:
return json.loads(cleaned_text)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse LLM JSON response: {e}")
logger.debug(f"Response text: {cleaned_text[:500]}")
# Return empty insights on parse failure
return {"insights": []}
def filter_by_confidence(
self,
insights: List[Insight],
min_confidence: float = 0.7
) -> List[Insight]:
"""
Filter insights by minimum confidence threshold.
Args:
insights: List of insights
min_confidence: Minimum confidence score (0.0-1.0)
Returns:
Filtered list of insights
"""
return [i for i in insights if i.confidence >= min_confidence]
def group_by_type(self, insights: List[Insight]) -> Dict[str, List[Insight]]:
"""
Group insights by type.
Args:
insights: List of insights
Returns:
Dict mapping type to list of insights
"""
grouped: Dict[str, List[Insight]] = {
"decision": [],
"pattern": [],
"solution": [],
"blocker": []
}
for insight in insights:
if insight.type in grouped:
grouped[insight.type].append(insight)
return grouped
def format_for_save(self, insight: Insight, session_id: str) -> str:
"""
Format insight as markdown for saving to knowledge base.
Args:
insight: Insight to format
session_id: Session ID for metadata
Returns:
Markdown string
"""
type_emoji = {
"decision": "💎",
"pattern": "🔄",
"solution": "✅",
"blocker": "🚧"
}
emoji = type_emoji.get(insight.type, "📝")
type_label = insight.type.capitalize()
markdown = f"""# {emoji} {insight.title}
**Type:** {type_label}
**Confidence:** {insight.confidence:.2f}
**Source:** Session `{session_id}`
## Details
{insight.content}
---
*Extracted from Claude Code session on {insight.metadata.get('extracted_date', 'unknown') if insight.metadata else 'unknown'}*
"""
return markdown
def extract_insights_sync(
session_summary: str,
insight_types: Optional[List[str]] = None,
min_confidence: float = 0.7
) -> List[Insight]:
"""
Synchronous wrapper for extract_insights (for non-async contexts).
Args:
session_summary: Condensed session transcript
insight_types: Filter to specific types
min_confidence: Minimum confidence threshold
Returns:
List of high-confidence Insight objects
"""
import asyncio
extractor = InsightExtractor()
# Run async extraction
loop = asyncio.get_event_loop()
insights = loop.run_until_complete(
extractor.extract(session_summary, insight_types)
)
# Filter by confidence
return extractor.filter_by_confidence(insights, min_confidence)

368
app/core/session_parser.py Normal file
View file

@ -0,0 +1,368 @@
"""
Session Parser for Claude Code built-in session files.
Parses .jsonl session files from ~/.claude/projects/ and extracts structured data.
"""
import json
import logging
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
logger = logging.getLogger(__name__)
@dataclass
class Message:
"""A user or assistant message."""
role: str # "user" or "assistant"
content: str
timestamp: str
uuid: str
parent_uuid: Optional[str] = None
@dataclass
class ToolCall:
"""A tool call made during the session."""
tool_name: str
timestamp: str
uuid: str
parent_uuid: Optional[str] = None
input_data: Optional[Dict[str, Any]] = None
@dataclass
class FileChange:
"""A file modification tracked during the session."""
file_path: str
timestamp: str
message_id: str
@dataclass
class SessionData:
"""Parsed session data."""
session_id: str
session_path: str
messages: List[Message] = field(default_factory=list)
tool_calls: List[ToolCall] = field(default_factory=list)
file_changes: List[FileChange] = field(default_factory=list)
start_time: Optional[str] = None
end_time: Optional[str] = None
git_branch: Optional[str] = None
cwd: Optional[str] = None
total_entries: int = 0
class SessionParser:
"""Parse Claude Code built-in session files (.jsonl format)."""
def __init__(self, session_path: str):
"""
Initialize parser with path to session file.
Args:
session_path: Path to .jsonl session file
"""
self.path = Path(session_path).expanduser()
if not self.path.exists():
raise FileNotFoundError(f"Session file not found: {self.path}")
self.session_id = self.path.stem
self.entries: List[Dict[str, Any]] = []
def parse(self) -> SessionData:
"""
Parse JSONL file and return structured session data.
Returns:
SessionData object with parsed information
"""
logger.info(f"Parsing session file: {self.path}")
# Read JSONL file
with open(self.path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
self.entries.append(entry)
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse line: {e}")
continue
# Build session data
session_data = SessionData(
session_id=self.session_id,
session_path=str(self.path),
total_entries=len(self.entries)
)
# Extract metadata from first entry if available
for entry in self.entries:
if entry.get("type") in ["user", "assistant"]:
if not session_data.cwd:
session_data.cwd = entry.get("cwd")
if not session_data.git_branch:
session_data.git_branch = entry.get("gitBranch")
break
# Parse entries
for entry in self.entries:
entry_type = entry.get("type")
if entry_type == "user":
self._parse_user_message(entry, session_data)
elif entry_type == "assistant":
self._parse_assistant_message(entry, session_data)
elif entry_type == "file-history-snapshot":
self._parse_file_snapshot(entry, session_data)
# Set start/end times from messages
if session_data.messages:
session_data.start_time = session_data.messages[0].timestamp
session_data.end_time = session_data.messages[-1].timestamp
logger.info(
f"Parsed session {self.session_id}: "
f"{len(session_data.messages)} messages, "
f"{len(session_data.tool_calls)} tool calls, "
f"{len(session_data.file_changes)} file changes"
)
return session_data
def _parse_user_message(self, entry: Dict[str, Any], session_data: SessionData) -> None:
"""Parse a user message entry."""
message_obj = entry.get("message", {})
content = message_obj.get("content", "")
session_data.messages.append(Message(
role="user",
content=content,
timestamp=entry.get("timestamp", ""),
uuid=entry.get("uuid", ""),
parent_uuid=entry.get("parentUuid")
))
def _parse_assistant_message(self, entry: Dict[str, Any], session_data: SessionData) -> None:
"""Parse an assistant message entry."""
message_obj = entry.get("message", {})
content_parts = message_obj.get("content", [])
# Extract text content
text_content = []
for part in content_parts:
if isinstance(part, dict):
if part.get("type") == "text":
text_content.append(part.get("text", ""))
elif part.get("type") == "tool_use":
# Track tool calls
tool_call = ToolCall(
tool_name=part.get("name", ""),
timestamp=entry.get("timestamp", ""),
uuid=entry.get("uuid", ""),
parent_uuid=entry.get("parentUuid"),
input_data=part.get("input")
)
session_data.tool_calls.append(tool_call)
# Only add message if there's text content
if text_content:
session_data.messages.append(Message(
role="assistant",
content="\n".join(text_content),
timestamp=entry.get("timestamp", ""),
uuid=entry.get("uuid", ""),
parent_uuid=entry.get("parentUuid")
))
def _parse_file_snapshot(self, entry: Dict[str, Any], session_data: SessionData) -> None:
"""Parse a file history snapshot entry."""
snapshot = entry.get("snapshot", {})
tracked_files = snapshot.get("trackedFileBackups", {})
for file_path in tracked_files.keys():
session_data.file_changes.append(FileChange(
file_path=file_path,
timestamp=snapshot.get("timestamp", ""),
message_id=entry.get("messageId", "")
))
def get_conversation(self) -> List[Message]:
"""
Extract user/assistant messages only (no tool calls).
Returns:
List of Message objects in chronological order
"""
if not self.entries:
session_data = self.parse()
else:
session_data = self.parse()
return session_data.messages
def get_tool_results(self) -> List[ToolCall]:
"""
Extract tool calls and results.
Returns:
List of ToolCall objects
"""
if not self.entries:
session_data = self.parse()
else:
session_data = self.parse()
return session_data.tool_calls
def get_file_changes(self) -> List[FileChange]:
"""
Extract file modifications from snapshots.
Returns:
List of FileChange objects
"""
if not self.entries:
session_data = self.parse()
else:
session_data = self.parse()
return session_data.file_changes
def get_summary_for_extraction(self, max_tokens: int = 8000) -> str:
"""
Build condensed conversation summary for LLM extraction.
Focuses on user/assistant messages and major tool calls.
Truncates to fit within token limit (roughly 4 chars per token).
Args:
max_tokens: Maximum tokens for summary (default: 8000)
Returns:
Condensed conversation string
"""
session_data = self.parse()
summary_parts = []
summary_parts.append(f"# Session: {self.session_id}")
summary_parts.append(f"Project: {session_data.cwd or 'Unknown'}")
summary_parts.append(f"Branch: {session_data.git_branch or 'Unknown'}")
summary_parts.append(f"Duration: {session_data.start_time} to {session_data.end_time}")
summary_parts.append(f"\n## Conversation ({len(session_data.messages)} messages)\n")
# Add messages
for msg in session_data.messages:
role_prefix = "USER" if msg.role == "user" else "ASSISTANT"
# Truncate very long messages
content = msg.content[:1000] if len(msg.content) > 1000 else msg.content
summary_parts.append(f"{role_prefix}: {content}\n")
# Add tool calls summary
if session_data.tool_calls:
summary_parts.append(f"\n## Tool Calls ({len(session_data.tool_calls)})\n")
for tool in session_data.tool_calls[:20]: # Limit to first 20
summary_parts.append(f"- {tool.tool_name}")
if tool.input_data:
# Show first few input keys
keys = list(tool.input_data.keys())[:3]
summary_parts.append(f" ({', '.join(keys)})")
summary_parts.append("\n")
# Add file changes summary
if session_data.file_changes:
summary_parts.append(f"\n## Files Modified ({len(session_data.file_changes)})\n")
unique_files = list(set(fc.file_path for fc in session_data.file_changes))
for file_path in unique_files[:10]: # Limit to first 10
summary_parts.append(f"- {file_path}\n")
full_summary = "".join(summary_parts)
# Truncate if needed (roughly 4 chars per token)
max_chars = max_tokens * 4
if len(full_summary) > max_chars:
full_summary = full_summary[:max_chars] + "\n\n[... truncated ...]"
return full_summary
def get_project_sessions_dir(project_path: str) -> Path:
"""
Get the Claude Code sessions directory for a project path.
Claude Code stores sessions in ~/.claude/projects/{encoded-path}/
where path like "/Users/user/Projects/myapp" becomes "-Users-user-Projects-myapp"
Args:
project_path: Absolute path to project directory
Returns:
Path to sessions directory
"""
# Encode path: replace slashes with hyphens
# Claude keeps the leading hyphen (e.g., "-Users-iamanmp-Projects-claude-os")
encoded_path = project_path.replace("/", "-")
sessions_dir = Path.home() / ".claude" / "projects" / encoded_path
return sessions_dir
def list_session_files(project_path: str, limit: int = 50) -> List[Dict[str, Any]]:
"""
List session files for a project.
Args:
project_path: Absolute path to project directory
limit: Maximum number of sessions to return (most recent first)
Returns:
List of session file info dicts
"""
sessions_dir = get_project_sessions_dir(project_path)
if not sessions_dir.exists():
logger.warning(f"Sessions directory not found: {sessions_dir}")
return []
# Find all .jsonl files
session_files = list(sessions_dir.glob("*.jsonl"))
# Sort by modification time (most recent first)
session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
# Limit results
session_files = session_files[:limit]
# Build info list
sessions_info = []
for session_file in session_files:
stat = session_file.stat()
# Try to get basic info without full parse
session_id = session_file.stem
created_time = datetime.fromtimestamp(stat.st_ctime).isoformat()
modified_time = datetime.fromtimestamp(stat.st_mtime).isoformat()
# Count lines to estimate message count
try:
with open(session_file, 'r') as f:
line_count = sum(1 for _ in f)
except Exception:
line_count = 0
sessions_info.append({
"id": session_id,
"path": str(session_file),
"created": created_time,
"modified": modified_time,
"size_bytes": stat.st_size,
"estimated_entries": line_count
})
return sessions_info

992
app/core/skill_manager.py Normal file
View file

@ -0,0 +1,992 @@
"""
Skill Manager for Claude Code skills.
Manages global skills (~/.claude/skills/) and project-level skills ({project}/.claude/skills/).
"""
import json
import logging
import shutil
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
logger = logging.getLogger(__name__)
@dataclass
class Skill:
"""A Claude Code skill."""
name: str
path: str
description: str
scope: str # "global" or "project"
source: str # "claude-os-core", "template", "custom"
content: str = ""
enabled: bool = True
category: Optional[str] = None
tags: List[str] = field(default_factory=list)
created: Optional[str] = None
modified: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"name": self.name,
"path": self.path,
"description": self.description,
"scope": self.scope,
"source": self.source,
"content": self.content,
"enabled": self.enabled,
"category": self.category,
"tags": self.tags,
"created": self.created,
"modified": self.modified
}
@dataclass
class SkillTemplate:
"""A skill template that can be installed."""
name: str
category: str
description: str
path: str
tags: List[str] = field(default_factory=list)
version: str = "1.0.0"
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"name": self.name,
"category": self.category,
"description": self.description,
"path": self.path,
"tags": self.tags,
"version": self.version
}
class SkillManager:
"""Manage Claude Code skills (global and project-level)."""
GLOBAL_SKILLS_DIR = Path.home() / ".claude" / "skills"
TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" / "skill-library"
# Core skills that should remain global
CORE_SKILLS = {"memory", "remember-this"}
def __init__(self, project_path: Optional[str] = None):
"""
Initialize skill manager.
Args:
project_path: Optional project path for project-level operations
"""
self.project_path = Path(project_path) if project_path else None
# Ensure global skills directory exists
self.GLOBAL_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
@property
def project_skills_dir(self) -> Optional[Path]:
"""Get project skills directory."""
if self.project_path:
return self.project_path / ".claude" / "skills"
return None
def list_global_skills(self) -> List[Skill]:
"""
List skills in ~/.claude/skills/
Returns:
List of global Skill objects
"""
skills = []
if not self.GLOBAL_SKILLS_DIR.exists():
return skills
for item in self.GLOBAL_SKILLS_DIR.iterdir():
if item.is_dir() or (item.is_symlink() and item.resolve().is_dir()):
try:
skill = self._parse_skill(item, scope="global")
if skill:
skills.append(skill)
except Exception as e:
logger.warning(f"Failed to parse skill {item.name}: {e}")
return sorted(skills, key=lambda s: s.name)
def list_project_skills(self) -> List[Skill]:
"""
List skills in {project}/.claude/skills/
Returns:
List of project Skill objects
"""
skills = []
if not self.project_skills_dir or not self.project_skills_dir.exists():
return skills
for item in self.project_skills_dir.iterdir():
if item.is_dir():
try:
skill = self._parse_skill(item, scope="project")
if skill:
skills.append(skill)
except Exception as e:
logger.warning(f"Failed to parse project skill {item.name}: {e}")
return sorted(skills, key=lambda s: s.name)
def list_all_skills(self, include_content: bool = False) -> Dict[str, List[Skill]]:
"""
List all skills (global and project).
Args:
include_content: Whether to include skill content
Returns:
Dict with "global" and "project" skill lists
"""
global_skills = self.list_global_skills()
project_skills = self.list_project_skills()
if not include_content:
for skill in global_skills + project_skills:
skill.content = ""
return {
"global": global_skills,
"project": project_skills
}
def list_templates(self, category: Optional[str] = None) -> List[SkillTemplate]:
"""
List available skill templates.
Args:
category: Optional category filter
Returns:
List of SkillTemplate objects
"""
templates = []
if not self.TEMPLATES_DIR.exists():
logger.warning(f"Templates directory not found: {self.TEMPLATES_DIR}")
return templates
for category_dir in self.TEMPLATES_DIR.iterdir():
if not category_dir.is_dir():
continue
if category and category_dir.name != category:
continue
for template_dir in category_dir.iterdir():
if not template_dir.is_dir():
continue
try:
template = self._parse_template(template_dir, category_dir.name)
if template:
templates.append(template)
except Exception as e:
logger.warning(f"Failed to parse template {template_dir.name}: {e}")
return sorted(templates, key=lambda t: (t.category, t.name))
def get_template_categories(self) -> List[str]:
"""
Get list of template categories.
Returns:
List of category names
"""
categories = []
if not self.TEMPLATES_DIR.exists():
return categories
for item in self.TEMPLATES_DIR.iterdir():
if item.is_dir():
categories.append(item.name)
return sorted(categories)
def get_skill(self, name: str, scope: str) -> Optional[Skill]:
"""
Get skill details including content.
Args:
name: Skill name
scope: "global" or "project"
Returns:
Skill object or None if not found
"""
if scope == "global":
skill_path = self.GLOBAL_SKILLS_DIR / name
elif scope == "project" and self.project_skills_dir:
skill_path = self.project_skills_dir / name
else:
return None
if not skill_path.exists():
# Check if it's a symlink
if skill_path.is_symlink():
skill_path = skill_path.resolve()
else:
return None
return self._parse_skill(skill_path, scope, include_content=True)
def install_template(
self,
template_name: str,
custom_name: Optional[str] = None
) -> Skill:
"""
Install a skill template to the project.
Args:
template_name: Name of template to install
custom_name: Optional custom name for the installed skill
Returns:
Installed Skill object
Raises:
ValueError: If template not found or no project path set
FileExistsError: If skill already exists
"""
if not self.project_path:
raise ValueError("No project path set")
# Find template
template = None
for t in self.list_templates():
if t.name == template_name:
template = t
break
if not template:
raise ValueError(f"Template not found: {template_name}")
# Determine target name and path
skill_name = custom_name or template_name
target_dir = self.project_skills_dir / skill_name
if target_dir.exists():
raise FileExistsError(f"Skill already exists: {skill_name}")
# Create project skills directory if needed
self.project_skills_dir.mkdir(parents=True, exist_ok=True)
# Copy template to project
template_path = Path(template.path)
shutil.copytree(template_path, target_dir)
# Update metadata if renamed
if custom_name:
metadata_file = target_dir / "metadata.json"
if metadata_file.exists():
with open(metadata_file, 'r') as f:
metadata = json.load(f)
metadata["name"] = custom_name
metadata["source"] = "template"
metadata["installed_from"] = template_name
metadata["installed_at"] = datetime.now().isoformat()
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2)
logger.info(f"Installed template {template_name} as {skill_name}")
return self._parse_skill(target_dir, scope="project", include_content=True)
def create_skill(
self,
name: str,
description: str,
content: str,
category: Optional[str] = None,
tags: Optional[List[str]] = None
) -> Skill:
"""
Create a custom skill for the project.
Args:
name: Skill name
description: Skill description
content: Skill content (markdown)
category: Optional category
tags: Optional tags
Returns:
Created Skill object
Raises:
ValueError: If no project path set
FileExistsError: If skill already exists
"""
if not self.project_path:
raise ValueError("No project path set")
target_dir = self.project_skills_dir / name
if target_dir.exists():
raise FileExistsError(f"Skill already exists: {name}")
# Create directories
self.project_skills_dir.mkdir(parents=True, exist_ok=True)
target_dir.mkdir()
# Write skill content
skill_file = target_dir / "skill.md"
with open(skill_file, 'w') as f:
f.write(content)
# Write metadata
metadata = {
"name": name,
"description": description,
"category": category,
"tags": tags or [],
"source": "custom",
"created_at": datetime.now().isoformat(),
"version": "1.0.0"
}
metadata_file = target_dir / "metadata.json"
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2)
logger.info(f"Created custom skill: {name}")
return self._parse_skill(target_dir, scope="project", include_content=True)
def update_skill(
self,
name: str,
content: Optional[str] = None,
description: Optional[str] = None,
tags: Optional[List[str]] = None
) -> Skill:
"""
Update an existing project skill.
Args:
name: Skill name
content: New content (optional)
description: New description (optional)
tags: New tags (optional)
Returns:
Updated Skill object
Raises:
ValueError: If no project path set
FileNotFoundError: If skill not found
"""
if not self.project_path:
raise ValueError("No project path set")
skill_dir = self.project_skills_dir / name
if not skill_dir.exists():
raise FileNotFoundError(f"Skill not found: {name}")
# Update content
if content is not None:
skill_file = skill_dir / "skill.md"
with open(skill_file, 'w') as f:
f.write(content)
# Update metadata
metadata_file = skill_dir / "metadata.json"
if metadata_file.exists():
with open(metadata_file, 'r') as f:
metadata = json.load(f)
else:
metadata = {"name": name}
if description is not None:
metadata["description"] = description
if tags is not None:
metadata["tags"] = tags
metadata["modified_at"] = datetime.now().isoformat()
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2)
logger.info(f"Updated skill: {name}")
return self._parse_skill(skill_dir, scope="project", include_content=True)
def delete_skill(self, name: str) -> bool:
"""
Delete a project skill.
Args:
name: Skill name
Returns:
True if deleted
Raises:
ValueError: If no project path set or trying to delete global skill
FileNotFoundError: If skill not found
"""
if not self.project_path:
raise ValueError("No project path set")
skill_dir = self.project_skills_dir / name
if not skill_dir.exists():
raise FileNotFoundError(f"Skill not found: {name}")
shutil.rmtree(skill_dir)
logger.info(f"Deleted skill: {name}")
return True
def _parse_skill(
self,
skill_path: Path,
scope: str,
include_content: bool = False
) -> Optional[Skill]:
"""
Parse a skill directory into a Skill object.
Args:
skill_path: Path to skill directory
scope: "global" or "project"
include_content: Whether to include skill content
Returns:
Skill object or None if invalid
"""
# Resolve symlinks
if skill_path.is_symlink():
resolved_path = skill_path.resolve()
else:
resolved_path = skill_path
if not resolved_path.exists() or not resolved_path.is_dir():
return None
name = skill_path.name
# Determine source
if name in self.CORE_SKILLS and scope == "global":
source = "claude-os-core"
elif scope == "global":
source = "template"
else:
source = "custom"
# Load metadata if exists
metadata_file = resolved_path / "metadata.json"
metadata = {}
if metadata_file.exists():
try:
with open(metadata_file, 'r') as f:
metadata = json.load(f)
source = metadata.get("source", source)
except Exception as e:
logger.warning(f"Failed to load metadata for {name}: {e}")
# Load content
content = ""
if include_content:
skill_file = resolved_path / "skill.md"
if skill_file.exists():
with open(skill_file, 'r') as f:
content = f.read()
else:
# Try other common names
for filename in ["README.md", "index.md", f"{name}.md"]:
alt_file = resolved_path / filename
if alt_file.exists():
with open(alt_file, 'r') as f:
content = f.read()
break
# Extract description from content if not in metadata
description = metadata.get("description", "")
if not description and content:
# Try to extract from first paragraph
lines = content.strip().split('\n')
for line in lines:
line = line.strip()
if line and not line.startswith('#'):
description = line[:200]
break
# Get file stats
stat = resolved_path.stat()
created = datetime.fromtimestamp(stat.st_ctime).isoformat()
modified = datetime.fromtimestamp(stat.st_mtime).isoformat()
return Skill(
name=name,
path=str(skill_path),
description=description,
scope=scope,
source=source,
content=content,
enabled=True,
category=metadata.get("category"),
tags=metadata.get("tags", []),
created=metadata.get("created_at", created),
modified=metadata.get("modified_at", modified)
)
def _parse_template(self, template_path: Path, category: str) -> Optional[SkillTemplate]:
"""
Parse a template directory into a SkillTemplate object.
Args:
template_path: Path to template directory
category: Template category
Returns:
SkillTemplate object or None if invalid
"""
if not template_path.is_dir():
return None
name = template_path.name
# Load metadata
metadata_file = template_path / "metadata.json"
metadata = {}
if metadata_file.exists():
try:
with open(metadata_file, 'r') as f:
metadata = json.load(f)
except Exception as e:
logger.warning(f"Failed to load template metadata for {name}: {e}")
# Get description
description = metadata.get("description", "")
if not description:
# Try to extract from skill.md
skill_file = template_path / "skill.md"
if skill_file.exists():
with open(skill_file, 'r') as f:
content = f.read()
lines = content.strip().split('\n')
for line in lines:
line = line.strip()
if line and not line.startswith('#'):
description = line[:200]
break
return SkillTemplate(
name=name,
category=category,
description=description,
path=str(template_path),
tags=metadata.get("tags", []),
version=metadata.get("version", "1.0.0")
)
# Convenience functions
def list_skills(project_path: Optional[str] = None) -> Dict[str, List[Dict]]:
"""
List all skills.
Args:
project_path: Optional project path
Returns:
Dict with global and project skill lists as dicts
"""
manager = SkillManager(project_path)
skills = manager.list_all_skills()
return {
"global": [s.to_dict() for s in skills["global"]],
"project": [s.to_dict() for s in skills["project"]]
}
def list_skill_templates(category: Optional[str] = None) -> List[Dict]:
"""
List available skill templates.
Args:
category: Optional category filter
Returns:
List of template dicts
"""
manager = SkillManager()
templates = manager.list_templates(category)
return [t.to_dict() for t in templates]
# ============================================================================
# COMMUNITY SKILLS - Fetch from external GitHub repositories
# ============================================================================
import httpx
import base64
import re
import tempfile
@dataclass
class CommunitySkill:
"""A skill from an external community repository."""
name: str
description: str
source: str # "anthropic", "superpowers", etc.
repo: str # Full repo path e.g., "anthropics/skills"
path: str # Path within repo e.g., "skills/frontend-design"
tags: List[str] = field(default_factory=list)
url: str = "" # GitHub URL
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"name": self.name,
"description": self.description,
"source": self.source,
"repo": self.repo,
"path": self.path,
"tags": self.tags,
"url": self.url
}
# Community skill sources configuration
COMMUNITY_SOURCES = {
"anthropic": {
"repo": "anthropics/skills",
"skills_path": "skills",
"name": "Anthropic Official",
"description": "Official skills from Anthropic"
},
"superpowers": {
"repo": "obra/superpowers",
"skills_path": "skills",
"name": "Superpowers",
"description": "Battle-tested skills for TDD, debugging, and collaboration"
}
}
class CommunitySkillsManager:
"""Fetch and install skills from community GitHub repositories."""
GITHUB_API = "https://api.github.com"
GITHUB_RAW = "https://raw.githubusercontent.com"
CACHE_TTL = 3600 # Cache for 1 hour
def __init__(self):
self._cache: Dict[str, Any] = {}
self._cache_times: Dict[str, float] = {}
def _is_cache_valid(self, key: str) -> bool:
"""Check if cache entry is still valid."""
if key not in self._cache_times:
return False
import time
return (time.time() - self._cache_times[key]) < self.CACHE_TTL
def _set_cache(self, key: str, value: Any) -> None:
"""Set cache entry."""
import time
self._cache[key] = value
self._cache_times[key] = time.time()
async def list_community_skills(self, source: Optional[str] = None) -> List[CommunitySkill]:
"""
List skills from community repositories.
Args:
source: Optional source filter ("anthropic", "superpowers", etc.)
Returns:
List of CommunitySkill objects
"""
skills = []
sources = {source: COMMUNITY_SOURCES[source]} if source else COMMUNITY_SOURCES
for source_key, config in sources.items():
try:
source_skills = await self._fetch_skills_from_repo(source_key, config)
skills.extend(source_skills)
except Exception as e:
logger.warning(f"Failed to fetch skills from {source_key}: {e}")
return skills
async def _fetch_skills_from_repo(
self,
source_key: str,
config: Dict[str, str]
) -> List[CommunitySkill]:
"""Fetch skill list from a GitHub repository."""
cache_key = f"skills_{source_key}"
if self._is_cache_valid(cache_key):
return self._cache[cache_key]
repo = config["repo"]
skills_path = config["skills_path"]
# Fetch directory listing from GitHub API
url = f"{self.GITHUB_API}/repos/{repo}/contents/{skills_path}"
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=15.0)
if response.status_code != 200:
logger.error(f"GitHub API error for {repo}: {response.status_code}")
return []
items = response.json()
skills = []
for item in items:
if item.get("type") != "dir":
continue
skill_name = item["name"]
skill_path = f"{skills_path}/{skill_name}"
# Try to fetch SKILL.md to get description
description = await self._fetch_skill_description(repo, skill_path)
skill = CommunitySkill(
name=skill_name,
description=description,
source=source_key,
repo=repo,
path=skill_path,
tags=self._infer_tags(skill_name, description),
url=f"https://github.com/{repo}/tree/main/{skill_path}"
)
skills.append(skill)
self._set_cache(cache_key, skills)
return skills
async def _fetch_skill_description(self, repo: str, skill_path: str) -> str:
"""Fetch skill description from SKILL.md."""
# Try both SKILL.md and skill.md
for filename in ["SKILL.md", "skill.md"]:
url = f"{self.GITHUB_RAW}/{repo}/main/{skill_path}/{filename}"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0)
if response.status_code == 200:
content = response.text
# Extract description from YAML frontmatter or first paragraph
return self._extract_description(content)
except Exception:
continue
return ""
def _extract_description(self, content: str) -> str:
"""Extract description from skill markdown content."""
# Try YAML frontmatter first
frontmatter_match = re.match(r'^---\s*\n(.*?)\n---', content, re.DOTALL)
if frontmatter_match:
frontmatter = frontmatter_match.group(1)
desc_match = re.search(r'description:\s*["\']?([^"\'\n]+)["\']?', frontmatter)
if desc_match:
return desc_match.group(1).strip()[:200]
# Fall back to first non-header paragraph
lines = content.split('\n')
for line in lines:
line = line.strip()
if line and not line.startswith('#') and not line.startswith('---'):
return line[:200]
return ""
def _infer_tags(self, name: str, description: str) -> List[str]:
"""Infer tags from skill name and description."""
tags = []
text = f"{name} {description}".lower()
tag_keywords = {
"testing": ["test", "tdd", "spec", "jest", "pytest", "rspec"],
"debugging": ["debug", "error", "fix", "trace"],
"frontend": ["frontend", "react", "vue", "ui", "css", "design"],
"backend": ["backend", "api", "server", "database"],
"documentation": ["doc", "readme", "markdown", "write"],
"automation": ["automat", "script", "workflow"],
"design": ["design", "canvas", "art", "visual", "brand"],
"git": ["git", "commit", "branch", "merge"],
}
for tag, keywords in tag_keywords.items():
if any(kw in text for kw in keywords):
tags.append(tag)
return tags
async def get_skill_content(self, repo: str, skill_path: str) -> Dict[str, str]:
"""
Fetch full skill content from GitHub.
Returns:
Dict with filename -> content mappings
"""
# Get list of files in skill directory
url = f"{self.GITHUB_API}/repos/{repo}/contents/{skill_path}"
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=15.0)
if response.status_code != 200:
raise ValueError(f"Failed to fetch skill: {response.status_code}")
items = response.json()
contents = {}
for item in items:
if item.get("type") != "file":
continue
filename = item["name"]
# Fetch file content
file_url = f"{self.GITHUB_RAW}/{repo}/main/{skill_path}/{filename}"
file_response = await client.get(file_url, timeout=10.0)
if file_response.status_code == 200:
contents[filename] = file_response.text
return contents
async def install_community_skill(
self,
skill: CommunitySkill,
project_path: str,
custom_name: Optional[str] = None
) -> Skill:
"""
Install a community skill to a project.
Args:
skill: CommunitySkill to install
project_path: Target project path
custom_name: Optional custom name for the skill
Returns:
Installed Skill object
"""
# Fetch skill content
contents = await self.get_skill_content(skill.repo, skill.path)
if not contents:
raise ValueError(f"No content found for skill: {skill.name}")
# Determine target directory
skill_name = custom_name or skill.name
target_dir = Path(project_path) / ".claude" / "skills" / skill_name
if target_dir.exists():
raise FileExistsError(f"Skill already exists: {skill_name}")
# Create directory
target_dir.mkdir(parents=True, exist_ok=True)
# Write files
for filename, content in contents.items():
# Normalize SKILL.md to skill.md
if filename.upper() == "SKILL.MD":
filename = "skill.md"
file_path = target_dir / filename
with open(file_path, 'w') as f:
f.write(content)
# Create/update metadata
metadata_file = target_dir / "metadata.json"
metadata = {
"name": skill_name,
"description": skill.description,
"source": f"community:{skill.source}",
"original_repo": skill.repo,
"original_path": skill.path,
"installed_at": datetime.now().isoformat(),
"tags": skill.tags
}
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2)
logger.info(f"Installed community skill {skill.name} from {skill.source}")
# Return as Skill object
manager = SkillManager(project_path)
return manager._parse_skill(target_dir, scope="project", include_content=True)
# Singleton instance
_community_manager: Optional[CommunitySkillsManager] = None
def get_community_manager() -> CommunitySkillsManager:
"""Get or create the community skills manager singleton."""
global _community_manager
if _community_manager is None:
_community_manager = CommunitySkillsManager()
return _community_manager
async def list_community_skills(source: Optional[str] = None) -> List[Dict]:
"""
List available community skills.
Args:
source: Optional source filter
Returns:
List of community skill dicts
"""
manager = get_community_manager()
skills = await manager.list_community_skills(source)
return [s.to_dict() for s in skills]
async def install_community_skill(
source: str,
skill_name: str,
project_path: str,
custom_name: Optional[str] = None
) -> Dict:
"""
Install a community skill to a project.
Args:
source: Source key ("anthropic", "superpowers")
skill_name: Name of the skill
project_path: Target project path
custom_name: Optional custom name
Returns:
Installed skill dict
"""
manager = get_community_manager()
# Find the skill
skills = await manager.list_community_skills(source)
skill = next((s for s in skills if s.name == skill_name), None)
if not skill:
raise ValueError(f"Skill not found: {skill_name} in {source}")
installed = await manager.install_community_skill(skill, project_path, custom_name)
return installed.to_dict()

View file

@ -11,13 +11,15 @@ Complete reference for all Claude OS MCP Server API endpoints.
1. [Knowledge Base Operations](#knowledge-base-operations)
2. [Hybrid Indexing](#hybrid-indexing-new)
3. [Project Management](#project-management)
4. [Agent-OS Spec Tracking](#agent-os-spec-tracking-new)
5. [Real-Time Spec Watcher](#real-time-spec-watcher-new)
6. [Hooks System](#hooks-system)
7. [File Watcher](#file-watcher)
8. [Authentication](#authentication)
9. [Utilities](#utilities)
10. [Health Check](#health-check)
4. [Skills Management](#skills-management-new)
5. [Session Parsing](#session-parsing-new)
6. [Agent-OS Spec Tracking](#agent-os-spec-tracking-new)
7. [Real-Time Spec Watcher](#real-time-spec-watcher-new)
8. [Hooks System](#hooks-system)
9. [File Watcher](#file-watcher)
10. [Authentication](#authentication)
11. [Utilities](#utilities)
12. [Health Check](#health-check)
---
@ -495,6 +497,353 @@ DELETE /api/projects/{id}
---
## Skills Management (NEW)
Manage Claude Code skills - list, install, create, and configure.
### List All Skills
```http
GET /api/skills?project_path=/path/to/project&include_content=false
```
Returns global and project-level skills.
**Response:**
```json
{
"global": [
{
"name": "memory",
"path": "/Users/username/.claude/skills/memory",
"description": "Save and recall information",
"scope": "global",
"source": "custom",
"enabled": true,
"category": null,
"tags": []
}
],
"project": [
{
"name": "rails-backend",
"path": "/path/to/project/.claude/skills/rails-backend",
"description": "Rails patterns and service objects",
"scope": "project",
"source": "template",
"enabled": true,
"category": "rails",
"tags": ["ruby", "rails", "backend"]
}
]
}
```
---
### List Skill Templates
```http
GET /api/skills/templates
```
Returns available local templates organized by category.
**Response:**
```json
{
"templates": [
{
"name": "rails-backend",
"category": "rails",
"description": "Rails patterns and service objects",
"path": "/path/to/claude-os/templates/skill-library/rails/rails-backend",
"tags": ["ruby", "rails", "backend"],
"version": "1.0.0"
}
],
"categories": ["general", "rails", "react", "testing"]
}
```
---
### Install Skill Template
```http
POST /api/skills/install?project_path=/path/to/project
Content-Type: application/json
{
"template_name": "rails-backend"
}
```
**Response:**
```json
{
"success": true,
"skill": {
"name": "rails-backend",
"path": "/path/to/project/.claude/skills/rails-backend",
"scope": "project",
"source": "template"
},
"message": "Installed skill 'rails-backend' to project"
}
```
---
### Create Custom Skill
```http
POST /api/skills?project_path=/path/to/project
Content-Type: application/json
{
"name": "my-skill",
"description": "My custom skill",
"content": "# My Skill\n\nSkill instructions here...",
"category": "custom",
"tags": ["custom"]
}
```
**Response:**
```json
{
"success": true,
"skill": {
"name": "my-skill",
"path": "/path/to/project/.claude/skills/my-skill",
"scope": "project",
"source": "custom"
}
}
```
---
### Get Skill Details
```http
GET /api/skills/{scope}/{name}?project_path=/path/to/project
```
**Parameters:**
- `scope`: `global` or `project`
- `name`: Skill name
**Response:**
```json
{
"name": "rails-backend",
"path": "/path/to/project/.claude/skills/rails-backend",
"description": "Rails patterns and service objects",
"scope": "project",
"source": "template",
"content": "# Rails Backend\n\n...",
"enabled": true,
"category": "rails",
"tags": ["ruby", "rails", "backend"],
"created": "2025-12-11T10:00:00Z",
"modified": "2025-12-11T10:00:00Z"
}
```
---
### Delete Skill
```http
DELETE /api/skills/{name}?project_path=/path/to/project
```
**Response:**
```json
{
"success": true,
"message": "Deleted skill 'my-skill' from project"
}
```
---
### List Community Sources
```http
GET /api/skills/community/sources
```
**Response:**
```json
{
"sources": {
"anthropic": {
"repo": "anthropics/skills",
"skills_path": "skills",
"name": "Anthropic Official",
"description": "Official skills from Anthropic"
},
"superpowers": {
"repo": "obra/superpowers",
"skills_path": "skills",
"name": "Superpowers",
"description": "Battle-tested skills for TDD, debugging, and collaboration"
}
}
}
```
---
### List Community Skills
```http
GET /api/skills/community?source=anthropic
```
**Parameters:**
- `source` (optional): Filter by source (`anthropic`, `superpowers`)
**Response:**
```json
{
"skills": [
{
"name": "pdf",
"source": "anthropic",
"repo": "anthropics/skills",
"path": "skills/pdf",
"description": "Comprehensive PDF manipulation toolkit...",
"readme_url": "https://github.com/anthropics/skills/tree/main/skills/pdf",
"raw_url": "https://raw.githubusercontent.com/anthropics/skills/main/skills/pdf"
}
],
"sources": {...},
"total": 36
}
```
---
### Install Community Skill
```http
POST /api/skills/community/install?project_path=/path/to/project
Content-Type: application/json
{
"name": "pdf",
"source": "anthropic",
"repo": "anthropics/skills",
"path": "skills/pdf",
"description": "PDF manipulation toolkit",
"readme_url": "https://github.com/anthropics/skills/tree/main/skills/pdf",
"raw_url": "https://raw.githubusercontent.com/anthropics/skills/main/skills/pdf"
}
```
**Response:**
```json
{
"success": true,
"skill": {
"name": "pdf",
"path": "/path/to/project/.claude/skills/pdf",
"scope": "project",
"source": "community:anthropic"
},
"message": "Installed community skill 'pdf' from anthropic"
}
```
---
## Session Parsing (NEW)
Parse Claude Code session files and extract insights.
### List Project Sessions
```http
GET /api/sessions?project_path=/path/to/project&limit=10
```
**Response:**
```json
{
"sessions": [
{
"session_id": "abc123",
"session_path": "/Users/username/.claude/projects/-path-to-project/abc123.jsonl",
"start_time": "2025-12-11T10:00:00Z",
"end_time": "2025-12-11T11:30:00Z",
"message_count": 24,
"tool_calls": 15,
"file_changes": 8
}
],
"total": 42
}
```
---
### Get Session Details
```http
GET /api/sessions/{session_id}?project_path=/path/to/project
```
**Response:**
```json
{
"session_id": "abc123",
"session_path": "/Users/username/.claude/projects/-path-to-project/abc123.jsonl",
"messages": [
{
"role": "user",
"content": "Help me fix the authentication bug",
"timestamp": "2025-12-11T10:00:00Z",
"uuid": "msg-001"
},
{
"role": "assistant",
"content": "I'll help you fix that...",
"timestamp": "2025-12-11T10:00:05Z",
"uuid": "msg-002"
}
],
"tool_calls": [
{
"tool_name": "Read",
"timestamp": "2025-12-11T10:00:10Z",
"input_data": {"file_path": "/path/to/auth.py"}
}
],
"file_changes": [
{
"file_path": "/path/to/auth.py",
"timestamp": "2025-12-11T10:05:00Z"
}
],
"start_time": "2025-12-11T10:00:00Z",
"end_time": "2025-12-11T11:30:00Z",
"git_branch": "fix-auth-bug",
"cwd": "/path/to/project"
}
```
---
### Get Session Summary
```http
GET /api/sessions/{session_id}/summary?project_path=/path/to/project&max_tokens=500
```
Returns a formatted summary suitable for LLM processing.
**Response:**
```json
{
"session_id": "abc123",
"summary": "# Session: abc123\nProject: /path/to/project\nBranch: fix-auth-bug\n\n## Conversation (24 messages)\n..."
}
```
---
## Agent-OS Spec Tracking (NEW)
Real-time tracking and visualization of agent-os specifications and tasks through the Kanban board.
@ -1102,5 +1451,5 @@ For issues, questions, or contributions:
---
**Last Updated:** 2025-10-31
**API Version:** 1.0
**Last Updated:** 2025-12-11
**API Version:** 2.3

View file

@ -0,0 +1,289 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080" style="background: linear-gradient(135deg, #0F172A 0%, #1E293B 100%);">
<defs>
<!-- Gradient definitions -->
<linearGradient id="cyanGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#00D9FF;stop-opacity:1" />
<stop offset="100%" style="stop-color:#00D9FF;stop-opacity:0.6" />
</linearGradient>
<linearGradient id="purpleGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8B5CF6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B5CF6;stop-opacity:0.6" />
</linearGradient>
<linearGradient id="pinkGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#FF00FF;stop-opacity:1" />
<stop offset="100%" style="stop-color:#FF00FF;stop-opacity:0.6" />
</linearGradient>
<linearGradient id="cyanPurpleGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#00D9FF;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B5CF6;stop-opacity:0.6" />
</linearGradient>
<linearGradient id="purplePinkGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8B5CF6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#FF00FF;stop-opacity:0.6" />
</linearGradient>
<!-- Glow filters -->
<filter id="glow">
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="strongGlow">
<feGaussianBlur stdDeviation="8" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- Stars pattern -->
<pattern id="stars" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
<circle cx="10" cy="20" r="1" fill="white" opacity="0.3"/>
<circle cx="40" cy="50" r="0.5" fill="white" opacity="0.5"/>
<circle cx="70" cy="10" r="1.5" fill="white" opacity="0.2"/>
<circle cx="85" cy="80" r="0.8" fill="white" opacity="0.4"/>
<circle cx="25" cy="90" r="1" fill="white" opacity="0.3"/>
</pattern>
</defs>
<!-- Background stars -->
<rect width="1920" height="1080" fill="url(#stars)" opacity="0.5"/>
<!-- Title -->
<text x="960" y="80" font-family="Arial, sans-serif" font-size="48" font-weight="bold" fill="#FFFFFF" text-anchor="middle" filter="url(#glow)">
CLAUDE OS ARCHITECTURE
</text>
<!-- Center Hub -->
<g id="centerHub">
<circle cx="960" cy="540" r="140" fill="none" stroke="url(#cyanGlow)" stroke-width="3" filter="url(#strongGlow)"/>
<circle cx="960" cy="540" r="135" fill="#1E293B" opacity="0.9"/>
<!-- Circuit pattern inside hub -->
<line x1="880" y1="540" x2="1040" y2="540" stroke="#00D9FF" stroke-width="1" opacity="0.3"/>
<line x1="960" y1="460" x2="960" y2="620" stroke="#00D9FF" stroke-width="1" opacity="0.3"/>
<circle cx="920" cy="500" r="3" fill="#00D9FF" opacity="0.5"/>
<circle cx="1000" cy="580" r="3" fill="#00D9FF" opacity="0.5"/>
<text x="960" y="510" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#00D9FF" text-anchor="middle">
Semantic Knowledge Base
</text>
<text x="960" y="540" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
SQLite + sqlite-vec
</text>
<text x="960" y="565" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
800+ indexed chunks
</text>
<text x="960" y="590" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Vector embeddings
</text>
</g>
<!-- Component 1: TOP-LEFT - Real-Time Learning -->
<g id="component1">
<rect x="200" y="200" width="280" height="160" rx="15" fill="#1E293B" stroke="url(#cyanGlow)" stroke-width="2" filter="url(#glow)"/>
<line x1="210" y1="220" x2="260" y2="220" stroke="#00D9FF" stroke-width="1" opacity="0.3"/>
<circle cx="265" cy="220" r="2" fill="#00D9FF" opacity="0.5"/>
<text x="340" y="235" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#00D9FF" text-anchor="middle">
🧠 Real-Time Learning
</text>
<text x="340" y="265" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Redis Pub/Sub
</text>
<text x="340" y="290" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
&lt;1ms latency
</text>
<text x="340" y="315" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
10+ patterns
</text>
<text x="340" y="345" font-family="Arial, sans-serif" font-size="14" fill="#00D9FF" text-anchor="middle" opacity="0.6">
(Cyan glow)
</text>
</g>
<!-- Component 2: TOP-RIGHT - Memory MCP -->
<g id="component2">
<rect x="1440" y="200" width="280" height="160" rx="15" fill="#1E293B" stroke="url(#purpleGlow)" stroke-width="2" filter="url(#glow)"/>
<line x1="1450" y1="220" x2="1500" y2="220" stroke="#8B5CF6" stroke-width="1" opacity="0.3"/>
<circle cx="1505" cy="220" r="2" fill="#8B5CF6" opacity="0.5"/>
<text x="1580" y="235" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#8B5CF6" text-anchor="middle">
💾 Memory MCP
</text>
<text x="1580" y="265" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Persistent Memory
</text>
<text x="1580" y="290" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Instant Recall
</text>
<text x="1580" y="315" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Natural Language
</text>
<text x="1580" y="345" font-family="Arial, sans-serif" font-size="14" fill="#8B5CF6" text-anchor="middle" opacity="0.6">
(Purple glow)
</text>
</g>
<!-- Component 3: RIGHT - Analyze-Project -->
<g id="component3">
<rect x="1440" y="460" width="280" height="160" rx="15" fill="#1E293B" stroke="url(#pinkGlow)" stroke-width="2" filter="url(#glow)"/>
<line x1="1450" y1="480" x2="1500" y2="480" stroke="#FF00FF" stroke-width="1" opacity="0.3"/>
<circle cx="1505" cy="480" r="2" fill="#FF00FF" opacity="0.5"/>
<text x="1580" y="495" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#FF00FF" text-anchor="middle">
🔍 Analyze-Project
</text>
<text x="1580" y="525" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Git Hooks
</text>
<text x="1580" y="550" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Auto-Index
</text>
<text x="1580" y="575" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Smart Docs
</text>
<text x="1580" y="605" font-family="Arial, sans-serif" font-size="14" fill="#FF00FF" text-anchor="middle" opacity="0.6">
(Pink glow)
</text>
</g>
<!-- Component 4: BOTTOM-RIGHT - Session Management -->
<g id="component4">
<rect x="1440" y="720" width="280" height="160" rx="15" fill="#1E293B" stroke="url(#purplePinkGlow)" stroke-width="2" filter="url(#glow)"/>
<line x1="1450" y1="740" x2="1500" y2="740" stroke="#FF00FF" stroke-width="1" opacity="0.3"/>
<circle cx="1505" cy="740" r="2" fill="#8B5CF6" opacity="0.5"/>
<text x="1580" y="755" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#FF00FF" text-anchor="middle">
🎯 Session Management
</text>
<text x="1580" y="785" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Auto Resume
</text>
<text x="1580" y="810" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Context Preservation
</text>
<text x="1580" y="835" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Zero Cold Start
</text>
<text x="1580" y="865" font-family="Arial, sans-serif" font-size="14" fill="#FF00FF" text-anchor="middle" opacity="0.6">
(Purple-pink glow)
</text>
</g>
<!-- Component 5: BOTTOM-LEFT - Semantic Search -->
<g id="component5">
<rect x="200" y="720" width="280" height="160" rx="15" fill="#1E293B" stroke="url(#cyanPurpleGlow)" stroke-width="2" filter="url(#glow)"/>
<line x1="210" y1="740" x2="260" y2="740" stroke="#00D9FF" stroke-width="1" opacity="0.3"/>
<circle cx="265" cy="740" r="2" fill="#8B5CF6" opacity="0.5"/>
<text x="340" y="755" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#00D9FF" text-anchor="middle">
📚 Semantic Search
</text>
<text x="340" y="785" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Vector Similarity
</text>
<text x="340" y="810" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Code Understanding
</text>
<text x="340" y="835" font-family="Arial, sans-serif" font-size="16" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Pattern Recognition
</text>
<text x="340" y="865" font-family="Arial, sans-serif" font-size="14" fill="#8B5CF6" text-anchor="middle" opacity="0.6">
(Cyan-purple glow)
</text>
</g>
<!-- Connection lines (circuit traces) -->
<!-- Component 1 to Center -->
<line x1="480" y1="280" x2="820" y2="540" stroke="#00D9FF" stroke-width="2" opacity="0.6" filter="url(#glow)"/>
<circle cx="480" cy="280" r="4" fill="#00D9FF" filter="url(#glow)"/>
<!-- Component 2 to Center -->
<line x1="1440" y1="280" x2="1100" y2="540" stroke="#8B5CF6" stroke-width="2" opacity="0.6" filter="url(#glow)"/>
<circle cx="1440" cy="280" r="4" fill="#8B5CF6" filter="url(#glow)"/>
<!-- Component 3 to Center -->
<line x1="1440" y1="540" x2="1100" y2="540" stroke="#FF00FF" stroke-width="2" opacity="0.6" filter="url(#glow)"/>
<circle cx="1440" cy="540" r="4" fill="#FF00FF" filter="url(#glow)"/>
<!-- Component 4 to Center -->
<line x1="1440" y1="800" x2="1100" y2="540" stroke="#FF00FF" stroke-width="2" opacity="0.6" filter="url(#glow)"/>
<circle cx="1440" cy="800" r="4" fill="#FF00FF" filter="url(#glow)"/>
<!-- Component 5 to Center -->
<line x1="480" y1="800" x2="820" y2="540" stroke="#00D9FF" stroke-width="2" opacity="0.6" filter="url(#glow)"/>
<circle cx="480" cy="800" r="4" fill="#00D9FF" filter="url(#glow)"/>
<!-- Flow Path: Center to MCP Server to Claude Code to Developer -->
<g id="flowPath">
<!-- Vertical connection from center hub down -->
<line x1="960" y1="680" x2="960" y2="900" stroke="#00D9FF" stroke-width="3" opacity="0.5" stroke-dasharray="5,5" filter="url(#glow)"/>
<!-- MCP Server box -->
<rect x="320" y="900" width="200" height="60" rx="10" fill="#1E293B" stroke="#00D9FF" stroke-width="2" opacity="0.9" filter="url(#glow)"/>
<text x="420" y="925" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#00D9FF" text-anchor="middle">
MCP Server
</text>
<text x="420" y="948" font-family="Arial, sans-serif" font-size="15" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
(port 8051)
</text>
<!-- Arrow from center to MCP -->
<line x1="520" y1="930" x2="740" y2="930" stroke="#00D9FF" stroke-width="3" opacity="0.8" filter="url(#glow)"/>
<polygon points="740,930 730,925 730,935" fill="#00D9FF" opacity="0.8"/>
<!-- Claude Code Interface box -->
<rect x="740" y="900" width="200" height="60" rx="10" fill="#1E293B" stroke="#8B5CF6" stroke-width="2" opacity="0.9" filter="url(#glow)"/>
<text x="840" y="925" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#8B5CF6" text-anchor="middle">
Claude Code
</text>
<text x="840" y="948" font-family="Arial, sans-serif" font-size="15" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
Interface
</text>
<!-- Arrow from MCP to Claude Code -->
<line x1="940" y1="930" x2="1160" y2="930" stroke="#8B5CF6" stroke-width="3" opacity="0.8" filter="url(#glow)"/>
<polygon points="1160,930 1150,925 1150,935" fill="#8B5CF6" opacity="0.8"/>
<!-- Developer box -->
<rect x="1160" y="900" width="200" height="60" rx="10" fill="#1E293B" stroke="#FF00FF" stroke-width="2" opacity="0.9" filter="url(#glow)"/>
<text x="1260" y="925" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#FF00FF" text-anchor="middle">
Developer
</text>
<text x="1260" y="948" font-family="Arial, sans-serif" font-size="15" fill="#FFFFFF" text-anchor="middle" opacity="0.8">
(You!)
</text>
<!-- Arrow pointing right for emphasis -->
<line x1="1360" y1="930" x2="1420" y2="930" stroke="#FF00FF" stroke-width="3" opacity="0.8" filter="url(#glow)"/>
<polygon points="1420,930 1410,925 1410,935" fill="#FF00FF" opacity="0.8"/>
</g>
<!-- Bottom Panel: Data Flow -->
<g id="bottomPanel">
<rect x="360" y="980" width="1200" height="60" rx="10" fill="#1E293B" stroke="#00D9FF" stroke-width="2" opacity="0.9" filter="url(#glow)"/>
<text x="960" y="1005" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#FFFFFF" text-anchor="middle">
Data Flow Pipeline:
</text>
<text x="960" y="1028" font-family="Arial, sans-serif" font-size="16" fill="#00D9FF" text-anchor="middle">
Git Commit → 3s indexing → SQLite → MCP → Claude → You
</text>
</g>
<!-- Decorative circuit lines in background -->
<line x1="100" y1="100" x2="300" y2="100" stroke="#00D9FF" stroke-width="1" opacity="0.1"/>
<line x1="1620" y1="100" x2="1820" y2="100" stroke="#FF00FF" stroke-width="1" opacity="0.1"/>
<line x1="100" y1="950" x2="300" y2="950" stroke="#8B5CF6" stroke-width="1" opacity="0.1"/>
<circle cx="150" cy="100" r="3" fill="#00D9FF" opacity="0.2"/>
<circle cx="1770" cy="100" r="3" fill="#FF00FF" opacity="0.2"/>
<circle cx="150" cy="950" r="3" fill="#8B5CF6" opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

190
docs/guides/AUTH_SETUP.md Normal file
View file

@ -0,0 +1,190 @@
# Claude OS Authentication Setup
Simple email/password authentication to keep strangers out of your Claude OS frontend.
## Features
✅ **Optional Authentication** - Disabled by default, enable when needed
✅ **Environment-based** - No database required for user management
✅ **JWT Tokens** - 7-day token expiration
✅ **Secure** - Bcrypt password hashing
✅ **Simple** - Single user account via environment variables
## Quick Start
### 1. Enable Authentication
Set environment variables in your `.env` file or server environment:
```bash
# Required: Email for login
CLAUDE_OS_EMAIL=admin@example.com
# Option 1: Plain password (development only - will be hashed automatically)
CLAUDE_OS_PASSWORD=your_secure_password_here
# Option 2: Pre-hashed password (recommended for production)
CLAUDE_OS_PASSWORD_HASH=$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5hhA82jdg8jpu
# Optional: Custom secret key for JWT (auto-generated if not set)
CLAUDE_OS_SECRET_KEY=your-super-secret-key-min-32-chars
```
### 2. Generate Password Hash (Production)
For production, use a pre-hashed password instead of plain text:
```bash
# Install dependencies first
cd ~/Projects/claude-os
source venv/bin/activate
pip install -r requirements.txt
# Generate hash
python3 -c "from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('your_password_here'))"
```
Copy the output and use it as `CLAUDE_OS_PASSWORD_HASH` in your `.env` file.
### 3. Restart Claude OS
```bash
# Local (Mac)
./stop.sh
./start.sh
# Production Server
sudo systemctl restart claude-os
```
### 4. Login
Visit your Claude OS frontend:
- Local: http://localhost:5173/login
- Production: https://your-domain.com/login
Use the email and password you configured.
## Production Deployment
### Server Environment Variables
Add to `/opt/claude-os/.env`:
```bash
CLAUDE_OS_EMAIL=admin@pistn.com
CLAUDE_OS_PASSWORD_HASH=$2b$12$xyz... # Generated hash
CLAUDE_OS_SECRET_KEY=your-32-char-secret
```
Restart the service:
```bash
sudo systemctl restart claude-os
```
## Disable Authentication
To disable authentication (open access):
```bash
# Remove or comment out these variables in .env:
# CLAUDE_OS_EMAIL=...
# CLAUDE_OS_PASSWORD=...
# CLAUDE_OS_PASSWORD_HASH=...
```
Restart Claude OS. The frontend will allow access without login.
## How It Works
### Backend (FastAPI)
- **`/api/auth/login`** - Login endpoint, returns JWT token
- **`/api/auth/me`** - Get current user info
- **`/api/auth/status`** - Check if authentication is enabled
### Frontend (React)
- **Login Page** - Beautiful gradient login form
- **Auth Context** - Manages authentication state
- **Protected Routes** - Automatically redirects to login if not authenticated
- **Token Storage** - JWT stored in localStorage (7-day expiration)
### Security Features
✅ **Bcrypt hashing** - Industry-standard password encryption
✅ **JWT tokens** - Stateless authentication
✅ **Automatic expiration** - Tokens expire after 7 days
✅ **HTTPS recommended** - Use SSL in production
✅ **No database needed** - Single user, environment-based
## Troubleshooting
### "Authentication is not configured"
You haven't set the `CLAUDE_OS_EMAIL` environment variable. Set it and restart.
### "Incorrect email or password"
Check your `.env` file:
- Email matches exactly (case-sensitive)
- If using `CLAUDE_OS_PASSWORD`, make sure it's correct
- If using `CLAUDE_OS_PASSWORD_HASH`, regenerate the hash
### Login page doesn't appear
Make sure your Claude OS frontend is running:
```bash
# Local development
cd frontend
npm run dev
# Production (should be served by Nginx)
curl http://localhost:5173
```
### Token expired
Tokens last 7 days. Just login again to get a new token.
## Multiple Users
This system is designed for single-user access. For multiple users:
1. **Simple approach**: Share one account with your team
2. **Advanced approach**: Extend the auth system to use a database (requires custom implementation)
For most teams deploying Claude OS internally, a single shared account is sufficient since it's:
- Behind your firewall
- For trusted team members only
- Just to keep strangers out (not enterprise-grade auth)
## Security Best Practices
### Development (Local)
```bash
CLAUDE_OS_EMAIL=dev@localhost
CLAUDE_OS_PASSWORD=dev123
```
Fine for local development. Password is hashed automatically.
### Production (Server)
```bash
CLAUDE_OS_EMAIL=admin@yourcompany.com
CLAUDE_OS_PASSWORD_HASH=$2b$12$xyz... # Pre-hashed
CLAUDE_OS_SECRET_KEY=randomly-generated-32-char-secret
```
✅ **Use hashed passwords**
✅ **Use strong secret keys**
✅ **Use HTTPS/SSL**
✅ **Rotate passwords periodically**
---
**Questions?** Check the main Claude OS README or open an issue on GitHub.

View file

@ -0,0 +1,431 @@
# Claude OS Backup & Restore Guide
This guide explains how to safely backup and restore your Claude OS installation, perfect for testing fresh installations without losing data.
## Quick Start
### 1. Backup Your Current Setup
```bash
./scripts/backup_claude_os.sh
```
This creates a timestamped backup in `backups/backup_YYYYMMDD_HHMMSS/` containing:
- SQLite database (all projects, knowledge bases, documents)
- Configuration files (.env, JSON configs)
- Uploaded documents
- Recent log files
- Symlink information
**Output example:**
```
Backup location: /Users/iamanmp/Projects/claude-os/backups/backup_20251101_135211
Backup size: 125M
Backup timestamp: 20251101_135211
To restore this backup later:
./scripts/restore_claude_os.sh 20251101_135211
```
### 2. Test Fresh Installation
Now you can safely test the installation process:
```bash
# Option 1: Test quick install
./install.sh
# Option 2: Test full setup
./setup.sh
```
### 3. Restore Your Data
If you need to restore your backup:
```bash
./scripts/restore_claude_os.sh 20251101_135211
```
Replace `20251101_135211` with your actual backup timestamp.
---
## What Gets Backed Up?
### Critical Data (Automatically Backed Up)
1. **SQLite Database** (`data/claude-os.db`)
- All projects
- All knowledge bases
- All documents and embeddings
- Search history
- Typically 100-200MB
2. **Configuration Files**
- `.env` - Environment variables (Ollama settings, API keys, etc.)
- `claude-os-config.json` - Project configuration
- `claude-os-state.json` - Current session state
- `claude-os-triggers.json` - Custom trigger phrases
3. **Uploaded Documents** (`data/uploads/`)
- Any files uploaded through the UI
4. **Recent Logs** (`logs/`)
- Recent log files (< 10MB each)
- Useful for debugging
5. **Symlink Information**
- Record of `~/.claude/commands/` symlinks
- Record of `~/.claude/skills/` symlinks
- For reference only (recreated by `install.sh`)
### What's NOT Backed Up (No Need)
- `node_modules/` - Reinstalled from package.json
- `venv/` - Recreated during installation
- Python packages - Reinstalled from requirements.txt
- Ollama models - Remain in system location
- Templates - Already in git repository
- Large log files (> 10MB)
---
## Detailed Usage
### Backup Command
```bash
./scripts/backup_claude_os.sh
```
**What it does:**
1. Creates timestamped backup directory
2. Copies all critical files
3. Records symlink state
4. Creates manifest file
5. Shows backup summary
**No arguments needed** - timestamps are automatic.
### Restore Command
```bash
./scripts/restore_claude_os.sh <timestamp>
```
**Examples:**
```bash
# Restore specific backup
./scripts/restore_claude_os.sh 20251101_135211
# List available backups
./scripts/restore_claude_os.sh
```
**What it does:**
1. Verifies backup exists
2. Shows manifest
3. Asks for confirmation (press Enter to continue)
4. Restores all files
5. Provides next steps
**⚠️ WARNING:** Restore will **overwrite** your current data! Make a new backup first if you want to keep current state.
---
## Testing Fresh Installations
### Recommended Workflow
1. **Backup first:**
```bash
./scripts/backup_claude_os.sh
```
2. **Stop all services:**
```bash
./stop_all_services.sh
```
3. **Clean up (optional but thorough):**
```bash
# Remove symlinks (will be recreated)
rm ~/.claude/commands/claude-os-*.md
rm ~/.claude/skills/memory
rm ~/.claude/skills/initialize-project
rm ~/.claude/skills/remember-this
# Remove virtual environment
rm -rf venv venv_py312 venv_py313
```
4. **Test installation:**
```bash
# Test quick install
./install.sh
# OR test full setup
./setup.sh
```
5. **Verify it works:**
```bash
# Start services
./start.sh
# Check health
curl http://localhost:8051/health
# Check UI
open http://localhost:5173
```
6. **If something goes wrong, restore:**
```bash
./scripts/restore_claude_os.sh 20251101_135211
./start.sh
```
---
## Common Scenarios
### Scenario 1: Test install.sh for a user report
```bash
# 1. Backup everything
./scripts/backup_claude_os.sh
# 2. Clean installation (keep data, test scripts)
rm -rf venv
rm ~/.claude/commands/claude-os-*.md
rm ~/.claude/skills/{memory,initialize-project,remember-this}
# 3. Test install as if you're a new user
./install.sh
# 4. Restore your data (database, configs)
./scripts/restore_claude_os.sh <timestamp>
./start.sh
```
### Scenario 2: Test setup.sh from scratch
```bash
# 1. Backup everything
./scripts/backup_claude_os.sh
# 2. Full clean (simulate fresh machine)
./stop_all_services.sh
rm -rf venv venv_*
rm -rf node_modules
rm -rf data/claude-os.db
rm ~/.claude/commands/claude-os-*.md
rm ~/.claude/skills/{memory,initialize-project,remember-this}
# 3. Test full setup
./setup.sh
./start_all_services.sh
# 4. Restore your data
./scripts/restore_claude_os.sh <timestamp>
./start.sh
```
### Scenario 3: Test project initialization
```bash
# 1. Backup current state
./scripts/backup_claude_os.sh
# 2. Create test project
cd ~/test-project
/claude-os-init # In Claude Code
# 3. If something breaks, restore
cd ~/Projects/claude-os
./scripts/restore_claude_os.sh <timestamp>
```
---
## Backup Best Practices
### When to Backup
- **Before testing installations** - Always!
- **Before major changes** - Modifying core files
- **Weekly** - If actively developing
- **Before upgrades** - Git pull, dependency updates
### Backup Management
```bash
# List all backups
ls -lh backups/
# Check backup size
du -sh backups/backup_*
# Remove old backups (keep last 5)
cd backups
ls -t | tail -n +6 | xargs rm -rf
# Archive old backups
tar -czf backups_archive_2025.tar.gz backups/
```
### Multiple Backups
The backup script creates **new backups** each time - it never overwrites:
```bash
./scripts/backup_claude_os.sh # Creates backup_20251101_135211
./scripts/backup_claude_os.sh # Creates backup_20251101_140530
./scripts/backup_claude_os.sh # Creates backup_20251101_141005
```
You can safely run it multiple times!
---
## Troubleshooting
### "Backup location not found"
```bash
# List available backups
ls -1 backups/ | grep backup_
# Use exact timestamp from list
./scripts/restore_claude_os.sh 20251101_135211
```
### "Permission denied"
```bash
# Make scripts executable
chmod +x scripts/backup_claude_os.sh scripts/restore_claude_os.sh
```
### "Database locked" during backup
```bash
# Stop services first
./stop_all_services.sh
# Then backup
./scripts/backup_claude_os.sh
```
### Restore doesn't fix everything
The restore script restores **data** but not:
- Python packages (run `pip install -r requirements.txt`)
- Node modules (run `cd frontend && npm install`)
- Symlinks (run `./install.sh`)
- Running services (run `./start.sh`)
**Full recovery process:**
```bash
./scripts/restore_claude_os.sh <timestamp>
./install.sh # Recreate symlinks
./start.sh # Start services
```
---
## File Locations Reference
### Backed Up Locations
```
claude-os/
├── data/
│ ├── claude-os.db ← BACKED UP (critical!)
│ └── uploads/ ← BACKED UP
├── .env ← BACKED UP (has secrets)
├── claude-os-config.json ← BACKED UP
├── claude-os-state.json ← BACKED UP
├── claude-os-triggers.json ← BACKED UP
└── logs/ ← BACKED UP (recent only)
~/.claude/
├── commands/claude-os-*.md ← Symlink info recorded
└── skills/{memory,etc} ← Symlink info recorded
```
### Backup Storage Location
```
claude-os/
└── backups/
├── backup_20251101_135211/
│ ├── claude-os.db
│ ├── .env
│ ├── claude-os-config.json
│ ├── claude-os-state.json
│ ├── claude-os-triggers.json
│ ├── uploads/
│ ├── logs/
│ ├── symlink_info.txt
│ └── MANIFEST.txt
└── backup_20251101_140530/
└── ...
```
---
## Security Notes
### .env File Contains Secrets
The `.env` file may contain:
- `CLAUDE_OS_PASSWORD` - Admin password
- `CLAUDE_OS_SECRET_KEY` - JWT secret
- API keys (if using OpenAI)
**Backups are stored locally** in `backups/` directory.
**⚠️ DO NOT:**
- Commit backups to git (.gitignore already excludes them)
- Share backup directories
- Store backups in public locations
### Recommended Backup Security
```bash
# Encrypt sensitive backups
tar -czf - backups/backup_20251101_135211 | \
openssl enc -aes-256-cbc -pbkdf2 -out backup_encrypted.tar.gz.enc
# Decrypt when needed
openssl enc -aes-256-cbc -pbkdf2 -d -in backup_encrypted.tar.gz.enc | \
tar -xzf -
```
---
## Summary
**You now have:**
✅ `backup_claude_os.sh` - Backup all your data
✅ `restore_claude_os.sh` - Restore from backup
✅ Safe testing workflow
✅ Your first backup created!
**Next steps:**
1. Your backup is ready: `backups/backup_20251101_135211/`
2. You can safely test `./install.sh`
3. If anything breaks: `./scripts/restore_claude_os.sh 20251101_135211`
4. Test away with confidence! 🚀
---
**Questions?**
- Check `backups/backup_*/MANIFEST.txt` for backup contents
- Run `./scripts/restore_claude_os.sh` with no arguments to list backups
- Backups are fast (30 seconds) - backup often!

View file

@ -0,0 +1,230 @@
# File Watcher Hooks - Quick Start Guide
## What Changed?
The `initialize-project` skill now **automatically configures file watchers** for your project's knowledge bases. No more manual setup needed!
## Before vs After
### Before (Manual Setup Required)
```bash
# Initialize project
initialize-project: 1
# Then manually enable hooks via API
curl -X POST http://localhost:8051/api/projects/1/hooks/knowledge_docs/enable \
-H "Content-Type: application/json" \
-d '{"folder_path": "/path/to/knowledge_docs"}'
# Then manually start the file watcher
curl -X POST http://localhost:8051/api/watcher/start/1
# Only THEN would new files auto-sync
```
### After (Fully Automatic)
```bash
# Initialize project
initialize-project: 1
# ✨ File watchers are now automatically configured!
# New files will be auto-synced immediately
```
## How File Watchers Work
When you initialize a project, the system automatically:
1. **Creates hooks for all 4 MCP types:**
- `knowledge_docs` → Watches your project's documentation folder
- `project_profile` → Watches `.claude-os/project-profile/`
- `project_index` → Watches `.claude-os/project-index/`
- `project_memories` → Watches `.claude-os/memories/`
2. **Starts the file watcher service**
- Monitors all configured folders
- Detects file changes within ~2 seconds
- Automatically syncs changes to knowledge bases
3. **Creates the necessary folders**
- Project folders are created if they don't exist
- Ready to accept new files immediately
## Default Folder Mappings
| MCP Type | Default Folder | Behavior |
|----------|---|---|
| `knowledge_docs` | `docs/` (or `documentation/`) | Watches for markdown, text, PDF files |
| `project_profile` | `.claude-os/project-profile/` | Auto-created, stores profile docs |
| `project_index` | `.claude-os/project-index/` | Auto-created, stores code index |
| `project_memories` | `.claude-os/memories/` | Auto-created, stores insights |
## Using Custom Folders
If you want to use a different folder for `knowledge_docs`:
```bash
# Create your custom folder
mkdir my-docs
# Enable hook for custom folder
curl -X POST "http://localhost:8051/api/projects/1/hooks/knowledge_docs/enable" \
-H "Content-Type: application/json" \
-d '{"folder_path": "/path/to/my-docs"}'
# Restart the watcher to pick up changes
curl -X POST "http://localhost:8051/api/watcher/restart/1"
```
## Verify Hooks Are Working
### Check Watcher Status
```bash
curl http://localhost:8051/api/watcher/status
```
Expected output shows your project is being watched:
```json
{
"status": {
"enabled": true,
"projects_watched": 1,
"projects": {
"1": {
"watched_paths": {
"knowledge_docs": "/path/to/docs",
"project_profile": "/path/to/.claude-os/project-profile",
...
},
"event_handlers": ["knowledge_docs", "project_profile", ...]
}
}
}
}
```
### Check Hooks Configuration
```bash
cat /path/to/project/.claude-os/hooks.json
```
Should show something like:
```json
{
"version": "1.0",
"project_id": 1,
"hooks": {
"knowledge_docs": {
"enabled": true,
"folder_path": "/path/to/docs",
"synced_files": {}
},
...
}
}
```
## Testing Auto-Sync
### Test knowledge_docs Auto-Sync
```bash
# Create a test file
echo "# Test Document" > /path/to/docs/test.md
# Wait 2-3 seconds for the watcher to pick it up
# Verify it was indexed
sqlite3 /path/to/claude-os/data/claude-os.db \
"SELECT COUNT(*) FROM documents WHERE doc_id LIKE 'test.md%' AND kb_id = 1"
```
Should show a count > 0 if successfully indexed.
## Troubleshooting
### Hooks not triggering auto-sync?
1. **Check if watcher is running:**
```bash
curl http://localhost:8051/api/watcher/status
```
If `"enabled": false`, start it:
```bash
curl -X POST "http://localhost:8051/api/watcher/start/1"
```
2. **Check hooks configuration:**
```bash
cat .claude-os/hooks.json
```
Verify `"enabled": true` for your MCP type
3. **Check folder permissions:**
- Ensure folder is readable by the MCP server
- Try creating a test file manually
4. **Check MCP server logs:**
- Look for errors in `/private/tmp/` or application logs
- Check if server is running: `ps aux | grep python | grep server`
### Files not appearing in search?
1. **Verify files were indexed:**
```bash
sqlite3 /path/to/claude-os/data/claude-os.db \
"SELECT COUNT(*) FROM documents WHERE kb_id = 1"
```
2. **Check file format is supported:**
- Default: `.md`, `.txt`, `.pdf`, `.py`, `.js`, `.ts`, `.json`, `.yaml`
- Configure custom patterns in hooks.json `file_patterns` array
3. **Manually trigger sync:**
```bash
curl -X POST "http://localhost:8051/api/projects/1/hooks/sync?mcp_type=knowledge_docs"
```
## Advanced: Restart Watchers
If watchers stop responding:
```bash
# Restart a specific project's watcher
curl -X POST "http://localhost:8051/api/watcher/restart/1"
# Or restart all watchers
# (Note: not available via API, requires server restart)
```
## Git Integration
The system also installs **git post-commit hooks** that:
- Auto-index changed files on each commit
- Periodically expand the index with new files (every 10 commits)
- Track commit count for smart indexing
This works seamlessly with the file watcher hooks.
## FAQ
**Q: Do I need to restart anything after running initialize-project?**
A: No! Everything is set up automatically. File watchers start immediately.
**Q: Can I add files while the watcher is running?**
A: Yes! Add files anytime. They'll be indexed within ~2 seconds.
**Q: What if I want to disable auto-sync for a folder?**
A: Disable the hook via API:
```bash
curl -X POST "http://localhost:8051/api/projects/1/hooks/knowledge_docs/disable"
```
**Q: Can multiple projects have watchers running?**
A: Yes! The system can monitor multiple projects simultaneously.
**Q: Do file watchers persist after server restart?**
A: No, watchers stop when the server restarts. They'll restart automatically when projects are loaded.
---
**Last Updated:** October 28, 2025

View file

@ -0,0 +1,373 @@
# The Ideal Claude OS Session Workflow
**What SHOULD happen vs what ACTUALLY happens (and how to fix it)**
---
## 🌅 Morning Coffee Scenario
You grab your coffee, open your terminal, and type:
```bash
cd ~/Projects/pistn
code .
# Claude Code CLI opens
```
---
## ✅ IDEAL: What SHOULD Happen Automatically
```
═══════════════════════════════════════════════════════════════
🚀 CLAUDE OS INITIALIZED
═══════════════════════════════════════════════════════════════
Detected Project: Pistn (Ruby on Rails)
Loading context... ⏳
[Claude automatically reads:]
✓ CLAUDE.md
✓ .claude-os/config.json
✓ claude-os-state.json
✓ agent-os/config.yml
[Claude automatically checks:]
✓ Git status (feature/appointment-redesign, 3 files modified)
✓ Recent commits (last 3)
✓ Project memories (searching "appointment dashboard recent")
[Claude automatically loads:]
✓ 5 relevant memories
✓ Coding standards
✓ Architectural patterns
✓ Kanban board (3 specs, 52 tasks, 45% complete)
═══════════════════════════════════════════════════════════════
📚 CONTEXT LOADED - Ready to code!
═══════════════════════════════════════════════════════════════
You were working on: Appointment Dashboard Redesign
Progress: 45% complete (23 of 52 tasks done)
Last session: 2 days ago
Duration: 2h 15m
Completed: Sidebar navigation, card layouts, toggle switches
Still in progress:
⏳ PHASE2-TASK3: Implement concern methods
⏳ PHASE2-TASK4: Add helper methods
═══════════════════════════════════════════════════════════════
💡 KEY REMINDERS
═══════════════════════════════════════════════════════════════
• 67-page implementation plan in memories
• Zero functionality loss requirement
• Tekmetric integration must work on save
• Use service objects for business logic
• Bootstrap 3.x only (no v4/v5)
═══════════════════════════════════════════════════════════════
🎯 SUGGESTED ACTIONS
═══════════════════════════════════════════════════════════════
1. Continue Phase 2 (30 min estimated)
2. Commit your 3 modified files first
3. Start something new
What would you like to do?
═══════════════════════════════════════════════════════════════
```
**You say:** "Let's continue phase 2"
**Claude:** "Perfect! Let me implement the concern methods for PHASE2-TASK3. Based on our service object pattern, I'll..."
---
## ❌ CURRENT: What ACTUALLY Happens
```
You: [Opens Claude Code]
Claude: Hello! How can I help you today?
You: Let's continue working on the appointment dashboard
Claude: I'd be happy to help with the appointment dashboard!
Could you tell me more about the project and what
you've been working on?
You: *Sigh* We're redesigning the appointment dashboard in
Pistn. We've been working on this for 2 days. I have
a 67-page implementation plan. We completed the sidebar
and cards. We're on phase 2...
Claude: Thanks for the context! What would you like to work
on specifically?
You: Let me just share the implementation plan again...
[Copies and pastes 67 pages]
```
**Problem:** You spend 15 minutes re-explaining everything Claude should already know!
---
## 🔧 THE FIX: Making It Work Today
**Until automatic session start is implemented in Claude Code itself, YOU need to trigger it manually:**
### Step 1: Add This to Your CLAUDE.md
```markdown
# IMPORTANT: Session Start Protocol
**EVERY TIME we start a conversation, YOU (Claude) must:**
1. **Check if this is a Claude OS project**
```bash
ls -la | grep -E "(CLAUDE.md|.claude-os|agent-os)"
```
2. **If yes, AUTOMATICALLY do the following WITHOUT me asking:**
a) Read these files:
- CLAUDE.md (you're reading it now!)
- .claude-os/config.json
- claude-os-state.json (if exists)
- agent-os/config.yml (if exists)
b) Check git context:
```bash
git branch --show-current
git status --short
git log -3 --oneline
```
c) Search my project memories:
- Use last session task + current branch + "recent"
- Load top 5 memories from {project}-project_memories
d) Load coding standards:
- Search {project}-project_profile for "coding standards"
e) Check Kanban board:
- GET /api/projects/{id}/kanban
- Show current spec progress
f) Present a session start summary showing:
- What we were working on last
- Current progress
- Key reminders
- Suggested next steps
3. **Then ask:** "What would you like to work on?"
**DO THIS AUTOMATICALLY. DO NOT wait for me to ask!**
```
### Step 2: Create a Morning Routine
Add this alias to your `~/.zshrc` or `~/.bashrc`:
```bash
alias pistn='cd ~/Projects/pistn && code . && cat .claude-os/session-reminder.txt'
```
Create `.claude-os/session-reminder.txt`:
```
═══════════════════════════════════════════════════════════════
🚨 REMINDER TO CLAUDE 🚨
═══════════════════════════════════════════════════════════════
This is a Claude OS project!
Please AUTOMATICALLY:
1. Read CLAUDE.md
2. Read .claude-os/config.json
3. Check git status
4. Search project_memories
5. Load coding standards
6. Check Kanban board
7. Present session start summary
DO NOT make me paste context manually!
═══════════════════════════════════════════════════════════════
```
Now you just type `pistn` and Claude will see the reminder!
### Step 3: Use Session Commands
When you want to ensure context is loaded:
```bash
# Option A: Explicit session start
/claude-os-session start "continue appointment dashboard"
# Option B: Just remind Claude
"Hey Claude, can you load my project context and tell me
where we left off?"
# Option C: Direct command
"Read CLAUDE.md, check git status, search my memories for
'appointment dashboard', and load my coding standards.
Then tell me what we were working on."
```
---
## 🎯 Making This Automatic (Future)
**What needs to happen for true automation:**
### In Claude Code CLI:
1. **Project Detection Hook**
- When CLI starts, check for `.claude-os/`
- If found, trigger automatic context loading
- Display session start summary
2. **Auto-Load Behavior**
- Read `CLAUDE.md` into system prompt
- Search knowledge bases automatically
- Present context before user types anything
3. **Session State Persistence**
- Save session state on exit
- Restore on next start
- Track what was being worked on
### In Claude OS:
4. **Session API Endpoint**
```http
GET /api/projects/{id}/session/start
Returns:
- Last session summary
- Relevant memories (top 5)
- Coding standards
- Git context
- Kanban status
- Suggested actions
```
5. **MCP Session Tool**
- Expose session data via MCP
- Claude Code can call it automatically
- No user intervention needed
---
## 📋 Checklist: Am I Using Sessions Effectively?
### ✅ Good Session Practice
- [ ] Claude presents context at start without me asking
- [ ] I never re-explain the project architecture
- [ ] Claude references past decisions automatically
- [ ] I can pick up exactly where I left off
- [ ] Claude suggests next steps based on history
### ❌ Bad Session Practice
- [ ] I paste the same context document every session
- [ ] I explain "we use service objects" every time
- [ ] Claude asks "what's your tech stack?" repeatedly
- [ ] I have to search for what I did yesterday
- [ ] Every session feels like starting over
---
## 🔥 Power User Tips
### 1. Create Session Shortcuts
```bash
# ~/.zshrc
alias cs-start='echo "Claude: load context, check memories, show status"'
alias cs-end='echo "Claude: analyze session, suggest saves, update state"'
alias cs-blocker='echo "Claude: track blocker and search for solutions"'
```
### 2. Use Quick Saves During Work
```bash
# When you discover something important
/claude-os-session save "Service objects return model on success, error on fail"
# When you hit a blocker
/claude-os-session blocker "Tekmetric API 500 errors on appointment sync"
# When you find a pattern
/claude-os-session pattern "Use localStorage for sidebar state persistence"
```
### 3. End Sessions Properly
```bash
# At end of day
/claude-os-session end
# Claude will:
# - Analyze what you did
# - Suggest high-value saves
# - Update statistics
# - Prepare context for next session
```
### 4. Check Status Mid-Session
```bash
/claude-os-session status
# Shows:
# - How long you've been working
# - What context is loaded
# - Active blockers
# - Next suggested action
```
---
## 🚀 The Ultimate Goal
**Imagine this future:**
You open Claude Code. No typing needed. Claude says:
```
Welcome back! It's been 2 days since your last session.
You were redesigning the Appointment Dashboard. You're 45% done
(23 of 52 tasks). Last session you completed the sidebar navigation
and card layouts.
You have 2 tasks in progress:
- PHASE2-TASK3: Implement concern methods (30 min estimated)
- PHASE2-TASK4: Add helper methods (20 min estimated)
I have your 67-page implementation plan loaded, plus 5 relevant
memories and all your coding standards.
Your git branch has 3 uncommitted files. Want to commit those
first or continue with PHASE2-TASK3?
```
**That's zero context loss. That's the vision.**
---
## 📖 Related Docs
- [SESSION_START_PROTOCOL.md](./SESSION_START_PROTOCOL.md) - Detailed protocol
- [claude-os-session.md](../../templates/commands/claude-os-session.md) - Command reference
- [WHAT_IS_CLAUDE_OS.md](./WHAT_IS_CLAUDE_OS.md) - Overall system guide
---
**Until full automation exists, use the CLAUDE.md instructions and session commands to get the same result manually. But it WILL be automatic soon!** 🚀

View file

@ -0,0 +1,548 @@
# Mandatory Sessions - The New Claude OS Model
**Every conversation is a session. Period.**
---
## 🎯 The Philosophy
**Old Model:** Sessions are optional, you choose when to track work
**New Model:** You're ALWAYS in a session. The only choice is WHAT session.
**Why?**
- Zero context loss
- Complete work history
- Automatic tracking
- Better insights over time
---
## 🚀 How It Works
### Every Conversation Start
```
═══════════════════════════════════════════════════════════════
🚀 CLAUDE OS - SESSION MANAGER
═══════════════════════════════════════════════════════════════
Project: Pistn
Last Session: Dashboard Redesign (2 days ago, 2h 15m)
Progress: 45% complete (PHASE2-TASK3 in progress)
Options:
1. Resume "Dashboard Redesign" [loads full context]
2. Start new session [what are you working on?]
3. Quick question [auto-session, no setup needed]
Choice: _
═══════════════════════════════════════════════════════════════
```
**You MUST pick one. No "just chatting" option.**
---
## 📋 Session Types
### 1. Feature Implementation
```
Type: feature
Duration: Tracked
Kanban: Linked to spec + tasks
Auto-saves: High-value patterns and decisions
Example: "Dashboard Redesign"
Context: Spec tasks, memories, coding standards
Tracking: Task completion, time per task, blockers
```
### 2. Bug Fix
```
Type: bug
Duration: Tracked
Priority: Detected from description (high/medium/low)
Auto-saves: Root cause, solution, prevention
Example: "API 500 Errors"
Context: Error logs, similar past issues, integration patterns
Tracking: Time to fix, solution approach, related issues
```
### 3. Exploration / Learning
```
Type: exploration
Duration: Tracked
Auto-saves: Key learnings, architecture insights
Example: "Understand authentication flow"
Context: Relevant code, architecture docs
Tracking: Files explored, patterns discovered
```
### 4. Refactoring / Maintenance
```
Type: maintenance
Duration: Tracked
Auto-saves: Refactoring patterns, improvements made
Example: "Clean up controller concerns"
Context: Coding standards, similar refactors
Tracking: Files changed, complexity reduced
```
### 5. Code Review
```
Type: review
Duration: Tracked
Auto-saves: Review comments, patterns identified
Example: "Review PR #234"
Context: Project standards, common issues
Tracking: Issues found, suggestions made
```
### 6. Quick Question (Auto-Managed)
```
Type: question
Duration: Auto-tracked (ends after 5 min inactivity)
Auto-saves: Only if valuable insight
Example: "How does Draper work?"
Context: Minimal (just project standards)
Tracking: Questions asked, answers given
```
---
## 🔄 Session Lifecycle
### Phase 1: Session Selection (Every Conversation Start)
**Option 1: Resume Existing Session**
```
You: "1" (resume)
Claude:
═══════════════════════════════════════════════════════════════
✓ RESUMING: Dashboard Redesign
═══════════════════════════════════════════════════════════════
[Loads all context automatically:]
✓ Spec: Group Account Rendering (45% complete)
✓ Current Task: PHASE2-TASK3 (Implement concern methods)
✓ 5 relevant memories loaded
✓ Coding standards loaded
✓ Git: 3 files modified on feature/appointment-redesign
Duration this session: 0h 0m
Total duration: 2h 15m
Ready! Let's continue implementing PHASE2-TASK3.
═══════════════════════════════════════════════════════════════
```
**Option 2: Start New Session**
```
You: "2" (new session)
Claude: "What are you working on?
(I'll detect the type and load relevant context)"
You: "Fix the Tekmetric API 500 errors"
Claude:
═══════════════════════════════════════════════════════════════
✓ NEW BUG SESSION STARTED
═══════════════════════════════════════════════════════════════
Session: "Fix Tekmetric API 500 errors"
Type: Bug Fix (detected)
Priority: High (500 errors are critical)
[Auto-loaded context:]
✓ Searched memories: "Tekmetric API integration errors"
✓ Found 3 similar issues from past sessions
✓ Loaded integration patterns
✓ Git status checked
Previous session "Dashboard Redesign" paused.
Let's debug this! What's the error message?
═══════════════════════════════════════════════════════════════
```
**Option 3: Quick Question**
```
You: "3" (quick question)
Claude: "What's your question?"
You: "How do I use Draper decorators?"
Claude:
═══════════════════════════════════════════════════════════════
💬 QUICK QUESTION SESSION
═══════════════════════════════════════════════════════════════
[Gives answer]
This auto-session will end after 5 minutes of inactivity.
Need more help, or ready to resume Dashboard Redesign?
═══════════════════════════════════════════════════════════════
```
### Phase 2: Active Session (During Work)
**Live Tracking:**
```
[Active Session: Dashboard Redesign]
Duration: 1h 23m
Files changed: 3
Quick saves: 2
Last activity: 2 minutes ago
/claude-os-session status → Shows current session details
/claude-os-session save "insight" → Quick save to memories
/claude-os-session switch "bug fix" → Switch to different session
```
**Smart Prompts:**
```
[After 2 hours of work]
Claude: "You've been working for 2h. You usually take a break around now.
Want to commit your work and pause the session?"
[When pattern detected]
Claude: "This looks like the service object pattern. Should I save
this as a reusable pattern for future reference?"
[When blocker encountered]
Claude: "Want me to track this as a blocker and search for solutions
from past sessions?"
```
### Phase 3: Session End (Explicit or Auto)
**Explicit End:**
```
You: "/claude-os-session end"
Claude:
═══════════════════════════════════════════════════════════════
📊 SESSION SUMMARY: Dashboard Redesign
═══════════════════════════════════════════════════════════════
Duration: 1h 23m
Files changed: 3 files
Quick saves: 2
Commits: 1
Work completed:
✓ Implemented concern methods for PHASE2-TASK3
✓ Fixed N+1 query issue
✓ Added helper methods for PHASE2-TASK4
Progress: 45% → 53% (8% increase!)
Tasks completed: 2 tasks (PHASE2-TASK3, PHASE2-TASK4)
═══════════════════════════════════════════════════════════════
💾 SMART SAVES DETECTED
═══════════════════════════════════════════════════════════════
I found 2 items worth saving:
1. 💎 HIGH VALUE - Concern Method Pattern
"Extracted group account logic to concern with proper
service object integration. Reusable pattern."
Save to project_profile? [Y/n]
2. 📊 MEDIUM VALUE - N+1 Query Fix
"Fixed N+1 in appointments loading by eager loading
with includes(:group_account)"
Save to project_memories? [Y/n]
═══════════════════════════════════════════════════════════════
```
**Auto-End (Inactivity):**
```
[After 15 minutes of inactivity]
Claude: "Session 'Dashboard Redesign' has been inactive for 15 minutes.
Options:
1. Keep session active
2. Pause session (can resume later)
3. End session (save and summarize)
Choice: _"
```
---
## 🎛️ Session Switching
### Mid-Session Switch
```
[Active: Dashboard Redesign, 45m elapsed]
You: "Actually I need to fix a bug first"
Claude: "Want to switch sessions?
1. Pause 'Dashboard Redesign' and start 'Bug Fix' session
2. Continue 'Dashboard Redesign' (handle bug within this session)
Choice: _"
You: "1"
Claude:
═══════════════════════════════════════════════════════════════
✓ SESSION SWITCHED
═══════════════════════════════════════════════════════════════
Paused: Dashboard Redesign (45m, 45% complete)
Started: Bug Fix Session
What's the bug?
═══════════════════════════════════════════════════════════════
```
### Multiple Sessions Same Day
```
═══════════════════════════════════════════════════════════════
📊 TODAY'S SESSIONS
═══════════════════════════════════════════════════════════════
09:00 - 11:15 Dashboard Redesign 2h 15m ✓ Ended
11:15 - 11:45 API Bug Fix 30m ✓ Ended
12:00 - 13:30 Dashboard Redesign 1h 30m ⏸ Paused
14:00 - 15:30 Code Review 1h 30m 🔄 Active
Total: 5h 45m across 4 sessions
Most time: Dashboard Redesign (3h 45m, 2 sessions)
═══════════════════════════════════════════════════════════════
```
---
## 📊 Session Statistics
### Daily Summary
```
═══════════════════════════════════════════════════════════════
📊 END OF DAY SUMMARY - November 4, 2025
═══════════════════════════════════════════════════════════════
Total sessions: 4
Total time: 5h 45m
Average session: 1h 26m
Session breakdown:
• Feature work: 3h 45m (65%)
• Bug fixes: 30m (9%)
• Code review: 1h 30m (26%)
Productivity:
• Tasks completed: 4 tasks
• Memories saved: 6 insights
• Patterns discovered: 2 patterns
• Blockers resolved: 1
Most productive session: Dashboard Redesign (2h 15m, 3 tasks)
Longest session: Dashboard Redesign (2h 15m)
Great day! 🚀
═══════════════════════════════════════════════════════════════
```
### Weekly Summary
```
Week of Oct 28 - Nov 3, 2025
Total sessions: 23
Total time: 32h 15m
Average daily: 4h 36m
Session types:
• Feature: 18 sessions (24h, 74%)
• Bug fix: 3 sessions (2h, 6%)
• Exploration: 2 sessions (6h 15m, 20%)
Top projects:
1. Pistn: 28h (Dashboard Redesign: 18h)
2. Claude OS: 4h 15m (Kanban feature: 4h 15m)
Memories saved: 23 insights
Patterns discovered: 5 patterns
Most productive day: Oct 31 (6h 30m, 8 tasks)
```
### Project Summary
```
Dashboard Redesign - Complete History
Total sessions: 5
Total time: 12h 45m
Status: In Progress (53% complete)
Session history:
• Oct 29: 2h 15m (PHASE1: Sidebar + Cards)
• Oct 31: 3h 00m (PHASE1: Toggles + Forms)
• Nov 1: 2h 30m (PHASE2: Concerns started)
• Nov 3: 3h 00m (PHASE2: Helper methods)
• Nov 4: 2h 00m (PHASE2: Testing)
Progress:
• Tasks completed: 27 of 52 (53%)
• Phases completed: 1 of 3
• Estimated remaining: 10h 30m
Velocity:
• Average: 2.4 tasks per session
• Average: 0.47h per task
• Projected completion: Nov 6
```
---
## 🎯 Commands Reference
### Session Management
```bash
# At conversation start (automatic prompt)
[Choose 1/2/3]
# During session
/claude-os-session status # Current session details
/claude-os-session save "note" # Quick save
/claude-os-session switch "task" # Switch to different session
/claude-os-session pause # Pause current session
/claude-os-session end # End with summary
# View history
/claude-os-session today # Today's sessions
/claude-os-session week # This week
/claude-os-session history [task] # History for specific task
```
---
## 🔧 Configuration
### Session Preferences (`.claude-os/config.json`)
```json
{
"session_management": {
"mandatory": true,
"auto_prompt_on_start": true,
"auto_save_frequency": "15_minutes",
"inactivity_timeout": 15,
"auto_switch_detection": true,
"daily_summary": true,
"break_reminders": {
"enabled": true,
"interval_minutes": 120
}
}
}
```
---
## 💡 Best Practices
### 1. Name Sessions Descriptively
```
✅ Good: "Fix Tekmetric sync 500 errors"
✅ Good: "Implement group account rendering"
❌ Bad: "Work on stuff"
❌ Bad: "Bug fix"
```
### 2. Use Session Types Correctly
- **Feature:** Building new functionality
- **Bug:** Fixing specific issues
- **Exploration:** Learning/understanding code
- **Maintenance:** Refactoring/cleanup
- **Review:** Code review
- **Question:** Quick questions (auto-managed)
### 3. End Sessions Cleanly
```
Don't just close terminal!
Use: /claude-os-session end
Benefits:
• Captures work summary
• Suggests valuable saves
• Updates statistics
• Prepares next session
```
### 4. Switch Sessions When Context Changes
```
Don't mix unrelated work in one session!
Working on Dashboard → API bug appears → Switch sessions!
Keeps tracking clean and relevant.
```
---
## 🚀 Benefits Recap
### For You:
- **Never lose work** - Everything tracked
- **Perfect continuity** - Resume exactly where you left off
- **Understand productivity** - Real metrics
- **Learn from history** - See what works
### For Me (Claude):
- **Complete context** - Know what we're working on
- **Smart suggestions** - Based on session type
- **Proactive saves** - Capture valuable insights
- **Better guidance** - Relevant to current task
### For The Team:
- **Shared knowledge** - All insights saved
- **Velocity tracking** - Understand capacity
- **Pattern recognition** - Learn what works
- **Onboarding** - New members see work history
---
## 🎯 The Goal
**Every moment of coding is tracked, categorized, and learned from.**
No more:
- ❌ "What was I doing?"
- ❌ "How long did that take?"
- ❌ "Where did we leave off?"
- ❌ "Did we solve this before?"
Only:
- ✅ Instant context on session start
- ✅ Complete work history
- ✅ Automatic learning
- ✅ Zero context loss
**This is the complete AI development system working at full power.** 🚀
---
**Next Steps:**
1. Update CLAUDE.md template with mandatory session flow
2. Update `/claude-os-session` command to reflect new model
3. Start using it TODAY in current projects
4. Track results and iterate
**Let's make sessions mandatory!** 💪

233
docs/guides/PORTS_GUIDE.md Normal file
View file

@ -0,0 +1,233 @@
# Claude OS Ports Guide
## Two Different Services
Claude OS has **two separate services** running on **different ports**:
### 1. MCP Server (Port 8051)
**Purpose:** API server for Claude Code integration
**Technology:** FastAPI (Python)
**URL:** `http://localhost:8051`
**Usage:**
- Used by Claude Code (via MCP protocol)
- Handles AI memory operations
- Manages knowledge bases
- Serves API endpoints
**⚠️ IMPORTANT:**
- **Do NOT open this in a web browser**
- It expects MCP protocol POST requests
- Browser GET requests will show "Method Not Allowed"
**How to start:**
```bash
./start.sh # Starts MCP server
```
---
### 2. Web UI (Port 5173)
**Purpose:** Visual interface for humans
**Technology:** React + Vite (JavaScript)
**URL:** `http://localhost:5173`
**Usage:**
- Browse knowledge bases
- Upload documents
- Search and query
- Project management
- Visual interface
**✅ CORRECT:**
- **Open this URL in your browser**
- Designed for human interaction
- Nice visual interface
**How to start:**
```bash
cd frontend
npm install # First time only
npm run dev # Starts on port 5173
```
---
## Quick Reference
| Service | Port | Open in Browser? | Purpose |
|---------|------|------------------|---------|
| **MCP Server** | 8051 | ❌ NO | For Claude Code (API) |
| **Web UI** | 5173 | ✅ YES | For humans (visual interface) |
---
## Common Confusion
### ❌ Wrong: Opening http://localhost:8051 in browser
**What you see:**
```json
{
"detail": "Method Not Allowed"
}
```
**Why:** The MCP server expects POST requests with MCP protocol data, not browser GET requests.
### ✅ Correct: Opening http://localhost:5173 in browser
**What you see:**
- Nice web interface
- Project management
- Knowledge base browser
- Document upload
---
## Typical Workflow
### 1. Start MCP Server (for Claude Code)
```bash
./start.sh
```
**Output:**
```
✅ Claude OS is running!
📡 MCP Server: http://localhost:8051
(For Claude Code integration - do NOT open in browser)
```
### 2. Start Web UI (optional, for visual interface)
```bash
cd frontend
npm run dev
```
**Output:**
```
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
```
Now you can:
- Use Claude Code with MCP server (port 8051)
- Open Web UI in browser (port 5173)
---
## Testing Each Service
### Test MCP Server (Port 8051)
**Correct way (API call):**
```bash
curl -X POST http://localhost:8051/mcp/query \
-H "Content-Type: application/json" \
-d '{"query": "test"}'
```
**Wrong way (browser):**
```
Open http://localhost:8051 in browser
→ Shows "Method Not Allowed" ❌
```
### Test Web UI (Port 5173)
**Correct way:**
```
Open http://localhost:5173 in browser
→ Shows nice web interface ✅
```
---
## Troubleshooting
### "Method Not Allowed" error
**Problem:** You're trying to access the MCP server (port 8051) in a browser
**Solution:**
- Don't open port 8051 in browser
- That's for Claude Code to use, not humans
- If you want a web interface, start the frontend (port 5173)
### "Connection refused" on port 5173
**Problem:** Web UI is not running
**Solution:**
```bash
cd frontend
npm install # If first time
npm run dev
```
### "Connection refused" on port 8051
**Problem:** MCP server is not running
**Solution:**
```bash
./start.sh
```
---
## Architecture Diagram
```
┌─────────────────────────────────────────┐
│ YOUR BROWSER │
│ http://localhost:5173 ✅ │
│ (Web UI - React) │
└──────────────┬──────────────────────────┘
│ HTTP requests
▼
┌─────────────────────────────────────────┐
│ MCP Server (FastAPI) │
│ http://localhost:8051 │
│ │
│ ┌──────────────┐ ┌─────────┐ │
│ │ API Routes │◄───┤ Claude │ │
│ │ │ │ Code │ │
│ └──────┬───────┘ └─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Database │ │
│ │ (SQLite) │ │
│ └──────────────┘ │
└─────────────────────────────────────────┘
```
**Access patterns:**
- Browser → Port 5173 (Web UI) → Port 8051 (MCP Server) → Database
- Claude Code → Port 8051 (MCP Server) → Database
---
## Summary
**Remember:**
- 🔴 **Port 8051:** For Claude Code (MCP protocol) - Don't open in browser!
- 🟢 **Port 5173:** For humans (Web UI) - Open this in browser!
**When you run `./start.sh`:**
- MCP server starts on 8051 (for Claude Code)
- Web UI does NOT start automatically
- Start Web UI separately with `cd frontend && npm run dev`
**Clear now?** 🚀

View file

@ -0,0 +1,415 @@
# Session Parsing & Insights Guide
**Parse Claude Code sessions and extract insights automatically.**
---
## Overview
Claude Code stores conversation history in `.jsonl` session files located at:
```
~/.claude/projects/{encoded-project-path}/{session-id}.jsonl
```
Claude OS can parse these files to:
- Extract conversations, tool calls, and file changes
- Generate summaries for context loading
- Extract insights (patterns, decisions, blockers)
- Build analytics on your development sessions
---
## Session File Format
Claude Code session files are JSON Lines format with various entry types:
```jsonl
{"type": "summary", "summary": "...", "leafUuid": "..."}
{"type": "user", "uuid": "msg-001", "message": {"role": "user", "content": "..."}}
{"type": "assistant", "uuid": "msg-002", "message": {"role": "assistant", "content": [...]}}
{"type": "file-history-snapshot", "messageId": "...", "snapshot": {...}}
```
### Entry Types
| Type | Description |
|------|-------------|
| `summary` | Session summary (usually at start) |
| `user` | User message |
| `assistant` | Assistant response (may contain tool_use) |
| `file-history-snapshot` | File changes made during session |
---
## Using the Session Parser
### Python API
```python
from app.core.session_parser import (
SessionParser,
get_project_sessions_dir,
list_session_files
)
# Find sessions for a project
project_path = "/Users/me/Projects/myapp"
sessions = list_session_files(project_path)
# Returns: ['/path/to/session1.jsonl', '/path/to/session2.jsonl']
# Parse a session
parser = SessionParser(sessions[0])
session_data = parser.parse()
# Access parsed data
print(f"Session ID: {session_data.session_id}")
print(f"Messages: {len(session_data.messages)}")
print(f"Tool Calls: {len(session_data.tool_calls)}")
print(f"File Changes: {len(session_data.file_changes)}")
print(f"Duration: {session_data.start_time} to {session_data.end_time}")
# Get conversation messages
messages = parser.get_conversation()
for msg in messages:
print(f"{msg.role}: {msg.content[:100]}...")
# Get summary for LLM processing
summary = parser.get_summary_for_extraction(max_tokens=500)
print(summary)
```
### REST API
```bash
# List sessions for a project
curl "http://localhost:8051/api/sessions?project_path=/path/to/project&limit=10"
# Get session details
curl "http://localhost:8051/api/sessions/abc123?project_path=/path/to/project"
# Get session summary
curl "http://localhost:8051/api/sessions/abc123/summary?project_path=/path/to/project&max_tokens=500"
```
---
## Session Data Structure
### SessionData
```python
@dataclass
class SessionData:
session_id: str
session_path: str
messages: List[Message]
tool_calls: List[ToolCall]
file_changes: List[FileChange]
start_time: Optional[str]
end_time: Optional[str]
git_branch: Optional[str]
cwd: Optional[str]
total_entries: int
```
### Message
```python
@dataclass
class Message:
role: str # "user" or "assistant"
content: str # Message content
timestamp: str # ISO timestamp
uuid: str # Message UUID
parent_uuid: Optional[str]
```
### ToolCall
```python
@dataclass
class ToolCall:
tool_name: str # e.g., "Read", "Write", "Bash"
timestamp: str
uuid: str
parent_uuid: Optional[str]
input_data: Dict # Tool input parameters
```
### FileChange
```python
@dataclass
class FileChange:
file_path: str
timestamp: str
message_id: str # Which message triggered this change
```
---
## Insight Extraction
The InsightExtractor analyzes sessions to identify:
- **Patterns** - Recurring solutions and approaches
- **Decisions** - Architecture and implementation choices
- **Blockers** - Problems encountered and how they were resolved
- **Discoveries** - New learnings about the codebase
### Using InsightExtractor
```python
from app.core.insight_extractor import InsightExtractor, Insight
# Create extractor with Ollama
extractor = InsightExtractor(
ollama_host="http://localhost:11434",
model="llama3.1:8b"
)
# Extract insights from a session
parser = SessionParser("/path/to/session.jsonl")
summary = parser.get_summary_for_extraction(max_tokens=1000)
insights = await extractor.extract_insights(summary)
for insight in insights:
print(f"Type: {insight.insight_type}")
print(f"Title: {insight.title}")
print(f"Content: {insight.content}")
print(f"Tags: {insight.tags}")
print("---")
```
### Insight Types
| Type | Description |
|------|-------------|
| `pattern` | A reusable solution or approach |
| `decision` | An architectural or implementation choice |
| `blocker` | A problem that was encountered |
| `discovery` | Something new learned about the codebase |
### Saving Insights to Memory
```python
from app.core.sqlite_manager import get_sqlite_manager
db = get_sqlite_manager()
for insight in insights:
# Save to project memories KB
db.add_document(
kb_name=f"{project_name}-project_memories",
content=f"# {insight.title}\n\n{insight.content}",
filename=f"insight-{insight.insight_type}-{timestamp}.md",
metadata={
"type": insight.insight_type,
"tags": insight.tags,
"session_id": session_data.session_id
}
)
```
---
## Session Summary Format
The `get_summary_for_extraction()` method generates a structured summary:
```
# Session: abc123
Project: /Users/me/Projects/myapp
Branch: feature-auth
Duration: 2025-12-11T10:00:00Z to 2025-12-11T11:30:00Z
## Conversation (24 messages)
USER: Help me implement user authentication...
ASSISTANT: I'll help you implement authentication. Let me start by...
USER: Can you add password hashing?
ASSISTANT: Of course! I'll use bcrypt for secure password hashing...
## Tool Calls
- Read: /Users/me/Projects/myapp/src/auth.py
- Write: /Users/me/Projects/myapp/src/auth.py
- Read: /Users/me/Projects/myapp/tests/test_auth.py
- Write: /Users/me/Projects/myapp/tests/test_auth.py
- Bash: pytest tests/test_auth.py
## File Changes
- /Users/me/Projects/myapp/src/auth.py
- /Users/me/Projects/myapp/tests/test_auth.py
```
---
## Project Sessions Directory
Claude Code encodes project paths for the sessions directory:
```
/Users/me/Projects/myapp
→ ~/.claude/projects/-Users-me-Projects-myapp/
```
The encoding:
1. Replaces `/` with `-`
2. Keeps the leading `-` (from the initial `/`)
### Finding Sessions
```python
from app.core.session_parser import get_project_sessions_dir, list_session_files
# Get sessions directory for a project
sessions_dir = get_project_sessions_dir("/Users/me/Projects/myapp")
# Returns: ~/.claude/projects/-Users-me-Projects-myapp
# List all session files
sessions = list_session_files("/Users/me/Projects/myapp")
# Returns sorted list of .jsonl files (newest first)
```
---
## Use Cases
### 1. Load Context at Session Start
```python
# At session start, load recent session insights
sessions = list_session_files(project_path)
if sessions:
parser = SessionParser(sessions[0])
summary = parser.get_summary_for_extraction(max_tokens=500)
# Use summary to prime Claude with recent context
```
### 2. Build Session Analytics
```python
# Analyze all sessions for a project
sessions = list_session_files(project_path)
stats = {
"total_sessions": len(sessions),
"total_messages": 0,
"total_tool_calls": 0,
"tools_used": {}
}
for session_path in sessions:
parser = SessionParser(session_path)
data = parser.parse()
stats["total_messages"] += len(data.messages)
stats["total_tool_calls"] += len(data.tool_calls)
for tc in data.tool_calls:
stats["tools_used"][tc.tool_name] = \
stats["tools_used"].get(tc.tool_name, 0) + 1
```
### 3. Extract and Save Patterns
```python
# Extract insights from recent sessions
extractor = InsightExtractor()
for session_path in sessions[:5]: # Last 5 sessions
parser = SessionParser(session_path)
summary = parser.get_summary_for_extraction()
insights = await extractor.extract_insights(summary)
for insight in insights:
if insight.insight_type == "pattern":
# Save to memories KB
save_to_memories(project_name, insight)
```
### 4. Debug Failed Sessions
```python
# Find sessions with errors
for session_path in sessions:
parser = SessionParser(session_path)
data = parser.parse()
# Look for error patterns in messages
for msg in data.messages:
if "error" in msg.content.lower() or "failed" in msg.content.lower():
print(f"Session {data.session_id} may have errors")
print(f" Message: {msg.content[:100]}...")
break
```
---
## Configuration
### Environment Variables
```bash
# Ollama settings for insight extraction
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b
```
### Truncation Settings
Large sessions are automatically truncated for LLM processing:
```python
# Default: 500 tokens (~2000 chars)
summary = parser.get_summary_for_extraction(max_tokens=500)
# For more context
summary = parser.get_summary_for_extraction(max_tokens=2000)
```
---
## Troubleshooting
### "No sessions found"
- Verify project path is correct
- Check that sessions exist: `ls ~/.claude/projects/`
- The encoded path includes leading hyphen: `-Users-me-Projects-myapp`
### "Failed to parse session"
- Session file may be corrupted
- Check for valid JSON on each line
- Malformed lines are skipped automatically
### "Insight extraction failed"
- Verify Ollama is running: `ollama list`
- Check model is available: `ollama pull llama3.1:8b`
- Check logs for API errors
---
## API Reference
### REST Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/sessions` | GET | List project sessions |
| `/api/sessions/{id}` | GET | Get session details |
| `/api/sessions/{id}/summary` | GET | Get session summary |
### MCP Tools
| Tool | Description |
|------|-------------|
| `mcp__code-forge__list_sessions` | List sessions for a project |
| `mcp__code-forge__get_session` | Get session details |
| `mcp__code-forge__get_session_summary` | Get formatted summary |
---
**See Also:**
- [API Reference](../API_REFERENCE.md) - Session API endpoints
- [Self Learning System](../SELF_LEARNING_SYSTEM.md) - How Claude learns
- [README](../../README.md) - Full Claude OS documentation

View file

@ -0,0 +1,410 @@
# Session Implementation Summary
**What we just built: Mandatory session management system for Claude OS**
---
## 🎯 The Big Idea
**Every conversation is a session. Always.**
No more optional tracking. No more lost context. No more "what was I doing?"
---
## 📚 What We Created
### 1. **MANDATORY_SESSIONS.md** (Complete Spec)
Location: `/Users/iamanmp/Projects/claude-os/docs/guides/MANDATORY_SESSIONS.md`
**Contents:**
- Session lifecycle (selection → active → end)
- 6 session types (feature, bug, exploration, maintenance, review, question)
- Session switching flow
- Statistics and tracking
- Commands reference
- Best practices
**Key Innovation:** Always-in-session model. User chooses WHICH session, not WHETHER to session.
### 2. **SESSION_START_PROTOCOL.md** (Technical Spec)
Location: `/Users/iamanmp/Projects/claude-os/docs/guides/SESSION_START_PROTOCOL.md`
**Contents:**
- 7-phase automatic startup protocol
- What to read, search, and display
- Configuration options
- When to skip auto-start
- Complete implementation checklist
**Key Innovation:** Automatic context loading in ~50 seconds
### 3. **IDEAL_SESSION_WORKFLOW.md** (User Guide)
Location: `/Users/iamanmp/Projects/claude-os/docs/guides/IDEAL_SESSION_WORKFLOW.md`
**Contents:**
- "Should happen" vs "Actually happens"
- Practical workarounds
- Power user tips
- Quick reference
**Key Innovation:** Shows the vision vs reality gap
### 4. **Updated CLAUDE.md Template**
Location: `/Users/iamanmp/Projects/claude-os/templates/project-files/CLAUDE.md.template`
**Changes:**
- Added mandatory session prompt
- 3-option choice (Resume/New/Question)
- Detailed session start protocol
- Links to all documentation
**Key Innovation:** Makes mandatory sessions the DEFAULT for all new projects
---
## 🔄 The Flow
### Conversation Start
```
User opens Claude Code in project directory
↓
Claude reads CLAUDE.md (automatic)
↓
Claude sees mandatory session protocol
↓
Claude prompts:
═══════════════════════════════════════════════════════════════
🚀 CLAUDE OS - SESSION MANAGER
═══════════════════════════════════════════════════════════════
Project: Pistn
Last Session: Dashboard Redesign (2 days ago, 2h 15m)
Progress: 45% complete
Options:
1. Resume "Dashboard Redesign" [loads full context]
2. Start new session [what are you working on?]
3. Quick question [auto-session, minimal context]
Choice: _
═══════════════════════════════════════════════════════════════
```
### User Chooses Option 1 (Resume)
```
Claude automatically:
✓ Reads .claude-os/config.json
✓ Reads claude-os-state.json
✓ Checks git status
✓ Searches project_memories
✓ Loads coding standards
✓ Checks Kanban board
✓ Presents comprehensive summary
═══════════════════════════════════════════════════════════════
✓ RESUMING: Dashboard Redesign
═══════════════════════════════════════════════════════════════
[Full context loaded]
Progress: 45% complete (23 of 52 tasks)
Current Task: PHASE2-TASK3 (Implement concern methods)
Key insights:
• 67-page implementation plan loaded
• Zero functionality loss requirement
• Bootstrap 3.x only
Ready! Let's continue PHASE2-TASK3.
═══════════════════════════════════════════════════════════════
```
### User Chooses Option 2 (New Session)
```
Claude: "What are you working on?"
User: "Fix the Tekmetric API 500 errors"
Claude automatically:
✓ Detects type: Bug Fix
✓ Pauses previous session
✓ Searches for "Tekmetric API errors"
✓ Loads relevant memories
✓ Starts tracking
═══════════════════════════════════════════════════════════════
✓ NEW BUG SESSION STARTED
═══════════════════════════════════════════════════════════════
Session: "Fix Tekmetric API 500 errors"
Type: Bug Fix
Priority: High
Found 3 similar issues from past sessions.
Dashboard Redesign session paused.
Let's debug! What's the error?
═══════════════════════════════════════════════════════════════
```
### User Chooses Option 3 (Quick Question)
```
Claude: "What's your question?"
User: "How do I use Draper decorators?"
Claude:
═══════════════════════════════════════════════════════════════
💬 QUICK QUESTION SESSION
═══════════════════════════════════════════════════════════════
[Answers question with minimal context loading]
Auto-ends after 5 min inactivity.
Ready to resume Dashboard Redesign?
═══════════════════════════════════════════════════════════════
```
---
## 📊 Session Types
### 1. Feature Implementation
- Linked to Kanban spec
- Tracks task completion
- Saves patterns and architecture decisions
### 2. Bug Fix
- Auto-detects priority from description
- Searches past similar issues
- Saves root cause and solution
### 3. Exploration / Learning
- Tracks files explored
- Saves key learnings
- Documents architecture insights
### 4. Refactoring / Maintenance
- Tracks complexity reduction
- Saves refactoring patterns
- Documents improvements
### 5. Code Review
- Tracks issues found
- Saves review patterns
- Documents suggestions
### 6. Quick Question (Auto)
- Auto-managed
- Minimal tracking
- Only saves if valuable
---
## 💾 What Gets Tracked
### Every Session:
- Start/end time
- Duration
- Files changed
- Git commits
- Quick saves (during session)
- Type (feature/bug/etc)
- Related spec (if applicable)
- Related tasks (if Kanban)
### At Session End:
- Work summary
- Memories saved
- Patterns discovered
- Blockers encountered
- Progress made
- Statistics updated
### Across Sessions:
- Total time per project
- Total time per task/spec
- Average session duration
- Most productive times
- Velocity (tasks/hour)
- Pattern usage frequency
---
## 🎯 Benefits
### Zero Context Loss
```
Before: "What was I doing?"
After: "Welcome back! You were on PHASE2-TASK3, 45% done."
```
### Complete Work History
```
Before: Manual time tracking, scattered notes
After: Automatic tracking, everything in one place
```
### Smart Insights
```
Before: Guess at velocity, no patterns tracked
After: "You average 2.4 tasks per session at 0.47h per task"
```
### Automatic Learning
```
Before: Solve same problems repeatedly
After: "We solved this before in session X, here's the solution"
```
### Better Planning
```
Before: "How long will this take? No idea."
After: "Based on past velocity, ~10h 30m remaining"
```
---
## 🚀 Next Steps
### For New Projects:
1. Run `/claude-os-init`
2. CLAUDE.md automatically includes mandatory session protocol
3. First conversation: Choose session type
4. Start working with full context!
### For Existing Projects:
1. Update CLAUDE.md with new session protocol section
2. Copy from template: `templates/project-files/CLAUDE.md.template`
3. Next conversation: Session prompt appears
4. Choose option and start working!
### For Claude OS Development:
1. **API Enhancement:**
- Add `/api/sessions/` endpoints
- Session start, end, pause, resume, switch
- Statistics endpoints
2. **Database Schema:**
- `sessions` table (id, project_id, type, task, started_at, ended_at, duration, files_changed, etc)
- Link to specs/tasks
- Statistics and tracking
3. **Frontend UI:**
- Session dashboard
- Live session timer
- Session history viewer
- Statistics visualizations
4. **CLI Integration:**
- `/claude-os-session` commands
- Auto-prompt on conversation start
- Background session tracking
---
## 📖 Documentation Tree
```
docs/guides/
├── MANDATORY_SESSIONS.md ← Complete specification
├── SESSION_START_PROTOCOL.md ← Technical implementation
├── IDEAL_SESSION_WORKFLOW.md ← User guide & vision
├── SESSION_IMPLEMENTATION_SUMMARY.md ← This file
└── WHAT_IS_CLAUDE_OS.md ← Updated with session info
templates/project-files/
└── CLAUDE.md.template ← Updated with mandatory sessions
templates/commands/
└── claude-os-session.md ← Session commands reference
```
---
## 🎉 What This Enables
### For You (User):
- Never lose context
- Track ALL work automatically
- Understand your velocity
- Learn from history
- Better planning
### For Claude (AI):
- Always have context
- Smart suggestions based on session type
- Proactive memory loading
- Better guidance
- Continuous learning
### For The Project:
- Complete work history
- Pattern recognition
- Knowledge preservation
- Velocity tracking
- Team insights
---
## 💡 The Vision Realized
**Remember the "amazing" Kanban board we just built?**
Now combine it with **mandatory sessions**:
```
You open Claude Code
↓
Claude: "Resume Dashboard Redesign?"
↓
You: "Yes"
↓
Claude: "You're on PHASE2-TASK3 of 52 tasks (45% complete).
I have your implementation plan, 5 memories, and
all coding standards loaded. Ready!"
↓
You work for 2 hours
↓
Claude: "You completed 2 tasks! Progress: 45% → 53%
Save these 2 patterns to memories? [Y/n]"
↓
You: "y"
↓
Claude: "✓ Saved! Great session. Total time on this spec: 15h.
Estimated 9h remaining. See you tomorrow!"
```
**That's the complete AI development system.** 🚀
---
## 📊 Current Status
### ✅ Completed:
- [x] Conceptual design
- [x] Complete documentation (4 guides)
- [x] CLAUDE.md template updated
- [x] Session command reference
- [x] Kanban board integration documented
### 🔄 Next (Optional):
- [ ] API endpoints for sessions
- [ ] Database schema for session tracking
- [ ] Frontend session dashboard
- [ ] Auto-tracking implementation
- [ ] Statistics visualization
### 🎯 Ready to Use:
**You can start using mandatory sessions TODAY!**
Just add the session protocol to your project's CLAUDE.md, and at the start of each conversation, I'll prompt for session choice.
---
**This is revolutionary. Let's do it!** 💪

View file

@ -0,0 +1,390 @@
# Session Start Protocol
**What Claude Should Do AUTOMATICALLY When You Start ANY Conversation**
---
## 🎯 The Problem
When you start Claude Code CLI:
- You're in a specific project directory
- You may have been working on something
- There's context from previous sessions
- You have knowledge in Claude OS
**But Claude starts with ZERO context.**
---
## ✅ The Solution: Automatic Session Start
**Every time a conversation starts, Claude should:**
### Phase 1: Detect Project (5 seconds)
```bash
# Check for Claude OS project
pwd
ls -la | grep -E "(CLAUDE.md|.claude-os|agent-os)"
```
**If `.claude-os/config.json` exists:**
- ✅ This is a Claude OS project
- → Proceed with full session start
**If not:**
- ℹ️ Generic mode (no project context)
- → Offer to initialize with `/claude-os-init`
---
### Phase 2: Read Project State (10 seconds)
**Files to read:**
1. **`CLAUDE.md`** - Project overview (always loaded first)
2. **`.claude-os/config.json`** - Project configuration
3. **`claude-os-state.json`** - Session state (if exists)
4. **`agent-os/config.yml`** - Agent-OS config (if using specs)
**Example:**
```
Reading CLAUDE.md...
Reading .claude-os/config.json...
Reading claude-os-state.json...
✓ Project: Pistn
✓ MCPs: 4 configured
✓ Last session: Oct 29, 2025 (2 days ago)
✓ Agent-OS: Enabled (3 active specs)
```
---
### Phase 3: Check Git Context (5 seconds)
```bash
git branch --show-current
git status --short
git log -3 --oneline
git remote get-url origin
```
**Extract:**
- Current branch name
- Uncommitted changes count
- Recent commits
- Whether we're ahead/behind remote
**Example:**
```
Branch: feature/appointment-redesign
Status: 3 files modified, 1 untracked
Recent commits:
- abc1234 Add sidebar navigation
- def5678 Convert panels to cards
- ghi9012 Update appointment form layout
```
---
### Phase 4: Search Project Memories (15 seconds)
**Search query based on:**
- Last session task (from `claude-os-state.json`)
- Current branch name
- Recent commit messages
- `+recent` modifier
**Example query:**
```
"appointment dashboard redesign recent"
```
**Search:**
- `{project}-project_memories` knowledge base
- Return top 5 most relevant memories
- Show me titles and key insights
**Example:**
```
📚 Found 5 relevant memories:
1. Appointment Dashboard Redesign Plan (Oct 28)
→ 67-page implementation plan, 5 tabs documented
2. Current Dashboard Analysis (Oct 28)
→ All features catalogued, zero functionality loss
3. Bootstrap to Modern Cards Pattern (Oct 25)
→ Reusable conversion pattern
4. Sidebar Navigation Pattern (Oct 22)
→ localStorage persistence, responsive
5. Tekmetric Integration Notes (Oct 20)
→ Must preserve API sync on save
```
---
### Phase 5: Load Coding Standards (10 seconds)
**Search `{project}-project_profile`:**
- Query: "coding standards architecture conventions"
- Load key architectural decisions
- Load tech stack preferences
**Example:**
```
📋 Coding Standards:
• Service objects for business logic
• Decorator pattern with Draper
• Fragment caching with Redis
• Bootstrap 3.x styling (no Bootstrap 4/5)
• RSpec for testing
• Concerns for shared controller logic
```
---
### Phase 6: Check Kanban Board (5 seconds)
**If Agent-OS is enabled:**
```http
GET /api/projects/{id}/kanban
```
**Show:**
- Total specs
- Active specs (not completed/archived)
- Current tasks in progress
- Next tasks todo
**Example:**
```
📊 Kanban Board:
• 3 specs total (52 tasks)
• 1 spec in progress: Group Account Rendering (45% complete)
• 2 tasks in progress:
- PHASE2-TASK3: Implement concern methods
- PHASE2-TASK4: Add helper methods
• 29 tasks remaining
```
---
### Phase 7: Present Session Start Summary (Display to User)
**Format:**
```
═══════════════════════════════════════════════════════════════
🚀 WELCOME BACK!
═══════════════════════════════════════════════════════════════
📁 Project: Pistn (Ruby on Rails)
🌿 Branch: feature/appointment-redesign
📅 Last Session: 2 days ago (Oct 29, 2025)
⏱️ Duration: 2h 15m
═══════════════════════════════════════════════════════════════
📚 CONTEXT LOADED
═══════════════════════════════════════════════════════════════
✓ 5 relevant memories found
✓ Coding standards loaded
✓ 3 architectural patterns available
✓ Kanban board synced (45% complete)
🎯 LAST TASK: Redesign Appointment Dashboard
We completed:
✓ Sidebar navigation component
✓ Bootstrap panels → modern cards
✓ iOS-style toggle switches
Still working on:
⏳ Phase 2: Implement concern methods
⏳ Phase 2: Add helper methods
═══════════════════════════════════════════════════════════════
💡 KEY INSIGHTS
═══════════════════════════════════════════════════════════════
• 67-page implementation plan available
• Zero functionality loss requirement
• Tekmetric integration must be preserved
• Bootstrap 3.x only (no v4/v5)
═══════════════════════════════════════════════════════════════
🔄 AVAILABLE PATTERNS
═══════════════════════════════════════════════════════════════
• Sidebar navigation (from user-auth work)
• Card-based layouts (from reports redesign)
• Modern toggle switches (already in app)
• Service object pattern (business logic)
• Draper decorators (view logic)
═══════════════════════════════════════════════════════════════
⚠️ STATUS
═══════════════════════════════════════════════════════════════
Git Status:
• 3 files modified
• 1 file untracked
• ⚠️ Uncommitted changes (from last session)
Blockers:
• None! ✓
═══════════════════════════════════════════════════════════════
🎯 SUGGESTED NEXT STEPS
═══════════════════════════════════════════════════════════════
Option 1: Continue Phase 2 Implementation
→ Implement concern methods for PHASE2-TASK3
→ Should take ~30 minutes based on estimate
Option 2: Commit Previous Work
→ 3 files modified need to be committed
→ Clean git state before starting new work
Option 3: Start Something New
→ Tell me what you'd like to work on
═══════════════════════════════════════════════════════════════
What would you like to do?
```
---
## 🎛️ Configuration
**Auto-start can be configured in `.claude-os/config.json`:**
```json
{
"session_management": {
"auto_start": true,
"auto_search_memories": true,
"max_memories_to_load": 5,
"search_days_back": 14,
"show_git_status": true,
"show_kanban_status": true,
"proactive_suggestions": true
}
}
```
---
## 🚫 When NOT to Auto-Start
**Skip session start if:**
- User message starts with a direct question (e.g., "What is...")
- User is clearly asking for help/docs (e.g., "How do I...")
- User explicitly says "ignore context" or "fresh start"
- No `.claude-os/` directory exists
**In these cases:**
- Answer the question directly
- Offer to initialize Claude OS after answering
---
## 📊 Session Tracking During Conversation
**Throughout the session, Claude should:**
### Track Key Events
- When user starts implementing something
- When they hit a blocker
- When they discover a pattern
- When they make an architectural decision
- When they solve a complex problem
### Proactive Memory References
```
You: "I need to add caching to this endpoint"
Claude: "FYI, we have a memory about fragment caching with Redis
from the reports redesign. Want me to use that pattern?"
```
### Auto-Save Suggestions
```
You: "Finally got the Tekmetric sync working with the new structure!"
Claude: "That sounds like a high-value solution. Would you like me
to save this to project memories for future reference?"
```
---
## 💾 Session End Protocol
**When conversation ends (or user says "done"):**
### Option 1: User Runs `/claude-os-session end`
- Follow the detailed end protocol from claude-os-session.md
- Analyze work, suggest saves, update statistics
### Option 2: Conversation Ends Naturally
- Claude should still provide a brief summary:
```
═══════════════════════════════════════════════════════════════
📊 SESSION COMPLETE
═══════════════════════════════════════════════════════════════
Duration: ~45 minutes
Work: Implemented concern methods for PHASE2-TASK3
Quick saves available:
• Concern method pattern for group accounts
• Service object integration approach
Run /claude-os-session end for full summary and saves
Or just continue working next time - I'll remember!
═══════════════════════════════════════════════════════════════
```
---
## 🎯 The Goal
**Every session should feel like:**
- Claude already knows everything
- Claude remembers what we were doing
- Claude proactively brings up relevant context
- Claude suggests next steps based on history
- Zero context loss between sessions
**NOT like:**
- Starting from scratch
- Repeating ourselves
- Explaining the same architecture again
- Searching for what we did before
---
## 🔄 Implementation Checklist
For Claude to do this automatically:
- [ ] Check for `.claude-os/` directory at conversation start
- [ ] Read `CLAUDE.md` if present
- [ ] Read `.claude-os/config.json` if present
- [ ] Read `claude-os-state.json` if present
- [ ] Run git status and git log
- [ ] Search project_memories with intelligent query
- [ ] Load coding standards from project_profile
- [ ] Check Kanban board status (if Agent-OS enabled)
- [ ] Present comprehensive session start summary
- [ ] Track key events during session
- [ ] Provide session end summary
---
**This protocol ensures you NEVER start a conversation from zero context again!** 🚀

View file

@ -0,0 +1,315 @@
# Claude OS Sharing Guide
## Overview
This guide explains how to share Claude OS with coworkers and set up new projects.
## Architecture
```
claude-os/
├── templates/ # All reusable templates
│ ├── commands/ # Slash commands (symlinked to ~/.claude/)
│ ├── skills/ # Skills (symlinked to ~/.claude/)
│ ├── agents/ # Agent-OS agents (8 specialized agents)
│ └── project-files/ # Files created during project init
│ ├── CLAUDE.md.template
│ ├── agent-os/ # Agent-OS project templates
│ └── .claude-os/ # Config templates
├── cli/ # CLI tools
│ └── claude-os-consolidate.sh
├── install.sh # One-command install for coworkers
└── app/ # Flask API server
```
## For You (Initial Setup)
### Step 1: Consolidate Your Files
Move all scattered commands and skills into templates/:
```bash
cd /Users/iamanmp/Projects/claude-os
./cli/claude-os-consolidate.sh
```
This will:
- Move commands from `~/.claude/commands/` → `templates/commands/`
- Move skills from `~/.claude/skills/` → `templates/skills/`
- Create symlinks so everything still works for you
- Update any hardcoded paths
### Step 2: Commit to Git
```bash
cd /Users/iamanmp/Projects/claude-os
git add templates/ cli/ install.sh SHARING_GUIDE.md
git commit -m "Add templates and installation system for sharing"
git push
```
### Step 3: Test Installation (Optional)
Test on a different machine or user account:
```bash
git clone your-repo/claude-os.git
cd claude-os
./install.sh
```
## For Coworkers (First Time Setup)
### Step 1: Clone Claude OS
```bash
git clone your-repo/claude-os.git
cd claude-os
```
### Step 2: Run Install Script
```bash
./install.sh
```
This will:
- ✅ Create `~/.claude/` directories
- ✅ Symlink all commands and skills
- ✅ Optional: Install Agent-OS (8 agents by Builder Methods)
- ✅ Ask about AI provider (Ollama or OpenAI)
- ✅ Set up Python virtual environment
- ✅ Install dependencies
- ✅ Configure MCP server
- ✅ Create start script
**Note**: Agent-OS is an optional integration created by Builder Methods (CasJam Media LLC) and MIT licensed. The installer will ask if you want to install it.
### Step 3: Start Claude OS
```bash
./start.sh
```
This starts the MCP server at `http://localhost:8051`
### Step 4: Initialize Their First Project
```bash
cd /path/to/their/project
```
In Claude Code, run:
```
/claude-os-init
```
Follow the prompts to:
- Name the project
- Specify tech stack
- Ingest documentation
- Analyze codebase
Done! Their project is now connected to Claude OS.
## For New Projects (After Setup)
Once Claude OS is installed, initializing new projects is simple:
```bash
cd /path/to/new/project
```
In Claude Code:
```
/claude-os-init
```
Answer the questions and you're done!
## What Gets Created During Project Init
```
your-project/
├── CLAUDE.md # Project context (auto-loaded every session)
├── .claude/ # Project-specific Claude Code files
│ ├── commands/ # (symlinks to Claude OS templates)
│ ├── skills/ # (symlinks to Claude OS templates)
│ ├── agents/ # Project-specific agents
│ ├── ARCHITECTURE.md # Generated by initialize-project
│ ├── CODING_STANDARDS.md
│ └── DEVELOPMENT_PRACTICES.md
└── .claude-os/ # Claude OS state/config
├── config.json # Project configuration
├── hooks.json # Project hooks
└── .gitignore # State files ignored
```
## Knowledge Bases Created
Each project gets 4 knowledge bases:
1. **`{project}-project_memories`** - Claude's memory for decisions, patterns, solutions
2. **`{project}-project_profile`** - Architecture, standards, practices
3. **`{project}-project_index`** - Automated codebase index
4. **`{project}-knowledge_docs`** - Your documentation (auto-ingested)
## Commands Available After Setup
All these work in any initialized project:
- `/claude-os-init` - Initialize new project
- `/claude-os-search` - Search project memories
- `/claude-os-save` - Save insights
- `/claude-os-remember` - Quick save to memories
- `/claude-os-list` - List knowledge bases
- `/claude-os-session` - Manage dev sessions
- `/claude-os-triggers` - Manage trigger phrases
## Skills Available
- `initialize-project` - Analyze codebase and generate standards
- `remember-this` - Auto-save when you say "remember this:"
- `memory` - Simple memory management
## Agent-OS Commands (If Installed)
If you chose to install Agent-OS during setup, you also get these commands:
- `/new-spec` - Initialize a new feature specification
- `/create-spec` - Full specification workflow
- `/plan-product` - Create product documentation
- `/implement-spec` - Implement a specification
**Agent-OS** is created by Builder Methods (CasJam Media LLC) and provides 8 specialized agents for spec-driven development. Learn more at https://github.com/builder-methods/agent-os
## Benefits for Your Team
### One-Time Setup
- Clone repo → run install → done
- No configuration needed
- Works on Mac/Linux (Windows with WSL)
### Project Isolation
- Each project has its own knowledge bases
- No cross-contamination
- Team members can work on multiple projects
### AI Memory Across Sessions
- Claude remembers decisions across conversations
- Searchable history of all work
- Pattern recognition improves over time
### Documentation Integration
- Automatically indexes your docs
- Searchable alongside code context
- Always up to date
## Updating Claude OS
When you add new commands/skills to templates:
```bash
# On your machine:
cd /Users/iamanmp/Projects/claude-os
git pull origin main
# Commands/skills auto-update via symlinks!
```
Coworkers do the same:
```bash
cd ~/Projects/claude-os
git pull origin main
# Their symlinks automatically point to updated templates
```
## Troubleshooting
### "Command not found: /claude-os-init"
Symlinks weren't created. Re-run:
```bash
cd /path/to/claude-os
./install.sh
```
### "Connection refused to localhost:8051"
Claude OS server isn't running:
```bash
cd /path/to/claude-os
./start.sh
```
### "Project already exists"
Project name is taken. Choose a different name or delete the old project via the UI at `http://localhost:8051`
### Commands work but changes don't persist
Make sure you're committing changes to the Claude OS repo, not your project repo:
```bash
cd /path/to/claude-os # NOT your project dir
git status
git add templates/
git commit -m "Updated templates"
git push
```
## Adding Custom Commands/Skills
### Add a New Command
1. Create file in `templates/commands/my-command.md`
2. Write command logic (see existing commands for examples)
3. Commit to git
4. Coworkers pull and get it automatically via symlinks
### Add a New Skill
1. Create directory in `templates/skills/my-skill/`
2. Add `SKILL.md` and any scripts
3. Commit to git
4. Coworkers pull and get it automatically
## Security Notes
- `.claude-os/` state files are git-ignored by default
- Never commit API keys or secrets to CLAUDE.md
- Each team member has their own Claude OS database
- Knowledge bases are local, not shared (unless you want to sync them)
## FAQ
**Q: Do I need to reinstall for each project?**
A: No! Install Claude OS once, then just run `/claude-os-init` in each project.
**Q: Can multiple people work on the same project?**
A: Yes! Each person has their own Claude OS database. Shared project knowledge goes in CLAUDE.md (git-tracked).
**Q: What if someone doesn't have Claude Code?**
A: They need Claude Code (the CLI tool) to use this. Free at https://claude.com/claude-code
**Q: How much disk space does it use?**
A: ~500MB for Claude OS + dependencies. Each project's knowledge base is ~10-100MB depending on docs.
**Q: Does this work on Windows?**
A: Yes, with WSL (Windows Subsystem for Linux). Native Windows support coming soon.
## Support
- 📚 Documentation: See README.md in this repo
- 🐛 Issues: Open an issue on GitHub
- 💬 Questions: Ask in team chat or @ the maintainer
## Next Steps
1. **For you**: Run `./cli/claude-os-consolidate.sh` to organize your files
2. **For coworkers**: Share the repo URL and have them run `./install.sh`
3. **For new projects**: Just run `/claude-os-init` and answer questions
---
**You built this! Now share it and make your whole team invincible! 🚀**

342
docs/guides/SKILLS_GUIDE.md Normal file
View file

@ -0,0 +1,342 @@
# Skills Library Guide
**Browse, install, and manage Claude Code skills with Claude OS.**
---
## What Are Skills?
Skills are reusable instruction sets that teach Claude specific capabilities. They're stored as markdown files in `.claude/skills/` directories and are automatically loaded by Claude Code when relevant.
Skills can include:
- Coding patterns and best practices
- Tool usage workflows (e.g., PDF manipulation, spreadsheet editing)
- Domain-specific knowledge (e.g., Rails patterns, React hooks)
- Development methodologies (e.g., TDD, debugging frameworks)
---
## Skill Types
### Global Skills (`~/.claude/skills/`)
Available in ALL projects. These are core skills that come with Claude OS:
| Skill | Description |
|-------|-------------|
| `memory` | Save and recall information across sessions |
| `remember-this` | Auto-save on trigger phrases like "remember this:" |
| `initialize-project` | Analyze codebase and generate documentation |
### Project Skills (`{project}/.claude/skills/`)
Available only in the specific project. Install from:
- **Local Templates** - Pre-built skills bundled with Claude OS
- **Community Skills** - Skills from GitHub repositories
- **Custom** - Skills you create yourself
---
## Using the Skills Command
The `/claude-os-skills` command provides full skills management:
```bash
# List all installed skills (global + project)
/claude-os-skills
# Browse available local templates
/claude-os-skills templates
# Install a template to your project
/claude-os-skills install <name>
# Create a custom skill interactively
/claude-os-skills create
# View skill details and content
/claude-os-skills view <name>
# Delete a project skill
/claude-os-skills delete <name>
```
### Example Output
```
═══════════════════════════════════════
📚 CLAUDE CODE SKILLS
═══════════════════════════════════════
🌐 GLOBAL SKILLS (always available)
────────────────────────────────────
✓ memory - Save and recall information
✓ remember-this - Auto-save on trigger phrases
✓ initialize-project - Analyze codebase
📁 PROJECT SKILLS (/path/to/project)
────────────────────────────────────
✓ rails-backend - Rails patterns and service objects
✓ rspec - RSpec testing patterns
💡 TIP: Run '/claude-os-skills templates' to see available templates
═══════════════════════════════════════
```
---
## Local Skill Templates
Claude OS includes a library of skill templates organized by category:
### Categories
| Category | Skills |
|----------|--------|
| **general** | `analyze-project`, `code-review` |
| **rails** | `rails-backend`, `rails-api`, `active-record` |
| **react** | `react-patterns`, `typescript-react`, `hooks` |
| **testing** | `rspec`, `jest`, `pytest`, `tdd` |
### Installing a Template
```bash
/claude-os-skills install rails-backend
```
This copies the template to your project's `.claude/skills/` directory.
---
## Community Skills
Browse and install skills from trusted GitHub repositories!
### Available Sources
| Source | Repository | Skills | Description |
|--------|------------|--------|-------------|
| **Anthropic Official** | `anthropics/skills` | 16 | Official skills from Anthropic |
| **Superpowers** | `obra/superpowers` | 20 | Battle-tested TDD, debugging, collaboration |
### Featured Community Skills
**From Anthropic Official:**
| Skill | Description |
|-------|-------------|
| `pdf` | Create, edit, analyze PDF documents |
| `xlsx` | Spreadsheet manipulation with formulas |
| `docx` | Word document creation and editing |
| `pptx` | Presentation creation and editing |
| `frontend-design` | Production-grade UI components |
| `mcp-builder` | Create MCP servers |
| `doc-coauthoring` | Collaborative documentation workflow |
| `canvas-design` | Visual art and poster creation |
| `webapp-testing` | Playwright-based web testing |
**From Superpowers:**
| Skill | Description |
|-------|-------------|
| `test-driven-development` | TDD workflow: red-green-refactor |
| `systematic-debugging` | Four-phase debugging framework |
| `root-cause-tracing` | Trace bugs back through call stack |
| `receiving-code-review` | Handle code review feedback |
| `requesting-code-review` | Dispatch code review subagent |
| `brainstorming` | Structured ideation process |
| `using-git-worktrees` | Isolated development branches |
| `verification-before-completion` | Evidence-based completion claims |
| `defense-in-depth` | Multi-layer validation |
### Installing via Web UI
1. Open http://localhost:5173
2. Select your project
3. Click the **Skills** tab
4. Click **Install Template** button
5. Switch to **Community Skills** tab
6. Browse skills from Anthropic Official and Superpowers
7. Click **Install** on any skill
### Installing via API
```bash
# List community sources
curl http://localhost:8051/api/skills/community/sources
# List all community skills
curl http://localhost:8051/api/skills/community
# Filter by source
curl "http://localhost:8051/api/skills/community?source=anthropic"
# Install a community skill
curl -X POST "http://localhost:8051/api/skills/community/install?project_path=/path/to/project" \
-H "Content-Type: application/json" \
-d '{
"name": "pdf",
"source": "anthropic",
"repo": "anthropics/skills",
"path": "skills/pdf"
}'
```
---
## Creating Custom Skills
### Via Command
```bash
/claude-os-skills create
```
Claude will interactively ask for:
1. Skill name (e.g., "deployment")
2. Description (one sentence)
3. Content (the skill instructions)
### Via API
```bash
curl -X POST "http://localhost:8051/api/skills?project_path=/path/to/project" \
-H "Content-Type: application/json" \
-d '{
"name": "my-deployment",
"description": "Deployment workflow for my project",
"content": "# Deployment Skill\n\n## Steps\n1. Run tests\n2. Build\n3. Deploy",
"category": "devops",
"tags": ["deployment", "ci-cd"]
}'
```
### Skill File Structure
A skill is a directory containing:
```
my-skill/
├── skill.md # Main skill instructions (required)
├── metadata.json # Skill metadata (optional)
└── examples/ # Example files (optional)
├── example1.md
└── example2.py
```
### Writing Good Skills
1. **Be Specific** - Focus on one capability
2. **Include Examples** - Show how to use the skill
3. **Document Triggers** - When should Claude use this skill?
4. **Test It** - Verify Claude follows the instructions
Example skill content:
```markdown
# Rails Service Objects
Use this skill when implementing business logic in Rails applications.
## When to Use
- Complex operations involving multiple models
- Operations that need to be tested in isolation
- Reusable business logic
## Pattern
```ruby
class CreateUser
def initialize(params)
@params = params
end
def call
user = User.new(@params)
if user.save
send_welcome_email(user)
Result.success(user)
else
Result.failure(user.errors)
end
end
private
def send_welcome_email(user)
UserMailer.welcome(user).deliver_later
end
end
```
## Usage
```ruby
result = CreateUser.new(user_params).call
if result.success?
redirect_to result.value
else
render :new, errors: result.errors
end
```
```
---
## Skill Locations
| Type | Location | Scope |
|------|----------|-------|
| Global Skills | `~/.claude/skills/` | All projects |
| Project Skills | `{project}/.claude/skills/` | Single project |
| Templates | `claude-os/templates/skill-library/` | Install to project |
| Community | GitHub repositories | Install to project |
---
## MCP Tools
Skills management is also available via MCP tools:
| Tool | Description |
|------|-------------|
| `mcp__code-forge__list_skills` | List all skills |
| `mcp__code-forge__list_skill_templates` | List available templates |
| `mcp__code-forge__install_skill_template` | Install a template |
| `mcp__code-forge__create_skill` | Create a custom skill |
| `mcp__code-forge__get_skill` | Get skill details |
| `mcp__code-forge__delete_skill` | Delete a project skill |
---
## Tips
1. **Start with templates** - Install what you need per-project
2. **Keep global minimal** - Only core skills should be global
3. **Customize after install** - Edit installed skills for your needs
4. **Create custom skills** - Document project-specific patterns
5. **Share with team** - Check `.claude/skills/` into git
---
## Troubleshooting
### "Skill not found"
- Check if the skill is installed: `/claude-os-skills`
- Verify the skill directory exists in `.claude/skills/`
### "Community skills not loading"
- Check internet connection
- GitHub API rate limits may apply (1 hour cache)
- Try refreshing: click the refresh button in UI
### "Skill not being used by Claude"
- Verify `skill.md` exists in the skill directory
- Check that the skill description matches your use case
- Explicitly invoke with: `skill: <name>`
---
**See Also:**
- [API Reference](../API_REFERENCE.md) - Skills API endpoints
- [README](../../README.md) - Full Claude OS documentation

View file

@ -0,0 +1,295 @@
# Start Scripts Guide
Claude OS has **two different start scripts** for different use cases:
---
## 1. `./start.sh` - MCP Server Only
**What it starts:**
- ✅ MCP Server (port 8051)
**What it does NOT start:**
- ❌ Frontend Web UI
- ❌ Redis
- ❌ RQ Workers
- ❌ Ollama
**Use when:**
- You only need Claude Code integration
- You don't need the visual web interface
- You want minimal services running
- Quick start for development
**Command:**
```bash
./start.sh
```
**Output:**
```
✅ Claude OS MCP Server is running!
📡 MCP Server: http://localhost:8051
(For Claude Code integration - do NOT open in browser)
💡 Want the full experience?
To start ALL services: ./start_all_services.sh
```
**What you can do:**
- ✅ Use Claude Code with `/claude-os-init`
- ✅ Use Claude Code commands (`/claude-os-search`, etc.)
- ❌ Can't access web UI (no frontend running)
- ❌ No real-time learning (no workers running)
---
## 2. `./start_all_services.sh` - Everything
**What it starts:**
- ✅ Ollama (port 11434) - Local AI models
- ✅ Redis (port 6379) - Cache & job queue
- ✅ RQ Workers - Background job processing
- ✅ MCP Server (port 8051) - API for Claude Code
- ✅ React Frontend (port 5173) - Web UI
**Use when:**
- You want the complete experience
- You need the web interface
- You want real-time learning features
- Production-like environment
**Command:**
```bash
./start_all_services.sh
```
**Output:**
```
==================================================
✅ All Services Started Successfully!
==================================================
📡 Service URLs:
🎨 Frontend: http://localhost:5173
🔌 API Server: http://localhost:8051
📚 API Docs: http://localhost:8051/docs
🔧 Ollama:
Host: http://localhost:11434
Models: llama3.1:latest, nomic-embed-text:latest
💾 Databases:
SQLite: data/claude-os.db
Redis: localhost:6379
🤖 Real-Time Learning System:
RQ Workers: listening on 3 queues
```
**What you can do:**
- ✅ Use Claude Code integration
- ✅ Open web UI at http://localhost:5173
- ✅ Browse knowledge bases visually
- ✅ Upload documents via UI
- ✅ Real-time learning system active
- ✅ Full feature set
---
## Quick Comparison
| Feature | `start.sh` | `start_all_services.sh` |
|---------|------------|-------------------------|
| **MCP Server** | ✅ Port 8051 | ✅ Port 8051 |
| **Web UI** | ❌ | ✅ Port 5173 |
| **Ollama** | ❌ (must start manually) | ✅ Auto-started |
| **Redis** | ❌ | ✅ Auto-started |
| **RQ Workers** | ❌ | ✅ Background jobs |
| **Real-time Learning** | ❌ | ✅ Active |
| **Startup Time** | ~1 second | ~30 seconds |
| **Resource Usage** | Minimal | Moderate |
---
## Which Should You Use?
### Use `./start.sh` if:
- Just testing Claude Code integration
- Don't need web interface
- Want faster startup
- Minimal resource usage
- Development/debugging
### Use `./start_all_services.sh` if:
- Want full functionality
- Need web interface
- Want to browse/manage KBs visually
- Production or demo environment
- Want real-time learning features
---
## Common Mistakes
### ❌ Mistake 1: Opening MCP server in browser
**Problem:**
```bash
./start.sh
# Then opening http://localhost:8051 in browser
# → "Method Not Allowed" error
```
**Why:** MCP server (port 8051) is for Claude Code API calls, not browsers
**Solution:**
- Don't open 8051 in browser
- Use `./start_all_services.sh` and open http://localhost:5173 instead
### ❌ Mistake 2: Expecting Web UI with start.sh
**Problem:**
```bash
./start.sh
# Then trying to access http://localhost:5173
# → Connection refused
```
**Why:** `start.sh` doesn't start the frontend
**Solution:**
- Use `./start_all_services.sh` instead
### ❌ Mistake 3: Wrong port numbers
**Problem:** Confusing which service runs on which port
**Remember:**
- **Port 8051:** MCP Server (for Claude Code) - Don't open in browser!
- **Port 5173:** Web UI (for humans) - Open this in browser!
---
## Stopping Services
### Stop MCP Server Only (`start.sh`)
**Option 1:** Press `Ctrl+C` in the terminal
**Option 2:** Kill by PID
```bash
# PID shown when you start it
kill <PID>
```
### Stop All Services (`start_all_services.sh`)
**Use the stop script:**
```bash
./stop_all_services.sh
```
This will stop:
- MCP Server
- Frontend
- RQ Workers
- Redis (optional)
- Ollama (optional)
---
## Restarting Services
### Restart MCP Only
```bash
# Stop (Ctrl+C or kill PID)
./start.sh
```
### Restart Everything
```bash
./restart_services.sh
```
Or manually:
```bash
./stop_all_services.sh
./start_all_services.sh
```
---
## Logs
### MCP Server Logs
**If started with `start.sh`:**
- Output shows in terminal
**If started with `start_all_services.sh`:**
```bash
tail -f logs/mcp_server.log
```
### Frontend Logs
```bash
tail -f logs/frontend.log
```
### RQ Workers Logs
```bash
tail -f logs/rq_workers.log
```
### All Logs
```bash
tail -f logs/*.log
```
---
## Installation Recommendation
After running `./install.sh`, the README says:
```
1️⃣ Start Claude OS:
./start.sh
```
**But for the full experience, you should use:**
```
1️⃣ Start Claude OS (all services):
./start_all_services.sh
```
Then you can:
- Use Claude Code at port 8051
- Open web UI at http://localhost:5173
- Have full functionality
---
## Summary
**Remember:**
- `./start.sh` = MCP server only (minimal, fast)
- `./start_all_services.sh` = Everything (full features)
**For most users:**
```bash
./start_all_services.sh # ← Recommended!
```
**Then access:**
- Web UI: http://localhost:5173 ✅ Open in browser
- MCP Server: http://localhost:8051 ❌ Don't open in browser (for Claude Code only)
**Problem solved!** 🎉

View file

@ -0,0 +1,896 @@
# What is Claude OS? 🚀
# **The AI Operating System That Turns Claude Into Your Most Knowledgeable Team Member**
---
## The Problem You're Facing Right Now 😤
Let's be honest. You've experienced this a thousand times:
```
You: "Claude, add a new feature to our auth system"
Claude: "Based on common best practices, here's a generic auth implementation..."
You: "No no no—we have a custom JWT handler, a special 2FA module for admins,
and timezone handling in the token service. This is all wrong."
Claude: "Oh! I didn't know that. Can you tell me about your auth system?"
You: *starts typing a 30-minute context document*
```
**Every. Single. Time.**
Or worse—you come back to a project after 3 weeks:
```
You: "Hey Claude, remind me what we did last week"
Claude: "I don't have any record of that. What did you work on?"
You: *searches through commit messages, slack history, and old notes*
```
**Claude is brilliant. But Claude starts from ZERO every conversation.**
That's about to change. Forever.
---
## Enter Claude OS: The AI That Actually Remembers 🧠
<p align="center">
<img src="frontend/public/assets/claude-os-architecture.svg" alt="Claude OS Architecture" width="100%"/>
</p>
> **⚡ NEW in v2.0:** Hybrid indexing with tree-sitter AST parsing!
> **10,000 files indexed in 3 seconds** (was 3-5 hours) | **600-1000x faster** | **80% fewer embeddings** | **Start coding immediately!**
Claude OS is a **complete operating system for AI-assisted development** that turns Claude from a brilliant generalist into **your project's most knowledgeable expert**—someone who:
- **Remembers everything** about your project across all sessions (and never forgets)
- **Learns automatically** from every conversation, commit, and decision you make
- **Understands your entire codebase** exactly like a senior developer who's been there for years
- **Detects architectural decisions, patterns, and edge cases** in real-time as you work
- **Becomes genuinely expert** on YOUR specific project, YOUR tech stack, and YOUR way of doing things
- **Adapts its responses** to match your coding style, conventions, and preferences
## How Claude OS Works: The Magic Behind the Intelligence 🎯
Claude OS is not just another tool—it's a **complete AI development operating system** built on **six interconnected pillars** that work together seamlessly:
---
### 1. 🧠 **Real-Time Learning System** (The Always-On Brain)
Imagine if Claude could eavesdrop on all your conversations and automatically update its knowledge of your project. That's exactly what this does.
```
Your Conversation → Redis Pub/Sub → AI Pattern Detection → Auto Knowledge Update
< 1ms latency 10 pattern types Instant indexing
```
**Here's what makes it revolutionary:**
- **Always watching, never sleeping** - RQ workers monitor conversations 24/7, 365 days a year
- **Lightning-fast detection** - Redis pub/sub broadcasts insights with < 1ms latency
- **Intelligent pattern recognition** - Detects 10+ different learning patterns:
- Architectural decisions ("We're moving from monolith to microservices")
- Technology changes ("Switching from PostgreSQL to MongoDB")
- Bug fixes and solutions ("Fixed timezone issue in auth tokens")
- Performance insights ("This N+1 query is killing us")
- Edge cases and gotchas ("Watch out for expired certs in prod")
- Team preferences ("We prefer composition over inheritance")
- Naming conventions ("All our handlers end with _handler")
- Common pitfalls ("Never use SELECT * here, it's slow")
- Integration patterns ("Always validate against the webhook signature")
- Security concerns ("Remember to sanitize user input")
- **High-confidence learning only** - 75-95% confidence thresholds prevent false learnings
- **Completely automatic** - Zero manual configuration or training needed
**Real Examples It Captures:**
```
"We're replacing our JWT library with a custom implementation"
→ Updated tech stack knowledge, removes old library references
"I found the issue—the cron job was running in UTC but our timestamps are local"
→ Remembered as critical insight in auth and scheduler modules
"This ORM query is generating 50 SQL statements per request"
→ Added to performance anti-patterns database
"Let's enforce this naming convention: all event handlers must be on_[event_name]"
→ Learned and will suggest this pattern in future code generation
```
### 2. 💾 **Memory MCP** (Your AI's Institutional Memory)
Your explicit memories. Think of this as Claude taking detailed notes on everything you tell it to remember.
```
You: "Remember: We use bcrypt with 12 rounds for password hashing, never change this"
→ Saved forever
→ Claude recalls this in EVERY future conversation about passwords
```
**Why this is a game-changer:**
- **Persistent across sessions** - Context survives between conversations, weeks, months
- **Natural language interface** - Just say "Remember:" to save anything. That's it.
- **Instant recall in context** - Claude doesn't search—it automatically brings up relevant memories when discussing that topic
- **Project-specific memory** - Each project has its own memory bank, no confusion
- **Private and secure** - Your memories are git-ignored, never tracked, completely private
- **Searchable** - Can browse and organize your memories anytime
**Examples of What You'd Remember:**
```
"Remember: Our payment processing only supports USD and EUR, not other currencies"
→ Claude never suggests converting to other currencies
"Remember: API v2 is deprecated. All new endpoints go in /api/v3/.
Redirect v2 traffic with 301 moved permanently."
→ Applied to every API endpoint Claude suggests
"Remember: Our database backups run at 2 AM UTC. That's our quiet window.
Never schedule migrations then."
→ Claude factors this into any infrastructure work
"Remember: The legacy_accounts table is for testing only and gets wiped weekly.
Never store real data there."
→ Prevents dangerous mistakes in code generation
```
---
### 3. 📚 **Semantic Knowledge Base** (Your Codebase as Living Documentation)
This is where the magic really happens. Claude doesn't just READ your code—it UNDERSTANDS it.
```
Your Entire Codebase
↓
Vector Embeddings (semantic understanding)
↓
Instant Semantic Search
↓
Context-Aware Responses (always relevant to YOUR code)
```
**What Claude knows:**
- **Every file and every function** - Line-by-line code understanding
- **Architecture patterns** - How your system actually works
- **Dependencies and relationships** - What connects to what and why
- **Historical evolution** - How code changed and why
- **Team patterns** - Naming conventions, code style, architectural preferences
- **Business logic** - What your code does and why it does it that way
- **Third-party integrations** - How you use Stripe, AWS, Auth0, etc.
- **Database schema** - Tables, relationships, constraints, indexes
- **API contracts** - Request/response formats, error codes, rate limits
- **Known issues** - Bugs, workarounds, temporary patches
**The Result:**
When you ask Claude about a feature, it searches through your entire codebase semantically and brings back the 5-10 most relevant code examples, architectural patterns, and related files—all automatically. No manual searching needed.
---
### 4. 🔍 **Analyze-Project Skill** (Lightning-Fast Hybrid Indexing)
This is Claude's way of getting a complete crash course in your project—**in seconds, not hours.**
```
/initialize-project [project-id]
↓
Phase 1: STRUCTURAL INDEXING (30 seconds for 10,000 files!)
→ Tree-sitter AST parsing (no LLM needed!)
→ Extract all symbols (classes, functions, signatures)
→ Build dependency graph
→ PageRank importance scoring
→ ✅ Ready to code IMMEDIATELY
↓
Phase 2: SELECTIVE SEMANTIC INDEXING (background, optional)
→ Embed top 20% most important files only
→ Full documentation embedding
→ 80% reduction in chunks (faster, cheaper)
→ Runs while you code
↓
3 Documentation Files Generated:
• CODING_STANDARDS.md
• ARCHITECTURE.md
• DEVELOPMENT_PRACTICES.md
↓
Git Hooks Installed (auto-indexing on every commit)
```
**⚡ NEW: Hybrid Two-Phase Indexing**
Inspired by [Aider](https://github.com/Aider-AI/aider), Claude OS now uses a revolutionary two-phase approach:
**Phase 1 - Structural Index (INSTANT):**
- **Tree-sitter parsing** - Language-agnostic AST extraction (supports 40+ languages)
- **No embeddings needed** - Parse syntax, extract symbols, zero LLM calls
- **Blazing fast** - 10,000 Ruby files indexed in **30 seconds** (vs 3-5 hours before!)
- **Immediate availability** - Start coding the moment indexing begins
- **Dependency graphing** - Understands which files import/require others
- **PageRank scoring** - Identifies most important files automatically
**Phase 2 - Semantic Index (SELECTIVE):**
- **Top 20% only** - Embed most important files (based on PageRank)
- **Documentation first** - Full embedding for docs, specs, README
- **Background processing** - Runs while you code, no waiting
- **80% fewer chunks** - Faster queries, lower costs, same intelligence
**Real Performance Numbers:**
| Project | Before (Full Embed) | After (Hybrid) | Speedup |
|---------|---------------------|----------------|---------|
| **Pistn (10,000 Ruby files)** | 3-5 hours | **3 seconds** + 20 min optional | **600-1000x faster!** |
| **Start coding** | After full index | **Immediately!** | Instant |
| **Chunks indexed** | 100,000+ | ~20,000 (80% reduction) | More efficient |
| **Query speed** | Semantic only | Structural + Semantic | Faster |
**How the indexing actually works:**
- **Intelligent priority** - Tree-sitter identifies key files (controllers, services, models) in seconds
- **Smart expansion** - PageRank scoring finds connected dependencies automatically
- **Real-time updates** - Git hooks trigger re-indexing on every commit (takes seconds!)
- **Pattern recognition** - Learns naming conventions, test patterns, error handling from AST
- **Architecture mapping** - Understands project structure, module relationships, dependency graph
**What it analyzes and learns:**
- Your file and directory structure and organization philosophy
- Test coverage patterns and testing conventions you follow
- Database schemas, relationships, constraints
- API endpoints, request/response formats, error codes
- Common error patterns and how you handle them
- Code style preferences and formatting conventions
- Build and deployment processes
- Environmental configuration and secrets management
**The Result:**
Claude instantly knows:
- Where features are implemented
- How to add new functionality in your style
- What patterns to follow for consistency
- Where related code lives
- What tests need updating
---
### 5. 🎯 **Session Management** (Never Lose Context Again)
Every time you start a new conversation, Claude OS automatically checks your session state and loads relevant context.
```
New Conversation Starts
↓
Claude OS checks: "What were we working on?"
↓
Loads: Active session, recent memories, current branch, decisions made
↓
Prompts: "Continue working on [feature]?" or "Start something new?"
```
**What this means for you:**
- **Zero cold starts** - Every conversation picks up where you left off
- **Automatic context loading** - Relevant memories surfaced immediately
- **Session tracking** - Track task, branch, decisions, blockers across days
- **Resume anywhere** - Start on laptop, continue on desktop, same context
- **Work tracking** - Know exactly what was accomplished, what's next
**The Result:**
No more "what was I doing?" moments. Claude knows what you're working on before you even ask.
---
### 6. 📊 **Code Structure MCP** (Your Codebase's DNA)
The newest addition to Claude OS—a **complete structural map** of your entire codebase, built in seconds.
```
Git Commit Detected
↓
Tree-sitter AST Parsing (< 3 seconds for 10,000 files!)
↓
Symbol Extraction
• Classes, functions, methods
• Import/require statements
• Function signatures
• File relationships
↓
Dependency Graph Built
↓
PageRank Importance Scoring
↓
SQLite Storage (instant queries!)
↓
Available to Claude via code_structure MCP
```
**What makes this revolutionary:**
- **Language-agnostic** - Supports 40+ languages (Ruby, Python, JS, TS, Go, Rust, Java, C++, and more!)
- **No LLM calls needed** - Pure AST parsing, zero API costs
- **Insanely fast** - 10,000 Ruby files parsed in 3 seconds (real benchmark on Pistn)
- **Complete symbol table** - Every class, function, import extracted and indexed
- **Dependency awareness** - Knows which files depend on others
- **Importance ranking** - PageRank algorithm identifies key files automatically
- **Git hook integration** - Auto-updates on every commit (< 1 second incremental)
**What Claude can do with this:**
```
You: "Where is the user authentication logic?"
Claude: "Found in 3 locations:
1. app/services/authentication_service.rb (class AuthenticationService)
- handle_login(email, password)
- generate_jwt_token(user)
- validate_token(token)
2. app/controllers/auth_controller.rb (imports AuthenticationService)
- login_action
- logout_action
3. app/models/user.rb (used by AuthenticationService)
- authenticate method
- password_hash field"
```
**The power:**
- **Instant symbol lookup** - Find any function/class in milliseconds
- **Cross-reference navigation** - See all files that import a module
- **Architecture understanding** - Visualize your codebase structure
- **Smart suggestions** - Claude knows where to add new code based on existing patterns
- **Refactoring safety** - See all dependencies before changing a function
**Integration with other pillars:**
- **Feeds PageRank scores** to Phase 2 semantic indexing (knows which files are most important)
- **Provides context** to Real-Time Learning (understands architectural changes)
- **Enriches Memory MCP** with code location references
- **Powers intelligent search** across your entire codebase structure
---
### 7. 🔌 **MCP Integration** (The Claude Code Bridge)
MCPs (Model Context Protocol) are the bridge between Claude OS and Claude Code. They expose all Claude OS knowledge directly to you.
```
Claude OS Knowledge Base
↓
MCP Server (http://localhost:8051)
↓
Claude Code Interface
↓
You get AI with complete project knowledge
```
**Your Available MCPs (5 per project):**
- **pistn-project-profile** - Your project's coding standards, architecture, and development practices
- **pistn-project-index** - Semantic index of key source files (vector embeddings for deep understanding)
- **pistn-knowledge-docs** - Project documentation and specifications (auto-ingested from docs/)
- **pistn-project-memories** - All your "Remember:" memories in one place (persistent across sessions)
- **pistn-code_structure** - ⭐ NEW! Complete structural map (38,406 symbols, dependency graph, PageRank scores)
Every MCP has:
- 📊 **Full semantic search** - Search code by meaning, not keywords
- 📚 **Complete documentation** - What each file does and why
- 🔗 **Cross-referenced** - See how modules connect
- 🎯 **Always current** - Auto-updated with every commit
---
---
## 🤝 **Team Collaboration & Sharing** (Built for Teams)
Claude OS isn't just for solo developers—it's designed for teams to share knowledge.
```
Your Claude OS Setup
↓
One Command: ./install.sh
↓
Teammates Get: All commands, skills, templates instantly
↓
Each Project: /claude-os-init (2 minutes)
↓
Team Shares: CLAUDE.md (in git), separate memories (private)
```
**How team sharing works:**
- **Template system** - Commands and skills shared via symlinks, updates propagate instantly
- **One-command install** - Teammates run `./install.sh`, done in 3 minutes
- **Project isolation** - Each project has separate knowledge bases
- **Shared context** - CLAUDE.md in git gives everyone the same project overview
- **Private memories** - Each developer's memories stay private (git-ignored)
- **Update once, benefit everywhere** - Update templates, all projects get improvements
**The Result:**
Onboard new developers in minutes, not weeks. They get institutional knowledge from day one.
---
## 🎨 **Agent-OS Integration** (Spec-Driven Development - Optional)
Partner with Agent-OS by Builder Methods for structured feature development.
**8 Specialized Agents:**
1. **spec-initializer** - Create feature specification directories
2. **spec-shaper** - Gather requirements through iterative questions
3. **spec-writer** - Generate detailed technical specifications
4. **tasks-list-creator** - Break specs into actionable tasks
5. **implementer** - Implement features following task list
6. **implementation-verifier** - Verify implementation completeness
7. **spec-verifier** - Ensure spec and tasks consistency
8. **product-planner** - Create product documentation
**The workflow:**
```
/new-spec user-authentication
↓
/create-spec (answers 1-3 questions at a time)
↓
Spec.md + Tasks.md generated
↓
/implement-spec user-authentication
↓
Feature implemented, tested, verified
```
**Why this matters:**
- **Structured planning** - Think before coding
- **Clear requirements** - No more "wait, what did we want?"
- **Task breakdown** - Implementation steps crystal clear
- **Verification built-in** - Ensures nothing missed
- **Documentation automatic** - Spec becomes permanent record
**Integration with Claude OS:**
Agent-OS agents search Claude OS memories before creating specs, ensuring they build on existing patterns and decisions. Perfect synergy.
---
## 📊 **Spec Tracking & Kanban Board** (Visual Project Management)
**NEW: Claude OS now automatically tracks all your Agent-OS specs and displays them as an interactive Kanban board!**
```
Agent-OS creates spec → Claude OS parses tasks.md → Kanban board displays progress
```
**What this means for you:**
- **Automatic discovery** - Claude OS scans your `agent-os/specs/` folder
- **Complete task tracking** - Every task, phase, dependency parsed and stored
- **Visual progress** - See all specs and their tasks organized by status (Todo, In Progress, Done, Blocked)
- **Real-time updates** - Update task status via API, progress auto-calculated
- **Archive completed specs** - Keep your board focused on active work
- **Time tracking** - Track estimated vs actual time for every task
- **Dependency awareness** - See which tasks depend on others
**The Kanban View:**
```
📋 Group Account Rendering [52 tasks, 45% complete]
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ TODO │ IN PROGRESS │ DONE │ BLOCKED │
├─────────────┼─────────────┼─────────────┼─────────────┤
│ PHASE2-TASK1│ PHASE1-TASK3│ PHASE1-TASK1│ │
│ PHASE2-TASK2│ │ PHASE1-TASK2│ │
│ PHASE3-TASK1│ │ ...23 more │ │
└─────────────┴─────────────┴─────────────┴─────────────┘
📋 Manual Appointment Times [15 tasks, 100% complete] ✅ ARCHIVED
```
**How it works:**
1. **Create spec with Agent-OS:**
```
/create-spec → agent-os/specs/2025-01-15-user-auth/tasks.md
```
2. **Claude OS syncs automatically:**
```
POST /api/projects/{id}/specs/sync
→ Parses all tasks.md files
→ Extracts metadata, phases, dependencies
→ Stores in SQLite
```
3. **View progress in real-time:**
```
GET /api/projects/{id}/kanban
→ See all specs grouped by status
→ Track completion percentage
→ Monitor time estimates
```
4. **Update task status as you work:**
```
PATCH /api/tasks/{id}/status
{
"status": "done",
"actual_minutes": 15
}
→ Spec progress auto-updates
→ Status changes from "planning" to "in_progress" to "completed"
```
5. **Archive when done:**
```
POST /api/specs/{id}/archive
→ Hides from default view
→ Preserves all history
→ Keeps board clean
```
**What gets tracked:**
- **Spec metadata** - Name, date, status, progress percentage
- **Individual tasks** - Task code (PHASE1-TASK1), title, description
- **Task status** - todo, in_progress, done, blocked
- **Time estimates** - Estimated minutes vs actual minutes spent
- **Dependencies** - Which tasks must complete before others
- **Risk levels** - Low, medium, high risk tasks
- **Phases** - Task organization by implementation phase
- **Completion timestamps** - When tasks started/completed
**API Endpoints:**
```bash
# Sync all specs from agent-os folder
POST /api/projects/{project_id}/specs/sync
# Get Kanban board view
GET /api/projects/{project_id}/kanban?include_archived=false
# Get all tasks for a spec
GET /api/specs/{spec_id}/tasks
# Update task status
PATCH /api/tasks/{task_id}/status
{
"status": "in_progress",
"actual_minutes": 15
}
# Archive/unarchive specs
POST /api/specs/{spec_id}/archive
POST /api/specs/{spec_id}/unarchive
```
**The result:**
Never lose track of implementation progress. See at a glance what's done, what's in flight, and what's next. Perfect for solo devs and teams alike.
---
## ⚡ **Smart Commands & Triggers** (Your AI Assistant on Autopilot)
Claude OS gives you powerful slash commands and automatic triggers.
**Essential Commands:**
- `/claude-os-search "query"` - Search all memories and docs
- `/claude-os-remember "content"` - Quick save to memories
- `/claude-os-save "title"` - Full-featured save with KB selection
- `/claude-os-list` - View all knowledge bases
- `/claude-os-session start` - Begin tracking a feature
- `/claude-os-triggers` - Manage automatic triggers
**Automatic Triggers:**
When you say phrases like these, Claude automatically saves to memories:
- "Remember this:"
- "Save this:"
- "Document this:"
- "Note this:"
- "Keep in mind:"
**The Result:**
Building institutional knowledge happens naturally as you work. No manual note-taking needed.
---
## What Can Claude OS Actually Do For You? 🚀
### ⚡ **Immediate Productivity Wins**
#### Instant Context Recovery
```
You: "What were we working on last week on that payment refund issue?"
Claude OS: "On Oct 20, you discovered that refunds for subscriptions
were failing because the payment processor's API changed their
transaction ID format. You created a migration script in
/services/payment_processor.js line 234 that maps old IDs to new format.
Tests are in __tests__/payment_processor.test.js lines 156-189.
You still need to test the edge case for subscriptions that refund across
month boundaries."
```
**Without Claude OS:**
```
You: "What were we working on last week?"
Claude: "I'm not sure. Can you remind me?"
*You search through Slack, emails, commits, pull requests for 30 minutes*
```
#### Architecture-Aware Code Generation
```
You: "Add a new handler for when users delete their accounts"
Claude OS: "I'll create account_deletion_handler.js in /handlers/ (matching
your naming convention). It will:
1. Use the same error handling pattern as user_creation_handler.js
2. Call the cleanup service you built in /services/account_cleanup.js
3. Queue async tasks using your RQ setup (looking at how
email_notification_handler does it)
4. Follow the transaction pattern from your database service
5. Include tests matching your test structure"
```
The generated code is not just correct—it's indistinguishable from your own.
#### Pattern-Based Bug Diagnosis
```
You: "This test is failing randomly"
Claude OS: "Looking at your timezone handling history, I see you've had
3 similar issues before:
- Oct 15: Token expiry UTC/local timezone mismatch (Fixed in commit b56f585)
- Oct 8: Cron job running in wrong timezone (Fixed in your scheduler)
- Sep 29: Database query returning dates in different timezone
This looks like the same issue in a different place. The test compares
timestamps without accounting for local timezone. Your fix before was
to use moment.utc() for comparisons. Same solution here."
```
### 🎯 **Architectural Decisions Made Easy**
```
You: "Should we add caching for this endpoint?"
Claude OS: "Looking at similar endpoints in your codebase:
- /api/users/:id - cached (line 234)
- /api/posts/:id - not cached (line 567)
The difference is that /users/:id has high read volume but rare updates,
while /posts/:id gets updated frequently. Your endpoint /api/product/:id
matches the user pattern (high read, low write).
Recommendation: Cache with 5-minute TTL like your /users endpoint."
```
### 📚 **Documentation That Stays Current**
Claude OS automatically generates and maintains:
- **CODING_STANDARDS.md** - How YOU code (not how textbooks say to)
- **ARCHITECTURE.md** - Your actual system design (not theory)
- **DEVELOPMENT_PRACTICES.md** - Your team's conventions (enforced automatically)
These update automatically as you work—zero manual maintenance.
### 🧠 **Institutional Knowledge That Doesn't Walk Out the Door**
```
Your intern learns from you:
"Remember: Never use SELECT * in this schema because the accounts table
has that 5MB blob field that kills performance. Always list specific columns."
Claude remembers this forever. When your intern leaves and a new person
joins, Claude will warn them: "Be specific with SELECT statements on
accounts table—there's a large blob field that causes performance issues."
```
---
## Why Claude OS Beats Everything Else ✨
| Feature | Claude Alone | ChatGPT | GitHub Copilot | Claude OS |
|---------|--------------|---------|-----------------|-----------|
| Remembers your code | ❌ | ❌ | ❌ | ✅ Perfectly |
| Understands your architecture | ❌ | ❌ | ❌ | ✅ Completely |
| Learns from your decisions | ❌ | ❌ | ❌ | ✅ Automatically |
| Writes in your style | ❌ | ❌ | Partial | ✅ Always |
| **Indexes 10,000 files** | N/A | N/A | N/A | **✅ 3 seconds** |
| **Tree-sitter AST parsing** | ❌ | ❌ | ❌ | **✅ 40+ languages** |
| **Hybrid structural + semantic** | ❌ | ❌ | ❌ | **✅ Both!** |
| Persists context between sessions | ❌ | ❌ | ❌ | ✅ Forever |
| Session management & tracking | ❌ | ❌ | ❌ | ✅ Built-in |
| Automatic context loading | ❌ | ❌ | ❌ | ✅ Every session |
| Spec-driven development | ❌ | ❌ | ❌ | ✅ Optional |
| Team sharing & collaboration | ❌ | ❌ | ❌ | ✅ One install |
| Detects your bugs before they happen | ❌ | ❌ | ❌ | ✅ Via patterns |
| Works offline | N/A | ✅ | ✅ | ✅ Yes |
| Requires zero setup per project | ❌ | ✅ | ✅ | ✅ One command |
| Free and open source | ❌ | ❌ | ❌ | ✅ 100% |
---
## The Multiplier Effect 📈
The magic isn't just that Claude is smart. It's that Claude gets **smarter over time**.
**Week 1:** Claude learns your project structure
**Week 2:** Claude starts suggesting patterns
**Week 3:** Claude catches bugs before they happen
**Week 4:** Claude contributes architectural ideas
**Month 2:** Claude is your most productive team member
This is the **compounding intelligence** that happens when AI actually understands context.
---
---
## The Complete System Architecture 🏗️
**Visual Overview:**
<p align="center">
<img src="frontend/public/assets/claude-os-architecture.svg" alt="Claude OS Architecture" width="100%"/>
</p>
**Technical Stack:**
```
┌─────────────────────────────────────────────────────────────────────┐
│ CLAUDE OS ECOSYSTEM v2.0 │
│ (Now with Hybrid Indexing!) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Real-Time │ │ Memory │ │ ⚡ NEW: Code Structure │ │
│ │ Learning │ │ System │ │ (Tree-sitter AST) │ │
│ │ (Redis) │ │ (MCP) │ │ 38,406 symbols! │ │
│ │ <1ms │ │ Instant │ │ 3 seconds to index │ │
│ └──────┬───────┘ └──────┬───────┘ └───────────┬─────────────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────────┘ │
│ │ │
│ ┌──────────────────▼─────────────────────────┐ │
│ │ Semantic Knowledge Base (SQLite) │ │
│ │ • 38,406 code symbols (structural index) │ │
│ │ • ~20,000 semantic chunks (80% reduction!)│ │
│ │ • Vector embeddings (selective, top 20%) │ │
│ │ • Dependency graph + PageRank scores │ │
│ │ • Team patterns & conventions │ │
│ │ • Architecture documentation │ │
│ │ • Persistent memory & insights │ │
│ └──────────────────┬─────────────────────────┘ │
│ │ │
│ ┌──────────────────▼─────────────────────────┐ │
│ │ MCP Server (http://localhost:8051) │ │
│ │ Exposes 5 MCPs per project: │ │
│ │ • project_memories (persistent) │ │
│ │ • project_profile (standards) │ │
│ │ • project_index (semantic) │ │
│ │ • knowledge_docs (documentation) │ │
│ │ • code_structure ⭐ NEW! (AST map) │ │
│ └──────────────────┬─────────────────────────┘ │
│ │ │
│ ┌──────────────────▼─────────────────────────┐ │
│ │ Claude Code Interface │ │
│ │ (Your AI with 600-1000x faster indexing!) │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Performance: Pistn (10,000 Ruby files) indexed in 3 seconds ⚡
```
---
## How This Compares to Traditional Development ⚖️
### ❌ Traditional Claude/AI Coding
- Each session starts from zero
- You have to copy-paste code for context
- Claude gives generic best-practice solutions
- You spend 30 minutes explaining your architecture
- Forgets everything when you close the conversation
- Can't detect your patterns or preferences
- Treats every project the same
### ✅ Claude OS AI Coding
- Sessions build on all previous knowledge
- Context is automatic and complete
- Claude gives YOUR solutions
- Claude already understands your architecture
- Remembers everything forever
- Detects and adapts to your patterns
- Becomes an expert on YOUR project
**The difference?** Claude OS transforms AI from a helpful tool into a **genuine team member who understands your project.**
---
## Why You Should Use Claude OS Right Now 💡
### You need this if you
- **Work on multiple projects** - Claude remembers each project separately
- **Have team members leaving** - Preserves institutional knowledge they'd take with them
- **Work on the same project long-term** - Claude gets smarter as it learns your patterns
- **Care about code consistency** - Claude enforces YOUR conventions, not textbook ones
- **Want faster development** - Context-aware AI is 10x faster than starting from zero
- **Debug tricky issues** - Claude remembers similar issues and past solutions
- **Onboard new developers** - They get the knowledge of everyone on your team
- **Maintain architectural consistency** - Claude knows and enforces your patterns
- **Document as you go** - Documentation updates automatically
- **Make better architectural decisions** - Claude references your own similar choices
### You absolutely need this if you
- Have a large or complex codebase
- Work in a startup where knowledge walks out the door with departing team members
- Deal with legacy code and need to understand it deeply
- Want to improve code quality systematically
- Need AI that understands YOUR specific way of building software
---
## Getting Started in 3 Steps
```bash
# 1. Install Claude OS (one-time, 3 minutes)
git clone https://github.co/brobertsaz/claude-os.git
cd claude-os
./install.sh
# 2. Start Claude OS
./start.sh
# 3. Initialize your project (2 minutes)
cd /path/to/your/project
/claude-os-init
# Answer a few questions, done!
```
Now every conversation with Claude includes your complete project knowledge.
**For Teams:**
Share the repo URL with teammates. They run `./install.sh` and get everything you have. Each project they work on runs `/claude-os-init` once. That's it.
---
## The Real Magic ✨
**Claude OS doesn't make Claude smarter.** Claude is already brilliant.
**Claude OS makes Claude USEFUL for your specific project.**
That's the magic. The difference between generic intelligence and practical expertise.
It's the difference between "a very smart person who's never seen your code" and "your most knowledgeable team member."
---
# 🚀 **Claude OS: The Future of AI-Assisted Development is Here**
*Stop explaining. Stop copying context. Stop starting over.*
*Start building with an AI that knows your code as well as you do.*
---
*Built with ❤️ by Anthropic Claude, who got tired of starting every conversation from zero.*

687
docs/index.html Normal file
View file

@ -0,0 +1,687 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude OS - The AI Operating System That Never Forgets</title>
<!-- Bootstrap 5 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- AOS (Animate On Scroll) -->
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<style>
:root {
--cyan: #00D9FF;
--purple: #8B5CF6;
--pink: #FF00FF;
--dark-bg: #0F172A;
--darker-bg: #1E293B;
}
* {
scroll-behavior: smooth;
}
body {
background: linear-gradient(135deg, var(--dark-bg) 0%, var(--darker-bg) 100%);
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
overflow-x: hidden;
}
/* Gradient Text */
.gradient-text {
background: linear-gradient(90deg, var(--cyan) 0%, var(--purple) 50%, var(--pink) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
}
/* Sections */
section {
min-height: 100vh;
display: flex;
align-items: center;
padding: 80px 0;
}
section.half-height {
min-height: 50vh;
}
/* Hero Section */
.hero {
text-align: center;
}
.hero h1 {
font-size: 4rem;
margin-bottom: 1rem;
}
.hero h2 {
font-size: 2rem;
color: #cbd5e1;
margin-bottom: 2rem;
}
.hero .emoji-row {
font-size: 1.5rem;
margin-top: 2rem;
}
/* Problem Box */
.problem-box {
background: rgba(255, 0, 0, 0.1);
border-left: 4px solid #ef4444;
padding: 2rem;
border-radius: 8px;
margin: 2rem 0;
}
.problem-box pre {
background: rgba(0, 0, 0, 0.3);
padding: 1.5rem;
border-radius: 6px;
overflow-x: auto;
}
.problem-box code {
color: #fca5a5;
font-size: 0.9rem;
}
/* Architecture Image */
.architecture-img {
max-width: 100%;
border-radius: 12px;
box-shadow: 0 0 40px rgba(0, 217, 255, 0.3);
}
/* Metric Badge */
.metric-badge {
display: inline-block;
font-size: 2.5rem;
color: var(--cyan);
font-weight: 700;
text-shadow: 0 0 20px rgba(0, 217, 255, 0.6);
margin: 1rem 0;
}
/* Pillar Cards */
.pillar-card {
background: rgba(30, 41, 59, 0.8);
border: 2px solid var(--purple);
border-radius: 12px;
padding: 1.5rem;
height: 100%;
transition: all 0.3s;
box-shadow: 0 0 20px rgba(139, 92, 246, 0.2);
}
.pillar-card:hover {
transform: translateY(-5px);
box-shadow: 0 0 30px rgba(139, 92, 246, 0.4);
}
.pillar-card h4 {
font-size: 1.3rem;
margin-bottom: 1rem;
}
.pillar-card.cyan {
border-color: var(--cyan);
}
.pillar-card.purple {
border-color: var(--purple);
}
.pillar-card.pink {
border-color: var(--pink);
}
/* Two Column Section */
.two-col-section h3 {
font-size: 1.8rem;
margin-bottom: 1.5rem;
}
.two-col-section ul {
font-size: 1.1rem;
line-height: 2;
}
/* Tables */
table {
margin: 2rem 0;
}
table thead {
background: var(--purple);
}
table th {
padding: 1rem;
font-weight: 600;
}
table td {
padding: 0.8rem;
border: 1px solid rgba(255, 255, 255, 0.1);
}
table tbody tr:hover {
background: rgba(139, 92, 246, 0.1);
}
/* Code Block */
.code-block {
background: rgba(30, 41, 59, 0.9);
border: 1px solid var(--purple);
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 0 20px rgba(139, 92, 246, 0.2);
}
.code-block code {
color: var(--cyan);
font-size: 0.95rem;
}
/* CTA Button */
.cta-button {
display: inline-block;
background: linear-gradient(90deg, var(--cyan) 0%, var(--purple) 100%);
color: white;
padding: 1.2rem 3rem;
border-radius: 10px;
font-size: 1.5rem;
font-weight: 700;
text-decoration: none;
margin: 2rem 0;
box-shadow: 0 0 30px rgba(0, 217, 255, 0.5);
transition: all 0.3s;
}
.cta-button:hover {
transform: scale(1.05);
box-shadow: 0 0 50px rgba(0, 217, 255, 0.8);
color: white;
}
/* Highlight */
.highlight {
color: var(--cyan);
font-weight: 600;
}
/* Section Titles */
h2.section-title {
font-size: 3rem;
margin-bottom: 2rem;
}
/* Responsive */
@media (max-width: 768px) {
.hero h1 {
font-size: 2.5rem;
}
.hero h2 {
font-size: 1.5rem;
}
h2.section-title {
font-size: 2rem;
}
section {
min-height: auto;
padding: 60px 0;
}
}
/* Navbar (optional sticky nav) */
.navbar-custom {
background: rgba(15, 23, 42, 0.95);
backdrop-filter: blur(10px);
}
.navbar-custom .nav-link {
color: #cbd5e1 !important;
transition: color 0.3s;
}
.navbar-custom .nav-link:hover {
color: var(--cyan) !important;
}
</style>
</head>
<body>
<!-- Hero Section -->
<section class="hero" id="hero">
<div class="container">
<h1 class="gradient-text" data-aos="fade-down">Claude OS</h1>
<h2 data-aos="fade-up" data-aos-delay="200">
The AI Operating System<br>That Never Forgets
</h2>
<div class="emoji-row" data-aos="fade-up" data-aos-delay="400">
<span>🧠 Persistent Memory</span> &nbsp;&nbsp;
<span>⚡ 600-1000x Faster</span> &nbsp;&nbsp;
<span>🚀 Zero Setup</span>
</div>
</div>
</section>
<!-- Problem Section -->
<section id="problem">
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<h2 class="section-title gradient-text" data-aos="fade-right">The Problem You're Facing</h2>
<div class="problem-box" data-aos="fade-up">
<p class="fs-5"><strong>Every. Single. Time.</strong></p>
<pre><code>You: "Claude, add a feature to our auth system"
Claude: "Based on common best practices..."
You: "No no no—we have custom JWT, 2FA,
timezone handling... This is all wrong."
Claude: "Oh! I didn't know that."
You: *starts typing 30-minute context document*</code></pre>
</div>
<p class="text-center fs-3 mt-4" data-aos="zoom-in" data-aos-delay="200">
<span class="highlight">Claude is brilliant. But Claude starts from ZERO every conversation.</span>
</p>
</div>
</div>
</div>
</section>
<!-- Solution + Architecture -->
<section id="solution">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">Enter Claude OS</h2>
<div class="row justify-content-center">
<div class="col-lg-10">
<img src="assets/claude-os-architecture.svg"
alt="Claude OS Architecture"
class="architecture-img"
data-aos="zoom-in"
data-aos-delay="200">
</div>
</div>
<div class="text-center mt-4" data-aos="fade-up" data-aos-delay="400">
<div class="metric-badge">10,000 files indexed in 3 seconds</div>
</div>
</div>
</section>
<!-- What It Does -->
<section id="what-it-does">
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<h2 class="section-title gradient-text" data-aos="fade-right">What Claude OS Does</h2>
<ul class="fs-5" data-aos="fade-up">
<li class="mb-3"><strong class="highlight">Remembers everything</strong> about your project across all sessions</li>
<li class="mb-3"><strong class="highlight">Learns automatically</strong> from every conversation, commit, and decision</li>
<li class="mb-3"><strong class="highlight">Understands your codebase</strong> like a senior developer</li>
<li class="mb-3"><strong class="highlight">Detects patterns</strong> in real-time as you work</li>
<li class="mb-3"><strong class="highlight">Adapts its responses</strong> to match YOUR coding style</li>
<li class="mb-3"><strong class="highlight">Never forgets</strong> — context persists forever</li>
</ul>
</div>
</div>
</div>
</section>
<!-- 6 Pillars -->
<section id="pillars">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">6 Core Pillars</h2>
<div class="row g-4">
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="0">
<div class="pillar-card cyan">
<h4 class="gradient-text">🧠 Real-Time Learning</h4>
<p>Redis Pub/Sub with &lt;1ms latency. Automatically detects 10+ pattern types from your conversations.</p>
</div>
</div>
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="100">
<div class="pillar-card purple">
<h4 class="gradient-text">💾 Memory MCP</h4>
<p>Persistent memory across sessions. Natural language save and instant recall.</p>
</div>
</div>
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="200">
<div class="pillar-card pink">
<h4 class="gradient-text">📚 Semantic Knowledge</h4>
<p>Vector embeddings for deep understanding. Instant semantic search across your codebase.</p>
</div>
</div>
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="300">
<div class="pillar-card cyan">
<h4 class="gradient-text">📊 Code Structure</h4>
<p><strong>NEW!</strong> Tree-sitter AST parsing. 38,406 symbols indexed in 3 seconds.</p>
</div>
</div>
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="400">
<div class="pillar-card purple">
<h4 class="gradient-text">🔍 Analyze-Project</h4>
<p>Hybrid indexing (structural + semantic). Git hooks for automatic updates.</p>
</div>
</div>
<div class="col-md-6 col-lg-4" data-aos="fade-up" data-aos-delay="500">
<div class="pillar-card pink">
<h4 class="gradient-text">🎯 Session Management</h4>
<p>Auto-resume sessions with full context preservation. Zero cold starts.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Hybrid Indexing -->
<section id="hybrid-indexing" class="two-col-section">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">⚡ NEW: Hybrid Indexing</h2>
<div class="row g-5">
<div class="col-md-6" data-aos="fade-right">
<h3 style="color: var(--cyan);">Phase 1: Structural</h3>
<ul>
<li>Tree-sitter AST parsing</li>
<li>Extract all symbols</li>
<li>Build dependency graph</li>
<li>PageRank scoring</li>
<li><strong class="metric-badge" style="font-size: 2rem;">3 seconds</strong> for 10k files!</li>
</ul>
</div>
<div class="col-md-6" data-aos="fade-left">
<h3 style="color: var(--purple);">Phase 2: Semantic</h3>
<ul>
<li>Top 20% most important files</li>
<li>Full documentation embedding</li>
<li>80% reduction in chunks</li>
<li>Runs in background</li>
<li><strong>Optional, while you code</strong></li>
</ul>
</div>
</div>
<div class="text-center mt-5" data-aos="zoom-in">
<p class="fs-2 gradient-text">600-1000x faster than before!</p>
</div>
</div>
</section>
<!-- Performance Metrics -->
<section id="performance">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">Real Performance Numbers</h2>
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="table-responsive" data-aos="fade-up">
<table class="table table-dark table-hover">
<thead>
<tr>
<th>Project</th>
<th>Before (Full Embed)</th>
<th>After (Hybrid)</th>
<th>Speedup</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Pistn (10k Ruby files)</strong></td>
<td>3-5 hours</td>
<td><span class="highlight fs-5">3 seconds</span></td>
<td><span class="highlight">600-1000x</span></td>
</tr>
<tr>
<td><strong>Start coding</strong></td>
<td>After full index</td>
<td><span class="highlight">Immediately!</span></td>
<td>Instant</td>
</tr>
<tr>
<td><strong>Chunks indexed</strong></td>
<td>100,000+</td>
<td>~20,000</td>
<td>80% reduction</td>
</tr>
<tr>
<td><strong>Query speed</strong></td>
<td>Semantic only</td>
<td>Structural + Semantic</td>
<td>Faster!</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- New in v2.3 -->
<section id="new-features">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">🆕 New in v2.3</h2>
<div class="row g-4 mt-3">
<div class="col-md-6" data-aos="fade-right">
<div class="pillar-card cyan">
<h4 class="gradient-text">🎯 Skills Library</h4>
<p><strong>Browse & install skills from the community!</strong></p>
<ul>
<li><strong>36+ skills</strong> from Anthropic & Superpowers</li>
<li>PDF, XLSX, frontend design, TDD, debugging</li>
<li>One-click install via Web UI</li>
<li>Create custom skills for your project</li>
</ul>
<code style="background: rgba(0,0,0,0.3); padding: 0.5rem; display: block; border-radius: 4px; margin-top: 1rem;">
/claude-os-skills templates
</code>
</div>
</div>
<div class="col-md-6" data-aos="fade-left">
<div class="pillar-card purple">
<h4 class="gradient-text">📊 Session Insights</h4>
<p><strong>Parse Claude Code sessions automatically!</strong></p>
<ul>
<li>Extract patterns, decisions, blockers</li>
<li>Analyze tool calls and file changes</li>
<li>Build analytics on your work</li>
<li>Auto-save insights to memory</li>
</ul>
<code style="background: rgba(0,0,0,0.3); padding: 0.5rem; display: block; border-radius: 4px; margin-top: 1rem;">
Session → Insights → Memory → Smarter AI
</code>
</div>
</div>
</div>
</div>
</section>
<!-- Comparison -->
<section id="comparison">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">How It Compares</h2>
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="table-responsive" data-aos="fade-up">
<table class="table table-dark table-hover">
<thead>
<tr>
<th>Feature</th>
<th>Claude Alone</th>
<th>ChatGPT</th>
<th>Copilot</th>
<th>Claude OS</th>
</tr>
</thead>
<tbody>
<tr>
<td>Remembers your code</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ Perfectly</span></td>
</tr>
<tr>
<td>Understands architecture</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ Completely</span></td>
</tr>
<tr>
<td><strong>Indexes 10k files</strong></td>
<td>N/A</td>
<td>N/A</td>
<td>N/A</td>
<td><span class="highlight fs-5">✅ 3 seconds</span></td>
</tr>
<tr>
<td>Learns from decisions</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ Automatically</span></td>
</tr>
<tr>
<td>Session management</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ Built-in</span></td>
</tr>
<tr>
<td>Free & open source</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ 100%</span></td>
</tr>
<tr>
<td><strong>Skills library</strong></td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td><span class="highlight">✅ 36+ skills</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- Use Cases -->
<section id="use-cases" class="two-col-section">
<div class="container">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">You Need This If You...</h2>
<div class="row g-5">
<div class="col-md-6" data-aos="fade-right">
<ul class="fs-5">
<li class="mb-3">Work on multiple projects</li>
<li class="mb-3">Have team members leaving</li>
<li class="mb-3">Work long-term on same project</li>
<li class="mb-3">Care about code consistency</li>
<li class="mb-3">Want faster development</li>
</ul>
</div>
<div class="col-md-6" data-aos="fade-left">
<ul class="fs-5">
<li class="mb-3">Debug tricky issues</li>
<li class="mb-3">Onboard new developers</li>
<li class="mb-3">Maintain architectural consistency</li>
<li class="mb-3">Document as you go</li>
<li class="mb-3">Make better decisions</li>
</ul>
</div>
</div>
</div>
</section>
<!-- Getting Started -->
<section id="getting-started">
<div class="container">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<h2 class="section-title gradient-text text-center" data-aos="fade-down">Getting Started in 3 Steps</h2>
<div class="code-block" data-aos="fade-up">
<pre><code># 1. Install Claude OS (one-time, 3 minutes)
git clone https://github.com/brobertsaz/claude-os.git
cd claude-os
./install.sh
# 2. Start Claude OS
./start.sh
# 3. Initialize your project (2 minutes)
cd /path/to/your/project
/claude-os-init
# Answer a few questions, done!</code></pre>
</div>
<p class="text-center fs-4 mt-4" data-aos="zoom-in" data-aos-delay="200">
<span class="highlight">Now every conversation includes your complete project knowledge.</span>
</p>
</div>
</div>
</div>
</section>
<!-- The Magic -->
<section id="magic" class="half-height">
<div class="container text-center">
<h2 class="section-title gradient-text" data-aos="fade-down">The Real Magic ✨</h2>
<p class="fs-3 mb-4" data-aos="fade-up" data-aos-delay="200">
Claude OS doesn't make Claude <strong class="highlight">smarter</strong>.
</p>
<p class="fs-3 mb-5" data-aos="fade-up" data-aos-delay="400">
Claude OS makes Claude <strong class="highlight">USEFUL</strong> for your specific project.
</p>
<p class="fs-2 gradient-text" data-aos="zoom-in" data-aos-delay="600">
Generic Intelligence → Practical Expertise
</p>
</div>
</section>
<!-- CTA -->
<section id="cta" class="half-height">
<div class="container text-center">
<h2 class="section-title gradient-text" data-aos="fade-down">Stop Starting From Zero</h2>
<p class="fs-4 mb-4" data-aos="fade-up" data-aos-delay="200">
Start building with an AI that knows your code<br>as well as you do.
</p>
<div data-aos="zoom-in" data-aos-delay="400">
<a href="https://github.com/brobertsaz/claude-os" class="cta-button">
Get Claude OS Now
</a>
</div>
<p class="mt-5" style="opacity: 0.8;" data-aos="fade-up" data-aos-delay="600">
100% Free & Open Source<br>
<small>Built with ❤️ by developers who got tired of starting every conversation from zero</small>
</p>
</div>
</section>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- AOS JS -->
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>
// Initialize AOS
AOS.init({
duration: 800,
once: true,
offset: 100
});
</script>
</body>
</html>

View file

@ -0,0 +1,862 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { motion, AnimatePresence } from 'framer-motion';
import {
Sparkles,
Plus,
Trash2,
Eye,
Download,
ChevronDown,
ChevronRight,
Globe,
FolderOpen,
Tag,
Search,
X,
Check,
ExternalLink,
Github,
RefreshCw
} from 'lucide-react';
import axios from 'axios';
interface Skill {
name: string;
path: string;
description: string;
scope: 'global' | 'project';
source: string;
content: string;
enabled: boolean;
category: string | null;
tags: string[];
created: string;
modified: string;
}
interface SkillTemplate {
name: string;
category: string;
description: string;
path: string;
tags: string[];
version: string;
}
interface CommunitySkill {
name: string;
source: string;
repo: string;
path: string;
description: string;
readme_url: string;
raw_url: string;
}
interface CommunitySource {
repo: string;
skills_path: string;
name: string;
description: string;
}
interface SkillsManagementProps {
projectPath: string;
}
export default function SkillsManagement({ projectPath }: SkillsManagementProps) {
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);
const [showTemplates, setShowTemplates] = useState(false);
const [showCreateModal, setShowCreateModal] = useState(false);
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set(['general', 'rails', 'react', 'testing']));
const [searchQuery, setSearchQuery] = useState('');
// Community skills state
const [templateTab, setTemplateTab] = useState<'local' | 'community'>('local');
const [selectedCommunitySource, setSelectedCommunitySource] = useState<string>('all');
// Create skill form state
const [newSkillName, setNewSkillName] = useState('');
const [newSkillDescription, setNewSkillDescription] = useState('');
const [newSkillContent, setNewSkillContent] = useState('');
const [newSkillCategory, setNewSkillCategory] = useState('');
const [newSkillTags, setNewSkillTags] = useState('');
const queryClient = useQueryClient();
// Fetch installed skills
const { data: skillsData, isLoading: skillsLoading } = useQuery({
queryKey: ['skills', projectPath],
queryFn: async () => {
const response = await axios.get('/api/skills', {
params: { project_path: projectPath, include_content: false }
});
return response.data;
},
});
// Fetch skill templates
const { data: templatesData, isLoading: templatesLoading } = useQuery({
queryKey: ['skill-templates'],
queryFn: async () => {
const response = await axios.get('/api/skills/templates');
return response.data;
},
});
// Fetch community sources
const { data: communitySourcesData } = useQuery({
queryKey: ['community-sources'],
queryFn: async () => {
const response = await axios.get('/api/skills/community/sources');
return response.data;
},
});
// Fetch community skills
const { data: communitySkillsData, isLoading: communityLoading, refetch: refetchCommunity } = useQuery({
queryKey: ['community-skills', selectedCommunitySource],
queryFn: async () => {
const params: { source?: string } = {};
if (selectedCommunitySource !== 'all') {
params.source = selectedCommunitySource;
}
const response = await axios.get('/api/skills/community', { params });
return response.data;
},
enabled: showTemplates && templateTab === 'community',
});
// Install template mutation
const installTemplateMutation = useMutation({
mutationFn: async (templateName: string) => {
const response = await axios.post('/api/skills/install', {
template_name: templateName,
}, {
params: { project_path: projectPath }
});
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['skills', projectPath] });
setShowTemplates(false);
},
});
// Install community skill mutation
const installCommunityMutation = useMutation({
mutationFn: async (skill: CommunitySkill) => {
const response = await axios.post('/api/skills/community/install', {
skill_name: skill.name,
source: skill.source,
}, {
params: { project_path: projectPath }
});
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['skills', projectPath] });
},
});
// Create skill mutation
const createSkillMutation = useMutation({
mutationFn: async () => {
const response = await axios.post('/api/skills', {
name: newSkillName,
description: newSkillDescription,
content: newSkillContent,
category: newSkillCategory || null,
tags: newSkillTags ? newSkillTags.split(',').map(t => t.trim()) : [],
}, {
params: { project_path: projectPath }
});
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['skills', projectPath] });
setShowCreateModal(false);
resetCreateForm();
},
});
// Delete skill mutation
const deleteSkillMutation = useMutation({
mutationFn: async (skillName: string) => {
await axios.delete(`/api/skills/${skillName}`, {
params: { project_path: projectPath }
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['skills', projectPath] });
setSelectedSkill(null);
},
});
// Get skill details
const { data: skillDetails, isLoading: detailsLoading } = useQuery({
queryKey: ['skill-details', selectedSkill?.name, selectedSkill?.scope],
queryFn: async () => {
if (!selectedSkill) return null;
const response = await axios.get(`/api/skills/${selectedSkill.scope}/${selectedSkill.name}`, {
params: { project_path: projectPath }
});
return response.data;
},
enabled: !!selectedSkill,
});
const resetCreateForm = () => {
setNewSkillName('');
setNewSkillDescription('');
setNewSkillContent('');
setNewSkillCategory('');
setNewSkillTags('');
};
const toggleCategory = (category: string) => {
const newExpanded = new Set(expandedCategories);
if (newExpanded.has(category)) {
newExpanded.delete(category);
} else {
newExpanded.add(category);
}
setExpandedCategories(newExpanded);
};
const globalSkills: Skill[] = skillsData?.global || [];
const projectSkills: Skill[] = skillsData?.project || [];
const templates: SkillTemplate[] = templatesData?.templates || [];
const categories: string[] = templatesData?.categories || [];
const communitySources: Record<string, CommunitySource> = communitySourcesData?.sources || {};
const communitySkills: CommunitySkill[] = communitySkillsData?.skills || [];
// Filter templates by search
const filteredTemplates = templates.filter(t =>
t.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
t.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
t.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
);
// Group templates by category
const templatesByCategory = categories.reduce((acc, cat) => {
acc[cat] = filteredTemplates.filter(t => t.category === cat);
return acc;
}, {} as Record<string, SkillTemplate[]>);
// Filter community skills by search
const filteredCommunitySkills = communitySkills.filter(s =>
s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
s.description.toLowerCase().includes(searchQuery.toLowerCase())
);
// Group community skills by source
const communityBySource = filteredCommunitySkills.reduce((acc, skill) => {
if (!acc[skill.source]) {
acc[skill.source] = [];
}
acc[skill.source].push(skill);
return acc;
}, {} as Record<string, CommunitySkill[]>);
return (
<div className="h-full flex flex-col">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Sparkles className="w-6 h-6 text-electric-teal" />
Skills Management
</h2>
<p className="text-light-grey text-sm mt-1">
Manage Claude Code skills for this project
</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowTemplates(true)}
className="btn-secondary flex items-center gap-2"
>
<Download className="w-4 h-4" />
Install Template
</button>
<button
onClick={() => setShowCreateModal(true)}
className="btn-primary flex items-center gap-2"
>
<Plus className="w-4 h-4" />
Create Skill
</button>
</div>
</div>
{/* Main Content */}
<div className="flex-1 flex gap-6 min-h-0">
{/* Skills List */}
<div className="w-80 flex flex-col">
{/* Global Skills */}
<div className="mb-4">
<h3 className="text-sm font-bold text-electric-teal mb-2 flex items-center gap-2">
<Globe className="w-4 h-4" />
Global Skills ({globalSkills.length})
</h3>
<div className="space-y-1">
{skillsLoading ? (
<div className="text-light-grey text-sm py-2">Loading...</div>
) : globalSkills.length === 0 ? (
<div className="text-light-grey text-sm py-2">No global skills</div>
) : (
globalSkills.map((skill) => (
<div
key={`global-${skill.name}`}
onClick={() => setSelectedSkill(skill)}
className={`p-3 rounded-lg border cursor-pointer transition-all ${
selectedSkill?.name === skill.name && selectedSkill?.scope === 'global'
? 'bg-electric-teal/20 border-electric-teal'
: 'bg-cool-blue/10 border-electric-teal/20 hover:border-electric-teal/50'
}`}
>
<div className="flex items-center justify-between">
<div className="font-medium text-white">{skill.name}</div>
<span className="text-xs bg-cool-blue/30 text-light-grey px-2 py-0.5 rounded">
{skill.source}
</span>
</div>
{skill.description && (
<div className="text-xs text-light-grey mt-1 line-clamp-2">
{skill.description}
</div>
)}
</div>
))
)}
</div>
</div>
{/* Project Skills */}
<div className="flex-1 min-h-0">
<h3 className="text-sm font-bold text-electric-teal mb-2 flex items-center gap-2">
<FolderOpen className="w-4 h-4" />
Project Skills ({projectSkills.length})
</h3>
<div className="space-y-1 overflow-y-auto max-h-[400px]">
{skillsLoading ? (
<div className="text-light-grey text-sm py-2">Loading...</div>
) : projectSkills.length === 0 ? (
<div className="text-center py-8">
<Sparkles className="w-12 h-12 mx-auto mb-2 text-electric-teal/30" />
<p className="text-light-grey text-sm">No project skills yet</p>
<p className="text-light-grey text-xs mt-1">
Install a template or create a custom skill
</p>
</div>
) : (
projectSkills.map((skill) => (
<div
key={`project-${skill.name}`}
onClick={() => setSelectedSkill(skill)}
className={`p-3 rounded-lg border cursor-pointer transition-all ${
selectedSkill?.name === skill.name && selectedSkill?.scope === 'project'
? 'bg-electric-teal/20 border-electric-teal'
: 'bg-cool-blue/10 border-electric-teal/20 hover:border-electric-teal/50'
}`}
>
<div className="flex items-center justify-between">
<div className="font-medium text-white">{skill.name}</div>
<span className="text-xs bg-blaze-orange/20 text-blaze-orange px-2 py-0.5 rounded">
{skill.source}
</span>
</div>
{skill.description && (
<div className="text-xs text-light-grey mt-1 line-clamp-2">
{skill.description}
</div>
)}
{skill.tags && skill.tags.length > 0 && (
<div className="flex gap-1 mt-2 flex-wrap">
{skill.tags.slice(0, 3).map(tag => (
<span key={tag} className="text-xs bg-cool-blue/20 text-light-grey px-1.5 py-0.5 rounded">
{tag}
</span>
))}
</div>
)}
</div>
))
)}
</div>
</div>
</div>
{/* Skill Details */}
<div className="flex-1 bg-cool-blue/10 border border-electric-teal/30 rounded-lg p-6 overflow-auto">
{selectedSkill ? (
detailsLoading ? (
<div className="flex items-center justify-center h-full">
<div className="text-light-grey">Loading skill details...</div>
</div>
) : skillDetails ? (
<div>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-xl font-bold text-white">{skillDetails.name}</h3>
<div className="flex items-center gap-2 mt-1">
<span className={`text-xs px-2 py-0.5 rounded ${
skillDetails.scope === 'global'
? 'bg-cool-blue/30 text-light-grey'
: 'bg-blaze-orange/20 text-blaze-orange'
}`}>
{skillDetails.scope}
</span>
<span className="text-xs bg-electric-teal/20 text-electric-teal px-2 py-0.5 rounded">
{skillDetails.source}
</span>
{skillDetails.category && (
<span className="text-xs bg-cool-blue/20 text-light-grey px-2 py-0.5 rounded">
{skillDetails.category}
</span>
)}
</div>
</div>
{skillDetails.scope === 'project' && (
<button
onClick={() => {
if (confirm(`Delete skill "${skillDetails.name}"?`)) {
deleteSkillMutation.mutate(skillDetails.name);
}
}}
className="p-2 rounded hover:bg-blaze-orange/20 transition-colors"
title="Delete skill"
>
<Trash2 className="w-5 h-5 text-blaze-orange" />
</button>
)}
</div>
{skillDetails.description && (
<p className="text-light-grey mb-4">{skillDetails.description}</p>
)}
{skillDetails.tags && skillDetails.tags.length > 0 && (
<div className="flex gap-2 mb-4 flex-wrap">
<Tag className="w-4 h-4 text-light-grey" />
{skillDetails.tags.map((tag: string) => (
<span key={tag} className="text-xs bg-cool-blue/20 text-light-grey px-2 py-1 rounded">
{tag}
</span>
))}
</div>
)}
<div className="border-t border-electric-teal/20 pt-4">
<h4 className="text-sm font-bold text-electric-teal mb-2">Content</h4>
<pre className="bg-deep-night/50 border border-electric-teal/20 rounded-lg p-4 overflow-auto max-h-[400px] text-sm text-light-grey whitespace-pre-wrap">
{skillDetails.content || 'No content available'}
</pre>
</div>
<div className="flex gap-4 text-xs text-light-grey mt-4">
<span>Created: {new Date(skillDetails.created).toLocaleDateString()}</span>
<span>Modified: {new Date(skillDetails.modified).toLocaleDateString()}</span>
</div>
</div>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-light-grey">Failed to load skill details</div>
</div>
)
) : (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<Eye className="w-16 h-16 text-electric-teal/30 mx-auto mb-4" />
<h3 className="text-lg font-bold text-light-grey mb-2">Select a Skill</h3>
<p className="text-light-grey text-sm">
Click on a skill to view its details and content
</p>
</div>
</div>
)}
</div>
</div>
{/* Install Template Modal */}
<AnimatePresence>
{showTemplates && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
onClick={() => setShowTemplates(false)}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="bg-deep-night border border-electric-teal/30 rounded-lg p-6 max-w-2xl w-full max-h-[80vh] overflow-hidden flex flex-col"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold text-white">Install Skills</h2>
<button
onClick={() => setShowTemplates(false)}
className="p-1 hover:bg-electric-teal/20 rounded"
>
<X className="w-5 h-5 text-light-grey" />
</button>
</div>
{/* Tab Selector */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setTemplateTab('local')}
className={`flex-1 py-2 px-4 rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2 ${
templateTab === 'local'
? 'bg-electric-teal text-deep-night'
: 'bg-cool-blue/20 text-light-grey hover:bg-cool-blue/30'
}`}
>
<FolderOpen className="w-4 h-4" />
Local Templates
</button>
<button
onClick={() => setTemplateTab('community')}
className={`flex-1 py-2 px-4 rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2 ${
templateTab === 'community'
? 'bg-electric-teal text-deep-night'
: 'bg-cool-blue/20 text-light-grey hover:bg-cool-blue/30'
}`}
>
<Github className="w-4 h-4" />
Community Skills
</button>
</div>
{/* Search and Source Filter */}
<div className="flex gap-2 mb-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-light-grey" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={templateTab === 'local' ? 'Search templates...' : 'Search community skills...'}
className="input w-full pl-10"
/>
</div>
{templateTab === 'community' && (
<>
<select
value={selectedCommunitySource}
onChange={(e) => setSelectedCommunitySource(e.target.value)}
className="input w-40"
>
<option value="all">All Sources</option>
{Object.entries(communitySources).map(([key, source]) => (
<option key={key} value={key}>{source.name}</option>
))}
</select>
<button
onClick={() => refetchCommunity()}
className="btn-secondary p-2"
title="Refresh community skills"
>
<RefreshCw className={`w-4 h-4 ${communityLoading ? 'animate-spin' : ''}`} />
</button>
</>
)}
</div>
{/* Content based on tab */}
<div className="flex-1 overflow-y-auto space-y-2">
{templateTab === 'local' ? (
// Local Templates
templatesLoading ? (
<div className="text-center py-8 text-light-grey">Loading templates...</div>
) : (
categories.map((category) => {
const categoryTemplates = templatesByCategory[category] || [];
if (categoryTemplates.length === 0) return null;
return (
<div key={category} className="border border-electric-teal/20 rounded-lg overflow-hidden">
<button
onClick={() => toggleCategory(category)}
className="w-full px-4 py-3 flex items-center justify-between bg-cool-blue/10 hover:bg-cool-blue/20 transition-colors"
>
<span className="font-semibold text-electric-teal capitalize">
{category} ({categoryTemplates.length})
</span>
{expandedCategories.has(category) ? (
<ChevronDown className="w-4 h-4 text-light-grey" />
) : (
<ChevronRight className="w-4 h-4 text-light-grey" />
)}
</button>
{expandedCategories.has(category) && (
<div className="divide-y divide-electric-teal/10">
{categoryTemplates.map((template) => {
const isInstalled = projectSkills.some(s => s.name === template.name);
return (
<div
key={template.name}
className="p-4 hover:bg-cool-blue/10 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="font-medium text-white">{template.name}</div>
<div className="text-sm text-light-grey mt-1">
{template.description}
</div>
<div className="flex gap-1 mt-2 flex-wrap">
{template.tags.map(tag => (
<span key={tag} className="text-xs bg-cool-blue/20 text-light-grey px-1.5 py-0.5 rounded">
{tag}
</span>
))}
</div>
</div>
<button
onClick={() => installTemplateMutation.mutate(template.name)}
disabled={isInstalled || installTemplateMutation.isPending}
className={`ml-4 px-3 py-1.5 rounded text-sm font-medium transition-colors ${
isInstalled
? 'bg-electric-teal/20 text-electric-teal cursor-not-allowed'
: 'bg-electric-teal text-deep-night hover:bg-electric-teal/80'
}`}
>
{isInstalled ? (
<span className="flex items-center gap-1">
<Check className="w-4 h-4" />
Installed
</span>
) : installTemplateMutation.isPending ? (
'Installing...'
) : (
'Install'
)}
</button>
</div>
</div>
);
})}
</div>
)}
</div>
);
})
)
) : (
// Community Skills
communityLoading ? (
<div className="text-center py-8">
<RefreshCw className="w-8 h-8 text-electric-teal animate-spin mx-auto mb-2" />
<div className="text-light-grey">Fetching community skills from GitHub...</div>
<div className="text-light-grey text-sm mt-1">This may take a moment</div>
</div>
) : filteredCommunitySkills.length === 0 ? (
<div className="text-center py-8">
<Github className="w-12 h-12 text-light-grey/30 mx-auto mb-2" />
<div className="text-light-grey">No community skills found</div>
<div className="text-light-grey text-sm mt-1">Try a different search or source</div>
</div>
) : (
Object.entries(communityBySource).map(([source, skills]) => {
const sourceInfo = communitySources[source];
return (
<div key={source} className="border border-electric-teal/20 rounded-lg overflow-hidden">
<button
onClick={() => toggleCategory(source)}
className="w-full px-4 py-3 flex items-center justify-between bg-cool-blue/10 hover:bg-cool-blue/20 transition-colors"
>
<div className="flex items-center gap-2">
<Github className="w-4 h-4 text-light-grey" />
<span className="font-semibold text-electric-teal">
{sourceInfo?.name || source} ({skills.length})
</span>
</div>
<div className="flex items-center gap-2">
<a
href={`https://github.com/${sourceInfo?.repo || source}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="text-light-grey hover:text-electric-teal"
>
<ExternalLink className="w-4 h-4" />
</a>
{expandedCategories.has(source) ? (
<ChevronDown className="w-4 h-4 text-light-grey" />
) : (
<ChevronRight className="w-4 h-4 text-light-grey" />
)}
</div>
</button>
{expandedCategories.has(source) && (
<div className="divide-y divide-electric-teal/10">
{skills.map((skill) => {
const isInstalled = projectSkills.some(s => s.name === skill.name);
return (
<div
key={`${skill.source}-${skill.name}`}
className="p-4 hover:bg-cool-blue/10 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2">
<div className="font-medium text-white">{skill.name}</div>
<a
href={skill.readme_url}
target="_blank"
rel="noopener noreferrer"
className="text-light-grey hover:text-electric-teal"
title="View on GitHub"
>
<ExternalLink className="w-3 h-3" />
</a>
</div>
<div className="text-sm text-light-grey mt-1">
{skill.description || 'No description available'}
</div>
<div className="text-xs text-light-grey/60 mt-1">
{skill.repo}
</div>
</div>
<button
onClick={() => installCommunityMutation.mutate(skill)}
disabled={isInstalled || installCommunityMutation.isPending}
className={`ml-4 px-3 py-1.5 rounded text-sm font-medium transition-colors ${
isInstalled
? 'bg-electric-teal/20 text-electric-teal cursor-not-allowed'
: 'bg-electric-teal text-deep-night hover:bg-electric-teal/80'
}`}
>
{isInstalled ? (
<span className="flex items-center gap-1">
<Check className="w-4 h-4" />
Installed
</span>
) : installCommunityMutation.isPending ? (
'Installing...'
) : (
'Install'
)}
</button>
</div>
</div>
);
})}
</div>
)}
</div>
);
})
)
)}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* Create Skill Modal */}
<AnimatePresence>
{showCreateModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
onClick={() => setShowCreateModal(false)}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="bg-deep-night border border-electric-teal/30 rounded-lg p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-2xl font-bold text-white mb-4">Create Custom Skill</h2>
<div className="space-y-4">
<div>
<label className="block text-sm text-light-grey mb-2">Skill Name *</label>
<input
type="text"
value={newSkillName}
onChange={(e) => setNewSkillName(e.target.value)}
placeholder="my-skill"
className="input w-full"
/>
</div>
<div>
<label className="block text-sm text-light-grey mb-2">Description *</label>
<input
type="text"
value={newSkillDescription}
onChange={(e) => setNewSkillDescription(e.target.value)}
placeholder="What this skill does..."
className="input w-full"
/>
</div>
<div>
<label className="block text-sm text-light-grey mb-2">Category (optional)</label>
<input
type="text"
value={newSkillCategory}
onChange={(e) => setNewSkillCategory(e.target.value)}
placeholder="e.g., backend, frontend, testing"
className="input w-full"
/>
</div>
<div>
<label className="block text-sm text-light-grey mb-2">Tags (comma-separated)</label>
<input
type="text"
value={newSkillTags}
onChange={(e) => setNewSkillTags(e.target.value)}
placeholder="ruby, rails, api"
className="input w-full"
/>
</div>
<div>
<label className="block text-sm text-light-grey mb-2">Content (Markdown) *</label>
<textarea
value={newSkillContent}
onChange={(e) => setNewSkillContent(e.target.value)}
placeholder="# My Skill&#10;&#10;Describe what this skill does and how to use it..."
className="input w-full h-64 font-mono text-sm resize-none"
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => {
setShowCreateModal(false);
resetCreateForm();
}}
className="btn-secondary flex-1"
>
Cancel
</button>
<button
onClick={() => createSkillMutation.mutate()}
disabled={!newSkillName.trim() || !newSkillDescription.trim() || !newSkillContent.trim() || createSkillMutation.isPending}
className="btn-primary flex-1"
>
{createSkillMutation.isPending ? 'Creating...' : 'Create Skill'}
</button>
</div>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

View file

@ -1,6 +1,6 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Home, Settings, Database, MessageSquare, Plus, Trash2, FolderOpen, Zap, Edit, Activity, Trello, FileSearch } from 'lucide-react';
import { Home, Settings, Database, MessageSquare, Plus, Trash2, FolderOpen, Zap, Edit, Activity, Trello, FileSearch, Sparkles } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { motion, AnimatePresence } from 'framer-motion';
import axios from 'axios';
@ -11,6 +11,7 @@ import DirectoryPicker from '../components/DirectoryPicker';
import ServiceDashboard from '../components/ServiceDashboard';
import KanbanBoard from '../components/KanbanBoard';
import JobsDashboard from '../components/JobsDashboard';
import SkillsManagement from '../components/SkillsManagement';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8051';
@ -34,7 +35,7 @@ interface ProjectMCP {
export default function MainApp() {
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const [selectedMCP, setSelectedMCP] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<'overview' | 'mcps' | 'chat' | 'services' | 'kanban' | 'jobs'>('overview');
const [activeTab, setActiveTab] = useState<'overview' | 'mcps' | 'chat' | 'services' | 'kanban' | 'jobs' | 'skills'>('overview');
const [showCreateProject, setShowCreateProject] = useState(false);
const [showProjectSetup, setShowProjectSetup] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
@ -358,6 +359,17 @@ export default function MainApp() {
<FileSearch className="w-4 h-4 inline mr-2" />
Jobs
</button>
<button
onClick={() => setActiveTab('skills')}
className={`px-4 py-2 rounded-lg font-semibold transition-all ${
activeTab === 'skills'
? 'bg-electric-teal text-deep-night'
: 'text-light-grey hover:text-white'
}`}
>
<Sparkles className="w-4 h-4 inline mr-2" />
Skills
</button>
</div>
{/* Tab Content */}
@ -452,6 +464,8 @@ export default function MainApp() {
<ServiceDashboard />
) : activeTab === 'jobs' ? (
<JobsDashboard />
) : activeTab === 'skills' ? (
<SkillsManagement projectPath={selectedProject.path} />
) : (
selectedMCP ? (
<ChatInterface

View file

@ -70,6 +70,14 @@ async def api_delete(path: str) -> dict:
return response.json()
async def api_put(path: str, data: dict = None) -> dict:
"""Make PUT request to API."""
async with httpx.AsyncClient() as client:
response = await client.put(api_url(path), json=data or {}, timeout=30.0)
response.raise_for_status()
return response.json()
@server.list_tools()
async def list_tools() -> list[Tool]:
"""List available Claude OS tools.
@ -262,6 +270,134 @@ async def list_tools() -> list[Tool]:
"properties": {},
"required": []
}
),
# Session Tools
Tool(
name="list_sessions",
description="List Claude Code session files for a project. Sessions are stored in ~/.claude/projects/{encoded-path}/ and contain conversation history.",
inputSchema={
"type": "object",
"properties": {
"project_path": {"type": "string", "description": "Absolute path to project directory (e.g., /Users/x/Projects/myapp)"},
"limit": {"type": "integer", "default": 50, "description": "Maximum sessions to return (default: 50)"}
},
"required": ["project_path"]
}
),
Tool(
name="extract_session_insights",
description="Extract insights from a Claude Code session file using LLM. Analyzes conversation to find decisions, patterns, solutions, and blockers. Optionally saves insights to a knowledge base.",
inputSchema={
"type": "object",
"properties": {
"session_path": {"type": "string", "description": "Absolute path to .jsonl session file"},
"kb_name": {"type": "string", "description": "Knowledge base name to save insights (optional)"},
"auto_save": {"type": "boolean", "default": False, "description": "Auto-save all insights without prompting"},
"insight_types": {
"type": "array",
"items": {"type": "string", "enum": ["decision", "pattern", "solution", "blocker"]},
"default": ["decision", "pattern", "solution", "blocker"],
"description": "Types of insights to extract"
},
"min_confidence": {"type": "number", "default": 0.7, "description": "Minimum confidence threshold (0.0-1.0)"}
},
"required": ["session_path"]
}
),
# Skill Management Tools
Tool(
name="list_skills",
description="List all skills (global and project-level). Global skills are in ~/.claude/skills/, project skills are in {project}/.claude/skills/.",
inputSchema={
"type": "object",
"properties": {
"project_path": {"type": "string", "description": "Project path to include project-level skills (optional)"},
"include_content": {"type": "boolean", "default": False, "description": "Include full skill content"}
},
"required": []
}
),
Tool(
name="list_skill_templates",
description="List available skill templates that can be installed to projects. Templates are organized by category.",
inputSchema={
"type": "object",
"properties": {
"category": {"type": "string", "description": "Filter by category (optional)"}
},
"required": []
}
),
Tool(
name="get_skill",
description="Get skill details including full content.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "Skill name"},
"scope": {"type": "string", "enum": ["global", "project"], "description": "Where the skill is located"},
"project_path": {"type": "string", "description": "Project path (required if scope is 'project')"}
},
"required": ["name", "scope"]
}
),
Tool(
name="install_skill_template",
description="Install a skill template to a project. Copies the template to {project}/.claude/skills/.",
inputSchema={
"type": "object",
"properties": {
"template_name": {"type": "string", "description": "Name of template to install"},
"project_path": {"type": "string", "description": "Project path to install to"},
"custom_name": {"type": "string", "description": "Custom name for installed skill (optional)"}
},
"required": ["template_name", "project_path"]
}
),
Tool(
name="create_skill",
description="Create a custom skill for a project.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "Skill name"},
"description": {"type": "string", "description": "Skill description"},
"content": {"type": "string", "description": "Skill content (markdown)"},
"project_path": {"type": "string", "description": "Project path"},
"category": {"type": "string", "description": "Category (optional)"},
"tags": {"type": "array", "items": {"type": "string"}, "description": "Tags (optional)"}
},
"required": ["name", "description", "content", "project_path"]
}
),
Tool(
name="update_skill",
description="Update an existing project skill.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "Skill name"},
"project_path": {"type": "string", "description": "Project path"},
"content": {"type": "string", "description": "New content (optional)"},
"description": {"type": "string", "description": "New description (optional)"},
"tags": {"type": "array", "items": {"type": "string"}, "description": "New tags (optional)"}
},
"required": ["name", "project_path"]
}
),
Tool(
name="delete_skill",
description="Delete a project skill. Cannot delete global skills.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "Skill name to delete"},
"project_path": {"type": "string", "description": "Project path"}
},
"required": ["name", "project_path"]
}
)
]
@ -366,6 +502,69 @@ async def _execute_tool(name: str, args: dict[str, Any]) -> dict:
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
# Session Tools
elif name == "list_sessions":
return await api_get(f"/api/sessions/list?project_path={args['project_path']}&limit={args.get('limit', 50)}")
elif name == "extract_session_insights":
return await api_post("/api/sessions/extract", {
"session_path": args["session_path"],
"kb_name": args.get("kb_name"),
"auto_save": args.get("auto_save", False),
"insight_types": args.get("insight_types"),
"min_confidence": args.get("min_confidence", 0.7)
})
# Skill Management Tools
elif name == "list_skills":
project_path = args.get("project_path", "")
include_content = args.get("include_content", False)
url = f"/api/skills?include_content={include_content}"
if project_path:
url += f"&project_path={project_path}"
return await api_get(url)
elif name == "list_skill_templates":
category = args.get("category")
url = "/api/skills/templates"
if category:
url += f"?category={category}"
return await api_get(url)
elif name == "get_skill":
scope = args["scope"]
name_param = args["name"]
project_path = args.get("project_path", "")
url = f"/api/skills/{scope}/{name_param}"
if project_path:
url += f"?project_path={project_path}"
return await api_get(url)
elif name == "install_skill_template":
return await api_post(f"/api/skills/install?project_path={args['project_path']}", {
"template_name": args["template_name"],
"custom_name": args.get("custom_name")
})
elif name == "create_skill":
return await api_post(f"/api/skills?project_path={args['project_path']}", {
"name": args["name"],
"description": args["description"],
"content": args["content"],
"category": args.get("category"),
"tags": args.get("tags")
})
elif name == "update_skill":
return await api_put(f"/api/skills/{args['name']}?project_path={args['project_path']}", {
"content": args.get("content"),
"description": args.get("description"),
"tags": args.get("tags")
})
elif name == "delete_skill":
return await api_delete(f"/api/skills/{args['name']}?project_path={args['project_path']}")
else:
return {"error": f"Unknown tool: {name}"}

View file

@ -2018,6 +2018,543 @@ async def api_unarchive_spec(spec_id: int):
raise HTTPException(status_code=500, detail=str(e))
# ============================================================================
# SESSION EXTRACTION ENDPOINTS
# ============================================================================
from app.core.session_parser import SessionParser, list_session_files
from app.core.insight_extractor import InsightExtractor
class SessionListRequest(BaseModel):
"""Request model for listing sessions."""
project_path: str
limit: Optional[int] = 50
class SessionExtractRequest(BaseModel):
"""Request model for extracting insights from a session."""
session_path: str
kb_name: Optional[str] = None
auto_save: bool = False
insight_types: Optional[List[str]] = None
min_confidence: float = 0.7
@app.get("/api/sessions/list")
async def api_list_sessions(project_path: str, limit: int = 50):
"""
REST API: List Claude Code session files for a project.
Args:
project_path: Absolute path to project directory
limit: Maximum number of sessions to return (default: 50)
Returns:
List of session file metadata
"""
try:
sessions = list_session_files(project_path, limit)
# Calculate total size
total_size_bytes = sum(s["size_bytes"] for s in sessions)
total_size_mb = total_size_bytes / (1024 * 1024)
return {
"project_path": project_path,
"sessions": sessions,
"total_sessions": len(sessions),
"total_size_mb": round(total_size_mb, 2)
}
except Exception as e:
logger.error(f"Failed to list sessions for {project_path}: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/sessions/extract")
async def api_extract_session_insights(request: SessionExtractRequest):
"""
REST API: Extract insights from a Claude Code session file.
Steps:
1. Parse session .jsonl file
2. Build conversation summary
3. Send to LLM for insight extraction
4. Optionally save insights to knowledge base
Args:
request: SessionExtractRequest with session_path, kb_name, etc.
Returns:
Extracted insights with metadata
"""
try:
# Parse session file
parser = SessionParser(request.session_path)
session_data = parser.parse()
# Build summary for extraction
summary = parser.get_summary_for_extraction()
# Extract insights using LLM
extractor = InsightExtractor()
insights = await extractor.extract(
session_summary=summary,
insight_types=request.insight_types
)
# Filter by confidence
filtered_insights = extractor.filter_by_confidence(
insights,
min_confidence=request.min_confidence
)
# Optionally save to knowledge base
saved_count = 0
if request.auto_save and request.kb_name:
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(request.kb_name):
raise HTTPException(
status_code=404,
detail=f"Knowledge base '{request.kb_name}' not found"
)
for insight in filtered_insights:
# Format as markdown
markdown = extractor.format_for_save(insight, session_data.session_id)
# Generate filename
filename = f"session-{session_data.session_id}-{insight.type}-{saved_count}.md"
# Save to KB
db_manager.upsert_document(
collection_name=request.kb_name,
doc_id=filename,
content=markdown,
metadata={
"title": insight.title,
"type": "session_insight",
"insight_type": insight.type,
"session_id": session_data.session_id,
"confidence": insight.confidence
}
)
saved_count += 1
# Calculate session duration
duration_minutes = None
if session_data.start_time and session_data.end_time:
try:
from datetime import datetime
start = datetime.fromisoformat(session_data.start_time.replace('Z', '+00:00'))
end = datetime.fromisoformat(session_data.end_time.replace('Z', '+00:00'))
duration_minutes = (end - start).total_seconds() / 60
except Exception:
pass
# Format response
insights_response = []
for insight in filtered_insights:
insights_response.append({
"type": insight.type,
"title": insight.title,
"content": insight.content,
"confidence": insight.confidence,
"saved": request.auto_save and request.kb_name is not None
})
return {
"session_id": session_data.session_id,
"session_path": request.session_path,
"session_date": session_data.start_time,
"duration_minutes": duration_minutes,
"message_count": len(session_data.messages),
"insights": insights_response,
"insights_saved": saved_count,
"kb_name": request.kb_name if request.auto_save else None
}
except FileNotFoundError as e:
logger.error(f"Session file not found: {e}")
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Failed to extract insights from session: {e}")
raise HTTPException(status_code=500, detail=str(e))
# ============================================================================
# SKILLS MANAGEMENT ENDPOINTS
# ============================================================================
from app.core.skill_manager import (
SkillManager,
list_skills,
list_skill_templates,
list_community_skills,
install_community_skill,
get_community_manager,
COMMUNITY_SOURCES
)
class SkillCreateRequest(BaseModel):
"""Request model for creating a custom skill."""
name: str
description: str
content: str
category: Optional[str] = None
tags: Optional[List[str]] = None
class SkillUpdateRequest(BaseModel):
"""Request model for updating a skill."""
content: Optional[str] = None
description: Optional[str] = None
tags: Optional[List[str]] = None
class SkillInstallRequest(BaseModel):
"""Request model for installing a template."""
template_name: str
custom_name: Optional[str] = None
@app.get("/api/skills")
async def api_list_skills(project_path: Optional[str] = None, include_content: bool = False):
"""
REST API: List all skills (global and project).
Args:
project_path: Optional project path for project-level skills
include_content: Whether to include skill content (default: False)
Returns:
Dict with global and project skill lists
"""
try:
manager = SkillManager(project_path)
skills = manager.list_all_skills(include_content=include_content)
return {
"global": [s.to_dict() for s in skills["global"]],
"project": [s.to_dict() for s in skills["project"]],
"total_global": len(skills["global"]),
"total_project": len(skills["project"])
}
except Exception as e:
logger.error(f"Failed to list skills: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/skills/templates")
async def api_list_skill_templates(category: Optional[str] = None):
"""
REST API: List available skill templates.
Args:
category: Optional category filter
Returns:
List of skill templates with categories
"""
try:
manager = SkillManager()
templates = manager.list_templates(category)
categories = manager.get_template_categories()
return {
"templates": [t.to_dict() for t in templates],
"categories": categories,
"total": len(templates)
}
except Exception as e:
logger.error(f"Failed to list skill templates: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/skills/{scope}/{name}")
async def api_get_skill(scope: str, name: str, project_path: Optional[str] = None):
"""
REST API: Get skill details including content.
Args:
scope: "global" or "project"
name: Skill name
project_path: Required if scope is "project"
Returns:
Skill details with content
"""
if scope not in ["global", "project"]:
raise HTTPException(status_code=400, detail="Scope must be 'global' or 'project'")
if scope == "project" and not project_path:
raise HTTPException(status_code=400, detail="project_path required for project skills")
try:
manager = SkillManager(project_path)
skill = manager.get_skill(name, scope)
if not skill:
raise HTTPException(status_code=404, detail=f"Skill not found: {name}")
return skill.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get skill {name}: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/skills/install")
async def api_install_skill_template(request: SkillInstallRequest, project_path: str):
"""
REST API: Install a skill template to a project.
Args:
request: SkillInstallRequest with template_name and optional custom_name
project_path: Project path to install to
Returns:
Installed skill details
"""
try:
manager = SkillManager(project_path)
skill = manager.install_template(
template_name=request.template_name,
custom_name=request.custom_name
)
logger.info(f"Installed skill template {request.template_name} to {project_path}")
return {
"message": f"Installed template '{request.template_name}' as '{skill.name}'",
"skill": skill.to_dict()
}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except FileExistsError as e:
raise HTTPException(status_code=409, detail=str(e))
except Exception as e:
logger.error(f"Failed to install skill template: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/skills")
async def api_create_skill(request: SkillCreateRequest, project_path: str):
"""
REST API: Create a custom skill for a project.
Args:
request: SkillCreateRequest with name, description, content, etc.
project_path: Project path to create skill in
Returns:
Created skill details
"""
try:
manager = SkillManager(project_path)
skill = manager.create_skill(
name=request.name,
description=request.description,
content=request.content,
category=request.category,
tags=request.tags
)
logger.info(f"Created custom skill '{request.name}' in {project_path}")
return {
"message": f"Created skill '{request.name}'",
"skill": skill.to_dict()
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except FileExistsError as e:
raise HTTPException(status_code=409, detail=str(e))
except Exception as e:
logger.error(f"Failed to create skill: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.put("/api/skills/{name}")
async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: str):
"""
REST API: Update an existing project skill.
Args:
name: Skill name
request: SkillUpdateRequest with content, description, tags
project_path: Project path
Returns:
Updated skill details
"""
try:
manager = SkillManager(project_path)
skill = manager.update_skill(
name=name,
content=request.content,
description=request.description,
tags=request.tags
)
logger.info(f"Updated skill '{name}' in {project_path}")
return {
"message": f"Updated skill '{name}'",
"skill": skill.to_dict()
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except FileNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Failed to update skill: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/api/skills/{name}")
async def api_delete_skill(name: str, project_path: str):
"""
REST API: Delete a project skill.
Args:
name: Skill name
project_path: Project path
Returns:
Success message
"""
try:
manager = SkillManager(project_path)
manager.delete_skill(name)
logger.info(f"Deleted skill '{name}' from {project_path}")
return {
"message": f"Deleted skill '{name}'",
"name": name
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except FileNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Failed to delete skill: {e}")
raise HTTPException(status_code=500, detail=str(e))
# ============================================================================
# COMMUNITY SKILLS ENDPOINTS
# ============================================================================
class CommunitySkillInstallRequest(BaseModel):
"""Request model for installing a community skill."""
source: str # "anthropic", "superpowers"
skill_name: str
custom_name: Optional[str] = None
@app.get("/api/skills/community/sources")
async def api_list_community_sources():
"""
REST API: List available community skill sources.
Returns:
Dict of available sources with metadata
"""
return {
"sources": {
key: {
"name": config["name"],
"description": config["description"],
"repo": config["repo"]
}
for key, config in COMMUNITY_SOURCES.items()
}
}
@app.get("/api/skills/community")
async def api_list_community_skills(source: Optional[str] = None):
"""
REST API: List skills from community repositories.
Args:
source: Optional source filter ("anthropic", "superpowers")
Returns:
List of community skills grouped by source
"""
try:
skills = await list_community_skills(source)
# Group by source
by_source = {}
for skill in skills:
src = skill["source"]
if src not in by_source:
by_source[src] = {
"name": COMMUNITY_SOURCES.get(src, {}).get("name", src),
"skills": []
}
by_source[src]["skills"].append(skill)
return {
"skills": skills, # Flat list for easy consumption
"sources": by_source, # Grouped by source
"total": len(skills)
}
except Exception as e:
logger.error(f"Failed to list community skills: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/skills/community/install")
async def api_install_community_skill(
request: CommunitySkillInstallRequest,
project_path: str
):
"""
REST API: Install a community skill to a project.
Fetches the skill from GitHub and installs it to the project's
.claude/skills/ directory.
Args:
request: CommunitySkillInstallRequest with source, skill_name
project_path: Project path to install to
Returns:
Installed skill details
"""
try:
skill = await install_community_skill(
source=request.source,
skill_name=request.skill_name,
project_path=project_path,
custom_name=request.custom_name
)
logger.info(
f"Installed community skill {request.skill_name} "
f"from {request.source} to {project_path}"
)
return {
"message": f"Installed '{request.skill_name}' from {request.source}",
"skill": skill
}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except FileExistsError as e:
raise HTTPException(status_code=409, detail=str(e))
except Exception as e:
logger.error(f"Failed to install community skill: {e}")
raise HTTPException(status_code=500, detail=str(e))
# ============================================================================
# OLLAMA ENDPOINTS
# ============================================================================

View file

@ -203,6 +203,107 @@ I found 3 things worth saving:
[y] Save all [n] Save none [s] Select individually
```
**Phase 2.5: Built-in Session History Extraction (Optional)**
After smart save prompts, offer to extract insights from the full conversation history:
```
═══════════════════════════════════════
📚 EXTRACT FROM SESSION HISTORY?
═══════════════════════════════════════
Claude Code recorded our full conversation in its built-in session file.
I can analyze it to extract additional insights we might have missed.
This captures things we discussed but didn't explicitly save:
• Decisions made during debugging
• Patterns discovered while exploring code
• Solutions to errors we encountered
• Blockers we hit and resolved
[y] Extract insights [n] Skip [v] Preview what I'd extract
```
**If user selects [y] Extract insights:**
```python
# Step 1: Find the current session file
# Sessions are stored at: ~/.claude/projects/{encoded-project-path}/
# Project path /Users/x/Projects/myapp becomes -Users-x-Projects-myapp
# Step 2: Call the extraction tool
mcp__code-forge__extract_session_insights
session_path: "~/.claude/projects/-Users-iamanmp-Projects-{project}/{session_id}.jsonl"
kb_name: "{project}-project_memories"
auto_save: false
```
**Step 3: Show extracted insights for approval:**
```
═══════════════════════════════════════
🔍 EXTRACTED INSIGHTS
═══════════════════════════════════════
Found 4 insights from session history:
1. 💎 HIGH (0.92) - Decision
"Hybrid Session Architecture"
Decided to combine Claude Code built-in sessions (for replay)
with Claude OS knowledge bases (for semantic search).
[Save] [Skip]
2. 💎 HIGH (0.88) - Pattern
"Session File Structure"
Built-in sessions use JSONL format with entry types: user,
assistant, file-history-snapshot. UUID parent chain for threading.
[Save] [Skip]
3. 📊 MEDIUM (0.75) - Solution
"Update Lock Error Resolution"
Claude update error "Another process updating" resolved by
checking version - was already on latest, no lock file existed.
[Save] [Skip]
4. 📊 MEDIUM (0.71) - Decision
"User-Controlled Extraction"
Session extraction should be opt-in with user approval for
each insight to avoid noise and respect privacy.
[Save] [Skip]
───────────────────────────────────────
[a] Save all [h] Save high confidence only (>0.8) [s] Skip all
```
**If user selects [v] Preview:**
Show a dry-run of what would be extracted without saving, then re-prompt.
**If user selects [n] Skip:**
Continue to Phase 3 without extraction.
**Configuration:**
Users can configure extraction behavior in their state file:
```json
{
"preferences": {
"session_extraction": {
"enabled": true,
"auto_prompt_on_end": true,
"min_confidence_threshold": 0.7,
"auto_save_high_confidence": false,
"insight_types": ["decisions", "patterns", "solutions", "blockers"]
}
}
}
```
If `auto_prompt_on_end` is false, skip this phase entirely.
If `auto_save_high_confidence` is true, automatically save insights above threshold.
**Phase 3: Update State & Stats**
```json
@ -309,7 +410,7 @@ When starting a session in a project without a state file, create one with this
```json
{
"version": "1.0.0",
"version": "1.1.0",
"current_session": {
"active": false,
"started_at": null,
@ -319,7 +420,8 @@ When starting a session in a project without a state file, create one with this
"context": [],
"decisions_made": [],
"blockers": [],
"patterns_discovered": []
"patterns_discovered": [],
"builtin_session_id": null
},
"last_session": null,
"active_memories": {
@ -331,6 +433,7 @@ When starting a session in a project without a state file, create one with this
"total_sessions": 0,
"total_memories_saved": 0,
"total_patterns_discovered": 0,
"total_insights_extracted": 0,
"most_referenced_memories": [],
"average_session_duration": 0
},
@ -339,9 +442,23 @@ When starting a session in a project without a state file, create one with this
"has_unresolved_blockers": false,
"patterns_ready_to_document": false
},
"preferences": {
"auto_search_on_start": true,
"max_memories_to_load": 5,
"search_days_back": 14,
"include_pattern_search": true,
"proactive_suggestions": true,
"session_extraction": {
"enabled": true,
"auto_prompt_on_end": true,
"min_confidence_threshold": 0.7,
"auto_save_high_confidence": false,
"insight_types": ["decisions", "patterns", "solutions", "blockers"]
}
},
"metadata": {
"last_updated": null,
"claude_os_version": "1.0.0",
"claude_os_version": "1.1.0",
"project_name": null
}
}
@ -445,21 +562,14 @@ I'll:
## CONFIGURATION
### Automatic Memory Search
All preferences are stored in the state file (`claude-os-state.json`). See the Default State Template above for all available options.
Edit state file to configure:
```json
{
"preferences": {
"auto_search_on_start": true,
"max_memories_to_load": 5,
"search_days_back": 14,
"include_pattern_search": true,
"proactive_suggestions": true
}
}
```
Key settings:
- `preferences.auto_search_on_start` - Search memories when starting session
- `preferences.max_memories_to_load` - How many memories to load (default: 5)
- `preferences.session_extraction.enabled` - Enable history extraction feature
- `preferences.session_extraction.auto_prompt_on_end` - Ask about extraction on session end
- `preferences.session_extraction.min_confidence_threshold` - Only show insights above this score
---

View file

@ -0,0 +1,262 @@
# Claude OS Skills Management
Manage Claude Code skills - list, install from templates, create custom, and configure.
## Commands
```
/claude-os-skills - List all skills (global + project)
/claude-os-skills templates - Show available templates
/claude-os-skills install <name> - Install template to project
/claude-os-skills create - Create a custom skill
/claude-os-skills view <name> - View skill details
/claude-os-skills delete <name> - Delete a project skill
```
---
## LIST SKILLS (default)
When user runs `/claude-os-skills` with no arguments:
**Step 1: Get Skills**
```
mcp__code-forge__list_skills
project_path: "{cwd}"
```
**Step 2: Display Results**
```
═══════════════════════════════════════
📚 CLAUDE CODE SKILLS
═══════════════════════════════════════
🌐 GLOBAL SKILLS (always available)
────────────────────────────────────
✓ memory - Save and recall information
✓ remember-this - Auto-save on trigger phrases
📁 PROJECT SKILLS ({cwd})
────────────────────────────────────
✓ rails-backend - Rails patterns and service objects
✓ rspec - RSpec testing patterns
💡 TIP: Run '/claude-os-skills templates' to see available templates
═══════════════════════════════════════
```
---
## LIST TEMPLATES
When user runs `/claude-os-skills templates`:
**Step 1: Get Templates**
```
mcp__code-forge__list_skill_templates
```
**Step 2: Display by Category**
```
═══════════════════════════════════════
📦 SKILL TEMPLATES
═══════════════════════════════════════
GENERAL
────────────────────────────────────
• initialize-project
Analyze codebase, generate docs
Tags: onboarding, analysis
RAILS
────────────────────────────────────
• rails-backend
Service objects, ActiveRecord, APIs
Tags: ruby, rails, backend
REACT
────────────────────────────────────
• react-patterns
Hooks, TypeScript, components
Tags: react, typescript, frontend
TESTING
────────────────────────────────────
• rspec
RSpec patterns, factories
Tags: ruby, rspec, tdd
───────────────────────────────────────
Install with: /claude-os-skills install <name>
═══════════════════════════════════════
```
---
## INSTALL TEMPLATE
When user runs `/claude-os-skills install <name>`:
**Step 1: Install Template**
```
mcp__code-forge__install_skill_template
template_name: "<name>"
project_path: "{cwd}"
```
**Step 2: Confirm**
```
✓ Installed '<name>' to {cwd}/.claude/skills/<name>
The skill is now available in this project!
To use it, Claude Code will automatically load it when relevant,
or you can explicitly invoke it with: skill: <name>
```
---
## CREATE CUSTOM SKILL
When user runs `/claude-os-skills create`:
**Step 1: Gather Information**
Ask the user for:
1. Skill name (e.g., "deployment", "code-review")
2. Description (one sentence)
3. Content (the skill instructions)
**Step 2: Create Skill**
```
mcp__code-forge__create_skill
name: "<name>"
description: "<description>"
content: "<content>"
project_path: "{cwd}"
```
**Step 3: Confirm**
```
✓ Created skill '<name>' in {cwd}/.claude/skills/<name>
The skill is now available in this project!
```
---
## VIEW SKILL
When user runs `/claude-os-skills view <name>`:
**Step 1: Get Skill Details**
First try project scope, then global:
```
mcp__code-forge__get_skill
name: "<name>"
scope: "project"
project_path: "{cwd}"
```
If not found:
```
mcp__code-forge__get_skill
name: "<name>"
scope: "global"
```
**Step 2: Display Skill**
```
═══════════════════════════════════════
📖 SKILL: <name>
═══════════════════════════════════════
Scope: project
Source: template
Category: rails
Tags: ruby, rails, backend
─────────────────────────────────────
<skill content here>
═══════════════════════════════════════
```
---
## DELETE SKILL
When user runs `/claude-os-skills delete <name>`:
**Step 1: Confirm with User**
```
⚠️ Are you sure you want to delete the '<name>' skill?
This will remove it from {cwd}/.claude/skills/<name>
Type 'yes' to confirm, or anything else to cancel.
```
**Step 2: Delete if Confirmed**
```
mcp__code-forge__delete_skill
name: "<name>"
project_path: "{cwd}"
```
**Step 3: Confirm**
```
✓ Deleted skill '<name>' from project
```
**Note:** Cannot delete global skills (memory, remember-this). These are core to Claude OS.
---
## SKILL LOCATIONS
- **Global Skills**: `~/.claude/skills/`
- Available in ALL projects
- Core skills: memory, remember-this
- **Project Skills**: `{project}/.claude/skills/`
- Available only in that project
- Installed from templates or custom created
- **Templates**: `claude-os/templates/skill-library/`
- Categorized templates to install
---
## TIPS
1. **Start with templates** - Install what you need per-project
2. **Keep global minimal** - Only core skills should be global
3. **Customize templates** - After installing, edit for your needs
4. **Create custom skills** - Document project-specific patterns
---
## EXAMPLES
```
/claude-os-skills
→ Lists all global and project skills
/claude-os-skills templates
→ Shows available templates by category
/claude-os-skills install rails-backend
→ Installs Rails patterns to current project
/claude-os-skills create
→ Interactive custom skill creation
/claude-os-skills view rails-backend
→ Shows full skill content
/claude-os-skills delete old-skill
→ Removes skill from project (after confirmation)
```

View file

@ -0,0 +1,7 @@
{
"name": "initialize-project",
"description": "Analyze a project's codebase and documentation to generate coding standards, architecture docs, and development practices. Perfect for new project onboarding.",
"category": "general",
"tags": ["onboarding", "analysis", "documentation", "setup"],
"version": "1.0.0"
}

View file

@ -0,0 +1,163 @@
---
name: analyze-project
description: "Analyze a project's codebase and documentation to generate coding standards, architecture docs, and development practices. Perfect for new project onboarding. Usage: 'analyze-project: /path/to/project' or 'analyze: /path/to/project'"
allowed-tools: Read, Write, Bash, Glob, Grep
---
# Project Analysis Skill
## Purpose
Automatically analyze any project's codebase, specifications, and coding patterns to generate comprehensive documentation for the project_profile MCP. This enables AI assistants to understand:
- **Coding Standards**: Conventions, naming patterns, style guides
- **Architecture**: Design principles, structural patterns, module organization
- **Development Practices**: Workflows, testing approaches, deployment patterns
## How to Use
Simply say one of:
- **"analyze-project: 4"** - Analyze project with database ID 4
- **"analyze: 4"**
- **"project analysis: 4"**
**How it works:**
1. Fetches project from Claude OS using the database ID
2. Registers all 4 MCPs with Claude Code (if not already registered)
3. Analyzes the project codebase
4. Generates 3 documentation files (saved locally)
5. Creates a concise project summary
6. **Displays summary for you to save to Claude's native memory**
7. MCPs are registered but NOT loaded (saves context tokens)
**Example Workflow:**
```
You: "analyze-project: 1"
→ Loads project from Claude OS (ID #1)
→ Registers 4 MCPs (myapp-project-profile, etc.)
→ Analyzes codebase
→ Generates 3 docs locally
→ Displays summary:
PROJECT: MyApp (ID: 1)
TYPE: Rails
... (save this to native memory)
→ MCPs ready to load on-demand when you need them
```
**Then:**
```
You (saving to memory): "Remember: [paste the summary above]"
When working on the project: "Load myapp-project-profile"
→ Loads that specific MCP into context only when needed
```
## What Happens
When you invoke this skill:
1. **Scan the project** for source files, specs, and documentation
2. **Analyze code patterns** - naming conventions, structure, style
3. **Review documentation** - README, spec files, guides
4. **Generate 3 documents**:
- `CODING_STANDARDS.md` - Style, conventions, patterns
- `ARCHITECTURE.md` - Design, structure, principles
- `DEVELOPMENT_PRACTICES.md` - Workflows, testing, deployment
5. **Save locally** to `.claude-os/project-profile/` for reference
6. **Ingest to MCP** (if project_id provided) - Adds docs to the project_profile knowledge base in Claude OS
## Document Structure
### CODING_STANDARDS.md
- Naming conventions (variables, functions, classes)
- Code style and formatting
- File organization
- Import/require patterns
- Comment and documentation style
- Linting/formatting rules
### ARCHITECTURE.md
- High-level design patterns
- Module organization
- Data flow and dependencies
- Database schema (if applicable)
- Key architectural decisions
- Technology stack
### DEVELOPMENT_PRACTICES.md
- Development workflow
- Testing strategy and patterns
- Git workflow (branching, commits)
- Code review process
- Deployment and release process
- Build and deployment pipelines
- Common debugging approaches
## Examples
### Example 1: Analyze a Rails Project
```
You: "analyze-project: /Users/me/Projects/my-rails-app"
→ I scan Rails structure (app/, config/, spec/, etc.)
→ Analyze Models, Controllers, Services, Views
→ Review gems and dependencies
→ Generate 3 docs with Rails-specific patterns
→ Save to mcp/kb/my-rails-app-project-profile/
→ Confirm: "✓ Project analysis complete. 3 docs saved to project_profile KB"
```
### Example 2: Analyze Specific Aspects
```
You: "analyze-project: /Users/me/Projects/api-service architecture"
→ Focus on architecture analysis
→ Generate ARCHITECTURE.md
→ Skip coding standards and practices (or generate minimal versions)
```
### Example 3: Re-analyze Project
```
You: "analyze-project: /Users/me/Projects/my-project"
→ Existing docs are replaced with fresh analysis
→ Useful after major refactoring or architecture changes
```
## Key Benefits
✅ **Consistency** - Apply same analysis to any project
✅ **Speed** - Generate docs in seconds instead of hours
✅ **Comprehensive** - Captures coding style, architecture, and practices
✅ **Automatic Integration** - Syncs directly to project_profile MCP
✅ **Reusable** - Same skill works for Rails, Python, Node, Go, etc.
✅ **AI-Friendly** - Docs enable Claude to write better code for your project
## Technical Details
**Output Location**: `mcp/kb/{project-name}-project-profile/`
**Document Names**:
- `CODING_STANDARDS.md`
- `ARCHITECTURE.md`
- `DEVELOPMENT_PRACTICES.md`
**Integration**: Automatically available in Claude OS's project_profile MCP
**Storage**: SQLite via project_profile knowledge base
**Sync**: Uses Claude OS file watcher for auto-updates
## Supported Project Types
- **Rails** - Ruby on Rails applications
- **Python** - Django, FastAPI, Flask projects
- **Node.js** - Express, NestJS, Next.js applications
- **Java** - Spring Boot, Maven/Gradle projects
- **Go** - Standard Go project structure
- **React** - React applications
- **Generic** - Any project with source code and documentation
---
**Pro Tip**: Run this skill immediately after creating a new project in Agent OS. The generated docs become the foundation for AI-assisted development, ensuring all code generation aligns with your project's patterns and practices.

View file

@ -0,0 +1,7 @@
{
"name": "rails-backend",
"description": "Rails backend patterns including service objects, ActiveRecord best practices, migrations, API endpoints, and background jobs.",
"category": "rails",
"tags": ["ruby", "rails", "backend", "api", "activerecord", "service-objects"],
"version": "1.0.0"
}

View file

@ -0,0 +1,148 @@
# Rails Backend Patterns
## Purpose
Provides standard Rails backend patterns including:
- Service objects for business logic
- ActiveRecord best practices
- Migration conventions
- API endpoint patterns
- Background job patterns (Sidekiq)
## Service Object Pattern
All complex business logic should be extracted into service objects:
```ruby
# app/services/user_registration_service.rb
class UserRegistrationService
def initialize(params)
@params = params
end
def call
return failure("Email already exists") if User.exists?(email: @params[:email])
user = User.create!(@params)
send_welcome_email(user)
success(user)
rescue ActiveRecord::RecordInvalid => e
failure(e.message)
end
private
def send_welcome_email(user)
UserMailer.welcome(user).deliver_later
end
def success(user)
OpenStruct.new(success?: true, user: user)
end
def failure(error)
OpenStruct.new(success?: false, error: error)
end
end
```
## ActiveRecord Best Practices
### Query Optimization
```ruby
# Always use includes to prevent N+1 queries
User.includes(:posts, :comments).where(active: true)
# Use find_each for large datasets
User.find_each(batch_size: 1000) do |user|
process_user(user)
end
# Use select to limit columns
User.select(:id, :name, :email).where(active: true)
```
### Scopes
```ruby
class User < ApplicationRecord
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
scope :with_posts, -> { joins(:posts).distinct }
end
```
## Migration Conventions
```ruby
class AddIndexToUsersEmail < ActiveRecord::Migration[7.0]
disable_ddl_transaction! # For concurrent indexes
def change
add_index :users, :email,
unique: true,
algorithm: :concurrently,
if_not_exists: true
end
end
```
## API Controller Pattern
```ruby
class Api::V1::UsersController < ApplicationController
before_action :authenticate_user!
before_action :set_user, only: [:show, :update, :destroy]
def index
@users = User.active.page(params[:page])
render json: UserSerializer.new(@users).serializable_hash
end
def create
result = UserRegistrationService.new(user_params).call
if result.success?
render json: UserSerializer.new(result.user).serializable_hash,
status: :created
else
render json: { error: result.error }, status: :unprocessable_entity
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, :password)
end
end
```
## Background Jobs
```ruby
class ProcessUserReportJob < ApplicationJob
queue_as :default
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
discard_on ActiveJob::DeserializationError
def perform(user_id)
user = User.find(user_id)
ReportService.new(user).generate
end
end
```
## Key Principles
1. **Fat models, skinny controllers** - Keep controllers thin
2. **Service objects** - Extract complex logic
3. **Query objects** - For complex database queries
4. **Decorators** - For view-specific logic (use Draper)
5. **Form objects** - For complex form handling
6. **Policy objects** - For authorization (use Pundit)

View file

@ -0,0 +1,7 @@
{
"name": "react-patterns",
"description": "Modern React patterns including hooks, TypeScript, state management, forms, error boundaries, and performance optimization.",
"category": "react",
"tags": ["react", "typescript", "hooks", "frontend", "components", "state-management"],
"version": "1.0.0"
}

View file

@ -0,0 +1,262 @@
# React Patterns and Best Practices
## Purpose
Provides modern React patterns including:
- Component organization
- Hooks best practices
- State management patterns
- TypeScript integration
- Performance optimization
## Component Structure
```typescript
// src/components/UserProfile/UserProfile.tsx
import { FC, memo } from 'react';
import { useUserProfile } from './useUserProfile';
import { UserProfileProps } from './types';
import styles from './UserProfile.module.css';
export const UserProfile: FC<UserProfileProps> = memo(({ userId }) => {
const { user, isLoading, error } = useUserProfile(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
return (
<div className={styles.container}>
<Avatar src={user.avatar} alt={user.name} />
<h2 className={styles.name}>{user.name}</h2>
<p className={styles.email}>{user.email}</p>
</div>
);
});
UserProfile.displayName = 'UserProfile';
```
## Custom Hooks
```typescript
// src/hooks/useAsync.ts
import { useState, useCallback } from 'react';
interface AsyncState<T> {
data: T | null;
isLoading: boolean;
error: Error | null;
}
export function useAsync<T>() {
const [state, setState] = useState<AsyncState<T>>({
data: null,
isLoading: false,
error: null,
});
const execute = useCallback(async (promise: Promise<T>) => {
setState({ data: null, isLoading: true, error: null });
try {
const data = await promise;
setState({ data, isLoading: false, error: null });
return data;
} catch (error) {
setState({ data: null, isLoading: false, error: error as Error });
throw error;
}
}, []);
return { ...state, execute };
}
```
## API Hook Pattern
```typescript
// src/hooks/useApi.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../api';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: api.getUsers,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
```
## Context Pattern
```typescript
// src/context/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (credentials: Credentials) => {
const user = await authApi.login(credentials);
setUser(user);
};
const logout = () => {
setUser(null);
authApi.logout();
};
return (
<AuthContext.Provider value={{
user,
login,
logout,
isAuthenticated: !!user
}}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
```
## Form Pattern (React Hook Form)
```typescript
// src/components/UserForm/UserForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
});
type FormData = z.infer<typeof schema>;
export function UserForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <span>{errors.name.message}</span>}
<input {...register('email')} placeholder="Email" />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Saving...' : 'Save'}
</button>
</form>
);
}
```
## Error Boundary
```typescript
// src/components/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong</div>;
}
return this.props.children;
}
}
```
## Performance Patterns
```typescript
// Memoization
const ExpensiveComponent = memo(({ data }) => {
// Only re-renders when data changes
return <div>{/* ... */}</div>;
});
// useMemo for expensive calculations
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// useCallback for stable function references
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []);
// Lazy loading
const HeavyComponent = lazy(() => import('./HeavyComponent'));
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
```
## Key Principles
1. **Composition over inheritance** - Compose components
2. **Single responsibility** - One component, one job
3. **Lift state up** - Share state at common ancestor
4. **Custom hooks** - Extract reusable logic
5. **TypeScript first** - Type everything
6. **Memoize wisely** - Don't over-optimize
7. **Error boundaries** - Graceful error handling

View file

@ -0,0 +1,7 @@
{
"name": "rspec",
"description": "RSpec testing patterns for Ruby/Rails including unit tests, request specs, factories, shared examples, and best practices.",
"category": "testing",
"tags": ["ruby", "rails", "rspec", "testing", "tdd", "factory-bot"],
"version": "1.0.0"
}

View file

@ -0,0 +1,221 @@
# RSpec Testing Patterns
## Purpose
Provides comprehensive RSpec testing patterns for Ruby/Rails applications:
- Unit testing conventions
- Integration testing patterns
- Factory Bot best practices
- Mocking and stubbing
- Test organization
## Test Structure
```ruby
# spec/services/user_registration_service_spec.rb
RSpec.describe UserRegistrationService do
describe '#call' do
subject(:service) { described_class.new(params) }
context 'with valid params' do
let(:params) { { name: 'Test', email: 'test@example.com', password: 'password123' } }
it 'creates a user' do
expect { service.call }.to change(User, :count).by(1)
end
it 'returns success' do
result = service.call
expect(result).to be_success
expect(result.user).to be_persisted
end
it 'sends welcome email' do
expect { service.call }
.to have_enqueued_mail(UserMailer, :welcome)
end
end
context 'with existing email' do
let(:params) { { name: 'Test', email: existing_user.email, password: 'password123' } }
let(:existing_user) { create(:user) }
it 'does not create a user' do
expect { service.call }.not_to change(User, :count)
end
it 'returns failure' do
result = service.call
expect(result).not_to be_success
expect(result.error).to include('Email already exists')
end
end
end
end
```
## Factory Bot Patterns
```ruby
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
name { Faker::Name.name }
password { 'password123' }
trait :admin do
role { :admin }
end
trait :with_posts do
transient do
posts_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
end
# Usage:
create(:user) # Basic user
create(:user, :admin) # Admin user
create(:user, :with_posts) # User with 3 posts
create(:user, :with_posts, posts_count: 5) # User with 5 posts
```
## Request Specs (API Testing)
```ruby
# spec/requests/api/v1/users_spec.rb
RSpec.describe 'Api::V1::Users', type: :request do
describe 'GET /api/v1/users' do
let!(:users) { create_list(:user, 3) }
before { get '/api/v1/users', headers: auth_headers }
it 'returns users' do
expect(response).to have_http_status(:ok)
expect(json_response['data'].size).to eq(3)
end
end
describe 'POST /api/v1/users' do
let(:valid_params) do
{
user: {
name: 'New User',
email: 'new@example.com',
password: 'password123'
}
}
end
context 'with valid params' do
it 'creates user' do
expect {
post '/api/v1/users', params: valid_params, headers: auth_headers
}.to change(User, :count).by(1)
expect(response).to have_http_status(:created)
end
end
end
end
```
## Model Specs
```ruby
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
describe 'validations' do
subject { build(:user) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
it { is_expected.to validate_presence_of(:name) }
end
describe 'associations' do
it { is_expected.to have_many(:posts).dependent(:destroy) }
it { is_expected.to have_many(:comments) }
it { is_expected.to belong_to(:organization).optional }
end
describe 'scopes' do
describe '.active' do
let!(:active_user) { create(:user, active: true) }
let!(:inactive_user) { create(:user, active: false) }
it 'returns only active users' do
expect(User.active).to contain_exactly(active_user)
end
end
end
describe '#full_name' do
let(:user) { build(:user, first_name: 'John', last_name: 'Doe') }
it 'returns combined name' do
expect(user.full_name).to eq('John Doe')
end
end
end
```
## Shared Examples
```ruby
# spec/support/shared_examples/authenticatable.rb
RSpec.shared_examples 'authenticatable' do
context 'without authentication' do
let(:auth_headers) { {} }
it 'returns unauthorized' do
subject
expect(response).to have_http_status(:unauthorized)
end
end
end
# Usage in specs:
describe 'GET /api/v1/users' do
subject { get '/api/v1/users', headers: auth_headers }
it_behaves_like 'authenticatable'
end
```
## Test Helpers
```ruby
# spec/support/request_helpers.rb
module RequestHelpers
def json_response
JSON.parse(response.body)
end
def auth_headers
user = create(:user)
{ 'Authorization' => "Bearer #{user.generate_token}" }
end
end
RSpec.configure do |config|
config.include RequestHelpers, type: :request
end
```
## Key Principles
1. **One assertion per test** - Keep tests focused
2. **Use `let` and `let!`** - Lazy vs eager loading
3. **Use `subject`** - Name what you're testing
4. **Use `described_class`** - Reference class being tested
5. **Use traits** - Compose variations cleanly
6. **Avoid database when possible** - Use `build` over `create`
7. **Use `aggregate_failures`** - Group related assertions

View file

@ -0,0 +1,260 @@
"""
Tests for session parser and insight extractor.
"""
import json
import pytest
from pathlib import Path
from app.core.session_parser import (
SessionParser,
get_project_sessions_dir,
list_session_files,
Message,
ToolCall,
FileChange,
SessionData
)
@pytest.fixture
def sample_session_file(tmp_path):
"""Create a sample session file for testing."""
session_file = tmp_path / "test-session.jsonl"
entries = [
{
"type": "summary",
"summary": "Test session summary",
"leafUuid": "abc123"
},
{
"type": "user",
"uuid": "msg-001",
"parentUuid": None,
"timestamp": "2025-12-11T10:00:00Z",
"cwd": "/Users/test/Projects/myapp",
"gitBranch": "main",
"sessionId": "test-session",
"message": {
"role": "user",
"content": "Hello, can you help me with testing?"
}
},
{
"type": "assistant",
"uuid": "msg-002",
"parentUuid": "msg-001",
"timestamp": "2025-12-11T10:00:05Z",
"message": {
"role": "assistant",
"content": [
{"type": "text", "text": "Of course! I can help you with testing."}
]
}
},
{
"type": "user",
"uuid": "msg-003",
"parentUuid": "msg-002",
"timestamp": "2025-12-11T10:00:10Z",
"message": {
"role": "user",
"content": "Can you write a test for me?"
}
},
{
"type": "assistant",
"uuid": "msg-004",
"parentUuid": "msg-003",
"timestamp": "2025-12-11T10:00:15Z",
"message": {
"role": "assistant",
"content": [
{"type": "text", "text": "I'll write a test for you."},
{
"type": "tool_use",
"id": "tool-001",
"name": "Write",
"input": {
"file_path": "/Users/test/Projects/myapp/test.py",
"content": "def test_example():\n pass"
}
}
]
}
},
{
"type": "file-history-snapshot",
"messageId": "msg-004",
"snapshot": {
"messageId": "msg-004",
"timestamp": "2025-12-11T10:00:20Z",
"trackedFileBackups": {
"/Users/test/Projects/myapp/test.py": {"content": "def test_example():\n pass"}
}
}
}
]
with open(session_file, 'w') as f:
for entry in entries:
f.write(json.dumps(entry) + '\n')
return session_file
def test_session_parser_init(sample_session_file):
"""Test SessionParser initialization."""
parser = SessionParser(str(sample_session_file))
assert parser.path == sample_session_file
assert parser.session_id == "test-session"
def test_session_parser_parse(sample_session_file):
"""Test parsing a session file."""
parser = SessionParser(str(sample_session_file))
session_data = parser.parse()
# Check basic metadata
assert session_data.session_id == "test-session"
assert session_data.cwd == "/Users/test/Projects/myapp"
assert session_data.git_branch == "main"
assert session_data.total_entries == 6
# Check messages (2 user + 2 assistant = 4 messages)
assert len(session_data.messages) == 4
assert session_data.messages[0].role == "user"
assert "help me with testing" in session_data.messages[0].content
assert session_data.messages[1].role == "assistant"
assert "help you with testing" in session_data.messages[1].content
# Check tool calls
assert len(session_data.tool_calls) == 1
assert session_data.tool_calls[0].tool_name == "Write"
assert session_data.tool_calls[0].input_data["file_path"] == "/Users/test/Projects/myapp/test.py"
# Check file changes
assert len(session_data.file_changes) == 1
assert session_data.file_changes[0].file_path == "/Users/test/Projects/myapp/test.py"
def test_session_parser_get_conversation(sample_session_file):
"""Test getting conversation messages."""
parser = SessionParser(str(sample_session_file))
messages = parser.get_conversation()
assert len(messages) == 4 # 2 user + 2 assistant messages
assert all(isinstance(msg, Message) for msg in messages)
assert messages[0].role == "user"
assert messages[1].role == "assistant"
def test_session_parser_get_summary(sample_session_file):
"""Test generating summary for extraction."""
parser = SessionParser(str(sample_session_file))
summary = parser.get_summary_for_extraction()
assert "test-session" in summary
assert "/Users/test/Projects/myapp" in summary
assert "main" in summary
assert "help me with testing" in summary
assert "Write" in summary
def test_get_project_sessions_dir():
"""Test encoding project path to sessions directory."""
project_path = "/Users/test/Projects/myapp"
sessions_dir = get_project_sessions_dir(project_path)
# Leading hyphen is preserved from the path encoding
expected = Path.home() / ".claude" / "projects" / "-Users-test-Projects-myapp"
assert sessions_dir == expected
def test_list_session_files_empty_dir(tmp_path):
"""Test listing sessions when directory doesn't exist."""
fake_project = str(tmp_path / "nonexistent")
sessions = list_session_files(fake_project)
assert sessions == []
def test_session_parser_invalid_file():
"""Test parser with non-existent file."""
with pytest.raises(FileNotFoundError):
parser = SessionParser("/path/to/nonexistent/file.jsonl")
def test_session_parser_malformed_json(tmp_path):
"""Test parser handles malformed JSON gracefully."""
session_file = tmp_path / "malformed.jsonl"
with open(session_file, 'w') as f:
f.write('{"type": "user", "content": "valid"}\n')
f.write('this is not json\n')
f.write('{"type": "assistant", "content": "also valid"}\n')
parser = SessionParser(str(session_file))
session_data = parser.parse()
# Should parse the valid lines, skip the malformed one
assert session_data.total_entries == 2
def test_session_data_defaults():
"""Test SessionData dataclass defaults."""
data = SessionData(
session_id="test",
session_path="/path/to/session.jsonl"
)
assert data.messages == []
assert data.tool_calls == []
assert data.file_changes == []
assert data.start_time is None
assert data.end_time is None
assert data.total_entries == 0
@pytest.mark.asyncio
async def test_insight_extractor_import():
"""Test that InsightExtractor can be imported."""
from app.core.insight_extractor import InsightExtractor, Insight
# Just verify imports work
assert InsightExtractor is not None
assert Insight is not None
def test_session_parser_truncation(tmp_path):
"""Test that very long sessions are truncated in summary."""
session_file = tmp_path / "long-session.jsonl"
# Create a very long message
long_content = "A" * 2000
entries = [
{
"type": "user",
"uuid": "msg-001",
"parentUuid": None,
"timestamp": "2025-12-11T10:00:00Z",
"cwd": "/test",
"sessionId": "long-session",
"message": {
"role": "user",
"content": long_content
}
}
]
with open(session_file, 'w') as f:
for entry in entries:
f.write(json.dumps(entry) + '\n')
parser = SessionParser(str(session_file))
summary = parser.get_summary_for_extraction(max_tokens=100)
# Summary should be shorter than the original content
# (header adds overhead, so just verify truncation occurred)
assert len(summary) < len(long_content)
assert "[... truncated ...]" in summary