fix: use skill activity in curator status

Treat skill views and edits as activity when curator reports and applies lifecycle transitions, so recently loaded or patched skills are not displayed or transitioned as never used.\n\nAdds regression tests for activity derivation, automatic transitions, and CLI status output.
This commit is contained in:
y0shualee
2026-04-30 20:23:17 +08:00
committed by Teknium
parent 564a649e6a
commit f4b76fa272
6 changed files with 193 additions and 16 deletions

View File

@@ -0,0 +1,56 @@
"""Regression tests for curator skill activity timestamps."""
import importlib
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
def _write_skill(skills_dir: Path, name: str) -> None:
skill_dir = skills_dir / name
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "SKILL.md").write_text(
f"---\nname: {name}\ndescription: test skill\n---\n\n# {name}\n",
encoding="utf-8",
)
@pytest.fixture
def curator_modules(tmp_path, monkeypatch):
home = tmp_path / ".hermes"
(home / "skills").mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setattr(Path, "home", lambda: tmp_path)
import tools.skill_usage as skill_usage
import agent.curator as curator
importlib.reload(skill_usage)
importlib.reload(curator)
return home, skill_usage, curator
def test_recent_view_activity_prevents_false_stale_transition(curator_modules, monkeypatch):
home, skill_usage, curator = curator_modules
skills_dir = home / "skills"
_write_skill(skills_dir, "recently-viewed")
now = datetime(2026, 4, 30, tzinfo=timezone.utc)
created_at = (now - timedelta(days=60)).isoformat()
last_viewed_at = (now - timedelta(days=1)).isoformat()
skill_usage.save_usage({
"recently-viewed": {
"created_at": created_at,
"last_viewed_at": last_viewed_at,
"view_count": 1,
"state": "active",
}
})
monkeypatch.setattr(curator, "get_stale_after_days", lambda: 30)
monkeypatch.setattr(curator, "get_archive_after_days", lambda: 90)
counts = curator.apply_automatic_transitions(now=now)
assert counts["marked_stale"] == 0
assert skill_usage.get_record("recently-viewed")["state"] == "active"

View File

@@ -0,0 +1,43 @@
"""Tests for the curator CLI status renderer."""
from types import SimpleNamespace
def test_status_uses_last_activity_not_only_last_used(monkeypatch, capsys):
import agent.curator as curator_state
import hermes_cli.curator as curator_cli
import tools.skill_usage as skill_usage
monkeypatch.setattr(curator_state, "load_state", lambda: {
"paused": False,
"last_run_at": None,
"last_run_summary": "(none)",
"run_count": 0,
})
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
monkeypatch.setattr(curator_state, "get_interval_hours", lambda: 168)
monkeypatch.setattr(curator_state, "get_stale_after_days", lambda: 30)
monkeypatch.setattr(curator_state, "get_archive_after_days", lambda: 90)
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: [
{
"name": "recently-viewed",
"state": "active",
"pinned": False,
"use_count": 0,
"view_count": 3,
"patch_count": 1,
"created_at": "2026-01-01T00:00:00+00:00",
"last_used_at": None,
"last_viewed_at": "2026-04-30T10:00:00+00:00",
"last_patched_at": "2026-04-30T11:00:00+00:00",
"last_activity_at": "2026-04-30T11:00:00+00:00",
"activity_count": 4,
}
])
assert curator_cli._cmd_status(SimpleNamespace()) == 0
out = capsys.readouterr().out
assert "least recently active" in out
assert "activity= 4" in out
assert "last_activity=never" not in out
assert "last_used=never" not in out

View File

@@ -401,6 +401,26 @@ def test_agent_created_report_excludes_bundled_and_hub(skills_home):
assert "hubbed" not in names
def test_agent_created_report_derives_activity_from_view_and_patch(skills_home, monkeypatch):
import tools.skill_usage as skill_usage
skills_dir = skills_home / "skills"
_write_skill(skills_dir, "mine")
timestamps = iter([
"2026-04-30T10:00:00+00:00",
"2026-04-30T11:00:00+00:00",
"2026-04-30T12:00:00+00:00",
"2026-04-30T13:00:00+00:00",
])
monkeypatch.setattr(skill_usage, "_now_iso", lambda: next(timestamps))
skill_usage.bump_view("mine")
skill_usage.bump_patch("mine")
row = next(r for r in skill_usage.agent_created_report() if r["name"] == "mine")
assert row["activity_count"] == 2
assert row["last_activity_at"] == "2026-04-30T12:00:00+00:00"
# ---------------------------------------------------------------------------
# Provenance guard — telemetry must not leak records for bundled/hub skills