empathy-framework 3.7.0__py3-none-any.whl → 3.7.1__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.
Files changed (267) hide show
  1. coach_wizards/code_reviewer_README.md +60 -0
  2. coach_wizards/code_reviewer_wizard.py +180 -0
  3. {empathy_framework-3.7.0.dist-info → empathy_framework-3.7.1.dist-info}/METADATA +20 -2
  4. empathy_framework-3.7.1.dist-info/RECORD +327 -0
  5. {empathy_framework-3.7.0.dist-info → empathy_framework-3.7.1.dist-info}/top_level.txt +5 -1
  6. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  7. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  8. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  9. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  10. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  11. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  12. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  13. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  14. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  15. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  16. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  17. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  18. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  19. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  20. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  21. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  22. empathy_llm_toolkit/agent_factory/base.py +305 -0
  23. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  24. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  25. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  26. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  27. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  28. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  29. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  30. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  31. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  32. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  33. empathy_llm_toolkit/cli/__init__.py +8 -0
  34. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  35. empathy_llm_toolkit/code_health.py +150 -3
  36. empathy_llm_toolkit/config/__init__.py +29 -0
  37. empathy_llm_toolkit/config/unified.py +295 -0
  38. empathy_llm_toolkit/routing/__init__.py +32 -0
  39. empathy_llm_toolkit/routing/model_router.py +362 -0
  40. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  41. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  42. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  43. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  44. empathy_llm_toolkit/security/README.md +262 -0
  45. empathy_llm_toolkit/security/__init__.py +62 -0
  46. empathy_llm_toolkit/security/audit_logger.py +929 -0
  47. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  48. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  49. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  50. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  51. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  52. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  53. empathy_llm_toolkit/wizards/__init__.py +38 -0
  54. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  55. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  56. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  57. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  58. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  59. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  60. empathy_os/__init__.py +52 -52
  61. empathy_os/adaptive/__init__.py +13 -0
  62. empathy_os/adaptive/task_complexity.py +127 -0
  63. empathy_os/cli.py +118 -8
  64. empathy_os/cli_unified.py +121 -1
  65. empathy_os/config/__init__.py +63 -0
  66. empathy_os/config/xml_config.py +239 -0
  67. empathy_os/dashboard/__init__.py +15 -0
  68. empathy_os/dashboard/server.py +743 -0
  69. empathy_os/memory/__init__.py +195 -0
  70. empathy_os/memory/claude_memory.py +466 -0
  71. empathy_os/memory/config.py +224 -0
  72. empathy_os/memory/control_panel.py +1298 -0
  73. empathy_os/memory/edges.py +179 -0
  74. empathy_os/memory/graph.py +567 -0
  75. empathy_os/memory/long_term.py +1193 -0
  76. empathy_os/memory/nodes.py +179 -0
  77. empathy_os/memory/redis_bootstrap.py +540 -0
  78. empathy_os/memory/security/__init__.py +31 -0
  79. empathy_os/memory/security/audit_logger.py +930 -0
  80. empathy_os/memory/security/pii_scrubber.py +640 -0
  81. empathy_os/memory/security/secrets_detector.py +678 -0
  82. empathy_os/memory/short_term.py +2119 -0
  83. empathy_os/memory/storage/__init__.py +15 -0
  84. empathy_os/memory/summary_index.py +583 -0
  85. empathy_os/memory/unified.py +619 -0
  86. empathy_os/metrics/__init__.py +12 -0
  87. empathy_os/metrics/prompt_metrics.py +190 -0
  88. empathy_os/models/__init__.py +136 -0
  89. empathy_os/models/__main__.py +13 -0
  90. empathy_os/models/cli.py +655 -0
  91. empathy_os/models/empathy_executor.py +354 -0
  92. empathy_os/models/executor.py +252 -0
  93. empathy_os/models/fallback.py +671 -0
  94. empathy_os/models/provider_config.py +563 -0
  95. empathy_os/models/registry.py +382 -0
  96. empathy_os/models/tasks.py +302 -0
  97. empathy_os/models/telemetry.py +548 -0
  98. empathy_os/models/token_estimator.py +378 -0
  99. empathy_os/models/validation.py +274 -0
  100. empathy_os/monitoring/__init__.py +52 -0
  101. empathy_os/monitoring/alerts.py +23 -0
  102. empathy_os/monitoring/alerts_cli.py +268 -0
  103. empathy_os/monitoring/multi_backend.py +271 -0
  104. empathy_os/monitoring/otel_backend.py +363 -0
  105. empathy_os/optimization/__init__.py +19 -0
  106. empathy_os/optimization/context_optimizer.py +272 -0
  107. empathy_os/plugins/__init__.py +28 -0
  108. empathy_os/plugins/base.py +361 -0
  109. empathy_os/plugins/registry.py +268 -0
  110. empathy_os/project_index/__init__.py +30 -0
  111. empathy_os/project_index/cli.py +335 -0
  112. empathy_os/project_index/crew_integration.py +430 -0
  113. empathy_os/project_index/index.py +425 -0
  114. empathy_os/project_index/models.py +501 -0
  115. empathy_os/project_index/reports.py +473 -0
  116. empathy_os/project_index/scanner.py +538 -0
  117. empathy_os/prompts/__init__.py +61 -0
  118. empathy_os/prompts/config.py +77 -0
  119. empathy_os/prompts/context.py +177 -0
  120. empathy_os/prompts/parser.py +285 -0
  121. empathy_os/prompts/registry.py +313 -0
  122. empathy_os/prompts/templates.py +208 -0
  123. empathy_os/resilience/__init__.py +56 -0
  124. empathy_os/resilience/circuit_breaker.py +256 -0
  125. empathy_os/resilience/fallback.py +179 -0
  126. empathy_os/resilience/health.py +300 -0
  127. empathy_os/resilience/retry.py +209 -0
  128. empathy_os/resilience/timeout.py +135 -0
  129. empathy_os/routing/__init__.py +43 -0
  130. empathy_os/routing/chain_executor.py +433 -0
  131. empathy_os/routing/classifier.py +217 -0
  132. empathy_os/routing/smart_router.py +234 -0
  133. empathy_os/routing/wizard_registry.py +307 -0
  134. empathy_os/trust/__init__.py +28 -0
  135. empathy_os/trust/circuit_breaker.py +579 -0
  136. empathy_os/validation/__init__.py +19 -0
  137. empathy_os/validation/xml_validator.py +281 -0
  138. empathy_os/wizard_factory_cli.py +170 -0
  139. empathy_os/workflows/__init__.py +360 -0
  140. empathy_os/workflows/base.py +1530 -0
  141. empathy_os/workflows/bug_predict.py +962 -0
  142. empathy_os/workflows/code_review.py +960 -0
  143. empathy_os/workflows/code_review_adapters.py +310 -0
  144. empathy_os/workflows/code_review_pipeline.py +720 -0
  145. empathy_os/workflows/config.py +600 -0
  146. empathy_os/workflows/dependency_check.py +648 -0
  147. empathy_os/workflows/document_gen.py +1069 -0
  148. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  149. empathy_os/workflows/health_check.py +679 -0
  150. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  151. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  152. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  153. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  154. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  155. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  156. empathy_os/workflows/manage_documentation.py +804 -0
  157. empathy_os/workflows/new_sample_workflow1.py +146 -0
  158. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  159. empathy_os/workflows/perf_audit.py +687 -0
  160. empathy_os/workflows/pr_review.py +748 -0
  161. empathy_os/workflows/progress.py +445 -0
  162. empathy_os/workflows/progress_server.py +322 -0
  163. empathy_os/workflows/refactor_plan.py +691 -0
  164. empathy_os/workflows/release_prep.py +808 -0
  165. empathy_os/workflows/research_synthesis.py +404 -0
  166. empathy_os/workflows/secure_release.py +585 -0
  167. empathy_os/workflows/security_adapters.py +297 -0
  168. empathy_os/workflows/security_audit.py +1050 -0
  169. empathy_os/workflows/step_config.py +234 -0
  170. empathy_os/workflows/test5.py +125 -0
  171. empathy_os/workflows/test5_README.md +158 -0
  172. empathy_os/workflows/test_gen.py +1855 -0
  173. empathy_os/workflows/test_lifecycle.py +526 -0
  174. empathy_os/workflows/test_maintenance.py +626 -0
  175. empathy_os/workflows/test_maintenance_cli.py +590 -0
  176. empathy_os/workflows/test_maintenance_crew.py +821 -0
  177. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  178. empathy_software_plugin/cli/__init__.py +120 -0
  179. empathy_software_plugin/cli/inspect.py +362 -0
  180. empathy_software_plugin/cli.py +3 -1
  181. empathy_software_plugin/wizards/__init__.py +42 -0
  182. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  183. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  184. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  185. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  186. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  187. empathy_software_plugin/wizards/base_wizard.py +288 -0
  188. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  189. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  190. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  191. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  192. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  193. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  194. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  195. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  196. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  197. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  198. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  199. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  200. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  201. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  202. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  203. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  204. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  205. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  206. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  207. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  208. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  209. empathy_software_plugin/wizards/security/__init__.py +32 -0
  210. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  211. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  212. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  213. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  214. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  215. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  216. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  217. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  218. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  219. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  220. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  221. hot_reload/README.md +473 -0
  222. hot_reload/__init__.py +62 -0
  223. hot_reload/config.py +84 -0
  224. hot_reload/integration.py +228 -0
  225. hot_reload/reloader.py +298 -0
  226. hot_reload/watcher.py +179 -0
  227. hot_reload/websocket.py +176 -0
  228. scaffolding/README.md +589 -0
  229. scaffolding/__init__.py +35 -0
  230. scaffolding/__main__.py +14 -0
  231. scaffolding/cli.py +240 -0
  232. test_generator/__init__.py +38 -0
  233. test_generator/__main__.py +14 -0
  234. test_generator/cli.py +226 -0
  235. test_generator/generator.py +325 -0
  236. test_generator/risk_analyzer.py +216 -0
  237. workflow_patterns/__init__.py +33 -0
  238. workflow_patterns/behavior.py +249 -0
  239. workflow_patterns/core.py +76 -0
  240. workflow_patterns/output.py +99 -0
  241. workflow_patterns/registry.py +255 -0
  242. workflow_patterns/structural.py +288 -0
  243. workflow_scaffolding/__init__.py +11 -0
  244. workflow_scaffolding/__main__.py +12 -0
  245. workflow_scaffolding/cli.py +206 -0
  246. workflow_scaffolding/generator.py +265 -0
  247. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  248. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  249. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  250. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  251. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  252. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  253. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  254. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  255. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  256. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  257. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  258. agents/compliance_anticipation_agent.py +0 -1422
  259. agents/compliance_db.py +0 -339
  260. agents/epic_integration_wizard.py +0 -530
  261. agents/notifications.py +0 -291
  262. agents/trust_building_behaviors.py +0 -872
  263. empathy_framework-3.7.0.dist-info/RECORD +0 -105
  264. {empathy_framework-3.7.0.dist-info → empathy_framework-3.7.1.dist-info}/WHEEL +0 -0
  265. {empathy_framework-3.7.0.dist-info → empathy_framework-3.7.1.dist-info}/entry_points.txt +0 -0
  266. {empathy_framework-3.7.0.dist-info → empathy_framework-3.7.1.dist-info}/licenses/LICENSE +0 -0
  267. /empathy_os/{monitoring.py → agent_monitoring.py} +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
+ }