argo-search 1.0.1 → 2.9.8

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 (472) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +735 -0
  3. package/README.es.md +777 -0
  4. package/README.ja.md +777 -0
  5. package/README.ko.md +777 -0
  6. package/README.md +778 -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/why-better.svg +104 -0
  12. package/assets/readme/workflow.svg +83 -0
  13. package/backends/domain_profiles.json +2356 -96
  14. package/backends/engine_registry.yaml +3428 -503
  15. package/backends/query_synonyms_cn.json +35 -0
  16. package/backends/quota_profiles.json +2488 -160
  17. package/backends/source_types_cn.json +109 -0
  18. package/backends/text_lexicon.json +44 -0
  19. package/bin/argo +772 -0
  20. package/bin/argo.js +102 -39
  21. package/config.yaml +5139 -367
  22. package/cordis.patch.yml +21 -0
  23. package/data/countries.json +40 -0
  24. package/data/lang_matrix/lang_capability.json +842 -0
  25. package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
  26. package/data/regions_cn.json +1 -0
  27. package/docs/ADDING_NEW_ENGINE.md +184 -0
  28. package/docs/ARGO_INTRO.md +124 -0
  29. package/docs/DATA_SOURCE_GAPS.md +81 -0
  30. package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
  31. package/docs/ENGINE_CATALOG.md +575 -0
  32. package/docs/MCP_SETUP.md +108 -0
  33. package/docs/PLATFORM_PATHS.md +111 -0
  34. package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
  35. package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
  36. package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
  37. package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
  38. package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
  39. package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
  40. package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
  41. package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
  42. package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
  43. package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
  44. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  45. package/docs/SEARCH_ARCHIVE.md +115 -0
  46. package/docs/assets/architecture.svg +104 -0
  47. package/docs/assets/hero.svg +98 -0
  48. package/docs/engines/bangumi.md +27 -0
  49. package/docs/engines/biorxiv.md +27 -0
  50. package/docs/engines/clawhub.md +27 -0
  51. package/docs/engines/crt_sh.md +27 -0
  52. package/docs/engines/datacite.md +27 -0
  53. package/docs/engines/deps_dev.md +27 -0
  54. package/docs/engines/douban_movie.md +27 -0
  55. package/docs/engines/endoflife.md +27 -0
  56. package/docs/engines/flk_law.md +27 -0
  57. package/docs/engines/fxtwitter.md +27 -0
  58. package/docs/engines/google_news.md +27 -0
  59. package/docs/engines/gov_regulations.md +27 -0
  60. package/docs/engines/guqian.md +27 -0
  61. package/docs/engines/hackernews.md +27 -0
  62. package/docs/engines/iplant.md +27 -0
  63. package/docs/engines/keenable.md +27 -0
  64. package/docs/engines/met_museum.md +27 -0
  65. package/docs/engines/nasa_images.md +27 -0
  66. package/docs/engines/netease_music.md +27 -0
  67. package/docs/engines/npm.md +27 -0
  68. package/docs/engines/nvd.md +27 -0
  69. package/docs/engines/openreview.md +27 -0
  70. package/docs/engines/openstd.md +27 -0
  71. package/docs/engines/parallel_free.md +27 -0
  72. package/docs/engines/people_daily.md +27 -0
  73. package/docs/engines/realtime_index.md +27 -0
  74. package/docs/engines/sec_edgar.md +27 -0
  75. package/docs/engines/seltz.md +27 -0
  76. package/docs/engines/skillsmp.md +27 -0
  77. package/docs/engines/sspai.md +27 -0
  78. package/docs/engines/std_samr.md +27 -0
  79. package/docs/engines/train.md +27 -0
  80. package/docs/engines/un_comtrade.md +27 -0
  81. package/docs/engines/wikisource.md +27 -0
  82. package/docs/engines/zdic.md +27 -0
  83. package/docs/engines/zenodo.md +27 -0
  84. package/docs/engines/zhihu_hot_app.md +27 -0
  85. package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
  86. package/engines/README.md +10 -0
  87. package/engines/_template_cli.yaml +39 -0
  88. package/engines/_template_http.yaml +37 -0
  89. package/engines/plugins/_template_plugin.py +50 -0
  90. package/engines/specs/_cisa_kev.yaml +54 -0
  91. package/engines/specs/_osv.yaml +63 -0
  92. package/engines/specs/aljazeera.yaml +43 -0
  93. package/engines/specs/antara.yaml +43 -0
  94. package/engines/specs/artic.yaml +20 -0
  95. package/engines/specs/aviation_weather.yaml +39 -0
  96. package/engines/specs/bangumi.yaml +21 -0
  97. package/engines/specs/biorxiv.yaml +24 -0
  98. package/engines/specs/carbon_intensity.yaml +32 -0
  99. package/engines/specs/cfets.yaml +41 -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/defillama.yaml +38 -0
  113. package/engines/specs/deps_dev.yaml +21 -0
  114. package/engines/specs/digup.yaml +36 -0
  115. package/engines/specs/doi.yaml +39 -0
  116. package/engines/specs/douban_movie.yaml +22 -0
  117. package/engines/specs/dw_news.yaml +47 -0
  118. package/engines/specs/egov_law.yaml +21 -0
  119. package/engines/specs/elpais.yaml +43 -0
  120. package/engines/specs/endoflife.yaml +21 -0
  121. package/engines/specs/energy_charts.yaml +21 -0
  122. package/engines/specs/factcheck_org.yaml +52 -0
  123. package/engines/specs/faz.yaml +43 -0
  124. package/engines/specs/federal_register.yaml +57 -0
  125. package/engines/specs/firecrawl.yaml +53 -0
  126. package/engines/specs/flk_law.yaml +24 -0
  127. package/engines/specs/folha.yaml +43 -0
  128. package/engines/specs/france24.yaml +44 -0
  129. package/engines/specs/full_fact.yaml +47 -0
  130. package/engines/specs/fxtwitter.yaml +36 -0
  131. package/engines/specs/gbfs_nyc.yaml +32 -0
  132. package/engines/specs/gdacs.yaml +21 -0
  133. package/engines/specs/gdelt.yaml +64 -0
  134. package/engines/specs/gleif.yaml +55 -0
  135. package/engines/specs/google_news.yaml +22 -0
  136. package/engines/specs/gov_regulations.yaml +38 -0
  137. package/engines/specs/guardian_rss.yaml +59 -0
  138. package/engines/specs/hkex.yaml +40 -0
  139. package/engines/specs/hurriyet.yaml +43 -0
  140. package/engines/specs/iplant.yaml +25 -0
  141. package/engines/specs/jikan.yaml +24 -0
  142. package/engines/specs/jolpica.yaml +20 -0
  143. package/engines/specs/k10plus.yaml +20 -0
  144. package/engines/specs/keenable.yaml +44 -0
  145. package/engines/specs/keenable_free.yaml +36 -0
  146. package/engines/specs/lefigaro.yaml +43 -0
  147. package/engines/specs/listenbrainz.yaml +19 -0
  148. package/engines/specs/matichon.yaml +43 -0
  149. package/engines/specs/met_museum.yaml +21 -0
  150. package/engines/specs/nasa_images.yaml +45 -0
  151. package/engines/specs/netease_music.yaml +38 -0
  152. package/engines/specs/nhk.yaml +43 -0
  153. package/engines/specs/nhtsa_vpic.yaml +20 -0
  154. package/engines/specs/noaa_swpc.yaml +20 -0
  155. package/engines/specs/nvd.yaml +38 -0
  156. package/engines/specs/obis.yaml +20 -0
  157. package/engines/specs/opencitations.yaml +52 -0
  158. package/engines/specs/openf1.yaml +21 -0
  159. package/engines/specs/openfoodfacts.yaml +45 -0
  160. package/engines/specs/openligadb.yaml +20 -0
  161. package/engines/specs/openreview.yaml +37 -0
  162. package/engines/specs/openstd.yaml +21 -0
  163. package/engines/specs/people_daily.yaml +22 -0
  164. package/engines/specs/realtime_index.yaml +34 -0
  165. package/engines/specs/ror.yaml +20 -0
  166. package/engines/specs/satnogs.yaml +20 -0
  167. package/engines/specs/sec_edgar.yaml +46 -0
  168. package/engines/specs/shfe.yaml +39 -0
  169. package/engines/specs/skillsmp.yaml +50 -0
  170. package/engines/specs/sogou_weixin_browser.yaml +30 -0
  171. package/engines/specs/soilgrids.yaml +25 -0
  172. package/engines/specs/sspai.yaml +36 -0
  173. package/engines/specs/stackexchange.yaml +46 -0
  174. package/engines/specs/std_samr.yaml +21 -0
  175. package/engines/specs/tass.yaml +43 -0
  176. package/engines/specs/tavily_free.yaml +56 -0
  177. package/engines/specs/tle_mirror.yaml +21 -0
  178. package/engines/specs/train.yaml +37 -0
  179. package/engines/specs/tvmaze.yaml +20 -0
  180. package/engines/specs/twitter_syndication.yaml +31 -0
  181. package/engines/specs/un_comtrade.yaml +23 -0
  182. package/engines/specs/unpaywall.yaml +54 -0
  183. package/engines/specs/vnpress.yaml +43 -0
  184. package/engines/specs/weather.yaml +32 -0
  185. package/engines/specs/who_don.yaml +21 -0
  186. package/engines/specs/who_gho.yaml +20 -0
  187. package/engines/specs/wikimedia_commons.yaml +34 -0
  188. package/engines/specs/wikisource.yaml +22 -0
  189. package/engines/specs/worms.yaml +19 -0
  190. package/engines/specs/x_trending.yaml +34 -0
  191. package/engines/specs/yna.yaml +43 -0
  192. package/engines/specs/ynet.yaml +43 -0
  193. package/engines/specs/yuanbao.yaml +93 -0
  194. package/engines/specs/zbmath.yaml +40 -0
  195. package/engines/specs/zdic.yaml +22 -0
  196. package/engines/specs/zenodo.yaml +27 -0
  197. package/engines/specs/zhihu_hot_app.yaml +35 -0
  198. package/mcp/clients.yaml +106 -0
  199. package/package.json +55 -12
  200. package/packages/dsh-plugin/README.md +152 -0
  201. package/packages/dsh-plugin/cordis.patch.yml +48 -0
  202. package/packages/dsh-plugin/dsh/client.js +1533 -0
  203. package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
  204. package/packages/dsh-plugin/dsh/index.js +527 -0
  205. package/packages/dsh-plugin/dsh/native-tools.mjs +749 -0
  206. package/packages/dsh-plugin/dsh/runner.js +666 -0
  207. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  208. package/packages/dsh-plugin/package.json +38 -0
  209. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  210. package/packages/dsh-plugin/test/client.test.js +406 -0
  211. package/packages/dsh-plugin/test/config.test.js +30 -0
  212. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  213. package/packages/dsh-plugin/test/index.test.js +1382 -0
  214. package/references/query-templates.md +96 -0
  215. package/references/query-templates.yaml +118 -0
  216. package/scripts/ab_eval.py +219 -0
  217. package/scripts/ab_eval_p0p1.py +266 -0
  218. package/scripts/adaptive.py +294 -70
  219. package/scripts/answer.py +154 -0
  220. package/scripts/archive_run.py +591 -0
  221. package/scripts/argo_engine_registry.py +172 -0
  222. package/scripts/argo_paths.py +728 -0
  223. package/scripts/article.py +136 -0
  224. package/scripts/article_meta.py +131 -0
  225. package/scripts/arxiv_source.py +201 -0
  226. package/scripts/badcase_ledger.py +252 -0
  227. package/scripts/batch_probe.py +241 -0
  228. package/scripts/benchmark.py +19 -0
  229. package/scripts/bounded_run.py +144 -0
  230. package/scripts/browser_auth.py +323 -0
  231. package/scripts/budget.py +247 -0
  232. package/scripts/budget_cli.py +93 -0
  233. package/scripts/build_release_assets.sh +65 -0
  234. package/scripts/burstiness_detector.py +147 -0
  235. package/scripts/cache.py +1066 -67
  236. package/scripts/cache_guard.py +271 -0
  237. package/scripts/cache_key_vdom.py +56 -0
  238. package/scripts/cache_similarity.py +170 -0
  239. package/scripts/caliber_isolation.py +195 -0
  240. package/scripts/calibrate_lowquality.py +210 -0
  241. package/scripts/candidate_envelope.py +366 -0
  242. package/scripts/chrome_cdp.py +871 -0
  243. package/scripts/ci_drift_gate.py +90 -0
  244. package/scripts/circuit_breaker.py +389 -0
  245. package/scripts/citations.py +334 -0
  246. package/scripts/cite_cli.py +59 -0
  247. package/scripts/cjk_tokens.py +95 -0
  248. package/scripts/clarify.py +47 -24
  249. package/scripts/cli_io.py +240 -0
  250. package/scripts/config.py +683 -45
  251. package/scripts/config_cache.py +402 -0
  252. package/scripts/conn_pool.py +220 -0
  253. package/scripts/content_boundary.py +46 -0
  254. package/scripts/content_security.py +685 -0
  255. package/scripts/content_signals.py +751 -0
  256. package/scripts/crawl.py +74 -35
  257. package/scripts/cross_lingual_trace.py +293 -0
  258. package/scripts/daemon.py +866 -0
  259. package/scripts/digup_bridge.py +214 -0
  260. package/scripts/dispatch_result.py +98 -0
  261. package/scripts/engine_admission.py +366 -0
  262. package/scripts/engine_dispatch.py +982 -0
  263. package/scripts/engine_env.py +539 -0
  264. package/scripts/engine_failure.py +411 -0
  265. package/scripts/engine_families.py +738 -0
  266. package/scripts/engine_policy.py +218 -0
  267. package/scripts/engine_requires.py +190 -0
  268. package/scripts/engine_status.py +322 -0
  269. package/scripts/engine_validate.py +638 -0
  270. package/scripts/engines.py +799 -294
  271. package/scripts/engines_base.py +1521 -0
  272. package/scripts/engines_builders.py +310 -0
  273. package/scripts/engines_builders_batch10.py +204 -0
  274. package/scripts/engines_builders_batch11.py +174 -0
  275. package/scripts/engines_builders_batch12.py +103 -0
  276. package/scripts/engines_builders_batch13.py +340 -0
  277. package/scripts/engines_builders_batch14.py +656 -0
  278. package/scripts/engines_builders_batch15.py +249 -0
  279. package/scripts/engines_builders_batch9.py +1468 -0
  280. package/scripts/engines_builders_cn.py +2283 -0
  281. package/scripts/engines_builders_data.py +2923 -0
  282. package/scripts/engines_builders_data_macro.py +775 -0
  283. package/scripts/engines_builders_feeds.py +255 -0
  284. package/scripts/engines_builders_intl.py +1239 -0
  285. package/scripts/engines_builders_keyless.py +111 -0
  286. package/scripts/engines_builders_search.py +440 -0
  287. package/scripts/engines_builders_tech.py +1359 -0
  288. package/scripts/evidence.py +620 -166
  289. package/scripts/evidence_loop.py +624 -0
  290. package/scripts/evidence_tier.py +298 -0
  291. package/scripts/except_sets.py +25 -0
  292. package/scripts/extract.py +55 -16
  293. package/scripts/fact_align.py +207 -0
  294. package/scripts/fact_crosscheck.py +230 -0
  295. package/scripts/fetch.py +25 -12
  296. package/scripts/fetch_browser.py +87 -0
  297. package/scripts/fetch_jsrun.py +122 -0
  298. package/scripts/fetch_quality.py +225 -0
  299. package/scripts/fetch_render_tinyfish.py +117 -0
  300. package/scripts/fetch_v3.py +1804 -0
  301. package/scripts/focus_extract.py +350 -0
  302. package/scripts/fulltext_store.py +169 -0
  303. package/scripts/fxtwitter_api.py +325 -0
  304. package/scripts/gen_engine_catalog.py +364 -0
  305. package/scripts/gen_native_tools.py +91 -0
  306. package/scripts/gen_routing_snapshot.py +195 -0
  307. package/scripts/health_check.py +140 -402
  308. package/scripts/health_probe.py +23 -8
  309. package/scripts/hot_state.py +134 -0
  310. package/scripts/http_client.py +1053 -0
  311. package/scripts/image_ops.py +430 -0
  312. package/scripts/image_vision/vision_probe.swift +126 -0
  313. package/scripts/install.ps1 +166 -0
  314. package/scripts/install.sh +221 -0
  315. package/scripts/job.py +1186 -0
  316. package/scripts/jump_resolver.py +235 -0
  317. package/scripts/lang_capability.py +150 -0
  318. package/scripts/lang_detect.py +289 -0
  319. package/scripts/lang_matrix_probe.py +329 -0
  320. package/scripts/lang_pref.py +311 -0
  321. package/scripts/link_source.py +340 -0
  322. package/scripts/local_image.py +970 -0
  323. package/scripts/local_seek.py +402 -0
  324. package/scripts/macro_countries.py +97 -0
  325. package/scripts/matrix_search_eval.py +1187 -0
  326. package/scripts/mcp_compact.py +234 -0
  327. package/scripts/mcp_diag.py +8 -3
  328. package/scripts/mcp_handlers.py +492 -0
  329. package/scripts/mcp_handlers_surface.py +157 -0
  330. package/scripts/mcp_launch.sh +124 -0
  331. package/scripts/mcp_local.py +241 -0
  332. package/scripts/mcp_server.py +125 -419
  333. package/scripts/mcp_setup.py +431 -0
  334. package/scripts/mcp_tools.py +331 -0
  335. package/scripts/mcp_transport.py +191 -0
  336. package/scripts/mcp_warm.py +127 -0
  337. package/scripts/min_version_scan.py +335 -0
  338. package/scripts/net_proxy.py +258 -0
  339. package/scripts/network_aware.py +152 -0
  340. package/scripts/output_transparency.py +200 -0
  341. package/scripts/paid_policy.py +98 -0
  342. package/scripts/paper.py +274 -0
  343. package/scripts/pdf_extract.py +518 -0
  344. package/scripts/phrase_filter.py +66 -0
  345. package/scripts/plan.py +422 -0
  346. package/scripts/postprocess_signals.py +150 -0
  347. package/scripts/pre-commit +48 -0
  348. package/scripts/query_enhance.py +157 -0
  349. package/scripts/query_operators.py +196 -0
  350. package/scripts/query_rewriter.py +116 -7
  351. package/scripts/query_signals.py +300 -0
  352. package/scripts/query_templates.py +167 -0
  353. package/scripts/query_understanding.py +411 -0
  354. package/scripts/query_variants.py +298 -0
  355. package/scripts/quota.py +633 -76
  356. package/scripts/rank_signals.py +775 -0
  357. package/scripts/ranking_eval.py +319 -0
  358. package/scripts/readability_extract.py +433 -0
  359. package/scripts/recompute.py +415 -0
  360. package/scripts/recovery.py +616 -0
  361. package/scripts/recovery_l0.py +160 -0
  362. package/scripts/recovery_runner.py +150 -0
  363. package/scripts/redskill/redskill_engine.py +332 -0
  364. package/scripts/regression_p0p1.py +318 -0
  365. package/scripts/replay_eval.py +658 -0
  366. package/scripts/rerank_tfidf.py +225 -0
  367. package/scripts/research.py +607 -403
  368. package/scripts/research_cli.py +361 -0
  369. package/scripts/research_dossier.py +492 -0
  370. package/scripts/research_expand.py +169 -0
  371. package/scripts/research_gates.py +272 -0
  372. package/scripts/research_report.py +209 -0
  373. package/scripts/research_strategy.py +37 -0
  374. package/scripts/research_work_packages.py +195 -0
  375. package/scripts/result_lang.py +311 -0
  376. package/scripts/retry_policy.py +76 -0
  377. package/scripts/robots_guard.py +259 -0
  378. package/scripts/route.py +921 -254
  379. package/scripts/route_bangs.py +40 -0
  380. package/scripts/route_cache.py +243 -0
  381. package/scripts/route_combo.py +917 -0
  382. package/scripts/route_domains.py +341 -0
  383. package/scripts/route_lang.py +605 -0
  384. package/scripts/route_log.py +63 -0
  385. package/scripts/route_policy.py +250 -0
  386. package/scripts/rrf_classic.py +66 -0
  387. package/scripts/screenshot.py +73 -0
  388. package/scripts/search.py +886 -406
  389. package/scripts/search_attribution.py +185 -0
  390. package/scripts/search_benchmark.py +407 -0
  391. package/scripts/search_cli.py +640 -0
  392. package/scripts/search_entry.py +395 -0
  393. package/scripts/search_output.py +690 -0
  394. package/scripts/search_pipeline.py +926 -0
  395. package/scripts/search_rank.py +991 -0
  396. package/scripts/search_types.py +129 -16
  397. package/scripts/seek_locator.py +68 -0
  398. package/scripts/semantic_evidence.py +391 -0
  399. package/scripts/serp_guard.py +336 -0
  400. package/scripts/serp_kill_log.py +74 -0
  401. package/scripts/single_flight.py +120 -0
  402. package/scripts/social_engines/__init__.py +10 -0
  403. package/scripts/social_engines/bilibili_engine.py +131 -0
  404. package/scripts/social_engines/hackernews_engine.py +113 -0
  405. package/scripts/social_engines/reddit_engine.py +172 -0
  406. package/scripts/social_engines/twitter_engine.py +397 -0
  407. package/scripts/social_engines/v2ex_engine.py +101 -0
  408. package/scripts/social_engines/weibo_engine.py +128 -0
  409. package/scripts/social_engines/xiaohongshu_engine.py +137 -0
  410. package/scripts/social_engines/zhihu_engine.py +139 -0
  411. package/scripts/social_research.py +201 -0
  412. package/scripts/sogou_weixin_browser.py +318 -0
  413. package/scripts/source_ledger.py +249 -0
  414. package/scripts/stage_timing.py +58 -0
  415. package/scripts/stats_cli.py +107 -0
  416. package/scripts/straggler_policy.py +60 -0
  417. package/scripts/stylometry_detector.py +231 -0
  418. package/scripts/sync_backends.py +353 -0
  419. package/scripts/sync_installs.py +29 -0
  420. package/scripts/template_cli.py +127 -0
  421. package/scripts/text_lexicon.py +72 -0
  422. package/scripts/tfidf_router.py +109 -37
  423. package/scripts/time_utils.py +233 -0
  424. package/scripts/topic_research_profiles.py +643 -0
  425. package/scripts/train.py +737 -0
  426. package/scripts/tweet.py +596 -0
  427. package/scripts/url_canon.py +217 -0
  428. package/scripts/url_optimize.py +56 -0
  429. package/scripts/url_safety.py +236 -0
  430. package/scripts/usage_log.py +116 -0
  431. package/scripts/usage_log_clean.py +113 -0
  432. package/scripts/v2ex_nodes.py +311 -0
  433. package/scripts/variant_recall.py +188 -0
  434. package/scripts/watch.py +205 -0
  435. package/scripts/wechat_article.py +258 -0
  436. package/scripts/wide_research.py +447 -0
  437. package/scripts/wx.py +349 -0
  438. package/scripts/x_article_fetch.py +141 -0
  439. package/scripts/x_search.py +234 -0
  440. package/scripts/x_trending.py +189 -0
  441. package/scripts/yaml_load.py +67 -0
  442. package/scripts/zhihu_quota.py +139 -0
  443. package/sub-skills/ego-search/SKILL.md +253 -0
  444. package/sub-skills/ego-search/scripts/ego_search.py +800 -0
  445. package/sub-skills/ego-search/scripts/merge.py +226 -0
  446. package/sub-skills/ego-search/scripts/quality.py +64 -0
  447. package/sub-skills/ego-search/scripts/runtime.py +172 -0
  448. package/sub-skills/ego-search/scripts/safety.py +109 -0
  449. package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
  450. package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
  451. package/sub-skills/js-run/SKILL.md +67 -0
  452. package/sub-skills/js-run/scripts/jsrun.py +112 -0
  453. package/sub-skills/js-run/scripts/shim.js +188 -0
  454. package/sub-skills/local-search/SKILL.md +50 -53
  455. package/sub-skills/local-search/bm25.py +186 -0
  456. package/sub-skills/local-search/cli_runner.py +331 -0
  457. package/sub-skills/local-search/config.yaml +152 -55
  458. package/sub-skills/local-search/engine_registry.py +91 -26
  459. package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
  460. package/sub-skills/local-search/local_search_adapter.py +13 -2
  461. package/sub-skills/local-search/parse_maps.yaml +40 -42
  462. package/sub-skills/local-search/parsers.py +328 -0
  463. package/sub-skills/local-search/search_v3.py +716 -242
  464. package/sub-skills/local-search/smart_router.py +47 -7
  465. package/sub-skills/local-seek/SKILL.md +110 -0
  466. package/sub-skills/local-seek/config/domains.yaml +20 -0
  467. package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
  468. package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
  469. package/sub-skills/local-seek/scripts/seek.py +1465 -0
  470. package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
  471. package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
  472. package/sub-skills/local-seek/scripts/seek_rank.py +197 -0
