attune-ai 2.0.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.
Files changed (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
@@ -0,0 +1,696 @@
1
+ """Dashboard command implementations for telemetry CLI.
2
+
3
+ Provides interactive HTML dashboard generation for telemetry data.
4
+
5
+ Copyright 2025 Smart-AI-Memory
6
+ Licensed under Fair Source License 0.9
7
+ """
8
+
9
+ import http.server
10
+ import socketserver
11
+ import tempfile
12
+ import webbrowser
13
+ from collections import Counter
14
+ from datetime import datetime
15
+ from typing import Any
16
+
17
+ from ..usage_tracker import UsageTracker
18
+
19
+
20
+ def cmd_telemetry_dashboard(args: Any) -> int:
21
+ """Open interactive telemetry dashboard in browser.
22
+
23
+ Args:
24
+ args: Parsed command-line arguments
25
+
26
+ Returns:
27
+ Exit code (0 for success)
28
+
29
+ """
30
+ tracker = UsageTracker.get_instance()
31
+ entries = tracker.export_to_dict(days=getattr(args, "days", 30))
32
+
33
+ if not entries:
34
+ print("No telemetry data available.")
35
+ return 0
36
+
37
+ # Calculate statistics
38
+ total_cost = sum(e.get("cost", 0) for e in entries)
39
+ total_calls = len(entries)
40
+ avg_duration = (
41
+ sum(e.get("duration_ms", 0) for e in entries) / total_calls if total_calls > 0 else 0
42
+ )
43
+
44
+ # Tier distribution
45
+ tiers = [e.get("tier", "UNKNOWN") for e in entries]
46
+ tier_counts = Counter(tiers)
47
+ tier_distribution = {tier: (count / total_calls) * 100 for tier, count in tier_counts.items()}
48
+
49
+ # Calculate savings (baseline: all PREMIUM tier)
50
+ premium_input_cost = 0.015 / 1000 # per token
51
+ premium_output_cost = 0.075 / 1000 # per token
52
+
53
+ baseline_cost = sum(
54
+ (e.get("tokens", {}).get("input", 0) * premium_input_cost)
55
+ + (e.get("tokens", {}).get("output", 0) * premium_output_cost)
56
+ for e in entries
57
+ )
58
+
59
+ saved = baseline_cost - total_cost
60
+ savings_pct = (saved / baseline_cost * 100) if baseline_cost > 0 else 0
61
+
62
+ # Generate HTML
63
+ html_content = f"""<!DOCTYPE html>
64
+ <html lang="en">
65
+ <head>
66
+ <meta charset="UTF-8">
67
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
68
+ <title>Empathy Telemetry Dashboard</title>
69
+ <style>
70
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
71
+ body {{
72
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
73
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
74
+ padding: 20px;
75
+ min-height: 100vh;
76
+ }}
77
+ .container {{
78
+ max-width: 1400px;
79
+ margin: 0 auto;
80
+ }}
81
+ .header {{
82
+ color: white;
83
+ text-align: center;
84
+ margin-bottom: 40px;
85
+ }}
86
+ .header h1 {{
87
+ font-size: 48px;
88
+ font-weight: 700;
89
+ margin-bottom: 10px;
90
+ }}
91
+ .header p {{
92
+ font-size: 18px;
93
+ opacity: 0.9;
94
+ }}
95
+ .stats-grid {{
96
+ display: grid;
97
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
98
+ gap: 20px;
99
+ margin-bottom: 30px;
100
+ }}
101
+ .stat-card {{
102
+ background: white;
103
+ border-radius: 12px;
104
+ padding: 30px;
105
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
106
+ }}
107
+ .savings-card {{
108
+ grid-column: span 2;
109
+ background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
110
+ color: white;
111
+ }}
112
+ .stat-label {{
113
+ font-size: 14px;
114
+ text-transform: uppercase;
115
+ letter-spacing: 1px;
116
+ margin-bottom: 10px;
117
+ opacity: 0.8;
118
+ }}
119
+ .stat-value {{
120
+ font-size: 56px;
121
+ font-weight: 700;
122
+ margin-bottom: 5px;
123
+ }}
124
+ .stat-sublabel {{
125
+ font-size: 16px;
126
+ opacity: 0.7;
127
+ }}
128
+ .tier-distribution {{
129
+ display: flex;
130
+ gap: 10px;
131
+ margin-top: 15px;
132
+ height: 50px;
133
+ }}
134
+ .tier-bar {{
135
+ flex: 1;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ border-radius: 8px;
140
+ font-weight: 600;
141
+ color: white;
142
+ font-size: 14px;
143
+ }}
144
+ .tier-premium {{ background: linear-gradient(135deg, #9c27b0, #7b1fa2); }}
145
+ .tier-capable {{ background: linear-gradient(135deg, #2196f3, #1976d2); }}
146
+ .tier-cheap {{ background: linear-gradient(135deg, #4caf50, #388e3c); }}
147
+ table {{
148
+ width: 100%;
149
+ background: white;
150
+ border-radius: 12px;
151
+ overflow: hidden;
152
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
153
+ }}
154
+ th, td {{
155
+ padding: 16px;
156
+ text-align: left;
157
+ }}
158
+ th {{
159
+ background: #f5f5f5;
160
+ font-weight: 600;
161
+ font-size: 13px;
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.5px;
164
+ color: #666;
165
+ }}
166
+ tr:hover {{
167
+ background: #f9f9f9;
168
+ }}
169
+ .tier-badge {{
170
+ display: inline-block;
171
+ padding: 4px 10px;
172
+ border-radius: 4px;
173
+ font-size: 11px;
174
+ font-weight: 600;
175
+ color: white;
176
+ }}
177
+ .badge-premium {{ background: #9c27b0; }}
178
+ .badge-capable {{ background: #2196f3; }}
179
+ .badge-cheap {{ background: #4caf50; }}
180
+ .cache-hit {{ color: #4caf50; font-weight: 600; }}
181
+ .cache-miss {{ color: #999; }}
182
+ </style>
183
+ </head>
184
+ <body>
185
+ <div class="container">
186
+ <div class="header">
187
+ <h1>📊 Empathy Telemetry Dashboard</h1>
188
+ <p>Last {len(entries)} LLM API calls • Real-time cost tracking</p>
189
+ </div>
190
+
191
+ <div class="stats-grid">
192
+ <div class="stat-card savings-card">
193
+ <div class="stat-label">Cost Savings (Tier Routing)</div>
194
+ <div class="stat-value">${saved:.2f}</div>
195
+ <div class="stat-sublabel">
196
+ {savings_pct:.1f}% saved • Baseline: ${baseline_cost:.2f} • Actual: ${
197
+ total_cost:.2f}
198
+ </div>
199
+ </div>
200
+
201
+ <div class="stat-card">
202
+ <div class="stat-label">Total Cost</div>
203
+ <div class="stat-value">${total_cost:.2f}</div>
204
+ <div class="stat-sublabel">{total_calls} API calls</div>
205
+ </div>
206
+
207
+ <div class="stat-card">
208
+ <div class="stat-label">Avg Duration</div>
209
+ <div class="stat-value">{avg_duration / 1000:.1f}s</div>
210
+ <div class="stat-sublabel">Per API call</div>
211
+ </div>
212
+ </div>
213
+
214
+ <div class="stat-card">
215
+ <div class="stat-label">Tier Distribution</div>
216
+ <div class="tier-distribution">
217
+ {
218
+ "".join(
219
+ f'<div class="tier-bar tier-{tier.lower()}">{tier}: {pct:.1f}%</div>'
220
+ for tier, pct in tier_distribution.items()
221
+ )
222
+ }
223
+ </div>
224
+ </div>
225
+
226
+ <h2 style="color: white; margin: 40px 0 20px 0; font-size: 28px;">Recent LLM Calls</h2>
227
+ <table>
228
+ <thead>
229
+ <tr>
230
+ <th>Time</th>
231
+ <th>Workflow</th>
232
+ <th>Stage</th>
233
+ <th>Tier</th>
234
+ <th>Cost</th>
235
+ <th>Tokens</th>
236
+ <th>Cache</th>
237
+ <th>Duration</th>
238
+ </tr>
239
+ </thead>
240
+ <tbody>
241
+ {
242
+ "".join(
243
+ f'''<tr>
244
+ <td>{datetime.fromisoformat(e.get("ts", "").replace("Z", "+00:00")).strftime("%H:%M:%S")}</td>
245
+ <td>{e.get("workflow", "")}</td>
246
+ <td>{e.get("stage", "")}</td>
247
+ <td><span class="tier-badge badge-{e.get("tier", "").lower()}">{e.get("tier", "")}</span></td>
248
+ <td>${e.get("cost", 0):.4f}</td>
249
+ <td>{e.get("tokens", {}).get("input", 0)}/{e.get("tokens", {}).get("output", 0)}</td>
250
+ <td class="cache-{"hit" if e.get("cache", {}).get("hit") else "miss"}">
251
+ {"HIT" if e.get("cache", {}).get("hit") else "MISS"}
252
+ </td>
253
+ <td>{e.get("duration_ms", 0) / 1000:.1f}s</td>
254
+ </tr>'''
255
+ for e in list(reversed(entries))[:20]
256
+ )
257
+ }
258
+ </tbody>
259
+ </table>
260
+ </div>
261
+ </body>
262
+ </html>"""
263
+
264
+ # Write to temp file
265
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
266
+ f.write(html_content)
267
+ temp_path = f.name
268
+
269
+ print(f"📊 Opening dashboard in browser: {temp_path}")
270
+ webbrowser.open(f"file://{temp_path}")
271
+
272
+ return 0
273
+
274
+
275
+ def cmd_file_test_dashboard(args: Any) -> int:
276
+ """Open interactive file test status dashboard in browser.
277
+
278
+ Args:
279
+ args: Parsed command-line arguments
280
+ - port: Port to serve on (default: 8765)
281
+
282
+ Returns:
283
+ Exit code (0 for success)
284
+ """
285
+ from attune.models.telemetry import get_telemetry_store
286
+
287
+ port = getattr(args, "port", 8765)
288
+
289
+ def generate_dashboard_html() -> str:
290
+ """Generate the dashboard HTML with current data."""
291
+ store = get_telemetry_store()
292
+ all_records = store.get_file_tests(limit=100000)
293
+
294
+ if not all_records:
295
+ return _generate_empty_dashboard()
296
+
297
+ # Get latest record per file
298
+ latest_by_file: dict[str, Any] = {}
299
+ for record in all_records:
300
+ existing = latest_by_file.get(record.file_path)
301
+ if existing is None or record.timestamp > existing.timestamp:
302
+ latest_by_file[record.file_path] = record
303
+
304
+ records = list(latest_by_file.values())
305
+
306
+ # Calculate stats
307
+ total = len(records)
308
+ passed = sum(1 for r in records if r.last_test_result == "passed")
309
+ failed = sum(1 for r in records if r.last_test_result in ("failed", "error"))
310
+ no_tests = sum(1 for r in records if r.last_test_result == "no_tests")
311
+ stale = sum(1 for r in records if r.is_stale)
312
+
313
+ # Sort by status priority: failed > stale > no_tests > passed
314
+ def sort_key(r):
315
+ if r.last_test_result in ("failed", "error"):
316
+ return (0, r.file_path)
317
+ if r.is_stale:
318
+ return (1, r.file_path)
319
+ if r.last_test_result == "no_tests":
320
+ return (2, r.file_path)
321
+ return (3, r.file_path)
322
+
323
+ records.sort(key=sort_key)
324
+
325
+ # Generate table rows
326
+ rows_html = ""
327
+ for record in records:
328
+ result = record.last_test_result
329
+ if result == "passed":
330
+ status_class = "passed"
331
+ status_icon = "✅"
332
+ elif result in ("failed", "error"):
333
+ status_class = "failed"
334
+ status_icon = "❌"
335
+ elif result == "no_tests":
336
+ status_class = "no-tests"
337
+ status_icon = "⚠️"
338
+ else:
339
+ status_class = "skipped"
340
+ status_icon = "⏭️"
341
+
342
+ stale_badge = '<span class="badge stale">STALE</span>' if record.is_stale else ""
343
+
344
+ try:
345
+ dt = datetime.fromisoformat(record.timestamp.rstrip("Z"))
346
+ ts_display = dt.strftime("%Y-%m-%d %H:%M")
347
+ except (ValueError, AttributeError):
348
+ ts_display = record.timestamp[:16] if record.timestamp else "-"
349
+
350
+ rows_html += f"""
351
+ <tr class="{status_class}">
352
+ <td class="file-path">{record.file_path}</td>
353
+ <td class="status">{status_icon} {result.upper()} {stale_badge}</td>
354
+ <td class="numeric">{record.test_count}</td>
355
+ <td class="numeric passed-count">{record.passed}</td>
356
+ <td class="numeric failed-count">{record.failed + record.errors}</td>
357
+ <td class="numeric">{record.duration_seconds:.1f}s</td>
358
+ <td class="timestamp">{ts_display}</td>
359
+ </tr>
360
+ """
361
+
362
+ return """<!DOCTYPE html>
363
+ <html lang="en">
364
+ <head>
365
+ <meta charset="UTF-8">
366
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
367
+ <title>File Test Status Dashboard</title>
368
+ <style>
369
+ * { margin: 0; padding: 0; box-sizing: border-box; }
370
+ body {
371
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
372
+ background: #ffffff;
373
+ color: #333;
374
+ padding: 20px;
375
+ min-height: 100vh;
376
+ }
377
+ .container { max-width: 1600px; margin: 0 auto; }
378
+ .header {
379
+ display: flex;
380
+ justify-content: space-between;
381
+ align-items: center;
382
+ margin-bottom: 30px;
383
+ padding-bottom: 20px;
384
+ border-bottom: 1px solid #e0e0e0;
385
+ }
386
+ .header h1 { font-size: 28px; color: #333; }
387
+ .refresh-btn {
388
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
389
+ color: white;
390
+ border: none;
391
+ padding: 12px 24px;
392
+ border-radius: 8px;
393
+ font-size: 16px;
394
+ cursor: pointer;
395
+ display: flex;
396
+ align-items: center;
397
+ gap: 8px;
398
+ transition: transform 0.2s, box-shadow 0.2s;
399
+ }
400
+ .refresh-btn:hover {
401
+ transform: translateY(-2px);
402
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
403
+ }
404
+ .refresh-btn:active { transform: translateY(0); }
405
+ .refresh-btn.spinning .icon { animation: spin 1s linear infinite; }
406
+ @keyframes spin { 100% { transform: rotate(360deg); } }
407
+ .stats {
408
+ display: grid;
409
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
410
+ gap: 20px;
411
+ margin-bottom: 30px;
412
+ }
413
+ .stat-card {
414
+ background: #f8f9fa;
415
+ border-radius: 12px;
416
+ padding: 20px;
417
+ text-align: center;
418
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
419
+ }
420
+ .stat-card.passed { border-left: 4px solid #22c55e; }
421
+ .stat-card.failed { border-left: 4px solid #ef4444; }
422
+ .stat-card.no-tests { border-left: 4px solid #f59e0b; }
423
+ .stat-card.stale { border-left: 4px solid #8b5cf6; }
424
+ .stat-card.total { border-left: 4px solid #3b82f6; }
425
+ .stat-value { font-size: 36px; font-weight: bold; }
426
+ .stat-label { font-size: 14px; color: #666; margin-top: 5px; }
427
+ .stat-card.passed .stat-value { color: #22c55e; }
428
+ .stat-card.failed .stat-value { color: #ef4444; }
429
+ .stat-card.no-tests .stat-value { color: #f59e0b; }
430
+ .stat-card.stale .stat-value { color: #8b5cf6; }
431
+ .stat-card.total .stat-value { color: #3b82f6; }
432
+ .filter-bar {
433
+ display: flex;
434
+ gap: 10px;
435
+ margin-bottom: 20px;
436
+ flex-wrap: wrap;
437
+ }
438
+ .filter-btn {
439
+ background: #f8f9fa;
440
+ color: #666;
441
+ border: 1px solid #e0e0e0;
442
+ padding: 8px 16px;
443
+ border-radius: 6px;
444
+ cursor: pointer;
445
+ transition: all 0.2s;
446
+ }
447
+ .filter-btn:hover, .filter-btn.active {
448
+ background: #667eea;
449
+ color: #fff;
450
+ border-color: #667eea;
451
+ }
452
+ .search-input {
453
+ flex: 1;
454
+ min-width: 200px;
455
+ background: #fff;
456
+ border: 1px solid #e0e0e0;
457
+ color: #333;
458
+ padding: 8px 16px;
459
+ border-radius: 6px;
460
+ font-size: 14px;
461
+ }
462
+ .search-input:focus {
463
+ outline: none;
464
+ border-color: #667eea;
465
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
466
+ }
467
+ table {
468
+ width: 100%;
469
+ border-collapse: collapse;
470
+ background: #fff;
471
+ border-radius: 12px;
472
+ overflow: hidden;
473
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
474
+ }
475
+ th, td { padding: 12px 16px; text-align: left; }
476
+ th {
477
+ background: #f8f9fa;
478
+ font-weight: 600;
479
+ color: #333;
480
+ position: sticky;
481
+ top: 0;
482
+ border-bottom: 2px solid #e0e0e0;
483
+ }
484
+ tr { border-bottom: 1px solid #f0f0f0; }
485
+ tr:hover { background: #f8f9fa; }
486
+ tr.failed { background: rgba(239, 68, 68, 0.08); }
487
+ tr.no-tests { background: rgba(245, 158, 11, 0.05); }
488
+ .file-path { font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; color: #333; }
489
+ .numeric { text-align: right; font-family: monospace; }
490
+ .passed-count { color: #22c55e; }
491
+ .failed-count { color: #ef4444; }
492
+ .timestamp { color: #888; font-size: 12px; }
493
+ .badge {
494
+ display: inline-block;
495
+ padding: 2px 8px;
496
+ border-radius: 4px;
497
+ font-size: 10px;
498
+ font-weight: bold;
499
+ margin-left: 8px;
500
+ }
501
+ .badge.stale { background: #8b5cf6; color: #fff; }
502
+ .hidden { display: none; }
503
+ .last-updated { color: #888; font-size: 12px; margin-top: 20px; text-align: center; }
504
+ </style>
505
+ </head>
506
+ <body>
507
+ <div class="container">
508
+ <div class="header">
509
+ <h1>📊 File Test Status Dashboard</h1>
510
+ <button class="refresh-btn" onclick="refreshData()">
511
+ <span class="icon">🔄</span>
512
+ <span>Refresh</span>
513
+ </button>
514
+ </div>
515
+
516
+ <div class="stats">
517
+ <div class="stat-card total">
518
+ <div class="stat-value">""" + str(total) + """</div>
519
+ <div class="stat-label">Total Files</div>
520
+ </div>
521
+ <div class="stat-card passed">
522
+ <div class="stat-value">""" + str(passed) + """</div>
523
+ <div class="stat-label">Passed</div>
524
+ </div>
525
+ <div class="stat-card failed">
526
+ <div class="stat-value">""" + str(failed) + """</div>
527
+ <div class="stat-label">Failed</div>
528
+ </div>
529
+ <div class="stat-card no-tests">
530
+ <div class="stat-value">""" + str(no_tests) + """</div>
531
+ <div class="stat-label">No Tests</div>
532
+ </div>
533
+ <div class="stat-card stale">
534
+ <div class="stat-value">""" + str(stale) + """</div>
535
+ <div class="stat-label">Stale</div>
536
+ </div>
537
+ </div>
538
+
539
+ <div class="filter-bar">
540
+ <button class="filter-btn active" data-filter="all">All</button>
541
+ <button class="filter-btn" data-filter="passed">✅ Passed</button>
542
+ <button class="filter-btn" data-filter="failed">❌ Failed</button>
543
+ <button class="filter-btn" data-filter="no-tests">⚠️ No Tests</button>
544
+ <button class="filter-btn" data-filter="stale">🔄 Stale</button>
545
+ <input type="text" class="search-input" placeholder="Search files..." id="searchInput">
546
+ </div>
547
+
548
+ <table id="fileTable">
549
+ <thead>
550
+ <tr>
551
+ <th>File Path</th>
552
+ <th>Status</th>
553
+ <th>Tests</th>
554
+ <th>Passed</th>
555
+ <th>Failed</th>
556
+ <th>Duration</th>
557
+ <th>Last Run</th>
558
+ </tr>
559
+ </thead>
560
+ <tbody>
561
+ """ + rows_html + """
562
+ </tbody>
563
+ </table>
564
+
565
+ <div class="last-updated">
566
+ Last updated: """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """
567
+ </div>
568
+ </div>
569
+
570
+ <script>
571
+ // Filter functionality
572
+ const filterBtns = document.querySelectorAll('.filter-btn');
573
+ const rows = document.querySelectorAll('#fileTable tbody tr');
574
+ const searchInput = document.getElementById('searchInput');
575
+
576
+ let currentFilter = 'all';
577
+
578
+ filterBtns.forEach(btn => {
579
+ btn.addEventListener('click', () => {
580
+ filterBtns.forEach(b => b.classList.remove('active'));
581
+ btn.classList.add('active');
582
+ currentFilter = btn.dataset.filter;
583
+ applyFilters();
584
+ });
585
+ });
586
+
587
+ searchInput.addEventListener('input', applyFilters);
588
+
589
+ function applyFilters() {
590
+ const searchTerm = searchInput.value.toLowerCase();
591
+ rows.forEach(row => {
592
+ const filePath = row.querySelector('.file-path').textContent.toLowerCase();
593
+ const matchesSearch = filePath.includes(searchTerm);
594
+ const matchesFilter = currentFilter === 'all' ||
595
+ (currentFilter === 'passed' && row.classList.contains('passed')) ||
596
+ (currentFilter === 'failed' && row.classList.contains('failed')) ||
597
+ (currentFilter === 'no-tests' && row.classList.contains('no-tests')) ||
598
+ (currentFilter === 'stale' && row.innerHTML.includes('STALE'));
599
+
600
+ row.classList.toggle('hidden', !(matchesSearch && matchesFilter));
601
+ });
602
+ }
603
+
604
+ // Refresh functionality
605
+ function refreshData() {
606
+ const btn = document.querySelector('.refresh-btn');
607
+ btn.classList.add('spinning');
608
+ btn.disabled = true;
609
+
610
+ // Reload the page to get fresh data
611
+ setTimeout(() => {
612
+ window.location.reload();
613
+ }, 500);
614
+ }
615
+
616
+ // Auto-refresh every 60 seconds (optional)
617
+ // setInterval(refreshData, 60000);
618
+ </script>
619
+ </body>
620
+ </html>"""
621
+
622
+ def _generate_empty_dashboard() -> str:
623
+ """Generate dashboard HTML when no data available."""
624
+ return """<!DOCTYPE html>
625
+ <html lang="en">
626
+ <head>
627
+ <meta charset="UTF-8">
628
+ <title>File Test Status Dashboard</title>
629
+ <style>
630
+ body {
631
+ font-family: -apple-system, sans-serif;
632
+ background: #ffffff;
633
+ color: #333;
634
+ display: flex;
635
+ justify-content: center;
636
+ align-items: center;
637
+ height: 100vh;
638
+ text-align: center;
639
+ }
640
+ .message { max-width: 500px; }
641
+ h1 { margin-bottom: 20px; color: #333; }
642
+ code {
643
+ background: #f8f9fa;
644
+ color: #333;
645
+ padding: 10px 20px;
646
+ border-radius: 6px;
647
+ display: block;
648
+ margin-top: 20px;
649
+ border: 1px solid #e0e0e0;
650
+ }
651
+ </style>
652
+ </head>
653
+ <body>
654
+ <div class="message">
655
+ <h1>📊 No Test Data Available</h1>
656
+ <p>Run the file test tracker to populate data:</p>
657
+ <code>empathy file-tests --scan</code>
658
+ <p style="margin-top: 20px; color: #888;">Or track individual files:</p>
659
+ <code>python -c "from attune.workflows.test_runner import track_file_tests; track_file_tests('src/your_file.py')"</code>
660
+ </div>
661
+ </body>
662
+ </html>"""
663
+
664
+ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
665
+ """Custom handler for the dashboard."""
666
+
667
+ def do_GET(self):
668
+ """Handle GET requests."""
669
+ if self.path == "/" or self.path == "/index.html":
670
+ self.send_response(200)
671
+ self.send_header("Content-type", "text/html")
672
+ self.end_headers()
673
+ html = generate_dashboard_html()
674
+ self.wfile.write(html.encode())
675
+ else:
676
+ self.send_error(404)
677
+
678
+ def log_message(self, format, *args):
679
+ """Suppress logging."""
680
+ pass
681
+
682
+ print(f"Starting File Test Dashboard on http://localhost:{port}")
683
+ print("Press Ctrl+C to stop the server")
684
+
685
+ # Open browser
686
+ webbrowser.open(f"http://localhost:{port}")
687
+
688
+ # Start server
689
+ with socketserver.TCPServer(("", port), DashboardHandler) as httpd:
690
+ httpd.allow_reuse_address = True
691
+ try:
692
+ httpd.serve_forever()
693
+ except KeyboardInterrupt:
694
+ print("\nDashboard server stopped.")
695
+
696
+ return 0