argo-search 2.9.7 → 2.9.9

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 (154) hide show
  1. package/README.en.md +73 -150
  2. package/README.es.md +73 -150
  3. package/README.ja.md +79 -156
  4. package/README.ko.md +78 -155
  5. package/README.md +80 -159
  6. package/SKILL.md +3 -3
  7. package/assets/readme/hero.svg +2 -2
  8. package/backends/domain_profiles.json +48 -0
  9. package/backends/engine_registry.yaml +118 -4
  10. package/backends/quota_profiles.json +92 -12
  11. package/backends/source_types_cn.json +27 -1
  12. package/bin/argo +78 -5
  13. package/bin/argo.js +33 -2
  14. package/config.yaml +269 -63
  15. package/docs/ARGO_INTRO.md +5 -3
  16. package/docs/ENGINE_CATALOG.md +50 -39
  17. package/docs/MCP_SETUP.md +3 -1
  18. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  19. package/docs/RELEASE_NOTES_v2.9.9.md +78 -0
  20. package/engines/specs/cfets.yaml +41 -0
  21. package/engines/specs/defillama.yaml +38 -0
  22. package/engines/specs/firecrawl.yaml +1 -1
  23. package/engines/specs/fxtwitter.yaml +5 -0
  24. package/engines/specs/gleif.yaml +55 -0
  25. package/engines/specs/hkex.yaml +40 -0
  26. package/engines/specs/keenable_free.yaml +36 -0
  27. package/engines/specs/shfe.yaml +39 -0
  28. package/engines/specs/stackexchange.yaml +2 -2
  29. package/engines/specs/tavily_free.yaml +56 -0
  30. package/engines/specs/yuanbao.yaml +93 -0
  31. package/mcp/clients.yaml +32 -0
  32. package/package.json +7 -2
  33. package/packages/dsh-plugin/README.md +25 -6
  34. package/packages/dsh-plugin/dsh/client.js +402 -47
  35. package/packages/dsh-plugin/dsh/diagnostics.js +97 -0
  36. package/packages/dsh-plugin/dsh/index.js +135 -1876
  37. package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
  38. package/packages/dsh-plugin/dsh/runner.js +666 -0
  39. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  40. package/packages/dsh-plugin/package.json +3 -3
  41. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  42. package/packages/dsh-plugin/test/client.test.js +177 -2
  43. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  44. package/packages/dsh-plugin/test/index.test.js +44 -0
  45. package/scripts/adaptive.py +126 -43
  46. package/scripts/article_meta.py +131 -0
  47. package/scripts/bench_tail_ab.py +281 -0
  48. package/scripts/bounded_run.py +91 -49
  49. package/scripts/budget.py +247 -0
  50. package/scripts/budget_cli.py +93 -0
  51. package/scripts/build_release_assets.sh +65 -0
  52. package/scripts/cache.py +60 -72
  53. package/scripts/ci_drift_gate.py +90 -0
  54. package/scripts/clock_bridge.py +22 -0
  55. package/scripts/content_boundary.py +46 -0
  56. package/scripts/content_security.py +119 -4
  57. package/scripts/content_signals.py +26 -5
  58. package/scripts/cross_lingual_trace.py +293 -0
  59. package/scripts/dispatch_result.py +105 -0
  60. package/scripts/engine_add.py +602 -0
  61. package/scripts/engine_dispatch.py +58 -86
  62. package/scripts/engine_env.py +5 -0
  63. package/scripts/engine_families.py +6 -0
  64. package/scripts/engine_policy.py +7 -7
  65. package/scripts/engines.py +21 -0
  66. package/scripts/engines_base.py +3 -3
  67. package/scripts/engines_builders.py +12 -0
  68. package/scripts/engines_builders_batch10.py +48 -3
  69. package/scripts/engines_builders_batch14.py +656 -0
  70. package/scripts/engines_builders_batch15.py +249 -0
  71. package/scripts/engines_builders_keyless.py +111 -0
  72. package/scripts/engines_builders_tech.py +0 -1
  73. package/scripts/evidence.py +31 -3
  74. package/scripts/evidence_loop.py +58 -22
  75. package/scripts/fetch_quality.py +13 -0
  76. package/scripts/fetch_v3.py +28 -3
  77. package/scripts/gen_routing_snapshot.py +11 -0
  78. package/scripts/http_client.py +24 -38
  79. package/scripts/install.ps1 +18 -0
  80. package/scripts/install.sh +16 -1
  81. package/scripts/job.py +3 -3
  82. package/scripts/local_seek.py +41 -5
  83. package/scripts/macro_countries.py +15 -0
  84. package/scripts/matrix_search_eval.py +192 -0
  85. package/scripts/mcp_compact.py +234 -0
  86. package/scripts/mcp_handlers.py +60 -642
  87. package/scripts/mcp_launch.sh +10 -8
  88. package/scripts/mcp_local.py +241 -0
  89. package/scripts/mcp_server.py +19 -14
  90. package/scripts/mcp_setup.py +33 -9
  91. package/scripts/mcp_tools.py +1 -0
  92. package/scripts/mcp_transport.py +1 -1
  93. package/scripts/mcp_warm.py +127 -0
  94. package/scripts/net_proxy.py +27 -0
  95. package/scripts/output_transparency.py +200 -0
  96. package/scripts/paid_policy.py +98 -0
  97. package/scripts/quota.py +158 -13
  98. package/scripts/recovery.py +7 -3
  99. package/scripts/recovery_runner.py +150 -0
  100. package/scripts/replay_eval.py +18 -0
  101. package/scripts/research.py +37 -21
  102. package/scripts/research_expand.py +24 -1
  103. package/scripts/retry_policy.py +76 -0
  104. package/scripts/route.py +6 -6
  105. package/scripts/route_cache.py +18 -0
  106. package/scripts/route_combo.py +26 -5
  107. package/scripts/route_domains.py +170 -31
  108. package/scripts/safe_conn.py +52 -0
  109. package/scripts/search.py +15 -27
  110. package/scripts/search_attribution.py +100 -3
  111. package/scripts/search_cli.py +24 -11
  112. package/scripts/search_entry.py +8 -1
  113. package/scripts/search_output.py +99 -15
  114. package/scripts/search_pipeline.py +62 -195
  115. package/scripts/search_rank.py +6 -0
  116. package/scripts/search_types.py +107 -9
  117. package/scripts/semantic_evidence.py +179 -3
  118. package/scripts/serp_guard.py +138 -7
  119. package/scripts/serp_kill_log.py +25 -2
  120. package/scripts/stats_cli.py +13 -1
  121. package/scripts/tail_completion.py +212 -0
  122. package/scripts/url_safety.py +126 -3
  123. package/scripts/usage_log.py +45 -0
  124. package/scripts/wide_research.py +45 -0
  125. package/scripts/zhihu_quota.py +139 -0
  126. package/sub-skills/local-search/bm25.py +27 -3
  127. package/sub-skills/local-search/cli_runner.py +331 -0
  128. package/sub-skills/local-search/parsers.py +328 -0
  129. package/sub-skills/local-search/search_v3.py +31 -674
  130. package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
  131. package/assets/readme/settings-keys.png +0 -0
  132. package/assets/readme/settings-tools.png +0 -0
  133. package/sub-skills/ego-search/references/browser-runtime.md +0 -43
  134. package/sub-skills/ego-search/references/install.md +0 -67
  135. package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
  136. package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
  137. package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
  138. package/sub-skills/js-run/tests/test_jsrun.py +0 -165
  139. package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
  140. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
  141. package/sub-skills/local-seek/references/git-integration.md +0 -54
  142. package/sub-skills/local-seek/references/remote-handoff.md +0 -46
  143. package/sub-skills/local-seek/references/rg-recipes.md +0 -96
  144. package/sub-skills/local-seek/references/strategies.md +0 -52
  145. package/sub-skills/local-seek/references/structural-search.md +0 -59
  146. package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
  147. package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
  148. package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
  149. package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
  150. package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
  151. package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
  152. package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
  153. package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
  154. package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
