From c45b51a8c424042f6d8992d2f403b4090b21cf35 Mon Sep 17 00:00:00 2001 From: sickn33 Date: Sat, 21 Mar 2026 10:18:57 +0100 Subject: [PATCH] chore(repo): Automate docs metadata sync Extend repository metadata syncing so npm run chain now keeps count-sensitive docs and package copy aligned with the live skills catalog. Add regression coverage for the curated-doc sync behavior and document the automation in the maintainer walkthrough and changelog. --- CHANGELOG.md | 1 + package.json | 4 +- tools/scripts/sync_repo_metadata.py | 237 +++++++++++++++++- tools/scripts/tests/run-test-suite.js | 1 + .../scripts/tests/test_sync_repo_metadata.py | 125 +++++++++ walkthrough.md | 1 + 6 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 tools/scripts/tests/test_sync_repo_metadata.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0c3bbe..f9efd016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Changed - Realigned README, package metadata, user docs, and GitHub About guidance to the current `1,304+` catalog state and `v8.4.0` release copy. +- Automated metadata propagation for curated docs and package copy so `npm run chain` now keeps README, package description, and the main count-sensitive user/maintainer docs aligned when the skill catalog changes. ## [8.4.0] - 2026-03-20 - "Discovery, Metadata, and Release Hardening" diff --git a/package.json b/package.json index b34d9a24..484d629f 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "index": "node tools/scripts/run-python.js tools/scripts/generate_index.py", "readme": "node tools/scripts/run-python.js tools/scripts/update_readme.py", "sync:metadata": "node tools/scripts/run-python.js tools/scripts/sync_repo_metadata.py", - "chain": "npm run validate && npm run index && npm run readme", - "sync:all": "npm run sync:metadata && npm run chain", + "chain": "npm run validate && npm run index && npm run sync:metadata", + "sync:all": "npm run chain", "catalog": "node tools/scripts/build-catalog.js", "build": "npm run chain && npm run catalog", "security:docs": "node tools/scripts/tests/docs_security_content.test.js", diff --git a/tools/scripts/sync_repo_metadata.py b/tools/scripts/sync_repo_metadata.py index 773d1d3c..d5943769 100644 --- a/tools/scripts/sync_repo_metadata.py +++ b/tools/scripts/sync_repo_metadata.py @@ -4,11 +4,240 @@ import json import os import re import sys +from pathlib import Path from update_readme import configure_utf8_output, find_repo_root, load_metadata, update_readme ABOUT_DESCRIPTION_RE = re.compile(r'"description"\s*:\s*"([^"]*)"') +README_TAGLINE_RE = re.compile( + r"^> \*\*Installable GitHub library of \d[\d,]*\+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants\.\*\*$", + re.MULTILINE, +) +README_RELEASE_RE = re.compile(r"^\*\*Current release: V[\d.]+\.\*\* .*?$", re.MULTILINE) +README_BROAD_COVERAGE_RE = re.compile( + r"^- \*\*Broad coverage with real utility\*\*: \d[\d,]*\+ skills across development, testing, security, infrastructure, product, and marketing\.$", + re.MULTILINE, +) +README_NEW_HERE_RE = re.compile( + r"^\*\*Antigravity Awesome Skills\*\* \(Release [\d.]+\) is a large, installable skill library.*$", + re.MULTILINE, +) +README_BROWSE_RE = re.compile( + r'^If you want a faster answer than "browse all \d[\d,]*\+ skills", start with a tool-specific guide:$', + re.MULTILINE, +) +GETTING_STARTED_TITLE_RE = re.compile( + r"^# Getting Started with Antigravity Awesome Skills \(V[\d.]+\)$", re.MULTILINE +) +BUNDLES_FOOTER_RE = re.compile( + r"^_Last updated: .*? \| Total Skills: \d[\d,]*\+ \| Total Bundles: \d+_$", + re.MULTILINE, +) + + +def build_about_description(metadata: dict) -> str: + return ( + f"Installable GitHub library of {metadata['total_skills_label']} agentic skills for " + "Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. " + "Includes installer CLI, bundles, workflows, and official/community skill collections." + ) + + +def replace_if_present(content: str, pattern: re.Pattern[str], replacement: str) -> tuple[str, bool]: + updated_content, count = pattern.subn(replacement, content, count=1) + return updated_content, count > 0 + + +def count_documented_bundles(content: str) -> int: + return len(re.findall(r'^### .*".*" Pack$', content, flags=re.MULTILINE)) + + +def sync_readme_copy(content: str, metadata: dict) -> str: + replacements = [ + ( + README_TAGLINE_RE, + ( + f"> **Installable GitHub library of {metadata['total_skills_label']} agentic skills " + "for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.**" + ), + ), + ( + README_RELEASE_RE, + ( + f"**Current release: V{metadata['version']}.** Trusted by 25k+ GitHub stargazers, " + "this repository combines official and community skill collections with bundles, " + "workflows, installation paths, and docs that help you go from first install to daily use quickly." + ), + ), + ( + README_BROAD_COVERAGE_RE, + ( + f"- **Broad coverage with real utility**: {metadata['total_skills_label']} skills across " + "development, testing, security, infrastructure, product, and marketing." + ), + ), + ( + README_NEW_HERE_RE, + ( + f"**Antigravity Awesome Skills** (Release {metadata['version']}) is a large, installable " + "skill library for AI coding assistants. It includes onboarding docs, bundles, workflows, " + "generated catalogs, and a CLI installer so you can move from discovery to actual usage " + "without manually stitching together dozens of repos." + ), + ), + ( + README_BROWSE_RE, + f'If you want a faster answer than "browse all {metadata["total_skills_label"]} skills", start with a tool-specific guide:', + ), + ] + + for pattern, replacement in replacements: + content, _ = replace_if_present(content, pattern, replacement) + + return content + + +def sync_getting_started(content: str, metadata: dict) -> str: + content, _ = replace_if_present( + content, + GETTING_STARTED_TITLE_RE, + f"# Getting Started with Antigravity Awesome Skills (V{metadata['version']})", + ) + return content + + +def sync_bundles_doc(content: str, metadata: dict) -> str: + bundle_count = count_documented_bundles(content) + if bundle_count == 0: + bundle_count = 36 + content, _ = replace_if_present( + content, + BUNDLES_FOOTER_RE, + f"_Last updated: March 2026 | Total Skills: {metadata['total_skills_label']} | Total Bundles: {bundle_count}_", + ) + return content + + +def sync_jetski_cortex(content: str, metadata: dict) -> str: + italian_skill_label = f"{metadata['total_skills']:,}".replace(",", ".") + replacements = [ + (r"\d[\d\.]*\+ skill", f"{italian_skill_label}+ skill"), + (r"\d[\d\.]* skill", f"{italian_skill_label} skill"), + ] + return sync_regex_text( + content, + replacements, + ) + + +def sync_simple_text(content: str, replacements: list[tuple[str, str]]) -> str: + for old_text, new_text in replacements: + content = content.replace(old_text, new_text) + return content + + +def sync_regex_text(content: str, replacements: list[tuple[str, str]]) -> str: + for pattern, replacement in replacements: + content = re.sub(pattern, replacement, content) + return content + + +def update_text_file(path: Path, transform, metadata: dict, dry_run: bool) -> bool: + if not path.is_file(): + return False + + original = path.read_text(encoding="utf-8") + updated = transform(original, metadata) + if updated == original: + return False + + if dry_run: + print(f"[dry-run] Would update {path}") + return True + + path.write_text(updated, encoding="utf-8", newline="\n") + print(f"✅ Updated {path}") + return True + + +def sync_curated_docs(base_dir: str, metadata: dict, dry_run: bool) -> int: + root = Path(base_dir) + + regex_text_replacements = [ + ( + root / "docs" / "users" / "claude-code-skills.md", + [ + (r"\d[\d,]*\+ skills", f"{metadata['total_skills_label']} skills"), + ], + ), + ( + root / "docs" / "users" / "gemini-cli-skills.md", + [ + (r"\d[\d,]*\+ files", f"{metadata['total_skills_label']} files"), + ], + ), + ( + root / "docs" / "users" / "usage.md", + [ + (r"\d[\d,]*\+ skill files", f"{metadata['total_skills_label']} skill files"), + (r"\d[\d,]*\+ tools", f"{metadata['total_skills_label']} tools"), + (r"all \d[\d,]*\+ skills", f"all {metadata['total_skills_label']} skills"), + (r"have \d[\d,]*\+ skills installed locally", f"have {metadata['total_skills_label']} skills installed locally"), + ], + ), + ( + root / "docs" / "users" / "visual-guide.md", + [ + (r"\d[\d,]*\+ skills live here", f"{metadata['total_skills_label']} skills live here"), + (r"\d[\d,]*\+ total", f"{metadata['total_skills_label']} total"), + (r"\d[\d,]*\+ SKILLS", f"{metadata['total_skills_label']} SKILLS"), + ], + ), + ( + root / "docs" / "users" / "kiro-integration.md", + [ + (r"\d[\d,]*\+ specialized areas", f"{metadata['total_skills_label']} specialized areas"), + ], + ), + ( + root / "docs" / "maintainers" / "repo-growth-seo.md", + [ + (r"\d[\d,]*\+ agentic skills", f"{metadata['total_skills_label']} agentic skills"), + (r"\d[\d,]*\+ Agentic Skills", f"{metadata['total_skills_label']} Agentic Skills"), + ], + ), + ( + root / "docs" / "maintainers" / "skills-update-guide.md", + [ + (r"All \d[\d,]*\+ skills from the skills directory", f"All {metadata['total_skills_label']} skills from the skills directory"), + ], + ), + ( + root / "docs" / "integrations" / "jetski-gemini-loader" / "README.md", + [ + (r"\d[\d,]*\+ skills", f"{metadata['total_skills_label']} skills"), + ], + ), + ] + + updated_files = 0 + updated_files += int(update_text_file(root / "README.md", sync_readme_copy, metadata, dry_run)) + updated_files += int(update_text_file(root / "docs" / "users" / "getting-started.md", sync_getting_started, metadata, dry_run)) + updated_files += int(update_text_file(root / "docs" / "users" / "bundles.md", sync_bundles_doc, metadata, dry_run)) + updated_files += int(update_text_file(root / "docs" / "integrations" / "jetski-cortex.md", sync_jetski_cortex, metadata, dry_run)) + + for path, replacements in regex_text_replacements: + updated_files += int( + update_text_file( + path, + lambda content, current_metadata, repl=replacements: sync_regex_text(content, repl), + metadata, + dry_run, + ) + ) + + return updated_files def update_package_description(base_dir: str, metadata: dict, dry_run: bool) -> bool: @@ -38,10 +267,7 @@ def update_package_description(base_dir: str, metadata: dict, dry_run: bool) -> def print_manual_github_about(metadata: dict) -> None: - description = ( - f"{metadata['total_skills_label']} curated SKILL.md files for Claude Code, " - "Cursor, Gemini CLI, Codex, Copilot, and Antigravity." - ) + description = build_about_description(metadata) print("\nManual GitHub repo settings update:") print(f"- About description: {description}") print("- Suggested topics: claude-code, cursor, gemini-cli, codex-cli, github-copilot, antigravity") @@ -72,10 +298,13 @@ def main() -> int: dry_run=args.dry_run, refresh_volatile=args.refresh_volatile ) package_updated = update_package_description(base_dir, metadata, args.dry_run) + docs_updated = sync_curated_docs(base_dir, metadata, args.dry_run) print_manual_github_about(readme_metadata) if args.dry_run and not package_updated: print("\n[dry-run] No package.json description changes required.") + if args.dry_run and docs_updated == 0: + print("[dry-run] No curated docs changes required.") return 0 diff --git a/tools/scripts/tests/run-test-suite.js b/tools/scripts/tests/run-test-suite.js index 21cf4ec8..efef97ea 100644 --- a/tools/scripts/tests/run-test-suite.js +++ b/tools/scripts/tests/run-test-suite.js @@ -27,6 +27,7 @@ const LOCAL_TEST_COMMANDS = [ [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_generate_index_categories.py")], [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_repair_description_usage_summaries.py")], [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_microsoft_skills_security.py")], + [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_repo_metadata.py")], [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_validate_skills_headings.py")], ]; const NETWORK_TEST_COMMANDS = [ diff --git a/tools/scripts/tests/test_sync_repo_metadata.py b/tools/scripts/tests/test_sync_repo_metadata.py new file mode 100644 index 00000000..c8943215 --- /dev/null +++ b/tools/scripts/tests/test_sync_repo_metadata.py @@ -0,0 +1,125 @@ +import importlib.util +import sys +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[3] +TOOLS_SCRIPTS_DIR = REPO_ROOT / "tools" / "scripts" +if str(TOOLS_SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(TOOLS_SCRIPTS_DIR)) + + +def load_module(relative_path: str, module_name: str): + module_path = REPO_ROOT / relative_path + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module + + +sync_repo_metadata = load_module( + "tools/scripts/sync_repo_metadata.py", + "sync_repo_metadata_test", +) + + +class SyncRepoMetadataTests(unittest.TestCase): + def test_sync_curated_docs_updates_counts_and_versions(self): + metadata = { + "version": "8.4.0", + "total_skills": 1304, + "total_skills_label": "1,304+", + } + + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + (root / "README.md").write_text( + """# 🌌 Antigravity Awesome Skills: 1,304+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More + +> **Installable GitHub library of 1,273+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.** + +**Current release: V8.3.0.** Trusted by 25k+ GitHub stargazers, this repository combines official and community skill collections with bundles, workflows, installation paths, and docs that help you go from first install to daily use quickly. + +- **Broad coverage with real utility**: 1,273+ skills across development, testing, security, infrastructure, product, and marketing. + +**Antigravity Awesome Skills** (Release 8.3.0) is a large, installable skill library for AI coding assistants. It includes onboarding docs, bundles, workflows, generated catalogs, and a CLI installer so you can move from discovery to actual usage without manually stitching together dozens of repos. + +If you want a faster answer than "browse all 1,273+ skills", start with a tool-specific guide: +""", + encoding="utf-8", + ) + (root / "docs" / "users").mkdir(parents=True) + (root / "docs" / "maintainers").mkdir(parents=True) + (root / "docs" / "integrations" / "jetski-gemini-loader").mkdir(parents=True) + + (root / "docs" / "users" / "getting-started.md").write_text( + "# Getting Started with Antigravity Awesome Skills (V8.3.0)\n", + encoding="utf-8", + ) + (root / "docs" / "users" / "claude-code-skills.md").write_text( + "- It includes 1,273+ skills instead of a narrow single-domain starter pack.\n", + encoding="utf-8", + ) + (root / "docs" / "users" / "gemini-cli-skills.md").write_text( + "- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,273+ files.\n", + encoding="utf-8", + ) + (root / "docs" / "users" / "usage.md").write_text( + "✅ **Downloaded 1,254+ skill files**\n- You installed a toolbox with 1,254+ tools\nDon't try to use all 1,254+ skills at once.\nNo. Even though you have 1,254+ skills installed locally\n", + encoding="utf-8", + ) + (root / "docs" / "users" / "visual-guide.md").write_text( + "1,254+ skills live here\n1,254+ total\n1,254+ SKILLS\n", + encoding="utf-8", + ) + (root / "docs" / "users" / "bundles.md").write_text( + '### 🚀 The "Essentials" Pack\n### 🌐 The "Web Wizard" Pack\n_Last updated: March 2026 | Total Skills: 1,254+ | Total Bundles: 99_\n', + encoding="utf-8", + ) + (root / "docs" / "users" / "kiro-integration.md").write_text( + "- **Domain expertise** across 1,254+ specialized areas\n", + encoding="utf-8", + ) + (root / "docs" / "maintainers" / "repo-growth-seo.md").write_text( + "> Installable GitHub library of 1,273+ agentic skills\n- use a clean preview image that says `1,273+ Agentic Skills`;\n", + encoding="utf-8", + ) + (root / "docs" / "maintainers" / "skills-update-guide.md").write_text( + "- All 1,254+ skills from the skills directory\n", + encoding="utf-8", + ) + (root / "docs" / "integrations" / "jetski-cortex.md").write_text( + "1.200+ skill\nCon oltre 1.200 skill, questo approccio\n", + encoding="utf-8", + ) + (root / "docs" / "integrations" / "jetski-gemini-loader" / "README.md").write_text( + "This pattern avoids context overflow when you have 1,200+ skills installed.\n", + encoding="utf-8", + ) + + updated_files = sync_repo_metadata.sync_curated_docs(str(root), metadata, dry_run=False) + + self.assertGreaterEqual(updated_files, 10) + self.assertIn("1,304+ agentic skills", (root / "README.md").read_text(encoding="utf-8")) + self.assertIn("V8.4.0", (root / "docs" / "users" / "getting-started.md").read_text(encoding="utf-8")) + self.assertIn("1,304+ files", (root / "docs" / "users" / "gemini-cli-skills.md").read_text(encoding="utf-8")) + self.assertIn("1,304+ specialized areas", (root / "docs" / "users" / "kiro-integration.md").read_text(encoding="utf-8")) + self.assertIn("Total Bundles: 2", (root / "docs" / "users" / "bundles.md").read_text(encoding="utf-8")) + self.assertIn("1.304+ skill", (root / "docs" / "integrations" / "jetski-cortex.md").read_text(encoding="utf-8")) + + def test_build_about_description_uses_live_skill_count(self): + description = sync_repo_metadata.build_about_description( + { + "total_skills_label": "1,304+", + } + ) + self.assertIn("1,304+ agentic skills", description) + self.assertIn("installer CLI", description) + + +if __name__ == "__main__": + unittest.main() diff --git a/walkthrough.md b/walkthrough.md index 1aa6d5b6..3de5ec38 100644 --- a/walkthrough.md +++ b/walkthrough.md @@ -125,3 +125,4 @@ - `docs/maintainers/repo-growth-seo.md` - `docs/maintainers/skills-update-guide.md` - Updated the changelog `Unreleased` section so the post-`v8.4.0` main branch state documents both the imported skill families and the docs/About realignment. +- Automated the recurring docs metadata maintenance by extending `tools/scripts/sync_repo_metadata.py`, wiring it into `npm run chain`, and adding a regression test so future skill-count/version updates propagate through the curated docs surface without manual patching.