argo-search 2.9.7 → 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 (140) hide show
  1. package/README.en.md +58 -145
  2. package/README.es.md +58 -145
  3. package/README.ja.md +64 -151
  4. package/README.ko.md +63 -150
  5. package/README.md +65 -154
  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 +74 -5
  13. package/bin/argo.js +33 -2
  14. package/config.yaml +219 -60
  15. package/docs/ENGINE_CATALOG.md +50 -39
  16. package/docs/MCP_SETUP.md +3 -1
  17. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  18. package/engines/specs/cfets.yaml +41 -0
  19. package/engines/specs/defillama.yaml +38 -0
  20. package/engines/specs/firecrawl.yaml +1 -1
  21. package/engines/specs/fxtwitter.yaml +5 -0
  22. package/engines/specs/gleif.yaml +55 -0
  23. package/engines/specs/hkex.yaml +40 -0
  24. package/engines/specs/keenable_free.yaml +36 -0
  25. package/engines/specs/shfe.yaml +39 -0
  26. package/engines/specs/stackexchange.yaml +2 -2
  27. package/engines/specs/tavily_free.yaml +56 -0
  28. package/engines/specs/yuanbao.yaml +93 -0
  29. package/mcp/clients.yaml +32 -0
  30. package/package.json +6 -2
  31. package/packages/dsh-plugin/README.md +23 -5
  32. package/packages/dsh-plugin/dsh/client.js +304 -15
  33. package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
  34. package/packages/dsh-plugin/dsh/index.js +112 -1875
  35. package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
  36. package/packages/dsh-plugin/dsh/runner.js +666 -0
  37. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  38. package/packages/dsh-plugin/package.json +2 -2
  39. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  40. package/packages/dsh-plugin/test/client.test.js +175 -1
  41. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  42. package/packages/dsh-plugin/test/index.test.js +44 -0
  43. package/scripts/adaptive.py +24 -1
  44. package/scripts/article_meta.py +131 -0
  45. package/scripts/budget.py +247 -0
  46. package/scripts/budget_cli.py +93 -0
  47. package/scripts/build_release_assets.sh +65 -0
  48. package/scripts/cache.py +60 -72
  49. package/scripts/ci_drift_gate.py +90 -0
  50. package/scripts/content_boundary.py +46 -0
  51. package/scripts/content_signals.py +26 -5
  52. package/scripts/cross_lingual_trace.py +293 -0
  53. package/scripts/dispatch_result.py +98 -0
  54. package/scripts/engine_dispatch.py +49 -86
  55. package/scripts/engine_env.py +5 -0
  56. package/scripts/engine_families.py +6 -0
  57. package/scripts/engine_policy.py +7 -7
  58. package/scripts/engines.py +21 -0
  59. package/scripts/engines_base.py +3 -3
  60. package/scripts/engines_builders.py +12 -0
  61. package/scripts/engines_builders_batch10.py +48 -3
  62. package/scripts/engines_builders_batch14.py +656 -0
  63. package/scripts/engines_builders_batch15.py +249 -0
  64. package/scripts/engines_builders_keyless.py +111 -0
  65. package/scripts/engines_builders_tech.py +0 -1
  66. package/scripts/evidence.py +31 -3
  67. package/scripts/evidence_loop.py +54 -22
  68. package/scripts/fetch_v3.py +25 -0
  69. package/scripts/gen_routing_snapshot.py +11 -0
  70. package/scripts/install.ps1 +18 -0
  71. package/scripts/install.sh +16 -1
  72. package/scripts/job.py +3 -3
  73. package/scripts/local_seek.py +41 -5
  74. package/scripts/macro_countries.py +15 -0
  75. package/scripts/matrix_search_eval.py +82 -0
  76. package/scripts/mcp_compact.py +234 -0
  77. package/scripts/mcp_handlers.py +60 -642
  78. package/scripts/mcp_launch.sh +10 -8
  79. package/scripts/mcp_local.py +241 -0
  80. package/scripts/mcp_server.py +19 -14
  81. package/scripts/mcp_setup.py +33 -9
  82. package/scripts/mcp_tools.py +1 -0
  83. package/scripts/mcp_transport.py +1 -1
  84. package/scripts/mcp_warm.py +127 -0
  85. package/scripts/output_transparency.py +200 -0
  86. package/scripts/paid_policy.py +98 -0
  87. package/scripts/quota.py +158 -13
  88. package/scripts/recovery_runner.py +150 -0
  89. package/scripts/replay_eval.py +18 -0
  90. package/scripts/research.py +37 -21
  91. package/scripts/research_expand.py +24 -1
  92. package/scripts/retry_policy.py +76 -0
  93. package/scripts/route.py +6 -6
  94. package/scripts/route_cache.py +18 -0
  95. package/scripts/route_combo.py +7 -0
  96. package/scripts/route_domains.py +32 -19
  97. package/scripts/search.py +15 -27
  98. package/scripts/search_attribution.py +100 -3
  99. package/scripts/search_cli.py +24 -11
  100. package/scripts/search_entry.py +7 -1
  101. package/scripts/search_output.py +99 -15
  102. package/scripts/search_pipeline.py +42 -193
  103. package/scripts/search_types.py +107 -9
  104. package/scripts/semantic_evidence.py +179 -3
  105. package/scripts/serp_guard.py +115 -7
  106. package/scripts/serp_kill_log.py +25 -2
  107. package/scripts/stats_cli.py +13 -1
  108. package/scripts/wide_research.py +45 -0
  109. package/scripts/zhihu_quota.py +139 -0
  110. package/sub-skills/local-search/bm25.py +27 -3
  111. package/sub-skills/local-search/cli_runner.py +331 -0
  112. package/sub-skills/local-search/parsers.py +328 -0
  113. package/sub-skills/local-search/search_v3.py +31 -674
  114. package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
  115. package/assets/readme/settings-keys.png +0 -0
  116. package/assets/readme/settings-tools.png +0 -0
  117. package/scripts/image_vision/vision_probe +0 -0
  118. package/scripts/local_engines.py +0 -321
  119. package/sub-skills/ego-search/references/browser-runtime.md +0 -43
  120. package/sub-skills/ego-search/references/install.md +0 -67
  121. package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
  122. package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
  123. package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
  124. package/sub-skills/js-run/tests/test_jsrun.py +0 -165
  125. package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
  126. package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
  127. package/sub-skills/local-seek/references/git-integration.md +0 -54
  128. package/sub-skills/local-seek/references/remote-handoff.md +0 -46
  129. package/sub-skills/local-seek/references/rg-recipes.md +0 -96
  130. package/sub-skills/local-seek/references/strategies.md +0 -52
  131. package/sub-skills/local-seek/references/structural-search.md +0 -59
  132. package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
  133. package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
  134. package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
  135. package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
  136. package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
  137. package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
  138. package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
  139. package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
  140. package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
