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,289 @@
1
+ """Test Generation Report Formatter.
2
+
3
+ Format test generation output as human-readable reports.
4
+
5
+ Copyright 2025 Smart-AI-Memory
6
+ Licensed under Fair Source License 0.9
7
+ """
8
+
9
+ import re
10
+
11
+
12
+ def format_test_gen_report(result: dict, input_data: dict) -> str:
13
+ """Format test generation output as a human-readable report.
14
+
15
+ Args:
16
+ result: The review stage result
17
+ input_data: Input data from previous stages
18
+
19
+ Returns:
20
+ Formatted report string
21
+
22
+ """
23
+ lines = []
24
+
25
+ # Header
26
+ total_tests = result.get("total_tests", 0)
27
+ files_covered = result.get("files_covered", 0)
28
+
29
+ lines.append("=" * 60)
30
+ lines.append("TEST GAP ANALYSIS REPORT")
31
+ lines.append("=" * 60)
32
+ lines.append("")
33
+
34
+ # Summary stats
35
+ total_candidates = input_data.get("total_candidates", 0)
36
+ hotspot_count = input_data.get("hotspot_count", 0)
37
+ untested_count = input_data.get("untested_count", 0)
38
+
39
+ lines.append("-" * 60)
40
+ lines.append("SUMMARY")
41
+ lines.append("-" * 60)
42
+ lines.append(f"Tests Generated: {total_tests}")
43
+ lines.append(f"Files Covered: {files_covered}")
44
+ lines.append(f"Total Candidates: {total_candidates}")
45
+ lines.append(f"Bug Hotspots Found: {hotspot_count}")
46
+ lines.append(f"Untested Files: {untested_count}")
47
+ lines.append("")
48
+
49
+ # Status indicator
50
+ if total_tests == 0:
51
+ lines.append("⚠️ No tests were generated")
52
+ elif total_tests < 5:
53
+ lines.append(f"🟡 Generated {total_tests} test(s) - consider adding more coverage")
54
+ elif total_tests < 20:
55
+ lines.append(f"🟢 Generated {total_tests} tests - good coverage")
56
+ else:
57
+ lines.append(f"✅ Generated {total_tests} tests - excellent coverage")
58
+ lines.append("")
59
+
60
+ # Scope notice for enterprise clarity
61
+ total_source = input_data.get("total_source_files", 0)
62
+ existing_tests = input_data.get("existing_test_files", 0)
63
+ coverage_pct = input_data.get("analysis_coverage_percent", 100)
64
+ large_project = input_data.get("large_project_warning", False)
65
+
66
+ if total_source > 0 or existing_tests > 0:
67
+ lines.append("-" * 60)
68
+ lines.append("SCOPE NOTICE")
69
+ lines.append("-" * 60)
70
+
71
+ if large_project:
72
+ lines.append("⚠️ LARGE PROJECT: Only high-priority files analyzed")
73
+ lines.append(f" Coverage: {coverage_pct:.0f}% of candidate files")
74
+ lines.append("")
75
+
76
+ lines.append(f"Source Files Found: {total_source}")
77
+ lines.append(f"Existing Test Files: {existing_tests}")
78
+ lines.append(f"Files Analyzed: {files_covered}")
79
+
80
+ if existing_tests > 0:
81
+ lines.append("")
82
+ lines.append("Note: This report identifies gaps in untested files.")
83
+ lines.append("Run 'pytest --co -q' for full test suite statistics.")
84
+ lines.append("")
85
+
86
+ # Parse XML review feedback if present
87
+ review = result.get("review_feedback", "")
88
+ xml_summary = ""
89
+ xml_findings = []
90
+ xml_tests = []
91
+ coverage_improvement = ""
92
+
93
+ if review and "<response>" in review:
94
+ # Extract summary
95
+ summary_match = re.search(r"<summary>(.*?)</summary>", review, re.DOTALL)
96
+ if summary_match:
97
+ xml_summary = summary_match.group(1).strip()
98
+
99
+ # Extract coverage improvement
100
+ coverage_match = re.search(
101
+ r"<coverage-improvement>(.*?)</coverage-improvement>",
102
+ review,
103
+ re.DOTALL,
104
+ )
105
+ if coverage_match:
106
+ coverage_improvement = coverage_match.group(1).strip()
107
+
108
+ # Extract findings
109
+ for finding_match in re.finditer(
110
+ r'<finding severity="(\w+)">(.*?)</finding>',
111
+ review,
112
+ re.DOTALL,
113
+ ):
114
+ severity = finding_match.group(1)
115
+ finding_content = finding_match.group(2)
116
+
117
+ title_match = re.search(r"<title>(.*?)</title>", finding_content, re.DOTALL)
118
+ location_match = re.search(r"<location>(.*?)</location>", finding_content, re.DOTALL)
119
+ fix_match = re.search(r"<fix>(.*?)</fix>", finding_content, re.DOTALL)
120
+
121
+ xml_findings.append(
122
+ {
123
+ "severity": severity,
124
+ "title": title_match.group(1).strip() if title_match else "Unknown",
125
+ "location": location_match.group(1).strip() if location_match else "",
126
+ "fix": fix_match.group(1).strip() if fix_match else "",
127
+ },
128
+ )
129
+
130
+ # Extract suggested tests
131
+ for test_match in re.finditer(r'<test target="([^"]+)">(.*?)</test>', review, re.DOTALL):
132
+ target = test_match.group(1)
133
+ test_content = test_match.group(2)
134
+
135
+ type_match = re.search(r"<type>(.*?)</type>", test_content, re.DOTALL)
136
+ desc_match = re.search(r"<description>(.*?)</description>", test_content, re.DOTALL)
137
+
138
+ xml_tests.append(
139
+ {
140
+ "target": target,
141
+ "type": type_match.group(1).strip() if type_match else "unit",
142
+ "description": desc_match.group(1).strip() if desc_match else "",
143
+ },
144
+ )
145
+
146
+ # Show parsed summary
147
+ if xml_summary:
148
+ lines.append("-" * 60)
149
+ lines.append("QUALITY ASSESSMENT")
150
+ lines.append("-" * 60)
151
+ # Word wrap the summary
152
+ words = xml_summary.split()
153
+ current_line = ""
154
+ for word in words:
155
+ if len(current_line) + len(word) + 1 <= 58:
156
+ current_line += (" " if current_line else "") + word
157
+ else:
158
+ lines.append(current_line)
159
+ current_line = word
160
+ if current_line:
161
+ lines.append(current_line)
162
+ lines.append("")
163
+
164
+ if coverage_improvement:
165
+ lines.append(f"📈 {coverage_improvement}")
166
+ lines.append("")
167
+
168
+ # Show findings by severity
169
+ if xml_findings:
170
+ lines.append("-" * 60)
171
+ lines.append("QUALITY FINDINGS")
172
+ lines.append("-" * 60)
173
+
174
+ severity_emoji = {"high": "🔴", "medium": "🟠", "low": "🟡", "info": "🔵"}
175
+ severity_order = {"high": 0, "medium": 1, "low": 2, "info": 3}
176
+
177
+ sorted_findings = sorted(xml_findings, key=lambda f: severity_order.get(f["severity"], 4))
178
+
179
+ for finding in sorted_findings:
180
+ emoji = severity_emoji.get(finding["severity"], "⚪")
181
+ lines.append(f"{emoji} [{finding['severity'].upper()}] {finding['title']}")
182
+ if finding["location"]:
183
+ lines.append(f" Location: {finding['location']}")
184
+ if finding["fix"]:
185
+ # Truncate long fix recommendations
186
+ fix_text = finding["fix"]
187
+ if len(fix_text) > 70:
188
+ fix_text = fix_text[:67] + "..."
189
+ lines.append(f" Fix: {fix_text}")
190
+ lines.append("")
191
+
192
+ # Show suggested tests
193
+ if xml_tests:
194
+ lines.append("-" * 60)
195
+ lines.append("SUGGESTED TESTS TO ADD")
196
+ lines.append("-" * 60)
197
+
198
+ for i, test in enumerate(xml_tests[:5], 1): # Limit to 5
199
+ lines.append(f"{i}. {test['target']} ({test['type']})")
200
+ if test["description"]:
201
+ desc = test["description"]
202
+ if len(desc) > 55:
203
+ desc = desc[:52] + "..."
204
+ lines.append(f" {desc}")
205
+ lines.append("")
206
+
207
+ if len(xml_tests) > 5:
208
+ lines.append(f" ... and {len(xml_tests) - 5} more suggested tests")
209
+ lines.append("")
210
+
211
+ # Generated tests breakdown (if no XML data)
212
+ generated_tests = input_data.get("generated_tests", [])
213
+ if generated_tests and not xml_findings:
214
+ lines.append("-" * 60)
215
+ lines.append("GENERATED TESTS BY FILE")
216
+ lines.append("-" * 60)
217
+ for test_file in generated_tests[:10]: # Limit display
218
+ source = test_file.get("source_file", "unknown")
219
+ test_count = test_file.get("test_count", 0)
220
+ # Shorten path for display
221
+ if len(source) > 50:
222
+ source = "..." + source[-47:]
223
+ lines.append(f" 📁 {source}")
224
+ lines.append(
225
+ f" └─ {test_count} test(s) → {test_file.get('test_file', 'test_*.py')}",
226
+ )
227
+ if len(generated_tests) > 10:
228
+ lines.append(f" ... and {len(generated_tests) - 10} more files")
229
+ lines.append("")
230
+
231
+ # Written files section
232
+ written_files = input_data.get("written_files", [])
233
+ if written_files:
234
+ lines.append("-" * 60)
235
+ lines.append("TESTS WRITTEN TO DISK")
236
+ lines.append("-" * 60)
237
+ for file_path in written_files[:10]:
238
+ # Shorten path for display
239
+ if len(file_path) > 55:
240
+ file_path = "..." + file_path[-52:]
241
+ lines.append(f" ✅ {file_path}")
242
+ if len(written_files) > 10:
243
+ lines.append(f" ... and {len(written_files) - 10} more files")
244
+ lines.append("")
245
+ lines.append(" Run: pytest <file> to execute these tests")
246
+ lines.append("")
247
+ elif input_data.get("tests_written") is False and total_tests > 0:
248
+ lines.append("-" * 60)
249
+ lines.append("GENERATED TESTS (NOT WRITTEN)")
250
+ lines.append("-" * 60)
251
+ lines.append(" ⚠️ Tests were generated but not written to disk.")
252
+ lines.append(" To write tests, run with: write_tests=True")
253
+ lines.append("")
254
+
255
+ # Recommendations
256
+ lines.append("-" * 60)
257
+ lines.append("NEXT STEPS")
258
+ lines.append("-" * 60)
259
+
260
+ high_findings = sum(1 for f in xml_findings if f["severity"] == "high")
261
+ medium_findings = sum(1 for f in xml_findings if f["severity"] == "medium")
262
+
263
+ if high_findings > 0:
264
+ lines.append(f" 🔴 Address {high_findings} high-priority finding(s) first")
265
+
266
+ if medium_findings > 0:
267
+ lines.append(f" 🟠 Review {medium_findings} medium-priority finding(s)")
268
+
269
+ if xml_tests:
270
+ lines.append(f" 📝 Consider adding {len(xml_tests)} suggested test(s)")
271
+
272
+ if hotspot_count > 0:
273
+ lines.append(f" 🔥 {hotspot_count} bug hotspot file(s) need priority testing")
274
+
275
+ if untested_count > 0:
276
+ lines.append(f" 📁 {untested_count} file(s) have no existing tests")
277
+
278
+ if not any([high_findings, medium_findings, xml_tests, hotspot_count, untested_count]):
279
+ lines.append(" ✅ Test suite is in good shape!")
280
+
281
+ lines.append("")
282
+
283
+ # Footer
284
+ lines.append("=" * 60)
285
+ model_tier = result.get("model_tier_used", "unknown")
286
+ lines.append(f"Review completed using {model_tier} tier model")
287
+ lines.append("=" * 60)
288
+
289
+ return "\n".join(lines)
@@ -0,0 +1,381 @@
1
+ """Test Template Generation.
2
+
3
+ Functions to generate pytest test code for functions and classes.
4
+
5
+ Copyright 2025 Smart-AI-Memory
6
+ Licensed under Fair Source License 0.9
7
+ """
8
+
9
+
10
+
11
+ def generate_test_for_function(module: str, func: dict) -> str:
12
+ """Generate executable tests for a function based on AST analysis."""
13
+ name = func["name"]
14
+ params = func.get("params") or [] # List of (name, type, default) tuples, handle None
15
+ param_names = func.get("param_names") or [p[0] if isinstance(p, tuple) else p for p in params]
16
+ is_async = func.get("is_async", False)
17
+ return_type = func.get("return_type")
18
+ raises = func.get("raises") or []
19
+ has_side_effects = func.get("has_side_effects", False)
20
+
21
+ # Generate test values based on parameter types
22
+ test_cases = generate_test_cases_for_params(params)
23
+ param_str = ", ".join(test_cases.get("valid_args", [""] * len(params)))
24
+
25
+ # Build parametrized test if we have multiple test cases
26
+ parametrize_cases = test_cases.get("parametrize_cases", [])
27
+
28
+ tests = []
29
+ tests.append(f"import pytest\nfrom {module} import {name}\n")
30
+
31
+ # Generate parametrized test if we have cases
32
+ if parametrize_cases and len(parametrize_cases) > 1:
33
+ param_names_str = ", ".join(param_names) if param_names else "value"
34
+ cases_str = ",\n ".join(parametrize_cases)
35
+
36
+ if is_async:
37
+ tests.append(
38
+ f'''
39
+ @pytest.mark.parametrize("{param_names_str}", [
40
+ {cases_str},
41
+ ])
42
+ @pytest.mark.asyncio
43
+ async def test_{name}_with_various_inputs({param_names_str}):
44
+ """Test {name} with various input combinations."""
45
+ result = await {name}({", ".join(param_names)})
46
+ assert result is not None
47
+ ''',
48
+ )
49
+ else:
50
+ tests.append(
51
+ f'''
52
+ @pytest.mark.parametrize("{param_names_str}", [
53
+ {cases_str},
54
+ ])
55
+ def test_{name}_with_various_inputs({param_names_str}):
56
+ """Test {name} with various input combinations."""
57
+ result = {name}({", ".join(param_names)})
58
+ assert result is not None
59
+ ''',
60
+ )
61
+ # Simple valid input test
62
+ elif is_async:
63
+ tests.append(
64
+ f'''
65
+ @pytest.mark.asyncio
66
+ async def test_{name}_returns_value():
67
+ """Test that {name} returns a value with valid inputs."""
68
+ result = await {name}({param_str})
69
+ assert result is not None
70
+ ''',
71
+ )
72
+ else:
73
+ tests.append(
74
+ f'''
75
+ def test_{name}_returns_value():
76
+ """Test that {name} returns a value with valid inputs."""
77
+ result = {name}({param_str})
78
+ assert result is not None
79
+ ''',
80
+ )
81
+
82
+ # Generate edge case tests based on parameter types
83
+ edge_cases = test_cases.get("edge_cases", [])
84
+ if edge_cases:
85
+ edge_cases_str = ",\n ".join(edge_cases)
86
+ if is_async:
87
+ tests.append(
88
+ f'''
89
+ @pytest.mark.parametrize("edge_input", [
90
+ {edge_cases_str},
91
+ ])
92
+ @pytest.mark.asyncio
93
+ async def test_{name}_edge_cases(edge_input):
94
+ """Test {name} with edge case inputs."""
95
+ try:
96
+ result = await {name}(edge_input)
97
+ # Function should either return a value or raise an expected error
98
+ assert result is not None or result == 0 or result == "" or result == []
99
+ except (ValueError, TypeError, KeyError) as e:
100
+ # Expected error for edge cases
101
+ assert str(e) # Error message should not be empty
102
+ ''',
103
+ )
104
+ else:
105
+ tests.append(
106
+ f'''
107
+ @pytest.mark.parametrize("edge_input", [
108
+ {edge_cases_str},
109
+ ])
110
+ def test_{name}_edge_cases(edge_input):
111
+ """Test {name} with edge case inputs."""
112
+ try:
113
+ result = {name}(edge_input)
114
+ # Function should either return a value or raise an expected error
115
+ assert result is not None or result == 0 or result == "" or result == []
116
+ except (ValueError, TypeError, KeyError) as e:
117
+ # Expected error for edge cases
118
+ assert str(e) # Error message should not be empty
119
+ ''',
120
+ )
121
+
122
+ # Generate exception tests for each raised exception
123
+ for exc_type in raises[:3]: # Limit to 3 exception types
124
+ if is_async:
125
+ tests.append(
126
+ f'''
127
+ @pytest.mark.asyncio
128
+ async def test_{name}_raises_{exc_type.lower()}():
129
+ """Test that {name} raises {exc_type} for invalid inputs."""
130
+ with pytest.raises({exc_type}):
131
+ await {name}(None) # Adjust input to trigger {exc_type}
132
+ ''',
133
+ )
134
+ else:
135
+ tests.append(
136
+ f'''
137
+ def test_{name}_raises_{exc_type.lower()}():
138
+ """Test that {name} raises {exc_type} for invalid inputs."""
139
+ with pytest.raises({exc_type}):
140
+ {name}(None) # Adjust input to trigger {exc_type}
141
+ ''',
142
+ )
143
+
144
+ # Add return type assertion if we know the type
145
+ if return_type and return_type not in ("None", "Any"):
146
+ type_check = get_type_assertion(return_type)
147
+ if type_check and not has_side_effects:
148
+ if is_async:
149
+ tests.append(
150
+ f'''
151
+ @pytest.mark.asyncio
152
+ async def test_{name}_returns_correct_type():
153
+ """Test that {name} returns the expected type."""
154
+ result = await {name}({param_str})
155
+ {type_check}
156
+ ''',
157
+ )
158
+ else:
159
+ tests.append(
160
+ f'''
161
+ def test_{name}_returns_correct_type():
162
+ """Test that {name} returns the expected type."""
163
+ result = {name}({param_str})
164
+ {type_check}
165
+ ''',
166
+ )
167
+
168
+ return "\n".join(tests)
169
+
170
+
171
+ def generate_test_cases_for_params(params: list) -> dict:
172
+ """Generate test cases based on parameter types."""
173
+ valid_args = []
174
+ parametrize_cases = []
175
+ edge_cases = []
176
+
177
+ for param in params:
178
+ if isinstance(param, tuple) and len(param) >= 2:
179
+ _name, type_hint, default = param[0], param[1], param[2] if len(param) > 2 else None
180
+ else:
181
+ _name = param if isinstance(param, str) else str(param)
182
+ type_hint = "Any"
183
+ default = None
184
+
185
+ # Generate valid value based on type
186
+ if "str" in type_hint.lower():
187
+ valid_args.append('"test_value"')
188
+ parametrize_cases.extend(['"hello"', '"world"', '"test_string"'])
189
+ edge_cases.extend(['""', '" "', '"a" * 1000'])
190
+ elif "int" in type_hint.lower():
191
+ valid_args.append("42")
192
+ parametrize_cases.extend(["0", "1", "100", "-1"])
193
+ edge_cases.extend(["0", "-1", "2**31 - 1"])
194
+ elif "float" in type_hint.lower():
195
+ valid_args.append("3.14")
196
+ parametrize_cases.extend(["0.0", "1.0", "-1.5", "100.5"])
197
+ edge_cases.extend(["0.0", "-0.0", "float('inf')"])
198
+ elif "bool" in type_hint.lower():
199
+ valid_args.append("True")
200
+ parametrize_cases.extend(["True", "False"])
201
+ elif "list" in type_hint.lower():
202
+ valid_args.append("[1, 2, 3]")
203
+ parametrize_cases.extend(["[]", "[1]", "[1, 2, 3]"])
204
+ edge_cases.extend(["[]", "[None]"])
205
+ elif "dict" in type_hint.lower():
206
+ valid_args.append('{"key": "value"}')
207
+ parametrize_cases.extend(["{}", '{"a": 1}', '{"key": "value"}'])
208
+ edge_cases.extend(["{}"])
209
+ elif default is not None:
210
+ valid_args.append(str(default))
211
+ else:
212
+ valid_args.append("None")
213
+ edge_cases.append("None")
214
+
215
+ return {
216
+ "valid_args": valid_args,
217
+ "parametrize_cases": parametrize_cases[:5], # Limit cases
218
+ "edge_cases": list(dict.fromkeys(edge_cases))[
219
+ :5
220
+ ], # Unique edge cases (preserves order)
221
+ }
222
+
223
+
224
+ def get_type_assertion(return_type: str) -> str | None:
225
+ """Generate assertion for return type checking."""
226
+ type_map = {
227
+ "str": "assert isinstance(result, str)",
228
+ "int": "assert isinstance(result, int)",
229
+ "float": "assert isinstance(result, (int, float))",
230
+ "bool": "assert isinstance(result, bool)",
231
+ "list": "assert isinstance(result, list)",
232
+ "dict": "assert isinstance(result, dict)",
233
+ "tuple": "assert isinstance(result, tuple)",
234
+ }
235
+ for type_name, assertion in type_map.items():
236
+ if type_name in return_type.lower():
237
+ return assertion
238
+ return None
239
+
240
+
241
+ def get_param_test_values(type_hint: str) -> list[str]:
242
+ """Get test values for a single parameter based on its type."""
243
+ type_hint_lower = type_hint.lower()
244
+ if "str" in type_hint_lower:
245
+ return ['"hello"', '"world"', '"test_string"']
246
+ if "int" in type_hint_lower:
247
+ return ["0", "1", "42", "-1"]
248
+ if "float" in type_hint_lower:
249
+ return ["0.0", "1.0", "3.14"]
250
+ if "bool" in type_hint_lower:
251
+ return ["True", "False"]
252
+ if "list" in type_hint_lower:
253
+ return ["[]", "[1, 2, 3]"]
254
+ if "dict" in type_hint_lower:
255
+ return ["{}", '{"key": "value"}']
256
+ return ['"test_value"']
257
+
258
+
259
+ def generate_test_for_class(module: str, cls: dict) -> str:
260
+ """Generate executable test class based on AST analysis."""
261
+ name = cls["name"]
262
+ init_params = cls.get("init_params", [])
263
+ methods = cls.get("methods", [])
264
+ required_params = cls.get("required_init_params", 0)
265
+ _docstring = cls.get("docstring", "") # Reserved for future use
266
+
267
+ # Generate constructor arguments - ensure we have values for ALL required params
268
+ init_args = generate_test_cases_for_params(init_params)
269
+ valid_args = init_args.get("valid_args", [])
270
+
271
+ # Ensure we have enough args for required params
272
+ while len(valid_args) < required_params:
273
+ valid_args.append('"test_value"')
274
+
275
+ init_arg_str = ", ".join(valid_args)
276
+
277
+ tests = []
278
+ tests.append(f"import pytest\nfrom {module} import {name}\n")
279
+
280
+ # Fixture for class instance
281
+ tests.append(
282
+ f'''
283
+ @pytest.fixture
284
+ def {name.lower()}_instance():
285
+ """Create a {name} instance for testing."""
286
+ return {name}({init_arg_str})
287
+ ''',
288
+ )
289
+
290
+ # Test initialization
291
+ tests.append(
292
+ f'''
293
+ class Test{name}:
294
+ """Tests for {name} class."""
295
+
296
+ def test_initialization(self):
297
+ """Test that {name} can be instantiated."""
298
+ instance = {name}({init_arg_str})
299
+ assert instance is not None
300
+ ''',
301
+ )
302
+
303
+ # Only generate parametrized tests for single-param classes to avoid tuple mismatches
304
+ if len(init_params) == 1 and init_params[0][2] is None:
305
+ # Single required param - safe to parametrize
306
+ param_name = init_params[0][0]
307
+ param_type = init_params[0][1]
308
+ cases = get_param_test_values(param_type)
309
+ if len(cases) > 1:
310
+ cases_str = ",\n ".join(cases)
311
+ tests.append(
312
+ f'''
313
+ @pytest.mark.parametrize("{param_name}", [
314
+ {cases_str},
315
+ ])
316
+ def test_initialization_with_various_args(self, {param_name}):
317
+ """Test {name} initialization with various arguments."""
318
+ instance = {name}({param_name})
319
+ assert instance is not None
320
+ ''',
321
+ )
322
+
323
+ # Generate tests for each public method
324
+ for method in methods[:5]: # Limit to 5 methods
325
+ method_name = method.get("name", "")
326
+ if method_name.startswith("_") and method_name != "__init__":
327
+ continue
328
+ if method_name == "__init__":
329
+ continue
330
+
331
+ method_params = method.get("params", [])[1:] # Skip self
332
+ is_async = method.get("is_async", False)
333
+ raises = method.get("raises", [])
334
+
335
+ # Generate method call args
336
+ method_args = generate_test_cases_for_params(method_params)
337
+ method_arg_str = ", ".join(method_args.get("valid_args", []))
338
+
339
+ if is_async:
340
+ tests.append(
341
+ f'''
342
+ @pytest.mark.asyncio
343
+ async def test_{method_name}_returns_value(self, {name.lower()}_instance):
344
+ """Test that {method_name} returns a value."""
345
+ result = await {name.lower()}_instance.{method_name}({method_arg_str})
346
+ assert result is not None or result == 0 or result == "" or result == []
347
+ ''',
348
+ )
349
+ else:
350
+ tests.append(
351
+ f'''
352
+ def test_{method_name}_returns_value(self, {name.lower()}_instance):
353
+ """Test that {method_name} returns a value."""
354
+ result = {name.lower()}_instance.{method_name}({method_arg_str})
355
+ assert result is not None or result == 0 or result == "" or result == []
356
+ ''',
357
+ )
358
+
359
+ # Add exception tests for methods that raise
360
+ for exc_type in raises[:2]:
361
+ if is_async:
362
+ tests.append(
363
+ f'''
364
+ @pytest.mark.asyncio
365
+ async def test_{method_name}_raises_{exc_type.lower()}(self, {name.lower()}_instance):
366
+ """Test that {method_name} raises {exc_type} for invalid inputs."""
367
+ with pytest.raises({exc_type}):
368
+ await {name.lower()}_instance.{method_name}(None)
369
+ ''',
370
+ )
371
+ else:
372
+ tests.append(
373
+ f'''
374
+ def test_{method_name}_raises_{exc_type.lower()}(self, {name.lower()}_instance):
375
+ """Test that {method_name} raises {exc_type} for invalid inputs."""
376
+ with pytest.raises({exc_type}):
377
+ {name.lower()}_instance.{method_name}(None)
378
+ ''',
379
+ )
380
+
381
+ return "\n".join(tests)