Files
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

129 lines
4.2 KiB
Python

"""Behavior contracts for journey node edit/delete (agent.learning_mutations).
Exercises the real on-disk resolution (skills dir + MEMORY.md/USER.md chunking)
against a temp HERMES_HOME, never mocks — the id→file mapping is the whole point.
"""
from __future__ import annotations
import pytest
from agent import learning_mutations as lm
from hermes_constants import get_hermes_home
_SKILL = """---
name: my-skill
description: A test skill.
---
# My Skill
Body.
"""
@pytest.fixture
def home():
base = get_hermes_home()
(base / "memories").mkdir(parents=True, exist_ok=True)
(base / "memories" / "MEMORY.md").write_text("alpha note\nline two\n§\nbeta note", encoding="utf-8")
(base / "memories" / "USER.md").write_text("user profile note", encoding="utf-8")
skill = base / "skills" / "my-skill"
skill.mkdir(parents=True, exist_ok=True)
(skill / "SKILL.md").write_text(_SKILL, encoding="utf-8")
return base
def test_parse_node_kind():
assert lm.parse_node_kind("memory:memory:0") == "memory"
assert lm.parse_node_kind("memory:profile:3") == "memory"
assert lm.parse_node_kind("debugging-hermes") == "skill"
def test_memory_global_index_maps_across_files(home):
# MEMORY.md → indices 0,1; USER.md → index 2 (global, memory cards first).
assert lm.node_detail("memory:memory:0")["content"].startswith("alpha note")
assert lm.node_detail("memory:memory:1")["content"] == "beta note"
assert lm.node_detail("memory:profile:2")["content"] == "user profile note"
def test_memory_label_is_first_line(home):
assert lm.node_detail("memory:memory:0")["label"] == "alpha note"
def test_delete_memory_rewrites_file(home):
assert lm.delete_node("memory:memory:0")["ok"]
remaining = (home / "memories" / "MEMORY.md").read_text(encoding="utf-8")
assert "alpha note" not in remaining
assert "beta note" in remaining
def test_edit_memory_replaces_chunk(home):
assert lm.edit_node("memory:profile:2", "rewritten profile")["ok"]
assert (home / "memories" / "USER.md").read_text(encoding="utf-8").strip() == "rewritten profile"
def test_edit_memory_empty_is_rejected(home):
res = lm.edit_node("memory:memory:1", " ")
assert not res["ok"]
assert "delete" in res["message"]
def test_stale_memory_index_errors(home):
res = lm.node_detail("memory:memory:9")
assert not res["ok"]
def test_bad_memory_id_returns_error(home):
res = lm.delete_node("memory:bogus:0")
assert not res["ok"]
def test_skill_detail_returns_skill_md(home):
d = lm.node_detail("my-skill")
assert d["ok"] and d["kind"] == "skill"
assert "name: my-skill" in d["content"]
def test_delete_skill_archives_recoverably(home):
res = lm.delete_node("my-skill")
assert res["ok"]
assert not (home / "skills" / "my-skill").exists()
assert (home / "skills" / ".archive" / "my-skill" / "SKILL.md").exists()
def test_delete_pinned_skill_refused(home):
from tools import skill_usage
skill_usage.set_pinned("my-skill", True)
res = lm.delete_node("my-skill")
assert not res["ok"]
assert "pinned" in res["message"]
assert (home / "skills" / "my-skill").exists()
def test_edit_skill_rewrites_and_validates(home):
bad = lm.edit_node("my-skill", "no frontmatter here")
assert not bad["ok"]
good = lm.edit_node("my-skill", _SKILL.replace("A test skill.", "Updated desc."))
assert good["ok"]
assert "Updated desc." in (home / "skills" / "my-skill" / "SKILL.md").read_text(encoding="utf-8")
def test_missing_skill_detail(home):
assert not lm.node_detail("nonexistent-skill")["ok"]
def test_memory_writes_match_memory_tool_format(home):
"""A journey mutation must leave the file byte-identical to what the memory
tool itself writes — same §-join, no trailing-newline drift — so the two
surfaces never fight over format and indices stay aligned."""
from tools.memory_tool import ENTRY_DELIMITER, MemoryStore
assert lm.edit_node("memory:memory:0", "alpha rewritten")["ok"]
path = home / "memories" / "MEMORY.md"
entries = MemoryStore._read_file(path)
assert entries == ["alpha rewritten", "beta note"]
assert path.read_text(encoding="utf-8") == ENTRY_DELIMITER.join(entries)