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,627 @@
1
+ """Test Maintenance Workflow - Automatic Test Lifecycle Management
2
+
3
+ Integrates with Project Index to:
4
+ - Track files requiring tests
5
+ - Detect when tests become stale
6
+ - Generate test plans based on file events
7
+ - Execute automatic test generation
8
+ - Report on test health
9
+
10
+ Key events handled:
11
+ - File created: Check if needs tests, queue for generation
12
+ - File modified: Check if tests need updating
13
+ - File deleted: Mark associated tests as orphaned
14
+
15
+ Copyright 2025 Smart AI Memory, LLC
16
+ Licensed under Fair Source 0.9
17
+ """
18
+
19
+ import heapq
20
+ import logging
21
+ from dataclasses import dataclass, field
22
+ from datetime import datetime
23
+ from enum import Enum
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ from ..project_index import FileRecord, ProjectIndex
28
+ from ..project_index.reports import ReportGenerator
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class TestAction(str, Enum):
34
+ """Actions that can be taken for test management."""
35
+
36
+ CREATE = "create" # Create new tests
37
+ UPDATE = "update" # Update existing tests
38
+ REVIEW = "review" # Review and possibly regenerate
39
+ DELETE = "delete" # Delete orphaned tests
40
+ SKIP = "skip" # No action needed
41
+ MANUAL = "manual" # Requires manual intervention
42
+
43
+
44
+ class TestPriority(str, Enum):
45
+ """Priority levels for test actions."""
46
+
47
+ CRITICAL = "critical" # High-impact files, blocking
48
+ HIGH = "high" # Important files
49
+ MEDIUM = "medium" # Standard priority
50
+ LOW = "low" # Nice to have
51
+ DEFERRED = "deferred" # Can wait
52
+
53
+
54
+ @dataclass
55
+ class TestPlanItem:
56
+ """A single item in a test maintenance plan."""
57
+
58
+ file_path: str
59
+ action: TestAction
60
+ priority: TestPriority
61
+ reason: str
62
+ test_file_path: str | None = None
63
+ estimated_effort: str = "unknown"
64
+ auto_executable: bool = True
65
+ metadata: dict[str, Any] = field(default_factory=dict)
66
+
67
+ def to_dict(self) -> dict[str, Any]:
68
+ return {
69
+ "file_path": self.file_path,
70
+ "action": self.action.value,
71
+ "priority": self.priority.value,
72
+ "reason": self.reason,
73
+ "test_file_path": self.test_file_path,
74
+ "estimated_effort": self.estimated_effort,
75
+ "auto_executable": self.auto_executable,
76
+ "metadata": self.metadata,
77
+ }
78
+
79
+
80
+ @dataclass
81
+ class TestMaintenancePlan:
82
+ """Complete test maintenance plan for a project."""
83
+
84
+ generated_at: datetime = field(default_factory=datetime.now)
85
+ items: list[TestPlanItem] = field(default_factory=list)
86
+ summary: dict[str, Any] = field(default_factory=dict)
87
+ options: list[dict[str, Any]] = field(default_factory=list)
88
+
89
+ def to_dict(self) -> dict[str, Any]:
90
+ return {
91
+ "generated_at": self.generated_at.isoformat(),
92
+ "items": [item.to_dict() for item in self.items],
93
+ "summary": self.summary,
94
+ "options": self.options,
95
+ }
96
+
97
+ def get_items_by_action(self, action: TestAction) -> list[TestPlanItem]:
98
+ return [item for item in self.items if item.action == action]
99
+
100
+ def get_items_by_priority(self, priority: TestPriority) -> list[TestPlanItem]:
101
+ return [item for item in self.items if item.priority == priority]
102
+
103
+ def get_auto_executable_items(self) -> list[TestPlanItem]:
104
+ return [item for item in self.items if item.auto_executable]
105
+
106
+
107
+ class TestMaintenanceWorkflow:
108
+ """Workflow for automatic test lifecycle management.
109
+
110
+ Integrates with Project Index to track and manage tests.
111
+ Can run automatically on file events or manually on demand.
112
+
113
+ Modes:
114
+ - analyze: Generate plan without executing
115
+ - execute: Execute plan items (with confirmation)
116
+ - auto: Automatically execute auto_executable items
117
+ - report: Generate detailed test health report
118
+ """
119
+
120
+ def __init__(self, project_root: str, index: ProjectIndex | None = None):
121
+ self.name = "test_maintenance"
122
+ self.description = "Automatic test lifecycle management"
123
+ self.project_root = Path(project_root)
124
+ self.index = index or ProjectIndex(str(project_root))
125
+ self._ensure_index_loaded()
126
+
127
+ def _ensure_index_loaded(self) -> None:
128
+ """Ensure index is loaded, refresh if needed."""
129
+ if not self.index.load():
130
+ logger.info("Index not found, refreshing...")
131
+ self.index.refresh()
132
+
133
+ async def run(self, context: dict[str, Any]) -> dict[str, Any]:
134
+ """Run the test maintenance workflow.
135
+
136
+ Context options:
137
+ mode: "analyze" | "execute" | "auto" | "report"
138
+ changed_files: List of files that changed (for event-driven)
139
+ max_items: Maximum items to process (default: 20)
140
+ priority_filter: Only process items of this priority or higher
141
+ dry_run: If True, don't actually execute (default: False)
142
+ """
143
+ mode = context.get("mode", "analyze")
144
+ changed_files = context.get("changed_files", [])
145
+ max_items = context.get("max_items", 20)
146
+ dry_run = context.get("dry_run", False)
147
+
148
+ # Refresh index if files changed
149
+ if changed_files:
150
+ self.index.refresh()
151
+
152
+ # Generate the plan
153
+ plan = self._generate_plan(changed_files, max_items)
154
+
155
+ result = {
156
+ "workflow": self.name,
157
+ "mode": mode,
158
+ "generated_at": datetime.now().isoformat(),
159
+ "plan": plan.to_dict(),
160
+ }
161
+
162
+ if mode == "analyze":
163
+ # Just return the plan
164
+ result["status"] = "plan_generated"
165
+ result["message"] = f"Generated plan with {len(plan.items)} items"
166
+
167
+ elif mode == "execute":
168
+ if dry_run:
169
+ result["status"] = "dry_run"
170
+ result["message"] = f"Would execute {len(plan.items)} items"
171
+ else:
172
+ execution_result = await self._execute_plan(plan, auto_only=False)
173
+ result["execution"] = execution_result
174
+ result["status"] = "executed"
175
+
176
+ elif mode == "auto":
177
+ auto_items = plan.get_auto_executable_items()
178
+ if dry_run:
179
+ result["status"] = "dry_run"
180
+ result["message"] = f"Would auto-execute {len(auto_items)} items"
181
+ else:
182
+ execution_result = await self._execute_plan(plan, auto_only=True)
183
+ result["execution"] = execution_result
184
+ result["status"] = "auto_executed"
185
+
186
+ elif mode == "report":
187
+ report = self._generate_report()
188
+ result["report"] = report
189
+ result["status"] = "report_generated"
190
+
191
+ return result
192
+
193
+ def _generate_plan(
194
+ self,
195
+ changed_files: list[str],
196
+ max_items: int,
197
+ ) -> TestMaintenancePlan:
198
+ """Generate a test maintenance plan."""
199
+ plan = TestMaintenancePlan()
200
+ items: list[TestPlanItem] = []
201
+
202
+ # If specific files changed, prioritize them
203
+ if changed_files:
204
+ for file_path in changed_files:
205
+ record = self.index.get_file(file_path)
206
+ if record:
207
+ item = self._create_plan_item_for_file(record, event="modified")
208
+ if item and item.action != TestAction.SKIP:
209
+ items.append(item)
210
+
211
+ # Add files needing tests (not in changed_files)
212
+ changed_set = set(changed_files)
213
+ for record in self.index.get_files_needing_tests():
214
+ if record.path not in changed_set:
215
+ item = self._create_plan_item_for_file(record, event="missing_tests")
216
+ if item and item.action != TestAction.SKIP:
217
+ items.append(item)
218
+
219
+ # Add stale test files
220
+ for record in self.index.get_stale_files():
221
+ if record.path not in changed_set:
222
+ item = self._create_plan_item_for_file(record, event="stale")
223
+ if item and item.action != TestAction.SKIP:
224
+ items.append(item)
225
+
226
+ # Sort by priority
227
+ priority_order = {
228
+ TestPriority.CRITICAL: 0,
229
+ TestPriority.HIGH: 1,
230
+ TestPriority.MEDIUM: 2,
231
+ TestPriority.LOW: 3,
232
+ TestPriority.DEFERRED: 4,
233
+ }
234
+
235
+ def get_sort_key(item: TestPlanItem) -> tuple[int, float]:
236
+ file_rec = self.index.get_file(item.file_path)
237
+ impact = float(-file_rec.impact_score) if file_rec else 0.0
238
+ return (priority_order[item.priority], impact)
239
+
240
+ items.sort(key=get_sort_key)
241
+
242
+ # Limit items
243
+ plan.items = items[:max_items]
244
+
245
+ # Generate summary
246
+ plan.summary = {
247
+ "total_items": len(items),
248
+ "shown_items": len(plan.items),
249
+ "by_action": {
250
+ action.value: len([i for i in items if i.action == action]) for action in TestAction
251
+ },
252
+ "by_priority": {
253
+ priority.value: len([i for i in items if i.priority == priority])
254
+ for priority in TestPriority
255
+ },
256
+ "auto_executable": len([i for i in items if i.auto_executable]),
257
+ "manual_required": len([i for i in items if not i.auto_executable]),
258
+ }
259
+
260
+ # Generate options for the user
261
+ plan.options = self._generate_options(plan)
262
+
263
+ return plan
264
+
265
+ def _create_plan_item_for_file(
266
+ self,
267
+ record: FileRecord,
268
+ event: str,
269
+ ) -> TestPlanItem | None:
270
+ """Create a plan item for a specific file."""
271
+ # Determine action based on event and file state
272
+ if event == "missing_tests":
273
+ action = TestAction.CREATE
274
+ reason = "File requires tests but none exist"
275
+ elif event == "stale":
276
+ action = TestAction.UPDATE
277
+ reason = f"Tests are {record.staleness_days} days stale"
278
+ elif event == "modified":
279
+ if record.tests_exist:
280
+ action = TestAction.REVIEW
281
+ reason = "Source file modified, tests may need update"
282
+ else:
283
+ action = TestAction.CREATE
284
+ reason = "Modified file needs tests"
285
+ elif event == "deleted":
286
+ action = TestAction.DELETE
287
+ reason = "Source file deleted, tests may be orphaned"
288
+ else:
289
+ action = TestAction.SKIP
290
+ reason = "No action needed"
291
+
292
+ # Determine priority based on impact score
293
+ if record.impact_score >= 10.0:
294
+ priority = TestPriority.CRITICAL
295
+ elif record.impact_score >= 5.0:
296
+ priority = TestPriority.HIGH
297
+ elif record.impact_score >= 2.0:
298
+ priority = TestPriority.MEDIUM
299
+ else:
300
+ priority = TestPriority.LOW
301
+
302
+ # Estimate effort
303
+ if record.lines_of_code < 50:
304
+ effort = "small (< 1 hour)"
305
+ elif record.lines_of_code < 200:
306
+ effort = "medium (1-2 hours)"
307
+ else:
308
+ effort = "large (2+ hours)"
309
+
310
+ # Determine if auto-executable
311
+ auto_executable = (
312
+ action in [TestAction.CREATE, TestAction.UPDATE]
313
+ and record.language == "python"
314
+ and record.lines_of_code < 500
315
+ )
316
+
317
+ return TestPlanItem(
318
+ file_path=record.path,
319
+ action=action,
320
+ priority=priority,
321
+ reason=reason,
322
+ test_file_path=record.test_file_path,
323
+ estimated_effort=effort,
324
+ auto_executable=auto_executable,
325
+ metadata={
326
+ "lines_of_code": record.lines_of_code,
327
+ "impact_score": record.impact_score,
328
+ "language": record.language,
329
+ "complexity": record.complexity_score,
330
+ },
331
+ )
332
+
333
+ def _generate_options(self, plan: TestMaintenancePlan) -> list[dict[str, Any]]:
334
+ """Generate execution options for the user."""
335
+ options = []
336
+
337
+ # Option 1: Execute all auto-executable
338
+ auto_count = len(plan.get_auto_executable_items())
339
+ if auto_count > 0:
340
+ options.append(
341
+ {
342
+ "id": "auto_all",
343
+ "name": "Auto-execute all",
344
+ "description": f"Automatically generate/update tests for {auto_count} files",
345
+ "item_count": auto_count,
346
+ "estimated_time": f"{auto_count * 5}-{auto_count * 15} minutes",
347
+ "command": "python -m attune.workflows.test_maintenance auto",
348
+ },
349
+ )
350
+
351
+ # Option 2: Critical only
352
+ critical_count = len(plan.get_items_by_priority(TestPriority.CRITICAL))
353
+ if critical_count > 0:
354
+ options.append(
355
+ {
356
+ "id": "critical_only",
357
+ "name": "Critical files only",
358
+ "description": f"Focus on {critical_count} critical high-impact files",
359
+ "item_count": critical_count,
360
+ "estimated_time": f"{critical_count * 10}-{critical_count * 20} minutes",
361
+ "command": "python -m attune.workflows.test_maintenance execute --priority critical",
362
+ },
363
+ )
364
+
365
+ # Option 3: Create new tests only
366
+ create_count = len(plan.get_items_by_action(TestAction.CREATE))
367
+ if create_count > 0:
368
+ options.append(
369
+ {
370
+ "id": "create_only",
371
+ "name": "Create new tests only",
372
+ "description": f"Generate tests for {create_count} files without tests",
373
+ "item_count": create_count,
374
+ "estimated_time": f"{create_count * 10}-{create_count * 20} minutes",
375
+ "command": "python -m attune.workflows.test_maintenance execute --action create",
376
+ },
377
+ )
378
+
379
+ # Option 4: Update stale tests only
380
+ update_count = len(plan.get_items_by_action(TestAction.UPDATE))
381
+ if update_count > 0:
382
+ options.append(
383
+ {
384
+ "id": "update_stale",
385
+ "name": "Update stale tests",
386
+ "description": f"Update {update_count} stale test files",
387
+ "item_count": update_count,
388
+ "estimated_time": f"{update_count * 5}-{update_count * 10} minutes",
389
+ "command": "python -m attune.workflows.test_maintenance execute --action update",
390
+ },
391
+ )
392
+
393
+ # Option 5: Manual review
394
+ options.append(
395
+ {
396
+ "id": "manual_review",
397
+ "name": "Manual review",
398
+ "description": "Review the plan and select specific items",
399
+ "item_count": len(plan.items),
400
+ "command": "python -m attune.workflows.test_maintenance analyze --json",
401
+ },
402
+ )
403
+
404
+ return options
405
+
406
+ async def _execute_plan(
407
+ self,
408
+ plan: TestMaintenancePlan,
409
+ auto_only: bool = False,
410
+ ) -> dict[str, Any]:
411
+ """Execute items in the plan."""
412
+ items_to_execute = plan.get_auto_executable_items() if auto_only else plan.items
413
+
414
+ # Use typed variables for proper type inference
415
+ succeeded = 0
416
+ failed = 0
417
+ skipped = 0
418
+ details: list[dict[str, Any]] = []
419
+
420
+ for item in items_to_execute:
421
+ try:
422
+ if item.action == TestAction.CREATE:
423
+ success = await self._create_tests_for_file(item)
424
+ elif item.action == TestAction.UPDATE:
425
+ success = await self._update_tests_for_file(item)
426
+ elif item.action == TestAction.REVIEW:
427
+ success = await self._review_tests_for_file(item)
428
+ elif item.action == TestAction.DELETE:
429
+ success = await self._delete_orphaned_tests(item)
430
+ else:
431
+ success = False
432
+ skipped += 1
433
+ continue
434
+
435
+ if success:
436
+ succeeded += 1
437
+ # Update index
438
+ self.index.update_file(
439
+ item.file_path,
440
+ tests_exist=True,
441
+ tests_last_modified=datetime.now(),
442
+ is_stale=False,
443
+ staleness_days=0,
444
+ )
445
+ else:
446
+ failed += 1
447
+
448
+ details.append(
449
+ {
450
+ "file": item.file_path,
451
+ "action": item.action.value,
452
+ "success": success,
453
+ },
454
+ )
455
+
456
+ except Exception as e:
457
+ logger.error(f"Error processing {item.file_path}: {e}")
458
+ failed += 1
459
+ details.append(
460
+ {
461
+ "file": item.file_path,
462
+ "action": item.action.value,
463
+ "success": False,
464
+ "error": str(e),
465
+ },
466
+ )
467
+
468
+ return {
469
+ "total": len(items_to_execute),
470
+ "succeeded": succeeded,
471
+ "failed": failed,
472
+ "skipped": skipped,
473
+ "details": details,
474
+ }
475
+
476
+ async def _create_tests_for_file(self, item: TestPlanItem) -> bool:
477
+ """Create tests for a file using test-gen workflow."""
478
+ # This would integrate with the test-gen workflow
479
+ # For now, return True as placeholder
480
+ logger.info(f"Would create tests for: {item.file_path}")
481
+ return True
482
+
483
+ async def _update_tests_for_file(self, item: TestPlanItem) -> bool:
484
+ """Update existing tests for a file."""
485
+ logger.info(f"Would update tests for: {item.file_path}")
486
+ return True
487
+
488
+ async def _review_tests_for_file(self, item: TestPlanItem) -> bool:
489
+ """Review and possibly regenerate tests."""
490
+ logger.info(f"Would review tests for: {item.file_path}")
491
+ return True
492
+
493
+ async def _delete_orphaned_tests(self, item: TestPlanItem) -> bool:
494
+ """Delete orphaned test files."""
495
+ logger.info(f"Would delete orphaned tests for: {item.file_path}")
496
+ return True
497
+
498
+ def _generate_report(self) -> dict[str, Any]:
499
+ """Generate detailed test health report."""
500
+ generator = ReportGenerator(
501
+ self.index.get_summary(),
502
+ self.index.get_all_files(),
503
+ )
504
+
505
+ return {
506
+ "health": generator.health_report(),
507
+ "test_gap": generator.test_gap_report(),
508
+ "staleness": generator.staleness_report(),
509
+ "coverage": generator.coverage_report(),
510
+ }
511
+
512
+ # ===== Event Handlers =====
513
+
514
+ async def on_file_created(self, file_path: str) -> dict[str, Any]:
515
+ """Handle file creation event."""
516
+ self.index.refresh()
517
+ record = self.index.get_file(file_path)
518
+
519
+ if not record:
520
+ return {"status": "not_indexed", "file": file_path}
521
+
522
+ if record.test_requirement.value == "required":
523
+ item = self._create_plan_item_for_file(record, event="missing_tests")
524
+ return {
525
+ "status": "needs_tests",
526
+ "file": file_path,
527
+ "plan_item": item.to_dict() if item else None,
528
+ "message": f"New file {file_path} requires tests",
529
+ }
530
+
531
+ return {"status": "no_tests_required", "file": file_path}
532
+
533
+ async def on_file_modified(self, file_path: str) -> dict[str, Any]:
534
+ """Handle file modification event."""
535
+ record = self.index.get_file(file_path)
536
+
537
+ if not record:
538
+ self.index.refresh()
539
+ record = self.index.get_file(file_path)
540
+
541
+ if not record:
542
+ return {"status": "not_indexed", "file": file_path}
543
+
544
+ # Mark as potentially stale
545
+ if record.tests_exist and record.test_file_path:
546
+ self.index.update_file(
547
+ file_path,
548
+ last_modified=datetime.now(),
549
+ is_stale=True,
550
+ )
551
+
552
+ item = self._create_plan_item_for_file(record, event="modified")
553
+ return {
554
+ "status": "tests_may_need_update",
555
+ "file": file_path,
556
+ "test_file": record.test_file_path,
557
+ "plan_item": item.to_dict() if item else None,
558
+ }
559
+
560
+ if record.test_requirement.value == "required":
561
+ item = self._create_plan_item_for_file(record, event="modified")
562
+ return {
563
+ "status": "needs_tests",
564
+ "file": file_path,
565
+ "plan_item": item.to_dict() if item else None,
566
+ }
567
+
568
+ return {"status": "no_action_needed", "file": file_path}
569
+
570
+ async def on_file_deleted(self, file_path: str) -> dict[str, Any]:
571
+ """Handle file deletion event."""
572
+ record = self.index.get_file(file_path)
573
+
574
+ if record and record.test_file_path:
575
+ test_path = self.project_root / record.test_file_path
576
+ if test_path.exists():
577
+ return {
578
+ "status": "orphaned_tests",
579
+ "file": file_path,
580
+ "test_file": record.test_file_path,
581
+ "message": f"Tests at {record.test_file_path} may be orphaned",
582
+ "action": "review_for_deletion",
583
+ }
584
+
585
+ # Refresh index to remove deleted file
586
+ self.index.refresh()
587
+
588
+ return {"status": "file_removed", "file": file_path}
589
+
590
+ # ===== Convenience Methods =====
591
+
592
+ def get_files_needing_tests(self, limit: int = 20) -> list[dict[str, Any]]:
593
+ """Get files that need tests, prioritized by impact."""
594
+ files = self.index.get_files_needing_tests()
595
+ return [
596
+ {
597
+ "path": f.path,
598
+ "impact_score": f.impact_score,
599
+ "lines_of_code": f.lines_of_code,
600
+ "language": f.language,
601
+ }
602
+ for f in heapq.nlargest(limit, files, key=lambda x: x.impact_score)
603
+ ]
604
+
605
+ def get_stale_tests(self, limit: int = 20) -> list[dict[str, Any]]:
606
+ """Get files with stale tests."""
607
+ files = self.index.get_stale_files()
608
+ return [
609
+ {
610
+ "path": f.path,
611
+ "test_file": f.test_file_path,
612
+ "staleness_days": f.staleness_days,
613
+ }
614
+ for f in heapq.nlargest(limit, files, key=lambda x: x.staleness_days)
615
+ ]
616
+
617
+ def get_test_health_summary(self) -> dict[str, Any]:
618
+ """Get quick test health summary."""
619
+ summary = self.index.get_summary()
620
+ return {
621
+ "files_requiring_tests": summary.files_requiring_tests,
622
+ "files_with_tests": summary.files_with_tests,
623
+ "files_without_tests": summary.files_without_tests,
624
+ "coverage_avg": summary.test_coverage_avg,
625
+ "stale_count": summary.stale_file_count,
626
+ "test_to_code_ratio": summary.test_to_code_ratio,
627
+ }