Skip to content

Commit 96a087e

Browse files
committed
Generate README skill counts from catalog
1 parent 036103b commit 96a087e

4 files changed

Lines changed: 52 additions & 17 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ This repository should behave like a maintainable documentation-and-automation s
438438
- Public site copy should frame Claude Code, GitHub Copilot, Gemini, and Codex as supported platforms with recognizable brand-style presentation, not as "AI agents".
439439
- Public landing page spacing should feel deliberate and compact; excessive whitespace between cards, sections, and step content is a regression.
440440
- Skill catalog cards on the public site must keep category badges and install commands on stable separate rows; badges must never collide with or visually break the command line.
441+
- Public README skill counts must be generated from the catalog source of truth; do not leave hand-maintained exact numbers in badges or intro copy where merges can make them stale.
441442

442443
### Dislikes
443444

@@ -451,6 +452,7 @@ This repository should behave like a maintainable documentation-and-automation s
451452
- Misleading public site wording that calls the supported platforms "AI agents" instead of showing them as platforms that use the skill catalog.
452453
- Bloated spacing on the public landing page, especially in Quick Start shells, step stacks, and sidebar cards.
453454
- Broken skill-card footers where category badges and install commands overlap, wrap awkwardly, or compete for the same horizontal space.
455+
- Stale exact skill counts in the public README after new skills merge, especially when the catalog source already knows the real count.
454456

455457
## Anti-Patterns
456458

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
[![NuGet](https://img.shields.io/nuget/v/dotnet-skills?logo=nuget&label=nuget)](https://www.nuget.org/packages/dotnet-skills)
44
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5-
[![Skills](https://img.shields.io/badge/skills-62-blue)](#catalog)
5+
[![Skills](https://img.shields.io/badge/skills-63-blue)](#catalog)
66
[![.NET](https://img.shields.io/badge/.NET-10+-512BD4?logo=dotnet)](https://dotnet.microsoft.com/)
77

88
**Stop explaining .NET to your AI. Start building.**
99

1010
We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing. Watching Codex generate `Startup.cs` for a Minimal API project.
1111

12-
This catalog fixes that. **62 skills** covering the entire .NET ecosystem—from ASP.NET Core to Orleans, from MAUI to Semantic Kernel. Install them once, and your AI agent actually knows modern .NET.
12+
This catalog fixes that. **63 skills** covering the entire .NET ecosystem—from ASP.NET Core to Orleans, from MAUI to Semantic Kernel. Install them once, and your AI agent actually knows modern .NET.
1313

1414
## Why This Matters
1515

catalog/skills.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@
180180
"compatibility": "Requires classic ASP.NET on .NET Framework.",
181181
"path": "skills/dotnet-legacy-aspnet/"
182182
},
183+
{
184+
"name": "dotnet-libvlc",
185+
"title": "LibVLC Skill",
186+
"version": "1.0.0",
187+
"category": "Desktop",
188+
"description": "Expert knowledge of the libvlc C API (3.x and 4.x), the multimedia framework behind VLC media player. Use when helping with LibVLC or LibVLCSharp for media playback, streaming, or transcoding.",
189+
"compatibility": ".NET 6+, .NET Standard 2.0+, .NET Framework 4.6.1+",
190+
"path": "skills/dotnet-libvlc/"
191+
},
183192
{
184193
"name": "dotnet-managedcode-communication",
185194
"title": "ManagedCode.Communication",

scripts/generate_catalog.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515

1616
BEGIN_MARKER = "<!-- BEGIN GENERATED CATALOG -->"
1717
END_MARKER = "<!-- END GENERATED CATALOG -->"
18+
README_SKILLS_BADGE_PATTERN = re.compile(
19+
r"(\[!\[Skills\]\(https://img\.shields\.io/badge/skills-)([^-)]+)(-blue\)\]\(#catalog\))"
20+
)
21+
README_SKILLS_INTRO_PATTERN = re.compile(
22+
r"(This catalog fixes that\. \*\*)([^*]+)(\*\* covering the entire \.NET ecosystem)"
23+
)
1824

1925
CATEGORY_ORDER = [
2026
"Core",
@@ -141,31 +147,49 @@ def render_catalog(skills: list[dict[str, str]]) -> str:
141147
return "\n".join(lines)
142148

143149

144-
def update_readme(rendered_catalog: str) -> bool:
145-
readme = README_PATH.read_text()
150+
def apply_readme_count_metadata(readme: str, skill_count: int) -> str:
151+
badge_updated, badge_replacements = README_SKILLS_BADGE_PATTERN.subn(
152+
lambda match: f"{match.group(1)}{skill_count}{match.group(3)}",
153+
readme,
154+
count=1,
155+
)
156+
if badge_replacements != 1:
157+
raise ValueError("README.md is missing the generated skills badge pattern")
158+
159+
intro_updated, intro_replacements = README_SKILLS_INTRO_PATTERN.subn(
160+
lambda match: f"{match.group(1)}{skill_count} skills{match.group(3)}",
161+
badge_updated,
162+
count=1,
163+
)
164+
if intro_replacements != 1:
165+
raise ValueError("README.md is missing the generated intro skill count pattern")
166+
167+
return intro_updated
168+
169+
170+
def render_readme(readme: str, rendered_catalog: str, skill_count: int) -> str:
171+
updated = apply_readme_count_metadata(readme, skill_count)
146172
pattern = re.compile(
147173
rf"{re.escape(BEGIN_MARKER)}.*?{re.escape(END_MARKER)}",
148174
flags=re.DOTALL,
149175
)
150-
if not pattern.search(readme):
176+
if not pattern.search(updated):
151177
raise ValueError("README.md is missing generated catalog markers")
152178

153-
updated = pattern.sub(rendered_catalog, readme)
179+
return pattern.sub(rendered_catalog, updated)
180+
181+
182+
def update_readme(rendered_catalog: str, skill_count: int) -> bool:
183+
readme = README_PATH.read_text()
184+
updated = render_readme(readme, rendered_catalog, skill_count)
154185
changed = updated != readme
155186
README_PATH.write_text(updated)
156187
return changed
157188

158189

159-
def check_readme(rendered_catalog: str) -> bool:
190+
def check_readme(rendered_catalog: str, skill_count: int) -> bool:
160191
readme = README_PATH.read_text()
161-
pattern = re.compile(
162-
rf"{re.escape(BEGIN_MARKER)}.*?{re.escape(END_MARKER)}",
163-
flags=re.DOTALL,
164-
)
165-
match = pattern.search(readme)
166-
if not match:
167-
raise ValueError("README.md is missing generated catalog markers")
168-
return match.group(0) == rendered_catalog
192+
return render_readme(readme, rendered_catalog, skill_count) == readme
169193

170194

171195
def write_manifest(skills: list[dict[str, str]]) -> None:
@@ -219,7 +243,7 @@ def main() -> int:
219243
return 0
220244

221245
if args.check:
222-
readme_ok = check_readme(rendered_catalog)
246+
readme_ok = check_readme(rendered_catalog, len(skills))
223247
manifest_ok = check_manifest(skills)
224248
if not readme_ok or not manifest_ok:
225249
if not readme_ok:
@@ -230,7 +254,7 @@ def main() -> int:
230254
print("Catalog is up to date.")
231255
return 0
232256

233-
update_readme(rendered_catalog)
257+
update_readme(rendered_catalog, len(skills))
234258
write_manifest(skills)
235259
print(f"Generated catalog for {len(skills)} skills.")
236260
return 0

0 commit comments

Comments
 (0)