package/scripts/crawl.py CHANGED
@@ -1,63 +1,94 @@
1
1
  #!/usr/bin/env python3
2
- """crawl.py — 站点级爬取"""
3
- import json, re, sys, time
4
- from concurrent.futures import ThreadPoolExecutor, as_completed
2
+ """crawl.py — 站点级爬取(fetch_v3 降级链:增强 HTTP → TLS 指纹 → Wayback)"""
3
+ import re, time
5
4
  from urllib.parse import urljoin, urlparse
6
- from fetch import fetch_page
5
+ from bounded_run import run_bounded, TaskError
6
+ from cli_io import dumps
7
+ # fetch_v3 而非裸 urllib fetch:UA 轮换 + Cookie 积累 + curl_cffi 指纹伪造,
8
+ # 反爬站(CF 保护等)从必然失败到大概率成功。批量场景禁浏览器降级
9
+ # (BFS 可能爬几十页,每页启动 Chrome CDP 会拖慢整体)。
10
+ from fetch_v3 import fetch_page_v3 as _fetch_page_v3
11
+
12
+
13
+ def _crawl_fetch(url: str, max_chars: int, timeout: int, raw: bool = True) -> dict:
14
+ """批量爬取用轻量降级链(不升浏览器):HTTP → TLS 指纹 → Wayback。
15
+
16
+ raw 默认 True:爬取需要原始 HTML 提取链接,且 fetch_v3 缓存不存 html,
17
+ 必须绕过缓存才能拿到完整页面。通过模块级 fetch_page 转发,
18
+ 测试可 monkeypatch crawl.fetch_page 注入假抓取。
19
+ """
20
+ return fetch_page(url, max_chars=max_chars, timeout=timeout, raw=raw)
21
+
22
+
23
+ # 默认实现指向 fetch_v3 的兼容入口;测试可整体替换此名字
24
+ fetch_page = _fetch_page_v3
7
25
 
