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,538 @@
1
+ """Project Scanner - Scans codebase to build file index.
2
+
3
+ Analyzes source files, matches them to tests, calculates metrics.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import ast
10
+ import fnmatch
11
+ import os
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from .models import FileCategory, FileRecord, IndexConfig, ProjectSummary, TestRequirement
17
+
18
+
19
+ class ProjectScanner:
20
+ """Scans a project directory and builds file metadata.
21
+
22
+ Used by ProjectIndex to populate and update the index.
23
+ """
24
+
25
+ def __init__(self, project_root: str, config: IndexConfig | None = None):
26
+ self.project_root = Path(project_root)
27
+ self.config = config or IndexConfig()
28
+ self._test_file_map: dict[str, str] = {} # source -> test mapping
29
+
30
+ def scan(self) -> tuple[list[FileRecord], ProjectSummary]:
31
+ """Scan the entire project and return file records and summary.
32
+
33
+ Returns:
34
+ Tuple of (list of FileRecords, ProjectSummary)
35
+
36
+ """
37
+ records: list[FileRecord] = []
38
+
39
+ # First pass: discover all files
40
+ all_files = self._discover_files()
41
+
42
+ # Build test file mapping
43
+ self._build_test_mapping(all_files)
44
+
45
+ # Second pass: analyze each file
46
+ for file_path in all_files:
47
+ record = self._analyze_file(file_path)
48
+ if record:
49
+ records.append(record)
50
+
51
+ # Third pass: build dependency graph
52
+ self._analyze_dependencies(records)
53
+
54
+ # Calculate impact scores
55
+ self._calculate_impact_scores(records)
56
+
57
+ # Determine attention needs
58
+ self._determine_attention_needs(records)
59
+
60
+ # Build summary
61
+ summary = self._build_summary(records)
62
+
63
+ return records, summary
64
+
65
+ def _discover_files(self) -> list[Path]:
66
+ """Discover all relevant files in the project."""
67
+ files = []
68
+
69
+ for root, dirs, filenames in os.walk(self.project_root):
70
+ # Filter out excluded directories
71
+ dirs[:] = [d for d in dirs if not self._is_excluded(Path(root) / d)]
72
+
73
+ for filename in filenames:
74
+ file_path = Path(root) / filename
75
+ rel_path = file_path.relative_to(self.project_root)
76
+
77
+ if not self._is_excluded(rel_path):
78
+ files.append(file_path)
79
+
80
+ return files
81
+
82
+ def _matches_glob_pattern(self, path: Path, pattern: str) -> bool:
83
+ """Check if a path matches a glob pattern (handles ** patterns)."""
84
+ rel_str = str(path)
85
+ path_parts = path.parts
86
+
87
+ # Handle ** glob patterns
88
+ if "**" in pattern:
89
+ # Convert ** pattern to work with fnmatch
90
+ # **/ at start means any path prefix
91
+ simple_pattern = pattern.replace("**/", "")
92
+
93
+ # Check if the pattern matches the path or any part of it
94
+ if fnmatch.fnmatch(rel_str, simple_pattern):
95
+ return True
96
+ if fnmatch.fnmatch(path.name, simple_pattern):
97
+ return True
98
+
99
+ # Check directory-based exclusions
100
+ if pattern.endswith("/**"):
101
+ dir_name = pattern.replace("**/", "").replace("/**", "")
102
+ if dir_name in path_parts:
103
+ return True
104
+
105
+ # Check for directory patterns like **/node_modules/**
106
+ if pattern.startswith("**/") and pattern.endswith("/**"):
107
+ dir_name = pattern[3:-3] # Extract directory name
108
+ if dir_name in path_parts:
109
+ return True
110
+ else:
111
+ if fnmatch.fnmatch(rel_str, pattern):
112
+ return True
113
+ if fnmatch.fnmatch(path.name, pattern):
114
+ return True
115
+
116
+ return False
117
+
118
+ def _is_excluded(self, path: Path) -> bool:
119
+ """Check if a path should be excluded."""
120
+ for pattern in self.config.exclude_patterns:
121
+ if self._matches_glob_pattern(path, pattern):
122
+ return True
123
+ return False
124
+
125
+ def _build_test_mapping(self, files: list[Path]) -> None:
126
+ """Build mapping from source files to their test files."""
127
+ test_files = [f for f in files if self._is_test_file(f)]
128
+
129
+ for test_file in test_files:
130
+ # Try to find corresponding source file
131
+ test_name = test_file.stem # e.g., "test_core"
132
+
133
+ # Common patterns: test_foo.py -> foo.py
134
+ if test_name.startswith("test_"):
135
+ source_name = test_name[5:] # Remove "test_" prefix
136
+ elif test_name.endswith("_test"):
137
+ source_name = test_name[:-5] # Remove "_test" suffix
138
+ else:
139
+ continue
140
+
141
+ # Search for matching source file
142
+ for source_file in files:
143
+ if source_file.stem == source_name and not self._is_test_file(source_file):
144
+ rel_source = str(source_file.relative_to(self.project_root))
145
+ rel_test = str(test_file.relative_to(self.project_root))
146
+ self._test_file_map[rel_source] = rel_test
147
+ break
148
+
149
+ def _is_test_file(self, path: Path) -> bool:
150
+ """Check if a file is a test file."""
151
+ name = path.stem
152
+ return (
153
+ name.startswith("test_")
154
+ or name.endswith("_test")
155
+ or "tests" in path.parts
156
+ or path.parent.name == "test"
157
+ )
158
+
159
+ def _analyze_file(self, file_path: Path) -> FileRecord | None:
160
+ """Analyze a single file and create its record."""
161
+ rel_path = str(file_path.relative_to(self.project_root))
162
+
163
+ # Determine category
164
+ category = self._determine_category(file_path)
165
+
166
+ # Determine language
167
+ language = self._determine_language(file_path)
168
+
169
+ # Get file stats
170
+ try:
171
+ stat = file_path.stat()
172
+ last_modified = datetime.fromtimestamp(stat.st_mtime)
173
+ except OSError:
174
+ last_modified = None
175
+
176
+ # Determine test requirement
177
+ test_requirement = self._determine_test_requirement(file_path, category)
178
+
179
+ # Find associated test file
180
+ test_file_path = self._test_file_map.get(rel_path)
181
+ tests_exist = test_file_path is not None
182
+
183
+ # Get test file modification time
184
+ tests_last_modified = None
185
+ if test_file_path:
186
+ test_full_path = self.project_root / test_file_path
187
+ if test_full_path.exists():
188
+ try:
189
+ tests_last_modified = datetime.fromtimestamp(test_full_path.stat().st_mtime)
190
+ except OSError:
191
+ pass
192
+
193
+ # Calculate staleness
194
+ staleness_days = 0
195
+ is_stale = False
196
+ if last_modified and tests_last_modified:
197
+ if last_modified > tests_last_modified:
198
+ staleness_days = (last_modified - tests_last_modified).days
199
+ is_stale = staleness_days >= self.config.staleness_threshold_days
200
+
201
+ # Analyze code metrics
202
+ metrics = self._analyze_code_metrics(file_path, language)
203
+
204
+ return FileRecord(
205
+ path=rel_path,
206
+ name=file_path.name,
207
+ category=category,
208
+ language=language,
209
+ test_requirement=test_requirement,
210
+ test_file_path=test_file_path,
211
+ tests_exist=tests_exist,
212
+ test_count=metrics.get("test_count", 0),
213
+ coverage_percent=0.0, # Will be populated from coverage data
214
+ last_modified=last_modified,
215
+ tests_last_modified=tests_last_modified,
216
+ last_indexed=datetime.now(),
217
+ staleness_days=staleness_days,
218
+ is_stale=is_stale,
219
+ lines_of_code=metrics.get("lines_of_code", 0),
220
+ lines_of_test=metrics.get("lines_of_test", 0),
221
+ complexity_score=metrics.get("complexity", 0.0),
222
+ has_docstrings=metrics.get("has_docstrings", False),
223
+ has_type_hints=metrics.get("has_type_hints", False),
224
+ lint_issues=0, # Will be populated from linter
225
+ imports=metrics.get("imports", []),
226
+ imported_by=[], # Populated in dependency analysis
227
+ import_count=len(metrics.get("imports", [])),
228
+ imported_by_count=0,
229
+ impact_score=0.0, # Calculated later
230
+ metadata={},
231
+ needs_attention=False,
232
+ attention_reasons=[],
233
+ )
234
+
235
+ def _determine_category(self, path: Path) -> FileCategory:
236
+ """Determine the category of a file."""
237
+ if self._is_test_file(path):
238
+ return FileCategory.TEST
239
+
240
+ suffix = path.suffix.lower()
241
+
242
+ # Config files
243
+ if suffix in [".yml", ".yaml", ".toml", ".ini", ".cfg", ".json"]:
244
+ return FileCategory.CONFIG
245
+
246
+ # Documentation
247
+ if suffix in [".md", ".rst", ".txt"] or path.name in ["README", "CHANGELOG", "LICENSE"]:
248
+ return FileCategory.DOCS
249
+
250
+ # Assets
251
+ if suffix in [".css", ".scss", ".html", ".svg", ".png", ".jpg", ".gif"]:
252
+ return FileCategory.ASSET
253
+
254
+ # Source code
255
+ if suffix in [".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".rs", ".java"]:
256
+ return FileCategory.SOURCE
257
+
258
+ return FileCategory.UNKNOWN
259
+
260
+ def _determine_language(self, path: Path) -> str:
261
+ """Determine the programming language of a file."""
262
+ suffix_map = {
263
+ ".py": "python",
264
+ ".js": "javascript",
265
+ ".ts": "typescript",
266
+ ".tsx": "typescript",
267
+ ".jsx": "javascript",
268
+ ".go": "go",
269
+ ".rs": "rust",
270
+ ".java": "java",
271
+ ".rb": "ruby",
272
+ ".php": "php",
273
+ ".cs": "csharp",
274
+ ".cpp": "cpp",
275
+ ".c": "c",
276
+ ".h": "c",
277
+ ".hpp": "cpp",
278
+ }
279
+ return suffix_map.get(path.suffix.lower(), "")
280
+
281
+ def _determine_test_requirement(self, path: Path, category: FileCategory) -> TestRequirement:
282
+ """Determine if a file requires tests."""
283
+ rel_path = path.relative_to(self.project_root)
284
+
285
+ # Test files don't need tests
286
+ if category == FileCategory.TEST:
287
+ return TestRequirement.NOT_APPLICABLE
288
+
289
+ # Config, docs, assets don't need tests
290
+ if category in [FileCategory.CONFIG, FileCategory.DOCS, FileCategory.ASSET]:
291
+ return TestRequirement.NOT_APPLICABLE
292
+
293
+ # Check exclusion patterns using glob matching
294
+ for pattern in self.config.no_test_patterns:
295
+ if self._matches_glob_pattern(rel_path, pattern):
296
+ return TestRequirement.NOT_APPLICABLE
297
+
298
+ # __init__.py files usually don't need tests unless they have logic
299
+ if path.name == "__init__.py":
300
+ try:
301
+ content = path.read_text(encoding="utf-8", errors="ignore")
302
+ # If it's just imports/exports, no tests needed
303
+ if len(content.strip().split("\n")) < 20:
304
+ return TestRequirement.OPTIONAL
305
+ except OSError:
306
+ pass
307
+
308
+ return TestRequirement.REQUIRED
309
+
310
+ def _analyze_code_metrics(self, path: Path, language: str) -> dict[str, Any]:
311
+ """Analyze code metrics for a file."""
312
+ metrics: dict[str, Any] = {
313
+ "lines_of_code": 0,
314
+ "lines_of_test": 0,
315
+ "complexity": 0.0,
316
+ "has_docstrings": False,
317
+ "has_type_hints": False,
318
+ "imports": [],
319
+ "test_count": 0,
320
+ }
321
+
322
+ if language != "python":
323
+ # For now, just count lines for non-Python
324
+ try:
325
+ content = path.read_text(encoding="utf-8", errors="ignore")
326
+ metrics["lines_of_code"] = len(content.split("\n"))
327
+ except OSError:
328
+ pass
329
+ return metrics
330
+
331
+ try:
332
+ content = path.read_text(encoding="utf-8", errors="ignore")
333
+ lines = content.split("\n")
334
+ metrics["lines_of_code"] = len(
335
+ [line for line in lines if line.strip() and not line.strip().startswith("#")],
336
+ )
337
+
338
+ # Parse AST for Python files
339
+ try:
340
+ tree = ast.parse(content)
341
+ metrics.update(self._analyze_python_ast(tree))
342
+ except (SyntaxError, ValueError):
343
+ # SyntaxError: invalid Python syntax
344
+ # ValueError: null bytes in source code
345
+ pass
346
+
347
+ except OSError:
348
+ pass
349
+
350
+ return metrics
351
+
352
+ def _analyze_python_ast(self, tree: ast.AST) -> dict[str, Any]:
353
+ """Analyze Python AST for metrics."""
354
+ result: dict[str, Any] = {
355
+ "has_docstrings": False,
356
+ "has_type_hints": False,
357
+ "imports": [],
358
+ "test_count": 0,
359
+ "complexity": 0.0,
360
+ }
361
+
362
+ for node in ast.walk(tree):
363
+ # Check for docstrings
364
+ if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.Module):
365
+ if ast.get_docstring(node):
366
+ result["has_docstrings"] = True
367
+
368
+ # Check for type hints
369
+ if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
370
+ if node.returns or any(arg.annotation for arg in node.args.args):
371
+ result["has_type_hints"] = True
372
+
373
+ # Count test functions
374
+ if node.name.startswith("test_"):
375
+ result["test_count"] += 1
376
+
377
+ # Simple complexity: count branches
378
+ for child in ast.walk(node):
379
+ if isinstance(
380
+ child,
381
+ ast.If | ast.For | ast.While | ast.Try | ast.ExceptHandler,
382
+ ):
383
+ result["complexity"] += 1.0
384
+
385
+ # Track imports
386
+ if isinstance(node, ast.Import):
387
+ for alias in node.names:
388
+ result["imports"].append(alias.name)
389
+ elif isinstance(node, ast.ImportFrom):
390
+ if node.module:
391
+ result["imports"].append(node.module)
392
+
393
+ return result
394
+
395
+ def _analyze_dependencies(self, records: list[FileRecord]) -> None:
396
+ """Build dependency graph between files."""
397
+ # Create lookup by module name
398
+ module_to_path: dict[str, str] = {}
399
+ for record in records:
400
+ if record.language == "python":
401
+ # Convert path to module name
402
+ module_name = record.path.replace("/", ".").replace("\\", ".").rstrip(".py")
403
+ module_to_path[module_name] = record.path
404
+
405
+ # Update imported_by relationships
406
+ for record in records:
407
+ for imp in record.imports:
408
+ # Find the imported module
409
+ for module_name, path in module_to_path.items():
410
+ if module_name.endswith(imp) or imp in module_name:
411
+ # Find the record for this path
412
+ for other in records:
413
+ if other.path == path:
414
+ if record.path not in other.imported_by:
415
+ other.imported_by.append(record.path)
416
+ other.imported_by_count = len(other.imported_by)
417
+ break
418
+ break
419
+
420
+ def _calculate_impact_scores(self, records: list[FileRecord]) -> None:
421
+ """Calculate impact score for each file."""
422
+ for record in records:
423
+ # Impact = imported_by_count * 2 + complexity * 0.5 + lines_of_code * 0.01
424
+ record.impact_score = (
425
+ record.imported_by_count * 2.0
426
+ + record.complexity_score * 0.5
427
+ + record.lines_of_code * 0.01
428
+ )
429
+
430
+ def _determine_attention_needs(self, records: list[FileRecord]) -> None:
431
+ """Determine which files need attention."""
432
+ for record in records:
433
+ reasons = []
434
+
435
+ # Stale tests
436
+ if record.is_stale:
437
+ reasons.append(f"Tests are {record.staleness_days} days stale")
438
+
439
+ # No tests but required
440
+ if record.test_requirement == TestRequirement.REQUIRED and not record.tests_exist:
441
+ reasons.append("Missing tests")
442
+
443
+ # Low coverage (if we have coverage data)
444
+ if (
445
+ record.coverage_percent > 0
446
+ and record.coverage_percent < self.config.low_coverage_threshold
447
+ ):
448
+ reasons.append(f"Low coverage ({record.coverage_percent:.1f}%)")
449
+
450
+ # High impact but no tests
451
+ if record.impact_score >= self.config.high_impact_threshold:
452
+ if not record.tests_exist and record.test_requirement == TestRequirement.REQUIRED:
453
+ reasons.append(f"High impact ({record.impact_score:.1f}) without tests")
454
+
455
+ record.attention_reasons = reasons
456
+ record.needs_attention = len(reasons) > 0
457
+
458
+ def _build_summary(self, records: list[FileRecord]) -> ProjectSummary:
459
+ """Build project summary from records."""
460
+ summary = ProjectSummary()
461
+
462
+ summary.total_files = len(records)
463
+ summary.source_files = sum(1 for r in records if r.category == FileCategory.SOURCE)
464
+ summary.test_files = sum(1 for r in records if r.category == FileCategory.TEST)
465
+ summary.config_files = sum(1 for r in records if r.category == FileCategory.CONFIG)
466
+ summary.doc_files = sum(1 for r in records if r.category == FileCategory.DOCS)
467
+
468
+ # Testing health
469
+ requiring_tests = [r for r in records if r.test_requirement == TestRequirement.REQUIRED]
470
+ summary.files_requiring_tests = len(requiring_tests)
471
+ summary.files_with_tests = sum(1 for r in requiring_tests if r.tests_exist)
472
+ summary.files_without_tests = summary.files_requiring_tests - summary.files_with_tests
473
+ summary.total_test_count = sum(
474
+ r.test_count for r in records if r.category == FileCategory.TEST
475
+ )
476
+
477
+ # Coverage average
478
+ covered = [r for r in records if r.coverage_percent > 0]
479
+ if covered:
480
+ summary.test_coverage_avg = sum(r.coverage_percent for r in covered) / len(covered)
481
+
482
+ # Staleness
483
+ stale = [r for r in records if r.is_stale]
484
+ summary.stale_file_count = len(stale)
485
+ if stale:
486
+ summary.avg_staleness_days = sum(r.staleness_days for r in stale) / len(stale)
487
+ top_stale = sorted(stale, key=lambda r: -r.staleness_days)[:5]
488
+ summary.most_stale_files = [r.path for r in top_stale]
489
+
490
+ # Code metrics
491
+ source_records = [r for r in records if r.category == FileCategory.SOURCE]
492
+ summary.total_lines_of_code = sum(r.lines_of_code for r in source_records)
493
+ summary.total_lines_of_test = sum(
494
+ r.lines_of_code for r in records if r.category == FileCategory.TEST
495
+ )
496
+ if summary.total_lines_of_code > 0:
497
+ summary.test_to_code_ratio = summary.total_lines_of_test / summary.total_lines_of_code
498
+ if source_records:
499
+ summary.avg_complexity = sum(r.complexity_score for r in source_records) / len(
500
+ source_records,
501
+ )
502
+
503
+ # Quality
504
+ if source_records:
505
+ summary.files_with_docstrings_pct = (
506
+ sum(1 for r in source_records if r.has_docstrings) / len(source_records) * 100
507
+ )
508
+ summary.files_with_type_hints_pct = (
509
+ sum(1 for r in source_records if r.has_type_hints) / len(source_records) * 100
510
+ )
511
+ summary.total_lint_issues = sum(r.lint_issues for r in records)
512
+
513
+ # High impact files
514
+ high_impact = sorted(records, key=lambda r: -r.impact_score)[:10]
515
+ summary.high_impact_files = [
516
+ r.path for r in high_impact if r.impact_score >= self.config.high_impact_threshold
517
+ ]
518
+
519
+ # Critical untested files (high impact + no tests)
520
+ critical = [
521
+ r
522
+ for r in records
523
+ if r.impact_score >= self.config.high_impact_threshold
524
+ and not r.tests_exist
525
+ and r.test_requirement == TestRequirement.REQUIRED
526
+ ]
527
+ summary.critical_untested_files = [
528
+ r.path for r in sorted(critical, key=lambda r: -r.impact_score)[:10]
529
+ ]
530
+
531
+ # Attention needed
532
+ needing_attention = [r for r in records if r.needs_attention]
533
+ summary.files_needing_attention = len(needing_attention)
534
+ summary.top_attention_files = [
535
+ r.path for r in sorted(needing_attention, key=lambda r: -r.impact_score)[:10]
536
+ ]
537
+
538
+ return summary
@@ -0,0 +1,61 @@
1
+ """XML-Enhanced Prompt System for Empathy Framework
2
+
3
+ Provides structured XML-based prompts for consistent LLM interactions
4
+ and response parsing across workflows.
5
+
6
+ Usage:
7
+ from empathy_os.prompts import (
8
+ XmlPromptConfig,
9
+ PromptContext,
10
+ XmlPromptTemplate,
11
+ XmlResponseParser,
12
+ get_template,
13
+ )
14
+
15
+ # Create a context
16
+ context = PromptContext.for_security_audit(code="...")
17
+
18
+ # Get a built-in template
19
+ template = get_template("security-audit")
20
+
21
+ # Render the prompt
22
+ prompt = template.render(context)
23
+
24
+ # Parse the response
25
+ parser = XmlResponseParser()
26
+ result = parser.parse(llm_response)
27
+
28
+ if result.success:
29
+ print(result.summary)
30
+ for finding in result.findings:
31
+ print(f"{finding.severity}: {finding.title}")
32
+
33
+ Copyright 2025 Smart-AI-Memory
34
+ Licensed under Fair Source License 0.9
35
+ """
36
+
37
+ from .config import XmlPromptConfig
38
+ from .context import PromptContext
39
+ from .parser import Finding, ParsedResponse, XmlResponseParser
40
+ from .registry import BUILTIN_TEMPLATES, get_template, list_templates, register_template
41
+ from .templates import PlainTextPromptTemplate, PromptTemplate, XmlPromptTemplate
42
+
43
+ __all__ = [
44
+ # Registry
45
+ "BUILTIN_TEMPLATES",
46
+ "Finding",
47
+ "ParsedResponse",
48
+ "PlainTextPromptTemplate",
49
+ # Context
50
+ "PromptContext",
51
+ # Templates
52
+ "PromptTemplate",
53
+ # Config
54
+ "XmlPromptConfig",
55
+ "XmlPromptTemplate",
56
+ # Parser
57
+ "XmlResponseParser",
58
+ "get_template",
59
+ "list_templates",
60
+ "register_template",
61
+ ]
@@ -0,0 +1,77 @@
1
+ """XML Prompt Configuration
2
+
3
+ Provides configuration dataclass for XML-enhanced prompts.
4
+
5
+ Copyright 2025 Smart-AI-Memory
6
+ Licensed under Fair Source License 0.9
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+
15
+ @dataclass
16
+ class XmlPromptConfig:
17
+ """Configuration for XML prompt behavior.
18
+
19
+ Attributes:
20
+ enabled: Whether XML prompts are enabled for this workflow/stage.
21
+ schema_version: XML schema version (default "1.0").
22
+ enforce_response_xml: If True, instruct model to respond with XML.
23
+ fallback_on_parse_error: If True, return raw text on XML parse failure.
24
+ template_name: Reference to a built-in template from BUILTIN_TEMPLATES.
25
+ custom_template: Inline XML template string (overrides template_name).
26
+ extra: Additional configuration options.
27
+
28
+ """
29
+
30
+ enabled: bool = False
31
+ schema_version: str = "1.0"
32
+ enforce_response_xml: bool = False
33
+ fallback_on_parse_error: bool = True
34
+ template_name: str | None = None
35
+ custom_template: str | None = None
36
+ extra: dict[str, Any] = field(default_factory=dict)
37
+
38
+ def merge_with(self, other: XmlPromptConfig) -> XmlPromptConfig:
39
+ """Merge this config with another, with 'other' taking precedence.
40
+
41
+ Useful for combining global defaults with workflow-specific overrides.
42
+ """
43
+ merged_extra = {**self.extra, **other.extra}
44
+ return XmlPromptConfig(
45
+ enabled=other.enabled if other.enabled else self.enabled,
46
+ schema_version=other.schema_version or self.schema_version,
47
+ enforce_response_xml=other.enforce_response_xml or self.enforce_response_xml,
48
+ fallback_on_parse_error=other.fallback_on_parse_error,
49
+ template_name=other.template_name or self.template_name,
50
+ custom_template=other.custom_template or self.custom_template,
51
+ extra=merged_extra,
52
+ )
53
+
54
+ @classmethod
55
+ def from_dict(cls, data: dict[str, Any]) -> XmlPromptConfig:
56
+ """Create XmlPromptConfig from a dictionary."""
57
+ return cls(
58
+ enabled=data.get("enabled", False),
59
+ schema_version=data.get("schema_version", "1.0"),
60
+ enforce_response_xml=data.get("enforce_response_xml", False),
61
+ fallback_on_parse_error=data.get("fallback_on_parse_error", True),
62
+ template_name=data.get("template_name"),
63
+ custom_template=data.get("custom_template"),
64
+ extra=data.get("extra", {}),
65
+ )
66
+
67
+ def to_dict(self) -> dict[str, Any]:
68
+ """Convert to dictionary for serialization."""
69
+ return {
70
+ "enabled": self.enabled,
71
+ "schema_version": self.schema_version,
72
+ "enforce_response_xml": self.enforce_response_xml,
73
+ "fallback_on_parse_error": self.fallback_on_parse_error,
74
+ "template_name": self.template_name,
75
+ "custom_template": self.custom_template,
76
+ "extra": self.extra,
77
+ }