empathy-framework 3.2.3__py3-none-any.whl → 3.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. coach_wizards/__init__.py +11 -12
  2. coach_wizards/accessibility_wizard.py +12 -12
  3. coach_wizards/api_wizard.py +12 -12
  4. coach_wizards/base_wizard.py +26 -20
  5. coach_wizards/cicd_wizard.py +15 -13
  6. coach_wizards/code_reviewer_README.md +60 -0
  7. coach_wizards/code_reviewer_wizard.py +180 -0
  8. coach_wizards/compliance_wizard.py +12 -12
  9. coach_wizards/database_wizard.py +12 -12
  10. coach_wizards/debugging_wizard.py +12 -12
  11. coach_wizards/documentation_wizard.py +12 -12
  12. coach_wizards/generate_wizards.py +1 -2
  13. coach_wizards/localization_wizard.py +101 -19
  14. coach_wizards/migration_wizard.py +12 -12
  15. coach_wizards/monitoring_wizard.py +12 -12
  16. coach_wizards/observability_wizard.py +12 -12
  17. coach_wizards/performance_wizard.py +12 -12
  18. coach_wizards/prompt_engineering_wizard.py +22 -25
  19. coach_wizards/refactoring_wizard.py +12 -12
  20. coach_wizards/scaling_wizard.py +12 -12
  21. coach_wizards/security_wizard.py +12 -12
  22. coach_wizards/testing_wizard.py +12 -12
  23. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/METADATA +513 -58
  24. empathy_framework-3.8.2.dist-info/RECORD +333 -0
  25. empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
  26. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
  27. empathy_healthcare_plugin/__init__.py +1 -2
  28. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  29. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  30. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  31. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  32. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  33. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  34. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  35. empathy_llm_toolkit/__init__.py +7 -7
  36. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  37. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  38. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  39. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  40. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  41. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  42. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  43. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  44. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  45. empathy_llm_toolkit/agent_factory/base.py +305 -0
  46. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  47. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  48. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  49. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  50. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  51. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  52. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  53. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  54. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  55. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  56. empathy_llm_toolkit/claude_memory.py +14 -15
  57. empathy_llm_toolkit/cli/__init__.py +8 -0
  58. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  59. empathy_llm_toolkit/code_health.py +177 -22
  60. empathy_llm_toolkit/config/__init__.py +29 -0
  61. empathy_llm_toolkit/config/unified.py +295 -0
  62. empathy_llm_toolkit/contextual_patterns.py +11 -12
  63. empathy_llm_toolkit/core.py +51 -49
  64. empathy_llm_toolkit/git_pattern_extractor.py +16 -12
  65. empathy_llm_toolkit/levels.py +6 -13
  66. empathy_llm_toolkit/pattern_confidence.py +14 -18
  67. empathy_llm_toolkit/pattern_resolver.py +10 -12
  68. empathy_llm_toolkit/pattern_summary.py +13 -11
  69. empathy_llm_toolkit/providers.py +194 -28
  70. empathy_llm_toolkit/routing/__init__.py +32 -0
  71. empathy_llm_toolkit/routing/model_router.py +362 -0
  72. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  73. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  74. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  75. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  76. empathy_llm_toolkit/security/README.md +262 -0
  77. empathy_llm_toolkit/security/__init__.py +62 -0
  78. empathy_llm_toolkit/security/audit_logger.py +929 -0
  79. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  80. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  81. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  82. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  83. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  84. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  85. empathy_llm_toolkit/session_status.py +18 -20
  86. empathy_llm_toolkit/state.py +20 -21
  87. empathy_llm_toolkit/wizards/__init__.py +38 -0
  88. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  89. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  90. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  91. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  92. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  93. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  94. empathy_os/__init__.py +76 -77
  95. empathy_os/adaptive/__init__.py +13 -0
  96. empathy_os/adaptive/task_complexity.py +127 -0
  97. empathy_os/{monitoring.py → agent_monitoring.py} +27 -27
  98. empathy_os/cache/__init__.py +117 -0
  99. empathy_os/cache/base.py +166 -0
  100. empathy_os/cache/dependency_manager.py +253 -0
  101. empathy_os/cache/hash_only.py +248 -0
  102. empathy_os/cache/hybrid.py +390 -0
  103. empathy_os/cache/storage.py +282 -0
  104. empathy_os/cli.py +515 -109
  105. empathy_os/cli_unified.py +189 -42
  106. empathy_os/config/__init__.py +63 -0
  107. empathy_os/config/xml_config.py +239 -0
  108. empathy_os/config.py +87 -36
  109. empathy_os/coordination.py +48 -54
  110. empathy_os/core.py +90 -99
  111. empathy_os/cost_tracker.py +20 -23
  112. empathy_os/dashboard/__init__.py +15 -0
  113. empathy_os/dashboard/server.py +743 -0
  114. empathy_os/discovery.py +9 -11
  115. empathy_os/emergence.py +20 -21
  116. empathy_os/exceptions.py +18 -30
  117. empathy_os/feedback_loops.py +27 -30
  118. empathy_os/levels.py +31 -34
  119. empathy_os/leverage_points.py +27 -28
  120. empathy_os/logging_config.py +11 -12
  121. empathy_os/memory/__init__.py +195 -0
  122. empathy_os/memory/claude_memory.py +466 -0
  123. empathy_os/memory/config.py +224 -0
  124. empathy_os/memory/control_panel.py +1298 -0
  125. empathy_os/memory/edges.py +179 -0
  126. empathy_os/memory/graph.py +567 -0
  127. empathy_os/memory/long_term.py +1194 -0
  128. empathy_os/memory/nodes.py +179 -0
  129. empathy_os/memory/redis_bootstrap.py +540 -0
  130. empathy_os/memory/security/__init__.py +31 -0
  131. empathy_os/memory/security/audit_logger.py +930 -0
  132. empathy_os/memory/security/pii_scrubber.py +640 -0
  133. empathy_os/memory/security/secrets_detector.py +678 -0
  134. empathy_os/memory/short_term.py +2119 -0
  135. empathy_os/memory/storage/__init__.py +15 -0
  136. empathy_os/memory/summary_index.py +583 -0
  137. empathy_os/memory/unified.py +619 -0
  138. empathy_os/metrics/__init__.py +12 -0
  139. empathy_os/metrics/prompt_metrics.py +190 -0
  140. empathy_os/models/__init__.py +136 -0
  141. empathy_os/models/__main__.py +13 -0
  142. empathy_os/models/cli.py +655 -0
  143. empathy_os/models/empathy_executor.py +354 -0
  144. empathy_os/models/executor.py +252 -0
  145. empathy_os/models/fallback.py +671 -0
  146. empathy_os/models/provider_config.py +563 -0
  147. empathy_os/models/registry.py +382 -0
  148. empathy_os/models/tasks.py +302 -0
  149. empathy_os/models/telemetry.py +548 -0
  150. empathy_os/models/token_estimator.py +378 -0
  151. empathy_os/models/validation.py +274 -0
  152. empathy_os/monitoring/__init__.py +52 -0
  153. empathy_os/monitoring/alerts.py +23 -0
  154. empathy_os/monitoring/alerts_cli.py +268 -0
  155. empathy_os/monitoring/multi_backend.py +271 -0
  156. empathy_os/monitoring/otel_backend.py +363 -0
  157. empathy_os/optimization/__init__.py +19 -0
  158. empathy_os/optimization/context_optimizer.py +272 -0
  159. empathy_os/pattern_library.py +29 -28
  160. empathy_os/persistence.py +30 -34
  161. empathy_os/platform_utils.py +261 -0
  162. empathy_os/plugins/__init__.py +28 -0
  163. empathy_os/plugins/base.py +361 -0
  164. empathy_os/plugins/registry.py +268 -0
  165. empathy_os/project_index/__init__.py +30 -0
  166. empathy_os/project_index/cli.py +335 -0
  167. empathy_os/project_index/crew_integration.py +430 -0
  168. empathy_os/project_index/index.py +425 -0
  169. empathy_os/project_index/models.py +501 -0
  170. empathy_os/project_index/reports.py +473 -0
  171. empathy_os/project_index/scanner.py +538 -0
  172. empathy_os/prompts/__init__.py +61 -0
  173. empathy_os/prompts/config.py +77 -0
  174. empathy_os/prompts/context.py +177 -0
  175. empathy_os/prompts/parser.py +285 -0
  176. empathy_os/prompts/registry.py +313 -0
  177. empathy_os/prompts/templates.py +208 -0
  178. empathy_os/redis_config.py +144 -58
  179. empathy_os/redis_memory.py +53 -56
  180. empathy_os/resilience/__init__.py +56 -0
  181. empathy_os/resilience/circuit_breaker.py +256 -0
  182. empathy_os/resilience/fallback.py +179 -0
  183. empathy_os/resilience/health.py +300 -0
  184. empathy_os/resilience/retry.py +209 -0
  185. empathy_os/resilience/timeout.py +135 -0
  186. empathy_os/routing/__init__.py +43 -0
  187. empathy_os/routing/chain_executor.py +433 -0
  188. empathy_os/routing/classifier.py +217 -0
  189. empathy_os/routing/smart_router.py +234 -0
  190. empathy_os/routing/wizard_registry.py +307 -0
  191. empathy_os/templates.py +12 -11
  192. empathy_os/trust/__init__.py +28 -0
  193. empathy_os/trust/circuit_breaker.py +579 -0
  194. empathy_os/trust_building.py +44 -36
  195. empathy_os/validation/__init__.py +19 -0
  196. empathy_os/validation/xml_validator.py +281 -0
  197. empathy_os/wizard_factory_cli.py +170 -0
  198. empathy_os/{workflows.py → workflow_commands.py} +123 -31
  199. empathy_os/workflows/__init__.py +360 -0
  200. empathy_os/workflows/base.py +1660 -0
  201. empathy_os/workflows/bug_predict.py +962 -0
  202. empathy_os/workflows/code_review.py +960 -0
  203. empathy_os/workflows/code_review_adapters.py +310 -0
  204. empathy_os/workflows/code_review_pipeline.py +720 -0
  205. empathy_os/workflows/config.py +600 -0
  206. empathy_os/workflows/dependency_check.py +648 -0
  207. empathy_os/workflows/document_gen.py +1069 -0
  208. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  209. empathy_os/workflows/health_check.py +679 -0
  210. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  211. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  212. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  213. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  214. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  215. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  216. empathy_os/workflows/manage_documentation.py +804 -0
  217. empathy_os/workflows/new_sample_workflow1.py +146 -0
  218. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  219. empathy_os/workflows/perf_audit.py +687 -0
  220. empathy_os/workflows/pr_review.py +748 -0
  221. empathy_os/workflows/progress.py +445 -0
  222. empathy_os/workflows/progress_server.py +322 -0
  223. empathy_os/workflows/refactor_plan.py +693 -0
  224. empathy_os/workflows/release_prep.py +808 -0
  225. empathy_os/workflows/research_synthesis.py +404 -0
  226. empathy_os/workflows/secure_release.py +585 -0
  227. empathy_os/workflows/security_adapters.py +297 -0
  228. empathy_os/workflows/security_audit.py +1046 -0
  229. empathy_os/workflows/step_config.py +234 -0
  230. empathy_os/workflows/test5.py +125 -0
  231. empathy_os/workflows/test5_README.md +158 -0
  232. empathy_os/workflows/test_gen.py +1855 -0
  233. empathy_os/workflows/test_lifecycle.py +526 -0
  234. empathy_os/workflows/test_maintenance.py +626 -0
  235. empathy_os/workflows/test_maintenance_cli.py +590 -0
  236. empathy_os/workflows/test_maintenance_crew.py +821 -0
  237. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  238. empathy_software_plugin/__init__.py +1 -2
  239. empathy_software_plugin/cli/__init__.py +120 -0
  240. empathy_software_plugin/cli/inspect.py +362 -0
  241. empathy_software_plugin/cli.py +35 -26
  242. empathy_software_plugin/plugin.py +4 -8
  243. empathy_software_plugin/wizards/__init__.py +42 -0
  244. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  245. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  246. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  247. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  248. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  249. empathy_software_plugin/wizards/base_wizard.py +288 -0
  250. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  251. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  252. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  253. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  254. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  255. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  256. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  257. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  258. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  259. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  260. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  261. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  262. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  263. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  264. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  265. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  266. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  267. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  268. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  269. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  270. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  271. empathy_software_plugin/wizards/security/__init__.py +32 -0
  272. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  273. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  274. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  275. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  276. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  277. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  278. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  279. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  280. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  281. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  282. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  283. hot_reload/README.md +473 -0
  284. hot_reload/__init__.py +62 -0
  285. hot_reload/config.py +84 -0
  286. hot_reload/integration.py +228 -0
  287. hot_reload/reloader.py +298 -0
  288. hot_reload/watcher.py +179 -0
  289. hot_reload/websocket.py +176 -0
  290. scaffolding/README.md +589 -0
  291. scaffolding/__init__.py +35 -0
  292. scaffolding/__main__.py +14 -0
  293. scaffolding/cli.py +240 -0
  294. test_generator/__init__.py +38 -0
  295. test_generator/__main__.py +14 -0
  296. test_generator/cli.py +226 -0
  297. test_generator/generator.py +325 -0
  298. test_generator/risk_analyzer.py +216 -0
  299. workflow_patterns/__init__.py +33 -0
  300. workflow_patterns/behavior.py +249 -0
  301. workflow_patterns/core.py +76 -0
  302. workflow_patterns/output.py +99 -0
  303. workflow_patterns/registry.py +255 -0
  304. workflow_patterns/structural.py +288 -0
  305. workflow_scaffolding/__init__.py +11 -0
  306. workflow_scaffolding/__main__.py +12 -0
  307. workflow_scaffolding/cli.py +206 -0
  308. workflow_scaffolding/generator.py +265 -0
  309. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  310. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  311. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  312. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  313. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  314. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  315. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  316. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  317. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  318. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  319. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  320. agents/compliance_anticipation_agent.py +0 -1427
  321. agents/epic_integration_wizard.py +0 -541
  322. agents/trust_building_behaviors.py +0 -891
  323. empathy_framework-3.2.3.dist-info/RECORD +0 -104
  324. empathy_framework-3.2.3.dist-info/entry_points.txt +0 -7
  325. empathy_llm_toolkit/htmlcov/status.json +0 -1
  326. empathy_llm_toolkit/security/htmlcov/status.json +0 -1
  327. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
  328. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
