
Sync Docs
- 1 installs
- 21 repo stars
- Updated August 5, 2026
- joaquimscosta/arkhe-claude-plugins
Syncs official Anthropic documentation into docs/reference/, computes diffs, and reports impacts on the skill validator, plugins, and project docs.
About
Runs a sync-and-diff script to update local Anthropic reference docs and analyze how the changes affect the skill validator, plugins, and documentation. A developer or maintainer uses it to keep reference docs current and understand downstream impact.
- Syncs Anthropic docs and computes diffs
- Impact analysis across validator, plugins, and docs
Sync Docs by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,366 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joaquimscosta/arkhe-claude-plugins --skill sync-docsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 5, 2026 |
| Repository | joaquimscosta/arkhe-claude-plugins ↗ |
What it does
Syncs official Anthropic documentation into docs/reference/, computes diffs, and reports impacts on the skill validator, plugins, and project docs.
Files
Sync Docs
Sync official Anthropic documentation into docs/reference/ and produce an impact analysis report.
Quick Start
Run the sync and diff script:
scripts/sync_and_diff.pyPreview changes without overwriting:
scripts/sync_and_diff.py --dry-runExecution
Phase 1: Sync & Diff
1. Execute scripts/sync_and_diff.py (or with --dry-run if $ARGUMENTS contains --dry-run) 2. Parse the JSON output from stdout 3. Display a summary table of changed/unchanged files
Phase 2: Impact Analysis
For each changed file, perform the following analysis. See WORKFLOW.md for detailed steps.
2a. Skill Validator Impact (CRITICAL priority)
Check discrepancies in the JSON output:
- `skill_frontmatter_keys.in_docs_not_validator` (CRITICAL): New fields in SKILLS.md the validator will reject as unknown (FM009). Report exact fields and line in
validate_skill.pyto update (ALLOWED_FRONTMATTER_KEYS, ~line 151). - `skill_frontmatter_keys.in_validator_not_docs` (WARNING): Fields the validator allows but SKILLS.md no longer documents. May be from SUBAGENTS.md (valid for
context: fork) or deprecated. - `subagent_frontmatter_keys` (INFO): New subagent fields from SUBAGENTS.md. Not all are valid in skill frontmatter — report for context. Only flag fields that overlap with skill usage (e.g.,
maxTurns,mcpServers,memory,skills). - `hook_events.in_docs_not_validator` (CRITICAL): New hook events the validator will reject (HK001). Report the line to update (
VALID_HOOK_EVENTS, ~line 1021). - `hook_events.in_validator_not_docs` (WARNING): Hook events the validator knows but docs no longer list.
- `memory_scopes`: Review manually if SUBAGENTS.md changed — check if memory scope options expanded.
2b. Plugin Component Impact (WARNING priority)
For each changed synced doc, identify the conceptual areas that changed (new fields, deprecated features, renamed concepts). Then:
1. Use Grep to search plugins/*/ for references to changed concepts 2. Flag plugins using deprecated patterns 3. Note new capabilities not yet leveraged (as INFO)
2c. Project Documentation Impact (WARNING priority)
Check these files for stale references to synced doc content:
CLAUDE.md— Plugin Component Guidelines section, Key Documentation Files sectiondocs/README.md— Synced Documentation section, learning pathsdocs/SKILL_DEVELOPMENT_BEST_PRACTICES.md— Frontmatter reference, patterns
Output Format
Present findings as a structured report:
=== Documentation Sync Impact Report ===
## Sync Summary
| File | Status | +Lines | -Lines | New Sections | Removed Sections |
|------|--------|--------|--------|--------------|-----------------|
...
## CRITICAL: Skill Validator Updates Required
(List each discrepancy with file, line number, and fix)
## WARNING: Plugin Components Affected
(List affected plugins and what changed)
## WARNING: Project Documentation Stale
(List affected doc files and what to update)
## INFO: New Capabilities Available
(List new features from updated docs that could be leveraged)If no files changed, report: "All documentation is up to date. No impacts detected."
References
- WORKFLOW.md — Detailed impact analysis steps
- EXAMPLES.md — Example report outputs
- TROUBLESHOOTING.md — Common issues
Examples: Sync Docs
Example 1: No Changes
All documentation is up to date — no upstream changes detected.
=== Documentation Sync Impact Report ===
## Sync Summary
| File | Status | +Lines | -Lines |
|----------------------|-----------|--------|--------|
| SUBAGENTS.md | unchanged | 0 | 0 |
| PLUGINS.md | unchanged | 0 | 0 |
| HOOKS.md | unchanged | 0 | 0 |
| SKILLS.md | unchanged | 0 | 0 |
| SETTINGS.md | unchanged | 0 | 0 |
| MCP.md | unchanged | 0 | 0 |
| PLUGINS_REFERENCE.md | unchanged | 0 | 0 |
| BEST_PRACTICES.md | unchanged | 0 | 0 |
All documentation is up to date. No impacts detected.Example 2: New Frontmatter Field Added (CRITICAL)
Anthropic adds a new isolation frontmatter field to SKILLS.md.
=== Documentation Sync Impact Report ===
## Sync Summary
| File | Status | +Lines | -Lines | New Sections |
|----------------------|-----------|--------|--------|--------------|
| SKILLS.md | changed | 12 | 0 | ### isolation |
| HOOKS.md | unchanged | 0 | 0 | |
| ... | unchanged | 0 | 0 | |
## CRITICAL: Skill Validator Updates Required
### 1. New frontmatter field: `isolation`
- **Source**: SKILLS.md now documents `isolation` field (line 201)
- **Impact**: Validator will flag `isolation` as "Unknown frontmatter key" (FM009)
- **Fix**: Add `'isolation'` to `ALLOWED_FRONTMATTER_KEYS` in
`.claude/skills/skill-validator/scripts/validate_skill.py` (~line 151)
- **Current value**:ALLOWED_FRONTMATTER_KEYS = { 'name', 'description', 'license', 'allowed-tools', 'metadata', 'model', 'context', 'agent', 'hooks', 'user-invocable', 'disable-model-invocation', 'argument-hint', 'maxTurns', 'mcpServers', 'memory', 'skills' }
- **Recommended**:ALLOWED_FRONTMATTER_KEYS = { 'name', 'description', 'license', 'allowed-tools', 'metadata', 'model', 'context', 'agent', 'hooks', 'user-invocable', 'disable-model-invocation', 'argument-hint', 'maxTurns', 'mcpServers', 'memory', 'skills', 'isolation' }
## WARNING: Project Documentation Stale
### 1. CLAUDE.md — Plugin Component Guidelines
- The YAML frontmatter reference in "Skill Files" section does not mention `isolation`
- **Fix**: Add `isolation` to the frontmatter field list
### 2. docs/SKILL_DEVELOPMENT_BEST_PRACTICES.md
- The frontmatter reference does not include the new field
- **Fix**: Add documentation for the `isolation` field
## INFO: New Capabilities Available
- The `isolation` field allows skills to run in isolated worktree contexts
- No plugins currently use this field — consider adopting for skills that
modify files destructivelyExample 3: New Hook Event Added (CRITICAL)
Anthropic adds WorktreeCreate and WorktreeRemove hook events.
=== Documentation Sync Impact Report ===
## Sync Summary
| File | Status | +Lines | -Lines | New Sections |
|----------|---------|--------|--------|------------------------------------|
| HOOKS.md | changed | 24 | 2 | ### WorktreeCreate, WorktreeRemove |
## CRITICAL: Skill Validator Updates Required
### 1. New hook events: `WorktreeCreate`, `WorktreeRemove`
- **Source**: HOOKS.md hook events table (lines 42-43)
- **Impact**: Validator will flag these as "Unknown hook event" (HK001)
- **Fix**: Add to `VALID_HOOK_EVENTS` in `validate_skill.py` (~line 1021)
- **Recommended**:VALID_HOOK_EVENTS = { 'PreToolUse', 'PostToolUse', 'Stop', 'SessionStart', 'UserPromptSubmit', 'PermissionRequest', 'PostToolUseFailure', 'Notification', 'SubagentStart', 'SubagentStop', 'TeammateIdle', 'TaskCompleted', 'PreCompact', 'SessionEnd', 'ConfigChange', 'WorktreeCreate', 'WorktreeRemove', }
## WARNING: Plugin Components Affected
No plugins currently use hook events that were modified.
## INFO: New Capabilities Available
- `WorktreeCreate` fires when a worktree is created via `--worktree` or `isolation: "worktree"`
- `WorktreeRemove` fires when a worktree is removed at session exit or subagent finish
- Consider using these in plugins that need to set up/tear down worktree-specific stateExample 4: Dry Run Mode
$ scripts/sync_and_diff.py --dry-run
Dry run: downloading to memory without overwriting...Output is identical JSON format but with "dry_run": true and no files are overwritten locally. Use this to preview what would change before committing to the sync.
#!/usr/bin/env python3
"""
Sync official Anthropic documentation and compute diffs for impact analysis.
Downloads updated docs via update-claude-docs.sh, compares before/after state,
extracts hardcoded constants from the skill validator, and identifies discrepancies
between what the docs define and what the validator enforces.
Usage:
sync_and_diff.py # Sync docs and output diff analysis as JSON
sync_and_diff.py --dry-run # Download to temp, compare, don't overwrite
Output: JSON to stdout with sync results, diffs, and discrepancies.
"""
import argparse
import difflib
import hashlib
import json
import re
import subprocess
import sys
import urllib.request
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# Project discovery
# ---------------------------------------------------------------------------
def find_project_root() -> Path:
"""Find the arkhe-claude-plugins project root."""
current = Path(__file__).resolve().parent
for _ in range(10):
if (current / ".claude-plugin" / "marketplace.json").exists():
return current
if (current / "plugins").is_dir() and (current / "CLAUDE.md").exists():
return current
current = current.parent
cwd = Path.cwd()
if (cwd / "plugins").is_dir():
return cwd
print("Error: Cannot find project root.", file=sys.stderr)
sys.exit(1)
# ---------------------------------------------------------------------------
# URL mappings parsing
# ---------------------------------------------------------------------------
def parse_url_mappings(root: Path) -> List[Tuple[str, str]]:
"""Extract URL|FILENAME pairs from update-claude-docs.sh."""
script_path = root / "docs" / "reference" / "update-claude-docs.sh"
if not script_path.exists():
print(f"Error: Sync script not found: {script_path}", file=sys.stderr)
sys.exit(1)
content = script_path.read_text()
matches = []
# Parse only non-comment lines inside the URL_MAPPINGS array
in_array = False
for line in content.splitlines():
stripped = line.strip()
if "URL_MAPPINGS=(" in stripped:
in_array = True
continue
if in_array and stripped == ")":
break
if in_array and not stripped.startswith("#"):
m = re.match(r'"(https://[^"]+)\|([^"]+)"', stripped)
if m:
matches.append((m.group(1), m.group(2)))
if not matches:
print("Error: No URL mappings found in sync script.", file=sys.stderr)
sys.exit(1)
return matches
# ---------------------------------------------------------------------------
# File snapshotting
# ---------------------------------------------------------------------------
def hash_content(content: str) -> str:
"""SHA-256 hash of content."""
return hashlib.sha256(content.encode("utf-8")).hexdigest()
def snapshot_files(root: Path, filenames: List[str]) -> Dict[str, str]:
"""Read current content of each synced doc. Returns {filename: content}."""
ref_dir = root / "docs" / "reference"
result = {}
for filename in filenames:
filepath = ref_dir / filename
if filepath.exists():
result[filename] = filepath.read_text()
else:
result[filename] = ""
return result
# ---------------------------------------------------------------------------
# Sync execution
# ---------------------------------------------------------------------------
def run_sync_script(root: Path) -> Tuple[int, str, str]:
"""Execute update-claude-docs.sh and return (exit_code, stdout, stderr)."""
script_path = root / "docs" / "reference" / "update-claude-docs.sh"
result = subprocess.run(
[str(script_path)],
capture_output=True,
text=True,
cwd=str(root / "docs" / "reference"),
timeout=300,
)
return result.returncode, result.stdout, result.stderr
def dry_run_download(url_mappings: List[Tuple[str, str]], timeout: int = 30) -> Dict[str, str]:
"""Download docs to memory without overwriting. Returns {filename: content}."""
user_agent = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
result = {}
for url, filename in url_mappings:
try:
req = urllib.request.Request(url, headers={"User-Agent": user_agent})
with urllib.request.urlopen(req, timeout=timeout) as resp:
content = resp.read().decode("utf-8")
if len(content.strip()) > 10:
result[filename] = content
else:
print(f"Warning: Empty content for {filename}", file=sys.stderr)
result[filename] = ""
except Exception as e:
print(f"Warning: Failed to download {filename}: {e}", file=sys.stderr)
result[filename] = ""
return result
# ---------------------------------------------------------------------------
# Diff computation
# ---------------------------------------------------------------------------
@dataclass
class FileDiff:
filename: str
changed: bool
hash_before: str
hash_after: str
added_lines: int = 0
removed_lines: int = 0
added_sections: List[str] = field(default_factory=list)
removed_sections: List[str] = field(default_factory=list)
diff: str = ""
def extract_sections(content: str) -> List[str]:
"""Extract markdown headings (## and ###) from content."""
return re.findall(r"^(#{2,3}\s+.+)$", content, re.MULTILINE)
def compute_diffs(before: Dict[str, str], after: Dict[str, str]) -> List[FileDiff]:
"""Compute unified diffs between before and after snapshots."""
results = []
all_files = sorted(set(before.keys()) | set(after.keys()))
for filename in all_files:
old = before.get(filename, "")
new = after.get(filename, "")
h_before = hash_content(old)
h_after = hash_content(new)
changed = h_before != h_after
fd = FileDiff(
filename=filename,
changed=changed,
hash_before=h_before,
hash_after=h_after,
)
if changed:
old_lines = old.splitlines(keepends=True)
new_lines = new.splitlines(keepends=True)
diff_lines = list(difflib.unified_diff(
old_lines, new_lines,
fromfile=f"a/{filename}", tofile=f"b/{filename}",
lineterm="",
))
# Count additions/removals (skip diff header lines)
for line in diff_lines:
if line.startswith("+") and not line.startswith("+++"):
fd.added_lines += 1
elif line.startswith("-") and not line.startswith("---"):
fd.removed_lines += 1
# Extract section-level changes
old_sections = set(extract_sections(old))
new_sections = set(extract_sections(new))
fd.added_sections = sorted(new_sections - old_sections)
fd.removed_sections = sorted(old_sections - new_sections)
# Truncate diff to ~200 lines
max_diff_lines = 200
if len(diff_lines) > max_diff_lines:
fd.diff = "".join(diff_lines[:max_diff_lines]) + \
f"\n... truncated ({len(diff_lines) - max_diff_lines} more lines)\n"
else:
fd.diff = "".join(diff_lines)
results.append(fd)
return results
# ---------------------------------------------------------------------------
# Validator constant extraction
# ---------------------------------------------------------------------------
@dataclass
class ValidatorConstants:
allowed_frontmatter_keys: List[str] = field(default_factory=list)
valid_hook_events: List[str] = field(default_factory=list)
valid_memory_scopes: List[str] = field(default_factory=list)
extraction_errors: List[str] = field(default_factory=list)
def extract_validator_constants(root: Path) -> ValidatorConstants:
"""Extract hardcoded constants from validate_skill.py using regex."""
validator_path = root / ".claude" / "skills" / "skill-validator" / "scripts" / "validate_skill.py"
constants = ValidatorConstants()
if not validator_path.exists():
constants.extraction_errors.append(f"Validator not found: {validator_path}")
return constants
content = validator_path.read_text()
# Extract ALLOWED_FRONTMATTER_KEYS
match = re.search(
r"ALLOWED_FRONTMATTER_KEYS\s*=\s*\{([^}]+)\}",
content, re.DOTALL,
)
if match:
keys = re.findall(r"'([^']+)'", match.group(1))
constants.allowed_frontmatter_keys = sorted(keys)
else:
constants.extraction_errors.append("Could not extract ALLOWED_FRONTMATTER_KEYS")
# Extract VALID_HOOK_EVENTS
match = re.search(
r"VALID_HOOK_EVENTS\s*=\s*\{([^}]+)\}",
content, re.DOTALL,
)
if match:
events = re.findall(r"'([^']+)'", match.group(1))
constants.valid_hook_events = sorted(events)
else:
constants.extraction_errors.append("Could not extract VALID_HOOK_EVENTS")
# Extract valid_scopes (local variable in validation function)
match = re.search(
r"valid_scopes\s*=\s*\{([^}]+)\}",
content,
)
if match:
scopes = re.findall(r"'([^']+)'", match.group(1))
constants.valid_memory_scopes = sorted(scopes)
else:
constants.extraction_errors.append("Could not extract valid_scopes")
return constants
# ---------------------------------------------------------------------------
# Doc field extraction
# ---------------------------------------------------------------------------
@dataclass
class DocFields:
skills_frontmatter_fields: List[str] = field(default_factory=list)
subagents_frontmatter_fields: List[str] = field(default_factory=list)
hook_events: List[str] = field(default_factory=list)
extraction_errors: List[str] = field(default_factory=list)
def extract_doc_fields(content_map: Dict[str, str]) -> DocFields:
"""Extract structured field definitions from synced documentation."""
fields = DocFields()
# --- SKILLS.md: frontmatter fields from the reference table ---
# The table has: | `field` | Required/No/Recommended | Description |
skills_content = content_map.get("SKILLS.md", "")
if skills_content:
skill_fields = re.findall(
r"^\|\s*`([a-z][-a-z]*)`\s*\|\s*(?:No|Yes|Recommended)\s*\|",
skills_content, re.MULTILINE,
)
fields.skills_frontmatter_fields = sorted(set(skill_fields))
else:
fields.extraction_errors.append("SKILLS.md not available for field extraction")
# --- SUBAGENTS.md: frontmatter fields table ---
# The table has: | `field` | Yes/No | Description |
subagents_content = content_map.get("SUBAGENTS.md", "")
if subagents_content:
sub_fields = re.findall(
r"^\|\s*`([a-zA-Z]+)`\s*\|\s*(?:No|Yes)\s*\|",
subagents_content, re.MULTILINE,
)
fields.subagents_frontmatter_fields = sorted(set(sub_fields))
else:
fields.extraction_errors.append("SUBAGENTS.md not available for field extraction")
# --- HOOKS.md: hook event names from the primary events table ---
# The events table has: | `EventName` | Description with lowercase start |
# We only match the first events table (lines ~29-45) by looking for
# PascalCase names followed by a description starting with "When"
hooks_content = content_map.get("HOOKS.md", "")
if hooks_content:
events = re.findall(
r"^\|\s*`([A-Z][a-zA-Z]+)`\s*\|\s*[A-Z]",
hooks_content, re.MULTILINE,
)
fields.hook_events = sorted(set(events))
else:
fields.extraction_errors.append("HOOKS.md not available for hook event extraction")
return fields
# ---------------------------------------------------------------------------
# Discrepancy computation
# ---------------------------------------------------------------------------
@dataclass
class SetDiscrepancy:
in_docs_not_validator: List[str] = field(default_factory=list)
in_validator_not_docs: List[str] = field(default_factory=list)
@dataclass
class Discrepancies:
# Skills-only frontmatter: SKILLS.md fields vs validator (CRITICAL)
skill_frontmatter_keys: SetDiscrepancy = field(default_factory=SetDiscrepancy)
# Subagent frontmatter: SUBAGENTS.md fields vs validator (INFO context)
subagent_frontmatter_keys: SetDiscrepancy = field(default_factory=SetDiscrepancy)
hook_events: SetDiscrepancy = field(default_factory=SetDiscrepancy)
memory_scopes: SetDiscrepancy = field(default_factory=SetDiscrepancy)
def compute_discrepancies(
validator: ValidatorConstants,
doc_fields: DocFields,
) -> Discrepancies:
"""Compute set differences between validator constants and doc-defined fields."""
disc = Discrepancies()
validator_fm_keys = set(validator.allowed_frontmatter_keys)
# Skill frontmatter: only SKILLS.md fields vs validator (CRITICAL)
# The validator validates SKILL.md frontmatter, so only SKILLS.md is authoritative
skill_fm_keys = set(doc_fields.skills_frontmatter_fields)
disc.skill_frontmatter_keys = SetDiscrepancy(
in_docs_not_validator=sorted(skill_fm_keys - validator_fm_keys),
in_validator_not_docs=sorted(validator_fm_keys - skill_fm_keys),
)
# Subagent frontmatter: SUBAGENTS.md fields vs validator (INFO)
# Some subagent fields (maxTurns, mcpServers, memory, skills) are valid in
# skill frontmatter when using context: fork. Others (tools, permissionMode)
# are agent-only. Report for context, not as CRITICAL.
subagent_fm_keys = set(doc_fields.subagents_frontmatter_fields)
disc.subagent_frontmatter_keys = SetDiscrepancy(
in_docs_not_validator=sorted(subagent_fm_keys - validator_fm_keys),
in_validator_not_docs=sorted(validator_fm_keys - subagent_fm_keys),
)
# Hook events
doc_events = set(doc_fields.hook_events)
validator_events = set(validator.valid_hook_events)
disc.hook_events = SetDiscrepancy(
in_docs_not_validator=sorted(doc_events - validator_events),
in_validator_not_docs=sorted(validator_events - doc_events),
)
# Memory scopes — extracted from SUBAGENTS.md prose, not a clean table.
# Manual check area — the script flags it as needing human review.
disc.memory_scopes = SetDiscrepancy()
return disc
# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
def build_output(
sync_exit_code: Optional[int],
sync_stdout: str,
sync_stderr: str,
file_diffs: List[FileDiff],
validator_constants: ValidatorConstants,
doc_fields: DocFields,
discrepancies: Discrepancies,
dry_run: bool,
) -> dict:
"""Build the final JSON output."""
changed_count = sum(1 for f in file_diffs if f.changed)
unchanged_count = sum(1 for f in file_diffs if not f.changed)
return {
"sync_result": {
"exit_code": sync_exit_code,
"stdout": sync_stdout,
"stderr": sync_stderr,
"summary": f"{changed_count} changed, {unchanged_count} unchanged",
},
"files": [asdict(f) for f in file_diffs],
"validator_constants": asdict(validator_constants),
"doc_fields": asdict(doc_fields),
"discrepancies": asdict(discrepancies),
"dry_run": dry_run,
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Sync Anthropic docs and compute diff analysis.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Download to temp, compare, don't overwrite local files.",
)
args = parser.parse_args()
root = find_project_root()
url_mappings = parse_url_mappings(root)
filenames = [filename for _, filename in url_mappings]
# Snapshot before
before = snapshot_files(root, filenames)
# Sync or dry-run
sync_exit_code = None
sync_stdout = ""
sync_stderr = ""
if args.dry_run:
print("Dry run: downloading to memory without overwriting...", file=sys.stderr)
after = dry_run_download(url_mappings)
# Fill in any files that failed to download with their current content
for filename in filenames:
if not after.get(filename):
after[filename] = before.get(filename, "")
else:
print("Running sync script...", file=sys.stderr)
sync_exit_code, sync_stdout, sync_stderr = run_sync_script(root)
print(f"Sync complete (exit code: {sync_exit_code})", file=sys.stderr)
# Snapshot after
after = snapshot_files(root, filenames)
# Compute diffs
file_diffs = compute_diffs(before, after)
# Extract validator constants
validator_constants = extract_validator_constants(root)
# Extract doc-defined fields from the AFTER content
doc_fields = extract_doc_fields(after)
# Compute discrepancies
discrepancies = compute_discrepancies(validator_constants, doc_fields)
# Output JSON
output = build_output(
sync_exit_code, sync_stdout, sync_stderr,
file_diffs, validator_constants, doc_fields,
discrepancies, args.dry_run,
)
print(json.dumps(output, indent=2))
if __name__ == "__main__":
main()
Troubleshooting: Sync Docs
Common Issues
"curl: command not found"
The underlying update-claude-docs.sh requires curl.
Fix: Install curl via your package manager:
# macOS (usually pre-installed)
brew install curl
# Ubuntu/Debian
sudo apt-get install curl"Permission denied" on sync script
The bash script or Python script lacks execute permission.
Fix:
chmod +x docs/reference/update-claude-docs.sh
chmod +x .claude/skills/sync-docs/scripts/sync_and_diff.pyNetwork errors during sync
Individual file failures are handled gracefully — the sync continues with remaining files. Failed files appear as unchanged in the diff output.
Symptoms: sync_result.exit_code is non-zero, some files show changed: false when changes were expected.
Fix: Check network connectivity and retry. The sync script has a 30-second per-file timeout.
Empty diffs despite known upstream changes
Possible causes: 1. Upstream docs haven't actually changed since last sync 2. Network returned cached content (CDN caching) 3. The sync script downloaded empty content and skipped the update (safety feature)
Diagnosis: Check sync_result.stdout for the script's own status messages (updated/failed/skipped counts).
Validator constant extraction failed
The Python script uses regex to extract hardcoded constants from validate_skill.py. If the code structure changes significantly, extraction may fail.
Symptoms: validator_constants.extraction_errors contains error messages.
Fix: The script reports which constants it couldn't extract. Fall back to manually reading validate_skill.py and comparing against the updated docs. Update the regex patterns in sync_and_diff.py if needed.
Doc field extraction returned empty
The script parses markdown tables from synced docs to extract field names. If the table format changes, extraction may fail.
Symptoms: doc_fields.skills_frontmatter_fields or doc_fields.hook_events is empty, with errors in doc_fields.extraction_errors.
Fix: Read the synced doc directly and check if the table format changed. Update the regex patterns in sync_and_diff.py if the markdown structure is different.
Dry-run mode fails to download
In dry-run mode, the script downloads docs via Python's urllib.request instead of the bash script. This may fail if:
- The URLs require specific headers or authentication
- Python's SSL certificates are outdated
- The server blocks Python's default user agent
Fix: Run without --dry-run to use the full bash script (which uses curl with proper headers). Or update Python's certificates:
# macOS
/Applications/Python\ 3.x/Install\ Certificates.commandScript hangs or times out
The sync script has a 30-second timeout per file. The Python wrapper has a 300-second total timeout.
Fix: Check if code.claude.com is accessible from your network. The script will time out gracefully and report failures.
Getting Help
If the impact analysis produces unexpected results: 1. Check the raw JSON output for errors in extraction_errors fields 2. Manually read the changed synced docs to verify what actually changed 3. Compare the validator_constants against doc_fields manually
Workflow: Sync Docs Impact Analysis
Detailed instructions for each phase of the impact analysis.
Phase 1: Run the Script
Execute the sync and diff script:
scripts/sync_and_diff.py # Full sync
scripts/sync_and_diff.py --dry-run # Preview onlyThe script outputs JSON to stdout. Parse it and use the structured data for analysis.
JSON Output Structure
{
"sync_result": { "exit_code": 0, "summary": "3 changed, 5 unchanged" },
"files": [
{
"filename": "SKILLS.md",
"changed": true,
"added_lines": 42,
"removed_lines": 15,
"added_sections": ["### New field: isolation"],
"removed_sections": [],
"diff": "... unified diff ..."
}
],
"validator_constants": {
"allowed_frontmatter_keys": ["agent", "allowed-tools", ...],
"valid_hook_events": ["ConfigChange", "Notification", ...],
"valid_memory_scopes": ["local", "project", "user"]
},
"doc_fields": {
"skills_frontmatter_fields": ["agent", "allowed-tools", ...],
"subagents_frontmatter_fields": ["description", "maxTurns", ...],
"hook_events": ["ConfigChange", "Notification", ...]
},
"discrepancies": {
"frontmatter_keys": {
"in_docs_not_validator": [],
"in_validator_not_docs": []
},
"hook_events": { ... },
"memory_scopes": { ... }
}
}Phase 2a: Skill Validator Impact
Frontmatter Keys
1. Read discrepancies.frontmatter_keys 2. If in_docs_not_validator is non-empty:
- Severity: CRITICAL
- Impact: The validator will flag these as "Unknown frontmatter key(s)" via rule FM009
- Fix: Add the new keys to
ALLOWED_FRONTMATTER_KEYSin.claude/skills/skill-validator/scripts/validate_skill.py(~line 151) - Report: List each new key and its description from the synced doc
3. If in_validator_not_docs is non-empty:
- Severity: WARNING
- Impact: The validator allows keys that docs don't document. These may be undocumented internal fields or deprecated.
- Action: Review whether these keys should remain in the validator
Hook Events
1. Read discrepancies.hook_events 2. If in_docs_not_validator is non-empty:
- Severity: CRITICAL
- Impact: The validator will flag these as "Unknown hook event" via rule HK001
- Fix: Add the new events to
VALID_HOOK_EVENTSinvalidate_skill.py(~line 1021)
3. If in_validator_not_docs is non-empty:
- Severity: WARNING
- Impact: The validator accepts events that docs no longer list
Memory Scopes
1. If SUBAGENTS.md changed, read the diff for the memory section 2. Look for new scope values beyond user, project, local 3. If found:
- Severity: CRITICAL
- Fix: Update
valid_scopesinvalidate_skill.py(~line 473)
Extraction Errors
Check validator_constants.extraction_errors and doc_fields.extraction_errors. If any:
- Report the error
- Fall back to manual inspection: read the relevant file directly and compare
Phase 2b: Plugin Component Impact
For each changed synced doc:
SKILLS.md Changes
1. Identify new/removed/modified frontmatter fields from the diff 2. Grep plugins/*/skills/*/SKILL.md for usage of affected fields 3. Check if any plugin skill uses a field that was removed or renamed
HOOKS.md Changes
1. Identify new/removed hook events from the diff 2. Grep plugins/*/ for hook configurations referencing affected events 3. Check hookify rules in .claude/ for affected events
SUBAGENTS.md Changes
1. Identify new agent configuration options 2. Grep plugins/*/agents/*.md for affected frontmatter fields 3. Note new capabilities (e.g., new agent fields) as INFO
PLUGINS_REFERENCE.md Changes
1. Identify changes to plugin manifest schema 2. Check all plugins/*/.claude-plugin/plugin.json files for compliance 3. Check .claude-plugin/marketplace.json for affected fields
BEST_PRACTICES.md Changes
1. Note any changes to recommended patterns 2. Cross-reference with CLAUDE.md guidelines section
SETTINGS.md / MCP.md Changes
1. Note configuration changes 2. Check .claude/settings.json and .mcp.json for affected settings
Phase 2c: Project Documentation Impact
CLAUDE.md
Read the following sections and compare against the updated synced docs:
- "Plugin Component Guidelines" → compare frontmatter field reference against SKILLS.md
- "Key Documentation Files" → verify descriptions match synced file content
- "Plugin Development Workflow" → verify patterns match current docs
docs/README.md
- "Synced Documentation" list → verify all 8 files are listed with correct descriptions
- Learning path references → verify links are still valid
docs/SKILL_DEVELOPMENT_BEST_PRACTICES.md
- Frontmatter reference sections → compare against SKILLS.md table
- Hook event references → compare against HOOKS.md table
- Any code patterns → verify they match current official recommendations
Phase 3: Report Generation
Compile findings into the report format specified in SKILL.md. Group by severity:
1. CRITICAL: Validator will reject valid configurations → must fix 2. WARNING: Stale references or deprecated patterns → should fix 3. INFO: New capabilities available → optional to leverage
For each finding, include:
- The specific file and line number affected
- What changed in the synced doc
- The recommended fix (with code snippet if applicable)