* fix: stabilize validation and tests on Windows * test: add Windows smoke coverage for skill activation * refactor: make setup_web script CommonJS * fix: repair aegisops-ai frontmatter * docs: add when-to-use guidance to core skills * docs: add when-to-use guidance to Apify skills * docs: add when-to-use guidance to Google and Expo skills * docs: add when-to-use guidance to Makepad skills * docs: add when-to-use guidance to git workflow skills * docs: add when-to-use guidance to fp-ts skills * docs: add when-to-use guidance to Three.js skills * docs: add when-to-use guidance to n8n skills * docs: add when-to-use guidance to health analysis skills * docs: add when-to-use guidance to writing and review skills * meta: sync generated catalog metadata * docs: add when-to-use guidance to Robius skills * docs: add when-to-use guidance to review and workflow skills * docs: add when-to-use guidance to science and data skills * docs: add when-to-use guidance to tooling and automation skills * docs: add when-to-use guidance to remaining skills * fix: gate bundle helper execution in Windows activation * chore: drop generated artifacts from contributor PR * docs(maintenance): Record PR 457 sweep Document the open issue triage, PR supersedence decision, local verification, and source-only cleanup that prepared PR #457 for re-running CI. --------- Co-authored-by: sickn33 <sickn33@users.noreply.github.com>
90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate cross-references in data/workflows.json and data/bundles.json.
|
|
- Every recommendedSkills slug in workflows must exist under skills/ (with SKILL.md).
|
|
- Every relatedBundles id in workflows must exist in bundles.json.
|
|
- Every skill slug in each bundle's skills list must exist under skills/.
|
|
Exits with 1 if any reference is broken.
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from _project_paths import find_repo_root
|
|
|
|
|
|
def collect_skill_ids(skills_dir):
|
|
"""Return set of relative paths (skill ids) that have SKILL.md. Matches listSkillIdsRecursive behavior."""
|
|
ids = set()
|
|
for root, dirs, files in os.walk(skills_dir):
|
|
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
|
if "SKILL.md" in files:
|
|
rel = os.path.relpath(root, skills_dir).replace(os.sep, "/")
|
|
ids.add(rel)
|
|
return ids
|
|
|
|
|
|
def main():
|
|
base_dir = str(find_repo_root(__file__))
|
|
skills_dir = os.path.join(base_dir, "skills")
|
|
data_dir = os.path.join(base_dir, "data")
|
|
|
|
workflows_path = os.path.join(data_dir, "workflows.json")
|
|
bundles_path = os.path.join(data_dir, "bundles.json")
|
|
|
|
if not os.path.exists(workflows_path):
|
|
print(f"Missing {workflows_path}")
|
|
sys.exit(1)
|
|
if not os.path.exists(bundles_path):
|
|
print(f"Missing {bundles_path}")
|
|
sys.exit(1)
|
|
|
|
skill_ids = collect_skill_ids(skills_dir)
|
|
with open(workflows_path, "r", encoding="utf-8") as f:
|
|
workflows_data = json.load(f)
|
|
with open(bundles_path, "r", encoding="utf-8") as f:
|
|
bundles_data = json.load(f)
|
|
|
|
bundle_ids = set(bundles_data.get("bundles", {}).keys())
|
|
errors = []
|
|
|
|
# Workflows: recommendedSkills and relatedBundles
|
|
for w in workflows_data.get("workflows", []):
|
|
w_id = w.get("id", "?")
|
|
for step in w.get("steps", []):
|
|
for slug in step.get("recommendedSkills", []):
|
|
if slug not in skill_ids:
|
|
errors.append(f"workflows.json workflow '{w_id}' recommends missing skill: {slug}")
|
|
for bid in w.get("relatedBundles", []):
|
|
if bid not in bundle_ids:
|
|
errors.append(f"workflows.json workflow '{w_id}' references missing bundle: {bid}")
|
|
|
|
# Bundles: every skill in each bundle
|
|
for bid, bundle in bundles_data.get("bundles", {}).items():
|
|
for slug in bundle.get("skills", []):
|
|
if slug not in skill_ids:
|
|
errors.append(f"bundles.json bundle '{bid}' lists missing skill: {slug}")
|
|
|
|
# Canonical bundles doc: skill links must point to existing skill dirs
|
|
bundles_md_path = os.path.join(base_dir, "docs", "users", "bundles.md")
|
|
if os.path.exists(bundles_md_path):
|
|
with open(bundles_md_path, "r", encoding="utf-8") as f:
|
|
bundles_md = f.read()
|
|
for m in re.finditer(r"\]\(\.\./\.\./skills/([^)]+)/\)", bundles_md):
|
|
slug = m.group(1).rstrip("/")
|
|
if slug not in skill_ids:
|
|
errors.append(f"docs/users/bundles.md links to missing skill: {slug}")
|
|
|
|
if errors:
|
|
for e in errors:
|
|
print(e)
|
|
print(f"\nTotal broken references: {len(errors)}")
|
|
sys.exit(1)
|
|
|
|
print("All workflow, bundle, and docs/users/bundles.md references are valid.")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|