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
attune/memory/graph.py ADDED
@@ -0,0 +1,570 @@
1
+ """Memory Graph - Cross-Workflow Knowledge Base
2
+
3
+ A knowledge graph that connects findings across all workflows,
4
+ enabling intelligent correlation and learning.
5
+
6
+ Features:
7
+ - Add findings from any workflow as nodes
8
+ - Connect related findings with typed edges
9
+ - Query for similar past findings
10
+ - Traverse relationships to find root causes
11
+
12
+ Storage: JSON file in patterns/ directory
13
+
14
+ Copyright 2025 Smart AI Memory, LLC
15
+ Licensed under Fair Source 0.9
16
+ """
17
+
18
+ import hashlib
19
+ import json
20
+ from collections import defaultdict, deque
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from attune.config import _validate_file_path
26
+
27
+ from .edges import REVERSE_EDGE_TYPES, Edge, EdgeType
28
+ from .nodes import Node, NodeType
29
+
30
+
31
+ class MemoryGraph:
32
+ """Knowledge graph for cross-workflow intelligence.
33
+
34
+ Stores nodes (findings) and edges (relationships) discovered
35
+ by workflows, enabling pattern correlation across sessions.
36
+
37
+ Usage:
38
+ graph = MemoryGraph()
39
+
40
+ # Add a bug finding
41
+ bug_id = graph.add_finding(
42
+ workflow="bug-predict",
43
+ finding={
44
+ "type": "bug",
45
+ "name": "Null reference in auth.py",
46
+ "description": "Missing null check on user object",
47
+ "file": "src/auth.py",
48
+ "line": 42,
49
+ "severity": "high"
50
+ }
51
+ )
52
+
53
+ # Connect to a fix
54
+ fix_id = graph.add_finding(
55
+ workflow="bug-predict",
56
+ finding={
57
+ "type": "fix",
58
+ "name": "Add null check",
59
+ "description": "Added guard clause for user object"
60
+ }
61
+ )
62
+ graph.add_edge(bug_id, fix_id, EdgeType.FIXED_BY)
63
+
64
+ # Find similar bugs
65
+ similar = graph.find_similar({"name": "Null reference"})
66
+ """
67
+
68
+ def __init__(self, path: str | Path = "patterns/memory_graph.json"):
69
+ """Initialize the memory graph.
70
+
71
+ Args:
72
+ path: Path to JSON storage file
73
+
74
+ """
75
+ self.path = Path(path)
76
+ self.nodes: dict[str, Node] = {}
77
+ self.edges: list[Edge] = []
78
+
79
+ # Indexes for fast lookup
80
+ self._edges_by_source: dict[str, list[Edge]] = defaultdict(list)
81
+ self._edges_by_target: dict[str, list[Edge]] = defaultdict(list)
82
+ self._nodes_by_type: dict[NodeType, list[str]] = defaultdict(list)
83
+ self._nodes_by_workflow: dict[str, list[str]] = defaultdict(list)
84
+ self._nodes_by_file: dict[str, list[str]] = defaultdict(list)
85
+
86
+ self._load()
87
+
88
+ def _load(self) -> None:
89
+ """Load graph from JSON file."""
90
+ if not self.path.exists():
91
+ # Ensure directory exists
92
+ self.path.parent.mkdir(parents=True, exist_ok=True)
93
+ self._save()
94
+ return
95
+
96
+ try:
97
+ with open(self.path) as f:
98
+ data = json.load(f)
99
+
100
+ # Load nodes
101
+ for node_data in data.get("nodes", []):
102
+ node = Node.from_dict(node_data)
103
+ self.nodes[node.id] = node
104
+ self._index_node(node)
105
+
106
+ # Load edges
107
+ for edge_data in data.get("edges", []):
108
+ edge = Edge.from_dict(edge_data)
109
+ self.edges.append(edge)
110
+ self._index_edge(edge)
111
+
112
+ except (json.JSONDecodeError, KeyError) as e:
113
+ print(f"Warning: Could not load graph from {self.path}: {e}")
114
+ self.nodes = {}
115
+ self.edges = []
116
+
117
+ def _save(self) -> None:
118
+ """Save graph to JSON file."""
119
+ data = {
120
+ "version": "1.0",
121
+ "updated_at": datetime.now().isoformat(),
122
+ "node_count": len(self.nodes),
123
+ "edge_count": len(self.edges),
124
+ "nodes": [node.to_dict() for node in self.nodes.values()],
125
+ "edges": [edge.to_dict() for edge in self.edges],
126
+ }
127
+
128
+ self.path.parent.mkdir(parents=True, exist_ok=True)
129
+ validated_path = _validate_file_path(str(self.path))
130
+ with open(validated_path, "w") as f:
131
+ json.dump(data, f, indent=2)
132
+
133
+ def _index_node(self, node: Node) -> None:
134
+ """Add node to indexes."""
135
+ self._nodes_by_type[node.type].append(node.id)
136
+ if node.source_workflow:
137
+ self._nodes_by_workflow[node.source_workflow].append(node.id)
138
+ if node.source_file:
139
+ self._nodes_by_file[node.source_file].append(node.id)
140
+
141
+ def _index_edge(self, edge: Edge) -> None:
142
+ """Add edge to indexes."""
143
+ self._edges_by_source[edge.source_id].append(edge)
144
+ self._edges_by_target[edge.target_id].append(edge)
145
+
146
+ def _generate_id(self, finding: dict[str, Any]) -> str:
147
+ """Generate unique ID for a finding."""
148
+ # Create hash from content
149
+ content = json.dumps(finding, sort_keys=True)
150
+ hash_val = hashlib.sha256(content.encode()).hexdigest()[:12]
151
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
152
+ return f"{finding.get('type', 'node')}_{timestamp}_{hash_val}"
153
+
154
+ def add_finding(self, workflow: str, finding: dict[str, Any]) -> str:
155
+ """Add a finding from any workflow, return node ID.
156
+
157
+ Args:
158
+ workflow: Name of the workflow adding this finding
159
+ finding: Dict with at least 'type' and 'name' keys
160
+
161
+ Returns:
162
+ Node ID for the created node
163
+
164
+ Example:
165
+ node_id = graph.add_finding(
166
+ workflow="security-audit",
167
+ finding={
168
+ "type": "vulnerability",
169
+ "name": "SQL Injection in query builder",
170
+ "description": "User input not sanitized",
171
+ "file": "src/db/query.py",
172
+ "line": 156,
173
+ "severity": "critical"
174
+ }
175
+ )
176
+
177
+ """
178
+ node_id = self._generate_id(finding)
179
+
180
+ # Map finding type to NodeType
181
+ type_str = finding.get("type", "pattern")
182
+ try:
183
+ node_type = NodeType(type_str)
184
+ except ValueError:
185
+ node_type = NodeType.PATTERN
186
+
187
+ node = Node(
188
+ id=node_id,
189
+ type=node_type,
190
+ name=finding.get("name", "Unnamed finding"),
191
+ description=finding.get("description", ""),
192
+ source_workflow=workflow,
193
+ source_file=finding.get("file", finding.get("source_file", "")),
194
+ source_line=finding.get("line", finding.get("source_line")),
195
+ severity=finding.get("severity", ""),
196
+ confidence=finding.get("confidence", 1.0),
197
+ metadata=finding.get("metadata", {}),
198
+ tags=finding.get("tags", []),
199
+ )
200
+
201
+ self.nodes[node_id] = node
202
+ self._index_node(node)
203
+ self._save()
204
+
205
+ return node_id
206
+
207
+ def add_edge(
208
+ self,
209
+ source_id: str,
210
+ target_id: str,
211
+ edge_type: EdgeType,
212
+ description: str = "",
213
+ workflow: str = "",
214
+ weight: float = 1.0,
215
+ bidirectional: bool = False,
216
+ ) -> str:
217
+ """Add an edge between two nodes.
218
+
219
+ Args:
220
+ source_id: Source node ID
221
+ target_id: Target node ID
222
+ edge_type: Type of relationship
223
+ description: Optional description of the relationship
224
+ workflow: Workflow that created this edge
225
+ weight: Strength of relationship (0.0 - 1.0)
226
+ bidirectional: If True, also create reverse edge
227
+
228
+ Returns:
229
+ Edge ID
230
+
231
+ """
232
+ if source_id not in self.nodes:
233
+ raise ValueError(f"Source node not found: {source_id}")
234
+ if target_id not in self.nodes:
235
+ raise ValueError(f"Target node not found: {target_id}")
236
+
237
+ edge = Edge(
238
+ source_id=source_id,
239
+ target_id=target_id,
240
+ type=edge_type,
241
+ description=description,
242
+ source_workflow=workflow,
243
+ weight=weight,
244
+ )
245
+
246
+ self.edges.append(edge)
247
+ self._index_edge(edge)
248
+
249
+ # Optionally create reverse edge
250
+ if bidirectional and edge_type in REVERSE_EDGE_TYPES:
251
+ reverse_type = REVERSE_EDGE_TYPES[edge_type]
252
+ reverse_edge = Edge(
253
+ source_id=target_id,
254
+ target_id=source_id,
255
+ type=reverse_type,
256
+ description=description,
257
+ source_workflow=workflow,
258
+ weight=weight,
259
+ )
260
+ self.edges.append(reverse_edge)
261
+ self._index_edge(reverse_edge)
262
+
263
+ self._save()
264
+ return edge.id
265
+
266
+ def get_node(self, node_id: str) -> Node | None:
267
+ """Get a node by ID."""
268
+ return self.nodes.get(node_id)
269
+
270
+ def find_related(
271
+ self,
272
+ node_id: str,
273
+ edge_types: list[EdgeType] | None = None,
274
+ direction: str = "outgoing",
275
+ max_depth: int = 1,
276
+ ) -> list[Node]:
277
+ """Find related nodes via specified edge types.
278
+
279
+ Args:
280
+ node_id: Starting node ID
281
+ edge_types: List of edge types to follow (None = all)
282
+ direction: "outgoing", "incoming", or "both"
283
+ max_depth: Maximum traversal depth
284
+
285
+ Returns:
286
+ List of related nodes
287
+
288
+ """
289
+ if node_id not in self.nodes:
290
+ return []
291
+
292
+ visited: set[str] = {node_id}
293
+ result: list[Node] = []
294
+ current_level: set[str] = {node_id}
295
+
296
+ for _ in range(max_depth):
297
+ next_level: set[str] = set()
298
+
299
+ for current_id in current_level:
300
+ edges_to_check: list[Edge] = []
301
+
302
+ if direction in ("outgoing", "both"):
303
+ edges_to_check.extend(self._edges_by_source.get(current_id, []))
304
+ if direction in ("incoming", "both"):
305
+ edges_to_check.extend(self._edges_by_target.get(current_id, []))
306
+
307
+ for edge in edges_to_check:
308
+ if edge_types and edge.type not in edge_types:
309
+ continue
310
+
311
+ # Get the other node
312
+ other_id = edge.target_id if edge.source_id == current_id else edge.source_id
313
+
314
+ if other_id not in visited:
315
+ visited.add(other_id)
316
+ next_level.add(other_id)
317
+ if other_id in self.nodes:
318
+ result.append(self.nodes[other_id])
319
+
320
+ if not next_level:
321
+ break
322
+ current_level = next_level
323
+
324
+ return result
325
+
326
+ def find_similar(
327
+ self,
328
+ finding: dict[str, Any],
329
+ threshold: float = 0.5,
330
+ limit: int = 10,
331
+ ) -> list[tuple[Node, float]]:
332
+ """Find similar past findings.
333
+
334
+ Uses simple text similarity on name and description.
335
+
336
+ Args:
337
+ finding: Dict with 'name' and/or 'description'
338
+ threshold: Minimum similarity score (0.0 - 1.0)
339
+ limit: Maximum results to return
340
+
341
+ Returns:
342
+ List of (node, similarity_score) tuples
343
+
344
+ """
345
+ query_name = finding.get("name", "").lower()
346
+ query_desc = finding.get("description", "").lower()
347
+ query_type = finding.get("type")
348
+ query_file = finding.get("file", "")
349
+
350
+ results: list[tuple[Node, float]] = []
351
+
352
+ for node in self.nodes.values():
353
+ score = 0.0
354
+ factors = 0.0
355
+
356
+ # Name similarity (word overlap)
357
+ if query_name and node.name:
358
+ name_words = set(query_name.split())
359
+ node_words = set(node.name.lower().split())
360
+ if name_words and node_words:
361
+ overlap = len(name_words & node_words)
362
+ union = len(name_words | node_words)
363
+ score += (overlap / union) * 0.5
364
+ factors += 0.5
365
+
366
+ # Description similarity
367
+ if query_desc and node.description:
368
+ desc_words = set(query_desc.split())
369
+ node_words = set(node.description.lower().split())
370
+ if desc_words and node_words:
371
+ overlap = len(desc_words & node_words)
372
+ union = len(desc_words | node_words)
373
+ score += (overlap / union) * 0.3
374
+ factors += 0.3
375
+
376
+ # Type match bonus
377
+ if query_type:
378
+ try:
379
+ if node.type == NodeType(query_type):
380
+ score += 0.15
381
+ factors += 0.15
382
+ except ValueError:
383
+ pass
384
+
385
+ # File match bonus
386
+ if query_file and node.source_file:
387
+ if query_file == node.source_file:
388
+ score += 0.05
389
+ factors += 0.05
390
+
391
+ # Normalize
392
+ if factors > 0:
393
+ score = score / factors
394
+
395
+ if score >= threshold:
396
+ results.append((node, score))
397
+
398
+ # Sort by score descending
399
+ results.sort(key=lambda x: x[1], reverse=True)
400
+ return results[:limit]
401
+
402
+ def find_by_type(self, node_type: NodeType) -> list[Node]:
403
+ """Find all nodes of a specific type."""
404
+ node_ids = self._nodes_by_type.get(node_type, [])
405
+ return [self.nodes[nid] for nid in node_ids if nid in self.nodes]
406
+
407
+ def find_by_workflow(self, workflow: str) -> list[Node]:
408
+ """Find all nodes created by a specific workflow."""
409
+ node_ids = self._nodes_by_workflow.get(workflow, [])
410
+ return [self.nodes[nid] for nid in node_ids if nid in self.nodes]
411
+
412
+ def find_by_file(self, file_path: str) -> list[Node]:
413
+ """Find all nodes related to a specific file."""
414
+ node_ids = self._nodes_by_file.get(file_path, [])
415
+ return [self.nodes[nid] for nid in node_ids if nid in self.nodes]
416
+
417
+ def get_path(
418
+ self,
419
+ source_id: str,
420
+ target_id: str,
421
+ edge_types: list[EdgeType] | None = None,
422
+ max_depth: int = 5,
423
+ ) -> list[tuple[Node, Edge | None]]:
424
+ """Find a path between two nodes using BFS.
425
+
426
+ Args:
427
+ source_id: Starting node ID
428
+ target_id: Target node ID
429
+ edge_types: Edge types to traverse (None = all)
430
+ max_depth: Maximum path length
431
+
432
+ Returns:
433
+ List of (node, edge_to_node) tuples representing the path
434
+
435
+ """
436
+ if source_id not in self.nodes or target_id not in self.nodes:
437
+ return []
438
+
439
+ # BFS with path tracking (deque for O(1) popleft)
440
+ visited: set[str] = {source_id}
441
+ queue: deque[list[tuple[str, Edge | None]]] = deque([[(source_id, None)]])
442
+
443
+ while queue:
444
+ path = queue.popleft()
445
+ current_id = path[-1][0]
446
+
447
+ if len(path) > max_depth:
448
+ continue
449
+
450
+ if current_id == target_id:
451
+ return [(self.nodes[nid], edge) for nid, edge in path]
452
+
453
+ for edge in self._edges_by_source.get(current_id, []):
454
+ if edge_types and edge.type not in edge_types:
455
+ continue
456
+ if edge.target_id not in visited:
457
+ visited.add(edge.target_id)
458
+ new_path = path + [(edge.target_id, edge)]
459
+ queue.append(new_path)
460
+
461
+ return []
462
+
463
+ def get_statistics(self) -> dict[str, Any]:
464
+ """Get graph statistics."""
465
+ type_counts: dict[str, int] = defaultdict(int)
466
+ workflow_counts: dict[str, int] = defaultdict(int)
467
+ severity_counts: dict[str, int] = defaultdict(int)
468
+
469
+ for node in self.nodes.values():
470
+ type_counts[node.type.value] += 1
471
+ if node.source_workflow:
472
+ workflow_counts[node.source_workflow] += 1
473
+ if node.severity:
474
+ severity_counts[node.severity] += 1
475
+
476
+ edge_type_counts: dict[str, int] = defaultdict(int)
477
+ for edge in self.edges:
478
+ edge_type_counts[edge.type.value] += 1
479
+
480
+ return {
481
+ "total_nodes": len(self.nodes),
482
+ "total_edges": len(self.edges),
483
+ "nodes_by_type": dict(type_counts),
484
+ "nodes_by_workflow": dict(workflow_counts),
485
+ "nodes_by_severity": dict(severity_counts),
486
+ "edges_by_type": dict(edge_type_counts),
487
+ "unique_files": len(self._nodes_by_file),
488
+ }
489
+
490
+ def update_node(self, node_id: str, updates: dict[str, Any]) -> bool:
491
+ """Update a node's properties.
492
+
493
+ Args:
494
+ node_id: Node to update
495
+ updates: Dict of properties to update
496
+
497
+ Returns:
498
+ True if updated, False if node not found
499
+
500
+ """
501
+ if node_id not in self.nodes:
502
+ return False
503
+
504
+ node = self.nodes[node_id]
505
+
506
+ if "status" in updates:
507
+ node.status = updates["status"]
508
+ if "description" in updates:
509
+ node.description = updates["description"]
510
+ if "severity" in updates:
511
+ node.severity = updates["severity"]
512
+ if "tags" in updates:
513
+ node.tags = updates["tags"]
514
+ if "metadata" in updates:
515
+ node.metadata.update(updates["metadata"])
516
+
517
+ node.updated_at = datetime.now()
518
+ self._save()
519
+ return True
520
+
521
+ def delete_node(self, node_id: str) -> bool:
522
+ """Delete a node and its connected edges.
523
+
524
+ Args:
525
+ node_id: Node to delete
526
+
527
+ Returns:
528
+ True if deleted, False if not found
529
+
530
+ """
531
+ if node_id not in self.nodes:
532
+ return False
533
+
534
+ # Remove from indexes
535
+ node = self.nodes[node_id]
536
+ if node.type in self._nodes_by_type:
537
+ self._nodes_by_type[node.type] = [
538
+ nid for nid in self._nodes_by_type[node.type] if nid != node_id
539
+ ]
540
+ if node.source_workflow in self._nodes_by_workflow:
541
+ self._nodes_by_workflow[node.source_workflow] = [
542
+ nid for nid in self._nodes_by_workflow[node.source_workflow] if nid != node_id
543
+ ]
544
+ if node.source_file in self._nodes_by_file:
545
+ self._nodes_by_file[node.source_file] = [
546
+ nid for nid in self._nodes_by_file[node.source_file] if nid != node_id
547
+ ]
548
+
549
+ # Remove connected edges
550
+ self.edges = [e for e in self.edges if e.source_id != node_id and e.target_id != node_id]
551
+ if node_id in self._edges_by_source:
552
+ del self._edges_by_source[node_id]
553
+ if node_id in self._edges_by_target:
554
+ del self._edges_by_target[node_id]
555
+
556
+ # Remove node
557
+ del self.nodes[node_id]
558
+ self._save()
559
+ return True
560
+
561
+ def clear(self) -> None:
562
+ """Clear all nodes and edges."""
563
+ self.nodes = {}
564
+ self.edges = []
565
+ self._edges_by_source = defaultdict(list)
566
+ self._edges_by_target = defaultdict(list)
567
+ self._nodes_by_type = defaultdict(list)
568
+ self._nodes_by_workflow = defaultdict(list)
569
+ self._nodes_by_file = defaultdict(list)
570
+ self._save()