Initial commit: Claude Code Project Tracker
Add comprehensive development intelligence system that tracks: - Development sessions with automatic start/stop - Full conversation history with semantic search - Tool usage and file operation analytics - Think time and engagement analysis - Git activity correlation - Learning pattern recognition - Productivity insights and metrics Features: - FastAPI backend with SQLite database - Modern web dashboard with interactive charts - Claude Code hook integration for automatic tracking - Comprehensive test suite with 100+ tests - Complete API documentation (OpenAPI/Swagger) - Privacy-first design with local data storage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
44ed9936b7
48 changed files with 9707 additions and 0 deletions
162
app/models/activity.py
Normal file
162
app/models/activity.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
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
|
||||
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.metadata:
|
||||
return self.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.metadata:
|
||||
return self.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.metadata:
|
||||
return self.metadata.get("task_type")
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue