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,83 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from ..atomic_io import atomic_write_json
8
+ from .models import ExtensionState, ProjectExtensionOverrides, normalize_extension_id
9
+ from .paths import extensions_state_path, project_extensions_path
10
+
11
+
12
+ def _load_json_object(path: Path) -> dict[str, Any] | None:
13
+ if not path.exists():
14
+ return None
15
+
16
+ try:
17
+ raw = json.loads(path.read_text(encoding="utf-8"))
18
+ except json.JSONDecodeError as e:
19
+ raise RuntimeError(f"Invalid JSON in {path}") from e
20
+ except OSError as e:
21
+ raise RuntimeError(f"Failed to read {path}") from e
22
+
23
+ if not isinstance(raw, dict):
24
+ raise RuntimeError(f"Invalid JSON format in {path}: expected object.")
25
+ return raw
26
+
27
+
28
+ def load_global_state() -> ExtensionState:
29
+ raw = _load_json_object(extensions_state_path())
30
+ if raw is None:
31
+ return ExtensionState()
32
+ return ExtensionState.model_validate(raw)
33
+
34
+
35
+ def save_global_state(state: ExtensionState) -> None:
36
+ atomic_write_json(extensions_state_path(), state.model_dump(mode="json"))
37
+
38
+
39
+ def load_project_state(repo_root: Path) -> ExtensionState:
40
+ raw = _load_json_object(project_extensions_path(repo_root))
41
+ if raw is None:
42
+ return ExtensionState()
43
+ return ExtensionState.model_validate(raw)
44
+
45
+
46
+ def save_project_state(repo_root: Path, state: ExtensionState) -> None:
47
+ atomic_write_json(project_extensions_path(repo_root), state.model_dump(mode="json"))
48
+
49
+
50
+ def load_project_overrides(repo_root: Path) -> ProjectExtensionOverrides:
51
+ raw = _load_json_object(project_extensions_path(repo_root))
52
+ if raw is None:
53
+ return ProjectExtensionOverrides()
54
+ return ProjectExtensionOverrides.model_validate(raw)
55
+
56
+
57
+ def compute_effective_enabled(
58
+ global_state: ExtensionState,
59
+ project_overrides: ProjectExtensionOverrides,
60
+ ) -> set[str]:
61
+ enabled: set[str] = {
62
+ normalize_extension_id(ext_id)
63
+ for ext_id in global_state.enabled
64
+ if normalize_extension_id(ext_id)
65
+ }
66
+
67
+ for ext_id, installed in global_state.installed.items():
68
+ if installed.enabled:
69
+ normalized = normalize_extension_id(ext_id)
70
+ if normalized:
71
+ enabled.add(normalized)
72
+
73
+ for ext_id in project_overrides.disabled:
74
+ normalized = normalize_extension_id(ext_id)
75
+ if normalized:
76
+ enabled.discard(normalized)
77
+
78
+ for ext_id in project_overrides.enabled:
79
+ normalized = normalize_extension_id(ext_id)
80
+ if normalized:
81
+ enabled.add(normalized)
82
+
83
+ return enabled
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ from datetime import UTC, datetime
7
+ from pathlib import Path
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ from ..atomic_io import atomic_write_json
12
+ from .paths import workspace_trust_path as _workspace_trust_path
13
+
14
+ WORKSPACE_TRUST_SCHEMA_VERSION = 1
15
+
16
+
17
+ class WorkspaceTrustEntry(BaseModel):
18
+ model_config = ConfigDict(extra="forbid")
19
+
20
+ workspace_root: str
21
+ granted_at: str
22
+ overrides_sha256: str
23
+
24
+
25
+ class WorkspaceTrustState(BaseModel):
26
+ model_config = ConfigDict(extra="forbid")
27
+
28
+ schema_version: int = WORKSPACE_TRUST_SCHEMA_VERSION
29
+ trusted: dict[str, WorkspaceTrustEntry] = Field(default_factory=dict)
30
+
31
+
32
+ def workspace_trust_path(*, user_config_dir: Path | None = None) -> Path:
33
+ return _workspace_trust_path(user_config_dir=user_config_dir)
34
+
35
+
36
+ def load_workspace_trust(*, user_config_dir: Path | None = None) -> WorkspaceTrustState:
37
+ path = workspace_trust_path(user_config_dir=user_config_dir)
38
+ if not path.exists():
39
+ return WorkspaceTrustState()
40
+ try:
41
+ raw = json.loads(path.read_text(encoding="utf-8"))
42
+ except json.JSONDecodeError as exc:
43
+ raise RuntimeError(f"Invalid JSON in {path}") from exc
44
+ except OSError as exc:
45
+ raise RuntimeError(f"Failed to read {path}") from exc
46
+ if not isinstance(raw, dict):
47
+ raise RuntimeError(f"Invalid JSON format in {path}: expected object.")
48
+ return WorkspaceTrustState.model_validate(raw)
49
+
50
+
51
+ def _canonical_workspace_root(repo_root: Path) -> str:
52
+ resolved = repo_root.expanduser().resolve(strict=False)
53
+ text = os.fspath(resolved)
54
+ if os.name == "nt":
55
+ drive, tail = os.path.splitdrive(text)
56
+ text = drive.lower() + tail
57
+ return text.rstrip("\\/") or text
58
+
59
+
60
+ def workspace_trust_key(repo_root: Path) -> str:
61
+ """SHA-256 of the canonical absolute path."""
62
+ canonical = _canonical_workspace_root(repo_root)
63
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
64
+
65
+
66
+ def is_workspace_trusted(
67
+ *,
68
+ repo_root: Path,
69
+ overrides_sha256: str,
70
+ user_config_dir: Path | None = None,
71
+ ) -> bool:
72
+ """Return True iff this repo and overrides-file hash were explicitly trusted."""
73
+ state = load_workspace_trust(user_config_dir=user_config_dir)
74
+ entry = state.trusted.get(workspace_trust_key(repo_root))
75
+ return entry is not None and entry.overrides_sha256 == overrides_sha256
76
+
77
+
78
+ def grant_workspace_trust(
79
+ *,
80
+ repo_root: Path,
81
+ overrides_sha256: str,
82
+ user_config_dir: Path | None = None,
83
+ ) -> None:
84
+ """Write a workspace trust entry atomically."""
85
+ state = load_workspace_trust(user_config_dir=user_config_dir)
86
+ key = workspace_trust_key(repo_root)
87
+ updated = WorkspaceTrustState(
88
+ schema_version=WORKSPACE_TRUST_SCHEMA_VERSION,
89
+ trusted={
90
+ **state.trusted,
91
+ key: WorkspaceTrustEntry(
92
+ workspace_root=_canonical_workspace_root(repo_root),
93
+ granted_at=datetime.now(UTC).replace(microsecond=0).isoformat(),
94
+ overrides_sha256=overrides_sha256,
95
+ ),
96
+ },
97
+ )
98
+ atomic_write_json(
99
+ workspace_trust_path(user_config_dir=user_config_dir),
100
+ updated.model_dump(mode="json"),
101
+ )
@@ -0,0 +1,369 @@
1
+ """Capture a failed task's work before its workspace is cleaned up.
2
+
3
+ Swarm failure cleanup removes the task worktree and branch so the next run is not
4
+ blocked by leftover state. That is correct housekeeping and wrong data handling: the
5
+ worktree is the only place some of the work exists. An uncommitted edit, or a new
6
+ file the worker never got to commit, disappeared with the directory.
7
+
8
+ So every cleanup path first calls :func:`preserve_failed_task_evidence`, which
9
+ writes the task's full diff (committed *and* uncommitted, plus untracked file
10
+ contents) and its verification log into ``<run>/execution/evidence/<task>/``. That
11
+ directory lives in the run, not in the worktree, so cleanup can then proceed exactly
12
+ as configured.
13
+
14
+ Capture is best effort by construction: it must never raise, never mutate the
15
+ workspace it is reading (no ``git add``, no index writes), and never be the reason a
16
+ cleanup is skipped. Whatever it could not capture is recorded in ``errors`` and in
17
+ ``evidence.json`` rather than silently dropped.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import subprocess
25
+ from collections.abc import Callable
26
+ from dataclasses import dataclass, field
27
+ from datetime import UTC, datetime
28
+ from pathlib import Path
29
+
30
+ from .execution_shared import safe_task_file_component
31
+ from .git_safe import build_git_cmd
32
+
33
+ EVIDENCE_DIR_NAME = "evidence"
34
+ PATCH_FILE_NAME = "patch.diff"
35
+ VERIFICATION_LOG_FILE_NAME = "verification.log"
36
+ METADATA_FILE_NAME = "evidence.json"
37
+
38
+ # Untracked files are the work most at risk (a new module the worker never
39
+ # committed), so their contents are captured, but a stray build directory must not
40
+ # turn evidence capture into a disk-filling copy.
41
+ MAX_UNTRACKED_FILES = 200
42
+ MAX_UNTRACKED_FILE_BYTES = 256 * 1024
43
+ MAX_UNTRACKED_TOTAL_BYTES = 8 * 1024 * 1024
44
+ _GIT_TIMEOUT_S = 120
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class PreservedTaskEvidence:
49
+ """Where the evidence landed, and what could not be captured."""
50
+
51
+ task_id: str
52
+ directory: Path | None
53
+ patch_path: Path | None = None
54
+ verification_log_path: Path | None = None
55
+ metadata_path: Path | None = None
56
+ captured_untracked_files: tuple[str, ...] = ()
57
+ truncated: bool = False
58
+ errors: tuple[str, ...] = ()
59
+
60
+ @property
61
+ def captured(self) -> bool:
62
+ return self.patch_path is not None or self.verification_log_path is not None
63
+
64
+ def to_payload(self) -> dict[str, object]:
65
+ return {
66
+ "task_id": self.task_id,
67
+ "directory": os.fspath(self.directory) if self.directory else None,
68
+ "patch_path": os.fspath(self.patch_path) if self.patch_path else None,
69
+ "verification_log_path": (
70
+ os.fspath(self.verification_log_path) if self.verification_log_path else None
71
+ ),
72
+ "captured_untracked_files": list(self.captured_untracked_files),
73
+ "truncated": bool(self.truncated),
74
+ "errors": list(self.errors),
75
+ }
76
+
77
+
78
+ GitRunner = Callable[[Path, list[str]], "subprocess.CompletedProcess[str]"]
79
+
80
+
81
+ def _default_git_runner(root: Path, args: list[str]) -> subprocess.CompletedProcess[str]:
82
+ return subprocess.run(
83
+ build_git_cmd(root, args),
84
+ check=False,
85
+ capture_output=True,
86
+ text=True,
87
+ timeout=_GIT_TIMEOUT_S,
88
+ )
89
+
90
+
91
+ def evidence_dir_for(execution_dir: Path, task_id: str) -> Path:
92
+ return Path(execution_dir) / EVIDENCE_DIR_NAME / safe_task_file_component(task_id)
93
+
94
+
95
+ def preserve_failed_task_evidence(
96
+ *,
97
+ execution_dir: Path,
98
+ task_id: str,
99
+ worktree_path: Path | None,
100
+ base_branch: str | None = None,
101
+ branch: str | None = None,
102
+ reason: str = "",
103
+ verification_log_path: Path | None = None,
104
+ verification_summary: str | None = None,
105
+ extra: dict[str, object] | None = None,
106
+ git_runner: GitRunner | None = None,
107
+ ) -> PreservedTaskEvidence:
108
+ """Write a failed task's diff and verification log into the run's artifacts.
109
+
110
+ Returns what was captured. Never raises: a capture failure is reported through
111
+ :attr:`PreservedTaskEvidence.errors` so the caller can warn and still clean up.
112
+ """
113
+ errors: list[str] = []
114
+ directory: Path | None = None
115
+ try:
116
+ directory = evidence_dir_for(execution_dir, task_id)
117
+ directory.mkdir(parents=True, exist_ok=True)
118
+ except OSError as e:
119
+ return PreservedTaskEvidence(
120
+ task_id=str(task_id),
121
+ directory=None,
122
+ errors=(f"could not create evidence directory: {e}",),
123
+ )
124
+
125
+ patch_path: Path | None = None
126
+ captured_untracked: tuple[str, ...] = ()
127
+ truncated = False
128
+ runner = git_runner or _default_git_runner
129
+ resolved_worktree = Path(worktree_path) if worktree_path is not None else None
130
+
131
+ if resolved_worktree is not None and resolved_worktree.exists():
132
+ capture = _capture_worktree_patch(
133
+ worktree_path=resolved_worktree,
134
+ base_branch=base_branch,
135
+ runner=runner,
136
+ )
137
+ errors.extend(capture.errors)
138
+ captured_untracked = capture.untracked_files
139
+ truncated = capture.truncated
140
+ try:
141
+ target = directory / PATCH_FILE_NAME
142
+ target.write_text(capture.text, encoding="utf-8")
143
+ patch_path = target
144
+ except OSError as e:
145
+ errors.append(f"could not write evidence patch: {e}")
146
+ elif resolved_worktree is None:
147
+ errors.append("no task workspace path was available to capture a diff from")
148
+ else:
149
+ errors.append(f"task workspace no longer exists: {os.fspath(resolved_worktree)}")
150
+
151
+ stored_log: Path | None = None
152
+ try:
153
+ stored_log = _write_verification_log(
154
+ directory=directory,
155
+ source=verification_log_path,
156
+ summary=verification_summary,
157
+ )
158
+ except OSError as e:
159
+ errors.append(f"could not write verification log: {e}")
160
+
161
+ metadata_path: Path | None = None
162
+ payload: dict[str, object] = {
163
+ "schema_version": 1,
164
+ "task_id": str(task_id),
165
+ "captured_at": datetime.now(UTC).isoformat(),
166
+ "reason": str(reason or ""),
167
+ "branch": str(branch or ""),
168
+ "base_branch": str(base_branch or ""),
169
+ "worktree_path": os.fspath(resolved_worktree) if resolved_worktree else None,
170
+ "patch_file": PATCH_FILE_NAME if patch_path is not None else None,
171
+ "verification_log_file": (VERIFICATION_LOG_FILE_NAME if stored_log is not None else None),
172
+ "verification_summary": verification_summary,
173
+ "captured_untracked_files": list(captured_untracked),
174
+ "truncated": bool(truncated),
175
+ "errors": list(errors),
176
+ }
177
+ if extra:
178
+ payload["extra"] = dict(extra)
179
+ try:
180
+ metadata_path = directory / METADATA_FILE_NAME
181
+ metadata_path.write_text(
182
+ json.dumps(payload, indent=2, sort_keys=True, default=str) + "\n",
183
+ encoding="utf-8",
184
+ )
185
+ except OSError as e:
186
+ metadata_path = None
187
+ errors.append(f"could not write evidence metadata: {e}")
188
+
189
+ return PreservedTaskEvidence(
190
+ task_id=str(task_id),
191
+ directory=directory,
192
+ patch_path=patch_path,
193
+ verification_log_path=stored_log,
194
+ metadata_path=metadata_path,
195
+ captured_untracked_files=captured_untracked,
196
+ truncated=truncated,
197
+ errors=tuple(errors),
198
+ )
199
+
200
+
201
+ @dataclass
202
+ class _PatchCapture:
203
+ text: str
204
+ untracked_files: tuple[str, ...] = ()
205
+ truncated: bool = False
206
+ errors: list[str] = field(default_factory=list)
207
+
208
+
209
+ def _capture_worktree_patch(
210
+ *,
211
+ worktree_path: Path,
212
+ base_branch: str | None,
213
+ runner: GitRunner,
214
+ ) -> _PatchCapture:
215
+ """Build one text artifact holding everything the worktree still has.
216
+
217
+ ``git diff <base>`` is used rather than ``format-patch``: it compares the working
218
+ tree against the base, so committed and uncommitted changes are both in the
219
+ output without staging anything.
220
+ """
221
+ sections: list[str] = [
222
+ "# Preserved Task Evidence",
223
+ f"# worktree: {os.fspath(worktree_path)}",
224
+ f"# base branch: {base_branch or '(unknown)'}",
225
+ "",
226
+ ]
227
+ errors: list[str] = []
228
+
229
+ status_ok, status_text = _git_text(runner, worktree_path, ["status", "--porcelain"])
230
+ if status_ok:
231
+ sections.extend(
232
+ ["## git status --porcelain", "", status_text.rstrip("\n") or "(clean)", ""]
233
+ )
234
+ else:
235
+ errors.append(f"git status failed: {status_text.strip() or 'unknown error'}")
236
+
237
+ if base_branch:
238
+ diff_ok, diff_text = _git_text(runner, worktree_path, ["diff", str(base_branch)])
239
+ label = f"## git diff {base_branch} (tracked changes vs base, committed and uncommitted)"
240
+ if diff_ok:
241
+ sections.extend([label, "", diff_text.rstrip("\n") or "(no tracked changes)", ""])
242
+ else:
243
+ errors.append(f"git diff against {base_branch} failed: {diff_text.strip()}")
244
+
245
+ # Always capture the working-tree delta too: when the base ref is unknown or the
246
+ # diff above failed, this is the only record of uncommitted work.
247
+ head_ok, head_text = _git_text(runner, worktree_path, ["diff", "HEAD"])
248
+ if head_ok:
249
+ sections.extend(
250
+ [
251
+ "## git diff HEAD (uncommitted tracked changes)",
252
+ "",
253
+ head_text.rstrip("\n") or "(none)",
254
+ "",
255
+ ]
256
+ )
257
+ else:
258
+ errors.append(f"git diff HEAD failed: {head_text.strip() or 'unknown error'}")
259
+
260
+ untracked_ok, untracked_text = _git_text(
261
+ runner,
262
+ worktree_path,
263
+ ["ls-files", "--others", "--exclude-standard"],
264
+ )
265
+ captured: list[str] = []
266
+ truncated = False
267
+ if untracked_ok:
268
+ candidates = [line.strip() for line in untracked_text.splitlines() if line.strip()]
269
+ if len(candidates) > MAX_UNTRACKED_FILES:
270
+ truncated = True
271
+ sections.append(
272
+ f"## NOTE: {len(candidates)} untracked files found; "
273
+ f"capturing the first {MAX_UNTRACKED_FILES}."
274
+ )
275
+ sections.append("")
276
+ candidates = candidates[:MAX_UNTRACKED_FILES]
277
+ total_bytes = 0
278
+ for relative in candidates:
279
+ path = worktree_path / relative
280
+ try:
281
+ if not path.is_file():
282
+ continue
283
+ size = path.stat().st_size
284
+ except OSError as e:
285
+ errors.append(f"could not stat untracked file {relative}: {e}")
286
+ continue
287
+ if size > MAX_UNTRACKED_FILE_BYTES or total_bytes + size > MAX_UNTRACKED_TOTAL_BYTES:
288
+ truncated = True
289
+ sections.extend(
290
+ [f"## untracked file: {relative}", "", f"(skipped: {size} bytes)", ""]
291
+ )
292
+ continue
293
+ try:
294
+ content = path.read_text(encoding="utf-8", errors="replace")
295
+ except OSError as e:
296
+ errors.append(f"could not read untracked file {relative}: {e}")
297
+ continue
298
+ total_bytes += size
299
+ captured.append(relative)
300
+ sections.extend([f"## untracked file: {relative}", "", content.rstrip("\n"), ""])
301
+ else:
302
+ errors.append(f"git ls-files failed: {untracked_text.strip() or 'unknown error'}")
303
+
304
+ if errors:
305
+ sections.extend(["## capture warnings", "", *[f"- {item}" for item in errors], ""])
306
+
307
+ return _PatchCapture(
308
+ text="\n".join(sections) + "\n",
309
+ untracked_files=tuple(captured),
310
+ truncated=truncated,
311
+ errors=errors,
312
+ )
313
+
314
+
315
+ def _git_text(runner: GitRunner, root: Path, args: list[str]) -> tuple[bool, str]:
316
+ try:
317
+ cp = runner(root, args)
318
+ except (OSError, subprocess.SubprocessError) as e:
319
+ return False, str(e)
320
+ stdout = cp.stdout or ""
321
+ if cp.returncode != 0:
322
+ return False, (cp.stderr or stdout or "").strip()
323
+ return True, stdout
324
+
325
+
326
+ def _write_verification_log(
327
+ *,
328
+ directory: Path,
329
+ source: Path | None,
330
+ summary: str | None,
331
+ ) -> Path | None:
332
+ target = directory / VERIFICATION_LOG_FILE_NAME
333
+ if source is not None:
334
+ resolved = Path(source)
335
+ if resolved.exists():
336
+ try:
337
+ text = resolved.read_text(encoding="utf-8", errors="replace")
338
+ except OSError as e:
339
+ text = f"(could not read verification artifact {os.fspath(resolved)}: {e})\n"
340
+ header = f"# Verification log copied from {os.fspath(resolved)}\n\n"
341
+ target.write_text(header + text, encoding="utf-8")
342
+ return target
343
+ if summary and str(summary).strip():
344
+ target.write_text(
345
+ "# Verification log\n\n"
346
+ "No verification artifact file was produced for this task.\n\n"
347
+ f"Summary: {summary}\n",
348
+ encoding="utf-8",
349
+ )
350
+ return target
351
+ target.write_text(
352
+ "# Verification log\n\nVerification produced no artifact and no summary for this task.\n",
353
+ encoding="utf-8",
354
+ )
355
+ return target
356
+
357
+
358
+ __all__ = [
359
+ "EVIDENCE_DIR_NAME",
360
+ "MAX_UNTRACKED_FILES",
361
+ "MAX_UNTRACKED_FILE_BYTES",
362
+ "MAX_UNTRACKED_TOTAL_BYTES",
363
+ "METADATA_FILE_NAME",
364
+ "PATCH_FILE_NAME",
365
+ "VERIFICATION_LOG_FILE_NAME",
366
+ "PreservedTaskEvidence",
367
+ "evidence_dir_for",
368
+ "preserve_failed_task_evidence",
369
+ ]