## 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>
162 lines
5.5 KiB
Python
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 |