feat(install): serve several sources instead of displacing them
~/.agents/skills was a single link straight at this checkout. That encoded an assumption nobody had stated — that this checkout is the only source of skills on the host. On flamingo it was not: Crush's directory held Anthropic's official set, and the only way to install this library was to move that set aside. A capability loss dressed up as a tidy-up. The canonical location becomes a real directory of per-skill symlinks, fed by the checkout plus anything in SKILLS_EXTRA_SOURCES. Harnesses keep their one directory link, so the count is N + one per harness rather than N x harnesses, which is what #23 was actually about. The three requirements #27 named, each with a test: - collisions are logged with both paths and the checkout wins, never resolved in silence; - removing a skill from a source prunes its link; - anything in the managed directory that is not our symlink is reported and left in place — the installer does not delete content it did not create. doctor learns the same model: sources are recorded at ~/.agents/skills-sources, a link into a configured source is healthy, a link into an unknown one is not, and the old count check becomes a per-skill check because a per-skill layout can lose one skill without changing any total. The pre-#27 wiring is named as legacy rather than reported as damage. count_skills needed find -L; without it symlinked skill directories count as zero. Verified live on koala: migrated in place to 42 per-skill links, then a second source added a 43rd alongside them, then a re-run without that source pruned it back to 42. Only doctor finding throughout was the genuine stale plugin. Closes #27 Bump-Type: minor
This commit is contained in:
+48
-22
@@ -37,11 +37,21 @@ new_fixture() {
|
||||
printf '%s' "$_f"
|
||||
}
|
||||
|
||||
# wire_clean <fixture> — the correct wiring: one canonical link, harnesses
|
||||
# pointing at it.
|
||||
# wire_clean <fixture> [extra-source-dir] — the correct wiring since #27: the
|
||||
# canonical location is a REAL directory of per-skill links, fed by one or more
|
||||
# sources, with each harness keeping its single directory link to it.
|
||||
wire_clean() {
|
||||
mkdir -p "$1/home/.agents" "$1/home/.config/crush" "$1/home/.config/opencode"
|
||||
ln -sfn "$1/ck/skills" "$1/home/.agents/skills"
|
||||
mkdir -p "$1/home/.agents/skills" "$1/home/.config/crush" "$1/home/.config/opencode"
|
||||
for _d in "$1"/ck/skills/*/; do
|
||||
ln -sfn "${_d%/}" "$1/home/.agents/skills/$(basename "$_d")"
|
||||
done
|
||||
printf '%s\n' "$1/ck/skills" > "$1/home/.agents/skills-sources"
|
||||
if [ -n "${2:-}" ]; then
|
||||
for _d in "$2"/*/; do
|
||||
ln -sfn "${_d%/}" "$1/home/.agents/skills/$(basename "$_d")"
|
||||
done
|
||||
printf '%s\n' "$2" >> "$1/home/.agents/skills-sources"
|
||||
fi
|
||||
ln -sfn "$1/home/.agents/skills" "$1/home/.config/crush/skills"
|
||||
ln -sfn "$1/home/.agents/skills" "$1/home/.config/opencode/skills"
|
||||
}
|
||||
@@ -79,27 +89,43 @@ assert_clean "$f" 'clean fixture exits 0'
|
||||
|
||||
# --- 2. dangling link (the 243-link case, gitignored and invisible) ----------
|
||||
f=$(new_fixture dangling); wire_clean "$f"
|
||||
ln -sfn "$f/ck/skills-that-moved" "$f/home/.agents/skills"
|
||||
assert_fails "$f" 'dangling' 'dangling canonical link is reported'
|
||||
ln -sfn "$f/ck/skills/vanished" "$f/home/.agents/skills/alpha"
|
||||
assert_fails "$f" 'dangling' 'dangling per-skill link is reported'
|
||||
|
||||
# --- 3. resolves, but to a DIFFERENT checkout (the silent-stale case) --------
|
||||
# --- 3. resolves, but into a source nobody configured (silent-stale case) ----
|
||||
# Worse than dangling: it works, so nothing complains, while serving content
|
||||
# from a checkout that is months behind.
|
||||
# from a checkout that is months behind. Since #27 a link may legitimately
|
||||
# point outside the checkout — but only into a source the installer recorded.
|
||||
f=$(new_fixture stale)
|
||||
mkdir -p "$f/other/skills/alpha" "$f/other/skills/beta"
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/other/skills/alpha/SKILL.md"
|
||||
printf -- '---\nname: beta\ndescription: b\n---\n' > "$f/other/skills/beta/SKILL.md"
|
||||
mkdir -p "$f/other/alpha"
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/other/alpha/SKILL.md"
|
||||
wire_clean "$f"
|
||||
ln -sfn "$f/other/skills" "$f/home/.agents/skills"
|
||||
assert_fails "$f" 'outside' 'link into a foreign checkout is reported'
|
||||
ln -sfn "$f/other/alpha" "$f/home/.agents/skills/alpha"
|
||||
assert_fails "$f" 'unknown source' 'link into an unconfigured source is reported'
|
||||
|
||||
# --- 4. count mismatch (a harness seeing fewer skills than the checkout) -----
|
||||
f=$(new_fixture count)
|
||||
mkdir -p "$f/home/.config/crush/skills/alpha"
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/home/.config/crush/skills/alpha/SKILL.md"
|
||||
mkdir -p "$f/home/.agents"
|
||||
# --- 3b. a link into a CONFIGURED extra source is healthy -------------------
|
||||
# The whole point of #27: Anthropic's official set alongside this library.
|
||||
f=$(new_fixture multisource)
|
||||
mkdir -p "$f/other/xlsx"
|
||||
printf -- '---\nname: xlsx\ndescription: x\n---\n' > "$f/other/xlsx/SKILL.md"
|
||||
wire_clean "$f" "$f/other"
|
||||
assert_clean "$f" 'a link into a configured extra source is healthy'
|
||||
|
||||
# --- 3c. the pre-#27 single-link wiring is named, not mistaken for damage ----
|
||||
f=$(new_fixture legacy)
|
||||
mkdir -p "$f/home/.agents" "$f/home/.config/crush" "$f/home/.config/opencode"
|
||||
ln -sfn "$f/ck/skills" "$f/home/.agents/skills"
|
||||
assert_fails "$f" 'count' 'skill-count mismatch is reported'
|
||||
ln -sfn "$f/home/.agents/skills" "$f/home/.config/crush/skills"
|
||||
ln -sfn "$f/home/.agents/skills" "$f/home/.config/opencode/skills"
|
||||
assert_fails "$f" 'single-link' 'legacy single-link wiring is reported as such'
|
||||
|
||||
# --- 4. a skill in the checkout with no link anywhere ------------------------
|
||||
# The multi-source replacement for the old count check: with per-skill links a
|
||||
# skill can be missing individually, which a total count would hide if an extra
|
||||
# source happened to supply the same number.
|
||||
f=$(new_fixture missing); wire_clean "$f"
|
||||
rm -f "$f/home/.agents/skills/beta"
|
||||
assert_fails "$f" 'beta' 'a checkout skill with no link is reported'
|
||||
|
||||
# --- 5. stray entry (the loose secret-hygiene.md that loaded nowhere) --------
|
||||
f=$(new_fixture stray); wire_clean "$f"
|
||||
@@ -119,9 +145,9 @@ assert_fails "$f" 'legacy' 'leftover per-skill Claude symlinks are reported'
|
||||
# resolution target, which is true but useless. It must say the dir is not a
|
||||
# link at all, because the fix (move it aside, re-run install.sh) is different
|
||||
# from the fix for a link into a foreign checkout.
|
||||
f=$(new_fixture realdir)
|
||||
mkdir -p "$f/home/.agents" "$f/home/.config/crush/skills/alpha"
|
||||
ln -sfn "$f/ck/skills" "$f/home/.agents/skills"
|
||||
f=$(new_fixture realdir); wire_clean "$f"
|
||||
rm -f "$f/home/.config/crush/skills"
|
||||
mkdir -p "$f/home/.config/crush/skills/alpha"
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/home/.config/crush/skills/alpha/SKILL.md"
|
||||
assert_fails "$f" 'not a link' 'real directory is reported as unmanaged, not as "outside"'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user