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,348 @@
1
+ """Swarm-layer write-scope path guard (defense in depth).
2
+
3
+ The agent layer already guards writes (deny prefixes + allow globs inside
4
+ ``agent/tools_assembly.py``), but from the swarm's perspective that is an
5
+ honor system: a bug there would let a worker write outside its task's
6
+ ``write_scope`` and silently poison sibling tasks or the merge. This module
7
+ is the swarm-owned second gate, applied structurally at the tool dispatch
8
+ boundary (every ``ToolDef.run`` is wrapped), so it holds even when the
9
+ agent-layer checks are buggy or bypassed.
10
+
11
+ Policy:
12
+ - WRITES must stay inside the task's normalized ``write_scope`` patterns
13
+ (plus the run-scoped scratch/temp roots); READS are never guarded.
14
+ - Violations are recorded as typed :class:`WriteScopeViolation` records and
15
+ surface as a tool error to the model; the swarm worker fails the task
16
+ closed when any violation was recorded, regardless of the agent exit code.
17
+ - Hardening: raw ``..`` traversal is rejected before resolution, containment
18
+ is re-verified with ``Path.resolve()`` (realpath) independently of the
19
+ tool's own resolution, writes through symlinked components are rejected
20
+ (a symlink inside the worktree silently rewrites which relative path gets
21
+ scope-checked), and matching is case-insensitive on win32-style
22
+ case-insensitive filesystems.
23
+
24
+ Out of scope (documented, unchanged): ``shell_run``/``shell_background``
25
+ cannot be path-guarded at dispatch ? the sandbox profile and the post-run
26
+ ``assess_scope_changes`` pass remain the controls for shell writes.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import fnmatch
32
+ import sys
33
+ import threading
34
+ from collections.abc import Callable, Iterator, Sequence
35
+ from dataclasses import dataclass
36
+ from pathlib import Path, PurePosixPath
37
+ from typing import Any
38
+
39
+ from .agent.errors import AgentRuntimeError
40
+ from .diff_paths import iter_patch_paths
41
+ from .task_scope import (
42
+ ancestor_directory_scope_patterns,
43
+ is_non_material_untracked_path,
44
+ scope_path_matches_pattern,
45
+ )
46
+
47
+ # Tool name -> ((path_field, base_field), ...) write targets. fs_copy guards
48
+ # only the destination and fs_move guards both ends, mirroring the agent
49
+ # layer's contract.
50
+ _WRITE_TOOL_PATH_FIELDS: dict[str, tuple[tuple[str, str], ...]] = {
51
+ "fs_write": (("path", "path_base"),),
52
+ "fs_edit": (("path", "path_base"),),
53
+ "fs_mkdir": (("path", "path_base"),),
54
+ "fs_delete": (("path", "path_base"),),
55
+ "fs_move": (
56
+ ("source_path", "source_path_base"),
57
+ ("destination_path", "destination_path_base"),
58
+ ),
59
+ "fs_copy": (("destination_path", "destination_path_base"),),
60
+ }
61
+ _PATCH_TOOL_NAMES = frozenset({"git_apply_patch"})
62
+
63
+
64
+ class SwarmWriteScopeViolationError(AgentRuntimeError):
65
+ """Raised at the tool dispatch boundary for an out-of-scope write.
66
+
67
+ Subclasses ``AgentRuntimeError`` so the turn engine degrades it to a
68
+ structured tool error the model can react to; the recorded violation
69
+ still fails the task at the swarm layer afterwards.
70
+ """
71
+
72
+
73
+ @dataclass(frozen=True, slots=True)
74
+ class WriteScopeViolation:
75
+ tool_name: str
76
+ raw_path: str
77
+ reason: str
78
+ detail: str
79
+ resolved_relpath: str | None = None
80
+
81
+ def to_payload(self) -> dict[str, Any]:
82
+ return {
83
+ "kind": "write_scope_guard_violation",
84
+ "tool": self.tool_name,
85
+ "path": self.raw_path,
86
+ "resolved_relpath": self.resolved_relpath,
87
+ "reason": self.reason,
88
+ "detail": self.detail,
89
+ }
90
+
91
+
92
+ def _normalize_slashes(value: str) -> str:
93
+ return value.replace("\\", "/").strip()
94
+
95
+
96
+ class SwarmWriteScopeGuard:
97
+ """Per-task hard write guard keyed by write_scope + run-scoped temp roots."""
98
+
99
+ def __init__(
100
+ self,
101
+ *,
102
+ worktree_root: Path,
103
+ allowed_patterns: Sequence[str],
104
+ extra_allowed_roots: Sequence[Path] = (),
105
+ case_insensitive: bool | None = None,
106
+ ) -> None:
107
+ self.worktree_root = Path(worktree_root).resolve()
108
+ self.allowed_patterns = tuple(
109
+ str(item).strip() for item in allowed_patterns if str(item).strip()
110
+ )
111
+ self.extra_allowed_roots = tuple(Path(item).resolve() for item in extra_allowed_roots)
112
+ self.case_insensitive = (
113
+ sys.platform == "win32" if case_insensitive is None else bool(case_insensitive)
114
+ )
115
+ self._ancestor_dirs_cf = {
116
+ _normalize_slashes(path).casefold()
117
+ for path in ancestor_directory_scope_patterns(
118
+ list(self.allowed_patterns), root=self.worktree_root
119
+ )
120
+ }
121
+ self._lock = threading.Lock()
122
+ self._violations: list[WriteScopeViolation] = []
123
+
124
+ @property
125
+ def violations(self) -> list[WriteScopeViolation]:
126
+ with self._lock:
127
+ return list(self._violations)
128
+
129
+ def check_tool_call(
130
+ self,
131
+ tool_name: str,
132
+ arguments: dict[str, Any],
133
+ *,
134
+ resolve_rel_path: Callable[..., str] | None = None,
135
+ ) -> None:
136
+ """Veto an out-of-scope write before the tool runs.
137
+
138
+ ``resolve_rel_path`` is the live tool-layer resolver (active-workdir
139
+ aware); when provided it determines what the tool would actually
140
+ write, and the guard independently re-verifies containment on top.
141
+ """
142
+ for raw_path, resolved_rel in self._iter_write_targets(
143
+ tool_name, arguments, resolve_rel_path=resolve_rel_path
144
+ ):
145
+ violation = self._check_target(
146
+ tool_name=tool_name,
147
+ raw_path=raw_path,
148
+ resolved_rel=resolved_rel,
149
+ )
150
+ if violation is not None:
151
+ with self._lock:
152
+ self._violations.append(violation)
153
+ raise SwarmWriteScopeViolationError(
154
+ "Write blocked by the swarm write-scope guard "
155
+ f"({violation.reason}): {violation.raw_path}. {violation.detail}"
156
+ )
157
+
158
+ def _iter_write_targets(
159
+ self,
160
+ tool_name: str,
161
+ arguments: dict[str, Any],
162
+ *,
163
+ resolve_rel_path: Callable[..., str] | None,
164
+ ) -> Iterator[tuple[str, str | None]]:
165
+ if tool_name in _PATCH_TOOL_NAMES:
166
+ patch_text = str(arguments.get("patch") or "")
167
+ for path in iter_patch_paths(patch_text):
168
+ yield path, None
169
+ return
170
+ fields = _WRITE_TOOL_PATH_FIELDS.get(tool_name)
171
+ if fields is None:
172
+ return
173
+ for path_field, base_field in fields:
174
+ raw_path = arguments.get(path_field)
175
+ if raw_path is None or not str(raw_path).strip():
176
+ continue
177
+ resolved_rel: str | None = None
178
+ if resolve_rel_path is not None:
179
+ try:
180
+ resolved_rel = resolve_rel_path(
181
+ raw_path=raw_path,
182
+ raw_base=arguments.get(base_field),
183
+ field_name=path_field,
184
+ base_field_name=base_field,
185
+ )
186
+ except AgentRuntimeError as exc:
187
+ violation = WriteScopeViolation(
188
+ tool_name=tool_name,
189
+ raw_path=str(raw_path),
190
+ reason="path_escape",
191
+ detail=f"tool path resolution rejected the target: {exc}",
192
+ )
193
+ with self._lock:
194
+ self._violations.append(violation)
195
+ raise SwarmWriteScopeViolationError(
196
+ "Write blocked by the swarm write-scope guard "
197
+ f"(path_escape): {raw_path}. {exc}"
198
+ ) from exc
199
+ yield str(raw_path), resolved_rel
200
+
201
+ def _check_target(
202
+ self,
203
+ *,
204
+ tool_name: str,
205
+ raw_path: str,
206
+ resolved_rel: str | None,
207
+ ) -> WriteScopeViolation | None:
208
+ raw_text = _normalize_slashes(str(raw_path))
209
+ if "\x00" in raw_text:
210
+ return WriteScopeViolation(
211
+ tool_name=tool_name,
212
+ raw_path=str(raw_path),
213
+ reason="invalid_path",
214
+ detail="write target contains a NUL byte.",
215
+ )
216
+ if any(part == ".." for part in PurePosixPath(raw_text).parts):
217
+ return WriteScopeViolation(
218
+ tool_name=tool_name,
219
+ raw_path=str(raw_path),
220
+ reason="parent_traversal",
221
+ detail="write targets must not contain '..' components.",
222
+ )
223
+
224
+ raw_as_path = Path(raw_text)
225
+ if raw_as_path.is_absolute():
226
+ resolved_abs = raw_as_path.resolve()
227
+ if self._is_under_extra_allowed_root(resolved_abs):
228
+ return None
229
+ rel = self._relative_to_root(resolved_abs)
230
+ if rel is None:
231
+ return WriteScopeViolation(
232
+ tool_name=tool_name,
233
+ raw_path=str(raw_path),
234
+ reason="path_escape",
235
+ detail="absolute write target resolves outside the task worktree.",
236
+ )
237
+ else:
238
+ rel = _normalize_slashes(resolved_rel) if resolved_rel else raw_text
239
+ while rel.startswith("./"):
240
+ rel = rel[2:]
241
+
242
+ candidate = self.worktree_root / rel
243
+ symlink_component = self._first_symlink_component(rel)
244
+ if symlink_component is not None:
245
+ return WriteScopeViolation(
246
+ tool_name=tool_name,
247
+ raw_path=str(raw_path),
248
+ resolved_relpath=rel,
249
+ reason="symlink_component",
250
+ detail=(
251
+ "write path passes through a symlink "
252
+ f"({symlink_component}); symlinked write targets are rejected."
253
+ ),
254
+ )
255
+ resolved_candidate = candidate.resolve()
256
+ if self._is_under_extra_allowed_root(resolved_candidate):
257
+ return None
258
+ if self._relative_to_root(resolved_candidate) is None:
259
+ return WriteScopeViolation(
260
+ tool_name=tool_name,
261
+ raw_path=str(raw_path),
262
+ resolved_relpath=rel,
263
+ reason="path_escape",
264
+ detail="write target resolves outside the task worktree.",
265
+ )
266
+
267
+ if self._rel_path_in_scope(rel, tool_name=tool_name):
268
+ return None
269
+ scope_preview = ", ".join(self.allowed_patterns[:8]) or "(empty write scope)"
270
+ return WriteScopeViolation(
271
+ tool_name=tool_name,
272
+ raw_path=str(raw_path),
273
+ resolved_relpath=rel,
274
+ reason="out_of_scope",
275
+ detail=f"write target is outside the task write scope: {scope_preview}.",
276
+ )
277
+
278
+ def _relative_to_root(self, path: Path) -> str | None:
279
+ try:
280
+ return path.relative_to(self.worktree_root).as_posix()
281
+ except ValueError:
282
+ if not self.case_insensitive:
283
+ return None
284
+ path_cf = str(path).replace("\\", "/").casefold()
285
+ root_cf = str(self.worktree_root).replace("\\", "/").casefold().rstrip("/")
286
+ if path_cf == root_cf:
287
+ return "."
288
+ if path_cf.startswith(root_cf + "/"):
289
+ return str(path).replace("\\", "/")[len(root_cf) + 1 :]
290
+ return None
291
+
292
+ def _is_under_extra_allowed_root(self, resolved: Path) -> bool:
293
+ for allowed_root in self.extra_allowed_roots:
294
+ try:
295
+ resolved.relative_to(allowed_root)
296
+ return True
297
+ except ValueError:
298
+ continue
299
+ return False
300
+
301
+ def _first_symlink_component(self, rel: str) -> str | None:
302
+ current = self.worktree_root
303
+ for part in PurePosixPath(rel).parts:
304
+ current = current / part
305
+ try:
306
+ if current.is_symlink():
307
+ return current.relative_to(self.worktree_root).as_posix()
308
+ except OSError:
309
+ return None
310
+ return None
311
+
312
+ def _rel_path_in_scope(self, rel: str, *, tool_name: str) -> bool:
313
+ rel_clean = _normalize_slashes(rel).rstrip("/")
314
+ if not rel_clean:
315
+ return False
316
+ if tool_name == "fs_delete" and is_non_material_untracked_path(rel_clean):
317
+ # Mirror the agent layer's carve-out: deleting non-material
318
+ # untracked scratch output outside scope stays allowed.
319
+ return True
320
+ rel_cf = rel_clean.casefold()
321
+ if tool_name == "fs_mkdir" and rel_cf in self._ancestor_dirs_cf:
322
+ return True
323
+ for pattern in self.allowed_patterns:
324
+ if scope_path_matches_pattern(rel_clean, pattern, root=self.worktree_root):
325
+ return True
326
+ if not self.case_insensitive:
327
+ return False
328
+ for pattern in self.allowed_patterns:
329
+ if self._matches_casefolded(rel_cf, pattern):
330
+ return True
331
+ return False
332
+
333
+ def _matches_casefolded(self, rel_cf: str, pattern: str) -> bool:
334
+ pattern_cf = _normalize_slashes(pattern).rstrip("/").casefold()
335
+ if not pattern_cf:
336
+ return False
337
+ if any(ch in pattern_cf for ch in "*?["):
338
+ variants = [pattern_cf]
339
+ if "/**/" in pattern_cf:
340
+ variants.append(pattern_cf.replace("/**/", "/"))
341
+ return any(fnmatch.fnmatchcase(rel_cf, variant) for variant in variants)
342
+ if rel_cf == pattern_cf:
343
+ return True
344
+ try:
345
+ is_dir_scope = (self.worktree_root / pattern).is_dir()
346
+ except OSError:
347
+ is_dir_scope = False
348
+ return is_dir_scope and rel_cf.startswith(pattern_cf + "/")
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from .task_readiness import is_clearly_non_mutating_task, normalized_text_list
8
+
9
+ _ORDERING_SIGNAL_RE = re.compile(
10
+ r"\b(?:after|based\s+on|depend(?:s|ed|ing)?\s+on|follow[- ]?up|following|"
11
+ r"next\s+(?:task|step|change|item|phase|work)|once|previous|prior|subsequent|then)\b",
12
+ re.IGNORECASE,
13
+ )
14
+ _EXPLICIT_PREVIOUS_RE = re.compile(
15
+ r"\b(?:after\s+that|after\s+the\s+previous|after\s+the\s+prior|follow[- ]?up|"
16
+ r"following\s+that|next\s+(?:task|step|change|item|phase|work)|"
17
+ r"previous\s+task|prior\s+task|then)\b",
18
+ re.IGNORECASE,
19
+ )
20
+ _WORD_RE = re.compile(r"[a-z0-9][a-z0-9_-]*", re.IGNORECASE)
21
+ _NON_DEPENDENCY_STATUSES = frozenset({"cancelled", "canceled", "done", "skipped", "superseded"})
22
+ _TOKEN_STOPWORDS = frozenset(
23
+ {
24
+ "add",
25
+ "after",
26
+ "and",
27
+ "app",
28
+ "change",
29
+ "create",
30
+ "docs",
31
+ "file",
32
+ "fix",
33
+ "for",
34
+ "from",
35
+ "implementation",
36
+ "make",
37
+ "modify",
38
+ "new",
39
+ "src",
40
+ "task",
41
+ "test",
42
+ "tests",
43
+ "the",
44
+ "then",
45
+ "this",
46
+ "update",
47
+ "with",
48
+ }
49
+ )
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class InferredTaskDependency:
54
+ task_id: str
55
+ depends_on: str
56
+ reason: str
57
+
58
+
59
+ def infer_ordered_predecessor_dependency(
60
+ *,
61
+ tasks: list[dict[str, Any]],
62
+ task: dict[str, Any],
63
+ ) -> InferredTaskDependency | None:
64
+ task_id = str(task.get("id") or "").strip()
65
+ if not task_id:
66
+ return None
67
+ if _task_is_read_only(task):
68
+ return None
69
+ deps = _task_dependencies(task)
70
+ if deps:
71
+ return None
72
+ text = _task_text(task)
73
+ if not text or not _ORDERING_SIGNAL_RE.search(text):
74
+ return None
75
+
76
+ previous_tasks: list[dict[str, Any]] = []
77
+ for candidate in tasks:
78
+ if candidate is task or str(candidate.get("id") or "").strip() == task_id:
79
+ break
80
+ previous_tasks.append(candidate)
81
+
82
+ current_tokens = _dependency_tokens(text)
83
+ explicit_previous = bool(_EXPLICIT_PREVIOUS_RE.search(text))
84
+ for previous in reversed(previous_tasks):
85
+ previous_id = str(previous.get("id") or "").strip()
86
+ if not previous_id or previous_id in deps:
87
+ continue
88
+ if _canonical_status(str(previous.get("status") or "")) in _NON_DEPENDENCY_STATUSES:
89
+ continue
90
+ if _task_is_read_only(previous) and not explicit_previous:
91
+ continue
92
+ previous_tokens = _dependency_tokens(_task_text(previous))
93
+ if explicit_previous or current_tokens & previous_tokens:
94
+ return InferredTaskDependency(
95
+ task_id=task_id,
96
+ depends_on=previous_id,
97
+ reason="ordered predecessor cue",
98
+ )
99
+ return None
100
+
101
+
102
+ def apply_ordered_dependency_inference(
103
+ *,
104
+ tasks: list[dict[str, Any]],
105
+ touched_task_ids: set[str],
106
+ ) -> tuple[bool, list[InferredTaskDependency]]:
107
+ changed = False
108
+ inferred: list[InferredTaskDependency] = []
109
+ for task in tasks:
110
+ if not isinstance(task, dict):
111
+ continue
112
+ task_id = str(task.get("id") or "").strip()
113
+ if task_id not in touched_task_ids:
114
+ continue
115
+ if _canonical_status(str(task.get("status") or "")) != "planned":
116
+ continue
117
+ dependency = infer_ordered_predecessor_dependency(tasks=tasks, task=task)
118
+ if dependency is None:
119
+ continue
120
+ deps = _task_dependencies(task)
121
+ if dependency.depends_on in deps:
122
+ continue
123
+ task["dependencies"] = [*deps, dependency.depends_on]
124
+ inferred.append(dependency)
125
+ changed = True
126
+ return changed, inferred
127
+
128
+
129
+ def _task_dependencies(task: dict[str, Any]) -> list[str]:
130
+ deps = task.get("dependencies") or []
131
+ if not isinstance(deps, list):
132
+ return []
133
+ return [str(dep).strip() for dep in deps if str(dep).strip()]
134
+
135
+
136
+ def _task_is_read_only(task: dict[str, Any]) -> bool:
137
+ return is_clearly_non_mutating_task(
138
+ title=str(task.get("title") or "").strip(),
139
+ description=str(task.get("description") or "").strip(),
140
+ acceptance_criteria=normalized_text_list(task.get("acceptance_criteria")),
141
+ )
142
+
143
+
144
+ def _task_text(task: dict[str, Any]) -> str:
145
+ parts = [
146
+ str(task.get("title") or ""),
147
+ str(task.get("description") or ""),
148
+ *normalized_text_list(task.get("acceptance_criteria")),
149
+ *normalized_text_list(task.get("estimated_files")),
150
+ *normalized_text_list(task.get("write_scope")),
151
+ ]
152
+ return "\n".join(parts)
153
+
154
+
155
+ def _dependency_tokens(text: str) -> set[str]:
156
+ normalized = re.sub(r"[./\\_-]+", " ", str(text or "").casefold())
157
+ tokens: set[str] = set()
158
+ for raw in _WORD_RE.findall(normalized):
159
+ token = raw.casefold()
160
+ if len(token) <= 2 or token in _TOKEN_STOPWORDS:
161
+ continue
162
+ tokens.add(token)
163
+ return tokens
164
+
165
+
166
+ def _canonical_status(status: str) -> str:
167
+ value = str(status or "").strip().lower()
168
+ if value == "todo":
169
+ return "planned"
170
+ return value or "planned"