8
26
  def crawl_sitemap(url, max_pages=20, timeout=10):
9
27
  """从 sitemap.xml 爬取"""
28
+ t0 = time.monotonic()
10
29
  sitemap_url = urljoin(url, '/sitemap.xml')
11
- result = fetch_page(sitemap_url, max_chars=50000, timeout=timeout, raw=True)
30
+ result = _crawl_fetch(sitemap_url, 50000, timeout)
12
31
  if not result['success']:
13
32
  # 尝试 robots.txt 指定的 sitemap
14
33
  robots_url = urljoin(url, '/robots.txt')
15
- robots_result = fetch_page(robots_url, max_chars=10000, timeout=timeout, raw=True)
34
+ robots_result = _crawl_fetch(robots_url, 10000, timeout)
16
35
  if robots_result['success'] and 'Sitemap:' in robots_result.get('html', ''):
17
36
  import re as _re
18
37
  sitemap_urls = _re.findall(r'Sitemap:\s*(.+)', robots_result['html'])
19
38
  if sitemap_urls:
20
39
  sitemap_url = sitemap_urls[0].strip()
21
- result = fetch_page(sitemap_url, max_chars=50000, timeout=timeout, raw=True)
40
+ result = _crawl_fetch(sitemap_url, 50000, timeout)
22
41
  if not result.get('success') or not result.get('html'):
