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,389 @@
1
+ #!/usr/bin/env python3
2
+ """circuit_breaker.py — 引擎熔断 + 查询级负缓存
3
+
4
+ 吸收 Hound 的 circuit-breaker 思路:
5
+ - 连续失败 / 空结果 → 打开熔断,冷却期内跳过该引擎
6
+ - 查询级负缓存:同一 query+engine 短 TTL 内不再打网络
7
+
8
+ 状态持久化:<状态目录>/circuit_breaker.json(由 argo_paths 派生)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import json
15
+ import os
16
+ import threading
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Any, Optional
20
+
21
+ # 本地状态目录唯一来源(env ARGO_STATE_DIR → config cache.db_path 父目录 → 旧路径)
22
+ import argo_paths as _paths
23
+
24
+ STATE_PATH = str(_paths.state_path("circuit_breaker.json"))
25
+
26
+ # 熔断参数
27
+ FAILURE_THRESHOLD = 2 # 连续失败次数
28
+ OPEN_SECONDS = 60 # 熔断冷却
29
+ EMPTY_NEGATIVE_TTL = 45 # 空结果负缓存(秒)
30
+ ERROR_NEGATIVE_TTL = 30 # 错误负缓存(秒)
31
+ HALF_OPEN_PROBE = True # 冷却后允许一次探测
32
+
33
+ # 自适应禁用(v2.7)
34
+ DISABLE_AFTER_OPENS = 3 # 连续 open 达此次数 → 自动禁用(持久跳过)
35
+ DISABLE_COOLDOWN_SECONDS = 3600 # 禁用后 1h 内不自动恢复(避免频繁探测)
36
+
37
+
38
+ class CircuitBreaker:
39
+ """进程内 + 磁盘共享的引擎熔断器。"""
40
+
41
+ def __init__(self, state_path: str = STATE_PATH):
42
+ self._path = state_path
43
+ self._lock = threading.RLock()
44
+ self._engines: dict[str, dict[str, Any]] = {}
45
+ self._neg: dict[str, dict[str, Any]] = {} # key → {expires, status}
46
+ self._last_save_ts: float = 0.0 # 最近一次成功落盘时刻(观测用)
47
+ self._sig: tuple[int, int] | None = None
48
+ self._load()
49
+
50
+ def _file_sig(self) -> tuple[int, int] | None:
51
+ try:
52
+ st = os.stat(self._path)
53
+ return (st.st_mtime_ns, st.st_size)
54
+ except OSError:
55
+ return None
56
+
57
+ def _fresh_locked(self) -> None:
58
+ sig = self._file_sig()
59
+ if sig != self._sig and (sig is not None or self._sig is not None):
60
+ if sig is None:
61
+ self._engines = {}
62
+ self._sig = None
63
+ else:
64
+ self._load()
65
+
66
+ def _load(self) -> None:
67
+ try:
68
+ # 直接 open 而非 os.path.exists + open:后者有 check-then-act 竞态
69
+ # (文件可能在两者之间被删除),且 FileNotFoundError 已被外层捕获。
70
+ # with 关闭句柄:原 open(...).read() 靠 CPython 引用计数兜住,
71
+ # 在别的解释器实现上会泄漏 fd(SIM115)
72
+ with open(self._path, encoding="utf-8") as f:
73
+ data = json.loads(f.read())
74
+ self._engines = data.get("engines") or {}
75
+ self._sig = self._file_sig()
76
+ # 负缓存仅进程内有效,不从磁盘恢复(避免长期脏状态)
77
+ except FileNotFoundError:
78
+ self._sig = None # 文件不存在,保留空态
79
+ except Exception as e:
80
+ # 解析失败**不清空**已有记忆:清空 = 已被判死的源重新进路由、
81
+ # auto-disable 全部重置,而且没有任何提示(用户看到的是「今天
82
+ # 网络又不行了」)。与 quota.py 的处理方式保持一致——那边
83
+ # 明确「损坏不清空,保留旧状态」,这里此前相反:坏文件一出现就悄悄清零。
84
+ # 首次加载(_engines 为空)时保留空态是安全的:没有记忆可丢。
85
+ import sys as _sys
86
+ print(f"[circuit-breaker] 状态文件不可解析({type(e).__name__}),"
87
+ f"保留内存态 {len(self._engines)} 条:{self._path}",
88
+ file=_sys.stderr)
89
+
90
+ # 引擎熔断 ────────────────────────────────────────────────────────────
91
+
92
+ def status(self, engine: str) -> dict[str, Any]:
93
+ """只读查询引擎熔断状态(不推进 half-open 探测、不写入文件)。
94
+
95
+ 供路由层做「配额/熔断感知沉底」:主引擎熔断打开时自动切换到
96
+ 相近备选,正常路径组合集合不变,缓存键不变,速度零影响。
97
+ """
98
+ with self._lock:
99
+ self._fresh_locked()
100
+ st = self._engines.get(engine) or {}
101
+ state = st.get("state", "closed")
102
+ opened_at = float(st.get("opened_at") or 0)
103
+ return {
104
+ "state": state,
105
+ "failures": int(st.get("failures") or 0),
106
+ "opened_at": opened_at,
107
+ "last_kind": st.get("last_kind"),
108
+ "last_attribution": st.get("last_attribution"),
109
+ # 用 time.time()(墙钟)而非 time.monotonic():熔断状态持久化到文件,
110
+ # 跨进程恢复时 opened_at 必须可比较。monotonic 在不同进程间基准不同,
111
+ # 换用会导致跨进程冷却判断错误。时钟回拨风险可控(只会让冷却提前结束)。
112
+ "cooldown_remain": max(0, int(OPEN_SECONDS - (time.time() - opened_at)))
113
+ if state == "open" else 0,
114
+ }
115
+
116
+ def allow(self, engine: str) -> tuple[bool, str]:
117
+ """是否允许调用该引擎。返回 (allowed, reason)。"""
118
+ with self._lock:
119
+ self._fresh_locked()
120
+ st = self._engines.get(engine) or {}
121
+ state = st.get("state", "closed")
122
+ opened_at = float(st.get("opened_at") or 0)
123
+
124
+ # 自适应禁用:disabled 引擎直接拒绝(不再 half-open 探测,省超时)。
125
+ # 但冷却期(DISABLE_COOLDOWN_SECONDS)过后自动转 half_open 探测,
126
+ # 避免引擎恢复后永久无入口(B4)。disabled_at=0 视为旧数据,直接放行探测。
127
+ if state == "disabled":
128
+ disabled_at = float(st.get("disabled_at") or 0)
129
+ if disabled_at == 0 or \
130
+ (time.time() - disabled_at) >= DISABLE_COOLDOWN_SECONDS:
131
+ def _reenable_probe() -> None:
132
+ # 锁内重读后再改:_reload_engines 刚整体替换过
133
+ # self._engines,闭包里的旧 st 会把另一进程刚落盘的
134
+ # 状态盖回去(017b01a 修的那类丢更新在 allow() 的漏网处)。
135
+ fresh = self._engines.get(engine) or {}
136
+ fresh["state"] = "half_open"
137
+ fresh["disabled_at"] = time.time() # 本次探测起点,失败则重新计冷却
138
+ self._engines[engine] = fresh
139
+ self._mutate_locked(_reenable_probe)
140
+ return True, "half_open_reenable"
141
+ return False, "auto_disabled"
142
+
143
+ if state == "open":
144
+ # 冷却期已过 → half-open 探测(除非已连续多次 open 触发自动禁用)
145
+ if time.time() - opened_at >= OPEN_SECONDS:
146
+ opens = int(st.get("opens") or 0)
147
+ disabled_at = float(st.get("disabled_at") or 0)
148
+ # 禁用条件:连续 open 达阈值,且距离上次禁用已超冷却(首次 disabled_at=0 视为可禁用)
149
+ can_disable = opens >= DISABLE_AFTER_OPENS and \
150
+ (disabled_at == 0 or
151
+ (time.time() - disabled_at) >= DISABLE_COOLDOWN_SECONDS)
152
+ if can_disable:
153
+ def _auto_disable() -> None:
154
+ fresh = self._engines.get(engine) or {} # 同上:锁内重读
155
+ fresh["state"] = "disabled"
156
+ fresh["disabled_at"] = time.time()
157
+ self._engines[engine] = fresh
158
+ self._mutate_locked(_auto_disable)
159
+ return False, "auto_disabled"
160
+ # half-open:允许一次探测
161
+ def _half_open() -> None:
162
+ fresh = self._engines.get(engine) or {} # 同上:锁内重读
163
+ fresh["state"] = "half_open"
164
+ self._engines[engine] = fresh
165
+ self._mutate_locked(_half_open)
166
+ return True, "half_open_probe"
167
+ remain = int(OPEN_SECONDS - (time.time() - opened_at))
168
+ return False, f"circuit_open:{remain}s"
169
+ return True, "closed"
170
+
171
+ def _mutate_locked(self, mutator) -> None:
172
+ """跨进程安全的「重读 → 改 → 写入」序列(与 quota._mutate_locked 同形)。
173
+
174
+ 进程内 RLock 只挡得住同进程线程;CLI / MCP server / 评测脚本三者并行时,
175
+ 各自在构造期读了一次旧状态、各自 +1、后写者覆盖前写者,计数直接丢失。
176
+ 这里在文件锁内重读最新磁盘态再改再写,增量才不丢。fail-open:锁层
177
+ 出问题绝不阻断搜索主路径(同 argo_paths.file_lock 的契约)。
178
+ """
179
+ with self._lock:
180
+ applied = False
181
+ try:
182
+ with _paths.file_lock(Path(self._path)):
183
+ self._reload_engines()
184
+ mutator()
185
+ applied = True
186
+ _paths.atomic_write_json(
187
+ Path(self._path),
188
+ {"engines": self._engines, "updated": time.time()},
189
+ indent=None,
190
+ )
191
+ self._sig = self._file_sig()
192
+ self._last_save_ts = time.time()
193
+ except Exception:
194
+ # 锁/写盘失败:若变更尚未落到内存则补一次,保证本进程内行为
195
+ # 正确;已应用过就绝不再跑(否则计数会翻倍)。丢的只是跨进程
196
+ # 可见性,下一次成功写入会带上。
197
+ if not applied:
198
+ try:
199
+ mutator()
200
+ except Exception:
201
+ pass
202
+
203
+ def _reload_engines(self) -> None:
204
+ """重读磁盘上的引擎态(只在文件锁内调用)。解析失败保留内存态。"""
205
+ try:
206
+ # 直接 open 而非 os.path.exists + open:消灭 check-then-act 竞态
207
+ with open(self._path, encoding="utf-8") as f:
208
+ self._engines = (json.loads(f.read()).get("engines") or {})
209
+ self._sig = self._file_sig()
210
+ except FileNotFoundError:
211
+ self._sig = None # 文件不存在,保留内存态
212
+ except Exception:
213
+ pass
214
+
215
+ def record_success(self, engine: str) -> None:
216
+ """引擎成功返回:本已健康 closed 且无失败计数时内存短路(零文件锁、零写盘)。"""
217
+ with self._lock:
218
+ self._fresh_locked()
219
+ st = self._engines.get(engine)
220
+ if not st or (
221
+ st.get("state", "closed") == "closed"
222
+ and not st.get("failures")
223
+ and not st.get("opens")
224
+ and not st.get("empty_streak")
225
+ ):
226
+ if st is None:
227
+ self._engines[engine] = {"state": "closed", "failures": 0, "opens": 0}
228
+ return
229
+
230
+ def _m() -> None:
231
+ self._engines[engine] = {
232
+ "state": "closed",
233
+ "failures": 0,
234
+ "opens": 0, # 重置连续 open 计数
235
+ }
236
+ self._mutate_locked(_m)
237
+
238
+ def record_failure(self, engine: str, kind: str = "error",
239
+ attribution: dict[str, Any] | None = None) -> None:
240
+ """kind: error | timeout | empty | blocked | rate-limited
241
+
242
+ attribution:可选的失败归因(失败现场记录,含 category/reason/detail)。
243
+ kind 是熔断策略用的粗粒度标签,attribution 是给「为什么坏」用的细粒度
244
+ 事实——两者维度不同,不能互相推导(把 kind 当响应文本再归类,只能得到
245
+ unknown)。持久化后 `--list-engines --detail` 才能显示真实原因。
246
+
247
+ empty 语义是「该查询无结果」——查询级信号,不是引擎级故障。
248
+ 它仍可触发 60s 短冷却(防止重复打无效源),但不累计 opens,
249
+ 因此永远不会驱动 auto-disable(否则聚合引擎/local 源等易空
250
+ 引擎会被误判为持续故障而静默禁用)。
251
+
252
+ blocked / rate-limited 语义是「源站行为,不是引擎坏了」——前者是
253
+ 请求在到达内容前被拦截(反爬/指纹/挑战页),后者是源端限流。
254
+ 同样只做 60s 短冷却、不累计 opens:把封锁/限流当引擎故障累计到
255
+ auto-disable 是封错人——引擎实现没有问题,换客户端形态、
256
+ 等冷却或等源站策略变化即可恢复。
257
+ """
258
+ self._mutate_locked(lambda: self._mutate_failure(engine, kind, attribution))
259
+
260
+ def _mutate_failure(self, engine: str, kind: str,
261
+ attribution: dict[str, Any] | None) -> None:
262
+ """`record_failure` 的状态变更体(只改内存,不落盘)。
263
+
264
+ 拆出来是为了让整段「重读 → 改 → 写」跑在同一个文件锁内:进程内
265
+ RLock 只挡同进程线程,CLI / MCP server / 评测脚本并行时各自持有
266
+ 构造期读到的旧快照、各自 +1、后写者覆盖前写者。实测 4 进程 × 20 次
267
+ record_failure 丢 48%(6 进程丢 35%,8 进程丢 44%),丢掉的正是
268
+ `failures` / `opens` 增量——`DISABLE_AFTER_OPENS` 永远攒不够,
269
+ 真坏掉的引擎于是持续被派发。这与 quota._mutate_locked 同一处理。
270
+ """
271
+ with self._lock:
272
+ st = self._engines.get(engine) or {"failures": 0, "state": "closed"}
273
+ drives_opens = kind not in ("empty", "blocked", "rate-limited")
274
+ # empty 权重低:两次 empty 才算一次 failure 贡献
275
+ if kind == "empty":
276
+ st["empty_streak"] = int(st.get("empty_streak") or 0) + 1
277
+ if st["empty_streak"] < 2:
278
+ self._engines[engine] = st
279
+ return
280
+ st["empty_streak"] = 0
281
+ st["failures"] = int(st.get("failures") or 0) + 1
282
+ st["last_fail"] = time.time()
283
+ st["last_kind"] = kind
284
+ if attribution:
285
+ st["last_attribution"] = dict(attribution)
286
+ # empty(无结果)与 blocked(被拦截)都是查询级/源站级信号,不驱动
287
+ # open 熔断:否则「空结果→open 60s→half_open→再 open」无效 churn,
288
+ # 还占主位阻塞 6s。仅 error/timeout 驱动稳定 state 切换(空结果由
289
+ # 负缓存短 TTL 保底)。2026-08 修复;blocked 2026-09 并入同语义。
290
+ if drives_opens and (st["failures"] >= FAILURE_THRESHOLD
291
+ or st.get("state") == "half_open"):
292
+ st["state"] = "open"
293
+ st["opened_at"] = time.time()
294
+ # 连续 open 计数:仅 error/timeout 计入(引擎级故障),
295
+ # empty 不计入,避免「查询无结果」被误判为引擎持续故障
296
+ st["opens"] = int(st.get("opens") or 0) + 1
297
+ self._engines[engine] = st
298
+
299
+ def reenable(self, engine: str) -> None:
300
+ """外部主动恢复(用户测试通过 / 新环境确认)。"""
301
+
302
+ def _m() -> None:
303
+ self._engines[engine] = {
304
+ "state": "closed", "failures": 0, "opens": 0,
305
+ "last_ok": time.time(), "reenabled_at": time.time(),
306
+ }
307
+
308
+ self._mutate_locked(_m)
309
+
310
+ def record_note(self, engine: str,
311
+ attribution: dict[str, Any] | None = None) -> None:
312
+ """只记录归因(「为什么不行」),完全不动熔断计数与状态。
313
+
314
+ 用于配额耗尽这类**既不是引擎故障、也不该算失败**的场景:配额状态机
315
+ 负责「停用多久」,这里只负责把原因留在可观测面上。此前 record_failure
316
+ 是唯一写归因的入口,导致 quota-exhausted 分支为了让配额接管而丢弃
317
+ 归因,博查 403 套餐额度不足永远显示 ready——P1 的原始动机场景。
318
+ """
319
+ if not attribution:
320
+ return
321
+
322
+ def _m() -> None:
323
+ st = self._engines.get(engine) or {"failures": 0, "state": "closed"}
324
+ st["last_attribution"] = dict(attribution)
325
+ self._engines[engine] = st
326
+
327
+ self._mutate_locked(_m)
328
+
329
+ def auto_disabled(self) -> list[str]:
330
+ """返回所有处于自动禁用状态的引擎。"""
331
+ with self._lock:
332
+ self._fresh_locked()
333
+ return [e for e, st in self._engines.items()
334
+ if st.get("state") == "disabled"]
335
+
336
+ # ── 查询级负缓存 ────────────────────────────────────────────────────────
337
+
338
+ @staticmethod
339
+ def _neg_key(query: str, engine: str) -> str:
340
+ raw = f"neg|{query}|{engine}"
341
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:24]
342
+
343
+ def set_negative(self, query: str, engine: str, status: str = "no-results",
344
+ ttl: int | None = None) -> None:
345
+ ttl = ttl if ttl is not None else (
346
+ EMPTY_NEGATIVE_TTL if status == "no-results" else ERROR_NEGATIVE_TTL
347
+ )
348
+ key = self._neg_key(query, engine)
349
+ with self._lock:
350
+ self._neg[key] = {
351
+ "expires": time.time() + ttl,
352
+ "status": status,
353
+ "engine": engine,
354
+ }
355
+
356
+ def get_negative(self, query: str, engine: str) -> Optional[dict[str, Any]]:
357
+ key = self._neg_key(query, engine)
358
+ with self._lock:
359
+ hit = self._neg.get(key)
360
+ if not hit:
361
+ return None
362
+ if time.time() >= float(hit.get("expires") or 0):
363
+ self._neg.pop(key, None)
364
+ return None
365
+ return hit
366
+
367
+ def clear_negative(self, query: str, engine: str) -> None:
368
+ key = self._neg_key(query, engine)
369
+ with self._lock:
370
+ self._neg.pop(key, None)
371
+
372
+ def stats(self) -> dict[str, Any]:
373
+ with self._lock:
374
+ open_engines = [e for e, s in self._engines.items() if s.get("state") == "open"]
375
+ return {
376
+ "open_engines": open_engines,
377
+ "tracked": len(self._engines),
378
+ "neg_entries": len(self._neg),
379
+ }
380
+
381
+
382
+ _breaker: CircuitBreaker | None = None
383
+
384
+
385
+ def get_breaker() -> CircuitBreaker:
386
+ global _breaker
387
+ if _breaker is None:
388
+ _breaker = CircuitBreaker()
389
+ return _breaker