chore: import zh skill smart-explore

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:01 +08:00
commit b898ce77f8
2 changed files with 518 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# WeHub 来源说明
- Skill 名称:`smart-explore`
- 中文类目:把代码库结构化为可检索工件
- 上游仓库:`thedotmack__claude-mem`
- 上游路径:`plugin/skills/smart-explore/SKILL.md`
- 上游链接:https://github.com/thedotmack/claude-mem/blob/HEAD/plugin/skills/smart-explore/SKILL.md
- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理
- 原作者、版权和许可证信息以上游仓库为准
+509
View File
@@ -0,0 +1,509 @@
---
name: smart-explore
description: "使用 tree-sitter AST 解析进行渐进式代码探索——先看结构,再深入细节。将每个文件的代码阅读量从 10-15k token 减少到 200-500 token。"
effort: low
---
# Smart Explore——渐进式代码探索
> **技能**:先阅读代码结构,再阅读代码。先让 Claude 展示函数签名和类型,然后仅在需要时深入查看特定函数。
**灵感来源**Alex NewmanClaude-MEM+ Aider 仓库地图模式(在 40k+ star 项目中得到验证)
## 问题
当 Claude 读取文件以理解代码库时,它会读取全部内容:
```
# 实际发生的情况
Read src/auth.rs → 400 行 → ~2,800 tokens
Read src/session.rs → 300 行 → ~2,100 tokens
Read src/user.rs → 500 行 → ~3,500 tokens
# 总计:3 个文件消耗 8,400 tokens
```
其中大部分内容都是无关的。Claude 只需要知道 `auth.rs` 中有 `fn login()``fn logout()`——而不是 400 行的实现细节。
**渐进式探索解决了这个问题**
```
步骤 1:auth.rs 里有什么? → ~200 tokens(仅签名)
步骤 2:展示 fn login() 主体 → ~350 tokens(一个函数)
步骤 3:谁调用了 login() → ~150 tokens(交叉引用)
# 总计:700 tokens,而不是 8,400——减少 92%
```
## 何时使用
| 信号 | 使用 smart-explore | 使用标准 Read |
|--------|-------------------|-------------------|
| "理解这个模块/功能" | ✅ | ❌ |
| 探索不熟悉的代码库 | ✅ | ❌ |
| 查找在何处添加功能 | ✅ | ❌ |
| 需要阅读某个特定函数 | ❌ | ✅ |
| 调试已知行 | ❌ | ✅ |
| 文件 < 100 行 | ❌ | ✅(直接读取即可) |
**不适用场景**
- 小型项目(< 20 个文件)——额外开销不划算
- 单文件任务——直接 Read 更快
- 已经知道要读什么——直接跳转
## 决策树
```
探索任务?
├─ 是,理解一个模块
│ └─ 每个文件超过 200 行?
│ ├─ 是 → smart-explore(先看结构)
│ └─ 否 → 直接 Read(文件很小)
├─ 搜索特定内容
│ └─ 按名称/模式 → Grep
│ └─ 按含义 → grepai 语义搜索
└─ 需要某个特定函数 → 使用 Read 加偏移量
```
## 三种方案(从易到难)
### 方案 A:无需设置——渐进式阅读规范
无需安装。只需改变你提示 Claude 的方式。
**添加到你的 CLAUDE.md**(或直接指示 Claude):
```markdown
## 代码探索协议
当被要求探索代码库或理解模块时:
1. **先看结构**:使用 Grep 查找函数/类定义
Rust
`rg "^\s*(pub\s+)?(async\s+)?fn |^\s*(pub\s+)?(struct|enum|trait|impl)\s" src/ --no-heading -n`
Python/TypeScript/JS
`rg "^\s*(async\s+)?(def |function |class |export (function|class|const))" src/ --no-heading -n`
注意:使用 `^\s*` 而不是 `^`——impl 块和类主体内部的方法是有缩进的。
`^` 模式会漏掉约 70% 的 Rust 方法。
2. **识别相关符号**:根据名称,挑选 2-3 个来阅读
3. **定向阅读**:使用 Read 加偏移量/行数限制来阅读特定函数
- 只读 auth.rs 的第 45-90 行,而不是整个文件
4. **交叉引用**:仅在需要时使用 Grep 查找调用方
- `rg "fn_name" --type rust -n`
探索时切勿从头到尾阅读文件。始终先看结构。
```
**适用**:任何 Claude Code 会话,零依赖。
---
### 方案 Btree-sitter CLI + 提取脚本
安装 tree-sitter CLI 并使用轻量级 Python 脚本提取签名。
**安装**
```bash
# macOS
brew install tree-sitter
# 验证
tree-sitter --version
```
**提取签名脚本**——保存为 `~/.claude/scripts/extract-signatures.py`
```python
#!/usr/bin/env python3
"""Extract function/class signatures from source files using tree-sitter CLI."""
import subprocess
import sys
import json
import re
from pathlib import Path
def extract_signatures(file_path: str) -> list[str]:
"""Extract function and type signatures without bodies."""
path = Path(file_path)
# Detect language from extension
lang_map = {
".rs": "rust", ".py": "python", ".ts": "typescript",
".tsx": "tsx", ".js": "javascript", ".jsx": "jsx",
".go": "go", ".rb": "ruby", ".java": "java",
}
lang = lang_map.get(path.suffix)
if not lang:
return [f"# Unsupported: {path.suffix}"]
# Use tree-sitter to parse and get JSON AST
try:
result = subprocess.run(
["tree-sitter", "parse", file_path, "--json"],
capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
return [f"# Parse error: {result.stderr[:100]}"]
except FileNotFoundError:
return ["# tree-sitter not installed: brew install tree-sitter"]
except subprocess.TimeoutExpired:
return ["# Parse timeout"]
# Read actual source for signature extraction
source_lines = path.read_text().splitlines()
signatures = []
# Regex-based signature extraction (faster than full AST for this use case)
patterns = {
"rust": [
(r"^(\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+[^{]*?)(?:\{|$)", "fn"),
(r"^(\s*(?:pub\s+)?struct\s+\w+[^{]*?)(?:\{|$)", "struct"),
(r"^(\s*(?:pub\s+)?enum\s+\w+[^{]*?)(?:\{|$)", "enum"),
(r"^(\s*(?:pub\s+)?trait\s+\w+[^{]*?)(?:\{|$)", "trait"),
(r"^(\s*impl\s+[^{]+?)(?:\{|$)", "impl"),
],
"python": [
(r"^(\s*(?:async\s+)?def\s+\w+[^:]*:)", "fn"),
(r"^(\s*class\s+\w+[^:]*:)", "class"),
],
"typescript": [
(r"^(\s*(?:export\s+)?(?:async\s+)?function\s+\w+[^{]*?)(?:\{|$)", "fn"),
(r"^(\s*(?:export\s+)?(?:default\s+)?class\s+\w+[^{]*?)(?:\{|$)", "class"),
(r"^(\s*(?:export\s+)?(?:const|let)\s+\w+\s*=\s*(?:async\s+)?\([^)]*\)\s*=>)", "arrow"),
(r"^(\s*(?:export\s+)?(?:interface|type)\s+\w+[^{=]*?)(?:\{|=|$)", "type"),
],
"go": [
(r"^(\s*func\s+[^{]+?)(?:\{|$)", "fn"),
(r"^(\s*type\s+\w+\s+(?:struct|interface)[^{]*?)(?:\{|$)", "type"),
],
"javascript": [
(r"^(\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+\w+[^{]*?)(?:\{|$)", "fn"),
(r"^(\s*(?:export\s+)?(?:default\s+)?class\s+\w+[^{]*?)(?:\{|$)", "class"),
(r"^(\s*(?:export\s+)?(?:const|let)\s+\w+\s*=\s*(?:async\s+)?\([^)]*\)\s*=>)", "arrow"),
],
}
lang_patterns = patterns.get(lang, [])
for i, line in enumerate(source_lines, 1):
for pattern, sig_type in lang_patterns:
match = re.match(pattern, line)
if match:
sig = match.group(1).strip().rstrip("{").strip()
signatures.append(f" {sig_type} {sig} (line {i})")
break
return signatures
def explore_directory(directory: str, extensions: list[str] | None = None) -> None:
"""Print structure of all source files in directory."""
if extensions is None:
extensions = [".rs", ".py", ".ts", ".tsx", ".js", ".go"]
path = Path(directory)
files = sorted(
f for ext in extensions
for f in path.rglob(f"*{ext}")
if not any(part.startswith(".") or part in ("node_modules", "target", "__pycache__", "dist")
for part in f.parts)
)
for file in files:
rel_path = file.relative_to(path)
sigs = extract_signatures(str(file))
if sigs:
print(f"\n{rel_path}:")
for sig in sigs:
print(sig)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: extract-signatures.py <file_or_dir> [ext1 ext2 ...]")
sys.exit(1)
target = sys.argv[1]
exts = sys.argv[2:] if len(sys.argv) > 2 else None
if Path(target).is_file():
sigs = extract_signatures(target)
for sig in sigs:
print(sig)
else:
explore_directory(target, exts)
```
**设为可执行**
```bash
chmod +x ~/.claude/scripts/extract-signatures.py
```
**用法**
```bash
# 单个文件
python3 ~/.claude/scripts/extract-signatures.py src/auth.rs
# 整个目录
python3 ~/.claude/scripts/extract-signatures.py src/
# 指定扩展名
python3 ~/.claude/scripts/extract-signatures.py src/ .ts .tsx
```
**输出示例**(针对一个 500 行的 Rust 文件):
```
src/auth.rs:
fn pub fn new(config: AuthConfig) -> Self (line 12)
fn pub async fn login(username: &str, password: &str) -> Result<Session> (line 28)
fn pub async fn logout(session_id: Uuid) -> Result<()> (line 67)
fn pub fn validate_session(token: &str) -> bool (line 89)
struct pub struct AuthConfig (line 110)
struct pub struct Session (line 125)
impl impl AuthService (line 140)
```
**Token 消耗**:每个文件约 50-150,而完整读取需要 2,000-5,000。
**添加到 CLAUDE.md** 以自动执行此操作:
```markdown
## 代码结构工具
在读取多个文件之前,运行:
`python3 ~/.claude/scripts/extract-signatures.py <directory>`
这会显示所有函数签名(不含文件主体)。用于识别需要阅读的特定函数,
然后使用 Read 加行偏移量。
```
---
### 方案 C:MCP 服务器(推荐用于大型项目)
对于超过 50 个文件的代码库,索引化 MCP 服务器提供更快的查找速度并能处理跨文件引用。
**按使用场景的最佳选择**
| 使用场景 | 推荐工具 | 安装 |
|---|---|---|
| 通用代码探索 | mcp-server-tree-sitter | `pip install mcp-server-tree-sitter` |
| PR 代码审查 | code-review-graph | `pip install code-review-graph` |
| 符号密集型工作流 | jCodeMunch(非商业用途) | `claude mcp add jcodemunch uvx jcodemunch-mcp` |
#### 选项 C1mcp-server-tree-sitter
```bash
pip install mcp-server-tree-sitter
# 添加到 Claude Code
claude mcp add tree-sitter python -m mcp_server_tree_sitter
```
**安装后可用的工具**
- `get_file_structure`——文件的签名和类型
- `run_ast_query`——自定义 tree-sitter 查询(高级)
- `find_symbols`——按名称在整个代码库中搜索
- `analyze_dependencies`——跨文件引用分析
**在 Claude Code 中配置**`~/.claude/settings.json`):
```json
{
"mcpServers": {
"tree-sitter": {
"command": "python",
"args": ["-m", "mcp_server_tree_sitter"]
}
}
}
```
#### 选项 C2code-review-graph(最适合 PR 审查)
```bash
pip install code-review-graph
code-review-graph install
```
**新增功能**:审查 PR 时,Claude 会自动获取变更文件及其依赖关系图。Claude 无需读取 30 个文件来理解变更影响,只需查看实际相关的 5 个文件。
**用法**
```
/review-pr 123
# code-review-graph 自动提供:
# - 变更的文件
# - 导入了已变更模块的文件
# - 受影响的类型定义
# - 变更代码的测试覆盖情况
```
#### 选项 C3jCodeMunch(符号查找)
```bash
claude mcp add jcodemunch uvx jcodemunch-mcp
```
**注意**:个人/开源项目免费。商业使用 $79/开发者。团队采用前请检查许可条款。
添加后,Claude 可以调用:
```
get_symbol("login") → 函数主体
find_callers("login") → 谁调用了它
get_class_hierarchy("User") → 继承树
get_dependencies("auth.rs") → 它导入了什么
```
---
## 工作流示例
### 示例 1:理解不熟悉的模块
**旧方式**4 次读取,约 12k tokens):
```
Read src/payments/processor.rs # 400 行
Read src/payments/validator.rs # 300 行
Read src/payments/gateway.rs # 500 行
Read src/payments/types.rs # 200 行
```
**Smart explore 方式**(1 次结构扫描 + 2 次定向读取,约 1.5k tokens):
```bash
# 步骤 1:获取结构(4 个文件共约 400 tokens
python3 ~/.claude/scripts/extract-signatures.py src/payments/
# 步骤 2:从签名中识别关键内容
# "process_payment() 调用了 validate_amount()——读取这两个"
# 步骤 3:只读取这些函数(带行偏移量)
Read src/payments/processor.rs (lines 45-90) # ~300 tokens
Read src/payments/validator.rs (lines 12-40) # ~200 tokens
```
**结果**:相同的理解程度,减少约 87% 的 tokens。
### 示例 2:查找在何处添加功能
```bash
# 目标:为认证服务添加速率限制
# 步骤 1auth 模块里有什么?
python3 ~/.claude/scripts/extract-signatures.py src/auth/
# 输出:
# src/auth/middleware.rs:
# fn pub fn authenticate(req: &Request) -> Result<Claims> (line 15)
# fn pub fn refresh_token(token: &str) -> Result<String> (line 45)
#
# src/auth/service.rs:
# fn pub fn validate(claims: &Claims) -> bool (line 8)
# fn pub async fn login(creds: &Credentials) -> Result<Token> (line 20)
# 步骤 2:速率限制应加在 middleware.rs 的 authenticate() 之前
# 只读取 authenticate 函数以了解注入点
Read src/auth/middleware.rs lines 15-44
# 步骤 3:添加功能——完成
```
### 示例 3Claude Code CLAUDE.md 集成
添加到项目的 `CLAUDE.md`
```markdown
## 代码探索协议
**对于此代码库上的任何探索/重构任务:**
1. **探索时切勿读取完整文件**——先进行结构扫描
2. 运行 `python3 ~/.claude/scripts/extract-signatures.py <module_dir>`
3. 从输出中识别 2-3 个相关函数
4. 只读取这些函数(使用 Read 加签名输出中的行偏移量)
5. 对于跨文件依赖:使用 Grep 搜索函数名称,不要读取调用方文件
**理由**:此代码库约有 80 个文件,平均 300 行。完整读取 = 每任务 15k+ tokens。结构优先 = 1-2k tokens。
```
---
## Token 基准测试(真实数据)
实测模式,非宣传数据:
| 操作 | 未使用 smart-explore | 使用 smart-explore | 节省 |
|---|---|---|---|
| 理解 5 个文件的模块 | ~18,000 tokens | ~2,500 tokens | ~86% |
| 查找在何处添加功能 | ~8,000 tokens | ~800 tokens | ~90% |
| PR 审查(10 个变更文件) | ~25,000 tokens | ~3,500 tokens | ~86% |
| 单个函数查找 | ~3,000 tokens | ~350 tokens | ~88% |
**背景说明**:数据基于典型文件(200-500 行)。文件越大节省越多,文件越小节省越少。Aider 项目(40k+ stars)独立验证了这种方法可以为整个大型仓库生成约 1,000 token 的摘要。
---
## 与互补工具的比较
| 工具 | 节省什么 | 何时使用 |
|---|---|---|
| **RTK** | 命令输出 tokensgit、cargo、npm | 运行 CLI 命令后 |
| **smart-explore**(本技能) | 代码读取 tokens | 读取源文件之前 |
| **grepai** | 多次 Grep 轮次→一次语义查询 | 按概念/意图搜索时 |
| **ast-grep** | 复杂结构重构 | 大规模代码转换 |
这些工具是互补的,而非竞争关系。一次典型的 30 分钟 Claude Code 会话中,四个工具都会用到。
---
## 故障排除
**tree-sitter CLI 未找到**
```bash
brew install tree-sitter # macOS
# 或:npm install -g tree-sitter-cli
```
**脚本未提取任何内容**
- 检查文件扩展名是否在支持列表中
- 验证正则表达式模式是否匹配你的语言风格
- 如有需要,将语言模式添加到脚本的 `patterns` 字典中
**MCP 服务器无法连接**
```bash
# 验证安装
python -m mcp_server_tree_sitter --help
# 添加 MCP 服务器后重启 Claude Code
# 检查 ~/.claude/settings.json 配置是否正确
```
**结果过于冗长**(签名太多):
- 过滤到特定子目录:`extract-signatures.py src/payments/`
- 使用扩展名过滤:`extract-signatures.py src/ .rs`(仅 Rust
- 对于大型代码库,按功能区域查询,而非整个 src/
---
## 资源
- [Aider Repo Map Architecture](https://aider.chat/docs/repomap.html)——参考实现(PageRank + tree-sitter
- [mcp-server-tree-sitter](https://github.com/wrale/mcp-server-tree-sitter)——纯 MCP 方案
- [code-review-graph](https://github.com/tirth8205/code-review-graph)——专注于 PR 审查,MIT,约 2k stars
- [jCodeMunch](https://github.com/jgravelle/jcodemunch-mcp)——符号查找 MCP(非商业免费)
- [tree-sitter.github.io](https://tree-sitter.github.io/tree-sitter/)——官方文档
---
**最后更新**2026 年 3 月
**兼容**Claude Code 2.0+
**依赖**tree-sitter CLI(方案 B),Python 3.10+(脚本)