Ryan Malloy bec1606c86 Add comprehensive documentation system and tool call tracking
## Documentation System
- Create complete documentation hub at /dashboard/docs with:
  - Getting Started guide with quick setup and troubleshooting
  - Hook Setup Guide with platform-specific configurations
  - API Reference with all endpoints and examples
  - FAQ with searchable questions and categories
- Add responsive design with interactive features
- Update navigation in base template

## Tool Call Tracking
- Add ToolCall model for tracking Claude Code tool usage
- Create /api/tool-calls endpoints for recording and analytics
- Add tool_call hook type with auto-session detection
- Include tool calls in project statistics and recalculation
- Track tool names, parameters, execution time, and success rates

## Project Enhancements
- Add project timeline and statistics pages (fix 404 errors)
- Create recalculation script for fixing zero statistics
- Update project stats to include tool call counts
- Enhance session model with tool call relationships

## Infrastructure
- Switch from requirements.txt to pyproject.toml/uv.lock
- Add data import functionality for claude.json files
- Update database connection to include all new models
- Add comprehensive API documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 05:58:27 -06:00

162 lines
5.5 KiB
Python

"""
Activity model for tracking tool usage and file operations.
"""
from datetime import datetime
from typing import Optional, Dict, Any
from sqlalchemy import String, Text, Integer, DateTime, JSON, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from .base import Base, TimestampMixin
class Activity(Base, TimestampMixin):
"""
Represents a single tool usage or file operation during development.
Activities are generated by Claude Code tool usage and provide
detailed insight into the development workflow.
"""
__tablename__ = "activities"
# Primary key
id: Mapped[int] = mapped_column(primary_key=True)
# Foreign keys
session_id: Mapped[int] = mapped_column(ForeignKey("sessions.id"), nullable=False, index=True)
conversation_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("conversations.id"),
nullable=True,
index=True
)
# Activity timing
timestamp: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=func.now(),
index=True
)
# Activity details
tool_name: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
action: Mapped[str] = mapped_column(String(100), nullable=False)
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
# Metadata and results
activity_metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Code change metrics (for Edit/Write operations)
lines_added: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
lines_removed: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# Relationships
session: Mapped["Session"] = relationship("Session", back_populates="activities")
conversation: Mapped[Optional["Conversation"]] = relationship(
"Conversation",
foreign_keys=[conversation_id]
)
def __repr__(self) -> str:
file_info = f", file='{self.file_path}'" if self.file_path else ""
return f"<Activity(id={self.id}, tool='{self.tool_name}', action='{self.action}'{file_info})>"
@property
def is_file_operation(self) -> bool:
"""Check if this activity involves file operations."""
return self.tool_name in {"Edit", "Write", "Read"}
@property
def is_code_execution(self) -> bool:
"""Check if this activity involves code/command execution."""
return self.tool_name in {"Bash", "Task"}
@property
def is_search_operation(self) -> bool:
"""Check if this activity involves searching."""
return self.tool_name in {"Grep", "Glob"}
@property
def total_lines_changed(self) -> int:
"""Get total lines changed (added + removed)."""
added = self.lines_added or 0
removed = self.lines_removed or 0
return added + removed
@property
def net_lines_changed(self) -> int:
"""Get net lines changed (added - removed)."""
added = self.lines_added or 0
removed = self.lines_removed or 0
return added - removed
def get_file_extension(self) -> Optional[str]:
"""Extract file extension from file path."""
if not self.file_path:
return None
if "." in self.file_path:
return self.file_path.split(".")[-1].lower()
return None
def get_programming_language(self) -> Optional[str]:
"""Infer programming language from file extension."""
ext = self.get_file_extension()
if not ext:
return None
language_map = {
"py": "python",
"js": "javascript",
"ts": "typescript",
"jsx": "javascript",
"tsx": "typescript",
"go": "go",
"rs": "rust",
"java": "java",
"cpp": "cpp",
"c": "c",
"h": "c",
"hpp": "cpp",
"rb": "ruby",
"php": "php",
"html": "html",
"css": "css",
"scss": "scss",
"sql": "sql",
"md": "markdown",
"yml": "yaml",
"yaml": "yaml",
"json": "json",
"xml": "xml",
"sh": "shell",
"bash": "shell",
}
return language_map.get(ext)
def is_successful(self) -> bool:
"""Check if the activity completed successfully."""
return self.success and not self.error_message
def get_command_executed(self) -> Optional[str]:
"""Get the command that was executed (for Bash activities)."""
if self.tool_name == "Bash" and self.activity_metadata:
return self.activity_metadata.get("command")
return None
def get_search_pattern(self) -> Optional[str]:
"""Get the search pattern (for Grep activities)."""
if self.tool_name == "Grep" and self.activity_metadata:
return self.activity_metadata.get("pattern")
return None
def get_task_type(self) -> Optional[str]:
"""Get the task type (for Task activities)."""
if self.tool_name == "Task" and self.activity_metadata:
return self.activity_metadata.get("task_type")
return None