alysis-code 0.13.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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ AUTO_WEB_SEARCH_ADAPTER = "auto"
4
+ OPENAI_RESPONSES_ADAPTER = "openai_responses"
5
+ XAI_RESPONSES_ADAPTER = "xai_responses"
6
+ ANTHROPIC_MESSAGES_ADAPTER = "anthropic_messages"
7
+ GEMINI_GROUNDING_ADAPTER = "gemini_grounding"
8
+ OPENROUTER_WEB_ADAPTER = "openrouter_web"
9
+ DASHSCOPE_CHAT_ADAPTER = "dashscope_chat"
10
+ MOONSHOT_KIMI_ADAPTER = "moonshot_kimi"
11
+ ZHIPU_WEB_SEARCH_ADAPTER = "zhipu_web_search"
12
+ VOLCENGINE_WEB_SEARCH_ADAPTER = "volcengine_web_search"
13
+ PERPLEXITY_SONAR_ADAPTER = "perplexity_sonar"
14
+ GROQ_COMPOUND_ADAPTER = "groq_compound"
15
+ MISTRAL_CONVERSATIONS_ADAPTER = "mistral_conversations"
16
+ MINIMAX_CODING_PLAN_ADAPTER = "minimax_coding_plan"
17
+ COHERE_WEB_SEARCH_ADAPTER = "cohere_web_search"
18
+ TAVILY_ADAPTER = "tavily"
19
+ DDGS_ADAPTER = "ddgs"
20
+
21
+ NATIVE_WEB_SEARCH_ADAPTERS: frozenset[str] = frozenset(
22
+ {
23
+ OPENAI_RESPONSES_ADAPTER,
24
+ XAI_RESPONSES_ADAPTER,
25
+ ANTHROPIC_MESSAGES_ADAPTER,
26
+ GEMINI_GROUNDING_ADAPTER,
27
+ OPENROUTER_WEB_ADAPTER,
28
+ DASHSCOPE_CHAT_ADAPTER,
29
+ MOONSHOT_KIMI_ADAPTER,
30
+ ZHIPU_WEB_SEARCH_ADAPTER,
31
+ VOLCENGINE_WEB_SEARCH_ADAPTER,
32
+ PERPLEXITY_SONAR_ADAPTER,
33
+ GROQ_COMPOUND_ADAPTER,
34
+ MISTRAL_CONVERSATIONS_ADAPTER,
35
+ MINIMAX_CODING_PLAN_ADAPTER,
36
+ COHERE_WEB_SEARCH_ADAPTER,
37
+ }
38
+ )
39
+ EXTERNAL_WEB_SEARCH_ADAPTERS: frozenset[str] = frozenset({TAVILY_ADAPTER, DDGS_ADAPTER})
40
+
41
+ VALID_WEB_SEARCH_ADAPTERS: frozenset[str] = frozenset(
42
+ {
43
+ AUTO_WEB_SEARCH_ADAPTER,
44
+ *NATIVE_WEB_SEARCH_ADAPTERS,
45
+ *EXTERNAL_WEB_SEARCH_ADAPTERS,
46
+ }
47
+ )
48
+ WEB_SEARCH_ADAPTER_CHOICES: tuple[str, ...] = tuple(sorted(VALID_WEB_SEARCH_ADAPTERS))
49
+
50
+
51
+ def web_search_adapter_is_native(adapter: str) -> bool:
52
+ return str(adapter or "").strip().lower() in NATIVE_WEB_SEARCH_ADAPTERS
53
+
54
+
55
+ def web_search_adapter_is_external(adapter: str) -> bool:
56
+ return str(adapter or "").strip().lower() in EXTERNAL_WEB_SEARCH_ADAPTERS
57
+
58
+
59
+ def normalize_web_search_adapter(raw: object) -> str:
60
+ value = str(raw or "").strip().lower()
61
+ if not value:
62
+ return AUTO_WEB_SEARCH_ADAPTER
63
+ if value in VALID_WEB_SEARCH_ADAPTERS:
64
+ return value
65
+ allowed = ", ".join(WEB_SEARCH_ADAPTER_CHOICES)
66
+ raise ValueError(f"web_search_adapter must be one of: {allowed}")
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ WEB_SEARCH_POLICY_OFF = "off"
4
+ WEB_SEARCH_POLICY_AUTO = "auto"
5
+ VALID_WEB_SEARCH_POLICIES: frozenset[str] = frozenset(
6
+ {
7
+ WEB_SEARCH_POLICY_OFF,
8
+ WEB_SEARCH_POLICY_AUTO,
9
+ }
10
+ )
11
+
12
+
13
+ def normalize_web_search_policy(raw: object) -> str:
14
+ """Normalize the legacy access switch used by existing configuration files.
15
+
16
+ Search intent is decided by the active model from the registered tool contract.
17
+ The former ``always`` classifier mode is accepted as a compatibility alias for
18
+ model-led ``auto`` behavior.
19
+ """
20
+
21
+ value = str(raw or "").strip().lower()
22
+ if not value or value == "always":
23
+ return WEB_SEARCH_POLICY_AUTO
24
+ if value in VALID_WEB_SEARCH_POLICIES:
25
+ return value
26
+ allowed = ", ".join(sorted(VALID_WEB_SEARCH_POLICIES))
27
+ raise ValueError(f"web_search_policy must be one of: {allowed}")
@@ -0,0 +1,389 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from .repo_scan import _MANIFEST_SPECS, _README_NAMES, scan_workspace
8
+ from .runtime_artifacts import ROOT_RUNTIME_ARTIFACT_DIR_NAMES
9
+ from .workspace_context import WorkspaceContext, WorkspaceContextError, resolve_workspace_context
10
+
11
+ _PROJECT_SIGNAL_DIR_NAMES = frozenset({"src", "tests"})
12
+ _BROAD_PLAIN_DIR_THRESHOLD = 12
13
+ _PROJECT_CONTAINER_DIR_NAMES = frozenset({"code", "dev", "projects", "repos", "src", "work"})
14
+ _MAX_DISCOVERY_CHILDREN = 24
15
+ _MAX_DISCOVERY_RESULTS = 8
16
+
17
+
18
+ class WorkspaceBindingError(RuntimeError):
19
+ pass
20
+
21
+
22
+ class WorkspaceRiskLevel:
23
+ HEALTHY = "healthy"
24
+ GUARDED = "guarded"
25
+ BLOCKED = "blocked"
26
+
27
+
28
+ class WorkspaceAction:
29
+ CHAT = "chat"
30
+ FORGE = "forge"
31
+ FORGE_PLAN = "forge_plan"
32
+ FORGE_RUN = "forge_run"
33
+ SWARM = "swarm"
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class WorkspacePolicy:
38
+ action: str
39
+ allow_guarded_override: bool
40
+ interactive_guarded_resolution: bool
41
+ interactive_allow_use_current: bool
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class WorkspaceBinding:
46
+ requested_path: Path
47
+ resolved_candidate_path: Path
48
+ created_path: bool
49
+ workspace_context: WorkspaceContext
50
+ binding_source: str
51
+ risk_level: str
52
+ broad_workspace_override_used: bool = False
53
+ risk_reasons: tuple[str, ...] = ()
54
+ warnings: tuple[str, ...] = ()
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class WorkspaceCandidate:
59
+ path: Path
60
+ score: int
61
+ project_signals: tuple[str, ...]
62
+ source: str
63
+
64
+ @property
65
+ def summary(self) -> str:
66
+ if self.project_signals:
67
+ return ", ".join(self.project_signals)
68
+ return "existing directory"
69
+
70
+
71
+ def prepare_workspace_path(path: Path, *, create_if_missing: bool = False) -> tuple[Path, bool]:
72
+ requested_path = path.expanduser().resolve(strict=False)
73
+ if requested_path.exists():
74
+ if not requested_path.is_dir():
75
+ raise WorkspaceBindingError(f"Workspace path is not a directory: {requested_path}")
76
+ return requested_path.resolve(), False
77
+
78
+ if not create_if_missing:
79
+ raise WorkspaceBindingError(
80
+ f"Workspace path does not exist: {requested_path}. "
81
+ "Pass create_if_missing=True to bootstrap it."
82
+ )
83
+
84
+ try:
85
+ requested_path.mkdir(parents=True, exist_ok=False)
86
+ except FileExistsError as e:
87
+ raise WorkspaceBindingError(f"Workspace path is not a directory: {requested_path}") from e
88
+ except OSError as e:
89
+ raise WorkspaceBindingError(f"Unable to create workspace path: {requested_path}") from e
90
+ return requested_path.resolve(), True
91
+
92
+
93
+ def resolve_workspace_binding(
94
+ path: Path,
95
+ *,
96
+ create_if_missing: bool = False,
97
+ allow_broad_workspace: bool = False,
98
+ source: str = "explicit_path",
99
+ ) -> WorkspaceBinding:
100
+ requested_path, created_path = prepare_workspace_path(
101
+ path,
102
+ create_if_missing=create_if_missing,
103
+ )
104
+ try:
105
+ workspace_context = resolve_workspace_context(requested_path)
106
+ except WorkspaceContextError as e:
107
+ raise WorkspaceBindingError(str(e)) from e
108
+
109
+ risk_level, risk_reasons = classify_workspace_risk(workspace_context)
110
+ warnings: list[str] = []
111
+ broad_workspace_override_used = False
112
+ if created_path:
113
+ warnings.append("Workspace directory was created at startup.")
114
+ if risk_level == WorkspaceRiskLevel.GUARDED and allow_broad_workspace:
115
+ broad_workspace_override_used = True
116
+ warnings.append("Guarded workspace accepted because allow_broad_workspace=True.")
117
+ return WorkspaceBinding(
118
+ requested_path=requested_path,
119
+ resolved_candidate_path=workspace_context.input_path,
120
+ created_path=created_path,
121
+ workspace_context=workspace_context,
122
+ binding_source=(source.strip() or "explicit_path"),
123
+ risk_level=risk_level,
124
+ broad_workspace_override_used=broad_workspace_override_used,
125
+ risk_reasons=risk_reasons,
126
+ warnings=tuple(warnings),
127
+ )
128
+
129
+
130
+ def workspace_policy(action: str) -> WorkspacePolicy:
131
+ normalized = str(action or "").strip().lower()
132
+ if normalized == WorkspaceAction.FORGE:
133
+ return WorkspacePolicy(
134
+ action=WorkspaceAction.FORGE,
135
+ allow_guarded_override=False,
136
+ interactive_guarded_resolution=False,
137
+ interactive_allow_use_current=False,
138
+ )
139
+ if normalized == WorkspaceAction.FORGE_PLAN:
140
+ return WorkspacePolicy(
141
+ action=WorkspaceAction.FORGE_PLAN,
142
+ allow_guarded_override=True,
143
+ interactive_guarded_resolution=True,
144
+ interactive_allow_use_current=False,
145
+ )
146
+ if normalized == WorkspaceAction.FORGE_RUN:
147
+ # Same strength as swarm: both execute plan tasks and mutate the workspace.
148
+ # It gets its own action so the guard message names the command the user ran.
149
+ return WorkspacePolicy(
150
+ action=WorkspaceAction.FORGE_RUN,
151
+ allow_guarded_override=True,
152
+ interactive_guarded_resolution=False,
153
+ interactive_allow_use_current=False,
154
+ )
155
+ if normalized == WorkspaceAction.SWARM:
156
+ return WorkspacePolicy(
157
+ action=WorkspaceAction.SWARM,
158
+ allow_guarded_override=True,
159
+ interactive_guarded_resolution=False,
160
+ interactive_allow_use_current=False,
161
+ )
162
+ return WorkspacePolicy(
163
+ action=WorkspaceAction.CHAT,
164
+ allow_guarded_override=True,
165
+ interactive_guarded_resolution=True,
166
+ interactive_allow_use_current=True,
167
+ )
168
+
169
+
170
+ def workspace_action_label(action: str) -> str:
171
+ normalized = workspace_policy(action).action
172
+ if normalized == WorkspaceAction.FORGE:
173
+ return "Forge"
174
+ if normalized == WorkspaceAction.FORGE_PLAN:
175
+ return "forge plan"
176
+ if normalized == WorkspaceAction.FORGE_RUN:
177
+ return "forge run"
178
+ if normalized == WorkspaceAction.SWARM:
179
+ return "forge swarm"
180
+ return "chat/run startup"
181
+
182
+
183
+ def workspace_policy_violation_message(
184
+ binding: WorkspaceBinding,
185
+ *,
186
+ action: str,
187
+ ) -> str:
188
+ policy = workspace_policy(action)
189
+ label = workspace_action_label(policy.action)
190
+ root_label = os.fspath(binding.workspace_context.workspace_root)
191
+ requested_label = os.fspath(binding.requested_path)
192
+ detail = "; ".join(binding.risk_reasons) or "workspace selection is not allowed"
193
+ common_guidance = "Use --path <project>, choose/create a project folder, or start alysis from inside a project."
194
+ if binding.risk_level == WorkspaceRiskLevel.BLOCKED:
195
+ return f"{label} is blocked for {requested_label}: {detail}. {common_guidance}"
196
+ if policy.action == WorkspaceAction.FORGE:
197
+ return (
198
+ f"{label} requires a narrower workspace than {root_label}: {detail}. {common_guidance}"
199
+ )
200
+ if policy.action in {WorkspaceAction.FORGE_RUN, WorkspaceAction.SWARM}:
201
+ return (
202
+ f"{label} requires a healthy workspace. Current binding for {requested_label} is "
203
+ f"guarded: {detail}. {common_guidance} "
204
+ f"Pass --allow-broad-workspace only if you intentionally want {label} to operate "
205
+ "on this broad workspace."
206
+ )
207
+ if policy.action == WorkspaceAction.FORGE_PLAN:
208
+ return (
209
+ f"{label} is guarded for {requested_label}: {detail}. {common_guidance} "
210
+ "Pass --allow-broad-workspace only if you intentionally want to plan from this "
211
+ "broad workspace."
212
+ )
213
+ return (
214
+ f"{label} is guarded for {requested_label}: {detail}. {common_guidance} "
215
+ "Pass --allow-broad-workspace to continue non-interactively."
216
+ )
217
+
218
+
219
+ def ensure_workspace_policy(
220
+ binding: WorkspaceBinding,
221
+ *,
222
+ action: str,
223
+ allow_broad_workspace: bool = False,
224
+ ) -> WorkspaceBinding:
225
+ policy = workspace_policy(action)
226
+ if binding.risk_level == WorkspaceRiskLevel.HEALTHY:
227
+ return binding
228
+ if binding.risk_level == WorkspaceRiskLevel.BLOCKED:
229
+ raise WorkspaceBindingError(
230
+ workspace_policy_violation_message(binding, action=policy.action)
231
+ )
232
+ if policy.allow_guarded_override and allow_broad_workspace:
233
+ return binding
234
+ raise WorkspaceBindingError(workspace_policy_violation_message(binding, action=policy.action))
235
+
236
+
237
+ def classify_workspace_risk(workspace_context: WorkspaceContext) -> tuple[str, tuple[str, ...]]:
238
+ workspace_root = workspace_context.workspace_root
239
+ if _is_filesystem_root(workspace_root):
240
+ return (
241
+ WorkspaceRiskLevel.BLOCKED,
242
+ ("filesystem root '/' is not a valid forge workspace",),
243
+ )
244
+
245
+ home_dir = _home_directory()
246
+ if home_dir is not None and workspace_root == home_dir:
247
+ return (
248
+ WorkspaceRiskLevel.GUARDED,
249
+ ("home directory is a broad workspace; pass an explicit override to continue",),
250
+ )
251
+
252
+ if workspace_context.git_root is not None:
253
+ return (WorkspaceRiskLevel.HEALTHY, ())
254
+
255
+ return _classify_plain_directory(workspace_context)
256
+
257
+
258
+ def _classify_plain_directory(workspace_context: WorkspaceContext) -> tuple[str, tuple[str, ...]]:
259
+ scan = scan_workspace(context=workspace_context)
260
+ top_level_names = {entry.get("path", "") for entry in scan.top_level_entries}
261
+ if not top_level_names:
262
+ return (WorkspaceRiskLevel.HEALTHY, ())
263
+
264
+ signals = project_signals_for_path(workspace_context.workspace_root)
265
+ has_project_signals = bool(signals)
266
+ if has_project_signals:
267
+ return (WorkspaceRiskLevel.HEALTHY, ())
268
+
269
+ if len(top_level_names) >= _BROAD_PLAIN_DIR_THRESHOLD:
270
+ return (
271
+ WorkspaceRiskLevel.GUARDED,
272
+ ("plain directory has many top-level entries without strong project signals",),
273
+ )
274
+
275
+ return (WorkspaceRiskLevel.HEALTHY, ())
276
+
277
+
278
+ def _home_directory() -> Path | None:
279
+ try:
280
+ return Path.home().resolve()
281
+ except OSError:
282
+ return None
283
+
284
+
285
+ def _is_filesystem_root(path: Path) -> bool:
286
+ normalized = path.resolve()
287
+ return normalized == normalized.parent and normalized == Path(os.path.abspath(os.sep))
288
+
289
+
290
+ def project_signals_for_path(path: Path) -> tuple[str, ...]:
291
+ resolved = path.expanduser().resolve(strict=False)
292
+ signals: list[str] = []
293
+ git_dir = resolved / ".git"
294
+ if git_dir.exists():
295
+ signals.append(".git")
296
+
297
+ manifest_names = [name for name, _kind in _MANIFEST_SPECS if (resolved / name).is_file()]
298
+ signals.extend(manifest_names[:3])
299
+
300
+ if any((resolved / name).is_file() for name in _README_NAMES):
301
+ signals.append("README")
302
+ if (resolved / "src").is_dir():
303
+ signals.append("src/")
304
+ if (resolved / "tests").is_dir():
305
+ signals.append("tests/")
306
+ return tuple(signals)
307
+
308
+
309
+ def discover_workspace_candidates(
310
+ path: Path,
311
+ *,
312
+ limit: int = _MAX_DISCOVERY_RESULTS,
313
+ ) -> tuple[WorkspaceCandidate, ...]:
314
+ base_dir = path.expanduser().resolve()
315
+ if not base_dir.exists() or not base_dir.is_dir():
316
+ return ()
317
+
318
+ candidates: dict[Path, WorkspaceCandidate] = {}
319
+ immediate_children = _iter_candidate_dirs(base_dir, max_children=_MAX_DISCOVERY_CHILDREN)
320
+ for child in immediate_children:
321
+ _maybe_record_candidate(
322
+ candidates,
323
+ child,
324
+ source="child",
325
+ )
326
+ if child.name.casefold() not in _PROJECT_CONTAINER_DIR_NAMES:
327
+ continue
328
+ for nested in _iter_candidate_dirs(child, max_children=_MAX_DISCOVERY_CHILDREN):
329
+ _maybe_record_candidate(
330
+ candidates,
331
+ nested,
332
+ source=f"nested:{child.name}",
333
+ )
334
+
335
+ ranked = sorted(
336
+ candidates.values(),
337
+ key=lambda item: (-item.score, item.path.name.casefold(), os.fspath(item.path)),
338
+ )
339
+ return tuple(ranked[: max(1, limit)])
340
+
341
+
342
+ def _iter_candidate_dirs(path: Path, *, max_children: int) -> list[Path]:
343
+ children: list[Path] = []
344
+ try:
345
+ entries = sorted(path.iterdir(), key=lambda candidate: candidate.name.casefold())
346
+ except OSError:
347
+ return []
348
+ for candidate in entries:
349
+ if len(children) >= max_children:
350
+ break
351
+ if not candidate.is_dir():
352
+ continue
353
+ if candidate.name.startswith(".") or candidate.name in ROOT_RUNTIME_ARTIFACT_DIR_NAMES:
354
+ continue
355
+ children.append(candidate.resolve())
356
+ return children
357
+
358
+
359
+ def _maybe_record_candidate(
360
+ existing: dict[Path, WorkspaceCandidate],
361
+ path: Path,
362
+ *,
363
+ source: str,
364
+ ) -> None:
365
+ signals = project_signals_for_path(path)
366
+ if not signals:
367
+ return
368
+ score = _project_signal_score(signals)
369
+ candidate = WorkspaceCandidate(
370
+ path=path,
371
+ score=score,
372
+ project_signals=signals,
373
+ source=source,
374
+ )
375
+ current = existing.get(path)
376
+ if current is None or candidate.score > current.score:
377
+ existing[path] = candidate
378
+
379
+
380
+ def _project_signal_score(signals: tuple[str, ...]) -> int:
381
+ score = 0
382
+ for signal in signals:
383
+ if signal == ".git":
384
+ score += 100
385
+ elif signal in {"src/", "tests/", "README"}:
386
+ score += 10
387
+ else:
388
+ score += 20
389
+ return score