"""Behavior contracts for the learning-graph assembler. Asserts invariants (edges resolve to real nodes, clusters cover every node, memory cards are represented consistently), never a snapshot of the live skill catalog — that catalog grows every release and a count assertion would be a change-detector. """ from __future__ import annotations from agent import learning_graph from hermes_constants import reset_hermes_home_override, set_hermes_home_override def _node(name: str, category: str, related=None): n = learning_graph.SkillNode(name=name, category=category) n.related = list(related or []) return n def test_density_stats_count_isolated_nodes(): nodes = { "a": _node("a", "x", related=["b"]), "b": _node("b", "x", related=["a"]), "c": _node("c", "y"), } stats = learning_graph.density_stats(nodes, learning_graph.build_edges(nodes)) assert stats["nodes"] == 3 assert stats["linked_nodes"] == 2 assert stats["isolated_pct"] == round(100 / 3, 1) def test_memory_is_cards_split_on_separator(tmp_path): home = tmp_path / ".hermes" (home / "memories").mkdir(parents=True) (home / "memories" / "MEMORY.md").write_text( "Project uses pytest with xdist\n§\nUser prefers concise responses", encoding="utf-8", ) token = set_hermes_home_override(home) try: graph = learning_graph.build_learning_graph() finally: reset_hermes_home_override(token) titles = [c["title"] for c in graph["memory"]] assert "Project uses pytest with xdist" in titles assert "User prefers concise responses" in titles # Memory cards remain typed cards and also appear as memory-kind nodes. assert all(c["source"] in {"memory", "profile"} for c in graph["memory"]) assert all("timestamp" in c for c in graph["memory"]) assert any(n["kind"] == "memory" for n in graph["nodes"]) def test_full_payload_shape_and_edge_integrity(tmp_path): home = tmp_path / ".hermes" home.mkdir() token = set_hermes_home_override(home) try: graph = learning_graph.build_learning_graph() finally: reset_hermes_home_override(token) ids = {n["id"] for n in graph["nodes"]} assert all(e["source"] in ids and e["target"] in ids for e in graph["edges"]) # Every node's category appears in the cluster list. cluster_cats = {c["category"] for c in graph["clusters"]} assert all(n["category"] in cluster_cats for n in graph["nodes"]) skill_nodes = [n for n in graph["nodes"] if n["kind"] == "skill"] assert graph["stats"]["nodes"] == len(skill_nodes) assert graph["stats"]["memory_nodes"] == len(graph["memory"]) assert all("timestamp" in n for n in graph["nodes"]) def test_foreground_created_skill_is_in_journey_before_first_use(tmp_path): """A skill created in the foreground (/learn, skill_manage) shows in the journey with zero uses, while an unmarked never-used local skill (hand-written) stays out.""" from tools import skill_usage home = tmp_path / ".hermes" for name in ("fresh-learn-skill", "hand-written"): (home / "skills" / "demo" / name).mkdir(parents=True) (home / "skills" / "demo" / name / "SKILL.md").write_text( f"---\nname: {name}\ndescription: d.\n---\n\n# {name}\n", encoding="utf-8") token = set_hermes_home_override(home) try: skill_usage.record_created("fresh-learn-skill", agent_created=False) skill_nodes = {n["id"] for n in learning_graph.build_learning_graph()["nodes"] if n["kind"] == "skill"} finally: reset_hermes_home_override(token) assert "fresh-learn-skill" in skill_nodes assert "hand-written" not in skill_nodes