From 694721223c219ddb25b5fa1310b6bbb5cc920555 Mon Sep 17 00:00:00 2001 From: sickn33 Date: Sat, 21 Mar 2026 10:48:00 +0100 Subject: [PATCH] feat(repo): Add contributor sync and consistency audits Add maintainer automation for repo-state hygiene so contributor acknowledgements, count-sensitive docs, and GitHub About metadata stay aligned from the same workflow. Cover the new scripts with regression tests and wire them into the local test suite to keep future maintenance changes from drifting silently. --- .github/MAINTENANCE.md | 20 +- CHANGELOG.md | 1 + README.md | 56 ++--- package.json | 6 + tools/scripts/audit_consistency.py | 214 ++++++++++++++++++ tools/scripts/check_stale_claims.py | 25 ++ tools/scripts/sync_contributors.py | 133 +++++++++++ tools/scripts/tests/run-test-suite.js | 2 + tools/scripts/tests/test_audit_consistency.py | 133 +++++++++++ tools/scripts/tests/test_sync_contributors.py | 86 +++++++ walkthrough.md | 1 + 11 files changed, 646 insertions(+), 31 deletions(-) create mode 100644 tools/scripts/audit_consistency.py create mode 100644 tools/scripts/check_stale_claims.py create mode 100644 tools/scripts/sync_contributors.py create mode 100644 tools/scripts/tests/test_audit_consistency.py create mode 100644 tools/scripts/tests/test_sync_contributors.py diff --git a/.github/MAINTENANCE.md b/.github/MAINTENANCE.md index 548ce0be..9030daf1 100644 --- a/.github/MAINTENANCE.md +++ b/.github/MAINTENANCE.md @@ -80,7 +80,20 @@ Before ANY commit that adds/modifies skills, run the chain: npm run catalog ``` -3. **COMMIT GENERATED FILES**: +3. **Optional maintainer sweep shortcut**: + ```bash + npm run sync:repo-state + ``` + This wraps `chain + catalog + sync:contributors + audit:consistency` for a full local repo-state refresh. + + When you need the live GitHub repo metadata updated too, run: + + ```bash + npm run sync:github-about + npm run audit:consistency:github + ``` + +4. **COMMIT GENERATED FILES**: ```bash git add README.md skills_index.json data/skills_index.json data/catalog.json data/bundles.json data/aliases.json CATALOG.md git commit -m "chore: sync generated files" @@ -180,8 +193,9 @@ We used this flow for PRs [#220](https://github.com/sickn33/antigravity-awesome- After you have merged several PRs or before cutting a release: 1. **Sync Contributors List**: - - Run: `git shortlog -sn --all` - - Update `## Repo Contributors` in README.md. + - Run: `npm run sync:contributors` + - This refreshes `## Repo Contributors` in `README.md` from the live GitHub contributor list while preserving custom bot/app links. + - If you are already doing a full maintainer sweep, prefer `npm run sync:repo-state`. 2. **Verify Table of Contents**: - Ensure all new headers have clean anchors. diff --git a/CHANGELOG.md b/CHANGELOG.md index 704f56d9..7eb3d062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. - Added an explicit `sync:github-about` automation path so GitHub About description, homepage, and topics can be refreshed from the same metadata source instead of being updated manually. +- Added contributor sync plus repo-state audit automation: `sync:contributors`, `check:stale-claims`, `audit:consistency`, and `sync:repo-state` now cover contributor acknowledgements, stale count/version drift, and end-to-end maintainer sanity checks. ## [8.4.0] - 2026-03-20 - "Discovery, Metadata, and Release Hardening" diff --git a/README.md b/README.md index 840a4fe8..2974a7d6 100644 --- a/README.md +++ b/README.md @@ -521,11 +521,6 @@ Made with [contrib.rocks](https://contrib.rocks). *(Image may be cached; [view l We officially thank the following contributors for their help in making this repository awesome! -- [@JayeHarrill](https://github.com/JayeHarrill) -- [@wd041216-bit](https://github.com/wd041216-bit) -- [@Champbreed](https://github.com/Champbreed) -- [@suhaibjanjua](https://github.com/suhaibjanjua) -- [@Wolfe-Jam](https://github.com/Wolfe-Jam) - [@sck000](https://github.com/sck000) - [@github-actions[bot]](https://github.com/apps/github-actions) - [@sickn33](https://github.com/sickn33) @@ -541,6 +536,8 @@ We officially thank the following contributors for their help in making this rep - [@itsmeares](https://github.com/itsmeares) - [@chauey](https://github.com/chauey) - [@ar27111994](https://github.com/ar27111994) +- [@suhaibjanjua](https://github.com/suhaibjanjua) +- [@Champbreed](https://github.com/Champbreed) - [@GuppyTheCat](https://github.com/GuppyTheCat) - [@Copilot](https://github.com/apps/copilot-swe-agent) - [@8hrsk](https://github.com/8hrsk) @@ -549,17 +546,23 @@ We officially thank the following contributors for their help in making this rep - [@0xrohitgarg](https://github.com/0xrohitgarg) - [@zebbern](https://github.com/zebbern) - [@talesperito](https://github.com/talesperito) +- [@fernandorych](https://github.com/fernandorych) - [@SnakeEye-sudo](https://github.com/SnakeEye-sudo) - [@nikolasdehor](https://github.com/nikolasdehor) -- [@fernandorych](https://github.com/fernandorych) -- [@taksrules](https://github.com/taksrules) - [@jackjin1997](https://github.com/jackjin1997) - [@HuynhNhatKhanh](https://github.com/HuynhNhatKhanh) +- [@taksrules](https://github.com/taksrules) - [@liyin2015](https://github.com/liyin2015) +- [@Gizzant](https://github.com/Gizzant) +- [@AssassinMaeve](https://github.com/AssassinMaeve) +- [@Musayrlsms](https://github.com/Musayrlsms) - [@arathiesh](https://github.com/arathiesh) - [@Tiger-Foxx](https://github.com/Tiger-Foxx) - [@RamonRiosJr](https://github.com/RamonRiosJr) -- [@Musayrlsms](https://github.com/Musayrlsms) +- [@ziuus](https://github.com/ziuus) +- [@Wolfe-Jam](https://github.com/Wolfe-Jam) +- [@Wittlesus](https://github.com/Wittlesus) +- [@wahidzzz](https://github.com/wahidzzz) - [@Vonfry](https://github.com/Vonfry) - [@vprudnikoff](https://github.com/vprudnikoff) - [@viktor-ferenczi](https://github.com/viktor-ferenczi) @@ -581,12 +584,17 @@ We officially thank the following contributors for their help in making this rep - [@yubing744](https://github.com/yubing744) - [@vuth-dogo](https://github.com/vuth-dogo) - [@yang1002378395-cmyk](https://github.com/yang1002378395-cmyk) +- [@viliawang-pm](https://github.com/viliawang-pm) +- [@tsilverberg](https://github.com/tsilverberg) - [@thuanlm215](https://github.com/thuanlm215) - [@shmlkv](https://github.com/shmlkv) - [@rafsilva85](https://github.com/rafsilva85) - [@nocodemf](https://github.com/nocodemf) +- [@marsiandeployer](https://github.com/marsiandeployer) +- [@ksgisang](https://github.com/ksgisang) - [@KrisnaSantosa15](https://github.com/KrisnaSantosa15) - [@junited31](https://github.com/junited31) +- [@fullstackcrew-alpha](https://github.com/fullstackcrew-alpha) - [@fbientrigo](https://github.com/fbientrigo) - [@dz3ai](https://github.com/dz3ai) - [@developer-victor](https://github.com/developer-victor) @@ -594,27 +602,24 @@ We officially thank the following contributors for their help in making this rep - [@christopherlhammer11-ai](https://github.com/christopherlhammer11-ai) - [@c1c3ru](https://github.com/c1c3ru) - [@buzzbysolcex](https://github.com/buzzbysolcex) +- [@BenZinaDaze](https://github.com/BenZinaDaze) - [@avimak](https://github.com/avimak) - [@antbotlab](https://github.com/antbotlab) - [@amalsam](https://github.com/amalsam) -- [@ziuus](https://github.com/ziuus) -- [@Wittlesus](https://github.com/Wittlesus) -- [@wahidzzz](https://github.com/wahidzzz) -- [@olgasafonova](https://github.com/olgasafonova) +- [@qcwssss](https://github.com/qcwssss) +- [@rcigor](https://github.com/rcigor) - [@hvasconcelos](https://github.com/hvasconcelos) - [@Guilherme-ruy](https://github.com/Guilherme-ruy) -- [@Gizzant](https://github.com/Gizzant) - [@Digidai](https://github.com/Digidai) - [@dbhat93](https://github.com/dbhat93) -- [@BenZinaDaze](https://github.com/BenZinaDaze) -- [@JaskiratAnand](https://github.com/JaskiratAnand) -- [@marsiandeployer](https://github.com/marsiandeployer) -- [@suhaibjanjua](https://github.com/suhaibjanjua) - [@decentraliser](https://github.com/decentraliser) - [@MAIOStudio](https://github.com/MAIOStudio) +- [@wd041216-bit](https://github.com/wd041216-bit) - [@conorbronsdon](https://github.com/conorbronsdon) +- [@ChaosRealmsAI](https://github.com/ChaosRealmsAI) - [@kriptoburak](https://github.com/kriptoburak) - [@BenedictKing](https://github.com/BenedictKing) +- [@fernandezbaptiste](https://github.com/fernandezbaptiste) - [@acbhatt12](https://github.com/acbhatt12) - [@Andruia](https://github.com/Andruia) - [@AlmogBaku](https://github.com/AlmogBaku) @@ -625,15 +630,19 @@ We officially thank the following contributors for their help in making this rep - [@ALEKGG1](https://github.com/ALEKGG1) - [@8144225309](https://github.com/8144225309) - [@1bcMax](https://github.com/1bcMax) +- [@olgasafonova](https://github.com/olgasafonova) - [@sharmanilay](https://github.com/sharmanilay) - [@KhaiTrang1995](https://github.com/KhaiTrang1995) - [@LocNguyenSGU](https://github.com/LocNguyenSGU) - [@nedcodes-ok](https://github.com/nedcodes-ok) - [@iftikharg786](https://github.com/iftikharg786) +- [@halith-smh](https://github.com/halith-smh) - [@mertbaskurt](https://github.com/mertbaskurt) - [@MatheusCampagnolo](https://github.com/MatheusCampagnolo) +- [@Marvin19700118](https://github.com/Marvin19700118) - [@djmahe4](https://github.com/djmahe4) - [@MArbeeGit](https://github.com/MArbeeGit) +- [@majorelalexis-stack](https://github.com/majorelalexis-stack) - [@Svobikl](https://github.com/Svobikl) - [@kromahlusenii-ops](https://github.com/kromahlusenii-ops) - [@Krishna-Modi12](https://github.com/Krishna-Modi12) @@ -643,17 +652,8 @@ We officially thank the following contributors for their help in making this rep - [@kage-art](https://github.com/kage-art) - [@whatiskadudoing](https://github.com/whatiskadudoing) - [@jonathimer](https://github.com/jonathimer) -- [@qcwssss](https://github.com/qcwssss) -- [@rcigor](https://github.com/rcigor) -- [@tsilverberg](https://github.com/tsilverberg) -- [@Marvin19700118](https://github.com/Marvin19700118) -- [@ChaosRealmsAI](https://github.com/ChaosRealmsAI) -- [@ksgisang](https://github.com/ksgisang) -- [@viliawang-pm](https://github.com/viliawang-pm) -- [@AssassinMaeve](https://github.com/AssassinMaeve) -- [@fernandezbaptiste](https://github.com/fernandezbaptiste) - ---- +- [@JayeHarrill](https://github.com/JayeHarrill) +- [@JaskiratAnand](https://github.com/JaskiratAnand) ## License diff --git a/package.json b/package.json index 213881d9..0559ec50 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,16 @@ "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", "sync:github-about": "node tools/scripts/run-python.js tools/scripts/sync_repo_metadata.py --apply-github-about", + "sync:contributors": "node tools/scripts/run-python.js tools/scripts/sync_contributors.py", "chain": "npm run validate && npm run index && npm run sync:metadata", "sync:all": "npm run chain", + "sync:repo-state": "npm run chain && npm run catalog && npm run sync:contributors && npm run audit:consistency", + "sync:repo-state:full": "npm run sync:repo-state && npm run sync:github-about && npm run audit:consistency:github", "catalog": "node tools/scripts/build-catalog.js", "build": "npm run chain && npm run catalog", + "check:stale-claims": "node tools/scripts/run-python.js tools/scripts/check_stale_claims.py", + "audit:consistency": "node tools/scripts/run-python.js tools/scripts/audit_consistency.py", + "audit:consistency:github": "node tools/scripts/run-python.js tools/scripts/audit_consistency.py --check-github-about", "security:docs": "node tools/scripts/tests/docs_security_content.test.js", "pr:preflight": "node tools/scripts/pr_preflight.js", "release:preflight": "node tools/scripts/release_workflow.js preflight", diff --git a/tools/scripts/audit_consistency.py b/tools/scripts/audit_consistency.py new file mode 100644 index 00000000..cd48c3a7 --- /dev/null +++ b/tools/scripts/audit_consistency.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path + +from _project_paths import find_repo_root +import sync_repo_metadata +from update_readme import configure_utf8_output, load_metadata, apply_metadata + + +def _read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def _package_expected_description(metadata: dict) -> str: + return ( + f"{metadata['total_skills_label']} agentic skills for Claude Code, Gemini CLI, " + "Cursor, Antigravity & more. Installer CLI." + ) + + +def _expected_readme(content: str, metadata: dict) -> str: + return sync_repo_metadata.sync_readme_copy(apply_metadata(content, metadata), metadata) + + +def _expected_getting_started(content: str, metadata: dict) -> str: + return sync_repo_metadata.sync_getting_started(content, metadata) + + +def _expected_bundles(content: str, metadata: dict) -> str: + return sync_repo_metadata.sync_bundles_doc(content, metadata) + + +def _expected_regex_sync(content: str, replacements: list[tuple[str, str]]) -> str: + return sync_repo_metadata.sync_regex_text(content, replacements) + + +def _expected_jetski_cortex(content: str, metadata: dict) -> str: + return sync_repo_metadata.sync_jetski_cortex(content, metadata) + + +def find_local_consistency_issues(base_dir: str | Path) -> list[str]: + root = Path(base_dir) + metadata = load_metadata(str(root)) + issues: list[str] = [] + + package_json = json.loads(_read_text(root / "package.json")) + if package_json.get("description") != _package_expected_description(metadata): + issues.append("package.json description is out of sync with the live skills count") + + file_checks = [ + ("README.md", _expected_readme), + ("docs/users/getting-started.md", _expected_getting_started), + ("docs/users/bundles.md", _expected_bundles), + ("docs/integrations/jetski-cortex.md", _expected_jetski_cortex), + ( + "docs/users/claude-code-skills.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [(r"\d[\d,]*\+ skills", f"{current_metadata['total_skills_label']} skills")], + ), + ), + ( + "docs/users/gemini-cli-skills.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [(r"\d[\d,]*\+ files", f"{current_metadata['total_skills_label']} files")], + ), + ), + ( + "docs/users/usage.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [ + (r"\d[\d,]*\+ skill files", f"{current_metadata['total_skills_label']} skill files"), + (r"\d[\d,]*\+ tools", f"{current_metadata['total_skills_label']} tools"), + (r"all \d[\d,]*\+ skills", f"all {current_metadata['total_skills_label']} skills"), + ( + r"have \d[\d,]*\+ skills installed locally", + f"have {current_metadata['total_skills_label']} skills installed locally", + ), + ], + ), + ), + ( + "docs/users/visual-guide.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [ + (r"\d[\d,]*\+ skills live here", f"{current_metadata['total_skills_label']} skills live here"), + (r"\d[\d,]*\+ total", f"{current_metadata['total_skills_label']} total"), + (r"\d[\d,]*\+ SKILLS", f"{current_metadata['total_skills_label']} SKILLS"), + ], + ), + ), + ( + "docs/users/kiro-integration.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [(r"\d[\d,]*\+ specialized areas", f"{current_metadata['total_skills_label']} specialized areas")], + ), + ), + ( + "docs/maintainers/repo-growth-seo.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [ + (r"\d[\d,]*\+ agentic skills", f"{current_metadata['total_skills_label']} agentic skills"), + (r"\d[\d,]*\+ Agentic Skills", f"{current_metadata['total_skills_label']} Agentic Skills"), + ], + ), + ), + ( + "docs/maintainers/skills-update-guide.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [ + ( + r"All \d[\d,]*\+ skills from the skills directory", + f"All {current_metadata['total_skills_label']} skills from the skills directory", + ) + ], + ), + ), + ( + "docs/integrations/jetski-gemini-loader/README.md", + lambda content, current_metadata: _expected_regex_sync( + content, + [(r"\d[\d,]*\+ skills", f"{current_metadata['total_skills_label']} skills")], + ), + ), + ] + + for relative_path, transform in file_checks: + path = root / relative_path + if not path.is_file(): + issues.append(f"{relative_path} is missing") + continue + original = _read_text(path) + expected = transform(original, metadata) + if original != expected: + issues.append(f"{relative_path} contains stale or inconsistent generated claims") + + return issues + + +def find_github_about_issues(base_dir: str | Path) -> list[str]: + root = Path(base_dir) + metadata = load_metadata(str(root)) + result = subprocess.run( + [ + "gh", + "repo", + "view", + metadata["repo"], + "--json", + "description,homepageUrl,repositoryTopics", + ], + check=True, + capture_output=True, + text=True, + ) + payload = json.loads(result.stdout) + issues: list[str] = [] + + if payload.get("description") != sync_repo_metadata.build_about_description(metadata): + issues.append("GitHub About description is out of sync") + if payload.get("homepageUrl") != sync_repo_metadata.GITHUB_HOMEPAGE_URL: + issues.append("GitHub About homepage is out of sync") + + current_topics = sorted( + entry["name"] for entry in payload.get("repositoryTopics", []) if isinstance(entry, dict) and "name" in entry + ) + expected_topics = sorted(sync_repo_metadata.build_about_topics()) + if current_topics != expected_topics: + issues.append("GitHub About topics are out of sync") + + return issues + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Audit repository consistency for generated claims.") + parser.add_argument( + "--check-github-about", + action="store_true", + help="Also verify the live GitHub About description, homepage, and topics via gh CLI.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + root = find_repo_root(__file__) + + issues = find_local_consistency_issues(root) + if args.check_github_about: + issues.extend(find_github_about_issues(root)) + + if issues: + for issue in issues: + print(f"❌ {issue}") + return 1 + + print("✅ Repository consistency audit passed.") + return 0 + + +if __name__ == "__main__": + configure_utf8_output() + sys.exit(main()) diff --git a/tools/scripts/check_stale_claims.py b/tools/scripts/check_stale_claims.py new file mode 100644 index 00000000..e103da2a --- /dev/null +++ b/tools/scripts/check_stale_claims.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import sys + +from _project_paths import find_repo_root +from audit_consistency import find_local_consistency_issues +from update_readme import configure_utf8_output + + +def main() -> int: + root = find_repo_root(__file__) + issues = find_local_consistency_issues(root) + if issues: + for issue in issues: + print(f"❌ {issue}") + return 1 + + print("✅ No stale claims detected in active docs.") + return 0 + + +if __name__ == "__main__": + configure_utf8_output() + sys.exit(main()) diff --git a/tools/scripts/sync_contributors.py b/tools/scripts/sync_contributors.py new file mode 100644 index 00000000..d920e093 --- /dev/null +++ b/tools/scripts/sync_contributors.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path + +from _project_paths import find_repo_root +from update_readme import configure_utf8_output, load_metadata + + +CONTRIBUTOR_SECTION_START = "We officially thank the following contributors for their help in making this repository awesome!\n\n" +SPECIAL_LINK_OVERRIDES = { + "Copilot": "https://github.com/apps/copilot-swe-agent", + "github-actions[bot]": "https://github.com/apps/github-actions", + "copilot-swe-agent[bot]": "https://github.com/apps/copilot-swe-agent", +} + + +def parse_existing_contributor_links(content: str) -> dict[str, str]: + links: dict[str, str] = {} + pattern = re.compile(r"^- \[@(?P