empathy-framework 4.6.6__py3-none-any.whl → 4.7.0__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.
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/METADATA +7 -6
- empathy_framework-4.7.0.dist-info/RECORD +354 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/top_level.txt +0 -2
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
- empathy_llm_toolkit/agent_factory/__init__.py +6 -6
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
- empathy_llm_toolkit/agents_md/__init__.py +22 -0
- empathy_llm_toolkit/agents_md/loader.py +218 -0
- empathy_llm_toolkit/agents_md/parser.py +271 -0
- empathy_llm_toolkit/agents_md/registry.py +307 -0
- empathy_llm_toolkit/commands/__init__.py +51 -0
- empathy_llm_toolkit/commands/context.py +375 -0
- empathy_llm_toolkit/commands/loader.py +301 -0
- empathy_llm_toolkit/commands/models.py +231 -0
- empathy_llm_toolkit/commands/parser.py +371 -0
- empathy_llm_toolkit/commands/registry.py +429 -0
- empathy_llm_toolkit/config/__init__.py +8 -8
- empathy_llm_toolkit/config/unified.py +3 -7
- empathy_llm_toolkit/context/__init__.py +22 -0
- empathy_llm_toolkit/context/compaction.py +455 -0
- empathy_llm_toolkit/context/manager.py +434 -0
- empathy_llm_toolkit/hooks/__init__.py +24 -0
- empathy_llm_toolkit/hooks/config.py +306 -0
- empathy_llm_toolkit/hooks/executor.py +289 -0
- empathy_llm_toolkit/hooks/registry.py +302 -0
- empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
- empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
- empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
- empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
- empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
- empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
- empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
- empathy_llm_toolkit/learning/__init__.py +30 -0
- empathy_llm_toolkit/learning/evaluator.py +438 -0
- empathy_llm_toolkit/learning/extractor.py +514 -0
- empathy_llm_toolkit/learning/storage.py +560 -0
- empathy_llm_toolkit/providers.py +4 -11
- empathy_llm_toolkit/security/__init__.py +17 -17
- empathy_llm_toolkit/utils/tokens.py +2 -5
- empathy_os/__init__.py +202 -70
- empathy_os/cache_monitor.py +5 -3
- empathy_os/cli/__init__.py +11 -55
- empathy_os/cli/__main__.py +29 -15
- empathy_os/cli/commands/inspection.py +21 -12
- empathy_os/cli/commands/memory.py +4 -12
- empathy_os/cli/commands/profiling.py +198 -0
- empathy_os/cli/commands/utilities.py +27 -7
- empathy_os/cli.py +28 -57
- empathy_os/cli_unified.py +525 -1164
- empathy_os/cost_tracker.py +9 -3
- empathy_os/dashboard/server.py +200 -2
- empathy_os/hot_reload/__init__.py +7 -7
- empathy_os/hot_reload/config.py +6 -7
- empathy_os/hot_reload/integration.py +35 -35
- empathy_os/hot_reload/reloader.py +57 -57
- empathy_os/hot_reload/watcher.py +28 -28
- empathy_os/hot_reload/websocket.py +2 -2
- empathy_os/memory/__init__.py +11 -4
- empathy_os/memory/claude_memory.py +1 -1
- empathy_os/memory/cross_session.py +8 -12
- empathy_os/memory/edges.py +6 -6
- empathy_os/memory/file_session.py +770 -0
- empathy_os/memory/graph.py +30 -30
- empathy_os/memory/nodes.py +6 -6
- empathy_os/memory/short_term.py +15 -9
- empathy_os/memory/unified.py +606 -140
- empathy_os/meta_workflows/agent_creator.py +3 -9
- empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
- empathy_os/meta_workflows/form_engine.py +6 -18
- empathy_os/meta_workflows/intent_detector.py +64 -24
- empathy_os/meta_workflows/models.py +3 -1
- empathy_os/meta_workflows/pattern_learner.py +13 -31
- empathy_os/meta_workflows/plan_generator.py +55 -47
- empathy_os/meta_workflows/session_context.py +2 -3
- empathy_os/meta_workflows/workflow.py +20 -51
- empathy_os/models/cli.py +2 -2
- empathy_os/models/tasks.py +1 -2
- empathy_os/models/telemetry.py +4 -1
- empathy_os/models/token_estimator.py +3 -1
- empathy_os/monitoring/alerts.py +938 -9
- empathy_os/monitoring/alerts_cli.py +346 -183
- empathy_os/orchestration/execution_strategies.py +12 -29
- empathy_os/orchestration/pattern_learner.py +20 -26
- empathy_os/orchestration/real_tools.py +6 -15
- empathy_os/platform_utils.py +2 -1
- empathy_os/plugins/__init__.py +2 -2
- empathy_os/plugins/base.py +64 -64
- empathy_os/plugins/registry.py +32 -32
- empathy_os/project_index/index.py +49 -15
- empathy_os/project_index/models.py +1 -2
- empathy_os/project_index/reports.py +1 -1
- empathy_os/project_index/scanner.py +1 -0
- empathy_os/redis_memory.py +10 -7
- empathy_os/resilience/__init__.py +1 -1
- empathy_os/resilience/health.py +10 -10
- empathy_os/routing/__init__.py +7 -7
- empathy_os/routing/chain_executor.py +37 -37
- empathy_os/routing/classifier.py +36 -36
- empathy_os/routing/smart_router.py +40 -40
- empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
- empathy_os/scaffolding/__init__.py +8 -8
- empathy_os/scaffolding/__main__.py +1 -1
- empathy_os/scaffolding/cli.py +28 -28
- empathy_os/socratic/__init__.py +3 -19
- empathy_os/socratic/ab_testing.py +25 -36
- empathy_os/socratic/blueprint.py +38 -38
- empathy_os/socratic/cli.py +34 -20
- empathy_os/socratic/collaboration.py +30 -28
- empathy_os/socratic/domain_templates.py +9 -1
- empathy_os/socratic/embeddings.py +17 -13
- empathy_os/socratic/engine.py +135 -70
- empathy_os/socratic/explainer.py +70 -60
- empathy_os/socratic/feedback.py +24 -19
- empathy_os/socratic/forms.py +15 -10
- empathy_os/socratic/generator.py +51 -35
- empathy_os/socratic/llm_analyzer.py +25 -23
- empathy_os/socratic/mcp_server.py +99 -159
- empathy_os/socratic/session.py +19 -13
- empathy_os/socratic/storage.py +98 -67
- empathy_os/socratic/success.py +38 -27
- empathy_os/socratic/visual_editor.py +51 -39
- empathy_os/socratic/web_ui.py +99 -66
- empathy_os/telemetry/cli.py +3 -1
- empathy_os/telemetry/usage_tracker.py +1 -3
- empathy_os/test_generator/__init__.py +3 -3
- empathy_os/test_generator/cli.py +28 -28
- empathy_os/test_generator/generator.py +64 -66
- empathy_os/test_generator/risk_analyzer.py +11 -11
- empathy_os/vscode_bridge.py +173 -0
- empathy_os/workflows/__init__.py +212 -120
- empathy_os/workflows/batch_processing.py +8 -24
- empathy_os/workflows/bug_predict.py +1 -1
- empathy_os/workflows/code_review.py +20 -5
- empathy_os/workflows/code_review_pipeline.py +13 -8
- empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
- empathy_os/workflows/manage_documentation.py +1 -0
- empathy_os/workflows/orchestrated_health_check.py +6 -11
- empathy_os/workflows/orchestrated_release_prep.py +3 -3
- empathy_os/workflows/pr_review.py +18 -10
- empathy_os/workflows/progressive/__init__.py +2 -12
- empathy_os/workflows/progressive/cli.py +14 -37
- empathy_os/workflows/progressive/core.py +12 -12
- empathy_os/workflows/progressive/orchestrator.py +166 -144
- empathy_os/workflows/progressive/reports.py +22 -31
- empathy_os/workflows/progressive/telemetry.py +8 -14
- empathy_os/workflows/progressive/test_gen.py +29 -48
- empathy_os/workflows/progressive/workflow.py +31 -70
- empathy_os/workflows/release_prep.py +21 -6
- empathy_os/workflows/release_prep_crew.py +1 -0
- empathy_os/workflows/secure_release.py +13 -6
- empathy_os/workflows/security_audit.py +8 -3
- empathy_os/workflows/test_coverage_boost_crew.py +3 -2
- empathy_os/workflows/test_maintenance_crew.py +1 -0
- empathy_os/workflows/test_runner.py +16 -12
- empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
- empathy_software_plugin/cli.py +0 -122
- coach_wizards/__init__.py +0 -45
- coach_wizards/accessibility_wizard.py +0 -91
- coach_wizards/api_wizard.py +0 -91
- coach_wizards/base_wizard.py +0 -209
- coach_wizards/cicd_wizard.py +0 -91
- coach_wizards/code_reviewer_README.md +0 -60
- coach_wizards/code_reviewer_wizard.py +0 -180
- coach_wizards/compliance_wizard.py +0 -91
- coach_wizards/database_wizard.py +0 -91
- coach_wizards/debugging_wizard.py +0 -91
- coach_wizards/documentation_wizard.py +0 -91
- coach_wizards/generate_wizards.py +0 -347
- coach_wizards/localization_wizard.py +0 -173
- coach_wizards/migration_wizard.py +0 -91
- coach_wizards/monitoring_wizard.py +0 -91
- coach_wizards/observability_wizard.py +0 -91
- coach_wizards/performance_wizard.py +0 -91
- coach_wizards/prompt_engineering_wizard.py +0 -661
- coach_wizards/refactoring_wizard.py +0 -91
- coach_wizards/scaling_wizard.py +0 -90
- coach_wizards/security_wizard.py +0 -92
- coach_wizards/testing_wizard.py +0 -91
- empathy_framework-4.6.6.dist-info/RECORD +0 -410
- empathy_llm_toolkit/wizards/__init__.py +0 -43
- empathy_llm_toolkit/wizards/base_wizard.py +0 -364
- empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
- empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
- empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
- empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
- empathy_os/wizard_factory_cli.py +0 -170
- empathy_software_plugin/wizards/__init__.py +0 -42
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
- empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
- empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
- empathy_software_plugin/wizards/base_wizard.py +0 -288
- empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
- empathy_software_plugin/wizards/code_review_wizard.py +0 -604
- empathy_software_plugin/wizards/debugging/__init__.py +0 -50
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
- empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
- empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
- empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
- empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
- empathy_software_plugin/wizards/debugging/verification.py +0 -369
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
- empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
- empathy_software_plugin/wizards/performance/__init__.py +0 -9
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
- empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
- empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
- empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
- empathy_software_plugin/wizards/security/__init__.py +0 -32
- empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
- empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
- empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
- empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
- empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
- empathy_software_plugin/wizards/testing/__init__.py +0 -27
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
- empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
- empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
- empathy_software_plugin/wizards/testing_wizard.py +0 -274
- wizards/__init__.py +0 -82
- wizards/admission_assessment_wizard.py +0 -644
- wizards/care_plan.py +0 -321
- wizards/clinical_assessment.py +0 -769
- wizards/discharge_planning.py +0 -77
- wizards/discharge_summary_wizard.py +0 -468
- wizards/dosage_calculation.py +0 -497
- wizards/incident_report_wizard.py +0 -454
- wizards/medication_reconciliation.py +0 -85
- wizards/nursing_assessment.py +0 -171
- wizards/patient_education.py +0 -654
- wizards/quality_improvement.py +0 -705
- wizards/sbar_report.py +0 -324
- wizards/sbar_wizard.py +0 -608
- wizards/shift_handoff_wizard.py +0 -535
- wizards/soap_note_wizard.py +0 -679
- wizards/treatment_plan.py +0 -15
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/WHEEL +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/entry_points.txt +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/licenses/LICENSE +0 -0
empathy_os/cost_tracker.py
CHANGED
|
@@ -142,8 +142,12 @@ class CostTracker:
|
|
|
142
142
|
with open(self.costs_summary) as f:
|
|
143
143
|
summary_data = json.load(f)
|
|
144
144
|
self.data["daily_totals"] = summary_data.get("daily_totals", {})
|
|
145
|
-
self.data["created_at"] = summary_data.get(
|
|
146
|
-
|
|
145
|
+
self.data["created_at"] = summary_data.get(
|
|
146
|
+
"created_at", self.data["created_at"]
|
|
147
|
+
)
|
|
148
|
+
self.data["last_updated"] = summary_data.get(
|
|
149
|
+
"last_updated", self.data["last_updated"]
|
|
150
|
+
)
|
|
147
151
|
return # Summary loaded, done
|
|
148
152
|
except (OSError, json.JSONDecodeError):
|
|
149
153
|
pass # Fall through to JSON fallback
|
|
@@ -155,7 +159,9 @@ class CostTracker:
|
|
|
155
159
|
json_data = json.load(f)
|
|
156
160
|
self.data["daily_totals"] = json_data.get("daily_totals", {})
|
|
157
161
|
self.data["created_at"] = json_data.get("created_at", self.data["created_at"])
|
|
158
|
-
self.data["last_updated"] = json_data.get(
|
|
162
|
+
self.data["last_updated"] = json_data.get(
|
|
163
|
+
"last_updated", self.data["last_updated"]
|
|
164
|
+
)
|
|
159
165
|
# Don't load requests here - they'll be lazy-loaded
|
|
160
166
|
except (OSError, json.JSONDecodeError):
|
|
161
167
|
pass # Use defaults
|
empathy_os/dashboard/server.py
CHANGED
|
@@ -42,6 +42,14 @@ except ImportError:
|
|
|
42
42
|
list_workflows = None # type: ignore[assignment]
|
|
43
43
|
HAS_WORKFLOWS = False
|
|
44
44
|
|
|
45
|
+
try:
|
|
46
|
+
from empathy_os.models.telemetry import TelemetryStore
|
|
47
|
+
|
|
48
|
+
HAS_TELEMETRY = True
|
|
49
|
+
except ImportError:
|
|
50
|
+
TelemetryStore = None # type: ignore[misc, assignment]
|
|
51
|
+
HAS_TELEMETRY = False
|
|
52
|
+
|
|
45
53
|
|
|
46
54
|
class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
47
55
|
"""HTTP request handler for the dashboard."""
|
|
@@ -69,6 +77,8 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
69
77
|
self._serve_health()
|
|
70
78
|
elif path == "/api/workflows":
|
|
71
79
|
self._serve_workflows()
|
|
80
|
+
elif path == "/api/tests":
|
|
81
|
+
self._serve_tests()
|
|
72
82
|
else:
|
|
73
83
|
self.send_error(404, "Not Found")
|
|
74
84
|
|
|
@@ -119,6 +129,70 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
119
129
|
data = {"error": "Workflows not available"}
|
|
120
130
|
self._send_json(data)
|
|
121
131
|
|
|
132
|
+
def _serve_tests(self):
|
|
133
|
+
"""Serve test tracking data as JSON."""
|
|
134
|
+
data = self._get_test_stats()
|
|
135
|
+
self._send_json(data)
|
|
136
|
+
|
|
137
|
+
def _get_test_stats(self) -> dict:
|
|
138
|
+
"""Get test tracking statistics.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Dictionary with test tracking data including:
|
|
142
|
+
- total_files: Total files with test records
|
|
143
|
+
- passed_files: Files with passing tests
|
|
144
|
+
- failed_files: Files with failing tests
|
|
145
|
+
- coverage_avg: Average coverage percentage
|
|
146
|
+
- recent_tests: Recent test executions
|
|
147
|
+
- files_needing_tests: Files that need attention
|
|
148
|
+
"""
|
|
149
|
+
if not HAS_TELEMETRY or TelemetryStore is None:
|
|
150
|
+
return {"error": "Telemetry not available"}
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
store = TelemetryStore(Path(self.empathy_dir))
|
|
154
|
+
|
|
155
|
+
# Get file test records and convert to dicts
|
|
156
|
+
file_tests_raw = store.get_file_tests(limit=100)
|
|
157
|
+
file_tests = [t.to_dict() if hasattr(t, "to_dict") else t for t in file_tests_raw]
|
|
158
|
+
|
|
159
|
+
# Calculate stats
|
|
160
|
+
total_files = len(file_tests)
|
|
161
|
+
passed_files = sum(1 for t in file_tests if t.get("last_test_result") == "passed")
|
|
162
|
+
failed_files = sum(1 for t in file_tests if t.get("last_test_result") == "failed")
|
|
163
|
+
|
|
164
|
+
# Coverage average (field is coverage_percent)
|
|
165
|
+
coverages = [
|
|
166
|
+
t.get("coverage_percent", 0) for t in file_tests if t.get("coverage_percent")
|
|
167
|
+
]
|
|
168
|
+
coverage_avg = sum(coverages) / len(coverages) if coverages else 0
|
|
169
|
+
|
|
170
|
+
# If no coverage data, use pass rate as a proxy
|
|
171
|
+
if coverage_avg == 0 and total_files > 0:
|
|
172
|
+
coverage_avg = (passed_files / total_files) * 100
|
|
173
|
+
|
|
174
|
+
# Get files needing attention (failed or stale) and convert to dicts
|
|
175
|
+
files_needing_raw = store.get_files_needing_tests(stale_only=False, failed_only=False)
|
|
176
|
+
files_needing_tests = [
|
|
177
|
+
t.to_dict() if hasattr(t, "to_dict") else t for t in files_needing_raw[:10]
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
# Get recent test executions and convert to dicts
|
|
181
|
+
recent_raw = store.get_test_executions(limit=10)
|
|
182
|
+
recent_executions = [t.to_dict() if hasattr(t, "to_dict") else t for t in recent_raw]
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"total_files": total_files,
|
|
186
|
+
"passed_files": passed_files,
|
|
187
|
+
"failed_files": failed_files,
|
|
188
|
+
"coverage_avg": round(coverage_avg, 1),
|
|
189
|
+
"files_needing_tests": files_needing_tests,
|
|
190
|
+
"recent_executions": recent_executions,
|
|
191
|
+
"file_tests": file_tests[:20], # Most recent 20
|
|
192
|
+
}
|
|
193
|
+
except Exception as e:
|
|
194
|
+
return {"error": str(e)}
|
|
195
|
+
|
|
122
196
|
def _send_json(self, data):
|
|
123
197
|
"""Send JSON response."""
|
|
124
198
|
content = json.dumps(data, indent=2, default=str)
|
|
@@ -183,7 +257,8 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
183
257
|
try:
|
|
184
258
|
tracker = CostTracker(self.empathy_dir)
|
|
185
259
|
cost_summary = tracker.get_summary(30) # noqa: F841
|
|
186
|
-
except Exception:
|
|
260
|
+
except Exception: # noqa: BLE001
|
|
261
|
+
# INTENTIONAL: Dashboard should render even if cost tracking unavailable.
|
|
187
262
|
pass
|
|
188
263
|
|
|
189
264
|
# Get workflow stats
|
|
@@ -198,9 +273,13 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
198
273
|
if HAS_WORKFLOWS and get_workflow_stats is not None:
|
|
199
274
|
try:
|
|
200
275
|
workflow_stats = get_workflow_stats()
|
|
201
|
-
except Exception:
|
|
276
|
+
except Exception: # noqa: BLE001
|
|
277
|
+
# INTENTIONAL: Dashboard should render even if workflow stats unavailable.
|
|
202
278
|
pass
|
|
203
279
|
|
|
280
|
+
# Get test stats
|
|
281
|
+
test_stats = self._get_test_stats()
|
|
282
|
+
|
|
204
283
|
return f"""<!DOCTYPE html>
|
|
205
284
|
<html lang="en">
|
|
206
285
|
<head>
|
|
@@ -507,6 +586,12 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
507
586
|
<div class="value">${workflow_stats.get("total_savings", 0):.2f}</div>
|
|
508
587
|
<div class="label">workflows + API</div>
|
|
509
588
|
</div>
|
|
589
|
+
|
|
590
|
+
<div class="card {"success" if test_stats.get("failed_files", 0) == 0 else "warning"}">
|
|
591
|
+
<h2>Test Coverage</h2>
|
|
592
|
+
<div class="value">{test_stats.get("coverage_avg", 0):.0f}%</div>
|
|
593
|
+
<div class="label">{test_stats.get("total_files", 0)} files tracked</div>
|
|
594
|
+
</div>
|
|
510
595
|
</div>
|
|
511
596
|
|
|
512
597
|
<div class="section">
|
|
@@ -539,6 +624,11 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
539
624
|
{self._render_recent_runs(workflow_stats)}
|
|
540
625
|
</div>
|
|
541
626
|
|
|
627
|
+
<div class="section">
|
|
628
|
+
<h2>Test Tracking</h2>
|
|
629
|
+
{self._render_test_tracking(test_stats)}
|
|
630
|
+
</div>
|
|
631
|
+
|
|
542
632
|
<div class="section">
|
|
543
633
|
<h2>Quick Commands</h2>
|
|
544
634
|
<p>Run these commands for common tasks:</p>
|
|
@@ -684,6 +774,114 @@ class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
|
684
774
|
|
|
685
775
|
return "".join(runs_html)
|
|
686
776
|
|
|
777
|
+
def _render_test_tracking(self, test_stats: dict) -> str:
|
|
778
|
+
"""Render test tracking section."""
|
|
779
|
+
if "error" in test_stats:
|
|
780
|
+
return f'<p style="color: var(--text-muted);">{test_stats["error"]}</p>'
|
|
781
|
+
|
|
782
|
+
total = test_stats.get("total_files", 0)
|
|
783
|
+
passed = test_stats.get("passed_files", 0)
|
|
784
|
+
failed = test_stats.get("failed_files", 0)
|
|
785
|
+
coverage = test_stats.get("coverage_avg", 0)
|
|
786
|
+
|
|
787
|
+
if total == 0:
|
|
788
|
+
return '<p style="color: var(--text-muted);">No test tracking data yet. Run tests with empathy to start tracking.</p>'
|
|
789
|
+
|
|
790
|
+
# Calculate pass rate
|
|
791
|
+
pass_rate = (passed / total * 100) if total > 0 else 0
|
|
792
|
+
|
|
793
|
+
# Stats grid
|
|
794
|
+
html = f"""
|
|
795
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem;">
|
|
796
|
+
<div style="text-align: center; padding: 1rem; background: var(--bg); border-radius: 8px;">
|
|
797
|
+
<div style="font-size: 1.5rem; font-weight: 600; color: var(--success);">{passed}</div>
|
|
798
|
+
<div style="font-size: 0.75rem; color: var(--text-muted);">Passing</div>
|
|
799
|
+
</div>
|
|
800
|
+
<div style="text-align: center; padding: 1rem; background: var(--bg); border-radius: 8px;">
|
|
801
|
+
<div style="font-size: 1.5rem; font-weight: 600; color: var(--danger);">{failed}</div>
|
|
802
|
+
<div style="font-size: 0.75rem; color: var(--text-muted);">Failing</div>
|
|
803
|
+
</div>
|
|
804
|
+
<div style="text-align: center; padding: 1rem; background: var(--bg); border-radius: 8px;">
|
|
805
|
+
<div style="font-size: 1.5rem; font-weight: 600; color: var(--primary);">{pass_rate:.0f}%</div>
|
|
806
|
+
<div style="font-size: 0.75rem; color: var(--text-muted);">Pass Rate</div>
|
|
807
|
+
</div>
|
|
808
|
+
<div style="text-align: center; padding: 1rem; background: var(--bg); border-radius: 8px;">
|
|
809
|
+
<div style="font-size: 1.5rem; font-weight: 600; color: var(--warning);">{coverage:.0f}%</div>
|
|
810
|
+
<div style="font-size: 0.75rem; color: var(--text-muted);">Coverage</div>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
# Files needing attention
|
|
816
|
+
files_needing = test_stats.get("files_needing_tests", [])
|
|
817
|
+
if files_needing:
|
|
818
|
+
html += """
|
|
819
|
+
<h3 style="margin-bottom: 0.5rem; font-size: 1rem;">Files Needing Attention</h3>
|
|
820
|
+
<table>
|
|
821
|
+
<thead>
|
|
822
|
+
<tr>
|
|
823
|
+
<th>File</th>
|
|
824
|
+
<th>Status</th>
|
|
825
|
+
<th>Last Run</th>
|
|
826
|
+
</tr>
|
|
827
|
+
</thead>
|
|
828
|
+
<tbody>
|
|
829
|
+
"""
|
|
830
|
+
for file_record in files_needing[:5]:
|
|
831
|
+
file_path = file_record.get("file_path", "unknown")
|
|
832
|
+
# Truncate long paths
|
|
833
|
+
display_path = ("..." + file_path[-40:]) if len(file_path) > 40 else file_path
|
|
834
|
+
result = file_record.get("last_test_result", "unknown")
|
|
835
|
+
timestamp = (
|
|
836
|
+
file_record.get("timestamp", "")[:10] if file_record.get("timestamp") else "-"
|
|
837
|
+
)
|
|
838
|
+
status_class = "resolved" if result == "passed" else "investigating"
|
|
839
|
+
html += f"""
|
|
840
|
+
<tr>
|
|
841
|
+
<td title="{file_path}">{display_path}</td>
|
|
842
|
+
<td><span class="status {status_class}">{result}</span></td>
|
|
843
|
+
<td>{timestamp}</td>
|
|
844
|
+
</tr>
|
|
845
|
+
"""
|
|
846
|
+
html += "</tbody></table>"
|
|
847
|
+
|
|
848
|
+
# Recent test executions
|
|
849
|
+
recent = test_stats.get("recent_executions", [])
|
|
850
|
+
if recent:
|
|
851
|
+
html += """
|
|
852
|
+
<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem; font-size: 1rem;">Recent Test Runs</h3>
|
|
853
|
+
"""
|
|
854
|
+
for execution in recent[:5]:
|
|
855
|
+
suite = execution.get("test_suite", "unknown")
|
|
856
|
+
total_tests = execution.get("total_tests", 0)
|
|
857
|
+
exec_passed = execution.get("passed", 0)
|
|
858
|
+
exec_failed = execution.get("failed", 0)
|
|
859
|
+
duration = execution.get("duration_seconds", 0)
|
|
860
|
+
timestamp = (
|
|
861
|
+
execution.get("timestamp", "")[:16].replace("T", " ")
|
|
862
|
+
if execution.get("timestamp")
|
|
863
|
+
else "-"
|
|
864
|
+
)
|
|
865
|
+
success = execution.get("success", False)
|
|
866
|
+
|
|
867
|
+
status_icon = "✓" if success else "✗"
|
|
868
|
+
status_color = "var(--success)" if success else "var(--danger)"
|
|
869
|
+
|
|
870
|
+
html += f"""
|
|
871
|
+
<div class="recent-run">
|
|
872
|
+
<span style="color: {status_color};">{status_icon}</span>
|
|
873
|
+
<span class="name">{suite}</span>
|
|
874
|
+
<span class="provider">{total_tests} tests</span>
|
|
875
|
+
<span class="result">
|
|
876
|
+
<span style="color: var(--success);">{exec_passed} passed</span>
|
|
877
|
+
{f'<span style="color: var(--danger);">{exec_failed} failed</span>' if exec_failed > 0 else ""}
|
|
878
|
+
<span class="time">{duration:.1f}s | {timestamp}</span>
|
|
879
|
+
</span>
|
|
880
|
+
</div>
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
return html
|
|
884
|
+
|
|
687
885
|
|
|
688
886
|
class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
689
887
|
"""Threaded HTTP server."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Hot-Reload Infrastructure for
|
|
1
|
+
"""Hot-Reload Infrastructure for Workflow Factory.
|
|
2
2
|
|
|
3
|
-
Enables real-time
|
|
3
|
+
Enables real-time workflow reloading during development without server restarts.
|
|
4
4
|
|
|
5
5
|
Features:
|
|
6
6
|
- File system monitoring with watchdog
|
|
@@ -13,7 +13,7 @@ Usage:
|
|
|
13
13
|
from hot_reload.integration import HotReloadIntegration
|
|
14
14
|
|
|
15
15
|
app = FastAPI()
|
|
16
|
-
hot_reload = HotReloadIntegration(app,
|
|
16
|
+
hot_reload = HotReloadIntegration(app, register_workflow)
|
|
17
17
|
|
|
18
18
|
@app.on_event("startup")
|
|
19
19
|
async def startup():
|
|
@@ -35,8 +35,8 @@ Licensed under Fair Source 0.9
|
|
|
35
35
|
|
|
36
36
|
from .config import HotReloadConfig, get_hot_reload_config
|
|
37
37
|
from .integration import HotReloadIntegration
|
|
38
|
-
from .reloader import ReloadResult,
|
|
39
|
-
from .watcher import
|
|
38
|
+
from .reloader import ReloadResult, WorkflowReloader
|
|
39
|
+
from .watcher import WorkflowFileWatcher
|
|
40
40
|
from .websocket import (
|
|
41
41
|
ReloadNotificationManager,
|
|
42
42
|
create_notification_callback,
|
|
@@ -45,8 +45,8 @@ from .websocket import (
|
|
|
45
45
|
|
|
46
46
|
__all__ = [
|
|
47
47
|
# Core components
|
|
48
|
-
"
|
|
49
|
-
"
|
|
48
|
+
"WorkflowFileWatcher",
|
|
49
|
+
"WorkflowReloader",
|
|
50
50
|
"ReloadResult",
|
|
51
51
|
# WebSocket
|
|
52
52
|
"ReloadNotificationManager",
|
empathy_os/hot_reload/config.py
CHANGED
|
@@ -16,7 +16,7 @@ class HotReloadConfig:
|
|
|
16
16
|
# Development mode (enables hot-reload)
|
|
17
17
|
self.enabled = os.getenv("HOT_RELOAD_ENABLED", "false").lower() == "true"
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Workflow directories to watch
|
|
20
20
|
self.watch_dirs = self._get_watch_dirs()
|
|
21
21
|
|
|
22
22
|
# WebSocket endpoint for notifications
|
|
@@ -26,20 +26,19 @@ class HotReloadConfig:
|
|
|
26
26
|
self.reload_delay = float(os.getenv("HOT_RELOAD_DELAY", "0.5"))
|
|
27
27
|
|
|
28
28
|
def _get_watch_dirs(self) -> list[Path]:
|
|
29
|
-
"""Get directories to watch for
|
|
29
|
+
"""Get directories to watch for workflow changes.
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
32
|
List of directories to watch
|
|
33
33
|
|
|
34
34
|
"""
|
|
35
|
-
# Default
|
|
35
|
+
# Default workflow directories
|
|
36
36
|
project_root = Path(__file__).parent.parent
|
|
37
37
|
|
|
38
38
|
default_dirs = [
|
|
39
|
-
project_root / "
|
|
40
|
-
project_root / "
|
|
41
|
-
project_root / "
|
|
42
|
-
project_root / "empathy_llm_toolkit" / "wizards",
|
|
39
|
+
project_root / "workflows",
|
|
40
|
+
project_root / "empathy_software_plugin" / "workflows",
|
|
41
|
+
project_root / "empathy_llm_toolkit" / "workflows",
|
|
43
42
|
]
|
|
44
43
|
|
|
45
44
|
# Filter to only existing directories
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Integration example for hot-reload with
|
|
1
|
+
"""Integration example for hot-reload with workflow API.
|
|
2
2
|
|
|
3
|
-
Shows how to integrate hot-reload into the existing
|
|
3
|
+
Shows how to integrate hot-reload into the existing workflow_api.py.
|
|
4
4
|
|
|
5
5
|
Copyright 2025 Smart AI Memory, LLC
|
|
6
6
|
Licensed under Fair Source 0.9
|
|
@@ -12,17 +12,17 @@ from collections.abc import Callable
|
|
|
12
12
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
13
13
|
|
|
14
14
|
from .config import get_hot_reload_config
|
|
15
|
-
from .reloader import
|
|
16
|
-
from .watcher import
|
|
15
|
+
from .reloader import WorkflowReloader
|
|
16
|
+
from .watcher import WorkflowFileWatcher
|
|
17
17
|
from .websocket import create_notification_callback, get_notification_manager
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class HotReloadIntegration:
|
|
23
|
-
"""Integrates hot-reload with
|
|
23
|
+
"""Integrates hot-reload with workflow API.
|
|
24
24
|
|
|
25
|
-
Example usage in
|
|
25
|
+
Example usage in workflow_api.py:
|
|
26
26
|
|
|
27
27
|
from hot_reload.integration import HotReloadIntegration
|
|
28
28
|
|
|
@@ -30,11 +30,11 @@ class HotReloadIntegration:
|
|
|
30
30
|
app = FastAPI()
|
|
31
31
|
|
|
32
32
|
# Initialize hot-reload (if enabled)
|
|
33
|
-
hot_reload = HotReloadIntegration(app,
|
|
33
|
+
hot_reload = HotReloadIntegration(app, register_workflow)
|
|
34
34
|
|
|
35
35
|
@app.on_event("startup")
|
|
36
36
|
async def startup_event():
|
|
37
|
-
|
|
37
|
+
init_workflows() # Initialize workflows
|
|
38
38
|
hot_reload.start() # Start hot-reload watcher
|
|
39
39
|
|
|
40
40
|
@app.on_event("shutdown")
|
|
@@ -52,7 +52,7 @@ class HotReloadIntegration:
|
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
app: FastAPI application instance
|
|
55
|
-
register_callback: Function to register
|
|
55
|
+
register_callback: Function to register workflow (workflow_id, workflow_class) -> bool
|
|
56
56
|
|
|
57
57
|
"""
|
|
58
58
|
self.app = app
|
|
@@ -61,32 +61,32 @@ class HotReloadIntegration:
|
|
|
61
61
|
|
|
62
62
|
# Initialize components
|
|
63
63
|
self.notification_callback = create_notification_callback()
|
|
64
|
-
self.reloader =
|
|
65
|
-
register_callback=self.
|
|
64
|
+
self.reloader = WorkflowReloader(
|
|
65
|
+
register_callback=self._register_workflow_wrapper,
|
|
66
66
|
notification_callback=self.notification_callback,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
self.watcher:
|
|
69
|
+
self.watcher: WorkflowFileWatcher | None = None
|
|
70
70
|
|
|
71
71
|
# Add WebSocket endpoint to app
|
|
72
72
|
if self.config.enabled:
|
|
73
73
|
self._setup_websocket_endpoint()
|
|
74
74
|
|
|
75
|
-
def
|
|
75
|
+
def _register_workflow_wrapper(self, workflow_id: str, workflow_class: type) -> bool:
|
|
76
76
|
"""Wrapper for register callback that handles errors.
|
|
77
77
|
|
|
78
78
|
Args:
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
workflow_id: Workflow identifier
|
|
80
|
+
workflow_class: Workflow class to register
|
|
81
81
|
|
|
82
82
|
Returns:
|
|
83
83
|
True if registration succeeded
|
|
84
84
|
|
|
85
85
|
"""
|
|
86
86
|
try:
|
|
87
|
-
return self.register_callback(
|
|
87
|
+
return self.register_callback(workflow_id, workflow_class)
|
|
88
88
|
except Exception as e:
|
|
89
|
-
logger.error(f"Error registering
|
|
89
|
+
logger.error(f"Error registering workflow {workflow_id}: {e}")
|
|
90
90
|
return False
|
|
91
91
|
|
|
92
92
|
def _setup_websocket_endpoint(self) -> None:
|
|
@@ -120,7 +120,7 @@ class HotReloadIntegration:
|
|
|
120
120
|
return
|
|
121
121
|
|
|
122
122
|
if not self.config.watch_dirs:
|
|
123
|
-
logger.warning("No
|
|
123
|
+
logger.warning("No workflow directories found to watch")
|
|
124
124
|
return
|
|
125
125
|
|
|
126
126
|
if self.watcher and self.watcher.is_running():
|
|
@@ -128,8 +128,8 @@ class HotReloadIntegration:
|
|
|
128
128
|
return
|
|
129
129
|
|
|
130
130
|
# Create watcher
|
|
131
|
-
self.watcher =
|
|
132
|
-
|
|
131
|
+
self.watcher = WorkflowFileWatcher(
|
|
132
|
+
workflow_dirs=self.config.watch_dirs,
|
|
133
133
|
reload_callback=self._on_file_change,
|
|
134
134
|
)
|
|
135
135
|
|
|
@@ -145,18 +145,18 @@ class HotReloadIntegration:
|
|
|
145
145
|
self.watcher = None
|
|
146
146
|
logger.info("Hot-reload stopped")
|
|
147
147
|
|
|
148
|
-
def _on_file_change(self,
|
|
148
|
+
def _on_file_change(self, workflow_id: str, file_path: str) -> None:
|
|
149
149
|
"""Handle file change event.
|
|
150
150
|
|
|
151
151
|
Args:
|
|
152
|
-
|
|
152
|
+
workflow_id: ID of workflow that changed
|
|
153
153
|
file_path: Path to changed file
|
|
154
154
|
|
|
155
155
|
"""
|
|
156
|
-
logger.info(f"File change detected: {
|
|
156
|
+
logger.info(f"File change detected: {workflow_id} ({file_path})")
|
|
157
157
|
|
|
158
|
-
# Reload
|
|
159
|
-
result = self.reloader.
|
|
158
|
+
# Reload workflow
|
|
159
|
+
result = self.reloader.reload_workflow(workflow_id, file_path)
|
|
160
160
|
|
|
161
161
|
if result.success:
|
|
162
162
|
logger.info(f"✓ {result.message}")
|
|
@@ -180,25 +180,25 @@ class HotReloadIntegration:
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
# Example usage in
|
|
183
|
+
# Example usage in workflow_api.py:
|
|
184
184
|
"""
|
|
185
185
|
from fastapi import FastAPI
|
|
186
186
|
from hot_reload.integration import HotReloadIntegration
|
|
187
187
|
|
|
188
|
-
app = FastAPI(title="Empathy
|
|
188
|
+
app = FastAPI(title="Empathy Workflow API")
|
|
189
189
|
|
|
190
190
|
# Global hot-reload instance
|
|
191
191
|
hot_reload = None
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
def
|
|
195
|
-
'''Register
|
|
194
|
+
def register_workflow(workflow_id: str, workflow_class: type, *args, **kwargs) -> bool:
|
|
195
|
+
'''Register workflow with WORKFLOWS dict'''
|
|
196
196
|
try:
|
|
197
|
-
|
|
198
|
-
logger.info(f"✓ Registered
|
|
197
|
+
WORKFLOWS[workflow_id] = workflow_class(*args, **kwargs)
|
|
198
|
+
logger.info(f"✓ Registered workflow: {workflow_id}")
|
|
199
199
|
return True
|
|
200
200
|
except Exception as e:
|
|
201
|
-
logger.error(f"Failed to register {
|
|
201
|
+
logger.error(f"Failed to register {workflow_id}: {e}")
|
|
202
202
|
return False
|
|
203
203
|
|
|
204
204
|
|
|
@@ -206,11 +206,11 @@ def register_wizard(wizard_id: str, wizard_class: type, *args, **kwargs) -> bool
|
|
|
206
206
|
async def startup_event():
|
|
207
207
|
global hot_reload
|
|
208
208
|
|
|
209
|
-
# Initialize
|
|
210
|
-
|
|
209
|
+
# Initialize workflows
|
|
210
|
+
init_workflows()
|
|
211
211
|
|
|
212
212
|
# Start hot-reload
|
|
213
|
-
hot_reload = HotReloadIntegration(app,
|
|
213
|
+
hot_reload = HotReloadIntegration(app, register_workflow)
|
|
214
214
|
hot_reload.start()
|
|
215
215
|
|
|
216
216
|
|