empathy_os/cli.py CHANGED
@@ -1,5 +1,4 @@
1
- """
2
- Command-Line Interface for Empathy Framework
1
+ """Command-Line Interface for Empathy Framework
3
2
 
4
3
  Provides CLI commands for:
5
4
  - Running interactive REPL (empathy run)
@@ -25,9 +24,10 @@ from empathy_os.discovery import show_tip_if_available
25
24
  from empathy_os.logging_config import get_logger
26
25
  from empathy_os.pattern_library import PatternLibrary
27
26
  from empathy_os.persistence import MetricsCollector, PatternPersistence, StateManager
27
+ from empathy_os.platform_utils import setup_asyncio_policy
28
28
  from empathy_os.templates import cmd_new
29
+ from empathy_os.wizard_factory_cli import add_wizard_factory_commands
29
30
  from empathy_os.workflows import (
30
- WorkflowConfig,
31
31
  cmd_fix_all,
32
32
  cmd_learn,
33
33
  cmd_morning,
@@ -285,7 +285,9 @@ def cmd_version(args):
285
285
  logger.info("Displaying version information")
286
286
  try:
287
287
  version = get_version("empathy")
288
- except Exception:
288
+ except Exception as e:
289
+ # Package metadata not available or invalid (development install)
290
+ logger.debug(f"Version not available: {e}")
289
291
  version = "unknown"
290
292
  logger.info(f"Empathy v{version}")
291
293
  logger.info("Copyright 2025 Smart-AI-Memory")
@@ -482,10 +484,9 @@ for a quick reference of all commands.
482
484
  print(" Congratulations! You've completed the onboarding!")
483
485
  print()
484
486
  _show_achievements(engine)
485
- else:
486
- if step_data["action"]:
487
- print(f" NEXT: Run '{step_data['action']}'")
488
- print(" Then run 'empathy onboard' to continue")
487
+ elif step_data["action"]:
488
+ print(f" NEXT: Run '{step_data['action']}'")
489
+ print(" Then run 'empathy onboard' to continue")
489
490
 
490
491
  print()
491
492
  print("-" * 60)
@@ -642,10 +643,21 @@ def cmd_validate(args):
642
643
  logger.info(f" Confidence Threshold: {config.confidence_threshold}")
643
644
  logger.info(f" Persistence Backend: {config.persistence_backend}")
644
645
  logger.info(f" Metrics Enabled: {config.metrics_enabled}")
645
- except Exception as e:
646
+ except (OSError, FileNotFoundError) as e:
647
+ # Config file not found or cannot be read
648
+ logger.error(f"Configuration file error: {e}")
649
+ logger.error(f"✗ Cannot read configuration file: {e}")
650
+ sys.exit(1)
651
+ except ValueError as e:
652
+ # Invalid configuration values
646
653
  logger.error(f"Configuration validation failed: {e}")
647
654
  logger.error(f"✗ Configuration invalid: {e}")
648
655
  sys.exit(1)
656
+ except Exception as e:
657
+ # Unexpected errors during config validation
658
+ logger.exception(f"Unexpected error validating configuration: {e}")
659
+ logger.error(f"✗ Configuration invalid: {e}")
660
+ sys.exit(1)
649
661
 
650
662
 
651
663
  def cmd_info(args):
@@ -736,8 +748,19 @@ def cmd_patterns_export(args):
736
748
 
737
749
  logger.info(f"Loaded {len(library.patterns)} patterns from {input_file}")
738
750
  logger.info(f"✓ Loaded {len(library.patterns)} patterns from {input_file}")
751
+ except (OSError, FileNotFoundError) as e:
752
+ # Input file not found or cannot be read
753
+ logger.error(f"Pattern file error: {e}")
754
+ logger.error(f"✗ Cannot read pattern file: {e}")
755
+ sys.exit(1)
756
+ except (ValueError, KeyError) as e:
757
+ # Invalid pattern data format
758
+ logger.error(f"Pattern data error: {e}")
759
+ logger.error(f"✗ Invalid pattern data: {e}")
760
+ sys.exit(1)
739
761
  except Exception as e:
740
- logger.error(f"Failed to load patterns: {e}")
762
+ # Unexpected errors loading patterns
763
+ logger.exception(f"Unexpected error loading patterns: {e}")
741
764
  logger.error(f"✗ Failed to load patterns: {e}")
742
765
  sys.exit(1)
743
766
 
@@ -750,8 +773,14 @@ def cmd_patterns_export(args):
750
773
 
751
774
  logger.info(f"Saved {len(library.patterns)} patterns to {output_file}")
752
775
  logger.info(f"✓ Saved {len(library.patterns)} patterns to {output_file}")
776
+ except (OSError, FileNotFoundError, PermissionError) as e:
777
+ # Cannot write output file
778
+ logger.error(f"Pattern file write error: {e}")
779
+ logger.error(f"✗ Cannot write pattern file: {e}")
780
+ sys.exit(1)
753
781
  except Exception as e:
754
- logger.error(f"Failed to save patterns: {e}")
782
+ # Unexpected errors saving patterns
783
+ logger.exception(f"Unexpected error saving patterns: {e}")
755
784
  logger.error(f"✗ Failed to save patterns: {e}")
756
785
  sys.exit(1)
757
786
 
@@ -783,7 +812,7 @@ def cmd_patterns_resolve(args):
783
812
  if not args.root_cause or not args.fix:
784
813
  print("✗ --root-cause and --fix are required when resolving a bug")
785
814
  print(
786
- " Example: empathy patterns resolve bug_123 --root-cause 'Null check' --fix 'Added ?.'"
815
+ " Example: empathy patterns resolve bug_123 --root-cause 'Null check' --fix 'Added ?.'",
787
816
  )
788
817
  sys.exit(1)
789
818
 
@@ -870,8 +899,8 @@ def cmd_review(args):
870
899
  "files": args.files,
871
900
  "staged_only": args.staged,
872
901
  "severity_threshold": args.severity,
873
- }
874
- )
902
+ },
903
+ ),
875
904
  )
