docs+skills: felhom-{build-deploy,ui-design,testing} skills + install_skills.py (junction); CLAUDE.md refresh (version-free); consolidated REPORT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Felhom scripts — Changelog
|
||||
|
||||
## install_skills.py — new: Claude Code skills installer (2026-07-03)
|
||||
|
||||
Installs `skills/*/SKILL.md` (felhom-build-deploy, felhom-ui-design, felhom-testing) into
|
||||
`~/.claude/skills/` as Windows junctions (`mklink /J`) so repo edits are live immediately; falls
|
||||
back to a full copy if junction creation fails or isn't followed (copy mode prints a re-run
|
||||
reminder). Idempotent — re-runs detect a correct junction and leave it. Verified: junctions ARE
|
||||
followed by Claude Code skill discovery (fresh-session probe found all three).
|
||||
|
||||
## reuse_refs_check.py — new gate: REUSE.md citation checker (2026-07-03)
|
||||
|
||||
Staleness defense for the new per-repo `REUSE.md` reuse maps. Takes repo roots as argv, extracts
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Install the Felhom Claude Code skills from <repo>/skills/ into ~/.claude/skills/.
|
||||
|
||||
Usage: python scripts/install_skills.py (run from the felhom.eu repo root or anywhere)
|
||||
|
||||
Personal skills (~/.claude/skills/) apply across all projects — the right scope for the 4-repo
|
||||
E:\\git workspace. Preferred install is a Windows junction (mklink /J) so edits in the repo are live
|
||||
immediately; if junction creation fails or isn't followed, falls back to a full COPY — in copy mode
|
||||
you must RE-RUN this script after editing skills/. Idempotent: safe to re-run any time.
|
||||
"""
|
||||
import os, shutil, subprocess, sys
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SRC = os.path.join(REPO, "skills")
|
||||
DST = os.path.join(os.path.expanduser("~"), ".claude", "skills")
|
||||
|
||||
fails = 0
|
||||
copy_mode_used = False
|
||||
|
||||
|
||||
def is_working_junction(target, src):
|
||||
"""True if target resolves into src (junction/symlink already correct)."""
|
||||
try:
|
||||
return os.path.isfile(os.path.join(target, "SKILL.md")) and \
|
||||
os.path.samefile(os.path.join(target, "SKILL.md"), os.path.join(src, "SKILL.md"))
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def remove_existing(target):
|
||||
# a junction must be removed with rmdir semantics (never recurse INTO it), a copy with rmtree
|
||||
try:
|
||||
os.rmdir(target) # works for junctions and empty dirs
|
||||
except OSError:
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
|
||||
|
||||
def install(name):
|
||||
global fails, copy_mode_used
|
||||
src = os.path.join(SRC, name)
|
||||
target = os.path.join(DST, name)
|
||||
if os.path.exists(target):
|
||||
if is_working_junction(target, src):
|
||||
print("OK %-22s junction (already installed, live-linked to repo)" % name)
|
||||
return
|
||||
remove_existing(target)
|
||||
# try junction first
|
||||
r = subprocess.run(["cmd", "/c", "mklink", "/J", target, src],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode == 0 and is_working_junction(target, src):
|
||||
print("OK %-22s junction -> %s" % (name, src))
|
||||
return
|
||||
# fall back to copy
|
||||
if os.path.exists(target):
|
||||
remove_existing(target)
|
||||
try:
|
||||
shutil.copytree(src, target)
|
||||
copy_mode_used = True
|
||||
print("OK %-22s COPY (junction failed: %s)" % (name, (r.stderr or r.stdout).strip() or "not followed"))
|
||||
except OSError as e:
|
||||
fails += 1
|
||||
print("FAIL %-22s %s" % (name, e))
|
||||
|
||||
|
||||
if not os.path.isdir(SRC):
|
||||
print("FAIL: no skills/ dir at %s" % SRC)
|
||||
sys.exit(2)
|
||||
os.makedirs(DST, exist_ok=True)
|
||||
names = sorted(d for d in os.listdir(SRC) if os.path.isfile(os.path.join(SRC, d, "SKILL.md")))
|
||||
if not names:
|
||||
print("FAIL: no skills found under %s" % SRC)
|
||||
sys.exit(2)
|
||||
for n in names:
|
||||
install(n)
|
||||
if copy_mode_used:
|
||||
print("\nNOTE: copy mode active for at least one skill — RE-RUN this script after editing skills/.")
|
||||
sys.exit(1 if fails else 0)
|
||||
Reference in New Issue
Block a user