23
42
  return {'url': url, 'pages': [], 'total': 0, 'error': 'sitemap not found or empty'}
24
- html = result.get('html', result.get('content', ''))
25
- # 同时尝试从 raw content 提取(兼容 XML 被 ContentExtractor 过滤的情况)
43
+ html = result.get('html') or result.get('content') or ''
26
44
  urls = re.findall(r'<loc>\s*(.*?)\s*</loc>', html)
27
- if not urls:
28
- urls = re.findall(r'<loc>(.*?)</loc>', result.get('content', ''))
29
45
  urls = urls[:max_pages]
30
46
  pages = []
31
- with ThreadPoolExecutor(max_workers=5) as ex:
32
- futures = {ex.submit(fetch_page, u, 2000, timeout): u for u in urls}
33
- for fut in as_completed(futures, timeout=timeout*2):
34
- try:
35
- r = fut.result()
36
- if r['success']:
37
- pages.append({'url': r['url'], 'content': r['content'][:500], 'depth': 0})
38
- except: pass
39
- return {'url': url, 'pages': pages, 'total': len(pages), 'elapsed_ms': int((time.time())*1000)}
47
+ # 总时限 timeout*2:到点未抓到的站点跳过,不再被卡住的单个 URL 拖住,也不会
48
+ # 因超时异常中断整次 sitemap 爬取。
49
+ finished, _unfinished = run_bounded(
50
+ urls, lambda u: _crawl_fetch(u, 2000, timeout), timeout * 2, max_workers=5)
51
+ for _u, r in finished:
52
+ if isinstance(r, TaskError) or not r.get('success'):
53
+ continue
54
+ pages.append({'url': r['url'], 'content': r['content'][:500], 'depth': 0})
55
+ return {'url': url, 'pages': pages, 'total': len(pages), 'elapsed_ms': int((time.monotonic() - t0) * 1000)}
40
56
 
