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,433 @@
1
+ """Chain Executor
2
+
3
+ Executes workflow chains based on triggers and conditions.
4
+ Handles auto-chaining, approval workflows, and chain tracking.
5
+
6
+ Copyright 2025 Smart AI Memory, LLC
7
+ Licensed under Fair Source 0.9
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ from .workflow_registry import WorkflowRegistry
18
+
19
+
20
+ @dataclass
21
+ class ChainTrigger:
22
+ """A trigger condition for auto-chaining."""
23
+
24
+ condition: str
25
+ next_workflow: str
26
+ approval_required: bool = False
27
+ reason: str = ""
28
+
29
+
30
+ @dataclass
31
+ class ChainConfig:
32
+ """Configuration for a workflow's chaining behavior."""
33
+
34
+ workflow_name: str
35
+ auto_chain: bool = True
36
+ description: str = ""
37
+ triggers: list[ChainTrigger] = field(default_factory=list)
38
+
39
+
40
+ @dataclass
41
+ class ChainStep:
42
+ """A step in an executed chain."""
43
+
44
+ workflow_name: str
45
+ triggered_by: str # condition or manual
46
+ approval_required: bool
47
+ approved: bool | None = None # None = pending
48
+ result: dict[str, Any] = field(default_factory=dict)
49
+ started_at: datetime | None = None
50
+ completed_at: datetime | None = None
51
+
52
+
53
+ @dataclass
54
+ class ChainExecution:
55
+ """Record of a chain execution."""
56
+
57
+ chain_id: str
58
+ initial_workflow: str
59
+ steps: list[ChainStep] = field(default_factory=list)
60
+ started_at: datetime = field(default_factory=datetime.now)
61
+ completed_at: datetime | None = None
62
+ status: str = "running" # running, completed, waiting_approval, failed
63
+ current_step: int = 0
64
+
65
+
66
+ class ChainExecutor:
67
+ """Executes workflow chains based on configuration and results.
68
+
69
+ Usage:
70
+ executor = ChainExecutor()
71
+
72
+ # Check for triggered chains after a workflow run
73
+ result = {"high_severity_count": 5, "vulnerability_type": "injection"}
74
+ next_steps = executor.get_triggered_chains("security-audit", result)
75
+
76
+ # Execute a chain template
77
+ execution = await executor.execute_template("full-security-review", input_data)
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ config_path: str | Path = ".attune/workflow_chains.yaml",
83
+ ):
84
+ """Initialize the chain executor.
85
+
86
+ Args:
87
+ config_path: Path to workflow_chains.yaml
88
+
89
+ """
90
+ self.config_path = Path(config_path)
91
+ self._configs: dict[str, ChainConfig] = {}
92
+ self._templates: dict[str, list[str]] = {}
93
+ self._global_settings: dict[str, Any] = {}
94
+ self._registry = WorkflowRegistry()
95
+ self._executions: list[ChainExecution] = []
96
+
97
+ self._load_config()
98
+
99
+ def _load_config(self) -> None:
100
+ """Load chain configuration from YAML file."""
101
+ if not self.config_path.exists():
102
+ return
103
+
104
+ try:
105
+ with open(self.config_path) as f:
106
+ data = yaml.safe_load(f) or {}
107
+
108
+ # Load global settings
109
+ self._global_settings = data.get("global", {})
110
+
111
+ # Load chain configs
112
+ chains = data.get("chains", {})
113
+ for workflow_name, config in chains.items():
114
+ triggers = []
115
+ for t in config.get("triggers", []):
116
+ triggers.append(
117
+ ChainTrigger(
118
+ condition=t.get("condition", ""),
119
+ next_workflow=t.get("next", ""),
120
+ approval_required=t.get("approval_required", False),
121
+ reason=t.get("reason", ""),
122
+ ),
123
+ )
124
+
125
+ self._configs[workflow_name] = ChainConfig(
126
+ workflow_name=workflow_name,
127
+ auto_chain=config.get("auto_chain", True),
128
+ description=config.get("description", ""),
129
+ triggers=triggers,
130
+ )
131
+
132
+ # Load templates
133
+ templates = data.get("templates", {})
134
+ for name, template in templates.items():
135
+ self._templates[name] = template.get("steps", [])
136
+
137
+ except (yaml.YAMLError, OSError) as e:
138
+ print(f"Warning: Could not load chain config: {e}")
139
+
140
+ def get_triggered_chains(
141
+ self,
142
+ workflow_name: str,
143
+ result: dict[str, Any],
144
+ ) -> list[ChainTrigger]:
145
+ """Get triggered chain steps based on workflow result.
146
+
147
+ Args:
148
+ workflow_name: The workflow that just completed
149
+ result: The workflow's result dictionary
150
+
151
+ Returns:
152
+ List of triggered ChainTriggers
153
+
154
+ """
155
+ if not self._global_settings.get("auto_chain_enabled", True):
156
+ return []
157
+
158
+ config = self._configs.get(workflow_name)
159
+ if not config or not config.auto_chain:
160
+ return []
161
+
162
+ triggered = []
163
+ for trigger in config.triggers:
164
+ if self._evaluate_condition(trigger.condition, result):
165
+ triggered.append(trigger)
166
+
167
+ return triggered
168
+
169
+ def _evaluate_condition(
170
+ self,
171
+ condition: str,
172
+ context: dict[str, Any],
173
+ ) -> bool:
174
+ """Evaluate a trigger condition against a result context.
175
+
176
+ Supports:
177
+ - Comparisons: var > 0, var == 'value', var < 10
178
+ - Boolean: var == true, var == false
179
+ - Existence: var != null
180
+
181
+ Args:
182
+ condition: The condition string
183
+ context: The result dictionary to evaluate against
184
+
185
+ Returns:
186
+ True if condition is met
187
+
188
+ """
189
+ if not condition:
190
+ return False
191
+
192
+ # Parse comparison operators
193
+ operators = {
194
+ "==": lambda a, b: a == b,
195
+ "!=": lambda a, b: a != b,
196
+ ">": lambda a, b: float(a) > float(b) if _is_numeric(a) and _is_numeric(b) else False,
197
+ "<": lambda a, b: float(a) < float(b) if _is_numeric(a) and _is_numeric(b) else False,
198
+ ">=": lambda a, b: float(a) >= float(b) if _is_numeric(a) and _is_numeric(b) else False,
199
+ "<=": lambda a, b: float(a) <= float(b) if _is_numeric(a) and _is_numeric(b) else False,
200
+ }
201
+
202
+ for op_str, op_func in operators.items():
203
+ if op_str in condition:
204
+ parts = condition.split(op_str)
205
+ if len(parts) == 2:
206
+ var_name = parts[0].strip()
207
+ value_str = parts[1].strip()
208
+
209
+ # Get actual value from context
210
+ actual = context.get(var_name)
211
+ if actual is None:
212
+ # Try nested access
213
+ actual = _get_nested(context, var_name)
214
+
215
+ if actual is None:
216
+ return False
217
+
218
+ # Parse expected value
219
+ expected = _parse_value(value_str)
220
+
221
+ try:
222
+ return bool(op_func(actual, expected))
223
+ except (ValueError, TypeError):
224
+ return False
225
+
226
+ return False
227
+
228
+ def should_trigger_chain(
229
+ self,
230
+ workflow_name: str,
231
+ result: dict[str, Any],
232
+ ) -> tuple[bool, list[ChainTrigger]]:
233
+ """Check if a chain should be triggered and return triggers.
234
+
235
+ Args:
236
+ workflow_name: The workflow that completed
237
+ result: The workflow's result
238
+
239
+ Returns:
240
+ Tuple of (should_trigger, list_of_triggers)
241
+
242
+ """
243
+ triggers = self.get_triggered_chains(workflow_name, result)
244
+ return len(triggers) > 0, triggers
245
+
246
+ def get_chain_config(self, workflow_name: str) -> ChainConfig | None:
247
+ """Get chain configuration for a workflow."""
248
+ return self._configs.get(workflow_name)
249
+
250
+ def get_template(self, template_name: str) -> list[str] | None:
251
+ """Get a chain template by name."""
252
+ return self._templates.get(template_name)
253
+
254
+ def list_templates(self) -> dict[str, list[str]]:
255
+ """List all available chain templates."""
256
+ return dict(self._templates)
257
+
258
+ def create_execution(
259
+ self,
260
+ initial_workflow: str,
261
+ triggered_steps: list[ChainTrigger] | None = None,
262
+ ) -> ChainExecution:
263
+ """Create a new chain execution record.
264
+
265
+ Args:
266
+ initial_workflow: The starting workflow
267
+ triggered_steps: Optional list of triggered next steps
268
+
269
+ Returns:
270
+ ChainExecution object
271
+
272
+ """
273
+ execution = ChainExecution(
274
+ chain_id=f"chain_{datetime.now().strftime('%Y%m%d%H%M%S')}",
275
+ initial_workflow=initial_workflow,
276
+ )
277
+
278
+ # Add initial step
279
+ execution.steps.append(
280
+ ChainStep(
281
+ workflow_name=initial_workflow,
282
+ triggered_by="manual",
283
+ approval_required=False,
284
+ approved=True,
285
+ ),
286
+ )
287
+
288
+ # Add triggered steps
289
+ if triggered_steps:
290
+ for trigger in triggered_steps:
291
+ execution.steps.append(
292
+ ChainStep(
293
+ workflow_name=trigger.next_workflow,
294
+ triggered_by=trigger.condition,
295
+ approval_required=trigger.approval_required,
296
+ ),
297
+ )
298
+
299
+ self._executions.append(execution)
300
+ return execution
301
+
302
+ def approve_step(self, execution: ChainExecution, step_index: int) -> bool:
303
+ """Approve a pending step in a chain execution."""
304
+ if step_index < len(execution.steps):
305
+ execution.steps[step_index].approved = True
306
+ return True
307
+ return False
308
+
309
+ def reject_step(self, execution: ChainExecution, step_index: int) -> bool:
310
+ """Reject a pending step in a chain execution."""
311
+ if step_index < len(execution.steps):
312
+ execution.steps[step_index].approved = False
313
+ return True
314
+ return False
315
+
316
+ def get_next_step(self, execution: ChainExecution) -> ChainStep | None:
317
+ """Get the next step to execute in a chain."""
318
+ max_depth = self._global_settings.get("max_chain_depth", 3)
319
+
320
+ for i, step in enumerate(execution.steps):
321
+ if i >= max_depth:
322
+ break
323
+
324
+ # Skip completed steps
325
+ if step.completed_at is not None:
326
+ continue
327
+
328
+ # Check approval
329
+ if step.approval_required and step.approved is None:
330
+ execution.status = "waiting_approval"
331
+ return None
332
+
333
+ if step.approval_required and step.approved is False:
334
+ continue # Skip rejected steps
335
+
336
+ return step
337
+
338
+ return None
339
+
340
+ def complete_step(
341
+ self,
342
+ execution: ChainExecution,
343
+ step: ChainStep,
344
+ result: dict[str, Any],
345
+ ) -> list[ChainTrigger]:
346
+ """Mark a step as complete and check for new triggers.
347
+
348
+ Args:
349
+ execution: The chain execution
350
+ step: The step that completed
351
+ result: The step's result
352
+
353
+ Returns:
354
+ List of newly triggered steps
355
+
356
+ """
357
+ step.completed_at = datetime.now()
358
+ step.result = result
359
+
360
+ # Check for new triggers
361
+ new_triggers = self.get_triggered_chains(step.workflow_name, result)
362
+
363
+ # Add new steps (if not already in chain)
364
+ existing_workflows = {s.workflow_name for s in execution.steps}
365
+ for trigger in new_triggers:
366
+ if trigger.next_workflow not in existing_workflows:
367
+ execution.steps.append(
368
+ ChainStep(
369
+ workflow_name=trigger.next_workflow,
370
+ triggered_by=trigger.condition,
371
+ approval_required=trigger.approval_required,
372
+ ),
373
+ )
374
+
375
+ # Check if chain is complete
376
+ all_done = all(
377
+ s.completed_at is not None or (s.approval_required and s.approved is False)
378
+ for s in execution.steps
379
+ )
380
+ if all_done:
381
+ execution.status = "completed"
382
+ execution.completed_at = datetime.now()
383
+
384
+ return new_triggers
385
+
386
+
387
+ def _is_numeric(value: Any) -> bool:
388
+ """Check if a value is numeric."""
389
+ if isinstance(value, int | float):
390
+ return True
391
+ if isinstance(value, str):
392
+ try:
393
+ float(value)
394
+ return True
395
+ except ValueError:
396
+ return False
397
+ return False
398
+
399
+
400
+ def _parse_value(value_str: str) -> Any:
401
+ """Parse a value string to appropriate type."""
402
+ value_str = value_str.strip().strip("'\"")
403
+
404
+ # Boolean
405
+ if value_str.lower() == "true":
406
+ return True
407
+ if value_str.lower() == "false":
408
+ return False
409
+ if value_str.lower() == "null":
410
+ return None
411
+
412
+ # Number
413
+ try:
414
+ if "." in value_str:
415
+ return float(value_str)
416
+ return int(value_str)
417
+ except ValueError:
418
+ pass
419
+
420
+ # String
421
+ return value_str
422
+
423
+
424
+ def _get_nested(obj: dict[str, Any], path: str) -> Any:
425
+ """Get a nested value from a dictionary using dot notation."""
426
+ parts = path.split(".")
427
+ current: Any = obj
428
+ for part in parts:
429
+ if isinstance(current, dict):
430
+ current = current.get(part)
431
+ else:
432
+ return None
433
+ return current
@@ -0,0 +1,217 @@
1
+ """LLM-based Request Classifier
2
+
3
+ Uses a cheap model (Haiku) to classify developer requests
4
+ and route them to appropriate workflow(s).
5
+
6
+ Copyright 2025 Smart AI Memory, LLC
7
+ Licensed under Fair Source 0.9
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from dataclasses import dataclass, field
13
+ from typing import Any
14
+
15
+ from .workflow_registry import WorkflowRegistry
16
+
17
+
18
+ @dataclass
19
+ class ClassificationResult:
20
+ """Result of classifying a developer request."""
21
+
22
+ primary_workflow: str
23
+ secondary_workflows: list[str] = field(default_factory=list)
24
+ confidence: float = 0.0
25
+ reasoning: str = ""
26
+ suggested_chain: list[str] = field(default_factory=list)
27
+ extracted_context: dict[str, Any] = field(default_factory=dict)
28
+
29
+
30
+ class HaikuClassifier:
31
+ """Uses Claude Haiku to classify requests to workflows.
32
+
33
+ Why Haiku:
34
+ - Cheapest tier model
35
+ - Fast response times
36
+ - Sufficient for classification tasks
37
+ - Cost-effective for high-volume routing
38
+ """
39
+
40
+ def __init__(self, api_key: str | None = None):
41
+ """Initialize the classifier.
42
+
43
+ Args:
44
+ api_key: Anthropic API key (defaults to ANTHROPIC_API_KEY env var)
45
+
46
+ """
47
+ self._api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
48
+ self._client = None
49
+ self._registry = WorkflowRegistry()
50
+
51
+ def _get_client(self):
52
+ """Lazy-load the Anthropic client."""
53
+ if self._client is None and self._api_key:
54
+ try:
55
+ import anthropic
56
+
57
+ self._client = anthropic.Anthropic(api_key=self._api_key)
58
+ except ImportError:
59
+ pass
60
+ return self._client
61
+
62
+ async def classify(
63
+ self,
64
+ request: str,
65
+ context: dict[str, Any] | None = None,
66
+ available_workflows: dict[str, str] | None = None,
67
+ ) -> ClassificationResult:
68
+ """Classify a developer request and determine which workflow(s) to invoke.
69
+
70
+ Args:
71
+ request: The developer's natural language request
72
+ context: Optional context (current file, project type, etc.)
73
+ available_workflows: Override for available workflow descriptions
74
+
75
+ Returns:
76
+ ClassificationResult with primary and secondary workflow recommendations
77
+
78
+ """
79
+ if available_workflows is None:
80
+ available_workflows = self._registry.get_descriptions_for_classification()
81
+
82
+ # Build classification prompt
83
+ workflow_list = "\n".join(f"- {name}: {desc}" for name, desc in available_workflows.items())
84
+
85
+ context_str = ""
86
+ if context:
87
+ context_str = f"\n\nContext:\n{json.dumps(context, indent=2)}"
88
+
89
+ system_prompt = """You are a request router that classifies requests to the appropriate workflow.
90
+
91
+ Analyze the request and determine:
92
+ 1. The PRIMARY workflow that best handles this request
93
+ 2. Any SECONDARY workflows that could provide additional value
94
+ 3. Your confidence level (0.0 - 1.0)
95
+ 4. Brief reasoning for your choice
96
+
97
+ Respond in JSON format:
98
+ {
99
+ "primary_workflow": "workflow-name",
100
+ "secondary_workflows": ["workflow-name-2"],
101
+ "confidence": 0.85,
102
+ "reasoning": "Brief explanation",
103
+ "extracted_context": {
104
+ "file_mentioned": "auth.py",
105
+ "issue_type": "performance"
106
+ }
107
+ }"""
108
+
109
+ user_prompt = f"""Available workflows:
110
+ {workflow_list}
111
+
112
+ Developer request: "{request}"{context_str}
113
+
114
+ Classify this request."""
115
+
116
+ # Try LLM classification
117
+ client = self._get_client()
118
+ if client:
119
+ try:
120
+ response = client.messages.create(
121
+ model="claude-3-5-haiku-20241022",
122
+ max_tokens=500,
123
+ system=system_prompt,
124
+ messages=[{"role": "user", "content": user_prompt}],
125
+ )
126
+
127
+ content = response.content[0].text if response.content else "{}"
128
+
129
+ # Parse JSON response
130
+ try:
131
+ # Extract JSON from response (handle markdown code blocks)
132
+ if "```json" in content:
133
+ content = content.split("```json")[1].split("```")[0]
134
+ elif "```" in content:
135
+ content = content.split("```")[1].split("```")[0]
136
+
137
+ data = json.loads(content.strip())
138
+ return ClassificationResult(
139
+ primary_workflow=data.get("primary_workflow", "code-review"),
140
+ secondary_workflows=data.get("secondary_workflows", []),
141
+ confidence=data.get("confidence", 0.5),
142
+ reasoning=data.get("reasoning", ""),
143
+ extracted_context=data.get("extracted_context", {}),
144
+ )
145
+ except json.JSONDecodeError:
146
+ pass
147
+
148
+ except Exception as e:
149
+ print(f"LLM classification error: {e}")
150
+
151
+ # Fallback to keyword-based classification
152
+ return self._keyword_classify(request, available_workflows)
153
+
154
+ def _keyword_classify(
155
+ self,
156
+ request: str,
157
+ available_workflows: dict[str, str],
158
+ ) -> ClassificationResult:
159
+ """Fallback keyword-based classification."""
160
+ request_lower = request.lower()
161
+
162
+ # Score each workflow based on keyword matches
163
+ scores: dict[str, float] = {}
164
+
165
+ for workflow in self._registry.list_all():
166
+ score = 0.0
167
+ for keyword in workflow.keywords:
168
+ if keyword in request_lower:
169
+ score += 1.0
170
+ # Exact word match bonus
171
+ if f" {keyword} " in f" {request_lower} ":
172
+ score += 0.5
173
+
174
+ if score > 0:
175
+ scores[workflow.name] = score
176
+
177
+ if not scores:
178
+ # Default to code-review
179
+ return ClassificationResult(
180
+ primary_workflow="code-review",
181
+ confidence=0.3,
182
+ reasoning="No keyword matches, defaulting to code-review",
183
+ )
184
+
185
+ # Sort by score
186
+ sorted_workflows = sorted(scores.items(), key=lambda x: x[1], reverse=True)
187
+ primary = sorted_workflows[0][0]
188
+ primary_score = sorted_workflows[0][1]
189
+
190
+ # Get secondary if significantly different
191
+ secondary = []
192
+ if len(sorted_workflows) > 1:
193
+ for name, score in sorted_workflows[1:3]:
194
+ if score >= primary_score * 0.5:
195
+ secondary.append(name)
196
+
197
+ # Normalize confidence
198
+ max_possible = max(len(w.keywords) for w in self._registry.list_all())
199
+ confidence = min(primary_score / max_possible, 1.0)
200
+
201
+ return ClassificationResult(
202
+ primary_workflow=primary,
203
+ secondary_workflows=secondary,
204
+ confidence=confidence,
205
+ reasoning=f"Keyword match score: {primary_score}",
206
+ )
207
+
208
+ def classify_sync(
209
+ self,
210
+ request: str,
211
+ context: dict[str, Any] | None = None,
212
+ ) -> ClassificationResult:
213
+ """Synchronous classification using keyword matching only."""
214
+ return self._keyword_classify(
215
+ request,
216
+ self._registry.get_descriptions_for_classification(),
217
+ )