Files
command-center/scripts/kb-lint.sh
T
Frank 651924a4bc docs(skills): collapse three overlapping skills trees into one verified tree
Three parallel trees documented the same ecosystem: omp-docs/skills/
(two overview pages), tools/npx-skills/ (10 pi-era npm pages), and
tools/vercel-labs-skills/ (11 CLI pages), plus a fourth leaderboard page
at engineering/skills-ecosystem.md. Verbatim overlap (identical SKILL.md
field tables in skills-spec.md vs skill-format.md; 13-agent vs 68-agent
copies of the same upstream table). Consolidated to four concept pages:

- skills/agent-skills.md: standard, format, loading, authoring (merges
  skills-spec, skill-format, skill-file-structure, skill-loading,
  best-practices, skills-vs-extensions)
- skills/skills-cli.md: CLI reference (merges commands, source-formats,
  installation-scope, skill-discovery, lock-file, env-variables,
  agent-support, related-links)
- skills/store-wiring.md: moved, fleet ops, links retargeted
- skills/ecosystem.md: replaces skills-ecosystem.md; stale 2026-08-12
  top-50 table dropped, replaced with a live top-10 re-queried from the
  skills.sh API tonight and the API contract re-verified (zero-based,
  200/page, {skills,total,hasMore,page}, total 9909)

All facts re-verified 2026-10-19 against primary sources:
agentskills.io, vercel-labs/skills README + src/cli.ts (command set,
--full-depth) + src/skill-lock.ts (v3 lock, XDG path, wipe-on-old),
live skills.sh API. Deleted superseded core/index-doc.md (old pi-era
docs index). Retargeted all inbound links (tools/index, engineering x3,
packages x3, pi/skills, core/index). Pi-era npm skill packaging is
listed as archived under /pi/.

Refs #29
2026-10-19 02:58:52 -07:00

63 lines
2.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# kb-lint.sh — OKF v0.1 conformance + link-integrity lint for a knowledge bundle.
# Usage: scripts/kb-lint.sh <kb-dir> (default: omp-docs)
# Exits 0 when the KB is conformant; prints one line per violation otherwise.
set -uo pipefail
KB="$(cd "${1:-omp-docs}" 2>/dev/null && pwd)" || { echo "kb-lint: no such dir: ${1:-omp-docs}" >&2; exit 2; }
python3 - "$KB" <<'PY'
import re, sys
import yaml
from pathlib import Path
kb = Path(sys.argv[1])
violations = []
for p in sorted(kb.rglob("*.md")):
rel = p.relative_to(kb)
text = p.read_text(errors="replace")
lines = text.splitlines()
if p.name == "index.md":
# OKF rule: index files are directory listings and carry NO frontmatter.
if text.startswith("---"):
violations.append(f"{rel}: index.md must not have frontmatter (OKF spec)")
continue
if not text.startswith("---"):
violations.append(f"{rel}: missing frontmatter")
continue
# Close the block at the first line that is exactly --- or ...; anything
# else (including fenced `---` deep in the body) is body, not a delimiter.
end = next((i for i in range(1, len(lines)) if lines[i].strip() in ("---", "...")), None)
if end is None:
violations.append(f"{rel}: frontmatter block is not closed (no '---' delimiter line)")
continue
try:
fm = yaml.safe_load("\n".join(lines[1:end])) or {}
except yaml.YAMLError:
violations.append(f"{rel}: frontmatter block is malformed YAML or not closed")
continue
if not isinstance(fm, dict) or not str(fm.get("type", "")).strip():
violations.append(f"{rel}: frontmatter lacks required 'type:'")
# link integrity: fenced code blocks hold example paths, not live links — strip first.
# Vendored spec is exempt: its example paths are illustrations, not live links.
for p in sorted(kb.rglob("*.md")):
if str(p.relative_to(kb)) == "references/okf-spec.md":
continue
text = re.sub(r"^```[^\n]*$^```[^\n]*$", "", p.read_text(errors="replace"), flags=re.M)
rel = p.relative_to(kb)
for m in re.finditer(r"\]\((?!https?:|#)([^)#\s]+\.md)", text):
target = (p.parent / m.group(1)).resolve() if not m.group(1).startswith("/") else (kb / m.group(1).lstrip("/"))
if not Path(target).is_file():
violations.append(f"{rel}: broken relative link -> {m.group(1)}")
for m in re.finditer(r"\[\[([^\]|#]+)", text):
# wikilinks resolve by filename anywhere in the KB (Obsidian semantics)
name = m.group(1).strip().rsplit("/", 1)[-1]
if not list(kb.rglob(name + ".md")):
violations.append(f"{rel}: broken wikilink -> [[{m.group(1).strip()}]]")
for v in violations:
print(v)
print(f"kb-lint: {len(violations)} violation(s)")
sys.exit(1 if violations else 0)
PY