feat: auto-regenerate skills stock-take + add 330-skill inventory
The skills-index page had drifted badly (claimed 66 skills; 330 are actually installed). Regenerate it from the live skill tree and add an automated regeneration script + related cron so it can never go stale again. - regenerate-skills-index.sh: full stock-take (inventory -> quartz build -> deploy) - systems/skills-index.md: rebuilt with all 330 skills across 26 categories - .gitignore: fix stray literal chars, ignore build artifacts
This commit is contained in:
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# regenerate-skills-index.sh
|
||||
# Stock-take: regenerate systems/skills-index.md from the live skill tree,
|
||||
# rebuild the Quartz site, and redeploy. Run on a schedule so the wiki never drifts.
|
||||
set -euo pipefail
|
||||
|
||||
WIKI="/home/hermes/workspace/agent-estate-wiki"
|
||||
QUARTZ="/home/hermes/workspace/quartz-temp"
|
||||
SKILLS="/home/hermes/.hermes/skills"
|
||||
|
||||
echo "[$(date -Is)] Starting skills stock-take"
|
||||
|
||||
# 1. Regenerate skills-index.md from the live skill tree
|
||||
python3 - "$SKILLS" "$WIKI/systems/skills-index.md" <<'PY'
|
||||
import sys, re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
|
||||
skills_root = Path(sys.argv[1])
|
||||
out_path = Path(sys.argv[2])
|
||||
skill_files = list(skills_root.rglob('SKILL.md'))
|
||||
|
||||
skills = defaultdict(list)
|
||||
for f in skill_files:
|
||||
rel = f.relative_to(skills_root)
|
||||
parts = rel.parts
|
||||
if len(parts) >= 3:
|
||||
cat, name = parts[0], parts[1]
|
||||
elif len(parts) == 2:
|
||||
cat, name = 'uncategorized', parts[0]
|
||||
else:
|
||||
continue
|
||||
desc = ''
|
||||
try:
|
||||
content = f.read_text(errors='ignore')
|
||||
m = re.search(r'^description:\s*(.+)$', content[:2000], re.M)
|
||||
if m:
|
||||
desc = m.group(1).strip().strip('"').strip("'")[:120]
|
||||
except Exception:
|
||||
pass
|
||||
skills[cat].append((name, desc))
|
||||
|
||||
total = sum(len(v) for v in skills.values())
|
||||
|
||||
L = []
|
||||
L.append('---')
|
||||
L.append('title: Installed Skills')
|
||||
L.append('type: system')
|
||||
L.append('status: active')
|
||||
L.append('created: 2026-07-22')
|
||||
L.append(f'updated: {date.today().isoformat()}')
|
||||
L.append(f'verified_on: {date.today().isoformat()}')
|
||||
L.append('confidence: high')
|
||||
L.append('tags: [skills, hermes]')
|
||||
L.append('sources: []')
|
||||
L.append('---')
|
||||
L.append('')
|
||||
L.append('# Installed Skills')
|
||||
L.append('')
|
||||
L.append(f'**Total installed: {total} skills** in `~/.hermes/skills/`.')
|
||||
L.append('')
|
||||
L.append('This page is auto-regenerated from the live skill inventory. Do not hand-edit.')
|
||||
L.append('')
|
||||
L.append('## Stock-take notes')
|
||||
L.append('- Category names come from the directory structure under `~/.hermes/skills/` (`category/skill/SKILL.md`).')
|
||||
L.append('- Skills stored directly under `~/.hermes/skills/` (no subfolder) are grouped under `uncategorized`.')
|
||||
L.append('- Deeper nested paths (e.g. `mlops/evaluation`) are grouped by their top-level category.')
|
||||
L.append('')
|
||||
|
||||
priority = ['core-hermes','autonomous-ai-agents','hermes','devops','productivity','creative','software-development','research','email','mlops','github','apple','frontend','media','note-taking','web-development','smart-home','mcp','uncategorized']
|
||||
def cat_order(cat):
|
||||
try: return priority.index(cat)
|
||||
except ValueError: return 100 + len(priority)
|
||||
cats = sorted(skills.items(), key=lambda x: (cat_order(x[0]), -len(x[1])))
|
||||
|
||||
for cat, items in cats:
|
||||
nice = cat.replace('-', ' ').title()
|
||||
L.append(f'## {nice} ({len(items)})')
|
||||
L.append('')
|
||||
for name, desc in sorted(items):
|
||||
L.append(f'- `{name}` — {desc}' if desc else f'- `{name}`')
|
||||
L.append('')
|
||||
|
||||
L.append('## Related')
|
||||
L.append('- [[systems/hermes-agent]]')
|
||||
L.append('- [[systems/omniroute]]')
|
||||
L.append('')
|
||||
|
||||
out_path.write_text('\n'.join(L))
|
||||
print(f"Regenerated {out_path} with {total} skills across {len(skills)} categories")
|
||||
PY
|
||||
|
||||
# 2. Sync content into Quartz
|
||||
rm -rf "$QUARTZ/content" "$QUARTZ/public"
|
||||
mkdir -p "$QUARTZ/content"
|
||||
python3 - "$WIKI" "$QUARTZ/content" <<'PY'
|
||||
import shutil, sys
|
||||
from pathlib import Path
|
||||
src = Path(sys.argv[1]); dst = Path(sys.argv[2])
|
||||
skip = {'.git','public','node_modules','quartz-site','serve.py','.gitignore','package.json','package-lock.json','.codewiki-state.json'}
|
||||
for item in src.iterdir():
|
||||
if item.name in skip: continue
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dst / item.name, dirs_exist_ok=True)
|
||||
elif item.suffix == '.md':
|
||||
shutil.copy2(item, dst / item.name)
|
||||
PY
|
||||
|
||||
# 3. Build Quartz
|
||||
cd "$QUARTZ"
|
||||
npx quartz build >/tmp/quartz-build.log 2>&1 || { echo "Quartz build failed"; tail -20 /tmp/quartz-build.log; exit 1; }
|
||||
|
||||
# 4. Deploy to public
|
||||
rm -rf "$WIKI/public"
|
||||
mkdir -p "$WIKI/public"
|
||||
cp -a "$QUARTZ/public/." "$WIKI/public/"
|
||||
|
||||
echo "[$(date -Is)] Completed: $(find "$WIKI/public" -name '*.html' | wc -l) html files deployed"
|
||||
Reference in New Issue
Block a user