tests/plugins/test_hooks_instructions_loaded.py
Rama main · réplica de lectura del repositorio original.
"""InstructionsLoaded: the hooks plugin fires Claude Code's event for each
instruction file the instructions plugin shows, read from its records."""
from __future__ import annotations
from pathlib import Path
from typing import Any
from noeta.plugins.compaction import Compaction
from noeta.plugins.compaction.extension import command_text
from noeta.plugins.instructions import Instructions
from noeta.testing import reply
from tests.plugins.test_hooks_events import Setup, hook
def _shown(payloads: list[dict[str, Any]]) -> list[tuple[str, str, str]]:
return [(Path(p["file_path"]).name, p["memory_type"], p["load_reason"]) for p in payloads]
async def test_instructions_loaded_fires_for_every_file_shown(tmp_path: Path) -> None:
home = tmp_path / "home"
home.mkdir()
(home / "NOETA.md").write_text("User rules.\n")
s = Setup(tmp_path, {"InstructionsLoaded": hook("loaded")},
[reply("", ("Read", {"file_path": "sub/x.py"})), reply("read"),
reply("again"), reply("summary text"), reply("after")],
extensions=(Compaction(auto=False),
Instructions(walk=False, user_dir=str(home))))
(s.work / "NOETA.md").write_text("Project rules. See @extra.md\n")
(s.work / "extra.md").write_text("Imported.\n")
(s.work / "sub").mkdir()
(s.work / "sub" / "NOETA.md").write_text("Sub rules.\n")
(s.work / "sub" / "x.py").write_text("x = 1\n")
rules = s.work / ".noeta" / "rules"
rules.mkdir(parents=True)
(rules / "py.md").write_text("---\npaths: ['**/*.py']\n---\nPython rules.\n")
await s.start()
loaded = s.runner.payloads("loaded")
assert _shown(loaded) == [
("NOETA.md", "User", "session_start"), ("NOETA.md", "Project", "session_start"),
("extra.md", "Project", "include"),
("py.md", "Project", "path_glob_match"), ("NOETA.md", "Project", "nested_traversal")]
assert loaded[0]["hook_event_name"] == "InstructionsLoaded"
assert loaded[2]["parent_file_path"] == str(s.work / "NOETA.md")
trigger = str((s.work / "sub" / "x.py").resolve())
assert loaded[3]["globs"] == ["**/*.py"] and loaded[3]["trigger_file_path"] == trigger
assert loaded[4]["trigger_file_path"] == trigger and "globs" not in loaded[4]
assert "trigger_file_path" not in loaded[0]
# A changed file is shown again with the next message: loaded again.
(s.work / "sub" / "NOETA.md").write_text("Sub rules, changed.\n")
await s.send("go on")
assert _shown(s.runner.payloads("loaded")[5:]) == [("NOETA.md", "Project", "session_start")]
# A compaction puts every file ahead of its summary.
await s.send(command_text(""))
compacted = s.runner.payloads("loaded")[6:]
assert {p["load_reason"] for p in compacted} == {"compact"}
assert len(compacted) == 5
async def test_instructions_loaded_matcher_is_the_load_reason(tmp_path: Path) -> None:
s = Setup(tmp_path, {"InstructionsLoaded": hook("loaded", "nested_traversal")},
[reply("", ("Read", {"file_path": "sub/x.txt"})), reply("read")],
extensions=(Instructions(walk=False),))
(s.work / "NOETA.md").write_text("Project rules.\n")
(s.work / "sub").mkdir()
(s.work / "sub" / "NOETA.md").write_text("Sub rules.\n")
(s.work / "sub" / "x.txt").write_text("x\n")
await s.start()
assert _shown(s.runner.payloads("loaded")) == [("NOETA.md", "Project", "nested_traversal")]