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>
413 lines
17 KiB
Python
413 lines
17 KiB
Python
"""
|
|
Test fixtures and sample data for the Claude Code Project Tracker.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Any
|
|
from faker import Faker
|
|
|
|
fake = Faker()
|
|
|
|
class TestDataFactory:
|
|
"""Factory for creating realistic test data."""
|
|
|
|
@staticmethod
|
|
def create_project_data(**overrides) -> Dict[str, Any]:
|
|
"""Create sample project data."""
|
|
data = {
|
|
"name": fake.company(),
|
|
"path": fake.file_path(depth=3, extension=""),
|
|
"git_repo": fake.url().replace("http://", "https://github.com/"),
|
|
"languages": fake.random_elements(
|
|
elements=["python", "javascript", "typescript", "go", "rust", "java", "cpp"],
|
|
length=fake.random_int(min=1, max=4),
|
|
unique=True
|
|
),
|
|
"created_at": fake.date_time_between(start_date="-1y", end_date="now"),
|
|
"last_session": fake.date_time_between(start_date="-30d", end_date="now"),
|
|
"total_sessions": fake.random_int(min=1, max=50),
|
|
"total_time_minutes": fake.random_int(min=30, max=2000),
|
|
"files_modified_count": fake.random_int(min=5, max=100),
|
|
"lines_changed_count": fake.random_int(min=100, max=5000)
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_session_data(project_id: int = 1, **overrides) -> Dict[str, Any]:
|
|
"""Create sample session data."""
|
|
start_time = fake.date_time_between(start_date="-7d", end_date="now")
|
|
duration = fake.random_int(min=5, max=180) # 5 minutes to 3 hours
|
|
|
|
data = {
|
|
"project_id": project_id,
|
|
"start_time": start_time,
|
|
"end_time": start_time + timedelta(minutes=duration),
|
|
"session_type": fake.random_element(elements=["startup", "resume", "clear"]),
|
|
"working_directory": fake.file_path(depth=3, extension=""),
|
|
"git_branch": fake.random_element(elements=["main", "develop", "feature/new-feature", "bugfix/issue-123"]),
|
|
"environment": {
|
|
"user": fake.user_name(),
|
|
"pwd": fake.file_path(depth=3, extension=""),
|
|
"python_version": "3.11.0",
|
|
"node_version": "18.17.0"
|
|
},
|
|
"duration_minutes": duration,
|
|
"activity_count": fake.random_int(min=3, max=50),
|
|
"conversation_count": fake.random_int(min=2, max=30),
|
|
"files_touched": [fake.file_path() for _ in range(fake.random_int(min=1, max=8))]
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_conversation_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
|
|
"""Create sample conversation data."""
|
|
prompts = [
|
|
"How do I implement user authentication?",
|
|
"Can you help me debug this error?",
|
|
"What's the best way to structure this code?",
|
|
"Help me optimize this function for performance",
|
|
"How do I add tests for this component?",
|
|
"Can you review this code for best practices?",
|
|
"What libraries should I use for this feature?",
|
|
"How do I handle errors in this async function?"
|
|
]
|
|
|
|
responses = [
|
|
"I can help you implement user authentication. Here's a comprehensive approach...",
|
|
"Let me analyze this error and provide a solution...",
|
|
"For code structure, I recommend following these patterns...",
|
|
"Here are several optimization strategies for your function...",
|
|
"Let's add comprehensive tests for this component...",
|
|
"I've reviewed your code and here are my recommendations...",
|
|
"For this feature, I suggest these well-maintained libraries...",
|
|
"Here's how to properly handle errors in async functions..."
|
|
]
|
|
|
|
data = {
|
|
"session_id": session_id,
|
|
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
|
|
"user_prompt": fake.random_element(elements=prompts),
|
|
"claude_response": fake.random_element(elements=responses),
|
|
"tools_used": fake.random_elements(
|
|
elements=["Edit", "Write", "Read", "Bash", "Grep", "Glob", "Task"],
|
|
length=fake.random_int(min=1, max=4),
|
|
unique=True
|
|
),
|
|
"files_affected": [
|
|
fake.file_path(extension=ext)
|
|
for ext in fake.random_elements(
|
|
elements=["py", "js", "ts", "go", "rs", "java", "cpp", "md"],
|
|
length=fake.random_int(min=0, max=3),
|
|
unique=True
|
|
)
|
|
],
|
|
"context": {
|
|
"intent": fake.random_element(elements=["debugging", "implementation", "learning", "optimization"]),
|
|
"complexity": fake.random_element(elements=["low", "medium", "high"])
|
|
},
|
|
"tokens_input": fake.random_int(min=50, max=500),
|
|
"tokens_output": fake.random_int(min=100, max=1000),
|
|
"exchange_type": fake.random_element(elements=["user_prompt", "claude_response"])
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_activity_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
|
|
"""Create sample activity data."""
|
|
tools = {
|
|
"Edit": {
|
|
"action": "file_edit",
|
|
"metadata": {"lines_changed": fake.random_int(min=1, max=50)},
|
|
"lines_added": fake.random_int(min=0, max=30),
|
|
"lines_removed": fake.random_int(min=0, max=20)
|
|
},
|
|
"Write": {
|
|
"action": "file_write",
|
|
"metadata": {"new_file": fake.boolean()},
|
|
"lines_added": fake.random_int(min=10, max=100),
|
|
"lines_removed": 0
|
|
},
|
|
"Read": {
|
|
"action": "file_read",
|
|
"metadata": {"file_size": fake.random_int(min=100, max=5000)},
|
|
"lines_added": 0,
|
|
"lines_removed": 0
|
|
},
|
|
"Bash": {
|
|
"action": "command_execution",
|
|
"metadata": {
|
|
"command": fake.random_element(elements=[
|
|
"npm install",
|
|
"pytest",
|
|
"git status",
|
|
"python main.py",
|
|
"docker build ."
|
|
]),
|
|
"exit_code": 0
|
|
},
|
|
"lines_added": 0,
|
|
"lines_removed": 0
|
|
},
|
|
"Grep": {
|
|
"action": "search",
|
|
"metadata": {
|
|
"pattern": fake.word(),
|
|
"matches_found": fake.random_int(min=0, max=20)
|
|
},
|
|
"lines_added": 0,
|
|
"lines_removed": 0
|
|
}
|
|
}
|
|
|
|
tool_name = fake.random_element(elements=list(tools.keys()))
|
|
tool_config = tools[tool_name]
|
|
|
|
data = {
|
|
"session_id": session_id,
|
|
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
|
|
"tool_name": tool_name,
|
|
"action": tool_config["action"],
|
|
"file_path": fake.file_path() if tool_name in ["Edit", "Write", "Read"] else None,
|
|
"metadata": tool_config["metadata"],
|
|
"success": fake.boolean(chance_of_getting_true=90),
|
|
"error_message": None if fake.boolean(chance_of_getting_true=90) else "Operation failed",
|
|
"lines_added": tool_config.get("lines_added", 0),
|
|
"lines_removed": tool_config.get("lines_removed", 0)
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_waiting_period_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
|
|
"""Create sample waiting period data."""
|
|
start_time = fake.date_time_between(start_date="-7d", end_date="now")
|
|
duration = fake.random_int(min=5, max=300) # 5 seconds to 5 minutes
|
|
|
|
activities = {
|
|
"thinking": "User is contemplating the response",
|
|
"research": "User is looking up documentation",
|
|
"external_work": "User is working in another application",
|
|
"break": "User stepped away from the computer"
|
|
}
|
|
|
|
likely_activity = fake.random_element(elements=list(activities.keys()))
|
|
|
|
data = {
|
|
"session_id": session_id,
|
|
"start_time": start_time,
|
|
"end_time": start_time + timedelta(seconds=duration),
|
|
"duration_seconds": duration,
|
|
"context_before": "Claude finished providing a detailed explanation",
|
|
"context_after": fake.random_element(elements=[
|
|
"User asked a follow-up question",
|
|
"User requested clarification",
|
|
"User provided additional context",
|
|
"User asked about a different topic"
|
|
]),
|
|
"likely_activity": likely_activity
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_git_operation_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
|
|
"""Create sample git operation data."""
|
|
operations = {
|
|
"commit": {
|
|
"command": "git commit -m 'Add new feature'",
|
|
"result": "[main abc123] Add new feature\\n 2 files changed, 15 insertions(+), 3 deletions(-)",
|
|
"files_changed": ["main.py", "utils.py"],
|
|
"lines_added": 15,
|
|
"lines_removed": 3,
|
|
"commit_hash": fake.sha1()[:8]
|
|
},
|
|
"push": {
|
|
"command": "git push origin main",
|
|
"result": "To https://github.com/user/repo.git\\n abc123..def456 main -> main",
|
|
"files_changed": [],
|
|
"lines_added": 0,
|
|
"lines_removed": 0,
|
|
"commit_hash": None
|
|
},
|
|
"pull": {
|
|
"command": "git pull origin main",
|
|
"result": "Already up to date.",
|
|
"files_changed": [],
|
|
"lines_added": 0,
|
|
"lines_removed": 0,
|
|
"commit_hash": None
|
|
},
|
|
"branch": {
|
|
"command": "git checkout -b feature/new-feature",
|
|
"result": "Switched to a new branch 'feature/new-feature'",
|
|
"files_changed": [],
|
|
"lines_added": 0,
|
|
"lines_removed": 0,
|
|
"commit_hash": None
|
|
}
|
|
}
|
|
|
|
operation = fake.random_element(elements=list(operations.keys()))
|
|
op_config = operations[operation]
|
|
|
|
data = {
|
|
"session_id": session_id,
|
|
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
|
|
"operation": operation,
|
|
"command": op_config["command"],
|
|
"result": op_config["result"],
|
|
"success": fake.boolean(chance_of_getting_true=95),
|
|
"files_changed": op_config["files_changed"],
|
|
"lines_added": op_config["lines_added"],
|
|
"lines_removed": op_config["lines_removed"],
|
|
"commit_hash": op_config["commit_hash"],
|
|
"branch_from": fake.random_element(elements=["main", "develop", "feature/old-feature"]) if operation == "merge" else None,
|
|
"branch_to": fake.random_element(elements=["main", "develop"]) if operation == "merge" else None
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
class SampleDataSets:
|
|
"""Pre-defined sample data sets for different test scenarios."""
|
|
|
|
@staticmethod
|
|
def productive_session() -> Dict[str, List[Dict[str, Any]]]:
|
|
"""Data for a highly productive development session."""
|
|
project_data = TestDataFactory.create_project_data(
|
|
name="E-commerce Platform",
|
|
languages=["python", "javascript", "typescript"],
|
|
total_sessions=25,
|
|
total_time_minutes=800
|
|
)
|
|
|
|
session_data = TestDataFactory.create_session_data(
|
|
session_type="startup",
|
|
duration_minutes=120,
|
|
activity_count=45,
|
|
conversation_count=12
|
|
)
|
|
|
|
conversations = [
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="How do I implement user authentication with JWT?",
|
|
tools_used=["Edit", "Write"],
|
|
files_affected=["auth.py", "models.py"]
|
|
),
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="Can you help me optimize this database query?",
|
|
tools_used=["Edit", "Read"],
|
|
files_affected=["queries.py"]
|
|
),
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="What's the best way to handle async errors?",
|
|
tools_used=["Edit"],
|
|
files_affected=["error_handler.py"]
|
|
)
|
|
]
|
|
|
|
activities = [
|
|
TestDataFactory.create_activity_data(tool_name="Edit", lines_added=25, lines_removed=5),
|
|
TestDataFactory.create_activity_data(tool_name="Write", lines_added=50, lines_removed=0),
|
|
TestDataFactory.create_activity_data(tool_name="Bash", metadata={"command": "pytest"}),
|
|
TestDataFactory.create_activity_data(tool_name="Read", file_path="/docs/api.md")
|
|
]
|
|
|
|
return {
|
|
"project": project_data,
|
|
"session": session_data,
|
|
"conversations": conversations,
|
|
"activities": activities
|
|
}
|
|
|
|
@staticmethod
|
|
def debugging_session() -> Dict[str, List[Dict[str, Any]]]:
|
|
"""Data for a debugging-focused session with lots of investigation."""
|
|
project_data = TestDataFactory.create_project_data(
|
|
name="Bug Tracking System",
|
|
languages=["go", "typescript"]
|
|
)
|
|
|
|
session_data = TestDataFactory.create_session_data(
|
|
session_type="resume",
|
|
duration_minutes=90,
|
|
activity_count=35,
|
|
conversation_count=8
|
|
)
|
|
|
|
conversations = [
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="I'm getting a panic in my Go application, can you help debug?",
|
|
tools_used=["Read", "Grep"],
|
|
files_affected=["main.go", "handler.go"],
|
|
context={"intent": "debugging", "complexity": "high"}
|
|
),
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="The tests are failing intermittently, what could be wrong?",
|
|
tools_used=["Read", "Bash"],
|
|
files_affected=["test_handler.go"]
|
|
)
|
|
]
|
|
|
|
# Lots of read operations for debugging
|
|
activities = [
|
|
TestDataFactory.create_activity_data(tool_name="Read") for _ in range(8)
|
|
] + [
|
|
TestDataFactory.create_activity_data(tool_name="Grep", metadata={"pattern": "error", "matches_found": 12}),
|
|
TestDataFactory.create_activity_data(tool_name="Bash", metadata={"command": "go test -v"}),
|
|
TestDataFactory.create_activity_data(tool_name="Edit", lines_added=3, lines_removed=1)
|
|
]
|
|
|
|
# Longer thinking periods during debugging
|
|
waiting_periods = [
|
|
TestDataFactory.create_waiting_period_data(
|
|
duration_seconds=180,
|
|
likely_activity="research",
|
|
context_after="User asked about error patterns"
|
|
),
|
|
TestDataFactory.create_waiting_period_data(
|
|
duration_seconds=120,
|
|
likely_activity="thinking",
|
|
context_after="User provided stack trace"
|
|
)
|
|
]
|
|
|
|
return {
|
|
"project": project_data,
|
|
"session": session_data,
|
|
"conversations": conversations,
|
|
"activities": activities,
|
|
"waiting_periods": waiting_periods
|
|
}
|
|
|
|
@staticmethod
|
|
def learning_session() -> Dict[str, List[Dict[str, Any]]]:
|
|
"""Data for a learning-focused session with lots of questions."""
|
|
project_data = TestDataFactory.create_project_data(
|
|
name="Learning Rust",
|
|
languages=["rust"],
|
|
total_sessions=5,
|
|
total_time_minutes=200
|
|
)
|
|
|
|
conversations = [
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="What's the difference between String and &str in Rust?",
|
|
context={"intent": "learning", "complexity": "medium"}
|
|
),
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="How do I handle ownership and borrowing correctly?",
|
|
context={"intent": "learning", "complexity": "high"}
|
|
),
|
|
TestDataFactory.create_conversation_data(
|
|
user_prompt="Can you explain Rust's trait system?",
|
|
context={"intent": "learning", "complexity": "high"}
|
|
)
|
|
]
|
|
|
|
return {
|
|
"project": project_data,
|
|
"conversations": conversations
|
|
} |