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,285 @@
1
+ """CLI commands for adaptive model routing statistics.
2
+
3
+ Provides commands to analyze model routing performance and get tier upgrade
4
+ recommendations based on historical telemetry data.
5
+
6
+ Copyright 2025 Smart-AI-Memory
7
+ Licensed under Fair Source License 0.9
8
+ """
9
+
10
+ import logging
11
+ from typing import Any
12
+
13
+ from attune.models import AdaptiveModelRouter
14
+ from attune.telemetry import UsageTracker
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def cmd_routing_stats(args: Any) -> int:
20
+ """Show routing statistics for a workflow.
21
+
22
+ Args:
23
+ args: Arguments with workflow, stage (optional), days
24
+
25
+ Returns:
26
+ 0 on success, 1 on error
27
+ """
28
+ try:
29
+ # Get telemetry and router
30
+ tracker = UsageTracker.get_instance()
31
+ router = AdaptiveModelRouter(telemetry=tracker)
32
+
33
+ # Get routing stats
34
+ stats = router.get_routing_stats(
35
+ workflow=args.workflow,
36
+ stage=args.stage if hasattr(args, "stage") and args.stage else None,
37
+ days=args.days,
38
+ )
39
+
40
+ if stats["total_calls"] == 0:
41
+ print(f"❌ No data found for workflow '{args.workflow}'")
42
+ print(f" (searched last {args.days} days)")
43
+ return 1
44
+
45
+ # Display stats
46
+ print("\n" + "=" * 70)
47
+ print(f"ADAPTIVE ROUTING STATISTICS - {stats['workflow']}")
48
+ if stats["stage"] != "all":
49
+ print(f"Stage: {stats['stage']}")
50
+ print("=" * 70)
51
+
52
+ print(f"\n📊 Overview (Last {stats['days_analyzed']} days)")
53
+ print(f" Total calls: {stats['total_calls']:,}")
54
+ print(f" Average cost: ${stats['avg_cost']:.4f}")
55
+ print(f" Average success rate: {stats['avg_success_rate']:.1%}")
56
+ print(f" Models used: {len(stats['models_used'])}")
57
+
58
+ # Per-model performance
59
+ print("\n📈 Per-Model Performance")
60
+ print("-" * 70)
61
+
62
+ for model in stats["models_used"]:
63
+ perf = stats["performance_by_model"][model]
64
+ print(f"\n {model}:")
65
+ print(f" Calls: {perf['calls']:,}")
66
+ print(f" Success rate: {perf['success_rate']:.1%}")
67
+ print(f" Avg cost: ${perf['avg_cost']:.4f}")
68
+ print(f" Avg latency: {perf['avg_latency_ms']:.0f}ms")
69
+
70
+ # Quality score calculation (from AdaptiveModelRouter)
71
+ quality_score = (perf["success_rate"] * 100) - (perf["avg_cost"] * 10)
72
+ print(f" Quality score: {quality_score:.2f}")
73
+
74
+ # Recommendations
75
+ print("\n💡 Recommendations")
76
+ print("-" * 70)
77
+
78
+ # Find best model
79
+ best_model = max(
80
+ stats["performance_by_model"].items(),
81
+ key=lambda x: (x[1]["success_rate"] * 100) - (x[1]["avg_cost"] * 10),
82
+ )
83
+
84
+ print(f" Best model: {best_model[0]}")
85
+ print(f" ({best_model[1]['success_rate']:.1%} success, ${best_model[1]['avg_cost']:.4f}/call)")
86
+
87
+ # Cost savings potential
88
+ if len(stats["models_used"]) > 1:
89
+ cheapest = min(
90
+ stats["performance_by_model"].items(),
91
+ key=lambda x: x[1]["avg_cost"],
92
+ )
93
+ most_expensive = max(
94
+ stats["performance_by_model"].items(),
95
+ key=lambda x: x[1]["avg_cost"],
96
+ )
97
+
98
+ if cheapest[0] != most_expensive[0]:
99
+ savings_per_call = most_expensive[1]["avg_cost"] - cheapest[1]["avg_cost"]
100
+ print("\n 💰 Potential savings:")
101
+ print(f" Using {cheapest[0]} instead of {most_expensive[0]}")
102
+ print(f" ${savings_per_call:.4f} per call")
103
+ if stats["total_calls"] > 0:
104
+ weekly_calls = (stats["total_calls"] / stats["days_analyzed"]) * 7
105
+ weekly_savings = savings_per_call * weekly_calls
106
+ print(f" ~${weekly_savings:.2f}/week potential")
107
+
108
+ return 0
109
+
110
+ except Exception as e:
111
+ logger.exception("Failed to get routing stats")
112
+ print(f"❌ Error: {e}")
113
+ return 1
114
+
115
+
116
+ def cmd_routing_check(args: Any) -> int:
117
+ """Check if tier upgrades are recommended for workflows.
118
+
119
+ Args:
120
+ args: Arguments with workflow (or --all), stage (optional)
121
+
122
+ Returns:
123
+ 0 on success, 1 on error
124
+ """
125
+ try:
126
+ # Get telemetry and router
127
+ tracker = UsageTracker.get_instance()
128
+ router = AdaptiveModelRouter(telemetry=tracker)
129
+
130
+ print("\n" + "=" * 70)
131
+ print("ADAPTIVE ROUTING - TIER UPGRADE RECOMMENDATIONS")
132
+ print("=" * 70)
133
+
134
+ if hasattr(args, "all") and args.all:
135
+ # Check all workflows
136
+ stats = tracker.get_stats(days=args.days)
137
+ workflows = list(stats["by_workflow"].keys())
138
+
139
+ if not workflows:
140
+ print("\n❌ No workflow data found")
141
+ return 1
142
+
143
+ print(f"\nChecking {len(workflows)} workflows (last {args.days} days)...\n")
144
+
145
+ upgrades_needed = []
146
+ upgrades_ok = []
147
+
148
+ for workflow_name in workflows:
149
+ should_upgrade, reason = router.recommend_tier_upgrade(
150
+ workflow=workflow_name, stage=None
151
+ )
152
+
153
+ if should_upgrade:
154
+ upgrades_needed.append((workflow_name, reason))
155
+ else:
156
+ upgrades_ok.append((workflow_name, reason))
157
+
158
+ # Show workflows needing upgrades
159
+ if upgrades_needed:
160
+ print(f"⚠️ {len(upgrades_needed)} workflow(s) need tier upgrade:")
161
+ print("-" * 70)
162
+ for workflow_name, reason in upgrades_needed:
163
+ print(f" • {workflow_name}")
164
+ print(f" {reason}")
165
+ print()
166
+
167
+ # Show workflows performing well
168
+ if upgrades_ok:
169
+ print(f"✓ {len(upgrades_ok)} workflow(s) performing well:")
170
+ print("-" * 70)
171
+ for workflow_name, reason in upgrades_ok:
172
+ print(f" • {workflow_name}: {reason}")
173
+ print()
174
+
175
+ # Summary
176
+ if upgrades_needed:
177
+ print("💡 Recommendation:")
178
+ print(" Enable adaptive routing to automatically upgrade tiers:")
179
+ print(" workflow = MyWorkflow(enable_adaptive_routing=True)")
180
+ return 0
181
+ else:
182
+ print("✓ All workflows performing well - no upgrades needed")
183
+ return 0
184
+
185
+ else:
186
+ # Check specific workflow
187
+ workflow_name = args.workflow
188
+
189
+ should_upgrade, reason = router.recommend_tier_upgrade(
190
+ workflow=workflow_name,
191
+ stage=args.stage if hasattr(args, "stage") and args.stage else None,
192
+ )
193
+
194
+ print(f"\nWorkflow: {workflow_name}")
195
+ if hasattr(args, "stage") and args.stage:
196
+ print(f"Stage: {args.stage}")
197
+ print(f"Analysis period: Last {args.days} days")
198
+ print()
199
+
200
+ if should_upgrade:
201
+ print("⚠️ TIER UPGRADE RECOMMENDED")
202
+ print(f" {reason}")
203
+ print()
204
+ print("💡 Action:")
205
+ print(" 1. Enable adaptive routing:")
206
+ print(" workflow = MyWorkflow(enable_adaptive_routing=True)")
207
+ print(" 2. Or manually upgrade tier in workflow config")
208
+ return 0
209
+ else:
210
+ print("✓ NO UPGRADE NEEDED")
211
+ print(f" {reason}")
212
+ return 0
213
+
214
+ except Exception as e:
215
+ logger.exception("Failed to check routing recommendations")
216
+ print(f"❌ Error: {e}")
217
+ return 1
218
+
219
+
220
+ def cmd_routing_models(args: Any) -> int:
221
+ """Show model performance comparison.
222
+
223
+ Args:
224
+ args: Arguments with provider, days
225
+
226
+ Returns:
227
+ 0 on success, 1 on error
228
+ """
229
+ try:
230
+ # Get telemetry
231
+ tracker = UsageTracker.get_instance()
232
+
233
+ # Get recent entries
234
+ entries = tracker.get_recent_entries(limit=100000, days=args.days)
235
+
236
+ if args.provider:
237
+ entries = [e for e in entries if e.get("provider") == args.provider]
238
+
239
+ if not entries:
240
+ print(f"❌ No data found for provider '{args.provider}'")
241
+ return 1
242
+
243
+ # Group by model
244
+ by_model: dict[str, list] = {}
245
+ for entry in entries:
246
+ model = entry["model"]
247
+ if model not in by_model:
248
+ by_model[model] = []
249
+ by_model[model].append(entry)
250
+
251
+ print("\n" + "=" * 70)
252
+ print(f"MODEL PERFORMANCE COMPARISON - {args.provider.upper()}")
253
+ print(f"Last {args.days} days")
254
+ print("=" * 70)
255
+
256
+ # Sort by total calls
257
+ models_sorted = sorted(by_model.items(), key=lambda x: len(x[1]), reverse=True)
258
+
259
+ print(f"\n📊 {len(models_sorted)} model(s) used\n")
260
+
261
+ for model, model_entries in models_sorted:
262
+ total = len(model_entries)
263
+ successes = sum(1 for e in model_entries if e.get("success", True))
264
+ success_rate = successes / total
265
+
266
+ avg_cost = sum(e.get("cost", 0.0) for e in model_entries) / total
267
+ avg_latency = sum(e.get("duration_ms", 0) for e in model_entries) / total
268
+
269
+ # Quality score
270
+ quality_score = (success_rate * 100) - (avg_cost * 10)
271
+
272
+ print(f" {model}")
273
+ print(f" Calls: {total:,}")
274
+ print(f" Success rate: {success_rate:.1%}")
275
+ print(f" Avg cost: ${avg_cost:.4f}")
276
+ print(f" Avg latency: {avg_latency:.0f}ms")
277
+ print(f" Quality score: {quality_score:.2f}")
278
+ print()
279
+
280
+ return 0
281
+
282
+ except Exception as e:
283
+ logger.exception("Failed to get model performance")
284
+ print(f"❌ Error: {e}")
285
+ return 1
@@ -0,0 +1,96 @@
1
+ """Setup commands for initialization and validation.
2
+
3
+ Copyright 2025 Smart-AI-Memory
4
+ Licensed under Fair Source License 0.9
5
+ """
6
+
7
+ import sys
8
+
9
+ from attune.config import EmpathyConfig, _validate_file_path, load_config
10
+ from attune.logging_config import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ def cmd_init(args):
16
+ """Initialize a new Empathy Framework project.
17
+
18
+ Creates a configuration file with sensible defaults.
19
+
20
+ Args:
21
+ args: Namespace object from argparse with attributes:
22
+ - format (str): Output format ('yaml' or 'json').
23
+ - output (str | None): Output file path.
24
+
25
+ Returns:
26
+ None: Creates configuration file at specified path.
27
+
28
+ Raises:
29
+ ValueError: If output path is invalid or unsafe.
30
+ """
31
+ config_format = args.format
32
+ output_path = args.output or f"attune.config.{config_format}"
33
+
34
+ # Validate output path to prevent path traversal attacks
35
+ validated_path = _validate_file_path(output_path)
36
+
37
+ logger.info(f"Initializing new Empathy Framework project with format: {config_format}")
38
+
39
+ # Create default config
40
+ config = EmpathyConfig()
41
+
42
+ # Save to file
43
+ if config_format == "yaml":
44
+ config.to_yaml(str(validated_path))
45
+ logger.info(f"Created YAML configuration file: {output_path}")
46
+ logger.info(f"✓ Created YAML configuration: {output_path}")
47
+ elif config_format == "json":
48
+ config.to_json(str(validated_path))
49
+ logger.info(f"Created JSON configuration file: {validated_path}")
50
+ logger.info(f"✓ Created JSON configuration: {validated_path}")
51
+
52
+ logger.info("\nNext steps:")
53
+ logger.info(f" 1. Edit {output_path} to customize settings")
54
+ logger.info(" 2. Use 'empathy run' to start using the framework")
55
+
56
+
57
+ def cmd_validate(args):
58
+ """Validate a configuration file.
59
+
60
+ Loads and validates the specified configuration file.
61
+
62
+ Args:
63
+ args: Namespace object from argparse with attributes:
64
+ - config (str): Path to configuration file to validate.
65
+
66
+ Returns:
67
+ None: Prints validation result. Exits with code 1 on failure.
68
+ """
69
+ filepath = args.config
70
+ logger.info(f"Validating configuration file: {filepath}")
71
+
72
+ try:
73
+ config = load_config(filepath=filepath, use_env=False)
74
+ config.validate()
75
+ logger.info(f"Configuration validation successful: {filepath}")
76
+ logger.info(f"✓ Configuration valid: {filepath}")
77
+ logger.info(f"\n User ID: {config.user_id}")
78
+ logger.info(f" Target Level: {config.target_level}")
79
+ logger.info(f" Confidence Threshold: {config.confidence_threshold}")
80
+ logger.info(f" Persistence Backend: {config.persistence_backend}")
81
+ logger.info(f" Metrics Enabled: {config.metrics_enabled}")
82
+ except (OSError, FileNotFoundError) as e:
83
+ # Config file not found or cannot be read
84
+ logger.error(f"Configuration file error: {e}")
85
+ logger.error(f"✗ Cannot read configuration file: {e}")
86
+ sys.exit(1)
87
+ except ValueError as e:
88
+ # Invalid configuration values
89
+ logger.error(f"Configuration validation failed: {e}")
90
+ logger.error(f"✗ Configuration invalid: {e}")
91
+ sys.exit(1)
92
+ except Exception as e:
93
+ # Unexpected errors during config validation
94
+ logger.exception(f"Unexpected error validating configuration: {e}")
95
+ logger.error(f"✗ Configuration invalid: {e}")
96
+ sys.exit(1)
@@ -0,0 +1,235 @@
1
+ """Status and health check commands for the CLI.
2
+
3
+ Copyright 2025 Smart-AI-Memory
4
+ Licensed under Fair Source License 0.9
5
+ """
6
+
7
+ import asyncio
8
+
9
+
10
+ def cmd_status(args):
11
+ """Session status assistant - prioritized project status report.
12
+
13
+ Collects and displays project status including patterns, git context,
14
+ and health metrics with priority scoring.
15
+
16
+ Args:
17
+ args: Namespace object from argparse with attributes:
18
+ - patterns_dir (str): Path to patterns directory (default: ./patterns).
19
+ - project_root (str): Project root directory (default: .).
20
+ - inactivity (int): Minutes of inactivity before showing status.
21
+ - full (bool): If True, show all items without limit.
22
+ - json (bool): If True, output as JSON format.
23
+ - select (int | None): Select specific item for action prompt.
24
+ - force (bool): If True, show status even with recent activity.
25
+
26
+ Returns:
27
+ None: Prints prioritized status report or JSON output.
28
+ """
29
+ from attune_llm.session_status import SessionStatusCollector
30
+
31
+ config = {"inactivity_minutes": args.inactivity}
32
+ collector = SessionStatusCollector(
33
+ patterns_dir=args.patterns_dir,
34
+ project_root=args.project_root,
35
+ config=config,
36
+ )
37
+
38
+ # Check if should show (unless forced)
39
+ if not args.force and not collector.should_show():
40
+ print("No status update needed (recent activity detected).")
41
+ print("Use --force to show status anyway.")
42
+ return
43
+
44
+ # Collect status
45
+ status = collector.collect()
46
+
47
+ # Handle selection
48
+ if args.select:
49
+ prompt = collector.get_action_prompt(status, args.select)
50
+ if prompt:
51
+ print(f"\nAction prompt for selection {args.select}:\n")
52
+ print(prompt)
53
+ else:
54
+ print(f"Invalid selection: {args.select}")
55
+ return
56
+
57
+ # Output
58
+ if args.json:
59
+ print(collector.format_json(status))
60
+ else:
61
+ max_items = None if args.full else 5
62
+ print()
63
+ print(collector.format_output(status, max_items=max_items))
64
+ print()
65
+
66
+ # Record interaction
67
+ collector.record_interaction()
68
+
69
+
70
+ def cmd_review(args):
71
+ """Pattern-based code review against historical bugs.
72
+
73
+ Note: This command has been deprecated. The underlying workflow module
74
+ has been removed. Use 'empathy workflow run bug-predict' instead.
75
+
76
+ Args:
77
+ args: Namespace object from argparse.
78
+
79
+ Returns:
80
+ None: Prints deprecation message.
81
+ """
82
+ print("⚠️ The 'review' command has been deprecated.")
83
+ print()
84
+ print("The CodeReviewWorkflow module has been removed.")
85
+ print("Please use one of these alternatives:")
86
+ print()
87
+ print(" empathy workflow run bug-predict # Scan for risky patterns")
88
+ print(" ruff check <files> # Fast linting")
89
+ print(" bandit -r <path> # Security scanning")
90
+ print()
91
+
92
+
93
+ def cmd_health(args):
94
+ """Code health assistant - run health checks and auto-fix issues.
95
+
96
+ Runs comprehensive health checks including linting, type checking,
97
+ and formatting with optional auto-fix capability.
98
+
99
+ Args:
100
+ args: Namespace object from argparse with attributes:
101
+ - check (str | None): Specific check to run (lint/type/format/test).
102
+ - deep (bool): If True, run comprehensive checks.
103
+ - fix (bool): If True, auto-fix issues where possible.
104
+ - threshold (str): Severity threshold for issues.
105
+ - project_root (str): Project root directory.
106
+ - patterns_dir (str): Path to patterns directory.
107
+ - details (bool): If True, show detailed issue list.
108
+ - compare (str | None): Compare against historical baseline.
109
+ - export (str | None): Export results to file.
110
+ - json (bool): If True, output as JSON format.
111
+
112
+ Returns:
113
+ None: Prints health check results and optionally fixes issues.
114
+ """
115
+ from attune_llm.code_health import (
116
+ AutoFixer,
117
+ CheckCategory,
118
+ HealthCheckRunner,
119
+ HealthTrendTracker,
120
+ format_health_output,
121
+ )
122
+
123
+ runner = HealthCheckRunner(
124
+ project_root=args.project_root,
125
+ )
126
+
127
+ # Determine what checks to run
128
+ if args.check:
129
+ # Run specific check
130
+ try:
131
+ category = CheckCategory(args.check)
132
+ report_future = runner.run_check(category)
133
+ result = asyncio.run(report_future)
134
+ # Create a minimal report with just this result
135
+ from attune_llm.code_health import HealthReport
136
+
137
+ report = HealthReport(project_root=args.project_root)
138
+ report.add_result(result)
139
+ except ValueError:
140
+ print(f"Unknown check category: {args.check}")
141
+ print(f"Available: {', '.join(c.value for c in CheckCategory)}")
142
+ return
143
+ elif args.deep:
144
+ # Run all checks
145
+ print("Running comprehensive health check...\n")
146
+ report = asyncio.run(runner.run_all())
147
+ else:
148
+ # Run quick checks (default)
149
+ report = asyncio.run(runner.run_quick())
150
+
151
+ # Handle fix mode
152
+ if args.fix:
153
+ fixer = AutoFixer()
154
+
155
+ if args.dry_run:
156
+ # Preview only
157
+ fixes = fixer.preview_fixes(report)
158
+ if fixes:
159
+ print("Would fix the following issues:\n")
160
+ for fix in fixes:
161
+ safe_indicator = " (safe)" if fix["safe"] else " (needs confirmation)"
162
+ print(f" [{fix['category']}] {fix['file']}")
163
+ print(f" {fix['issue']}")
164
+ print(f" Command: {fix['fix_command']}{safe_indicator}")
165
+ print()
166
+ else:
167
+ print("No auto-fixable issues found.")
168
+ return
169
+
170
+ # Apply fixes
171
+ if args.check:
172
+ try:
173
+ category = CheckCategory(args.check)
174
+ result = asyncio.run(fixer.fix_category(report, category))
175
+ except ValueError:
176
+ result = {"fixed": [], "skipped": [], "failed": []}
177
+ else:
178
+ result = asyncio.run(fixer.fix_all(report, interactive=args.interactive))
179
+
180
+ # Report fix results
181
+ if result["fixed"]:
182
+ print(f"✓ Fixed {len(result['fixed'])} issue(s)")
183
+ for fix in result["fixed"][:5]:
184
+ print(f" - {fix['file_path']}: {fix['message']}")
185
+ if len(result["fixed"]) > 5:
186
+ print(f" ... and {len(result['fixed']) - 5} more")
187
+
188
+ if result["skipped"]:
189
+ if args.interactive:
190
+ print(f"\n⚠ Skipped {len(result['skipped'])} issue(s) (could not auto-fix)")
191
+ else:
192
+ print(
193
+ f"\n⚠ Skipped {len(result['skipped'])} issue(s) (use --interactive to review)",
194
+ )
195
+
196
+ if result["failed"]:
197
+ print(f"\n✗ Failed to fix {len(result['failed'])} issue(s)")
198
+
199
+ return
200
+
201
+ # Handle trends
202
+ if args.trends:
203
+ tracker = HealthTrendTracker(project_root=args.project_root)
204
+ trends = tracker.get_trends(days=args.trends)
205
+
206
+ print(f"📈 Health Trends ({trends['period_days']} days)\n")
207
+ print(f"Average Score: {trends['average_score']}/100")
208
+ print(f"Trend: {trends['trend_direction']} ({trends['score_change']:+d})")
209
+
210
+ if trends["data_points"]:
211
+ print("\nRecent scores:")
212
+ for point in trends["data_points"][:7]:
213
+ print(f" {point['date']}: {point['score']}/100")
214
+
215
+ hotspots = tracker.identify_hotspots()
216
+ if hotspots:
217
+ print("\n🔥 Hotspots (files with recurring issues):")
218
+ for spot in hotspots[:5]:
219
+ print(f" {spot['file']}: {spot['issue_count']} issues")
220
+
221
+ return
222
+
223
+ # Output report
224
+ if args.json:
225
+ import json
226
+
227
+ print(json.dumps(report.to_dict(), indent=2, default=str))
228
+ else:
229
+ level = 3 if args.full else (2 if args.details else 1)
230
+ print(format_health_output(report, level=level))
231
+
232
+ # Record to trend history
233
+ if not args.check: # Only record full runs
234
+ tracker = HealthTrendTracker(project_root=args.project_root)
235
+ tracker.record_check(report)