0adaddacaa
* refactor: restructure project to Apps/frontend and Apps/backend - Move auto-claude-ui to Apps/frontend with feature-based architecture - Move auto-claude to Apps/backend - Switch from pnpm to npm for frontend - Update Node.js requirement to v24.12.0 LTS - Add pre-commit hooks for lint, typecheck, and security audit - Add commit-msg hook for conventional commits - Fix CommonJS compatibility issues (postcss.config, postinstall scripts) - Update README with comprehensive setup and contribution guidelines - Configure ESLint to ignore .cjs files - 0 npm vulnerabilities Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(refactor): clean code and move to npm * feat(refactor): clean code and move to npm * chore: update to v2.7.0, remove Docker deps (LadybugDB is embedded) * feat: v2.8.0 - update workflows and configs for Apps/ structure, npm * fix: resolve Python lint errors (F401, I001) * fix: update test paths for Apps/backend structure * fix: add missing facade files and update paths for Apps/backend structure - Fix ruff lint error I001 in auto_claude_tools.py - Create missing facade files to match upstream (agent, ci_discovery, critique, etc.) - Update test paths from auto-claude/ to Apps/backend/ - Update .pre-commit-config.yaml paths for Apps/ structure - Add pytest to pre-commit hooks (skip slow/integration/Windows-incompatible tests) - Fix Unicode encoding in test_agent_architecture.py for Windows Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat: improve readme * fix: new path * fix: correct release workflow and docs for Apps/ restructure - Fix ARM64 macOS build: pnpm → npm, auto-claude-ui → Apps/frontend - Fix artifact upload paths in release.yml - Update Node.js version to 24 for consistency - Update CLI-USAGE.md with Apps/backend paths - Update RELEASE.md with Apps/frontend/package.json paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: rename Apps/ to apps/ and fix backend path resolution - Rename Apps/ folder to apps/ for consistency with JS/Node conventions - Update all path references across CI/CD workflows, docs, and config files - Fix frontend Python path resolver to look for 'backend' instead of 'auto-claude' - Update path-resolver.ts to correctly find apps/backend in development mode This completes the Apps restructure from PR #122 and prepares for v2.8.0 release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(electron): correct preload script path from .js to .mjs electron-vite builds the preload script as ESM (index.mjs) but the main process was looking for CommonJS (index.js). This caused the preload to fail silently, making the app fall back to browser mock mode with fake data and non-functional IPC handlers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * - Introduced `dev:debug` script to enable debugging during development. - Added `dev:mcp` script for running the frontend in MCP mode. These enhancements streamline the development process for frontend developers. * refactor(memory): make Graphiti memory mandatory and remove Docker dependency Memory is now a core component of Auto Claude rather than optional: - Python 3.12+ is required for the backend (not just memory layer) - Graphiti is enabled by default in .env.example - Removed all FalkorDB/Docker references (migrated to embedded LadybugDB) - Deleted guides/DOCKER-SETUP.md and docker-handlers.ts - Updated onboarding UI to remove "optional" language - Updated all documentation to reflect LadybugDB architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add cross-platform Windows support for npm scripts - Add scripts/install-backend.js for cross-platform Python venv setup - Auto-detects Python 3.12 (py -3.12 on Windows, python3.12 on Unix) - Handles platform-specific venv paths - Add scripts/test-backend.js for cross-platform pytest execution - Update package.json to use Node.js scripts instead of shell commands - Update CONTRIBUTING.md with correct paths and instructions: - apps/backend/ and apps/frontend/ paths - Python 3.12 requirement (memory system now required) - Platform-specific install commands (winget, brew, apt) - npm instead of pnpm - Quick Start section with npm run install:all 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * remove doc * fix(frontend): correct Ollama detector script path after apps restructure The Ollama status check was failing because memory-handlers.ts was looking for ollama_model_detector.py at auto-claude/ but the script is now at apps/backend/ after the directory restructure. This caused "Ollama not running" to display even when Ollama was actually running and accessible. * chore: bump version to 2.7.2 Downgrade version from 2.8.0 to 2.7.2 as the Apps/ restructure is better suited as a patch release rather than a minor release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update package-lock.json for Windows compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs(contributing): add hotfix workflow and update paths for apps/ structure Add Git Flow hotfix workflow documentation with step-by-step guide and ASCII diagram showing the branching strategy. Update all paths from auto-claude/auto-claude-ui to apps/backend/apps/frontend and migrate package manager references from pnpm to npm to match the new project structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): remove duplicate ARM64 build from Intel runner The Intel runner was building both x64 and arm64 architectures, while a separate ARM64 runner also builds arm64 natively. This caused duplicate ARM64 builds, wasting CI resources. Now each runner builds only its native architecture: - Intel runner: x64 only - ARM64 runner: arm64 only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Alex Madera <e.a_madera@hotmail.com> Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
245 lines
8.5 KiB
Python
245 lines
8.5 KiB
Python
"""
|
|
Context Builder
|
|
===============
|
|
|
|
Main builder class that orchestrates context building for tasks.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
from dataclasses import asdict
|
|
from pathlib import Path
|
|
|
|
from .categorizer import FileCategorizer
|
|
from .graphiti_integration import fetch_graph_hints, is_graphiti_enabled
|
|
from .keyword_extractor import KeywordExtractor
|
|
from .models import FileMatch, TaskContext
|
|
from .pattern_discovery import PatternDiscoverer
|
|
from .search import CodeSearcher
|
|
from .service_matcher import ServiceMatcher
|
|
|
|
|
|
class ContextBuilder:
|
|
"""Builds task-specific context by searching the codebase."""
|
|
|
|
def __init__(self, project_dir: Path, project_index: dict | None = None):
|
|
self.project_dir = project_dir.resolve()
|
|
self.project_index = project_index or self._load_project_index()
|
|
|
|
# Initialize components
|
|
self.searcher = CodeSearcher(self.project_dir)
|
|
self.service_matcher = ServiceMatcher(self.project_index)
|
|
self.keyword_extractor = KeywordExtractor()
|
|
self.categorizer = FileCategorizer()
|
|
self.pattern_discoverer = PatternDiscoverer(self.project_dir)
|
|
|
|
def _load_project_index(self) -> dict:
|
|
"""Load project index from file or create new one (.auto-claude is the installed instance)."""
|
|
index_file = self.project_dir / ".auto-claude" / "project_index.json"
|
|
if index_file.exists():
|
|
with open(index_file) as f:
|
|
return json.load(f)
|
|
|
|
# Try to create one
|
|
from analyzer import analyze_project
|
|
|
|
return analyze_project(self.project_dir)
|
|
|
|
def build_context(
|
|
self,
|
|
task: str,
|
|
services: list[str] | None = None,
|
|
keywords: list[str] | None = None,
|
|
include_graph_hints: bool = True,
|
|
) -> TaskContext:
|
|
"""
|
|
Build context for a specific task.
|
|
|
|
Args:
|
|
task: Description of the task
|
|
services: List of service names to search (None = auto-detect)
|
|
keywords: Additional keywords to search for
|
|
include_graph_hints: Whether to include historical hints from Graphiti
|
|
|
|
Returns:
|
|
TaskContext with relevant files and patterns
|
|
"""
|
|
# Auto-detect services if not specified
|
|
if not services:
|
|
services = self.service_matcher.suggest_services(task)
|
|
|
|
# Extract keywords from task if not provided
|
|
if not keywords:
|
|
keywords = self.keyword_extractor.extract_keywords(task)
|
|
|
|
# Search each service
|
|
all_matches: list[FileMatch] = []
|
|
service_contexts = {}
|
|
|
|
for service_name in services:
|
|
service_info = self.project_index.get("services", {}).get(service_name)
|
|
if not service_info:
|
|
continue
|
|
|
|
service_path = Path(service_info.get("path", service_name))
|
|
if not service_path.is_absolute():
|
|
service_path = self.project_dir / service_path
|
|
|
|
# Search this service
|
|
matches = self.searcher.search_service(service_path, service_name, keywords)
|
|
all_matches.extend(matches)
|
|
|
|
# Load or generate service context
|
|
service_contexts[service_name] = self._get_service_context(
|
|
service_path, service_name, service_info
|
|
)
|
|
|
|
# Categorize matches
|
|
files_to_modify, files_to_reference = self.categorizer.categorize_matches(
|
|
all_matches, task
|
|
)
|
|
|
|
# Discover patterns from reference files
|
|
patterns = self.pattern_discoverer.discover_patterns(
|
|
files_to_reference, keywords
|
|
)
|
|
|
|
# Get graph hints (synchronously wrap async call)
|
|
graph_hints = []
|
|
if include_graph_hints and is_graphiti_enabled():
|
|
try:
|
|
# Run the async function in a new event loop if necessary
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
# We're already in an async context - this shouldn't happen in CLI
|
|
# but handle it gracefully
|
|
graph_hints = []
|
|
except RuntimeError:
|
|
# No event loop running - create one
|
|
graph_hints = asyncio.run(
|
|
fetch_graph_hints(task, str(self.project_dir))
|
|
)
|
|
except Exception:
|
|
# Graphiti is optional - fail gracefully
|
|
graph_hints = []
|
|
|
|
return TaskContext(
|
|
task_description=task,
|
|
scoped_services=services,
|
|
files_to_modify=[
|
|
asdict(f) if isinstance(f, FileMatch) else f for f in files_to_modify
|
|
],
|
|
files_to_reference=[
|
|
asdict(f) if isinstance(f, FileMatch) else f for f in files_to_reference
|
|
],
|
|
patterns_discovered=patterns,
|
|
service_contexts=service_contexts,
|
|
graph_hints=graph_hints,
|
|
)
|
|
|
|
async def build_context_async(
|
|
self,
|
|
task: str,
|
|
services: list[str] | None = None,
|
|
keywords: list[str] | None = None,
|
|
include_graph_hints: bool = True,
|
|
) -> TaskContext:
|
|
"""
|
|
Build context for a specific task (async version).
|
|
|
|
This version is preferred when called from async code as it can
|
|
properly await the graph hints retrieval.
|
|
|
|
Args:
|
|
task: Description of the task
|
|
services: List of service names to search (None = auto-detect)
|
|
keywords: Additional keywords to search for
|
|
include_graph_hints: Whether to include historical hints from Graphiti
|
|
|
|
Returns:
|
|
TaskContext with relevant files and patterns
|
|
"""
|
|
# Auto-detect services if not specified
|
|
if not services:
|
|
services = self.service_matcher.suggest_services(task)
|
|
|
|
# Extract keywords from task if not provided
|
|
if not keywords:
|
|
keywords = self.keyword_extractor.extract_keywords(task)
|
|
|
|
# Search each service
|
|
all_matches: list[FileMatch] = []
|
|
service_contexts = {}
|
|
|
|
for service_name in services:
|
|
service_info = self.project_index.get("services", {}).get(service_name)
|
|
if not service_info:
|
|
continue
|
|
|
|
service_path = Path(service_info.get("path", service_name))
|
|
if not service_path.is_absolute():
|
|
service_path = self.project_dir / service_path
|
|
|
|
# Search this service
|
|
matches = self.searcher.search_service(service_path, service_name, keywords)
|
|
all_matches.extend(matches)
|
|
|
|
# Load or generate service context
|
|
service_contexts[service_name] = self._get_service_context(
|
|
service_path, service_name, service_info
|
|
)
|
|
|
|
# Categorize matches
|
|
files_to_modify, files_to_reference = self.categorizer.categorize_matches(
|
|
all_matches, task
|
|
)
|
|
|
|
# Discover patterns from reference files
|
|
patterns = self.pattern_discoverer.discover_patterns(
|
|
files_to_reference, keywords
|
|
)
|
|
|
|
# Get graph hints asynchronously
|
|
graph_hints = []
|
|
if include_graph_hints:
|
|
graph_hints = await fetch_graph_hints(task, str(self.project_dir))
|
|
|
|
return TaskContext(
|
|
task_description=task,
|
|
scoped_services=services,
|
|
files_to_modify=[
|
|
asdict(f) if isinstance(f, FileMatch) else f for f in files_to_modify
|
|
],
|
|
files_to_reference=[
|
|
asdict(f) if isinstance(f, FileMatch) else f for f in files_to_reference
|
|
],
|
|
patterns_discovered=patterns,
|
|
service_contexts=service_contexts,
|
|
graph_hints=graph_hints,
|
|
)
|
|
|
|
def _get_service_context(
|
|
self,
|
|
service_path: Path,
|
|
service_name: str,
|
|
service_info: dict,
|
|
) -> dict:
|
|
"""Get or generate context for a service."""
|
|
# Check for SERVICE_CONTEXT.md
|
|
context_file = service_path / "SERVICE_CONTEXT.md"
|
|
if context_file.exists():
|
|
return {
|
|
"source": "SERVICE_CONTEXT.md",
|
|
"content": context_file.read_text()[:2000], # First 2000 chars
|
|
}
|
|
|
|
# Generate basic context from service info
|
|
return {
|
|
"source": "generated",
|
|
"language": service_info.get("language"),
|
|
"framework": service_info.get("framework"),
|
|
"type": service_info.get("type"),
|
|
"entry_point": service_info.get("entry_point"),
|
|
"key_directories": service_info.get("key_directories", {}),
|
|
}
|