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,738 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+ from dataclasses import dataclass, replace
5
+ from enum import StrEnum
6
+ from pathlib import Path, PurePosixPath
7
+ from typing import Any
8
+
9
+ from ..branding import env_get
10
+ from ..pipeline_facts import pipeline_meaningful_stage
11
+ from ..verification_command_analysis import (
12
+ VerificationCommandEvidentiaryCapability,
13
+ analyze_verification_command,
14
+ )
15
+ from ..verification_contract import build_verification_command_specs
16
+ from ..verify_gate import assess_verification_command_execution
17
+ from .verification_commands import (
18
+ _canonicalize_verification_command_for_match,
19
+ _has_disallowed_shell_control_flow,
20
+ _marker_fallback_is_verification_attempt,
21
+ _matching_effective_verification_commands,
22
+ _normalize_shell_command_for_match,
23
+ _parse_verification_command_shape,
24
+ )
25
+
26
+ # Recognized static-analysis families that inspect code without executing it.
27
+ # They do not qualify as post-edit *execution* evidence for the ordering rule
28
+ # (they are supplementary signals only); test runners and black-box checks do.
29
+ _STATIC_ONLY_EVIDENCE_FAMILIES = frozenset({"mypy", "ruff:check"})
30
+
31
+
32
+ def _evidence_v2_enabled(cfg: Any | None) -> bool:
33
+ """Kill-switch for the fact-based evidence classifier (evidence v2).
34
+
35
+ ``ALYSIS_EVIDENCE_V2`` (off/0/false/no/disabled) wins over the config
36
+ value; default is on. Mirrors the route-arbitration kill-switch idiom.
37
+ """
38
+ env_value = env_get("ALYSIS_EVIDENCE_V2")
39
+ if env_value is not None:
40
+ normalized = str(env_value).strip().lower()
41
+ if normalized in {"off", "0", "false", "no", "disabled"}:
42
+ return False
43
+ if normalized in {"on", "1", "true", "yes", "enabled"}:
44
+ return True
45
+ return bool(getattr(cfg, "evidence_v2_enabled", True))
46
+
47
+
48
+ def command_is_qualifying_execution_evidence(command: str) -> bool:
49
+ """True when ``command`` executes code as real verification (not syntax-only).
50
+
51
+ Used by the ordering rule to decide whether an observed execution counts as
52
+ post-edit execution evidence. Judged from the analyzed command family, not a
53
+ string pattern: an assertive test/validation program qualifies; ``ast.parse``
54
+ / ``py_compile`` (non-assertive) and static analyzers (mypy, ruff check) do
55
+ not. For a pipeline, the meaningful first-stage program is judged.
56
+ """
57
+ target = pipeline_meaningful_stage(command) or command
58
+ analysis = analyze_verification_command(target, trusted=True)
59
+ if analysis.rejection_reason:
60
+ return False
61
+ if analysis.evidentiary_capability != VerificationCommandEvidentiaryCapability.ASSERTIVE:
62
+ return False
63
+ if analysis.command_family in _STATIC_ONLY_EVIDENCE_FAMILIES:
64
+ return False
65
+ # An inline interpreter snippet (e.g. ``python -c '<code>'``) only executes
66
+ # code as a real check when it asserts; ``ast.parse`` / import-only snippets
67
+ # are static and must not qualify as post-edit execution evidence.
68
+ parts = analysis.parts
69
+ if (
70
+ len(parts) >= 2
71
+ and parts[1] == "-c"
72
+ and analysis.command_family != "assertion:python_snippet"
73
+ ):
74
+ return False
75
+ return True
76
+
77
+
78
+ class VerificationEvidenceCategory(StrEnum):
79
+ AUTHORITATIVE = "AUTHORITATIVE"
80
+ REPO_NATIVE = "REPO_NATIVE"
81
+ TASK_ACCEPTANCE = "TASK_ACCEPTANCE"
82
+ NOT_VERIFICATION = "NOT_VERIFICATION"
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class VerificationEvidence:
87
+ category: VerificationEvidenceCategory
88
+ normalized_command: str
89
+ matched_command: str | None = None
90
+ real_execution: bool | None = None
91
+ allowed_to_satisfy_contract: bool = False
92
+ reason: str = ""
93
+ covered_verification_commands: tuple[str, ...] = ()
94
+ supplemental_only: bool = False
95
+
96
+ @property
97
+ def evidence_verdict(self) -> str:
98
+ """Machine-readable verdict for telemetry.
99
+
100
+ ``pass`` — executed and accepted as satisfying verification;
101
+ ``fail`` — executed but did not pass (or mutated a material path);
102
+ ``unavailable`` — a pipeline whose per-stage status could not be observed;
103
+ ``supplemental`` — self-authored, cannot independently confirm the spec;
104
+ ``not_verification`` — the command is not a verification attempt.
105
+ """
106
+ if self.category == VerificationEvidenceCategory.NOT_VERIFICATION:
107
+ if self.reason == "pipeline_stage_status_unavailable":
108
+ return "unavailable"
109
+ return "not_verification"
110
+ if self.supplemental_only:
111
+ return "supplemental"
112
+ return "pass" if self.allowed_to_satisfy_contract else "fail"
113
+
114
+ def as_payload(self) -> dict[str, object]:
115
+ return {
116
+ "evidence_category": self.category.value,
117
+ "evidence_verdict": self.evidence_verdict,
118
+ "normalized_command": self.normalized_command,
119
+ "matched_command": self.matched_command,
120
+ "real_execution": self.real_execution,
121
+ "allowed_to_satisfy_contract": self.allowed_to_satisfy_contract,
122
+ "reason": self.reason,
123
+ "covered_verification_commands": list(self.covered_verification_commands),
124
+ "supplemental_only": self.supplemental_only,
125
+ }
126
+
127
+
128
+ _INTERPRETER_HEADS = {
129
+ "python",
130
+ "python3",
131
+ "py",
132
+ "node",
133
+ "ruby",
134
+ "rscript",
135
+ "r",
136
+ "bash",
137
+ "sh",
138
+ "zsh",
139
+ }
140
+ _OBSERVATION_HEADS = {
141
+ "cat",
142
+ "echo",
143
+ "find",
144
+ "grep",
145
+ "head",
146
+ "less",
147
+ "ls",
148
+ "more",
149
+ "printf",
150
+ "pwd",
151
+ "tail",
152
+ "type",
153
+ "wc",
154
+ "which",
155
+ }
156
+ _META_OPTIONS = {"--help", "-h", "--version", "-v", "version", "help"}
157
+ _VALIDATION_NAME_MARKERS = (
158
+ "accept",
159
+ "acceptance",
160
+ "check",
161
+ "smoke",
162
+ "test",
163
+ "validate",
164
+ "validation",
165
+ "verify",
166
+ )
167
+ _VALIDATION_DIR_MARKERS = {
168
+ "acceptance",
169
+ "check",
170
+ "checks",
171
+ "script",
172
+ "scripts",
173
+ "smoke",
174
+ "test",
175
+ "tests",
176
+ "validation",
177
+ "verify",
178
+ }
179
+
180
+
181
+ def _execution_allows_acceptance(
182
+ *,
183
+ exit_code: int | None,
184
+ real_execution: bool | None,
185
+ ) -> bool:
186
+ if real_execution is not True:
187
+ return False
188
+ if exit_code is not None and exit_code != 0:
189
+ return False
190
+ return True
191
+
192
+
193
+ def _invalid_contract_rejection_reason(
194
+ *,
195
+ matches: set[str],
196
+ known_verification_commands: list[str],
197
+ authoritative: bool,
198
+ ) -> str:
199
+ if not matches:
200
+ return ""
201
+ source = (
202
+ "environment.authoritative_verification_commands"
203
+ if authoritative
204
+ else "task_refinement.explicit_user_command"
205
+ )
206
+ contract_type = "authoritative_override" if authoritative else "task_acceptance"
207
+ normalized_matches = {_normalize_shell_command_for_match(command) for command in matches}
208
+ for spec in build_verification_command_specs(
209
+ tuple(known_verification_commands),
210
+ source=source,
211
+ contract_type=contract_type,
212
+ ):
213
+ if _normalize_shell_command_for_match(spec.original_text) not in normalized_matches:
214
+ continue
215
+ if spec.validation_status.value == "INVALID":
216
+ return spec.rejection_reason or "invalid_verification_command"
217
+ return ""
218
+
219
+
220
+ def _contract_execution_allows_acceptance(
221
+ *,
222
+ exit_code: int | None,
223
+ real_execution: bool | None,
224
+ ) -> bool:
225
+ if real_execution is not True:
226
+ return False
227
+ if exit_code is not None and exit_code != 0:
228
+ return False
229
+ return True
230
+
231
+
232
+ def _command_parts(command: str) -> list[str]:
233
+ canonical = _canonicalize_verification_command_for_match(command)
234
+ if not canonical:
235
+ return []
236
+ try:
237
+ return shlex.split(canonical, posix=True)
238
+ except ValueError:
239
+ return []
240
+
241
+
242
+ def _path_key(path: str, *, root: Path | None = None) -> str:
243
+ cleaned = str(path or "").strip().replace("\\", "/")
244
+ while cleaned.startswith("./"):
245
+ cleaned = cleaned[2:]
246
+ if root is not None and cleaned:
247
+ try:
248
+ resolved = Path(cleaned).expanduser().resolve()
249
+ cleaned = resolved.relative_to(root.resolve()).as_posix()
250
+ except (OSError, ValueError):
251
+ pass
252
+ return cleaned.casefold()
253
+
254
+
255
+ def _is_repo_local_path(path: str) -> bool:
256
+ if not path or path.startswith("-"):
257
+ return False
258
+ pure = PurePosixPath(path.replace("\\", "/"))
259
+ return not pure.is_absolute() and ".." not in pure.parts
260
+
261
+
262
+ def _is_repo_local_executable_path(path: str) -> bool:
263
+ normalized = path.replace("\\", "/")
264
+ return _is_repo_local_path(path) and ("/" in normalized or normalized.startswith("."))
265
+
266
+
267
+ def _path_has_validation_signal(path: str) -> bool:
268
+ pure = PurePosixPath(path.replace("\\", "/"))
269
+ parts = tuple(part.casefold() for part in pure.parts if part not in {"", "."})
270
+ name = pure.stem.casefold()
271
+ if any(marker in name for marker in _VALIDATION_NAME_MARKERS):
272
+ return True
273
+ return bool(set(parts[:-1]) & _VALIDATION_DIR_MARKERS)
274
+
275
+
276
+ def _interpreter_script_path(parts: list[str]) -> str | None:
277
+ if not parts:
278
+ return None
279
+ head = Path(parts[0]).name.casefold()
280
+ if head.endswith(".exe"):
281
+ head = head[:-4]
282
+ if head not in _INTERPRETER_HEADS:
283
+ return None
284
+ tail = parts[1:]
285
+ if not tail:
286
+ return None
287
+ if tail[0] in _META_OPTIONS:
288
+ return None
289
+ if head in {"python", "python3", "py"} and tail[0] == "-m":
290
+ return None
291
+ for item in tail:
292
+ if item == "--":
293
+ continue
294
+ if _is_repo_local_path(item):
295
+ return item
296
+ return None
297
+
298
+
299
+ def _diff_or_cmp_has_real_operands(parts: list[str]) -> bool:
300
+ if not parts:
301
+ return False
302
+ head = Path(parts[0]).name.casefold()
303
+ if head not in {"cmp", "diff"}:
304
+ return False
305
+ operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
306
+ return len(operands) >= 2 and all(_is_repo_local_path(item) for item in operands[:2])
307
+
308
+
309
+ def _is_known_non_executing_verification_form(parts: list[str]) -> bool:
310
+ lowered = [part.casefold() for part in parts]
311
+ if not lowered:
312
+ return True
313
+ if any(part in {"--help", "-h", "--version"} for part in lowered):
314
+ return True
315
+ if lowered[0] in {"pytest", "py.test"}:
316
+ return any(part in {"--collect-only", "--co", "--setup-plan"} for part in lowered[1:])
317
+ if len(lowered) >= 3 and lowered[0] in {"python", "python3", "py"} and lowered[1] == "-m":
318
+ if lowered[2] == "pytest":
319
+ return any(part in {"--collect-only", "--co", "--setup-plan"} for part in lowered[3:])
320
+ if lowered[:2] == ["go", "test"]:
321
+ if any(part in {"-c", "-list"} or part.startswith("-list=") for part in lowered[2:]):
322
+ return True
323
+ for index, part in enumerate(lowered[2:], start=2):
324
+ if part == "-run" and index + 1 < len(lowered) and lowered[index + 1] == "^$":
325
+ return True
326
+ if part == "-run=^$":
327
+ return True
328
+ if lowered[:2] == ["cargo", "test"]:
329
+ return any(part in {"--no-run", "--list"} for part in lowered[2:])
330
+ if lowered[:2] == ["ruff", "check"]:
331
+ return any(part in {"--fix", "--fix-only"} for part in lowered[2:])
332
+ if lowered[0] == "mypy":
333
+ return "--install-types" in lowered[1:]
334
+ return False
335
+
336
+
337
+ def _task_specific_reason(
338
+ *,
339
+ parts: list[str],
340
+ changed_paths: set[str],
341
+ root: Path | None,
342
+ ) -> str | None:
343
+ if not parts:
344
+ return None
345
+ head = Path(parts[0]).name.casefold()
346
+ if head.endswith(".exe"):
347
+ head = head[:-4]
348
+ if head in _OBSERVATION_HEADS:
349
+ return None
350
+ if any(part.casefold() in _META_OPTIONS for part in parts[1:]):
351
+ return None
352
+ if _diff_or_cmp_has_real_operands(parts):
353
+ return "real_output_comparison"
354
+
355
+ script_path = _interpreter_script_path(parts)
356
+ if script_path:
357
+ script_key = _path_key(script_path, root=root)
358
+ changed_keys = {_path_key(item, root=root) for item in changed_paths}
359
+ if script_key in changed_keys:
360
+ return "changed_repo_local_script_execution"
361
+ if _path_has_validation_signal(script_path):
362
+ return "repo_local_validation_script"
363
+ return None
364
+
365
+ executable = parts[0]
366
+ if _is_repo_local_executable_path(executable) and _path_has_validation_signal(executable):
367
+ return "repo_local_validation_executable"
368
+ return None
369
+
370
+
371
+ def classify_verification_evidence(
372
+ command: str,
373
+ *,
374
+ known_verification_commands: list[str] | tuple[str, ...] | None = None,
375
+ authoritative: bool = False,
376
+ changed_paths: set[str] | list[str] | tuple[str, ...] | None = None,
377
+ material_touched_paths: set[str] | list[str] | tuple[str, ...] | None = None,
378
+ exit_code: int | None = None,
379
+ output: str = "",
380
+ real_execution: bool | None = None,
381
+ root: Path | None = None,
382
+ stage_status: list[int] | tuple[int, ...] | None = None,
383
+ evidence_v2: bool = True,
384
+ ) -> VerificationEvidence:
385
+ """Classify an observed command as verification evidence.
386
+
387
+ Evidence v2 (default) judges observed *execution facts*: for a shell
388
+ pipeline the meaningful program is the first stage and its per-stage exit
389
+ status (``stage_status``, captured via PIPESTATUS) is the ground truth, so a
390
+ piped ``pytest ... | tail`` counts as passing/failing evidence per the
391
+ pipeline's real first-stage code rather than being rejected for containing a
392
+ ``|``. When per-stage status is unavailable the pipeline is treated as
393
+ unverified (``pipeline_stage_status_unavailable``) so it can never be
394
+ silently accepted. With ``evidence_v2`` off, the legacy string-shape
395
+ classifier runs unchanged.
396
+ """
397
+ if evidence_v2:
398
+ first_stage = pipeline_meaningful_stage(command)
399
+ if first_stage is not None:
400
+ return _classify_pipeline_evidence(
401
+ command=command,
402
+ first_stage=first_stage,
403
+ stage_status=list(stage_status) if stage_status is not None else None,
404
+ known_verification_commands=known_verification_commands,
405
+ authoritative=authoritative,
406
+ changed_paths=changed_paths,
407
+ material_touched_paths=material_touched_paths,
408
+ output=output,
409
+ root=root,
410
+ )
411
+ return _classify_verification_evidence_legacy(
412
+ command,
413
+ known_verification_commands=known_verification_commands,
414
+ authoritative=authoritative,
415
+ changed_paths=changed_paths,
416
+ material_touched_paths=material_touched_paths,
417
+ exit_code=exit_code,
418
+ output=output,
419
+ real_execution=real_execution,
420
+ root=root,
421
+ )
422
+
423
+
424
+ def _classify_pipeline_evidence(
425
+ *,
426
+ command: str,
427
+ first_stage: str,
428
+ stage_status: list[int] | None,
429
+ known_verification_commands: list[str] | tuple[str, ...] | None,
430
+ authoritative: bool,
431
+ changed_paths: set[str] | list[str] | tuple[str, ...] | None,
432
+ material_touched_paths: set[str] | list[str] | tuple[str, ...] | None,
433
+ output: str,
434
+ root: Path | None,
435
+ ) -> VerificationEvidence:
436
+ normalized = _normalize_shell_command_for_match(command)
437
+ if not stage_status:
438
+ # No observed per-stage status and no ground truth obtained: the
439
+ # pipeline's real first-stage outcome is unknown, so it is not evidence.
440
+ return VerificationEvidence(
441
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
442
+ normalized_command=normalized,
443
+ real_execution=None,
444
+ reason="pipeline_stage_status_unavailable",
445
+ )
446
+ first_stage_exit = stage_status[0]
447
+ evidence = _classify_verification_evidence_legacy(
448
+ first_stage,
449
+ known_verification_commands=known_verification_commands,
450
+ authoritative=authoritative,
451
+ changed_paths=changed_paths,
452
+ material_touched_paths=material_touched_paths,
453
+ exit_code=first_stage_exit,
454
+ output=output,
455
+ real_execution=None,
456
+ root=root,
457
+ )
458
+ # Keep the observed (piped) command as the normalized command for telemetry;
459
+ # the covered contract commands already reflect the matched contract text.
460
+ return replace(evidence, normalized_command=normalized)
461
+
462
+
463
+ def _classify_verification_evidence_legacy(
464
+ command: str,
465
+ *,
466
+ known_verification_commands: list[str] | tuple[str, ...] | None = None,
467
+ authoritative: bool = False,
468
+ changed_paths: set[str] | list[str] | tuple[str, ...] | None = None,
469
+ material_touched_paths: set[str] | list[str] | tuple[str, ...] | None = None,
470
+ exit_code: int | None = None,
471
+ output: str = "",
472
+ real_execution: bool | None = None,
473
+ root: Path | None = None,
474
+ ) -> VerificationEvidence:
475
+ normalized = _normalize_shell_command_for_match(command)
476
+ if not normalized:
477
+ return VerificationEvidence(
478
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
479
+ normalized_command="",
480
+ real_execution=real_execution,
481
+ reason="empty_command",
482
+ )
483
+
484
+ if real_execution is None and exit_code is not None:
485
+ assessment = assess_verification_command_execution(
486
+ command=command,
487
+ exit_code=exit_code,
488
+ output=output,
489
+ )
490
+ real_execution = assessment.real_execution
491
+
492
+ known = [str(item) for item in (known_verification_commands or []) if str(item).strip()]
493
+ analysis = analyze_verification_command(
494
+ command,
495
+ trusted=authoritative or bool(known),
496
+ )
497
+ material_touched = tuple(
498
+ sorted(str(item) for item in (material_touched_paths or []) if str(item).strip())
499
+ )
500
+ execution_ok = _execution_allows_acceptance(
501
+ exit_code=exit_code,
502
+ real_execution=real_execution,
503
+ )
504
+ mutation_reason = "mutated_material_paths" if material_touched else ""
505
+ trusted_shell_matches = _matching_trusted_shell_expressions(
506
+ observed_command=command,
507
+ known_verification_commands=known,
508
+ authoritative=authoritative,
509
+ )
510
+ if trusted_shell_matches:
511
+ contract_execution_ok = _contract_execution_allows_acceptance(
512
+ exit_code=exit_code,
513
+ real_execution=real_execution,
514
+ )
515
+ capability_ok = (
516
+ analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
517
+ and not analysis.rejection_reason
518
+ )
519
+ allowed = contract_execution_ok and capability_ok and not material_touched
520
+ category = (
521
+ VerificationEvidenceCategory.AUTHORITATIVE
522
+ if authoritative
523
+ else VerificationEvidenceCategory.REPO_NATIVE
524
+ )
525
+ if not capability_ok:
526
+ reason = (
527
+ analysis.rejection_reason
528
+ or analysis.inconclusive_reason
529
+ or "unknown_verification_capability"
530
+ )
531
+ elif not contract_execution_ok:
532
+ reason = "non_executing_or_failed_contract_command"
533
+ elif mutation_reason:
534
+ reason = mutation_reason
535
+ else:
536
+ reason = "matched_authoritative_contract" if authoritative else "matched_contract"
537
+ return VerificationEvidence(
538
+ category=category,
539
+ normalized_command=normalized,
540
+ matched_command=sorted(trusted_shell_matches)[0],
541
+ real_execution=real_execution,
542
+ allowed_to_satisfy_contract=allowed,
543
+ reason=reason,
544
+ covered_verification_commands=tuple(sorted(trusted_shell_matches)),
545
+ )
546
+
547
+ if _has_disallowed_shell_control_flow(command):
548
+ return VerificationEvidence(
549
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
550
+ normalized_command=normalized,
551
+ real_execution=real_execution,
552
+ reason=analysis.rejection_reason or "disallowed_shell_control_flow",
553
+ )
554
+
555
+ matches = _matching_effective_verification_commands(
556
+ observed_command=normalized,
557
+ effective_verification_commands=known,
558
+ )
559
+ if matches:
560
+ invalid_contract_reason = _invalid_contract_rejection_reason(
561
+ matches=matches,
562
+ known_verification_commands=known,
563
+ authoritative=authoritative,
564
+ )
565
+ contract_execution_ok = _contract_execution_allows_acceptance(
566
+ exit_code=exit_code,
567
+ real_execution=real_execution,
568
+ )
569
+ capability_ok = (
570
+ analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
571
+ and not analysis.rejection_reason
572
+ )
573
+ allowed = (
574
+ not invalid_contract_reason
575
+ and contract_execution_ok
576
+ and capability_ok
577
+ and not material_touched
578
+ )
579
+ category = (
580
+ VerificationEvidenceCategory.AUTHORITATIVE
581
+ if authoritative
582
+ else VerificationEvidenceCategory.REPO_NATIVE
583
+ )
584
+ if invalid_contract_reason:
585
+ reason = invalid_contract_reason
586
+ elif not capability_ok:
587
+ reason = (
588
+ analysis.rejection_reason
589
+ or analysis.inconclusive_reason
590
+ or "unknown_verification_capability"
591
+ )
592
+ elif not contract_execution_ok:
593
+ reason = "non_executing_or_failed_contract_command"
594
+ elif mutation_reason:
595
+ reason = mutation_reason
596
+ else:
597
+ reason = "matched_authoritative_contract" if authoritative else "matched_contract"
598
+ return VerificationEvidence(
599
+ category=category,
600
+ normalized_command=normalized,
601
+ matched_command=sorted(matches)[0],
602
+ real_execution=real_execution,
603
+ allowed_to_satisfy_contract=allowed,
604
+ reason=reason,
605
+ covered_verification_commands=tuple(sorted(matches)),
606
+ )
607
+
608
+ parts = _command_parts(normalized)
609
+ task_reason = _task_specific_reason(
610
+ parts=parts,
611
+ changed_paths={str(item) for item in (changed_paths or []) if str(item).strip()},
612
+ root=root,
613
+ )
614
+ if known:
615
+ if task_reason:
616
+ return VerificationEvidence(
617
+ category=VerificationEvidenceCategory.TASK_ACCEPTANCE,
618
+ normalized_command=normalized,
619
+ real_execution=real_execution,
620
+ allowed_to_satisfy_contract=False,
621
+ reason="supplemental_only_contract_exists",
622
+ supplemental_only=True,
623
+ )
624
+ return VerificationEvidence(
625
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
626
+ normalized_command=normalized,
627
+ real_execution=real_execution,
628
+ reason="does_not_match_effective_contract",
629
+ )
630
+
631
+ if task_reason:
632
+ if (
633
+ analysis.evidentiary_capability
634
+ == VerificationCommandEvidentiaryCapability.NON_ASSERTIVE
635
+ or analysis.rejection_reason
636
+ ):
637
+ return VerificationEvidence(
638
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
639
+ normalized_command=normalized,
640
+ real_execution=real_execution,
641
+ reason=analysis.rejection_reason
642
+ or analysis.capability_reason
643
+ or "non_assertive_verifier",
644
+ )
645
+ allowed = execution_ok and not material_touched
646
+ reason = task_reason if allowed else mutation_reason or "non_executing_or_failed_task_check"
647
+ return VerificationEvidence(
648
+ category=VerificationEvidenceCategory.TASK_ACCEPTANCE,
649
+ normalized_command=normalized,
650
+ real_execution=real_execution,
651
+ allowed_to_satisfy_contract=allowed,
652
+ reason=reason,
653
+ )
654
+
655
+ if (
656
+ _parse_verification_command_shape(normalized) is not None
657
+ and analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
658
+ and not analysis.rejection_reason
659
+ ):
660
+ allowed = execution_ok and not material_touched
661
+ reason = (
662
+ "repo_native_command"
663
+ if allowed
664
+ else mutation_reason or "non_executing_or_failed_repo_native_command"
665
+ )
666
+ return VerificationEvidence(
667
+ category=VerificationEvidenceCategory.REPO_NATIVE,
668
+ normalized_command=normalized,
669
+ real_execution=real_execution,
670
+ allowed_to_satisfy_contract=allowed,
671
+ reason=reason,
672
+ )
673
+
674
+ if _marker_fallback_is_verification_attempt(
675
+ normalized
676
+ ) and not _is_known_non_executing_verification_form(parts):
677
+ if (
678
+ analysis.evidentiary_capability != VerificationCommandEvidentiaryCapability.ASSERTIVE
679
+ or analysis.rejection_reason
680
+ ):
681
+ return VerificationEvidence(
682
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
683
+ normalized_command=normalized,
684
+ real_execution=real_execution,
685
+ reason=analysis.rejection_reason
686
+ or analysis.inconclusive_reason
687
+ or "unknown_verification_capability",
688
+ )
689
+ allowed = execution_ok and not material_touched
690
+ reason = (
691
+ "repo_native_command"
692
+ if allowed
693
+ else mutation_reason or "non_executing_or_failed_repo_native_command"
694
+ )
695
+ return VerificationEvidence(
696
+ category=VerificationEvidenceCategory.REPO_NATIVE,
697
+ normalized_command=normalized,
698
+ real_execution=real_execution,
699
+ allowed_to_satisfy_contract=allowed,
700
+ reason=reason,
701
+ )
702
+
703
+ return VerificationEvidence(
704
+ category=VerificationEvidenceCategory.NOT_VERIFICATION,
705
+ normalized_command=normalized,
706
+ real_execution=real_execution,
707
+ reason="no_verification_signal",
708
+ )
709
+
710
+
711
+ def _matching_trusted_shell_expressions(
712
+ *,
713
+ observed_command: str,
714
+ known_verification_commands: list[str],
715
+ authoritative: bool,
716
+ ) -> set[str]:
717
+ if not known_verification_commands:
718
+ return set()
719
+ source = (
720
+ "environment.authoritative_verification_commands"
721
+ if authoritative
722
+ else "task_refinement.explicit_user_command"
723
+ )
724
+ contract_type = "authoritative_override" if authoritative else "task_acceptance"
725
+ observed = _normalize_shell_command_for_match(observed_command)
726
+ out: set[str] = set()
727
+ for spec in build_verification_command_specs(
728
+ tuple(known_verification_commands),
729
+ source=source,
730
+ contract_type=contract_type,
731
+ ):
732
+ if (
733
+ spec.execution_mode.value == "TRUSTED_SHELL_EXPRESSION"
734
+ and spec.validation_status.value == "VALID"
735
+ and observed == _normalize_shell_command_for_match(spec.original_text)
736
+ ):
737
+ out.add(spec.original_text)
738
+ return out