refactor: flatten skills directory structure
- Restructure skills/research-zh and skills/research-en to flat layout - Each skill now in its own directory (research, research-deep, etc.) - Update installation: cp -r skills/research-zh/* ~/.claude/skills/ - Update command references from /research:deep to /research-deep Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
46529e4b88
commit
bb7b8453e2
@@ -0,0 +1,144 @@
|
||||
---
|
||||
user-invocable: true
|
||||
allowed-tools: Read, Write, Glob, WebSearch, Task, AskUserQuestion
|
||||
description: 对目标话题进行初步调研,生成调研outline。用于学术调研、benchmark调研、技术选型等场景。
|
||||
---
|
||||
|
||||
# Research Skill - 初步调研
|
||||
|
||||
## 触发方式
|
||||
`/research <topic>`
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 模型内部知识生成初步框架
|
||||
基于topic,利用模型已有知识生成:
|
||||
- 该领域的主要研究对象/items列表
|
||||
- 建议的调研字段框架
|
||||
|
||||
输出{step1_output},使用AskUserQuestion确认:
|
||||
- items列表是否需要增减?
|
||||
- 字段框架是否满足需求?
|
||||
|
||||
### Step 2: Web Search补充
|
||||
使用AskUserQuestion询问时间范围(如:最近6个月、2024年至今、不限)。
|
||||
|
||||
**参数获取**:
|
||||
- `{topic}`: 用户输入的调研话题
|
||||
- `{YYYY-MM-DD}`: 当前日期
|
||||
- `{step1_output}`: Step 1生成的完整输出内容
|
||||
- `{time_range}`: 用户指定的时间范围
|
||||
|
||||
**硬约束**:以下prompt必须严格复述,仅替换{xxx}中的变量,禁止改写结构或措辞。
|
||||
|
||||
启动1个web-search-agent(后台),**Prompt模板**:
|
||||
```python
|
||||
prompt = f"""## 任务
|
||||
调研话题: {topic}
|
||||
当前日期: {YYYY-MM-DD}
|
||||
|
||||
基于以下初步框架,补充最新items和推荐调研字段。
|
||||
|
||||
## 已有框架
|
||||
{step1_output}
|
||||
|
||||
## 目标
|
||||
1. 验证已有items是否遗漏重要对象
|
||||
2. 根据遗漏对象进行补充items
|
||||
3. 继续搜索{topic}相关且{time_range}内的items并补充
|
||||
4. 补充新fields
|
||||
|
||||
## 输出要求
|
||||
直接返回结构化结果(不写文件):
|
||||
|
||||
### 补充Items
|
||||
- item_name: 简要说明(为什么应该加入)
|
||||
...
|
||||
|
||||
### 推荐补充字段
|
||||
- field_name: 字段描述(为什么需要这个维度)
|
||||
...
|
||||
|
||||
### 信息来源
|
||||
- [来源1](url1)
|
||||
- [来源2](url2)
|
||||
"""
|
||||
```
|
||||
|
||||
**One-shot示例**(假设调研AI Coding发展史):
|
||||
```
|
||||
## 任务
|
||||
调研话题: AI Coding 发展史
|
||||
当前日期: 2025-12-30
|
||||
|
||||
基于以下初步框架,补充最新items和推荐调研字段。
|
||||
|
||||
## 已有框架
|
||||
### Items列表
|
||||
1. GitHub Copilot: Microsoft/GitHub开发,首个主流AI编程助手
|
||||
2. Cursor: AI-first IDE,基于VSCode
|
||||
...
|
||||
|
||||
### 字段框架
|
||||
- 基本信息: name, release_date, company
|
||||
- 技术特性: underlying_model, context_window
|
||||
...
|
||||
|
||||
## 目标
|
||||
1. 验证已有items是否遗漏重要对象
|
||||
2. 根据遗漏对象进行补充items
|
||||
3. 继续搜索AI Coding 发展史相关且2024年至今内的items并补充
|
||||
4. 补充新fields
|
||||
|
||||
## 输出要求
|
||||
直接返回结构化结果(不写文件):
|
||||
|
||||
### 补充Items
|
||||
- item_name: 简要说明(为什么应该加入)
|
||||
...
|
||||
|
||||
### 推荐补充字段
|
||||
- field_name: 字段描述(为什么需要这个维度)
|
||||
...
|
||||
|
||||
### 信息来源
|
||||
- [来源1](url1)
|
||||
- [来源2](url2)
|
||||
```
|
||||
|
||||
### Step 3: 询问用户已有字段
|
||||
使用AskUserQuestion询问用户是否有已定义的字段文件,如有则读取并合并。
|
||||
|
||||
### Step 4: 生成Outline(分离文件)
|
||||
合并{step1_output}、{step2_output}和用户已有字段,生成两个文件:
|
||||
|
||||
**outline.yaml**(items + 配置):
|
||||
- topic: 调研主题
|
||||
- items: 调研对象列表
|
||||
- execution:
|
||||
- batch_size: 并行agent数量(需AskUserQuestion确认)
|
||||
- items_per_agent: 每个agent调研项目数(需AskUserQuestion确认)
|
||||
- output_dir: 结果输出目录(默认./results)
|
||||
|
||||
**fields.yaml**(字段定义):
|
||||
- 字段分类和定义
|
||||
- 每个字段的name、description、detail_level
|
||||
- detail_level分层:极简 → 简要 → 详细
|
||||
- uncertain: 不确定字段列表(保留字段,deep阶段自动填充)
|
||||
|
||||
### Step 5: 输出并确认
|
||||
- 创建目录: `./{topic_slug}/`
|
||||
- 保存: `outline.yaml` 和 `fields.yaml`
|
||||
- 展示给用户确认
|
||||
|
||||
## 输出路径
|
||||
```
|
||||
{当前工作目录}/{topic_slug}/
|
||||
├── outline.yaml # items列表 + execution配置
|
||||
└── fields.yaml # 字段定义
|
||||
```
|
||||
|
||||
## 后续命令
|
||||
- `/research-add-items` - 补充items
|
||||
- `/research-add-fields` - 补充字段
|
||||
- `/research-deep` - 开始深度调研
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
CATEGORY_MAPPING = {
|
||||
"基本信息": ["basic_info", "基本信息"],
|
||||
"技术特性": ["technical_features", "technical_characteristics", "技术特性"],
|
||||
"性能指标": ["performance_metrics", "performance", "性能指标"],
|
||||
"里程碑意义": ["milestone_significance", "milestones", "里程碑意义"],
|
||||
"商业信息": ["business_info", "commercial_info", "商业信息"],
|
||||
"竞争与生态": ["competition_ecosystem", "competition", "竞争与生态"],
|
||||
"历史沿革": ["history", "历史沿革"],
|
||||
"市场定位": ["market_positioning", "market", "市场定位"],
|
||||
}
|
||||
|
||||
_SKIP_KEYS = {"_source_file", "uncertain"}
|
||||
|
||||
|
||||
def load_fields_yaml(fields_path):
|
||||
with fields_path.open(encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
items = [
|
||||
(field["name"], category["category"], field.get("required", False))
|
||||
for category in data.get("field_categories", [])
|
||||
for field in category.get("fields", [])
|
||||
]
|
||||
all_fields = {name for name, _, _ in items}
|
||||
required_fields = {name for name, _, required in items if required}
|
||||
field_categories = {name: category for name, category, _ in items}
|
||||
return all_fields, required_fields, field_categories
|
||||
|
||||
|
||||
def extract_json_fields(data, category_mapping=None):
|
||||
category_mapping = CATEGORY_MAPPING if category_mapping is None else category_mapping
|
||||
nested_keys = {k for keys in category_mapping.values() for k in keys}
|
||||
fields = set()
|
||||
stack = [(data, True)]
|
||||
while stack:
|
||||
obj, is_category_level = stack.pop()
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
if k in _SKIP_KEYS:
|
||||
continue
|
||||
if is_category_level and k in nested_keys:
|
||||
if isinstance(v, dict):
|
||||
stack.append((v, True))
|
||||
continue
|
||||
fields.add(k)
|
||||
elif isinstance(obj, list):
|
||||
stack.extend((item, is_category_level) for item in obj if isinstance(item, dict))
|
||||
return fields
|
||||
|
||||
|
||||
def validate_json(json_path, all_fields, required_fields, field_categories):
|
||||
with json_path.open(encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
json_fields = extract_json_fields(data)
|
||||
covered = all_fields & json_fields
|
||||
missing = all_fields - json_fields
|
||||
extra = json_fields - all_fields
|
||||
missing_required = missing & required_fields
|
||||
missing_by_category = defaultdict(list)
|
||||
for field in missing:
|
||||
missing_by_category[field_categories.get(field, "未知")].append(field)
|
||||
return {
|
||||
"file": json_path.name,
|
||||
"total_defined": len(all_fields),
|
||||
"covered": len(covered),
|
||||
"missing": len(missing),
|
||||
"extra": len(extra),
|
||||
"coverage_rate": len(covered) / len(all_fields) * 100 if all_fields else 100,
|
||||
"missing_required": sorted(missing_required),
|
||||
"missing_optional": sorted(missing - required_fields),
|
||||
"missing_by_category": {k: sorted(v) for k, v in missing_by_category.items()},
|
||||
"extra_fields": sorted(extra),
|
||||
"valid": len(missing_required) == 0,
|
||||
}
|
||||
|
||||
|
||||
def print_result(result, verbose=True):
|
||||
status = "通过" if result["valid"] else "失败"
|
||||
line = "=" * 60
|
||||
print(f"\n{line}")
|
||||
print(f"[{status}] {result['file']}")
|
||||
print(line)
|
||||
print(f"覆盖率: {result['coverage_rate']:.1f}% ({result['covered']}/{result['total_defined']})")
|
||||
if result["missing_required"]:
|
||||
print(f"\n[错误] 缺少必填字段 ({len(result['missing_required'])}):")
|
||||
print("\n".join(f" - {f}" for f in result["missing_required"]))
|
||||
if verbose and result["missing_optional"]:
|
||||
missing_required = set(result["missing_required"])
|
||||
print(f"\n[警告] 缺少可选字段 ({len(result['missing_optional'])}):")
|
||||
for cat in sorted(result["missing_by_category"]):
|
||||
optional = [f for f in result["missing_by_category"][cat] if f not in missing_required]
|
||||
if optional:
|
||||
print(f" [{cat}]: {', '.join(optional)}")
|
||||
if verbose and result["extra_fields"]:
|
||||
extra = result["extra_fields"]
|
||||
print(f"\n[信息] 额外字段 ({len(extra)}):")
|
||||
print(f" {', '.join(extra[:10])}")
|
||||
if len(extra) > 10:
|
||||
print(f" ... 还有 {len(extra) - 10} 个")
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="验证JSON文件是否覆盖fields.yaml中定义的所有字段")
|
||||
parser.add_argument("--fields", "-f", type=str, help="fields.yaml路径", default="fields.yaml")
|
||||
parser.add_argument("--json", "-j", type=str, nargs="*", help="要验证的JSON文件路径")
|
||||
parser.add_argument("--dir", "-d", type=str, help="包含JSON文件的目录", default="results")
|
||||
parser.add_argument("--quiet", "-q", action="store_true", help="仅显示摘要")
|
||||
args = parser.parse_args()
|
||||
fields_path = Path(args.fields)
|
||||
if not fields_path.exists():
|
||||
for p in (Path.cwd() / "fields.yaml", Path.cwd().parent / "fields.yaml"):
|
||||
if p.exists():
|
||||
fields_path = p
|
||||
break
|
||||
if not fields_path.exists():
|
||||
print(f"[错误] 找不到fields.yaml: {fields_path}")
|
||||
sys.exit(1)
|
||||
print(f"字段定义文件: {fields_path}")
|
||||
all_fields, required_fields, field_categories = load_fields_yaml(fields_path)
|
||||
print(f"总字段数: {len(all_fields)} (必填: {len(required_fields)}, 可选: {len(all_fields) - len(required_fields)})")
|
||||
json_files = (
|
||||
[Path(p) for p in args.json]
|
||||
if args.json
|
||||
else sorted(Path(args.dir).glob("*.json")) if Path(args.dir).exists() else []
|
||||
)
|
||||
if not json_files:
|
||||
print("[警告] 未找到JSON文件")
|
||||
sys.exit(0)
|
||||
results = []
|
||||
for json_path in json_files:
|
||||
if not json_path.exists():
|
||||
print(f"[警告] 文件不存在: {json_path}")
|
||||
continue
|
||||
result = validate_json(json_path, all_fields, required_fields, field_categories)
|
||||
results.append(result)
|
||||
print_result(result, verbose=not args.quiet)
|
||||
line = "=" * 60
|
||||
print(f"\n{line}")
|
||||
print("汇总")
|
||||
print(line)
|
||||
passed = sum(1 for r in results if r["valid"])
|
||||
avg_coverage = sum(r["coverage_rate"] for r in results) / len(results) if results else 0
|
||||
print(f"验证通过: {passed}/{len(results)}")
|
||||
print(f"平均覆盖率: {avg_coverage:.1f}%")
|
||||
if passed < len(results):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user