@@ -1,222 +0,0 @@
1
- #!/usr/bin/env python3
2
- """tests/test_content_rank.py — 内容 BM25F 重排 + fzf V2 评分契约(2026-10-07 方案甲)。
3
-
4
- 守四类不变式:
5
- A. 重排闸门:逃生门/正则查询/count/context 一律保持 (路径,行号) 原序
6
- B. 重排质量:文件名命中 > 纯行命中;多词全覆盖 > 单词擦边;噪声档
7
- 分层在重排后依然成立(真源在前)
8
- C. 确定性:打分只依赖内容,发射序打乱输出逐位一致(2026-10-06 契约延续)
9
- D. fzf V2:边界/驼峰/连续/gap 惩罚的相对次序与 fzf 口径一致,全等最优
10
-
11
- 打桩纪律:unittest.mock.patch.object(自动恢复)——直接给 s.run 赋值且
12
- 不恢复会把同进程后续测试的真实 rg 顶掉(2026-10-06 实锤教训,见
13
- test_determinism_20261006.py 模块头)。
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import subprocess
19
- import sys
20
- import unittest
21
- from pathlib import Path
22
- from unittest.mock import patch
23
-
24
- ROOT = Path(__file__).resolve().parent.parent
25
- sys.path.insert(0, str(ROOT / "scripts"))
26
-
27
- import seek as s # noqa: E402
28
- import seek_rank as r # noqa: E402
29
-
30
-
31
- def _proc(stdout: str, rc: int = 0) -> subprocess.CompletedProcess:
32
- return subprocess.CompletedProcess(args=[], returncode=rc,
33
- stdout=stdout, stderr="")
34
-
35
-
36
- class TestRerankGate(unittest.TestCase):
37
- """A. 闸门:不该重排的场景必须保持 (路径,行号) 原序。"""
38
-
39
- ROWS = [
40
- ("b/beta.py", 5, "alpha"),
41
- ("a/alpha.py", 9, "alpha"),
42
- ("c/gamma.md", 1, "alpha"),
43
- ]
44
-
45
- def _search(self, raw_query, fixed=True, ctx=0, count=False):
46
- with patch.object(s, "run", return_value=_proc("a/x.py:1:hit\n")):
47
- # 直接对池行调用重排闸门(不走 rg,隔离网络与文件系统)
48
- return s._rerank_content(list(self.ROWS), raw_query, ctx, count, fixed)
49
-
50
- def test_escape_hatch_restores_order(self):
51
- import os
52
- old = os.environ.pop("ARGO_SEEK_CONTENT_RERANK", None)
53
- os.environ["ARGO_SEEK_CONTENT_RERANK"] = "0"
54
- try:
55
- self.assertEqual(self._search("alpha"), self.ROWS)
56
- finally:
57
- os.environ.pop("ARGO_SEEK_CONTENT_RERANK", None)
58
- if old is not None:
59
- os.environ["ARGO_SEEK_CONTENT_RERANK"] = old
60
-
61
- def test_regex_query_not_reranked(self):
62
- # fixed=False(正则/PCRE2):词元切分只会产生碎片,闸门必须放行原序
63
- self.assertEqual(self._search(r"\d{4}", fixed=False), self.ROWS)
64
-
65
- def test_count_and_context_not_reranked(self):
66
- self.assertEqual(self._search("alpha", count=True), self.ROWS)
67
- self.assertEqual(self._search("alpha", ctx=3), self.ROWS)
68
-
69
- def test_no_token_query_not_reranked(self):
70
- self.assertEqual(self._search("!!!"), self.ROWS)
71
-
72
-
73
- class TestRerankQuality(unittest.TestCase):
74
- """B. 重排质量:相关度次序的三条核心判据。"""
75
-
76
- def test_filename_match_beats_content_only(self):
77
- rows = [
78
- ("docs/notes.md", 12, "支持噪声档降权的说明文字。"),
79
- ("src/noise_tier.py", 3, "placeholder"),
80
- ("src/other.py", 8, "噪声相关但文件无关。"),
81
- ]
82
- out = r.rank_rows(rows, "noise_tier")
83
- self.assertEqual(out[0][0], "src/noise_tier.py")
84
-
85
- def test_multi_token_coverage_beats_partial(self):
86
- rows = [
87
- ("a/one.md", 1, "只提到缓存一个词。"),
88
- ("b/both.md", 2, "语义缓存与 minhash 软命中都在这行。"),
89
- ]
90
- out = r.rank_rows(rows, "语义缓存 minhash")
91
- self.assertEqual(out[0][0], "b/both.md")
92
-
93
- def test_verbatim_phrase_beats_bigram_scatter(self):
94
- # 二元组分不清「噪声档的实现行」与「只有噪声二字的无关行」:
95
- # 逐字短语加成必须把前者顶上去
96
- rows = [
97
- ("a/x.md", 1, "设备噪声很大。"),
98
- ("b/y.py", 2, "噪声档的阈值定义在此。"),
99
- ]
100
- out = r.rank_rows(rows, "噪声档")
101
- self.assertEqual(out[0][0], "b/y.py")
102
-
103
- def test_ties_break_by_path_line(self):
104
- rows = [("b.py", 2, "same"), ("a.py", 9, "same"), ("a.py", 1, "same")]
105
- out = r.rank_rows(rows, "same")
106
- self.assertEqual([(p, l) for p, l, _ in out],
107
- [("a.py", 1), ("a.py", 9), ("b.py", 2)])
108
-
109
- def test_noise_floor_after_rerank(self):
110
- # 重排改变池内次序后,噪声档分层(真源在前)必须依然成立——
111
- # 分层发生在 rg_search 的 _apply_noise_floor(重排之后),走全链路验证
112
- rows = [
113
- "tmp/x.py:1:noise tier keyword",
114
- "src/y.py:2:noise tier keyword here",
115
- ]
116
- with patch.object(s, "run", return_value=_proc("\n".join(rows) + "\n")):
117
- out, err = s.rg_search(["noise"], ".", [], [], 0, False, 5,
118
- fixed=True, raw_query="noise tier keyword")
119
- self.assertIsNone(err)
120
- self.assertTrue(out, "真源行不应消失")
121
- self.assertTrue(out[0][0].startswith("src/"),
122
- f"真源应排最前,实际: {out[0][0]}")
123
-
124
-
125
- class TestRerankDeterminism(unittest.TestCase):
126
- """C. 确定性:打平发射序(2026-10-06 契约在重排开启下延续)。"""
127
-
128
- def test_output_independent_of_emission_order(self):
129
- rows = [f"src/f{i:03d}.py:{j + 1}:alpha beta hit {i}-{j}"
130
- for i in range(40) for j in range(1)]
131
- with patch.object(s, "run", return_value=_proc("\n".join(rows) + "\n")):
132
- a, err = s.rg_search(["alpha"], ".", [], [], 0, False, 5,
133
- fixed=True, raw_query="alpha beta")
134
- import random
135
- shuffled = rows[:]
136
- random.Random(7).shuffle(shuffled)
137
- with patch.object(s, "run", return_value=_proc("\n".join(shuffled) + "\n")):
138
- b, err2 = s.rg_search(["alpha"], ".", [], [], 0, False, 5,
139
- fixed=True, raw_query="alpha beta")
140
- self.assertIsNone(err)
141
- self.assertIsNone(err2)
142
- self.assertEqual(a, b)
143
-
144
-
145
- class TestFzfV2(unittest.TestCase):
146
- """D. fzf FuzzyMatchV2 口径。"""
147
-
148
- def test_no_match_is_zero(self):
149
- self.assertEqual(s._fzf_score("zzz.txt", "rep"), 0)
150
-
151
- def test_smart_case(self):
152
- self.assertGreater(s._fzf_score("Report_v2.md", "report"), 0)
153
- # 模式带大写 → 区分大小写:R 匹配、r 不配
154
- self.assertEqual(s._fzf_score("report.md", "R"), 0)
155
-
156
- def test_boundary_beats_midword(self):
157
- # bar 在 foo_bar(词边界)> xbarx(词中)
158
- self.assertGreater(s._fzf_score("foo_bar.py", "bar"),
159
- s._fzf_score("xbarx.py", "bar"))
160
-
161
- def test_consecutive_beats_gappy(self):
162
- self.assertGreater(s._fzf_score("xabc.md", "abc"),
163
- s._fzf_score("axbxc.md", "abc"))
164
-
165
- def test_camel_bonus(self):
166
- self.assertGreater(s._fzf_score("parseHTTPResponse.py", "hr"), 0)
167
-
168
- def test_dp_optimal_alignment(self):
169
- # xaabc 搜 ab:DP 应发现「跳过词首 a、取边界 b」的对齐优于顺位连打
170
- # (顺位贪心:a@1 + b@4 有 gap;最优:a@1 + b@4 不如 a@3?——此处锚定
171
- # 已知反超事实:xaabc 的分必须高于等长无边界对照 axbxc 的同模式分)
172
- self.assertGreater(s._fzf_score("xaabc", "ab"),
173
- s._fzf_score("axbxc", "ab"))
174
-
175
- def test_exact_beats_partial(self):
176
- self.assertGreater(s._fzf_score("seek.py", "seek.py"),
177
- s._fzf_score("seek_helper.py", "seek.py"))
178
-
179
- def test_exact_case_beats_icase(self):
180
- self.assertGreaterEqual(s._fzf_score("Seek.py", "seek.py"),
181
- s._fzf_score("seek.py", "seek.py")
182
- ) # 全等大写版含 case bonus,不小于纯 icase
183
-
184
-
185
- class TestFrecencyWiring(unittest.TestCase):
186
- """frecency 默认关闭:不启用时不产生任何行为差异。"""
187
-
188
- def test_disabled_by_default(self):
189
- import os
190
- old = os.environ.pop("ARGO_SEEK_FRECENCY", None)
191
- try:
192
- import seek_frecency
193
- self.assertFalse(seek_frecency.enabled())
194
- self.assertEqual(seek_frecency.boost("/any/path"), 0.0)
195
- finally:
196
- if old is not None:
197
- os.environ["ARGO_SEEK_FRECENCY"] = old
198
-
199
- def test_boost_and_record_roundtrip(self):
200
- import os
201
- import tempfile
202
- old_flag = os.environ.pop("ARGO_SEEK_FRECENCY", None)
203
- old_db = os.environ.pop("ARGO_SEEK_FRECENCY_DB", None)
204
- with tempfile.TemporaryDirectory() as td:
205
- os.environ["ARGO_SEEK_FRECENCY"] = "1"
206
- os.environ["ARGO_SEEK_FRECENCY_DB"] = str(Path(td) / "f.json")
207
- try:
208
- import seek_frecency
209
- seek_frecency.record(["/a/b.py", "/c/d.py"])
210
- self.assertGreater(seek_frecency.boost("/a/b.py"), 0.0)
211
- self.assertEqual(seek_frecency.boost("/z/missing.py"), 0.0)
212
- finally:
213
- os.environ.pop("ARGO_SEEK_FRECENCY", None)
214
- os.environ.pop("ARGO_SEEK_FRECENCY_DB", None)
215
- if old_flag is not None:
216
- os.environ["ARGO_SEEK_FRECENCY"] = old_flag
217
- if old_db is not None:
218
- os.environ["ARGO_SEEK_FRECENCY_DB"] = old_db
219
-
220
-
221
- if __name__ == "__main__":
222
- unittest.main()
@@ -1,145 +0,0 @@
1
- #!/usr/bin/env python3
2
- """tests/test_determinism_20261006.py — 宽查询输出确定性回归(方案一,2026-10-06)。
3
-
4
- 守 v1.4.1 审计遗留的定性修复:
5
- 1. rg 并行遍历发射序不定 →「先到先得」截断拿到任意的 cap 条,同查询
6
- 两次跑结果集与排序都不同。修复后有界堆按 (路径,行号,内容) 选池,
7
- **同一命中集无论以什么顺序发射,输出逐条一致**。
8
- 2. count 模式并列计数按路径升序决出稳定次序。
9
- 3. grep 兜底路径同款语义。
10
- 4. 锁定 ja/ko 2-gram 既有行为(CJK_RE 覆盖假名/谚文——2026-10-06 实测
11
- 已支持,修正审计记忆里「无分词只能整词匹配」的过时记录)。
12
-
13
- 打桩纪律:一律 unittest.mock.patch.object(自动恢复)。首版直接给
14
- s.run 赋值且不恢复,把同进程后续测试的真实 rg 全部替换成假输出——
15
- 套件 9 红的根因(2026-10-06 实锤,教训记录在此防再犯)。
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import subprocess
21
- import sys
22
- import unittest
23
- from pathlib import Path
24
- from unittest.mock import patch
25
-
26
- ROOT = Path(__file__).resolve().parent.parent
27
- sys.path.insert(0, str(ROOT / "scripts"))
28
-
29
- import seek as s # noqa: E402
30
-
31
-
32
- def _proc(stdout: str, rc: int = 0) -> subprocess.CompletedProcess:
33
- return subprocess.CompletedProcess(args=[], returncode=rc,
34
- stdout=stdout, stderr="")
35
-
36
-
37
- def _matches(n_files: int, lines_per: int = 1) -> list[str]:
38
- """n_files 个文件的 rg 命中行(路径 f{i:03d}.py 保证字典序可控)。"""
39
- out = []
40
- for i in range(n_files):
41
- for j in range(lines_per):
42
- out.append(f"src/f{i:03d}.py:{j + 1}:hit {i}-{j}")
43
- return out
44
-
45
-
46
- class RgBodyDeterminism(unittest.TestCase):
47
- """rg 主体路径:池子选择与发射序无关。"""
48
-
49
- def test_output_independent_of_emission_order(self):
50
- rows = _matches(40)
51
- # max_results=5 → cap = max(5, min(20, 400)) = 20:命中 40 > cap,
52
- # 池子选择真正发生截断
53
- for drop_noise in (True, False):
54
- with self.subTest(drop_noise=drop_noise):
55
- with patch.object(s, "run",
56
- return_value=_proc("\n".join(rows) + "\n")):
57
- a, err = s.rg_search(["q"], ".", [], [], 0, False, 5,
58
- fixed=True, drop_noise=drop_noise)
59
- with patch.object(s, "run",
60
- return_value=_proc(
61
- "\n".join(reversed(rows)) + "\n")):
62
- b, err2 = s.rg_search(["q"], ".", [], [], 0, False, 5,
63
- fixed=True, drop_noise=drop_noise)
64
- self.assertIsNone(err)
65
- self.assertIsNone(err2)
66
- self.assertEqual(a, b, "同一命中集、不同发射序 → 输出必须一致")
67
-
68
- def test_pool_is_path_smallest_and_sorted(self):
69
- rows = _matches(40)
70
- with patch.object(s, "run",
71
- return_value=_proc("\n".join(rows) + "\n")):
72
- out, err = s.rg_search(["q"], ".", [], [], 0, False, 5,
73
- fixed=True, drop_noise=False)
74
- self.assertIsNone(err)
75
- self.assertEqual([r[0] for r in out],
76
- [f"src/f{i:03d}.py" for i in range(5)])
77
- self.assertEqual(len(out), 5)
78
-
79
- def test_noise_floor_layering_deterministic(self):
80
- """clean 在前 noisy 在后的分层语义在确定池子上仍然成立。"""
81
- rows = (["src/clean_{:03d}.py:1:hit".format(i) for i in range(30)]
82
- + ["src/repos/lib_{:03d}.py:1:hit".format(i) for i in range(30)])
83
- outs = []
84
- for order in (rows, list(reversed(rows))):
85
- with patch.object(s, "run",
86
- return_value=_proc("\n".join(order) + "\n")):
87
- out, err = s.rg_search(["q"], ".", [], [], 0, False, 5,
88
- fixed=True, drop_noise=True)
89
- self.assertIsNone(err)
90
- outs.append(out)
91
- self.assertEqual(outs[0], outs[1])
92
- self.assertTrue(all("/repos/" not in r[0] for r in outs[0]),
93
- "clean 够数时噪声档不得混入")
94
-
95
-
96
- class CountTieDeterminism(unittest.TestCase):
97
- def test_equal_counts_sorted_by_path(self):
98
- rows = ["src/b.py:5", "src/a.py:5", "src/c.py:2"]
99
- with patch.object(s, "run",
100
- return_value=_proc("\n".join(rows) + "\n")):
101
- out, err = s.rg_search(["q"], ".", [], [], 0, True, 10,
102
- fixed=True, drop_noise=False)
103
- self.assertIsNone(err)
104
- self.assertEqual([r[0] for r in out],
105
- ["src/a.py", "src/b.py", "src/c.py"])
106
-
107
-
108
- class GrepFallbackDeterminism(unittest.TestCase):
109
- def test_body_and_count_deterministic(self):
110
- rows = _matches(40)
111
- for order in (rows, list(reversed(rows))):
112
- with patch.object(s, "run",
113
- return_value=_proc("\n".join(order) + "\n")):
114
- body, err = s.grep_search("grep", ["q"], ".", [], [], 0,
115
- False, 5, fixed=True)
116
- self.assertIsNone(err)
117
- self.assertEqual([r[0] for r in body],
118
- [f"src/f{i:03d}.py" for i in range(5)])
119
- cnt_rows = ["src/b.py:5", "src/a.py:5"]
120
- with patch.object(s, "run",
121
- return_value=_proc("\n".join(cnt_rows) + "\n")):
122
- cnt, err = s.grep_search("grep", ["q"], ".", [], [], 0,
123
- True, 10, fixed=True)
124
- self.assertIsNone(err)
125
- self.assertEqual([r[0] for r in cnt], ["src/a.py", "src/b.py"])
126
-
127
-
128
- class JaKoBigramLocked(unittest.TestCase):
129
- """锁定既有行为(实测于 2026-10-06):CJK_RE 已覆盖假名/谚文。"""
130
-
131
- def test_japanese_compound_gets_bigrams(self):
132
- pats, literal = s.build_patterns("プロンプトエンジニアリング入門")
133
- self.assertIn("プロンプトエンジニアリング入門", pats) # 整词
134
- self.assertIn("プロ", pats) # 首个 2-gram
135
- self.assertTrue(literal)
136
-
137
- def test_korean_spaced_words_and_compound(self):
138
- pats, _ = s.build_patterns("프로프트 검색 설정")
139
- self.assertIn("프로프트", pats) # 复合词整词
140
- self.assertIn("프로", pats) # 复合词 2-gram
141
- self.assertIn("검색", pats) # 空格分隔词保持整词
142
-
143
-
144
- if __name__ == "__main__":
145
- unittest.main()
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env python3
2
- """噪声档降权的回归测试(2026-09-27 新增,方案 C3)。
3
-
4
- 实测背景(Documents/GPT 搜 "import",rg 61899 处命中):
5
- vendor/生成物/归档 7% + 测试/fixture/benchmark 27% + tmp/ 7% = 41%
6
- 搜代码时,四成命中是「不想看到的东西」。原来的 DEFAULT_EXCLUDES 挡掉了
7
- node_modules/dist 之类,但漏了三类最大的:repos/(克隆仓)、tests|fixtures|
8
- benchmark、tmp/。
9
-
10
- **为什么是降权不是排除**:排除 = 这些内容永远搜不到。搜「某个克隆仓里怎么
11
- 写的」「我的测试怎么写的」是真实且常见的用法,排除会让工具在这些查询上
12
- 直接回答「未找到匹配」——那比返回噪声更糟。降权 + 无命中自动回落,两者兼得。
13
-
14
- 本文件锁定的性质:
15
- 1. 默认搜不到噪声档内容(降权生效);
16
- 2. 噪声档独有内容**仍可达**(回落生效,不制造「明明有却搜不到」);
17
- 3. --include-noise 显式关闭降权;
18
- 4. 回落不影响正常命中(有真源时不去噪声档白跑一趟)。
19
- """
20
- from __future__ import annotations
21
-
22
- import os
23
- import subprocess
24
- import sys
25
- from pathlib import Path
26
-
27
- SEEK = Path(__file__).resolve().parent.parent / "scripts" / "seek.py"
28
-
29
-
30
- def _have(*tools) -> bool:
31
- import shutil
32
- return all(shutil.which(t) for t in tools)
33
-
34
-
35
- def _run(args, cwd):
36
- return subprocess.run([sys.executable, str(SEEK)] + args, cwd=cwd,
37
- capture_output=True, text=True, timeout=120)
38
-
39
-
40
- def _make_tree(root: Path):
41
- """构造一棵含「真实源」与「噪声档」两类内容的最小目录树。
42
-
43
- 标记词刻意选 `zzmarker_*`:pytest 的 tmp_path 目录名会带测试函数名
44
- (如 `test_noise_tier_is_deprioritiz0`),若标记词是 `pytest` 之类,
45
- 路径本身就会命中,测试变成在验证「路径里有这个词」而非「噪声档降权」。
46
- 这是第一版用例写错的地方。
47
- """
48
- (root / "src").mkdir(parents=True)
49
- (root / "src" / "app.py").write_text("import os\nimport sys\n", encoding="utf-8")
50
- (root / "repos" / "thirdparty").mkdir(parents=True)
51
- (root / "repos" / "thirdparty" / "lib.py").write_text(
52
- "import zzzarker_clonelib\n", encoding="utf-8")
53
- (root / "tests").mkdir()
54
- (root / "tests" / "test_x.py").write_text(
55
- "import zzzarker_fixture\n", encoding="utf-8")
56
-
57
-
58
- def test_noise_tier_is_deprioritized_and_reachable(tmp_path):
59
- if not _have("rg"):
60
- import pytest
61
- pytest.skip("rg 未安装")
62
- _make_tree(tmp_path)
63
- proj = str(tmp_path)
64
-
65
- # 噪声档独有内容:搜得到(相对根判定,本例的 tests/ 在根之下),
66
- # 且**不触发**回落重搜——它本来就没被排除,只是被排到真源之后。
67
- r = _run(["zzzarker_fixture", "--path", proj], tmp_path)
68
- assert "test_x.py" in r.stdout, r.stdout
69
- assert "噪声档" not in r.stdout, "没被排除就不该多跑一趟回落"
70
-
71
- # 正常源:不受降权影响
72
- r2 = _run(["import", "--path", proj], tmp_path)
73
- assert "app.py" in r2.stdout, r2.stdout
74
-
75
-
76
- def test_deprioritized_means_source_wins(tmp_path):
77
- """同一查询同时命中真源与噪声档时,真源必须排在前面。"""
78
- if not _have("rg"):
79
- import pytest
80
- pytest.skip("rg 未安装")
81
- (tmp_path / "src").mkdir()
82
- (tmp_path / "src" / "real.py").write_text("import shared_token\n", encoding="utf-8")
83
- (tmp_path / "repos").mkdir()
84
- (tmp_path / "repos" / "copy.py").write_text("import shared_token\n", encoding="utf-8")
85
- proj = str(tmp_path)
86
-
87
- r = _run(["shared_token", "--path", proj, "--max", "1"], tmp_path)
88
- assert "real.py" in r.stdout, r.stdout
89
- assert "copy.py" not in r.stdout, r.stdout
90
-
91
-
92
- def test_include_noise_flag_is_explicit(tmp_path):
93
- if not _have("rg"):
94
- import pytest
95
- pytest.skip("rg 未安装")
96
- _make_tree(tmp_path)
97
- proj = str(tmp_path)
98
- r = _run(["zzzarker_fixture", "--path", proj, "--include-noise"], tmp_path)
99
- assert "test_x.py" in r.stdout, r.stdout
100
- # 显式开启时不应再回落(那会多跑一趟)
101
- assert "噪声档" not in r.stdout, r.stdout
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env python3
2
- """pcre2 判据回归测试(2026-09-27)。
3
-
4
- 守的缺陷:`pcre2_supported()` 曾用「跑一次匹配看返回码」判定 rg 是否支持 PCRE2:
5
-
6
- proc = run(["rg", "--pcre2", "-e", "x", os.devnull])
7
- _pcre2_ok = proc is not None and proc.returncode == 0
8
-
9
- `/dev/null` 永远没有匹配,rg 无匹配时返回 1,于是判据恒为 False——本机
10
- rg 明明带 +pcre2,却永远被判为「未编译 PCRE2」,所有 look-around /
11
- 反向引用查询被拒绝(用户可见的报错:「请简化查询」)。
12
-
13
- 根因是**判据的观察量与被判定的性质无关**:探测目标的匹配结果说明不了
14
- 二进制是否带某个特性。修法改读 `rg --version` 的 features 行。
15
-
16
- 这组测试把「返回码语义」钉死:只 mock run() 的返回值,覆盖
17
- - features 行含 +pcre2 → True
18
- - features 行是 -pcre2 → False
19
- - rg 不存在(run 返回 None)→ False
20
- - 只判 features 行,不被「匹配有无」影响(原缺陷的形态)
21
-
22
- 运行:
23
- cd ~/.agents/skills/argo
24
- python3 -m pytest sub-skills/local-seek/tests/test_pcre2_probe.py -q
25
- """
26
-
27
- from __future__ import annotations
28
-
29
- import subprocess
30
- import sys
31
- import unittest
32
- from pathlib import Path
33
- from unittest import mock
34
-
35
- SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
36
- sys.path.insert(0, str(SCRIPTS))
37
-
38
- import seek as s # noqa: E402
39
-
40
-
41
- def _proc(stdout: str, returncode: int = 0):
42
- p = mock.Mock()
43
- p.stdout = stdout
44
- p.returncode = returncode
45
- return p
46
-
47
-
48
- class TestPcre2Probe(unittest.TestCase):
49
- def setUp(self):
50
- # 模块级缓存必须在每个用例前清掉,否则第一个用例的结果会被后续复用
51
- self._saved = s._pcre2_ok
52
- s._pcre2_ok = None
53
- self.addCleanup(lambda: setattr(s, "_pcre2_ok", self._saved))
54
-
55
- def test_features_line_with_pcre2_is_true(self):
56
- """正面:features:+pcre2 → 支持(本机 rg 15.0.0 的真实形态)。"""
57
- out = "ripgrep 15.0.0 (rev 3a612f88b8)\n\nfeatures:+pcre2\nsimd(compile):+NEON\n"
58
- with mock.patch.object(s, "run", return_value=_proc(out)):
59
- self.assertTrue(s.pcre2_supported())
60
-
61
- def test_features_line_without_pcre2_is_false(self):
62
- """反面:features 行明确是 -pcre2 → 不支持,走 grep 回退。"""
63
- out = "ripgrep 13.0.0\n\nfeatures:-pcre2\n"
64
- with mock.patch.object(s, "run", return_value=_proc(out)):
65
- self.assertFalse(s.pcre2_supported())
66
-
67
- def test_missing_rg_is_false(self):
68
- """rg 不存在时 run() 返回 None → False(不得抛异常)。"""
69
- with mock.patch.object(s, "run", return_value=None):
70
- self.assertFalse(s.pcre2_supported())
71
-
72
- def test_does_not_depend_on_match_result(self):
73
- """核心回归:判据不得再依赖「有没有匹配」。
74
-
75
- 原缺陷的形态是 `returncode == 0`——把「无匹配(1)」误读成「不支持」。
76
- 这里给一个**无匹配语义**的返回码 1 但 features 行含 +pcre2,
77
- 新判据必须仍判 True。若有人把它改回看返回码,本用例会红。
78
- """
79
- out = "ripgrep 15.0.0\n\nfeatures:+pcre2\n"
80
- with mock.patch.object(s, "run", return_value=_proc(out, returncode=1)):
81
- self.assertTrue(s.pcre2_supported(),
82
- "判据又依赖返回码了:无匹配(rc=1)被误读成不支持 PCRE2")
83
-
84
- def test_probe_targets_version_not_a_match_run(self):
85
- """确认探测命令本身是 --version,而不是「跑一次匹配」。"""
86
- captured = {}
87
-
88
- def _fake(cmd, **kwargs):
89
- captured["cmd"] = cmd
90
- return _proc("ripgrep 15.0.0\nfeatures:+pcre2\n")
91
-
92
- with mock.patch.object(s, "run", side_effect=_fake):
93
- s.pcre2_supported()
94
- self.assertIn("--version", captured["cmd"],
95
- "探测命令必须是 rg --version(读特性声明),不是匹配探测")
96
-
97
- def test_module_cache_avoids_repeated_probe(self):
98
- """缓存语义:只探一次,第二次不再调 run()。"""
99
- calls = []
100
-
101
- def _fake(cmd, **kwargs):
102
- calls.append(cmd)
103
- return _proc("ripgrep 15.0.0\nfeatures:+pcre2\n")
104
-
105
- with mock.patch.object(s, "run", side_effect=_fake):
106
- s.pcre2_supported()
107
- s.pcre2_supported()
108
- s.pcre2_supported()
109
- self.assertEqual(len(calls), 1, "模块级缓存失效,重复探测")
110
-
111
-
112
- class TestPcre2EndToEnd(unittest.TestCase):
113
- """真机对照:本机 rg 带 +pcre2 时,look-around 查询真的能出结果。
114
-
115
- 不断言「一定支持」(别的机器可能没有),而是断言**判据与真机能力一致**:
116
- 先读 rg --version 拿真值,再要求 pcre2_supported() 与之一致。
117
- """
118
-
119
- def test_agrees_with_real_rg(self):
120
- try:
121
- real = subprocess.run(["rg", "--version"], capture_output=True,
122
- text=True, timeout=10)
123
- except (FileNotFoundError, subprocess.TimeoutExpired):
124
- self.skipTest("本机无 rg,跳过真机对照")
125
- truth = "+pcre2" in (real.stdout or "")
126
- saved = s._pcre2_ok
127
- s._pcre2_ok = None
128
- try:
129
- self.assertEqual(s.pcre2_supported(), truth,
130
- "判据与 rg 自报的特性不一致")
131
- finally:
132
- s._pcre2_ok = saved
133
-
134
-
135
- if __name__ == "__main__":
136
- unittest.main()
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env python3
2
- """local-seek 文件名相关性排序 + 拼音首字母单测(离线)
3
-
4
- 覆盖:
5
- 1. pinyin_initials:中文→拼音首字母(pypinyin 优先 / GB2312 保底)
6
- 2. _fzf_score:fzf 式文件名评分(smart case / 连续 / 边界 / 全等)
7
- 3. _file_pinyin_bonus:正向(中文→缩写)与反向(缩写→中文文件名)加分
8
- 4. _rank_path_results:按相关性 + mtime 排序
9
- 5. _looks_like_pinyin_abbrev:拼音缩写判定
10
-
11
- 运行:
12
- cd ~/.agents/skills/argo
13
- python3.14 -m pytest sub-skills/local-seek/tests/test_rank_pinyin.py -q
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import sys
19
- import unittest
20
- from pathlib import Path
21
-
22
- SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
23
- sys.path.insert(0, str(SCRIPTS))
24
-
25
- import seek as s # noqa: E402
26
-
27
-
28
- class TestPinyin(unittest.TestCase):
29
- def test_zh_initials(self):
30
- self.assertEqual(s.pinyin_initials("新建夹"), "xjj")
31
-
32
- def test_non_cjk_kept(self):
33
- self.assertIn("report", s.pinyin_initials("report_v2.md"))
34
-
35
- def test_empty(self):
36
- self.assertEqual(s.pinyin_initials(""), "")
37
-
38
-
39
- class TestFzfScore(unittest.TestCase):
40
- def test_match_boundary(self):
41
- self.assertGreater(s._fzf_score("新建夹.pdf", "新"), 0)
42
-
43
- def test_continuous(self):
44
- self.assertGreater(s._fzf_score("report_v2.md", "rep"), 0)
45
-
46
- def test_no_match(self):
47
- self.assertEqual(s._fzf_score("zzz.txt", "rep"), 0)
48
-
49
- def test_case_insensitive(self):
50
- self.assertGreater(s._fzf_score("Report_v2.md", "report"), 0)
51
-
52
-
53
- class TestPinyinBonus(unittest.TestCase):
54
- def test_forward(self):
55
- self.assertEqual(s._file_pinyin_bonus("新建夹.pdf", "新建夹"), 50)
56
-
57
- def test_reverse(self):
58
- self.assertEqual(s._file_pinyin_bonus("新建夹.pdf", "xjj"), 8)
59
-
60
- def test_none(self):
61
- self.assertEqual(s._file_pinyin_bonus("other.txt", "xjj"), 0)
62
-
63
-
64
- class TestRank(unittest.TestCase):
65
- def test_rank_top_by_relevance(self):
66
- r = [("a/zzz.txt", 0, ""), ("b/新建夹.pdf", 0, ""), ("c/report_v2.md", 0, "")] # noqa: E741
67
- ranked = s._rank_path_results(r, "新")
68
- self.assertEqual(ranked[0][0], "b/新建夹.pdf")
69
-
70
- def test_abbrev_look(self):
71
- self.assertTrue(s._looks_like_pinyin_abbrev("xjj"))
72
- self.assertFalse(s._looks_like_pinyin_abbrev("新"))
73
- self.assertFalse(s._looks_like_pinyin_abbrev("report"))
74
-
75
-
76
- if __name__ == "__main__":
77
- unittest.main()