empathy-framework 2.4.0__py3-none-any.whl → 3.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coach_wizards/__init__.py +13 -12
- coach_wizards/accessibility_wizard.py +12 -12
- coach_wizards/api_wizard.py +12 -12
- coach_wizards/base_wizard.py +26 -20
- coach_wizards/cicd_wizard.py +15 -13
- coach_wizards/code_reviewer_README.md +60 -0
- coach_wizards/code_reviewer_wizard.py +180 -0
- coach_wizards/compliance_wizard.py +12 -12
- coach_wizards/database_wizard.py +12 -12
- coach_wizards/debugging_wizard.py +12 -12
- coach_wizards/documentation_wizard.py +12 -12
- coach_wizards/generate_wizards.py +1 -2
- coach_wizards/localization_wizard.py +101 -19
- coach_wizards/migration_wizard.py +12 -12
- coach_wizards/monitoring_wizard.py +12 -12
- coach_wizards/observability_wizard.py +12 -12
- coach_wizards/performance_wizard.py +12 -12
- coach_wizards/prompt_engineering_wizard.py +661 -0
- coach_wizards/refactoring_wizard.py +12 -12
- coach_wizards/scaling_wizard.py +12 -12
- coach_wizards/security_wizard.py +12 -12
- coach_wizards/testing_wizard.py +12 -12
- empathy_framework-3.8.2.dist-info/METADATA +1176 -0
- empathy_framework-3.8.2.dist-info/RECORD +333 -0
- empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
- {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
- empathy_healthcare_plugin/__init__.py +1 -2
- empathy_healthcare_plugin/monitors/__init__.py +9 -0
- empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
- empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
- empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
- empathy_llm_toolkit/__init__.py +7 -7
- empathy_llm_toolkit/agent_factory/__init__.py +53 -0
- empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
- empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
- empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
- empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
- empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
- empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
- empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
- empathy_llm_toolkit/agent_factory/base.py +305 -0
- empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
- empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
- empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
- empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
- empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
- empathy_llm_toolkit/agent_factory/decorators.py +286 -0
- empathy_llm_toolkit/agent_factory/factory.py +558 -0
- empathy_llm_toolkit/agent_factory/framework.py +192 -0
- empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
- empathy_llm_toolkit/agent_factory/resilient.py +320 -0
- empathy_llm_toolkit/claude_memory.py +14 -15
- empathy_llm_toolkit/cli/__init__.py +8 -0
- empathy_llm_toolkit/cli/sync_claude.py +487 -0
- empathy_llm_toolkit/code_health.py +186 -28
- empathy_llm_toolkit/config/__init__.py +29 -0
- empathy_llm_toolkit/config/unified.py +295 -0
- empathy_llm_toolkit/contextual_patterns.py +11 -12
- empathy_llm_toolkit/core.py +168 -53
- empathy_llm_toolkit/git_pattern_extractor.py +17 -13
- empathy_llm_toolkit/levels.py +6 -13
- empathy_llm_toolkit/pattern_confidence.py +14 -18
- empathy_llm_toolkit/pattern_resolver.py +10 -12
- empathy_llm_toolkit/pattern_summary.py +16 -14
- empathy_llm_toolkit/providers.py +194 -28
- empathy_llm_toolkit/routing/__init__.py +32 -0
- empathy_llm_toolkit/routing/model_router.py +362 -0
- empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
- empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
- empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
- empathy_llm_toolkit/security/README.md +262 -0
- empathy_llm_toolkit/security/__init__.py +62 -0
- empathy_llm_toolkit/security/audit_logger.py +929 -0
- empathy_llm_toolkit/security/audit_logger_example.py +152 -0
- empathy_llm_toolkit/security/pii_scrubber.py +640 -0
- empathy_llm_toolkit/security/secrets_detector.py +678 -0
- empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
- empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
- empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
- empathy_llm_toolkit/session_status.py +20 -22
- empathy_llm_toolkit/state.py +28 -21
- empathy_llm_toolkit/wizards/__init__.py +38 -0
- empathy_llm_toolkit/wizards/base_wizard.py +364 -0
- empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
- empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
- empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
- empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
- empathy_os/__init__.py +125 -84
- empathy_os/adaptive/__init__.py +13 -0
- empathy_os/adaptive/task_complexity.py +127 -0
- empathy_os/{monitoring.py → agent_monitoring.py} +28 -28
- empathy_os/cache/__init__.py +117 -0
- empathy_os/cache/base.py +166 -0
- empathy_os/cache/dependency_manager.py +253 -0
- empathy_os/cache/hash_only.py +248 -0
- empathy_os/cache/hybrid.py +390 -0
- empathy_os/cache/storage.py +282 -0
- empathy_os/cli.py +1516 -70
- empathy_os/cli_unified.py +597 -0
- empathy_os/config/__init__.py +63 -0
- empathy_os/config/xml_config.py +239 -0
- empathy_os/config.py +95 -37
- empathy_os/coordination.py +72 -68
- empathy_os/core.py +94 -107
- empathy_os/cost_tracker.py +74 -55
- empathy_os/dashboard/__init__.py +15 -0
- empathy_os/dashboard/server.py +743 -0
- empathy_os/discovery.py +17 -14
- empathy_os/emergence.py +21 -22
- empathy_os/exceptions.py +18 -30
- empathy_os/feedback_loops.py +30 -33
- empathy_os/levels.py +32 -35
- empathy_os/leverage_points.py +31 -32
- empathy_os/logging_config.py +19 -16
- empathy_os/memory/__init__.py +195 -0
- empathy_os/memory/claude_memory.py +466 -0
- empathy_os/memory/config.py +224 -0
- empathy_os/memory/control_panel.py +1298 -0
- empathy_os/memory/edges.py +179 -0
- empathy_os/memory/graph.py +567 -0
- empathy_os/memory/long_term.py +1194 -0
- empathy_os/memory/nodes.py +179 -0
- empathy_os/memory/redis_bootstrap.py +540 -0
- empathy_os/memory/security/__init__.py +31 -0
- empathy_os/memory/security/audit_logger.py +930 -0
- empathy_os/memory/security/pii_scrubber.py +640 -0
- empathy_os/memory/security/secrets_detector.py +678 -0
- empathy_os/memory/short_term.py +2119 -0
- empathy_os/memory/storage/__init__.py +15 -0
- empathy_os/memory/summary_index.py +583 -0
- empathy_os/memory/unified.py +619 -0
- empathy_os/metrics/__init__.py +12 -0
- empathy_os/metrics/prompt_metrics.py +190 -0
- empathy_os/models/__init__.py +136 -0
- empathy_os/models/__main__.py +13 -0
- empathy_os/models/cli.py +655 -0
- empathy_os/models/empathy_executor.py +354 -0
- empathy_os/models/executor.py +252 -0
- empathy_os/models/fallback.py +671 -0
- empathy_os/models/provider_config.py +563 -0
- empathy_os/models/registry.py +382 -0
- empathy_os/models/tasks.py +302 -0
- empathy_os/models/telemetry.py +548 -0
- empathy_os/models/token_estimator.py +378 -0
- empathy_os/models/validation.py +274 -0
- empathy_os/monitoring/__init__.py +52 -0
- empathy_os/monitoring/alerts.py +23 -0
- empathy_os/monitoring/alerts_cli.py +268 -0
- empathy_os/monitoring/multi_backend.py +271 -0
- empathy_os/monitoring/otel_backend.py +363 -0
- empathy_os/optimization/__init__.py +19 -0
- empathy_os/optimization/context_optimizer.py +272 -0
- empathy_os/pattern_library.py +30 -29
- empathy_os/persistence.py +35 -37
- empathy_os/platform_utils.py +261 -0
- empathy_os/plugins/__init__.py +28 -0
- empathy_os/plugins/base.py +361 -0
- empathy_os/plugins/registry.py +268 -0
- empathy_os/project_index/__init__.py +30 -0
- empathy_os/project_index/cli.py +335 -0
- empathy_os/project_index/crew_integration.py +430 -0
- empathy_os/project_index/index.py +425 -0
- empathy_os/project_index/models.py +501 -0
- empathy_os/project_index/reports.py +473 -0
- empathy_os/project_index/scanner.py +538 -0
- empathy_os/prompts/__init__.py +61 -0
- empathy_os/prompts/config.py +77 -0
- empathy_os/prompts/context.py +177 -0
- empathy_os/prompts/parser.py +285 -0
- empathy_os/prompts/registry.py +313 -0
- empathy_os/prompts/templates.py +208 -0
- empathy_os/redis_config.py +144 -58
- empathy_os/redis_memory.py +79 -77
- empathy_os/resilience/__init__.py +56 -0
- empathy_os/resilience/circuit_breaker.py +256 -0
- empathy_os/resilience/fallback.py +179 -0
- empathy_os/resilience/health.py +300 -0
- empathy_os/resilience/retry.py +209 -0
- empathy_os/resilience/timeout.py +135 -0
- empathy_os/routing/__init__.py +43 -0
- empathy_os/routing/chain_executor.py +433 -0
- empathy_os/routing/classifier.py +217 -0
- empathy_os/routing/smart_router.py +234 -0
- empathy_os/routing/wizard_registry.py +307 -0
- empathy_os/templates.py +19 -14
- empathy_os/trust/__init__.py +28 -0
- empathy_os/trust/circuit_breaker.py +579 -0
- empathy_os/trust_building.py +67 -58
- empathy_os/validation/__init__.py +19 -0
- empathy_os/validation/xml_validator.py +281 -0
- empathy_os/wizard_factory_cli.py +170 -0
- empathy_os/{workflows.py → workflow_commands.py} +131 -37
- empathy_os/workflows/__init__.py +360 -0
- empathy_os/workflows/base.py +1660 -0
- empathy_os/workflows/bug_predict.py +962 -0
- empathy_os/workflows/code_review.py +960 -0
- empathy_os/workflows/code_review_adapters.py +310 -0
- empathy_os/workflows/code_review_pipeline.py +720 -0
- empathy_os/workflows/config.py +600 -0
- empathy_os/workflows/dependency_check.py +648 -0
- empathy_os/workflows/document_gen.py +1069 -0
- empathy_os/workflows/documentation_orchestrator.py +1205 -0
- empathy_os/workflows/health_check.py +679 -0
- empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
- empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
- empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
- empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
- empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
- empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
- empathy_os/workflows/manage_documentation.py +804 -0
- empathy_os/workflows/new_sample_workflow1.py +146 -0
- empathy_os/workflows/new_sample_workflow1_README.md +150 -0
- empathy_os/workflows/perf_audit.py +687 -0
- empathy_os/workflows/pr_review.py +748 -0
- empathy_os/workflows/progress.py +445 -0
- empathy_os/workflows/progress_server.py +322 -0
- empathy_os/workflows/refactor_plan.py +693 -0
- empathy_os/workflows/release_prep.py +808 -0
- empathy_os/workflows/research_synthesis.py +404 -0
- empathy_os/workflows/secure_release.py +585 -0
- empathy_os/workflows/security_adapters.py +297 -0
- empathy_os/workflows/security_audit.py +1046 -0
- empathy_os/workflows/step_config.py +234 -0
- empathy_os/workflows/test5.py +125 -0
- empathy_os/workflows/test5_README.md +158 -0
- empathy_os/workflows/test_gen.py +1855 -0
- empathy_os/workflows/test_lifecycle.py +526 -0
- empathy_os/workflows/test_maintenance.py +626 -0
- empathy_os/workflows/test_maintenance_cli.py +590 -0
- empathy_os/workflows/test_maintenance_crew.py +821 -0
- empathy_os/workflows/xml_enhanced_crew.py +285 -0
- empathy_software_plugin/__init__.py +1 -2
- empathy_software_plugin/cli/__init__.py +120 -0
- empathy_software_plugin/cli/inspect.py +362 -0
- empathy_software_plugin/cli.py +49 -27
- empathy_software_plugin/plugin.py +4 -8
- empathy_software_plugin/wizards/__init__.py +42 -0
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
- empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
- empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
- empathy_software_plugin/wizards/base_wizard.py +288 -0
- empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
- empathy_software_plugin/wizards/code_review_wizard.py +606 -0
- empathy_software_plugin/wizards/debugging/__init__.py +50 -0
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
- empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
- empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
- empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
- empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
- empathy_software_plugin/wizards/debugging/verification.py +369 -0
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
- empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
- empathy_software_plugin/wizards/performance/__init__.py +9 -0
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
- empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
- empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
- empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
- empathy_software_plugin/wizards/security/__init__.py +32 -0
- empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
- empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
- empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
- empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
- empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
- empathy_software_plugin/wizards/testing/__init__.py +27 -0
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
- empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
- empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
- empathy_software_plugin/wizards/testing_wizard.py +274 -0
- hot_reload/README.md +473 -0
- hot_reload/__init__.py +62 -0
- hot_reload/config.py +84 -0
- hot_reload/integration.py +228 -0
- hot_reload/reloader.py +298 -0
- hot_reload/watcher.py +179 -0
- hot_reload/websocket.py +176 -0
- scaffolding/README.md +589 -0
- scaffolding/__init__.py +35 -0
- scaffolding/__main__.py +14 -0
- scaffolding/cli.py +240 -0
- test_generator/__init__.py +38 -0
- test_generator/__main__.py +14 -0
- test_generator/cli.py +226 -0
- test_generator/generator.py +325 -0
- test_generator/risk_analyzer.py +216 -0
- workflow_patterns/__init__.py +33 -0
- workflow_patterns/behavior.py +249 -0
- workflow_patterns/core.py +76 -0
- workflow_patterns/output.py +99 -0
- workflow_patterns/registry.py +255 -0
- workflow_patterns/structural.py +288 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
- agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
- agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
- agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
- agents/compliance_anticipation_agent.py +0 -1427
- agents/epic_integration_wizard.py +0 -541
- agents/trust_building_behaviors.py +0 -891
- empathy_framework-2.4.0.dist-info/METADATA +0 -485
- empathy_framework-2.4.0.dist-info/RECORD +0 -102
- empathy_framework-2.4.0.dist-info/entry_points.txt +0 -6
- empathy_llm_toolkit/htmlcov/status.json +0 -1
- empathy_llm_toolkit/security/htmlcov/status.json +0 -1
- {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
- {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Project Index - Main index class with persistence.
|
|
2
|
+
|
|
3
|
+
Manages the project index, persists to JSON, syncs with Redis.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .models import FileRecord, IndexConfig, ProjectSummary
|
|
16
|
+
from .scanner import ProjectScanner
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProjectIndex:
|
|
22
|
+
"""Central project index with file metadata.
|
|
23
|
+
|
|
24
|
+
Features:
|
|
25
|
+
- JSON persistence in .empathy/project_index.json
|
|
26
|
+
- Optional Redis sync for real-time access
|
|
27
|
+
- Query API for workflows and agents
|
|
28
|
+
- Update API for writing metadata
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
SCHEMA_VERSION = "1.0"
|
|
32
|
+
DEFAULT_INDEX_PATH = ".empathy/project_index.json"
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
project_root: str,
|
|
37
|
+
config: IndexConfig | None = None,
|
|
38
|
+
redis_client: Any | None = None,
|
|
39
|
+
):
|
|
40
|
+
self.project_root = Path(project_root)
|
|
41
|
+
self.config = config or IndexConfig()
|
|
42
|
+
self.redis_client = redis_client
|
|
43
|
+
|
|
44
|
+
# In-memory state
|
|
45
|
+
self._records: dict[str, FileRecord] = {}
|
|
46
|
+
self._summary: ProjectSummary = ProjectSummary()
|
|
47
|
+
self._generated_at: datetime | None = None
|
|
48
|
+
|
|
49
|
+
# Index file path
|
|
50
|
+
self._index_path = self.project_root / self.DEFAULT_INDEX_PATH
|
|
51
|
+
|
|
52
|
+
# ===== Persistence =====
|
|
53
|
+
|
|
54
|
+
def load(self) -> bool:
|
|
55
|
+
"""Load index from JSON file.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if loaded successfully, False otherwise
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
if not self._index_path.exists():
|
|
62
|
+
logger.info(f"No index found at {self._index_path}")
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
with open(self._index_path, encoding="utf-8") as f:
|
|
67
|
+
data = json.load(f)
|
|
68
|
+
|
|
69
|
+
# Validate schema version
|
|
70
|
+
if data.get("schema_version") != self.SCHEMA_VERSION:
|
|
71
|
+
logger.warning("Schema version mismatch, regenerating index")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
# Load config
|
|
75
|
+
if "config" in data:
|
|
76
|
+
self.config = IndexConfig.from_dict(data["config"])
|
|
77
|
+
|
|
78
|
+
# Load summary
|
|
79
|
+
if "summary" in data:
|
|
80
|
+
self._summary = ProjectSummary.from_dict(data["summary"])
|
|
81
|
+
|
|
82
|
+
# Load records
|
|
83
|
+
self._records = {}
|
|
84
|
+
for path, record_data in data.get("files", {}).items():
|
|
85
|
+
self._records[path] = FileRecord.from_dict(record_data)
|
|
86
|
+
|
|
87
|
+
# Load timestamp
|
|
88
|
+
if data.get("generated_at"):
|
|
89
|
+
self._generated_at = datetime.fromisoformat(data["generated_at"])
|
|
90
|
+
|
|
91
|
+
logger.info(f"Loaded index with {len(self._records)} files")
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
|
95
|
+
logger.error(f"Failed to load index: {e}")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def save(self) -> bool:
|
|
99
|
+
"""Save index to JSON file.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if saved successfully, False otherwise
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
# Ensure directory exists
|
|
107
|
+
self._index_path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
data = {
|
|
110
|
+
"schema_version": self.SCHEMA_VERSION,
|
|
111
|
+
"project": self.project_root.name,
|
|
112
|
+
"generated_at": datetime.now().isoformat(),
|
|
113
|
+
"config": self.config.to_dict(),
|
|
114
|
+
"summary": self._summary.to_dict(),
|
|
115
|
+
"files": {path: record.to_dict() for path, record in self._records.items()},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
with open(self._index_path, "w", encoding="utf-8") as f:
|
|
119
|
+
json.dump(data, f, indent=2, default=str)
|
|
120
|
+
|
|
121
|
+
logger.info(f"Saved index with {len(self._records)} files to {self._index_path}")
|
|
122
|
+
|
|
123
|
+
# Sync to Redis if enabled
|
|
124
|
+
if self.redis_client and self.config.use_redis:
|
|
125
|
+
self._sync_to_redis()
|
|
126
|
+
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
except OSError as e:
|
|
130
|
+
logger.error(f"Failed to save index: {e}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def _sync_to_redis(self) -> None:
|
|
134
|
+
"""Sync index to Redis for real-time access."""
|
|
135
|
+
if not self.redis_client:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
prefix = self.config.redis_key_prefix
|
|
140
|
+
|
|
141
|
+
# Store summary
|
|
142
|
+
self.redis_client.set(
|
|
143
|
+
f"{prefix}:summary",
|
|
144
|
+
json.dumps(self._summary.to_dict()),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Store each file record
|
|
148
|
+
for path, record in self._records.items():
|
|
149
|
+
self.redis_client.hset(
|
|
150
|
+
f"{prefix}:files",
|
|
151
|
+
path,
|
|
152
|
+
json.dumps(record.to_dict()),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Store metadata
|
|
156
|
+
self.redis_client.set(
|
|
157
|
+
f"{prefix}:meta",
|
|
158
|
+
json.dumps(
|
|
159
|
+
{
|
|
160
|
+
"generated_at": datetime.now().isoformat(),
|
|
161
|
+
"file_count": len(self._records),
|
|
162
|
+
},
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
logger.info(f"Synced index to Redis with prefix {prefix}")
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"Failed to sync to Redis: {e}")
|
|
170
|
+
|
|
171
|
+
# ===== Index Operations =====
|
|
172
|
+
|
|
173
|
+
def refresh(self) -> None:
|
|
174
|
+
"""Refresh the entire index by scanning the project.
|
|
175
|
+
|
|
176
|
+
This rebuilds the index from scratch.
|
|
177
|
+
"""
|
|
178
|
+
logger.info(f"Refreshing index for {self.project_root}")
|
|
179
|
+
|
|
180
|
+
scanner = ProjectScanner(str(self.project_root), self.config)
|
|
181
|
+
records, summary = scanner.scan()
|
|
182
|
+
|
|
183
|
+
# Update internal state
|
|
184
|
+
self._records = {r.path: r for r in records}
|
|
185
|
+
self._summary = summary
|
|
186
|
+
self._generated_at = datetime.now()
|
|
187
|
+
|
|
188
|
+
# Save to disk
|
|
189
|
+
self.save()
|
|
190
|
+
|
|
191
|
+
logger.info(
|
|
192
|
+
f"Index refreshed: {len(self._records)} files, {summary.files_needing_attention} need attention",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def update_file(self, path: str, **updates: Any) -> bool:
|
|
196
|
+
"""Update metadata for a specific file.
|
|
197
|
+
|
|
198
|
+
This is the write API for workflows and agents.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
path: Relative path to the file
|
|
202
|
+
**updates: Key-value pairs to update
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
True if updated successfully
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
if path not in self._records:
|
|
209
|
+
logger.warning(f"File not in index: {path}")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
record = self._records[path]
|
|
213
|
+
|
|
214
|
+
# Apply updates
|
|
215
|
+
for key, value in updates.items():
|
|
216
|
+
if hasattr(record, key):
|
|
217
|
+
setattr(record, key, value)
|
|
218
|
+
else:
|
|
219
|
+
# Store in metadata
|
|
220
|
+
record.metadata[key] = value
|
|
221
|
+
|
|
222
|
+
record.last_indexed = datetime.now()
|
|
223
|
+
|
|
224
|
+
# Save changes
|
|
225
|
+
self.save()
|
|
226
|
+
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
def update_coverage(self, coverage_data: dict[str, float]) -> int:
|
|
230
|
+
"""Update coverage data for files.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
coverage_data: Dict mapping file paths to coverage percentages
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Number of files updated
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
updated = 0
|
|
240
|
+
|
|
241
|
+
for path, coverage in coverage_data.items():
|
|
242
|
+
# Normalize path
|
|
243
|
+
path = path.removeprefix("./")
|
|
244
|
+
|
|
245
|
+
if path in self._records:
|
|
246
|
+
self._records[path].coverage_percent = coverage
|
|
247
|
+
updated += 1
|
|
248
|
+
|
|
249
|
+
if updated > 0:
|
|
250
|
+
# Recalculate summary
|
|
251
|
+
self._recalculate_summary()
|
|
252
|
+
self.save()
|
|
253
|
+
|
|
254
|
+
logger.info(f"Updated coverage for {updated} files")
|
|
255
|
+
return updated
|
|
256
|
+
|
|
257
|
+
def _recalculate_summary(self) -> None:
|
|
258
|
+
"""Recalculate summary from current records."""
|
|
259
|
+
records = list(self._records.values())
|
|
260
|
+
|
|
261
|
+
# Testing health with coverage
|
|
262
|
+
covered = [r for r in records if r.coverage_percent > 0]
|
|
263
|
+
if covered:
|
|
264
|
+
self._summary.test_coverage_avg = sum(r.coverage_percent for r in covered) / len(
|
|
265
|
+
covered,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# ===== Query API =====
|
|
269
|
+
|
|
270
|
+
def get_file(self, path: str) -> FileRecord | None:
|
|
271
|
+
"""Get record for a specific file."""
|
|
272
|
+
return self._records.get(path)
|
|
273
|
+
|
|
274
|
+
def get_summary(self) -> ProjectSummary:
|
|
275
|
+
"""Get project summary."""
|
|
276
|
+
return self._summary
|
|
277
|
+
|
|
278
|
+
def get_all_files(self) -> list[FileRecord]:
|
|
279
|
+
"""Get all file records."""
|
|
280
|
+
return list(self._records.values())
|
|
281
|
+
|
|
282
|
+
def get_files_needing_tests(self) -> list[FileRecord]:
|
|
283
|
+
"""Get files that need tests but don't have them."""
|
|
284
|
+
return [
|
|
285
|
+
r
|
|
286
|
+
for r in self._records.values()
|
|
287
|
+
if r.test_requirement.value == "required" and not r.tests_exist
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
def get_stale_files(self) -> list[FileRecord]:
|
|
291
|
+
"""Get files with stale tests."""
|
|
292
|
+
return [r for r in self._records.values() if r.is_stale]
|
|
293
|
+
|
|
294
|
+
def get_files_needing_attention(self) -> list[FileRecord]:
|
|
295
|
+
"""Get files that need attention."""
|
|
296
|
+
return sorted(
|
|
297
|
+
[r for r in self._records.values() if r.needs_attention],
|
|
298
|
+
key=lambda r: -r.impact_score,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def get_high_impact_files(self) -> list[FileRecord]:
|
|
302
|
+
"""Get high-impact files sorted by impact score."""
|
|
303
|
+
return sorted(
|
|
304
|
+
[
|
|
305
|
+
r
|
|
306
|
+
for r in self._records.values()
|
|
307
|
+
if r.impact_score >= self.config.high_impact_threshold
|
|
308
|
+
],
|
|
309
|
+
key=lambda r: -r.impact_score,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def get_files_by_category(self, category: str) -> list[FileRecord]:
|
|
313
|
+
"""Get files by category."""
|
|
314
|
+
return [r for r in self._records.values() if r.category.value == category]
|
|
315
|
+
|
|
316
|
+
def get_files_by_language(self, language: str) -> list[FileRecord]:
|
|
317
|
+
"""Get files by programming language."""
|
|
318
|
+
return [r for r in self._records.values() if r.language == language]
|
|
319
|
+
|
|
320
|
+
def search_files(self, pattern: str) -> list[FileRecord]:
|
|
321
|
+
"""Search files by path pattern."""
|
|
322
|
+
import fnmatch
|
|
323
|
+
|
|
324
|
+
return [r for r in self._records.values() if fnmatch.fnmatch(r.path, pattern)]
|
|
325
|
+
|
|
326
|
+
def get_dependents(self, path: str) -> list[FileRecord]:
|
|
327
|
+
"""Get files that depend on the given file."""
|
|
328
|
+
record = self._records.get(path)
|
|
329
|
+
if not record:
|
|
330
|
+
return []
|
|
331
|
+
return [self._records[p] for p in record.imported_by if p in self._records]
|
|
332
|
+
|
|
333
|
+
def get_dependencies(self, path: str) -> list[FileRecord]:
|
|
334
|
+
"""Get files that the given file depends on."""
|
|
335
|
+
record = self._records.get(path)
|
|
336
|
+
if not record:
|
|
337
|
+
return []
|
|
338
|
+
# Match imports to paths
|
|
339
|
+
results = []
|
|
340
|
+
for imp in record.imports:
|
|
341
|
+
for other_path, other_record in self._records.items():
|
|
342
|
+
if imp in other_path.replace("/", ".").replace("\\", "."):
|
|
343
|
+
results.append(other_record)
|
|
344
|
+
break
|
|
345
|
+
return results
|
|
346
|
+
|
|
347
|
+
# ===== Statistics =====
|
|
348
|
+
|
|
349
|
+
def get_test_gap_stats(self) -> dict[str, Any]:
|
|
350
|
+
"""Get statistics about test gaps."""
|
|
351
|
+
files_needing_tests = self.get_files_needing_tests()
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
"files_without_tests": len(files_needing_tests),
|
|
355
|
+
"high_impact_untested": len(
|
|
356
|
+
[
|
|
357
|
+
f
|
|
358
|
+
for f in files_needing_tests
|
|
359
|
+
if f.impact_score >= self.config.high_impact_threshold
|
|
360
|
+
],
|
|
361
|
+
),
|
|
362
|
+
"total_loc_untested": sum(f.lines_of_code for f in files_needing_tests),
|
|
363
|
+
"by_directory": self._group_by_directory(files_needing_tests),
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
def get_staleness_stats(self) -> dict[str, Any]:
|
|
367
|
+
"""Get statistics about stale tests."""
|
|
368
|
+
stale = self.get_stale_files()
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
"stale_count": len(stale),
|
|
372
|
+
"avg_staleness_days": sum(f.staleness_days for f in stale) / len(stale) if stale else 0,
|
|
373
|
+
"max_staleness_days": max((f.staleness_days for f in stale), default=0),
|
|
374
|
+
"by_directory": self._group_by_directory(stale),
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
def _group_by_directory(self, records: list[FileRecord]) -> dict[str, int]:
|
|
378
|
+
"""Group records by top-level directory."""
|
|
379
|
+
counts: dict[str, int] = {}
|
|
380
|
+
for r in records:
|
|
381
|
+
parts = r.path.split("/")
|
|
382
|
+
if len(parts) > 1:
|
|
383
|
+
dir_name = parts[0]
|
|
384
|
+
else:
|
|
385
|
+
dir_name = "."
|
|
386
|
+
counts[dir_name] = counts.get(dir_name, 0) + 1
|
|
387
|
+
return counts
|
|
388
|
+
|
|
389
|
+
# ===== Context for Workflows =====
|
|
390
|
+
|
|
391
|
+
def get_context_for_workflow(self, workflow_type: str) -> dict[str, Any]:
|
|
392
|
+
"""Get relevant context for a specific workflow type.
|
|
393
|
+
|
|
394
|
+
This provides a filtered view of the index tailored to workflow needs.
|
|
395
|
+
"""
|
|
396
|
+
if workflow_type == "test_gen":
|
|
397
|
+
files = self.get_files_needing_tests()
|
|
398
|
+
return {
|
|
399
|
+
"files_needing_tests": [f.to_dict() for f in files[:20]],
|
|
400
|
+
"summary": self.get_test_gap_stats(),
|
|
401
|
+
"priority_files": [
|
|
402
|
+
f.path for f in files if f.impact_score >= self.config.high_impact_threshold
|
|
403
|
+
][:10],
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if workflow_type == "code_review":
|
|
407
|
+
return {
|
|
408
|
+
"high_impact_files": [f.to_dict() for f in self.get_high_impact_files()[:10]],
|
|
409
|
+
"stale_files": [f.to_dict() for f in self.get_stale_files()[:10]],
|
|
410
|
+
"summary": self._summary.to_dict(),
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if workflow_type == "security_audit":
|
|
414
|
+
return {
|
|
415
|
+
"all_source_files": [f.to_dict() for f in self.get_files_by_category("source")],
|
|
416
|
+
"untested_files": [f.to_dict() for f in self.get_files_needing_tests()],
|
|
417
|
+
"summary": self._summary.to_dict(),
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
"summary": self._summary.to_dict(),
|
|
422
|
+
"files_needing_attention": [
|
|
423
|
+
f.to_dict() for f in self.get_files_needing_attention()[:20]
|
|
424
|
+
],
|
|
425
|
+
}
|