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,526 @@
1
+ """Test Lifecycle Manager - Event-Driven Test Management
2
+
3
+ Monitors file changes and automatically manages test lifecycle:
4
+ - Tracks when source files are created/modified/deleted
5
+ - Queues test generation tasks
6
+ - Schedules maintenance runs
7
+ - Integrates with git hooks and CI/CD
8
+
9
+ Can operate in different modes:
10
+ - watch: Monitor file changes in real-time
11
+ - hook: Process git hook events
12
+ - scheduled: Run periodic maintenance
13
+ - manual: User-triggered operations
14
+
15
+ Copyright 2025 Smart AI Memory, LLC
16
+ Licensed under Fair Source 0.9
17
+ """
18
+
19
+ import asyncio
20
+ import json
21
+ import logging
22
+ from collections.abc import Callable
23
+ from dataclasses import dataclass, field
24
+ from datetime import datetime, timedelta
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ from ..project_index import ProjectIndex
29
+ from .test_maintenance import TestAction, TestMaintenanceWorkflow, TestPriority
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @dataclass
35
+ class TestTask:
36
+ """A queued test management task."""
37
+
38
+ id: str
39
+ file_path: str
40
+ action: TestAction
41
+ priority: TestPriority
42
+ created_at: datetime = field(default_factory=datetime.now)
43
+ scheduled_for: datetime | None = None
44
+ status: str = "pending" # pending, running, completed, failed
45
+ result: dict[str, Any] | None = None
46
+
47
+ def to_dict(self) -> dict[str, Any]:
48
+ return {
49
+ "id": self.id,
50
+ "file_path": self.file_path,
51
+ "action": self.action.value,
52
+ "priority": self.priority.value,
53
+ "created_at": self.created_at.isoformat(),
54
+ "scheduled_for": self.scheduled_for.isoformat() if self.scheduled_for else None,
55
+ "status": self.status,
56
+ "result": self.result,
57
+ }
58
+
59
+
60
+ class TestLifecycleManager:
61
+ """Manages the lifecycle of tests based on source file events.
62
+
63
+ Key responsibilities:
64
+ - Queue tasks when files change
65
+ - Process tasks based on priority
66
+ - Track task history
67
+ - Generate maintenance reports
68
+ - Integrate with CI/CD pipelines
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ project_root: str,
74
+ index: ProjectIndex | None = None,
75
+ auto_execute: bool = False,
76
+ queue_file: str | None = None,
77
+ ):
78
+ self.project_root = Path(project_root)
79
+ self.index = index or ProjectIndex(str(project_root))
80
+ self.auto_execute = auto_execute
81
+
82
+ # Task queue
83
+ self._queue: list[TestTask] = []
84
+ self._history: list[TestTask] = []
85
+ self._task_counter = 0
86
+
87
+ # Queue persistence
88
+ self._queue_file = (
89
+ Path(queue_file) if queue_file else self.project_root / ".empathy" / "test_queue.json"
90
+ )
91
+
92
+ # Callbacks
93
+ self._on_task_queued: list[Callable[[TestTask], None]] = []
94
+ self._on_task_completed: list[Callable[[TestTask], None]] = []
95
+
96
+ # Load existing queue
97
+ self._load_queue()
98
+
99
+ # ===== Event Handlers =====
100
+
101
+ async def on_file_created(self, file_path: str) -> TestTask | None:
102
+ """Handle file creation."""
103
+ # Refresh index to include new file
104
+ self.index.refresh()
105
+
106
+ record = self.index.get_file(file_path)
107
+ if not record:
108
+ return None
109
+
110
+ if record.test_requirement.value != "required":
111
+ logger.debug(f"File {file_path} does not require tests")
112
+ return None
113
+
114
+ # Queue test creation
115
+ task = self._create_task(
116
+ file_path=file_path,
117
+ action=TestAction.CREATE,
118
+ priority=self._determine_priority(record),
119
+ )
120
+
121
+ logger.info(f"Queued test creation for new file: {file_path}")
122
+ return task
123
+
124
+ async def on_file_modified(self, file_path: str) -> TestTask | None:
125
+ """Handle file modification."""
126
+ record = self.index.get_file(file_path)
127
+ if not record:
128
+ return None
129
+
130
+ if record.test_requirement.value != "required":
131
+ return None
132
+
133
+ # Update index
134
+ self.index.update_file(file_path, last_modified=datetime.now())
135
+
136
+ if record.tests_exist:
137
+ # Queue test review/update
138
+ task = self._create_task(
139
+ file_path=file_path,
140
+ action=TestAction.REVIEW,
141
+ priority=self._determine_priority(record),
142
+ )
143
+ logger.info(f"Queued test review for modified file: {file_path}")
144
+ else:
145
+ # Queue test creation
146
+ task = self._create_task(
147
+ file_path=file_path,
148
+ action=TestAction.CREATE,
149
+ priority=self._determine_priority(record),
150
+ )
151
+ logger.info(f"Queued test creation for modified file: {file_path}")
152
+
153
+ return task
154
+
155
+ async def on_file_deleted(self, file_path: str) -> TestTask | None:
156
+ """Handle file deletion."""
157
+ record = self.index.get_file(file_path)
158
+ if not record or not record.test_file_path:
159
+ return None
160
+
161
+ # Queue orphan check
162
+ task = self._create_task(
163
+ file_path=file_path,
164
+ action=TestAction.DELETE,
165
+ priority=TestPriority.LOW,
166
+ )
167
+
168
+ logger.info(f"Queued orphan test check for deleted file: {file_path}")
169
+
170
+ # Refresh index
171
+ self.index.refresh()
172
+
173
+ return task
174
+
175
+ async def on_files_changed(self, changed_files: list[str]) -> list[TestTask]:
176
+ """Handle multiple file changes (e.g., from git hook)."""
177
+ tasks = []
178
+
179
+ for file_path in changed_files:
180
+ # Determine if file exists
181
+ full_path = self.project_root / file_path
182
+ if full_path.exists():
183
+ # Could be create or modify - check if in index
184
+ if self.index.get_file(file_path):
185
+ task = await self.on_file_modified(file_path)
186
+ else:
187
+ task = await self.on_file_created(file_path)
188
+ else:
189
+ task = await self.on_file_deleted(file_path)
190
+
191
+ if task:
192
+ tasks.append(task)
193
+
194
+ return tasks
195
+
196
+ # ===== Task Management =====
197
+
198
+ def _create_task(
199
+ self,
200
+ file_path: str,
201
+ action: TestAction,
202
+ priority: TestPriority,
203
+ ) -> TestTask:
204
+ """Create and queue a new task."""
205
+ self._task_counter += 1
206
+
207
+ task = TestTask(
208
+ id=f"task_{self._task_counter}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
209
+ file_path=file_path,
210
+ action=action,
211
+ priority=priority,
212
+ )
213
+
214
+ # Check for duplicate
215
+ existing = self._find_pending_task(file_path, action)
216
+ if existing:
217
+ logger.debug(f"Task already queued for {file_path}")
218
+ return existing
219
+
220
+ self._queue.append(task)
221
+ self._save_queue()
222
+
223
+ # Notify callbacks
224
+ for callback in self._on_task_queued:
225
+ callback(task)
226
+
227
+ # Auto-execute if enabled
228
+ if self.auto_execute:
229
+ asyncio.create_task(self._execute_task(task))
230
+
231
+ return task
232
+
233
+ def _find_pending_task(self, file_path: str, action: TestAction) -> TestTask | None:
234
+ """Find existing pending task for file."""
235
+ for task in self._queue:
236
+ if task.file_path == file_path and task.action == action and task.status == "pending":
237
+ return task
238
+ return None
239
+
240
+ def _determine_priority(self, record) -> TestPriority:
241
+ """Determine task priority based on file impact."""
242
+ if record.impact_score >= 10.0:
243
+ return TestPriority.CRITICAL
244
+ if record.impact_score >= 5.0:
245
+ return TestPriority.HIGH
246
+ if record.impact_score >= 2.0:
247
+ return TestPriority.MEDIUM
248
+ return TestPriority.LOW
249
+
250
+ async def _execute_task(self, task: TestTask) -> bool:
251
+ """Execute a single task."""
252
+ task.status = "running"
253
+ self._save_queue()
254
+
255
+ try:
256
+ workflow = TestMaintenanceWorkflow(str(self.project_root), self.index)
257
+
258
+ # Create a mini-plan with just this task
259
+ result = await workflow.run(
260
+ {
261
+ "mode": "execute",
262
+ "changed_files": [task.file_path],
263
+ "max_items": 1,
264
+ },
265
+ )
266
+
267
+ task.status = "completed"
268
+ task.result = result
269
+
270
+ # Move to history
271
+ self._queue.remove(task)
272
+ self._history.append(task)
273
+
274
+ # Notify callbacks
275
+ for callback in self._on_task_completed:
276
+ callback(task)
277
+
278
+ return True
279
+
280
+ except Exception as e:
281
+ logger.error(f"Task {task.id} failed: {e}")
282
+ task.status = "failed"
283
+ task.result = {"error": str(e)}
284
+ return False
285
+
286
+ finally:
287
+ self._save_queue()
288
+
289
+ # ===== Queue Operations =====
290
+
291
+ def get_queue(self) -> list[dict[str, Any]]:
292
+ """Get current task queue."""
293
+ return [task.to_dict() for task in self._queue]
294
+
295
+ def get_pending_count(self) -> int:
296
+ """Get number of pending tasks."""
297
+ return len([t for t in self._queue if t.status == "pending"])
298
+
299
+ def get_queue_by_priority(self, priority: TestPriority) -> list[TestTask]:
300
+ """Get tasks by priority."""
301
+ return [t for t in self._queue if t.priority == priority and t.status == "pending"]
302
+
303
+ def clear_queue(self) -> int:
304
+ """Clear all pending tasks. Returns count of cleared tasks."""
305
+ count = len(self._queue)
306
+ self._queue.clear()
307
+ self._save_queue()
308
+ return count
309
+
310
+ async def process_queue(
311
+ self,
312
+ max_tasks: int = 10,
313
+ priority_filter: TestPriority | None = None,
314
+ ) -> dict[str, Any]:
315
+ """Process pending tasks in queue."""
316
+ tasks_to_process = [t for t in self._queue if t.status == "pending"]
317
+
318
+ if priority_filter:
319
+ priority_order = {
320
+ TestPriority.CRITICAL: 0,
321
+ TestPriority.HIGH: 1,
322
+ TestPriority.MEDIUM: 2,
323
+ TestPriority.LOW: 3,
324
+ TestPriority.DEFERRED: 4,
325
+ }
326
+ filter_level = priority_order[priority_filter]
327
+ tasks_to_process = [
328
+ t for t in tasks_to_process if priority_order[t.priority] <= filter_level
329
+ ]
330
+
331
+ # Sort by priority
332
+ tasks_to_process.sort(
333
+ key=lambda t: (
334
+ {"critical": 0, "high": 1, "medium": 2, "low": 3, "deferred": 4}[t.priority.value],
335
+ t.created_at,
336
+ ),
337
+ )
338
+
339
+ # Limit
340
+ tasks_to_process = tasks_to_process[:max_tasks]
341
+
342
+ # Use typed variables for proper type inference
343
+ processed = 0
344
+ succeeded = 0
345
+ failed = 0
346
+ details: list[dict] = []
347
+
348
+ for task in tasks_to_process:
349
+ processed += 1
350
+ success = await self._execute_task(task)
351
+ if success:
352
+ succeeded += 1
353
+ else:
354
+ failed += 1
355
+ details.append(task.to_dict())
356
+
357
+ return {
358
+ "processed": processed,
359
+ "succeeded": succeeded,
360
+ "failed": failed,
361
+ "details": details,
362
+ }
363
+
364
+ # ===== Persistence =====
365
+
366
+ def _save_queue(self) -> None:
367
+ """Save queue to file."""
368
+ try:
369
+ self._queue_file.parent.mkdir(parents=True, exist_ok=True)
370
+
371
+ data = {
372
+ "queue": [t.to_dict() for t in self._queue],
373
+ "history": [t.to_dict() for t in self._history[-100:]], # Keep last 100
374
+ "counter": self._task_counter,
375
+ "saved_at": datetime.now().isoformat(),
376
+ }
377
+
378
+ with open(self._queue_file, "w") as f:
379
+ json.dump(data, f, indent=2)
380
+
381
+ except Exception as e:
382
+ logger.error(f"Failed to save queue: {e}")
383
+
384
+ def _load_queue(self) -> None:
385
+ """Load queue from file."""
386
+ if not self._queue_file.exists():
387
+ return
388
+
389
+ try:
390
+ with open(self._queue_file) as f:
391
+ data = json.load(f)
392
+
393
+ self._task_counter = data.get("counter", 0)
394
+
395
+ # Restore queue
396
+ for task_data in data.get("queue", []):
397
+ task = TestTask(
398
+ id=task_data["id"],
399
+ file_path=task_data["file_path"],
400
+ action=TestAction(task_data["action"]),
401
+ priority=TestPriority(task_data["priority"]),
402
+ created_at=datetime.fromisoformat(task_data["created_at"]),
403
+ status=task_data["status"],
404
+ )
405
+ self._queue.append(task)
406
+
407
+ logger.info(f"Loaded {len(self._queue)} tasks from queue")
408
+
409
+ except Exception as e:
410
+ logger.error(f"Failed to load queue: {e}")
411
+
412
+ # ===== Scheduling =====
413
+
414
+ def schedule_maintenance(
415
+ self,
416
+ interval_hours: int = 24,
417
+ auto_execute: bool = False,
418
+ ) -> dict[str, Any]:
419
+ """Schedule periodic maintenance runs."""
420
+ next_run = datetime.now() + timedelta(hours=interval_hours)
421
+
422
+ return {
423
+ "scheduled": True,
424
+ "interval_hours": interval_hours,
425
+ "next_run": next_run.isoformat(),
426
+ "auto_execute": auto_execute,
427
+ "command": f"python -m attune.workflows.test_maintenance {'auto' if auto_execute else 'analyze'}",
428
+ }
429
+
430
+ async def run_maintenance(self, auto_execute: bool = False) -> dict[str, Any]:
431
+ """Run a full maintenance cycle."""
432
+ # Refresh index
433
+ self.index.refresh()
434
+
435
+ # Run workflow
436
+ workflow = TestMaintenanceWorkflow(str(self.project_root), self.index)
437
+ mode = "auto" if auto_execute else "analyze"
438
+
439
+ result = await workflow.run({"mode": mode})
440
+
441
+ return {
442
+ "maintenance_run": True,
443
+ "timestamp": datetime.now().isoformat(),
444
+ "result": result,
445
+ }
446
+
447
+ # ===== Git Hook Integration =====
448
+
449
+ async def process_git_pre_commit(self, staged_files: list[str]) -> dict[str, Any]:
450
+ """Process git pre-commit hook.
451
+
452
+ Returns warnings about files being committed without tests.
453
+ """
454
+ warnings = []
455
+ blocking = []
456
+
457
+ for file_path in staged_files:
458
+ record = self.index.get_file(file_path)
459
+ if not record:
460
+ continue
461
+
462
+ if record.test_requirement.value == "required" and not record.tests_exist:
463
+ if record.impact_score >= 5.0:
464
+ blocking.append(
465
+ {
466
+ "file": file_path,
467
+ "reason": f"High-impact file ({record.impact_score:.1f}) without tests",
468
+ },
469
+ )
470
+ else:
471
+ warnings.append(
472
+ {
473
+ "file": file_path,
474
+ "reason": "File requires tests but none exist",
475
+ },
476
+ )
477
+
478
+ return {
479
+ "hook": "pre-commit",
480
+ "staged_files": len(staged_files),
481
+ "blocking": blocking,
482
+ "warnings": warnings,
483
+ "allow_commit": len(blocking) == 0,
484
+ "message": (
485
+ f"Commit blocked: {len(blocking)} high-impact files need tests"
486
+ if blocking
487
+ else f"Commit allowed with {len(warnings)} test warnings"
488
+ ),
489
+ }
490
+
491
+ async def process_git_post_commit(self, changed_files: list[str]) -> dict[str, Any]:
492
+ """Process git post-commit hook."""
493
+ tasks = await self.on_files_changed(changed_files)
494
+
495
+ return {
496
+ "hook": "post-commit",
497
+ "changed_files": len(changed_files),
498
+ "tasks_queued": len(tasks),
499
+ "tasks": [t.to_dict() for t in tasks],
500
+ }
501
+
502
+ # ===== Callbacks =====
503
+
504
+ def on_task_queued(self, callback: Callable[[TestTask], None]) -> None:
505
+ """Register callback for when task is queued."""
506
+ self._on_task_queued.append(callback)
507
+
508
+ def on_task_completed(self, callback: Callable[[TestTask], None]) -> None:
509
+ """Register callback for when task completes."""
510
+ self._on_task_completed.append(callback)
511
+
512
+ # ===== Status =====
513
+
514
+ def get_status(self) -> dict[str, Any]:
515
+ """Get lifecycle manager status."""
516
+ return {
517
+ "queue_size": len(self._queue),
518
+ "pending": len([t for t in self._queue if t.status == "pending"]),
519
+ "running": len([t for t in self._queue if t.status == "running"]),
520
+ "auto_execute": self.auto_execute,
521
+ "by_priority": {
522
+ priority.value: len([t for t in self._queue if t.priority == priority])
523
+ for priority in TestPriority
524
+ },
525
+ "history_size": len(self._history),
526
+ }