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.
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
125
tools/scripts/tests/test_sync_repo_metadata.py
Normal file
125
tools/scripts/tests/test_sync_repo_metadata.py
Normal file
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user