168 lines
5.9 KiB
Bash
Executable File
168 lines
5.9 KiB
Bash
Executable File
#!/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"
|