argo-search 1.0.1 → 2.9.7

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 (463) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +822 -0
  3. package/README.es.md +864 -0
  4. package/README.ja.md +864 -0
  5. package/README.ko.md +864 -0
  6. package/README.md +867 -320
  7. package/SKILL.md +135 -0
  8. package/assets/readme/hero.svg +121 -0
  9. package/assets/readme/made-with-beautify.svg +10 -0
  10. package/assets/readme/proof-routes.svg +73 -0
  11. package/assets/readme/settings-keys.png +0 -0
  12. package/assets/readme/settings-tools.png +0 -0
  13. package/assets/readme/why-better.svg +104 -0
  14. package/assets/readme/workflow.svg +83 -0
  15. package/backends/domain_profiles.json +2308 -96
  16. package/backends/engine_registry.yaml +3314 -503
  17. package/backends/query_synonyms_cn.json +35 -0
  18. package/backends/quota_profiles.json +2409 -161
  19. package/backends/source_types_cn.json +83 -0
  20. package/backends/text_lexicon.json +44 -0
  21. package/bin/argo +703 -0
  22. package/bin/argo.js +71 -39
  23. package/config.yaml +4976 -363
  24. package/cordis.patch.yml +21 -0
  25. package/data/countries.json +40 -0
  26. package/data/lang_matrix/lang_capability.json +842 -0
  27. package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
  28. package/data/regions_cn.json +1 -0
  29. package/docs/ADDING_NEW_ENGINE.md +184 -0
  30. package/docs/ARGO_INTRO.md +124 -0
  31. package/docs/DATA_SOURCE_GAPS.md +81 -0
  32. package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
  33. package/docs/ENGINE_CATALOG.md +564 -0
  34. package/docs/MCP_SETUP.md +106 -0
  35. package/docs/PLATFORM_PATHS.md +111 -0
  36. package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
  37. package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
  38. package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
  39. package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
  40. package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
  41. package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
  42. package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
  43. package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
  44. package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
  45. package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
  46. package/docs/SEARCH_ARCHIVE.md +115 -0
  47. package/docs/assets/architecture.svg +104 -0
  48. package/docs/assets/hero.svg +98 -0
  49. package/docs/engines/bangumi.md +27 -0
  50. package/docs/engines/biorxiv.md +27 -0
  51. package/docs/engines/clawhub.md +27 -0
  52. package/docs/engines/crt_sh.md +27 -0
  53. package/docs/engines/datacite.md +27 -0
  54. package/docs/engines/deps_dev.md +27 -0
  55. package/docs/engines/douban_movie.md +27 -0
  56. package/docs/engines/endoflife.md +27 -0
  57. package/docs/engines/flk_law.md +27 -0
  58. package/docs/engines/fxtwitter.md +27 -0
  59. package/docs/engines/google_news.md +27 -0
  60. package/docs/engines/gov_regulations.md +27 -0
  61. package/docs/engines/guqian.md +27 -0
  62. package/docs/engines/hackernews.md +27 -0
  63. package/docs/engines/iplant.md +27 -0
  64. package/docs/engines/keenable.md +27 -0
  65. package/docs/engines/met_museum.md +27 -0
  66. package/docs/engines/nasa_images.md +27 -0
  67. package/docs/engines/netease_music.md +27 -0
  68. package/docs/engines/npm.md +27 -0
  69. package/docs/engines/nvd.md +27 -0
  70. package/docs/engines/openreview.md +27 -0
  71. package/docs/engines/openstd.md +27 -0
  72. package/docs/engines/parallel_free.md +27 -0
  73. package/docs/engines/people_daily.md +27 -0
  74. package/docs/engines/realtime_index.md +27 -0
  75. package/docs/engines/sec_edgar.md +27 -0
  76. package/docs/engines/seltz.md +27 -0
  77. package/docs/engines/skillsmp.md +27 -0
  78. package/docs/engines/sspai.md +27 -0
  79. package/docs/engines/std_samr.md +27 -0
  80. package/docs/engines/train.md +27 -0
  81. package/docs/engines/un_comtrade.md +27 -0
  82. package/docs/engines/wikisource.md +27 -0
  83. package/docs/engines/zdic.md +27 -0
  84. package/docs/engines/zenodo.md +27 -0
  85. package/docs/engines/zhihu_hot_app.md +27 -0
  86. package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
  87. package/engines/README.md +10 -0
  88. package/engines/_template_cli.yaml +39 -0
  89. package/engines/_template_http.yaml +37 -0
  90. package/engines/plugins/_template_plugin.py +50 -0
  91. package/engines/specs/_cisa_kev.yaml +54 -0
  92. package/engines/specs/_osv.yaml +63 -0
  93. package/engines/specs/aljazeera.yaml +43 -0
  94. package/engines/specs/antara.yaml +43 -0
  95. package/engines/specs/artic.yaml +20 -0
  96. package/engines/specs/aviation_weather.yaml +39 -0
  97. package/engines/specs/bangumi.yaml +21 -0
  98. package/engines/specs/biorxiv.yaml +24 -0
  99. package/engines/specs/carbon_intensity.yaml +32 -0
  100. package/engines/specs/cinii.yaml +44 -0
  101. package/engines/specs/clawhub.yaml +49 -0
  102. package/engines/specs/cleveland.yaml +19 -0
  103. package/engines/specs/cn_ai_news.yaml +43 -0
  104. package/engines/specs/core.yaml +35 -0
  105. package/engines/specs/crt_sh.yaml +38 -0
  106. package/engines/specs/ct24.yaml +43 -0
  107. package/engines/specs/datacite.yaml +27 -0
  108. package/engines/specs/datagov_uk.yaml +41 -0
  109. package/engines/specs/ddgs_images.yaml +45 -0
  110. package/engines/specs/deepseek_search.yaml +74 -0
  111. package/engines/specs/deezer.yaml +20 -0
  112. package/engines/specs/deps_dev.yaml +21 -0
  113. package/engines/specs/digup.yaml +36 -0
  114. package/engines/specs/doi.yaml +39 -0
  115. package/engines/specs/douban_movie.yaml +22 -0
  116. package/engines/specs/dw_news.yaml +47 -0
  117. package/engines/specs/egov_law.yaml +21 -0
  118. package/engines/specs/elpais.yaml +43 -0
  119. package/engines/specs/endoflife.yaml +21 -0
  120. package/engines/specs/energy_charts.yaml +21 -0
  121. package/engines/specs/factcheck_org.yaml +52 -0
  122. package/engines/specs/faz.yaml +43 -0
  123. package/engines/specs/federal_register.yaml +57 -0
  124. package/engines/specs/firecrawl.yaml +53 -0
  125. package/engines/specs/flk_law.yaml +24 -0
  126. package/engines/specs/folha.yaml +43 -0
  127. package/engines/specs/france24.yaml +44 -0
  128. package/engines/specs/full_fact.yaml +47 -0
  129. package/engines/specs/fxtwitter.yaml +31 -0
  130. package/engines/specs/gbfs_nyc.yaml +32 -0
  131. package/engines/specs/gdacs.yaml +21 -0
  132. package/engines/specs/gdelt.yaml +64 -0
  133. package/engines/specs/google_news.yaml +22 -0
  134. package/engines/specs/gov_regulations.yaml +38 -0
  135. package/engines/specs/guardian_rss.yaml +59 -0
  136. package/engines/specs/hurriyet.yaml +43 -0
  137. package/engines/specs/iplant.yaml +25 -0
  138. package/engines/specs/jikan.yaml +24 -0
  139. package/engines/specs/jolpica.yaml +20 -0
  140. package/engines/specs/k10plus.yaml +20 -0
  141. package/engines/specs/keenable.yaml +44 -0
  142. package/engines/specs/lefigaro.yaml +43 -0
  143. package/engines/specs/listenbrainz.yaml +19 -0
  144. package/engines/specs/matichon.yaml +43 -0
  145. package/engines/specs/met_museum.yaml +21 -0
  146. package/engines/specs/nasa_images.yaml +45 -0
  147. package/engines/specs/netease_music.yaml +38 -0
  148. package/engines/specs/nhk.yaml +43 -0
  149. package/engines/specs/nhtsa_vpic.yaml +20 -0
  150. package/engines/specs/noaa_swpc.yaml +20 -0
  151. package/engines/specs/nvd.yaml +38 -0
  152. package/engines/specs/obis.yaml +20 -0
  153. package/engines/specs/opencitations.yaml +52 -0
  154. package/engines/specs/openf1.yaml +21 -0
  155. package/engines/specs/openfoodfacts.yaml +45 -0
  156. package/engines/specs/openligadb.yaml +20 -0
  157. package/engines/specs/openreview.yaml +37 -0
  158. package/engines/specs/openstd.yaml +21 -0
  159. package/engines/specs/people_daily.yaml +22 -0
  160. package/engines/specs/realtime_index.yaml +34 -0
  161. package/engines/specs/ror.yaml +20 -0
  162. package/engines/specs/satnogs.yaml +20 -0
  163. package/engines/specs/sec_edgar.yaml +46 -0
  164. package/engines/specs/skillsmp.yaml +50 -0
  165. package/engines/specs/sogou_weixin_browser.yaml +30 -0
  166. package/engines/specs/soilgrids.yaml +25 -0
  167. package/engines/specs/sspai.yaml +36 -0
  168. package/engines/specs/stackexchange.yaml +46 -0
  169. package/engines/specs/std_samr.yaml +21 -0
  170. package/engines/specs/tass.yaml +43 -0
  171. package/engines/specs/tle_mirror.yaml +21 -0
  172. package/engines/specs/train.yaml +37 -0
  173. package/engines/specs/tvmaze.yaml +20 -0
  174. package/engines/specs/twitter_syndication.yaml +31 -0
  175. package/engines/specs/un_comtrade.yaml +23 -0
  176. package/engines/specs/unpaywall.yaml +54 -0
  177. package/engines/specs/vnpress.yaml +43 -0
  178. package/engines/specs/weather.yaml +32 -0
  179. package/engines/specs/who_don.yaml +21 -0
  180. package/engines/specs/who_gho.yaml +20 -0
  181. package/engines/specs/wikimedia_commons.yaml +34 -0
  182. package/engines/specs/wikisource.yaml +22 -0
  183. package/engines/specs/worms.yaml +19 -0
  184. package/engines/specs/x_trending.yaml +34 -0
  185. package/engines/specs/yna.yaml +43 -0
  186. package/engines/specs/ynet.yaml +43 -0
  187. package/engines/specs/zbmath.yaml +40 -0
  188. package/engines/specs/zdic.yaml +22 -0
  189. package/engines/specs/zenodo.yaml +27 -0
  190. package/engines/specs/zhihu_hot_app.yaml +35 -0
  191. package/mcp/clients.yaml +74 -0
  192. package/package.json +51 -12
  193. package/packages/dsh-plugin/README.md +134 -0
  194. package/packages/dsh-plugin/cordis.patch.yml +48 -0
  195. package/packages/dsh-plugin/dsh/client.js +1244 -0
  196. package/packages/dsh-plugin/dsh/index.js +2290 -0
  197. package/packages/dsh-plugin/dsh/native-tools.mjs +743 -0
  198. package/packages/dsh-plugin/package.json +38 -0
  199. package/packages/dsh-plugin/test/client.test.js +232 -0
  200. package/packages/dsh-plugin/test/config.test.js +30 -0
  201. package/packages/dsh-plugin/test/index.test.js +1338 -0
  202. package/references/query-templates.md +96 -0
  203. package/references/query-templates.yaml +118 -0
  204. package/scripts/ab_eval.py +219 -0
  205. package/scripts/ab_eval_p0p1.py +266 -0
  206. package/scripts/adaptive.py +271 -70
  207. package/scripts/answer.py +154 -0
  208. package/scripts/archive_run.py +591 -0
  209. package/scripts/argo_engine_registry.py +172 -0
  210. package/scripts/argo_paths.py +728 -0
  211. package/scripts/article.py +136 -0
  212. package/scripts/arxiv_source.py +201 -0
  213. package/scripts/badcase_ledger.py +252 -0
  214. package/scripts/batch_probe.py +241 -0
  215. package/scripts/benchmark.py +19 -0
  216. package/scripts/bounded_run.py +144 -0
  217. package/scripts/browser_auth.py +323 -0
  218. package/scripts/burstiness_detector.py +147 -0
  219. package/scripts/cache.py +1078 -67
  220. package/scripts/cache_guard.py +271 -0
  221. package/scripts/cache_key_vdom.py +56 -0
  222. package/scripts/cache_similarity.py +170 -0
  223. package/scripts/caliber_isolation.py +195 -0
  224. package/scripts/calibrate_lowquality.py +210 -0
  225. package/scripts/candidate_envelope.py +366 -0
  226. package/scripts/chrome_cdp.py +871 -0
  227. package/scripts/circuit_breaker.py +389 -0
  228. package/scripts/citations.py +334 -0
  229. package/scripts/cite_cli.py +59 -0
  230. package/scripts/cjk_tokens.py +95 -0
  231. package/scripts/clarify.py +47 -24
  232. package/scripts/cli_io.py +240 -0
  233. package/scripts/config.py +683 -45
  234. package/scripts/config_cache.py +402 -0
  235. package/scripts/conn_pool.py +220 -0
  236. package/scripts/content_security.py +685 -0
  237. package/scripts/content_signals.py +730 -0
  238. package/scripts/crawl.py +74 -35
  239. package/scripts/daemon.py +866 -0
  240. package/scripts/digup_bridge.py +214 -0
  241. package/scripts/engine_admission.py +366 -0
  242. package/scripts/engine_dispatch.py +1019 -0
  243. package/scripts/engine_env.py +534 -0
  244. package/scripts/engine_failure.py +411 -0
  245. package/scripts/engine_families.py +732 -0
  246. package/scripts/engine_policy.py +218 -0
  247. package/scripts/engine_requires.py +190 -0
  248. package/scripts/engine_status.py +322 -0
  249. package/scripts/engine_validate.py +638 -0
  250. package/scripts/engines.py +778 -294
  251. package/scripts/engines_base.py +1521 -0
  252. package/scripts/engines_builders.py +298 -0
  253. package/scripts/engines_builders_batch10.py +159 -0
  254. package/scripts/engines_builders_batch11.py +174 -0
  255. package/scripts/engines_builders_batch12.py +103 -0
  256. package/scripts/engines_builders_batch13.py +340 -0
  257. package/scripts/engines_builders_batch9.py +1468 -0
  258. package/scripts/engines_builders_cn.py +2283 -0
  259. package/scripts/engines_builders_data.py +2923 -0
  260. package/scripts/engines_builders_data_macro.py +775 -0
  261. package/scripts/engines_builders_feeds.py +255 -0
  262. package/scripts/engines_builders_intl.py +1239 -0
  263. package/scripts/engines_builders_search.py +440 -0
  264. package/scripts/engines_builders_tech.py +1360 -0
  265. package/scripts/evidence.py +588 -162
  266. package/scripts/evidence_loop.py +592 -0
  267. package/scripts/evidence_tier.py +298 -0
  268. package/scripts/except_sets.py +25 -0
  269. package/scripts/extract.py +55 -16
  270. package/scripts/fact_align.py +207 -0
  271. package/scripts/fact_crosscheck.py +230 -0
  272. package/scripts/fetch.py +25 -12
  273. package/scripts/fetch_browser.py +87 -0
  274. package/scripts/fetch_jsrun.py +122 -0
  275. package/scripts/fetch_quality.py +225 -0
  276. package/scripts/fetch_render_tinyfish.py +117 -0
  277. package/scripts/fetch_v3.py +1779 -0
  278. package/scripts/focus_extract.py +350 -0
  279. package/scripts/fulltext_store.py +169 -0
  280. package/scripts/fxtwitter_api.py +325 -0
  281. package/scripts/gen_engine_catalog.py +364 -0
  282. package/scripts/gen_native_tools.py +91 -0
  283. package/scripts/gen_routing_snapshot.py +184 -0
  284. package/scripts/health_check.py +140 -402
  285. package/scripts/health_probe.py +23 -8
  286. package/scripts/hot_state.py +134 -0
  287. package/scripts/http_client.py +1053 -0
  288. package/scripts/image_ops.py +430 -0
  289. package/scripts/image_vision/vision_probe +0 -0
  290. package/scripts/image_vision/vision_probe.swift +126 -0
  291. package/scripts/install.ps1 +148 -0
  292. package/scripts/install.sh +206 -0
  293. package/scripts/job.py +1186 -0
  294. package/scripts/jump_resolver.py +235 -0
  295. package/scripts/lang_capability.py +150 -0
  296. package/scripts/lang_detect.py +289 -0
  297. package/scripts/lang_matrix_probe.py +329 -0
  298. package/scripts/lang_pref.py +311 -0
  299. package/scripts/link_source.py +340 -0
  300. package/scripts/local_engines.py +321 -0
  301. package/scripts/local_image.py +970 -0
  302. package/scripts/local_seek.py +366 -0
  303. package/scripts/macro_countries.py +82 -0
  304. package/scripts/matrix_search_eval.py +1105 -0
  305. package/scripts/mcp_diag.py +8 -3
  306. package/scripts/mcp_handlers.py +1074 -0
  307. package/scripts/mcp_handlers_surface.py +157 -0
  308. package/scripts/mcp_launch.sh +122 -0
  309. package/scripts/mcp_server.py +118 -417
  310. package/scripts/mcp_setup.py +407 -0
  311. package/scripts/mcp_tools.py +330 -0
  312. package/scripts/mcp_transport.py +191 -0
  313. package/scripts/min_version_scan.py +335 -0
  314. package/scripts/net_proxy.py +258 -0
  315. package/scripts/network_aware.py +152 -0
  316. package/scripts/paper.py +274 -0
  317. package/scripts/pdf_extract.py +518 -0
  318. package/scripts/phrase_filter.py +66 -0
  319. package/scripts/plan.py +422 -0
  320. package/scripts/postprocess_signals.py +150 -0
  321. package/scripts/pre-commit +48 -0
  322. package/scripts/query_enhance.py +157 -0
  323. package/scripts/query_operators.py +196 -0
  324. package/scripts/query_rewriter.py +116 -7
  325. package/scripts/query_signals.py +300 -0
  326. package/scripts/query_templates.py +167 -0
  327. package/scripts/query_understanding.py +411 -0
  328. package/scripts/query_variants.py +298 -0
  329. package/scripts/quota.py +488 -76
  330. package/scripts/rank_signals.py +775 -0
  331. package/scripts/ranking_eval.py +319 -0
  332. package/scripts/readability_extract.py +433 -0
  333. package/scripts/recompute.py +415 -0
  334. package/scripts/recovery.py +616 -0
  335. package/scripts/recovery_l0.py +160 -0
  336. package/scripts/redskill/redskill_engine.py +332 -0
  337. package/scripts/regression_p0p1.py +318 -0
  338. package/scripts/replay_eval.py +640 -0
  339. package/scripts/rerank_tfidf.py +225 -0
  340. package/scripts/research.py +591 -403
  341. package/scripts/research_cli.py +361 -0
  342. package/scripts/research_dossier.py +492 -0
  343. package/scripts/research_expand.py +146 -0
  344. package/scripts/research_gates.py +272 -0
  345. package/scripts/research_report.py +209 -0
  346. package/scripts/research_strategy.py +37 -0
  347. package/scripts/research_work_packages.py +195 -0
  348. package/scripts/result_lang.py +311 -0
  349. package/scripts/robots_guard.py +259 -0
  350. package/scripts/route.py +921 -254
  351. package/scripts/route_bangs.py +40 -0
  352. package/scripts/route_cache.py +225 -0
  353. package/scripts/route_combo.py +910 -0
  354. package/scripts/route_domains.py +328 -0
  355. package/scripts/route_lang.py +605 -0
  356. package/scripts/route_log.py +63 -0
  357. package/scripts/route_policy.py +250 -0
  358. package/scripts/rrf_classic.py +66 -0
  359. package/scripts/screenshot.py +73 -0
  360. package/scripts/search.py +897 -405
  361. package/scripts/search_attribution.py +88 -0
  362. package/scripts/search_benchmark.py +407 -0
  363. package/scripts/search_cli.py +627 -0
  364. package/scripts/search_entry.py +389 -0
  365. package/scripts/search_output.py +606 -0
  366. package/scripts/search_pipeline.py +1077 -0
  367. package/scripts/search_rank.py +991 -0
  368. package/scripts/search_types.py +27 -12
  369. package/scripts/seek_locator.py +68 -0
  370. package/scripts/semantic_evidence.py +215 -0
  371. package/scripts/serp_guard.py +228 -0
  372. package/scripts/serp_kill_log.py +51 -0
  373. package/scripts/single_flight.py +120 -0
  374. package/scripts/social_engines/__init__.py +10 -0
  375. package/scripts/social_engines/bilibili_engine.py +131 -0
  376. package/scripts/social_engines/hackernews_engine.py +113 -0
  377. package/scripts/social_engines/reddit_engine.py +172 -0
  378. package/scripts/social_engines/twitter_engine.py +397 -0
  379. package/scripts/social_engines/v2ex_engine.py +101 -0
  380. package/scripts/social_engines/weibo_engine.py +128 -0
  381. package/scripts/social_engines/xiaohongshu_engine.py +137 -0
  382. package/scripts/social_engines/zhihu_engine.py +139 -0
  383. package/scripts/social_research.py +201 -0
  384. package/scripts/sogou_weixin_browser.py +318 -0
  385. package/scripts/source_ledger.py +249 -0
  386. package/scripts/stage_timing.py +58 -0
  387. package/scripts/stats_cli.py +95 -0
  388. package/scripts/straggler_policy.py +60 -0
  389. package/scripts/stylometry_detector.py +231 -0
  390. package/scripts/sync_backends.py +353 -0
  391. package/scripts/sync_installs.py +29 -0
  392. package/scripts/template_cli.py +127 -0
  393. package/scripts/text_lexicon.py +72 -0
  394. package/scripts/tfidf_router.py +109 -37
  395. package/scripts/time_utils.py +233 -0
  396. package/scripts/topic_research_profiles.py +643 -0
  397. package/scripts/train.py +737 -0
  398. package/scripts/tweet.py +596 -0
  399. package/scripts/url_canon.py +217 -0
  400. package/scripts/url_optimize.py +56 -0
  401. package/scripts/url_safety.py +236 -0
  402. package/scripts/usage_log.py +116 -0
  403. package/scripts/usage_log_clean.py +113 -0
  404. package/scripts/v2ex_nodes.py +311 -0
  405. package/scripts/variant_recall.py +188 -0
  406. package/scripts/watch.py +205 -0
  407. package/scripts/wechat_article.py +258 -0
  408. package/scripts/wide_research.py +402 -0
  409. package/scripts/wx.py +349 -0
  410. package/scripts/x_article_fetch.py +141 -0
  411. package/scripts/x_search.py +234 -0
  412. package/scripts/x_trending.py +189 -0
  413. package/scripts/yaml_load.py +67 -0
  414. package/sub-skills/ego-search/SKILL.md +253 -0
  415. package/sub-skills/ego-search/references/browser-runtime.md +43 -0
  416. package/sub-skills/ego-search/references/install.md +67 -0
  417. package/sub-skills/ego-search/references/original-ego-upgrade.md +51 -0
  418. package/sub-skills/ego-search/scripts/ego_search.py +800 -0
  419. package/sub-skills/ego-search/scripts/merge.py +226 -0
  420. package/sub-skills/ego-search/scripts/quality.py +64 -0
  421. package/sub-skills/ego-search/scripts/runtime.py +172 -0
  422. package/sub-skills/ego-search/scripts/safety.py +109 -0
  423. package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
  424. package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
  425. package/sub-skills/ego-search/tests/test_query_normalize.py +68 -0
  426. package/sub-skills/js-run/SKILL.md +67 -0
  427. package/sub-skills/js-run/scripts/jsrun.py +112 -0
  428. package/sub-skills/js-run/scripts/shim.js +188 -0
  429. package/sub-skills/js-run/tests/fixtures/challenge_like.js +27 -0
  430. package/sub-skills/js-run/tests/test_jsrun.py +165 -0
  431. package/sub-skills/local-search/SKILL.md +50 -53
  432. package/sub-skills/local-search/bm25.py +162 -0
  433. package/sub-skills/local-search/config.yaml +152 -55
  434. package/sub-skills/local-search/engine_registry.py +91 -26
  435. package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
  436. package/sub-skills/local-search/local_search_adapter.py +13 -2
  437. package/sub-skills/local-search/parse_maps.yaml +40 -42
  438. package/sub-skills/local-search/search_v3.py +1182 -65
  439. package/sub-skills/local-search/smart_router.py +47 -7
  440. package/sub-skills/local-seek/SKILL.md +110 -0
  441. package/sub-skills/local-seek/config/domains.yaml +20 -0
  442. package/sub-skills/local-seek/references/auto-behaviors.md +24 -0
  443. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +62 -0
  444. package/sub-skills/local-seek/references/git-integration.md +54 -0
  445. package/sub-skills/local-seek/references/remote-handoff.md +46 -0
  446. package/sub-skills/local-seek/references/rg-recipes.md +96 -0
  447. package/sub-skills/local-seek/references/strategies.md +52 -0
  448. package/sub-skills/local-seek/references/structural-search.md +59 -0
  449. package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
  450. package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
  451. package/sub-skills/local-seek/scripts/seek.py +1465 -0
  452. package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
  453. package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
  454. package/sub-skills/local-seek/scripts/seek_rank.py +193 -0
  455. package/sub-skills/local-seek/tests/test_content_rank.py +222 -0
  456. package/sub-skills/local-seek/tests/test_determinism_20261006.py +145 -0
  457. package/sub-skills/local-seek/tests/test_noise_tier.py +101 -0
  458. package/sub-skills/local-seek/tests/test_pcre2_probe.py +136 -0
  459. package/sub-skills/local-seek/tests/test_rank_pinyin.py +77 -0
  460. package/sub-skills/local-seek/tests/test_regression_20261003.py +169 -0
  461. package/sub-skills/local-seek/tests/test_regression_20261006.py +146 -0
  462. package/sub-skills/local-seek/tests/test_seek_degradation.py +111 -0
  463. package/sub-skills/local-seek/tests/test_seek_fts.py +313 -0
