#!/usr/bin/env bash
# verify-skills.sh  -  v6.2.H
#
# Verifies the integrity of SKILL.md files under a skills root against the
# .skill-manifest.json that was shipped alongside them.
#
# Usage: verify-skills.sh [--root <dir>] [--quiet]
#
# Default root: ./pipeline/skills (in a source checkout) OR $HOME/.claude/skills
# if that exists and the source one doesn't  -  lets the install script call
# `verify-skills.sh` post-copy without knowing which side it's on.
#
# Behavior:
#   - Manifest missing → exit 0, print "fail-open: no manifest (first install)"
#     Rationale: we never block first-time installers who don't have a manifest
#     yet. The manifest only catches DRIFT between install runs.
#   - Manifest present, all hashes match → exit 0, print summary count.
#   - Any hash mismatch or missing file → exit 2, print diff lines.
#   - Any extra SKILL.md on disk not in manifest → exit 3 (lower severity),
#     print warnings. User likely added a local skill; not tampering.
#
# Exit codes:
#   0  -  verified (or fail-open first-install)
#   1  -  setup error (missing tooling)
#   2  -  drift detected (hash mismatch / missing file)
#   3  -  extras detected (files on disk not in manifest)

set -euo pipefail

ROOT_OVERRIDE=""
QUIET=0
while [ $# -gt 0 ]; do
  case "$1" in
    --root)  ROOT_OVERRIDE="$2"; shift 2 ;;
    --quiet) QUIET=1; shift ;;
    *)       shift ;;
  esac
done

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

if [ -n "$ROOT_OVERRIDE" ]; then
  SKILLS_ROOT="$ROOT_OVERRIDE"
elif [ -d "$REPO_ROOT/pipeline/skills" ]; then
  SKILLS_ROOT="$REPO_ROOT/pipeline/skills"
elif [ -d "$HOME/.claude/skills" ]; then
  SKILLS_ROOT="$HOME/.claude/skills"
else
  echo "verify-skills: no skills root found" >&2
  exit 1
fi

MANIFEST="$SKILLS_ROOT/.skill-manifest.json"
log() { [ "$QUIET" = "1" ] || printf '%s\n' "$*"; }

if [ ! -f "$MANIFEST" ]; then
  log "verify-skills: fail-open: no manifest at $MANIFEST (first install)"
  exit 0
fi

command -v jq >/dev/null 2>&1 || { echo "verify-skills: jq required" >&2; exit 1; }

hasher=""
if command -v sha256sum >/dev/null 2>&1; then
  hasher=sha256sum
elif command -v shasum >/dev/null 2>&1; then
  hasher="shasum -a 256"
else
  echo "verify-skills: need sha256sum or shasum" >&2
  exit 1
fi

drift=()
missing=()
extra=()

# Build a quick lookup from manifest
while IFS=$'\t' read -r rel expected; do
  full="$SKILLS_ROOT/$rel"
  if [ ! -f "$full" ]; then
    missing+=("$rel")
    continue
  fi
  actual=$($hasher "$full" 2>/dev/null | awk '{print $1}')
  if [ "$actual" != "$expected" ]; then
    drift+=("$rel (expected $expected, got $actual)")
  fi
done < <(jq -r '.entries[] | [.path, .sha256] | @tsv' "$MANIFEST")

# Detect extras (files on disk not in manifest)  -  warn only
# shellcheck disable=SC2046
known_paths=$(jq -r '.entries[].path' "$MANIFEST" | sort -u)
while IFS= read -r -d '' f; do
  rel="${f#"$SKILLS_ROOT"/}"
  if ! grep -qx "$rel" <<< "$known_paths"; then
    extra+=("$rel")
  fi
done < <(find "$SKILLS_ROOT" -type f -name 'SKILL.md' -print0 2>/dev/null)

drift_count=${#drift[@]}
missing_count=${#missing[@]}
extra_count=${#extra[@]}

log "verify-skills: manifest OK for $SKILLS_ROOT"
log "  drift:   $drift_count"
log "  missing: $missing_count"
log "  extras:  $extra_count"

if [ "$drift_count" -gt 0 ] || [ "$missing_count" -gt 0 ]; then
  if [ "$QUIET" != "1" ]; then
    printf '\n'
    for d in "${drift[@]:-}";   do [ -n "$d" ] && printf '  DRIFT   %s\n' "$d"; done
    for m in "${missing[@]:-}"; do [ -n "$m" ] && printf '  MISSING %s\n' "$m"; done
  fi
  exit 2
fi

if [ "$extra_count" -gt 0 ]; then
  if [ "$QUIET" != "1" ]; then
    printf '\n'
    for e in "${extra[@]:-}"; do [ -n "$e" ] && printf '  EXTRA   %s\n' "$e"; done
  fi
  exit 3
fi

exit 0
