230 lines
8.2 KiB
Python
230 lines
8.2 KiB
Python
#!/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
|
|
from sync_editorial_bundles import load_editorial_bundles, render_bundles_doc
|
|
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, root: Path) -> str:
|
|
manifest_path = root / "data" / "editorial-bundles.json"
|
|
template_path = root / "tools" / "templates" / "editorial-bundles.md.tmpl"
|
|
if manifest_path.is_file() and template_path.is_file():
|
|
bundles = load_editorial_bundles(root)
|
|
return render_bundles_doc(root, metadata, bundles)
|
|
|
|
bundle_count = sync_repo_metadata.count_documented_bundles(content)
|
|
if bundle_count == 0:
|
|
bundle_count = 36
|
|
expected, _ = sync_repo_metadata.replace_if_present(
|
|
content,
|
|
sync_repo_metadata.BUNDLES_FOOTER_RE,
|
|
f"_Last updated: March 2026 | Total Skills: {metadata['total_skills_label']} | Total Bundles: {bundle_count}_",
|
|
)
|
|
return expected
|
|
|
|
|
|
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", lambda content, current_metadata: _expected_bundles(content, current_metadata, root)),
|
|
("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())
|