@@ -0,0 +1,96 @@
1
+ # 查询模板(Query Templates)
2
+
3
+ 命名好的检索模式:一步展开并执行,可组合、可扩展。日常用 `argo template`
4
+ 即可,本文件解释机制与扩展方式。
5
+
6
+ ## 快速上手
7
+
8
+ ```bash
9
+ argo template --list # 列出全部模板(按分组)
10
+ argo template ai_model_release # 用模板搜索
11
+ argo template ai_model_release --add "GPT-5" # 模板 + 追加词(空格即 AND)
12
+ argo template ai_zh --sort top -n 20 --json # 转发参数给底层命令
13
+ argo template ai_model_release --dry-run # 只看展开后的命令与查询串
14
+ ```
15
+
16
+ `argo template` 只做三件事:**取模板 → 展开成查询串 → 转发给该模板的 command**。
17
+ 转发参数(`-n` / `--sort` / `--json` / `--engine` / `--since` …)原样透传,
18
+ 语义仍由底层命令持有——本机制不复制任何搜索开关定义。
19
+
20
+ ## 随包模板
21
+
22
+ 源自宝玉([@dotey](https://x.com/dotey))《X AI 资讯检索》方法论,全部走
23
+ `command: x-search`(X 原生搜索),查询统一带 `-is:retweet`(去转发)与
24
+ `lang:`(语言)。
25
+
26
+ | 分组 | id | 说明 |
27
+ |------|----|------|
28
+ | AI 模型 | `ai_model_release` | 前沿模型发布/正式开放 |
29
+ | AI 模型 | `ai_new_model` | 新模型 / 开放权重 / 开源模型 |
30
+ | AI 产品 | `ai_agent_product` | AI Agent / MCP / agent skills 发布 |
31
+ | AI 产品 | `ai_coding_agent` | 编码 Agent 更新(Cursor / Claude Code / Codex / Devin …) |
32
+ | AI 行业 | `ai_industry_funding` | 融资 / 并购 / 破产 |
33
+ | AI 行业 | `ai_industry_policy` | 监管 / 诉讼 / 禁令 / 行政令 |
34
+ | AI 行业 | `ai_security` | 安全 / 漏洞 / 越狱 / 泄露 |
35
+ | AI 爆料 | `ai_leak` | 爆料 / 泄露 / 即将发布 |
36
+ | AI 中文 | `ai_zh` | 中文 AI 资讯(发布 / 开源 / 上线) |
37
+ | 示例 | `ai_media_tools` | 语音/视频/媒体类 AI 工具发布(自定义方向样例) |
38
+
39
+ 热度筛选:加 `--sort top` 走 X 的 Top 榜(默认 `latest` 最新);或拿到结果后
40
+ 按 `social_meta.likes/reposts` 排序。
41
+
42
+ ## 加自己的模板(零代码)
43
+
44
+ 把同名文件放到用户注册表即可,**不改仓库**:
45
+
46
+ ```
47
+ $XDG_CONFIG_HOME/argo/query-templates.yaml # 缺省 ~/.config/argo/query-templates.yaml
48
+ ```
49
+
50
+ 或用环境变量 `ARGO_TEMPLATES_FILE` 指路径。合并规则:新 id 追加,同名 id 覆盖
51
+ 随包条目;`disabled: true` 停用随包条目。
52
+
53
+ ```yaml
54
+ version: 1
55
+ templates:
56
+ quantum_news:
57
+ description: 量子计算进展
58
+ category: 我的方向
59
+ command: x-search
60
+ query: '(quantum computing OR qubit) (breakthrough OR milestone) -is:retweet lang:en'
61
+ ```
62
+
63
+ `command` 白名单只有两个:`x-search`(X 原生搜索)与 `search`(通用多源搜索,
64
+ 走 `argo search`)。模板是数据,**不拥有执行任意命令的能力**。
65
+
66
+ ## X 原生搜索语法速查(`command: x-search` 时)
67
+
68
+ | 语法 | 含义 |
69
+ |------|------|
70
+ | `-is:retweet` | 去转发 |
71
+ | `lang:en` / `lang:zh` | 语言 |
72
+ | `from:OpenAI` | 盯人(可 OR:`from:OpenAI OR from:AnthropicAI`) |
73
+ | `min_faves:100` / `min_retweets:50` | 热度门 |
74
+ | `since:2026-10-01 until:2026-10-16` | 时间窗 |
75
+ | `-filter:replies` | 排除回复 |
76
+ | `filter:media` / `filter:images` / `filter:videos` | 只要媒体 |
77
+ | 空格=AND、`OR`=或、`-词`=排除、`( )`=分组 | 布尔与分组 |
78
+
79
+ 「盯一批官方号」的典型写法:`from:OpenAI OR from:AnthropicAI OR from:GoogleDeepMind -is:retweet lang:en`,
80
+ 存成一条模板即可复用。
81
+
82
+ ## 通道说明(为什么是 x-search 而不是 fxtwitter)
83
+
84
+ X 布尔语法只有 `x.com/search` 本体认,通用网页搜索引擎一律忽略——所以要
85
+ 「盯人 / 按关键词找发布」只能走 X 原生搜索。`argo x-search` 借 ego-browser
86
+ 驱动用户已登录的浏览器抓 `x.com/search`(同 `x_trending` 引擎的通道思路)。
87
+
88
+ > 注:`engines/specs/fxtwitter.yaml` 的 HTTP 免登录搜索通道,上游公开实例的
89
+ > `/2/search` 已停用(对任意查询恒返回 404 + 空结果,status/profile 端点仍正常)。
90
+ > 关键词搜索请走 `argo x-search`;单条推文按 URL 取正文仍走 `argo tweet`。
91
+
92
+ ## 与其它能力的关系
93
+
94
+ - **模板 ≠ 深度研究**:模板是「一次结构化检索」,要问题分解 / 多源取证 / 达标检查走 `argo research`。
95
+ - **模板 ≠ 事实核验**:社交帖是叙事/舆情,不进事实真值(见 SKILL.md「Agent 执行纪律」)。要核到走 `argo evidence` / `--verify`。
96
+ - **交叉核对**:X 上的说法对照 Hacker News / 官方博客 / 主流媒体(宝玉方法论第 4 条)——用 `argo search` 或 `argo research` 完成,模板只负责把 X 侧取全。
@@ -0,0 +1,118 @@
1
+ # Argo 查询模板注册表(Query Template Registry)
2
+ #
3
+ # 用途:把「命名好的检索模式」沉淀成可复用、可组合、可扩展的数据。
4
+ # 用户不必每次手打长串布尔查询,也不必记住哪套词组合最有效。
5
+ #
6
+ # 调用:argo template <id> [--add 词 ...] [--dry-run] [--list]
7
+ # 例:argo template ai_model_release --add "GPT-5" -n 20
8
+ # → 展开 query 并把 --add 追加成 AND 项,转发给该模板的 command
9
+ #
10
+ # 字段:
11
+ # description 一句话说明(--list 显示)
12
+ # category 分组(--list 按它分组;任意字符串,用户可自定义)
13
+ # command 目标 argo 子命令,白名单:search | x-search(缺省 search)
14
+ # query 查询串(原样下推;X 原生语法见文末)
15
+ # tags 可选,检索/过滤用
16
+ # note 可选,给维护者/用户的提醒(--list --json 会带出)
17
+ #
18
+ # 扩展(不改本文件):把同名文件放到 $XDG_CONFIG_HOME/argo/query-templates.yaml
19
+ # (缺省 ~/.config/argo/query-templates.yaml),或用 ARGO_TEMPLATES_FILE 指路径。
20
+ # 用户文件按 id 合并:新增即追加,同名即覆盖;`disabled: true` 可停用随包模板。
21
+ # 这样新增领域(量子计算 / 生物医药 / 加密货币 …)零代码,只加几行 YAML。
22
+ #
23
+ # 随包模板源自宝玉(@dotey)《X AI 资讯检索》方法论,全部针对 X 原生搜索
24
+ # (command: x-search),查询统一带 `-is:retweet`(去转发)与 `lang:`(语言)。
25
+
26
+ version: 1
27
+
28
+ templates:
29
+
30
+ # ── 模型发布 ────────────────────────────────────────────────────────────
31
+ ai_model_release:
32
+ description: 前沿模型发布/正式开放
33
+ category: AI 模型
34
+ command: x-search
35
+ tags: [ai, model, release]
36
+ query: '(GPT OR Claude OR Gemini OR Llama OR Grok OR Qwen OR DeepSeek OR Mistral) (release OR launch OR announce OR "now available") -is:retweet lang:en'
37
+
38
+ ai_new_model:
39
+ description: 新模型 / 开放权重 / 开源模型
40
+ category: AI 模型
41
+ command: x-search
42
+ tags: [ai, model, open-weights]
43
+ query: '("new model" OR "open weights" OR "open-source model") AI -is:retweet lang:en'
44
+
45
+ # ── 产品与 Agent ────────────────────────────────────────────────────────
46
+ ai_agent_product:
47
+ description: AI Agent / MCP / agent skills 发布
48
+ category: AI 产品
49
+ command: x-search
50
+ tags: [ai, agent, mcp]
51
+ query: '("AI agent" OR "coding agent" OR "agent skills" OR MCP) (launch OR ships OR introducing) -is:retweet lang:en'
52
+
53
+ ai_coding_agent:
54
+ description: 编码 Agent 更新(Cursor / Claude Code / Codex / Devin …)
55
+ category: AI 产品
56
+ command: x-search
57
+ tags: [ai, coding-agent]
58
+ query: '(Cursor OR "Claude Code" OR Codex OR Antigravity OR Devin) (launch OR update) -is:retweet lang:en'
59
+
60
+ # ── 行业大事 ────────────────────────────────────────────────────────────
61
+ ai_industry_funding:
62
+ description: 融资 / 并购 / 破产
63
+ category: AI 行业
64
+ command: x-search
65
+ tags: [ai, funding, ma]
66
+ query: 'AI (acquires OR acquisition OR "raises" OR bankruptcy OR "Series A" OR "Series B") -is:retweet lang:en'
67
+
68
+ ai_industry_policy:
69
+ description: 监管 / 诉讼 / 禁令 / 行政令
70
+ category: AI 行业
71
+ command: x-search
72
+ tags: [ai, policy, regulation]
73
+ query: 'AI (regulation OR lawsuit OR ban OR "executive order" OR "AI Act") -is:retweet lang:en'
74
+
75
+ ai_security:
76
+ description: 安全 / 漏洞 / 越狱 / 泄露
77
+ category: AI 行业
78
+ command: x-search
79
+ tags: [ai, security]
80
+ query: 'AI ("safety" OR "security" OR vulnerability OR jailbreak OR leak) -is:retweet lang:en'
81
+
82
+ # ── 爆料 ────────────────────────────────────────────────────────────────
83
+ ai_leak:
84
+ description: 爆料 / 泄露 / 即将发布
85
+ category: AI 爆料
86
+ command: x-search
87
+ tags: [ai, leak, rumor]
88
+ query: '(spotted OR leaked OR "coming soon") (GPT OR Claude OR Gemini) -is:retweet lang:en'
89
+
90
+ # ── 中文 ────────────────────────────────────────────────────────────────
91
+ ai_zh:
92
+ description: 中文 AI 资讯(发布 / 开源 / 上线)
93
+ category: AI 中文
94
+ command: x-search
95
+ tags: [ai, zh]
96
+ query: '(大模型 OR AI) (发布 OR 开源 OR 上线) lang:zh -is:retweet'
97
+
98
+ # ── 自定义方向示例 ──────────────────────────────────────────────────────
99
+ # 「使用者自己的方向」——按人调整(此处以 AI 语音/视频/媒体工具为例)。
100
+ # 想加自己的方向:复制本条目改 id 与 query 即可(用户注册表更佳,见文件头)。
101
+ ai_media_tools:
102
+ description: 语音/视频/媒体类 AI 工具发布(自定义方向示例)
103
+ category: 示例(自定义方向)
104
+ command: x-search
105
+ tags: [ai, media, tts, example]
106
+ note: 这是「按使用者方向调整」的样例,替换关键词即可变成你自己的方向。
107
+ query: '("text to speech" OR TTS OR "video editing" OR "speech to text") AI (release OR open) -is:retweet lang:en'
108
+
109
+ # ── X 原生搜索语法速查(供改 query 时参考)─────────────────────────────────
110
+ # 去转发 -is:retweet
111
+ # 语言 lang:en / lang:zh
112
+ # 盯人 from:OpenAI (可 OR:from:OpenAI OR from:AnthropicAI)
113
+ # 热度门 min_faves:100 / min_retweets:50
114
+ # 时间窗 since:2026-10-01 until:2026-10-16
115
+ # 排除回复 -filter:replies
116
+ # 只要媒体 filter:media / filter:images / filter:videos
117
+ # 布尔与分组 空格=AND,OR=或,-词=排除,( ) 分组
118
+ # 示例:from:OpenAI -is:retweet min_faves:50 since:2026-10-01
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env python3
2
+ """ab_eval — 交错 A/B 实测(逐对随机化运行顺序,防顺序偏差)。
3
+
4
+ 为什么存在(2026-10-07 教训):非交错基准里,暖连接红利让「后跑的一组」
5
+ 系统性变慢/变快,造出过 37% 的假阳性——本仓历史同类假阳性至少三次
6
+ (roundtrip 第三次假阳性、benchmark 阈值改旗标、10-07 事件化证伪轮)。
7
+ 本工具把正确测量固化成可复用能力:**逐对抛硬币定运行序**,逐对差值出
8
+ 统计量(中位差、A 胜率、精确二项符号检验),单次快慢不作数。
9
+
10
+ 与 ab_eval_p0p1.py 的分工:那是 P0/P1 优化轮的一次性验收脚本(固定场景
11
+ 清单);本工具是通用 A/B 测量器——两条带 {query} 占位符的命令即可对测
12
+ (不同引擎组合、开关、参数档)。
13
+
14
+ 用法:
15
+ python3 scripts/ab_eval.py --pairs 10 \
16
+ --cmd-a "python3 scripts/search.py {query} --json --fields agent" \
17
+ --cmd-b "python3 scripts/search.py {query} --json --fields agent --verify 2" \
18
+ --query "蓝牙耳机推荐"
19
+
20
+ 子进程默认注入 ARGO_USAGE_LOG=0:A/B 用的是合成查询对,不进生产使用账本
21
+ (口径同 10-10 评测隔离原则);--usage-log 保留写入。
22
+
23
+ 输出 stdout 单个 JSON:per_pair(含当对运行序)+ stats。exit 恒 0(测量
24
+ 工具不是门禁);质量守卫字段 count_median_a/b 供人工判断结果数回退。
25
+
26
+ 尾判据(2026-10-15 增补,B-4):stats 里除中位差/符号检验外,还给
27
+ p90_a/b、max_a/b 与 tail_verdict。**中位差不等于改动可用**——中位由最快的
28
+ 那个引擎决定,把并发从 3 提到 6 只可能动尾部(上游限流、连接竞争):中位纹
29
+ 丝不动而 p90/max 翻倍的改动,只看中位会被放行。判据因此是「尾不退化」:
30
+ p90 与 max 各自不超过容忍倍数才 passed。
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import json
36
+ import math
37
+ import os
38
+ import random
39
+ import shlex
40
+ import statistics
41
+ import subprocess
42
+ import sys
43
+ import time
44
+
45
+ _RUN_TIMEOUT_S = 120
46
+
47
+ # 尾退化容忍倍数:p90 允许 +10%、max 允许 +25%(单点极值天然更抖,给宽些)。
48
+ # 判据是「不退化」而不是「必须变快」:并发上调的收益在中位/成功率,不在尾部。
49
+ _DEFAULT_P90_TOL = 1.10
50
+ _DEFAULT_MAX_TOL = 1.25
51
+
52
+
53
+ def percentile_nearest_rank(values: list[float], p: float) -> float | None:
54
+ """最近秩分位数(ceil(p·n) 取第 k 小),与 adaptive.latency_percentile 同口径。
55
+
56
+ 不用 statistics.quantiles:它要求 n≥2 且走插值,n=10 的 p90 会取到第 9 与
57
+ 第 10 个样本之间的值——尾判据要的是「真实观测到的第 90 百分位」,插值会
58
+ 把单点极值摊薄。
59
+ """
60
+ vals = sorted(float(v) for v in values)
61
+ if not vals:
62
+ return None
63
+ k = max(1, min(len(vals), math.ceil(float(p) * len(vals))))
64
+ return vals[k - 1]
65
+
66
+
67
+ def tail_verdict(a_ms: list[float], b_ms: list[float], *,
68
+ p90_tol: float = _DEFAULT_P90_TOL,
69
+ max_tol: float = _DEFAULT_MAX_TOL) -> dict:
70
+ """B 相对 A 的尾延迟判定:p90 与 max 都不超容忍倍数才算 passed。
71
+
72
+ 只比同一批配对的耗时样本(A/B 各自独立取数,不配对相减——尾延迟关心的是
73
+ 「最坏那一档变成了多少」,不是逐对差值的分布)。
74
+ """
75
+ a_p90 = percentile_nearest_rank(a_ms, 0.9)
76
+ b_p90 = percentile_nearest_rank(b_ms, 0.9)
77
+ a_max = max(a_ms) if a_ms else None
78
+ b_max = max(b_ms) if b_ms else None
79
+
80
+ def _ratio(a, b):
81
+ if a is None or b is None or a <= 0:
82
+ return None
83
+ return round(b / a, 3)
84
+
85
+ r90, rmax = _ratio(a_p90, b_p90), _ratio(a_max, b_max)
86
+ reasons = []
87
+ if r90 is not None and r90 > p90_tol:
88
+ reasons.append(f"p90 退化 {r90:.2f}x > {p90_tol:.2f}x")
89
+ if rmax is not None and rmax > max_tol:
90
+ reasons.append(f"max 退化 {rmax:.2f}x > {max_tol:.2f}x")
91
+ if a_p90 is None or b_p90 is None:
92
+ reasons.append("样本不足,无法判定")
93
+ return {
94
+ "p90_a_ms": a_p90, "p90_b_ms": b_p90, "p90_ratio": r90,
95
+ "max_a_ms": a_max, "max_b_ms": b_max, "max_ratio": rmax,
96
+ "p90_tol": p90_tol, "max_tol": max_tol,
97
+ "passed": not reasons,
98
+ "reason": ";".join(reasons) or "尾延迟未退化",
99
+ }
100
+
101
+
102
+ def _run_once(cmd: str, query: str, keep_usage: bool) -> dict:
103
+ # {query} 由本函数负责加引号(shlex.quote):含空格查询此前会把命令拆成
104
+ # 多个 argv,argparse 直接 rc=2——占位符两侧不要再自行加引号。
105
+ substituted = cmd.replace("{query}", shlex.quote(query))
106
+ env = dict(os.environ)
107
+ if not keep_usage:
108
+ env["ARGO_USAGE_LOG"] = "0"
109
+ t0 = time.monotonic()
110
+ try:
111
+ proc = subprocess.run(substituted, shell=True, capture_output=True,
112
+ text=True, encoding="utf-8", errors="replace",
113
+ timeout=_RUN_TIMEOUT_S, env=env)
114
+ except subprocess.TimeoutExpired:
115
+ return {"ok": False, "reason": f"timeout>{_RUN_TIMEOUT_S}s"}
116
+ wall_ms = round((time.monotonic() - t0) * 1000)
117
+ if proc.returncode != 0:
118
+ return {"ok": False, "reason": f"rc={proc.returncode}",
119
+ "stderr_tail": proc.stderr[-200:], "wall_ms": wall_ms}
120
+ try:
121
+ d = json.loads(proc.stdout)
122
+ except json.JSONDecodeError:
123
+ return {"ok": False, "reason": "stdout 非 JSON", "wall_ms": wall_ms,
124
+ "stdout_head": proc.stdout[:120]}
125
+ timing = d.get("timing") or {}
126
+ return {"ok": True, "elapsed_ms": timing.get("elapsed_ms", wall_ms),
127
+ "count": d.get("count"), "wall_ms": wall_ms}
128
+
129
+
130
+ def _sign_test_p(wins: int, losses: int) -> float:
131
+ """精确二项双尾符号检验(无平局计入 n)。"""
132
+ n = wins + losses
133
+ if n == 0:
134
+ return 1.0
135
+ k = min(wins, losses)
136
+ tail = sum(_comb(n, i) for i in range(0, k + 1)) / (2 ** n)
137
+ return min(1.0, 2 * tail)
138
+
139
+
140
+ def _comb(n: int, k: int) -> float:
141
+ from math import comb
142
+ return comb(n, k)
143
+
144
+
145
+ def evaluate(cmd_a: str, cmd_b: str, query: str, pairs: int, seed: int | None,
146
+ keep_usage: bool) -> dict:
147
+ rng = random.Random(seed)
148
+ per_pair = []
149
+ for i in range(pairs):
150
+ a_first = rng.random() < 0.5
151
+ runs = {}
152
+ for label in (("a", "b") if a_first else ("b", "a")):
153
+ cmd = cmd_a if label == "a" else cmd_b
154
+ runs[label] = _run_once(cmd, query, keep_usage)
155
+ per_pair.append({"pair": i + 1, "a_first": a_first,
156
+ "a": runs["a"], "b": runs["b"]})
157
+ diffs, a_wins, b_wins, ties = [], 0, 0, 0
158
+ for r in per_pair:
159
+ a, b = r["a"], r["b"]
160
+ if not (a.get("ok") and b.get("ok")):
161
+ continue # 失败对不进统计,账面保留
162
+ d = a["elapsed_ms"] - b["elapsed_ms"]
163
+ diffs.append(d)
164
+ if d < 0:
165
+ a_wins += 1
166
+ elif d > 0:
167
+ b_wins += 1
168
+ else:
169
+ ties += 1
170
+ counts_a = [r["a"]["count"] for r in per_pair
171
+ if r["a"].get("ok") and r["a"].get("count") is not None]
172
+ counts_b = [r["b"]["count"] for r in per_pair
173
+ if r["b"].get("ok") and r["b"].get("count") is not None]
174
+ elapsed_a = [r["a"]["elapsed_ms"] for r in per_pair
175
+ if r["a"].get("ok") and r["a"].get("elapsed_ms") is not None]
176
+ elapsed_b = [r["b"]["elapsed_ms"] for r in per_pair
177
+ if r["b"].get("ok") and r["b"].get("elapsed_ms") is not None]
178
+ verdict = tail_verdict(elapsed_a, elapsed_b)
179
+ stats = {
180
+ "valid_pairs": len(diffs),
181
+ "failed_pairs": pairs - len(diffs),
182
+ "median_diff_ms": statistics.median(diffs) if diffs else None,
183
+ "mean_diff_ms": round(statistics.fmean(diffs), 1) if diffs else None,
184
+ "a_wins": a_wins, "b_wins": b_wins, "ties": ties,
185
+ "sign_test_p": round(_sign_test_p(a_wins, b_wins), 4),
186
+ "count_median_a": statistics.median(counts_a) if counts_a else None,
187
+ "count_median_b": statistics.median(counts_b) if counts_b else None,
188
+ "p90_a_ms": verdict["p90_a_ms"], "p90_b_ms": verdict["p90_b_ms"],
189
+ "p90_ratio": verdict["p90_ratio"],
190
+ "max_a_ms": verdict["max_a_ms"], "max_b_ms": verdict["max_b_ms"],
191
+ "max_ratio": verdict["max_ratio"],
192
+ # 尾判据与中位判据并列给出:两者结论不一致时以 tail_verdict 为准
193
+ # (中位不动的改动同样可能把 p90/max 拉爆)。
194
+ "tail_verdict": verdict,
195
+ }
196
+ return {"query": query, "pairs": pairs, "seed": seed,
197
+ "order": "逐对随机(a_first 字段记录当对顺序)",
198
+ "per_pair": per_pair, "stats": stats}
199
+
200
+
201
+ def main(argv: list[str] | None = None) -> int:
202
+ ap = argparse.ArgumentParser(description="交错 A/B 实测(随机化逐对顺序)")
203
+ ap.add_argument("--cmd-a", required=True, help="A 变体命令,含 {query} 占位")
204
+ ap.add_argument("--cmd-b", required=True, help="B 变体命令,含 {query} 占位")
205
+ ap.add_argument("--query", required=True)
206
+ ap.add_argument("--pairs", type=int, default=10)
207
+ ap.add_argument("--seed", type=int, default=None,
208
+ help="固定种子可复现当轮运行序")
209
+ ap.add_argument("--usage-log", action="store_true",
210
+ help="子进程查询写入生产使用账本(默认不写)")
211
+ args = ap.parse_args(argv)
212
+ report = evaluate(args.cmd_a, args.cmd_b, args.query, args.pairs,
213
+ args.seed, args.usage_log)
214
+ print(json.dumps(report, ensure_ascii=False))
215
+ return 0
216
+
217
+
218
+ if __name__ == "__main__":
219
+ sys.exit(main())