[Config] Add coding guidelines and code-fixer agent
Major additions: - Add CODING_GUIDELINES.md with "NO EMOJIS" rule - Create code-fixer agent for automated violation fixes - Add offline mode v2 hooks with local caching/queue - Add periodic context save with invisible Task Scheduler setup - Add agent coordination rules and database connection docs Infrastructure: - Update hooks: task-complete-v2, user-prompt-submit-v2 - Add periodic_save_check.py for auto-save every 5min - Add PowerShell scripts: setup_periodic_save.ps1, update_to_invisible.ps1 - Add sync-contexts script for queue synchronization Documentation: - OFFLINE_MODE.md, PERIODIC_SAVE_INVISIBLE_SETUP.md - Migration procedures and verification docs - Fix flashing window guide Updates: - Update agent configs (backup, code-review, coding, database, gitea, testing) - Update claude.md with coding guidelines reference - Update .gitignore for new cache/queue directories Status: Pre-automated-fixer baseline commit Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
5
.claude/.periodic-save-state.json
Normal file
5
.claude/.periodic-save-state.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"active_seconds": 3240,
|
||||
"last_update": "2026-01-17T19:51:24.350999+00:00",
|
||||
"last_save": null
|
||||
}
|
||||
272
.claude/AGENT_COORDINATION_RULES.md
Normal file
272
.claude/AGENT_COORDINATION_RULES.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Agent Coordination Rules
|
||||
|
||||
**CRITICAL: Main Claude is a COORDINATOR, not an executor**
|
||||
|
||||
---
|
||||
|
||||
## Core Principle
|
||||
|
||||
**Main Claude Instance:**
|
||||
- Coordinates work between user and agents
|
||||
- Makes decisions and plans
|
||||
- Presents concise results to user
|
||||
- **NEVER performs database operations directly**
|
||||
- **NEVER makes direct API calls to ClaudeTools API**
|
||||
|
||||
**Agents:**
|
||||
- Execute specific tasks (database, coding, testing, etc.)
|
||||
- Return concise summaries
|
||||
- Preserve Main Claude's context space
|
||||
|
||||
---
|
||||
|
||||
## Database Operations - ALWAYS Use Database Agent
|
||||
|
||||
### ❌ WRONG (What I Was Doing)
|
||||
|
||||
```bash
|
||||
# Main Claude making direct queries
|
||||
ssh guru@172.16.3.30 "mysql -u claudetools ... SELECT ..."
|
||||
curl http://172.16.3.30:8001/api/conversation-contexts ...
|
||||
```
|
||||
|
||||
### ✅ CORRECT (What Should Happen)
|
||||
|
||||
```
|
||||
Main Claude → Task tool → Database Agent → Returns summary
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```
|
||||
User: "How many contexts are saved?"
|
||||
|
||||
Main Claude: "Let me check the database"
|
||||
↓
|
||||
Launches Database Agent with task: "Count conversation_contexts in database"
|
||||
↓
|
||||
Database Agent: Queries database, returns: "7 contexts found"
|
||||
↓
|
||||
Main Claude to User: "There are 7 contexts saved in the database"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Responsibilities
|
||||
|
||||
### Database Agent (`.claude/agents/database.md`)
|
||||
**ONLY agent authorized for database operations**
|
||||
|
||||
**Handles:**
|
||||
- All SELECT, INSERT, UPDATE, DELETE queries
|
||||
- Context storage and retrieval
|
||||
- Data validation and integrity
|
||||
- Transaction management
|
||||
- Query optimization
|
||||
|
||||
**Returns:** Concise summaries, not raw SQL results
|
||||
|
||||
**When to use:**
|
||||
- Saving contexts to database
|
||||
- Retrieving contexts from database
|
||||
- Checking record counts
|
||||
- Any database operation
|
||||
|
||||
### Coding Agent (`.claude/agents/coding.md`)
|
||||
**Handles code writing and modifications**
|
||||
|
||||
**When to use:**
|
||||
- Writing new code
|
||||
- Modifying existing code
|
||||
- Creating scripts
|
||||
|
||||
### Testing Agent (`.claude/agents/testing.md`)
|
||||
**Handles test execution**
|
||||
|
||||
**When to use:**
|
||||
- Running tests
|
||||
- Executing validation scripts
|
||||
- Performance testing
|
||||
|
||||
### Code Review Agent (`.claude/agents/code-review.md`)
|
||||
**Reviews code quality**
|
||||
|
||||
**When to use:**
|
||||
- After significant code changes
|
||||
- Before committing
|
||||
|
||||
### Gitea Agent (`.claude/agents/gitea.md`)
|
||||
**Handles Git operations**
|
||||
|
||||
**When to use:**
|
||||
- Git commits
|
||||
- Push to remote
|
||||
- Branch management
|
||||
|
||||
### Backup Agent (`.claude/agents/backup.md`)
|
||||
**Manages backups**
|
||||
|
||||
**When to use:**
|
||||
- Creating backups
|
||||
- Restoring data
|
||||
- Backup verification
|
||||
|
||||
---
|
||||
|
||||
## Violation Examples from This Session
|
||||
|
||||
### ❌ Violation 1: Direct Database Queries
|
||||
```bash
|
||||
ssh guru@172.16.3.30 "mysql ... SELECT COUNT(*) FROM conversation_contexts"
|
||||
```
|
||||
**Should have been:** Database Agent task
|
||||
|
||||
### ❌ Violation 2: Direct API Calls
|
||||
```bash
|
||||
curl -X POST http://172.16.3.30:8001/api/conversation-contexts ...
|
||||
```
|
||||
**Should have been:** Database Agent task
|
||||
|
||||
### ❌ Violation 3: Direct Context Creation
|
||||
```bash
|
||||
curl ... -d '{"context_type": "session_summary", ...}'
|
||||
```
|
||||
**Should have been:** Database Agent task
|
||||
|
||||
---
|
||||
|
||||
## Correct Coordination Flow
|
||||
|
||||
### Example: Save Context to Database
|
||||
|
||||
**User Request:** "Save the current context"
|
||||
|
||||
**Main Claude Actions:**
|
||||
1. ✅ Summarize what needs to be saved
|
||||
2. ✅ Launch Database Agent with task:
|
||||
```
|
||||
"Save session context to database:
|
||||
- Title: [summary]
|
||||
- Dense summary: [compressed context]
|
||||
- Tags: [relevant tags]
|
||||
- Score: 8.5"
|
||||
```
|
||||
3. ✅ Receive agent response: "Context saved with ID abc-123"
|
||||
4. ✅ Tell user: "Context saved successfully"
|
||||
|
||||
**What Main Claude Does NOT Do:**
|
||||
- ❌ Make direct curl calls
|
||||
- ❌ Make direct SQL queries
|
||||
- ❌ Return raw database results to user
|
||||
|
||||
---
|
||||
|
||||
## Example: Retrieve Contexts
|
||||
|
||||
**User Request:** "What contexts do we have about offline mode?"
|
||||
|
||||
**Main Claude Actions:**
|
||||
1. ✅ Launch Database Agent with task:
|
||||
```
|
||||
"Search conversation_contexts for entries related to 'offline mode'.
|
||||
Return: titles, scores, and brief summaries of top 5 results"
|
||||
```
|
||||
2. ✅ Receive agent summary:
|
||||
```
|
||||
Found 3 contexts:
|
||||
1. "Offline Mode Implementation" (score 9.5)
|
||||
2. "Offline Mode Testing" (score 8.0)
|
||||
3. "Offline Mode Documentation" (score 7.5)
|
||||
```
|
||||
3. ✅ Present to user in conversational format
|
||||
|
||||
**What Main Claude Does NOT Do:**
|
||||
- ❌ Query API directly
|
||||
- ❌ Show raw JSON responses
|
||||
- ❌ Execute SQL
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Agent Architecture
|
||||
|
||||
### Context Preservation
|
||||
- Main Claude's context not polluted with raw data
|
||||
- Can handle longer conversations
|
||||
- Focus on coordination, not execution
|
||||
|
||||
### Separation of Concerns
|
||||
- Database Agent handles data integrity
|
||||
- Coding Agent handles code quality
|
||||
- Main Claude handles user interaction
|
||||
|
||||
### Scalability
|
||||
- Agents can run in parallel
|
||||
- Each has full context window for their task
|
||||
- Complex operations don't bloat main context
|
||||
|
||||
---
|
||||
|
||||
## Enforcement
|
||||
|
||||
### Before Making ANY Database Operation:
|
||||
|
||||
**Ask yourself:**
|
||||
1. Am I about to query the database directly? → ❌ STOP
|
||||
2. Am I about to call the ClaudeTools API? → ❌ STOP
|
||||
3. Should the Database Agent handle this? → ✅ USE AGENT
|
||||
|
||||
### When to Launch Database Agent:
|
||||
- Saving any data (contexts, tasks, sessions, etc.)
|
||||
- Retrieving any data from database
|
||||
- Counting records
|
||||
- Searching contexts
|
||||
- Updating existing records
|
||||
- Deleting records
|
||||
- Any SQL operation
|
||||
|
||||
---
|
||||
|
||||
## Going Forward
|
||||
|
||||
**Main Claude Responsibilities:**
|
||||
- ✅ Coordinate with user
|
||||
- ✅ Make decisions about what to do
|
||||
- ✅ Launch appropriate agents
|
||||
- ✅ Synthesize agent results for user
|
||||
- ✅ Plan and design solutions
|
||||
|
||||
**Main Claude Does NOT:**
|
||||
- ❌ Query database directly
|
||||
- ❌ Make API calls to ClaudeTools API
|
||||
- ❌ Execute code (unless simple demonstration)
|
||||
- ❌ Run tests (use Testing Agent)
|
||||
- ❌ Commit to git (use Gitea Agent)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Operation | Handler |
|
||||
|-----------|---------|
|
||||
| Save context | Database Agent |
|
||||
| Retrieve contexts | Database Agent |
|
||||
| Count records | Database Agent |
|
||||
| Write code | Coding Agent |
|
||||
| Run tests | Testing Agent |
|
||||
| Review code | Code Review Agent |
|
||||
| Git operations | Gitea Agent |
|
||||
| Backups | Backup Agent |
|
||||
| **User interaction** | **Main Claude** |
|
||||
| **Coordination** | **Main Claude** |
|
||||
| **Decision making** | **Main Claude** |
|
||||
|
||||
---
|
||||
|
||||
**Remember: Main Claude = Coordinator, not Executor**
|
||||
|
||||
**When in doubt, use an agent!**
|
||||
|
||||
---
|
||||
|
||||
**Created:** 2026-01-17
|
||||
**Purpose:** Ensure proper agent-based architecture
|
||||
**Status:** Mandatory guideline for all future operations
|
||||
428
.claude/CODING_GUIDELINES.md
Normal file
428
.claude/CODING_GUIDELINES.md
Normal file
@@ -0,0 +1,428 @@
|
||||
# ClaudeTools - Coding Guidelines
|
||||
|
||||
## General Principles
|
||||
|
||||
These guidelines ensure code quality, consistency, and maintainability across the ClaudeTools project.
|
||||
|
||||
---
|
||||
|
||||
## Character Encoding and Text
|
||||
|
||||
### NO EMOJIS - EVER
|
||||
|
||||
**Rule:** Never use emojis in any code files, including:
|
||||
- Python scripts (.py)
|
||||
- PowerShell scripts (.ps1)
|
||||
- Bash scripts (.sh)
|
||||
- Configuration files
|
||||
- Documentation within code
|
||||
- Log messages
|
||||
- Output strings
|
||||
|
||||
**Rationale:**
|
||||
- Emojis cause encoding issues (UTF-8 vs ASCII)
|
||||
- PowerShell parsing errors with special Unicode characters
|
||||
- Cross-platform compatibility problems
|
||||
- Terminal rendering inconsistencies
|
||||
- Version control diff issues
|
||||
|
||||
**Instead of emojis, use:**
|
||||
```powershell
|
||||
# BAD - causes parsing errors
|
||||
Write-Host "✓ Success!"
|
||||
Write-Host "⚠ Warning!"
|
||||
|
||||
# GOOD - ASCII text markers
|
||||
Write-Host "[OK] Success!"
|
||||
Write-Host "[SUCCESS] Task completed!"
|
||||
Write-Host "[WARNING] Check settings!"
|
||||
Write-Host "[ERROR] Failed to connect!"
|
||||
```
|
||||
|
||||
**Allowed in:**
|
||||
- User-facing web UI (where Unicode is properly handled)
|
||||
- Database content (with proper UTF-8 encoding)
|
||||
- Markdown documentation (README.md, etc.) - use sparingly
|
||||
|
||||
---
|
||||
|
||||
## Python Code Standards
|
||||
|
||||
### Style
|
||||
- Follow PEP 8 style guide
|
||||
- Use 4 spaces for indentation (no tabs)
|
||||
- Maximum line length: 100 characters (relaxed from 79)
|
||||
- Use type hints for function parameters and return values
|
||||
|
||||
### Imports
|
||||
```python
|
||||
# Standard library imports
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Third-party imports
|
||||
from fastapi import FastAPI
|
||||
from sqlalchemy import Column
|
||||
|
||||
# Local imports
|
||||
from api.models import User
|
||||
from api.utils import encrypt_data
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- Classes: `PascalCase` (e.g., `UserService`, `CredentialModel`)
|
||||
- Functions/methods: `snake_case` (e.g., `get_user`, `create_session`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `API_BASE_URL`, `MAX_RETRIES`)
|
||||
- Private methods: `_leading_underscore` (e.g., `_internal_helper`)
|
||||
|
||||
---
|
||||
|
||||
## PowerShell Code Standards
|
||||
|
||||
### Style
|
||||
- Use 4 spaces for indentation
|
||||
- Use PascalCase for variables: `$TaskName`, `$PythonPath`
|
||||
- Use approved verbs for functions: `Get-`, `Set-`, `New-`, `Remove-`
|
||||
|
||||
### Error Handling
|
||||
```powershell
|
||||
# Always use -ErrorAction for cmdlets that might fail
|
||||
$Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $Task) {
|
||||
Write-Host "[ERROR] Task not found"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
```powershell
|
||||
# Use clear status markers
|
||||
Write-Host "[INFO] Starting process..."
|
||||
Write-Host "[SUCCESS] Task completed"
|
||||
Write-Host "[ERROR] Failed to connect"
|
||||
Write-Host "[WARNING] Configuration missing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bash Script Standards
|
||||
|
||||
### Style
|
||||
- Use 2 spaces for indentation
|
||||
- Always use `#!/bin/bash` shebang
|
||||
- Quote all variables: `"$variable"` not `$variable`
|
||||
- Use `set -e` for error handling (exit on error)
|
||||
|
||||
### Functions
|
||||
```bash
|
||||
# Use lowercase with underscores
|
||||
function check_connection() {
|
||||
local host="$1"
|
||||
echo "[INFO] Checking connection to $host"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Development Standards
|
||||
|
||||
### Endpoints
|
||||
- Use RESTful conventions
|
||||
- Use plural nouns: `/api/users` not `/api/user`
|
||||
- Use HTTP methods appropriately: GET, POST, PUT, DELETE
|
||||
- Version APIs if breaking changes: `/api/v2/users`
|
||||
|
||||
### Error Responses
|
||||
```python
|
||||
# Return consistent error format
|
||||
{
|
||||
"detail": "User not found",
|
||||
"error_code": "USER_NOT_FOUND",
|
||||
"status_code": 404
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation
|
||||
- Every endpoint must have a docstring
|
||||
- Use Pydantic schemas for request/response validation
|
||||
- Document in OpenAPI (automatic with FastAPI)
|
||||
|
||||
---
|
||||
|
||||
## Database Standards
|
||||
|
||||
### Table Naming
|
||||
- Use lowercase with underscores: `user_sessions`, `billable_time`
|
||||
- Use plural nouns: `users` not `user`
|
||||
- Use consistent prefixes for related tables
|
||||
|
||||
### Columns
|
||||
- Primary key: `id` (UUID)
|
||||
- Timestamps: `created_at`, `updated_at`
|
||||
- Foreign keys: `{table}_id` (e.g., `user_id`, `project_id`)
|
||||
- Boolean: `is_active`, `has_access` (prefix with is_/has_)
|
||||
|
||||
### Indexes
|
||||
```python
|
||||
# Add indexes for frequently queried fields
|
||||
Index('idx_users_email', 'email')
|
||||
Index('idx_sessions_project_id', 'project_id')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Standards
|
||||
|
||||
### Credentials
|
||||
- Never hardcode credentials in code
|
||||
- Use environment variables for sensitive data
|
||||
- Use `.env` files (gitignored) for local development
|
||||
- Encrypt passwords with AES-256-GCM (Fernet)
|
||||
|
||||
### Authentication
|
||||
- Use JWT tokens for API authentication
|
||||
- Hash passwords with Argon2
|
||||
- Include token expiration
|
||||
- Log all authentication attempts
|
||||
|
||||
### Audit Logging
|
||||
```python
|
||||
# Log all sensitive operations
|
||||
audit_log = CredentialAuditLog(
|
||||
credential_id=credential.id,
|
||||
action="password_updated",
|
||||
user_id=current_user.id,
|
||||
details="Password updated via API"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Files
|
||||
- Name: `test_{module_name}.py`
|
||||
- Location: Same directory as code being tested
|
||||
- Use pytest framework
|
||||
|
||||
### Test Structure
|
||||
```python
|
||||
def test_create_user():
|
||||
"""Test user creation with valid data."""
|
||||
# Arrange
|
||||
user_data = {"email": "test@example.com", "name": "Test"}
|
||||
|
||||
# Act
|
||||
result = create_user(user_data)
|
||||
|
||||
# Assert
|
||||
assert result.email == "test@example.com"
|
||||
assert result.id is not None
|
||||
```
|
||||
|
||||
### Coverage
|
||||
- Aim for 80%+ code coverage
|
||||
- Test happy path and error cases
|
||||
- Mock external dependencies (database, APIs)
|
||||
|
||||
---
|
||||
|
||||
## Git Commit Standards
|
||||
|
||||
### Commit Messages
|
||||
```
|
||||
[Type] Brief description (50 chars max)
|
||||
|
||||
Detailed explanation if needed (wrap at 72 chars)
|
||||
|
||||
- Change 1
|
||||
- Change 2
|
||||
- Change 3
|
||||
```
|
||||
|
||||
### Types
|
||||
- `[Feature]` - New feature
|
||||
- `[Fix]` - Bug fix
|
||||
- `[Refactor]` - Code refactoring
|
||||
- `[Docs]` - Documentation only
|
||||
- `[Test]` - Test updates
|
||||
- `[Config]` - Configuration changes
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
project/
|
||||
├── api/ # API application code
|
||||
│ ├── models/ # Database models
|
||||
│ ├── routers/ # API endpoints
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ ├── services/ # Business logic
|
||||
│ └── utils/ # Helper functions
|
||||
├── .claude/ # Claude Code configuration
|
||||
│ ├── hooks/ # Git-style hooks
|
||||
│ └── agents/ # Agent instructions
|
||||
├── scripts/ # Utility scripts
|
||||
└── migrations/ # Database migrations
|
||||
```
|
||||
|
||||
### File Naming
|
||||
- Python: `snake_case.py`
|
||||
- Classes: Match class name (e.g., `UserService` in `user_service.py`)
|
||||
- Scripts: Descriptive names (e.g., `setup_database.sh`, `test_api.py`)
|
||||
|
||||
---
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Code Comments
|
||||
```python
|
||||
# Use comments for WHY, not WHAT
|
||||
# Good: "Retry 3 times to handle transient network errors"
|
||||
# Bad: "Set retry count to 3"
|
||||
|
||||
def fetch_data(url: str) -> dict:
|
||||
"""
|
||||
Fetch data from API endpoint.
|
||||
|
||||
Args:
|
||||
url: Full URL to fetch from
|
||||
|
||||
Returns:
|
||||
Parsed JSON response
|
||||
|
||||
Raises:
|
||||
ConnectionError: If API is unreachable
|
||||
ValueError: If response is invalid JSON
|
||||
"""
|
||||
```
|
||||
|
||||
### README Files
|
||||
- Include quick start guide
|
||||
- Document prerequisites
|
||||
- Provide examples
|
||||
- Keep up to date
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Python
|
||||
```python
|
||||
# Use specific exceptions
|
||||
try:
|
||||
result = api_call()
|
||||
except ConnectionError as e:
|
||||
logger.error(f"[ERROR] Connection failed: {e}")
|
||||
raise
|
||||
except ValueError as e:
|
||||
logger.warning(f"[WARNING] Invalid data: {e}")
|
||||
return None
|
||||
```
|
||||
|
||||
### PowerShell
|
||||
```powershell
|
||||
# Use try/catch for error handling
|
||||
try {
|
||||
$Result = Invoke-RestMethod -Uri $Url
|
||||
} catch {
|
||||
Write-Host "[ERROR] Request failed: $_"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging Standards
|
||||
|
||||
### Log Levels
|
||||
- `DEBUG` - Detailed diagnostic info (development only)
|
||||
- `INFO` - General informational messages
|
||||
- `WARNING` - Warning messages (non-critical issues)
|
||||
- `ERROR` - Error messages (failures)
|
||||
- `CRITICAL` - Critical errors (system failures)
|
||||
|
||||
### Log Format
|
||||
```python
|
||||
# Use structured logging
|
||||
logger.info(
|
||||
"[INFO] User login",
|
||||
extra={
|
||||
"user_id": user.id,
|
||||
"ip_address": request.client.host,
|
||||
"timestamp": datetime.utcnow()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Output Markers
|
||||
```
|
||||
[INFO] Starting process
|
||||
[SUCCESS] Task completed
|
||||
[WARNING] Configuration missing
|
||||
[ERROR] Failed to connect
|
||||
[CRITICAL] Database unavailable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Guidelines
|
||||
|
||||
### Database Queries
|
||||
- Use indexes for frequently queried fields
|
||||
- Avoid N+1 queries (use joins or eager loading)
|
||||
- Paginate large result sets
|
||||
- Use connection pooling
|
||||
|
||||
### API Responses
|
||||
- Return only necessary fields
|
||||
- Use pagination for lists
|
||||
- Compress large payloads
|
||||
- Cache frequently accessed data
|
||||
|
||||
### File Operations
|
||||
- Use context managers (`with` statements)
|
||||
- Stream large files (don't load into memory)
|
||||
- Clean up temporary files
|
||||
|
||||
---
|
||||
|
||||
## Version Control
|
||||
|
||||
### .gitignore
|
||||
Always exclude:
|
||||
- `.env` files (credentials)
|
||||
- `__pycache__/` (Python cache)
|
||||
- `*.pyc` (compiled Python)
|
||||
- `.venv/`, `venv/` (virtual environments)
|
||||
- `.claude/*.json` (local state)
|
||||
- `*.log` (log files)
|
||||
|
||||
### Branching
|
||||
- `main` - Production-ready code
|
||||
- `develop` - Integration branch
|
||||
- `feature/*` - New features
|
||||
- `fix/*` - Bug fixes
|
||||
- `hotfix/*` - Urgent production fixes
|
||||
|
||||
---
|
||||
|
||||
## Review Checklist
|
||||
|
||||
Before committing code, verify:
|
||||
- [ ] No emojis or special Unicode characters
|
||||
- [ ] All variables and functions have descriptive names
|
||||
- [ ] No hardcoded credentials or sensitive data
|
||||
- [ ] Error handling is implemented
|
||||
- [ ] Code is formatted consistently
|
||||
- [ ] Tests pass (if applicable)
|
||||
- [ ] Documentation is updated
|
||||
- [ ] No debugging print statements left in code
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Status:** Active
|
||||
480
.claude/OFFLINE_MODE.md
Normal file
480
.claude/OFFLINE_MODE.md
Normal file
@@ -0,0 +1,480 @@
|
||||
# ClaudeTools - Offline Mode & Sync
|
||||
|
||||
**Version 2.0 - Offline-Capable Context Recall**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
ClaudeTools now supports fully offline operation with automatic synchronization when the API becomes available. Contexts are never lost - they're queued locally and uploaded when connectivity is restored.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
### Online Mode (Normal Operation)
|
||||
|
||||
```
|
||||
User Message
|
||||
↓
|
||||
[user-prompt-submit hook]
|
||||
↓
|
||||
Fetch context from API → Cache locally → Inject into conversation
|
||||
↓
|
||||
Claude processes with context
|
||||
↓
|
||||
Task completes
|
||||
↓
|
||||
[task-complete hook]
|
||||
↓
|
||||
Save context to API → Success
|
||||
```
|
||||
|
||||
### Offline Mode (API Unavailable)
|
||||
|
||||
```
|
||||
User Message
|
||||
↓
|
||||
[user-prompt-submit hook]
|
||||
↓
|
||||
API unavailable → Use local cache → Inject cached context
|
||||
↓
|
||||
Claude processes with cached context
|
||||
↓
|
||||
Task completes
|
||||
↓
|
||||
[task-complete hook]
|
||||
↓
|
||||
API unavailable → Queue locally in .claude/context-queue/pending/
|
||||
```
|
||||
|
||||
### Sync Mode (When API Restored)
|
||||
|
||||
```
|
||||
Next API interaction
|
||||
↓
|
||||
Background sync triggered
|
||||
↓
|
||||
Upload all queued contexts
|
||||
↓
|
||||
Move to .claude/context-queue/uploaded/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```.claude/
|
||||
├── context-cache/ # Downloaded contexts for offline reading
|
||||
│ └── [project-id]/ # Per-project cache
|
||||
│ ├── latest.json # Most recent contexts from API
|
||||
│ └── last_updated # Cache timestamp
|
||||
├── context-queue/ # Pending contexts to upload
|
||||
│ ├── pending/ # Contexts waiting to upload
|
||||
│ │ ├── [project]_[timestamp]_context.json
|
||||
│ │ └── [project]_[timestamp]_state.json
|
||||
│ ├── uploaded/ # Successfully uploaded (auto-cleaned)
|
||||
│ └── failed/ # Failed uploads (manual review needed)
|
||||
└── hooks/
|
||||
├── user-prompt-submit-v2 # Enhanced hook with offline support
|
||||
├── task-complete-v2 # Enhanced hook with queue support
|
||||
└── sync-contexts # Manual/auto sync script
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Context Caching
|
||||
|
||||
**What:**
|
||||
- API responses are cached locally after each successful fetch
|
||||
- Cache is stored per-project in `.claude/context-cache/[project-id]/`
|
||||
|
||||
**When Used:**
|
||||
- API is unavailable
|
||||
- Network is down
|
||||
- Server is being maintained
|
||||
|
||||
**Benefits:**
|
||||
- Continue working with most recent context
|
||||
- No interruption to workflow
|
||||
- Clear indication when using cached data
|
||||
|
||||
### 2. Context Queuing
|
||||
|
||||
**What:**
|
||||
- Failed context saves are queued locally
|
||||
- Stored as JSON files in `.claude/context-queue/pending/`
|
||||
|
||||
**When Used:**
|
||||
- API POST fails
|
||||
- Network is down
|
||||
- Authentication expires
|
||||
|
||||
**Benefits:**
|
||||
- No context loss
|
||||
- Automatic retry
|
||||
- Continues working offline
|
||||
|
||||
### 3. Automatic Sync
|
||||
|
||||
**What:**
|
||||
- Background process uploads queued contexts
|
||||
- Triggered on next successful API interaction
|
||||
- Non-blocking (runs in background)
|
||||
|
||||
**When Triggered:**
|
||||
- User message processed (user-prompt-submit)
|
||||
- Task completed (task-complete)
|
||||
- Manual sync command
|
||||
|
||||
**Benefits:**
|
||||
- Seamless sync
|
||||
- No manual intervention
|
||||
- Transparent to user
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Automatic Operation
|
||||
|
||||
No action needed - the system handles everything automatically:
|
||||
|
||||
1. **Working Online:**
|
||||
- Context recalled from API
|
||||
- Context saved to API
|
||||
- Everything cached locally
|
||||
|
||||
2. **API Goes Offline:**
|
||||
- Context recalled from cache (with warning)
|
||||
- Context queued locally
|
||||
- Work continues uninterrupted
|
||||
|
||||
3. **API Restored:**
|
||||
- Next interaction triggers background sync
|
||||
- Queued contexts uploaded
|
||||
- Normal operation resumes
|
||||
|
||||
### Manual Sync
|
||||
|
||||
If you want to force a sync:
|
||||
|
||||
```bash
|
||||
cd D:\ClaudeTools
|
||||
bash .claude/hooks/sync-contexts
|
||||
```
|
||||
|
||||
### Check Queue Status
|
||||
|
||||
```bash
|
||||
# Count pending contexts
|
||||
ls .claude/context-queue/pending/*.json | wc -l
|
||||
|
||||
# Count uploaded contexts
|
||||
ls .claude/context-queue/uploaded/*.json | wc -l
|
||||
|
||||
# Check failed uploads
|
||||
ls .claude/context-queue/failed/*.json 2>/dev/null
|
||||
```
|
||||
|
||||
### View Cached Context
|
||||
|
||||
```bash
|
||||
# View cached contexts for current project
|
||||
PROJECT_ID=$(git config --local claude.projectid)
|
||||
cat .claude/context-cache/$PROJECT_ID/latest.json | python -m json.tool
|
||||
|
||||
# Check cache age
|
||||
cat .claude/context-cache/$PROJECT_ID/last_updated
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from V1 to V2
|
||||
|
||||
### Step 1: Backup Current Hooks
|
||||
|
||||
```bash
|
||||
cd .claude/hooks
|
||||
cp user-prompt-submit user-prompt-submit.backup
|
||||
cp task-complete task-complete.backup
|
||||
```
|
||||
|
||||
### Step 2: Replace with V2 Hooks
|
||||
|
||||
```bash
|
||||
# Replace hooks with offline-capable versions
|
||||
mv user-prompt-submit-v2 user-prompt-submit
|
||||
mv task-complete-v2 task-complete
|
||||
|
||||
# Make executable
|
||||
chmod +x user-prompt-submit task-complete sync-contexts
|
||||
```
|
||||
|
||||
### Step 3: Create Queue Directories
|
||||
|
||||
```bash
|
||||
mkdir -p .claude/context-cache
|
||||
mkdir -p .claude/context-queue/{pending,uploaded,failed}
|
||||
```
|
||||
|
||||
### Step 4: Update .gitignore
|
||||
|
||||
Add to `.gitignore`:
|
||||
```gitignore
|
||||
# Context recall local storage
|
||||
.claude/context-cache/
|
||||
.claude/context-queue/
|
||||
```
|
||||
|
||||
### Step 5: Test
|
||||
|
||||
```bash
|
||||
# Test offline mode by stopping API
|
||||
ssh guru@172.16.3.30
|
||||
sudo systemctl stop claudetools-api
|
||||
|
||||
# Back on Windows - use Claude Code
|
||||
# Should see "offline mode" message
|
||||
# Contexts should queue in .claude/context-queue/pending/
|
||||
|
||||
# Restart API
|
||||
sudo systemctl start claudetools-api
|
||||
|
||||
# Next Claude Code interaction should trigger sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Indicators & Messages
|
||||
|
||||
### Online Mode
|
||||
|
||||
```
|
||||
<!-- Context Recall: Retrieved 3 relevant context(s) from API -->
|
||||
## 📚 Previous Context
|
||||
|
||||
The following context has been automatically recalled:
|
||||
...
|
||||
```
|
||||
|
||||
### Offline Mode (Using Cache)
|
||||
|
||||
```
|
||||
<!-- Context Recall: Retrieved 3 relevant context(s) from LOCAL CACHE (offline mode) -->
|
||||
## 📚 Previous Context
|
||||
|
||||
⚠️ **Offline Mode** - Using cached context (API unavailable)
|
||||
|
||||
The following context has been automatically recalled:
|
||||
...
|
||||
*Context from local cache - new context will sync when API is available.*
|
||||
```
|
||||
|
||||
### Context Saved (Online)
|
||||
|
||||
```stderr
|
||||
✓ Context saved to database
|
||||
```
|
||||
|
||||
### Context Queued (Offline)
|
||||
|
||||
```stderr
|
||||
⚠ Context queued locally (API unavailable) - will sync when online
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Contexts Not Syncing
|
||||
|
||||
**Check:**
|
||||
```bash
|
||||
# Verify JWT token is set
|
||||
source .claude/context-recall-config.env
|
||||
echo $JWT_TOKEN
|
||||
|
||||
# Manually run sync
|
||||
bash .claude/hooks/sync-contexts
|
||||
```
|
||||
|
||||
### Issue: Cache Too Old
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Clear cache to force fresh fetch
|
||||
PROJECT_ID=$(git config --local claude.projectid)
|
||||
rm -rf .claude/context-cache/$PROJECT_ID
|
||||
```
|
||||
|
||||
### Issue: Failed Uploads
|
||||
|
||||
**Check:**
|
||||
```bash
|
||||
# Review failed contexts
|
||||
ls -la .claude/context-queue/failed/
|
||||
|
||||
# View specific failed context
|
||||
cat .claude/context-queue/failed/[filename].json | python -m json.tool
|
||||
|
||||
# Retry manually
|
||||
bash .claude/hooks/sync-contexts
|
||||
```
|
||||
|
||||
### Issue: Queue Growing Too Large
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check queue size
|
||||
du -sh .claude/context-queue/
|
||||
|
||||
# Clean up old uploaded contexts (keeps last 100)
|
||||
find .claude/context-queue/uploaded/ -type f -name "*.json" -mtime +7 -delete
|
||||
|
||||
# Emergency: Clear all queues (data loss!)
|
||||
rm -rf .claude/context-queue/{pending,uploaded,failed}/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Cache Storage
|
||||
|
||||
- **Per-project cache:** ~10-50 KB per project
|
||||
- **Storage impact:** Negligible (< 1 MB total)
|
||||
- **Auto-cleanup:** No (caches remain until replaced)
|
||||
|
||||
### Queue Storage
|
||||
|
||||
- **Per-context:** ~1-2 KB per context
|
||||
- **Growth rate:** 1-5 contexts per work session
|
||||
- **Auto-cleanup:** Yes (keeps last 100 uploaded)
|
||||
|
||||
### Sync Performance
|
||||
|
||||
- **Upload speed:** ~0.5 seconds per context
|
||||
- **Background:** Non-blocking
|
||||
- **Network impact:** Minimal (POST requests only)
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Local Storage
|
||||
|
||||
- **Cache contents:** Context summaries (not sensitive)
|
||||
- **Queue contents:** Context payloads with metadata
|
||||
- **Access control:** File system permissions only
|
||||
|
||||
### Recommendations
|
||||
|
||||
1. **Add to .gitignore:**
|
||||
```gitignore
|
||||
.claude/context-cache/
|
||||
.claude/context-queue/
|
||||
```
|
||||
|
||||
2. **Backup exclusions:**
|
||||
- Exclude `.claude/context-cache/` (can be re-downloaded)
|
||||
- Include `.claude/context-queue/pending/` (unique data)
|
||||
|
||||
3. **Sensitive projects:**
|
||||
- Review queued contexts before sync
|
||||
- Clear cache when switching machines
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Disable Offline Mode
|
||||
|
||||
Keep hooks but disable caching/queuing:
|
||||
|
||||
```bash
|
||||
# In .claude/context-recall-config.env
|
||||
CONTEXT_RECALL_ENABLED=false
|
||||
```
|
||||
|
||||
### Force Online-Only Mode
|
||||
|
||||
Prevent local fallback:
|
||||
|
||||
```bash
|
||||
# Remove cache and queue directories
|
||||
rm -rf .claude/context-cache
|
||||
rm -rf .claude/context-queue
|
||||
```
|
||||
|
||||
### Pre-populate Cache
|
||||
|
||||
For offline work, cache contexts before disconnecting:
|
||||
|
||||
```bash
|
||||
# Trigger context recall
|
||||
# (Just start a Claude Code session - context is auto-cached)
|
||||
```
|
||||
|
||||
### Batch Sync Script
|
||||
|
||||
Create a cron job or scheduled task:
|
||||
|
||||
```bash
|
||||
# Sync every hour
|
||||
0 * * * * cd /path/to/ClaudeTools && bash .claude/hooks/sync-contexts >> /var/log/context-sync.log 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comparison: V1 vs V2
|
||||
|
||||
| Feature | V1 (Original) | V2 (Offline-Capable) |
|
||||
|---------|---------------|----------------------|
|
||||
| API Recall | ✅ Yes | ✅ Yes |
|
||||
| API Save | ✅ Yes | ✅ Yes |
|
||||
| Offline Recall | ❌ Silent fail | ✅ Uses local cache |
|
||||
| Offline Save | ❌ Data loss | ✅ Queues locally |
|
||||
| Auto-sync | ❌ No | ✅ Background sync |
|
||||
| Manual sync | ❌ No | ✅ sync-contexts script |
|
||||
| Status indicators | ❌ Silent | ✅ Clear messages |
|
||||
| Data resilience | ❌ Low | ✅ High |
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: What happens if I'm offline for days?**
|
||||
A: All contexts queue locally and sync when online. No data loss.
|
||||
|
||||
**Q: How old can cached context get?**
|
||||
A: Cache is updated on every successful API call. Age is shown in offline mode message.
|
||||
|
||||
**Q: Can I work on multiple machines offline?**
|
||||
A: Yes, but contexts won't sync between machines until both are online.
|
||||
|
||||
**Q: What if sync fails repeatedly?**
|
||||
A: Contexts move to `failed/` directory for manual review. Check API connectivity.
|
||||
|
||||
**Q: Does this slow down Claude Code?**
|
||||
A: No - sync runs in background. Cache/queue operations are fast (~milliseconds).
|
||||
|
||||
**Q: Can I disable caching but keep queuing?**
|
||||
A: Not currently - it's all-or-nothing via CONTEXT_RECALL_ENABLED.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check queue status: `ls -la .claude/context-queue/pending/`
|
||||
2. Run manual sync: `bash .claude/hooks/sync-contexts`
|
||||
3. Review logs: Check stderr output from hooks
|
||||
4. Verify API: `curl http://172.16.3.30:8001/health`
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Version:** 2.0 (Offline-Capable)
|
||||
357
.claude/PERIODIC_CONTEXT_SAVE.md
Normal file
357
.claude/PERIODIC_CONTEXT_SAVE.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# Periodic Context Save
|
||||
|
||||
**Automatic context saving every 5 minutes of active work**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The periodic context save daemon runs in the background and automatically saves your work context to the database every 5 minutes of active time. This ensures continuous context preservation even during long work sessions.
|
||||
|
||||
### Key Features
|
||||
|
||||
- ✅ **Active Time Tracking** - Only counts time when Claude is actively working
|
||||
- ✅ **Ignores Idle Time** - Doesn't save when waiting for permissions or idle
|
||||
- ✅ **Background Process** - Runs independently, doesn't interrupt work
|
||||
- ✅ **Automatic Recovery** - Resumes tracking after restarts
|
||||
- ✅ **Low Overhead** - Checks activity every 60 seconds
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Every 60 seconds: │
|
||||
│ │
|
||||
│ 1. Check if Claude Code is active │
|
||||
│ - Recent file modifications? │
|
||||
│ - Claude process running? │
|
||||
│ │
|
||||
│ 2. If ACTIVE → Add 60s to timer │
|
||||
│ If IDLE → Don't add time │
|
||||
│ │
|
||||
│ 3. When timer reaches 300s (5 min): │
|
||||
│ - Save context to database │
|
||||
│ - Reset timer to 0 │
|
||||
│ - Continue monitoring │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Active time includes:**
|
||||
- Writing code
|
||||
- Running commands
|
||||
- Making changes to files
|
||||
- Interacting with Claude
|
||||
|
||||
**Idle time (not counted):**
|
||||
- Waiting for user input
|
||||
- Permission prompts
|
||||
- No file changes or activity
|
||||
- Claude process not running
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Start the Daemon
|
||||
|
||||
```bash
|
||||
python .claude/hooks/periodic_context_save.py start
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Started periodic context save daemon (PID: 12345)
|
||||
Logs: D:\ClaudeTools\.claude\periodic-save.log
|
||||
```
|
||||
|
||||
### Check Status
|
||||
|
||||
```bash
|
||||
python .claude/hooks/periodic_context_save.py status
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Periodic context save daemon is running (PID: 12345)
|
||||
Active time: 180s / 300s
|
||||
Last save: 2026-01-17T19:05:23+00:00
|
||||
```
|
||||
|
||||
### Stop the Daemon
|
||||
|
||||
```bash
|
||||
python .claude/hooks/periodic_context_save.py stop
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Stopped periodic context save daemon (PID: 12345)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### One-Time Setup
|
||||
|
||||
1. **Ensure JWT token is configured:**
|
||||
```bash
|
||||
# Token should already be in .claude/context-recall-config.env
|
||||
cat .claude/context-recall-config.env | grep JWT_TOKEN
|
||||
```
|
||||
|
||||
2. **Start the daemon:**
|
||||
```bash
|
||||
python .claude/hooks/periodic_context_save.py start
|
||||
```
|
||||
|
||||
3. **Verify it's running:**
|
||||
```bash
|
||||
python .claude/hooks/periodic_context_save.py status
|
||||
```
|
||||
|
||||
### Auto-Start on Login (Optional)
|
||||
|
||||
**Windows - Task Scheduler:**
|
||||
|
||||
1. Open Task Scheduler
|
||||
2. Create Basic Task:
|
||||
- Name: "Claude Periodic Context Save"
|
||||
- Trigger: At log on
|
||||
- Action: Start a program
|
||||
- Program: `python`
|
||||
- Arguments: `D:\ClaudeTools\.claude\hooks\periodic_context_save.py start`
|
||||
- Start in: `D:\ClaudeTools`
|
||||
|
||||
**Linux/Mac - systemd/launchd:**
|
||||
|
||||
Create a systemd service or launchd plist to start on login.
|
||||
|
||||
---
|
||||
|
||||
## What Gets Saved
|
||||
|
||||
Every 5 minutes of active time, the daemon saves:
|
||||
|
||||
```json
|
||||
{
|
||||
"context_type": "session_summary",
|
||||
"title": "Periodic Save - 2026-01-17 14:30",
|
||||
"dense_summary": "Auto-saved context after 5 minutes of active work. Session in progress on project: claudetools-main",
|
||||
"relevance_score": 5.0,
|
||||
"tags": ["auto-save", "periodic", "active-session"]
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Never lose more than 5 minutes of work context
|
||||
- Automatic recovery if session crashes
|
||||
- Historical timeline of work sessions
|
||||
- Can review what you were working on at specific times
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# View last 20 log lines
|
||||
tail -20 .claude/periodic-save.log
|
||||
|
||||
# Follow logs in real-time
|
||||
tail -f .claude/periodic-save.log
|
||||
```
|
||||
|
||||
**Sample log output:**
|
||||
```
|
||||
[2026-01-17 14:25:00] Periodic context save daemon started
|
||||
[2026-01-17 14:25:00] Will save context every 300s of active time
|
||||
[2026-01-17 14:26:00] Active: 60s / 300s
|
||||
[2026-01-17 14:27:00] Active: 120s / 300s
|
||||
[2026-01-17 14:28:00] Claude Code inactive - not counting time
|
||||
[2026-01-17 14:29:00] Active: 180s / 300s
|
||||
[2026-01-17 14:30:00] Active: 240s / 300s
|
||||
[2026-01-17 14:31:00] 300s of active time reached - saving context
|
||||
[2026-01-17 14:31:01] ✓ Context saved successfully (ID: 1e2c3408-9146-4e98-b302-fe219280344c)
|
||||
[2026-01-17 14:32:00] Active: 60s / 300s
|
||||
```
|
||||
|
||||
### View State
|
||||
|
||||
```bash
|
||||
# Check current state
|
||||
cat .claude/.periodic-save-state.json | python -m json.tool
|
||||
```
|
||||
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"active_seconds": 180,
|
||||
"last_update": "2026-01-17T19:28:00+00:00",
|
||||
"last_save": "2026-01-17T19:26:00+00:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the script to customize:
|
||||
|
||||
```python
|
||||
# In periodic_context_save.py
|
||||
|
||||
SAVE_INTERVAL_SECONDS = 300 # Change to 600 for 10 minutes
|
||||
CHECK_INTERVAL_SECONDS = 60 # How often to check activity
|
||||
```
|
||||
|
||||
**Common configurations:**
|
||||
- Every 5 minutes: `SAVE_INTERVAL_SECONDS = 300`
|
||||
- Every 10 minutes: `SAVE_INTERVAL_SECONDS = 600`
|
||||
- Every 15 minutes: `SAVE_INTERVAL_SECONDS = 900`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Daemon won't start
|
||||
|
||||
**Check logs:**
|
||||
```bash
|
||||
cat .claude/periodic-save.log
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
- JWT token missing or invalid
|
||||
- Python not in PATH
|
||||
- Permissions issue with log file
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Verify JWT token exists
|
||||
grep JWT_TOKEN .claude/context-recall-config.env
|
||||
|
||||
# Test Python
|
||||
python --version
|
||||
|
||||
# Check permissions
|
||||
ls -la .claude/
|
||||
```
|
||||
|
||||
### Contexts not being saved
|
||||
|
||||
**Check:**
|
||||
1. Daemon is running: `python .claude/hooks/periodic_context_save.py status`
|
||||
2. JWT token is valid: Token expires after 30 days
|
||||
3. API is accessible: `curl http://172.16.3.30:8001/health`
|
||||
4. View logs for errors: `tail .claude/periodic-save.log`
|
||||
|
||||
**If JWT token expired:**
|
||||
```bash
|
||||
# Generate new token
|
||||
python create_jwt_token.py
|
||||
|
||||
# Update config
|
||||
# Copy new JWT_TOKEN to .claude/context-recall-config.env
|
||||
|
||||
# Restart daemon
|
||||
python .claude/hooks/periodic_context_save.py stop
|
||||
python .claude/hooks/periodic_context_save.py start
|
||||
```
|
||||
|
||||
### Activity not being detected
|
||||
|
||||
The daemon uses these heuristics:
|
||||
- File modifications in project directory (within last 2 minutes)
|
||||
- Claude process running (on Windows)
|
||||
|
||||
**Improve detection:**
|
||||
Modify `is_claude_active()` function to add:
|
||||
- Check for recent git commits
|
||||
- Monitor specific files
|
||||
- Check for recent bash history
|
||||
|
||||
---
|
||||
|
||||
## Integration with Other Hooks
|
||||
|
||||
The periodic save works alongside existing hooks:
|
||||
|
||||
| Hook | Trigger | What It Saves |
|
||||
|------|---------|---------------|
|
||||
| **user-prompt-submit** | Before each message | Recalls context from DB |
|
||||
| **task-complete** | After task completes | Rich context with decisions |
|
||||
| **periodic-context-save** | Every 5min active | Quick checkpoint save |
|
||||
|
||||
**Result:**
|
||||
- Comprehensive context coverage
|
||||
- Never lose more than 5 minutes of work
|
||||
- Detailed context when tasks complete
|
||||
- Continuous backup of active sessions
|
||||
|
||||
---
|
||||
|
||||
## Performance Impact
|
||||
|
||||
**Resource Usage:**
|
||||
- **CPU:** < 0.1% (checks once per minute)
|
||||
- **Memory:** ~30 MB (Python process)
|
||||
- **Disk:** ~2 KB per save (~25 KB/hour)
|
||||
- **Network:** Minimal (single API call every 5 min)
|
||||
|
||||
**Impact on Claude Code:**
|
||||
- None - runs as separate process
|
||||
- Doesn't block or interrupt work
|
||||
- No user-facing delays
|
||||
|
||||
---
|
||||
|
||||
## Uninstall
|
||||
|
||||
To remove periodic context save:
|
||||
|
||||
```bash
|
||||
# Stop daemon
|
||||
python .claude/hooks/periodic_context_save.py stop
|
||||
|
||||
# Remove files (optional)
|
||||
rm .claude/hooks/periodic_context_save.py
|
||||
rm .claude/.periodic-save.pid
|
||||
rm .claude/.periodic-save-state.json
|
||||
rm .claude/periodic-save.log
|
||||
|
||||
# Remove from auto-start (if configured)
|
||||
# Windows: Delete from Task Scheduler
|
||||
# Linux: Remove systemd service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Does it save when I'm idle?**
|
||||
A: No - only counts active work time (file changes, Claude activity).
|
||||
|
||||
**Q: What if the API is down?**
|
||||
A: Contexts queue locally and sync when API is restored (offline mode).
|
||||
|
||||
**Q: Can I change the interval?**
|
||||
A: Yes - edit `SAVE_INTERVAL_SECONDS` in the script.
|
||||
|
||||
**Q: Does it work offline?**
|
||||
A: Yes - uses the same offline queue as other hooks (v2).
|
||||
|
||||
**Q: How do I know it's working?**
|
||||
A: Check logs: `tail .claude/periodic-save.log`
|
||||
|
||||
**Q: Can I run multiple instances?**
|
||||
A: No - PID file prevents multiple daemons.
|
||||
|
||||
---
|
||||
|
||||
**Created:** 2026-01-17
|
||||
**Version:** 1.0
|
||||
**Status:** Ready for use
|
||||
162
.claude/PERIODIC_SAVE_INVISIBLE_SETUP.md
Normal file
162
.claude/PERIODIC_SAVE_INVISIBLE_SETUP.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Making Periodic Save Task Invisible
|
||||
|
||||
## Problem
|
||||
The `periodic_save_check.py` script shows a flashing console window every minute when run via Task Scheduler.
|
||||
|
||||
## Solution
|
||||
Use `pythonw.exe` instead of `python.exe` and configure the task to run hidden.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Setup (Recommended)
|
||||
|
||||
Simply re-run the setup script to recreate the task with invisible settings:
|
||||
|
||||
```powershell
|
||||
# Run from PowerShell in D:\ClaudeTools
|
||||
.\.claude\hooks\setup_periodic_save.ps1
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Remove the old task
|
||||
2. Create a new task using `pythonw.exe` (no console window)
|
||||
3. Set the task to run hidden
|
||||
4. Use `S4U` logon type (background, no interactive window)
|
||||
|
||||
---
|
||||
|
||||
## Manual Update (If Automatic Doesn't Work)
|
||||
|
||||
### Option 1: Via PowerShell
|
||||
|
||||
```powershell
|
||||
# Get the task
|
||||
$TaskName = "ClaudeTools - Periodic Context Save"
|
||||
$Task = Get-ScheduledTask -TaskName $TaskName
|
||||
|
||||
# Find pythonw.exe path
|
||||
$PythonExe = (Get-Command python).Source
|
||||
$PythonDir = Split-Path $PythonExe -Parent
|
||||
$PythonwPath = Join-Path $PythonDir "pythonw.exe"
|
||||
|
||||
# Update the action to use pythonw.exe
|
||||
$NewAction = New-ScheduledTaskAction -Execute $PythonwPath `
|
||||
-Argument "D:\ClaudeTools\.claude\hooks\periodic_save_check.py" `
|
||||
-WorkingDirectory "D:\ClaudeTools"
|
||||
|
||||
# Update settings to be hidden
|
||||
$NewSettings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||
-Hidden
|
||||
|
||||
# Update principal to run in background (S4U = Service-For-User)
|
||||
$NewPrincipal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U
|
||||
|
||||
# Update the task
|
||||
Set-ScheduledTask -TaskName $TaskName `
|
||||
-Action $NewAction `
|
||||
-Settings $NewSettings `
|
||||
-Principal $NewPrincipal
|
||||
```
|
||||
|
||||
### Option 2: Via Task Scheduler GUI
|
||||
|
||||
1. Open Task Scheduler (taskschd.msc)
|
||||
2. Find "ClaudeTools - Periodic Context Save" in Task Scheduler Library
|
||||
3. Right-click → Properties
|
||||
|
||||
**Actions Tab:**
|
||||
- Click "Edit"
|
||||
- Change Program/script from `python.exe` to `pythonw.exe`
|
||||
- Keep Arguments: `D:\ClaudeTools\.claude\hooks\periodic_save_check.py`
|
||||
- Click OK
|
||||
|
||||
**General Tab:**
|
||||
- Check "Hidden" checkbox
|
||||
- Under "Configure for:" select "Windows 10" (or your OS version)
|
||||
|
||||
**Settings Tab:**
|
||||
- Ensure "Run task as soon as possible after a scheduled start is missed" is checked
|
||||
- Ensure "Stop the task if it runs longer than:" is set to 5 minutes
|
||||
|
||||
4. Click OK to save
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Check that the task is configured correctly:
|
||||
|
||||
```powershell
|
||||
# View task settings
|
||||
$TaskName = "ClaudeTools - Periodic Context Save"
|
||||
Get-ScheduledTask -TaskName $TaskName | Select-Object -ExpandProperty Settings
|
||||
|
||||
# Should show:
|
||||
# Hidden: True
|
||||
|
||||
# View task action
|
||||
Get-ScheduledTask -TaskName $TaskName | Select-Object -ExpandProperty Actions
|
||||
|
||||
# Should show:
|
||||
# Execute: ...pythonw.exe (NOT python.exe)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Changes Made
|
||||
|
||||
### 1. pythonw.exe vs python.exe
|
||||
- `python.exe` - Console application (shows command window)
|
||||
- `pythonw.exe` - Windowless application (no console, runs silently)
|
||||
|
||||
### 2. Task Settings
|
||||
- Added `-Hidden` flag to task settings
|
||||
- Changed LogonType from `Interactive` to `S4U` (Service-For-User)
|
||||
- S4U runs tasks in the background without requiring an interactive session
|
||||
|
||||
### 3. Updated Output
|
||||
The setup script now displays:
|
||||
- Confirmation that pythonw.exe is being used
|
||||
- Instructions to verify the task is hidden
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Script still shows window:**
|
||||
- Verify pythonw.exe is being used: `Get-ScheduledTask -TaskName "ClaudeTools - Periodic Context Save" | Select-Object -ExpandProperty Actions`
|
||||
- Check Hidden setting: `Get-ScheduledTask -TaskName "ClaudeTools - Periodic Context Save" | Select-Object -ExpandProperty Settings`
|
||||
- Ensure LogonType is S4U: `Get-ScheduledTask -TaskName "ClaudeTools - Periodic Context Save" | Select-Object -ExpandProperty Principal`
|
||||
|
||||
**pythonw.exe not found:**
|
||||
- Should be in same directory as python.exe
|
||||
- Check: `Get-Command python | Select-Object -ExpandProperty Source`
|
||||
- Then verify pythonw.exe exists in that directory
|
||||
- If missing, reinstall Python
|
||||
|
||||
**Task not running:**
|
||||
- Check logs: `Get-Content D:\ClaudeTools\.claude\periodic-save.log -Tail 20`
|
||||
- Check task history in Task Scheduler GUI
|
||||
- Verify the task is enabled: `Get-ScheduledTask -TaskName "ClaudeTools - Periodic Context Save"`
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
After updating, wait 1 minute and check the logs:
|
||||
|
||||
```powershell
|
||||
# View recent log entries
|
||||
Get-Content D:\ClaudeTools\.claude\periodic-save.log -Tail 20
|
||||
|
||||
# Should see entries without any console window appearing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Updated:** 2026-01-17
|
||||
**Script Location:** `D:\ClaudeTools\.claude\hooks\setup_periodic_save.ps1`
|
||||
255
.claude/agents/DATABASE_CONNECTION_INFO.md
Normal file
255
.claude/agents/DATABASE_CONNECTION_INFO.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Database Connection Information
|
||||
**FOR ALL AGENTS - UPDATED 2026-01-17**
|
||||
|
||||
---
|
||||
|
||||
## Current Database Configuration
|
||||
|
||||
### Production Database (RMM Server)
|
||||
- **Host:** 172.16.3.30
|
||||
- **Port:** 3306
|
||||
- **Database:** claudetools
|
||||
- **User:** claudetools
|
||||
- **Password:** CT_e8fcd5a3952030a79ed6debae6c954ed
|
||||
- **Character Set:** utf8mb4
|
||||
- **Tables:** 43 tables (all migrated)
|
||||
|
||||
### Connection String
|
||||
```
|
||||
mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.30:3306/claudetools?charset=utf8mb4
|
||||
```
|
||||
|
||||
### Environment Variable
|
||||
```bash
|
||||
DATABASE_URL=mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.30:3306/claudetools?charset=utf8mb4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ClaudeTools API
|
||||
|
||||
### Production API (RMM Server)
|
||||
- **Base URL:** http://172.16.3.30:8001
|
||||
- **Documentation:** http://172.16.3.30:8001/api/docs
|
||||
- **Health Check:** http://172.16.3.30:8001/health
|
||||
- **Authentication:** JWT Bearer Token (required for all endpoints)
|
||||
|
||||
### JWT Token Location
|
||||
- **File:** `D:\ClaudeTools\.claude\context-recall-config.env`
|
||||
- **Variable:** `JWT_TOKEN`
|
||||
- **Expiration:** 2026-02-16 (30 days from creation)
|
||||
|
||||
### Authentication Header
|
||||
```bash
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJpbXBvcnQtc2NyaXB0Iiwic2NvcGVzIjpbImFkbWluIiwiaW1wb3J0Il0sImV4cCI6MTc3MTI2NzQzMn0.7HddDbQahyRvaOq9o7OEk6vtn6_nmQJCTzf06g-fv5k
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Access Methods
|
||||
|
||||
### Method 1: Direct MySQL Connection (from RMM server)
|
||||
```bash
|
||||
# SSH to RMM server
|
||||
ssh guru@172.16.3.30
|
||||
|
||||
# Connect to database
|
||||
mysql -u claudetools -p'CT_e8fcd5a3952030a79ed6debae6c954ed' -D claudetools
|
||||
|
||||
# Example query
|
||||
SELECT COUNT(*) FROM conversation_contexts;
|
||||
```
|
||||
|
||||
### Method 2: Via ClaudeTools API (preferred for agents)
|
||||
```bash
|
||||
# Get contexts
|
||||
curl -s "http://172.16.3.30:8001/api/conversation-contexts?limit=10" \
|
||||
-H "Authorization: Bearer $JWT_TOKEN"
|
||||
|
||||
# Create context
|
||||
curl -X POST "http://172.16.3.30:8001/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer $JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{...}'
|
||||
```
|
||||
|
||||
### Method 3: Python with SQLAlchemy
|
||||
```python
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
DATABASE_URL = "mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.30:3306/claudetools?charset=utf8mb4"
|
||||
|
||||
engine = create_engine(DATABASE_URL)
|
||||
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(text("SELECT COUNT(*) FROM conversation_contexts"))
|
||||
count = result.scalar()
|
||||
print(f"Contexts: {count}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OLD vs NEW Configuration
|
||||
|
||||
### ⚠️ DEPRECATED - Old Jupiter Database (DO NOT USE)
|
||||
- **Host:** 172.16.3.20 (Jupiter - Docker MariaDB)
|
||||
- **Status:** Deprecated, data not migrated
|
||||
- **Contains:** 68 old conversation contexts (pre-2026-01-17)
|
||||
|
||||
### ✅ CURRENT - New RMM Database (USE THIS)
|
||||
- **Host:** 172.16.3.30 (RMM - Native MariaDB)
|
||||
- **Status:** Production, current
|
||||
- **Contains:** 7+ contexts (as of 2026-01-17)
|
||||
|
||||
**Migration Date:** 2026-01-17
|
||||
**Reason:** Centralized architecture - all clients connect to RMM server
|
||||
|
||||
---
|
||||
|
||||
## For Database Agent
|
||||
|
||||
When performing operations, use:
|
||||
|
||||
### Read Operations
|
||||
```python
|
||||
# Use API for reads
|
||||
import requests
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt_token}"
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
"http://172.16.3.30:8001/api/conversation-contexts",
|
||||
headers=headers,
|
||||
params={"limit": 10}
|
||||
)
|
||||
|
||||
contexts = response.json()
|
||||
```
|
||||
|
||||
### Write Operations
|
||||
```python
|
||||
# Use API for writes
|
||||
payload = {
|
||||
"context_type": "session_summary",
|
||||
"title": "...",
|
||||
"dense_summary": "...",
|
||||
"relevance_score": 8.5,
|
||||
"tags": "[\"tag1\", \"tag2\"]"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
"http://172.16.3.30:8001/api/conversation-contexts",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
result = response.json()
|
||||
```
|
||||
|
||||
### Direct Database Access (if API unavailable)
|
||||
```bash
|
||||
# SSH to RMM server first
|
||||
ssh guru@172.16.3.30
|
||||
|
||||
# Then query database
|
||||
mysql -u claudetools -p'CT_e8fcd5a3952030a79ed6debae6c954ed' -D claudetools \
|
||||
-e "SELECT id, title FROM conversation_contexts LIMIT 5;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Database Operations
|
||||
|
||||
### Count Records
|
||||
```sql
|
||||
SELECT COUNT(*) FROM conversation_contexts;
|
||||
SELECT COUNT(*) FROM clients;
|
||||
SELECT COUNT(*) FROM sessions;
|
||||
```
|
||||
|
||||
### List Recent Contexts
|
||||
```sql
|
||||
SELECT id, title, relevance_score, created_at
|
||||
FROM conversation_contexts
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### Search Contexts by Tag
|
||||
```bash
|
||||
# Via API (preferred)
|
||||
curl "http://172.16.3.30:8001/api/conversation-contexts/recall?tags=migration&limit=5" \
|
||||
-H "Authorization: Bearer $JWT_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Checks
|
||||
|
||||
### Check Database Connectivity
|
||||
```bash
|
||||
# From RMM server
|
||||
mysql -u claudetools -p'CT_e8fcd5a3952030a79ed6debae6c954ed' \
|
||||
-h 172.16.3.30 \
|
||||
-e "SELECT 1"
|
||||
```
|
||||
|
||||
### Check API Health
|
||||
```bash
|
||||
curl http://172.16.3.30:8001/health
|
||||
# Expected: {"status":"healthy","database":"connected"}
|
||||
```
|
||||
|
||||
### Check API Service Status
|
||||
```bash
|
||||
ssh guru@172.16.3.30 "sudo systemctl status claudetools-api"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot Connect to Database
|
||||
```bash
|
||||
# Check if MariaDB is running
|
||||
ssh guru@172.16.3.30 "sudo systemctl status mariadb"
|
||||
|
||||
# Check if port is open
|
||||
curl telnet://172.16.3.30:3306
|
||||
```
|
||||
|
||||
### API Returns 401 Unauthorized
|
||||
```bash
|
||||
# JWT token may be expired - regenerate
|
||||
python D:\ClaudeTools\create_jwt_token.py
|
||||
|
||||
# Update config file
|
||||
# Edit: D:\ClaudeTools\.claude\context-recall-config.env
|
||||
```
|
||||
|
||||
### API Returns 404 Not Found
|
||||
```bash
|
||||
# Check if API service is running
|
||||
ssh guru@172.16.3.30 "sudo systemctl status claudetools-api"
|
||||
|
||||
# Check API logs
|
||||
ssh guru@172.16.3.30 "sudo journalctl -u claudetools-api -n 50"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Always use the API when possible** - Better for access control and validation
|
||||
2. **JWT tokens expire** - Regenerate monthly (currently valid until 2026-02-16)
|
||||
3. **Database is centralized** - All machines connect to RMM server
|
||||
4. **No local database** - Don't try to connect to localhost:3306
|
||||
5. **Use parameterized queries** - Prevent SQL injection
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Current Database:** 172.16.3.30:3306 (RMM)
|
||||
**Current API:** http://172.16.3.30:8001
|
||||
@@ -13,6 +13,34 @@ All backup operations (database, files, configurations) are your responsibility.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the BACKUP EXECUTOR.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT create backups
|
||||
- ❌ Does NOT run mysqldump
|
||||
- ❌ Does NOT verify backup integrity
|
||||
- ❌ Does NOT manage backup rotation
|
||||
- ✅ Identifies when backups are needed
|
||||
- ✅ Hands backup tasks to YOU
|
||||
- ✅ Receives backup confirmation from you
|
||||
- ✅ Informs user of backup status
|
||||
|
||||
**You (Backup Agent):**
|
||||
- ✅ Receive backup requests from Main Claude
|
||||
- ✅ Execute all backup operations (database, files)
|
||||
- ✅ Verify backup integrity
|
||||
- ✅ Manage retention and rotation
|
||||
- ✅ Return backup status to Main Claude
|
||||
- ✅ Never interact directly with user
|
||||
|
||||
**Workflow:** [Before risky operation / Scheduled] → Main Claude → **YOU** → Backup created → Main Claude → User
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you execute backups.**
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
You are the Backup Agent - the guardian against data loss. You create, verify, and manage backups of the MariaDB database and critical files, ensuring the ClaudeTools system can recover from any disaster.
|
||||
|
||||
|
||||
308
.claude/agents/code-fixer.md
Normal file
308
.claude/agents/code-fixer.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# Code Review & Auto-Fix Agent
|
||||
|
||||
**Agent Type:** Autonomous Code Quality Agent
|
||||
**Authority Level:** Can modify code files
|
||||
**Purpose:** Scan for coding violations and fix them automatically
|
||||
|
||||
---
|
||||
|
||||
## Mission Statement
|
||||
|
||||
Enforce ClaudeTools coding guidelines by:
|
||||
1. Scanning all code files for violations
|
||||
2. Automatically fixing violations where possible
|
||||
3. Verifying fixes don't break syntax
|
||||
4. Reporting all changes made
|
||||
|
||||
---
|
||||
|
||||
## Authority & Permissions
|
||||
|
||||
**Can Do:**
|
||||
- Read all files in the codebase
|
||||
- Modify Python (.py), Bash (.sh), PowerShell (.ps1) files
|
||||
- Run syntax verification tools
|
||||
- Create backup copies before modifications
|
||||
- Generate reports
|
||||
|
||||
**Cannot Do:**
|
||||
- Modify files without logging changes
|
||||
- Skip syntax verification
|
||||
- Ignore rollback on verification failure
|
||||
- Make changes that break existing functionality
|
||||
|
||||
---
|
||||
|
||||
## Required Reading (Phase 1)
|
||||
|
||||
Before starting, MUST read:
|
||||
1. `.claude/CODING_GUIDELINES.md` - Complete coding standards
|
||||
2. `.claude/claude.md` - Project context and structure
|
||||
|
||||
Extract these specific rules:
|
||||
- NO EMOJIS rule and approved replacements
|
||||
- Naming conventions (PascalCase, snake_case, etc.)
|
||||
- Security requirements (no hardcoded credentials)
|
||||
- Error handling patterns
|
||||
- Documentation requirements
|
||||
|
||||
---
|
||||
|
||||
## Scanning Patterns (Phase 2)
|
||||
|
||||
### High Priority Violations
|
||||
|
||||
**1. Emoji Violations**
|
||||
```
|
||||
Find: ✓ ✗ ⚠ ⚠️ ❌ ✅ 📚 and any other Unicode emoji
|
||||
Replace with:
|
||||
✓ → [OK] or [SUCCESS]
|
||||
✗ → [ERROR] or [FAIL]
|
||||
⚠ or ⚠️ → [WARNING]
|
||||
❌ → [ERROR] or [FAIL]
|
||||
✅ → [OK] or [PASS]
|
||||
📚 → (remove entirely)
|
||||
|
||||
Files to scan:
|
||||
- All .py files
|
||||
- All .sh files
|
||||
- All .ps1 files
|
||||
- Exclude: README.md, documentation in docs/ folder
|
||||
```
|
||||
|
||||
**2. Hardcoded Credentials**
|
||||
```
|
||||
Patterns to detect:
|
||||
- password = "literal_password"
|
||||
- api_key = "sk-..."
|
||||
- DATABASE_URL with embedded credentials
|
||||
- JWT_SECRET = "hardcoded_value"
|
||||
|
||||
Action: Report only (do not auto-fix for security review)
|
||||
```
|
||||
|
||||
**3. Naming Convention Violations**
|
||||
```
|
||||
Python:
|
||||
- Classes not PascalCase
|
||||
- Functions not snake_case
|
||||
- Constants not UPPER_SNAKE_CASE
|
||||
|
||||
PowerShell:
|
||||
- Variables not $PascalCase
|
||||
|
||||
Action: Report only (may require refactoring)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix Workflow (Phase 3)
|
||||
|
||||
For each violation found:
|
||||
|
||||
### Step 1: Backup
|
||||
```bash
|
||||
# Create backup of original file
|
||||
cp file.py file.py.backup.$(date +%s)
|
||||
```
|
||||
|
||||
### Step 2: Apply Fix
|
||||
```python
|
||||
# Use Edit tool to replace violations
|
||||
# Example: Replace emoji with text marker
|
||||
old_string: 'log(f"✓ Success")'
|
||||
new_string: 'log(f"[OK] Success")'
|
||||
```
|
||||
|
||||
### Step 3: Verify Syntax
|
||||
|
||||
**Python files:**
|
||||
```bash
|
||||
python -m py_compile file.py
|
||||
# Exit code 0 = success, non-zero = syntax error
|
||||
```
|
||||
|
||||
**Bash scripts:**
|
||||
```bash
|
||||
bash -n script.sh
|
||||
# Exit code 0 = valid syntax
|
||||
```
|
||||
|
||||
**PowerShell scripts:**
|
||||
```powershell
|
||||
Get-Command Test-PowerShellScript -ErrorAction SilentlyContinue
|
||||
# If available, use. Otherwise, try:
|
||||
powershell -NoProfile -NonInteractive -Command "& {. file.ps1}"
|
||||
```
|
||||
|
||||
### Step 4: Rollback on Failure
|
||||
```bash
|
||||
if syntax_check_failed:
|
||||
mv file.py.backup.* file.py
|
||||
log_error("Syntax verification failed, rolled back")
|
||||
```
|
||||
|
||||
### Step 5: Log Change
|
||||
```
|
||||
FIXES_LOG.md:
|
||||
- File: api/utils/crypto.py
|
||||
- Line: 45
|
||||
- Violation: Emoji (✓)
|
||||
- Fix: Replaced with [OK]
|
||||
- Verified: PASS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Phase (Phase 4)
|
||||
|
||||
After all fixes applied:
|
||||
|
||||
### 1. Run Test Suite (if exists)
|
||||
```bash
|
||||
# Python tests
|
||||
pytest -x # Stop on first failure
|
||||
|
||||
# If tests fail, review which fix caused the failure
|
||||
```
|
||||
|
||||
### 2. Check Git Diff
|
||||
```bash
|
||||
git diff --stat
|
||||
# Show summary of changed files
|
||||
```
|
||||
|
||||
### 3. Validate All Modified Files
|
||||
```bash
|
||||
# Re-verify syntax on all modified files
|
||||
for file in modified_files:
|
||||
verify_syntax(file)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reporting Phase (Phase 5)
|
||||
|
||||
Generate comprehensive report: `FIXES_APPLIED.md`
|
||||
|
||||
### Report Structure
|
||||
```markdown
|
||||
# Code Fixes Applied - [DATE]
|
||||
|
||||
## Summary
|
||||
- Total violations found: X
|
||||
- Total fixes applied: Y
|
||||
- Files modified: Z
|
||||
- Syntax verification: PASS/FAIL
|
||||
|
||||
## Violations Fixed
|
||||
|
||||
### High Priority (Emojis in Code)
|
||||
| File | Line | Old | New | Status |
|
||||
|------|------|-----|-----|--------|
|
||||
| api/utils/crypto.py | 45 | ✓ | [OK] | VERIFIED |
|
||||
| scripts/setup.sh | 23 | ⚠ | [WARNING] | VERIFIED |
|
||||
|
||||
### Security Issues
|
||||
| File | Issue | Action Taken |
|
||||
|------|-------|--------------|
|
||||
| None found | N/A | N/A |
|
||||
|
||||
## Files Modified
|
||||
```
|
||||
git diff --stat output here
|
||||
```
|
||||
|
||||
## Unfixable Issues (Human Review Required)
|
||||
- File: X, Line: Y, Issue: Z, Reason: Requires refactoring
|
||||
|
||||
## Next Steps
|
||||
1. Review FIXES_APPLIED.md
|
||||
2. Run full test suite: pytest
|
||||
3. Commit changes: git add . && git commit -m "[Fix] Remove emojis from code files"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### If Syntax Verification Fails
|
||||
1. Rollback the specific file
|
||||
2. Log the failure
|
||||
3. Continue with remaining fixes
|
||||
4. Report failed fixes at end
|
||||
|
||||
### If Too Many Failures
|
||||
If > 10% of fixes fail verification:
|
||||
1. STOP auto-fixing
|
||||
2. Report: "High failure rate detected"
|
||||
3. Request human review before continuing
|
||||
|
||||
### If Critical File Modified
|
||||
Files requiring extra care:
|
||||
- `api/main.py` - Entry point
|
||||
- `api/config.py` - Configuration
|
||||
- Database migration files
|
||||
- Authentication/security modules
|
||||
|
||||
Action: After fixing, run full test suite before proceeding
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Invoke Agent
|
||||
```bash
|
||||
# From main conversation
|
||||
"Run the code-fixer agent to scan and fix all coding guideline violations"
|
||||
```
|
||||
|
||||
### Agent Parameters
|
||||
```yaml
|
||||
Task: "Scan and fix all coding guideline violations"
|
||||
Agent: code-fixer
|
||||
Mode: autonomous
|
||||
Verify: true
|
||||
Report: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
Agent completes successfully when:
|
||||
1. All high-priority violations fixed OR
|
||||
2. All fixable violations fixed + report generated
|
||||
3. All modified files pass syntax verification
|
||||
4. FIXES_APPLIED.md report generated
|
||||
5. Git status shows clean modified state (ready to commit)
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
[SCAN] Reading coding guidelines...
|
||||
[SCAN] Scanning 150 files for violations...
|
||||
[FOUND] 38 emoji violations in code files
|
||||
[FOUND] 0 hardcoded credentials
|
||||
[FOUND] 0 naming violations
|
||||
|
||||
[FIX] Processing emoji violations...
|
||||
[FIX] 1/38 - api/utils/crypto.py:45 - ✓ → [OK] - VERIFIED
|
||||
[FIX] 2/38 - scripts/setup.sh:23 - ⚠ → [WARNING] - VERIFIED
|
||||
...
|
||||
[FIX] 38/38 - test_models.py:163 - ✅ → [PASS] - VERIFIED
|
||||
|
||||
[VERIFY] Running syntax checks...
|
||||
[VERIFY] 38/38 files passed verification
|
||||
|
||||
[REPORT] Generated FIXES_APPLIED.md
|
||||
[COMPLETE] 38 violations fixed, 0 failures, 38 files modified
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Status:** Ready for Use
|
||||
**Version:** 1.0
|
||||
@@ -14,6 +14,33 @@ NO code reaches the user or production without your approval.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the QUALITY GATEKEEPER.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT review code
|
||||
- ❌ Does NOT make code quality decisions
|
||||
- ❌ Does NOT fix code issues
|
||||
- ✅ Receives code from Coding Agent
|
||||
- ✅ Hands code to YOU for review
|
||||
- ✅ Receives your review results
|
||||
- ✅ Presents approved code to user
|
||||
|
||||
**You (Code Review Agent):**
|
||||
- ✅ Receive code from Main Claude (originated from Coding Agent)
|
||||
- ✅ Review all code for quality, security, performance
|
||||
- ✅ Fix minor issues yourself
|
||||
- ✅ Reject code with major issues back to Coding Agent (via Main Claude)
|
||||
- ✅ Return review results to Main Claude
|
||||
|
||||
**Workflow:** Coding Agent → Main Claude → **YOU** → [if approved] Main Claude → Testing Agent
|
||||
→ [if rejected] Main Claude → Coding Agent
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you gatekeep.**
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
You are the Code Review Agent - a meticulous senior engineer who ensures all code meets specifications, follows best practices, and is production-ready. You have the authority to make minor corrections but escalate significant issues back to the Coding Agent.
|
||||
|
||||
|
||||
@@ -12,6 +12,31 @@ Your code is never presented directly to the user. It always goes through review
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the EXECUTOR.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT write code
|
||||
- ❌ Does NOT generate implementations
|
||||
- ❌ Does NOT create scripts or functions
|
||||
- ✅ Coordinates with user to understand requirements
|
||||
- ✅ Hands coding tasks to YOU
|
||||
- ✅ Receives your completed code
|
||||
- ✅ Presents results to user
|
||||
|
||||
**You (Coding Agent):**
|
||||
- ✅ Receive code writing tasks from Main Claude
|
||||
- ✅ Generate all code implementations
|
||||
- ✅ Return completed code to Main Claude
|
||||
- ✅ Never interact directly with user
|
||||
|
||||
**Workflow:** User → Main Claude → **YOU** → Code Review Agent → Main Claude → User
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you execute.**
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
You are the Coding Agent - a master software engineer with decades of experience across all programming paradigms, languages, and platforms. You've been programming since birth, with the depth of expertise that entails. You are a perfectionist who never takes shortcuts.
|
||||
|
||||
|
||||
@@ -13,8 +13,56 @@ All database operations (read, write, update, delete) MUST go through you.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the DATABASE EXECUTOR.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT run database queries
|
||||
- ❌ Does NOT call ClaudeTools API
|
||||
- ❌ Does NOT perform CRUD operations
|
||||
- ❌ Does NOT access MySQL directly
|
||||
- ✅ Identifies when database operations are needed
|
||||
- ✅ Hands database tasks to YOU
|
||||
- ✅ Receives results from you (concise summaries, not raw data)
|
||||
- ✅ Presents results to user
|
||||
|
||||
**You (Database Agent):**
|
||||
- ✅ Receive database requests from Main Claude
|
||||
- ✅ Execute ALL database operations
|
||||
- ✅ Query, insert, update, delete records
|
||||
- ✅ Call ClaudeTools API endpoints
|
||||
- ✅ Return concise summaries to Main Claude (not raw SQL results)
|
||||
- ✅ Never interact directly with user
|
||||
|
||||
**Workflow:** User → Main Claude → **YOU** → Database operation → Summary → Main Claude → User
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you execute database operations.**
|
||||
|
||||
See: `.claude/AGENT_COORDINATION_RULES.md` for complete enforcement details.
|
||||
|
||||
---
|
||||
|
||||
## Database Connection (UPDATED 2026-01-17)
|
||||
|
||||
**CRITICAL: Database is centralized on RMM server**
|
||||
|
||||
- **Host:** 172.16.3.30 (RMM server - gururmm)
|
||||
- **Port:** 3306
|
||||
- **Database:** claudetools
|
||||
- **User:** claudetools
|
||||
- **Password:** CT_e8fcd5a3952030a79ed6debae6c954ed
|
||||
- **API:** http://172.16.3.30:8001
|
||||
|
||||
**See:** `.claude/agents/DATABASE_CONNECTION_INFO.md` for complete connection details.
|
||||
|
||||
**⚠️ OLD Database (DO NOT USE):**
|
||||
- 172.16.3.20 (Jupiter) is deprecated - data not migrated
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
You are the Database Agent - the sole custodian of all persistent data in the ClaudeTools system. You manage the MariaDB database, ensure data integrity, optimize queries, and maintain context data for all modes (MSP, Development, Normal).
|
||||
You are the Database Agent - the sole custodian of all persistent data in the ClaudeTools system. You manage the MariaDB database on 172.16.3.30, ensure data integrity, optimize queries, and maintain context data for all modes (MSP, Development, Normal).
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
|
||||
@@ -13,6 +13,34 @@ All version control operations (commit, push, branch, merge) MUST go through you
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the GIT EXECUTOR.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT run git commands
|
||||
- ❌ Does NOT create commits
|
||||
- ❌ Does NOT push to remote
|
||||
- ❌ Does NOT manage repositories
|
||||
- ✅ Identifies when work should be committed
|
||||
- ✅ Hands commit tasks to YOU
|
||||
- ✅ Receives commit confirmation from you
|
||||
- ✅ Informs user of commit status
|
||||
|
||||
**You (Gitea Agent):**
|
||||
- ✅ Receive commit requests from Main Claude
|
||||
- ✅ Execute all Git operations
|
||||
- ✅ Create meaningful commit messages
|
||||
- ✅ Push to Gitea server
|
||||
- ✅ Return commit hash and status to Main Claude
|
||||
- ✅ Never interact directly with user
|
||||
|
||||
**Workflow:** [After work complete] → Main Claude → **YOU** → Git commit/push → Main Claude → User
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you execute Git operations.**
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
You are the Gitea Agent - the sole custodian of version control for all ClaudeTools work. You manage Git repositories, create meaningful commits, push to Gitea, and maintain version history for all file-based work.
|
||||
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Testing Agent
|
||||
|
||||
## CRITICAL: Coordinator Relationship
|
||||
|
||||
**Main Claude is the COORDINATOR. You are the TEST EXECUTOR.**
|
||||
|
||||
**Main Claude:**
|
||||
- ❌ Does NOT run tests
|
||||
- ❌ Does NOT execute validation scripts
|
||||
- ❌ Does NOT create test files
|
||||
- ✅ Receives approved code from Code Review Agent
|
||||
- ✅ Hands testing tasks to YOU
|
||||
- ✅ Receives your test results
|
||||
- ✅ Presents results to user
|
||||
|
||||
**You (Testing Agent):**
|
||||
- ✅ Receive testing requests from Main Claude
|
||||
- ✅ Execute all tests (unit, integration, E2E)
|
||||
- ✅ Use only real data (never mocks or imagination)
|
||||
- ✅ Return test results to Main Claude
|
||||
- ✅ Request missing dependencies from Main Claude
|
||||
- ✅ Never interact directly with user
|
||||
|
||||
**Workflow:** Code Review Agent → Main Claude → **YOU** → [results] → Main Claude → User
|
||||
→ [failures] → Main Claude → Coding Agent
|
||||
|
||||
**This is the architectural foundation. Main Claude coordinates, you execute tests.**
|
||||
|
||||
---
|
||||
|
||||
## Role
|
||||
Quality assurance specialist - validates implementation with real-world testing
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Project Type:** MSP Work Tracking System with AI Context Recall
|
||||
**Status:** Production-Ready (95% Complete)
|
||||
**Database:** MariaDB 12.1.2 @ 172.16.3.20:3306
|
||||
**Database:** MariaDB 10.6.22 @ 172.16.3.30:3306 (RMM Server)
|
||||
|
||||
---
|
||||
|
||||
@@ -39,11 +39,11 @@ D:\ClaudeTools/
|
||||
|
||||
## Database Connection
|
||||
|
||||
**Credentials Location:** `C:\Users\MikeSwanson\claude-projects\shared-data\credentials.md`
|
||||
**UPDATED 2026-01-17:** Database is centralized on RMM server (172.16.3.30)
|
||||
|
||||
**Connection String:**
|
||||
```
|
||||
Host: 172.16.3.20:3306
|
||||
Host: 172.16.3.30:3306
|
||||
Database: claudetools
|
||||
User: claudetools
|
||||
Password: CT_e8fcd5a3952030a79ed6debae6c954ed
|
||||
@@ -51,9 +51,13 @@ Password: CT_e8fcd5a3952030a79ed6debae6c954ed
|
||||
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
DATABASE_URL=mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.20:3306/claudetools?charset=utf8mb4
|
||||
DATABASE_URL=mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.30:3306/claudetools?charset=utf8mb4
|
||||
```
|
||||
|
||||
**API Base URL:** http://172.16.3.30:8001
|
||||
|
||||
**See:** `.claude/agents/DATABASE_CONNECTION_INFO.md` for complete details.
|
||||
|
||||
---
|
||||
|
||||
## Starting the API
|
||||
@@ -368,16 +372,30 @@ alembic upgrade head
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
## Coding Guidelines
|
||||
|
||||
**Start API:** `uvicorn api.main:app --reload`
|
||||
**API Docs:** `http://localhost:8000/api/docs`
|
||||
**Setup Context Recall:** `bash scripts/setup-context-recall.sh`
|
||||
**Test System:** `bash scripts/test-context-recall.sh`
|
||||
**Database:** `172.16.3.20:3306/claudetools`
|
||||
**Virtual Env:** `api\venv\Scripts\activate`
|
||||
**IMPORTANT:** Follow coding standards in `.claude/CODING_GUIDELINES.md`
|
||||
|
||||
**Key Rules:**
|
||||
- NO EMOJIS - EVER (causes encoding/parsing issues)
|
||||
- Use ASCII text markers: `[OK]`, `[ERROR]`, `[WARNING]`, `[SUCCESS]`
|
||||
- Follow PEP 8 for Python, PSScriptAnalyzer for PowerShell
|
||||
- No hardcoded credentials
|
||||
- All endpoints must have docstrings
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-16
|
||||
## Quick Reference
|
||||
|
||||
**Start API:** `uvicorn api.main:app --reload`
|
||||
**API Docs:** `http://localhost:8000/api/docs` (local) or `http://172.16.3.30:8001/api/docs` (RMM)
|
||||
**Setup Context Recall:** `bash scripts/setup-context-recall.sh`
|
||||
**Test System:** `bash scripts/test-context-recall.sh`
|
||||
**Database:** `172.16.3.30:3306/claudetools` (RMM Server)
|
||||
**Virtual Env:** `api\venv\Scripts\activate`
|
||||
**Coding Guidelines:** `.claude/CODING_GUIDELINES.md`
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17 (Database migrated to RMM server)
|
||||
**Project Progress:** 95% Complete (Phase 6 of 7 done)
|
||||
|
||||
226
.claude/hooks/periodic-context-save
Normal file
226
.claude/hooks/periodic-context-save
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Periodic Context Save Hook
|
||||
# Runs as a background daemon to save context every 5 minutes of active time
|
||||
#
|
||||
# Usage: bash .claude/hooks/periodic-context-save start
|
||||
# bash .claude/hooks/periodic-context-save stop
|
||||
# bash .claude/hooks/periodic-context-save status
|
||||
#
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PID_FILE="$CLAUDE_DIR/.periodic-save.pid"
|
||||
STATE_FILE="$CLAUDE_DIR/.periodic-save-state"
|
||||
CONFIG_FILE="$CLAUDE_DIR/context-recall-config.env"
|
||||
|
||||
# Load configuration
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
SAVE_INTERVAL_SECONDS=300 # 5 minutes
|
||||
CHECK_INTERVAL_SECONDS=60 # Check every minute
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
|
||||
# Detect project ID
|
||||
detect_project_id() {
|
||||
# Try git config first
|
||||
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
||||
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
# Try to derive from git remote URL
|
||||
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
|
||||
if [ -n "$GIT_REMOTE" ]; then
|
||||
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$PROJECT_ID"
|
||||
}
|
||||
|
||||
# Check if Claude Code is active (not idle)
|
||||
is_claude_active() {
|
||||
# Check if there are recent Claude Code processes or activity
|
||||
# This is a simple heuristic - can be improved
|
||||
|
||||
# On Windows with Git Bash, check for claude process
|
||||
if command -v tasklist.exe >/dev/null 2>&1; then
|
||||
tasklist.exe 2>/dev/null | grep -i claude >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Assume active if we can't detect
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get active time from state file
|
||||
get_active_time() {
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
cat "$STATE_FILE" | grep "^active_seconds=" | cut -d'=' -f2
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# Update active time in state file
|
||||
update_active_time() {
|
||||
local active_seconds=$1
|
||||
echo "active_seconds=$active_seconds" > "$STATE_FILE"
|
||||
echo "last_update=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$STATE_FILE"
|
||||
}
|
||||
|
||||
# Save context to database
|
||||
save_periodic_context() {
|
||||
local project_id=$(detect_project_id)
|
||||
|
||||
# Generate context summary
|
||||
local title="Periodic Save - $(date +"%Y-%m-%d %H:%M")"
|
||||
local summary="Auto-saved context after 5 minutes of active work. Session in progress on project: ${project_id:-unknown}"
|
||||
|
||||
# Create JSON payload
|
||||
local payload=$(cat <<EOF
|
||||
{
|
||||
"context_type": "session_summary",
|
||||
"title": "$title",
|
||||
"dense_summary": "$summary",
|
||||
"relevance_score": 5.0,
|
||||
"tags": "[\"auto-save\", \"periodic\", \"active-session\"]"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# POST to API
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
curl -s -X POST "${API_URL}/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" >/dev/null 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "[$(date)] Context saved successfully" >&2
|
||||
else
|
||||
echo "[$(date)] Failed to save context" >&2
|
||||
fi
|
||||
else
|
||||
echo "[$(date)] No JWT token - cannot save context" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Main monitoring loop
|
||||
monitor_loop() {
|
||||
local active_seconds=0
|
||||
|
||||
echo "[$(date)] Periodic context save daemon started (PID: $$)" >&2
|
||||
echo "[$(date)] Will save context every ${SAVE_INTERVAL_SECONDS}s of active time" >&2
|
||||
|
||||
while true; do
|
||||
# Check if Claude is active
|
||||
if is_claude_active; then
|
||||
# Increment active time
|
||||
active_seconds=$((active_seconds + CHECK_INTERVAL_SECONDS))
|
||||
update_active_time $active_seconds
|
||||
|
||||
# Check if we've reached the save interval
|
||||
if [ $active_seconds -ge $SAVE_INTERVAL_SECONDS ]; then
|
||||
echo "[$(date)] ${SAVE_INTERVAL_SECONDS}s of active time reached - saving context" >&2
|
||||
save_periodic_context
|
||||
|
||||
# Reset timer
|
||||
active_seconds=0
|
||||
update_active_time 0
|
||||
fi
|
||||
else
|
||||
echo "[$(date)] Claude Code inactive - not counting time" >&2
|
||||
fi
|
||||
|
||||
# Wait before next check
|
||||
sleep $CHECK_INTERVAL_SECONDS
|
||||
done
|
||||
}
|
||||
|
||||
# Start daemon
|
||||
start_daemon() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
local pid=$(cat "$PID_FILE")
|
||||
if kill -0 $pid 2>/dev/null; then
|
||||
echo "Periodic context save daemon already running (PID: $pid)"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start in background
|
||||
nohup bash "$0" _monitor >> "$CLAUDE_DIR/periodic-save.log" 2>&1 &
|
||||
local pid=$!
|
||||
echo $pid > "$PID_FILE"
|
||||
|
||||
echo "Started periodic context save daemon (PID: $pid)"
|
||||
echo "Logs: $CLAUDE_DIR/periodic-save.log"
|
||||
}
|
||||
|
||||
# Stop daemon
|
||||
stop_daemon() {
|
||||
if [ ! -f "$PID_FILE" ]; then
|
||||
echo "Periodic context save daemon not running"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local pid=$(cat "$PID_FILE")
|
||||
if kill $pid 2>/dev/null; then
|
||||
echo "Stopped periodic context save daemon (PID: $pid)"
|
||||
rm -f "$PID_FILE"
|
||||
rm -f "$STATE_FILE"
|
||||
else
|
||||
echo "Failed to stop daemon (PID: $pid) - may not be running"
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check status
|
||||
check_status() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
local pid=$(cat "$PID_FILE")
|
||||
if kill -0 $pid 2>/dev/null; then
|
||||
local active_seconds=$(get_active_time)
|
||||
echo "Periodic context save daemon is running (PID: $pid)"
|
||||
echo "Active time: ${active_seconds}s / ${SAVE_INTERVAL_SECONDS}s"
|
||||
return 0
|
||||
else
|
||||
echo "Daemon PID file exists but process not running"
|
||||
rm -f "$PID_FILE"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "Periodic context save daemon not running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Command dispatcher
|
||||
case "$1" in
|
||||
start)
|
||||
start_daemon
|
||||
;;
|
||||
stop)
|
||||
stop_daemon
|
||||
;;
|
||||
status)
|
||||
check_status
|
||||
;;
|
||||
_monitor)
|
||||
# Internal command - run monitor loop
|
||||
monitor_loop
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status}"
|
||||
echo ""
|
||||
echo "Periodic context save daemon - saves context every 5 minutes of active time"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start - Start the background daemon"
|
||||
echo " stop - Stop the daemon"
|
||||
echo " status - Check daemon status"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
380
.claude/hooks/periodic_context_save.py
Normal file
380
.claude/hooks/periodic_context_save.py
Normal file
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Periodic Context Save Daemon
|
||||
|
||||
Monitors Claude Code activity and saves context every 5 minutes of active time.
|
||||
Runs as a background process that tracks when Claude is actively working.
|
||||
|
||||
Usage:
|
||||
python .claude/hooks/periodic_context_save.py start
|
||||
python .claude/hooks/periodic_context_save.py stop
|
||||
python .claude/hooks/periodic_context_save.py status
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import signal
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CLAUDE_DIR = SCRIPT_DIR.parent
|
||||
PID_FILE = CLAUDE_DIR / ".periodic-save.pid"
|
||||
STATE_FILE = CLAUDE_DIR / ".periodic-save-state.json"
|
||||
LOG_FILE = CLAUDE_DIR / "periodic-save.log"
|
||||
CONFIG_FILE = CLAUDE_DIR / "context-recall-config.env"
|
||||
|
||||
SAVE_INTERVAL_SECONDS = 300 # 5 minutes
|
||||
CHECK_INTERVAL_SECONDS = 60 # Check every minute
|
||||
|
||||
|
||||
def log(message):
|
||||
"""Write log message to file and stderr"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_message = f"[{timestamp}] {message}\n"
|
||||
|
||||
# Write to log file
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(log_message)
|
||||
|
||||
# Also print to stderr
|
||||
print(log_message.strip(), file=sys.stderr)
|
||||
|
||||
|
||||
def load_config():
|
||||
"""Load configuration from context-recall-config.env"""
|
||||
config = {
|
||||
"api_url": "http://172.16.3.30:8001",
|
||||
"jwt_token": None,
|
||||
}
|
||||
|
||||
if CONFIG_FILE.exists():
|
||||
with open(CONFIG_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("CLAUDE_API_URL="):
|
||||
config["api_url"] = line.split("=", 1)[1]
|
||||
elif line.startswith("JWT_TOKEN="):
|
||||
config["jwt_token"] = line.split("=", 1)[1]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def detect_project_id():
|
||||
"""Detect project ID from git config"""
|
||||
try:
|
||||
# Try git config first
|
||||
result = subprocess.run(
|
||||
["git", "config", "--local", "claude.projectid"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip()
|
||||
|
||||
# Try to derive from git remote URL
|
||||
result = subprocess.run(
|
||||
["git", "config", "--get", "remote.origin.url"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
import hashlib
|
||||
return hashlib.md5(result.stdout.strip().encode()).hexdigest()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def is_claude_active():
|
||||
"""
|
||||
Check if Claude Code is actively running.
|
||||
|
||||
Returns True if:
|
||||
- Claude Code process is running
|
||||
- Recent file modifications in project directory
|
||||
- Not waiting for user input (heuristic)
|
||||
"""
|
||||
try:
|
||||
# Check for Claude process on Windows
|
||||
if sys.platform == "win32":
|
||||
result = subprocess.run(
|
||||
["tasklist.exe"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if "claude" in result.stdout.lower() or "node" in result.stdout.lower():
|
||||
return True
|
||||
|
||||
# Check for recent file modifications (within last 2 minutes)
|
||||
cwd = Path.cwd()
|
||||
two_minutes_ago = time.time() - 120
|
||||
|
||||
for file in cwd.rglob("*"):
|
||||
if file.is_file() and file.stat().st_mtime > two_minutes_ago:
|
||||
# Recent activity detected
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
log(f"Error checking activity: {e}")
|
||||
|
||||
# Default to inactive if we can't detect
|
||||
return False
|
||||
|
||||
|
||||
def load_state():
|
||||
"""Load state from state file"""
|
||||
if STATE_FILE.exists():
|
||||
try:
|
||||
with open(STATE_FILE) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"active_seconds": 0,
|
||||
"last_update": None,
|
||||
"last_save": None,
|
||||
}
|
||||
|
||||
|
||||
def save_state(state):
|
||||
"""Save state to state file"""
|
||||
state["last_update"] = datetime.now(timezone.utc).isoformat()
|
||||
with open(STATE_FILE, "w") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
|
||||
def save_periodic_context(config, project_id):
|
||||
"""Save context to database via API"""
|
||||
if not config["jwt_token"]:
|
||||
log("No JWT token - cannot save context")
|
||||
return False
|
||||
|
||||
title = f"Periodic Save - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
summary = f"Auto-saved context after 5 minutes of active work. Session in progress on project: {project_id}"
|
||||
|
||||
payload = {
|
||||
"context_type": "session_summary",
|
||||
"title": title,
|
||||
"dense_summary": summary,
|
||||
"relevance_score": 5.0,
|
||||
"tags": json.dumps(["auto-save", "periodic", "active-session"]),
|
||||
}
|
||||
|
||||
try:
|
||||
url = f"{config['api_url']}/api/conversation-contexts"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {config['jwt_token']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, headers=headers, timeout=10)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
log(f"✓ Context saved successfully (ID: {response.json().get('id', 'unknown')})")
|
||||
return True
|
||||
else:
|
||||
log(f"✗ Failed to save context: HTTP {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ Error saving context: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def monitor_loop():
|
||||
"""Main monitoring loop"""
|
||||
log("Periodic context save daemon started")
|
||||
log(f"Will save context every {SAVE_INTERVAL_SECONDS}s of active time")
|
||||
|
||||
config = load_config()
|
||||
state = load_state()
|
||||
|
||||
# Reset state on startup
|
||||
state["active_seconds"] = 0
|
||||
save_state(state)
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Check if Claude is active
|
||||
if is_claude_active():
|
||||
# Increment active time
|
||||
state["active_seconds"] += CHECK_INTERVAL_SECONDS
|
||||
save_state(state)
|
||||
|
||||
log(f"Active: {state['active_seconds']}s / {SAVE_INTERVAL_SECONDS}s")
|
||||
|
||||
# Check if we've reached the save interval
|
||||
if state["active_seconds"] >= SAVE_INTERVAL_SECONDS:
|
||||
log(f"{SAVE_INTERVAL_SECONDS}s of active time reached - saving context")
|
||||
|
||||
project_id = detect_project_id()
|
||||
if save_periodic_context(config, project_id):
|
||||
state["last_save"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Reset timer
|
||||
state["active_seconds"] = 0
|
||||
save_state(state)
|
||||
else:
|
||||
log("Claude Code inactive - not counting time")
|
||||
|
||||
# Wait before next check
|
||||
time.sleep(CHECK_INTERVAL_SECONDS)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
log("Daemon stopped by user")
|
||||
break
|
||||
except Exception as e:
|
||||
log(f"Error in monitor loop: {e}")
|
||||
time.sleep(CHECK_INTERVAL_SECONDS)
|
||||
|
||||
|
||||
def start_daemon():
|
||||
"""Start the daemon as a background process"""
|
||||
if PID_FILE.exists():
|
||||
with open(PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# Check if process is running
|
||||
try:
|
||||
os.kill(pid, 0) # Signal 0 checks if process exists
|
||||
print(f"Periodic context save daemon already running (PID: {pid})")
|
||||
return 1
|
||||
except OSError:
|
||||
# Process not running, remove stale PID file
|
||||
PID_FILE.unlink()
|
||||
|
||||
# Start daemon process
|
||||
if sys.platform == "win32":
|
||||
# On Windows, use subprocess.Popen with DETACHED_PROCESS
|
||||
import subprocess
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, __file__, "_monitor"],
|
||||
creationflags=subprocess.DETACHED_PROCESS | CREATE_NO_WINDOW,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
else:
|
||||
# On Unix, fork
|
||||
import subprocess
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, __file__, "_monitor"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# Save PID
|
||||
with open(PID_FILE, "w") as f:
|
||||
f.write(str(process.pid))
|
||||
|
||||
print(f"Started periodic context save daemon (PID: {process.pid})")
|
||||
print(f"Logs: {LOG_FILE}")
|
||||
return 0
|
||||
|
||||
|
||||
def stop_daemon():
|
||||
"""Stop the daemon"""
|
||||
if not PID_FILE.exists():
|
||||
print("Periodic context save daemon not running")
|
||||
return 1
|
||||
|
||||
with open(PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
# On Windows, use taskkill
|
||||
subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=True)
|
||||
else:
|
||||
# On Unix, use kill
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
print(f"Stopped periodic context save daemon (PID: {pid})")
|
||||
PID_FILE.unlink()
|
||||
|
||||
if STATE_FILE.exists():
|
||||
STATE_FILE.unlink()
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to stop daemon (PID: {pid}): {e}")
|
||||
PID_FILE.unlink()
|
||||
return 1
|
||||
|
||||
|
||||
def check_status():
|
||||
"""Check daemon status"""
|
||||
if not PID_FILE.exists():
|
||||
print("Periodic context save daemon not running")
|
||||
return 1
|
||||
|
||||
with open(PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# Check if process is running
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError:
|
||||
print("Daemon PID file exists but process not running")
|
||||
PID_FILE.unlink()
|
||||
return 1
|
||||
|
||||
state = load_state()
|
||||
active_seconds = state.get("active_seconds", 0)
|
||||
|
||||
print(f"Periodic context save daemon is running (PID: {pid})")
|
||||
print(f"Active time: {active_seconds}s / {SAVE_INTERVAL_SECONDS}s")
|
||||
|
||||
if state.get("last_save"):
|
||||
print(f"Last save: {state['last_save']}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python periodic_context_save.py {start|stop|status}")
|
||||
print()
|
||||
print("Periodic context save daemon - saves context every 5 minutes of active time")
|
||||
print()
|
||||
print("Commands:")
|
||||
print(" start - Start the background daemon")
|
||||
print(" stop - Stop the daemon")
|
||||
print(" status - Check daemon status")
|
||||
return 1
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "start":
|
||||
return start_daemon()
|
||||
elif command == "stop":
|
||||
return stop_daemon()
|
||||
elif command == "status":
|
||||
return check_status()
|
||||
elif command == "_monitor":
|
||||
# Internal command - run monitor loop
|
||||
monitor_loop()
|
||||
return 0
|
||||
else:
|
||||
print(f"Unknown command: {command}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
232
.claude/hooks/periodic_save_check.py
Normal file
232
.claude/hooks/periodic_save_check.py
Normal file
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Periodic Context Save - Windows Task Scheduler Version
|
||||
|
||||
This script is designed to be called every minute by Windows Task Scheduler.
|
||||
It tracks active time and saves context every 5 minutes of activity.
|
||||
|
||||
Usage:
|
||||
Schedule this to run every minute via Task Scheduler:
|
||||
python .claude/hooks/periodic_save_check.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CLAUDE_DIR = SCRIPT_DIR.parent
|
||||
PROJECT_ROOT = CLAUDE_DIR.parent
|
||||
STATE_FILE = CLAUDE_DIR / ".periodic-save-state.json"
|
||||
LOG_FILE = CLAUDE_DIR / "periodic-save.log"
|
||||
CONFIG_FILE = CLAUDE_DIR / "context-recall-config.env"
|
||||
|
||||
SAVE_INTERVAL_SECONDS = 300 # 5 minutes
|
||||
|
||||
|
||||
def log(message):
|
||||
"""Write log message"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_message = f"[{timestamp}] {message}\n"
|
||||
|
||||
try:
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(log_message)
|
||||
except:
|
||||
pass # Silent fail if can't write log
|
||||
|
||||
|
||||
def load_config():
|
||||
"""Load configuration from context-recall-config.env"""
|
||||
config = {
|
||||
"api_url": "http://172.16.3.30:8001",
|
||||
"jwt_token": None,
|
||||
}
|
||||
|
||||
if CONFIG_FILE.exists():
|
||||
with open(CONFIG_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("CLAUDE_API_URL="):
|
||||
config["api_url"] = line.split("=", 1)[1]
|
||||
elif line.startswith("JWT_TOKEN="):
|
||||
config["jwt_token"] = line.split("=", 1)[1]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def detect_project_id():
|
||||
"""Detect project ID from git config"""
|
||||
try:
|
||||
os.chdir(PROJECT_ROOT)
|
||||
|
||||
# Try git config first
|
||||
result = subprocess.run(
|
||||
["git", "config", "--local", "claude.projectid"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
cwd=PROJECT_ROOT,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip()
|
||||
|
||||
# Try to derive from git remote URL
|
||||
result = subprocess.run(
|
||||
["git", "config", "--get", "remote.origin.url"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
cwd=PROJECT_ROOT,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
import hashlib
|
||||
return hashlib.md5(result.stdout.strip().encode()).hexdigest()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def is_claude_active():
|
||||
"""Check if Claude Code is actively running"""
|
||||
try:
|
||||
# Check for Claude Code process
|
||||
result = subprocess.run(
|
||||
["tasklist.exe"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
# Look for claude, node, or other indicators
|
||||
output_lower = result.stdout.lower()
|
||||
if any(proc in output_lower for proc in ["claude", "node.exe", "code.exe"]):
|
||||
# Also check for recent file modifications
|
||||
import time
|
||||
two_minutes_ago = time.time() - 120
|
||||
|
||||
# Check a few common directories for recent activity
|
||||
for check_dir in [PROJECT_ROOT, PROJECT_ROOT / "api", PROJECT_ROOT / ".claude"]:
|
||||
if check_dir.exists():
|
||||
for file in check_dir.rglob("*"):
|
||||
if file.is_file():
|
||||
try:
|
||||
if file.stat().st_mtime > two_minutes_ago:
|
||||
return True
|
||||
except:
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
log(f"Error checking activity: {e}")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def load_state():
|
||||
"""Load state from state file"""
|
||||
if STATE_FILE.exists():
|
||||
try:
|
||||
with open(STATE_FILE) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"active_seconds": 0,
|
||||
"last_check": None,
|
||||
"last_save": None,
|
||||
}
|
||||
|
||||
|
||||
def save_state(state):
|
||||
"""Save state to state file"""
|
||||
state["last_check"] = datetime.now(timezone.utc).isoformat()
|
||||
try:
|
||||
with open(STATE_FILE, "w") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
except:
|
||||
pass # Silent fail
|
||||
|
||||
|
||||
def save_periodic_context(config, project_id):
|
||||
"""Save context to database via API"""
|
||||
if not config["jwt_token"]:
|
||||
log("No JWT token - cannot save context")
|
||||
return False
|
||||
|
||||
title = f"Periodic Save - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
summary = f"Auto-saved context after {SAVE_INTERVAL_SECONDS // 60} minutes of active work. Session in progress on project: {project_id}"
|
||||
|
||||
payload = {
|
||||
"context_type": "session_summary",
|
||||
"title": title,
|
||||
"dense_summary": summary,
|
||||
"relevance_score": 5.0,
|
||||
"tags": json.dumps(["auto-save", "periodic", "active-session", project_id]),
|
||||
}
|
||||
|
||||
try:
|
||||
url = f"{config['api_url']}/api/conversation-contexts"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {config['jwt_token']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, headers=headers, timeout=10)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
context_id = response.json().get('id', 'unknown')
|
||||
log(f"✓ Context saved (ID: {context_id}, Active time: {SAVE_INTERVAL_SECONDS}s)")
|
||||
return True
|
||||
else:
|
||||
log(f"✗ Failed to save: HTTP {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ Error saving context: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - called every minute by Task Scheduler"""
|
||||
config = load_config()
|
||||
state = load_state()
|
||||
|
||||
# Check if Claude is active
|
||||
if is_claude_active():
|
||||
# Increment active time (60 seconds per check)
|
||||
state["active_seconds"] += 60
|
||||
|
||||
# Check if we've reached the save interval
|
||||
if state["active_seconds"] >= SAVE_INTERVAL_SECONDS:
|
||||
log(f"{SAVE_INTERVAL_SECONDS}s active time reached - saving context")
|
||||
|
||||
project_id = detect_project_id()
|
||||
if save_periodic_context(config, project_id):
|
||||
state["last_save"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Reset timer
|
||||
state["active_seconds"] = 0
|
||||
|
||||
save_state(state)
|
||||
else:
|
||||
# Not active - don't increment timer but save state
|
||||
save_state(state)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as e:
|
||||
log(f"Fatal error: {e}")
|
||||
sys.exit(1)
|
||||
11
.claude/hooks/periodic_save_windows.bat
Normal file
11
.claude/hooks/periodic_save_windows.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
REM Windows wrapper for periodic context save
|
||||
REM Can be run from Task Scheduler every minute
|
||||
|
||||
cd /d D:\ClaudeTools
|
||||
|
||||
REM Run the check-and-save script
|
||||
python .claude\hooks\periodic_save_check.py
|
||||
|
||||
REM Exit silently
|
||||
exit /b 0
|
||||
69
.claude/hooks/setup_periodic_save.ps1
Normal file
69
.claude/hooks/setup_periodic_save.ps1
Normal file
@@ -0,0 +1,69 @@
|
||||
# Setup Periodic Context Save - Windows Task Scheduler
|
||||
# This script creates a scheduled task to run periodic_save_check.py every minute
|
||||
# Uses pythonw.exe to run without console window
|
||||
|
||||
$TaskName = "ClaudeTools - Periodic Context Save"
|
||||
$ScriptPath = "D:\ClaudeTools\.claude\hooks\periodic_save_check.py"
|
||||
$WorkingDir = "D:\ClaudeTools"
|
||||
|
||||
# Use pythonw.exe instead of python.exe to run without console window
|
||||
$PythonExe = (Get-Command python).Source
|
||||
$PythonDir = Split-Path $PythonExe -Parent
|
||||
$PythonwPath = Join-Path $PythonDir "pythonw.exe"
|
||||
|
||||
# Fallback to python.exe if pythonw.exe doesn't exist (shouldn't happen)
|
||||
if (-not (Test-Path $PythonwPath)) {
|
||||
Write-Warning "pythonw.exe not found at $PythonwPath, falling back to python.exe"
|
||||
$PythonwPath = $PythonExe
|
||||
}
|
||||
|
||||
# Check if task already exists
|
||||
$ExistingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ExistingTask) {
|
||||
Write-Host "Task '$TaskName' already exists. Removing old task..."
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
# Create action to run Python script with pythonw.exe (no console window)
|
||||
$Action = New-ScheduledTaskAction -Execute $PythonwPath `
|
||||
-Argument $ScriptPath `
|
||||
-WorkingDirectory $WorkingDir
|
||||
|
||||
# Create trigger to run every minute (indefinitely)
|
||||
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1)
|
||||
|
||||
# Create settings - Hidden and DisallowStartIfOnBatteries set to false
|
||||
$Settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||
-Hidden
|
||||
|
||||
# Create principal (run as current user, no window)
|
||||
$Principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U
|
||||
|
||||
# Register the task
|
||||
Register-ScheduledTask -TaskName $TaskName `
|
||||
-Action $Action `
|
||||
-Trigger $Trigger `
|
||||
-Settings $Settings `
|
||||
-Principal $Principal `
|
||||
-Description "Automatically saves Claude Code context every 5 minutes of active work"
|
||||
|
||||
Write-Host "[SUCCESS] Scheduled task created successfully!"
|
||||
Write-Host ""
|
||||
Write-Host "Task Name: $TaskName"
|
||||
Write-Host "Runs: Every 1 minute (HIDDEN - no console window)"
|
||||
Write-Host "Action: Checks activity and saves context every 5 minutes"
|
||||
Write-Host "Executable: $PythonwPath (pythonw.exe = no window)"
|
||||
Write-Host ""
|
||||
Write-Host "To verify task is hidden:"
|
||||
Write-Host " Get-ScheduledTask -TaskName '$TaskName' | Select-Object -ExpandProperty Settings"
|
||||
Write-Host ""
|
||||
Write-Host "To remove:"
|
||||
Write-Host " Unregister-ScheduledTask -TaskName '$TaskName' -Confirm:`$false"
|
||||
Write-Host ""
|
||||
Write-Host "View logs:"
|
||||
Write-Host ' Get-Content D:\ClaudeTools\.claude\periodic-save.log -Tail 20'
|
||||
110
.claude/hooks/sync-contexts
Normal file
110
.claude/hooks/sync-contexts
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Sync Queued Contexts to Database
|
||||
# Uploads any locally queued contexts to the central API
|
||||
# Can be run manually or called automatically by hooks
|
||||
#
|
||||
# Usage: bash .claude/hooks/sync-contexts
|
||||
#
|
||||
|
||||
# Load configuration
|
||||
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CONFIG_FILE="$CLAUDE_DIR/context-recall-config.env"
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
||||
PENDING_DIR="$QUEUE_DIR/pending"
|
||||
UPLOADED_DIR="$QUEUE_DIR/uploaded"
|
||||
FAILED_DIR="$QUEUE_DIR/failed"
|
||||
|
||||
# Exit if no JWT token
|
||||
if [ -z "$JWT_TOKEN" ]; then
|
||||
echo "ERROR: No JWT token available" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create directories if they don't exist
|
||||
mkdir -p "$PENDING_DIR" "$UPLOADED_DIR" "$FAILED_DIR" 2>/dev/null
|
||||
|
||||
# Check if there are any pending files
|
||||
PENDING_COUNT=$(find "$PENDING_DIR" -type f -name "*.json" 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$PENDING_COUNT" -eq 0 ]; then
|
||||
# No pending contexts to sync
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==================================="
|
||||
echo "Syncing Queued Contexts"
|
||||
echo "==================================="
|
||||
echo "Found $PENDING_COUNT pending context(s)"
|
||||
echo ""
|
||||
|
||||
# Process each pending file
|
||||
SUCCESS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
for QUEUE_FILE in "$PENDING_DIR"/*.json; do
|
||||
# Skip if no files match
|
||||
[ -e "$QUEUE_FILE" ] || continue
|
||||
|
||||
FILENAME=$(basename "$QUEUE_FILE")
|
||||
echo "Processing: $FILENAME"
|
||||
|
||||
# Read the payload
|
||||
PAYLOAD=$(cat "$QUEUE_FILE")
|
||||
|
||||
# Determine endpoint based on filename
|
||||
if [[ "$FILENAME" == *"_state.json" ]]; then
|
||||
ENDPOINT="${API_URL}/api/project-states"
|
||||
else
|
||||
ENDPOINT="${API_URL}/api/conversation-contexts"
|
||||
fi
|
||||
|
||||
# Try to POST to API
|
||||
RESPONSE=$(curl -s --max-time 10 -w "\n%{http_code}" \
|
||||
-X POST "$ENDPOINT" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" 2>/dev/null)
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||
# Success - move to uploaded directory
|
||||
mv "$QUEUE_FILE" "$UPLOADED_DIR/"
|
||||
echo " ✓ Uploaded successfully"
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
# Failed - move to failed directory for manual review
|
||||
mv "$QUEUE_FILE" "$FAILED_DIR/"
|
||||
echo " ✗ Upload failed (HTTP $HTTP_CODE) - moved to failed/"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
echo "Sync Complete"
|
||||
echo "==================================="
|
||||
echo "Successful: $SUCCESS_COUNT"
|
||||
echo "Failed: $FAIL_COUNT"
|
||||
echo ""
|
||||
|
||||
# Clean up old uploaded files (keep last 100)
|
||||
UPLOADED_COUNT=$(find "$UPLOADED_DIR" -type f -name "*.json" 2>/dev/null | wc -l)
|
||||
if [ "$UPLOADED_COUNT" -gt 100 ]; then
|
||||
echo "Cleaning up old uploaded contexts (keeping last 100)..."
|
||||
find "$UPLOADED_DIR" -type f -name "*.json" -printf '%T@ %p\n' | \
|
||||
sort -n | \
|
||||
head -n -100 | \
|
||||
cut -d' ' -f2- | \
|
||||
xargs rm -f
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,13 +1,14 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: task-complete
|
||||
# Claude Code Hook: task-complete (v2 - with offline support)
|
||||
# Runs AFTER a task is completed
|
||||
# Saves conversation context to the database for future recall
|
||||
# FALLBACK: Queues locally when API is unavailable, syncs later
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://localhost:8000)
|
||||
# CLAUDE_API_URL - API base URL (default: http://172.16.3.30:8001)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# TASK_SUMMARY - Summary of completed task (auto-generated by Claude)
|
||||
# TASK_FILES - Files modified during task (comma-separated)
|
||||
@@ -20,9 +21,15 @@ if [ -f "$CONFIG_FILE" ]; then
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://localhost:8000}"
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
|
||||
# Local storage paths
|
||||
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
||||
PENDING_DIR="$QUEUE_DIR/pending"
|
||||
UPLOADED_DIR="$QUEUE_DIR/uploaded"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
@@ -42,13 +49,17 @@ else
|
||||
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# Exit if no project ID or JWT token
|
||||
if [ -z "$PROJECT_ID" ] || [ -z "$JWT_TOKEN" ]; then
|
||||
# Exit if no project ID
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create queue directories if they don't exist
|
||||
mkdir -p "$PENDING_DIR" "$UPLOADED_DIR" 2>/dev/null
|
||||
|
||||
# Gather task information
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
TIMESTAMP_FILENAME=$(date -u +"%Y%m%d_%H%M%S")
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "none")
|
||||
|
||||
@@ -104,13 +115,6 @@ CONTEXT_PAYLOAD=$(cat <<EOF
|
||||
EOF
|
||||
)
|
||||
|
||||
# POST to conversation-contexts endpoint
|
||||
RESPONSE=$(curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$CONTEXT_PAYLOAD" 2>/dev/null)
|
||||
|
||||
# Update project state
|
||||
PROJECT_STATE_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
@@ -126,15 +130,53 @@ PROJECT_STATE_PAYLOAD=$(cat <<EOF
|
||||
EOF
|
||||
)
|
||||
|
||||
curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/project-states" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PROJECT_STATE_PAYLOAD" 2>/dev/null >/dev/null
|
||||
# Try to POST to API if we have a JWT token
|
||||
API_SUCCESS=false
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
RESPONSE=$(curl -s --max-time 5 -w "\n%{http_code}" \
|
||||
-X POST "${API_URL}/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$CONTEXT_PAYLOAD" 2>/dev/null)
|
||||
|
||||
# Log success (optional - comment out for silent operation)
|
||||
if [ -n "$RESPONSE" ]; then
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||
API_SUCCESS=true
|
||||
|
||||
# Also update project state
|
||||
curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/project-states" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PROJECT_STATE_PAYLOAD" 2>/dev/null >/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# If API call failed, queue locally
|
||||
if [ "$API_SUCCESS" = "false" ]; then
|
||||
# Save context to pending queue
|
||||
QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_context.json"
|
||||
echo "$CONTEXT_PAYLOAD" > "$QUEUE_FILE"
|
||||
|
||||
# Save project state to pending queue
|
||||
STATE_QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_state.json"
|
||||
echo "$PROJECT_STATE_PAYLOAD" > "$STATE_QUEUE_FILE"
|
||||
|
||||
echo "⚠ Context queued locally (API unavailable) - will sync when online" >&2
|
||||
|
||||
# Try to sync in background (opportunistic)
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
else
|
||||
echo "✓ Context saved to database" >&2
|
||||
|
||||
# Trigger background sync of any queued items
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
182
.claude/hooks/task-complete-v2
Normal file
182
.claude/hooks/task-complete-v2
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: task-complete (v2 - with offline support)
|
||||
# Runs AFTER a task is completed
|
||||
# Saves conversation context to the database for future recall
|
||||
# FALLBACK: Queues locally when API is unavailable, syncs later
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://172.16.3.30:8001)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# TASK_SUMMARY - Summary of completed task (auto-generated by Claude)
|
||||
# TASK_FILES - Files modified during task (comma-separated)
|
||||
#
|
||||
|
||||
# Load configuration if exists
|
||||
CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/context-recall-config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
|
||||
# Local storage paths
|
||||
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
||||
PENDING_DIR="$QUEUE_DIR/pending"
|
||||
UPLOADED_DIR="$QUEUE_DIR/uploaded"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect project ID (same logic as user-prompt-submit)
|
||||
if [ -z "$CLAUDE_PROJECT_ID" ]; then
|
||||
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
||||
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
|
||||
if [ -n "$GIT_REMOTE" ]; then
|
||||
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
else
|
||||
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# Exit if no project ID
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create queue directories if they don't exist
|
||||
mkdir -p "$PENDING_DIR" "$UPLOADED_DIR" 2>/dev/null
|
||||
|
||||
# Gather task information
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
TIMESTAMP_FILENAME=$(date -u +"%Y%m%d_%H%M%S")
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "none")
|
||||
|
||||
# Get recent git changes
|
||||
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | head -10 | tr '\n' ',' | sed 's/,$//')
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
CHANGED_FILES="${TASK_FILES:-}"
|
||||
fi
|
||||
|
||||
# Create task summary
|
||||
if [ -z "$TASK_SUMMARY" ]; then
|
||||
# Generate basic summary from git log if no summary provided
|
||||
TASK_SUMMARY=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "Task completed")
|
||||
fi
|
||||
|
||||
# Build context payload
|
||||
CONTEXT_TITLE="Session: ${TIMESTAMP}"
|
||||
CONTEXT_TYPE="session_summary"
|
||||
RELEVANCE_SCORE=7.0
|
||||
|
||||
# Create dense summary
|
||||
DENSE_SUMMARY="Task completed on branch '${GIT_BRANCH}' (commit: ${GIT_COMMIT}).
|
||||
|
||||
Summary: ${TASK_SUMMARY}
|
||||
|
||||
Modified files: ${CHANGED_FILES:-none}
|
||||
|
||||
Timestamp: ${TIMESTAMP}"
|
||||
|
||||
# Escape JSON strings
|
||||
escape_json() {
|
||||
echo "$1" | python3 -c "import sys, json; print(json.dumps(sys.stdin.read())[1:-1])"
|
||||
}
|
||||
|
||||
ESCAPED_TITLE=$(escape_json "$CONTEXT_TITLE")
|
||||
ESCAPED_SUMMARY=$(escape_json "$DENSE_SUMMARY")
|
||||
|
||||
# Save context to database
|
||||
CONTEXT_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"project_id": "${PROJECT_ID}",
|
||||
"context_type": "${CONTEXT_TYPE}",
|
||||
"title": ${ESCAPED_TITLE},
|
||||
"dense_summary": ${ESCAPED_SUMMARY},
|
||||
"relevance_score": ${RELEVANCE_SCORE},
|
||||
"metadata": {
|
||||
"git_branch": "${GIT_BRANCH}",
|
||||
"git_commit": "${GIT_COMMIT}",
|
||||
"files_modified": "${CHANGED_FILES}",
|
||||
"timestamp": "${TIMESTAMP}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Update project state
|
||||
PROJECT_STATE_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"project_id": "${PROJECT_ID}",
|
||||
"state_data": {
|
||||
"last_task_completion": "${TIMESTAMP}",
|
||||
"last_git_commit": "${GIT_COMMIT}",
|
||||
"last_git_branch": "${GIT_BRANCH}",
|
||||
"recent_files": "${CHANGED_FILES}"
|
||||
},
|
||||
"state_type": "task_completion"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Try to POST to API if we have a JWT token
|
||||
API_SUCCESS=false
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
RESPONSE=$(curl -s --max-time 5 -w "\n%{http_code}" \
|
||||
-X POST "${API_URL}/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$CONTEXT_PAYLOAD" 2>/dev/null)
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||
API_SUCCESS=true
|
||||
|
||||
# Also update project state
|
||||
curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/project-states" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PROJECT_STATE_PAYLOAD" 2>/dev/null >/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# If API call failed, queue locally
|
||||
if [ "$API_SUCCESS" = "false" ]; then
|
||||
# Save context to pending queue
|
||||
QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_context.json"
|
||||
echo "$CONTEXT_PAYLOAD" > "$QUEUE_FILE"
|
||||
|
||||
# Save project state to pending queue
|
||||
STATE_QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_state.json"
|
||||
echo "$PROJECT_STATE_PAYLOAD" > "$STATE_QUEUE_FILE"
|
||||
|
||||
echo "⚠ Context queued locally (API unavailable) - will sync when online" >&2
|
||||
|
||||
# Try to sync in background (opportunistic)
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
else
|
||||
echo "✓ Context saved to database" >&2
|
||||
|
||||
# Trigger background sync of any queued items
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
140
.claude/hooks/task-complete.v1.backup
Normal file
140
.claude/hooks/task-complete.v1.backup
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: task-complete
|
||||
# Runs AFTER a task is completed
|
||||
# Saves conversation context to the database for future recall
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://localhost:8000)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# TASK_SUMMARY - Summary of completed task (auto-generated by Claude)
|
||||
# TASK_FILES - Files modified during task (comma-separated)
|
||||
#
|
||||
|
||||
# Load configuration if exists
|
||||
CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/context-recall-config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://localhost:8000}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect project ID (same logic as user-prompt-submit)
|
||||
if [ -z "$CLAUDE_PROJECT_ID" ]; then
|
||||
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
||||
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
|
||||
if [ -n "$GIT_REMOTE" ]; then
|
||||
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
else
|
||||
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# Exit if no project ID or JWT token
|
||||
if [ -z "$PROJECT_ID" ] || [ -z "$JWT_TOKEN" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Gather task information
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "none")
|
||||
|
||||
# Get recent git changes
|
||||
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | head -10 | tr '\n' ',' | sed 's/,$//')
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
CHANGED_FILES="${TASK_FILES:-}"
|
||||
fi
|
||||
|
||||
# Create task summary
|
||||
if [ -z "$TASK_SUMMARY" ]; then
|
||||
# Generate basic summary from git log if no summary provided
|
||||
TASK_SUMMARY=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "Task completed")
|
||||
fi
|
||||
|
||||
# Build context payload
|
||||
CONTEXT_TITLE="Session: ${TIMESTAMP}"
|
||||
CONTEXT_TYPE="session_summary"
|
||||
RELEVANCE_SCORE=7.0
|
||||
|
||||
# Create dense summary
|
||||
DENSE_SUMMARY="Task completed on branch '${GIT_BRANCH}' (commit: ${GIT_COMMIT}).
|
||||
|
||||
Summary: ${TASK_SUMMARY}
|
||||
|
||||
Modified files: ${CHANGED_FILES:-none}
|
||||
|
||||
Timestamp: ${TIMESTAMP}"
|
||||
|
||||
# Escape JSON strings
|
||||
escape_json() {
|
||||
echo "$1" | python3 -c "import sys, json; print(json.dumps(sys.stdin.read())[1:-1])"
|
||||
}
|
||||
|
||||
ESCAPED_TITLE=$(escape_json "$CONTEXT_TITLE")
|
||||
ESCAPED_SUMMARY=$(escape_json "$DENSE_SUMMARY")
|
||||
|
||||
# Save context to database
|
||||
CONTEXT_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"project_id": "${PROJECT_ID}",
|
||||
"context_type": "${CONTEXT_TYPE}",
|
||||
"title": ${ESCAPED_TITLE},
|
||||
"dense_summary": ${ESCAPED_SUMMARY},
|
||||
"relevance_score": ${RELEVANCE_SCORE},
|
||||
"metadata": {
|
||||
"git_branch": "${GIT_BRANCH}",
|
||||
"git_commit": "${GIT_COMMIT}",
|
||||
"files_modified": "${CHANGED_FILES}",
|
||||
"timestamp": "${TIMESTAMP}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# POST to conversation-contexts endpoint
|
||||
RESPONSE=$(curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/conversation-contexts" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$CONTEXT_PAYLOAD" 2>/dev/null)
|
||||
|
||||
# Update project state
|
||||
PROJECT_STATE_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"project_id": "${PROJECT_ID}",
|
||||
"state_data": {
|
||||
"last_task_completion": "${TIMESTAMP}",
|
||||
"last_git_commit": "${GIT_COMMIT}",
|
||||
"last_git_branch": "${GIT_BRANCH}",
|
||||
"recent_files": "${CHANGED_FILES}"
|
||||
},
|
||||
"state_type": "task_completion"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
curl -s --max-time 5 \
|
||||
-X POST "${API_URL}/api/project-states" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PROJECT_STATE_PAYLOAD" 2>/dev/null >/dev/null
|
||||
|
||||
# Log success (optional - comment out for silent operation)
|
||||
if [ -n "$RESPONSE" ]; then
|
||||
echo "✓ Context saved to database" >&2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
85
.claude/hooks/update_to_invisible.ps1
Normal file
85
.claude/hooks/update_to_invisible.ps1
Normal file
@@ -0,0 +1,85 @@
|
||||
# Quick Update - Make Existing Periodic Save Task Invisible
|
||||
# This script updates the existing task to run without showing a window
|
||||
|
||||
$TaskName = "ClaudeTools - Periodic Context Save"
|
||||
|
||||
Write-Host "Updating task '$TaskName' to run invisibly..."
|
||||
Write-Host ""
|
||||
|
||||
# Check if task exists
|
||||
$Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $Task) {
|
||||
Write-Host "ERROR: Task '$TaskName' not found."
|
||||
Write-Host "Run setup_periodic_save.ps1 to create it first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find pythonw.exe path
|
||||
$PythonExe = (Get-Command python).Source
|
||||
$PythonDir = Split-Path $PythonExe -Parent
|
||||
$PythonwPath = Join-Path $PythonDir "pythonw.exe"
|
||||
|
||||
if (-not (Test-Path $PythonwPath)) {
|
||||
Write-Host "ERROR: pythonw.exe not found at $PythonwPath"
|
||||
Write-Host "Please reinstall Python to get pythonw.exe"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Found pythonw.exe at: $PythonwPath"
|
||||
|
||||
# Update the action to use pythonw.exe
|
||||
$NewAction = New-ScheduledTaskAction -Execute $PythonwPath `
|
||||
-Argument "D:\ClaudeTools\.claude\hooks\periodic_save_check.py" `
|
||||
-WorkingDirectory "D:\ClaudeTools"
|
||||
|
||||
# Update settings to be hidden
|
||||
$NewSettings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||
-Hidden
|
||||
|
||||
# Update principal to run in background (S4U = Service-For-User)
|
||||
$NewPrincipal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U
|
||||
|
||||
# Get existing trigger (preserve it)
|
||||
$ExistingTrigger = $Task.Triggers
|
||||
|
||||
# Update the task
|
||||
Set-ScheduledTask -TaskName $TaskName `
|
||||
-Action $NewAction `
|
||||
-Settings $NewSettings `
|
||||
-Principal $NewPrincipal `
|
||||
-Trigger $ExistingTrigger | Out-Null
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[SUCCESS] Task updated successfully!"
|
||||
Write-Host ""
|
||||
Write-Host "Changes made:"
|
||||
Write-Host " 1. Changed executable: python.exe -> pythonw.exe"
|
||||
Write-Host " 2. Set task to Hidden"
|
||||
Write-Host " 3. Changed LogonType: Interactive -> S4U (background)"
|
||||
Write-Host ""
|
||||
Write-Host "Verification:"
|
||||
|
||||
# Show current settings
|
||||
$UpdatedTask = Get-ScheduledTask -TaskName $TaskName
|
||||
$Settings = $UpdatedTask.Settings
|
||||
$Action = $UpdatedTask.Actions[0]
|
||||
$Principal = $UpdatedTask.Principal
|
||||
|
||||
Write-Host " Executable: $($Action.Execute)"
|
||||
Write-Host " Hidden: $($Settings.Hidden)"
|
||||
Write-Host " LogonType: $($Principal.LogonType)"
|
||||
Write-Host ""
|
||||
|
||||
if ($Settings.Hidden -and $Action.Execute -like "*pythonw.exe" -and $Principal.LogonType -eq "S4U") {
|
||||
Write-Host "[OK] All settings correct - task will run invisibly!"
|
||||
} else {
|
||||
Write-Host "[WARNING] Some settings may not be correct - please verify manually"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "The task will now run invisibly without showing any console window."
|
||||
Write-Host ""
|
||||
@@ -1,13 +1,14 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: user-prompt-submit
|
||||
# Claude Code Hook: user-prompt-submit (v2 - with offline support)
|
||||
# Runs BEFORE each user message is processed
|
||||
# Injects relevant context from the database into the conversation
|
||||
# FALLBACK: Uses local cache when API is unavailable
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://localhost:8000)
|
||||
# CLAUDE_API_URL - API base URL (default: http://172.16.3.30:8001)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# MIN_RELEVANCE_SCORE - Minimum score for context (default: 5.0)
|
||||
# MAX_CONTEXTS - Maximum number of contexts to retrieve (default: 10)
|
||||
@@ -20,11 +21,16 @@ if [ -f "$CONFIG_FILE" ]; then
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://localhost:8000}"
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
MIN_SCORE="${MIN_RELEVANCE_SCORE:-5.0}"
|
||||
MAX_ITEMS="${MAX_CONTEXTS:-10}"
|
||||
|
||||
# Local storage paths
|
||||
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CACHE_DIR="$CLAUDE_DIR/context-cache"
|
||||
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
@@ -53,41 +59,74 @@ if [ -z "$PROJECT_ID" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Exit if no JWT token
|
||||
if [ -z "$JWT_TOKEN" ]; then
|
||||
exit 0
|
||||
# Create cache directory if it doesn't exist
|
||||
PROJECT_CACHE_DIR="$CACHE_DIR/$PROJECT_ID"
|
||||
mkdir -p "$PROJECT_CACHE_DIR" 2>/dev/null
|
||||
|
||||
# Try to sync any queued contexts first (opportunistic)
|
||||
if [ -d "$QUEUE_DIR/pending" ] && [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# Build API request URL
|
||||
RECALL_URL="${API_URL}/api/conversation-contexts/recall"
|
||||
QUERY_PARAMS="project_id=${PROJECT_ID}&limit=${MAX_ITEMS}&min_relevance_score=${MIN_SCORE}"
|
||||
|
||||
# Fetch context from API (with timeout and error handling)
|
||||
CONTEXT_RESPONSE=$(curl -s --max-time 3 \
|
||||
"${RECALL_URL}?${QUERY_PARAMS}" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Accept: application/json" 2>/dev/null)
|
||||
# Try to fetch context from API (with timeout and error handling)
|
||||
API_AVAILABLE=false
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
CONTEXT_RESPONSE=$(curl -s --max-time 3 \
|
||||
"${RECALL_URL}?${QUERY_PARAMS}" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Accept: application/json" 2>/dev/null)
|
||||
|
||||
# Check if request was successful
|
||||
if [ $? -ne 0 ] || [ -z "$CONTEXT_RESPONSE" ]; then
|
||||
# Silent failure - API unavailable
|
||||
exit 0
|
||||
if [ $? -eq 0 ] && [ -n "$CONTEXT_RESPONSE" ]; then
|
||||
# Check if response is valid JSON (not an error)
|
||||
echo "$CONTEXT_RESPONSE" | python3 -c "import sys, json; json.load(sys.stdin)" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
API_AVAILABLE=true
|
||||
# Save to cache for offline use
|
||||
echo "$CONTEXT_RESPONSE" > "$PROJECT_CACHE_DIR/latest.json"
|
||||
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > "$PROJECT_CACHE_DIR/last_updated"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parse and format context (expects JSON array of context objects)
|
||||
# Example response: [{"title": "...", "dense_summary": "...", "relevance_score": 8.5}, ...]
|
||||
# Fallback to local cache if API unavailable
|
||||
if [ "$API_AVAILABLE" = "false" ]; then
|
||||
if [ -f "$PROJECT_CACHE_DIR/latest.json" ]; then
|
||||
CONTEXT_RESPONSE=$(cat "$PROJECT_CACHE_DIR/latest.json")
|
||||
CACHE_AGE="unknown"
|
||||
if [ -f "$PROJECT_CACHE_DIR/last_updated" ]; then
|
||||
CACHE_AGE=$(cat "$PROJECT_CACHE_DIR/last_updated")
|
||||
fi
|
||||
echo "<!-- Using cached context (API unavailable) - Last updated: $CACHE_AGE -->" >&2
|
||||
else
|
||||
# No cache available, exit silently
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parse and format context
|
||||
CONTEXT_COUNT=$(echo "$CONTEXT_RESPONSE" | grep -o '"id"' | wc -l)
|
||||
|
||||
if [ "$CONTEXT_COUNT" -gt 0 ]; then
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) -->"
|
||||
if [ "$API_AVAILABLE" = "true" ]; then
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) from API -->"
|
||||
else
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) from LOCAL CACHE (offline mode) -->"
|
||||
fi
|
||||
echo ""
|
||||
echo "## 📚 Previous Context"
|
||||
echo ""
|
||||
echo "The following context has been automatically recalled from previous sessions:"
|
||||
if [ "$API_AVAILABLE" = "false" ]; then
|
||||
echo "⚠️ **Offline Mode** - Using cached context (API unavailable)"
|
||||
echo ""
|
||||
fi
|
||||
echo "The following context has been automatically recalled:"
|
||||
echo ""
|
||||
|
||||
# Extract and format each context entry
|
||||
# Note: This uses simple text parsing. For production, consider using jq if available.
|
||||
echo "$CONTEXT_RESPONSE" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
@@ -111,7 +150,11 @@ except:
|
||||
" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "*This context was automatically injected to help maintain continuity across sessions.*"
|
||||
if [ "$API_AVAILABLE" = "true" ]; then
|
||||
echo "*Context automatically injected to maintain continuity across sessions.*"
|
||||
else
|
||||
echo "*Context from local cache - new context will sync when API is available.*"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
|
||||
162
.claude/hooks/user-prompt-submit-v2
Normal file
162
.claude/hooks/user-prompt-submit-v2
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: user-prompt-submit (v2 - with offline support)
|
||||
# Runs BEFORE each user message is processed
|
||||
# Injects relevant context from the database into the conversation
|
||||
# FALLBACK: Uses local cache when API is unavailable
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://172.16.3.30:8001)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# MIN_RELEVANCE_SCORE - Minimum score for context (default: 5.0)
|
||||
# MAX_CONTEXTS - Maximum number of contexts to retrieve (default: 10)
|
||||
#
|
||||
|
||||
# Load configuration if exists
|
||||
CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/context-recall-config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
MIN_SCORE="${MIN_RELEVANCE_SCORE:-5.0}"
|
||||
MAX_ITEMS="${MAX_CONTEXTS:-10}"
|
||||
|
||||
# Local storage paths
|
||||
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CACHE_DIR="$CLAUDE_DIR/context-cache"
|
||||
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect project ID from git repo if not set
|
||||
if [ -z "$CLAUDE_PROJECT_ID" ]; then
|
||||
# Try to get from git config
|
||||
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
||||
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
# Try to derive from git remote URL
|
||||
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
|
||||
if [ -n "$GIT_REMOTE" ]; then
|
||||
# Hash the remote URL to create a consistent ID
|
||||
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
else
|
||||
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# Exit if no project ID available
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
# Silent exit - no context available
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create cache directory if it doesn't exist
|
||||
PROJECT_CACHE_DIR="$CACHE_DIR/$PROJECT_ID"
|
||||
mkdir -p "$PROJECT_CACHE_DIR" 2>/dev/null
|
||||
|
||||
# Try to sync any queued contexts first (opportunistic)
|
||||
if [ -d "$QUEUE_DIR/pending" ] && [ -n "$JWT_TOKEN" ]; then
|
||||
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# Build API request URL
|
||||
RECALL_URL="${API_URL}/api/conversation-contexts/recall"
|
||||
QUERY_PARAMS="project_id=${PROJECT_ID}&limit=${MAX_ITEMS}&min_relevance_score=${MIN_SCORE}"
|
||||
|
||||
# Try to fetch context from API (with timeout and error handling)
|
||||
API_AVAILABLE=false
|
||||
if [ -n "$JWT_TOKEN" ]; then
|
||||
CONTEXT_RESPONSE=$(curl -s --max-time 3 \
|
||||
"${RECALL_URL}?${QUERY_PARAMS}" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Accept: application/json" 2>/dev/null)
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$CONTEXT_RESPONSE" ]; then
|
||||
# Check if response is valid JSON (not an error)
|
||||
echo "$CONTEXT_RESPONSE" | python3 -c "import sys, json; json.load(sys.stdin)" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
API_AVAILABLE=true
|
||||
# Save to cache for offline use
|
||||
echo "$CONTEXT_RESPONSE" > "$PROJECT_CACHE_DIR/latest.json"
|
||||
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > "$PROJECT_CACHE_DIR/last_updated"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback to local cache if API unavailable
|
||||
if [ "$API_AVAILABLE" = "false" ]; then
|
||||
if [ -f "$PROJECT_CACHE_DIR/latest.json" ]; then
|
||||
CONTEXT_RESPONSE=$(cat "$PROJECT_CACHE_DIR/latest.json")
|
||||
CACHE_AGE="unknown"
|
||||
if [ -f "$PROJECT_CACHE_DIR/last_updated" ]; then
|
||||
CACHE_AGE=$(cat "$PROJECT_CACHE_DIR/last_updated")
|
||||
fi
|
||||
echo "<!-- Using cached context (API unavailable) - Last updated: $CACHE_AGE -->" >&2
|
||||
else
|
||||
# No cache available, exit silently
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parse and format context
|
||||
CONTEXT_COUNT=$(echo "$CONTEXT_RESPONSE" | grep -o '"id"' | wc -l)
|
||||
|
||||
if [ "$CONTEXT_COUNT" -gt 0 ]; then
|
||||
if [ "$API_AVAILABLE" = "true" ]; then
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) from API -->"
|
||||
else
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) from LOCAL CACHE (offline mode) -->"
|
||||
fi
|
||||
echo ""
|
||||
echo "## 📚 Previous Context"
|
||||
echo ""
|
||||
if [ "$API_AVAILABLE" = "false" ]; then
|
||||
echo "⚠️ **Offline Mode** - Using cached context (API unavailable)"
|
||||
echo ""
|
||||
fi
|
||||
echo "The following context has been automatically recalled:"
|
||||
echo ""
|
||||
|
||||
# Extract and format each context entry
|
||||
echo "$CONTEXT_RESPONSE" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
contexts = json.load(sys.stdin)
|
||||
if isinstance(contexts, list):
|
||||
for i, ctx in enumerate(contexts, 1):
|
||||
title = ctx.get('title', 'Untitled')
|
||||
summary = ctx.get('dense_summary', '')
|
||||
score = ctx.get('relevance_score', 0)
|
||||
ctx_type = ctx.get('context_type', 'unknown')
|
||||
|
||||
print(f'### {i}. {title} (Score: {score}/10)')
|
||||
print(f'*Type: {ctx_type}*')
|
||||
print()
|
||||
print(summary)
|
||||
print()
|
||||
print('---')
|
||||
print()
|
||||
except:
|
||||
pass
|
||||
" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
if [ "$API_AVAILABLE" = "true" ]; then
|
||||
echo "*Context automatically injected to maintain continuity across sessions.*"
|
||||
else
|
||||
echo "*Context from local cache - new context will sync when API is available.*"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Exit successfully
|
||||
exit 0
|
||||
119
.claude/hooks/user-prompt-submit.v1.backup
Normal file
119
.claude/hooks/user-prompt-submit.v1.backup
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claude Code Hook: user-prompt-submit
|
||||
# Runs BEFORE each user message is processed
|
||||
# Injects relevant context from the database into the conversation
|
||||
#
|
||||
# Expected environment variables:
|
||||
# CLAUDE_PROJECT_ID - UUID of the current project
|
||||
# JWT_TOKEN - Authentication token for API
|
||||
# CLAUDE_API_URL - API base URL (default: http://localhost:8000)
|
||||
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
||||
# MIN_RELEVANCE_SCORE - Minimum score for context (default: 5.0)
|
||||
# MAX_CONTEXTS - Maximum number of contexts to retrieve (default: 10)
|
||||
#
|
||||
|
||||
# Load configuration if exists
|
||||
CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/context-recall-config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Default values
|
||||
API_URL="${CLAUDE_API_URL:-http://localhost:8000}"
|
||||
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
||||
MIN_SCORE="${MIN_RELEVANCE_SCORE:-5.0}"
|
||||
MAX_ITEMS="${MAX_CONTEXTS:-10}"
|
||||
|
||||
# Exit early if disabled
|
||||
if [ "$ENABLED" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect project ID from git repo if not set
|
||||
if [ -z "$CLAUDE_PROJECT_ID" ]; then
|
||||
# Try to get from git config
|
||||
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
||||
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
# Try to derive from git remote URL
|
||||
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
|
||||
if [ -n "$GIT_REMOTE" ]; then
|
||||
# Hash the remote URL to create a consistent ID
|
||||
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
else
|
||||
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# Exit if no project ID available
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
# Silent exit - no context available
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Exit if no JWT token
|
||||
if [ -z "$JWT_TOKEN" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build API request URL
|
||||
RECALL_URL="${API_URL}/api/conversation-contexts/recall"
|
||||
QUERY_PARAMS="project_id=${PROJECT_ID}&limit=${MAX_ITEMS}&min_relevance_score=${MIN_SCORE}"
|
||||
|
||||
# Fetch context from API (with timeout and error handling)
|
||||
CONTEXT_RESPONSE=$(curl -s --max-time 3 \
|
||||
"${RECALL_URL}?${QUERY_PARAMS}" \
|
||||
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
||||
-H "Accept: application/json" 2>/dev/null)
|
||||
|
||||
# Check if request was successful
|
||||
if [ $? -ne 0 ] || [ -z "$CONTEXT_RESPONSE" ]; then
|
||||
# Silent failure - API unavailable
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse and format context (expects JSON array of context objects)
|
||||
# Example response: [{"title": "...", "dense_summary": "...", "relevance_score": 8.5}, ...]
|
||||
CONTEXT_COUNT=$(echo "$CONTEXT_RESPONSE" | grep -o '"id"' | wc -l)
|
||||
|
||||
if [ "$CONTEXT_COUNT" -gt 0 ]; then
|
||||
echo "<!-- Context Recall: Retrieved $CONTEXT_COUNT relevant context(s) -->"
|
||||
echo ""
|
||||
echo "## 📚 Previous Context"
|
||||
echo ""
|
||||
echo "The following context has been automatically recalled from previous sessions:"
|
||||
echo ""
|
||||
|
||||
# Extract and format each context entry
|
||||
# Note: This uses simple text parsing. For production, consider using jq if available.
|
||||
echo "$CONTEXT_RESPONSE" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
contexts = json.load(sys.stdin)
|
||||
if isinstance(contexts, list):
|
||||
for i, ctx in enumerate(contexts, 1):
|
||||
title = ctx.get('title', 'Untitled')
|
||||
summary = ctx.get('dense_summary', '')
|
||||
score = ctx.get('relevance_score', 0)
|
||||
ctx_type = ctx.get('context_type', 'unknown')
|
||||
|
||||
print(f'### {i}. {title} (Score: {score}/10)')
|
||||
print(f'*Type: {ctx_type}*')
|
||||
print()
|
||||
print(summary)
|
||||
print()
|
||||
print('---')
|
||||
print()
|
||||
except:
|
||||
pass
|
||||
" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "*This context was automatically injected to help maintain continuity across sessions.*"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Exit successfully
|
||||
exit 0
|
||||
Reference in New Issue
Block a user