#!/bin/sh
# devbox-skill-reconcile — hand skillset-OWNED skills back to the live clone.
#
# WHY THIS EXISTS
# ---------------
# entrypoint-user.sh links the image-baked skills into ~/.agents/skills/ EARLY
# (before pi-deploy), because the smoke readiness probe gates on markers that
# only land later, and a link created after that gate produced a flaky
# assertion. Those links are created with a `[ ! -e ]` guard — "only when
# absent" — and the skillset deploy runs LAST, treating already-present links
# as foreign and leaving them alone. Net effect through v1.8.4: the baked copy
# always won, so an edit pushed to a skillset-owned skill was invisible in
# every container until the next image build (measured on two hosts: live
# skillset md5 129bcc4752 vs baked 5236024fef, the new section absent).
#
# The fix is NOT "the skillset always wins". Ownership is per-skill (see
# rootfs/usr/local/share/pi-devbox/skills/VENDORED.md):
#
#   pi-devbox-environment  authored in pi-devbox   → baked IS canonical
#   pi-extensions          owned by the package repo, copied over the snapshot
#                          at build time; skillset carries a DOWNSTREAM copy
#                          that can lag → baked must keep winning
#   mempalace              owned by the skillset repo; baked is a snapshot
#                          fallback for containers with no skillset mounted
#                          → the live clone must win when it is present
#
# So only skills listed in skills/skillset-owned.txt are handed over. Baked
# links stay as the fallback (the early-link race fix is untouched), and a user
# override always beats both: a real directory is never replaced, and neither is
# a symlink that already points somewhere other than the baked tree.
#
# Usage: devbox-skill-reconcile <skillset-root> [skills-dir] [baked-src]
#   skillset-root  the mounted skillset repo (contains skills/<name>/)
#   skills-dir     default $HOME/.agents/skills
#   baked-src      default /usr/local/share/pi-devbox/skills
#
# Idempotent, and silent unless it changes something. Exits 0 when there is
# nothing to do (no skillset, no list) so the entrypoint never fails on it.
set -eu

SKILLSET_ROOT="${1:-}"
SKILLS_DIR="${2:-$HOME/.agents/skills}"
BAKED_SRC="${3:-/usr/local/share/pi-devbox/skills}"
BAKED_SRC="${BAKED_SRC%/}"          # a trailing slash would make the prefix
                                   # match below ("$BAKED_SRC"/*) match nothing

[ -n "$SKILLSET_ROOT" ] || exit 0
[ -d "$SKILLSET_ROOT/skills" ] || exit 0
[ -d "$SKILLS_DIR" ] || exit 0

# Absolutise BOTH roots before they are used, because each has its own way of
# failing silently when relative: a relative symlink TARGET is resolved against
# the link's directory (~/.agents/skills), not $PWD, so it would dangle on
# creation; and a relative BAKED_SRC would never prefix-match the absolute
# target that `readlink` reports, so every skill would be skipped and the fix
# would look like it had simply done nothing.
SKILLSET_ROOT=$(CDPATH= cd -- "$SKILLSET_ROOT" 2>/dev/null && pwd) || exit 0
BAKED_SRC=$(CDPATH= cd -- "$BAKED_SRC" 2>/dev/null && pwd) || exit 0
OWNED_LIST="$BAKED_SRC/skillset-owned.txt"
[ -f "$OWNED_LIST" ] || exit 0

while IFS= read -r _line || [ -n "$_line" ]; do
    # strip comments and surrounding whitespace; skip blanks
    _name=$(printf '%s\n' "$_line" | sed -e 's/#.*$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
    [ -n "$_name" ] || continue
    # defensive: a list entry must be a plain skill name, never a path
    case "$_name" in */*|.*) continue ;; esac

    _live="$SKILLSET_ROOT/skills/$_name"
    _link="$SKILLS_DIR/$_name"

    # the skillset does not ship it → the baked fallback is all there is
    [ -d "$_live" ] || continue
    # a real directory is a user override → never touch
    [ -L "$_link" ] || continue

    # only ever replace OUR OWN link. readlink is deliberate: `readlink -f`
    # would resolve a link that already points into the skillset clone and,
    # since both trees hold a same-named skill, could not tell them apart.
    _target=$(readlink "$_link" 2>/dev/null || true)
    case "$_target" in
        "$BAKED_SRC"/*|"$BAKED_SRC") ;;   # baked link → ours to replace
        *) continue ;;                     # user/foreign target → leave alone
    esac

    # -n so an existing symlink-to-directory is replaced rather than followed
    # (without it, ln would create $_link/$_name inside the baked tree).
    if ln -sfn "$_live" "$_link" 2>/dev/null; then
        printf 'skill %s: baked snapshot -> live skillset (%s)\n' "$_name" "$_live"
    fi
done < "$OWNED_LIST"