41
57
  def crawl_bfs(url, max_pages=10, max_depth=2, timeout=8):
42
- """BFS 爬取"""
58
+ """BFS 爬取(按层并行 frontier,去重后入队)"""
59
+ from collections import deque
43
60
  visited = set()
44
61
  pages = []
45
- queue = [(url, 0)]
62
+ queue = deque([(url, 0)])
46
63
  while queue and len(pages) < max_pages:
47
- current_url, depth = queue.pop(0)
48
- if current_url in visited or depth > max_depth:
49
- continue
50
- visited.add(current_url)
51
- result = fetch_page(current_url, 2000, timeout)
52
- if result['success']:
53
- pages.append({'url': current_url, 'content': result['content'][:500], 'depth': depth})
54
- links = re.findall(r'href=["\']([^"\'#]+)', result['content'])
64
+ # 取一层(最多 5 个)并行抓取
65
+ frontier = []
66
+ while queue and len(frontier) < 5:
67
+ u, d = queue.popleft()
68
+ if u in visited or d > max_depth:
69
+ continue
70
+ visited.add(u)
71
+ frontier.append((u, d))
72
+ if not frontier:
73
+ break
74
+ # 一层最多 5 个,总时限 timeout*2;到点未完成的 URL 跳过,不被单个慢站拖住。
75
+ finished, _unfinished = run_bounded(
76
+ frontier, lambda ud: _crawl_fetch(ud[0], 2000, timeout),
77
+ timeout * 2, max_workers=min(len(frontier), 5))
78
+ for (u, d), result in finished:
79
+ if isinstance(result, TaskError) or not result.get('success'):
80
+ continue
81
+ pages.append({'url': u, 'content': result['content'][:500], 'depth': d})
82
+ if len(pages) >= max_pages:
83
+ break
84
+ html = result.get('html') or result.get('content') or ''
85
+ links = re.findall(r'href=["\']([^"\'#]+)', html)
55
86
  for link in links[:5]:
