fix: address code review issues from Claude and GPT-5.2
- report/SKILL.md: add AskUserQuestion to allowed-tools - validate_json.py: remove unused imports (List, Any) - validate_json.py: sort output lists for deterministic order - validate_json.py: limit recursion to category level only - validate_json.py: add list-of-dict handling - EN validate_json.py: remove Chinese from CATEGORY_MAPPING - EN deep/SKILL.md: change output language from Chinese to English - deep/SKILL.md: add slug handling for filenames - report/SKILL.md: fix uncertain_fields -> uncertain naming - README: add PyYAML dependency note 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
98ddfce42d
commit
779c447670
@@ -24,6 +24,9 @@ cp -r skills/research-zh ~/.claude/skills/research
|
|||||||
|
|
||||||
# Required: Install agent
|
# Required: Install agent
|
||||||
cp agents/web-search-agent.md ~/.claude/agents/
|
cp agents/web-search-agent.md ~/.claude/agents/
|
||||||
|
|
||||||
|
# Required: Install Python dependency
|
||||||
|
pip install pyyaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ cp -r skills/research-en ~/.claude/skills/research
|
|||||||
|
|
||||||
# 必需:安装agent
|
# 必需:安装agent
|
||||||
cp agents/web-search-agent.md ~/.claude/agents/
|
cp agents/web-search-agent.md ~/.claude/agents/
|
||||||
|
|
||||||
|
# 必需:安装Python依赖
|
||||||
|
pip install pyyaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## 命令
|
## 命令
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Find `*/outline.yaml` file in current working directory, read items list, execut
|
|||||||
- `{item_related_info}`: item's complete yaml content (name + category + description etc.)
|
- `{item_related_info}`: item's complete yaml content (name + category + description etc.)
|
||||||
- `{output_dir}`: execution.output_dir from outline.yaml (default: ./results)
|
- `{output_dir}`: execution.output_dir from outline.yaml (default: ./results)
|
||||||
- `{fields_path}`: absolute path to {topic}/fields.yaml
|
- `{fields_path}`: absolute path to {topic}/fields.yaml
|
||||||
- `{output_path}`: absolute path to {output_dir}/{item_name}.json
|
- `{output_path}`: absolute path to {output_dir}/{item_name_slug}.json (slugify item_name: replace spaces with _, remove special chars)
|
||||||
|
|
||||||
**Hard Constraint**: The following prompt must be strictly reproduced, only replacing variables in {xxx}, do not modify structure or wording.
|
**Hard Constraint**: The following prompt must be strictly reproduced, only replacing variables in {xxx}, do not modify structure or wording.
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ Read {fields_path} to get all field definitions
|
|||||||
1. Output JSON according to fields defined in fields.yaml
|
1. Output JSON according to fields defined in fields.yaml
|
||||||
2. Mark uncertain field values with [uncertain]
|
2. Mark uncertain field values with [uncertain]
|
||||||
3. Add uncertain array at the end of JSON, listing all uncertain field names
|
3. Add uncertain array at the end of JSON, listing all uncertain field names
|
||||||
4. All field values must be in Chinese (research can be in English, but final JSON values in Chinese)
|
4. All field values must be in English
|
||||||
|
|
||||||
## Output Path
|
## Output Path
|
||||||
{output_path}
|
{output_path}
|
||||||
@@ -70,7 +70,7 @@ Read /home/weizhena/AIcoding/aicoding-history/fields.yaml to get all field defin
|
|||||||
1. Output JSON according to fields defined in fields.yaml
|
1. Output JSON according to fields defined in fields.yaml
|
||||||
2. Mark uncertain field values with [uncertain]
|
2. Mark uncertain field values with [uncertain]
|
||||||
3. Add uncertain array at the end of JSON, listing all uncertain field names
|
3. Add uncertain array at the end of JSON, listing all uncertain field names
|
||||||
4. All field values must be in Chinese (research can be in English, but final JSON values in Chinese)
|
4. All field values must be in English
|
||||||
|
|
||||||
## Output Path
|
## Output Path
|
||||||
/home/weizhena/AIcoding/aicoding-history/results/GitHub_Copilot.json
|
/home/weizhena/AIcoding/aicoding-history/results/GitHub_Copilot.json
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: Summarize deep research results into markdown report, cover all fields, skip uncertain values.
|
description: Summarize deep research results into markdown report, cover all fields, skip uncertain values.
|
||||||
allowed-tools: Read, Write, Glob, Bash
|
allowed-tools: Read, Write, Glob, Bash, AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# Research Report - Summary Report
|
# Research Report - Summary Report
|
||||||
@@ -75,7 +75,7 @@ CATEGORY_MAPPING = {
|
|||||||
Collect fields that exist in JSON but not defined in fields.yaml, put in "Other Info" category. Note to filter:
|
Collect fields that exist in JSON but not defined in fields.yaml, put in "Other Info" category. Note to filter:
|
||||||
- Internal fields: `_source_file`, `uncertain`
|
- Internal fields: `_source_file`, `uncertain`
|
||||||
- Nested structure top-level keys: `basic_info`, `technical_features` etc.
|
- Nested structure top-level keys: `basic_info`, `technical_features` etc.
|
||||||
- `uncertain_fields` list: Display each field name on separate line, don't compress into one line
|
- `uncertain` array: Display each field name on separate line, don't compress into one line
|
||||||
|
|
||||||
**5. Uncertain Value Skipping**
|
**5. Uncertain Value Skipping**
|
||||||
Skip conditions:
|
Skip conditions:
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ import json
|
|||||||
import yaml
|
import yaml
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Set, Any, Tuple
|
from typing import Dict, Set, Tuple
|
||||||
|
|
||||||
# Category mapping (supports both Chinese and English keys)
|
# Category mapping (English keys only)
|
||||||
CATEGORY_MAPPING = {
|
CATEGORY_MAPPING = {
|
||||||
"basic_info": ["basic_info", "基本信息"],
|
"basic_info": ["basic_info", "Basic Info"],
|
||||||
"technical_features": ["technical_features", "technical_characteristics", "技术特性"],
|
"technical_features": ["technical_features", "technical_characteristics", "Technical Features"],
|
||||||
"performance_metrics": ["performance_metrics", "performance", "性能指标"],
|
"performance_metrics": ["performance_metrics", "performance", "Performance Metrics"],
|
||||||
"milestone_significance": ["milestone_significance", "milestones", "里程碑意义"],
|
"milestone_significance": ["milestone_significance", "milestones", "Milestone Significance"],
|
||||||
"business_info": ["business_info", "commercial_info", "商业信息"],
|
"business_info": ["business_info", "commercial_info", "Business Info"],
|
||||||
"competition_ecosystem": ["competition_ecosystem", "competition", "竞争与生态"],
|
"competition_ecosystem": ["competition_ecosystem", "competition", "Competition Ecosystem"],
|
||||||
"history": ["history", "历史沿革"],
|
"history": ["history", "History"],
|
||||||
"market_positioning": ["market_positioning", "market", "市场定位"],
|
"market_positioning": ["market_positioning", "market", "Market Positioning"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -53,30 +53,41 @@ def load_fields_yaml(fields_path: Path) -> Tuple[Set[str], Set[str], Dict[str, s
|
|||||||
def extract_json_fields(data: Dict, category_mapping: Dict = None) -> Set[str]:
|
def extract_json_fields(data: Dict, category_mapping: Dict = None) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Extract all field names from JSON (supports both flat and nested structures)
|
Extract all field names from JSON (supports both flat and nested structures)
|
||||||
|
Only extracts field names at category level, not nested dict/list values
|
||||||
"""
|
"""
|
||||||
if category_mapping is None:
|
if category_mapping is None:
|
||||||
category_mapping = CATEGORY_MAPPING
|
category_mapping = CATEGORY_MAPPING
|
||||||
|
|
||||||
# Get all possible nested keys
|
# Get all possible nested keys (category containers)
|
||||||
nested_keys = set()
|
nested_keys = set()
|
||||||
for keys in category_mapping.values():
|
for keys in category_mapping.values():
|
||||||
nested_keys.update(keys)
|
nested_keys.update(keys)
|
||||||
|
|
||||||
fields = set()
|
fields = set()
|
||||||
|
|
||||||
def collect_fields(d: Dict, is_top_level: bool = True):
|
def collect_fields(d, is_category_level: bool = True):
|
||||||
for k, v in d.items():
|
"""
|
||||||
# Skip internal fields
|
Collect fields from dict or list structures
|
||||||
if k in {"_source_file", "uncertain"}:
|
is_category_level: True if we're at top level or inside a category container
|
||||||
continue
|
"""
|
||||||
# If it's a top-level nested key, recurse into it
|
if isinstance(d, dict):
|
||||||
if is_top_level and k in nested_keys:
|
for k, v in d.items():
|
||||||
if isinstance(v, dict):
|
# Skip internal fields
|
||||||
collect_fields(v, is_top_level=False)
|
if k in {"_source_file", "uncertain"}:
|
||||||
else:
|
continue
|
||||||
fields.add(k)
|
# If it's a category container key, recurse into it
|
||||||
if isinstance(v, dict):
|
if is_category_level and k in nested_keys:
|
||||||
collect_fields(v, is_top_level=False)
|
if isinstance(v, dict):
|
||||||
|
collect_fields(v, is_category_level=True)
|
||||||
|
else:
|
||||||
|
# This is a field name, add it
|
||||||
|
fields.add(k)
|
||||||
|
# Don't recurse into field values (avoid counting nested keys as fields)
|
||||||
|
elif isinstance(d, list):
|
||||||
|
# Handle list-of-dict structures at category level
|
||||||
|
for item in d:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
collect_fields(item, is_category_level=is_category_level)
|
||||||
|
|
||||||
collect_fields(data)
|
collect_fields(data)
|
||||||
return fields
|
return fields
|
||||||
@@ -110,6 +121,10 @@ def validate_json(json_path: Path, all_fields: Set[str], required_fields: Set[st
|
|||||||
missing_by_category[cat] = []
|
missing_by_category[cat] = []
|
||||||
missing_by_category[cat].append(field)
|
missing_by_category[cat].append(field)
|
||||||
|
|
||||||
|
# Sort lists within categories for deterministic output
|
||||||
|
for cat in missing_by_category:
|
||||||
|
missing_by_category[cat].sort()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"file": json_path.name,
|
"file": json_path.name,
|
||||||
"total_defined": len(all_fields),
|
"total_defined": len(all_fields),
|
||||||
@@ -117,10 +132,10 @@ def validate_json(json_path: Path, all_fields: Set[str], required_fields: Set[st
|
|||||||
"missing": len(missing),
|
"missing": len(missing),
|
||||||
"extra": len(extra),
|
"extra": len(extra),
|
||||||
"coverage_rate": len(covered) / len(all_fields) * 100 if all_fields else 100,
|
"coverage_rate": len(covered) / len(all_fields) * 100 if all_fields else 100,
|
||||||
"missing_required": list(missing_required),
|
"missing_required": sorted(missing_required),
|
||||||
"missing_optional": list(missing_optional),
|
"missing_optional": sorted(missing_optional),
|
||||||
"missing_by_category": missing_by_category,
|
"missing_by_category": missing_by_category,
|
||||||
"extra_fields": list(extra),
|
"extra_fields": sorted(extra),
|
||||||
"valid": len(missing_required) == 0, # Valid if all required fields are covered
|
"valid": len(missing_required) == 0, # Valid if all required fields are covered
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +155,8 @@ def print_result(result: Dict, verbose: bool = True):
|
|||||||
|
|
||||||
if verbose and result["missing_optional"]:
|
if verbose and result["missing_optional"]:
|
||||||
print(f"\n[WARN] Missing optional fields ({len(result['missing_optional'])}):")
|
print(f"\n[WARN] Missing optional fields ({len(result['missing_optional'])}):")
|
||||||
for cat, fields in result["missing_by_category"].items():
|
for cat in sorted(result["missing_by_category"].keys()):
|
||||||
|
fields = result["missing_by_category"][cat]
|
||||||
optional_fields = [f for f in fields if f not in result["missing_required"]]
|
optional_fields = [f for f in fields if f not in result["missing_required"]]
|
||||||
if optional_fields:
|
if optional_fields:
|
||||||
print(f" [{cat}]: {', '.join(optional_fields)}")
|
print(f" [{cat}]: {', '.join(optional_fields)}")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ allowed-tools: Bash, Read, Write, Glob, WebSearch, Task
|
|||||||
- `{item_related_info}`: item的完整yaml内容(name + category + description等)
|
- `{item_related_info}`: item的完整yaml内容(name + category + description等)
|
||||||
- `{output_dir}`: outline.yaml中execution.output_dir(默认./results)
|
- `{output_dir}`: outline.yaml中execution.output_dir(默认./results)
|
||||||
- `{fields_path}`: {topic}/fields.yaml的绝对路径
|
- `{fields_path}`: {topic}/fields.yaml的绝对路径
|
||||||
- `{output_path}`: {output_dir}/{item_name}.json的绝对路径
|
- `{output_path}`: {output_dir}/{item_name_slug}.json的绝对路径(slugify处理item_name:空格替换为_,移除特殊字符)
|
||||||
|
|
||||||
**硬约束**:以下prompt必须严格复述,仅替换{xxx}中的变量,禁止改写结构或措辞。
|
**硬约束**:以下prompt必须严格复述,仅替换{xxx}中的变量,禁止改写结构或措辞。
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: 将deep调研结果汇总为markdown报告,覆盖所有字段,跳过不确定值。
|
description: 将deep调研结果汇总为markdown报告,覆盖所有字段,跳过不确定值。
|
||||||
allowed-tools: Read, Write, Glob, Bash
|
allowed-tools: Read, Write, Glob, Bash, AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# Research Report - 汇总报告
|
# Research Report - 汇总报告
|
||||||
@@ -75,7 +75,7 @@ CATEGORY_MAPPING = {
|
|||||||
收集JSON中有但fields.yaml中没定义的字段,放入"其他信息"分类。注意过滤:
|
收集JSON中有但fields.yaml中没定义的字段,放入"其他信息"分类。注意过滤:
|
||||||
- 内部字段:`_source_file`, `uncertain`
|
- 内部字段:`_source_file`, `uncertain`
|
||||||
- 嵌套结构顶级key:`basic_info`, `technical_features`等
|
- 嵌套结构顶级key:`basic_info`, `technical_features`等
|
||||||
- `uncertain_fields`列表:需要逐行显示每个字段名,不要压缩成一行
|
- `uncertain`数组:需要逐行显示每个字段名,不要压缩成一行
|
||||||
|
|
||||||
**5. 不确定值跳过**
|
**5. 不确定值跳过**
|
||||||
跳过条件:
|
跳过条件:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import json
|
|||||||
import yaml
|
import yaml
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Set, Any, Tuple
|
from typing import Dict, Set, Tuple
|
||||||
|
|
||||||
# Category中英文映射
|
# Category中英文映射
|
||||||
CATEGORY_MAPPING = {
|
CATEGORY_MAPPING = {
|
||||||
@@ -53,30 +53,41 @@ def load_fields_yaml(fields_path: Path) -> Tuple[Set[str], Set[str], Dict[str, s
|
|||||||
def extract_json_fields(data: Dict, category_mapping: Dict = None) -> Set[str]:
|
def extract_json_fields(data: Dict, category_mapping: Dict = None) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
从JSON中提取所有字段名(支持扁平和嵌套结构)
|
从JSON中提取所有字段名(支持扁平和嵌套结构)
|
||||||
|
只提取category级别的字段名,不递归到字段值的dict/list中
|
||||||
"""
|
"""
|
||||||
if category_mapping is None:
|
if category_mapping is None:
|
||||||
category_mapping = CATEGORY_MAPPING
|
category_mapping = CATEGORY_MAPPING
|
||||||
|
|
||||||
# 获取所有可能的嵌套key
|
# 获取所有可能的嵌套key(category容器)
|
||||||
nested_keys = set()
|
nested_keys = set()
|
||||||
for keys in category_mapping.values():
|
for keys in category_mapping.values():
|
||||||
nested_keys.update(keys)
|
nested_keys.update(keys)
|
||||||
|
|
||||||
fields = set()
|
fields = set()
|
||||||
|
|
||||||
def collect_fields(d: Dict, is_top_level: bool = True):
|
def collect_fields(d, is_category_level: bool = True):
|
||||||
for k, v in d.items():
|
"""
|
||||||
# 跳过内部字段
|
从dict或list结构中收集字段
|
||||||
if k in {"_source_file", "uncertain"}:
|
is_category_level: True表示在顶层或category容器内部
|
||||||
continue
|
"""
|
||||||
# 如果是嵌套结构的顶级key,递归进入
|
if isinstance(d, dict):
|
||||||
if is_top_level and k in nested_keys:
|
for k, v in d.items():
|
||||||
if isinstance(v, dict):
|
# 跳过内部字段
|
||||||
collect_fields(v, is_top_level=False)
|
if k in {"_source_file", "uncertain"}:
|
||||||
else:
|
continue
|
||||||
fields.add(k)
|
# 如果是category容器key,递归进入
|
||||||
if isinstance(v, dict):
|
if is_category_level and k in nested_keys:
|
||||||
collect_fields(v, is_top_level=False)
|
if isinstance(v, dict):
|
||||||
|
collect_fields(v, is_category_level=True)
|
||||||
|
else:
|
||||||
|
# 这是一个字段名,添加它
|
||||||
|
fields.add(k)
|
||||||
|
# 不递归到字段值中(避免将嵌套key误计为字段)
|
||||||
|
elif isinstance(d, list):
|
||||||
|
# 处理category级别的list-of-dict结构
|
||||||
|
for item in d:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
collect_fields(item, is_category_level=is_category_level)
|
||||||
|
|
||||||
collect_fields(data)
|
collect_fields(data)
|
||||||
return fields
|
return fields
|
||||||
@@ -110,6 +121,10 @@ def validate_json(json_path: Path, all_fields: Set[str], required_fields: Set[st
|
|||||||
missing_by_category[cat] = []
|
missing_by_category[cat] = []
|
||||||
missing_by_category[cat].append(field)
|
missing_by_category[cat].append(field)
|
||||||
|
|
||||||
|
# 对category内的列表排序,确保输出确定性
|
||||||
|
for cat in missing_by_category:
|
||||||
|
missing_by_category[cat].sort()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"file": json_path.name,
|
"file": json_path.name,
|
||||||
"total_defined": len(all_fields),
|
"total_defined": len(all_fields),
|
||||||
@@ -117,10 +132,10 @@ def validate_json(json_path: Path, all_fields: Set[str], required_fields: Set[st
|
|||||||
"missing": len(missing),
|
"missing": len(missing),
|
||||||
"extra": len(extra),
|
"extra": len(extra),
|
||||||
"coverage_rate": len(covered) / len(all_fields) * 100 if all_fields else 100,
|
"coverage_rate": len(covered) / len(all_fields) * 100 if all_fields else 100,
|
||||||
"missing_required": list(missing_required),
|
"missing_required": sorted(missing_required),
|
||||||
"missing_optional": list(missing_optional),
|
"missing_optional": sorted(missing_optional),
|
||||||
"missing_by_category": missing_by_category,
|
"missing_by_category": missing_by_category,
|
||||||
"extra_fields": list(extra),
|
"extra_fields": sorted(extra),
|
||||||
"valid": len(missing_required) == 0, # required字段全覆盖则valid
|
"valid": len(missing_required) == 0, # required字段全覆盖则valid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +155,8 @@ def print_result(result: Dict, verbose: bool = True):
|
|||||||
|
|
||||||
if verbose and result["missing_optional"]:
|
if verbose and result["missing_optional"]:
|
||||||
print(f"\n[WARN] 缺失可选字段 ({len(result['missing_optional'])}):")
|
print(f"\n[WARN] 缺失可选字段 ({len(result['missing_optional'])}):")
|
||||||
for cat, fields in result["missing_by_category"].items():
|
for cat in sorted(result["missing_by_category"].keys()):
|
||||||
|
fields = result["missing_by_category"][cat]
|
||||||
optional_fields = [f for f in fields if f not in result["missing_required"]]
|
optional_fields = [f for f in fields if f not in result["missing_required"]]
|
||||||
if optional_fields:
|
if optional_fields:
|
||||||
print(f" [{cat}]: {', '.join(optional_fields)}")
|
print(f" [{cat}]: {', '.join(optional_fields)}")
|
||||||
|
|||||||
Reference in New Issue
Block a user