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,895 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .agent_loop import run_agent
12
+ from .branding import env_get_from
13
+ from .config import AppConfig, ConfigError, clone_cfg
14
+ from .execution_shared import (
15
+ build_task_execution_instruction_bundle,
16
+ mirror_plan_into_worktree,
17
+ mirror_selected_knowledge_into_worktree,
18
+ prepare_task_execution_knowledge,
19
+ resolve_managed_task_step_budget,
20
+ safe_task_file_component,
21
+ snapshot_runtime_tree,
22
+ snapshot_workspace_tree,
23
+ )
24
+ from .forge import RunPaths, now_iso
25
+ from .git_ops import (
26
+ DEFAULT_COMMIT_AUTHOR_EMAIL,
27
+ DEFAULT_COMMIT_AUTHOR_NAME,
28
+ GitOpsError,
29
+ delete_branch,
30
+ ensure_not_staged_prefixes,
31
+ ensure_not_staged_runtime_artifacts,
32
+ format_patch_stdout,
33
+ merge_no_ff,
34
+ stage_all,
35
+ staged_files,
36
+ unstage_staged_prefixes,
37
+ unstage_staged_runtime_artifacts,
38
+ )
39
+ from .git_safe import build_git_cmd
40
+ from .git_worktrees import ensure_task_worktree, remove_task_worktree
41
+ from .knowledge_base import rebuild_knowledge_index, write_issue_entry, write_task_attempt_entry
42
+ from .knowledge_capture import (
43
+ RecordingSurface,
44
+ mark_knowledge_capture_promotion_skipped,
45
+ persist_execution_knowledge_capture,
46
+ promote_validated_knowledge_capture,
47
+ )
48
+ from .merge_conflict_reviewer import list_unmerged_files, try_abort_merge
49
+ from .model_router import (
50
+ ROLE_CODING,
51
+ ROLE_CONFLICT_RESOLVE,
52
+ resolve_model_for_role,
53
+ )
54
+ from .runtime_artifacts import has_grounded_rust_target_runtime_artifacts
55
+ from .runtime_kind import RuntimeKind
56
+ from .surface import NoopSurface
57
+ from .surface.console import make_console
58
+ from .task_scope import list_changed_files_including_untracked
59
+ from .verify_gate import (
60
+ ResolvedVerifyCommands,
61
+ VerifyError,
62
+ resolve_task_aware_verify_command_selection,
63
+ resolve_verify_command_selection,
64
+ resolve_verify_commands,
65
+ run_task_verification,
66
+ )
67
+
68
+ _VERIFY_MODES = {"off", "warn", "strict"}
69
+
70
+ CONFLICT_RESOLVER_SYSTEM_PROMPT = """You are CONFLICT_RESOLVER. You are an autonomous coding agent operating inside a dedicated conflict-resolution worktree.
71
+
72
+ Mission
73
+ - Resolve git merge conflicts safely and correctly.
74
+ - Resolve merge conflicts only. Do not implement unrelated features.
75
+ - Only modify files that are part of the merge conflict set / allowed scope.
76
+ - Ensure there are no conflict markers left and the repo is in a clean, merge-completable state.
77
+
78
+ Rules
79
+ - Start by inspecting git status and identifying unmerged files.
80
+ - Edit ONLY the conflicted files (and only within the allowed write scope).
81
+ - Remove all conflict markers (<<<<<<<, =======, >>>>>>>) and produce valid code.
82
+ - Prefer minimal changes: preserve intended behavior from both sides when possible.
83
+ - Do not refactor unrelated code.
84
+ - Do not modify .alysis/ or other denied prefixes unless explicitly instructed.
85
+
86
+ Tool usage
87
+ - Prefer search_rg plus fs_read_lines for focused conflict inspection; use fs_read when you need broader file context.
88
+ - Prefer fs_edit for deterministic localized edits in one existing conflicted file.
89
+ - Prefer git_apply_patch for broader or context-heavy conflict edits where unified diff context matters.
90
+ - After edits, search for remaining conflict markers.
91
+ - Use git_status to ensure no unmerged paths remain.
92
+ - If verification tools/commands are available in this run, use the most relevant targeted checks to ensure the resolution is sound.
93
+
94
+ Docs and tests
95
+ - If conflict resolution changes user-facing behavior, update README/docs.
96
+ - If the conflict touches logic likely to break, ensure tests are present and propose/execute relevant verification.
97
+
98
+ Final response
99
+ - Summarize which files were resolved and any remaining risks/assumptions.
100
+ - Report verification commands run (or proposed) and results.
101
+ """
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class ConflictAutoResolveSettings:
106
+ enabled: bool = True
107
+ verify_mode: str = "strict"
108
+ max_attempts: int = 1
109
+
110
+
111
+ @dataclass(frozen=True)
112
+ class AutoResolveOutcome:
113
+ success: bool
114
+ task_id: str
115
+ conflict_branch: str
116
+ worktree_repo_path: Path
117
+ result_json_path: Path
118
+ report_path: Path
119
+ patch_path: Path
120
+ merge_commit_hash: str | None
121
+ verify_summary: str | None
122
+ warnings: list[str]
123
+ error: str | None
124
+ agent_exit_code: int | None = None
125
+ salvaged_nonzero_exit: bool = False
126
+
127
+
128
+ def _parse_bool(raw: object, *, default: bool) -> bool:
129
+ if raw is None:
130
+ return default
131
+ value = str(raw).strip().lower()
132
+ if value in {"1", "true", "yes", "on"}:
133
+ return True
134
+ if value in {"0", "false", "no", "off"}:
135
+ return False
136
+ return default
137
+
138
+
139
+ def _parse_verify_mode(raw: object, *, default: str) -> str:
140
+ value = str(raw).strip().lower() if raw is not None else default
141
+ if value in _VERIFY_MODES:
142
+ return value
143
+ return default
144
+
145
+
146
+ def _parse_positive_int(raw: object, *, default: int) -> int:
147
+ try:
148
+ parsed = int(str(raw).strip())
149
+ except (TypeError, ValueError):
150
+ return default
151
+ if parsed <= 0:
152
+ return default
153
+ return parsed
154
+
155
+
156
+ def load_conflict_auto_resolve_settings(
157
+ *,
158
+ cfg: AppConfig,
159
+ env: Mapping[str, str] | None = None,
160
+ ) -> ConflictAutoResolveSettings:
161
+ source = env if env is not None else os.environ
162
+ enabled = True
163
+ verify_mode = "strict"
164
+ max_attempts = 1
165
+
166
+ cfg_raw = cfg.extra_fields.get("conflict_auto_resolve")
167
+ if isinstance(cfg_raw, dict):
168
+ enabled = _parse_bool(cfg_raw.get("enabled"), default=enabled)
169
+ verify_mode = _parse_verify_mode(cfg_raw.get("verify_mode"), default=verify_mode)
170
+ max_attempts = _parse_positive_int(cfg_raw.get("max_attempts"), default=max_attempts)
171
+
172
+ enabled = _parse_bool(
173
+ env_get_from(source, "ALYSIS_CONFLICT_AUTO_RESOLVE"),
174
+ default=enabled,
175
+ )
176
+ verify_mode = _parse_verify_mode(
177
+ env_get_from(source, "ALYSIS_CONFLICT_AUTO_RESOLVE_VERIFY"),
178
+ default=verify_mode,
179
+ )
180
+ max_attempts = _parse_positive_int(
181
+ env_get_from(source, "ALYSIS_CONFLICT_AUTO_RESOLVE_MAX_ATTEMPTS"),
182
+ default=max_attempts,
183
+ )
184
+ return ConflictAutoResolveSettings(
185
+ enabled=enabled,
186
+ verify_mode=verify_mode,
187
+ max_attempts=max_attempts,
188
+ )
189
+
190
+
191
+ def task_conflict_attempts(task: dict[str, Any]) -> int:
192
+ raw = task.get("conflict_attempts")
193
+ try:
194
+ attempts = int(raw) if raw is not None else 0
195
+ except (TypeError, ValueError):
196
+ return 0
197
+ return max(0, attempts)
198
+
199
+
200
+ def can_attempt_conflict_auto_resolve(
201
+ *,
202
+ task: dict[str, Any],
203
+ settings: ConflictAutoResolveSettings,
204
+ ) -> bool:
205
+ return settings.enabled and task_conflict_attempts(task) < settings.max_attempts
206
+
207
+
208
+ def bump_conflict_attempt(task: dict[str, Any]) -> int:
209
+ next_attempt = task_conflict_attempts(task) + 1
210
+ task["conflict_attempts"] = next_attempt
211
+ return next_attempt
212
+
213
+
214
+ def _run_git(
215
+ root: Path,
216
+ args: list[str],
217
+ *,
218
+ extra_config: dict[str, str] | None = None,
219
+ ) -> subprocess.CompletedProcess[str]:
220
+ cmd = build_git_cmd(root, args, extra_config=extra_config)
221
+ return subprocess.run(
222
+ cmd,
223
+ check=False,
224
+ capture_output=True,
225
+ text=True,
226
+ )
227
+
228
+
229
+ def _run_git_checked(
230
+ root: Path,
231
+ args: list[str],
232
+ *,
233
+ extra_config: dict[str, str] | None = None,
234
+ error_message: str,
235
+ ) -> subprocess.CompletedProcess[str]:
236
+ cp = _run_git(root, args, extra_config=extra_config)
237
+ if cp.returncode != 0:
238
+ detail = (cp.stderr or cp.stdout).strip()
239
+ raise GitOpsError(f"{error_message}: {detail or 'unknown error'}")
240
+ return cp
241
+
242
+
243
+ def _git_head(root: Path) -> str:
244
+ cp = _run_git_checked(
245
+ root,
246
+ ["rev-parse", "HEAD"],
247
+ error_message="failed to resolve commit hash",
248
+ )
249
+ return cp.stdout.strip()
250
+
251
+
252
+ def _truncate(text: str, *, max_chars: int = 4000) -> str:
253
+ if len(text) <= max_chars:
254
+ return text
255
+ return text[:max_chars] + "...(truncated)"
256
+
257
+
258
+ def _format_agent_exception_summary(exc: Exception) -> str:
259
+ name = exc.__class__.__name__
260
+ message = str(exc).strip()
261
+ return f"{name}: {message}" if message else name
262
+
263
+
264
+ def _merge_changed_files(*path_groups: list[str]) -> list[str]:
265
+ seen: set[str] = set()
266
+ merged: list[str] = []
267
+ for group in path_groups:
268
+ for raw in group:
269
+ value = str(raw).strip().replace("\\", "/")
270
+ while value.startswith("./"):
271
+ value = value[2:]
272
+ if not value or value in seen:
273
+ continue
274
+ seen.add(value)
275
+ merged.append(value)
276
+ return merged
277
+
278
+
279
+ _CONFLICT_MARKER_PREFIXES = ("<<<<<<<", "=======", ">>>>>>>")
280
+
281
+
282
+ def _paths_with_conflict_markers(root: Path, paths: list[str]) -> list[str]:
283
+ marker_paths: list[str] = []
284
+ seen: set[str] = set()
285
+ for raw_path in paths:
286
+ rel_path = str(raw_path or "").strip()
287
+ if not rel_path or rel_path in seen:
288
+ continue
289
+ seen.add(rel_path)
290
+ target = root / rel_path
291
+ if not target.is_file():
292
+ continue
293
+ try:
294
+ lines = target.read_text(encoding="utf-8", errors="replace").splitlines()
295
+ except OSError:
296
+ continue
297
+ if any(line.startswith(_CONFLICT_MARKER_PREFIXES) for line in lines):
298
+ marker_paths.append(rel_path)
299
+ return marker_paths
300
+
301
+
302
+ def _build_conflict_instruction(
303
+ *,
304
+ instruction_bundle: Any,
305
+ ) -> str:
306
+ return str(instruction_bundle.instruction)
307
+
308
+
309
+ def _conflict_instruction_sections(*, unmerged_files: list[str]) -> list[str]:
310
+ files_block = (
311
+ "\n".join(f"- `{path}`" for path in unmerged_files) if unmerged_files else "- (none)"
312
+ )
313
+ return [
314
+ "\n".join(
315
+ [
316
+ "## Conflict Resolution Scope",
317
+ "",
318
+ "- Resolve only the merge conflict.",
319
+ "- Do not expand scope beyond the conflict resolution itself.",
320
+ "- Edit only the currently unmerged files listed below.",
321
+ "- Preserve the task intent, but do not treat plan context as permission to broaden scope.",
322
+ "",
323
+ "### Unmerged Files",
324
+ "",
325
+ files_block,
326
+ ]
327
+ )
328
+ ]
329
+
330
+
331
+ def _write_auto_resolve_artifacts(
332
+ *,
333
+ conflict_dir: Path,
334
+ result_payload: dict[str, Any],
335
+ report_text: str,
336
+ patch_text: str,
337
+ context_text: str | None = None,
338
+ budget_payload: dict[str, Any] | None = None,
339
+ ) -> tuple[Path, Path, Path]:
340
+ conflict_dir.mkdir(parents=True, exist_ok=True)
341
+ result_path = conflict_dir / "auto_resolve_result.json"
342
+ report_path = conflict_dir / "auto_resolve_report.md"
343
+ patch_path = conflict_dir / "auto_resolve_patch.diff"
344
+ result_path.write_text(
345
+ json.dumps(result_payload, indent=2, sort_keys=True) + "\n",
346
+ encoding="utf-8",
347
+ )
348
+ report_path.write_text(report_text.rstrip() + "\n", encoding="utf-8")
349
+ patch_path.write_text(patch_text if patch_text else "(no patch generated)\n", encoding="utf-8")
350
+ if context_text is not None:
351
+ (conflict_dir / "auto_resolve_context.md").write_text(
352
+ context_text.rstrip() + "\n",
353
+ encoding="utf-8",
354
+ )
355
+ if budget_payload is not None:
356
+ (conflict_dir / "auto_resolve_budget.json").write_text(
357
+ json.dumps(budget_payload, indent=2, sort_keys=True) + "\n",
358
+ encoding="utf-8",
359
+ )
360
+ return result_path, report_path, patch_path
361
+
362
+
363
+ def attempt_auto_resolve_conflict(
364
+ *,
365
+ paths: RunPaths,
366
+ plan: dict[str, Any],
367
+ task: dict[str, Any],
368
+ cfg: AppConfig,
369
+ api_key_override: str | None,
370
+ base_branch: str,
371
+ task_branch: str,
372
+ keep_worktrees: bool,
373
+ settings: ConflictAutoResolveSettings | None = None,
374
+ verify_commands: list[str] | None = None,
375
+ verify_command_selection: ResolvedVerifyCommands | None = None,
376
+ ) -> AutoResolveOutcome:
377
+ effective_settings = settings or load_conflict_auto_resolve_settings(cfg=cfg)
378
+ task_id = str(task.get("id") or "").strip()
379
+ safe_task = safe_task_file_component(task_id)
380
+ conflict_branch = f"conflict/{safe_task.lower()}"
381
+ worktree_repo_path = paths.run_dir / "conflict_worktrees" / safe_task / "repo"
382
+ conflict_dir = paths.execution_dir / "conflicts" / safe_task
383
+ verify_artifact_path = conflict_dir / "auto_resolve_verify.txt"
384
+
385
+ started_at = now_iso()
386
+ warnings: list[str] = []
387
+ errors: list[str] = []
388
+ verify_summary: str | None = None
389
+ merge_commit_hash: str | None = None
390
+ patch_text = "(no patch generated)\n"
391
+ agent_model = ""
392
+ unresolved_after: list[str] = []
393
+ worktree_kept = True
394
+ conflict_instruction_text: str | None = None
395
+ conflict_budget_payload: dict[str, Any] | None = None
396
+ conflict_paths: list[str] = []
397
+ recording_surface = RecordingSurface(NoopSurface())
398
+ resolved_verify_selection: ResolvedVerifyCommands | None = None
399
+ resolved_verify_commands: list[str] = []
400
+ prompt_verification_enabled = False
401
+ agent_exit_code: int | None = None
402
+ salvaged_nonzero_exit = False
403
+ runtime_artifacts_changed = False
404
+ material_changed_files: list[str] = []
405
+ if effective_settings.verify_mode != "off":
406
+ if verify_command_selection is not None:
407
+ resolved_verify_selection = verify_command_selection
408
+ if verify_commands is not None:
409
+ normalized_commands = tuple(
410
+ resolve_verify_commands(
411
+ cfg=cfg,
412
+ verify_cmd=verify_commands,
413
+ root=worktree_repo_path,
414
+ )
415
+ )
416
+ if normalized_commands != resolved_verify_selection.commands:
417
+ resolved_verify_selection = resolve_verify_command_selection(
418
+ cfg=cfg,
419
+ verify_cmd=verify_commands,
420
+ root=worktree_repo_path,
421
+ )
422
+ resolved_verify_selection = resolve_task_aware_verify_command_selection(
423
+ cfg=cfg,
424
+ verify_cmd=(verify_commands if resolved_verify_selection is None else None),
425
+ task=task,
426
+ root=worktree_repo_path,
427
+ selection=resolved_verify_selection,
428
+ plan_requirements=[
429
+ str(item).strip() for item in (plan.get("requirements") or []) if str(item).strip()
430
+ ]
431
+ if isinstance(plan, dict)
432
+ else None,
433
+ )
434
+ resolved_verify_commands = list(resolved_verify_selection.commands)
435
+ prompt_verification_enabled = bool(resolved_verify_commands)
436
+
437
+ try:
438
+ ensure_task_worktree(
439
+ root=paths.root,
440
+ worktree_repo_path=worktree_repo_path,
441
+ branch=conflict_branch,
442
+ base_branch=base_branch,
443
+ )
444
+ mirror_plan_into_worktree(run_paths=paths, worktree_repo_path=worktree_repo_path)
445
+
446
+ merge_cp = _run_git(
447
+ worktree_repo_path,
448
+ ["merge", "--no-ff", task_branch, "-m", f"Resolve {task_id}"],
449
+ )
450
+ unmerged_files = list_unmerged_files(worktree_repo_path)
451
+ if merge_cp.returncode != 0 and not unmerged_files:
452
+ detail = (merge_cp.stderr or merge_cp.stdout).strip()
453
+ raise GitOpsError(
454
+ f"failed to initialize conflict resolution merge: {detail or 'unknown error'}"
455
+ )
456
+
457
+ if unmerged_files:
458
+ conflict_paths = list(unmerged_files)
459
+ run_cfg = clone_cfg(cfg)
460
+ if effective_settings.verify_mode != "off":
461
+ run_cfg.verify_commands = list(resolved_verify_commands)
462
+ try:
463
+ run_cfg.model = resolve_model_for_role(
464
+ cfg=cfg,
465
+ role=ROLE_CONFLICT_RESOLVE,
466
+ plan=plan,
467
+ fallback_to_default=False,
468
+ prefer_context="forge",
469
+ )
470
+ except ConfigError:
471
+ run_cfg.model = resolve_model_for_role(
472
+ cfg=cfg,
473
+ role=ROLE_CODING,
474
+ plan=plan,
475
+ prefer_context="forge",
476
+ )
477
+ agent_model = run_cfg.model
478
+ prepared_knowledge = prepare_task_execution_knowledge(
479
+ run_paths=paths,
480
+ task=task,
481
+ selection_label="conflict_auto_resolve",
482
+ extra_paths=unmerged_files,
483
+ )
484
+ mirror_selected_knowledge_into_worktree(
485
+ materialized=prepared_knowledge,
486
+ run_paths=paths,
487
+ worktree_repo_path=worktree_repo_path,
488
+ )
489
+ instruction_bundle = build_task_execution_instruction_bundle(
490
+ plan=plan,
491
+ task=task,
492
+ root=worktree_repo_path,
493
+ cfg=run_cfg,
494
+ role_model=run_cfg.model,
495
+ mode="auto",
496
+ yes=True,
497
+ deny_write_prefixes=[".alysis"],
498
+ allow_write_globs=unmerged_files,
499
+ non_interactive=True,
500
+ verification_enabled=prompt_verification_enabled,
501
+ authoritative_verification_commands=(
502
+ resolved_verify_commands if prompt_verification_enabled else None
503
+ ),
504
+ trusted_system_prompt_override=CONFLICT_RESOLVER_SYSTEM_PROMPT,
505
+ api_key=api_key_override,
506
+ subagents_enabled=False,
507
+ leading_sections=[
508
+ *_conflict_instruction_sections(unmerged_files=unmerged_files),
509
+ prepared_knowledge.prompt_section,
510
+ ],
511
+ )
512
+ conflict_instruction_text = instruction_bundle.instruction
513
+ task_image_paths = list(instruction_bundle.image_paths) or None
514
+ conflict_attempt_count = task_conflict_attempts(task)
515
+ if conflict_attempt_count <= 0:
516
+ conflict_attempt_count = 1
517
+ conflict_file_count = len(unmerged_files)
518
+ conflict_step_budget = resolve_managed_task_step_budget(
519
+ cfg=run_cfg,
520
+ plan=plan,
521
+ task=task,
522
+ kind="conflict_resolution",
523
+ mode="auto",
524
+ verification_enabled=effective_settings.verify_mode != "off",
525
+ max_steps_override=None,
526
+ attempt_count=conflict_attempt_count,
527
+ image_count=len(task_image_paths or []),
528
+ conflict_file_count=conflict_file_count,
529
+ )
530
+ conflict_budget_payload = instruction_bundle.to_budget_artifact_payload()
531
+ conflict_budget_payload["step_budget"] = conflict_step_budget.to_payload()
532
+ instruction = _build_conflict_instruction(
533
+ instruction_bundle=instruction_bundle,
534
+ )
535
+ before_runtime_snapshot = snapshot_runtime_tree(worktree_repo_path)
536
+ before_workspace_snapshot = snapshot_workspace_tree(worktree_repo_path)
537
+ run_agent_exception_summary: str | None = None
538
+ try:
539
+ agent_exit_code = run_agent(
540
+ cfg=run_cfg,
541
+ root=worktree_repo_path,
542
+ instruction=instruction,
543
+ mode="auto",
544
+ runtime_kind=RuntimeKind.CONFLICT_AUTO_RESOLVE,
545
+ yes=True,
546
+ max_steps=conflict_step_budget.resolved_max_steps,
547
+ no_log=False,
548
+ api_key_override=api_key_override,
549
+ console=make_console(),
550
+ surface=recording_surface,
551
+ deny_write_prefixes=[".alysis"],
552
+ allow_write_globs=unmerged_files,
553
+ non_interactive=True,
554
+ session_log_dir_override=conflict_dir / "sessions",
555
+ session_id_override=f"{safe_task}-conflict",
556
+ usage_role=f"conflict_resolve:{task_id}",
557
+ enable_compaction=False,
558
+ enable_tool_output_offload=True,
559
+ enable_conversation_summarization=True,
560
+ compaction_profile="execution",
561
+ enable_chat_turn_step_budget=False,
562
+ one_shot_execution=True,
563
+ trusted_system_prompt_override=CONFLICT_RESOLVER_SYSTEM_PROMPT,
564
+ verification_enabled=prompt_verification_enabled,
565
+ authoritative_verification_commands=(
566
+ resolved_verify_commands if prompt_verification_enabled else None
567
+ ),
568
+ subagents_enabled=False,
569
+ )
570
+ except Exception as e: # noqa: BLE001
571
+ run_agent_exception_summary = _format_agent_exception_summary(e)
572
+ agent_exit_code = None
573
+
574
+ after_runtime_snapshot = snapshot_runtime_tree(worktree_repo_path)
575
+ after_workspace_snapshot = snapshot_workspace_tree(worktree_repo_path)
576
+ runtime_artifacts_changed = before_runtime_snapshot != after_runtime_snapshot
577
+ workspace_delta_files = {
578
+ path
579
+ for path in (set(before_workspace_snapshot) | set(after_workspace_snapshot))
580
+ if before_workspace_snapshot.get(path) != after_workspace_snapshot.get(path)
581
+ }
582
+ current_changed_files = list_changed_files_including_untracked(worktree_repo_path)
583
+ material_changed_files = [
584
+ path for path in current_changed_files if path in workspace_delta_files
585
+ ]
586
+ material_changed_files = _merge_changed_files(material_changed_files)
587
+ material_changes_detected = bool(material_changed_files)
588
+ nonzero_agent_exit = (
589
+ agent_exit_code is not None
590
+ and agent_exit_code != 0
591
+ and run_agent_exception_summary is None
592
+ )
593
+ if run_agent_exception_summary is not None:
594
+ raise RuntimeError(
595
+ "conflict resolver agent raised before returning normally: "
596
+ f"{run_agent_exception_summary}"
597
+ )
598
+ if agent_exit_code is None:
599
+ raise RuntimeError("conflict resolver agent did not return an exit code")
600
+ if nonzero_agent_exit:
601
+ if runtime_artifacts_changed:
602
+ raise RuntimeError(
603
+ "conflict resolver agent failed with exit code "
604
+ f"{agent_exit_code}; protected runtime "
605
+ "artifacts under .alysis changed"
606
+ )
607
+ if material_changes_detected:
608
+ raise RuntimeError(
609
+ "conflict resolver agent failed with exit code "
610
+ f"{agent_exit_code} after producing material conflict-resolution changes; "
611
+ "refusing to accept partial conflict auto-resolve result"
612
+ )
613
+ raise RuntimeError(
614
+ "conflict resolver agent failed with exit code "
615
+ f"{agent_exit_code} and produced no material conflict-resolution changes"
616
+ )
617
+
618
+ marker_paths = _paths_with_conflict_markers(worktree_repo_path, conflict_paths)
619
+ if marker_paths:
620
+ raise RuntimeError(
621
+ "conflict markers remain unresolved in: " + ", ".join(marker_paths[:20])
622
+ )
623
+
624
+ stage_all(worktree_repo_path)
625
+ unstage_staged_prefixes(
626
+ worktree_repo_path,
627
+ [".alysis", ".alysis_images", "alysis-feedback"],
628
+ )
629
+ ensure_not_staged_prefixes(
630
+ worktree_repo_path,
631
+ [".alysis", ".alysis_images", "alysis-feedback"],
632
+ )
633
+ if has_grounded_rust_target_runtime_artifacts(worktree_repo_path):
634
+ staged_now = staged_files(worktree_repo_path)
635
+ unstage_staged_runtime_artifacts(
636
+ worktree_repo_path,
637
+ current_paths=staged_now,
638
+ )
639
+ staged_now = staged_files(worktree_repo_path)
640
+ ensure_not_staged_runtime_artifacts(
641
+ worktree_repo_path,
642
+ current_paths=staged_now,
643
+ )
644
+ unresolved_after = list_unmerged_files(worktree_repo_path)
645
+ if unresolved_after:
646
+ raise RuntimeError(
647
+ "conflict paths remain unmerged after staging resolved files: "
648
+ + ", ".join(unresolved_after[:20])
649
+ )
650
+ _run_git_checked(
651
+ worktree_repo_path,
652
+ ["commit", "--no-edit"],
653
+ extra_config={
654
+ "user.name": DEFAULT_COMMIT_AUTHOR_NAME,
655
+ "user.email": DEFAULT_COMMIT_AUTHOR_EMAIL,
656
+ },
657
+ error_message="failed to commit conflict resolution",
658
+ )
659
+
660
+ if effective_settings.verify_mode != "off" and resolved_verify_commands:
661
+ verify_result = run_task_verification(
662
+ root=worktree_repo_path,
663
+ commands=resolved_verify_commands,
664
+ artifact_path=verify_artifact_path,
665
+ cfg=cfg,
666
+ )
667
+ verify_summary = verify_result.summary
668
+ if not verify_result.all_passed:
669
+ if effective_settings.verify_mode == "strict":
670
+ raise RuntimeError(f"strict conflict verify failed: {verify_result.summary}")
671
+ warnings.append(f"Conflict verify warning: {verify_result.summary}")
672
+ elif effective_settings.verify_mode == "strict":
673
+ verify_summary = "verification skipped: no authoritative commands available"
674
+ raise RuntimeError(
675
+ "strict conflict verify could not run: no authoritative commands available"
676
+ )
677
+ elif effective_settings.verify_mode != "off":
678
+ verify_summary = "verification skipped: no authoritative commands available"
679
+ else:
680
+ verify_summary = "verification disabled (conflict auto-resolve verify=off)"
681
+
682
+ merge_commit_hash = _git_head(worktree_repo_path)
683
+ patch_text = format_patch_stdout(worktree_repo_path, base_branch=base_branch)
684
+
685
+ _run_git_checked(
686
+ paths.root,
687
+ ["checkout", base_branch],
688
+ error_message=f"failed to checkout base branch {base_branch}",
689
+ )
690
+ ff_cp = _run_git(paths.root, ["merge", "--ff-only", conflict_branch])
691
+ if ff_cp.returncode != 0:
692
+ try:
693
+ merge_commit_hash = merge_no_ff(
694
+ paths.root,
695
+ base_branch=base_branch,
696
+ task_branch=conflict_branch,
697
+ message=f"Merge {task_id}: auto-resolved conflict",
698
+ )
699
+ except GitOpsError as e:
700
+ cleanup_ok, cleanup_log = try_abort_merge(paths.root, base_branch=base_branch)
701
+ errors.append(f"failed to merge conflict branch into base: {e}")
702
+ if not cleanup_ok:
703
+ errors.append(
704
+ "failed to cleanup repository after merge error; "
705
+ f"cleanup log: {_truncate(cleanup_log, max_chars=600)}"
706
+ )
707
+ raise
708
+ else:
709
+ merge_commit_hash = _git_head(paths.root)
710
+
711
+ if not keep_worktrees:
712
+ try:
713
+ remove_task_worktree(
714
+ root=paths.root,
715
+ worktree_repo_path=worktree_repo_path,
716
+ force=True,
717
+ )
718
+ worktree_kept = False
719
+ except Exception as e: # noqa: BLE001
720
+ warnings.append(f"conflict worktree cleanup failed: {e}")
721
+ try:
722
+ delete_branch(paths.root, conflict_branch)
723
+ except Exception as e: # noqa: BLE001
724
+ warnings.append(f"conflict branch cleanup failed: {e}")
725
+ else:
726
+ worktree_kept = True
727
+
728
+ success = True
729
+ error_message: str | None = None
730
+ except (GitOpsError, RuntimeError, VerifyError, ConfigError) as e:
731
+ success = False
732
+ error_message = str(e)
733
+ errors.append(str(e))
734
+
735
+ finished_at = now_iso()
736
+ result_payload = {
737
+ "task_id": task_id,
738
+ "base_branch": base_branch,
739
+ "task_branch": task_branch,
740
+ "conflict_branch": conflict_branch,
741
+ "worktree_repo_path": os.fspath(worktree_repo_path),
742
+ "worktree_kept": worktree_kept,
743
+ "success": success,
744
+ "merge_commit_hash": merge_commit_hash,
745
+ "verify_mode": effective_settings.verify_mode,
746
+ "verify_summary": verify_summary,
747
+ "verify_artifact_path": (
748
+ os.fspath(verify_artifact_path) if verify_artifact_path.exists() else None
749
+ ),
750
+ "context_artifact_path": (
751
+ os.fspath(conflict_dir / "auto_resolve_context.md")
752
+ if conflict_instruction_text is not None
753
+ else None
754
+ ),
755
+ "budget_artifact_path": (
756
+ os.fspath(conflict_dir / "auto_resolve_budget.json")
757
+ if conflict_budget_payload is not None
758
+ else None
759
+ ),
760
+ "agent_model": agent_model,
761
+ "agent_exit_code": agent_exit_code,
762
+ "salvaged_nonzero_exit": salvaged_nonzero_exit,
763
+ "warnings": warnings,
764
+ "errors": errors,
765
+ "unresolved_after": unresolved_after,
766
+ "started_at": started_at,
767
+ "finished_at": finished_at,
768
+ }
769
+ report_lines = [
770
+ f"# Conflict Auto-Resolve: {task_id}",
771
+ "",
772
+ f"- Success: {'yes' if success else 'no'}",
773
+ f"- Base Branch: `{base_branch}`",
774
+ f"- Task Branch: `{task_branch}`",
775
+ f"- Conflict Branch: `{conflict_branch}`",
776
+ f"- Worktree: `{worktree_repo_path}`",
777
+ f"- Verify Mode: `{effective_settings.verify_mode}`",
778
+ f"- Verify Summary: {verify_summary or '(none)'}",
779
+ f"- Merge Commit: `{merge_commit_hash or '-'}`",
780
+ f"- Agent Exit Code: `{agent_exit_code if agent_exit_code is not None else '-'}`",
781
+ f"- Salvaged Non-Zero Exit: {'yes' if salvaged_nonzero_exit else 'no'}",
782
+ "",
783
+ ]
784
+ if warnings:
785
+ report_lines.append("## Warnings")
786
+ report_lines.append("")
787
+ report_lines.extend(f"- {item}" for item in warnings)
788
+ report_lines.append("")
789
+ if errors:
790
+ report_lines.append("## Errors")
791
+ report_lines.append("")
792
+ report_lines.extend(f"- {item}" for item in errors)
793
+ report_lines.append("")
794
+ if not warnings and not errors:
795
+ report_lines.append("No warnings or errors recorded.")
796
+ report_lines.append("")
797
+ report_text = "\n".join(report_lines).rstrip() + "\n"
798
+
799
+ result_path, report_path, patch_path = _write_auto_resolve_artifacts(
800
+ conflict_dir=conflict_dir,
801
+ result_payload=result_payload,
802
+ report_text=report_text,
803
+ patch_text=patch_text,
804
+ context_text=conflict_instruction_text,
805
+ budget_payload=conflict_budget_payload,
806
+ )
807
+ persisted_capture = persist_execution_knowledge_capture(
808
+ paths=paths,
809
+ task=task,
810
+ source="conflict_auto_resolve",
811
+ assistant_message=recording_surface.final_assistant_message,
812
+ artifact_dir=conflict_dir / "knowledge_capture" / safe_task_file_component(started_at),
813
+ report_path=report_path,
814
+ patch_path=patch_path,
815
+ verify_artifact_path=verify_artifact_path if verify_artifact_path.exists() else None,
816
+ budget_artifact_path=(conflict_dir / "auto_resolve_budget.json")
817
+ if conflict_budget_payload is not None
818
+ else None,
819
+ session_artifact_dir=(conflict_dir / "sessions")
820
+ if (conflict_dir / "sessions").exists()
821
+ else None,
822
+ )
823
+ if success:
824
+ promote_validated_knowledge_capture(
825
+ paths=paths,
826
+ task=task,
827
+ artifact_dir=persisted_capture.artifact_dir,
828
+ )
829
+ else:
830
+ mark_knowledge_capture_promotion_skipped(
831
+ artifact_dir=persisted_capture.artifact_dir,
832
+ reason="conflict auto-resolve outcome was not accepted",
833
+ )
834
+ if success:
835
+ attempt_summary = "Conflict auto-resolve succeeded."
836
+ else:
837
+ attempt_summary = f"Conflict auto-resolve failed: {error_message or 'unknown error'}"
838
+ reported_changed_files = material_changed_files or conflict_paths or unresolved_after
839
+ write_task_attempt_entry(
840
+ paths=paths,
841
+ task=task,
842
+ source="conflict_auto_resolve",
843
+ result="success" if success else "failure",
844
+ summary=attempt_summary,
845
+ changed_files=reported_changed_files,
846
+ verify_summary=verify_summary,
847
+ report_path=report_path,
848
+ patch_path=patch_path,
849
+ verify_artifact_path=verify_artifact_path if verify_artifact_path.exists() else None,
850
+ budget_artifact_path=(conflict_dir / "auto_resolve_budget.json")
851
+ if conflict_budget_payload is not None
852
+ else None,
853
+ session_artifact_dir=(conflict_dir / "sessions")
854
+ if (conflict_dir / "sessions").exists()
855
+ else None,
856
+ acceptance_state="accepted" if success else "rejected",
857
+ extra_tags=[
858
+ "conflict_auto_resolve",
859
+ ],
860
+ )
861
+ if not success or unresolved_after:
862
+ write_issue_entry(
863
+ paths=paths,
864
+ task=task,
865
+ source="conflict_auto_resolve",
866
+ title=f"{task_id}: conflict auto-resolve failed",
867
+ summary=error_message or "Conflict auto-resolve did not finish cleanly.",
868
+ paths_in_scope=unresolved_after or reported_changed_files,
869
+ report_path=report_path,
870
+ patch_path=patch_path,
871
+ verify_artifact_path=verify_artifact_path if verify_artifact_path.exists() else None,
872
+ budget_artifact_path=(conflict_dir / "auto_resolve_budget.json")
873
+ if conflict_budget_payload is not None
874
+ else None,
875
+ session_artifact_dir=(conflict_dir / "sessions")
876
+ if (conflict_dir / "sessions").exists()
877
+ else None,
878
+ tags=["conflict_auto_resolve", "merge_conflict"],
879
+ )
880
+ rebuild_knowledge_index(paths)
881
+ return AutoResolveOutcome(
882
+ success=success,
883
+ task_id=task_id,
884
+ conflict_branch=conflict_branch,
885
+ worktree_repo_path=worktree_repo_path,
886
+ result_json_path=result_path,
887
+ report_path=report_path,
888
+ patch_path=patch_path,
889
+ merge_commit_hash=merge_commit_hash,
890
+ verify_summary=verify_summary,
891
+ warnings=warnings,
892
+ error=error_message,
893
+ agent_exit_code=agent_exit_code,
894
+ salvaged_nonzero_exit=salvaged_nonzero_exit,
895
+ )