chore: import zh skill smart-explore
This commit is contained in:
@@ -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 清单整理
|
||||
- 原作者、版权和许可证信息以上游仓库为准
|
||||
@@ -0,0 +1,509 @@
|
||||
---
|
||||
name: smart-explore
|
||||
description: "使用 tree-sitter AST 解析进行渐进式代码探索——先看结构,再深入细节。将每个文件的代码阅读量从 10-15k token 减少到 200-500 token。"
|
||||
effort: low
|
||||
---
|
||||
|
||||
# Smart Explore——渐进式代码探索
|
||||
|
||||
> **技能**:先阅读代码结构,再阅读代码。先让 Claude 展示函数签名和类型,然后仅在需要时深入查看特定函数。
|
||||
|
||||
**灵感来源**:Alex Newman(Claude-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 会话,零依赖。
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:tree-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` |
|
||||
|
||||
#### 选项 C1:mcp-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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 选项 C2:code-review-graph(最适合 PR 审查)
|
||||
|
||||
```bash
|
||||
pip install code-review-graph
|
||||
code-review-graph install
|
||||
```
|
||||
|
||||
**新增功能**:审查 PR 时,Claude 会自动获取变更文件及其依赖关系图。Claude 无需读取 30 个文件来理解变更影响,只需查看实际相关的 5 个文件。
|
||||
|
||||
**用法**:
|
||||
|
||||
```
|
||||
/review-pr 123
|
||||
# code-review-graph 自动提供:
|
||||
# - 变更的文件
|
||||
# - 导入了已变更模块的文件
|
||||
# - 受影响的类型定义
|
||||
# - 变更代码的测试覆盖情况
|
||||
```
|
||||
|
||||
#### 选项 C3:jCodeMunch(符号查找)
|
||||
|
||||
```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
|
||||
# 目标:为认证服务添加速率限制
|
||||
# 步骤 1:auth 模块里有什么?
|
||||
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:添加功能——完成
|
||||
```
|
||||
|
||||
### 示例 3:Claude 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** | 命令输出 tokens(git、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+(脚本)
|
||||
Reference in New Issue
Block a user