Wiki maintenance 2026-09-11: regenerate skills-index (339 skills), add freshness report, create wiki-maintain.sh automation script
This commit is contained in:
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env bash
|
||||
# wiki-maintain.sh — Agent Estate Wiki maintenance entrypoint
|
||||
# Run daily at 03:00 AWST via cron (wiki-stocktake-maintenance job)
|
||||
#
|
||||
# Does three things:
|
||||
# 1. Regenerates systems/skills-index.md from live ~/.hermes/skills/ tree
|
||||
# 2. Audits EVERY page for staleness (>21 days since `updated` frontmatter)
|
||||
# 3. Rebuilds Quartz static site into public/
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
WIKI_DIR="/home/hermes/workspace/agent-estate-wiki"
|
||||
SKILLS_DIR="$HOME/.hermes/skills"
|
||||
STOCKTAKE_DIR="$WIKI_DIR/raw/stocktake"
|
||||
QUARTZ_CONTENT_DIR="$WIKI_DIR/public"
|
||||
TODAY=$(date +%Y%m%d)
|
||||
|
||||
cd "$WIKI_DIR"
|
||||
|
||||
echo "[wiki-maintain] Starting maintenance at $(date -Iseconds)"
|
||||
echo ""
|
||||
|
||||
# =============================================================================
|
||||
# [1/3] Regenerate skills-index.md from live skill tree
|
||||
# =============================================================================
|
||||
echo "[1/3] Regenerating systems/skills-index.md from $SKILLS_DIR..."
|
||||
|
||||
python3 << 'PYTHON_SCRIPT'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
import subprocess
|
||||
|
||||
skills_dir = Path("/home/hermes/.hermes/skills")
|
||||
output = Path("/home/hermes/workspace/agent-estate-wiki/systems/skills-index.md")
|
||||
|
||||
# Use find to get all SKILL.md files, then group by their immediate parent category
|
||||
# This handles nested structures like context-engineering-collection/skills/foo/SKILL.md
|
||||
result = subprocess.run(
|
||||
["find", str(skills_dir), "-name", "SKILL.md", "-type", "f"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
skill_files = [Path(p) for p in result.stdout.strip().split('\n') if p]
|
||||
|
||||
# Group by the top-level category (first directory under skills_dir)
|
||||
categories = {}
|
||||
for skill_md in skill_files:
|
||||
# Get relative path from skills_dir
|
||||
try:
|
||||
rel = skill_md.relative_to(skills_dir)
|
||||
except ValueError:
|
||||
continue # Skip if not under skills_dir (e.g., broken symlinks)
|
||||
|
||||
# Top-level category is the first part of the path
|
||||
category = rel.parts[0]
|
||||
skill_name = skill_md.parent.name # The directory containing SKILL.md
|
||||
|
||||
# Read the description from the skill file
|
||||
try:
|
||||
content = skill_md.read_text(errors='ignore')[:2000]
|
||||
lines = content.split('\n')
|
||||
desc = ""
|
||||
in_frontmatter = False
|
||||
for line in lines:
|
||||
if line.strip() == '---':
|
||||
in_frontmatter = not in_frontmatter
|
||||
continue
|
||||
if not in_frontmatter and line.strip():
|
||||
desc = line.strip()
|
||||
break
|
||||
if len(desc) > 200:
|
||||
desc = desc[:197] + "..."
|
||||
except Exception as e:
|
||||
desc = f"[error reading: {e}]"
|
||||
|
||||
if category not in categories:
|
||||
categories[category] = []
|
||||
# Avoid duplicates (same skill_name in same category)
|
||||
if not any(name == skill_name for name, _ in categories[category]):
|
||||
categories[category].append((skill_name, desc))
|
||||
|
||||
# Sort skills within each category
|
||||
for cat in categories:
|
||||
categories[cat] = sorted(categories[cat], key=lambda x: x[0])
|
||||
|
||||
# Build the markdown
|
||||
today = date.today().isoformat()
|
||||
lines = [
|
||||
"---",
|
||||
"title: Installed Skills",
|
||||
"type: system",
|
||||
"status: active",
|
||||
"created: 2026-07-22",
|
||||
f"updated: {today}",
|
||||
f"verified_on: {today}",
|
||||
"confidence: high",
|
||||
"tags: [skills, hermes]",
|
||||
"sources: []",
|
||||
"---",
|
||||
"",
|
||||
"# Installed Skills",
|
||||
"",
|
||||
f"**Total installed: {sum(len(v) for v in categories.values())} skills** in `~/.hermes/skills/`.",
|
||||
"",
|
||||
"This page is auto-regenerated from the live skill inventory. Do not hand-edit.",
|
||||
"",
|
||||
"## Stock-take notes",
|
||||
"- Category = directory structure under `~/.hermes/skills/` (`category/skill/SKILL.md`).",
|
||||
"- Skills directly under `~/.hermes/skills/` group under `uncategorized`.",
|
||||
"",
|
||||
]
|
||||
|
||||
# Write categories (sorted, excluding 'uncategorized' which goes last)
|
||||
for cat_name in sorted(categories.keys()):
|
||||
skills = categories[cat_name]
|
||||
# Format category name (capitalize first letter of each word)
|
||||
cat_display = cat_name.replace('-', ' ').title()
|
||||
lines.append(f"## {cat_display} ({len(skills)})")
|
||||
lines.append("")
|
||||
for skill_name, desc in skills:
|
||||
# Format skill name
|
||||
skill_display = skill_name.replace('-', ' ')
|
||||
if desc:
|
||||
lines.append(f"- `{skill_name}` — {desc}")
|
||||
else:
|
||||
lines.append(f"- `{skill_name}` — (no description)")
|
||||
lines.append("")
|
||||
|
||||
output.write_text('\n'.join(lines))
|
||||
print(f" Written {output} with {sum(len(v) for v in categories.values())} skills in {len(categories)} categories")
|
||||
PYTHON_SCRIPT
|
||||
|
||||
echo ""
|
||||
|
||||
# =============================================================================
|
||||
# [2/3] Freshness audit — check every page's `updated` frontmatter
|
||||
# =============================================================================
|
||||
REPORT="$STOCKTAKE_DIR/freshness-$TODAY.txt"
|
||||
echo "[2/3] Auditing wiki freshness (threshold: >21 days)..."
|
||||
python3 "$STOCKTAKE_DIR/_freshness_audit.py" "$WIKI_DIR" "$REPORT" 21
|
||||
|
||||
echo ""
|
||||
|
||||
# =============================================================================
|
||||
# [3/3] Rebuild Quartz static site
|
||||
# =============================================================================
|
||||
echo "[3/3] Checking Quartz build..."
|
||||
# Quartz is already built into public/ — if there's a build script, run it
|
||||
# For now, the public/ dir is the Quartz output, regenerated when content changes
|
||||
# If Quartz CLI is available, we'd run: npx quartz build
|
||||
if command -v npx &> /dev/null && [ -f "$WIKI_DIR/quartz.config.ts" ]; then
|
||||
echo " Running Quartz build..."
|
||||
cd "$WIKI_DIR"
|
||||
npx quartz build 2>&1 | head -20 || echo " Quartz build had issues (non-fatal)"
|
||||
else
|
||||
echo " Quartz build skipped (no quartz.config.ts or npx not available)"
|
||||
echo " public/ directory contains the current static site"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[wiki-maintain] Maintenance complete at $(date -Iseconds)"
|
||||
echo " Freshness report: $REPORT"
|
||||
echo ""
|
||||
|
||||
# Print summary
|
||||
head -5 "$REPORT"
|
||||
tail -3 "$REPORT"
|
||||
Reference in New Issue
Block a user