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,574 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import shlex
5
+ from dataclasses import dataclass, field
6
+ from enum import StrEnum
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from .verification_command_analysis import (
11
+ VerificationCommandEvidentiaryCapability,
12
+ analyze_verification_command,
13
+ )
14
+
15
+
16
+ class VerificationCommandExecutionMode(StrEnum):
17
+ ARGV = "ARGV"
18
+ TRUSTED_SHELL_EXPRESSION = "TRUSTED_SHELL_EXPRESSION"
19
+ INTERPRETER_SNIPPET = "INTERPRETER_SNIPPET"
20
+ INVALID = "INVALID"
21
+
22
+
23
+ class VerificationCommandProvenance(StrEnum):
24
+ HOST_AUTHORITATIVE = "HOST_AUTHORITATIVE"
25
+ EXPLICIT_USER_COMMAND = "EXPLICIT_USER_COMMAND"
26
+ PREEXISTING_REPO_NATIVE = "PREEXISTING_REPO_NATIVE"
27
+ PREEXISTING_TASK_CHECKER = "PREEXISTING_TASK_CHECKER"
28
+ INFERRED_HEURISTIC = "INFERRED_HEURISTIC"
29
+
30
+
31
+ class VerificationCommandTrustLevel(StrEnum):
32
+ TRUSTED = "TRUSTED"
33
+ UNTRUSTED = "UNTRUSTED"
34
+
35
+
36
+ class VerificationCommandRequirement(StrEnum):
37
+ REQUIRED = "REQUIRED"
38
+ ADVISORY = "ADVISORY"
39
+
40
+
41
+ class VerificationCommandValidationStatus(StrEnum):
42
+ VALID = "VALID"
43
+ INVALID = "INVALID"
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class VerificationCommandSpec:
48
+ command_id: str
49
+ original_text: str
50
+ display_text: str
51
+ execution_mode: VerificationCommandExecutionMode
52
+ provenance: VerificationCommandProvenance
53
+ trust_level: VerificationCommandTrustLevel
54
+ requirement: VerificationCommandRequirement
55
+ working_directory: str = "."
56
+ timeout_policy: str = "session_verification_timeout"
57
+ acceptance_criterion_ids: tuple[str, ...] = tuple()
58
+ validation_status: VerificationCommandValidationStatus = (
59
+ VerificationCommandValidationStatus.VALID
60
+ )
61
+ rejection_reason: str = ""
62
+ evidentiary_capability: VerificationCommandEvidentiaryCapability = (
63
+ VerificationCommandEvidentiaryCapability.UNKNOWN
64
+ )
65
+ capability_reason: str = ""
66
+ argv: tuple[str, ...] = field(default_factory=tuple)
67
+
68
+ def as_payload(self) -> dict[str, Any]:
69
+ return {
70
+ "command_id": self.command_id,
71
+ "original_text": self.original_text,
72
+ "display_text": self.display_text,
73
+ "execution_mode": self.execution_mode.value,
74
+ "provenance": self.provenance.value,
75
+ "trust_level": self.trust_level.value,
76
+ "requirement": self.requirement.value,
77
+ "working_directory": self.working_directory,
78
+ "timeout_policy": self.timeout_policy,
79
+ "acceptance_criterion_ids": list(self.acceptance_criterion_ids),
80
+ "validation_status": self.validation_status.value,
81
+ "rejection_reason": self.rejection_reason,
82
+ "evidentiary_capability": self.evidentiary_capability.value,
83
+ "capability_reason": self.capability_reason,
84
+ "argv": list(self.argv),
85
+ }
86
+
87
+
88
+ # Rejection reasons that mean "Alysis Code could not classify this command",
89
+ # as opposed to "Alysis Code recognized this command and refused it". Only the
90
+ # former is survivable: an unclassifiable command costs the session a
91
+ # verification contract, while a refused one (a vacuous verifier, a masked
92
+ # failure, an unsafe pipeline) would actively manufacture false evidence, so
93
+ # accepting it is worse than stopping.
94
+ UNCLASSIFIABLE_VERIFY_REJECTION_REASONS = frozenset(
95
+ {
96
+ "unknown_verification_capability",
97
+ "unrecognized_command",
98
+ }
99
+ )
100
+
101
+
102
+ def rejection_reason_is_unclassifiable(reason: str) -> bool:
103
+ return str(reason or "").strip() in UNCLASSIFIABLE_VERIFY_REJECTION_REASONS
104
+
105
+
106
+ _TRUSTED_SHELL_PROVENANCE = {
107
+ VerificationCommandProvenance.HOST_AUTHORITATIVE,
108
+ VerificationCommandProvenance.EXPLICIT_USER_COMMAND,
109
+ VerificationCommandProvenance.PREEXISTING_REPO_NATIVE,
110
+ VerificationCommandProvenance.PREEXISTING_TASK_CHECKER,
111
+ }
112
+ _DISALLOWED_VERIFICATION_SHELL_TOKENS = {"||", "&&", ";", "|", "&"}
113
+ _SUPPORTED_VERIFY_HEADS = {
114
+ "pytest",
115
+ "py.test",
116
+ "unittest",
117
+ "mypy",
118
+ "ruff",
119
+ "go",
120
+ "cargo",
121
+ "npm",
122
+ "pnpm",
123
+ "yarn",
124
+ "make",
125
+ "just",
126
+ "test",
127
+ "[",
128
+ "cmp",
129
+ "diff",
130
+ "grep",
131
+ }
132
+ _PYTHON_EXECUTABLES = {"python", "python3", "py"}
133
+ _SHELL_WRAPPER_HEADS = {"bash", "sh", "zsh"}
134
+ _VACUOUS_SUCCESS_HEADS = {"true", ":"}
135
+ _NON_ASSERTIVE_OBSERVATION_HEADS = {
136
+ "cat",
137
+ "echo",
138
+ "find",
139
+ "head",
140
+ "less",
141
+ "ls",
142
+ "more",
143
+ "printf",
144
+ "pwd",
145
+ "tail",
146
+ "type",
147
+ "wc",
148
+ "which",
149
+ }
150
+
151
+
152
+ def build_verification_command_specs(
153
+ commands: tuple[str, ...] | list[str],
154
+ *,
155
+ source: str,
156
+ contract_type: str = "",
157
+ acceptance_criterion_ids: tuple[str, ...] = tuple(),
158
+ ) -> tuple[VerificationCommandSpec, ...]:
159
+ return tuple(
160
+ build_verification_command_spec(
161
+ command,
162
+ source=source,
163
+ contract_type=contract_type,
164
+ acceptance_criterion_ids=acceptance_criterion_ids,
165
+ index=index,
166
+ )
167
+ for index, command in enumerate(commands, start=1)
168
+ if str(command).strip()
169
+ )
170
+
171
+
172
+ def build_verification_command_spec(
173
+ command: str,
174
+ *,
175
+ source: str,
176
+ contract_type: str = "",
177
+ acceptance_criterion_ids: tuple[str, ...] = tuple(),
178
+ index: int = 1,
179
+ ) -> VerificationCommandSpec:
180
+ original = str(command or "").strip()
181
+ provenance = _provenance_for_source(source=source, contract_type=contract_type)
182
+ trust_level = (
183
+ VerificationCommandTrustLevel.TRUSTED
184
+ if provenance in _TRUSTED_SHELL_PROVENANCE
185
+ else VerificationCommandTrustLevel.UNTRUSTED
186
+ )
187
+ requirement = (
188
+ VerificationCommandRequirement.ADVISORY
189
+ if provenance == VerificationCommandProvenance.INFERRED_HEURISTIC
190
+ and contract_type in {"generic_fallback", "unavailable"}
191
+ else VerificationCommandRequirement.REQUIRED
192
+ )
193
+ parsed_parts: tuple[str, ...] = tuple()
194
+ validation_status = VerificationCommandValidationStatus.VALID
195
+ rejection_reason = ""
196
+ execution_mode = VerificationCommandExecutionMode.ARGV
197
+ analysis = analyze_verification_command(
198
+ original,
199
+ source=source,
200
+ contract_type=contract_type,
201
+ trusted=_source_is_trusted(source=source, contract_type=contract_type),
202
+ )
203
+ evidentiary_capability = analysis.evidentiary_capability
204
+ capability_reason = analysis.capability_reason
205
+ if analysis.parts:
206
+ parsed_parts = analysis.parts
207
+ if not original:
208
+ validation_status = VerificationCommandValidationStatus.INVALID
209
+ execution_mode = VerificationCommandExecutionMode.INVALID
210
+ rejection_reason = "empty_command"
211
+ else:
212
+ try:
213
+ tuple(shlex.split(original, posix=True))
214
+ except ValueError as exc:
215
+ validation_status = VerificationCommandValidationStatus.INVALID
216
+ execution_mode = VerificationCommandExecutionMode.INVALID
217
+ rejection_reason = f"parse_error: {exc}"
218
+ else:
219
+ requires_shell_expression = (
220
+ analysis.shell_control_flow != "none"
221
+ or analysis.unwrapped_command != analysis.normalized_command
222
+ )
223
+ if not analysis.parts and not analysis.rejection_reason:
224
+ validation_status = VerificationCommandValidationStatus.INVALID
225
+ execution_mode = VerificationCommandExecutionMode.INVALID
226
+ rejection_reason = analysis.inconclusive_reason or "unrecognized_command"
227
+ elif analysis.rejection_reason:
228
+ validation_status = VerificationCommandValidationStatus.INVALID
229
+ execution_mode = VerificationCommandExecutionMode.INVALID
230
+ rejection_reason = analysis.rejection_reason
231
+ elif (
232
+ analysis.evidentiary_capability
233
+ != VerificationCommandEvidentiaryCapability.ASSERTIVE
234
+ ):
235
+ validation_status = VerificationCommandValidationStatus.INVALID
236
+ execution_mode = VerificationCommandExecutionMode.INVALID
237
+ rejection_reason = analysis.inconclusive_reason or "unknown_verification_capability"
238
+ elif requires_shell_expression:
239
+ if provenance in _TRUSTED_SHELL_PROVENANCE:
240
+ execution_mode = VerificationCommandExecutionMode.TRUSTED_SHELL_EXPRESSION
241
+ else:
242
+ validation_status = VerificationCommandValidationStatus.INVALID
243
+ execution_mode = VerificationCommandExecutionMode.INVALID
244
+ rejection_reason = "untrusted_shell_expression"
245
+ elif _is_python_interpreter_snippet(parsed_parts):
246
+ execution_mode = VerificationCommandExecutionMode.INTERPRETER_SNIPPET
247
+ command_id = _stable_command_id(
248
+ source=source,
249
+ contract_type=contract_type,
250
+ command=original,
251
+ index=index,
252
+ )
253
+ return VerificationCommandSpec(
254
+ command_id=command_id,
255
+ original_text=original,
256
+ display_text=original,
257
+ execution_mode=execution_mode,
258
+ provenance=provenance,
259
+ trust_level=trust_level,
260
+ requirement=requirement,
261
+ acceptance_criterion_ids=acceptance_criterion_ids,
262
+ validation_status=validation_status,
263
+ rejection_reason=rejection_reason,
264
+ evidentiary_capability=evidentiary_capability,
265
+ capability_reason=capability_reason,
266
+ argv=parsed_parts if execution_mode == VerificationCommandExecutionMode.ARGV else tuple(),
267
+ )
268
+
269
+
270
+ def trusted_shell_expression_commands(
271
+ commands: tuple[str, ...] | list[str],
272
+ *,
273
+ source: str,
274
+ contract_type: str = "",
275
+ ) -> set[str]:
276
+ return {
277
+ _normalize_exact_command(spec.original_text)
278
+ for spec in build_verification_command_specs(
279
+ commands,
280
+ source=source,
281
+ contract_type=contract_type,
282
+ )
283
+ if spec.execution_mode == VerificationCommandExecutionMode.TRUSTED_SHELL_EXPRESSION
284
+ and spec.validation_status == VerificationCommandValidationStatus.VALID
285
+ }
286
+
287
+
288
+ def command_matches_trusted_shell_expression(
289
+ observed_command: str,
290
+ known_commands: tuple[str, ...] | list[str],
291
+ *,
292
+ source: str,
293
+ contract_type: str = "",
294
+ ) -> set[str]:
295
+ observed = _normalize_exact_command(observed_command)
296
+ if not observed:
297
+ return set()
298
+ trusted = trusted_shell_expression_commands(
299
+ known_commands,
300
+ source=source,
301
+ contract_type=contract_type,
302
+ )
303
+ return {command for command in trusted if observed == command}
304
+
305
+
306
+ def command_specs_payload(specs: tuple[VerificationCommandSpec, ...]) -> list[dict[str, Any]]:
307
+ return [spec.as_payload() for spec in specs]
308
+
309
+
310
+ def _source_is_trusted(*, source: str, contract_type: str) -> bool:
311
+ provenance = _provenance_for_source(source=source, contract_type=contract_type)
312
+ return provenance in _TRUSTED_SHELL_PROVENANCE
313
+
314
+
315
+ def _provenance_for_source(
316
+ *,
317
+ source: str,
318
+ contract_type: str,
319
+ ) -> VerificationCommandProvenance:
320
+ source = str(source or "")
321
+ contract_type = str(contract_type or "")
322
+ if source == "environment.authoritative_verification_commands" or contract_type in {
323
+ "authoritative_override",
324
+ "explicit_override",
325
+ }:
326
+ return (
327
+ VerificationCommandProvenance.HOST_AUTHORITATIVE
328
+ if source == "environment.authoritative_verification_commands"
329
+ else VerificationCommandProvenance.EXPLICIT_USER_COMMAND
330
+ )
331
+ if source == "cli.verify_cmd" or source.startswith("task_refinement.explicit"):
332
+ return VerificationCommandProvenance.EXPLICIT_USER_COMMAND
333
+ if source == "task_refinement.explicit_user_command" or contract_type == "task_acceptance":
334
+ return VerificationCommandProvenance.EXPLICIT_USER_COMMAND
335
+ if source == "repo_scan.likely_test_commands" or contract_type == "repo_native":
336
+ return VerificationCommandProvenance.PREEXISTING_REPO_NATIVE
337
+ if source.startswith("task_refinement.") and contract_type == "task_inferred":
338
+ return VerificationCommandProvenance.INFERRED_HEURISTIC
339
+ return VerificationCommandProvenance.INFERRED_HEURISTIC
340
+
341
+
342
+ def _normalize_exact_command(command: str) -> str:
343
+ return " ".join(str(command or "").strip().split())
344
+
345
+
346
+ def _has_disallowed_shell_control_flow(raw: str) -> bool:
347
+ analysis = analyze_verification_command(raw, trusted=True)
348
+ return analysis.shell_control_flow in {"unsafe", "pipeline"} or analysis.rejection_reason in {
349
+ "disallowed_shell_control_flow",
350
+ "unsafe_pipeline",
351
+ }
352
+
353
+
354
+ def _normalize_and_unwrap_shell_command(raw: str) -> str:
355
+ normalized = _normalize_exact_command(raw)
356
+ while True:
357
+ wrapped = _unwrap_shell_wrapper_command(normalized)
358
+ if not wrapped or wrapped == normalized:
359
+ return normalized
360
+ normalized = wrapped
361
+
362
+
363
+ def _unwrap_shell_wrapper_command(normalized: str) -> str | None:
364
+ try:
365
+ parts = shlex.split(normalized, posix=True)
366
+ except ValueError:
367
+ return None
368
+ if not parts:
369
+ return None
370
+ head = Path(parts[0]).name.casefold()
371
+ if head in _SHELL_WRAPPER_HEADS and len(parts) == 3 and parts[1] == "-lc":
372
+ return _normalize_exact_command(parts[2])
373
+ if head == "fish" and len(parts) == 3 and parts[1] == "-c":
374
+ return _normalize_exact_command(parts[2])
375
+ if head == "cmd" and len(parts) == 3 and parts[1].casefold() == "/c":
376
+ return _normalize_exact_command(parts[2])
377
+ if head in {"powershell", "pwsh"} and len(parts) == 3 and parts[1].casefold() == "-command":
378
+ return _normalize_exact_command(parts[2])
379
+ return None
380
+
381
+
382
+ def _shell_tokens(raw: str) -> list[str] | None:
383
+ normalized = _normalize_and_unwrap_shell_command(raw)
384
+ if not normalized:
385
+ return None
386
+ try:
387
+ lexer = shlex.shlex(normalized, posix=True, punctuation_chars="|&;")
388
+ lexer.whitespace_split = True
389
+ lexer.commenters = ""
390
+ return list(lexer)
391
+ except ValueError:
392
+ return None
393
+
394
+
395
+ def _semantic_verification_rejection_reason(
396
+ raw: str,
397
+ parsed_parts: tuple[str, ...],
398
+ ) -> str:
399
+ tokens = _shell_tokens(raw)
400
+ if tokens and _has_failure_masking_control_flow(tokens):
401
+ return "failure_masking_control_flow"
402
+
403
+ unwrapped = _normalize_and_unwrap_shell_command(raw)
404
+ try:
405
+ parts = tuple(shlex.split(unwrapped, posix=True))
406
+ except ValueError:
407
+ parts = parsed_parts
408
+ if _is_vacuous_success_command(parts):
409
+ return "vacuous_verifier"
410
+ if _is_non_assertive_observation_command(parts):
411
+ return "non_assertive_observation"
412
+ if _is_non_assertive_python_snippet(parts):
413
+ return "vacuous_verifier"
414
+ if _is_compile_only_python_command(parts):
415
+ return "non_assertive_observation"
416
+ return ""
417
+
418
+
419
+ def _has_failure_masking_control_flow(tokens: list[str]) -> bool:
420
+ for index, token in enumerate(tokens[:-1]):
421
+ if token not in {"||", ";", "|"}:
422
+ continue
423
+ right = _next_simple_command_tokens(tokens[index + 1 :])
424
+ if _is_vacuous_success_command(tuple(right)):
425
+ return True
426
+ if token != "|" and _is_non_assertive_observation_command(tuple(right)):
427
+ return True
428
+ return False
429
+
430
+
431
+ def _next_simple_command_tokens(tokens: list[str]) -> list[str]:
432
+ out: list[str] = []
433
+ for token in tokens:
434
+ if token in _DISALLOWED_VERIFICATION_SHELL_TOKENS:
435
+ break
436
+ out.append(token)
437
+ return out
438
+
439
+
440
+ def _command_head(parts: tuple[str, ...]) -> str:
441
+ if not parts:
442
+ return ""
443
+ head = Path(parts[0].replace("\\", "/")).name.casefold()
444
+ if head.endswith(".exe"):
445
+ head = head[:-4]
446
+ return head
447
+
448
+
449
+ def _is_vacuous_success_command(parts: tuple[str, ...]) -> bool:
450
+ if not parts:
451
+ return False
452
+ head = _command_head(parts)
453
+ if len(parts) == 1 and (head in _VACUOUS_SUCCESS_HEADS or parts[0].strip() == ":"):
454
+ return True
455
+ if len(parts) == 2 and head in {"exit", "return"} and parts[1] == "0":
456
+ return True
457
+ return _is_non_assertive_python_snippet(parts)
458
+
459
+
460
+ def _is_non_assertive_observation_command(parts: tuple[str, ...]) -> bool:
461
+ if not parts:
462
+ return False
463
+ head = _command_head(parts)
464
+ if head in {"grep"}:
465
+ return False
466
+ return head in _NON_ASSERTIVE_OBSERVATION_HEADS
467
+
468
+
469
+ def _is_non_assertive_python_snippet(parts: tuple[str, ...]) -> bool:
470
+ if not _is_python_interpreter_snippet(parts):
471
+ return False
472
+ snippet = " ".join(parts[2:]).strip().casefold()
473
+ compact = "".join(snippet.split())
474
+ if compact in {
475
+ "pass",
476
+ "print('ok')",
477
+ 'print("ok")',
478
+ "print(1)",
479
+ "sys.exit(0)",
480
+ "raisesystemexit(0)",
481
+ }:
482
+ return True
483
+ if compact.startswith("importsys;") and compact.endswith("sys.exit(0)"):
484
+ return True
485
+ return False
486
+
487
+
488
+ def _is_compile_only_python_command(parts: tuple[str, ...]) -> bool:
489
+ if len(parts) < 3:
490
+ return False
491
+ head = _command_head(parts)
492
+ return (
493
+ head in _PYTHON_EXECUTABLES
494
+ and parts[1] == "-m"
495
+ and parts[2]
496
+ in {
497
+ "compileall",
498
+ "py_compile",
499
+ }
500
+ )
501
+
502
+
503
+ def _parse_verification_command_shape(raw: str) -> tuple[str, ...] | None:
504
+ try:
505
+ parts = shlex.split(raw, posix=True)
506
+ except ValueError:
507
+ return None
508
+ if not parts:
509
+ return None
510
+ while parts and _looks_like_env_assignment(parts[0]):
511
+ parts = parts[1:]
512
+ if not parts:
513
+ return None
514
+ if parts[0] == "env":
515
+ parts = parts[1:]
516
+ while parts and _looks_like_env_assignment(parts[0]):
517
+ parts = parts[1:]
518
+ if (
519
+ len(parts) >= 3
520
+ and Path(parts[0]).name.casefold() in _PYTHON_EXECUTABLES
521
+ and parts[1] == "-m"
522
+ ):
523
+ parts = [parts[2], *parts[3:]]
524
+ if not parts:
525
+ return None
526
+ head = Path(parts[0]).name.casefold()
527
+ if head not in _SUPPORTED_VERIFY_HEADS:
528
+ return None
529
+ if head in {"test", "["}:
530
+ return tuple(parts) if len(parts) >= 3 else None
531
+ if head in {"cmp", "diff"}:
532
+ operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
533
+ return tuple(parts) if len(operands) >= 2 else None
534
+ if head == "grep":
535
+ has_quiet = any(part in {"-q", "--quiet"} for part in parts[1:])
536
+ operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
537
+ return tuple(parts) if has_quiet and len(operands) >= 2 else None
538
+ if head == "ruff" and (len(parts) < 2 or parts[1] != "check"):
539
+ return None
540
+ if head in {"go", "cargo", "npm", "pnpm", "yarn"} and len(parts) < 2:
541
+ return None
542
+ if head == "go" and parts[1] != "test":
543
+ return None
544
+ if head == "cargo" and parts[1] not in {"test", "check"}:
545
+ return None
546
+ if head in {"npm", "pnpm", "yarn"} and parts[1] != "test":
547
+ return None
548
+ return tuple(parts)
549
+
550
+
551
+ def _looks_like_env_assignment(part: str) -> bool:
552
+ if "=" not in part or not part:
553
+ return False
554
+ name = part.split("=", 1)[0]
555
+ return name.replace("_", "a").isalnum() and not name[0].isdigit()
556
+
557
+
558
+ def _is_python_interpreter_snippet(parts: tuple[str, ...]) -> bool:
559
+ return (
560
+ len(parts) >= 3
561
+ and Path(parts[0]).name.casefold() in _PYTHON_EXECUTABLES
562
+ and parts[1] == "-c"
563
+ )
564
+
565
+
566
+ def _stable_command_id(
567
+ *,
568
+ source: str,
569
+ contract_type: str,
570
+ command: str,
571
+ index: int,
572
+ ) -> str:
573
+ digest = hashlib.sha256(f"{source}\0{contract_type}\0{index}\0{command}".encode()).hexdigest()
574
+ return f"vc_{digest[:16]}"