876
905
 
877
906
  # Output results
@@ -972,7 +1001,7 @@ def cmd_health(args):
972
1001
  print(f"\n⚠ Skipped {len(result['skipped'])} issue(s) (could not auto-fix)")
973
1002
  else:
974
1003
  print(
975
- f"\n⚠ Skipped {len(result['skipped'])} issue(s) (use --interactive to review)"
1004
+ f"\n⚠ Skipped {len(result['skipped'])} issue(s) (use --interactive to review)",
976
1005
  )
977
1006
 
978
1007
  if result["failed"]:
@@ -1043,8 +1072,19 @@ def cmd_metrics_show(args):
1043
1072
  logger.info(f" Level 3: {stats.get('level_3_count', 0)} uses")
1044
1073
  logger.info(f" Level 4: {stats.get('level_4_count', 0)} uses")
1045
1074
  logger.info(f" Level 5: {stats.get('level_5_count', 0)} uses")
1075
+ except (OSError, FileNotFoundError) as e:
1076
+ # Database file not found
1077
+ logger.error(f"Metrics database error: {e}")
1078
+ logger.error(f"✗ Cannot read metrics database: {e}")
1079
+ sys.exit(1)
1080
+ except KeyError as e:
1081
+ # User not found in database
1082
+ logger.error(f"User not found in metrics: {e}")
1083
+ logger.error(f"✗ User {user_id} not found: {e}")
1084
+ sys.exit(1)
1046
1085
  except Exception as e:
1047
- logger.error(f"Failed to retrieve metrics for user {user_id}: {e}")
1086
+ # Unexpected errors retrieving metrics
1087
+ logger.exception(f"Unexpected error retrieving metrics for user {user_id}: {e}")
1048
1088
  logger.error(f"✗ Failed to retrieve metrics: {e}")
1049
1089
  sys.exit(1)
1050
1090
 
@@ -1098,7 +1138,17 @@ def cmd_run(args):
1098
1138
  persistence_enabled=config.persistence_enabled,
1099
1139
  )
1100
1140
  print("✓ Empathy OS initialized")
1141
+ except ValueError as e:
1142
+ # Invalid configuration parameters
1143
+ print(f"✗ Configuration error: {e}")
1144
+ sys.exit(1)
1145
+ except (OSError, FileNotFoundError, PermissionError) as e:
1146
+ # Cannot access required files/directories
1147
+ print(f"✗ File system error: {e}")
1148
+ sys.exit(1)
1101
1149
  except Exception as e:
1150
+ # Unexpected initialization failure
1151
+ logger.exception(f"Unexpected error initializing Empathy OS: {e}")
1102
1152
  print(f"✗ Failed to initialize Empathy OS: {e}")
1103
1153
  sys.exit(1)
1104
1154
 
@@ -1182,7 +1232,12 @@ def cmd_run(args):
1182
1232
  except KeyboardInterrupt:
1183
1233
  print("\n\n👋 Goodbye!")
1184
1234
  break
1235
+ except (ValueError, KeyError) as e:
1236
+ # Invalid input or response structure
1237
+ print(f"\n✗ Input error: {e}\n")
1185
1238
  except Exception as e:
1239
+ # Unexpected errors in interactive loop - log and continue
1240
+ logger.exception(f"Unexpected error in interactive loop: {e}")
1186
1241
  print(f"\n✗ Error: {e}\n")
1187
1242
 
1188
1243
 
@@ -1225,7 +1280,13 @@ def cmd_inspect(args):
1225
1280
  print(f"✗ Pattern library not found: {db_path}")
1226
1281
  print(" Tip: Use 'empathy-framework wizard' to set up your first project")
1227
1282
  sys.exit(1)
1283
+ except (ValueError, KeyError) as e:
1284
+ # Invalid pattern data format
1285
+ print(f"✗ Invalid pattern data: {e}")
1286
+ sys.exit(1)
1228
1287
  except Exception as e:
1288
+ # Unexpected errors loading patterns
1289
+ logger.exception(f"Unexpected error loading patterns: {e}")
1229
1290
  print(f"✗ Failed to load patterns: {e}")
1230
1291
  sys.exit(1)
1231
1292
 
@@ -1247,7 +1308,17 @@ def cmd_inspect(args):
1247
1308
  for level in range(1, 6):
1248
1309
  count = stats.get(f"level_{level}_count", 0)
1249
1310
  print(f" Level {level}: {count} times")
1311
+ except (OSError, FileNotFoundError) as e:
1312
+ # Database file not found
1313
+ print(f"✗ Metrics database not found: {e}")
1314
+ sys.exit(1)
1315
+ except KeyError as e:
1316
+ # User not found
1317
+ print(f"✗ User {user_id} not found: {e}")
1318
+ sys.exit(1)
1250
1319
  except Exception as e:
1320
+ # Unexpected errors loading metrics
1321
+ logger.exception(f"Unexpected error loading metrics: {e}")
1251
1322
  print(f"✗ Failed to load metrics: {e}")
1252
1323
  sys.exit(1)
1253
1324
 
