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.
- package/LICENSE +201 -21
- package/README.en.md +822 -0
- package/README.es.md +864 -0
- package/README.ja.md +864 -0
- package/README.ko.md +864 -0
- package/README.md +867 -320
- package/SKILL.md +135 -0
- package/assets/readme/hero.svg +121 -0
- package/assets/readme/made-with-beautify.svg +10 -0
- package/assets/readme/proof-routes.svg +73 -0
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/assets/readme/why-better.svg +104 -0
- package/assets/readme/workflow.svg +83 -0
- package/backends/domain_profiles.json +2308 -96
- package/backends/engine_registry.yaml +3314 -503
- package/backends/query_synonyms_cn.json +35 -0
- package/backends/quota_profiles.json +2409 -161
- package/backends/source_types_cn.json +83 -0
- package/backends/text_lexicon.json +44 -0
- package/bin/argo +703 -0
- package/bin/argo.js +71 -39
- package/config.yaml +4976 -363
- package/cordis.patch.yml +21 -0
- package/data/countries.json +40 -0
- package/data/lang_matrix/lang_capability.json +842 -0
- package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
- package/data/regions_cn.json +1 -0
- package/docs/ADDING_NEW_ENGINE.md +184 -0
- package/docs/ARGO_INTRO.md +124 -0
- package/docs/DATA_SOURCE_GAPS.md +81 -0
- package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
- package/docs/ENGINE_CATALOG.md +564 -0
- package/docs/MCP_SETUP.md +106 -0
- package/docs/PLATFORM_PATHS.md +111 -0
- package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
- package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
- package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
- package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
- package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
- package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
- package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
- package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
- package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
- package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
- package/docs/SEARCH_ARCHIVE.md +115 -0
- package/docs/assets/architecture.svg +104 -0
- package/docs/assets/hero.svg +98 -0
- package/docs/engines/bangumi.md +27 -0
- package/docs/engines/biorxiv.md +27 -0
- package/docs/engines/clawhub.md +27 -0
- package/docs/engines/crt_sh.md +27 -0
- package/docs/engines/datacite.md +27 -0
- package/docs/engines/deps_dev.md +27 -0
- package/docs/engines/douban_movie.md +27 -0
- package/docs/engines/endoflife.md +27 -0
- package/docs/engines/flk_law.md +27 -0
- package/docs/engines/fxtwitter.md +27 -0
- package/docs/engines/google_news.md +27 -0
- package/docs/engines/gov_regulations.md +27 -0
- package/docs/engines/guqian.md +27 -0
- package/docs/engines/hackernews.md +27 -0
- package/docs/engines/iplant.md +27 -0
- package/docs/engines/keenable.md +27 -0
- package/docs/engines/met_museum.md +27 -0
- package/docs/engines/nasa_images.md +27 -0
- package/docs/engines/netease_music.md +27 -0
- package/docs/engines/npm.md +27 -0
- package/docs/engines/nvd.md +27 -0
- package/docs/engines/openreview.md +27 -0
- package/docs/engines/openstd.md +27 -0
- package/docs/engines/parallel_free.md +27 -0
- package/docs/engines/people_daily.md +27 -0
- package/docs/engines/realtime_index.md +27 -0
- package/docs/engines/sec_edgar.md +27 -0
- package/docs/engines/seltz.md +27 -0
- package/docs/engines/skillsmp.md +27 -0
- package/docs/engines/sspai.md +27 -0
- package/docs/engines/std_samr.md +27 -0
- package/docs/engines/train.md +27 -0
- package/docs/engines/un_comtrade.md +27 -0
- package/docs/engines/wikisource.md +27 -0
- package/docs/engines/zdic.md +27 -0
- package/docs/engines/zenodo.md +27 -0
- package/docs/engines/zhihu_hot_app.md +27 -0
- package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
- package/engines/README.md +10 -0
- package/engines/_template_cli.yaml +39 -0
- package/engines/_template_http.yaml +37 -0
- package/engines/plugins/_template_plugin.py +50 -0
- package/engines/specs/_cisa_kev.yaml +54 -0
- package/engines/specs/_osv.yaml +63 -0
- package/engines/specs/aljazeera.yaml +43 -0
- package/engines/specs/antara.yaml +43 -0
- package/engines/specs/artic.yaml +20 -0
- package/engines/specs/aviation_weather.yaml +39 -0
- package/engines/specs/bangumi.yaml +21 -0
- package/engines/specs/biorxiv.yaml +24 -0
- package/engines/specs/carbon_intensity.yaml +32 -0
- package/engines/specs/cinii.yaml +44 -0
- package/engines/specs/clawhub.yaml +49 -0
- package/engines/specs/cleveland.yaml +19 -0
- package/engines/specs/cn_ai_news.yaml +43 -0
- package/engines/specs/core.yaml +35 -0
- package/engines/specs/crt_sh.yaml +38 -0
- package/engines/specs/ct24.yaml +43 -0
- package/engines/specs/datacite.yaml +27 -0
- package/engines/specs/datagov_uk.yaml +41 -0
- package/engines/specs/ddgs_images.yaml +45 -0
- package/engines/specs/deepseek_search.yaml +74 -0
- package/engines/specs/deezer.yaml +20 -0
- package/engines/specs/deps_dev.yaml +21 -0
- package/engines/specs/digup.yaml +36 -0
- package/engines/specs/doi.yaml +39 -0
- package/engines/specs/douban_movie.yaml +22 -0
- package/engines/specs/dw_news.yaml +47 -0
- package/engines/specs/egov_law.yaml +21 -0
- package/engines/specs/elpais.yaml +43 -0
- package/engines/specs/endoflife.yaml +21 -0
- package/engines/specs/energy_charts.yaml +21 -0
- package/engines/specs/factcheck_org.yaml +52 -0
- package/engines/specs/faz.yaml +43 -0
- package/engines/specs/federal_register.yaml +57 -0
- package/engines/specs/firecrawl.yaml +53 -0
- package/engines/specs/flk_law.yaml +24 -0
- package/engines/specs/folha.yaml +43 -0
- package/engines/specs/france24.yaml +44 -0
- package/engines/specs/full_fact.yaml +47 -0
- package/engines/specs/fxtwitter.yaml +31 -0
- package/engines/specs/gbfs_nyc.yaml +32 -0
- package/engines/specs/gdacs.yaml +21 -0
- package/engines/specs/gdelt.yaml +64 -0
- package/engines/specs/google_news.yaml +22 -0
- package/engines/specs/gov_regulations.yaml +38 -0
- package/engines/specs/guardian_rss.yaml +59 -0
- package/engines/specs/hurriyet.yaml +43 -0
- package/engines/specs/iplant.yaml +25 -0
- package/engines/specs/jikan.yaml +24 -0
- package/engines/specs/jolpica.yaml +20 -0
- package/engines/specs/k10plus.yaml +20 -0
- package/engines/specs/keenable.yaml +44 -0
- package/engines/specs/lefigaro.yaml +43 -0
- package/engines/specs/listenbrainz.yaml +19 -0
- package/engines/specs/matichon.yaml +43 -0
- package/engines/specs/met_museum.yaml +21 -0
- package/engines/specs/nasa_images.yaml +45 -0
- package/engines/specs/netease_music.yaml +38 -0
- package/engines/specs/nhk.yaml +43 -0
- package/engines/specs/nhtsa_vpic.yaml +20 -0
- package/engines/specs/noaa_swpc.yaml +20 -0
- package/engines/specs/nvd.yaml +38 -0
- package/engines/specs/obis.yaml +20 -0
- package/engines/specs/opencitations.yaml +52 -0
- package/engines/specs/openf1.yaml +21 -0
- package/engines/specs/openfoodfacts.yaml +45 -0
- package/engines/specs/openligadb.yaml +20 -0
- package/engines/specs/openreview.yaml +37 -0
- package/engines/specs/openstd.yaml +21 -0
- package/engines/specs/people_daily.yaml +22 -0
- package/engines/specs/realtime_index.yaml +34 -0
- package/engines/specs/ror.yaml +20 -0
- package/engines/specs/satnogs.yaml +20 -0
- package/engines/specs/sec_edgar.yaml +46 -0
- package/engines/specs/skillsmp.yaml +50 -0
- package/engines/specs/sogou_weixin_browser.yaml +30 -0
- package/engines/specs/soilgrids.yaml +25 -0
- package/engines/specs/sspai.yaml +36 -0
- package/engines/specs/stackexchange.yaml +46 -0
- package/engines/specs/std_samr.yaml +21 -0
- package/engines/specs/tass.yaml +43 -0
- package/engines/specs/tle_mirror.yaml +21 -0
- package/engines/specs/train.yaml +37 -0
- package/engines/specs/tvmaze.yaml +20 -0
- package/engines/specs/twitter_syndication.yaml +31 -0
- package/engines/specs/un_comtrade.yaml +23 -0
- package/engines/specs/unpaywall.yaml +54 -0
- package/engines/specs/vnpress.yaml +43 -0
- package/engines/specs/weather.yaml +32 -0
- package/engines/specs/who_don.yaml +21 -0
- package/engines/specs/who_gho.yaml +20 -0
- package/engines/specs/wikimedia_commons.yaml +34 -0
- package/engines/specs/wikisource.yaml +22 -0
- package/engines/specs/worms.yaml +19 -0
- package/engines/specs/x_trending.yaml +34 -0
- package/engines/specs/yna.yaml +43 -0
- package/engines/specs/ynet.yaml +43 -0
- package/engines/specs/zbmath.yaml +40 -0
- package/engines/specs/zdic.yaml +22 -0
- package/engines/specs/zenodo.yaml +27 -0
- package/engines/specs/zhihu_hot_app.yaml +35 -0
- package/mcp/clients.yaml +74 -0
- package/package.json +51 -12
- package/packages/dsh-plugin/README.md +134 -0
- package/packages/dsh-plugin/cordis.patch.yml +48 -0
- package/packages/dsh-plugin/dsh/client.js +1244 -0
- package/packages/dsh-plugin/dsh/index.js +2290 -0
- package/packages/dsh-plugin/dsh/native-tools.mjs +743 -0
- package/packages/dsh-plugin/package.json +38 -0
- package/packages/dsh-plugin/test/client.test.js +232 -0
- package/packages/dsh-plugin/test/config.test.js +30 -0
- package/packages/dsh-plugin/test/index.test.js +1338 -0
- package/references/query-templates.md +96 -0
- package/references/query-templates.yaml +118 -0
- package/scripts/ab_eval.py +219 -0
- package/scripts/ab_eval_p0p1.py +266 -0
- package/scripts/adaptive.py +271 -70
- package/scripts/answer.py +154 -0
- package/scripts/archive_run.py +591 -0
- package/scripts/argo_engine_registry.py +172 -0
- package/scripts/argo_paths.py +728 -0
- package/scripts/article.py +136 -0
- package/scripts/arxiv_source.py +201 -0
- package/scripts/badcase_ledger.py +252 -0
- package/scripts/batch_probe.py +241 -0
- package/scripts/benchmark.py +19 -0
- package/scripts/bounded_run.py +144 -0
- package/scripts/browser_auth.py +323 -0
- package/scripts/burstiness_detector.py +147 -0
- package/scripts/cache.py +1078 -67
- package/scripts/cache_guard.py +271 -0
- package/scripts/cache_key_vdom.py +56 -0
- package/scripts/cache_similarity.py +170 -0
- package/scripts/caliber_isolation.py +195 -0
- package/scripts/calibrate_lowquality.py +210 -0
- package/scripts/candidate_envelope.py +366 -0
- package/scripts/chrome_cdp.py +871 -0
- package/scripts/circuit_breaker.py +389 -0
- package/scripts/citations.py +334 -0
- package/scripts/cite_cli.py +59 -0
- package/scripts/cjk_tokens.py +95 -0
- package/scripts/clarify.py +47 -24
- package/scripts/cli_io.py +240 -0
- package/scripts/config.py +683 -45
- package/scripts/config_cache.py +402 -0
- package/scripts/conn_pool.py +220 -0
- package/scripts/content_security.py +685 -0
- package/scripts/content_signals.py +730 -0
- package/scripts/crawl.py +74 -35
- package/scripts/daemon.py +866 -0
- package/scripts/digup_bridge.py +214 -0
- package/scripts/engine_admission.py +366 -0
- package/scripts/engine_dispatch.py +1019 -0
- package/scripts/engine_env.py +534 -0
- package/scripts/engine_failure.py +411 -0
- package/scripts/engine_families.py +732 -0
- package/scripts/engine_policy.py +218 -0
- package/scripts/engine_requires.py +190 -0
- package/scripts/engine_status.py +322 -0
- package/scripts/engine_validate.py +638 -0
- package/scripts/engines.py +778 -294
- package/scripts/engines_base.py +1521 -0
- package/scripts/engines_builders.py +298 -0
- package/scripts/engines_builders_batch10.py +159 -0
- package/scripts/engines_builders_batch11.py +174 -0
- package/scripts/engines_builders_batch12.py +103 -0
- package/scripts/engines_builders_batch13.py +340 -0
- package/scripts/engines_builders_batch9.py +1468 -0
- package/scripts/engines_builders_cn.py +2283 -0
- package/scripts/engines_builders_data.py +2923 -0
- package/scripts/engines_builders_data_macro.py +775 -0
- package/scripts/engines_builders_feeds.py +255 -0
- package/scripts/engines_builders_intl.py +1239 -0
- package/scripts/engines_builders_search.py +440 -0
- package/scripts/engines_builders_tech.py +1360 -0
- package/scripts/evidence.py +588 -162
- package/scripts/evidence_loop.py +592 -0
- package/scripts/evidence_tier.py +298 -0
- package/scripts/except_sets.py +25 -0
- package/scripts/extract.py +55 -16
- package/scripts/fact_align.py +207 -0
- package/scripts/fact_crosscheck.py +230 -0
- package/scripts/fetch.py +25 -12
- package/scripts/fetch_browser.py +87 -0
- package/scripts/fetch_jsrun.py +122 -0
- package/scripts/fetch_quality.py +225 -0
- package/scripts/fetch_render_tinyfish.py +117 -0
- package/scripts/fetch_v3.py +1779 -0
- package/scripts/focus_extract.py +350 -0
- package/scripts/fulltext_store.py +169 -0
- package/scripts/fxtwitter_api.py +325 -0
- package/scripts/gen_engine_catalog.py +364 -0
- package/scripts/gen_native_tools.py +91 -0
- package/scripts/gen_routing_snapshot.py +184 -0
- package/scripts/health_check.py +140 -402
- package/scripts/health_probe.py +23 -8
- package/scripts/hot_state.py +134 -0
- package/scripts/http_client.py +1053 -0
- package/scripts/image_ops.py +430 -0
- package/scripts/image_vision/vision_probe +0 -0
- package/scripts/image_vision/vision_probe.swift +126 -0
- package/scripts/install.ps1 +148 -0
- package/scripts/install.sh +206 -0
- package/scripts/job.py +1186 -0
- package/scripts/jump_resolver.py +235 -0
- package/scripts/lang_capability.py +150 -0
- package/scripts/lang_detect.py +289 -0
- package/scripts/lang_matrix_probe.py +329 -0
- package/scripts/lang_pref.py +311 -0
- package/scripts/link_source.py +340 -0
- package/scripts/local_engines.py +321 -0
- package/scripts/local_image.py +970 -0
- package/scripts/local_seek.py +366 -0
- package/scripts/macro_countries.py +82 -0
- package/scripts/matrix_search_eval.py +1105 -0
- package/scripts/mcp_diag.py +8 -3
- package/scripts/mcp_handlers.py +1074 -0
- package/scripts/mcp_handlers_surface.py +157 -0
- package/scripts/mcp_launch.sh +122 -0
- package/scripts/mcp_server.py +118 -417
- package/scripts/mcp_setup.py +407 -0
- package/scripts/mcp_tools.py +330 -0
- package/scripts/mcp_transport.py +191 -0
- package/scripts/min_version_scan.py +335 -0
- package/scripts/net_proxy.py +258 -0
- package/scripts/network_aware.py +152 -0
- package/scripts/paper.py +274 -0
- package/scripts/pdf_extract.py +518 -0
- package/scripts/phrase_filter.py +66 -0
- package/scripts/plan.py +422 -0
- package/scripts/postprocess_signals.py +150 -0
- package/scripts/pre-commit +48 -0
- package/scripts/query_enhance.py +157 -0
- package/scripts/query_operators.py +196 -0
- package/scripts/query_rewriter.py +116 -7
- package/scripts/query_signals.py +300 -0
- package/scripts/query_templates.py +167 -0
- package/scripts/query_understanding.py +411 -0
- package/scripts/query_variants.py +298 -0
- package/scripts/quota.py +488 -76
- package/scripts/rank_signals.py +775 -0
- package/scripts/ranking_eval.py +319 -0
- package/scripts/readability_extract.py +433 -0
- package/scripts/recompute.py +415 -0
- package/scripts/recovery.py +616 -0
- package/scripts/recovery_l0.py +160 -0
- package/scripts/redskill/redskill_engine.py +332 -0
- package/scripts/regression_p0p1.py +318 -0
- package/scripts/replay_eval.py +640 -0
- package/scripts/rerank_tfidf.py +225 -0
- package/scripts/research.py +591 -403
- package/scripts/research_cli.py +361 -0
- package/scripts/research_dossier.py +492 -0
- package/scripts/research_expand.py +146 -0
- package/scripts/research_gates.py +272 -0
- package/scripts/research_report.py +209 -0
- package/scripts/research_strategy.py +37 -0
- package/scripts/research_work_packages.py +195 -0
- package/scripts/result_lang.py +311 -0
- package/scripts/robots_guard.py +259 -0
- package/scripts/route.py +921 -254
- package/scripts/route_bangs.py +40 -0
- package/scripts/route_cache.py +225 -0
- package/scripts/route_combo.py +910 -0
- package/scripts/route_domains.py +328 -0
- package/scripts/route_lang.py +605 -0
- package/scripts/route_log.py +63 -0
- package/scripts/route_policy.py +250 -0
- package/scripts/rrf_classic.py +66 -0
- package/scripts/screenshot.py +73 -0
- package/scripts/search.py +897 -405
- package/scripts/search_attribution.py +88 -0
- package/scripts/search_benchmark.py +407 -0
- package/scripts/search_cli.py +627 -0
- package/scripts/search_entry.py +389 -0
- package/scripts/search_output.py +606 -0
- package/scripts/search_pipeline.py +1077 -0
- package/scripts/search_rank.py +991 -0
- package/scripts/search_types.py +27 -12
- package/scripts/seek_locator.py +68 -0
- package/scripts/semantic_evidence.py +215 -0
- package/scripts/serp_guard.py +228 -0
- package/scripts/serp_kill_log.py +51 -0
- package/scripts/single_flight.py +120 -0
- package/scripts/social_engines/__init__.py +10 -0
- package/scripts/social_engines/bilibili_engine.py +131 -0
- package/scripts/social_engines/hackernews_engine.py +113 -0
- package/scripts/social_engines/reddit_engine.py +172 -0
- package/scripts/social_engines/twitter_engine.py +397 -0
- package/scripts/social_engines/v2ex_engine.py +101 -0
- package/scripts/social_engines/weibo_engine.py +128 -0
- package/scripts/social_engines/xiaohongshu_engine.py +137 -0
- package/scripts/social_engines/zhihu_engine.py +139 -0
- package/scripts/social_research.py +201 -0
- package/scripts/sogou_weixin_browser.py +318 -0
- package/scripts/source_ledger.py +249 -0
- package/scripts/stage_timing.py +58 -0
- package/scripts/stats_cli.py +95 -0
- package/scripts/straggler_policy.py +60 -0
- package/scripts/stylometry_detector.py +231 -0
- package/scripts/sync_backends.py +353 -0
- package/scripts/sync_installs.py +29 -0
- package/scripts/template_cli.py +127 -0
- package/scripts/text_lexicon.py +72 -0
- package/scripts/tfidf_router.py +109 -37
- package/scripts/time_utils.py +233 -0
- package/scripts/topic_research_profiles.py +643 -0
- package/scripts/train.py +737 -0
- package/scripts/tweet.py +596 -0
- package/scripts/url_canon.py +217 -0
- package/scripts/url_optimize.py +56 -0
- package/scripts/url_safety.py +236 -0
- package/scripts/usage_log.py +116 -0
- package/scripts/usage_log_clean.py +113 -0
- package/scripts/v2ex_nodes.py +311 -0
- package/scripts/variant_recall.py +188 -0
- package/scripts/watch.py +205 -0
- package/scripts/wechat_article.py +258 -0
- package/scripts/wide_research.py +402 -0
- package/scripts/wx.py +349 -0
- package/scripts/x_article_fetch.py +141 -0
- package/scripts/x_search.py +234 -0
- package/scripts/x_trending.py +189 -0
- package/scripts/yaml_load.py +67 -0
- package/sub-skills/ego-search/SKILL.md +253 -0
- package/sub-skills/ego-search/references/browser-runtime.md +43 -0
- package/sub-skills/ego-search/references/install.md +67 -0
- package/sub-skills/ego-search/references/original-ego-upgrade.md +51 -0
- package/sub-skills/ego-search/scripts/ego_search.py +800 -0
- package/sub-skills/ego-search/scripts/merge.py +226 -0
- package/sub-skills/ego-search/scripts/quality.py +64 -0
- package/sub-skills/ego-search/scripts/runtime.py +172 -0
- package/sub-skills/ego-search/scripts/safety.py +109 -0
- package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
- package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
- package/sub-skills/ego-search/tests/test_query_normalize.py +68 -0
- package/sub-skills/js-run/SKILL.md +67 -0
- package/sub-skills/js-run/scripts/jsrun.py +112 -0
- package/sub-skills/js-run/scripts/shim.js +188 -0
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +27 -0
- package/sub-skills/js-run/tests/test_jsrun.py +165 -0
- package/sub-skills/local-search/SKILL.md +50 -53
- package/sub-skills/local-search/bm25.py +162 -0
- package/sub-skills/local-search/config.yaml +152 -55
- package/sub-skills/local-search/engine_registry.py +91 -26
- package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
- package/sub-skills/local-search/local_search_adapter.py +13 -2
- package/sub-skills/local-search/parse_maps.yaml +40 -42
- package/sub-skills/local-search/search_v3.py +1182 -65
- package/sub-skills/local-search/smart_router.py +47 -7
- package/sub-skills/local-seek/SKILL.md +110 -0
- package/sub-skills/local-seek/config/domains.yaml +20 -0
- package/sub-skills/local-seek/references/auto-behaviors.md +24 -0
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +62 -0
- package/sub-skills/local-seek/references/git-integration.md +54 -0
- package/sub-skills/local-seek/references/remote-handoff.md +46 -0
- package/sub-skills/local-seek/references/rg-recipes.md +96 -0
- package/sub-skills/local-seek/references/strategies.md +52 -0
- package/sub-skills/local-seek/references/structural-search.md +59 -0
- package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
- package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
- package/sub-skills/local-seek/scripts/seek.py +1465 -0
- package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
- package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
- package/sub-skills/local-seek/scripts/seek_rank.py +193 -0
- package/sub-skills/local-seek/tests/test_content_rank.py +222 -0
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +145 -0
- package/sub-skills/local-seek/tests/test_noise_tier.py +101 -0
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +136 -0
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +77 -0
- package/sub-skills/local-seek/tests/test_regression_20261003.py +169 -0
- package/sub-skills/local-seek/tests/test_regression_20261006.py +146 -0
- package/sub-skills/local-seek/tests/test_seek_degradation.py +111 -0
- package/sub-skills/local-seek/tests/test_seek_fts.py +313 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""local_seek.py — 本地文件命中(--include-local 用)。
|
|
3
|
+
|
|
4
|
+
从 search.py 拆出:本地搜索的宽泛根守卫、子进程调用、结果缓存。
|
|
5
|
+
search.py 的 _run_local_seek 调用点改为从本模块导入。
|
|
6
|
+
|
|
7
|
+
三个边界(2026-09-27 实测补齐,此前会拖垮整次搜索):
|
|
8
|
+
|
|
9
|
+
1. **宽泛根 / 自身安装树直接不查**(见 `_local_seek_dir`):cwd 是 home、
|
|
10
|
+
根目录,或落在 argo 自身安装树内时返回 None 并跳过。前者是 22.6 s 事故
|
|
11
|
+
的根因修复(「显式传 --path」解决不了:子进程继承 cwd,显式传与默认
|
|
12
|
+
等价);后者治「常驻 MCP 进程 cwd = 技能目录」导致结果被自身源码污染。
|
|
13
|
+
2. **超时从 20 s 收到 _LOCAL_SEEK_TIMEOUT_S**。本地命中是增强项,不是
|
|
14
|
+
主结果;它的等待上限必须显著小于用户对一次搜索的耐心。
|
|
15
|
+
3. **TimeoutExpired 必须在这里吞掉**。此前没有 try,该异常一路冒泡到
|
|
16
|
+
CLI/MCP 调用方(实测栈打到 `subprocess.py:1268`);上层虽有兜底,
|
|
17
|
+
但本地命中失败本就不该让整次搜索承担异常路径。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
import time
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
# 本机文件命中子进程超时(秒)。**必须显著小于 fast/auto 的总预算**:
|
|
31
|
+
# 本地命中是尾部增强项,不是主结果,它的等待不配与搜索引擎同等。
|
|
32
|
+
_LOCAL_SEEK_TIMEOUT_S = 3.0
|
|
33
|
+
|
|
34
|
+
# 「宽泛根」判定:这些目录**不能**当作本地检索范围。
|
|
35
|
+
# 跨平台补齐(2026-09-29):/mnt(WSL 挂载根)、/cygdrive(Cygwin)——
|
|
36
|
+
# 此前是 macOS 视角清单,Windows/WSL 的等价宽根漏网。
|
|
37
|
+
_BROAD_LOCAL_ROOTS = ("/", "/tmp", "/private/tmp", "/var", "/usr", "/System",
|
|
38
|
+
"/Library", "/Applications", "/Volumes", "/Network",
|
|
39
|
+
"/mnt", "/cygdrive")
|
|
40
|
+
|
|
41
|
+
# Windows 盘符根(C:\ / C:/ / C:):与上面清单同义,但形态是「字母+冒号」,
|
|
42
|
+
# 用模式判而不用枚举(盘符有 26 个)。
|
|
43
|
+
_WIN_DRIVE_ROOT_RE = re.compile(r"^[A-Za-z]:[\\/]?$")
|
|
44
|
+
|
|
45
|
+
# argo 自身安装树的根。常驻 MCP 进程的 cwd 就是它(宿主在技能目录里启动
|
|
46
|
+
# 服务),而 `--include-local`(fast/budget 默认开)按 cwd 取范围——于是
|
|
47
|
+
# 每次搜索都把本项目自己的源码与测试当作「本机命中」拼进结果。实测一次
|
|
48
|
+
# 查询 10 条里 5 条是 `tests/*.py`,score 0.9。它不是「宽泛根」而是
|
|
49
|
+
# 「自己的树」:对任何真实查询都只是噪声,故与宽泛根同等跳过。要搜本机
|
|
50
|
+
# 文件仍可 cd 到目标目录,或用 local-seek 的 seek.py --path <目录>。
|
|
51
|
+
_OWN_INSTALL_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
52
|
+
|
|
53
|
+
# 本地文件命中缓存:key = (query, max_n, target) → (写入时间, 结果列表)。
|
|
54
|
+
# 文件修改后 TTL 内可能返回旧结果,对本地文件搜索可接受(文件不频繁修改)。
|
|
55
|
+
# 容量上限 64 条,超出时整表清空(低频增强项,LRU 的简单替代)。
|
|
56
|
+
#
|
|
57
|
+
# TTL 300s → 3600s(2026-10-15,B-4):本地文件命中是「同一台机器上重复问
|
|
58
|
+
# 同一件事」的增强项,5 分钟的窗在日常用法里几乎等于没有缓存——改一次笔记
|
|
59
|
+
# 之后再问同一个问题,命中率主要被 TTL 而不是被内容变化决定。代价是文件改
|
|
60
|
+
# 动后最长 1 小时可能返回旧命中,本地文件搜索可接受(与落盘缓存同一取舍,
|
|
61
|
+
# 两者共用这个常量才不会再分叉)。
|
|
62
|
+
_LOCAL_SEEK_CACHE: dict[str, tuple[float, list[dict[str, Any]]]] = {}
|
|
63
|
+
_LOCAL_SEEK_TTL_S = 3600.0
|
|
64
|
+
_LOCAL_SEEK_CACHE_MAX = 64
|
|
65
|
+
|
|
66
|
+
# H2:seek 模块按解析后真实路径缓存(importlib 显式文件定位,不占通用
|
|
67
|
+
# 模块名、不污染 sys.path),进程内只加载一次(实测 0.5ms)。
|
|
68
|
+
_SEEK_MODULE_CACHE: dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
# include-local 的本地搜索 worker 单例(住在被导入模块而非 search.py 顶层,
|
|
71
|
+
# 防 daemon runpy.run_module 重跑 search 顶层时覆写泄漏线程池)。
|
|
72
|
+
_LOCAL_SEEK_EXECUTOR = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_local_seek_executor():
|
|
76
|
+
global _LOCAL_SEEK_EXECUTOR
|
|
77
|
+
if _LOCAL_SEEK_EXECUTOR is None:
|
|
78
|
+
import concurrent.futures
|
|
79
|
+
_LOCAL_SEEK_EXECUTOR = concurrent.futures.ThreadPoolExecutor(
|
|
80
|
+
max_workers=1, thread_name_prefix="local-seek")
|
|
81
|
+
return _LOCAL_SEEK_EXECUTOR
|
|
82
|
+
|
|
83
|
+
# H3:跨进程落盘缓存。CLI 每次新进程,进程内缓存救不了 CLI 重复查询;
|
|
84
|
+
# 落盘缓存让 TTL 内的重复查询(同 query+dir+max)零成本。陈旧语义与
|
|
85
|
+
# 进程内缓存一致,**共用 `_LOCAL_SEEK_TTL_S`(现 3600s)**——两处各写一个字
|
|
86
|
+
# 面量正是它们会分叉的原因。写侧原子替换 + 全异常吞掉:缓存永不破坏搜索。
|
|
87
|
+
_SEEK_DISK_CACHE_MAX = 64
|
|
88
|
+
# 条目形状版本:跨版本改 results 条目字段时,TTL 内的旧形状条目按 miss 处理
|
|
89
|
+
_SEEK_DISK_SCHEMA = 1
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _seek_disk_cache_path() -> Path:
|
|
93
|
+
try:
|
|
94
|
+
import argo_paths
|
|
95
|
+
return Path(argo_paths.state_path("local_seek_cache.json"))
|
|
96
|
+
except Exception:
|
|
97
|
+
return Path(os.path.expanduser(
|
|
98
|
+
"~/.cache/unified-search/local_seek_cache.json"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _seek_disk_cache_get(key: str) -> list[dict[str, Any]] | None:
|
|
102
|
+
try:
|
|
103
|
+
p = _seek_disk_cache_path()
|
|
104
|
+
if not p.is_file():
|
|
105
|
+
return None
|
|
106
|
+
with p.open("r", encoding="utf-8") as f:
|
|
107
|
+
data = json.load(f)
|
|
108
|
+
rec = data.get(key) if isinstance(data, dict) else None
|
|
109
|
+
if not isinstance(rec, dict):
|
|
110
|
+
return None
|
|
111
|
+
if rec.get("v") != _SEEK_DISK_SCHEMA:
|
|
112
|
+
return None
|
|
113
|
+
if time.time() - rec.get("ts", 0) >= _LOCAL_SEEK_TTL_S:
|
|
114
|
+
return None
|
|
115
|
+
results = rec.get("results")
|
|
116
|
+
if not isinstance(results, list):
|
|
117
|
+
return None
|
|
118
|
+
# 返回拷贝:缓存持有所有权,调用方就地改写(_engine 标记等)不得污染缓存
|
|
119
|
+
return [dict(r) if isinstance(r, dict) else r for r in results]
|
|
120
|
+
except Exception:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _seek_disk_cache_put(key: str, results: list[dict[str, Any]]) -> None:
|
|
125
|
+
try:
|
|
126
|
+
p = _seek_disk_cache_path()
|
|
127
|
+
now = time.time()
|
|
128
|
+
try:
|
|
129
|
+
with p.open("r", encoding="utf-8") as f:
|
|
130
|
+
data = json.load(f)
|
|
131
|
+
data = data if isinstance(data, dict) else {}
|
|
132
|
+
except Exception:
|
|
133
|
+
data = {} # 损坏文件:整表重建(缓存可牺牲,搜索不可牺牲)
|
|
134
|
+
data = {k: v for k, v in data.items()
|
|
135
|
+
if isinstance(v, dict) and now - v.get("ts", 0) < _LOCAL_SEEK_TTL_S}
|
|
136
|
+
data[key] = {"v": _SEEK_DISK_SCHEMA, "ts": now, "results": results}
|
|
137
|
+
if len(data) > _SEEK_DISK_CACHE_MAX:
|
|
138
|
+
for k in sorted(data, key=lambda k: data[k].get("ts", 0)
|
|
139
|
+
)[:len(data) - _SEEK_DISK_CACHE_MAX]:
|
|
140
|
+
data.pop(k, None)
|
|
141
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
try:
|
|
143
|
+
import argo_paths
|
|
144
|
+
argo_paths.atomic_write_json(p, data, indent=None) # mkstemp 唯一 tmp,失败自清理
|
|
145
|
+
except (ImportError, AttributeError, OSError):
|
|
146
|
+
tmp = p.with_name(f"{p.name}.{os.getpid()}.tmp")
|
|
147
|
+
with tmp.open("w", encoding="utf-8") as f:
|
|
148
|
+
json.dump(data, f, ensure_ascii=False)
|
|
149
|
+
os.replace(tmp, p) # 原子替换:并发写不会留下半截文件
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _is_broad_local_root(path: str) -> bool:
|
|
155
|
+
"""path 是否是「不能当作本地检索范围」的宽泛根。
|
|
156
|
+
|
|
157
|
+
判据从 _local_seek_dir 提取为可复用函数:MCP 的 argo_local_search
|
|
158
|
+
默认 path="~",与 CLI 的 cwd 缺省是同一类事故形态(2026-09-27 实测
|
|
159
|
+
MCP 侧对整个 home 跑 rg,30 s 后报错返回),两侧必须共用同一判据。
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
real = os.path.realpath(path)
|
|
163
|
+
except OSError:
|
|
164
|
+
return True
|
|
165
|
+
home = os.path.realpath(os.path.expanduser("~"))
|
|
166
|
+
if real == home:
|
|
167
|
+
return True
|
|
168
|
+
# 盘符根同时查原始与 realpath 形态:realpath 在 POSIX 上会把 "C:\"
|
|
169
|
+
# 当相对路径拼接(cwd + "/C:\"),只有 raw 形态还能保住判据。
|
|
170
|
+
if _WIN_DRIVE_ROOT_RE.match(path) or _WIN_DRIVE_ROOT_RE.match(real):
|
|
171
|
+
return True
|
|
172
|
+
for root in _BROAD_LOCAL_ROOTS:
|
|
173
|
+
if real == root or real == os.path.realpath(root):
|
|
174
|
+
return True
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_own_install_root(path: str) -> bool:
|
|
179
|
+
"""path 是否落在 argo 自身安装树内(含其根目录本身)。"""
|
|
180
|
+
try:
|
|
181
|
+
real = os.path.realpath(path)
|
|
182
|
+
except OSError:
|
|
183
|
+
return False
|
|
184
|
+
return real == _OWN_INSTALL_ROOT or real.startswith(_OWN_INSTALL_ROOT + os.sep)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _local_seek_dir() -> str | None:
|
|
188
|
+
"""本地检索的适用范围;宽泛根或自身安装树返回 None(据此跳过本地命中)。
|
|
189
|
+
|
|
190
|
+
宁可返回 None 也不返回一个「象征性收窄」的路径:本地命中是增强项,
|
|
191
|
+
在范围不合理时**不产出**比产出噪音 + 20 s 等待更符合用户预期。
|
|
192
|
+
用户想搜特定树时 cd 到目标目录,或用 local-seek 的 seek.py 显式给
|
|
193
|
+
--path(网络类查询另有 argo search --local-first 走本地引擎聚合),
|
|
194
|
+
那条路径不受此守卫影响。
|
|
195
|
+
"""
|
|
196
|
+
cwd = os.getcwd()
|
|
197
|
+
if _is_broad_local_root(cwd) or _is_own_install_root(cwd):
|
|
198
|
+
return None
|
|
199
|
+
return cwd
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _run_local_seek(query: str, max_n: int = 5,
|
|
203
|
+
search_dir: str | None = None) -> list[dict[str, Any]]:
|
|
204
|
+
"""本机文件命中(include-local 用):进程内调 seek.run_query,JSON 并入。
|
|
205
|
+
|
|
206
|
+
fast/budget 模式下由 super_search 自动调用(_resolve_include_local),
|
|
207
|
+
auto/deep 需显式 --include-local / MCP include_local。评分与
|
|
208
|
+
MCP argo_local_search 同口径(0.9 精确 / 0.7 扩展)。
|
|
209
|
+
进程内失败回退子进程形态(_seek_query_subprocess),能力不回退。
|
|
210
|
+
"""
|
|
211
|
+
target = search_dir or _local_seek_dir()
|
|
212
|
+
if not target:
|
|
213
|
+
# 宽泛根:不查,但必须留下可归因的痕迹(docstring 第 1 条)。
|
|
214
|
+
# 「跳过」与「查了没有」在返回值上都是空列表,但含义完全不同——
|
|
215
|
+
# 静默跳过会让用户以为本地没匹配,而事实是根本没查。返回一个
|
|
216
|
+
# 占位「结果」又会污染 results(它只是个说明,不是命中),所以
|
|
217
|
+
# 说明走 stderr:与 CLI 既有的 include-local 异常提示同一条通道。
|
|
218
|
+
sys.stderr.write(
|
|
219
|
+
" [include-local] 当前目录不适合本地检索(home / 根目录过宽,或位于 argo "
|
|
220
|
+
"自身安装树内——只会命中自身源码),已跳过;"
|
|
221
|
+
"要搜本机文件请 cd 到目标目录后重试,或用 local-seek 的 seek.py --path <目录>\n")
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
# 缓存:同 query+target 在 TTL 内直接命中,避免重复冷启动 seek.py 子进程。
|
|
225
|
+
# 文件修改后 TTL 内可能返回旧结果,对本地文件搜索可接受(文件不频繁修改)。
|
|
226
|
+
cache_key = f"{query}|{max_n}|{target}"
|
|
227
|
+
cached = _LOCAL_SEEK_CACHE.get(cache_key)
|
|
228
|
+
if cached is not None and time.time() - cached[0] < _LOCAL_SEEK_TTL_S:
|
|
229
|
+
return cached[1]
|
|
230
|
+
|
|
231
|
+
# H3:跨进程落盘缓存(CLI 每次新进程,进程内缓存救不了 CLI 重复查询;
|
|
232
|
+
# MCP 常驻进程通常在内存层就命中,落盘层对它是兜底)。命中后回填内存层。
|
|
233
|
+
disk_hit = _seek_disk_cache_get(cache_key)
|
|
234
|
+
if disk_hit is not None:
|
|
235
|
+
if len(_LOCAL_SEEK_CACHE) >= _LOCAL_SEEK_CACHE_MAX:
|
|
236
|
+
_LOCAL_SEEK_CACHE.clear()
|
|
237
|
+
_LOCAL_SEEK_CACHE[cache_key] = (time.time(), disk_hit)
|
|
238
|
+
return disk_hit
|
|
239
|
+
|
|
240
|
+
# 安装感知 + 唯一来源:委托 seek_locator 统一发现 local-seek/scripts/seek.py
|
|
241
|
+
# (打包子技能优先,ARGO_LOCAL_SEEK_PATH / ARGO_LOCAL_SEEK_ROOTS 承载自定义/遗留)。
|
|
242
|
+
# 不硬编码 ~/.agents/skills|~/.claude/skills 主机路径(SKILL.md 明令禁止)。
|
|
243
|
+
from seek_locator import resolve_seek_py
|
|
244
|
+
seek_py = resolve_seek_py()
|
|
245
|
+
if not seek_py or not os.path.isfile(seek_py):
|
|
246
|
+
return []
|
|
247
|
+
|
|
248
|
+
# H2(2026-09-29):进程内直调 seek.run_query——省掉子进程解释器启动
|
|
249
|
+
# (实测 30-110ms/次,子进程总成本 60-140ms vs 进程内 ~30ms,落点即 rg
|
|
250
|
+
# 本体)。seek 模块 stdlib-only,import 实测 0.5ms;time_budget 下传到
|
|
251
|
+
# 内部 rg/fd 子进程(子进程可硬杀,线程不可杀——不传会占死单线程 executor)。
|
|
252
|
+
# 进程内失败(模块加载/执行异常)回退子进程路径:能力不回退,只是慢。
|
|
253
|
+
payload = None
|
|
254
|
+
try:
|
|
255
|
+
payload = seek_query_payload(query, target, max_n,
|
|
256
|
+
time_budget=_LOCAL_SEEK_TIMEOUT_S)
|
|
257
|
+
except Exception:
|
|
258
|
+
payload = None
|
|
259
|
+
if payload is None:
|
|
260
|
+
payload = _seek_query_subprocess(seek_py, query, target, max_n)
|
|
261
|
+
if payload is None:
|
|
262
|
+
return []
|
|
263
|
+
out = _payload_to_hits(payload, max_n, cache_key)
|
|
264
|
+
_seek_disk_cache_put(cache_key, out) # H3:写穿到落盘层
|
|
265
|
+
return out
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _load_seek_module(seek_py: str):
|
|
269
|
+
"""按解析后真实路径加载 seek 模块(importlib 显式文件定位,不污染
|
|
270
|
+
sys.path、不占通用模块名),按路径缓存——ARGO_LOCAL_SEEK_PATH 指向
|
|
271
|
+
不同文件时各自独立加载。失败返回 None。"""
|
|
272
|
+
mod = _SEEK_MODULE_CACHE.get(seek_py)
|
|
273
|
+
if mod is not None:
|
|
274
|
+
return mod
|
|
275
|
+
import importlib.util
|
|
276
|
+
spec = importlib.util.spec_from_file_location("argo_seek_impl", seek_py)
|
|
277
|
+
if spec is None or spec.loader is None:
|
|
278
|
+
return None
|
|
279
|
+
mod = importlib.util.module_from_spec(spec)
|
|
280
|
+
spec.loader.exec_module(mod)
|
|
281
|
+
_SEEK_MODULE_CACHE[seek_py] = mod
|
|
282
|
+
return mod
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def seek_query_payload(query: str, target: str, max_n: int,
|
|
286
|
+
exact: bool = False, time_budget: float | None = None
|
|
287
|
+
) -> dict[str, Any] | None:
|
|
288
|
+
"""H2 公共入口:进程内调 seek.run_query 取 JSON payload。
|
|
289
|
+
|
|
290
|
+
include-local(_run_local_seek)与 MCP argo_local_search 共用——两条
|
|
291
|
+
调用路径同一实现(单一来源)。失败/无匹配返回 None,调用方自行回退
|
|
292
|
+
(子进程形态或直接报无结果)。
|
|
293
|
+
"""
|
|
294
|
+
from seek_locator import resolve_seek_py
|
|
295
|
+
seek_py = resolve_seek_py()
|
|
296
|
+
if not seek_py or not os.path.isfile(seek_py):
|
|
297
|
+
return None
|
|
298
|
+
mod = _load_seek_module(seek_py)
|
|
299
|
+
if mod is None:
|
|
300
|
+
return None
|
|
301
|
+
argv = [query, "--json", "--max", str(max(max_n, 1)), "--path", target]
|
|
302
|
+
if exact:
|
|
303
|
+
argv.append("--exact")
|
|
304
|
+
text, rc = mod.run_query(argv, time_budget=time_budget)
|
|
305
|
+
if rc != 0:
|
|
306
|
+
# rc=1 是「搜了没命中」,不是「真失败」——区分两者避免子进程回退
|
|
307
|
+
# 把同一搜索再跑一遍(实测 787ms vs 命中路径 12ms,双跑税 ~775ms)。
|
|
308
|
+
# 无命中返回空结果(不回退);真失败返回 None(调用方回退子进程)。
|
|
309
|
+
if rc == 1 and not text.strip():
|
|
310
|
+
return {"results": [], "total": 0}
|
|
311
|
+
return None
|
|
312
|
+
if not text.strip():
|
|
313
|
+
return None
|
|
314
|
+
return json.loads(text)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _seek_query_subprocess(seek_py: str, query: str, target: str,
|
|
318
|
+
max_n: int) -> dict[str, Any] | None:
|
|
319
|
+
"""子进程形态取 payload(H2 前的唯一路径,现为进程内失败的回退)。"""
|
|
320
|
+
import subprocess as _sp
|
|
321
|
+
cmd = [sys.executable, seek_py, query, "--json", "--max", str(max(max_n, 1)),
|
|
322
|
+
"--path", target]
|
|
323
|
+
try:
|
|
324
|
+
r = _sp.run(
|
|
325
|
+
cmd, capture_output=True, text=True,
|
|
326
|
+
encoding="utf-8", errors="replace", timeout=_LOCAL_SEEK_TIMEOUT_S,
|
|
327
|
+
env={**os.environ, "PYTHONUTF8": "1"}, # 子进程是自家 seek.py,双向显式 UTF-8
|
|
328
|
+
)
|
|
329
|
+
except _sp.TimeoutExpired:
|
|
330
|
+
return None # 本地命中是增强项:超时即弃,不冒泡(docstring 第 2、3 条)
|
|
331
|
+
except OSError:
|
|
332
|
+
return None # 解释器/脚本不可执行等环境问题:同样不该拖垮主搜索
|
|
333
|
+
if r.returncode != 0 or not r.stdout.strip():
|
|
334
|
+
return None
|
|
335
|
+
try:
|
|
336
|
+
return json.loads(r.stdout)
|
|
337
|
+
except ValueError:
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _payload_to_hits(payload: dict[str, Any], max_n: int,
|
|
342
|
+
cache_key: str) -> list[dict[str, Any]]:
|
|
343
|
+
"""seek payload → include-local 结果条目(评分与 MCP argo_local_search
|
|
344
|
+
同口径:0.9 精确命中 / 0.7 扩展召回),并写入进程内缓存。"""
|
|
345
|
+
hits = payload.get("results") or payload.get("files") or []
|
|
346
|
+
seek_mode = payload.get("mode", "fast")
|
|
347
|
+
hit_score = 0.9 if seek_mode == "fast" else 0.7
|
|
348
|
+
out = []
|
|
349
|
+
for h in hits[:max_n]:
|
|
350
|
+
if not isinstance(h, dict):
|
|
351
|
+
continue
|
|
352
|
+
path = h.get("path") or h.get("file") or ""
|
|
353
|
+
line = h.get("line") or h.get("lineno") or 1
|
|
354
|
+
url = f"file://{path}" + (f"#{line}" if str(line).isdigit() else "")
|
|
355
|
+
out.append({
|
|
356
|
+
"title": path,
|
|
357
|
+
"url": url,
|
|
358
|
+
"snippet": (h.get("snippet") or h.get("text") or h.get("line_text") or "")[:160],
|
|
359
|
+
"source": "local_files",
|
|
360
|
+
"score": hit_score,
|
|
361
|
+
"kind": "local",
|
|
362
|
+
})
|
|
363
|
+
if len(_LOCAL_SEEK_CACHE) >= _LOCAL_SEEK_CACHE_MAX:
|
|
364
|
+
_LOCAL_SEEK_CACHE.clear()
|
|
365
|
+
_LOCAL_SEEK_CACHE[cache_key] = (time.time(), out)
|
|
366
|
+
return out
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""macro_countries.py — 宏观域的「国家词」判定与国名/代码表。
|
|
3
|
+
|
|
4
|
+
## 为什么单独成模块
|
|
5
|
+
|
|
6
|
+
这张表 + 两个纯文本谓词原先住在 `engines_builders_data_macro.py` 里,而那个
|
|
7
|
+
模块会连带来整条 HTTP 栈(`engines_base` → `http_client` → urllib/ssl/cookiejar)。
|
|
8
|
+
`route.py` 为了调用 `is_foreign_macro_query` 就得在模块顶层 import 它——实测
|
|
9
|
+
**36 ms**,占 `import route` 的绝大部分(路由总耗时约 42 ms)。
|
|
10
|
+
|
|
11
|
+
路由是每次调用的必经路径(连缓存命中也要走一遍),而 HTTP 栈只有真正打网时
|
|
12
|
+
才需要。把纯逻辑与网络栈拆开,路由路径就再也不付这笔钱;顺带也不用为了省它
|
|
13
|
+
而引入「首次调用才 import」的惰性包装(那会带来隐藏状态与首调用毛刺)。
|
|
14
|
+
|
|
15
|
+
依赖只有 `re`。本模块必须保持零重依赖——这是它能被路由层直接引用的前提。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
__all__ = ["WORLDBANK_COUNTRIES", "match_country", "is_foreign_macro_query"]
|
|
23
|
+
|
|
24
|
+
# 国家名/别名 → ISO 代码。worldbank 引擎解析国家、fred 引擎国家词守卫、
|
|
25
|
+
# route 层分流的唯一来源(此前注释里写着「共用」,但三处都在同一个模块里,
|
|
26
|
+
# 拆出来之后这句话才真正成立)。
|
|
27
|
+
WORLDBANK_COUNTRIES: dict[str, str] = {
|
|
28
|
+
"中国": "CHN", "china": "CHN", "美国": "USA", "usa": "USA", "us": "USA",
|
|
29
|
+
"united states": "USA", "america": "USA", "日本": "JPN", "japan": "JPN",
|
|
30
|
+
"德国": "DEU", "germany": "DEU", "英国": "GBR", "uk": "GBR",
|
|
31
|
+
"united kingdom": "GBR", "britain": "GBR", "法国": "FRA", "france": "FRA",
|
|
32
|
+
"印度": "IND", "india": "IND", "巴西": "BRA", "brazil": "BRA",
|
|
33
|
+
"俄罗斯": "RUS", "russia": "RUS", "韩国": "KOR", "korea": "KOR",
|
|
34
|
+
"south korea": "KOR", "加拿大": "CAN", "canada": "CAN",
|
|
35
|
+
"澳大利亚": "AUS", "australia": "AUS", "意大利": "ITA", "italy": "ITA",
|
|
36
|
+
"西班牙": "ESP", "spain": "ESP", "世界": "WLD", "world": "WLD",
|
|
37
|
+
"global": "WLD", "欧元区": "EMU", "eurozone": "EMU", "香港": "HKG",
|
|
38
|
+
"hong kong": "HKG", "台湾": "TWN", "taiwan": "TWN", "新加坡": "SGP",
|
|
39
|
+
"singapore": "SGP",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def match_country(query: str, mapping: dict[str, str]) -> str:
|
|
44
|
+
"""大小写不敏感国家匹配(中文原名 + 英文别名)。
|
|
45
|
+
|
|
46
|
+
短别名(us/uk/eu 等 ≤3 字符)用词边界匹配,避免 'focus'/'consensus'
|
|
47
|
+
之类包含子串的普通词误判为国家。
|
|
48
|
+
"""
|
|
49
|
+
low = query.lower()
|
|
50
|
+
for name, code in mapping.items():
|
|
51
|
+
# 中文/长英文名直接子串匹配;短别名(≤3 字符)必须走词边界分支
|
|
52
|
+
if name in query and (not name.isascii() or len(name) > 3):
|
|
53
|
+
return code
|
|
54
|
+
if name.isascii():
|
|
55
|
+
if len(name) <= 3:
|
|
56
|
+
if re.search(rf"(?<![a-z]){re.escape(name)}(?![a-z])", low):
|
|
57
|
+
return code
|
|
58
|
+
elif name in low:
|
|
59
|
+
return code
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# 非美国国家/地区子集:FRED 是美国计算方式,只有「明确指向别国」时才让位。
|
|
64
|
+
# 从主表派生而非另抄一份,避免两张表漂移。
|
|
65
|
+
_NON_US_COUNTRIES: dict[str, str] = {
|
|
66
|
+
name: code for name, code in WORLDBANK_COUNTRIES.items() if code != "USA"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_foreign_macro_query(query: str) -> bool:
|
|
71
|
+
"""查询是否明确指向非美国国家/地区。
|
|
72
|
+
|
|
73
|
+
FRED 序列均为美国或全球计算方式,遇到「中国GDP」「日本通胀」这类查询时
|
|
74
|
+
应放弃响应(返回 True),由 worldbank 按国家参数接管,避免美国数据冒充。
|
|
75
|
+
|
|
76
|
+
判定复用 `match_country` 的词边界逻辑,而不是自己写一遍朴素子串匹配。
|
|
77
|
+
此前这里是 `name in query`:`us` 这类 ≤3 字符别名会命中任何含该子串的
|
|
78
|
+
英文词——实测 `focus on growth`、`consensus 预测`、`business survey`
|
|
79
|
+
全被判成「外国宏观查询」,FRED 因而被无谓地排除。同一个概念(国家词识别)
|
|
80
|
+
在同一模块里有两套行为不同的实现,正是这类漏判的来源。
|
|
81
|
+
"""
|
|
82
|
+
return bool(match_country(query, _NON_US_COUNTRIES))
|