commit c284fb12b08cbfbbbb75b2c2b93736320fceacc2 Author: Pikaliov Date: Mon May 4 11:09:40 2026 +0300 Initial commit — research-en skill bundle 5-skill bundle for structured, resumable, parallel deep research: - /research — preliminary outline + fields generation - /research-add-items — supplement research objects - /research-add-fields — supplement field definitions - /research-deep — parallel agents per item, resumable, JSON-validated - /research-report — JSON to markdown report with TOC Includes validate_json.py for fields.yaml coverage check (PyYAML required). Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..120d131 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Python build artifacts +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +*.egg +.eggs/ +build/ +dist/ + +# Virtual environments +.venv/ +venv/ +env/ +ENV/ +.python-version + +# Coverage / testing +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.dmypy.json + +# IDE / OS +.idea/ +.vscode/ +!.vscode/settings.json +!.vscode/launch.json +!.vscode/extensions.json +*.swp +*.swo +*~ +.DS_Store +Thumbs.db +desktop.ini + +# Jupyter +.ipynb_checkpoints/ + +# Secrets — never commit +.env +.env.local +*.key +*.pem +credentials.json + +# Temp +*.log +*.tmp +*.bak +*.backup +*.orig +~$* diff --git a/README.md b/README.md new file mode 100644 index 0000000..5bf24e8 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# research-en + +Claude Code skill bundle for **structured, resumable, parallel deep research**. Five user-invocable slash commands take a topic from a back-of-the-envelope idea to a finished Markdown report, with web search, per-item parallel agents, JSON validation, and field-by-field coverage checks. + +English variant. The bundle ships five skills that share a single project directory layout (`./{topic_slug}/`) and YAML/JSON contract. + +## Pipeline + +``` +/research → outline.yaml + fields.yaml + ↓ +/research-add-items (optional, supplement items) +/research-add-fields (optional, supplement fields) + ↓ +/research-deep → results/*.json (one per item, parallel agents) + ↓ +/research-report → generate_report.py + report.md +``` + +## Skills + +| Skill | Trigger | What it does | +|-------|---------|--------------| +| `research` | `/research ` | Step 1 — generate initial item list + field framework from model knowledge; Step 2 — launch 1 background web-search-agent to supplement; Step 3 — merge user's existing fields if any; Step 4 — write `outline.yaml` (items + execution config) and `fields.yaml` (field defs with `detail_level: brief|moderate|detailed`). | +| `research-add-items` | `/research-add-items` | Append new research objects to `outline.yaml` (user input + optional web search), dedup, in-place update. | +| `research-add-fields` | `/research-add-fields` | Append new field definitions to `fields.yaml` (user input + optional web search), category + `detail_level` confirmed by user. | +| `research-deep` | `/research-deep` | Auto-locate `outline.yaml`, resume from completed JSONs, batch-launch background web-search-agents (`items_per_agent` per agent, `batch_size` parallel), each writes `{output_dir}/{item_slug}.json` per `fields.yaml`, validates with `validate_json.py`. | +| `research-report` | `/research-report` | Read all JSONs + `fields.yaml`, ask which numeric fields to surface in the TOC, generate `generate_report.py` (handles flat/nested JSON, multi-language category mapping, complex-value formatting, uncertain skipping), execute it to produce `report.md`. | + +## File contracts + +### `outline.yaml` +```yaml +topic: +items: + - name: + category: + description: +execution: + batch_size: + items_per_agent: + output_dir: ./results +``` + +### `fields.yaml` +```yaml +field_categories: + - category: Basic Info + fields: + - name: + description: + detail_level: brief | moderate | detailed + required: true | false +uncertain: [] # reserved, populated during deep phase +``` + +### `results/{item_slug}.json` +```json +{ + "name": "...", + "release_date": "...", + "underlying_model": "[uncertain]", + "uncertain": ["underlying_model", ...] +} +``` + +Both flat (`{"name": ...}`) and nested (`{"basic_info": {"name": ...}}`) layouts are supported throughout. + +## Validation + +Every deep-research agent finishes with: + +```bash +python research/validate_json.py -f -j +``` + +`validate_json.py` reports per-file: +- coverage % (covered / defined fields) +- missing required (FAIL if non-empty) +- missing optional, grouped by category +- extra fields not defined in `fields.yaml` + +Exit code is non-zero if any required field is missing. + +## Installation + +Drop this repo into your vault's Claude Code skills directory: + +```bash +git clone https://git.lissad.keenetic.name/Pikaliov/research-en.git \ + .claude/skills/research-en +``` + +Or as a submodule: + +```bash +git submodule add https://git.lissad.keenetic.name/Pikaliov/research-en.git \ + .claude/skills/research-en +``` + +The five skills are auto-discovered on the next Claude Code session. Verify with `/help` — you should see `/research`, `/research-add-items`, `/research-add-fields`, `/research-deep`, `/research-report`. + +**Runtime dependency** (for `validate_json.py`): + +```bash +pip install pyyaml +``` + +`research-deep` calls `validate_json.py` via the `~/.claude/skills/research/validate_json.py` path inside its prompt template — adjust if your install location differs. + +## File layout + +``` +research-en/ +├── README.md — this file +├── research/ +│ ├── SKILL.md — /research (preliminary) +│ └── validate_json.py — JSON ↔ fields.yaml coverage check (PyYAML) +├── research-add-items/SKILL.md — /research-add-items +├── research-add-fields/SKILL.md — /research-add-fields +├── research-deep/SKILL.md — /research-deep (parallel, resumable) +└── research-report/SKILL.md — /research-report (markdown synth) +``` + +## Allowed tools per skill + +| Skill | Tools | +|-------|-------| +| `research` | Read, Write, Glob, WebSearch, Task, AskUserQuestion | +| `research-add-items` | Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion | +| `research-add-fields` | Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion | +| `research-deep` | Bash, Read, Write, Glob, WebSearch, Task | +| `research-report` | Read, Write, Glob, Bash, AskUserQuestion | + +## Worked example + +```text +/research "AI Coding Assistants since 2024" +# → ./ai_coding_assistants/{outline.yaml, fields.yaml} + +/research-add-items +# → adds Cursor, Windsurf, Aider, Continue, etc. + +/research-deep +# → batch=4 agents, 2 items each → results/*.json +# → validate_json.py runs after each → coverage report + +/research-report +# → ask: "Which fields in TOC besides name?" → github_stars, swe_bench_score +# → ./ai_coding_assistants/{generate_report.py, report.md} +``` + +## Hard constraints (in skill prompts) + +- All field values must be in **English** (deep phase). +- Mark uncertain values with `[uncertain]`, list them in trailing `uncertain` array. +- Prompt templates inside `research/SKILL.md` and `research-deep/SKILL.md` are **hard-reproduce**: only `{xxx}` placeholders may be substituted, structure and wording must not be modified. +- Resume support: `research-deep` skips items that already have a JSON file in `output_dir`. +- Batch gating: `research-deep` waits for user approval between batches (interactive). diff --git a/research-add-fields/SKILL.md b/research-add-fields/SKILL.md new file mode 100644 index 0000000..e5a09c9 --- /dev/null +++ b/research-add-fields/SKILL.md @@ -0,0 +1,32 @@ +--- +name: research-add-fields +user-invocable: true +description: Add field definitions to existing research outline. +allowed-tools: Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion +--- + +# Research Add Fields - Supplement Research Fields + +## Trigger +`/research-add-fields` + +## Workflow + +### Step 1: Auto-locate Fields File +Find `*/fields.yaml` file in current working directory, auto-read existing fields definitions. + +### Step 2: Get Supplement Source +Ask user to choose: +- **A. User direct input**: User provides field names and descriptions +- **B. Web Search**: Launch agent to search common fields in this domain + +### Step 3: Display and Confirm +- Display suggested new fields list +- User confirms which fields to add +- User specifies field category and detail_level + +### Step 4: Save Update +Append confirmed fields to fields.yaml, save file. + +## Output +Updated `{topic}/fields.yaml` file (in-place modification, requires user confirmation) diff --git a/research-add-items/SKILL.md b/research-add-items/SKILL.md new file mode 100644 index 0000000..32e0f7b --- /dev/null +++ b/research-add-items/SKILL.md @@ -0,0 +1,30 @@ +--- +name: research-add-items +user-invocable: true +description: Add items (research objects) to existing research outline. +allowed-tools: Bash, Read, Write, Glob, WebSearch, Task, AskUserQuestion +--- + +# Research Add Items - Supplement Research Objects + +## Trigger +`/research-add-items` + +## Workflow + +### Step 1: Auto-locate Outline +Find `*/outline.yaml` file in current working directory, auto-read. + +### Step 2: Get Supplement Sources in Parallel +Simultaneously: +- **A. Ask user**: What items to supplement? Any specific names? +- **B. Ask if Web Search needed**: Launch agent to search for more items? + +### Step 3: Merge and Update +- Append new items to outline.yaml +- Display to user for confirmation +- Avoid duplicates +- Save updated outline + +## Output +Updated `{topic}/outline.yaml` file (in-place modification) diff --git a/research-deep/SKILL.md b/research-deep/SKILL.md new file mode 100644 index 0000000..19c4ebc --- /dev/null +++ b/research-deep/SKILL.md @@ -0,0 +1,100 @@ +--- +name: research-deep +user-invocable: true +description: Read research outline, launch independent agent for each item for deep research. Disable task output. +allowed-tools: Bash, Read, Write, Glob, WebSearch, Task +--- + +# Research Deep - Deep Research + +## Trigger +`/research-deep` + +## Workflow + +### Step 1: Auto-locate Outline +Find `*/outline.yaml` file in current working directory, read items list, execution config (including items_per_agent). + +### Step 2: Resume Check +- Check completed JSON files in output_dir +- Skip completed items + +### Step 3: Batch Execution +- Batch by batch_size (need user approval before next batch) +- Each agent handles items_per_agent items +- Launch web-search-agent (background parallel, disable task output) + +**Parameter Retrieval**: +- `{topic}`: topic field from outline.yaml +- `{item_name}`: item's name field +- `{item_related_info}`: item's complete yaml content (name + category + description etc.) +- `{output_dir}`: execution.output_dir from outline.yaml (default: ./results) +- `{fields_path}`: absolute path to {topic}/fields.yaml +- `{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. + +**Prompt Template**: +```python +prompt = f"""## Task +Research {item_related_info}, output structured JSON to {output_path} + +## Field Definitions +Read {fields_path} to get all field definitions + +## Output Requirements +1. Output JSON according to fields defined in fields.yaml +2. Mark uncertain field values with [uncertain] +3. Add uncertain array at the end of JSON, listing all uncertain field names +4. All field values must be in English + +## Output Path +{output_path} + +## Validation +After completing JSON output, run validation script to ensure complete field coverage: +python ~/.claude/skills/research/validate_json.py -f {fields_path} -j {output_path} +Task is complete only after validation passes. +""" +``` + +**One-shot Example** (assuming researching GitHub Copilot): +``` +## Task +Research name: GitHub Copilot +category: International Product +description: Developed by Microsoft/GitHub, first mainstream AI coding assistant, ~40% market share, output structured JSON to {project_dir}/results/GitHub_Copilot.json + +## Field Definitions +Read {project_dir}/fields.yaml to get all field definitions + +## Output Requirements +1. Output JSON according to fields defined in fields.yaml +2. Mark uncertain field values with [uncertain] +3. Add uncertain array at the end of JSON, listing all uncertain field names +4. All field values must be in English + +## Output Path +{project_dir}/results/GitHub_Copilot.json + +## Validation +After completing JSON output, run validation script to ensure complete field coverage: +python ~/.claude/skills/research/validate_json.py -f {project_dir}/fields.yaml -j {project_dir}/results/GitHub_Copilot.json +Task is complete only after validation passes. +``` + +### Step 4: Wait and Monitor +- Wait for current batch to complete +- Launch next batch +- Display progress + +### Step 5: Summary Report +After all complete, output: +- Completion count +- Failed/uncertain marked items +- Output directory + +## Agent Config +- Background execution: Yes +- Task Output: Disabled (agent has explicit output file when complete) +- Resume support: Yes diff --git a/research-report/SKILL.md b/research-report/SKILL.md new file mode 100644 index 0000000..494c486 --- /dev/null +++ b/research-report/SKILL.md @@ -0,0 +1,93 @@ +--- +name: research-report +user-invocable: true +description: Summarize deep research results into markdown report, cover all fields, skip uncertain values. +allowed-tools: Read, Write, Glob, Bash, AskUserQuestion +--- + +# Research Report - Summary Report + +## Trigger +`/research-report` + +## Workflow + +### Step 1: Locate Results Directory +Find `*/outline.yaml` in current working directory, read topic and output_dir config. + +### Step 2: Scan Optional Summary Fields +Read all JSON results, extract fields suitable for TOC display (numeric, short metrics), e.g.: +- github_stars +- google_scholar_cites +- swe_bench_score +- user_scale +- valuation +- release_date + +Use AskUserQuestion to ask user: +- Which fields to display in TOC besides item name? +- Provide dynamic options list (based on actual fields in JSON) + +### Step 3: Generate Python Conversion Script +Generate `generate_report.py` in `{topic}/` directory, script requirements: +- Read all JSON from output_dir +- Read fields.yaml to get field structure +- Cover all field values from each JSON +- Skip fields with values containing [uncertain] +- Skip fields listed in uncertain array +- Generate markdown report format: Table of contents (with anchor links + user-selected summary fields) + Detailed content (by field category) +- Save to `{topic}/report.md` + +**TOC Format Requirements**: +- Must include every item +- Each item displays: number, name (anchor link), user-selected summary fields +- Example: `1. [GitHub Copilot](#github-copilot) - Stars: 10k | Score: 85%` + +#### Script Technical Requirements (Must Follow) + +**1. JSON Structure Compatibility** +Support two JSON structures: +- Flat structure: Fields directly at top level `{"name": "xxx", "release_date": "xxx"}` +- Nested structure: Fields in category sub-dict `{"basic_info": {"name": "xxx"}, "technical_features": {...}}` + +Field lookup order: Top level -> category mapping key -> Traverse all nested dicts + +**2. Category Multi-language Mapping** +fields.yaml category names and JSON keys can be any combination (CN-CN, CN-EN, EN-CN, EN-EN). Must establish bidirectional mapping: +```python +CATEGORY_MAPPING = { + "Basic Info": ["basic_info", "Basic Info"], + "Technical Features": ["technical_features", "technical_characteristics", "Technical Features"], + "Performance Metrics": ["performance_metrics", "performance", "Performance Metrics"], + "Milestone Significance": ["milestone_significance", "milestones", "Milestone Significance"], + "Business Info": ["business_info", "commercial_info", "Business Info"], + "Competition & Ecosystem": ["competition_ecosystem", "competition", "Competition & Ecosystem"], + "History": ["history", "History"], + "Market Positioning": ["market_positioning", "market", "Market Positioning"], +} +``` + +**3. Complex Value Formatting** +- list of dicts (e.g., key_events, funding_history): Format each dict as one line, separate kv with ` | ` +- Normal list: Short lists joined with comma, long lists displayed with line breaks +- Nested dict: Recursive formatting, display with semicolon or line breaks +- Long text strings (over 100 chars): Add line breaks `
` or use blockquote format for readability + +**4. Extra Fields Collection** +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` +- Nested structure top-level keys: `basic_info`, `technical_features` etc. +- `uncertain` array: Display each field name on separate line, don't compress into one line + +**5. Uncertain Value Skipping** +Skip conditions: +- Field value contains `[uncertain]` string +- Field name is in `uncertain` array +- Field value is None or empty string + +### Step 4: Execute Script +Run `python {topic}/generate_report.py` + +## Output +- `{topic}/generate_report.py` - Conversion script +- `{topic}/report.md` - Summary report diff --git a/research/SKILL.md b/research/SKILL.md new file mode 100644 index 0000000..46f0962 --- /dev/null +++ b/research/SKILL.md @@ -0,0 +1,145 @@ +--- +name: research +user-invocable: true +allowed-tools: Read, Write, Glob, WebSearch, Task, AskUserQuestion +description: Conduct preliminary research on a topic and generate research outline. For academic research, benchmark research, technology selection, etc. +--- + +# Research Skill - Preliminary Research + +## Trigger +`/research ` + +## Workflow + +### Step 1: Generate Initial Framework from Model Knowledge +Based on topic, use model's existing knowledge to generate: +- Main research objects/items list in this domain +- Suggested research field framework + +Output {step1_output}, use AskUserQuestion to confirm: +- Need to add/remove items? +- Does field framework meet requirements? + +### Step 2: Web Search Supplement +Use AskUserQuestion to ask for time range (e.g., last 6 months, since 2024, unlimited). + +**Parameter Retrieval**: +- `{topic}`: User input research topic +- `{YYYY-MM-DD}`: Current date +- `{step1_output}`: Complete output from Step 1 +- `{time_range}`: User specified time range + +**Hard Constraint**: The following prompt must be strictly reproduced, only replacing variables in {xxx}, do not modify structure or wording. + +Launch 1 web-search-agent (background), **Prompt Template**: +```python +prompt = f"""## Task +Research topic: {topic} +Current date: {YYYY-MM-DD} + +Based on the following initial framework, supplement latest items and recommended research fields. + +## Existing Framework +{step1_output} + +## Goals +1. Verify if existing items are missing important objects +2. Supplement items based on missing objects +3. Continue searching for {topic} related items within {time_range} and supplement +4. Supplement new fields + +## Output Requirements +Return structured results directly (do not write files): + +### Supplementary Items +- item_name: Brief explanation (why it should be added) +... + +### Recommended Supplementary Fields +- field_name: Field description (why this dimension is needed) +... + +### Sources +- [Source1](url1) +- [Source2](url2) +""" +``` + +**One-shot Example** (assuming researching AI Coding History): +``` +## Task +Research topic: AI Coding History +Current date: 2025-12-30 + +Based on the following initial framework, supplement latest items and recommended research fields. + +## Existing Framework +### Items List +1. GitHub Copilot: Developed by Microsoft/GitHub, first mainstream AI coding assistant +2. Cursor: AI-first IDE, based on VSCode +... + +### Field Framework +- Basic Info: name, release_date, company +- Technical Features: underlying_model, context_window +... + +## Goals +1. Verify if existing items are missing important objects +2. Supplement items based on missing objects +3. Continue searching for AI Coding History related items within since 2024 and supplement +4. Supplement new fields + +## Output Requirements +Return structured results directly (do not write files): + +### Supplementary Items +- item_name: Brief explanation (why it should be added) +... + +### Recommended Supplementary Fields +- field_name: Field description (why this dimension is needed) +... + +### Sources +- [Source1](url1) +- [Source2](url2) +``` + +### Step 3: Ask User for Existing Fields +Use AskUserQuestion to ask if user has existing field definition file, if so read and merge. + +### Step 4: Generate Outline (Separate Files) +Merge {step1_output}, {step2_output} and user's existing fields, generate two files: + +**outline.yaml** (items + config): +- topic: Research topic +- items: Research objects list +- execution: + - batch_size: Number of parallel agents (confirm with AskUserQuestion) + - items_per_agent: Items per agent (confirm with AskUserQuestion) + - output_dir: Results output directory (default: ./results) + +**fields.yaml** (field definitions): +- Field categories and definitions +- Each field's name, description, detail_level +- detail_level hierarchy: brief -> moderate -> detailed +- uncertain: Uncertain fields list (reserved field, auto-filled in deep phase) + +### Step 5: Output and Confirm +- Create directory: `./{topic_slug}/` +- Save: `outline.yaml` and `fields.yaml` +- Show to user for confirmation + +## Output Path +``` +{current_working_directory}/{topic_slug}/ + ├── outline.yaml # items list + execution config + └── fields.yaml # field definitions +``` + +## Follow-up Commands +- `/research-add-items` - Supplement items +- `/research-add-fields` - Supplement fields +- `/research-deep` - Start deep research diff --git a/research/validate_json.py b/research/validate_json.py new file mode 100644 index 0000000..208e144 --- /dev/null +++ b/research/validate_json.py @@ -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": ["basic_info", "Basic Info"], + "technical_features": ["technical_features", "technical_characteristics", "Technical Features"], + "performance_metrics": ["performance_metrics", "performance", "Performance Metrics"], + "milestone_significance": ["milestone_significance", "milestones", "Milestone Significance"], + "business_info": ["business_info", "commercial_info", "Business Info"], + "competition_ecosystem": ["competition_ecosystem", "competition", "Competition Ecosystem"], + "history": ["history", "History"], + "market_positioning": ["market_positioning", "market", "Market Positioning"], +} + +_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, "Unknown")].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 = "PASS" if result["valid"] else "FAIL" + line = "=" * 60 + print(f"\n{line}") + print(f"[{status}] {result['file']}") + print(line) + print(f"Coverage: {result['coverage_rate']:.1f}% ({result['covered']}/{result['total_defined']})") + if result["missing_required"]: + print(f"\n[ERROR] Missing required fields ({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[WARN] Missing optional fields ({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[INFO] Extra fields ({len(extra)}):") + print(f" {', '.join(extra[:10])}") + if len(extra) > 10: + print(f" ... and {len(extra) - 10} more") + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Validate whether JSON files cover all fields defined in fields.yaml") + parser.add_argument("--fields", "-f", type=str, help="Path to fields.yaml", default="fields.yaml") + parser.add_argument("--json", "-j", type=str, nargs="*", help="JSON file paths to validate") + parser.add_argument("--dir", "-d", type=str, help="Directory containing JSON files", default="results") + parser.add_argument("--quiet", "-q", action="store_true", help="Show summary only") + 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"[ERROR] fields.yaml not found: {fields_path}") + sys.exit(1) + print(f"Field definition file: {fields_path}") + all_fields, required_fields, field_categories = load_fields_yaml(fields_path) + print(f"Total fields: {len(all_fields)} (required: {len(required_fields)}, optional: {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("[WARN] No JSON files found") + sys.exit(0) + results = [] + for json_path in json_files: + if not json_path.exists(): + print(f"[WARN] File not found: {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("Summary") + 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"Validation passed: {passed}/{len(results)}") + print(f"Average coverage: {avg_coverage:.1f}%") + if passed < len(results): + sys.exit(1) + + +if __name__ == "__main__": + main()