@@ -1264,7 +1335,13 @@ def cmd_inspect(args):
1264
1335
  print("\n Users:")
1265
1336
  for uid in users:
1266
1337
  print(f" • {uid}")
1338
+ except (OSError, FileNotFoundError) as e:
1339
+ # State directory not found
1340
+ print(f"✗ State directory not found: {e}")
1341
+ sys.exit(1)
1267
1342
  except Exception as e:
1343
+ # Unexpected errors loading state
1344
+ logger.exception(f"Unexpected error loading state: {e}")
1268
1345
  print(f"✗ Failed to load state: {e}")
1269
1346
  sys.exit(1)
1270
1347
 
@@ -1316,7 +1393,17 @@ def cmd_export(args):
1316
1393
  print(f"✗ Source file not found: {db_path}")
1317
1394
  print(" Tip: Patterns are saved automatically when using the framework")
1318
1395
  sys.exit(1)
1396
+ except (OSError, PermissionError) as e:
1397
+ # Cannot write output file
1398
+ print(f"✗ Cannot write to file: {e}")
1399
+ sys.exit(1)
1400
+ except (ValueError, KeyError) as e:
1401
+ # Invalid pattern data
1402
+ print(f"✗ Invalid pattern data: {e}")
1403
+ sys.exit(1)
1319
1404
  except Exception as e:
1405
+ # Unexpected errors during export
1406
+ logger.exception(f"Unexpected error exporting patterns: {e}")
1320
1407
  print(f"✗ Export failed: {e}")
1321
1408
  sys.exit(1)
1322
1409
 
@@ -1369,7 +1456,17 @@ def cmd_import(args):
1369
1456
  except FileNotFoundError:
1370
1457
  print(f"✗ Input file not found: {input_file}")
1371
1458
  sys.exit(1)
1459
+ except (ValueError, KeyError) as e:
1460
+ # Invalid pattern data format
1461
+ print(f"✗ Invalid pattern data: {e}")
1462
+ sys.exit(1)
1463
+ except (OSError, PermissionError) as e:
1464
+ # Cannot read input or write to database
1465
+ print(f"✗ File access error: {e}")
1466
+ sys.exit(1)
1372
1467
  except Exception as e:
1468
+ # Unexpected errors during import
1469
+ logger.exception(f"Unexpected error importing patterns: {e}")
1373
1470
  print(f"✗ Import failed: {e}")
1374
1471
  sys.exit(1)
1375
1472
 
@@ -1413,13 +1510,29 @@ def cmd_wizard(args):
1413
1510
  print("\n3. Which LLM provider will you use?")
1414
1511
  print(" [1] Anthropic Claude ⭐ Recommended")
1415
1512
  print(" [2] OpenAI GPT-4")
1416
- print(" [3] Local (Ollama)")
1417
- print(" [4] Skip (configure later)")
1418
-
1419
- llm_choice = input("\nYour choice (1-4) [1]: ").strip() or "1"
1420
- llm_map = {"1": "anthropic", "2": "openai", "3": "ollama", "4": None}
1513
+ print(" [3] Google Gemini (2M context)")
1514
+ print(" [4] Local (Ollama)")
1515
+ print(" [5] Hybrid (mix best models from each provider)")
1516
+ print(" [6] Skip (configure later)")
1517
+
1518
+ llm_choice = input("\nYour choice (1-6) [1]: ").strip() or "1"
1519
+ llm_map = {
1520
+ "1": "anthropic",
1521
+ "2": "openai",
1522
+ "3": "google",
1523
+ "4": "ollama",
1524
+ "5": "hybrid",
1525
+ "6": None,
1526
+ }
1421
1527
  llm_provider = llm_map.get(llm_choice, "anthropic")
1422
1528
 
1529
+ # If hybrid selected, launch interactive tier selection
1530
+ if llm_provider == "hybrid":
1531
+ from empathy_os.models.provider_config import configure_hybrid_interactive
1532
+
1533
+ configure_hybrid_interactive()
1534
+ llm_provider = None # Already saved by hybrid config
1535
+
1423
1536
  # Step 4: User ID
1424
1537
  print("\n4. What user ID should we use?")
1425
1538
  user_id = input("User ID [default_user]: ").strip() or "default_user"
@@ -1480,14 +1593,94 @@ llm_provider: "{llm_provider}"
1480
1593
  print("\nNext steps:")
1481
1594
  print(f" 1. Edit {output_file} to customize settings")
1482
1595
 
1483
- if llm_provider in ["anthropic", "openai"]:
1484
- env_var = "ANTHROPIC_API_KEY" if llm_provider == "anthropic" else "OPENAI_API_KEY"
1596
+ if llm_provider in ["anthropic", "openai", "google"]:
1597
+ env_var_map = {
1598
+ "anthropic": "ANTHROPIC_API_KEY",
1599
+ "openai": "OPENAI_API_KEY",
1600
+ "google": "GOOGLE_API_KEY",
1601
+ }
1602
+ env_var = env_var_map.get(llm_provider, "API_KEY")
1485
1603
  print(f" 2. Set {env_var} environment variable")
1486
1604
 
1487
1605
  print(" 3. Run: empathy-framework run --config empathy.config.yml")
1488
1606
  print("\nHappy empathizing! 🧠✨\n")
1489
1607
 
1490
1608
 
1609
+ def cmd_provider_hybrid(args):
1610
+ """Configure hybrid mode - pick best models for each tier."""
1611
+ from empathy_os.models.provider_config import configure_hybrid_interactive
1612
+
1613
+ configure_hybrid_interactive()
1614
+
1615
+
1616
+ def cmd_provider_show(args):
1617
+ """Show current provider configuration."""
1618
+ from empathy_os.models.provider_config import ProviderConfig
1619
+ from empathy_os.workflows.config import WorkflowConfig
1620
+
1621
+ print("\n" + "=" * 60)
1622
+ print("Provider Configuration")
1623
+ print("=" * 60)
1624
+
1625
+ # Detect available providers
1626
+ config = ProviderConfig.auto_detect()
1627
+ print(
1628
+ f"\nDetected API keys for: {', '.join(config.available_providers) if config.available_providers else 'None'}",
1629
+ )
1630
+
1631
+ # Load workflow config
1632
+ wf_config = WorkflowConfig.load()
1633
+ print(f"\nDefault provider: {wf_config.default_provider}")
1634
+
1635
+ # Show effective models
1636
+ print("\nEffective model mapping:")
1637
+ if wf_config.custom_models and "hybrid" in wf_config.custom_models:
1638
+ hybrid = wf_config.custom_models["hybrid"]
1639
+ for tier in ["cheap", "capable", "premium"]:
1640
+ model = hybrid.get(tier, "not configured")
1641
+ print(f" {tier:8} → {model}")
1642
+ else:
1643
+ from empathy_os.models import MODEL_REGISTRY
1644
+
1645
+ provider = wf_config.default_provider
1646
+ if provider in MODEL_REGISTRY:
1647
+ for tier in ["cheap", "capable", "premium"]:
1648
+ model_info = MODEL_REGISTRY[provider].get(tier)
1649
+ if model_info:
1650
+ print(f" {tier:8} → {model_info.id} ({provider})")
1651
+
1652
+ print()
1653
+
1654
+
1655
+ def cmd_provider_set(args):
1656
+ """Set default provider."""
1657
+ from pathlib import Path
1658
+
1659
+ import yaml
1660
+
1661
+ provider = args.name
1662
+ workflows_path = Path(".empathy/workflows.yaml")
1663
+
1664
+ # Load existing config or create new
1665
+ if workflows_path.exists():
1666
+ with open(workflows_path) as f:
1667
+ config = yaml.safe_load(f) or {}
1668
+ else:
1669
+ config = {}
1670
+ workflows_path.parent.mkdir(parents=True, exist_ok=True)
1671
+
1672
+ config["default_provider"] = provider
1673
+
1674
+ with open(workflows_path, "w") as f:
1675
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
1676
+
1677
+ print(f"✓ Default provider set to: {provider}")
1678
+ print(f" Saved to: {workflows_path}")
1679
+
1680
+ if provider == "hybrid":
1681
+ print("\n Tip: Run 'empathy provider hybrid' to customize tier models")
1682
+
1683
+
1491
1684
  def cmd_sync_claude(args):
1492
1685
  """Sync patterns to Claude Code rules directory."""
1493
1686
  import json as json_mod
@@ -1569,7 +1762,7 @@ def _generate_claude_rule(category: str, patterns: list) -> str:
1569
1762
  "",
1570
1763
  "When debugging similar issues, consider these historical fixes:",