56
- full = urljoin(current_url, link)
57
- if urlparse(full).netloc == urlparse(url).netloc and full not in visited:
58
- queue.append((full, depth+1))
87
+ full = urljoin(u, link)
88
+ if (urlparse(full).netloc == urlparse(url).netloc
89
+ and full not in visited):
90
+ queue.append((full, d + 1))
59
91
  return {'url': url, 'pages': pages, 'total': len(pages)}
60
-
61
92
  if __name__ == '__main__':
62
93
  import argparse
63
94
  p = argparse.ArgumentParser()
@@ -65,9 +96,17 @@ if __name__ == '__main__':
65
96
  p.add_argument('--strategy', default='bfs', choices=['sitemap','bfs'])
66
97
  p.add_argument('--max-pages', type=int, default=10)
67
98
  p.add_argument('--max-depth', type=int, default=2)
99
+ # bin/argo 的 usage 承诺了 Common flags「--json」,此前 crawl/extract 的
100
+ # parser 里没有它,Agent 照文档传参直接撞墙(error: unrecognized
101
+ # arguments,exit 2)。这里接住该开关并给出真实语义:默认仍是截断预览
102
+ # (防上下文膨胀),--json 输出完整可解析 JSON——截断的 JSON 不是合法
103
+ # JSON,调用方 json.loads 必失败,等于「拿不到数据还得自己猜」。
104
+ p.add_argument('--json', action='store_true',
105
+ help='输出完整 JSON(默认为 2000 字符截断预览)')
68
106
  args = p.parse_args()
69
107
  if args.strategy == 'sitemap':
70
108
  r = crawl_sitemap(args.url, args.max_pages)
71
109
  else:
72
110
  r = crawl_bfs(args.url, args.max_pages, args.max_depth)
