maintain: general wiki freshness mechanism + restore tailscale routes page

Replace the skills-only regeneration script with a general wiki-maintain.sh that:
- regenerates skills-index from live skill tree (330 skills / 26 categories)
- audits EVERY page for staleness vs its updated date (>21d flagged)
- rebuilds + redeploys the Quartz site
- writes a dated freshness report to raw/stocktake/

Also fix the tailscale.md page (it had drifted to only 4 routes; reality now has
11 CT460 routes incl /webui, /agent-estate, /synergy-prep, /evening-grid, /wiki
and the /wiki proxy was also missing from the atomic apply script — added there).

Cron wiki-stocktake-maintenance (1a8e90ea2271) now runs this daily 03:00 and
commits+pushes so the wiki cannot silently drift again.
This commit is contained in:
Tony0410
2026-08-16 00:16:48 +08:00
parent b12d8b5e3d
commit 20a3164dca
5 changed files with 257 additions and 148 deletions
+14 -8
View File
@@ -3,8 +3,8 @@ title: Tailscale
type: infrastructure type: infrastructure
status: active status: active
created: 2026-07-26 created: 2026-07-26
updated: 2026-07-27 updated: 2026-08-16
verified_on: 2026-07-27 verified_on: 2026-08-16
confidence: high confidence: high
tags: [tailscale, vpn, networking, infrastructure] tags: [tailscale, vpn, networking, infrastructure]
--- ---
@@ -20,12 +20,18 @@ WireGuard-based mesh VPN connecting all homelab hosts and providing HTTPS access
- **Runtipi node:** CT201 (`runtipi.kangaroo-eel.ts.net`) - **Runtipi node:** CT201 (`runtipi.kangaroo-eel.ts.net`)
## CT460 Serve Routes ## CT460 Serve Routes
| Path | Target | Notes | || Path | Target | Notes |
|---|---|---| ||---|---|---|---|
| `/` | `http://127.0.0.1:9119` | Hermes WebUI | || `/` | `http://127.0.0.1:9119` | Hermes WebUI |
| `/jobs` | `http://127.0.0.1:9099` | Jobs Dashboard | || `/ui` | `http://127.0.0.1:5175/` | Hermes UI (Vite) |
| `/desktop` | `http://127.0.0.1:6081` | CUA noVNC | || `/jobs` | `http://127.0.0.1:9099` | Jobs Dashboard |
| `/vnc-camofox` | `http://127.0.0.1:6080` | Camofox VNC | || `/webui` | `http://127.0.0.1:8787/` | Hermes WebUI (Standalone) |
|| `/desktop` | `http://127.0.0.1:6081` | CUA noVNC |
|| `/vnc-camofox` | `http://127.0.0.1:6080` | Camofox VNC |
|| `/agent-estate` | `http://127.0.0.1:8769/agent-estate-role-dashboard.html` | Role dashboard |
|| `/synergy-prep` | `http://127.0.0.1:9123/` | Synergy prep |
|| `/evening-grid.html` | `http://127.0.0.1:9099/evening-grid.html` | Evening grid |
|| `/wiki` | `http://100.118.5.51:9120` | Agent Estate Wiki (Quartz) |
## CT460 Funnel Routes ## CT460 Funnel Routes
| Port | Target | Notes | | Port | Target | Notes |
+89
View File
@@ -0,0 +1,89 @@
# Wiki freshness audit — 2026-08-16
Threshold: >21 days since `updated`
## STALE (36)
- [ 25d] comparisons/camoufox-vs-chrome.md (updated 2026-07-22)
- [ 25d] comparisons/hermes-vs-nanobot.md (updated 2026-07-22)
- [ 25d] concepts/agent-memory.md (updated 2026-07-22)
- [ 25d] concepts/model-routing.md (updated 2026-07-22)
- [ 25d] concepts/persistent-shells.md (updated 2026-07-22)
- [ 25d] concepts/tool-calling.md (updated 2026-07-22)
- [ 25d] decisions/index.md (updated 2026-07-22)
- [ 25d] decisions/keep-openrouter-as-fallback.md (updated 2026-07-22)
- [ 25d] decisions/persistent-shell-enabled.md (updated 2026-07-22)
- [ 25d] experiments/index.md (updated 2026-07-22)
- [ 25d] infrastructure/backups.md (updated 2026-07-22)
- [ 25d] infrastructure/credentials-map.md (updated 2026-07-22)
- [ 25d] infrastructure/docker-services.md (updated 2026-07-22)
- [ 25d] infrastructure/domains-and-tunnels.md (updated 2026-07-22)
- [ 25d] infrastructure/network-map.md (updated 2026-07-22)
- [ 25d] queries/retained-research-answers.md (updated 2026-07-22)
- [ 25d] runbooks/backup-wiki.md (updated 2026-07-22)
- [ 25d] runbooks/diagnose-docker.md (updated 2026-07-22)
- [ 25d] runbooks/provider-health.md (updated 2026-07-22)
- [ 25d] runbooks/recover-docker-service.md (updated 2026-07-22)
- [ 25d] runbooks/restart-browser.md (updated 2026-07-22)
- [ 25d] runbooks/restart-hermes.md (updated 2026-07-22)
- [ 25d] runbooks/restore-openrouter.md (updated 2026-07-22)
- [ 25d] runbooks/test-openrouter.md (updated 2026-07-22)
- [ 25d] runbooks/troubleshoot-tool-loop.md (updated 2026-07-22)
- [ 25d] runbooks/update-hermes-safely.md (updated 2026-07-22)
- [ 25d] systems/cloudflare-tunnel.md (updated 2026-07-22)
- [ 25d] systems/current-profile.md (updated 2026-07-22)
- [ 25d] systems/headless-server.md (updated 2026-07-22)
- [ 25d] systems/home-assistant.md (updated 2026-07-22)
- [ 25d] systems/messaging-integrations.md (updated 2026-07-22)
- [ 25d] systems/openrouter.md (updated 2026-07-22)
- [ 25d] systems/terminal-backend.md (updated 2026-07-22)
- [ 22d] runbooks/gateway-resource-resilience.md (updated 2026-07-25)
- [ 22d] systems/hermes-agent.md (updated 2026-07-25)
- [ 22d] systems/tool-search.md (updated 2026-07-25)
## FRESH (33)
- [ 1d] agents/hermes-chaos.md
- [ 1d] agents/hermes-production.md
- [ 1d] agents/index.md
- [ 1d] agents/nanobot.md
- [ 1d] agents/open-claw.md
- [ 1d] agents/qwenpaw.md
- [ 1d] agents/zeroclaw.md
- [ 4d] comparisons/memory-backends.md
- [ 4d] comparisons/model-routers.md
- [ 14d] comparisons/steel-vs-camoufox.md
- [ 14d] concepts/browser-automation.md
- [ 2d] current-state.md
- [ 14d] decisions/rollback-steel-to-camofox.md
- [ 15d] incidents/2026-08-01-mnemosyne-capture-failure.md
- [ 2d] infrastructure/hosts.md
- [ 3d] infrastructure/proxmox.md
- [ 0d] infrastructure/tailscale.md
- [ 14d] reports/uber-eats/index.md
- [ 19d] runbooks/job-radar.md
- [ 19d] runbooks/multiplexer-setup.md
- [ 14d] systems/browser-backend.md
- [ 2d] systems/guanaco.md
- [ 17d] systems/hermes-ui.md
- [ 21d] systems/homepage-dashboard.md
- [ 15d] systems/memory-backend.md
- [ 14d] systems/mnemosyne.md
- [ 2d] systems/model-providers.md
- [ 9d] systems/nanobot.md
- [ 21d] systems/obsidian.md
- [ 2d] systems/omniroute.md
- [ 18d] systems/opencode-go.md
- [ 6d] systems/scheduled-tasks.md
- [ 0d] systems/skills-index.md
## NO updated DATE (7)
- SCHEMA.md
- experiments/active/example.md
- incidents/2026-07-29-mnemosyne-relay.md
- incidents/2026-08-04-omniroute-free-deepseek-silent-stops.md
- index.md
- log.md
- reports/uber-eats/2026-07.md
TOTAL: 36 stale, 33 fresh, 7 no-date of 76 pages
-119
View File
@@ -1,119 +0,0 @@
#!/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"
+20 -21
View File
@@ -3,8 +3,8 @@ title: Installed Skills
type: system type: system
status: active status: active
created: 2026-07-22 created: 2026-07-22
updated: 2026-08-15 updated: 2026-08-16
verified_on: 2026-08-15 verified_on: 2026-08-16
confidence: high confidence: high
tags: [skills, hermes] tags: [skills, hermes]
sources: [] sources: []
@@ -17,9 +17,8 @@ sources: []
This page is auto-regenerated from the live skill inventory. Do not hand-edit. This page is auto-regenerated from the live skill inventory. Do not hand-edit.
## Stock-take notes ## Stock-take notes
- Category names come from the directory structure under `~/.hermes/skills/` (`category/skill/SKILL.md`). - Category = directory structure under `~/.hermes/skills/` (`category/skill/SKILL.md`).
- Skills stored directly under `~/.hermes/skills/` (no subfolder) are grouped under `uncategorized`. - Skills directly under `~/.hermes/skills/` group under `uncategorized`.
- Deeper nested paths (e.g. `mlops/evaluation`) are grouped by their top-level category.
## Autonomous Ai Agents (8) ## Autonomous Ai Agents (8)
@@ -397,10 +396,26 @@ This page is auto-regenerated from the live skill inventory. Do not hand-edit.
- `working-with-legacy-code` — Safely change and test untested codebases using Feathers'' "Working Effectively with Legacy Code". Use when the user men - `working-with-legacy-code` — Safely change and test untested codebases using Feathers'' "Working Effectively with Legacy Code". Use when the user men
- `yuanbao` — Yuanbao (元宝) groups: @mention users, query info/members. - `yuanbao` — Yuanbao (元宝) groups: @mention users, query info/members.
## Conduct (1)
- `simple-directives-execution` — Execute now when told 'just do X' — no investigation.
## Cron (1)
- `cron-digest-formatting` — Use when formatting cron Telegram news digests for Anthony.
## Data Science (1)
- `jupyter-live-kernel` — Iterative Python via live Jupyter kernel (hamelnb).
## Finance (1) ## Finance (1)
- `3-statement-model` — Build integrated IS/BS/CF financial workbooks in Excel. - `3-statement-model` — Build integrated IS/BS/CF financial workbooks in Excel.
## Job Hunting (1)
- `seek-extraction-patterns` — Extract salary, closing dates, and job details from SEEK (au.seek.com) listings at scale. Use when enriching a batch of
## Omniroute (1) ## Omniroute (1)
- `omniroute-skills` — Interact with OmniRoute's Skills framework — list, install, enable, execute, and debug skills via REST API and MCP tools - `omniroute-skills` — Interact with OmniRoute's Skills framework — list, install, enable, execute, and debug skills via REST API and MCP tools
@@ -413,22 +428,6 @@ This page is auto-regenerated from the live skill inventory. Do not hand-edit.
- `wiki-web-hosting` — Publish Markdown wikis as Quartz sites over Tailscale. - `wiki-web-hosting` — Publish Markdown wikis as Quartz sites over Tailscale.
## Job Hunting (1)
- `seek-extraction-patterns` — Extract salary, closing dates, and job details from SEEK (au.seek.com) listings at scale. Use when enriching a batch of
## Conduct (1)
- `simple-directives-execution` — Execute now when told 'just do X' — no investigation.
## Cron (1)
- `cron-digest-formatting` — Use when formatting cron Telegram news digests for Anthony.
## Data Science (1)
- `jupyter-live-kernel` — Iterative Python via live Jupyter kernel (hamelnb).
## Related ## Related
- [[systems/hermes-agent]] - [[systems/hermes-agent]]
- [[systems/omniroute]] - [[systems/omniroute]]
+134
View File
@@ -0,0 +1,134 @@
#!/bin/bash
# wiki-maintain.sh — General Agent Estate Wiki freshness maintenance.
# Regenerates machine-derivable pages from live state, audits every page for
# staleness, rebuilds the Quartz site, and deploys. Run on a schedule so the
# wiki never silently drifts from reality.
#
# Usage: ./wiki-maintain.sh [--no-build]
# --no-build regenerate content + audit but skip quartz build/deploy
set -euo pipefail
WIKI="/home/hermes/workspace/agent-estate-wiki"
QUARTZ="/home/hermes/workspace/quartz-temp"
SKILLS="/home/hermes/.hermes/skills"
STALE_DAYS=21
NO_BUILD=false
[[ "${1:-}" == "--no-build" ]] && NO_BUILD=true
mkdir -p "$WIKI/raw/stocktake"
REPORT="$WIKI/raw/stocktake/freshness-$(date +%Y%m%d).txt"
echo "[$(date -Is)] Agent Estate Wiki maintenance start"
echo " wiki=${WIKI}"
echo " stale-threshold=${STALE_DAYS}d"
# ── 1. Regenerate skills-index from 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(sys.argv[2])
files = sorted(skills_root.rglob('SKILL.md'))
skills = defaultdict(list)
for f in 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:
m = re.search(r'^description:\s*(.+)$', f.read_text(errors='ignore')[: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 = ['---','title: Installed Skills','type: system','status: active',
'created: 2026-07-22', f'updated: {date.today().isoformat()}',
f'verified_on: {date.today().isoformat()}','confidence: high',
'tags: [skills, hermes]','sources: []','---','','# Installed Skills','',
f'**Total installed: {total} 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`.','']
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 order(c):
try: return priority.index(c)
except ValueError: return 100+len(priority)
for cat, items in sorted(skills.items(), key=lambda x:(order(x[0]),-len(x[1]))):
L.append(f'## {cat.replace("-"," ").title()} ({len(items)})'); L.append('')
for name, desc in sorted(items):
L.append(f'- `{name}` — {desc}' if desc else f'- `{name}`')
L.append('')
L += ['## Related','- [[systems/hermes-agent]]','- [[systems/omniroute]]','']
out.write_text('\n'.join(L))
print(f"[1/3] skills: {total} across {len(skills)} categories -> {out}")
PY
# ── 2. Audit every page for staleness ────────────────────────────────────────
python3 - "$WIKI" "$REPORT" "$STALE_DAYS" <<'PY'
import sys, re
from pathlib import Path
from datetime import date
wiki = Path(sys.argv[1]); report = Path(sys.argv[2]); threshold = int(sys.argv[3])
today = date.today()
stale, fresh, nodate = [], [], []
for f in sorted(wiki.rglob('*.md')):
rel = f.relative_to(wiki)
if str(rel).startswith('raw/'): continue
content = f.read_text(errors='ignore')
upd = re.search(r'^updated:\s*(\S+)', content[:800], re.M)
if not upd:
nodate.append(str(rel)); continue
try: d = date.fromisoformat(upd.group(1))
except ValueError: nodate.append(str(rel)); continue
age = (today - d).days
(stale if age > threshold else fresh).append((str(rel), age, upd.group(1)))
stale.sort(key=lambda x:-x[1])
lines = [f"# Wiki freshness audit — {today.isoformat()}",
f"Threshold: >{threshold} days since `updated`",
f"", f"## STALE ({len(stale)})", ""]
for rel, age, d in stale:
lines.append(f"- [{age:>3}d] {rel} (updated {d})")
lines += [f"", f"## FRESH ({len(fresh)})", ""]
for rel, age, d in fresh:
lines.append(f"- [{age:>3}d] {rel}")
lines += [f"", f"## NO updated DATE ({len(nodate)})", ""]
for rel in nodate:
lines.append(f"- {rel}")
lines += ["", f"TOTAL: {len(stale)} stale, {len(fresh)} fresh, {len(nodate)} no-date of {len(stale)+len(fresh)+len(nodate)} pages"]
report.write_text('\n'.join(lines))
print(f"[2/3] audit: {len(stale)} stale (>={threshold}d), {len(fresh)} fresh, {len(nodate)} no-date -> {report}")
PY
# ── 3. Sync content, build, deploy ─────────────────────────────────────────
if $NO_BUILD; then
echo "[3/3] --no-build: content + audit done, skipping quartz build"
exit 0
fi
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','regenerate-skills-index.sh',
'wiki-maintain.sh','raw'}
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
cd "$QUARTZ"
npx quartz build >/tmp/quartz-build.log 2>&1 || { echo "Quartz build FAILED"; tail -20 /tmp/quartz-build.log; exit 1; }
rm -rf "$WIKI/public"
mkdir -p "$WIKI/public"
cp -a "$QUARTZ/public/." "$WIKI/public/"
echo "[3/3] build+deploy: $(find "$WIKI/public" -name '*.html' | wc -l) html files"
echo "[$(date -Is)] Wiki maintenance complete"
echo ""
echo "=== Freshness summary ==="
grep -E '^TOTAL:' "$REPORT" || true