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,622 @@
1
+ """Test Execution and Coverage Tracking Utilities for Tier 1 Automation.
2
+
3
+ Provides explicit opt-in utilities for tracking test executions and coverage metrics.
4
+ Use these functions when you want to track test/coverage data for Tier 1 monitoring.
5
+
6
+ Copyright 2025 Smart-AI-Memory
7
+ Licensed under Fair Source License 0.9
8
+ """
9
+
10
+ import logging
11
+ import shlex
12
+ import subprocess
13
+ import uuid
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ try:
19
+ import defusedxml.ElementTree as ET
20
+ except ImportError:
21
+ import xml.etree.ElementTree as ET # noqa: S405
22
+
23
+ # Import Element for type hints only (defusedxml doesn't expose it)
24
+ if TYPE_CHECKING:
25
+ from xml.etree.ElementTree import Element
26
+
27
+ from attune.models import (
28
+ CoverageRecord,
29
+ FileTestRecord,
30
+ TestExecutionRecord,
31
+ get_telemetry_store,
32
+ )
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def run_tests_with_tracking(
38
+ test_suite: str = "unit",
39
+ test_files: list[str] | None = None,
40
+ command: str | None = None,
41
+ workflow_id: str | None = None,
42
+ triggered_by: str = "manual",
43
+ ) -> TestExecutionRecord:
44
+ """Run tests with explicit tracking (opt-in for Tier 1 monitoring).
45
+
46
+ Args:
47
+ test_suite: Test suite name (unit, integration, e2e, all)
48
+ test_files: Specific test files to run (optional)
49
+ command: Custom test command (defaults to pytest)
50
+ workflow_id: Optional workflow ID to link this execution
51
+ triggered_by: Who/what triggered this (manual, workflow, ci, pre_commit)
52
+
53
+ Returns:
54
+ TestExecutionRecord with execution results
55
+
56
+ Example:
57
+ >>> from attune.workflows.test_runner import run_tests_with_tracking
58
+ >>> result = run_tests_with_tracking(
59
+ ... test_suite="unit",
60
+ ... test_files=["tests/unit/test_config.py"],
61
+ ... )
62
+ >>> print(f"Tests passed: {result.success}")
63
+
64
+ """
65
+ execution_id = f"test-{uuid.uuid4()}"
66
+ timestamp = datetime.utcnow().isoformat() + "Z"
67
+ started_at = datetime.utcnow()
68
+
69
+ # Build command
70
+ if command is None:
71
+ if test_files:
72
+ files_str = " ".join(test_files)
73
+ command = f"pytest {files_str} -v --tb=short"
74
+ else:
75
+ if test_suite == "all":
76
+ command = "pytest tests/ -v --tb=short"
77
+ else:
78
+ command = f"pytest tests/{test_suite}/ -v --tb=short"
79
+
80
+ # Determine working directory
81
+ working_directory = str(Path.cwd())
82
+
83
+ # Run tests
84
+ logger.info(f"Running tests: {command}")
85
+ try:
86
+ # Use shlex.split to safely parse command without shell=True
87
+ cmd_args = shlex.split(command)
88
+ result = subprocess.run(
89
+ cmd_args,
90
+ shell=False,
91
+ capture_output=True,
92
+ text=True,
93
+ timeout=600, # 10 minute timeout
94
+ )
95
+
96
+ # Parse pytest output for test counts
97
+ output = result.stdout + result.stderr
98
+ total_tests, passed, failed, skipped, errors = _parse_pytest_output(output)
99
+
100
+ success = result.returncode == 0
101
+ exit_code = result.returncode
102
+
103
+ # Parse failures from output
104
+ failed_tests = _parse_pytest_failures(output) if failed > 0 or errors > 0 else []
105
+
106
+ except subprocess.TimeoutExpired:
107
+ logger.error("Test execution timed out after 600 seconds")
108
+ total_tests, passed, failed, skipped, errors = 0, 0, 0, 0, 1
109
+ success = False
110
+ exit_code = 124 # Timeout exit code
111
+ failed_tests = [{"name": "timeout", "file": "unknown", "error": "Test execution timed out"}]
112
+
113
+ except Exception as e:
114
+ logger.error(f"Test execution failed: {e}")
115
+ total_tests, passed, failed, skipped, errors = 0, 0, 0, 0, 1
116
+ success = False
117
+ exit_code = 1
118
+ failed_tests = [{"name": "execution_error", "file": "unknown", "error": str(e)}]
119
+
120
+ # Calculate duration
121
+ completed_at = datetime.utcnow()
122
+ duration_seconds = (completed_at - started_at).total_seconds()
123
+
124
+ # Create test execution record
125
+ record = TestExecutionRecord(
126
+ execution_id=execution_id,
127
+ timestamp=timestamp,
128
+ test_suite=test_suite,
129
+ test_files=test_files or [],
130
+ triggered_by=triggered_by,
131
+ command=command,
132
+ working_directory=working_directory,
133
+ duration_seconds=duration_seconds,
134
+ total_tests=total_tests,
135
+ passed=passed,
136
+ failed=failed,
137
+ skipped=skipped,
138
+ errors=errors,
139
+ success=success,
140
+ exit_code=exit_code,
141
+ failed_tests=failed_tests,
142
+ workflow_id=workflow_id,
143
+ )
144
+
145
+ # Log to telemetry store
146
+ try:
147
+ store = get_telemetry_store()
148
+ store.log_test_execution(record)
149
+ logger.info(f"Test execution tracked: {execution_id}")
150
+ except Exception as e:
151
+ logger.warning(f"Failed to log test execution: {e}")
152
+
153
+ return record
154
+
155
+
156
+ def track_coverage(
157
+ coverage_file: str = "coverage.xml",
158
+ workflow_id: str | None = None,
159
+ ) -> CoverageRecord:
160
+ """Track test coverage from coverage.xml file (opt-in for Tier 1 monitoring).
161
+
162
+ Args:
163
+ coverage_file: Path to coverage.xml file
164
+ workflow_id: Optional workflow ID to link this record
165
+
166
+ Returns:
167
+ CoverageRecord with coverage metrics
168
+
169
+ Example:
170
+ >>> from attune.workflows.test_runner import track_coverage
171
+ >>> coverage = track_coverage("coverage.xml")
172
+ >>> print(f"Coverage: {coverage.overall_percentage:.1f}%")
173
+
174
+ """
175
+ record_id = f"cov-{uuid.uuid4()}"
176
+ timestamp = datetime.utcnow().isoformat() + "Z"
177
+
178
+ coverage_path = Path(coverage_file)
179
+ if not coverage_path.exists():
180
+ raise FileNotFoundError(f"Coverage file not found: {coverage_file}")
181
+
182
+ # Parse coverage.xml
183
+ try:
184
+ # Uses defusedxml when available (see imports), coverage.xml is from trusted pytest/coverage tools
185
+ tree = ET.parse(coverage_path) # nosec B314
186
+ root = tree.getroot()
187
+
188
+ # Get overall metrics
189
+ lines_total = int(root.attrib.get("lines-valid", 0))
190
+ lines_covered = int(root.attrib.get("lines-covered", 0))
191
+ branches_total = int(root.attrib.get("branches-valid", 0))
192
+ branches_covered = int(root.attrib.get("branches-covered", 0))
193
+
194
+ if lines_total > 0:
195
+ overall_percentage = (lines_covered / lines_total) * 100
196
+ else:
197
+ overall_percentage = 0.0
198
+
199
+ # Get previous coverage if available
200
+ previous_percentage = _get_previous_coverage()
201
+
202
+ # Determine trend
203
+ if previous_percentage is not None:
204
+ change = overall_percentage - previous_percentage
205
+ if change > 1.0:
206
+ trend = "improving"
207
+ elif change < -1.0:
208
+ trend = "declining"
209
+ else:
210
+ trend = "stable"
211
+ else:
212
+ trend = "stable"
213
+
214
+ # Analyze files
215
+ files_analyzed = _analyze_coverage_files(root)
216
+
217
+ record = CoverageRecord(
218
+ record_id=record_id,
219
+ timestamp=timestamp,
220
+ overall_percentage=overall_percentage,
221
+ lines_total=lines_total,
222
+ lines_covered=lines_covered,
223
+ branches_total=branches_total,
224
+ branches_covered=branches_covered,
225
+ files_total=files_analyzed["total"],
226
+ files_well_covered=files_analyzed["well_covered"],
227
+ files_critical=files_analyzed["critical"],
228
+ untested_files=files_analyzed["untested"],
229
+ critical_gaps=files_analyzed["gaps"],
230
+ previous_percentage=previous_percentage,
231
+ trend=trend,
232
+ coverage_format="xml",
233
+ coverage_file=str(coverage_path),
234
+ workflow_id=workflow_id,
235
+ )
236
+
237
+ # Log to telemetry store
238
+ try:
239
+ store = get_telemetry_store()
240
+ store.log_coverage(record)
241
+ logger.info(f"Coverage tracked: {record_id} ({overall_percentage:.1f}%)")
242
+ except Exception as e:
243
+ logger.warning(f"Failed to log coverage: {e}")
244
+
245
+ return record
246
+
247
+ except ET.ParseError as e:
248
+ raise ValueError(f"Invalid coverage.xml format: {e}")
249
+
250
+
251
+ # Helper functions
252
+
253
+
254
+ def _parse_pytest_output(output: str) -> tuple[int, int, int, int, int]:
255
+ """Parse pytest output for test counts.
256
+
257
+ Returns:
258
+ Tuple of (total_tests, passed, failed, skipped, errors)
259
+
260
+ """
261
+ import re
262
+
263
+ # Look for pytest summary line like "5 passed, 2 failed, 1 skipped in 1.23s"
264
+ match = re.search(r"(\d+)\s+passed", output)
265
+ passed = int(match.group(1)) if match else 0
266
+
267
+ match = re.search(r"(\d+)\s+failed", output)
268
+ failed = int(match.group(1)) if match else 0
269
+
270
+ match = re.search(r"(\d+)\s+skipped", output)
271
+ skipped = int(match.group(1)) if match else 0
272
+
273
+ match = re.search(r"(\d+)\s+error", output)
274
+ errors = int(match.group(1)) if match else 0
275
+
276
+ total_tests = passed + failed + skipped + errors
277
+
278
+ return total_tests, passed, failed, skipped, errors
279
+
280
+
281
+ def _parse_pytest_failures(output: str) -> list[dict[str, str]]:
282
+ """Parse pytest output for failure details.
283
+
284
+ Returns:
285
+ List of dicts with name, file, error, traceback
286
+
287
+ """
288
+ failures = []
289
+ lines = output.split("\n")
290
+
291
+ # Simple parser - looks for FAILED lines
292
+ for line in lines:
293
+ if "FAILED " in line:
294
+ parts = line.split("::")
295
+ if len(parts) >= 2:
296
+ file_path = parts[0].replace("FAILED ", "").strip()
297
+ test_name = parts[1].split()[0] if len(parts) > 1 else "unknown"
298
+
299
+ failures.append({"name": test_name, "file": file_path, "error": "Test failed"})
300
+
301
+ return failures[:10] # Limit to 10 failures
302
+
303
+
304
+ def _get_previous_coverage() -> float | None:
305
+ """Get previous coverage percentage from telemetry store.
306
+
307
+ Returns:
308
+ Previous coverage percentage or None
309
+
310
+ """
311
+ try:
312
+ store = get_telemetry_store()
313
+ records = store.get_coverage_history(limit=2)
314
+
315
+ if len(records) >= 2:
316
+ # Second-to-last record is the previous one
317
+ return records[-2].overall_percentage
318
+ elif len(records) == 1:
319
+ return records[0].overall_percentage
320
+ else:
321
+ return None
322
+
323
+ except Exception:
324
+ return None
325
+
326
+
327
+ def _analyze_coverage_files(root: "Element") -> dict[str, Any]:
328
+ """Analyze file-level coverage from XML.
329
+
330
+ Returns:
331
+ Dict with total, well_covered, critical, untested, gaps
332
+
333
+ """
334
+ files_total = 0
335
+ files_well_covered = 0 # >= 80%
336
+ files_critical = 0 # < 50%
337
+ untested_files = []
338
+ critical_gaps = []
339
+
340
+ for package in root.findall(".//package"):
341
+ for class_elem in package.findall("classes/class"):
342
+ files_total += 1
343
+ filename = class_elem.attrib.get("filename", "unknown")
344
+ line_rate = float(class_elem.attrib.get("line-rate", 0))
345
+ coverage_pct = line_rate * 100
346
+
347
+ if coverage_pct >= 80:
348
+ files_well_covered += 1
349
+ elif coverage_pct < 50:
350
+ files_critical += 1
351
+ critical_gaps.append(
352
+ {"file": filename, "coverage": coverage_pct, "priority": "high"}
353
+ )
354
+
355
+ if coverage_pct == 0:
356
+ untested_files.append(filename)
357
+
358
+ return {
359
+ "total": files_total,
360
+ "well_covered": files_well_covered,
361
+ "critical": files_critical,
362
+ "untested": untested_files[:10], # Limit to 10
363
+ "gaps": critical_gaps[:10], # Limit to 10
364
+ }
365
+
366
+
367
+ def track_file_tests(
368
+ source_file: str,
369
+ test_file: str | None = None,
370
+ workflow_id: str | None = None,
371
+ ) -> FileTestRecord:
372
+ """Track test execution for a specific source file.
373
+
374
+ Runs tests associated with a source file and creates a FileTestRecord.
375
+
376
+ Args:
377
+ source_file: Path to the source file to test
378
+ test_file: Path to the test file (auto-detected if not provided)
379
+ workflow_id: Optional workflow ID to link this execution
380
+
381
+ Returns:
382
+ FileTestRecord with per-file test results
383
+
384
+ Example:
385
+ >>> from attune.workflows.test_runner import track_file_tests
386
+ >>> result = track_file_tests("src/attune/config.py")
387
+ >>> print(f"Tests for config.py: {result.last_test_result}")
388
+ """
389
+ timestamp = datetime.utcnow().isoformat() + "Z"
390
+ started_at = datetime.utcnow()
391
+
392
+ source_path = Path(source_file)
393
+
394
+ # Auto-detect test file if not provided
395
+ if test_file is None:
396
+ test_file = _find_test_file(source_file)
397
+
398
+ # Get file modification times
399
+ source_modified_at = None
400
+ tests_modified_at = None
401
+
402
+ if source_path.exists():
403
+ source_modified_at = datetime.fromtimestamp(source_path.stat().st_mtime).isoformat() + "Z"
404
+
405
+ if test_file:
406
+ test_path = Path(test_file)
407
+ if test_path.exists():
408
+ tests_modified_at = datetime.fromtimestamp(test_path.stat().st_mtime).isoformat() + "Z"
409
+
410
+ # Check if we have tests to run
411
+ if test_file is None or not Path(test_file).exists():
412
+ # No tests found for this file
413
+ record = FileTestRecord(
414
+ file_path=source_file,
415
+ timestamp=timestamp,
416
+ last_test_result="no_tests",
417
+ test_count=0,
418
+ test_file_path=test_file,
419
+ source_modified_at=source_modified_at,
420
+ tests_modified_at=tests_modified_at,
421
+ is_stale=False,
422
+ workflow_id=workflow_id,
423
+ )
424
+ _log_file_test(record)
425
+ return record
426
+
427
+ # Run pytest for this specific test file
428
+ command = f"pytest {test_file} -v --tb=short"
429
+
430
+ logger.info(f"Running tests for {source_file}: {command}")
431
+ try:
432
+ cmd_args = shlex.split(command)
433
+ result = subprocess.run(
434
+ cmd_args,
435
+ shell=False,
436
+ capture_output=True,
437
+ text=True,
438
+ timeout=300, # 5 minute timeout per file
439
+ )
440
+
441
+ output = result.stdout + result.stderr
442
+ total_tests, passed, failed, skipped, errors = _parse_pytest_output(output)
443
+
444
+ # Determine result status
445
+ if result.returncode == 0:
446
+ last_test_result = "passed"
447
+ elif failed > 0:
448
+ last_test_result = "failed"
449
+ elif errors > 0:
450
+ last_test_result = "error"
451
+ elif skipped == total_tests:
452
+ last_test_result = "skipped"
453
+ else:
454
+ last_test_result = "failed"
455
+
456
+ failed_tests = _parse_pytest_failures(output) if failed > 0 or errors > 0 else []
457
+ execution_id = f"file-{uuid.uuid4()}"
458
+
459
+ except subprocess.TimeoutExpired:
460
+ logger.error(f"Test execution timed out for {source_file}")
461
+ total_tests, passed, failed, skipped, errors = 0, 0, 0, 0, 1
462
+ last_test_result = "error"
463
+ failed_tests = [{"name": "timeout", "file": test_file, "error": "Timed out"}]
464
+ execution_id = f"file-{uuid.uuid4()}"
465
+
466
+ except Exception as e:
467
+ logger.error(f"Test execution failed for {source_file}: {e}")
468
+ total_tests, passed, failed, skipped, errors = 0, 0, 0, 0, 1
469
+ last_test_result = "error"
470
+ failed_tests = [{"name": "execution_error", "file": test_file, "error": str(e)}]
471
+ execution_id = f"file-{uuid.uuid4()}"
472
+
473
+ # Calculate duration
474
+ completed_at = datetime.utcnow()
475
+ duration_seconds = (completed_at - started_at).total_seconds()
476
+
477
+ # Check staleness (source modified after tests last modified)
478
+ is_stale = False
479
+ if source_modified_at and tests_modified_at:
480
+ is_stale = source_modified_at > tests_modified_at
481
+
482
+ record = FileTestRecord(
483
+ file_path=source_file,
484
+ timestamp=timestamp,
485
+ last_test_result=last_test_result,
486
+ test_count=total_tests,
487
+ passed=passed,
488
+ failed=failed,
489
+ skipped=skipped,
490
+ errors=errors,
491
+ duration_seconds=duration_seconds,
492
+ test_file_path=test_file,
493
+ failed_tests=failed_tests,
494
+ source_modified_at=source_modified_at,
495
+ tests_modified_at=tests_modified_at,
496
+ is_stale=is_stale,
497
+ execution_id=execution_id,
498
+ workflow_id=workflow_id,
499
+ )
500
+
501
+ _log_file_test(record)
502
+ return record
503
+
504
+
505
+ def get_file_test_status(file_path: str) -> FileTestRecord | None:
506
+ """Get the latest test status for a specific file.
507
+
508
+ Args:
509
+ file_path: Path to the source file
510
+
511
+ Returns:
512
+ Latest FileTestRecord or None if no tests recorded
513
+ """
514
+ store = get_telemetry_store()
515
+ return store.get_latest_file_test(file_path)
516
+
517
+
518
+ def get_files_needing_tests(
519
+ stale_only: bool = False,
520
+ failed_only: bool = False,
521
+ ) -> list[FileTestRecord]:
522
+ """Get files that need test attention.
523
+
524
+ Args:
525
+ stale_only: Only return files with stale tests
526
+ failed_only: Only return files with failed tests
527
+
528
+ Returns:
529
+ List of FileTestRecord for files needing attention
530
+ """
531
+ store = get_telemetry_store()
532
+ return store.get_files_needing_tests(stale_only=stale_only, failed_only=failed_only)
533
+
534
+
535
+ def _find_test_file(source_file: str) -> str | None:
536
+ """Find the test file for a given source file.
537
+
538
+ Uses comprehensive search to find test files:
539
+ 1. First checks explicit patterns based on source file location
540
+ 2. Falls back to glob search for test_{filename}.py anywhere in tests/
541
+
542
+ Args:
543
+ source_file: Path to the source file
544
+
545
+ Returns:
546
+ Path to test file or None if not found
547
+ """
548
+ source_path = Path(source_file)
549
+ filename = source_path.stem
550
+ parent = source_path.parent
551
+
552
+ # Skip __init__.py - rarely have dedicated tests
553
+ if filename == "__init__":
554
+ return None
555
+
556
+ # Build list of explicit patterns to check first (most specific)
557
+ patterns = []
558
+
559
+ # Extract module info from source path
560
+ # e.g., src/attune/models/registry.py -> module="models"
561
+ module_name = None
562
+ if "src" in source_path.parts:
563
+ try:
564
+ src_idx = source_path.parts.index("src")
565
+ rel_parts = source_path.parts[src_idx + 1 : -1] # Exclude src and filename
566
+ if len(rel_parts) >= 2:
567
+ # e.g., ('attune', 'models') -> module_name = 'models'
568
+ module_name = rel_parts[-1]
569
+ except (ValueError, IndexError):
570
+ pass
571
+
572
+ # Priority 1: Module-specific test directory
573
+ # e.g., src/attune/models/registry.py -> tests/unit/models/test_registry.py
574
+ if module_name:
575
+ patterns.extend(
576
+ [
577
+ Path("tests") / "unit" / module_name / f"test_{filename}.py",
578
+ Path("tests") / module_name / f"test_{filename}.py",
579
+ Path("tests") / "integration" / module_name / f"test_{filename}.py",
580
+ ]
581
+ )
582
+
583
+ # Priority 2: Standard locations
584
+ patterns.extend(
585
+ [
586
+ Path("tests") / "unit" / f"test_{filename}.py",
587
+ Path("tests") / f"test_{filename}.py",
588
+ Path("tests") / "integration" / f"test_{filename}.py",
589
+ parent / f"test_{filename}.py",
590
+ ]
591
+ )
592
+
593
+ # Check explicit patterns first
594
+ for pattern in patterns:
595
+ if pattern.exists():
596
+ return str(pattern)
597
+
598
+ # Priority 3: Glob search - find test_{filename}.py anywhere in tests/
599
+ tests_dir = Path("tests")
600
+ if tests_dir.exists():
601
+ # Search for exact match first
602
+ matches = list(tests_dir.rglob(f"test_{filename}.py"))
603
+ if matches:
604
+ # Return the first match (preferring shorter paths)
605
+ matches.sort(key=lambda p: len(p.parts))
606
+ return str(matches[0])
607
+
608
+ return None
609
+
610
+
611
+ def _log_file_test(record: FileTestRecord) -> None:
612
+ """Log a FileTestRecord to the telemetry store.
613
+
614
+ Args:
615
+ record: FileTestRecord to log
616
+ """
617
+ try:
618
+ store = get_telemetry_store()
619
+ store.log_file_test(record)
620
+ logger.info(f"File test tracked: {record.file_path} ({record.last_test_result})")
621
+ except Exception as e:
622
+ logger.warning(f"Failed to log file test: {e}")