reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
---
name: research-add-fields
user-invocable: true
description: 向现有调研outline补充字段定义。
allowed-tools: Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion
---
# Research Add Fields - 补充调研字段
## 触发方式
`/research-add-fields`
## 执行流程
### Step 1: 自动定位Fields文件
在当前工作目录查找 `*/fields.yaml` 文件,自动读取现有fields定义。
### Step 2: 获取补充来源
询问用户选择:
- **A. 用户直接输入**:用户提供字段名称和描述
- **B. Web Search搜索**:启动web-search-agent搜索该领域常用字段
### Step 3: 展示并确认
- 展示建议的新字段列表
- 用户确认哪些字段需要添加
- 用户指定字段分类和detail_level
### Step 4: 保存更新
将确认的字段追加到fields.yaml,保存文件。
## 输出
更新后的 `{topic}/fields.yaml` 文件(原地修改,需用户确认)

View File

@@ -0,0 +1,30 @@
---
name: research-add-items
user-invocable: true
description: 向现有调研outline补充items(调研对象)。
allowed-tools: Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion
---
# Research Add Items - 补充调研对象
## 触发方式
`/research-add-items`
## 执行流程
### Step 1: 自动定位Outline
在当前工作目录查找 `*/outline.yaml` 文件,自动读取。
### Step 2: 并行获取补充来源
同时进行:
- **A. 询问用户**:需要补充哪些items?有具体名称吗?
- **B. 询问是否需要Web Search**:是否启动agent搜索更多items?
### Step 3: 合并更新
- 将新items追加到outline.yaml
- 展示给用户确认
- 避免重复
- 保存更新后的outline
## 输出
更新后的 `{topic}/outline.yaml` 文件(原地修改)

View File

@@ -0,0 +1,100 @@
---
name: research-deep
user-invocable: true
description: 读取调研outline,为每个item启动独立agent进行深度调研。禁用task output。
allowed-tools: Bash, Read, Write, Glob, WebSearch, Task
---
# Research Deep - 深度调研
## 触发方式
`/research-deep`
## 执行流程
### Step 1: 自动定位Outline
在当前工作目录查找 `*/outline.yaml` 文件,读取items列表、execution配置(含items_per_agent)。
### Step 2: 断点续传检查
- 检查output_dir下已完成的JSON文件
- 跳过已完成的items
### Step 3: 分批执行
- 按batch_size分批(完成一批需要得到用户同意才可进行下一批)
- 每个agent负责items_per_agent个项目
- 启动web-search-agent(后台并行,禁用task output)
**参数获取**:
- `{topic}`: outline.yaml中的topic字段
- `{item_name}`: item的name字段
- `{item_related_info}`: item的完整yaml内容(name + category + description等)
- `{output_dir}`: outline.yaml中execution.output_dir(默认./results)
- `{fields_path}`: {topic}/fields.yaml的绝对路径
- `{output_path}`: {output_dir}/{item_name_slug}.json的绝对路径(slugify处理item_name:空格替换为_,移除特殊字符)
**硬约束**:以下prompt必须严格复述,仅替换{xxx}中的变量,禁止改写结构或措辞。
**Prompt模板**:
```python
prompt = f"""## 任务
调研 {item_related_info},输出结构化JSON到 {output_path}
## 字段定义
读取 {fields_path} 获取所有字段定义
## 输出要求
1. 按fields.yaml定义的字段输出JSON
2. 不确定的字段值标注[不确定]
3. JSON末尾添加uncertain数组,列出所有不确定的字段名
4. 所有字段值必须使用中文输出(调研过程可用英文,但最终JSON值为中文)
## 输出路径
{output_path}
## 验证
完成JSON输出后,运行验证脚本确保字段完整覆盖:
python ~/.claude/skills/research/validate_json.py -f {fields_path} -j {output_path}
验证通过后才算完成任务。
"""
```
**One-shot示例**(假设调研GitHub Copilot):
```
## 任务
调研 name: GitHub Copilot
category: 国际产品
description: Microsoft/GitHub开发,首个主流AI编程助手,市场份额约40%,输出结构化JSON到 {project_dir}/results/GitHub_Copilot.json
## 字段定义
读取 {project_dir}/fields.yaml 获取所有字段定义
## 输出要求
1. 按fields.yaml定义的字段输出JSON
2. 不确定的字段值标注[不确定]
3. JSON末尾添加uncertain数组,列出所有不确定的字段名
4. 所有字段值必须使用中文输出(调研过程可用英文,但最终JSON值为中文)
## 输出路径
{project_dir}/results/GitHub_Copilot.json
## 验证
完成JSON输出后,运行验证脚本确保字段完整覆盖:
python ~/.claude/skills/research/validate_json.py -f {project_dir}/fields.yaml -j {project_dir}/results/GitHub_Copilot.json
验证通过后才算完成任务。
```
### Step 4: 等待与监控
- 等待当前批次完成
- 启动下一批
- 显示进度
### Step 5: 汇总报告
全部完成后输出:
- 完成数量
- 失败/不确定标记的items
- 输出目录
## Agent配置
- 后台执行: 是
- Task Output: 禁用(agent完成时有明确输出文件)
- 断点续传: 是

View File

