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,474 @@
1
+ """Project Index Reports - Generate actionable reports from index data.
2
+
3
+ Reports for project management, sprint planning, and architecture decisions.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import heapq
10
+ from datetime import datetime
11
+ from typing import Any
12
+
13
+ from .models import FileRecord, ProjectSummary, TestRequirement
14
+
15
+
16
+ class ReportGenerator:
17
+ """Generates reports from project index data.
18
+
19
+ Reports are designed for:
20
+ - Human consumption (markdown)
21
+ - Agent/crew consumption (structured data)
22
+ - Dashboard display (summary metrics)
23
+ """
24
+
25
+ def __init__(self, summary: ProjectSummary, records: list[FileRecord]):
26
+ self.summary = summary
27
+ self.records = records
28
+ self._source_records = [r for r in records if r.category.value == "source"]
29
+
30
+ # ===== Test Gap Reports =====
31
+
32
+ def test_gap_report(self) -> dict[str, Any]:
33
+ """Generate comprehensive test gap report.
34
+
35
+ Used by test-gen workflow and agents.
36
+ """
37
+ needing_tests = [
38
+ r
39
+ for r in self._source_records
40
+ if r.test_requirement == TestRequirement.REQUIRED and not r.tests_exist
41
+ ]
42
+
43
+ # Prioritize by impact
44
+ prioritized = sorted(needing_tests, key=lambda r: -r.impact_score)
45
+
46
+ return {
47
+ "report_type": "test_gap",
48
+ "generated_at": datetime.now().isoformat(),
49
+ "summary": {
50
+ "total_files_needing_tests": len(needing_tests),
51
+ "total_loc_untested": sum(r.lines_of_code for r in needing_tests),
52
+ "high_impact_untested": sum(1 for r in needing_tests if r.impact_score >= 5.0),
53
+ },
54
+ "priority_files": [
55
+ {
56
+ "path": r.path,
57
+ "impact_score": r.impact_score,
58
+ "lines_of_code": r.lines_of_code,
59
+ "imported_by_count": r.imported_by_count,
60
+ "reason": f"High impact ({r.impact_score:.1f}), {r.lines_of_code} LOC",
61
+ }
62
+ for r in prioritized[:20]
63
+ ],
64
+ "by_directory": self._group_by_directory(needing_tests),
65
+ "recommendations": self._test_recommendations(needing_tests),
66
+ }
67
+
68
+ def _test_recommendations(self, needing_tests: list[FileRecord]) -> list[str]:
69
+ """Generate test recommendations."""
70
+ recommendations = []
71
+
72
+ high_impact = [r for r in needing_tests if r.impact_score >= 5.0]
73
+ if high_impact:
74
+ recommendations.append(
75
+ f"PRIORITY: {len(high_impact)} high-impact files need tests. "
76
+ f"Start with: {', '.join(r.name for r in high_impact[:3])}",
77
+ )
78
+
79
+ if len(needing_tests) > 50:
80
+ recommendations.append(
81
+ f"Consider batch test generation - {len(needing_tests)} files need tests",
82
+ )
83
+
84
+ return recommendations
85
+
86
+ # ===== Staleness Reports =====
87
+
88
+ def staleness_report(self) -> dict[str, Any]:
89
+ """Generate test staleness report.
90
+
91
+ Identifies files where code changed but tests didn't update.
92
+ """
93
+ stale = [r for r in self._source_records if r.is_stale]
94
+ stale_sorted = sorted(stale, key=lambda r: -r.staleness_days)
95
+
96
+ return {
97
+ "report_type": "staleness",
98
+ "generated_at": datetime.now().isoformat(),
99
+ "summary": {
100
+ "stale_file_count": len(stale),
101
+ "avg_staleness_days": (
102
+ sum(r.staleness_days for r in stale) / len(stale) if stale else 0
103
+ ),
104
+ "max_staleness_days": max((r.staleness_days for r in stale), default=0),
105
+ },
106
+ "stale_files": [
107
+ {
108
+ "path": r.path,
109
+ "staleness_days": r.staleness_days,
110
+ "last_modified": r.last_modified.isoformat() if r.last_modified else None,
111
+ "test_file": r.test_file_path,
112
+ "tests_last_modified": (
113
+ r.tests_last_modified.isoformat() if r.tests_last_modified else None
114
+ ),
115
+ }
116
+ for r in stale_sorted[:20]
117
+ ],
118
+ "recommendations": [
119
+ f"Update tests for: {r.path} ({r.staleness_days} days stale)"
120
+ for r in stale_sorted[:5]
121
+ ],
122
+ }
123
+
124
+ # ===== Coverage Reports =====
125
+
126
+ def coverage_report(self) -> dict[str, Any]:
127
+ """Generate coverage analysis report."""
128
+ with_coverage = [r for r in self._source_records if r.coverage_percent > 0]
129
+ low_coverage = [r for r in with_coverage if r.coverage_percent < 50]
130
+
131
+ return {
132
+ "report_type": "coverage",
133
+ "generated_at": datetime.now().isoformat(),
134
+ "summary": {
135
+ "avg_coverage": self.summary.test_coverage_avg,
136
+ "files_with_data": len(with_coverage),
137
+ "files_below_50pct": len(low_coverage),
138
+ },
139
+ "low_coverage_files": [
140
+ {
141
+ "path": r.path,
142
+ "coverage_percent": r.coverage_percent,
143
+ "impact_score": r.impact_score,
144
+ }
145
+ for r in heapq.nsmallest(20, low_coverage, key=lambda r: r.coverage_percent)
146
+ ],
147
+ "coverage_by_directory": self._coverage_by_directory(),
148
+ }
149
+
150
+ def _coverage_by_directory(self) -> dict[str, float]:
151
+ """Calculate average coverage by directory."""
152
+ dir_coverage: dict[str, list[float]] = {}
153
+
154
+ for r in self._source_records:
155
+ if r.coverage_percent > 0:
156
+ parts = r.path.split("/")
157
+ dir_name = parts[0] if len(parts) > 1 else "."
158
+ if dir_name not in dir_coverage:
159
+ dir_coverage[dir_name] = []
160
+ dir_coverage[dir_name].append(r.coverage_percent)
161
+
162
+ return {
163
+ dir_name: sum(coverages) / len(coverages)
164
+ for dir_name, coverages in dir_coverage.items()
165
+ }
166
+
167
+ # ===== Project Health Report =====
168
+
169
+ def health_report(self) -> dict[str, Any]:
170
+ """Generate overall project health report.
171
+
172
+ Comprehensive view for project managers and architects.
173
+ """
174
+ health_score = self._calculate_health_score()
175
+
176
+ return {
177
+ "report_type": "health",
178
+ "generated_at": datetime.now().isoformat(),
179
+ "health_score": health_score,
180
+ "health_grade": self._health_grade(health_score),
181
+ "summary": {
182
+ "total_files": self.summary.total_files,
183
+ "source_files": self.summary.source_files,
184
+ "test_files": self.summary.test_files,
185
+ "test_coverage_avg": self.summary.test_coverage_avg,
186
+ "test_to_code_ratio": self.summary.test_to_code_ratio,
187
+ "stale_file_count": self.summary.stale_file_count,
188
+ "files_needing_attention": self.summary.files_needing_attention,
189
+ },
190
+ "strengths": self._identify_strengths(),
191
+ "concerns": self._identify_concerns(),
192
+ "action_items": self._generate_action_items(),
193
+ }
194
+
195
+ def _calculate_health_score(self) -> float:
196
+ """Calculate overall health score (0-100)."""
197
+ score = 50.0 # Base score
198
+
199
+ # Coverage bonus/penalty (up to +/- 25 points)
200
+ if self.summary.test_coverage_avg >= 80:
201
+ score += 25
202
+ elif self.summary.test_coverage_avg >= 60:
203
+ score += 15
204
+ elif self.summary.test_coverage_avg >= 40:
205
+ score += 5
206
+ elif self.summary.test_coverage_avg < 20:
207
+ score -= 15
208
+
209
+ # Test existence bonus/penalty (up to +/- 15 points)
210
+ if self.summary.files_requiring_tests > 0:
211
+ test_ratio = self.summary.files_with_tests / self.summary.files_requiring_tests
212
+ score += (test_ratio - 0.5) * 30 # 0% = -15, 50% = 0, 100% = +15
213
+
214
+ # Staleness penalty (up to -10 points)
215
+ if self.summary.source_files > 0:
216
+ stale_ratio = self.summary.stale_file_count / self.summary.source_files
217
+ score -= stale_ratio * 20
218
+
219
+ # Documentation bonus (up to +10 points)
220
+ if self.summary.files_with_docstrings_pct >= 80:
221
+ score += 10
222
+ elif self.summary.files_with_docstrings_pct >= 50:
223
+ score += 5
224
+
225
+ return max(0, min(100, score))
226
+
227
+ def _health_grade(self, score: float) -> str:
228
+ """Convert score to letter grade."""
229
+ if score >= 90:
230
+ return "A"
231
+ if score >= 80:
232
+ return "B"
233
+ if score >= 70:
234
+ return "C"
235
+ if score >= 60:
236
+ return "D"
237
+ return "F"
238
+
239
+ def _identify_strengths(self) -> list[str]:
240
+ """Identify project strengths."""
241
+ strengths = []
242
+
243
+ if self.summary.test_coverage_avg >= 70:
244
+ strengths.append(f"Good test coverage ({self.summary.test_coverage_avg:.1f}%)")
245
+
246
+ if self.summary.files_with_docstrings_pct >= 70:
247
+ strengths.append(
248
+ f"Well documented ({self.summary.files_with_docstrings_pct:.1f}% with docstrings)",
249
+ )
250
+
251
+ if self.summary.files_with_type_hints_pct >= 70:
252
+ strengths.append(
253
+ f"Strong typing ({self.summary.files_with_type_hints_pct:.1f}% with type hints)",
254
+ )
255
+
256
+ if self.summary.stale_file_count == 0:
257
+ strengths.append("All tests are up to date")
258
+
259
+ return strengths
260
+
261
+ def _identify_concerns(self) -> list[str]:
262
+ """Identify project concerns."""
263
+ concerns = []
264
+
265
+ if self.summary.test_coverage_avg < 50:
266
+ concerns.append(f"Low test coverage ({self.summary.test_coverage_avg:.1f}%)")
267
+
268
+ if self.summary.files_without_tests > 10:
269
+ concerns.append(f"{self.summary.files_without_tests} source files without tests")
270
+
271
+ if self.summary.stale_file_count > 5:
272
+ concerns.append(f"{self.summary.stale_file_count} files have stale tests")
273
+
274
+ if self.summary.critical_untested_files:
275
+ concerns.append(
276
+ f"{len(self.summary.critical_untested_files)} high-impact files lack tests",
277
+ )
278
+
279
+ return concerns
280
+
281
+ def _generate_action_items(self) -> list[dict[str, Any]]:
282
+ """Generate prioritized action items."""
283
+ items = []
284
+
285
+ # Critical untested files
286
+ for path in self.summary.critical_untested_files[:3]:
287
+ items.append(
288
+ {
289
+ "priority": "high",
290
+ "action": f"Add tests for {path}",
291
+ "reason": "High-impact file without tests",
292
+ },
293
+ )
294
+
295
+ # Stale tests
296
+ for path in self.summary.most_stale_files[:3]:
297
+ items.append(
298
+ {
299
+ "priority": "medium",
300
+ "action": f"Update tests for {path}",
301
+ "reason": "Tests are stale",
302
+ },
303
+ )
304
+
305
+ return items
306
+
307
+ # ===== Sprint Planning Report =====
308
+
309
+ def sprint_planning_report(self, sprint_capacity: int = 10) -> dict[str, Any]:
310
+ """Generate sprint planning report.
311
+
312
+ Suggests files to address based on priority and capacity.
313
+ """
314
+ attention_files = [r for r in self._source_records if r.needs_attention]
315
+ prioritized = sorted(attention_files, key=lambda r: -r.impact_score)
316
+
317
+ # Select files up to sprint capacity
318
+ sprint_files = prioritized[:sprint_capacity]
319
+
320
+ return {
321
+ "report_type": "sprint_planning",
322
+ "generated_at": datetime.now().isoformat(),
323
+ "sprint_capacity": sprint_capacity,
324
+ "suggested_work": [
325
+ {
326
+ "path": r.path,
327
+ "impact_score": r.impact_score,
328
+ "reasons": r.attention_reasons,
329
+ "estimated_effort": self._estimate_effort(r),
330
+ }
331
+ for r in sprint_files
332
+ ],
333
+ "backlog": [
334
+ {"path": r.path, "reasons": r.attention_reasons}
335
+ for r in prioritized[sprint_capacity : sprint_capacity + 10]
336
+ ],
337
+ "metrics_to_track": [
338
+ "Test coverage change",
339
+ "Files needing attention (before/after)",
340
+ "Staleness reduction",
341
+ ],
342
+ }
343
+
344
+ def _estimate_effort(self, record: FileRecord) -> str:
345
+ """Estimate effort to address file."""
346
+ loc = record.lines_of_code
347
+
348
+ if not record.tests_exist:
349
+ if loc < 50:
350
+ return "small (1-2 hours)"
351
+ if loc < 200:
352
+ return "medium (2-4 hours)"
353
+ return "large (4+ hours)"
354
+ if record.is_stale:
355
+ return "small (update existing tests)"
356
+ return "varies"
357
+
358
+ # ===== Markdown Reports =====
359
+
360
+ def to_markdown(self, report_type: str = "health") -> str:
361
+ """Generate markdown formatted report.
362
+
363
+ For human consumption or documentation.
364
+ """
365
+ if report_type == "health":
366
+ return self._health_markdown()
367
+ if report_type == "test_gap":
368
+ return self._test_gap_markdown()
369
+ if report_type == "staleness":
370
+ return self._staleness_markdown()
371
+ return self._health_markdown()
372
+
373
+ def _health_markdown(self) -> str:
374
+ """Generate health report in markdown."""
375
+ report = self.health_report()
376
+
377
+ lines = [
378
+ "# Project Health Report",
379
+ "",
380
+ f"**Generated:** {report['generated_at']}",
381
+ f"**Health Score:** {report['health_score']:.1f}/100 ({report['health_grade']})",
382
+ "",
383
+ "## Summary",
384
+ "",
385
+ f"- **Total Files:** {report['summary']['total_files']}",
386
+ f"- **Source Files:** {report['summary']['source_files']}",
387
+ f"- **Test Files:** {report['summary']['test_files']}",
388
+ f"- **Average Coverage:** {report['summary']['test_coverage_avg']:.1f}%",
389
+ f"- **Files Needing Attention:** {report['summary']['files_needing_attention']}",
390
+ "",
391
+ ]
392
+
393
+ if report["strengths"]:
394
+ lines.extend(["## Strengths", ""])
395
+ for strength in report["strengths"]:
396
+ lines.append(f"- {strength}")
397
+ lines.append("")
398
+
399
+ if report["concerns"]:
400
+ lines.extend(["## Concerns", ""])
401
+ for concern in report["concerns"]:
402
+ lines.append(f"- {concern}")
403
+ lines.append("")
404
+
405
+ if report["action_items"]:
406
+ lines.extend(["## Action Items", ""])
407
+ for item in report["action_items"]:
408
+ lines.append(f"- [{item['priority'].upper()}] {item['action']}")
409
+ lines.append("")
410
+
411
+ return "\n".join(lines)
412
+
413
+ def _test_gap_markdown(self) -> str:
414
+ """Generate test gap report in markdown."""
415
+ report = self.test_gap_report()
416
+
417
+ lines = [
418
+ "# Test Gap Report",
419
+ "",
420
+ f"**Generated:** {report['generated_at']}",
421
+ "",
422
+ "## Summary",
423
+ "",
424
+ f"- **Files Needing Tests:** {report['summary']['total_files_needing_tests']}",
425
+ f"- **Lines of Code Untested:** {report['summary']['total_loc_untested']}",
426
+ f"- **High Impact Untested:** {report['summary']['high_impact_untested']}",
427
+ "",
428
+ "## Priority Files",
429
+ "",
430
+ ]
431
+
432
+ for i, f in enumerate(report["priority_files"][:10], 1):
433
+ lines.append(f"{i}. `{f['path']}` - {f['reason']}")
434
+
435
+ lines.append("")
436
+
437
+ return "\n".join(lines)
438
+
439
+ def _staleness_markdown(self) -> str:
440
+ """Generate staleness report in markdown."""
441
+ report = self.staleness_report()
442
+
443
+ lines = [
444
+ "# Test Staleness Report",
445
+ "",
446
+ f"**Generated:** {report['generated_at']}",
447
+ "",
448
+ "## Summary",
449
+ "",
450
+ f"- **Stale Files:** {report['summary']['stale_file_count']}",
451
+ f"- **Average Staleness:** {report['summary']['avg_staleness_days']:.1f} days",
452
+ f"- **Maximum Staleness:** {report['summary']['max_staleness_days']} days",
453
+ "",
454
+ "## Stale Files",
455
+ "",
456
+ ]
457
+
458
+ for f in report["stale_files"][:10]:
459
+ lines.append(f"- `{f['path']}` - {f['staleness_days']} days stale")
460
+
461
+ lines.append("")
462
+
463
+ return "\n".join(lines)
464
+
465
+ # ===== Utility =====
466
+
467
+ def _group_by_directory(self, records: list[FileRecord]) -> dict[str, int]:
468
+ """Group records by top-level directory."""
469
+ counts: dict[str, int] = {}
470
+ for r in records:
471
+ parts = r.path.split("/")
472
+ dir_name = parts[0] if len(parts) > 1 else "."
473
+ counts[dir_name] = counts.get(dir_name, 0) + 1
474
+ return counts