1571
1764
  "",
1572
- ]
1765
+ ],
1573
1766
  )
1574
1767
  for p in patterns[:20]: # Limit to 20 most recent
1575
1768
  bug_type = p.get("bug_type", "unknown")
@@ -1591,7 +1784,7 @@ def _generate_claude_rule(category: str, patterns: list) -> str:
1591
1784
  "",
1592
1785
  "Previously reviewed security items:",
1593
1786
  "",
1594
- ]
1787
+ ],
1595
1788
  )
1596
1789
  for p in patterns[:20]:
1597
1790
  decision = p.get("decision", "unknown")
@@ -1608,7 +1801,7 @@ def _generate_claude_rule(category: str, patterns: list) -> str:
1608
1801
  "",
1609
1802
  "Known technical debt items:",
1610
1803
  "",
1611
- ]
1804
+ ],
1612
1805
  )
1613
1806
  for p in patterns[:20]:
1614
1807
  lines.append(f"- {p.get('description', str(p))}")
@@ -1618,7 +1811,7 @@ def _generate_claude_rule(category: str, patterns: list) -> str:
1618
1811
  [
1619
1812
  f"## {category.title()} Items",
1620
1813
  "",
1621
- ]
1814
+ ],
1622
1815
  )
1623
1816
  for p in patterns[:20]:
1624
1817
  lines.append(f"- {p.get('description', str(p)[:100])}")
@@ -1627,8 +1820,7 @@ def _generate_claude_rule(category: str, patterns: list) -> str:
1627
1820
 
1628
1821
 
1629
1822
  def _extract_workflow_content(final_output):
1630
- """
1631
- Extract readable content from workflow final_output.
1823
+ """Extract readable content from workflow final_output.
1632
1824
 
1633
1825
  Workflows return their results in various formats - this extracts
1634
1826
  the actual content users want to see.
@@ -1643,7 +1835,9 @@ def _extract_workflow_content(final_output):
1643
1835
  # If it's a dict, try to extract meaningful content
1644
1836
  if isinstance(final_output, dict):
1645
1837
  # Common keys that contain the main output
1838
+ # formatted_report is first - preferred for security-audit and other formatted outputs
1646
1839
  content_keys = [
1840
+ "formatted_report", # Human-readable formatted output (security-audit, etc.)
1647
1841
  "answer",
1648
1842
  "synthesis",
1649
1843
  "result",
@@ -1661,11 +1855,11 @@ def _extract_workflow_content(final_output):
1661
1855
  "plan",
1662
1856
  ]
1663
1857
  for key in content_keys:
1664
- if key in final_output and final_output[key]:
1858
+ if final_output.get(key):
1665
1859
  val = final_output[key]
1666
1860
  if isinstance(val, str):
1667
1861
  return val
1668
- elif isinstance(val, dict):
1862
+ if isinstance(val, dict):
1669
1863
  # Recursively extract
1670
1864
  return _extract_workflow_content(val)
1671
1865
 
@@ -1761,8 +1955,14 @@ def cmd_workflow(args):
1761
1955
  try:
1762
1956
  workflow_cls = get_workflow(name)
1763
1957
 
1764
- # Get provider (default to anthropic if not specified)
1765
- provider = args.provider if args.provider else "anthropic"
1958
+ # Get provider from CLI arg, or fall back to config's default_provider
1959
+ if args.provider:
1960
+ provider = args.provider
1961
+ else:
1962
+ from empathy_os.workflows.config import WorkflowConfig
1963
+
1964
+ wf_config = WorkflowConfig.load()
1965
+ provider = wf_config.default_provider
1766
1966
  workflow = workflow_cls(provider=provider)
1767
1967
 
1768
1968
  # Parse input
@@ -1770,8 +1970,17 @@ def cmd_workflow(args):
1770
1970
  if args.input:
1771
1971
  input_data = json_mod.loads(args.input)
1772
1972
 
1773
- print(f"\n Running workflow: {name} (provider: {provider})")
1774
- print("=" * 50)
1973
+ # Add test-gen specific flags to input_data (only for test-gen workflow)
1974
+ if name == "test-gen":
1975
+ if getattr(args, "write_tests", False):
1976
+ input_data["write_tests"] = True
1977
+ if getattr(args, "output_dir", None):
1978
+ input_data["output_dir"] = args.output_dir
1979
+
1980
+ # Only print header when not in JSON mode
1981
+ if not args.json:
1982
+ print(f"\n Running workflow: {name} (provider: {provider})")
1983
+ print("=" * 50)
1775
1984
 
1776
1985
  # Execute workflow
1777
1986
  result = asyncio.run(workflow.execute(**input_data))
@@ -1779,6 +1988,11 @@ def cmd_workflow(args):
1779
1988
  # Extract the actual content - handle different result types
1780
1989
  if hasattr(result, "final_output"):
1781
1990
  output_content = _extract_workflow_content(result.final_output)
1991
+ elif hasattr(result, "metadata") and isinstance(result.metadata, dict):
1992
+ # Check for formatted_report in metadata (e.g., HealthCheckResult)
1993
+ output_content = result.metadata.get("formatted_report")
1994
+ if not output_content and hasattr(result, "summary"):
1995
+ output_content = result.summary
1782
1996
  elif hasattr(result, "summary"):
1783
1997
  output_content = result.summary
1784
1998
  else:
@@ -1789,37 +2003,81 @@ def cmd_workflow(args):
1789
2003
  if duration_ms is None and hasattr(result, "duration_seconds"):
1790
2004
  duration_ms = int(result.duration_seconds * 1000)
1791
2005
 
1792
- # Get cost info if available
2006
+ # Get cost info if available (check cost_report first, then direct cost attribute)
1793
2007
  cost_report = getattr(result, "cost_report", None)
1794
- total_cost = cost_report.total_cost if cost_report else 0.0
1795
- savings = cost_report.savings if cost_report else 0.0
2008
+ if cost_report and hasattr(cost_report, "total_cost"):
2009
+ total_cost = cost_report.total_cost
2010
+ savings = getattr(cost_report, "savings", 0.0)
2011
+ else:
2012
+ # Fall back to direct cost attribute (e.g., CodeReviewPipelineResult)
2013
+ total_cost = getattr(result, "cost", 0.0)
2014
+ savings = 0.0
1796
2015
 
1797
2016
  if args.json:
2017
+ # Extract error from various result types
2018
+ error = getattr(result, "error", None)
2019
+ if not error and not result.success:
2020
+ blockers = getattr(result, "blockers", [])
2021
+ if blockers:
2022
+ error = "; ".join(blockers)
2023
+ else:
2024
+ metadata = getattr(result, "metadata", {})
2025
+ error = metadata.get("error") if isinstance(metadata, dict) else None
2026
+
1798
2027
  # JSON output includes both content and metadata
2028
+ # Include final_output for programmatic access (VSCode panels, etc.)
2029
+ raw_final_output = getattr(result, "final_output", None)
2030
+ if raw_final_output and isinstance(raw_final_output, dict):
2031
+ # Make a copy to avoid modifying the original
2032
+ final_output_serializable = {}
2033
+ for k, v in raw_final_output.items():
2034
+ # Skip non-serializable items
2035
+ if isinstance(v, set):
2036
+ final_output_serializable[k] = list(v)
2037
+ elif v is None or isinstance(v, str | int | float | bool | list | dict):
2038
+ final_output_serializable[k] = v
2039
+ else:
2040
+ try:
2041
+ final_output_serializable[k] = str(v)
2042
+ except Exception as e: # noqa: BLE001
2043
+ # INTENTIONAL: Silently skip any non-serializable objects
2044
+ # This is a best-effort serialization for JSON output
2045
+ # We cannot predict all possible object types users might return
2046
+ logger.debug(f"Cannot serialize field {k}: {e}")
2047
+ pass
2048
+ else:
2049
+ final_output_serializable = None
2050
+
1799
2051
  output = {
1800
2052
  "success": result.success,
1801
2053
  "output": output_content,
2054
+ "final_output": final_output_serializable,
1802
2055
  "cost": total_cost,
1803
2056
  "savings": savings,
1804
2057
  "duration_ms": duration_ms or 0,
1805
- "error": getattr(result, "error", None),
2058
+ "error": error,
1806
2059
  }
1807
2060
  print(json_mod.dumps(output, indent=2))
2061
+ # Display the actual results - this is what users want to see
2062
+ elif result.success:
2063
+ if output_content:
2064
+ print(f"\n{output_content}\n")
2065
+ else:
2066
+ print("\n✓ Workflow completed successfully.\n")
1808
2067
  else:
1809
- # Display the actual results - this is what users want to see
1810
- if result.success:
1811
- if output_content:
1812
- print(f"\n{output_content}\n")
2068
+ # Extract error from various result types
2069
+ error_msg = getattr(result, "error", None)
2070
+ if not error_msg:
2071
+ # Check for blockers (CodeReviewPipelineResult)
2072
+ blockers = getattr(result, "blockers", [])
2073
+ if blockers:
2074
+ error_msg = "; ".join(blockers)
1813
2075
  else:
1814
- print("\n✓ Workflow completed successfully.\n")
1815
-
1816
- # Brief footer with timing (detailed costs available via 'empathy costs')
1817
- print("-" * 50)
1818
- ms = duration_ms or 0
1819
- print(f"Completed in {ms}ms | Cost: ${total_cost:.4f} (saved ${savings:.4f})")
1820
- else:
1821
- error_msg = getattr(result, "error", None) or "Unknown error"
1822
- print(f"\n✗ Workflow failed: {error_msg}\n")
2076
+ # Check metadata for error
2077
+ metadata = getattr(result, "metadata", {})
2078
+ error_msg = metadata.get("error") if isinstance(metadata, dict) else None
2079
+ error_msg = error_msg or "Unknown error"
2080
+ print(f"\n✗ Workflow failed: {error_msg}\n")
1823
2081
 
1824
2082
  except KeyError as e:
1825
2083
  print(f"Error: {e}")
@@ -1898,7 +2156,7 @@ def cmd_frameworks(args):
1898
2156
  json_mod.dumps(
1899
2157
  {"use_case": recommend_use_case, "recommended": recommended.value, **info},
1900
2158
  indent=2,
1901
- )
2159
+ ),
1902
2160
  )
1903
2161
  else:
1904
2162
  print(f"\nRecommended framework for '{recommend_use_case}': {info['name']}")
@@ -1925,7 +2183,7 @@ def cmd_frameworks(args):
1925
2183
  for f in frameworks
1926
2184
  ],
1927
2185
  indent=2,
1928
- )
2186
+ ),
1929
2187
  )
1930
2188
  else:
1931
2189
  print("\n" + "=" * 60)
@@ -1950,6 +2208,9 @@ def cmd_frameworks(args):
1950
2208
 
1951
2209
  def main():
1952
2210
  """Main CLI entry point"""
