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,200 @@
1
+ """Deterministic, read-only code review agent.
2
+
3
+ The agent deliberately reviews caller-supplied text only. It never resolves a
4
+ path, invokes a shell, or changes a workspace, so it can be used safely from
5
+ both the CLI and a Google ADK function tool.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import PurePath
14
+ from typing import Any, Dict, Iterable, List
15
+
16
+ from .base import AgentResult, BaseAgent
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class CodeReviewFinding:
21
+ """A stable, evidence-bearing static-review finding."""
22
+
23
+ severity: str
24
+ rule: str
25
+ message: str
26
+ line: int | None
27
+ evidence: str
28
+
29
+ def to_dict(self) -> Dict[str, Any]:
30
+ return {
31
+ "severity": self.severity,
32
+ "rule": self.rule,
33
+ "message": self.message,
34
+ "line": self.line,
35
+ "evidence": self.evidence,
36
+ }
37
+
38
+
39
+ class CodeReviewAgent(BaseAgent):
40
+ """Find high-signal correctness and security hazards without an LLM."""
41
+
42
+ name = "code_review"
43
+ description = "Read-only deterministic code review for user-supplied snippets and diffs"
44
+ MAX_SOURCE_CHARS = 48_000
45
+ _SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
46
+ _SECRET_ASSIGNMENT = re.compile(
47
+ r"\b(?:api[_-]?key|secret|token|password)\b\s*=\s*['\"][^'\"]{8,}['\"]",
48
+ re.IGNORECASE,
49
+ )
50
+ _TOKEN_LITERAL = re.compile(r"\b(?:(?:ghp|github_pat)_[A-Za-z0-9_-]{12,}|sk-[A-Za-z0-9_-]{12,})\b", re.IGNORECASE)
51
+ _SHELL_TRUE = re.compile(r"\b(?:subprocess\.[A-Za-z_]+|os\.system)\b.*\bshell\s*=\s*True", re.IGNORECASE)
52
+ _EVAL_EXEC = re.compile(r"\b(?:eval|exec)\s*\(")
53
+ _PICKLE = re.compile(r"\bpickle\.loads?\s*\(")
54
+ _UNSAFE_YAML = re.compile(r"\byaml\.load\s*\(")
55
+ _TLS_DISABLED = re.compile(r"\brequests\.[A-Za-z_]+\s*\(.*\bverify\s*=\s*False", re.IGNORECASE)
56
+
57
+ @staticmethod
58
+ def _safe_filename(filename: str) -> str:
59
+ name = PurePath(str(filename or "snippet.py")).name
60
+ return name[:160] or "snippet.py"
61
+
62
+ @classmethod
63
+ def _append(
64
+ cls,
65
+ findings: List[CodeReviewFinding],
66
+ severity: str,
67
+ rule: str,
68
+ message: str,
69
+ line: int | None,
70
+ evidence: str,
71
+ ) -> None:
72
+ if rule == "hardcoded-secret":
73
+ evidence = "<credential value redacted>"
74
+ findings.append(
75
+ CodeReviewFinding(
76
+ severity=severity,
77
+ rule=rule,
78
+ message=message,
79
+ line=line,
80
+ evidence=evidence.strip()[:240],
81
+ )
82
+ )
83
+
84
+ @classmethod
85
+ def _scan_lines(cls, lines: Iterable[str], findings: List[CodeReviewFinding]) -> None:
86
+ for number, line in enumerate(lines, start=1):
87
+ if cls._SECRET_ASSIGNMENT.search(line) or cls._TOKEN_LITERAL.search(line):
88
+ cls._append(
89
+ findings, "high", "hardcoded-secret",
90
+ "疑似硬编码凭据;改用环境变量或受管密钥,并立即轮换已暴露的值。",
91
+ number, line,
92
+ )
93
+ if cls._SHELL_TRUE.search(line):
94
+ cls._append(
95
+ findings, "high", "shell-true",
96
+ "shell=True 会把不可信输入交给 shell 解释;改用参数列表和严格的输入校验。",
97
+ number, line,
98
+ )
99
+ if cls._EVAL_EXEC.search(line):
100
+ cls._append(
101
+ findings, "high", "dynamic-execution",
102
+ "eval/exec 可执行不可信文本;使用受限解析器或显式分发表。",
103
+ number, line,
104
+ )
105
+ if cls._PICKLE.search(line):
106
+ cls._append(
107
+ findings, "high", "unsafe-deserialization",
108
+ "pickle 反序列化不可信数据可执行任意代码;改用 JSON 或签名的可信工件。",
109
+ number, line,
110
+ )
111
+ if cls._UNSAFE_YAML.search(line) and "Loader=" not in line:
112
+ cls._append(
113
+ findings, "high", "unsafe-yaml-load",
114
+ "yaml.load 未指定安全 Loader;使用 yaml.safe_load。",
115
+ number, line,
116
+ )
117
+ if cls._TLS_DISABLED.search(line):
118
+ cls._append(
119
+ findings, "high", "tls-verification-disabled",
120
+ "TLS 证书校验被关闭;除受控测试外不得使用 verify=False。",
121
+ number, line,
122
+ )
123
+
124
+ @classmethod
125
+ def review_source(
126
+ cls,
127
+ source: str,
128
+ *,
129
+ filename: str = "snippet.py",
130
+ language: str = "python",
131
+ is_diff: bool = False,
132
+ ) -> List[CodeReviewFinding]:
133
+ """Review source text or an already-provided diff without touching disk."""
134
+ if not isinstance(source, str) or not source.strip():
135
+ return [CodeReviewFinding("medium", "empty-input", "没有可审查的代码内容。", None, "")]
136
+ if len(source) > cls.MAX_SOURCE_CHARS:
137
+ return [CodeReviewFinding(
138
+ "medium", "input-truncated",
139
+ f"审查输入超过 {cls.MAX_SOURCE_CHARS} 字符;请按文件或提交拆分后复查。",
140
+ None, "",
141
+ )]
142
+
143
+ findings: List[CodeReviewFinding] = []
144
+ lines = source.splitlines()
145
+ scan_lines = [line[1:] for line in lines if line.startswith("+") and not line.startswith("+++")] if is_diff else lines
146
+ cls._scan_lines(scan_lines, findings)
147
+
148
+ inferred_python = language.lower() in {"py", "python"} or cls._safe_filename(filename).endswith(".py")
149
+ if inferred_python and not is_diff:
150
+ try:
151
+ tree = ast.parse(source, filename=cls._safe_filename(filename))
152
+ except SyntaxError as exc:
153
+ cls._append(
154
+ findings, "critical", "python-syntax-error",
155
+ "Python 代码无法解析,运行前会失败。",
156
+ exc.lineno, (exc.text or "").strip(),
157
+ )
158
+ else:
159
+ for node in ast.walk(tree):
160
+ if isinstance(node, ast.ExceptHandler) and node.type is None:
161
+ cls._append(
162
+ findings, "medium", "bare-except",
163
+ "裸 except 会吞掉系统退出和中断;至少捕获 Exception,并记录或处理错误。",
164
+ node.lineno, "except:",
165
+ )
166
+
167
+ unique = {(item.rule, item.line, item.evidence): item for item in findings}
168
+ return sorted(unique.values(), key=lambda item: (cls._SEVERITY_ORDER[item.severity], item.line or 0, item.rule))
169
+
170
+ @classmethod
171
+ def format_findings(cls, findings: List[CodeReviewFinding]) -> str:
172
+ if not findings:
173
+ return "确定性检查未发现已覆盖规则中的问题;仍需执行测试并进行语义审查。"
174
+ rows = []
175
+ for finding in findings:
176
+ location = f"第 {finding.line} 行" if finding.line else "输入级"
177
+ rows.append(f"[{finding.severity.upper()}] {location} {finding.rule}: {finding.message}")
178
+ return "\n".join(rows)
179
+
180
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
181
+ source = str(data.get("source") or data.get("content") or data.get("diff") or "")
182
+ filename = self._safe_filename(str(data.get("filename") or symbol or "snippet.py"))
183
+ findings = self.review_source(
184
+ source,
185
+ filename=filename,
186
+ language=str(data.get("language") or "python"),
187
+ is_diff=bool(data.get("is_diff")),
188
+ )
189
+ return AgentResult(
190
+ agent=self.name,
191
+ symbol=filename,
192
+ analysis=self.format_findings(findings),
193
+ confidence=0.99 if source else 0.0,
194
+ signal="HOLD",
195
+ key_points=[f"{item.severity}: {item.rule}" for item in findings[:8]],
196
+ data_used={"filename": filename, "finding_count": len(findings)},
197
+ degraded=False,
198
+ provenance=["deterministic_static_review"],
199
+ limitations=["只审查调用方提供的文本;不会读取文件、执行命令或运行测试。"],
200
+ )
@@ -0,0 +1,37 @@
1
+ """Deep analysis pipeline — Claude-Code-style layered research on top of AgentTeam.
2
+
3
+ Adds the four "deep" layers the flat team pipeline was missing:
4
+
5
+ P0 deepen — tool-augmented evidence gathering for material/uncertain findings
6
+ P1 hierarchical + critic — theme-grouped sub-synthesis, then a self-check pass
7
+ P2 quant fusion — fuse ML/backtest/risk signals as ground truth + calibrate confidence
8
+ P3 tiered output + provenance — brief / standard / deep reports with data lineage
9
+
10
+ Everything degrades gracefully: any layer whose dependency (LLM, quant tools, data
11
+ cleaner) is unavailable is skipped, never raised. The deterministic parts (theme
12
+ grouping, calibration math, critic rules, tier rendering) run with no LLM/network,
13
+ which keeps the pipeline testable.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from .models import (
19
+ Critique,
20
+ CritiqueIssue,
21
+ DeepAnalysisResult,
22
+ Provenance,
23
+ QuantEvidence,
24
+ ThemeGroup,
25
+ )
26
+ from .pipeline import DeepAnalysisPipeline, run_deep_analysis
27
+
28
+ __all__ = [
29
+ "Critique",
30
+ "CritiqueIssue",
31
+ "DeepAnalysisResult",
32
+ "Provenance",
33
+ "QuantEvidence",
34
+ "ThemeGroup",
35
+ "DeepAnalysisPipeline",
36
+ "run_deep_analysis",
37
+ ]
@@ -0,0 +1,144 @@
1
+ """P2 closed loop — log predictions, later score them against realised price.
2
+
3
+ Calibration only improves if confidence is checked against what actually happened.
4
+ This module:
5
+
6
+ 1. logs every deep verdict (symbol, signal, confidence, reference price, time),
7
+ 2. once a prediction's horizon has elapsed, fetches the realised return and
8
+ marks it correct/incorrect,
9
+ 3. feeds the outcome into CalibrationStore so the reliability factor — and thus
10
+ future calibrated confidence — drifts toward the true hit-rate.
11
+
12
+ Everything is injectable (price function, clock) so it tests without network.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import time
20
+ import uuid
21
+ from pathlib import Path
22
+ from typing import Callable, Dict, List, Optional
23
+
24
+ from .quant_fusion import CalibrationStore
25
+
26
+ _BULL = ("STRONG_BUY", "BUY")
27
+ _BEAR = ("STRONG_SELL", "SELL")
28
+
29
+
30
+ def correctness(signal: str, ret: float, threshold: float = 0.02) -> bool:
31
+ """Was the call right given the realised return?"""
32
+ if signal in _BULL:
33
+ return ret > threshold
34
+ if signal in _BEAR:
35
+ return ret < -threshold
36
+ return abs(ret) <= threshold # HOLD: right when it stayed flat
37
+
38
+
39
+ class PredictionLog:
40
+ """Append-only log of deep verdicts, JSON-list backed (low volume)."""
41
+
42
+ def __init__(self, path: Optional[Path] = None):
43
+ self.path = Path(path or os.path.expanduser("~/.arthera/deep_predictions.json"))
44
+ self._items: List[Dict] = []
45
+ try:
46
+ if self.path.exists():
47
+ self._items = json.loads(self.path.read_text())
48
+ except Exception:
49
+ self._items = []
50
+
51
+ def _save(self) -> None:
52
+ try:
53
+ self.path.parent.mkdir(parents=True, exist_ok=True)
54
+ self.path.write_text(json.dumps(self._items, ensure_ascii=False, indent=2))
55
+ except Exception:
56
+ pass
57
+
58
+ def log(self, symbol: str, signal: str, confidence: float,
59
+ ref_price: float, ts: Optional[float] = None) -> str:
60
+ pid = uuid.uuid4().hex[:12]
61
+ self._items.append({
62
+ "id": pid, "symbol": symbol, "signal": signal,
63
+ "confidence": float(confidence), "ref_price": float(ref_price),
64
+ "ts": ts if ts is not None else time.time(), "evaluated": False,
65
+ })
66
+ self._save()
67
+ return pid
68
+
69
+ def pending(self, horizon_days: float, now: Optional[float] = None) -> List[Dict]:
70
+ now = now if now is not None else time.time()
71
+ cutoff = now - horizon_days * 86400
72
+ return [p for p in self._items if not p.get("evaluated") and p["ts"] <= cutoff]
73
+
74
+ def mark_evaluated(self, pid: str, ret: float, correct: bool,
75
+ source: str = "price") -> None:
76
+ for p in self._items:
77
+ if p["id"] == pid:
78
+ p["evaluated"] = True
79
+ p["realised_return"] = ret
80
+ p["correct"] = correct
81
+ p["source"] = source
82
+ break
83
+ self._save()
84
+
85
+
86
+ def evaluate_due(
87
+ store: CalibrationStore,
88
+ log: PredictionLog,
89
+ price_fn: Callable[[str], Optional[float]],
90
+ horizon_days: float = 5.0,
91
+ threshold: float = 0.02,
92
+ now: Optional[float] = None,
93
+ ) -> Dict[str, int]:
94
+ """Score every prediction past its horizon and update calibration. Returns counts."""
95
+ evaluated = hits = 0
96
+ for p in log.pending(horizon_days, now=now):
97
+ try:
98
+ cur = price_fn(p["symbol"])
99
+ except Exception:
100
+ cur = None
101
+ if not cur or not p.get("ref_price"):
102
+ continue
103
+ ret = (cur - p["ref_price"]) / p["ref_price"]
104
+ ok = correctness(p["signal"], ret, threshold)
105
+ store.record_outcome(p["signal"], p["confidence"], ok)
106
+ log.mark_evaluated(p["id"], round(ret, 5), ok)
107
+ evaluated += 1
108
+ hits += 1 if ok else 0
109
+ return {"evaluated": evaluated, "hits": hits,
110
+ "hit_rate": round(hits / evaluated, 3) if evaluated else 0.0}
111
+
112
+
113
+ def evaluate_from_ledger(
114
+ store: CalibrationStore,
115
+ log: PredictionLog,
116
+ ledger,
117
+ ) -> Dict[str, int]:
118
+ """Score predictions against the portfolio ledger's REALISED P&L (actual closed
119
+ trades) — a stronger ground truth than market price for symbols you traded.
120
+
121
+ BUY is right when the symbol's realised P&L is positive, SELL when negative.
122
+ HOLD is skipped (no clean threshold without a cost basis). ``ledger`` is any
123
+ object exposing ``get_realized_pnl() -> [{symbol, realized_pnl, ...}]``.
124
+ """
125
+ try:
126
+ realized = ledger.get_realized_pnl()
127
+ except Exception:
128
+ return {"evaluated": 0, "hits": 0, "hit_rate": 0.0}
129
+ pnl_map = {str(r.get("symbol", "")).upper(): r.get("realized_pnl", 0.0)
130
+ for r in (realized or []) if r.get("realized_pnl")}
131
+
132
+ evaluated = hits = 0
133
+ for p in log.pending(0.0): # any un-evaluated prediction
134
+ sym = str(p["symbol"]).upper()
135
+ if sym not in pnl_map or p["signal"] not in (_BULL + _BEAR):
136
+ continue
137
+ pnl = pnl_map[sym]
138
+ ok = (pnl > 0) if p["signal"] in _BULL else (pnl < 0)
139
+ store.record_outcome(p["signal"], p["confidence"], ok)
140
+ log.mark_evaluated(p["id"], round(pnl, 2), ok, source="ledger")
141
+ evaluated += 1
142
+ hits += 1 if ok else 0
143
+ return {"evaluated": evaluated, "hits": hits,
144
+ "hit_rate": round(hits / evaluated, 3) if evaluated else 0.0}
@@ -0,0 +1,125 @@
1
+ """P1b — a self-check pass over the assembled analysis.
2
+
3
+ Claude Code verifies its own work; the flat pipeline never did. The critic applies
4
+ deterministic rules (no LLM, fully testable) to flag the failure modes that quietly
5
+ ruin a research note: thin agent coverage, no risk angle, a strong call on weak
6
+ confidence, or the quant signal contradicting the qualitative verdict. An optional
7
+ LLM pass can add free-text findings on top.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import List, Optional
13
+
14
+ from ..base import AgentResult
15
+ from .models import Critique, CritiqueIssue, Provenance, QuantEvidence
16
+ from .themes import theme_of
17
+
18
+ _STRONG = ("STRONG_BUY", "STRONG_SELL")
19
+
20
+
21
+ def critique(
22
+ agent_results: List[AgentResult],
23
+ final_signal: str,
24
+ calibrated_confidence: float,
25
+ quant: Optional[QuantEvidence] = None,
26
+ agreement: str = "neutral",
27
+ provenance: Optional[List[Provenance]] = None,
28
+ key_point_count: int = 0,
29
+ ) -> Critique:
30
+ """Deterministic self-check. Returns a Critique; ``passed`` is False on any
31
+ high-severity issue so the caller can soften an over-confident conclusion."""
32
+ issues: List[CritiqueIssue] = []
33
+
34
+ total = len(agent_results)
35
+ ok = sum(1 for r in agent_results if r.success)
36
+
37
+ # 1) coverage
38
+ if ok < 2:
39
+ issues.append(CritiqueIssue("high", "thin_coverage",
40
+ f"只有 {ok} 个 agent 成功,结论证据不足,不应作为决策依据。"))
41
+ elif total and ok / total < 0.5:
42
+ issues.append(CritiqueIssue("medium", "thin_coverage",
43
+ f"{total - ok}/{total} 个 agent 失败,覆盖偏薄。"))
44
+
45
+ # 2) risk angle present?
46
+ has_risk = any(r.success and theme_of(r.agent) == "risk" for r in agent_results)
47
+ if not has_risk:
48
+ issues.append(CritiqueIssue("medium", "missing_risk",
49
+ "缺少有效的风险维度分析,下行风险可能被低估。"))
50
+
51
+ # 3) quant contradicts qualitative verdict
52
+ if quant and quant.available and agreement == "disagree":
53
+ issues.append(CritiqueIssue("high", "conflict",
54
+ f"量化信号({quant.verdict()})与定性结论({final_signal})相反,置信度已下调;建议人工复核。"))
55
+
56
+ # 4) strong call on weak confidence
57
+ if final_signal in _STRONG and calibrated_confidence < 0.5:
58
+ issues.append(CritiqueIssue("medium", "unsupported",
59
+ f"给出强信号 {final_signal} 但校准后置信度仅 {calibrated_confidence:.0%},"
60
+ "强度与把握不匹配。"))
61
+
62
+ # 5) strong call with little supporting evidence
63
+ if final_signal in _STRONG and key_point_count < 3:
64
+ issues.append(CritiqueIssue("medium", "unsupported",
65
+ "强信号但关键论据少于 3 条,论证偏薄。"))
66
+
67
+ # 6) stale data
68
+ for p in (provenance or []):
69
+ if p.freshness.endswith("d old"):
70
+ issues.append(CritiqueIssue("low", "stale_data",
71
+ f"{p.field} 数据为 {p.freshness}(来源 {p.source}),注意时效。"))
72
+
73
+ passed = not any(i.severity == "high" for i in issues)
74
+ return Critique(issues=issues, passed=passed)
75
+
76
+
77
+ def soften_signal(final_signal: str) -> str:
78
+ """Step a verdict one notch toward HOLD (used when the critic fails)."""
79
+ ladder = {"STRONG_BUY": "BUY", "BUY": "HOLD", "HOLD": "HOLD",
80
+ "SELL": "HOLD", "STRONG_SELL": "SELL"}
81
+ return ladder.get(final_signal, "HOLD")
82
+
83
+
84
+ _SEV = {"高": "high", "中": "medium", "低": "low",
85
+ "high": "high", "medium": "medium", "low": "low"}
86
+
87
+
88
+ def parse_llm_issues(text: str, max_issues: int = 3) -> List[CritiqueIssue]:
89
+ """Parse the LLM reviewer's reply (``高|问题`` per line) into issues. Pure/testable."""
90
+ import re
91
+ issues: List[CritiqueIssue] = []
92
+ for line in (text or "").splitlines():
93
+ line = line.strip().lstrip("-•*0123456789. ").strip()
94
+ if not line or line.upper().startswith("OK") or line in ("无", "无问题"):
95
+ continue
96
+ parts = re.split(r"[||::]", line, maxsplit=1)
97
+ if len(parts) == 2 and _SEV.get(parts[0].strip().lower()):
98
+ sev, msg = _SEV[parts[0].strip().lower()], parts[1].strip()
99
+ else:
100
+ sev, msg = "medium", line
101
+ if msg:
102
+ issues.append(CritiqueIssue(sev, "llm_review", msg))
103
+ if len(issues) >= max_issues:
104
+ break
105
+ return issues
106
+
107
+
108
+ async def llm_critique(
109
+ symbol: str,
110
+ synthesis: str,
111
+ theme_summaries: str,
112
+ llm,
113
+ max_issues: int = 3,
114
+ ) -> List[CritiqueIssue]:
115
+ """Optional LLM reviewer — flags unsupported claims / missing risk / contradiction
116
+ / overconfidence in the synthesis. Returns extra issues (empty if no LLM)."""
117
+ if llm is None or not (synthesis or "").strip():
118
+ return []
119
+ from .deepen import _collect_llm
120
+ system = ("你是严格的研究审稿人。只挑【确凿的】问题:无数据支撑的论断、漏掉的下行风险、"
121
+ "自相矛盾、过度自信。每行一个,格式 `高|问题` / `中|问题` / `低|问题`,"
122
+ f"最多 {max_issues} 条。没有问题就只输出 OK。不要多余的话。")
123
+ user = f"标的: {symbol}\n各维度小结: {theme_summaries}\n\n综合结论:\n{synthesis}\n\n审查:"
124
+ resp = await _collect_llm(llm, system, user, max_tokens=300)
125
+ return parse_llm_issues(resp, max_issues)