73
- print(json.dumps(r, ensure_ascii=False, indent=2)[:2000])
111
+ out = dumps(r)
112
+ print(out if args.json else out[:2000])
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env python3
2
+ """cross_lingual_trace.py — 跨语言长程搜索与多源任务溯源引擎。
3
+
4
+ 架构定位:
5
+ 解耦自 research.py 与 wide_research.py。当单语言(如中文或特定小语种)
6
+ 信息贫瘠、被低质农场包围,或任务明确要求事实溯源(源头论文、首发漏洞、
7
+ 规范草案、最初作者、原始出处)时,自动触发跨语言长程追踪(Pivot & Trace),
8
+ 并进行跨语言证据交叉比对(Reconciliation & Disagreement Check)。
9
+
10
+ 核心阶段:
11
+ 1. 需求与充分性判定(assess_cross_lingual_need):
12
+ 检测结果稀疏度、权威源覆盖率以及溯源关键词(起源/出处/最早/first proposed 等)。
13
+ 2. 跨语言实体锚点与溯源子查询生成(build_cross_lingual_trace_queries):
14
+ 将查询投影至目标语种(以英文为主,或日/德/法垂直语境),生成多阶段溯源子查询。
15
+ 3. 跨语言多源证据对账与综合(synthesize_cross_lingual_evidence):
16
+ 将跨语言采集回来的结果与原语言结果按一手源等级、时序和互补关系对齐,
17
+ 沉淀出 primary_origins(一手源)、complements(互补证据)与 disagreements(口径差异)。
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import re
23
+ from typing import Any
24
+
25
+
26
+ # 溯源与跨语言强触发词
27
+ _TRACING_KEYWORDS = (
28
+ "溯源", "起源", "出处", "最初", "源头", "提出者", "发明", "草案",
29
+ "原型", "首发", "首个", "第一篇", "历史沿革", "来龙去脉",
30
+ "origin", "provenance", "root cause", "first proposed", "author of",
31
+ "initial draft", "whitepaper", "specification", "cve-", "rfc "
32
+ )
33
+
34
+ _SOURCE_TYPE_RANKS = {
35
+ "official": 4,
36
+ "academic": 4,
37
+ "standard": 4,
38
+ "repository": 3,
39
+ "reference": 3,
40
+ "news": 2,
41
+ "community": 2,
42
+ "general": 1,
43
+ }
44
+
45
+
46
+ def assess_cross_lingual_need(
47
+ query: str,
48
+ results: list[dict[str, Any]] | None,
49
+ domain: str = "",
50
+ min_sufficient_results: int = 3,
51
+ force_trace: bool = False,
52
+ ) -> dict[str, Any]:
53
+ """评估是否需要启动跨语言长程搜索与任务溯源。
54
+
55
+ 触发条件(满足任意一条):
56
+ 1. 显式开启 force_trace 或查询包含溯源关键词;
57
+ 2. 结果稀疏(有效结果 < min_sufficient_results);
58
+ 3. 高置信度/权威一手信源缺失(authority_level >= 3 的结果为 0);
59
+ 4. 强跨语言领域(security_search, academic, package_intel, rfc)。
60
+ """
61
+ q_low = (query or "").lower()
62
+ has_tracing_keyword = any(kw in q_low for kw in _TRACING_KEYWORDS)
63
+
64
+ valid_results = [r for r in (results or []) if isinstance(r, dict)]
65
+ res_count = len(valid_results)
66
+
67
+ high_auth_count = sum(
68
+ 1 for r in valid_results
69
+ if float(r.get("authority") or r.get("score") or 0.0) >= 0.7
70
+ or int(r.get("authority_level") or 0) >= 3
71
+ )
72
+
73
+ domain_low = (domain or "").lower()
74
+ is_cross_lingual_domain = any(
75
+ d in domain_low for d in ("security", "academic", "package", "rfc", "macro", "us_stock")
76
+ )
77
+
78
+ is_sparse = res_count < min_sufficient_results
79
+ no_authoritative = (res_count > 0 and high_auth_count == 0)
80
+
81
+ needed = force_trace or has_tracing_keyword or is_sparse or no_authoritative or (
82
+ is_cross_lingual_domain and high_auth_count < 2
83
+ )
84
+
85
+ reasons: list[str] = []
86
+ if force_trace:
87
+ reasons.append("显式指定跨语言长程溯源")
88
+ if has_tracing_keyword:
89
+ reasons.append("命中溯源意图关键词")
90
+ if is_sparse:
91
+ reasons.append(f"原语言结果不足(仅 {res_count} 条,门槛 {min_sufficient_results})")
92
+ if no_authoritative:
93
+ reasons.append("原语言缺乏高置信度一手权威信源")
94
+ if is_cross_lingual_domain:
95
+ reasons.append(f"领域「{domain}」天然具备跨语言溯源需求")
96
+
97
+ target_langs = ["en"]
98
+ if re.search(r"[\u3040-\u309f\u30a0-\u30ff]", query):
99
+ target_langs = ["en", "zh"]
100
+ elif re.search(r"(日企|日本|动漫|acg|丰田|索尼|任天堂)", query, re.I):
101
+ target_langs = ["en", "ja"]
102
+
103
+ return {
104
+ "needed": bool(needed),
105
+ "target_langs": target_langs,
106
+ "is_tracing": bool(has_tracing_keyword or force_trace),
107
+ "is_sparse": bool(is_sparse),
108
+ "reasons": reasons,
109
+ "high_auth_count": high_auth_count,
110
+ "result_count": res_count,
111
+ }
112
+
113
+
114
+ def _extract_core_entity(query: str) -> str:
115
+ """从复杂查询中提取核心溯源实体(英文锚点或关键名词)。"""
116
+ q = (query or "").strip()
117
+ # 优先抽取其中的西文标识(如 CVE-2024-1234, React, Transformer, SQLite 等)
118
+ ascii_matches = re.findall(r"[A-Za-z0-9_\-\.\:\/]{3,}", q)
119
+ if ascii_matches:
120
+ # 过滤掉通用词
121
+ clean_terms = [m for m in ascii_matches if m.lower() not in ("what", "how", "why", "when", "with")]
122
+ if clean_terms:
123
+ return " ".join(clean_terms[:3])
124
+
125
+ # 如果纯 CJK,剥离常见的动词与提问词
126
+ cleaned = re.sub(
127
+ r"(如何|怎么|为什么|溯源|起源|出处|最初|源头|提出者|发明|的来历|是谁|什么时候|最新|评价)",
128
+ " ",
129
+ q,
130
+ ).strip()
131
+ words = [w for w in re.split(r"\s+", cleaned) if len(w) >= 2]
132
+ return words[0] if words else q
133
+
134
+
135
+ def build_cross_lingual_trace_queries(
136
+ query: str,
137
+ target_lang: str = "en",
138
+ focus: str = "comprehensive",
139
+ ) -> list[dict[str, str]]:
140
+ """生成分阶段跨语言长程溯源子查询。
141
+
142
+ 返回结构化的子查询列表,标注意图(intent)、策略(strategy)与语言标记。
143
+ """
144
+ entity = _extract_core_entity(query)
145
+ subs: list[dict[str, str]] = []
146
+
147
+ if target_lang == "en":
148
+ # 阶段 1:一手原始出处与概念提出(Origin & Inception)
149
+ subs.append({
150
+ "query": f"{entity} original paper first proposed inception origin",
151
+ "intent": "原始论文/源头提出者与发布背景溯源",
152
+ "strategy": "origin_trace",
153
+ "lang": "en",
154
+ })
155
+ # 阶段 2:规范、代码仓与官方权威文档(Specification & Implementation)
156
+ subs.append({
157
+ "query": f"{entity} official repository documentation specification RFC",
158
+ "intent": "官方规范、权威仓库与技术标准追踪",
159
+ "strategy": "primary_spec",
160
+ "lang": "en",
161
+ })
162
+ # 阶段 3:演进考证与关键争议(Evolution & Historical Milestone)
163
+ subs.append({
164
+ "query": f"{entity} milestone evolution history benchmark analysis",
165
+ "intent": "历史演进里程碑与事实交叉考证",
166
+ "strategy": "cross_check",
167
+ "lang": "en",
168
+ })
169
+ elif target_lang == "ja":
170
+ subs.append({
171
+ "query": f"{entity} 開発経緯 歴史 初出 仕様",
172
+ "intent": "日文原始出处与开发历史溯源",
173
+ "strategy": "origin_trace",
174
+ "lang": "ja",
175
+ })
176
+ subs.append({
177
+ "query": f"{entity} 公式ドキュメント 仕様書 論文",
178
+ "intent": "日文官方规范与文档追踪",
179
+ "strategy": "primary_spec",
180
+ "lang": "ja",
181
+ })
182
+ else:
183
+ subs.append({
184
+ "query": f"{entity} origin official source history",
185
+ "intent": f"{target_lang} 语种源头与官方文档追踪",
186
+ "strategy": "origin_trace",
187
+ "lang": target_lang,
188
+ })
189
+
190
+ return subs
191
+
192
+
193
+ def _classify_source_nature(url: str, title: str) -> str:
194
+ """根据 URL 和标题对信源进行性质分类。"""
195
+ u_low = (url or "").lower()
196
+ t_low = (title or "").lower()
197
+
198
+ if any(h in u_low for h in ("arxiv.org", "doi.org", "acm.org", "ieee.org", "biorxiv.org", "semanticscholar.org")):
199
+ return "academic"
200
+ if any(h in u_low for h in ("github.com", "gitlab.com", "bitbucket.org", "git.kernel.org")):
201
+ return "repository"
202
+ if any(h in u_low for h in ("rfc-editor.org", "w3.org", "iso.org", "ecma-international.org", "cve.mitre.org", "nvd.nist.gov")):
203
+ return "standard"
204
+ if any(h in u_low for h in (".gov", ".edu", "europa.eu", "who.int", "un.org")):
205
+ return "official"
206
+ if any(h in u_low for h in ("wikipedia.org", "wikidata.org", "baike.baidu.com")):
207
+ return "reference"
208
+ if any(k in t_low for k in ("official", "announcement", "release note", "white paper", "specification")):
209
+ return "official"
210
+ return "general"
211
+
212
+
213
+ def synthesize_cross_lingual_evidence(
214
+ primary_results: list[dict[str, Any]],
215
+ cross_results: list[dict[str, Any]],
216
+ query: str,
217
+ ) -> dict[str, Any]:
218
+ """将原语言与跨语言长程检索证据进行交叉对账。
219
+
220
+ 产出:
221
+ - primary_origins: 权威一手信源(论文、代码仓、标准机构)
222
+ - complements: 跨语言带来的增量补充事实
223
+ - disagreements: 跨语言比对中发现的时间/口径分歧
224
+ - provenance_chain: 结构化溯源脉络
225
+ """
226
+ seen_urls: set[str] = set()
227
+ primary_origins: list[dict[str, Any]] = []
228
+ complements: list[dict[str, Any]] = []
229
+ disagreements: list[str] = []
230
+
231
+ all_cross = [r for r in cross_results if isinstance(r, dict)]
232
+
233
+ # 1. 甄选跨语言一手源
234
+ for r in all_cross:
235
+ u = r.get("url") or ""
236
+ if not u or u in seen_urls:
237
+ continue
238
+ seen_urls.add(u)
239
+ t = r.get("title") or ""
240
+ nature = _classify_source_nature(u, t)
241
+ entry = {
242
+ "title": t,
243
+ "url": u,
244
+ "snippet": r.get("snippet") or "",
245
+ "nature": nature,
246
+ "source": r.get("source") or "",
247
+ "authority_rank": _SOURCE_TYPE_RANKS.get(nature, 1),
248
+ }
249
+ if nature in ("academic", "standard", "official", "repository"):
250
+ primary_origins.append(entry)
251
+ else:
252
+ complements.append(entry)
253
+
254
+ # 排序:权威一手源排在最前
255
+ primary_origins.sort(key=lambda x: x["authority_rank"], reverse=True)
256
+
257
+ # 2. 检查年份/时间戳分歧
258
+ year_re = re.compile(r"\b(19\d{2}|20\d{2})\b")
259
+ prim_years = set()
260
+ for r in primary_results:
261
+ text = f"{r.get('title', '')} {r.get('snippet', '')}"
262
+ prim_years.update(year_re.findall(text))
263
+
264
+ cross_years = set()
265
+ for r in all_cross:
266
+ text = f"{r.get('title', '')} {r.get('snippet', '')}"
267
+ cross_years.update(year_re.findall(text))
268
+
269
+ if prim_years and cross_years and not (prim_years & cross_years):
270
+ disagreements.append(
271
+ f"跨语言时间线存在差异:原语言提及时段 {sorted(prim_years)},而外文一手源提及时段 {sorted(cross_years)}"
272
+ )
273
+
274
+ # 3. 构造溯源链条
275
+ provenance_chain: list[dict[str, str]] = []
276
+ for origin in primary_origins[:4]:
277
+ provenance_chain.append({
278
+ "stage": "一手信源定位",
279
+ "source_type": origin["nature"],
280
+ "title": origin["title"][:80],
281
+ "url": origin["url"],
282
+ "evidence": origin["snippet"][:140],
283
+ })
284
+
285
+ return {
286
+ "status": "completed" if (primary_origins or complements) else "no_incremental_evidence",
287
+ "primary_origins": primary_origins[:6],
288
+ "complements_count": len(complements),
289
+ "complements": complements[:6],
290
+ "disagreements": disagreements,
291
+ "provenance_chain": provenance_chain,
292
+ "total_cross_sources": len(all_cross),
293
+ }