@@ -0,0 +1,93 @@
---
name: research-report
user-invocable: true
description: 将deep调研结果汇总为markdown报告,覆盖所有字段,跳过不确定值。
allowed-tools: Read, Write, Glob, Bash, AskUserQuestion
---
# Research Report - 汇总报告
## 触发方式
`/research-report`
## 执行流程
### Step 1: 定位结果目录
在当前工作目录查找 `*/outline.yaml`,读取topic和output_dir配置。
### Step 2: 扫描可选摘要字段
读取所有JSON结果,提取适合在目录中显示的字段(数值型、简短指标),例如:
- github_stars
- google_scholar_cites
- swe_bench_score
- user_scale
- valuation
- release_date
使用AskUserQuestion询问用户:
- 目录中除了item名称外,还需要显示哪些字段?
- 提供动态选项列表(基于实际JSON中存在的字段)
### Step 3: 生成Python转换脚本
在 `{topic}/` 目录下生成 `generate_report.py`,脚本要求:
- 读取output_dir下所有JSON
- 读取fields.yaml获取字段结构
- 覆盖每个JSON的所有字段值
- 跳过值包含[不确定]的字段
- 跳过uncertain数组中列出的字段
- 生成markdown报告格式:目录(带锚点跳转+用户选择的摘要字段)+ 详细内容(按字段分类)
- 保存到 `{topic}/report.md`
**目录格式要求**:
- 必须包含每一个item
- 每个item显示:序号、名称(锚点链接)、用户选择的摘要字段
- 示例:`1. [GitHub Copilot](#github-copilot) - Stars: 10k | Score: 85%`
#### 脚本技术要点(必须遵循)
**1. JSON结构兼容**
支持两种JSON结构:
- 扁平结构:字段直接在顶层 `{"name": "xxx", "release_date": "xxx"}`
- 嵌套结构:字段在category子dict中 `{"basic_info": {"name": "xxx"}, "technical_features": {...}}`
字段查找顺序:顶层 -> category映射key -> 遍历所有嵌套dict
**2. Category多语言映射**
fields.yaml的category名与JSON的key可能是任意组合(中中、中英、英中、英英)。必须建立双向映射:
```python
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", "市场定位"],
}
```
**3. 复杂值格式化**
- list of dicts(如key_events, funding_history):每个dict格式化为一行,用` | `分隔kv
- 普通list:短列表用逗号连接,长列表换行显示
- 嵌套dict:递归格式化,用分号或换行显示
- 长文本字符串(超过100字符):添加换行符`<br>`或使用blockquote格式,提高可读性
**4. 额外字段收集**
收集JSON中有但fields.yaml中没定义的字段,放入"其他信息"分类。注意过滤:
- 内部字段:`_source_file`, `uncertain`
- 嵌套结构顶级key:`basic_info`, `technical_features`等
- `uncertain`数组:需要逐行显示每个字段名,不要压缩成一行
**5. 不确定值跳过**
跳过条件:
- 字段值包含`[不确定]`字符串
- 字段名在`uncertain`数组中
- 字段值为None或空字符串
### Step 4: 执行脚本
运行 `python {topic}/generate_report.py`
## 输出
- `{topic}/generate_report.py` - 转换脚本
- `{topic}/report.md` - 汇总报告

View File

@@ -0,0 +1,145 @@
---
name: research
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` - 开始深度调研

View File

@@ -0,0 +1,197 @@
#!/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):
"""解析 fields.yaml,仅接受 research skill 实际生成的唯一 schema:
fields:
<category>:
- {name: ..., description: ..., detail_level: ...}
...
uncertain: []
只接受这一种格式。不符合的 fields.yaml 会直接报错退出,
而不是以零字段静默通过。
Required 语义(避免校验器空转通过/说谎):
- 若任一字段显式带 `required:` 键 -> 沿用 opt-in 语义
- 否则(detail_level 式、无标记)-> 全部字段视为必填,因为脚本的目的就是完整覆盖。
"""
with fields_path.open(encoding="utf-8") as f:
data = yaml.safe_load(f) or {}
defs = [] # (name, category, required_or_None)
fn = data.get("fields")
if not isinstance(fn, dict):
print(f"[错误] fields.yaml 必须使用 `fields: {{<category>: [{{name, ...}}]}}` 格式;当前为 {type(fn).__name__ if fn is not None else 'None'}。")
sys.exit(1)
for cname, flist in fn.items():
if cname in _SKIP_KEYS:
continue
if not isinstance(flist, list):
print(f"[错误] 分类 `{cname}` 必须映射为字段字典列表;当前为 {type(flist).__name__}。")
sys.exit(1)
for field in flist:
if isinstance(field, dict) and "name" in field:
defs.append((str(field["name"]), str(cname), field.get("required", None)))
else:
print(f"[错误] `{cname}` 下的字段必须是带 `name` 键的字典;当前为 {field!r}。")
sys.exit(1)
if not defs:
print("[错误] fields.yaml 解析到零个字段。请确保至少有一个分类及其字段字典。")
sys.exit(1)
all_fields = {n for n, _, _ in defs}
if any(r is not None for _, _, r in defs):
required_fields = {n for n, _, r in defs if r}
else:
required_fields = set(all_fields)
field_categories = {n: c for n, c, _ in defs}
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()