2211
+ # Configure Windows-compatible asyncio event loop policy
2212
+ setup_asyncio_policy()
2213
+
1953
2214
  parser = argparse.ArgumentParser(
1954
2215
  prog="empathy",
1955
2216
  description="Empathy - Build AI systems with 5 levels of empathy",
@@ -2002,32 +2263,45 @@ def main():
2002
2263
  parser_patterns_export.add_argument("input", help="Input file path")
2003
2264
  parser_patterns_export.add_argument("output", help="Output file path")
2004
2265
  parser_patterns_export.add_argument(
2005
- "--input-format", choices=["json", "sqlite"], default="json"
2266
+ "--input-format",
2267
+ choices=["json", "sqlite"],
2268
+ default="json",
2006
2269
  )
2007
2270
  parser_patterns_export.add_argument(
2008
- "--output-format", choices=["json", "sqlite"], default="json"
2271
+ "--output-format",
2272
+ choices=["json", "sqlite"],
2273
+ default="json",
2009
2274
  )
2010
2275
  parser_patterns_export.set_defaults(func=cmd_patterns_export)
2011
2276
 
2012
2277
  # Patterns resolve - mark investigating bugs as resolved
2013
2278
  parser_patterns_resolve = patterns_subparsers.add_parser(
2014
- "resolve", help="Resolve investigating bug patterns"
2279
+ "resolve",
2280
+ help="Resolve investigating bug patterns",
2015
2281
  )
2016
2282
  parser_patterns_resolve.add_argument(
2017
- "bug_id", nargs="?", help="Bug ID to resolve (omit to list investigating)"
2283
+ "bug_id",
2284
+ nargs="?",
2285
+ help="Bug ID to resolve (omit to list investigating)",
2018
2286
  )
2019
2287
  parser_patterns_resolve.add_argument("--root-cause", help="Description of the root cause")
2020
2288
  parser_patterns_resolve.add_argument("--fix", help="Description of the fix applied")
2021
2289
  parser_patterns_resolve.add_argument("--fix-code", help="Code snippet of the fix")
2022
2290
  parser_patterns_resolve.add_argument("--time", type=int, help="Resolution time in minutes")
2023
2291
  parser_patterns_resolve.add_argument(
2024
- "--resolved-by", default="@developer", help="Who resolved it"
2292
+ "--resolved-by",
2293
+ default="@developer",
2294
+ help="Who resolved it",
2025
2295
  )
2026
2296
  parser_patterns_resolve.add_argument(
2027
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2297
+ "--patterns-dir",
2298
+ default="./patterns",
2299
+ help="Path to patterns directory",
2028
2300
  )
2029
2301
  parser_patterns_resolve.add_argument(
2030
- "--no-regenerate", action="store_true", help="Skip regenerating summary"
2302
+ "--no-regenerate",
2303
+ action="store_true",
2304
+ help="Skip regenerating summary",
2031
2305
  )
2032
2306
  parser_patterns_resolve.set_defaults(func=cmd_patterns_resolve)
2033
2307
 
@@ -2048,7 +2322,9 @@ def main():
2048
2322
  # State list
2049
2323
  parser_state_list = state_subparsers.add_parser("list", help="List saved states")
2050
2324
  parser_state_list.add_argument(
2051
- "--state-dir", default="./empathy_state", help="State directory path"
2325
+ "--state-dir",
2326
+ default="./empathy_state",
2327
+ help="State directory path",
2052
2328
  )
2053
2329
  parser_state_list.set_defaults(func=cmd_state_list)
2054
2330
 
@@ -2057,7 +2333,10 @@ def main():
2057
2333
  parser_run.add_argument("--config", "-c", help="Configuration file path")
2058
2334
  parser_run.add_argument("--user-id", help="User ID (default: cli_user)")
2059
2335
  parser_run.add_argument(
2060
- "--level", type=int, default=4, help="Target empathy level (1-5, default: 4)"
2336
+ "--level",
2337
+ type=int,
2338
+ default=4,
2339
+ help="Target empathy level (1-5, default: 4)",
2061
2340
  )
2062
2341
  parser_run.set_defaults(func=cmd_run)
2063
2342
 
@@ -2071,21 +2350,27 @@ def main():
2071
2350
  parser_inspect.add_argument("--user-id", help="User ID to filter by (optional)")
2072
2351
  parser_inspect.add_argument("--db", help="Database path (default: .empathy/patterns.db)")
2073
2352
  parser_inspect.add_argument(
2074
- "--state-dir", help="State directory path (default: .empathy/state)"
2353
+ "--state-dir",
2354
+ help="State directory path (default: .empathy/state)",
2075
2355
  )
2076
2356
  parser_inspect.set_defaults(func=cmd_inspect)
2077
2357
 
2078
2358
  # Export command
2079
2359
  parser_export = subparsers.add_parser(
2080
- "export", help="Export patterns to file for sharing/backup"
2360
+ "export",
2361
+ help="Export patterns to file for sharing/backup",
2081
2362
  )
2082
2363
  parser_export.add_argument("output", help="Output file path")
2083
2364
  parser_export.add_argument(
2084
- "--user-id", help="User ID to export (optional, exports all if not specified)"
2365
+ "--user-id",
2366
+ help="User ID to export (optional, exports all if not specified)",
2085
2367
  )
2086
2368
  parser_export.add_argument("--db", help="Database path (default: .empathy/patterns.db)")
2087
2369
  parser_export.add_argument(
2088
- "--format", default="json", choices=["json"], help="Export format (default: json)"
2370
+ "--format",
2371
+ default="json",
2372
+ choices=["json"],
2373
+ help="Export format (default: json)",
2089
2374
  )
2090
2375
  parser_export.set_defaults(func=cmd_export)
2091
2376
 
@@ -2097,20 +2382,59 @@ def main():
2097
2382
 
2098
2383
  # Wizard command (Interactive setup)
2099
2384
  parser_wizard = subparsers.add_parser(
2100
- "wizard", help="Interactive setup wizard for creating configuration"
2385
+ "wizard",
2386
+ help="Interactive setup wizard for creating configuration",
2101
2387
  )
2102
2388
  parser_wizard.set_defaults(func=cmd_wizard)
2103
2389
 
2390
+ # Provider command (Model provider configuration)
2391
+ parser_provider = subparsers.add_parser(
2392
+ "provider",
2393
+ help="Configure model providers and hybrid mode",
2394
+ )
2395
+ provider_subparsers = parser_provider.add_subparsers(dest="provider_cmd")
2396
+
2397
+ # provider hybrid - Interactive hybrid configuration
2398
+ parser_provider_hybrid = provider_subparsers.add_parser(
2399
+ "hybrid",
2400
+ help="Configure hybrid mode - pick best models for each tier",
2401
+ )
2402
+ parser_provider_hybrid.set_defaults(func=cmd_provider_hybrid)
2403
+
2404
+ # provider show - Show current configuration
2405
+ parser_provider_show = provider_subparsers.add_parser(
2406
+ "show",
2407
+ help="Show current provider configuration",
2408
+ )
2409
+ parser_provider_show.set_defaults(func=cmd_provider_show)
2410
+
2411
+ # provider set - Quick set single provider
2412
+ parser_provider_set = provider_subparsers.add_parser(
2413
+ "set",
2414
+ help="Set default provider (anthropic, openai, google, ollama)",
2415
+ )
2416
+ parser_provider_set.add_argument(
2417
+ "name",
2418
+ choices=["anthropic", "openai", "google", "ollama", "hybrid"],
2419
+ help="Provider name",
2420
+ )
2421
+ parser_provider_set.set_defaults(func=cmd_provider_set)
2422
+
2104
2423
  # Status command (Session status assistant)
2105
2424
  parser_status = subparsers.add_parser(
2106
- "status", help="Session status - prioritized project status report"
2425
+ "status",
2426
+ help="Session status - prioritized project status report",
2107
2427
  )
2108
2428
  parser_status.add_argument(
2109
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2429
+ "--patterns-dir",
2430
+ default="./patterns",
2431
+ help="Path to patterns directory",
2110
2432
  )
2111
2433
  parser_status.add_argument("--project-root", default=".", help="Project root directory")
2112
2434
  parser_status.add_argument(
2113
- "--force", action="store_true", help="Force show status regardless of inactivity"
2435
+ "--force",
2436
+ action="store_true",
2437
+ help="Force show status regardless of inactivity",
2114
2438
  )
2115
2439
  parser_status.add_argument("--full", action="store_true", help="Show all items (no limit)")
2116
2440
  parser_status.add_argument("--json", action="store_true", help="Output as JSON")
@@ -2125,7 +2449,8 @@ def main():
2125
2449
 
2126
2450
  # Review command (Pattern-based code review)
2127
2451
  parser_review = subparsers.add_parser(
2128
- "review", help="Pattern-based code review against historical bugs"
2452
+ "review",
2453
+ help="Pattern-based code review against historical bugs",
2129
2454
  )
2130
2455
  parser_review.add_argument("files", nargs="*", help="Files to review (default: recent changes)")
2131
2456
  parser_review.add_argument("--staged", action="store_true", help="Review staged changes only")
@@ -2141,10 +2466,13 @@ def main():
2141
2466
 
2142
2467
  # Health command (Code Health Assistant)
2143
2468
  parser_health = subparsers.add_parser(
2144
- "health", help="Code health assistant - run checks and auto-fix issues"
2469
+ "health",
2470
+ help="Code health assistant - run checks and auto-fix issues",
2145
2471
  )
2146
2472
  parser_health.add_argument(
2147
- "--deep", action="store_true", help="Run comprehensive checks (slower)"
2473
+ "--deep",
2474
+ action="store_true",
2475
+ help="Run comprehensive checks (slower)",
2148
2476
  )
2149
2477
  parser_health.add_argument(
2150
2478
  "--check",
@@ -2153,20 +2481,31 @@ def main():
2153
2481
  )
2154
2482
  parser_health.add_argument("--fix", action="store_true", help="Auto-fix issues where possible")
2155
2483
  parser_health.add_argument(
2156
- "--dry-run", action="store_true", help="Show what would be fixed without applying"
2484
+ "--dry-run",
2485
+ action="store_true",
2486
+ help="Show what would be fixed without applying",
2157
2487
  )
2158
2488
  parser_health.add_argument(
2159
- "--interactive", action="store_true", help="Prompt before applying non-safe fixes"
2489
+ "--interactive",
2490
+ action="store_true",
2491
+ help="Prompt before applying non-safe fixes",
2160
2492
  )
2161
2493
  parser_health.add_argument("--details", action="store_true", help="Show detailed issue list")
2162
2494
  parser_health.add_argument(
2163
- "--full", action="store_true", help="Show full report with all details"
2495
+ "--full",
2496
+ action="store_true",
2497
+ help="Show full report with all details",
2164
2498
  )
2165
2499
  parser_health.add_argument(
2166
- "--trends", type=int, metavar="DAYS", help="Show health trends over N days"
2500
+ "--trends",
2501
+ type=int,
2502
+ metavar="DAYS",
2503
+ help="Show health trends over N days",
2167
2504
  )
2168
2505
  parser_health.add_argument(
2169
- "--project-root", default=".", help="Project root directory (default: current)"
2506
+ "--project-root",
2507
+ default=".",
2508
+ help="Project root directory (default: current)",
2170
2509
  )
2171
2510
  parser_health.add_argument("--json", action="store_true", help="Output as JSON")
2172
2511
  parser_health.set_defaults(func=cmd_health)
@@ -2177,10 +2516,13 @@ def main():
2177
2516
 
2178
2517
  # Morning command (start-of-day briefing)
2179
2518
  parser_morning = subparsers.add_parser(
2180
- "morning", help="Start-of-day briefing with patterns, debt, and focus areas"
2519
+ "morning",
2520
+ help="Start-of-day briefing with patterns, debt, and focus areas",
2181
2521
  )
2182
2522
  parser_morning.add_argument(
2183
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2523
+ "--patterns-dir",
2524
+ default="./patterns",
2525
+ help="Path to patterns directory",
2184
2526
  )
2185
2527
  parser_morning.add_argument("--project-root", default=".", help="Project root directory")
2186
2528
  parser_morning.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
@@ -2189,48 +2531,77 @@ def main():
2189
2531
  # Ship command (pre-commit validation)
2190
2532
  parser_ship = subparsers.add_parser("ship", help="Pre-commit validation pipeline")
2191
2533
  parser_ship.add_argument(
2192
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2534
+ "--patterns-dir",
2535
+ default="./patterns",
2536
+ help="Path to patterns directory",
2193
2537
  )
2194
2538
  parser_ship.add_argument("--project-root", default=".", help="Project root directory")
2195
2539
  parser_ship.add_argument(
2196
- "--skip-sync", action="store_true", help="Skip syncing patterns to Claude"
2540
+ "--skip-sync",
2541
+ action="store_true",
2542
+ help="Skip syncing patterns to Claude",
2543
+ )
2544
+ parser_ship.add_argument(
2545
+ "--tests-only",
2546
+ action="store_true",
2547
+ help="Run tests only (skip lint/format checks)",
2548
+ )
2549
+ parser_ship.add_argument(
2550
+ "--security-only",
2551
+ action="store_true",
2552
+ help="Run security checks only",
2197
2553
  )
2198
2554
  parser_ship.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
2199
2555
  parser_ship.set_defaults(func=cmd_ship)
2200
2556
 
2201
2557
  # Fix-all command (auto-fix everything)
2202
2558
  parser_fix_all = subparsers.add_parser(
2203
- "fix-all", help="Auto-fix all fixable lint and format issues"
2559
+ "fix-all",
2560
+ help="Auto-fix all fixable lint and format issues",
2204
2561
  )
2205
2562
  parser_fix_all.add_argument("--project-root", default=".", help="Project root directory")
2206
2563
  parser_fix_all.add_argument(
2207
- "--dry-run", action="store_true", help="Show what would be fixed without applying"
2564
+ "--dry-run",
2565
+ action="store_true",
2566
+ help="Show what would be fixed without applying",
2208
2567
  )
2209
2568
  parser_fix_all.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
2210
2569
  parser_fix_all.set_defaults(func=cmd_fix_all)
2211
2570
 
2212
2571
  # Learn command (pattern learning from git history)
2213
2572
  parser_learn = subparsers.add_parser(
2214
- "learn", help="Learn patterns from git history and bug fixes"
2573
+ "learn",
2574
+ help="Learn patterns from git history and bug fixes",
2215
2575
  )
2216
2576
  parser_learn.add_argument(
2217
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2577
+ "--patterns-dir",
2578
+ default="./patterns",
2579
+ help="Path to patterns directory",
2218
2580
  )
2219
2581
  parser_learn.add_argument(
2220
- "--analyze", type=int, metavar="N", help="Analyze last N commits (default: 10)"
2582
+ "--analyze",
2583
+ type=int,
2584
+ metavar="N",
2585
+ help="Analyze last N commits (default: 10)",
2221
2586
  )
2222
2587
  parser_learn.add_argument(
2223
- "--watch", action="store_true", help="Watch for new commits (not yet implemented)"
2588
+ "--watch",
2589
+ action="store_true",
2590
+ help="Watch for new commits (not yet implemented)",
2224
2591
  )
2225
2592
  parser_learn.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
2226
2593
  parser_learn.set_defaults(func=cmd_learn)
2227
2594
 
2228
2595
  # Costs command (cost tracking dashboard)
2229
2596
  parser_costs = subparsers.add_parser(
2230
- "costs", help="View API cost tracking and savings from model routing"
2597
+ "costs",
2598
+ help="View API cost tracking and savings from model routing",
2231
2599
  )
2232
2600
  parser_costs.add_argument(
2233
- "--days", type=int, default=7, help="Number of days to include (default: 7)"
2601
+ "--days",
2602
+ type=int,
2603
+ default=7,
2604
+ help="Number of days to include (default: 7)",
2234
2605
  )
2235
2606
  parser_costs.add_argument("--empathy-dir", default=".empathy", help="Empathy data directory")
2236
2607
  parser_costs.add_argument("--json", action="store_true", help="Output as JSON")
@@ -2252,16 +2623,25 @@ def main():
2252
2623
  # Dashboard command (visual web interface)
2253
2624
  parser_dashboard = subparsers.add_parser("dashboard", help="Launch visual dashboard in browser")
2254
2625
  parser_dashboard.add_argument(
2255
- "--port", type=int, default=8765, help="Port to run on (default: 8765)"
2626
+ "--port",
2627
+ type=int,
2628
+ default=8765,
2629
+ help="Port to run on (default: 8765)",
2256
2630
  )
2257
2631
  parser_dashboard.add_argument(
2258
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2632
+ "--patterns-dir",
2633
+ default="./patterns",
2634
+ help="Path to patterns directory",
2259
2635
  )
2260
2636
  parser_dashboard.add_argument(
2261
- "--empathy-dir", default=".empathy", help="Empathy data directory"
2637
+ "--empathy-dir",
2638
+ default=".empathy",
2639
+ help="Empathy data directory",
2262
2640
  )
2263
2641
  parser_dashboard.add_argument(
2264
- "--no-browser", action="store_true", help="Don't open browser automatically"
2642
+ "--no-browser",
2643
+ action="store_true",
2644
+ help="Don't open browser automatically",
2265
2645
  )
2266
2646
  parser_dashboard.set_defaults(func=cmd_dashboard)
2267
2647
 
@@ -2271,7 +2651,9 @@ def main():
2271
2651
  help="List and manage agent frameworks (LangChain, LangGraph, AutoGen, Haystack)",
2272
2652
  )
