aria-code 0.52.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 (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. aria_code-0.52.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,598 @@
1
+ """Run verifiable tasks and score them by exit code.
2
+
3
+ The shape of one task
4
+ ---------------------
5
+ A fixture directory, a prompt, and a command::
6
+
7
+ - id: fix-failing-test
8
+ prompt: "tests/test_math.py 有一个测试失败了,修好它"
9
+ fixture: fix_failing_test
10
+ verify: "{python} -m pytest -q"
11
+ requires: [pytest]
12
+
13
+ The fixture is copied to a scratch directory, the agent is turned loose in the
14
+ copy, and ``verify`` decides the outcome. Exit 0 is a pass. Nothing else
15
+ counts — not a confident summary, not a diff that looks right.
16
+
17
+ The property that makes a suite trustworthy
18
+ -------------------------------------------
19
+ **Every task must start red.** Before the agent runs, the harness runs
20
+ ``verify`` on the untouched fixture. If it already passes, the task is
21
+ reported ``INVALID`` and scored as neither pass nor fail.
22
+
23
+ This check is worth more than any individual task. A suite that silently
24
+ accumulates already-green tasks reports a rising pass rate while measuring
25
+ less and less, and the failure is invisible precisely because the number looks
26
+ good — you cannot tell a task the agent solved from a task that was never
27
+ broken. Running the check every time costs one command per task and makes the
28
+ number mean something.
29
+
30
+ "Red" has to mean the check ran
31
+ ------------------------------
32
+ The pre-flight alone is not enough, and the first run of this harness proved
33
+ it: every task reported red, and every task was red because the ``python3`` on
34
+ PATH had no pytest. The suite looked healthy while measuring nothing at all —
35
+ the same silent-inflation failure the pre-flight exists to prevent, arriving
36
+ through the back door.
37
+
38
+ Two things close it. ``{python}`` in a command resolves to the interpreter
39
+ running the harness, so a suite verifies against the environment it was
40
+ launched with rather than whatever ``python3`` happens to mean on this
41
+ machine. And a task may declare ``requires: [pytest]``; a missing import is
42
+ reported as ``ERROR`` — excluded from the score — instead of being counted as
43
+ a red test the agent is expected to fix.
44
+
45
+ Isolation
46
+ ---------
47
+ Tasks run in a copy under a scratch root, never in the fixture and never in
48
+ the repository. An agent that deletes the workspace, writes outside it, or
49
+ leaves it in a broken state affects nothing but its own run's directory, which
50
+ is what makes it safe to keep destructive tasks in the suite — those are the
51
+ ones worth having.
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ import importlib.util
57
+ import json
58
+ import shutil
59
+ import subprocess
60
+ import sys
61
+ import tempfile
62
+ import time
63
+ from dataclasses import dataclass, field
64
+ from pathlib import Path
65
+ from typing import Any, Callable, Iterable, List, Optional, Sequence
66
+
67
+ __all__ = [
68
+ "SuiteResult",
69
+ "TaskResult",
70
+ "TaskSpec",
71
+ "Solver",
72
+ "load_suite",
73
+ "run_suite",
74
+ "run_task",
75
+ ]
76
+
77
+ # Outcomes. Kept as four values rather than a boolean because "the agent
78
+ # failed" and "the task was broken" call for opposite responses, and collapsing
79
+ # them is how a rotten suite goes unnoticed.
80
+ PASS = "pass"
81
+ FAIL = "fail"
82
+ INVALID = "invalid" # the fixture was already green: the task measures nothing
83
+ ERROR = "error" # the harness or the solver blew up; not the agent's score
84
+
85
+ _MAX_LOG_CHARS = 4000
86
+
87
+ # Residue that must not travel from a fixture into a task workspace.
88
+ # Directory names that hold build/run residue rather than source.
89
+ _RUN_RESIDUE = frozenset({
90
+ ".git", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache",
91
+ ".tox", "node_modules", ".DS_Store",
92
+ })
93
+
94
+ _IGNORED_FIXTURE_ENTRIES = shutil.ignore_patterns(
95
+ "__pycache__", "*.pyc", "*.pyo", ".pytest_cache", ".mypy_cache",
96
+ ".ruff_cache", ".git", "node_modules", ".DS_Store",
97
+ )
98
+
99
+
100
+ def _trim(text: str, limit: int = _MAX_LOG_CHARS) -> str:
101
+ body = (text or "").strip()
102
+ if len(body) <= limit:
103
+ return body
104
+ half = limit // 2
105
+ return f"{body[:half]}\n… [truncated] …\n{body[-half:]}"
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class TaskSpec:
110
+ """One verifiable task."""
111
+
112
+ id: str
113
+ prompt: str
114
+ verify: str
115
+ fixture: str = ""
116
+ timeout: int = 300
117
+ solve_timeout: int = 900
118
+ tags: tuple[str, ...] = ()
119
+ setup: tuple[str, ...] = ()
120
+ # Importable modules the check needs. Missing ones make the task ERROR
121
+ # rather than FAIL: an absent pytest is not a bug for the agent to fix.
122
+ requires: tuple[str, ...] = ()
123
+ # Files the agent must not modify. Defaults to the tests, because the
124
+ # check IS the tests: an agent that edits them can turn any task green
125
+ # without doing the work, and the score would be indistinguishable from a
126
+ # real solve. Editing one is scored FAIL no matter what the check says.
127
+ protect: tuple[str, ...] = ("test_*.py", "*_test.py", "tests/**")
128
+ # A task may legitimately start green when it is a regression guard: the
129
+ # point is that the agent must not *break* it. Opting out is explicit so
130
+ # that it is a decision someone made, not a fixture that quietly rotted.
131
+ allow_green_start: bool = False
132
+
133
+ @classmethod
134
+ def from_dict(cls, data: dict) -> "TaskSpec":
135
+ missing = [key for key in ("id", "prompt", "verify") if not data.get(key)]
136
+ if missing:
137
+ raise ValueError(f"task is missing required field(s): {', '.join(missing)}")
138
+ return cls(
139
+ id=str(data["id"]),
140
+ prompt=str(data["prompt"]),
141
+ verify=str(data["verify"]),
142
+ fixture=str(data.get("fixture") or ""),
143
+ timeout=int(data.get("timeout") or 300),
144
+ solve_timeout=int(data.get("solve_timeout") or 900),
145
+ tags=tuple(str(t) for t in (data.get("tags") or ())),
146
+ setup=tuple(str(c) for c in (data.get("setup") or ())),
147
+ requires=tuple(str(m) for m in (data.get("requires") or ())),
148
+ protect=(
149
+ tuple(str(g) for g in data["protect"])
150
+ if "protect" in data else cls.protect
151
+ ),
152
+ allow_green_start=bool(data.get("allow_green_start", False)),
153
+ )
154
+
155
+
156
+ @dataclass(frozen=True)
157
+ class TaskResult:
158
+ task_id: str
159
+ outcome: str
160
+ seconds: float = 0.0
161
+ exit_code: Optional[int] = None
162
+ detail: str = ""
163
+ log: str = ""
164
+ tags: tuple[str, ...] = ()
165
+ # What the agent actually touched. A red check with an empty list is a
166
+ # different failure from a red check after real edits: the first says the
167
+ # agent never engaged, the second says it engaged and got it wrong. Without
168
+ # this the two are indistinguishable in a report, and they call for
169
+ # opposite investigations.
170
+ changed: tuple[str, ...] = ()
171
+
172
+ @property
173
+ def counted(self) -> bool:
174
+ """Whether this result belongs in the pass rate at all."""
175
+ return self.outcome in (PASS, FAIL)
176
+
177
+ def to_dict(self) -> dict:
178
+ return {
179
+ "task_id": self.task_id,
180
+ "outcome": self.outcome,
181
+ "seconds": round(self.seconds, 2),
182
+ "exit_code": self.exit_code,
183
+ "detail": self.detail,
184
+ "tags": list(self.tags),
185
+ "changed": list(self.changed),
186
+ }
187
+
188
+
189
+ @dataclass
190
+ class SuiteResult:
191
+ name: str
192
+ results: List[TaskResult] = field(default_factory=list)
193
+
194
+ @property
195
+ def passed(self) -> int:
196
+ return sum(1 for r in self.results if r.outcome == PASS)
197
+
198
+ @property
199
+ def failed(self) -> int:
200
+ return sum(1 for r in self.results if r.outcome == FAIL)
201
+
202
+ @property
203
+ def invalid(self) -> int:
204
+ return sum(1 for r in self.results if r.outcome == INVALID)
205
+
206
+ @property
207
+ def errored(self) -> int:
208
+ return sum(1 for r in self.results if r.outcome == ERROR)
209
+
210
+ @property
211
+ def scored(self) -> int:
212
+ return sum(1 for r in self.results if r.counted)
213
+
214
+ @property
215
+ def pass_rate(self) -> float:
216
+ """Passes over *scored* tasks.
217
+
218
+ Invalid and errored tasks are excluded rather than counted as
219
+ failures. Counting them down would let one broken fixture look like an
220
+ agent regression; counting them up would let it hide one.
221
+ """
222
+ return (self.passed / self.scored) if self.scored else 0.0
223
+
224
+ def by_tag(self) -> dict:
225
+ """Pass rate per tag — the per-industry scoreboard."""
226
+ buckets: dict[str, list[TaskResult]] = {}
227
+ for result in self.results:
228
+ for tag in result.tags:
229
+ buckets.setdefault(tag, []).append(result)
230
+ out = {}
231
+ for tag, results in sorted(buckets.items()):
232
+ scored = [r for r in results if r.counted]
233
+ out[tag] = {
234
+ "passed": sum(1 for r in scored if r.outcome == PASS),
235
+ "scored": len(scored),
236
+ "pass_rate": round(
237
+ sum(1 for r in scored if r.outcome == PASS) / len(scored), 4
238
+ ) if scored else 0.0,
239
+ }
240
+ return out
241
+
242
+ def to_dict(self) -> dict:
243
+ return {
244
+ "suite": self.name,
245
+ "pass_rate": round(self.pass_rate, 4),
246
+ "passed": self.passed,
247
+ "failed": self.failed,
248
+ "invalid": self.invalid,
249
+ "errored": self.errored,
250
+ "scored": self.scored,
251
+ "by_tag": self.by_tag(),
252
+ "per_task": self.per_task(),
253
+ "results": [r.to_dict() for r in self.results],
254
+ }
255
+
256
+ def merge(self, other: "SuiteResult") -> None:
257
+ """Fold another run of the same suite into this one."""
258
+ self.results.extend(other.results)
259
+
260
+ def per_task(self) -> dict:
261
+ """Passes over attempts for each task id, across every repeat.
262
+
263
+ A single sample is not a score. Observed on the first real run: one
264
+ task passed alone and failed in the same suite minutes later, from the
265
+ same model. Reporting 2/5 without saying how many attempts produced it
266
+ invites reading noise as a regression.
267
+ """
268
+ buckets: dict[str, list[TaskResult]] = {}
269
+ for result in self.results:
270
+ buckets.setdefault(result.task_id, []).append(result)
271
+ out = {}
272
+ for task_id, results in buckets.items():
273
+ scored = [r for r in results if r.counted]
274
+ out[task_id] = {
275
+ "passed": sum(1 for r in scored if r.outcome == PASS),
276
+ "attempts": len(scored),
277
+ "outcomes": [r.outcome for r in results],
278
+ }
279
+ return out
280
+
281
+ def summary_line(self) -> str:
282
+ parts = [f"{self.name}: {self.passed}/{self.scored} ({self.pass_rate:.0%})"]
283
+ if self.invalid:
284
+ parts.append(f"{self.invalid} invalid")
285
+ if self.errored:
286
+ parts.append(f"{self.errored} error")
287
+ return " · ".join(parts)
288
+
289
+
290
+ # ``(prompt, workspace) -> anything``. Injected so the harness can be tested,
291
+ # and so the same suite can score a different agent — the CLI, a subagent
292
+ # backend, or a competitor — without the suite knowing which.
293
+ Solver = Callable[[str, Path], Any]
294
+
295
+
296
+ def _resolve(command: str) -> str:
297
+ """Substitute ``{python}`` with the interpreter running the harness.
298
+
299
+ Without this a suite silently verifies against whatever ``python3`` means
300
+ on PATH, which is how five tasks once reported red because that
301
+ interpreter had no pytest installed.
302
+ """
303
+ return (command or "").replace("{python}", sys.executable)
304
+
305
+
306
+ def _missing_modules(names: Iterable[str]) -> list[str]:
307
+ missing = []
308
+ for name in names:
309
+ try:
310
+ if importlib.util.find_spec(name) is None:
311
+ missing.append(name)
312
+ except (ImportError, ValueError):
313
+ missing.append(name)
314
+ return missing
315
+
316
+
317
+ def _violates_protection(changed: Iterable[str], patterns: Iterable[str]) -> tuple[str, ...]:
318
+ """Protected files the agent modified."""
319
+ import fnmatch
320
+
321
+ globs = tuple(patterns)
322
+ hits: list[str] = []
323
+ for rel in changed:
324
+ name = rel.replace("\\", "/")
325
+ base = name.rsplit("/", 1)[-1]
326
+ if any(fnmatch.fnmatch(name, g) or fnmatch.fnmatch(base, g) for g in globs):
327
+ hits.append(rel)
328
+ return tuple(hits)
329
+
330
+
331
+ def _snapshot(root: Path) -> dict[str, str]:
332
+ """Content digests for every file in *root*, keyed by relative path."""
333
+ import hashlib
334
+
335
+ out: dict[str, str] = {}
336
+ for path in sorted(root.rglob("*")):
337
+ if not path.is_file():
338
+ continue
339
+ rel = str(path.relative_to(root))
340
+ # Residue the *check* writes, not the agent's work. Reporting it makes
341
+ # every task look like it touched a dozen files and buries the one edit
342
+ # that matters.
343
+ if any(part in _RUN_RESIDUE for part in rel.replace("\\", "/").split("/")):
344
+ continue
345
+ try:
346
+ out[rel] = hashlib.sha256(path.read_bytes()).hexdigest()
347
+ except OSError:
348
+ continue
349
+ return out
350
+
351
+
352
+ def _changed_paths(before: dict[str, str], after: dict[str, str]) -> tuple[str, ...]:
353
+ """Files the agent created, edited, or deleted."""
354
+ touched = [
355
+ rel for rel in sorted(set(before) | set(after))
356
+ if before.get(rel) != after.get(rel)
357
+ ]
358
+ return tuple(touched)
359
+
360
+
361
+ def _solver_failure(outcome: Any) -> Optional[tuple[int, str]]:
362
+ """``(exit_code, log)`` when the solver reports it did not complete.
363
+
364
+ Duck-typed on ``returncode`` so a plain callable that returns nothing —
365
+ every in-process solver, and the check-only one — is unaffected, while a
366
+ ``subprocess.CompletedProcess`` from the real agent is inspected.
367
+ """
368
+ if outcome is None:
369
+ return None
370
+ code = getattr(outcome, "returncode", None)
371
+ if not isinstance(code, int) or code == 0:
372
+ return None
373
+ log = "\n".join(
374
+ part for part in (
375
+ str(getattr(outcome, "stderr", "") or ""),
376
+ str(getattr(outcome, "stdout", "") or ""),
377
+ ) if part.strip()
378
+ )
379
+ return code, log
380
+
381
+
382
+ def _run(command: str, cwd: Path, timeout: int) -> tuple[int, str]:
383
+ command = _resolve(command)
384
+ try:
385
+ proc = subprocess.run(
386
+ command,
387
+ shell=True,
388
+ cwd=str(cwd),
389
+ capture_output=True,
390
+ text=True,
391
+ timeout=timeout,
392
+ check=False,
393
+ )
394
+ except subprocess.TimeoutExpired:
395
+ # A check that never finishes is a failed check. Reporting it as
396
+ # anything else would let an infinite loop score as a pass.
397
+ return 124, f"verification timed out after {timeout}s"
398
+ except Exception as exc:
399
+ return 125, f"could not run command: {exc}"
400
+ output = "\n".join(part for part in (proc.stdout, proc.stderr) if part.strip())
401
+ return proc.returncode, output
402
+
403
+
404
+ def load_suite(path: str | Path) -> tuple[str, list[TaskSpec]]:
405
+ """Read a YAML suite file into task specs."""
406
+ import yaml
407
+
408
+ file_path = Path(path).expanduser().resolve()
409
+ data = yaml.safe_load(file_path.read_text(encoding="utf-8")) or {}
410
+ name = str(data.get("suite") or file_path.stem)
411
+ tasks = [TaskSpec.from_dict(entry) for entry in (data.get("tasks") or [])]
412
+
413
+ duplicates = {t.id for t in tasks if [x.id for x in tasks].count(t.id) > 1}
414
+ if duplicates:
415
+ raise ValueError(f"duplicate task id(s) in {file_path.name}: {', '.join(sorted(duplicates))}")
416
+ return name, tasks
417
+
418
+
419
+ def _prepare_workspace(task: TaskSpec, fixtures_root: Path, scratch: Path) -> Path:
420
+ workspace = scratch / task.id
421
+ if workspace.exists():
422
+ shutil.rmtree(workspace)
423
+ if task.fixture:
424
+ source = fixtures_root / task.fixture
425
+ if not source.is_dir():
426
+ raise FileNotFoundError(f"fixture not found: {source}")
427
+ # Never copy build residue. A __pycache__ carried over from a run of
428
+ # the fixture in place holds bytecode for the *broken* source, and
429
+ # Python will happily import it — so a task could score red after a
430
+ # correct fix, or green before one.
431
+ shutil.copytree(source, workspace, ignore=_IGNORED_FIXTURE_ENTRIES)
432
+ else:
433
+ workspace.mkdir(parents=True)
434
+ return workspace
435
+
436
+
437
+ def run_task(
438
+ task: TaskSpec,
439
+ *,
440
+ solver: Solver,
441
+ fixtures_root: str | Path,
442
+ scratch_root: str | Path | None = None,
443
+ keep_workspace: bool = False,
444
+ ) -> TaskResult:
445
+ """Prepare, pre-check, solve, and score one task."""
446
+ started = time.time()
447
+ fixtures = Path(fixtures_root).expanduser().resolve()
448
+ scratch = Path(scratch_root).expanduser().resolve() if scratch_root else Path(
449
+ tempfile.mkdtemp(prefix="aria-eval-")
450
+ )
451
+ scratch.mkdir(parents=True, exist_ok=True)
452
+
453
+ def _result(outcome: str, **kwargs) -> TaskResult:
454
+ return TaskResult(
455
+ task_id=task.id, outcome=outcome,
456
+ seconds=time.time() - started, tags=task.tags, **kwargs,
457
+ )
458
+
459
+ # Everything below runs inside this try so the scratch directory is removed
460
+ # on EVERY path, not just the happy one.
461
+ #
462
+ # It used to open after the setup checks, and the two error returns above
463
+ # it — a missing fixture, a missing dependency — leaked one temp directory
464
+ # each. That is once per error path per run, and the test suite exercises
465
+ # both deliberately, so a full test run leaked two directories every time.
466
+ # 57 of them had accumulated in $TMPDIR before anyone noticed, which is the
467
+ # shape of this class of bug: too small to be felt, never self-correcting.
468
+ try:
469
+ try:
470
+ workspace = _prepare_workspace(task, fixtures, scratch)
471
+ except Exception as exc:
472
+ return _result(ERROR, detail=f"workspace setup failed: {exc}")
473
+
474
+ missing = _missing_modules(task.requires)
475
+ if missing:
476
+ return _result(
477
+ ERROR,
478
+ detail=f"environment is missing {', '.join(missing)} — the check cannot run",
479
+ )
480
+
481
+ for command in task.setup:
482
+ code, output = _run(command, workspace, task.timeout)
483
+ if code != 0:
484
+ return _result(ERROR, exit_code=code,
485
+ detail=f"setup command failed: {command}",
486
+ log=_trim(output))
487
+
488
+ # ── the pre-flight ────────────────────────────────────────────────
489
+ # Confirm the task is actually broken before asking anyone to fix it.
490
+ before_code, before_log = _run(task.verify, workspace, task.timeout)
491
+ if before_code == 0 and not task.allow_green_start:
492
+ return _result(
493
+ INVALID, exit_code=0,
494
+ detail="fixture already passes its own check — this task measures nothing",
495
+ log=_trim(before_log),
496
+ )
497
+
498
+ # ── the agent's turn ──────────────────────────────────────────────
499
+ before = _snapshot(workspace)
500
+ try:
501
+ outcome = solver(task.prompt, workspace)
502
+ except Exception as exc:
503
+ # A solver crash is not the agent failing the task; scoring it as
504
+ # a fail would blame the model for a harness or provider outage.
505
+ return _result(ERROR, detail=f"solver raised: {exc}")
506
+
507
+ # ── the score ─────────────────────────────────────────────────────
508
+ # The check runs no matter what the solver reported, because the check
509
+ # is the source of truth and the solver's exit code is not. A run that
510
+ # edited the file correctly and then exited 1 on an empty final message
511
+ # is a PASS: the work is on disk and the tests are green.
512
+ changed = _changed_paths(before, _snapshot(workspace))
513
+
514
+ # Checked before the verdict, and it overrides a green check. Observed
515
+ # in a real run: an agent edited test_settlement.py rather than the
516
+ # module under test. The suite would have called that a PASS and the
517
+ # number would have been a lie.
518
+ tampered = _violates_protection(changed, task.protect)
519
+ if tampered:
520
+ return _result(
521
+ FAIL,
522
+ detail=f"modified protected file(s): {', '.join(tampered)}",
523
+ changed=changed,
524
+ )
525
+
526
+ after_code, after_log = _run(task.verify, workspace, task.timeout)
527
+ if after_code == 0:
528
+ return _result(PASS, exit_code=0, changed=changed)
529
+
530
+ # Red — now the solver's status decides who is to blame. A subprocess
531
+ # that died in seconds on a provider outage or a crash never gave the
532
+ # agent its turn, and scoring that as FAIL makes an infrastructure
533
+ # problem look like an incapable model. That is exactly the confusion
534
+ # this harness separates PASS/FAIL from ERROR to avoid.
535
+ failure = _solver_failure(outcome)
536
+ if failure is not None:
537
+ code, log = failure
538
+ return _result(
539
+ ERROR, exit_code=code,
540
+ detail=f"the agent did not complete (exit {code}); check still red",
541
+ log=_trim(log), changed=changed,
542
+ )
543
+
544
+ detail = f"`{task.verify}` exited {after_code}"
545
+ if not changed:
546
+ detail += " — the agent changed nothing"
547
+ return _result(
548
+ FAIL, exit_code=after_code, detail=detail,
549
+ log=_trim(after_log), changed=changed,
550
+ )
551
+ finally:
552
+ if not keep_workspace and scratch_root is None:
553
+ shutil.rmtree(scratch, ignore_errors=True)
554
+
555
+
556
+ def run_suite(
557
+ tasks: Sequence[TaskSpec],
558
+ *,
559
+ solver: Solver,
560
+ fixtures_root: str | Path,
561
+ name: str = "suite",
562
+ scratch_root: str | Path | None = None,
563
+ only: Iterable[str] = (),
564
+ tags: Iterable[str] = (),
565
+ on_result: Optional[Callable[[TaskResult], None]] = None,
566
+ keep_workspace: bool = False,
567
+ ) -> SuiteResult:
568
+ """Run every task and collect the scoreboard."""
569
+ wanted_ids = {str(i) for i in only if i}
570
+ wanted_tags = {str(t) for t in tags if t}
571
+
572
+ selected = [
573
+ task for task in tasks
574
+ if (not wanted_ids or task.id in wanted_ids)
575
+ and (not wanted_tags or wanted_tags & set(task.tags))
576
+ ]
577
+
578
+ suite = SuiteResult(name=name)
579
+ for task in selected:
580
+ result = run_task(
581
+ task,
582
+ solver=solver,
583
+ fixtures_root=fixtures_root,
584
+ scratch_root=scratch_root,
585
+ keep_workspace=keep_workspace,
586
+ )
587
+ suite.results.append(result)
588
+ if on_result is not None:
589
+ on_result(result)
590
+ return suite
591
+
592
+
593
+ def write_report(suite: SuiteResult, path: str | Path) -> Path:
594
+ """Persist the scoreboard as JSON so runs can be compared over time."""
595
+ out = Path(path).expanduser().resolve()
596
+ out.parent.mkdir(parents=True, exist_ok=True)
597
+ out.write_text(json.dumps(suite.to_dict(), ensure_ascii=False, indent=2), encoding="utf-8")
598
+ return out