@@ -1,313 +0,0 @@
1
- """seek_fts 单元测试:索引生命周期、CJK 短语、降级契约与结果 parity。
2
-
3
- 契约基线见 seek_fts 模块 docstring;行抽取口径对齐 rg 恒定 -i。
4
- """
5
- import json
6
- import os
7
- import subprocess
8
- import sys
9
- from pathlib import Path
10
-
11
- import pytest
12
-
13
- SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
14
- sys.path.insert(0, str(SCRIPTS))
15
-
16
- import seek_fts # noqa: E402
17
-
18
-
19
- @pytest.fixture()
20
- def fts_env(tmp_path, monkeypatch):
21
- """独立索引库 + 开关打开;返回 (状态目录, 搜索树)。"""
22
- state = tmp_path / "state"
23
- tree = tmp_path / "tree"
24
- state.mkdir()
25
- tree.mkdir()
26
- monkeypatch.setenv("ARGO_STATE_DIR", str(state))
27
- monkeypatch.setenv("ARGO_LOCAL_FTS", "1")
28
- return state, tree
29
-
30
-
31
- def _write(tree: Path, rel: str, text: str) -> Path:
32
- p = tree / rel
33
- p.parent.mkdir(parents=True, exist_ok=True)
34
- p.write_text(text, encoding="utf-8")
35
- return p
36
-
37
-
38
- def _build(tree):
39
- info = seek_fts.refresh(str(tree))
40
- assert info["complete"] == 1, f"首建必须完成: {info}"
41
- return info
42
-
43
-
44
- # ── 建库与查询 ────────────────────────────────────────────────────────────────
45
-
46
- def test_build_and_english_query(fts_env):
47
- _state, tree = fts_env
48
- _write(tree, "a.py", "def alpha(x):\n return StateRoot(x)\n")
49
- _write(tree, "b.md", "plain text without the term\n")
50
- _build(tree)
51
- rows = seek_fts.content_candidates("stateroot", str(tree))
52
- assert rows == [(str(tree / "a.py"), 2, "return StateRoot(x)")]
53
-
54
-
55
- def test_cjk_two_char_phrase(fts_env):
56
- """wechat-digger 实战坑回归:2 字中文必须以逐字短语命中。"""
57
- _state, tree = fts_env
58
- _write(tree, "n.md", "前面若干字,迁移完成于此。\n英文行 migration done\n")
59
- _build(tree)
60
- rows = seek_fts.content_candidates("迁移", str(tree))
61
- assert rows == [(str(tree / "n.md"), 1, "前面若干字,迁移完成于此。")]
62
-
63
-
64
- def test_case_insensitive_matches_rg(fts_env):
65
- """rg 恒定 -i:小写查询必须命中大写原文。"""
66
- _state, tree = fts_env
67
- _write(tree, "c.py", "USE UPPER CASE HERE\n")
68
- _build(tree)
69
- assert seek_fts.content_candidates("upper case", str(tree)) == [
70
- (str(tree / "c.py"), 1, "USE UPPER CASE HERE")]
71
-
72
-
73
- def test_line_truncation_matches_seek_truncate(fts_env):
74
- _state, tree = fts_env
75
- long_line = "x" * 200 + " needle " + "y" * 200
76
- _write(tree, "l.txt", long_line + "\n")
77
- _build(tree)
78
- rows = seek_fts.content_candidates("needle", str(tree))
79
- assert rows[0][2] == "x" * 119 + "…"
80
-
81
-
82
- # ── 降级契约 ─────────────────────────────────────────────────────────────────
83
-
84
- def test_disabled_flag_returns_none(fts_env, monkeypatch):
85
- _state, tree = fts_env
86
- _write(tree, "a.py", "needle here\n")
87
- _build(tree)
88
- monkeypatch.setenv("ARGO_LOCAL_FTS", "0")
89
- assert seek_fts.content_candidates("needle", str(tree)) is None
90
- assert not seek_fts.query_suitable("needle")
91
-
92
-
93
- def test_regex_metachars_rejected(fts_env):
94
- _state, tree = fts_env
95
- assert not seek_fts.query_suitable("foo.*bar")
96
- assert not seek_fts.query_suitable("interface{}")
97
- assert not seek_fts.query_suitable("a|b")
98
- assert seek_fts.query_suitable("plain_words")
99
-
100
-
101
- def test_incomplete_index_falls_back(fts_env):
102
- """未建成(complete=0)宁可慢不可漏:返回 None 走 rg。"""
103
- _state, tree = fts_env
104
- _write(tree, "a.py", "needle here\n")
105
- seek_fts.refresh(str(tree), budget_s=0.0) # 0 预算:清单都不走完
106
- assert seek_fts.content_candidates("needle", str(tree)) is None
107
-
108
-
109
- def test_zero_hit_on_complete_index_is_truthy_empty(fts_env):
110
- """索引完整时零命中是真没有([]),不再回退 rg 空扫。"""
111
- _state, tree = fts_env
112
- _write(tree, "a.py", "something\n")
113
- _build(tree)
114
- assert seek_fts.content_candidates("zzzznothing", str(tree)) == []
115
-
116
-
117
- def test_subdir_query_uses_parent_index(fts_env):
118
- _state, tree = fts_env
119
- _write(tree, "sub/deep.py", "needle in subdir\n")
120
- _write(tree, "top.py", "needle at top\n")
121
- _build(tree)
122
- rows = seek_fts.content_candidates("needle", str(tree / "sub"))
123
- assert rows == [(str(tree / "sub" / "deep.py"), 1, "needle in subdir")]
124
-
125
-
126
- # ── 增量生命周期 ─────────────────────────────────────────────────────────────
127
-
128
- def test_incremental_add_modify_delete(fts_env):
129
- _state, tree = fts_env
130
- f = _write(tree, "a.py", "old content needle\n")
131
- _write(tree, "gone.py", "needle will vanish\n")
132
- _build(tree)
133
- f.write_text("changed entirely\n", encoding="utf-8")
134
- _write(tree, "new.py", "fresh needle lands\n", )
135
- (tree / "gone.py").unlink()
136
- _build(tree) # 再次 refresh 收齐增删改
137
- assert seek_fts.content_candidates("needle", str(tree)) == [
138
- (str(tree / "new.py"), 1, "fresh needle lands")]
139
-
140
-
141
- def test_gitignore_respected_via_rg_listing(fts_env):
142
- """覆盖面 parity:git 仓里 .gitignore 的文件不进索引(rg --files 的语义)。
143
-
144
- 非 git 目录 rg 本来就不应用 .gitignore(搜索与清单同一行为,parity 成立),
145
- 所以这里必须建真仓。
146
- """
147
- _state, tree = fts_env
148
- subprocess.run(["git", "init", "-q", str(tree)], check=True, timeout=30)
149
- (tree / ".gitignore").write_text("ignored*\n", encoding="utf-8")
150
- _write(tree, "kept.py", "needle kept\n")
151
- _write(tree, "ignored.py", "needle hidden by gitignore\n")
152
- _build(tree)
153
- assert seek_fts.content_candidates("needle", str(tree)) == [
154
- (str(tree / "kept.py"), 1, "needle kept")]
155
-
156
-
157
- def test_binary_files_skipped(fts_env):
158
- _state, tree = fts_env
159
- (tree / "bin.dat").write_bytes(b"needle\x00binary")
160
- _write(tree, "text.py", "needle text\n")
161
- _build(tree)
162
- assert seek_fts.content_candidates("needle", str(tree)) == [
163
- (str(tree / "text.py"), 1, "needle text")]
164
-
165
-
166
- def test_large_body_capped(fts_env):
167
- """正文截到 _MAX_BODY_BYTES:更深行不在索引(已知偏差),截内行可命中。"""
168
- _state, tree = fts_env
169
- head = "needle in head\n"
170
- body = "x" * seek_fts._MAX_BODY_BYTES + "\n" # 超界大行,挤掉 tail
171
- tail = "needle in tail\n"
172
- _write(tree, "big.log", head + body + tail)
173
- _build(tree)
174
- rows = seek_fts.content_candidates("needle", str(tree))
175
- assert [r[2] for r in rows] == ["needle in head"]
176
-
177
-
178
- def test_oversized_file_skipped_entirely(fts_env, monkeypatch):
179
- """超过 _MAX_FILE_BYTES 的文件整体不入索引(2026-10-11:992MB 索引主因
180
- 是 9-33MB 会话转写被全文入库);限额调小便于测试,正文行 rg 兜底。"""
181
- _state, tree = fts_env
182
- monkeypatch.setattr(seek_fts, "_MAX_FILE_BYTES", 64)
183
- _write(tree, "small.py", "needle small\n")
184
- _write(tree, "rollout.jsonl", "x" * 200 + "\nneedle hidden\n")
185
- _build(tree)
186
- assert seek_fts.content_candidates("needle", str(tree)) == [
187
- (str(tree / "small.py"), 1, "needle small")]
188
-
189
-
190
- def test_machine_output_dirs_excluded(fts_env):
191
- """rollout/logs/backups 目录不入索引(机器输出非知识文件,rg 主路仍在)。"""
192
- _state, tree = fts_env
193
- _write(tree, "keep.md", "needle kept\n")
194
- _write(tree, "cli/rollout/sess.jsonl", "needle rollout\n")
195
- _write(tree, "cli/log/app.jsonl", "needle log\n")
196
- _write(tree, "v2/logs/run.txt", "needle logs\n")
197
- _write(tree, "backups/dump.sql", "needle backup\n")
198
- _build(tree)
199
- assert seek_fts.content_candidates("needle", str(tree)) == [
200
- (str(tree / "keep.md"), 1, "needle kept")]
201
-
202
-
203
- # ── 一致性与健壮性 ───────────────────────────────────────────────────────────
204
-
205
- def test_determinism_two_runs_same_order(fts_env):
206
- _state, tree = fts_env
207
- for i in range(6):
208
- _write(tree, f"f{i}.py", f"needle {i}\n")
209
- _build(tree)
210
- first = seek_fts.content_candidates("needle", str(tree))
211
- assert first == seek_fts.content_candidates("needle", str(tree))
212
-
213
-
214
- def test_corrupt_db_falls_back_to_rg(fts_env):
215
- _state, tree = fts_env
216
- _write(tree, "a.py", "needle here\n")
217
- _build(tree)
218
- seek_fts.index_db_path().write_bytes(b"not a sqlite file at all")
219
- assert seek_fts.content_candidates("needle", str(tree)) is None
220
-
221
-
222
- def test_mtime_window_filters_files(fts_env):
223
- import time as _t
224
- _state, tree = fts_env
225
- p = _write(tree, "a.py", "needle here\n")
226
- _build(tree)
227
- future = _t.time() + 3600
228
- os.utime(p, (future, future))
229
- seek_fts.refresh(str(tree)) # 把新 mtime 收进索引
230
- rows = seek_fts.content_candidates("needle", str(tree),
231
- until_ts=_t.time())
232
- assert rows == [] # 文件 mtime 在窗口外 → 不命中
233
-
234
-
235
- def test_cli_rebuild_and_status(fts_env):
236
- _state, tree = fts_env
237
- _write(tree, "a.py", "needle\n")
238
- env = dict(os.environ)
239
- r = subprocess.run(
240
- [sys.executable, str(SCRIPTS / "seek_fts.py"), "rebuild", str(tree)],
241
- capture_output=True, text=True, env=env, timeout=60)
242
- assert r.returncode == 0, r.stderr
243
- assert json.loads(r.stdout)["complete"] == 1
244
- r = subprocess.run(
245
- [sys.executable, str(SCRIPTS / "seek_fts.py"), "status", str(tree)],
246
- capture_output=True, text=True, env=env, timeout=60)
247
- assert json.loads(r.stdout)["indexed"] == 1
248
-
249
-
250
- def test_seek_integration_fts_and_rg_parity(fts_env):
251
- """端到端:同一条查询,FTS 开与关(rg 主路)结果集一致。"""
252
- _state, tree = fts_env
253
- _write(tree, "x.py", "alpha does stateroot things\nsecond line\n")
254
- _write(tree, "y.md", "另一段:状态根相关\nstateroot mentioned\n")
255
- _build(tree)
256
- env = dict(os.environ)
257
- cmd = [sys.executable, str(SCRIPTS / "seek.py"), "stateroot",
258
- "--path", str(tree), "--json", "--include-noise"]
259
- on = json.loads(subprocess.run(cmd, capture_output=True, text=True,
260
- env={**env, "ARGO_LOCAL_FTS": "1"},
261
- timeout=60).stdout)["results"]
262
- off = json.loads(subprocess.run(cmd, capture_output=True, text=True,
263
- env={**env, "ARGO_LOCAL_FTS": "0"},
264
- timeout=60).stdout)["results"]
265
- assert [(r["path"], r["line"]) for r in on] == \
266
- [(r["path"], r["line"]) for r in off]
267
- assert on, "两条路都必须命中"
268
-
269
-
270
- def test_type_filter_bypasses_fts_fastpath(fts_env):
271
- """带 --type 时不得走 FTS 快路(快路不做扩展名过滤,会静默混入范围外结果)。
272
-
273
- 回归(2026-10-12):此前 --type py 在 ARGO_LOCAL_FTS=1 下仍命中 .md 文件。
274
- """
275
- _state, tree = fts_env
276
- _write(tree, "hit.py", "stateroot in python\n")
277
- _write(tree, "noise.md", "stateroot in markdown\n")
278
- _build(tree)
279
- env = dict(os.environ)
280
- cmd = [sys.executable, str(SCRIPTS / "seek.py"), "stateroot",
281
- "--path", str(tree), "--type", "py", "--json", "--include-noise"]
282
- r = subprocess.run(cmd, capture_output=True, text=True,
283
- env={**env, "ARGO_LOCAL_FTS": "1"}, timeout=60)
284
- results = json.loads(r.stdout)["results"]
285
- assert results, "py 文件必须命中"
286
- assert all(x["path"].endswith(".py") for x in results), \
287
- f"--type py 下混入了非 py 结果: {[x['path'] for x in results]}"
288
-
289
-
290
- def test_exclude_filter_bypasses_fts_fastpath(fts_env):
291
- """带 --exclude 时不得走 FTS 快路(快路不做排除,会绕掉自污染守卫)。"""
292
- _state, tree = fts_env
293
- _write(tree, "keep.py", "stateroot keep\n")
294
- _write(tree, "skip.py", "stateroot skip\n")
295
- _build(tree)
296
- env = dict(os.environ)
297
- cmd = [sys.executable, str(SCRIPTS / "seek.py"), "stateroot",
298
- "--path", str(tree), "--exclude", "skip.py", "--json",
299
- "--include-noise"]
300
- r = subprocess.run(cmd, capture_output=True, text=True,
301
- env={**env, "ARGO_LOCAL_FTS": "1"}, timeout=60)
302
- results = json.loads(r.stdout)["results"]
303
- assert results and all(not x["path"].endswith("skip.py") for x in results)
304
-
305
-
306
- def test_like_prefix_dir_boundary(tmp_path):
307
- """_like_prefix 目录语义:/a/b 不得匹配 /a/bc(兄弟树误配回归)。"""
308
- (tmp_path / "a" / "b").mkdir(parents=True)
309
- (tmp_path / "a" / "bc").mkdir(parents=True)
310
- pat = seek_fts._like_prefix(str(tmp_path / "a" / "b"))
311
- assert pat.endswith(os.sep + "%")
312
- # 根路径特判://% 永不匹配(静默假空回归)
313
- assert seek_fts._like_prefix("/") == "/%"