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,1004 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import os
5
+ import re
6
+ import shlex
7
+ from dataclasses import dataclass
8
+ from enum import StrEnum
9
+ from pathlib import Path, PurePosixPath
10
+
11
+ from .config import (
12
+ normalize_verify_module_invocation,
13
+ split_verify_command_parts,
14
+ strip_verify_runner_prefix,
15
+ )
16
+
17
+
18
+ class VerificationCommandEvidentiaryCapability(StrEnum):
19
+ ASSERTIVE = "ASSERTIVE"
20
+ NON_ASSERTIVE = "NON_ASSERTIVE"
21
+ UNKNOWN = "UNKNOWN"
22
+
23
+
24
+ class VerificationCommandStatus(StrEnum):
25
+ PASSED = "passed"
26
+ SKIPPED = "skipped"
27
+ FAILED = "failed"
28
+ INCONCLUSIVE = "inconclusive"
29
+ NOT_EXECUTED = "not_executed"
30
+ STALE = "stale"
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class CheckerEntrypointFingerprint:
35
+ display_path: str
36
+ resolved_path: str
37
+ is_regular_file: bool
38
+ size: int | None
39
+ sha256: str | None
40
+
41
+ def as_payload(self) -> dict[str, object]:
42
+ return {
43
+ "display_path": self.display_path,
44
+ "resolved_path": self.resolved_path,
45
+ "is_regular_file": self.is_regular_file,
46
+ "size": self.size,
47
+ "sha256": self.sha256,
48
+ }
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class VerificationCommandAnalysis:
53
+ original_command: str
54
+ normalized_command: str
55
+ unwrapped_command: str
56
+ primary_command: str
57
+ canonical_command: str | None
58
+ parts: tuple[str, ...]
59
+ command_family: str | None
60
+ provenance_source: str = ""
61
+ trust_source: str = ""
62
+ evidentiary_capability: VerificationCommandEvidentiaryCapability = (
63
+ VerificationCommandEvidentiaryCapability.UNKNOWN
64
+ )
65
+ capability_reason: str = ""
66
+ shell_control_flow: str = "none"
67
+ pipeline_policy: str = "none"
68
+ checker_entrypoint_paths: tuple[str, ...] = tuple()
69
+ checker_integrity_required: bool = False
70
+ checker_fingerprints: tuple[CheckerEntrypointFingerprint, ...] = tuple()
71
+ real_execution_required: bool = True
72
+ rejection_reason: str = ""
73
+ inconclusive_reason: str = ""
74
+ cd_target: str | None = None
75
+
76
+ @property
77
+ def is_valid_verifier(self) -> bool:
78
+ return (
79
+ not self.rejection_reason
80
+ and self.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
81
+ )
82
+
83
+
84
+ _ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
85
+ _SHELL_CONTROL_TOKENS = {"||", "&&", ";", "|", "&"}
86
+ _SHELL_WRAPPER_HEADS = {"bash", "sh", "zsh"}
87
+ _PYTHON_EXECUTABLES = {"python", "python3", "py"}
88
+ _VACUOUS_SUCCESS_HEADS = {"true", ":"}
89
+ _OBSERVATION_HEADS = {
90
+ "cat",
91
+ "echo",
92
+ "find",
93
+ "head",
94
+ "less",
95
+ "ls",
96
+ "more",
97
+ "printf",
98
+ "pwd",
99
+ "tail",
100
+ "tee",
101
+ "type",
102
+ "wc",
103
+ "which",
104
+ }
105
+ _VALIDATION_COMMAND_PATH_MARKERS = {
106
+ "accept",
107
+ "acceptance",
108
+ "check",
109
+ "smoke",
110
+ "test",
111
+ "validate",
112
+ "validation",
113
+ "verify",
114
+ }
115
+ _META_OPTIONS = {"--help", "-h", "--version", "version", "help"}
116
+ _PYTEST_NON_VERIFICATION_OPTIONS = {"--fixtures", "--markers", "--collect-only", "--co"}
117
+ _PYTEST_NON_EXECUTING_OPTIONS = {"--setup-plan"}
118
+ _CARGO_TEST_NON_EXECUTING_OPTIONS = {"--no-run", "--list"}
119
+ _GO_TEST_NON_EXECUTING_OPTIONS = {"-c", "-list"}
120
+ _RUFF_CHECK_NON_EXECUTING_OPTIONS = {"--fix", "--fix-only"}
121
+ _MYPY_NON_EXECUTING_OPTIONS = {"--install-types"}
122
+ _NODE_ASSERTIVE_SCRIPTS = {"test", "build", "lint", "typecheck", "check", "verify"}
123
+ _TEST_SURFACE_PARTS = frozenset(
124
+ {"__tests__", "fixture", "fixtures", "spec", "specs", "test", "tests"}
125
+ )
126
+
127
+ _PYTHON_VERIFICATION_SUFFIXES = frozenset({".py", ".pyi", ".toml", ".ini", ".cfg"})
128
+ _NODE_VERIFICATION_SUFFIXES = frozenset(
129
+ {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".json"}
130
+ )
131
+ _GO_VERIFICATION_SUFFIXES = frozenset({".go", ".mod", ".sum"})
132
+ _RUST_VERIFICATION_SUFFIXES = frozenset({".rs", ".toml", ".lock"})
133
+ _JVM_VERIFICATION_SUFFIXES = frozenset(
134
+ {".java", ".kt", ".kts", ".groovy", ".scala", ".xml", ".gradle"}
135
+ )
136
+ _DOTNET_VERIFICATION_SUFFIXES = frozenset({".cs", ".fs", ".vb", ".csproj", ".fsproj", ".sln"})
137
+ _ELIXIR_VERIFICATION_SUFFIXES = frozenset({".ex", ".exs"})
138
+ _ASSERTION_VERIFICATION_SUFFIXES = frozenset(
139
+ {
140
+ *_PYTHON_VERIFICATION_SUFFIXES,
141
+ *_NODE_VERIFICATION_SUFFIXES,
142
+ *_GO_VERIFICATION_SUFFIXES,
143
+ *_RUST_VERIFICATION_SUFFIXES,
144
+ *_JVM_VERIFICATION_SUFFIXES,
145
+ *_DOTNET_VERIFICATION_SUFFIXES,
146
+ *_ELIXIR_VERIFICATION_SUFFIXES,
147
+ }
148
+ )
149
+ _TOOLCHAIN_BASENAMES: dict[str, frozenset[str]] = {
150
+ "python": frozenset({"pyproject.toml", "setup.cfg", "setup.py", "tox.ini"}),
151
+ "node": frozenset({"package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"}),
152
+ "go": frozenset({"go.mod", "go.sum"}),
153
+ "rust": frozenset({"cargo.toml", "cargo.lock"}),
154
+ "jvm": frozenset({"pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle"}),
155
+ "dotnet": frozenset({"global.json", "nuget.config"}),
156
+ "elixir": frozenset({"mix.exs", "mix.lock"}),
157
+ "broad": frozenset({"makefile", "justfile"}),
158
+ }
159
+ _VERIFICATION_FAMILY_TOOLCHAINS: dict[str, str] = {
160
+ "pytest": "python",
161
+ "unittest": "python",
162
+ "mypy": "python",
163
+ "ruff:check": "python",
164
+ "node:test": "node",
165
+ "npm:test": "node",
166
+ "npm:build": "node",
167
+ "npm:lint": "node",
168
+ "npm:typecheck": "node",
169
+ "npm:check": "node",
170
+ "npm:verify": "node",
171
+ "pnpm:test": "node",
172
+ "pnpm:build": "node",
173
+ "pnpm:lint": "node",
174
+ "pnpm:typecheck": "node",
175
+ "pnpm:check": "node",
176
+ "pnpm:verify": "node",
177
+ "yarn:test": "node",
178
+ "yarn:build": "node",
179
+ "yarn:lint": "node",
180
+ "yarn:typecheck": "node",
181
+ "yarn:check": "node",
182
+ "yarn:verify": "node",
183
+ "go:test": "go",
184
+ "cargo:test": "rust",
185
+ "cargo:check": "rust",
186
+ "maven:test": "jvm",
187
+ "maven:verify": "jvm",
188
+ "gradle:test": "jvm",
189
+ "dotnet:test": "dotnet",
190
+ "mix:test": "elixir",
191
+ "make:test": "broad",
192
+ "make:check": "broad",
193
+ "make:verify": "broad",
194
+ "just:test": "broad",
195
+ "just:check": "broad",
196
+ "just:verify": "broad",
197
+ }
198
+ _TOOLCHAIN_SUFFIXES: dict[str, frozenset[str]] = {
199
+ "python": _PYTHON_VERIFICATION_SUFFIXES,
200
+ "node": _NODE_VERIFICATION_SUFFIXES,
201
+ "go": _GO_VERIFICATION_SUFFIXES,
202
+ "rust": _RUST_VERIFICATION_SUFFIXES,
203
+ "jvm": _JVM_VERIFICATION_SUFFIXES,
204
+ "dotnet": _DOTNET_VERIFICATION_SUFFIXES,
205
+ "elixir": _ELIXIR_VERIFICATION_SUFFIXES,
206
+ "broad": _ASSERTION_VERIFICATION_SUFFIXES,
207
+ }
208
+
209
+
210
+ def _normalize_rel_match_path(raw: str, *, strip_trailing_slash: bool = True) -> str:
211
+ cleaned = str(raw).strip().replace("\\", "/")
212
+ while cleaned.startswith("./"):
213
+ cleaned = cleaned[2:]
214
+ if strip_trailing_slash:
215
+ cleaned = cleaned.rstrip("/")
216
+ return cleaned
217
+
218
+
219
+ def path_matches_verification_toolchain(rel_path: str, toolchain: str) -> bool:
220
+ normalized = _normalize_rel_match_path(rel_path)
221
+ if not normalized:
222
+ return False
223
+ lowered = normalized.casefold()
224
+ path = PurePosixPath(lowered)
225
+ basename = path.name
226
+ if basename in _TOOLCHAIN_BASENAMES.get(toolchain, frozenset()):
227
+ return True
228
+ if toolchain != "broad" and any(part in _TEST_SURFACE_PARTS for part in path.parts[:-1]):
229
+ return True
230
+ return path.suffix in _TOOLCHAIN_SUFFIXES.get(toolchain, frozenset())
231
+
232
+
233
+ def path_has_known_verification_surface(rel_path: str) -> bool:
234
+ return any(
235
+ path_matches_verification_toolchain(rel_path, toolchain)
236
+ for toolchain in _TOOLCHAIN_SUFFIXES
237
+ if toolchain != "broad"
238
+ )
239
+
240
+
241
+ def verification_commands_apply_to_paths(
242
+ paths: set[str] | frozenset[str],
243
+ commands: list[str] | tuple[str, ...] | set[str] | None,
244
+ ) -> bool:
245
+ if not paths or not commands:
246
+ return False
247
+ toolchains: set[str] = set()
248
+ for command in commands:
249
+ family = analyze_verification_command(str(command), trusted=True).command_family
250
+ if family is None:
251
+ continue
252
+ toolchain = _VERIFICATION_FAMILY_TOOLCHAINS.get(family)
253
+ if toolchain is None and family.startswith("assertion:"):
254
+ toolchain = "broad"
255
+ if toolchain is not None:
256
+ toolchains.add(toolchain)
257
+ return any(
258
+ path_matches_verification_toolchain(path, toolchain)
259
+ for path in paths
260
+ for toolchain in toolchains
261
+ )
262
+
263
+
264
+ def paths_require_verification(paths: set[str] | frozenset[str]) -> bool:
265
+ if not paths:
266
+ return False
267
+ return any(path_has_known_verification_surface(path) for path in paths)
268
+
269
+
270
+ def analyze_verification_command(
271
+ command: str,
272
+ *,
273
+ source: str = "",
274
+ contract_type: str = "",
275
+ trusted: bool = False,
276
+ workspace_root: Path | None = None,
277
+ capture_checker_fingerprints: bool = False,
278
+ ) -> VerificationCommandAnalysis:
279
+ original = str(command or "").strip()
280
+ normalized = _normalize_command(original)
281
+ if not normalized:
282
+ return _analysis(
283
+ original,
284
+ normalized,
285
+ "",
286
+ "",
287
+ rejection_reason="empty_command",
288
+ capability=VerificationCommandEvidentiaryCapability.NON_ASSERTIVE,
289
+ capability_reason="empty_command",
290
+ source=source,
291
+ contract_type=contract_type,
292
+ )
293
+ if "\n" in original or "\r" in original:
294
+ return _analysis(
295
+ original,
296
+ normalized,
297
+ normalized,
298
+ normalized,
299
+ rejection_reason="disallowed_shell_control_flow",
300
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
301
+ capability_reason="multi_line_shell_expression",
302
+ shell_control_flow="unsafe",
303
+ source=source,
304
+ contract_type=contract_type,
305
+ )
306
+
307
+ unwrapped = normalized
308
+ while True:
309
+ wrapped = _unwrap_shell_wrapper_command(unwrapped)
310
+ if not wrapped or wrapped == unwrapped:
311
+ break
312
+ unwrapped = wrapped
313
+
314
+ tokens = _shell_tokens(unwrapped)
315
+ if tokens is None:
316
+ return _analysis(
317
+ original,
318
+ normalized,
319
+ unwrapped,
320
+ unwrapped,
321
+ rejection_reason="parse_error",
322
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
323
+ capability_reason="parse_error",
324
+ source=source,
325
+ contract_type=contract_type,
326
+ )
327
+ if not tokens:
328
+ return _analysis(
329
+ original,
330
+ normalized,
331
+ unwrapped,
332
+ unwrapped,
333
+ rejection_reason="empty_command",
334
+ capability=VerificationCommandEvidentiaryCapability.NON_ASSERTIVE,
335
+ capability_reason="empty_command",
336
+ source=source,
337
+ contract_type=contract_type,
338
+ )
339
+
340
+ shell_control_flow = "none"
341
+ pipeline_policy = "none"
342
+ cd_target: str | None = None
343
+ primary_tokens = tokens
344
+ if "|" in tokens:
345
+ pipeline_policy = "rejected_without_pipefail"
346
+ return _analysis(
347
+ original,
348
+ normalized,
349
+ unwrapped,
350
+ _join_tokens(tokens),
351
+ rejection_reason="unsafe_pipeline",
352
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
353
+ capability_reason="pipeline_exit_status_can_mask_upstream_failure",
354
+ shell_control_flow="pipeline",
355
+ pipeline_policy=pipeline_policy,
356
+ source=source,
357
+ contract_type=contract_type,
358
+ )
359
+ if len(tokens) >= 4 and tokens[0] == "cd" and tokens[2] == "&&":
360
+ shell_control_flow = "safe_cd_and"
361
+ cd_target = tokens[1]
362
+ cd_rejection = _cd_target_rejection_reason(cd_target, workspace_root=workspace_root)
363
+ primary_tokens = tokens[3:]
364
+ if cd_rejection:
365
+ return _analysis(
366
+ original,
367
+ normalized,
368
+ unwrapped,
369
+ _join_tokens(primary_tokens),
370
+ rejection_reason=cd_rejection,
371
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
372
+ capability_reason=cd_rejection,
373
+ shell_control_flow=shell_control_flow,
374
+ cd_target=cd_target,
375
+ source=source,
376
+ contract_type=contract_type,
377
+ )
378
+ if any(token in _SHELL_CONTROL_TOKENS for token in primary_tokens):
379
+ reason = "unsafe_pipeline" if "|" in primary_tokens else "disallowed_shell_control_flow"
380
+ return _analysis(
381
+ original,
382
+ normalized,
383
+ unwrapped,
384
+ _join_tokens(primary_tokens),
385
+ rejection_reason=reason,
386
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
387
+ capability_reason=reason,
388
+ shell_control_flow="unsafe",
389
+ pipeline_policy=(
390
+ "rejected_without_pipefail" if reason == "unsafe_pipeline" else "none"
391
+ ),
392
+ cd_target=cd_target,
393
+ source=source,
394
+ contract_type=contract_type,
395
+ )
396
+ elif any(token in _SHELL_CONTROL_TOKENS for token in tokens):
397
+ reason = "unsafe_pipeline" if "|" in tokens else "disallowed_shell_control_flow"
398
+ return _analysis(
399
+ original,
400
+ normalized,
401
+ unwrapped,
402
+ _join_tokens(tokens),
403
+ rejection_reason=reason,
404
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
405
+ capability_reason=reason,
406
+ shell_control_flow="unsafe",
407
+ pipeline_policy=(
408
+ "rejected_without_pipefail" if reason == "unsafe_pipeline" else "none"
409
+ ),
410
+ source=source,
411
+ contract_type=contract_type,
412
+ )
413
+
414
+ primary = _join_tokens(primary_tokens) if shell_control_flow == "safe_cd_and" else unwrapped
415
+ parts = _canonical_parts(primary)
416
+ if not parts:
417
+ return _analysis(
418
+ original,
419
+ normalized,
420
+ unwrapped,
421
+ primary,
422
+ rejection_reason="unrecognized_command",
423
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
424
+ capability_reason="unrecognized_command",
425
+ shell_control_flow=shell_control_flow,
426
+ cd_target=cd_target,
427
+ source=source,
428
+ contract_type=contract_type,
429
+ )
430
+ canonical = shlex.join(parts)
431
+ rejection = _non_assertive_rejection_reason(parts)
432
+ if rejection:
433
+ return _analysis(
434
+ original,
435
+ normalized,
436
+ unwrapped,
437
+ primary,
438
+ canonical_command=canonical,
439
+ parts=tuple(parts),
440
+ rejection_reason=rejection,
441
+ capability=VerificationCommandEvidentiaryCapability.NON_ASSERTIVE,
442
+ capability_reason=rejection,
443
+ shell_control_flow=shell_control_flow,
444
+ cd_target=cd_target,
445
+ source=source,
446
+ contract_type=contract_type,
447
+ )
448
+
449
+ family = _command_family(parts, trusted=trusted)
450
+ checker_paths = _checker_entrypoint_paths(parts, trusted=trusted)
451
+ fingerprints = (
452
+ tuple(
453
+ _fingerprint_checker_path(path, workspace_root=workspace_root) for path in checker_paths
454
+ )
455
+ if capture_checker_fingerprints
456
+ else tuple()
457
+ )
458
+ if family is not None:
459
+ return _analysis(
460
+ original,
461
+ normalized,
462
+ unwrapped,
463
+ primary,
464
+ canonical_command=canonical,
465
+ parts=tuple(parts),
466
+ family=family,
467
+ capability=VerificationCommandEvidentiaryCapability.ASSERTIVE,
468
+ capability_reason="recognized_assertive_verification_command",
469
+ shell_control_flow=shell_control_flow,
470
+ checker_paths=checker_paths,
471
+ checker_integrity_required=bool(checker_paths),
472
+ checker_fingerprints=fingerprints,
473
+ cd_target=cd_target,
474
+ source=source,
475
+ contract_type=contract_type,
476
+ )
477
+
478
+ inconclusive = _unknown_capability_reason(parts)
479
+ return _analysis(
480
+ original,
481
+ normalized,
482
+ unwrapped,
483
+ primary,
484
+ canonical_command=canonical,
485
+ parts=tuple(parts),
486
+ capability=VerificationCommandEvidentiaryCapability.UNKNOWN,
487
+ capability_reason=inconclusive,
488
+ inconclusive_reason=inconclusive,
489
+ shell_control_flow=shell_control_flow,
490
+ checker_paths=checker_paths,
491
+ checker_integrity_required=bool(checker_paths),
492
+ checker_fingerprints=fingerprints,
493
+ cd_target=cd_target,
494
+ source=source,
495
+ contract_type=contract_type,
496
+ )
497
+
498
+
499
+ _BENIGN_NON_EXECUTION_REASONS = frozenset(
500
+ {
501
+ "dotnet_test_zero_tests",
502
+ "go_test_no_test_files",
503
+ "go_test_no_tests_to_run",
504
+ "gradle_test_zero_tests",
505
+ "maven_test_zero_tests",
506
+ "maven_verify_zero_tests",
507
+ "no_applicable_verification",
508
+ "npm_test_zero_tests",
509
+ "pnpm_test_zero_tests",
510
+ "pytest_no_tests_collected",
511
+ "unittest_no_tests_run",
512
+ "verification_nothing_to_do",
513
+ "yarn_test_zero_tests",
514
+ }
515
+ )
516
+
517
+
518
+ def is_benign_non_execution_reason(reason: str | None) -> bool:
519
+ return str(reason or "").strip() in _BENIGN_NON_EXECUTION_REASONS
520
+
521
+
522
+ def command_status_from_execution(
523
+ *,
524
+ exit_code: int,
525
+ real_execution: bool | None,
526
+ non_execution_reason: str | None = None,
527
+ ) -> VerificationCommandStatus:
528
+ if non_execution_reason == "stale_verification":
529
+ return VerificationCommandStatus.STALE
530
+ if is_benign_non_execution_reason(non_execution_reason):
531
+ return VerificationCommandStatus.SKIPPED
532
+ if exit_code != 0:
533
+ return VerificationCommandStatus.FAILED
534
+ if real_execution is True:
535
+ return VerificationCommandStatus.PASSED
536
+ if real_execution is False:
537
+ return VerificationCommandStatus.NOT_EXECUTED
538
+ return VerificationCommandStatus.INCONCLUSIVE
539
+
540
+
541
+ def _analysis(
542
+ original: str,
543
+ normalized: str,
544
+ unwrapped: str,
545
+ primary: str,
546
+ *,
547
+ canonical_command: str | None = None,
548
+ parts: tuple[str, ...] = tuple(),
549
+ family: str | None = None,
550
+ capability: VerificationCommandEvidentiaryCapability,
551
+ capability_reason: str,
552
+ shell_control_flow: str = "none",
553
+ pipeline_policy: str = "none",
554
+ checker_paths: tuple[str, ...] = tuple(),
555
+ checker_integrity_required: bool = False,
556
+ checker_fingerprints: tuple[CheckerEntrypointFingerprint, ...] = tuple(),
557
+ rejection_reason: str = "",
558
+ inconclusive_reason: str = "",
559
+ cd_target: str | None = None,
560
+ source: str = "",
561
+ contract_type: str = "",
562
+ ) -> VerificationCommandAnalysis:
563
+ return VerificationCommandAnalysis(
564
+ original_command=original,
565
+ normalized_command=normalized,
566
+ unwrapped_command=unwrapped,
567
+ primary_command=primary,
568
+ canonical_command=canonical_command,
569
+ parts=parts,
570
+ command_family=family,
571
+ provenance_source=source,
572
+ trust_source=contract_type,
573
+ evidentiary_capability=capability,
574
+ capability_reason=capability_reason,
575
+ shell_control_flow=shell_control_flow,
576
+ pipeline_policy=pipeline_policy,
577
+ checker_entrypoint_paths=checker_paths,
578
+ checker_integrity_required=checker_integrity_required,
579
+ checker_fingerprints=checker_fingerprints,
580
+ real_execution_required=True,
581
+ rejection_reason=rejection_reason,
582
+ inconclusive_reason=inconclusive_reason,
583
+ cd_target=cd_target,
584
+ )
585
+
586
+
587
+ def _normalize_command(command: str) -> str:
588
+ return " ".join(str(command or "").strip().split())
589
+
590
+
591
+ def _shell_tokens(command: str) -> list[str] | None:
592
+ try:
593
+ lexer = shlex.shlex(command, posix=True, punctuation_chars="|&;")
594
+ lexer.whitespace_split = True
595
+ lexer.commenters = ""
596
+ return list(lexer)
597
+ except ValueError:
598
+ return None
599
+
600
+
601
+ def _join_tokens(tokens: list[str]) -> str:
602
+ return shlex.join(tokens)
603
+
604
+
605
+ def _unwrap_shell_wrapper_command(command: str) -> str | None:
606
+ parts = split_verify_command_parts(command)
607
+ if not parts:
608
+ return None
609
+ head = _command_head(parts[0])
610
+ if head in _SHELL_WRAPPER_HEADS and len(parts) == 3 and parts[1] in {"-c", "-lc"}:
611
+ return _normalize_command(parts[2])
612
+ if head == "fish" and len(parts) == 3 and parts[1] == "-c":
613
+ return _normalize_command(parts[2])
614
+ if head == "cmd" and len(parts) == 3 and parts[1].casefold() == "/c":
615
+ return _normalize_command(parts[2])
616
+ if head in {"powershell", "pwsh"} and len(parts) == 3 and parts[1].casefold() == "-command":
617
+ return _normalize_command(parts[2])
618
+ return None
619
+
620
+
621
+ def _cd_target_rejection_reason(target: str, *, workspace_root: Path | None) -> str:
622
+ if not target or target.startswith("-"):
623
+ return "disallowed_cd_target"
624
+ if workspace_root is None:
625
+ return ""
626
+ if "$" in target or "`" in target:
627
+ return "disallowed_cd_target"
628
+ if target.startswith("/"):
629
+ return ""
630
+ try:
631
+ root = workspace_root.resolve()
632
+ raw = Path(os.path.expanduser(target))
633
+ if raw.is_absolute():
634
+ return ""
635
+ candidate = raw if raw.is_absolute() else root / raw
636
+ candidate.resolve().relative_to(root)
637
+ except (OSError, RuntimeError, ValueError):
638
+ return "disallowed_cd_target"
639
+ return ""
640
+
641
+
642
+ def _canonical_parts(command: str) -> list[str] | None:
643
+ parts = split_verify_command_parts(command)
644
+ if not parts:
645
+ return None
646
+ while True:
647
+ changed = False
648
+ stripped_env = _strip_env_prefix(parts)
649
+ if stripped_env is None:
650
+ return None
651
+ if stripped_env != parts:
652
+ parts = stripped_env
653
+ changed = True
654
+ stripped_runner = strip_verify_runner_prefix(parts)
655
+ if stripped_runner is None:
656
+ return None
657
+ if stripped_runner != parts:
658
+ parts = stripped_runner
659
+ changed = True
660
+ if parts and _command_head(parts[0]) == "command":
661
+ parts = parts[1:]
662
+ changed = True
663
+ if not parts:
664
+ return None
665
+ if not changed:
666
+ break
667
+ parts = normalize_verify_module_invocation(parts)
668
+ return parts or None
669
+
670
+
671
+ def _strip_env_prefix(parts: list[str]) -> list[str] | None:
672
+ out = list(parts)
673
+ if not out:
674
+ return None
675
+ if out[0] == "env":
676
+ out = out[1:]
677
+ if not out or out[0].startswith("-"):
678
+ return None
679
+ while out and _ENV_ASSIGNMENT_RE.match(out[0]):
680
+ out = out[1:]
681
+ return out or None
682
+
683
+
684
+ def _command_head(token: str) -> str:
685
+ head = Path(str(token).replace("\\", "/")).name.casefold()
686
+ if head.endswith(".exe"):
687
+ head = head[:-4]
688
+ return head
689
+
690
+
691
+ def _non_assertive_rejection_reason(parts: list[str]) -> str:
692
+ if _is_vacuous_success_command(parts):
693
+ return "vacuous_verifier"
694
+ if _is_non_assertive_observation_command(parts):
695
+ return "non_assertive_observation"
696
+ if _is_non_assertive_python_snippet(parts):
697
+ return "vacuous_verifier"
698
+ if _is_compile_only_python_command(parts):
699
+ return "non_assertive_observation"
700
+ if _is_non_executing_verification_form(parts):
701
+ return "non_assertive_verification_mode"
702
+ return ""
703
+
704
+
705
+ def _is_vacuous_success_command(parts: list[str]) -> bool:
706
+ if not parts:
707
+ return False
708
+ head = _command_head(parts[0])
709
+ if len(parts) == 1 and (head in _VACUOUS_SUCCESS_HEADS or parts[0].strip() == ":"):
710
+ return True
711
+ if len(parts) == 2 and head in {"exit", "return"} and parts[1] == "0":
712
+ return True
713
+ return _is_non_assertive_python_snippet(parts)
714
+
715
+
716
+ def _is_non_assertive_observation_command(parts: list[str]) -> bool:
717
+ if not parts:
718
+ return False
719
+ head = _command_head(parts[0])
720
+ if head in {"grep", "curl"}:
721
+ return False
722
+ return head in _OBSERVATION_HEADS
723
+
724
+
725
+ def _is_python_interpreter_snippet(parts: list[str]) -> bool:
726
+ return len(parts) >= 3 and _command_head(parts[0]) in _PYTHON_EXECUTABLES and parts[1] == "-c"
727
+
728
+
729
+ def _is_non_assertive_python_snippet(parts: list[str]) -> bool:
730
+ if not _is_python_interpreter_snippet(parts):
731
+ return False
732
+ snippet = " ".join(parts[2:]).strip().casefold()
733
+ compact = "".join(snippet.split())
734
+ if compact in {
735
+ "pass",
736
+ "print('ok')",
737
+ 'print("ok")',
738
+ "print(1)",
739
+ "sys.exit(0)",
740
+ "raisesystemexit(0)",
741
+ }:
742
+ return True
743
+ return compact.startswith("importsys;") and compact.endswith("sys.exit(0)")
744
+
745
+
746
+ def _is_compile_only_python_command(parts: list[str]) -> bool:
747
+ return (
748
+ len(parts) >= 3
749
+ and _command_head(parts[0]) in _PYTHON_EXECUTABLES
750
+ and parts[1] == "-m"
751
+ and parts[2] in {"compileall", "py_compile"}
752
+ )
753
+
754
+
755
+ def _is_non_executing_verification_form(parts: list[str]) -> bool:
756
+ lowered = [part.casefold() for part in parts]
757
+ if not lowered:
758
+ return True
759
+ if any(part in _META_OPTIONS for part in lowered):
760
+ return True
761
+ if lowered[0] in {"pytest", "py.test"}:
762
+ return any(
763
+ part in _PYTEST_NON_VERIFICATION_OPTIONS | _PYTEST_NON_EXECUTING_OPTIONS
764
+ for part in lowered[1:]
765
+ )
766
+ if lowered[:2] == ["go", "test"]:
767
+ if any(
768
+ part in _GO_TEST_NON_EXECUTING_OPTIONS or part.startswith("-list=")
769
+ for part in lowered[2:]
770
+ ):
771
+ return True
772
+ for index, part in enumerate(lowered[2:], start=2):
773
+ if part == "-run" and index + 1 < len(lowered) and lowered[index + 1] == "^$":
774
+ return True
775
+ if part == "-run=^$":
776
+ return True
777
+ if lowered[:2] == ["cargo", "test"]:
778
+ return any(part in _CARGO_TEST_NON_EXECUTING_OPTIONS for part in lowered[2:])
779
+ if lowered[:2] == ["ruff", "check"]:
780
+ return any(part in _RUFF_CHECK_NON_EXECUTING_OPTIONS for part in lowered[2:])
781
+ if lowered[0] == "mypy":
782
+ return any(part in _MYPY_NON_EXECUTING_OPTIONS for part in lowered[1:])
783
+ return False
784
+
785
+
786
+ def _command_family(parts: list[str], *, trusted: bool) -> str | None:
787
+ if not parts:
788
+ return None
789
+ head = _command_head(parts[0])
790
+ tail = [part.casefold() for part in parts[1:]]
791
+ if head in {"pytest", "py.test"}:
792
+ return "pytest"
793
+ if head == "unittest":
794
+ return "unittest"
795
+ if head == "mypy":
796
+ return "mypy"
797
+ if head == "go" and tail and tail[0] == "test":
798
+ return "go:test"
799
+ if head == "node" and tail and tail[0] == "--test":
800
+ return "node:test"
801
+ if head == "cargo" and tail and tail[0] in {"test", "check"}:
802
+ return f"cargo:{tail[0]}"
803
+ if head in {"mvn", "mvnw", "./mvnw"} and tail and tail[0] in {"test", "verify"}:
804
+ return f"maven:{tail[0]}"
805
+ if head in {"gradle", "gradlew", "./gradlew"} and tail and tail[0] == "test":
806
+ return "gradle:test"
807
+ if head == "dotnet" and tail and tail[0] == "test":
808
+ return "dotnet:test"
809
+ if head == "mix" and tail and tail[0] == "test":
810
+ return "mix:test"
811
+ if head in {"npm", "pnpm", "yarn"}:
812
+ script = _node_assertive_script(parts)
813
+ if script:
814
+ return f"{head}:{script}"
815
+ if head in {"make", "just"} and tail and tail[0] in {"test", "check", "verify"}:
816
+ return f"{head}:{tail[0]}"
817
+ if head == "ruff" and tail and tail[0] == "check":
818
+ return "ruff:check"
819
+ if head in {"test", "["} and len(tail) >= 2:
820
+ return "assertion:test"
821
+ if head in {"cmp", "diff"}:
822
+ operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
823
+ if len(operands) >= 2:
824
+ return f"assertion:{head}"
825
+ if head == "grep":
826
+ has_quiet = any(part in {"-q", "--quiet"} for part in tail)
827
+ operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
828
+ if has_quiet and len(operands) >= 2:
829
+ return "assertion:grep"
830
+ if head == "curl" and _curl_has_fail_semantics(parts[1:]):
831
+ return "assertion:http_readiness"
832
+ if _is_assertive_python_snippet(parts):
833
+ return "assertion:python_snippet"
834
+ if _repo_local_validation_executable(parts):
835
+ return "assertion:repo_local_executable"
836
+ if _interpreter_validation_script(parts):
837
+ return "assertion:repo_local_script"
838
+ if trusted and _checker_entrypoint_paths(parts, trusted=trusted):
839
+ return "assertion:custom_checker"
840
+ return None
841
+
842
+
843
+ def _curl_has_fail_semantics(args: list[str]) -> bool:
844
+ for arg in args:
845
+ if arg == "--fail" or arg == "--fail-with-body":
846
+ return True
847
+ if arg.startswith("--fail-"):
848
+ return True
849
+ if arg.startswith("-") and not arg.startswith("--") and "f" in arg[1:]:
850
+ return True
851
+ return False
852
+
853
+
854
+ def _node_assertive_script(parts: list[str]) -> str | None:
855
+ if not parts:
856
+ return None
857
+ head = _command_head(parts[0])
858
+ idx = 1
859
+ while idx < len(parts):
860
+ token = parts[idx].casefold()
861
+ if token in {"--prefix", "--dir", "--cwd", "-c"} and idx + 1 < len(parts):
862
+ idx += 2
863
+ continue
864
+ if (
865
+ token.startswith("--prefix=")
866
+ or token.startswith("--dir=")
867
+ or token.startswith("--cwd=")
868
+ ):
869
+ idx += 1
870
+ continue
871
+ break
872
+ if idx >= len(parts):
873
+ return None
874
+ command = parts[idx].casefold()
875
+ if command == "run" and idx + 1 < len(parts):
876
+ script = parts[idx + 1].casefold()
877
+ elif head == "yarn" and command not in {"run", "exec"}:
878
+ script = command
879
+ else:
880
+ script = command
881
+ if script.startswith("test:"):
882
+ return "test"
883
+ return script if script in _NODE_ASSERTIVE_SCRIPTS else None
884
+
885
+
886
+ def _unknown_capability_reason(parts: list[str]) -> str:
887
+ if parts and _command_head(parts[0]) == "curl":
888
+ return "http_probe_requires_curl_fail"
889
+ return "unknown_verification_capability"
890
+
891
+
892
+ def _is_assertive_python_snippet(parts: list[str]) -> bool:
893
+ if not _is_python_interpreter_snippet(parts):
894
+ return False
895
+ snippet = " ".join(parts[2:]).strip()
896
+ compact = snippet.replace(" ", "")
897
+ return "assert " in snippet or ";assert" in compact or compact.startswith("assert")
898
+
899
+
900
+ def _repo_local_validation_executable(parts: list[str]) -> bool:
901
+ if not parts:
902
+ return False
903
+ executable = PurePosixPath(parts[0].replace("\\", "/"))
904
+ if "/" not in parts[0] and not parts[0].startswith("."):
905
+ return False
906
+ markers = {executable.stem.casefold(), *(part.casefold() for part in executable.parts[:-1])}
907
+ return any(marker in value for value in markers for marker in _VALIDATION_COMMAND_PATH_MARKERS)
908
+
909
+
910
+ def _interpreter_validation_script(parts: list[str]) -> bool:
911
+ path = _interpreter_script_path(parts)
912
+ if not path:
913
+ return False
914
+ script = PurePosixPath(path.replace("\\", "/"))
915
+ markers = {script.stem.casefold(), *(part.casefold() for part in script.parts[:-1])}
916
+ return any(marker in value for value in markers for marker in _VALIDATION_COMMAND_PATH_MARKERS)
917
+
918
+
919
+ def _checker_entrypoint_paths(parts: list[str], *, trusted: bool) -> tuple[str, ...]:
920
+ if not parts:
921
+ return tuple()
922
+ script = _interpreter_script_path(parts)
923
+ if script:
924
+ return (script,)
925
+ head = parts[0]
926
+ if _is_repo_local_path(head) and ("/" in head.replace("\\", "/") or head.startswith(".")):
927
+ return (head,)
928
+ return tuple()
929
+
930
+
931
+ def _interpreter_script_path(parts: list[str]) -> str | None:
932
+ if not parts:
933
+ return None
934
+ head = _command_head(parts[0])
935
+ if head not in {"python", "python3", "py", "node", "ruby", "rscript", "r", "bash", "sh", "zsh"}:
936
+ return None
937
+ tail = parts[1:]
938
+ if not tail or tail[0].casefold() in _META_OPTIONS:
939
+ return None
940
+ if head in _PYTHON_EXECUTABLES and tail[0] == "-m":
941
+ return None
942
+ for item in tail:
943
+ if item == "--" or item.startswith("-"):
944
+ continue
945
+ if _is_repo_local_path(item):
946
+ return item
947
+ return None
948
+
949
+
950
+ def _is_repo_local_path(path: str) -> bool:
951
+ if not path or path.startswith("-"):
952
+ return False
953
+ pure = PurePosixPath(path.replace("\\", "/"))
954
+ return not pure.is_absolute() and ".." not in pure.parts
955
+
956
+
957
+ def _fingerprint_checker_path(
958
+ display_path: str,
959
+ *,
960
+ workspace_root: Path | None,
961
+ max_bytes: int = 2_000_000,
962
+ ) -> CheckerEntrypointFingerprint:
963
+ base = workspace_root.resolve() if workspace_root is not None else Path.cwd().resolve()
964
+ raw = Path(display_path)
965
+ candidate = raw if raw.is_absolute() else base / raw
966
+ try:
967
+ resolved = candidate.resolve(strict=True)
968
+ except OSError:
969
+ return CheckerEntrypointFingerprint(
970
+ display_path=display_path,
971
+ resolved_path=os.fspath(candidate),
972
+ is_regular_file=False,
973
+ size=None,
974
+ sha256=None,
975
+ )
976
+ try:
977
+ stat = resolved.stat()
978
+ except OSError:
979
+ return CheckerEntrypointFingerprint(
980
+ display_path=display_path,
981
+ resolved_path=os.fspath(resolved),
982
+ is_regular_file=False,
983
+ size=None,
984
+ sha256=None,
985
+ )
986
+ if not resolved.is_file() or stat.st_size > max_bytes:
987
+ return CheckerEntrypointFingerprint(
988
+ display_path=display_path,
989
+ resolved_path=os.fspath(resolved),
990
+ is_regular_file=resolved.is_file(),
991
+ size=int(stat.st_size),
992
+ sha256=None,
993
+ )
994
+ digest = hashlib.sha256()
995
+ with resolved.open("rb") as handle:
996
+ for chunk in iter(lambda: handle.read(65536), b""):
997
+ digest.update(chunk)
998
+ return CheckerEntrypointFingerprint(
999
+ display_path=display_path,
1000
+ resolved_path=os.fspath(resolved),
1001
+ is_regular_file=True,
1002
+ size=int(stat.st_size),
1003
+ sha256=digest.hexdigest(),
1004
+ )