#!/usr/bin/env bats # update-skills.py keeps forked skills/commands in sync with their upstreams # via per-fork manifests (upstreams//manifest.json), a committed baseline # snapshot (upstreams//baseline/), and 3-way merges against it. The # script is read-only against fork targets: check classifies, merge-file # merges to stdout; only bootstrap and mark-synced write (manifest + baseline). setup() { REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" SCRIPT="$REPO_ROOT/scripts/update-skills.py" TMP="$(mktemp -d)" CACHE="$TMP/cache" } teardown() { rm -rf "$TMP" } # --- fixture helpers ------------------------------------------------------- git_up() { git -C "$TMP/up" -c user.name=test -c user.email=test@test "$@" } make_upstream() { mkdir -p "$TMP/up/skills/demo" git -C "$TMP/up" init -q -b main printf 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n' \ > "$TMP/up/skills/demo/SKILL.md" printf 'def helper():\n return 1\n' > "$TMP/up/skills/demo/helper.py" git_up add -A git_up commit -qm "initial" } make_repo() { mkdir -p "$TMP/root/upstreams/demo" "$TMP/root/demo-skill" cp "$TMP/up/skills/demo/SKILL.md" "$TMP/up/skills/demo/helper.py" \ "$TMP/root/demo-skill/" cat > "$TMP/root/upstreams/demo/manifest.json" < "$TMP/up/skills/demo/new-up.md" git_up rm -q skills/demo/helper.py git_up add -A git_up commit -qm "add one, delete one" echo "new local file" > "$TMP/root/demo-skill/new-local.md" run_us check demo [ "$status" -eq 0 ] [[ "$output" == *"upstream-new"*"new-up.md"* ]] [[ "$output" == *"local-new"*"new-local.md"* ]] [[ "$output" == *"upstream-deleted"*"helper.py"* ]] } @test "check --json emits valid JSON with upstream commit and files" { make_upstream make_repo bootstrap_demo run_us check demo --json [ "$status" -eq 0 ] echo "$output" | python3 -c ' import json, sys d = json.load(sys.stdin) assert d["name"] == "demo" assert len(d["upstream_commit"]) == 40 assert any(f["path"] == "SKILL.md" for f in d["files"]) ' } @test "check without baseline degrades to no-baseline statuses" { make_upstream make_repo sed -i 's/line8/line8 local edit/' "$TMP/root/demo-skill/SKILL.md" run_us check demo [ "$status" -eq 0 ] [[ "$output" == *"no-baseline"* ]] } # --- merge-file ------------------------------------------------------------ @test "merge-file merges non-overlapping edits cleanly" { make_upstream make_repo bootstrap_demo sed -i 's/line1/line1 upstream edit/' "$TMP/up/skills/demo/SKILL.md" git_up commit -qam "upstream edit" sed -i 's/line8/line8 local edit/' "$TMP/root/demo-skill/SKILL.md" python3 "$SCRIPT" --root "$TMP/root" --cache "$CACHE" check demo > /dev/null run_us merge-file demo SKILL.md [ "$status" -eq 0 ] [[ "$output" == *"line1 upstream edit"* ]] [[ "$output" == *"line8 local edit"* ]] [[ "$output" != *"<<<<<<<"* ]] } @test "merge-file emits conflict markers on overlapping edits" { make_upstream make_repo bootstrap_demo sed -i 's/line4/line4 upstream edit/' "$TMP/up/skills/demo/SKILL.md" git_up commit -qam "upstream edit" sed -i 's/line4/line4 local edit/' "$TMP/root/demo-skill/SKILL.md" python3 "$SCRIPT" --root "$TMP/root" --cache "$CACHE" check demo > /dev/null run_us merge-file demo SKILL.md [ "$status" -eq 1 ] [[ "$output" == *"<<<<<<<"* ]] [[ "$output" == *"line4 upstream edit"* ]] [[ "$output" == *"line4 local edit"* ]] } @test "merge-file reports a hard git error instead of masking it as a conflict" { make_upstream make_repo bootstrap_demo printf 'up\x00stream' > "$TMP/up/skills/demo/SKILL.md" git_up commit -qam "binary upstream" printf 'lo\x00cal' > "$TMP/root/demo-skill/SKILL.md" python3 "$SCRIPT" --root "$TMP/root" --cache "$CACHE" check demo > /dev/null run_us merge-file demo SKILL.md [ "$status" -eq 2 ] [[ "$output" == *"merge-file failed"* ]] } # --- mark-synced ----------------------------------------------------------- @test "mark-synced refreshes baseline and last_synced_commit" { make_upstream make_repo bootstrap_demo sed -i 's/line1/line1 upstream edit/' "$TMP/up/skills/demo/SKILL.md" git_up commit -qam "upstream edit" sha=$(git_up rev-parse HEAD) python3 "$SCRIPT" --root "$TMP/root" --cache "$CACHE" check demo > /dev/null run_us mark-synced demo [ "$status" -eq 0 ] grep -q "line1 upstream edit" "$TMP/root/upstreams/demo/baseline/SKILL.md" grep -q "$sha" "$TMP/root/upstreams/demo/manifest.json" } # --- files map (the arch-decide shape) -------------------------------------- @test "files map restricts tracking to mapped files under target paths" { make_upstream make_repo mkdir -p "$TMP/root/commands" cp "$TMP/up/skills/demo/SKILL.md" "$TMP/root/commands/demo.md" cat > "$TMP/root/upstreams/demo/manifest.json" < "$TMP/root/demo-skill/node_modules/x/x.js" echo "x" > "$TMP/root/demo-skill/__pycache__/y.pyc" run_us check demo [ "$status" -eq 0 ] [[ "$output" != *"node_modules"* ]] [[ "$output" != *"__pycache__"* ]] } # --- errors ------------------------------------------------------------------ @test "unknown fork errors and names it" { make_upstream make_repo run_us check nosuchfork [ "$status" -ne 0 ] [[ "$output" == *"nosuchfork"* ]] } @test "unreachable upstream degrades with a clear error" { make_upstream make_repo python3 - "$TMP/root/upstreams/demo/manifest.json" <<'EOF' import json, sys p = sys.argv[1] d = json.load(open(p)) d["url"] = "file:///nonexistent/upstream/repo" json.dump(d, open(p, "w"), indent=2) EOF run_us check demo [ "$status" -ne 0 ] [[ "$output" == *"demo"* ]] [[ "$output" == *"clone"* ]] }