9fb19eae63
Every job sync.sh did has a better replacement: - Per-harness skill dirs (~/.claude/skills, ~/.codex/skills, ~/.agents/skills): `npx skills add . -g -y` writes to every detected harness's home dir and uses symlinks by default. Edits propagate live — no re-deploy step. - Hermes (~/.hermes/skills/research/last30days): `hermes skills install mvanhorn/last30days-skill --force` pulls from GitHub and handles the deploy itself. The script wrapping was redundant. - OpenClaw variant: `clawhub install last30days-official` is what users already run per the README; the maintainer doesn't need a separate variant-deploy step in the public repo's scripts. - Claude marketplace cache (~/.claude/plugins/cache/...): this was a "test against the official install path" hack we shouldn't have been recommending. With PR #400's resolver collapse, STEP 0 no longer enforces the cache as the only valid SKILL.md location. Just install the skill normally via `npx skills` or the marketplace. Cleanup: - DELETE skills/last30days/scripts/sync.sh - tests/test_version_consistency.py — drop test_sync_cache_path_uses_skill_version - CLAUDE.md — replace the sync.sh command + rule with `npx skills add . -g -y` - HERMES_SETUP.md — Installation now uses `hermes skills install --force`; developer-alternative section shows the symlink pattern for live editing - render.py — _skill_version docstring no longer attributes the ".claude-plugin absent" case to sync.sh; explains it via per-harness install paths in general - .github/PULL_REQUEST_TEMPLATE.md — drop the "Ran bash scripts/sync.sh" checklist item CHANGELOG and historical docs (release notes, plan files) keep their existing sync.sh mentions as accurate history.
67 lines
2.6 KiB
Python
67 lines
2.6 KiB
Python
import re
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SKILL_ROOT = ROOT / "skills" / "last30days"
|
|
|
|
|
|
def _skill_version() -> str:
|
|
text = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
match = re.search(r'^version:\s*"([^"]+)"\s*$', text, re.MULTILINE)
|
|
if not match:
|
|
raise AssertionError("SKILL.md version frontmatter not found")
|
|
return match.group(1)
|
|
|
|
|
|
class TestVersionConsistency(unittest.TestCase):
|
|
def test_root_skill_header_matches_frontmatter_version(self) -> None:
|
|
text = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
version = _skill_version()
|
|
self.assertIn(f"# last30days v{version}:", text)
|
|
|
|
def test_memory_save_dir_uses_single_env_variable(self) -> None:
|
|
skill_text = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
compare_text = (SKILL_ROOT / "scripts" / "compare.sh").read_text(encoding="utf-8")
|
|
default_assignment = 'LAST30DAYS_MEMORY_DIR="${LAST30DAYS_MEMORY_DIR:-$HOME/Documents/Last30Days}"'
|
|
|
|
self.assertIn(default_assignment, skill_text)
|
|
self.assertIn(default_assignment, compare_text)
|
|
self.assertNotIn("--save-dir=~/Documents/Last30Days", skill_text)
|
|
self.assertIn('--save-dir="${LAST30DAYS_MEMORY_DIR}"', skill_text)
|
|
|
|
def test_no_stray_hardcoded_memory_dir_paths(self) -> None:
|
|
allowed_suffixes = {".md", ".py", ".sh", ".txt", ".yml", ".yaml", ".json"}
|
|
skip_dirs = {".git", "assets", "fixtures", "docs"}
|
|
offenders = []
|
|
|
|
for path in ROOT.rglob("*"):
|
|
if not path.is_file() or path.suffix not in allowed_suffixes:
|
|
continue
|
|
if skip_dirs.intersection(path.relative_to(ROOT).parts):
|
|
continue
|
|
if path.relative_to(ROOT) == Path("tests/test_version_consistency.py"):
|
|
continue
|
|
|
|
try:
|
|
lines = path.read_text(encoding="utf-8").splitlines()
|
|
except UnicodeDecodeError:
|
|
continue
|
|
|
|
for line_number, line in enumerate(lines, start=1):
|
|
if "~/Documents/Last30Days" not in line and "$HOME/Documents/Last30Days" not in line:
|
|
continue
|
|
allowed_default = (
|
|
"LAST30DAYS_MEMORY_DIR" in line
|
|
and ("defaults to" in line or "${LAST30DAYS_MEMORY_DIR:-$HOME/Documents/Last30Days}" in line)
|
|
)
|
|
if not allowed_default:
|
|
offenders.append(f"{path.relative_to(ROOT)}:{line_number}: {line.strip()}")
|
|
|
|
self.assertEqual([], offenders)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|