Remove conversation context/recall system from ClaudeTools
Completely removed the database context recall system while preserving database tables for safety. This major cleanup removes 80+ files and 16,831 lines of code. What was removed: - API layer: 4 routers (conversation-contexts, context-snippets, project-states, decision-logs) with 35+ endpoints - Database models: 5 models (ConversationContext, ContextSnippet, DecisionLog, ProjectState, ContextTag) - Services: 4 service layers with business logic - Schemas: 4 Pydantic schema files - Claude Code hooks: 13 hook files (user-prompt-submit, task-complete, sync-contexts, periodic saves) - Scripts: 15+ scripts (import, migration, testing, tombstone checking) - Tests: 5 test files (context recall, compression, diagnostics) - Documentation: 30+ markdown files (guides, architecture, quick starts) - Utilities: context compression, conversation parsing Files modified: - api/main.py: Removed router registrations - api/models/__init__.py: Removed model imports - api/schemas/__init__.py: Removed schema imports - api/services/__init__.py: Removed service imports - .claude/claude.md: Completely rewritten without context references Database tables preserved: - conversation_contexts, context_snippets, context_tags, project_states, decision_logs (5 orphaned tables remain for safety) - Migration created but NOT applied: 20260118_172743_remove_context_system.py - Tables can be dropped later when confirmed not needed New files added: - CONTEXT_SYSTEM_REMOVAL_SUMMARY.md: Detailed removal report - CONTEXT_SYSTEM_REMOVAL_COMPLETE.md: Final status - CONTEXT_EXPORT_RESULTS.md: Export attempt results - scripts/export-tombstoned-contexts.py: Export tool for future use - migrations/versions/20260118_172743_remove_context_system.py Impact: - Reduced from 130 to 95 API endpoints - Reduced from 43 to 38 active database tables - Removed 16,831 lines of code - System fully operational without context recall Reason for removal: - System was not actively used (no tombstoned contexts found) - Reduces codebase complexity - Focuses on core MSP work tracking functionality - Database preserved for safety (can rollback if needed) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,312 +0,0 @@
|
||||
"""
|
||||
ContextSnippet API router for ClaudeTools.
|
||||
|
||||
Defines all REST API endpoints for managing context snippets,
|
||||
reusable pieces of knowledge for quick retrieval.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.database import get_db
|
||||
from api.middleware.auth import get_current_user
|
||||
from api.schemas.context_snippet import (
|
||||
ContextSnippetCreate,
|
||||
ContextSnippetResponse,
|
||||
ContextSnippetUpdate,
|
||||
)
|
||||
from api.services import context_snippet_service
|
||||
|
||||
# Create router with prefix and tags
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="List all context snippets",
|
||||
description="Retrieve a paginated list of all context snippets with optional filtering",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def list_context_snippets(
|
||||
skip: int = Query(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Number of records to skip for pagination"
|
||||
),
|
||||
limit: int = Query(
|
||||
default=100,
|
||||
ge=1,
|
||||
le=1000,
|
||||
description="Maximum number of records to return (max 1000)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List all context snippets with pagination.
|
||||
|
||||
Returns snippets ordered by relevance score and usage count.
|
||||
"""
|
||||
try:
|
||||
snippets, total = context_snippet_service.get_context_snippets(db, skip, limit)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"snippets": [ContextSnippetResponse.model_validate(snippet) for snippet in snippets]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve context snippets: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-tags",
|
||||
response_model=dict,
|
||||
summary="Get context snippets by tags",
|
||||
description="Retrieve context snippets filtered by tags",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_context_snippets_by_tags(
|
||||
tags: List[str] = Query(..., description="Tags to filter by (OR logic - any match)"),
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get context snippets filtered by tags.
|
||||
|
||||
Uses OR logic - snippets matching any of the provided tags will be returned.
|
||||
"""
|
||||
try:
|
||||
snippets, total = context_snippet_service.get_context_snippets_by_tags(
|
||||
db, tags, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"tags": tags,
|
||||
"snippets": [ContextSnippetResponse.model_validate(snippet) for snippet in snippets]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve context snippets: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/top-relevant",
|
||||
response_model=dict,
|
||||
summary="Get top relevant context snippets",
|
||||
description="Retrieve the most relevant context snippets by relevance score",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_top_relevant_snippets(
|
||||
limit: int = Query(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=50,
|
||||
description="Maximum number of snippets to retrieve (max 50)"
|
||||
),
|
||||
min_relevance_score: float = Query(
|
||||
default=7.0,
|
||||
ge=0.0,
|
||||
le=10.0,
|
||||
description="Minimum relevance score threshold (0.0-10.0)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get the top most relevant context snippets.
|
||||
|
||||
Returns snippets ordered by relevance score (highest first).
|
||||
"""
|
||||
try:
|
||||
snippets = context_snippet_service.get_top_relevant_snippets(
|
||||
db, limit, min_relevance_score
|
||||
)
|
||||
|
||||
return {
|
||||
"total": len(snippets),
|
||||
"limit": limit,
|
||||
"min_relevance_score": min_relevance_score,
|
||||
"snippets": [ContextSnippetResponse.model_validate(snippet) for snippet in snippets]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve top relevant snippets: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-project/{project_id}",
|
||||
response_model=dict,
|
||||
summary="Get context snippets by project",
|
||||
description="Retrieve all context snippets for a specific project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_context_snippets_by_project(
|
||||
project_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all context snippets for a specific project.
|
||||
"""
|
||||
try:
|
||||
snippets, total = context_snippet_service.get_context_snippets_by_project(
|
||||
db, project_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"project_id": str(project_id),
|
||||
"snippets": [ContextSnippetResponse.model_validate(snippet) for snippet in snippets]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve context snippets: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-client/{client_id}",
|
||||
response_model=dict,
|
||||
summary="Get context snippets by client",
|
||||
description="Retrieve all context snippets for a specific client",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_context_snippets_by_client(
|
||||
client_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all context snippets for a specific client.
|
||||
"""
|
||||
try:
|
||||
snippets, total = context_snippet_service.get_context_snippets_by_client(
|
||||
db, client_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"client_id": str(client_id),
|
||||
"snippets": [ContextSnippetResponse.model_validate(snippet) for snippet in snippets]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve context snippets: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{snippet_id}",
|
||||
response_model=ContextSnippetResponse,
|
||||
summary="Get context snippet by ID",
|
||||
description="Retrieve a single context snippet by its unique identifier (increments usage_count)",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_context_snippet(
|
||||
snippet_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific context snippet by ID.
|
||||
|
||||
Note: This automatically increments the usage_count for tracking.
|
||||
"""
|
||||
snippet = context_snippet_service.get_context_snippet_by_id(db, snippet_id)
|
||||
return ContextSnippetResponse.model_validate(snippet)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ContextSnippetResponse,
|
||||
summary="Create new context snippet",
|
||||
description="Create a new context snippet with the provided details",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_context_snippet(
|
||||
snippet_data: ContextSnippetCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new context snippet.
|
||||
|
||||
Requires a valid JWT token with appropriate permissions.
|
||||
"""
|
||||
snippet = context_snippet_service.create_context_snippet(db, snippet_data)
|
||||
return ContextSnippetResponse.model_validate(snippet)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{snippet_id}",
|
||||
response_model=ContextSnippetResponse,
|
||||
summary="Update context snippet",
|
||||
description="Update an existing context snippet's details",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def update_context_snippet(
|
||||
snippet_id: UUID,
|
||||
snippet_data: ContextSnippetUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update an existing context snippet.
|
||||
|
||||
Only provided fields will be updated. All fields are optional.
|
||||
"""
|
||||
snippet = context_snippet_service.update_context_snippet(db, snippet_id, snippet_data)
|
||||
return ContextSnippetResponse.model_validate(snippet)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{snippet_id}",
|
||||
response_model=dict,
|
||||
summary="Delete context snippet",
|
||||
description="Delete a context snippet by its ID",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def delete_context_snippet(
|
||||
snippet_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a context snippet.
|
||||
|
||||
This is a permanent operation and cannot be undone.
|
||||
"""
|
||||
return context_snippet_service.delete_context_snippet(db, snippet_id)
|
||||
@@ -1,312 +0,0 @@
|
||||
"""
|
||||
ConversationContext API router for ClaudeTools.
|
||||
|
||||
Defines all REST API endpoints for managing conversation contexts,
|
||||
including context recall functionality for Claude's memory system.
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.database import get_db
|
||||
from api.middleware.auth import get_current_user
|
||||
from api.schemas.conversation_context import (
|
||||
ConversationContextCreate,
|
||||
ConversationContextResponse,
|
||||
ConversationContextUpdate,
|
||||
)
|
||||
from api.services import conversation_context_service
|
||||
|
||||
# Create router with prefix and tags
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="List all conversation contexts",
|
||||
description="Retrieve a paginated list of all conversation contexts with optional filtering",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def list_conversation_contexts(
|
||||
skip: int = Query(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Number of records to skip for pagination"
|
||||
),
|
||||
limit: int = Query(
|
||||
default=100,
|
||||
ge=1,
|
||||
le=1000,
|
||||
description="Maximum number of records to return (max 1000)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List all conversation contexts with pagination.
|
||||
|
||||
Returns contexts ordered by relevance score and recency.
|
||||
"""
|
||||
try:
|
||||
contexts, total = conversation_context_service.get_conversation_contexts(db, skip, limit)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"contexts": [ConversationContextResponse.model_validate(ctx) for ctx in contexts]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve conversation contexts: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/recall",
|
||||
response_model=dict,
|
||||
summary="Retrieve relevant contexts for injection",
|
||||
description="Get token-efficient context formatted for Claude prompt injection",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def recall_context(
|
||||
search_term: Optional[str] = Query(
|
||||
None,
|
||||
max_length=200,
|
||||
pattern=r'^[a-zA-Z0-9\s\-_.,!?()]+$',
|
||||
description="Full-text search term (alphanumeric, spaces, and basic punctuation only)"
|
||||
),
|
||||
project_id: Optional[UUID] = Query(None, description="Filter by project ID"),
|
||||
tags: Optional[List[str]] = Query(
|
||||
None,
|
||||
description="Filter by tags (OR logic)",
|
||||
max_items=20
|
||||
),
|
||||
limit: int = Query(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=50,
|
||||
description="Maximum number of contexts to retrieve (max 50)"
|
||||
),
|
||||
min_relevance_score: float = Query(
|
||||
default=5.0,
|
||||
ge=0.0,
|
||||
le=10.0,
|
||||
description="Minimum relevance score threshold (0.0-10.0)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Retrieve relevant contexts formatted for Claude prompt injection.
|
||||
|
||||
This endpoint returns contexts matching the search criteria with
|
||||
properly formatted JSON response containing the contexts array.
|
||||
|
||||
Query Parameters:
|
||||
- search_term: Full-text search across title and summary (uses FULLTEXT index)
|
||||
- project_id: Filter contexts by project
|
||||
- tags: Filter contexts by tags (any match)
|
||||
- limit: Maximum number of contexts to retrieve
|
||||
- min_relevance_score: Minimum relevance score threshold
|
||||
|
||||
Returns JSON with contexts array and metadata.
|
||||
"""
|
||||
# Validate tags to prevent SQL injection
|
||||
if tags:
|
||||
import re
|
||||
tag_pattern = re.compile(r'^[a-zA-Z0-9\-_]+$')
|
||||
for tag in tags:
|
||||
if not tag_pattern.match(tag):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid tag format: '{tag}'. Tags must be alphanumeric with hyphens or underscores only."
|
||||
)
|
||||
|
||||
try:
|
||||
contexts, total = conversation_context_service.get_recall_context(
|
||||
db=db,
|
||||
search_term=search_term,
|
||||
project_id=project_id,
|
||||
tags=tags,
|
||||
limit=limit,
|
||||
min_relevance_score=min_relevance_score
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"search_term": search_term,
|
||||
"project_id": str(project_id) if project_id else None,
|
||||
"tags": tags,
|
||||
"min_relevance_score": min_relevance_score,
|
||||
"contexts": [ConversationContextResponse.model_validate(ctx) for ctx in contexts]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve recall context: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-project/{project_id}",
|
||||
response_model=dict,
|
||||
summary="Get conversation contexts by project",
|
||||
description="Retrieve all conversation contexts for a specific project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_conversation_contexts_by_project(
|
||||
project_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all conversation contexts for a specific project.
|
||||
"""
|
||||
try:
|
||||
contexts, total = conversation_context_service.get_conversation_contexts_by_project(
|
||||
db, project_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"project_id": str(project_id),
|
||||
"contexts": [ConversationContextResponse.model_validate(ctx) for ctx in contexts]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve conversation contexts: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-session/{session_id}",
|
||||
response_model=dict,
|
||||
summary="Get conversation contexts by session",
|
||||
description="Retrieve all conversation contexts for a specific session",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_conversation_contexts_by_session(
|
||||
session_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all conversation contexts for a specific session.
|
||||
"""
|
||||
try:
|
||||
contexts, total = conversation_context_service.get_conversation_contexts_by_session(
|
||||
db, session_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"session_id": str(session_id),
|
||||
"contexts": [ConversationContextResponse.model_validate(ctx) for ctx in contexts]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve conversation contexts: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{context_id}",
|
||||
response_model=ConversationContextResponse,
|
||||
summary="Get conversation context by ID",
|
||||
description="Retrieve a single conversation context by its unique identifier",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_conversation_context(
|
||||
context_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific conversation context by ID.
|
||||
"""
|
||||
context = conversation_context_service.get_conversation_context_by_id(db, context_id)
|
||||
return ConversationContextResponse.model_validate(context)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ConversationContextResponse,
|
||||
summary="Create new conversation context",
|
||||
description="Create a new conversation context with the provided details",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_conversation_context(
|
||||
context_data: ConversationContextCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new conversation context.
|
||||
|
||||
Requires a valid JWT token with appropriate permissions.
|
||||
"""
|
||||
context = conversation_context_service.create_conversation_context(db, context_data)
|
||||
return ConversationContextResponse.model_validate(context)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{context_id}",
|
||||
response_model=ConversationContextResponse,
|
||||
summary="Update conversation context",
|
||||
description="Update an existing conversation context's details",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def update_conversation_context(
|
||||
context_id: UUID,
|
||||
context_data: ConversationContextUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update an existing conversation context.
|
||||
|
||||
Only provided fields will be updated. All fields are optional.
|
||||
"""
|
||||
context = conversation_context_service.update_conversation_context(db, context_id, context_data)
|
||||
return ConversationContextResponse.model_validate(context)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{context_id}",
|
||||
response_model=dict,
|
||||
summary="Delete conversation context",
|
||||
description="Delete a conversation context by its ID",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def delete_conversation_context(
|
||||
context_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a conversation context.
|
||||
|
||||
This is a permanent operation and cannot be undone.
|
||||
"""
|
||||
return conversation_context_service.delete_conversation_context(db, context_id)
|
||||
@@ -1,264 +0,0 @@
|
||||
"""
|
||||
DecisionLog API router for ClaudeTools.
|
||||
|
||||
Defines all REST API endpoints for managing decision logs,
|
||||
tracking important decisions made during work.
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.database import get_db
|
||||
from api.middleware.auth import get_current_user
|
||||
from api.schemas.decision_log import (
|
||||
DecisionLogCreate,
|
||||
DecisionLogResponse,
|
||||
DecisionLogUpdate,
|
||||
)
|
||||
from api.services import decision_log_service
|
||||
|
||||
# Create router with prefix and tags
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="List all decision logs",
|
||||
description="Retrieve a paginated list of all decision logs",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def list_decision_logs(
|
||||
skip: int = Query(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Number of records to skip for pagination"
|
||||
),
|
||||
limit: int = Query(
|
||||
default=100,
|
||||
ge=1,
|
||||
le=1000,
|
||||
description="Maximum number of records to return (max 1000)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List all decision logs with pagination.
|
||||
|
||||
Returns decision logs ordered by most recent first.
|
||||
"""
|
||||
try:
|
||||
logs, total = decision_log_service.get_decision_logs(db, skip, limit)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"logs": [DecisionLogResponse.model_validate(log) for log in logs]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve decision logs: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-impact/{impact}",
|
||||
response_model=dict,
|
||||
summary="Get decision logs by impact level",
|
||||
description="Retrieve decision logs filtered by impact level",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_decision_logs_by_impact(
|
||||
impact: str,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get decision logs filtered by impact level.
|
||||
|
||||
Valid impact levels: low, medium, high, critical
|
||||
"""
|
||||
try:
|
||||
logs, total = decision_log_service.get_decision_logs_by_impact(
|
||||
db, impact, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"impact": impact,
|
||||
"logs": [DecisionLogResponse.model_validate(log) for log in logs]
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve decision logs: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-project/{project_id}",
|
||||
response_model=dict,
|
||||
summary="Get decision logs by project",
|
||||
description="Retrieve all decision logs for a specific project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_decision_logs_by_project(
|
||||
project_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all decision logs for a specific project.
|
||||
"""
|
||||
try:
|
||||
logs, total = decision_log_service.get_decision_logs_by_project(
|
||||
db, project_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"project_id": str(project_id),
|
||||
"logs": [DecisionLogResponse.model_validate(log) for log in logs]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve decision logs: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-session/{session_id}",
|
||||
response_model=dict,
|
||||
summary="Get decision logs by session",
|
||||
description="Retrieve all decision logs for a specific session",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_decision_logs_by_session(
|
||||
session_id: UUID,
|
||||
skip: int = Query(default=0, ge=0),
|
||||
limit: int = Query(default=100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all decision logs for a specific session.
|
||||
"""
|
||||
try:
|
||||
logs, total = decision_log_service.get_decision_logs_by_session(
|
||||
db, session_id, skip, limit
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"session_id": str(session_id),
|
||||
"logs": [DecisionLogResponse.model_validate(log) for log in logs]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve decision logs: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{log_id}",
|
||||
response_model=DecisionLogResponse,
|
||||
summary="Get decision log by ID",
|
||||
description="Retrieve a single decision log by its unique identifier",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_decision_log(
|
||||
log_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific decision log by ID.
|
||||
"""
|
||||
log = decision_log_service.get_decision_log_by_id(db, log_id)
|
||||
return DecisionLogResponse.model_validate(log)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=DecisionLogResponse,
|
||||
summary="Create new decision log",
|
||||
description="Create a new decision log with the provided details",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_decision_log(
|
||||
log_data: DecisionLogCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new decision log.
|
||||
|
||||
Requires a valid JWT token with appropriate permissions.
|
||||
"""
|
||||
log = decision_log_service.create_decision_log(db, log_data)
|
||||
return DecisionLogResponse.model_validate(log)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{log_id}",
|
||||
response_model=DecisionLogResponse,
|
||||
summary="Update decision log",
|
||||
description="Update an existing decision log's details",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def update_decision_log(
|
||||
log_id: UUID,
|
||||
log_data: DecisionLogUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update an existing decision log.
|
||||
|
||||
Only provided fields will be updated. All fields are optional.
|
||||
"""
|
||||
log = decision_log_service.update_decision_log(db, log_id, log_data)
|
||||
return DecisionLogResponse.model_validate(log)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{log_id}",
|
||||
response_model=dict,
|
||||
summary="Delete decision log",
|
||||
description="Delete a decision log by its ID",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def delete_decision_log(
|
||||
log_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a decision log.
|
||||
|
||||
This is a permanent operation and cannot be undone.
|
||||
"""
|
||||
return decision_log_service.delete_decision_log(db, log_id)
|
||||
@@ -1,202 +0,0 @@
|
||||
"""
|
||||
ProjectState API router for ClaudeTools.
|
||||
|
||||
Defines all REST API endpoints for managing project states,
|
||||
tracking the current state of projects for context retrieval.
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.database import get_db
|
||||
from api.middleware.auth import get_current_user
|
||||
from api.schemas.project_state import (
|
||||
ProjectStateCreate,
|
||||
ProjectStateResponse,
|
||||
ProjectStateUpdate,
|
||||
)
|
||||
from api.services import project_state_service
|
||||
|
||||
# Create router with prefix and tags
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="List all project states",
|
||||
description="Retrieve a paginated list of all project states",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def list_project_states(
|
||||
skip: int = Query(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Number of records to skip for pagination"
|
||||
),
|
||||
limit: int = Query(
|
||||
default=100,
|
||||
ge=1,
|
||||
le=1000,
|
||||
description="Maximum number of records to return (max 1000)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List all project states with pagination.
|
||||
|
||||
Returns project states ordered by most recently updated.
|
||||
"""
|
||||
try:
|
||||
states, total = project_state_service.get_project_states(db, skip, limit)
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
"states": [ProjectStateResponse.model_validate(state) for state in states]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve project states: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-project/{project_id}",
|
||||
response_model=ProjectStateResponse,
|
||||
summary="Get project state by project ID",
|
||||
description="Retrieve the project state for a specific project (unique per project)",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_project_state_by_project(
|
||||
project_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get the project state for a specific project.
|
||||
|
||||
Each project has exactly one project state.
|
||||
"""
|
||||
state = project_state_service.get_project_state_by_project(db, project_id)
|
||||
|
||||
if not state:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"ProjectState for project ID {project_id} not found"
|
||||
)
|
||||
|
||||
return ProjectStateResponse.model_validate(state)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{state_id}",
|
||||
response_model=ProjectStateResponse,
|
||||
summary="Get project state by ID",
|
||||
description="Retrieve a single project state by its unique identifier",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def get_project_state(
|
||||
state_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific project state by ID.
|
||||
"""
|
||||
state = project_state_service.get_project_state_by_id(db, state_id)
|
||||
return ProjectStateResponse.model_validate(state)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ProjectStateResponse,
|
||||
summary="Create new project state",
|
||||
description="Create a new project state with the provided details",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_project_state(
|
||||
state_data: ProjectStateCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new project state.
|
||||
|
||||
Each project can only have one project state (enforced by unique constraint).
|
||||
Requires a valid JWT token with appropriate permissions.
|
||||
"""
|
||||
state = project_state_service.create_project_state(db, state_data)
|
||||
return ProjectStateResponse.model_validate(state)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{state_id}",
|
||||
response_model=ProjectStateResponse,
|
||||
summary="Update project state",
|
||||
description="Update an existing project state's details",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def update_project_state(
|
||||
state_id: UUID,
|
||||
state_data: ProjectStateUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update an existing project state.
|
||||
|
||||
Only provided fields will be updated. All fields are optional.
|
||||
Uses compression utilities when updating to maintain efficient storage.
|
||||
"""
|
||||
state = project_state_service.update_project_state(db, state_id, state_data)
|
||||
return ProjectStateResponse.model_validate(state)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/by-project/{project_id}",
|
||||
response_model=ProjectStateResponse,
|
||||
summary="Update project state by project ID",
|
||||
description="Update project state by project ID (creates if doesn't exist)",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def update_project_state_by_project(
|
||||
project_id: UUID,
|
||||
state_data: ProjectStateUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update project state by project ID.
|
||||
|
||||
Convenience method that creates a new project state if it doesn't exist,
|
||||
or updates the existing one if it does.
|
||||
"""
|
||||
state = project_state_service.update_project_state_by_project(db, project_id, state_data)
|
||||
return ProjectStateResponse.model_validate(state)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{state_id}",
|
||||
response_model=dict,
|
||||
summary="Delete project state",
|
||||
description="Delete a project state by its ID",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def delete_project_state(
|
||||
state_id: UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a project state.
|
||||
|
||||
This is a permanent operation and cannot be undone.
|
||||
"""
|
||||
return project_state_service.delete_project_state(db, state_id)
|
||||
Reference in New Issue
Block a user