Skip to content

Commit 8ebd50d

Browse files
committed
Add per-skill .version file and update instructions to llms.txt
Each skill zip (individual and fat) now contains a .version JSON file with the commit SHA and ISO date of the last change to that skill's files, plus zip_url and llms_txt back-references so an agent can discover update info without prior knowledge of the registry. manifest.json gains commit and date fields per skill for the same reason. llms.txt gains a "Checking for and applying updates" section explaining the comparison workflow (manifest commit vs. installed .version), and the install section now mentions the .version file. Version reflects per-skill git history, not publish time, so an unrelated skill change does not mark all skills as modified.
1 parent f5e7682 commit 8ebd50d

1 file changed

Lines changed: 38 additions & 4 deletions

File tree

.github/scripts/build_site.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
import re
1414
import json
15+
import subprocess
1516
import zipfile
1617
import html as html_lib
1718
from pathlib import Path
@@ -25,6 +26,17 @@
2526
out_dir.mkdir(parents=True, exist_ok=True)
2627

2728

29+
def get_skill_version(skill_dir: Path) -> tuple[str, str]:
30+
result = subprocess.run(
31+
["git", "log", "--max-count=1", "--format=%H %aI", "--", str(skill_dir)],
32+
capture_output=True, text=True,
33+
)
34+
if result.returncode == 0 and result.stdout.strip():
35+
commit, date = result.stdout.strip().split(" ", 1)
36+
return commit, date.strip()
37+
return sha, ""
38+
39+
2840
def parse_frontmatter(path: Path) -> tuple[str, str]:
2941
content = path.read_text(encoding="utf-8")
3042
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
@@ -55,22 +67,29 @@ def parse_frontmatter(path: Path) -> tuple[str, str]:
5567
zip_path = out_dir / zip_name
5668

5769
skill_files = sorted(f for f in skill_dir.rglob("*") if f.is_file())
70+
commit, date = get_skill_version(skill_dir)
71+
version_obj = {"commit": commit, "date": date,
72+
"zip_url": f"{site_url}/{zip_name}",
73+
"llms_txt": f"{site_url}/llms.txt"}
5874

5975
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
6076
for f in skill_files:
6177
zf.write(f, f.relative_to(skill_dir))
78+
zf.writestr(".version", json.dumps(version_obj, indent=2))
6279

6380
name, desc = parse_frontmatter(skill_md)
64-
skills.append({"name": name, "description": desc, "zip": zip_name})
65-
all_skill_entries.append((zip_stem, skill_dir, skill_files))
81+
skills.append({"name": name, "description": desc, "zip": zip_name,
82+
"commit": commit, "date": date})
83+
all_skill_entries.append((zip_stem, skill_dir, skill_files, version_obj))
6684
print(f" packaged: {zip_name} ({name})")
6785

6886
# Fat zip: all skills with their zip-stem as the directory prefix
6987
fat_zip_name = "all-skills.zip"
7088
with zipfile.ZipFile(out_dir / fat_zip_name, "w", zipfile.ZIP_DEFLATED) as zf:
71-
for zip_stem, skill_dir, skill_files in all_skill_entries:
89+
for zip_stem, skill_dir, skill_files, version_obj in all_skill_entries:
7290
for f in skill_files:
7391
zf.write(f, Path(zip_stem) / f.relative_to(skill_dir))
92+
zf.writestr(str(Path(zip_stem) / ".version"), json.dumps(version_obj, indent=2))
7493
print(f" packaged: {fat_zip_name} (all skills)")
7594

7695

@@ -83,6 +102,8 @@ def parse_frontmatter(path: Path) -> tuple[str, str]:
83102
"name": s["name"],
84103
"description": s["description"],
85104
"zip_url": f"{site_url}/{s['zip']}",
105+
"commit": s["commit"],
106+
"date": s["date"],
86107
}
87108
for s in skills
88109
],
@@ -121,14 +142,27 @@ def parse_frontmatter(path: Path) -> tuple[str, str]:
121142
122143
After extraction, verify success by confirming that
123144
`~/.claude/skills/<skill-name>/SKILL.md` exists and its `name:` frontmatter
124-
field matches the skill name.
145+
field matches the skill name. Each zip also contains a `.version` file (JSON)
146+
with `commit`, `date`, `zip_url`, and `llms_txt` fields — used to check for
147+
updates without re-downloading the zip.
125148
126149
Each `SKILL.md` has YAML frontmatter with `name:` (kebab-case identifier) and
127150
`description:` fields, followed by Markdown content that the agent reads to
128151
understand the skill's scope and sub-files.
129152
130153
All skills: {site_url}/{fat_zip_name}
131154
155+
## Checking for and applying updates
156+
157+
To check whether installed skills are up to date:
158+
159+
1. Fetch `{site_url}/manifest.json`.
160+
2. For each installed skill, read `~/.claude/skills/<skill-name>/.version`
161+
and compare its `commit` field against the `commit` field in the manifest.
162+
3. If they differ, the skill has been updated. Re-download the zip from
163+
`zip_url` and extract it into `~/.claude/skills/<skill-name>/`, overwriting
164+
existing files (same steps as initial installation).
165+
132166
## Available skills
133167
134168
{skill_lines}

0 commit comments

Comments
 (0)