Files
skill-evolution/tests/test_skill_index.py
Krystie 52b77bfcad [grade=A] add SKILL_EVOLUTION_SKILLS_DIR env var for per-Hermes-profile skills trees
Forked from Carlo1911/skill-evolution main. Adds env-var override so a single
installed pipeline can target per-profile skills trees (default ~/.hermes/skills,
krystie ~/.hermes/profiles/krystie/skills, sami-story ~/.hermes/profiles/sami-story/skills)
without monkeypatching HOME or the SKILLS_DIR module constant.

- scripts/skill_index.py: add resolve_skills_dir() + SKILLS_DIR_ENV_VAR, wire into
  scan_skills() with explicit-arg > env-var > default resolution order. Read at
  call time (matches fetch_sessions.get_state_db_path() convention).
- tests/test_skill_index.py: 2 new tests covering nonblank env var and blank
  fallback (the latter monkeypatches SKILLS_DIR + invokes scan_skills() with no
  args so the test actually exercises the fallback expression, not just the
  explicit arg path).
- 49/49 conformance tests pass.
- Codex grade: A (verified 2026-08-05).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-08-05 15:09:59 -07:00

97 lines
3.9 KiB
Python

"""Tests for scripts/skill_index.py's scan of the installed-skills tree."""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
import skill_index
def _write_skill(root, category, name, description="does a thing"):
skill_dir = root / category / name
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "SKILL.md").write_text(
f"---\nname: {name}\ndescription: {description}\n---\n\n# {name}\n\nBody.\n"
)
def test_scan_skills_indexes_live_categories(tmp_path):
_write_skill(tmp_path, "general-skills", "money-admin-messaging")
_write_skill(tmp_path, "devops", "deploy-helper")
found = skill_index.scan_skills(str(tmp_path))
assert {s["name"] for s in found} == {"money-admin-messaging", "deploy-helper"}
assert {s["category"] for s in found} == {"general-skills", "devops"}
def test_scan_skills_skips_dot_directories(tmp_path):
"""Dot-prefixed dirs under skills/ are internal, not live skill categories.
`~/.hermes/skills/` really holds `.archive/` (retired skills),
`.curator_backups/` (timestamped snapshots) and `.hub/` (lockfiles, audit log,
quarantine). Indexing them tells the analyzer a retired skill "currently exists",
and -- because the same name can live in both places -- makes
optimize_skill.py's duplicate-name check reject the live skill as ambiguous.
"""
_write_skill(tmp_path, "general-skills", "money-admin-messaging")
_write_skill(tmp_path, ".archive", "money-admin-messaging")
_write_skill(tmp_path, ".curator_backups", "old-thing")
found = skill_index.scan_skills(str(tmp_path))
assert [s["category"] for s in found] == ["general-skills"]
assert len(found) == 1, "a retired copy must not shadow or duplicate the live skill"
def test_scan_skills_dot_filter_keeps_names_unambiguous(tmp_path):
"""The live skill must resolve to exactly one record even with an archived twin."""
_write_skill(tmp_path, "general-skills", "money-admin-messaging")
_write_skill(tmp_path, ".archive", "money-admin-messaging")
matches = [s for s in skill_index.scan_skills(str(tmp_path))
if s["name"] == "money-admin-messaging"]
assert len(matches) == 1
assert matches[0]["category"] == "general-skills"
def test_scan_skills_returns_empty_for_missing_dir(tmp_path):
assert skill_index.scan_skills(str(tmp_path / "nope")) == []
def test_scan_skills_honors_skills_dir_env_var(tmp_path, monkeypatch):
"""SKILL_EVOLUTION_SKILLS_DIR redirects the scan at call time.
Without this, a single installed pipeline can only target one skills tree
(the hardcoded ~/.hermes/skills default). Per-Hermes-profile layouts
(~/.hermes/profiles/<name>/skills/) need to override it per run without
monkeypatching HOME or the module-level SKILLS_DIR constant.
"""
live = tmp_path / "live"
other = tmp_path / "other"
_write_skill(live, "general-skills", "live-skill")
_write_skill(other, "general-skills", "other-skill")
monkeypatch.setenv(skill_index.SKILLS_DIR_ENV_VAR, str(other))
found = skill_index.scan_skills()
assert {s["name"] for s in found} == {"other-skill"}
def test_scan_skills_env_var_blank_falls_back_to_default(tmp_path, monkeypatch):
"""A blank/whitespace env var disables the override (matches fetch_sessions'
get_state_db_path()/get_state_file() convention -- the same read-at-call-time
fallback behaviour for missing/blank env vars)."""
_write_skill(tmp_path, "general-skills", "fallback-skill")
# Monkeypatch the default to a path that is ONLY reachable via the env-var
# fallback -- if the blank env var were ever silently winning the override,
# the test would still pass; we want to confirm the default wins.
monkeypatch.setattr(skill_index, "SKILLS_DIR", str(tmp_path))
monkeypatch.setenv(skill_index.SKILLS_DIR_ENV_VAR, " ")
found = skill_index.scan_skills()
assert {s["name"] for s in found} == {"fallback-skill"}