2273
2653
  parser_frameworks.add_argument(
2274
- "--all", action="store_true", help="Show all frameworks including uninstalled"
2654
+ "--all",
2655
+ action="store_true",
2656
+ help="Show all frameworks including uninstalled",
2275
2657
  )
2276
2658
  parser_frameworks.add_argument(
2277
2659
  "--recommend",
@@ -2304,9 +2686,9 @@ def main():
2304
2686
  parser_workflow.add_argument(
2305
2687
  "--provider",
2306
2688
  "-p",
2307
- choices=["anthropic", "openai", "ollama", "hybrid"],
2689
+ choices=["anthropic", "openai", "google", "ollama", "hybrid"],
2308
2690
  default=None, # None means use config
2309
- help="Model provider: anthropic, openai, ollama, or hybrid (mix of best models)",
2691
+ help="Model provider: anthropic, openai, google, ollama, or hybrid (mix of best models)",
2310
2692
  )
2311
2693
  parser_workflow.add_argument(
2312
2694
  "--force",
@@ -2314,14 +2696,27 @@ def main():
2314
2696
  help="Force overwrite existing config file",
2315
2697
  )
2316
2698
  parser_workflow.add_argument("--json", action="store_true", help="Output as JSON")
2699
+ parser_workflow.add_argument(
2700
+ "--write-tests",
2701
+ action="store_true",
2702
+ help="(test-gen workflow) Write generated tests to disk",
2703
+ )
2704
+ parser_workflow.add_argument(
2705
+ "--output-dir",
2706
+ default="tests/generated",
2707
+ help="(test-gen workflow) Output directory for generated tests",
2708
+ )
2317
2709
  parser_workflow.set_defaults(func=cmd_workflow)
2318
2710
 
2319
2711
  # Sync-claude command (sync patterns to Claude Code)
2320
2712
  parser_sync_claude = subparsers.add_parser(
2321
- "sync-claude", help="Sync learned patterns to Claude Code rules"
2713
+ "sync-claude",
2714
+ help="Sync learned patterns to Claude Code rules",
2322
2715
  )
2323
2716
  parser_sync_claude.add_argument(
2324
- "--patterns-dir", default="./patterns", help="Path to patterns directory"
2717
+ "--patterns-dir",
2718
+ default="./patterns",
2719
+ help="Path to patterns directory",
2325
2720
  )
2326
2721
  parser_sync_claude.add_argument(
2327
2722
  "--output-dir",
@@ -2342,13 +2737,16 @@ def main():
2342
2737
  help="Category to show (getting-started, daily-workflow, code-quality, etc.)",
2343
2738
  )
2344
2739
  parser_cheatsheet.add_argument(
2345
- "--compact", action="store_true", help="Show commands only without descriptions"
2740
+ "--compact",
2741
+ action="store_true",
2742
+ help="Show commands only without descriptions",
2346
2743
  )
2347
2744
  parser_cheatsheet.set_defaults(func=cmd_cheatsheet)
2348
2745
 
2349
2746
  # Onboard command (interactive tutorial)
2350
2747
  parser_onboard = subparsers.add_parser(
2351
- "onboard", help="Interactive onboarding tutorial for new users"
2748
+ "onboard",
2749
+ help="Interactive onboarding tutorial for new users",
2352
2750
  )
2353
2751
  parser_onboard.add_argument("--step", type=int, help="Jump to a specific step (1-5)")
2354
2752
  parser_onboard.add_argument("--reset", action="store_true", help="Reset onboarding progress")
@@ -2356,7 +2754,8 @@ def main():
2356
2754
 
2357
2755
  # Explain command (detailed command explanations)
2358
2756
  parser_explain = subparsers.add_parser(
2359
- "explain", help="Get detailed explanation of how a command works"
2757
+ "explain",
2758
+ help="Get detailed explanation of how a command works",
2360
2759
  )
2361
2760
  parser_explain.add_argument(
2362
2761
  "command",
@@ -2367,10 +2766,14 @@ def main():
2367
2766
 
2368
2767
  # Achievements command (progress tracking)
2369
2768
  parser_achievements = subparsers.add_parser(
2370
- "achievements", help="View your usage statistics and achievements"
2769
+ "achievements",
2770
+ help="View your usage statistics and achievements",
2371
2771
  )
2372
2772
  parser_achievements.set_defaults(func=cmd_achievements)
2373
2773
 
2774
+ # Wizard Factory commands (create wizards 12x faster)
2775
+ add_wizard_factory_commands(subparsers)
2776
+
2374
2777
  # Parse arguments
2375
2778
  args = parser.parse_args()
2376
2779
 
@@ -2382,13 +2785,16 @@ def main():
2382
2785
  if args.command and args.command not in ("dashboard", "run"):
2383
2786
  try:
2384
2787
  show_tip_if_available(args.command)
2385
- except Exception:
2386
- pass # Don't fail on discovery errors
2788
+ except Exception as e: # noqa: BLE001
2789
+ # INTENTIONAL: Discovery tips are optional UX enhancements
2790
+ # They should never cause command execution to fail
2791
+ # Cannot predict all possible errors from discovery system
2792
+ logger.debug(f"Discovery tip not available for {args.command}: {e}")
2793
+ pass
2387
2794
 
2388
2795
  return result if result is not None else 0
2389
- else:
2390
- parser.print_help()
2391
- return 0
2796
+ parser.print_help()
2797
+ return 0
2392
2798
 
2393
2799
 
2394
2800
  if __name__ == "__main__":