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,958 @@
1
+ """Web UI Components for Socratic Workflow Builder
2
+
3
+ Provides components for rendering Socratic forms in web interfaces:
4
+ - React component schemas
5
+ - HTML template rendering
6
+ - API endpoint helpers
7
+ - WebSocket support for real-time sessions
8
+
9
+ Copyright 2026 Smart-AI-Memory
10
+ Licensed under Fair Source License 0.9
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from dataclasses import asdict, dataclass
17
+ from typing import Any
18
+
19
+ from .blueprint import WorkflowBlueprint
20
+ from .forms import FieldType, Form, FormField
21
+ from .session import SocraticSession
22
+
23
+ # =============================================================================
24
+ # REACT COMPONENT SCHEMAS
25
+ # =============================================================================
26
+
27
+
28
+ @dataclass
29
+ class ReactFormSchema:
30
+ """Schema for rendering forms in React.
31
+
32
+ Can be directly consumed by a React frontend to render
33
+ the Socratic form with appropriate components.
34
+ """
35
+
36
+ form_id: str
37
+ title: str
38
+ description: str
39
+ progress: float
40
+ round_number: int
41
+ is_final: bool
42
+ fields: list[dict[str, Any]]
43
+ categories: list[str]
44
+
45
+ @classmethod
46
+ def from_form(cls, form: Form) -> ReactFormSchema:
47
+ """Create schema from a Form object."""
48
+ fields = []
49
+
50
+ for f in form.fields:
51
+ field_schema = {
52
+ "id": f.id,
53
+ "type": _field_type_to_component(f.field_type),
54
+ "label": f.label,
55
+ "helpText": f.help_text,
56
+ "placeholder": f.placeholder,
57
+ "default": f.default,
58
+ "category": f.category,
59
+ "required": f.validation.required,
60
+ "validation": {
61
+ "minLength": f.validation.min_length,
62
+ "maxLength": f.validation.max_length,
63
+ "minValue": f.validation.min_value,
64
+ "maxValue": f.validation.max_value,
65
+ "pattern": f.validation.pattern,
66
+ },
67
+ "showWhen": f.show_when,
68
+ "options": (
69
+ [
70
+ {
71
+ "value": o.value,
72
+ "label": o.label,
73
+ "description": o.description,
74
+ "icon": o.icon,
75
+ "recommended": o.recommended,
76
+ }
77
+ for o in f.options
78
+ ]
79
+ if f.options
80
+ else None
81
+ ),
82
+ }
83
+ fields.append(field_schema)
84
+
85
+ return cls(
86
+ form_id=form.id,
87
+ title=form.title,
88
+ description=form.description,
89
+ progress=form.progress,
90
+ round_number=form.round_number,
91
+ is_final=form.is_final,
92
+ fields=fields,
93
+ categories=form.categories or list(dict.fromkeys(f.category for f in form.fields)),
94
+ )
95
+
96
+ def to_json(self) -> str:
97
+ """Serialize to JSON for API response."""
98
+ return json.dumps(asdict(self), indent=2)
99
+
100
+
101
+ def _field_type_to_component(field_type: FieldType) -> str:
102
+ """Map FieldType to React component name."""
103
+ mapping = {
104
+ FieldType.SINGLE_SELECT: "RadioGroup",
105
+ FieldType.MULTI_SELECT: "CheckboxGroup",
106
+ FieldType.TEXT: "TextInput",
107
+ FieldType.TEXT_AREA: "TextArea",
108
+ FieldType.SLIDER: "Slider",
109
+ FieldType.BOOLEAN: "Switch",
110
+ FieldType.NUMBER: "NumberInput",
111
+ FieldType.GROUP: "FieldGroup",
112
+ }
113
+ return mapping.get(field_type, "TextInput")
114
+
115
+
116
+ @dataclass
117
+ class ReactSessionSchema:
118
+ """Schema for session state in React."""
119
+
120
+ session_id: str
121
+ state: str
122
+ goal: str
123
+ domain: str | None
124
+ confidence: float
125
+ current_round: int
126
+ requirements_completeness: float
127
+ ready_to_generate: bool
128
+ ambiguities: list[str]
129
+ assumptions: list[str]
130
+
131
+ @classmethod
132
+ def from_session(cls, session: SocraticSession) -> ReactSessionSchema:
133
+ """Create schema from a SocraticSession."""
134
+ return cls(
135
+ session_id=session.session_id,
136
+ state=session.state.value,
137
+ goal=session.goal,
138
+ domain=session.goal_analysis.domain if session.goal_analysis else None,
139
+ confidence=session.goal_analysis.confidence if session.goal_analysis else 0,
140
+ current_round=session.current_round,
141
+ requirements_completeness=session.requirements.completeness_score(),
142
+ ready_to_generate=session.can_generate(),
143
+ ambiguities=session.goal_analysis.ambiguities if session.goal_analysis else [],
144
+ assumptions=session.goal_analysis.assumptions if session.goal_analysis else [],
145
+ )
146
+
147
+ def to_json(self) -> str:
148
+ """Serialize to JSON."""
149
+ return json.dumps(asdict(self), indent=2)
150
+
151
+
152
+ @dataclass
153
+ class ReactBlueprintSchema:
154
+ """Schema for blueprint display in React."""
155
+
156
+ id: str
157
+ name: str
158
+ description: str
159
+ domain: str
160
+ languages: list[str]
161
+ quality_focus: list[str]
162
+ automation_level: str
163
+ agents: list[dict[str, Any]]
164
+ stages: list[dict[str, Any]]
165
+ success_criteria: dict[str, Any] | None
166
+
167
+ @classmethod
168
+ def from_blueprint(cls, blueprint: WorkflowBlueprint) -> ReactBlueprintSchema:
169
+ """Create schema from a WorkflowBlueprint."""
170
+ agents = []
171
+ for agent in blueprint.agents:
172
+ agents.append(
173
+ {
174
+ "id": agent.spec.id,
175
+ "name": agent.spec.name,
176
+ "role": agent.spec.role.value,
177
+ "goal": agent.spec.goal,
178
+ "backstory": (
179
+ agent.spec.backstory[:200] + "..."
180
+ if len(agent.spec.backstory) > 200
181
+ else agent.spec.backstory
182
+ ),
183
+ "modelTier": agent.spec.model_tier,
184
+ "tools": [t.name for t in agent.spec.tools],
185
+ }
186
+ )
187
+
188
+ stages = []
189
+ for stage in blueprint.stages:
190
+ stages.append(
191
+ {
192
+ "id": stage.id,
193
+ "name": stage.name,
194
+ "description": stage.description,
195
+ "agents": stage.agent_ids,
196
+ "parallel": stage.parallel,
197
+ "dependsOn": stage.depends_on,
198
+ }
199
+ )
200
+
201
+ success_criteria = None
202
+ if blueprint.success_criteria:
203
+ success_criteria = blueprint.success_criteria.to_dict()
204
+
205
+ return cls(
206
+ id=blueprint.id,
207
+ name=blueprint.name,
208
+ description=blueprint.description,
209
+ domain=blueprint.domain,
210
+ languages=blueprint.supported_languages,
211
+ quality_focus=blueprint.quality_focus,
212
+ automation_level=blueprint.automation_level,
213
+ agents=agents,
214
+ stages=stages,
215
+ success_criteria=success_criteria,
216
+ )
217
+
218
+ def to_json(self) -> str:
219
+ """Serialize to JSON."""
220
+ return json.dumps(asdict(self), indent=2)
221
+
222
+
223
+ # =============================================================================
224
+ # HTML TEMPLATE RENDERING
225
+ # =============================================================================
226
+
227
+
228
+ def render_form_html(form: Form, action_url: str = "/api/socratic/submit") -> str:
229
+ """Render a form as HTML.
230
+
231
+ Args:
232
+ form: Form to render
233
+ action_url: Form submission URL
234
+
235
+ Returns:
236
+ HTML string
237
+ """
238
+ html_parts = [
239
+ f'<form id="{form.id}" action="{action_url}" method="POST" class="socratic-form">',
240
+ ' <div class="form-header">',
241
+ f" <h2>{_escape_html(form.title)}</h2>",
242
+ f' <p class="form-description">{_escape_html(form.description)}</p>',
243
+ ' <div class="progress-bar">',
244
+ f' <div class="progress-fill" style="width: {form.progress * 100}%"></div>',
245
+ f' <span class="progress-text">{form.progress:.0%}</span>',
246
+ " </div>",
247
+ " </div>",
248
+ ' <div class="form-fields">',
249
+ ]
250
+
251
+ # Group fields by category
252
+ fields_by_category = form.get_fields_by_category()
253
+
254
+ for category, fields in fields_by_category.items():
255
+ if len(fields_by_category) > 1:
256
+ html_parts.append(f' <fieldset class="field-category" data-category="{category}">')
257
+ html_parts.append(f" <legend>{category.title()}</legend>")
258
+
259
+ for field in fields:
260
+ html_parts.append(_render_field_html(field))
261
+
262
+ if len(fields_by_category) > 1:
263
+ html_parts.append(" </fieldset>")
264
+
265
+ html_parts.extend(
266
+ [
267
+ " </div>",
268
+ ' <div class="form-actions">',
269
+ ' <button type="submit" class="btn-primary">Continue</button>',
270
+ " </div>",
271
+ "</form>",
272
+ ]
273
+ )
274
+
275
+ return "\n".join(html_parts)
276
+
277
+
278
+ def _render_field_html(field: FormField) -> str:
279
+ """Render a single field as HTML."""
280
+ required = "required" if field.validation.required else ""
281
+ required_indicator = '<span class="required">*</span>' if field.validation.required else ""
282
+
283
+ # Show when data attribute
284
+ show_when = ""
285
+ if field.show_when:
286
+ show_when = f" data-show-when='{json.dumps(field.show_when)}'"
287
+
288
+ parts = [
289
+ f' <div class="form-field" data-field-id="{field.id}"{show_when}>',
290
+ f' <label for="{field.id}">{_escape_html(field.label)}{required_indicator}</label>',
291
+ ]
292
+
293
+ if field.help_text:
294
+ parts.append(f' <p class="help-text">{_escape_html(field.help_text)}</p>')
295
+
296
+ # Render input based on type
297
+ if field.field_type == FieldType.SINGLE_SELECT:
298
+ parts.append(' <div class="radio-group">')
299
+ for opt in field.options:
300
+ rec_class = " recommended" if opt.recommended else ""
301
+ parts.append(f' <label class="radio-option{rec_class}">')
302
+ parts.append(
303
+ f' <input type="radio" name="{field.id}" value="{opt.value}" {required}>'
304
+ )
305
+ parts.append(f' <span class="option-label">{_escape_html(opt.label)}</span>')
306
+ if opt.description:
307
+ parts.append(
308
+ f' <span class="option-desc">{_escape_html(opt.description)}</span>'
309
+ )
310
+ parts.append(" </label>")
311
+ parts.append(" </div>")
312
+
313
+ elif field.field_type == FieldType.MULTI_SELECT:
314
+ parts.append(' <div class="checkbox-group">')
315
+ for opt in field.options:
316
+ rec_class = " recommended" if opt.recommended else ""
317
+ parts.append(f' <label class="checkbox-option{rec_class}">')
318
+ parts.append(f' <input type="checkbox" name="{field.id}" value="{opt.value}">')
319
+ parts.append(f' <span class="option-label">{_escape_html(opt.label)}</span>')
320
+ if opt.description:
321
+ parts.append(
322
+ f' <span class="option-desc">{_escape_html(opt.description)}</span>'
323
+ )
324
+ parts.append(" </label>")
325
+ parts.append(" </div>")
326
+
327
+ elif field.field_type == FieldType.TEXT_AREA:
328
+ max_len = (
329
+ f' maxlength="{field.validation.max_length}"' if field.validation.max_length else ""
330
+ )
331
+ parts.append(
332
+ f' <textarea id="{field.id}" name="{field.id}" placeholder="{_escape_html(field.placeholder)}"{max_len} {required}></textarea>'
333
+ )
334
+
335
+ elif field.field_type == FieldType.BOOLEAN:
336
+ parts.append(' <div class="switch-container">')
337
+ parts.append(' <label class="switch">')
338
+ parts.append(
339
+ f' <input type="checkbox" id="{field.id}" name="{field.id}" value="true">'
340
+ )
341
+ parts.append(' <span class="slider"></span>')
342
+ parts.append(" </label>")
343
+ parts.append(" </div>")
344
+
345
+ elif field.field_type == FieldType.SLIDER:
346
+ min_val = field.validation.min_value or 0
347
+ max_val = field.validation.max_value or 100
348
+ parts.append(' <div class="slider-container">')
349
+ parts.append(
350
+ f' <input type="range" id="{field.id}" name="{field.id}" min="{min_val}" max="{max_val}">'
351
+ )
352
+ parts.append(f' <output for="{field.id}"></output>')
353
+ parts.append(" </div>")
354
+
355
+ else: # TEXT, NUMBER
356
+ input_type = "number" if field.field_type == FieldType.NUMBER else "text"
357
+ max_len = (
358
+ f' maxlength="{field.validation.max_length}"' if field.validation.max_length else ""
359
+ )
360
+ parts.append(
361
+ f' <input type="{input_type}" id="{field.id}" name="{field.id}" placeholder="{_escape_html(field.placeholder)}"{max_len} {required}>'
362
+ )
363
+
364
+ parts.append(" </div>")
365
+
366
+ return "\n".join(parts)
367
+
368
+
369
+ def _escape_html(text: str) -> str:
370
+ """Escape HTML special characters."""
371
+ return (
372
+ text.replace("&", "&amp;")
373
+ .replace("<", "&lt;")
374
+ .replace(">", "&gt;")
375
+ .replace('"', "&quot;")
376
+ .replace("'", "&#x27;")
377
+ )
378
+
379
+
380
+ # =============================================================================
381
+ # CSS STYLES
382
+ # =============================================================================
383
+
384
+
385
+ FORM_CSS = """
386
+ /* Socratic Form Styles */
387
+
388
+ .socratic-form {
389
+ max-width: 800px;
390
+ margin: 0 auto;
391
+ padding: 2rem;
392
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
393
+ }
394
+
395
+ .form-header {
396
+ margin-bottom: 2rem;
397
+ }
398
+
399
+ .form-header h2 {
400
+ margin: 0 0 0.5rem 0;
401
+ font-size: 1.75rem;
402
+ color: #1a1a2e;
403
+ }
404
+
405
+ .form-description {
406
+ color: #666;
407
+ margin: 0 0 1rem 0;
408
+ }
409
+
410
+ .progress-bar {
411
+ background: #e0e0e0;
412
+ border-radius: 10px;
413
+ height: 20px;
414
+ position: relative;
415
+ overflow: hidden;
416
+ }
417
+
418
+ .progress-fill {
419
+ background: linear-gradient(90deg, #4CAF50, #8BC34A);
420
+ height: 100%;
421
+ border-radius: 10px;
422
+ transition: width 0.3s ease;
423
+ }
424
+
425
+ .progress-text {
426
+ position: absolute;
427
+ right: 10px;
428
+ top: 50%;
429
+ transform: translateY(-50%);
430
+ font-size: 0.75rem;
431
+ font-weight: 600;
432
+ color: #333;
433
+ }
434
+
435
+ .form-fields {
436
+ display: flex;
437
+ flex-direction: column;
438
+ gap: 1.5rem;
439
+ }
440
+
441
+ .form-field {
442
+ background: #f8f9fa;
443
+ padding: 1.25rem;
444
+ border-radius: 8px;
445
+ border: 1px solid #e0e0e0;
446
+ }
447
+
448
+ .form-field label {
449
+ display: block;
450
+ font-weight: 600;
451
+ margin-bottom: 0.5rem;
452
+ color: #1a1a2e;
453
+ }
454
+
455
+ .form-field .required {
456
+ color: #e53935;
457
+ margin-left: 0.25rem;
458
+ }
459
+
460
+ .help-text {
461
+ font-size: 0.875rem;
462
+ color: #666;
463
+ margin: 0 0 0.75rem 0;
464
+ }
465
+
466
+ /* Text inputs */
467
+ input[type="text"],
468
+ input[type="number"],
469
+ textarea {
470
+ width: 100%;
471
+ padding: 0.75rem;
472
+ border: 1px solid #ccc;
473
+ border-radius: 6px;
474
+ font-size: 1rem;
475
+ transition: border-color 0.2s, box-shadow 0.2s;
476
+ }
477
+
478
+ input[type="text"]:focus,
479
+ input[type="number"]:focus,
480
+ textarea:focus {
481
+ outline: none;
482
+ border-color: #4CAF50;
483
+ box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
484
+ }
485
+
486
+ textarea {
487
+ min-height: 100px;
488
+ resize: vertical;
489
+ }
490
+
491
+ /* Radio and checkbox groups */
492
+ .radio-group,
493
+ .checkbox-group {
494
+ display: flex;
495
+ flex-direction: column;
496
+ gap: 0.75rem;
497
+ }
498
+
499
+ .radio-option,
500
+ .checkbox-option {
501
+ display: flex;
502
+ align-items: flex-start;
503
+ gap: 0.75rem;
504
+ padding: 0.75rem;
505
+ background: white;
506
+ border: 1px solid #e0e0e0;
507
+ border-radius: 6px;
508
+ cursor: pointer;
509
+ transition: border-color 0.2s, background 0.2s;
510
+ }
511
+
512
+ .radio-option:hover,
513
+ .checkbox-option:hover {
514
+ border-color: #4CAF50;
515
+ background: #f0f7f0;
516
+ }
517
+
518
+ .radio-option.recommended,
519
+ .checkbox-option.recommended {
520
+ border-color: #4CAF50;
521
+ }
522
+
523
+ .radio-option input,
524
+ .checkbox-option input {
525
+ margin-top: 0.25rem;
526
+ }
527
+
528
+ .option-label {
529
+ font-weight: 500;
530
+ color: #1a1a2e;
531
+ }
532
+
533
+ .option-desc {
534
+ display: block;
535
+ font-size: 0.875rem;
536
+ color: #666;
537
+ margin-top: 0.25rem;
538
+ }
539
+
540
+ /* Switch/toggle */
541
+ .switch-container {
542
+ display: flex;
543
+ align-items: center;
544
+ }
545
+
546
+ .switch {
547
+ position: relative;
548
+ width: 50px;
549
+ height: 28px;
550
+ }
551
+
552
+ .switch input {
553
+ opacity: 0;
554
+ width: 0;
555
+ height: 0;
556
+ }
557
+
558
+ .switch .slider {
559
+ position: absolute;
560
+ cursor: pointer;
561
+ top: 0;
562
+ left: 0;
563
+ right: 0;
564
+ bottom: 0;
565
+ background-color: #ccc;
566
+ border-radius: 28px;
567
+ transition: 0.3s;
568
+ }
569
+
570
+ .switch .slider:before {
571
+ position: absolute;
572
+ content: "";
573
+ height: 22px;
574
+ width: 22px;
575
+ left: 3px;
576
+ bottom: 3px;
577
+ background-color: white;
578
+ border-radius: 50%;
579
+ transition: 0.3s;
580
+ }
581
+
582
+ .switch input:checked + .slider {
583
+ background-color: #4CAF50;
584
+ }
585
+
586
+ .switch input:checked + .slider:before {
587
+ transform: translateX(22px);
588
+ }
589
+
590
+ /* Slider/range */
591
+ .slider-container {
592
+ display: flex;
593
+ align-items: center;
594
+ gap: 1rem;
595
+ }
596
+
597
+ .slider-container input[type="range"] {
598
+ flex: 1;
599
+ height: 6px;
600
+ -webkit-appearance: none;
601
+ background: #e0e0e0;
602
+ border-radius: 3px;
603
+ }
604
+
605
+ .slider-container input[type="range"]::-webkit-slider-thumb {
606
+ -webkit-appearance: none;
607
+ width: 20px;
608
+ height: 20px;
609
+ background: #4CAF50;
610
+ border-radius: 50%;
611
+ cursor: pointer;
612
+ }
613
+
614
+ /* Category fieldsets */
615
+ .field-category {
616
+ border: none;
617
+ padding: 0;
618
+ margin: 0;
619
+ }
620
+
621
+ .field-category legend {
622
+ font-size: 1.25rem;
623
+ font-weight: 600;
624
+ color: #1a1a2e;
625
+ padding: 0;
626
+ margin-bottom: 1rem;
627
+ }
628
+
629
+ /* Form actions */
630
+ .form-actions {
631
+ margin-top: 2rem;
632
+ display: flex;
633
+ justify-content: flex-end;
634
+ gap: 1rem;
635
+ }
636
+
637
+ .btn-primary {
638
+ background: linear-gradient(90deg, #4CAF50, #45a049);
639
+ color: white;
640
+ border: none;
641
+ padding: 0.875rem 2rem;
642
+ font-size: 1rem;
643
+ font-weight: 600;
644
+ border-radius: 6px;
645
+ cursor: pointer;
646
+ transition: transform 0.2s, box-shadow 0.2s;
647
+ }
648
+
649
+ .btn-primary:hover {
650
+ transform: translateY(-1px);
651
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
652
+ }
653
+
654
+ .btn-primary:active {
655
+ transform: translateY(0);
656
+ }
657
+
658
+ /* Conditional field visibility */
659
+ .form-field[data-show-when] {
660
+ display: none;
661
+ }
662
+
663
+ .form-field[data-show-when].visible {
664
+ display: block;
665
+ }
666
+ """
667
+
668
+
669
+ # =============================================================================
670
+ # JAVASCRIPT FOR FORM INTERACTIVITY
671
+ # =============================================================================
672
+
673
+
674
+ FORM_JS = """
675
+ // Socratic Form Interactivity
676
+
677
+ class SocraticForm {
678
+ constructor(formElement) {
679
+ this.form = formElement;
680
+ this.fields = {};
681
+ this.init();
682
+ }
683
+
684
+ init() {
685
+ // Index all fields
686
+ this.form.querySelectorAll('.form-field').forEach(field => {
687
+ const fieldId = field.dataset.fieldId;
688
+ this.fields[fieldId] = {
689
+ element: field,
690
+ showWhen: field.dataset.showWhen ? JSON.parse(field.dataset.showWhen) : null,
691
+ };
692
+ });
693
+
694
+ // Add change listeners
695
+ this.form.addEventListener('change', (e) => this.handleChange(e));
696
+
697
+ // Initial visibility check
698
+ this.updateVisibility();
699
+
700
+ // Slider output sync
701
+ this.form.querySelectorAll('input[type="range"]').forEach(slider => {
702
+ const output = slider.nextElementSibling;
703
+ if (output && output.tagName === 'OUTPUT') {
704
+ output.textContent = slider.value;
705
+ slider.addEventListener('input', () => {
706
+ output.textContent = slider.value;
707
+ });
708
+ }
709
+ });
710
+ }
711
+
712
+ handleChange(event) {
713
+ this.updateVisibility();
714
+ }
715
+
716
+ getValues() {
717
+ const values = {};
718
+ const formData = new FormData(this.form);
719
+
720
+ for (const [key, value] of formData.entries()) {
721
+ if (values[key]) {
722
+ // Multi-select: convert to array
723
+ if (!Array.isArray(values[key])) {
724
+ values[key] = [values[key]];
725
+ }
726
+ values[key].push(value);
727
+ } else {
728
+ values[key] = value;
729
+ }
730
+ }
731
+
732
+ return values;
733
+ }
734
+
735
+ updateVisibility() {
736
+ const values = this.getValues();
737
+
738
+ Object.entries(this.fields).forEach(([fieldId, field]) => {
739
+ if (!field.showWhen) {
740
+ field.element.classList.add('visible');
741
+ return;
742
+ }
743
+
744
+ const shouldShow = this.evaluateCondition(field.showWhen, values);
745
+ field.element.classList.toggle('visible', shouldShow);
746
+
747
+ // Disable hidden inputs to exclude from submission
748
+ const inputs = field.element.querySelectorAll('input, textarea, select');
749
+ inputs.forEach(input => {
750
+ input.disabled = !shouldShow;
751
+ });
752
+ });
753
+ }
754
+
755
+ evaluateCondition(condition, values) {
756
+ for (const [fieldId, expected] of Object.entries(condition)) {
757
+ const actual = values[fieldId];
758
+
759
+ if (Array.isArray(expected)) {
760
+ // Any of condition
761
+ if (Array.isArray(actual)) {
762
+ if (!actual.some(v => expected.includes(v))) return false;
763
+ } else {
764
+ if (!expected.includes(actual)) return false;
765
+ }
766
+ } else {
767
+ // Exact match
768
+ if (Array.isArray(actual)) {
769
+ if (!actual.includes(expected)) return false;
770
+ } else {
771
+ if (actual !== expected) return false;
772
+ }
773
+ }
774
+ }
775
+ return true;
776
+ }
777
+ }
778
+
779
+ // Auto-initialize forms
780
+ document.addEventListener('DOMContentLoaded', () => {
781
+ document.querySelectorAll('.socratic-form').forEach(form => {
782
+ new SocraticForm(form);
783
+ });
784
+ });
785
+
786
+ // Async form submission
787
+ async function submitSocraticForm(form, url) {
788
+ const socraticForm = form._socraticForm || new SocraticForm(form);
789
+ const values = socraticForm.getValues();
790
+
791
+ try {
792
+ const response = await fetch(url, {
793
+ method: 'POST',
794
+ headers: {
795
+ 'Content-Type': 'application/json',
796
+ },
797
+ body: JSON.stringify(values),
798
+ });
799
+
800
+ if (!response.ok) {
801
+ throw new Error(`HTTP ${response.status}`);
802
+ }
803
+
804
+ return await response.json();
805
+ } catch (error) {
806
+ console.error('Form submission failed:', error);
807
+ throw error;
808
+ }
809
+ }
810
+ """
811
+
812
+
813
+ # =============================================================================
814
+ # API HELPERS
815
+ # =============================================================================
816
+
817
+
818
+ @dataclass
819
+ class APIResponse:
820
+ """Standard API response format."""
821
+
822
+ success: bool
823
+ data: dict[str, Any] | None = None
824
+ error: str | None = None
825
+ next_action: str | None = None # "continue", "generate", "complete"
826
+
827
+ def to_json(self) -> str:
828
+ """Serialize to JSON."""
829
+ return json.dumps(asdict(self), indent=2)
830
+
831
+
832
+ def create_form_response(
833
+ session: SocraticSession,
834
+ form: Form | None,
835
+ builder: Any, # SocraticWorkflowBuilder
836
+ ) -> APIResponse:
837
+ """Create API response for form request.
838
+
839
+ Args:
840
+ session: Current session
841
+ form: Form to display (or None if ready to generate)
842
+ builder: SocraticWorkflowBuilder instance
843
+
844
+ Returns:
845
+ APIResponse with form data or generation prompt
846
+ """
847
+ session_schema = ReactSessionSchema.from_session(session)
848
+
849
+ if form:
850
+ form_schema = ReactFormSchema.from_form(form)
851
+ return APIResponse(
852
+ success=True,
853
+ data={
854
+ "session": asdict(session_schema),
855
+ "form": asdict(form_schema),
856
+ },
857
+ next_action="continue",
858
+ )
859
+ elif builder.is_ready_to_generate(session):
860
+ return APIResponse(
861
+ success=True,
862
+ data={
863
+ "session": asdict(session_schema),
864
+ "message": "Ready to generate workflow",
865
+ },
866
+ next_action="generate",
867
+ )
868
+ else:
869
+ return APIResponse(
870
+ success=False,
871
+ error="Unable to determine next step",
872
+ )
873
+
874
+
875
+ def create_blueprint_response(
876
+ blueprint: WorkflowBlueprint,
877
+ session: SocraticSession,
878
+ ) -> APIResponse:
879
+ """Create API response for generated blueprint.
880
+
881
+ Args:
882
+ blueprint: Generated blueprint
883
+ session: Source session
884
+
885
+ Returns:
886
+ APIResponse with blueprint data
887
+ """
888
+ blueprint_schema = ReactBlueprintSchema.from_blueprint(blueprint)
889
+ session_schema = ReactSessionSchema.from_session(session)
890
+
891
+ return APIResponse(
892
+ success=True,
893
+ data={
894
+ "session": asdict(session_schema),
895
+ "blueprint": asdict(blueprint_schema),
896
+ },
897
+ next_action="complete",
898
+ )
899
+
900
+
901
+ # =============================================================================
902
+ # EXPORT FUNCTIONS
903
+ # =============================================================================
904
+
905
+
906
+ def get_form_assets() -> dict[str, str]:
907
+ """Get CSS and JS assets for forms.
908
+
909
+ Returns:
910
+ Dictionary with 'css' and 'js' keys
911
+ """
912
+ return {
913
+ "css": FORM_CSS,
914
+ "js": FORM_JS,
915
+ }
916
+
917
+
918
+ def render_complete_page(form: Form, session: SocraticSession) -> str:
919
+ """Render a complete HTML page with form.
920
+
921
+ Args:
922
+ form: Form to render
923
+ session: Current session
924
+
925
+ Returns:
926
+ Complete HTML page
927
+ """
928
+ form_html = render_form_html(form)
929
+ session_data = ReactSessionSchema.from_session(session)
930
+
931
+ return f"""<!DOCTYPE html>
932
+ <html lang="en">
933
+ <head>
934
+ <meta charset="UTF-8">
935
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
936
+ <title>Socratic Workflow Builder</title>
937
+ <style>
938
+ {FORM_CSS}
939
+ </style>
940
+ </head>
941
+ <body>
942
+ <div class="container">
943
+ <div class="session-info">
944
+ <span class="domain-badge">{session_data.domain or "General"}</span>
945
+ <span class="confidence">Confidence: {session_data.confidence:.0%}</span>
946
+ </div>
947
+
948
+ {form_html}
949
+ </div>
950
+
951
+ <script>
952
+ {FORM_JS}
953
+
954
+ // Session data for client-side use
955
+ window.socraticSession = {session_data.to_json()};
956
+ </script>
957
+ </body>
958
+ </html>"""