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,915 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import re
6
+ import warnings
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .config import AppConfig
12
+ from .knowledge_capture import render_execution_knowledge_capture_rules
13
+ from .model_registry import ModelRegistry
14
+ from .token_budget import compute_input_budget, estimate_tokens, trim_text_to_budget
15
+
16
+ _IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tif", ".tiff"}
17
+ _ASSET_FALLBACK_COUNT = 6
18
+ _IMAGE_FALLBACK_COUNT = 4
19
+ _DEFAULT_EXECUTION_PLAN_REQUIREMENTS_COUNT = 12
20
+ _DEFAULT_EXECUTION_PLAN_TASK_COUNT = 16
21
+ _DEFAULT_EXECUTION_ASSET_COUNT = 12
22
+ LOGGER = logging.getLogger(__name__)
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class _ExecutionPackReductionState:
27
+ requirement_count: int
28
+ task_list_count: int
29
+ asset_count: int
30
+ project_goal_chars: int
31
+ summary_chars: int
32
+ task_description_chars: int
33
+ acceptance_criteria_count: int
34
+ estimated_files_count: int
35
+ write_scope_count: int
36
+ relevant_assets_full_inline_count: int
37
+ relevant_assets_focused_count: int
38
+ relevant_assets_reference_count: int
39
+ include_may_need_section: bool
40
+ include_pinned_section: bool
41
+ strategy: str
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class TaskContextPackResult:
46
+ content: str
47
+ artifact_text: str
48
+ instruction_token_budget: int
49
+ instruction_token_estimate: int
50
+ truncated: bool
51
+ truncation_strategy: str
52
+
53
+
54
+ def _string_list(value: Any) -> list[str]:
55
+ if not isinstance(value, list):
56
+ return []
57
+ out: list[str] = []
58
+ for item in value:
59
+ text = str(item).strip()
60
+ if text:
61
+ out.append(text)
62
+ return out
63
+
64
+
65
+ def _task_text_blob(task: dict[str, Any]) -> str:
66
+ text_parts = [
67
+ str(task.get("title") or ""),
68
+ str(task.get("description") or ""),
69
+ *[str(x) for x in _string_list(task.get("acceptance_criteria"))],
70
+ ]
71
+ return " ".join(text_parts).strip().lower()
72
+
73
+
74
+ def _scope_entries(task: dict[str, Any]) -> list[str]:
75
+ return [
76
+ item.lower()
77
+ for item in (
78
+ _string_list(task.get("estimated_files")) + _string_list(task.get("write_scope"))
79
+ )
80
+ ]
81
+
82
+
83
+ def compact_plan_for_execution(plan: dict[str, Any]) -> dict[str, Any]:
84
+ tasks_compact: list[dict[str, Any]] = []
85
+ for task in plan.get("tasks") or []:
86
+ if not isinstance(task, dict):
87
+ continue
88
+ tasks_compact.append(
89
+ {
90
+ "id": str(task.get("id") or "").strip(),
91
+ "title": str(task.get("title") or "").strip(),
92
+ "deps": _string_list(task.get("dependencies")),
93
+ }
94
+ )
95
+
96
+ requirements = _string_list(plan.get("requirements"))
97
+ assets_compact: list[dict[str, str]] = []
98
+ assets_raw = plan.get("assets") or []
99
+ uses_legacy_assets = _uses_legacy_assets(plan)
100
+ if uses_legacy_assets and isinstance(assets_raw, list):
101
+ for asset in assets_raw[:25]:
102
+ if not isinstance(asset, dict):
103
+ continue
104
+ assets_compact.append(
105
+ {
106
+ "stored_path": str(asset.get("stored_path") or "").strip(),
107
+ "text_copy_path": str(asset.get("text_copy_path") or "").strip(),
108
+ }
109
+ )
110
+
111
+ return {
112
+ "schema_version": _plan_schema_version(plan),
113
+ "project_goal": str(plan.get("project_goal") or "").strip(),
114
+ "summary": str(plan.get("summary") or "").strip(),
115
+ "requirements_tail": requirements[-20:],
116
+ "tasks": tasks_compact,
117
+ "assets": assets_compact,
118
+ "uses_legacy_assets": uses_legacy_assets,
119
+ }
120
+
121
+
122
+ def _asset_relevance_score(asset: dict[str, Any], *, task: dict[str, Any]) -> int:
123
+ stored_path = str(asset.get("stored_path") or "").strip()
124
+ text_copy_path = str(asset.get("text_copy_path") or "").strip()
125
+ basename = Path(stored_path).name.lower()
126
+ text_basename = Path(text_copy_path).name.lower()
127
+ stem = Path(stored_path).stem.lower()
128
+ task_text = _task_text_blob(task)
129
+ scopes = _scope_entries(task)
130
+
131
+ score = 0
132
+ if basename and basename in task_text:
133
+ score += 6
134
+ if text_basename and text_basename in task_text:
135
+ score += 5
136
+ if stem and stem in task_text:
137
+ score += 4
138
+
139
+ for scope in scopes:
140
+ if not scope:
141
+ continue
142
+ if basename and (basename in scope or scope in basename):
143
+ score += 3
144
+ if stem and stem in scope:
145
+ score += 2
146
+ if text_basename and text_basename in scope:
147
+ score += 2
148
+
149
+ return score
150
+
151
+
152
+ def select_relevant_assets(plan: dict[str, Any], task: dict[str, Any]) -> list[dict[str, Any]]:
153
+ if not _uses_legacy_assets(plan):
154
+ return []
155
+ warnings.warn(
156
+ "select_relevant_assets() is deprecated. Use task asset_briefing and the "
157
+ "first-class AssetIndex-based asset system.",
158
+ DeprecationWarning,
159
+ stacklevel=2,
160
+ )
161
+ LOGGER.warning("select_relevant_assets deprecated legacy_flow=true")
162
+ assets = [asset for asset in (plan.get("assets") or []) if isinstance(asset, dict)]
163
+ if not assets:
164
+ return []
165
+
166
+ scored: list[tuple[int, int, dict[str, Any]]] = []
167
+ for idx, asset in enumerate(assets):
168
+ score = _asset_relevance_score(asset, task=task)
169
+ scored.append((score, idx, asset))
170
+
171
+ matches = [item for item in scored if item[0] > 0]
172
+ if matches:
173
+ matches.sort(key=lambda item: (-item[0], item[1]))
174
+ return [asset for _score, _idx, asset in matches]
175
+
176
+ return assets[-_ASSET_FALLBACK_COUNT:]
177
+
178
+
179
+ def _is_image_asset(asset: dict[str, Any]) -> bool:
180
+ stored_path = str(asset.get("stored_path") or "").strip()
181
+ if not stored_path:
182
+ return False
183
+ return Path(stored_path).suffix.lower() in _IMAGE_EXTENSIONS
184
+
185
+
186
+ def select_relevant_image_paths(
187
+ *,
188
+ plan: dict[str, Any],
189
+ task: dict[str, Any],
190
+ root: Path,
191
+ max_images: int = _IMAGE_FALLBACK_COUNT,
192
+ ) -> list[str]:
193
+ if not _uses_legacy_assets(plan):
194
+ return []
195
+ warnings.warn(
196
+ "select_relevant_image_paths() is deprecated. Use cfg.assets.worker.inline_images "
197
+ "with first-class task asset bindings.",
198
+ DeprecationWarning,
199
+ stacklevel=2,
200
+ )
201
+ LOGGER.warning("select_relevant_image_paths deprecated legacy_flow=true")
202
+ selected_assets = [
203
+ asset for asset in select_relevant_assets(plan, task) if _is_image_asset(asset)
204
+ ]
205
+ if not selected_assets:
206
+ all_images = [
207
+ asset
208
+ for asset in (plan.get("assets") or [])
209
+ if isinstance(asset, dict) and _is_image_asset(asset)
210
+ ]
211
+ selected_assets = all_images[-max_images:]
212
+
213
+ paths: list[str] = []
214
+ seen: set[str] = set()
215
+ root_resolved = root.resolve()
216
+ for asset in selected_assets:
217
+ stored_path = str(asset.get("stored_path") or "").strip()
218
+ if not stored_path:
219
+ continue
220
+ candidate = (root / stored_path).resolve()
221
+ try:
222
+ candidate.relative_to(root_resolved)
223
+ except ValueError:
224
+ continue
225
+ if not candidate.exists() or not candidate.is_file():
226
+ continue
227
+ path_s = os.fspath(candidate)
228
+ if path_s in seen:
229
+ continue
230
+ seen.add(path_s)
231
+ paths.append(path_s)
232
+ if len(paths) >= max_images:
233
+ break
234
+ return paths
235
+
236
+
237
+ def _truncate_value(text: str, max_chars: int) -> str:
238
+ raw = text.strip()
239
+ if not raw:
240
+ return ""
241
+ if max_chars <= 0:
242
+ return "(omitted to fit budget)"
243
+ if len(raw) <= max_chars:
244
+ return raw
245
+ if max_chars <= 3:
246
+ return raw[:max_chars]
247
+ return raw[: max_chars - 3].rstrip() + "..."
248
+
249
+
250
+ def _preview_list(items: list[str], *, limit: int, empty_label: str = "(none)") -> str:
251
+ normalized = [item.strip() for item in items if item and item.strip()]
252
+ if not normalized:
253
+ return empty_label
254
+ effective_limit = max(0, int(limit))
255
+ if effective_limit <= 0:
256
+ return "(omitted to fit budget)"
257
+ if len(normalized) <= effective_limit:
258
+ return ", ".join(normalized)
259
+ shown = ", ".join(normalized[:effective_limit])
260
+ return f"{shown}, ... (+{len(normalized) - effective_limit} more)"
261
+
262
+
263
+ def _render_plan_lines(
264
+ *,
265
+ compact: dict[str, Any],
266
+ state: _ExecutionPackReductionState,
267
+ ) -> list[str]:
268
+ plan_lines: list[str] = [
269
+ "## Plan Summary",
270
+ "",
271
+ f"- Project Goal: {_truncate_value(str(compact.get('project_goal') or ''), state.project_goal_chars) or '(not set)'}",
272
+ f"- Summary: {_truncate_value(str(compact.get('summary') or ''), state.summary_chars) or '(not set)'}",
273
+ "- Recent Requirements:",
274
+ ]
275
+ requirements_tail = list(compact.get("requirements_tail") or [])
276
+ if state.requirement_count > 0 and requirements_tail:
277
+ selected_requirements = requirements_tail[-state.requirement_count :]
278
+ for req in selected_requirements:
279
+ plan_lines.append(f" - {req}")
280
+ omitted_requirements = len(requirements_tail) - len(selected_requirements)
281
+ if omitted_requirements > 0:
282
+ plan_lines.append(f" - ... ({omitted_requirements} earlier requirements omitted)")
283
+ else:
284
+ plan_lines.append(" - (omitted to fit budget)" if requirements_tail else " - (none)")
285
+
286
+ plan_lines.append("- Task List:")
287
+ tasks = list(compact.get("tasks") or [])
288
+ if state.task_list_count > 0 and tasks:
289
+ selected_tasks = tasks[: state.task_list_count]
290
+ for item in selected_tasks:
291
+ deps = ", ".join(item.get("deps") or []) or "(none)"
292
+ plan_lines.append(f" - {item.get('id', '')}: {item.get('title', '')} (deps: {deps})")
293
+ omitted_tasks = len(tasks) - len(selected_tasks)
294
+ if omitted_tasks > 0:
295
+ plan_lines.append(f" - ... ({omitted_tasks} additional tasks omitted)")
296
+ else:
297
+ plan_lines.append(" - (omitted to fit budget)" if tasks else " - (none)")
298
+ return plan_lines
299
+
300
+
301
+ def _render_assets_lines(
302
+ *,
303
+ selected_assets: list[dict[str, Any]],
304
+ asset_count: int,
305
+ plan_schema_version: int,
306
+ uses_legacy_assets: bool,
307
+ ) -> list[str]:
308
+ if not uses_legacy_assets or plan_schema_version >= 2:
309
+ if selected_assets:
310
+ LOGGER.warning(
311
+ "legacy_selected_assets suppressed for schema_version=%s", plan_schema_version
312
+ )
313
+ return []
314
+ assets_lines: list[str] = ["## Selected Assets", ""]
315
+ if not selected_assets:
316
+ assets_lines.append("- (none)")
317
+ assets_lines.append(
318
+ "- If text_copy is available, read that file for extracted text details."
319
+ )
320
+ return assets_lines
321
+
322
+ effective_count = max(1, min(max(0, int(asset_count)), len(selected_assets)))
323
+ shown_assets = selected_assets[:effective_count]
324
+ for asset in shown_assets:
325
+ stored = str(asset.get("stored_path") or "").strip() or "(missing stored_path)"
326
+ text_copy = str(asset.get("text_copy_path") or "").strip() or "(none)"
327
+ assets_lines.append(f"- stored={stored}; text_copy={text_copy}")
328
+ omitted_assets = len(selected_assets) - len(shown_assets)
329
+ if omitted_assets > 0:
330
+ assets_lines.append(f"- ... ({omitted_assets} additional assets omitted)")
331
+ assets_lines.append("- If text_copy is available, read that file for extracted text details.")
332
+ return assets_lines
333
+
334
+
335
+ def _plan_schema_version(plan: dict[str, Any]) -> int:
336
+ try:
337
+ return int(plan.get("schema_version", 1) or 1)
338
+ except (TypeError, ValueError):
339
+ return 1
340
+
341
+
342
+ def _uses_legacy_assets(plan: dict[str, Any]) -> bool:
343
+ if _plan_schema_version(plan) >= 2:
344
+ return False
345
+ for task in plan.get("tasks") or []:
346
+ if isinstance(task, dict) and "asset_briefing" in task:
347
+ return False
348
+ return True
349
+
350
+
351
+ def _safe_int(value: Any, *, default: int = 0) -> int:
352
+ try:
353
+ return int(value)
354
+ except (TypeError, ValueError):
355
+ return default
356
+
357
+
358
+ def _render_task_lines(
359
+ *,
360
+ task: dict[str, Any],
361
+ state: _ExecutionPackReductionState,
362
+ ) -> list[str]:
363
+ task_lines: list[str] = [
364
+ "## Task Specification",
365
+ "",
366
+ f"- ID: `{str(task.get('id') or '').strip()}`",
367
+ f"- Title: {str(task.get('title') or '').strip() or '(none)'}",
368
+ (
369
+ "- Description: "
370
+ + (
371
+ _truncate_value(
372
+ str(task.get("description") or ""),
373
+ state.task_description_chars,
374
+ )
375
+ or "(none)"
376
+ )
377
+ ),
378
+ (
379
+ "- Acceptance Criteria: "
380
+ + _preview_list(
381
+ _string_list(task.get("acceptance_criteria")),
382
+ limit=state.acceptance_criteria_count,
383
+ )
384
+ ),
385
+ "- Dependencies: " + (", ".join(_string_list(task.get("dependencies"))) or "(none)"),
386
+ (
387
+ "- Estimated Files: "
388
+ + _preview_list(
389
+ _string_list(task.get("estimated_files")),
390
+ limit=state.estimated_files_count,
391
+ )
392
+ ),
393
+ (
394
+ "- Write Scope: "
395
+ + _preview_list(
396
+ _string_list(task.get("write_scope")),
397
+ limit=state.write_scope_count,
398
+ )
399
+ ),
400
+ f"- Branch: `{str(task.get('branch') or '').strip() or '(not set)'}`",
401
+ f"- Status: `{str(task.get('status') or '').strip() or '(unknown)'}`",
402
+ ]
403
+ return task_lines
404
+
405
+
406
+ def _reduce_relevant_assets_section(
407
+ section: str,
408
+ *,
409
+ state: _ExecutionPackReductionState,
410
+ ) -> list[str]:
411
+ text = str(section or "").strip()
412
+ if not text:
413
+ return []
414
+ if not state.include_may_need_section:
415
+ text = _drop_named_section(text, "May-need assets")
416
+ if not state.include_pinned_section:
417
+ text = _drop_named_section(text, "Pinned assets")
418
+ if state.relevant_assets_full_inline_count <= 0 and state.relevant_assets_focused_count <= 0:
419
+ text = _drop_asset_content_blocks(text)
420
+ text = _reduce_relevant_asset_blocks(text, state=state)
421
+ return [line.rstrip() for line in text.splitlines()]
422
+
423
+
424
+ def _drop_named_section(text: str, heading: str) -> str:
425
+ lines = text.splitlines()
426
+ out: list[str] = []
427
+ skipping = False
428
+ for line in lines:
429
+ if line.strip().startswith(heading):
430
+ skipping = True
431
+ continue
432
+ if skipping and line.startswith("### "):
433
+ continue
434
+ if skipping and line.endswith(":") and not line.startswith("- "):
435
+ skipping = False
436
+ if not skipping:
437
+ out.append(line)
438
+ return "\n".join(out).strip()
439
+
440
+
441
+ def _drop_asset_content_blocks(text: str) -> str:
442
+ return re.sub(
443
+ r"\n?<asset_content\b[^>]*>.*?</asset_content>\n?",
444
+ "\n",
445
+ text,
446
+ flags=re.DOTALL,
447
+ ).strip()
448
+
449
+
450
+ def _reduce_relevant_asset_blocks(text: str, *, state: _ExecutionPackReductionState) -> str:
451
+ prelude, blocks = _split_relevant_asset_blocks(text)
452
+ if not blocks:
453
+ return text
454
+ remaining_blocks = max(0, int(state.relevant_assets_reference_count))
455
+ if remaining_blocks <= 0:
456
+ return ""
457
+ remaining_full = max(0, int(state.relevant_assets_full_inline_count))
458
+ remaining_focused = max(0, int(state.relevant_assets_focused_count))
459
+ out: list[str] = list(prelude)
460
+ for block in blocks:
461
+ if remaining_blocks <= 0:
462
+ continue
463
+ remaining_blocks -= 1
464
+ mode = _asset_block_mode(block)
465
+ block_text = "\n".join(block).strip()
466
+ if mode == "full_inline":
467
+ if remaining_full > 0:
468
+ remaining_full -= 1
469
+ else:
470
+ block_text = _downgrade_asset_block_to_reference(block_text)
471
+ elif mode == "focused_extract":
472
+ if remaining_focused > 0:
473
+ remaining_focused -= 1
474
+ else:
475
+ block_text = _downgrade_asset_block_to_reference(block_text)
476
+ if block_text:
477
+ if out and out[-1].strip():
478
+ out.append("")
479
+ out.extend(block_text.splitlines())
480
+ return "\n".join(out).strip()
481
+
482
+
483
+ def _split_relevant_asset_blocks(text: str) -> tuple[list[str], list[list[str]]]:
484
+ prelude: list[str] = []
485
+ blocks: list[list[str]] = []
486
+ current: list[str] | None = None
487
+ for line in text.splitlines():
488
+ if line.startswith("### "):
489
+ if current is not None:
490
+ blocks.append(current)
491
+ current = [line]
492
+ continue
493
+ if current is None:
494
+ prelude.append(line)
495
+ else:
496
+ current.append(line)
497
+ if current is not None:
498
+ blocks.append(current)
499
+ return prelude, blocks
500
+
501
+
502
+ def _asset_block_mode(block: list[str]) -> str:
503
+ for line in block:
504
+ if line.startswith("- Mode: full_inline"):
505
+ return "full_inline"
506
+ if line.startswith("- Mode: focused_extract"):
507
+ return "focused_extract"
508
+ if line.startswith("- Mode: reference_only"):
509
+ return "reference_only"
510
+ return "summary"
511
+
512
+
513
+ def _downgrade_asset_block_to_reference(block_text: str) -> str:
514
+ without_content = _drop_asset_content_blocks(block_text)
515
+ lines: list[str] = []
516
+ replaced = False
517
+ for line in without_content.splitlines():
518
+ if line.startswith("- Mode: "):
519
+ lines.append(
520
+ "- Mode: reference_only - inline content omitted by budget reduction; "
521
+ "consult asset_read if needed"
522
+ )
523
+ replaced = True
524
+ else:
525
+ lines.append(line)
526
+ if not replaced:
527
+ lines.insert(
528
+ 1 if lines else 0,
529
+ "- Mode: reference_only - inline content omitted by budget reduction; consult asset_read if needed",
530
+ )
531
+ return "\n".join(lines).strip()
532
+
533
+
534
+ def _relevant_asset_counts(section: str | None) -> tuple[int, int, int]:
535
+ text = str(section or "")
536
+ if not text.strip():
537
+ return 0, 0, 0
538
+ blocks = len(re.findall(r"^### ", text, flags=re.MULTILINE))
539
+ full = len(re.findall(r"^- Mode: full_inline", text, flags=re.MULTILINE))
540
+ focused = len(re.findall(r"^- Mode: focused_extract", text, flags=re.MULTILINE))
541
+ return blocks, full, focused
542
+
543
+
544
+ def _render_execution_pack(
545
+ *,
546
+ role_model: str,
547
+ resolved_budget: int,
548
+ compact: dict[str, Any],
549
+ selected_assets: list[dict[str, Any]],
550
+ task: dict[str, Any],
551
+ state: _ExecutionPackReductionState,
552
+ note: str | None,
553
+ leading_sections: list[str] | None = None,
554
+ relevant_assets_section: str | None = None,
555
+ ) -> str:
556
+ assets_lines = _render_assets_lines(
557
+ selected_assets=selected_assets,
558
+ asset_count=state.asset_count,
559
+ plan_schema_version=_safe_int(compact.get("schema_version"), default=1),
560
+ uses_legacy_assets=bool(compact.get("uses_legacy_assets")),
561
+ )
562
+ task_lines = _render_task_lines(task=task, state=state)
563
+ plan_lines = _render_plan_lines(compact=compact, state=state)
564
+ rules_lines: list[str] = [
565
+ "## Execution Rules",
566
+ "",
567
+ "- Implement ONLY this task; do not start other tasks.",
568
+ "- Read files before making claims.",
569
+ "- Prefer minimal coherent changes.",
570
+ "- If tests exist, run them.",
571
+ "- Do not modify anything under .alysis/ (plan artifacts and run state are read-only).",
572
+ "- You may read attached plan assets as needed; do NOT modify anything under .alysis/.",
573
+ "- Do not run git branch/checkout/add/commit/reset commands; branch management and commits are handled by the swarm runner.",
574
+ *render_execution_knowledge_capture_rules(),
575
+ ]
576
+ header_lines = [
577
+ "# Task Context Pack",
578
+ "",
579
+ f"- Model: `{role_model}`",
580
+ f"- Instruction budget (tokens): `{resolved_budget}`",
581
+ ]
582
+ if note:
583
+ header_lines.extend(["", note])
584
+ leading_lines: list[str] = []
585
+ for section in leading_sections or []:
586
+ section_text = section.strip()
587
+ if not section_text:
588
+ continue
589
+ if leading_lines:
590
+ leading_lines.append("")
591
+ leading_lines.extend(section_text.splitlines())
592
+ relevant_assets_lines = _reduce_relevant_assets_section(
593
+ relevant_assets_section or "",
594
+ state=state,
595
+ )
596
+
597
+ pack = (
598
+ "\n".join(
599
+ [
600
+ *header_lines,
601
+ *(["", *leading_lines] if leading_lines else []),
602
+ *(["", *relevant_assets_lines] if relevant_assets_lines else []),
603
+ "",
604
+ *assets_lines,
605
+ "",
606
+ *task_lines,
607
+ "",
608
+ *plan_lines,
609
+ "",
610
+ *rules_lines,
611
+ "",
612
+ ]
613
+ ).rstrip()
614
+ + "\n"
615
+ )
616
+ return pack
617
+
618
+
619
+ def _reduction_candidates(
620
+ *,
621
+ compact: dict[str, Any],
622
+ selected_assets: list[dict[str, Any]],
623
+ task: dict[str, Any],
624
+ relevant_assets_section: str | None = None,
625
+ prefer_startup_headroom_reduction: bool = False,
626
+ ) -> list[_ExecutionPackReductionState]:
627
+ requirements = list(compact.get("requirements_tail") or [])
628
+ tasks = list(compact.get("tasks") or [])
629
+ assets_count = len(selected_assets)
630
+ relevant_asset_count, relevant_full_count, relevant_focused_count = _relevant_asset_counts(
631
+ relevant_assets_section
632
+ )
633
+ acceptance_count = len(_string_list(task.get("acceptance_criteria")))
634
+ estimated_count = len(_string_list(task.get("estimated_files")))
635
+ scope_count = len(_string_list(task.get("write_scope")))
636
+
637
+ def _cap(total: int, wanted: int, *, minimum_when_nonempty: int = 0) -> int:
638
+ if total <= 0:
639
+ return 0
640
+ capped = min(total, max(0, int(wanted)))
641
+ if capped <= 0:
642
+ return minimum_when_nonempty
643
+ return capped
644
+
645
+ candidates = [
646
+ _ExecutionPackReductionState(
647
+ requirement_count=_cap(len(requirements), _DEFAULT_EXECUTION_PLAN_REQUIREMENTS_COUNT),
648
+ task_list_count=_cap(len(tasks), _DEFAULT_EXECUTION_PLAN_TASK_COUNT),
649
+ asset_count=_cap(assets_count, _DEFAULT_EXECUTION_ASSET_COUNT, minimum_when_nonempty=1),
650
+ project_goal_chars=240,
651
+ summary_chars=600,
652
+ task_description_chars=1600,
653
+ acceptance_criteria_count=_cap(acceptance_count, 12),
654
+ estimated_files_count=_cap(estimated_count, 12),
655
+ write_scope_count=_cap(scope_count, 12),
656
+ relevant_assets_full_inline_count=relevant_full_count,
657
+ relevant_assets_focused_count=relevant_focused_count,
658
+ relevant_assets_reference_count=relevant_asset_count,
659
+ include_may_need_section=True,
660
+ include_pinned_section=True,
661
+ strategy="execution_priority",
662
+ ),
663
+ _ExecutionPackReductionState(
664
+ requirement_count=_cap(len(requirements), 8),
665
+ task_list_count=_cap(len(tasks), 10),
666
+ asset_count=_cap(assets_count, _DEFAULT_EXECUTION_ASSET_COUNT, minimum_when_nonempty=1),
667
+ project_goal_chars=160,
668
+ summary_chars=320,
669
+ task_description_chars=1200,
670
+ acceptance_criteria_count=_cap(acceptance_count, 8),
671
+ estimated_files_count=_cap(estimated_count, 8),
672
+ write_scope_count=_cap(scope_count, 8),
673
+ relevant_assets_full_inline_count=relevant_full_count,
674
+ relevant_assets_focused_count=relevant_focused_count,
675
+ relevant_assets_reference_count=relevant_asset_count,
676
+ include_may_need_section=True,
677
+ include_pinned_section=True,
678
+ strategy="execution_priority_reduced_plan",
679
+ ),
680
+ _ExecutionPackReductionState(
681
+ requirement_count=_cap(len(requirements), 4),
682
+ task_list_count=_cap(len(tasks), 5),
683
+ asset_count=_cap(assets_count, 8, minimum_when_nonempty=1),
684
+ project_goal_chars=96,
685
+ summary_chars=160,
686
+ task_description_chars=800,
687
+ acceptance_criteria_count=_cap(acceptance_count, 5),
688
+ estimated_files_count=_cap(estimated_count, 6),
689
+ write_scope_count=_cap(scope_count, 6),
690
+ relevant_assets_full_inline_count=relevant_full_count,
691
+ relevant_assets_focused_count=relevant_focused_count,
692
+ relevant_assets_reference_count=relevant_asset_count,
693
+ include_may_need_section=False,
694
+ include_pinned_section=True,
695
+ strategy="execution_priority_reduced_plan_detail",
696
+ ),
697
+ _ExecutionPackReductionState(
698
+ requirement_count=_cap(len(requirements), 2),
699
+ task_list_count=_cap(len(tasks), 3),
700
+ asset_count=_cap(assets_count, 4, minimum_when_nonempty=1),
701
+ project_goal_chars=64,
702
+ summary_chars=96,
703
+ task_description_chars=400,
704
+ acceptance_criteria_count=_cap(acceptance_count, 3),
705
+ estimated_files_count=_cap(estimated_count, 4),
706
+ write_scope_count=_cap(scope_count, 4),
707
+ relevant_assets_full_inline_count=0,
708
+ relevant_assets_focused_count=relevant_focused_count,
709
+ relevant_assets_reference_count=relevant_asset_count,
710
+ include_may_need_section=False,
711
+ include_pinned_section=True,
712
+ strategy="execution_priority_reduced_plan_and_assets",
713
+ ),
714
+ _ExecutionPackReductionState(
715
+ requirement_count=0,
716
+ task_list_count=0,
717
+ asset_count=_cap(assets_count, 2, minimum_when_nonempty=1),
718
+ project_goal_chars=0,
719
+ summary_chars=0,
720
+ task_description_chars=240,
721
+ acceptance_criteria_count=_cap(acceptance_count, 2),
722
+ estimated_files_count=_cap(estimated_count, 3),
723
+ write_scope_count=_cap(scope_count, 3),
724
+ relevant_assets_full_inline_count=0,
725
+ relevant_assets_focused_count=0,
726
+ relevant_assets_reference_count=relevant_asset_count,
727
+ include_may_need_section=False,
728
+ include_pinned_section=False,
729
+ strategy="execution_priority_minimal_plan",
730
+ ),
731
+ _ExecutionPackReductionState(
732
+ requirement_count=0,
733
+ task_list_count=0,
734
+ asset_count=_cap(assets_count, 1, minimum_when_nonempty=1),
735
+ project_goal_chars=0,
736
+ summary_chars=0,
737
+ task_description_chars=160,
738
+ acceptance_criteria_count=_cap(acceptance_count, 1),
739
+ estimated_files_count=_cap(estimated_count, 2),
740
+ write_scope_count=_cap(scope_count, 2),
741
+ relevant_assets_full_inline_count=0,
742
+ relevant_assets_focused_count=0,
743
+ relevant_assets_reference_count=min(relevant_asset_count, 1),
744
+ include_may_need_section=False,
745
+ include_pinned_section=False,
746
+ strategy="execution_priority_minimal",
747
+ ),
748
+ ]
749
+ if prefer_startup_headroom_reduction:
750
+ # Managed execution startup headroom can require a smaller first-step
751
+ # pack than the normal hard-limit budget path, so append stricter
752
+ # candidates instead of widening the default reduction for all callers.
753
+ candidates.extend(
754
+ [
755
+ _ExecutionPackReductionState(
756
+ requirement_count=0,
757
+ task_list_count=0,
758
+ asset_count=_cap(assets_count, 1, minimum_when_nonempty=1),
759
+ project_goal_chars=0,
760
+ summary_chars=0,
761
+ task_description_chars=120,
762
+ acceptance_criteria_count=_cap(acceptance_count, 1),
763
+ estimated_files_count=_cap(estimated_count, 2),
764
+ write_scope_count=_cap(scope_count, 2),
765
+ relevant_assets_full_inline_count=0,
766
+ relevant_assets_focused_count=0,
767
+ relevant_assets_reference_count=min(relevant_asset_count, 1),
768
+ include_may_need_section=False,
769
+ include_pinned_section=False,
770
+ strategy="execution_priority_startup_headroom",
771
+ ),
772
+ _ExecutionPackReductionState(
773
+ requirement_count=0,
774
+ task_list_count=0,
775
+ asset_count=0,
776
+ project_goal_chars=0,
777
+ summary_chars=0,
778
+ task_description_chars=96,
779
+ acceptance_criteria_count=_cap(acceptance_count, 1),
780
+ estimated_files_count=_cap(estimated_count, 2),
781
+ write_scope_count=_cap(scope_count, 2),
782
+ relevant_assets_full_inline_count=0,
783
+ relevant_assets_focused_count=0,
784
+ relevant_assets_reference_count=0,
785
+ include_may_need_section=False,
786
+ include_pinned_section=False,
787
+ strategy="execution_priority_startup_headroom_no_assets",
788
+ ),
789
+ _ExecutionPackReductionState(
790
+ requirement_count=0,
791
+ task_list_count=0,
792
+ asset_count=0,
793
+ project_goal_chars=0,
794
+ summary_chars=0,
795
+ task_description_chars=64,
796
+ acceptance_criteria_count=0,
797
+ estimated_files_count=_cap(estimated_count, 1),
798
+ write_scope_count=_cap(scope_count, 1),
799
+ relevant_assets_full_inline_count=0,
800
+ relevant_assets_focused_count=0,
801
+ relevant_assets_reference_count=0,
802
+ include_may_need_section=False,
803
+ include_pinned_section=False,
804
+ strategy="execution_priority_startup_headroom_minimal",
805
+ ),
806
+ ]
807
+ )
808
+ return candidates
809
+
810
+
811
+ def build_task_context_pack(
812
+ *,
813
+ cfg: AppConfig,
814
+ plan: dict[str, Any],
815
+ task: dict[str, Any],
816
+ role_model: str,
817
+ model_registry: ModelRegistry | None = None,
818
+ instruction_token_budget: int | None = None,
819
+ leading_sections: list[str] | None = None,
820
+ relevant_assets_section: str | None = None,
821
+ prefer_startup_headroom_reduction: bool = False,
822
+ ) -> str:
823
+ return build_task_context_pack_result(
824
+ cfg=cfg,
825
+ plan=plan,
826
+ task=task,
827
+ role_model=role_model,
828
+ model_registry=model_registry,
829
+ instruction_token_budget=instruction_token_budget,
830
+ leading_sections=leading_sections,
831
+ relevant_assets_section=relevant_assets_section,
832
+ prefer_startup_headroom_reduction=prefer_startup_headroom_reduction,
833
+ ).content
834
+
835
+
836
+ def build_task_context_pack_result(
837
+ *,
838
+ cfg: AppConfig,
839
+ plan: dict[str, Any],
840
+ task: dict[str, Any],
841
+ role_model: str,
842
+ model_registry: ModelRegistry | None = None,
843
+ instruction_token_budget: int | None = None,
844
+ leading_sections: list[str] | None = None,
845
+ relevant_assets_section: str | None = None,
846
+ prefer_startup_headroom_reduction: bool = False,
847
+ ) -> TaskContextPackResult:
848
+ if instruction_token_budget is None:
849
+ registry = model_registry or ModelRegistry(cfg=cfg)
850
+ meta = registry.get(role_model)
851
+ resolved_budget = compute_input_budget(meta)
852
+ else:
853
+ resolved_budget = max(0, int(instruction_token_budget))
854
+ compact = compact_plan_for_execution(plan)
855
+ selected_assets = select_relevant_assets(plan, task)
856
+
857
+ reduction_candidates = _reduction_candidates(
858
+ compact=compact,
859
+ selected_assets=selected_assets,
860
+ task=task,
861
+ relevant_assets_section=relevant_assets_section,
862
+ prefer_startup_headroom_reduction=prefer_startup_headroom_reduction,
863
+ )
864
+ selected_pack = ""
865
+ selected_strategy = "execution_priority"
866
+ reduction_applied = False
867
+ for idx, state in enumerate(reduction_candidates):
868
+ note = None
869
+ if idx > 0:
870
+ note = (
871
+ "> NOTE: Context pack TRUNCATED using execution-priority reduction to preserve "
872
+ "Selected Assets, Task Specification, and Execution Rules."
873
+ )
874
+ candidate = _render_execution_pack(
875
+ role_model=role_model,
876
+ resolved_budget=resolved_budget,
877
+ compact=compact,
878
+ selected_assets=selected_assets,
879
+ task=task,
880
+ state=state,
881
+ note=note,
882
+ leading_sections=leading_sections,
883
+ relevant_assets_section=relevant_assets_section,
884
+ )
885
+ if estimate_tokens(candidate) <= resolved_budget:
886
+ selected_pack = candidate
887
+ selected_strategy = state.strategy
888
+ reduction_applied = idx > 0
889
+ break
890
+ if idx == len(reduction_candidates) - 1:
891
+ selected_pack = candidate
892
+ selected_strategy = state.strategy
893
+ reduction_applied = True
894
+
895
+ trimmed, was_trimmed = trim_text_to_budget(selected_pack, resolved_budget)
896
+ was_truncated = reduction_applied or was_trimmed
897
+ truncation_strategy = (
898
+ f"{selected_strategy}_then_head_tail" if was_trimmed else selected_strategy
899
+ )
900
+ if was_trimmed:
901
+ trimmed, _ = trim_text_to_budget(
902
+ "> NOTE: Context pack TRUNCATED using execution-priority reduction to preserve "
903
+ "Selected Assets, Task Specification, and Execution Rules."
904
+ "\n\n" + trimmed,
905
+ resolved_budget,
906
+ )
907
+
908
+ return TaskContextPackResult(
909
+ content=trimmed,
910
+ artifact_text=selected_pack,
911
+ instruction_token_budget=resolved_budget,
912
+ instruction_token_estimate=max(0, estimate_tokens(trimmed)),
913
+ truncated=was_truncated,
914
+ truncation_strategy=truncation_strategy,
915
+ )