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.
- package/README.en.md +73 -150
- package/README.es.md +73 -150
- package/README.ja.md +79 -156
- package/README.ko.md +78 -155
- package/README.md +80 -159
- package/SKILL.md +3 -3
- package/assets/readme/hero.svg +2 -2
- package/backends/domain_profiles.json +48 -0
- package/backends/engine_registry.yaml +118 -4
- package/backends/quota_profiles.json +92 -12
- package/backends/source_types_cn.json +27 -1
- package/bin/argo +78 -5
- package/bin/argo.js +33 -2
- package/config.yaml +269 -63
- package/docs/ARGO_INTRO.md +5 -3
- package/docs/ENGINE_CATALOG.md +50 -39
- package/docs/MCP_SETUP.md +3 -1
- package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
- package/docs/RELEASE_NOTES_v2.9.9.md +78 -0
- package/engines/specs/cfets.yaml +41 -0
- package/engines/specs/defillama.yaml +38 -0
- package/engines/specs/firecrawl.yaml +1 -1
- package/engines/specs/fxtwitter.yaml +5 -0
- package/engines/specs/gleif.yaml +55 -0
- package/engines/specs/hkex.yaml +40 -0
- package/engines/specs/keenable_free.yaml +36 -0
- package/engines/specs/shfe.yaml +39 -0
- package/engines/specs/stackexchange.yaml +2 -2
- package/engines/specs/tavily_free.yaml +56 -0
- package/engines/specs/yuanbao.yaml +93 -0
- package/mcp/clients.yaml +32 -0
- package/package.json +7 -2
- package/packages/dsh-plugin/README.md +25 -6
- package/packages/dsh-plugin/dsh/client.js +402 -47
- package/packages/dsh-plugin/dsh/diagnostics.js +97 -0
- package/packages/dsh-plugin/dsh/index.js +135 -1876
- package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
- package/packages/dsh-plugin/dsh/runner.js +666 -0
- package/packages/dsh-plugin/dsh/wide-research.js +878 -0
- package/packages/dsh-plugin/package.json +3 -3
- package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
- package/packages/dsh-plugin/test/client.test.js +177 -2
- package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
- package/packages/dsh-plugin/test/index.test.js +44 -0
- package/scripts/adaptive.py +126 -43
- package/scripts/article_meta.py +131 -0
- package/scripts/bench_tail_ab.py +281 -0
- package/scripts/bounded_run.py +91 -49
- package/scripts/budget.py +247 -0
- package/scripts/budget_cli.py +93 -0
- package/scripts/build_release_assets.sh +65 -0
- package/scripts/cache.py +60 -72
- package/scripts/ci_drift_gate.py +90 -0
- package/scripts/clock_bridge.py +22 -0
- package/scripts/content_boundary.py +46 -0
- package/scripts/content_security.py +119 -4
- package/scripts/content_signals.py +26 -5
- package/scripts/cross_lingual_trace.py +293 -0
- package/scripts/dispatch_result.py +105 -0
- package/scripts/engine_add.py +602 -0
- package/scripts/engine_dispatch.py +58 -86
- package/scripts/engine_env.py +5 -0
- package/scripts/engine_families.py +6 -0
- package/scripts/engine_policy.py +7 -7
- package/scripts/engines.py +21 -0
- package/scripts/engines_base.py +3 -3
- package/scripts/engines_builders.py +12 -0
- package/scripts/engines_builders_batch10.py +48 -3
- package/scripts/engines_builders_batch14.py +656 -0
- package/scripts/engines_builders_batch15.py +249 -0
- package/scripts/engines_builders_keyless.py +111 -0
- package/scripts/engines_builders_tech.py +0 -1
- package/scripts/evidence.py +31 -3
- package/scripts/evidence_loop.py +58 -22
- package/scripts/fetch_quality.py +13 -0
- package/scripts/fetch_v3.py +28 -3
- package/scripts/gen_routing_snapshot.py +11 -0
- package/scripts/http_client.py +24 -38
- package/scripts/install.ps1 +18 -0
- package/scripts/install.sh +16 -1
- package/scripts/job.py +3 -3
- package/scripts/local_seek.py +41 -5
- package/scripts/macro_countries.py +15 -0
- package/scripts/matrix_search_eval.py +192 -0
- package/scripts/mcp_compact.py +234 -0
- package/scripts/mcp_handlers.py +60 -642
- package/scripts/mcp_launch.sh +10 -8
- package/scripts/mcp_local.py +241 -0
- package/scripts/mcp_server.py +19 -14
- package/scripts/mcp_setup.py +33 -9
- package/scripts/mcp_tools.py +1 -0
- package/scripts/mcp_transport.py +1 -1
- package/scripts/mcp_warm.py +127 -0
- package/scripts/net_proxy.py +27 -0
- package/scripts/output_transparency.py +200 -0
- package/scripts/paid_policy.py +98 -0
- package/scripts/quota.py +158 -13
- package/scripts/recovery.py +7 -3
- package/scripts/recovery_runner.py +150 -0
- package/scripts/replay_eval.py +18 -0
- package/scripts/research.py +37 -21
- package/scripts/research_expand.py +24 -1
- package/scripts/retry_policy.py +76 -0
- package/scripts/route.py +6 -6
- package/scripts/route_cache.py +18 -0
- package/scripts/route_combo.py +26 -5
- package/scripts/route_domains.py +170 -31
- package/scripts/safe_conn.py +52 -0
- package/scripts/search.py +15 -27
- package/scripts/search_attribution.py +100 -3
- package/scripts/search_cli.py +24 -11
- package/scripts/search_entry.py +8 -1
- package/scripts/search_output.py +99 -15
- package/scripts/search_pipeline.py +62 -195
- package/scripts/search_rank.py +6 -0
- package/scripts/search_types.py +107 -9
- package/scripts/semantic_evidence.py +179 -3
- package/scripts/serp_guard.py +138 -7
- package/scripts/serp_kill_log.py +25 -2
- package/scripts/stats_cli.py +13 -1
- package/scripts/tail_completion.py +212 -0
- package/scripts/url_safety.py +126 -3
- package/scripts/usage_log.py +45 -0
- package/scripts/wide_research.py +45 -0
- package/scripts/zhihu_quota.py +139 -0
- package/sub-skills/local-search/bm25.py +27 -3
- package/sub-skills/local-search/cli_runner.py +331 -0
- package/sub-skills/local-search/parsers.py +328 -0
- package/sub-skills/local-search/search_v3.py +31 -674
- package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/sub-skills/ego-search/references/browser-runtime.md +0 -43
- package/sub-skills/ego-search/references/install.md +0 -67
- package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
- package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
- package/sub-skills/js-run/tests/test_jsrun.py +0 -165
- package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
- package/sub-skills/local-seek/references/git-integration.md +0 -54
- package/sub-skills/local-seek/references/remote-handoff.md +0 -46
- package/sub-skills/local-seek/references/rg-recipes.md +0 -96
- package/sub-skills/local-seek/references/strategies.md +0 -52
- package/sub-skills/local-seek/references/structural-search.md +0 -59
- package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
- package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
- package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
- package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
- package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
- package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# rg 分场景配方
|
|
2
|
-
|
|
3
|
-
统一入口 `seek.py` 已覆盖多数场景,以下配方用于非常规操作或手动微调。
|
|
4
|
-
所有命令均尊重 .gitignore,自动排除 node_modules 等(见 config/domains.yaml)。
|
|
5
|
-
|
|
6
|
-
## 基础
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
# 全文搜(当前目录)
|
|
10
|
-
rg "关键词"
|
|
11
|
-
|
|
12
|
-
# 指定目录
|
|
13
|
-
rg "关键词" ~/notes
|
|
14
|
-
|
|
15
|
-
# 忽略大小写
|
|
16
|
-
rg -i "keyword"
|
|
17
|
-
|
|
18
|
-
# 正则
|
|
19
|
-
rg "func[0-9]+\(" src/
|
|
20
|
-
|
|
21
|
-
# 固定字符串(避免正则转义,含特殊字符时用)
|
|
22
|
-
rg -F "a.b[c]" .
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## 定位类
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
# 只看文件名(最省 token,先看分布)
|
|
29
|
-
rg -l "关键词" .
|
|
30
|
-
rg --files-with-matches "关键词" .
|
|
31
|
-
|
|
32
|
-
# 每文件命中数(先数后看)
|
|
33
|
-
rg --count-matches "关键词" .
|
|
34
|
-
|
|
35
|
-
# 只看行号不输出内容
|
|
36
|
-
rg -l --line-number "关键词" .
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## 上下文类
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# 前后 N 行
|
|
43
|
-
rg -C 2 "关键词" .
|
|
44
|
-
rg -A 3 "关键词" . # 只后文
|
|
45
|
-
rg -B 1 "关键词" . # 只前文
|
|
46
|
-
|
|
47
|
-
# 输出到文件避免刷屏(超大批量时)
|
|
48
|
-
rg "关键词" . > /tmp/rg_out.txt
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 类型过滤
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# 按扩展名
|
|
55
|
-
rg -g "*.py" "关键词" .
|
|
56
|
-
rg -g "*.{py,ts,js}" "关键词" .
|
|
57
|
-
|
|
58
|
-
# 排除
|
|
59
|
-
rg -g "!test_*" "关键词" .
|
|
60
|
-
rg -g "!*.min.js" "关键词" .
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## 常用语义场景
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
# 函数/类定义在哪
|
|
67
|
-
rg -n "def handle_request|class AuthService" src/
|
|
68
|
-
# 跨语言:rg "function (getUser|setUser)" .
|
|
69
|
-
|
|
70
|
-
# 跨文件引用(谁在调用某函数)
|
|
71
|
-
rg "send_email\(" .
|
|
72
|
-
|
|
73
|
-
# TODO/FIXME 盘点
|
|
74
|
-
rg -n "TODO|FIXME|HACK" --type-add 'todo:*.{py,js,ts,go,rs}' -t todo .
|
|
75
|
-
|
|
76
|
-
# 只搜中文
|
|
77
|
-
rg -n "[\u4e00-\u9fff]" .
|
|
78
|
-
|
|
79
|
-
# 找 JSON/YAML 里的键
|
|
80
|
-
rg -n '"max_results"' config/
|
|
81
|
-
|
|
82
|
-
# 日志排查(最近日志)
|
|
83
|
-
rg "ERROR|panic" --glob "*.log" .
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## 性能
|
|
87
|
-
|
|
88
|
-
- 大仓库先 `--count` 或 `-l`,避免输出千行
|
|
89
|
-
- 指定 `-g` 类型或目录,rg 只扫必要范围
|
|
90
|
-
- 超过 30s 无结果:换词或换 mdfind(--spotlight),不要重复等待
|
|
91
|
-
- rg 退出码:0=有匹配,1=无匹配,2=报错。脚本已处理,手动用时注意
|
|
92
|
-
|
|
93
|
-
## 非代码文件(可选扩展)
|
|
94
|
-
|
|
95
|
-
rga(ripgrep-all)未安装时可临时用 mdfind 覆盖 PDF/Office 场景;
|
|
96
|
-
若常搜这类文件,装 rga:`brew install ripgrep-all`
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# 渐进式策略与 token 纪律
|
|
2
|
-
|
|
3
|
-
本 skill 的核心不是「更快地搜索」,而是「用最少的 token 拿到答案」。
|
|
4
|
-
工具输出就是压缩结果,模型不该有探索成本。
|
|
5
|
-
|
|
6
|
-
## 三层渐进(永远按此顺序)
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
L1 定位 seek.py "词" --count / --filename
|
|
10
|
-
→ 输出「文件:命中数」或文件路径列表,每行 < 80 字符
|
|
11
|
-
↓ 从中挑出最相关的 1-3 个文件
|
|
12
|
-
L2 上下文 seek.py "词" --context 2 --path <该文件所在目录>
|
|
13
|
-
→ 输出「路径:行号:命中行 ± 2 行」
|
|
14
|
-
↓ 确定要精读的段落
|
|
15
|
-
L3 精读 read_file 该文件,用行号偏移局部读取
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
任何一步已经能回答用户,就停。不要为完整性多搜。
|
|
19
|
-
|
|
20
|
-
## 搜索范围扩大的顺序(先窄后宽)
|
|
21
|
-
|
|
22
|
-
1. 当前目录 + 默认排除(最常命中)
|
|
23
|
-
2. `--path` 扩大到知识域(~/notes、~/Documents 等,见 domains.yaml)
|
|
24
|
-
3. `--spotlight` 全盘兜底
|
|
25
|
-
|
|
26
|
-
禁止跳级:用户问「找代码里某个函数」却直接 mdfind 全盘,浪费且不准。
|
|
27
|
-
|
|
28
|
-
## 输出量控制
|
|
29
|
-
|
|
30
|
-
| 手段 | 效果 |
|
|
31
|
-
|------|------|
|
|
32
|
-
| `--max 20` | 截断结果数,防止刷屏 |
|
|
33
|
-
| `--count` | 只报分布,不报内容 |
|
|
34
|
-
| `--type py,ts` | 只扫目标类型 |
|
|
35
|
-
| `--context 0`(默认) | 不输出上下文行 |
|
|
36
|
-
| `-l` 手动模式 | 只列文件名 |
|
|
37
|
-
|
|
38
|
-
## 常见失败与换词策略
|
|
39
|
-
|
|
40
|
-
| 现象 | 处理 |
|
|
41
|
-
|------|------|
|
|
42
|
-
| 无匹配 | 换同义词/拆词/去掉修饰词,重试 1 次后换工具 |
|
|
43
|
-
| 命中过多 | 加 `--type` / `--max` / 更具体的关键词(加引号做短语匹配) |
|
|
44
|
-
| 结果太泛 | 指定目录缩小范围 |
|
|
45
|
-
| 搜代码但命中注释/文档 | 换更精确的符号名或结构模式 |
|
|
46
|
-
| Spotlight 搜不到 | 确认目录已索引;退回 rg + 明确 --path |
|
|
47
|
-
|
|
48
|
-
## 与 Argo 的分工
|
|
49
|
-
|
|
50
|
-
- 用户要「网上查」→ argo(联网)
|
|
51
|
-
- 用户要「本地找/搜我的文件」→ local-seek
|
|
52
|
-
- 混合需求(如「查网上的同时翻本地笔记」)→ 并行调用,互不干扰
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# 结构搜索手册(--structural)
|
|
2
|
-
|
|
3
|
-
按「语义」而非「字符串」检索代码模式:找裸 except、空 catch、装饰函数这类
|
|
4
|
-
「看着就知道有问题/有特征」的结构。零安装实现:rg -U 多行匹配 + 语言感知
|
|
5
|
-
pattern,本机装 ast-grep 后可升级为 AST 精确匹配(见文末)。
|
|
6
|
-
|
|
7
|
-
## 用法
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# 按语义名或中文别名检索,输出同普通搜索(路径:行号:片段)
|
|
11
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py "裸except" --structural
|
|
12
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py "empty-catch" --structural --path ~/repo
|
|
13
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py "函数定义" --structural --path ~/repo --type py
|
|
14
|
-
python3 ~/.agents/skills/argo/sub-skills/local-seek/scripts/seek.py "不存在语义" --structural # 报可用列表
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
查询词可写语义名(empty-catch)或任意中文别名(空catch、裸异常),大小写不敏感。
|
|
18
|
-
|
|
19
|
-
## 内置规则
|
|
20
|
-
|
|
21
|
-
| 语义名 | 中文别名 | 查什么 | 覆盖语言 |
|
|
22
|
-
|--------|---------|--------|---------|
|
|
23
|
-
| empty-catch | 空catch、空捕获、空异常处理 | `catch (...) {}` 空体 | js/jsx/ts/tsx |
|
|
24
|
-
| bare-except | 裸except、无类型except | `except:` 无异常类型 | py |
|
|
25
|
-
| unwrapped-error | 错误未包装、裸return err | `return err` 直接返回错误 | go |
|
|
26
|
-
| decorated-fn | 装饰函数、装饰器函数 | 带 `@decorator` 的函数定义 | py |
|
|
27
|
-
| function-def | 函数定义、找函数 | 函数定义行 | py/js/ts/go/rs |
|
|
28
|
-
| class-def | 类定义、找类 | 类/结构体定义行 | py/js/ts/go/java |
|
|
29
|
-
|
|
30
|
-
## 实现说明
|
|
31
|
-
|
|
32
|
-
- 每条规则 = 若干 (扩展名集合, 正则) 对,逐语言跑 `rg -U -i --line-number`。
|
|
33
|
-
- `-U` 启用多行模式,decorated-fn 才能跨行匹配 `@装饰器\n def`。
|
|
34
|
-
- 语言无命中(rg 退出码 1)静默跳过,不报错。
|
|
35
|
-
- 与正文搜索共用排除规则(domains.yaml + --exclude)。
|
|
36
|
-
- 局限:正则近似,非真实 AST。缩进风格怪异的代码、嵌套场景可能漏报;
|
|
37
|
-
`catch {}` 内只有注释也算空体(正则按字面空体判断)。
|
|
38
|
-
|
|
39
|
-
## 扩展新规则
|
|
40
|
-
|
|
41
|
-
在 seek.py 的 `STRUCTURAL_RULES` 字典追加一条:
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
"todo-count": {
|
|
45
|
-
"aliases": ("待办统计", "todo盘点"),
|
|
46
|
-
"patterns": [
|
|
47
|
-
(("py", "js", "ts"), r"TODO|FIXME|HACK"),
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
规则键即语义名,aliases 提供中文入口,patterns 按语言给正则。
|
|
53
|
-
加完跑 `python3 scripts/eval_seek.py` 确认无回归,再补一条结构用例。
|
|
54
|
-
|
|
55
|
-
## 升级路径(装 ast-grep 后)
|
|
56
|
-
|
|
57
|
-
ast-grep 能精确匹配 AST 节点(真·空 catch 块、未捕获的裸 except),
|
|
58
|
-
消除正则的漏报误报。届时在 structural_search 里检测 `sg` 命令存在则优先
|
|
59
|
-
AST,缺失时回落当前 rg -U 实现,行为对调用方透明。
|
|
@@ -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
|