Python · 4513 bytes Raw Blame History
1 """Tests for project memory and notepad tools."""
2
3 from __future__ import annotations
4
5 import json
6 from pathlib import Path
7
8 import pytest
9
10 from loader.agent.loop import AgentConfig
11 from loader.llm.base import CompletionResponse, ToolCall
12 from loader.runtime.executor import ToolExecutionState, ToolExecutor
13 from loader.runtime.hooks import HookManager, MemoryLifecycleHook
14 from loader.runtime.permissions import PermissionMode, build_permission_policy
15 from loader.runtime.tracing import RuntimeTracer
16 from loader.tools.base import create_default_registry
17 from tests.helpers.runtime_harness import ScriptedBackend, run_scenario
18
19
20 @pytest.mark.asyncio
21 async def test_project_memory_tools_round_trip(temp_dir: Path) -> None:
22 registry = create_default_registry(temp_dir)
23
24 write_result = await registry.execute(
25 "project_memory_write",
26 memory={"techStack": "Python", "build": "uv run pytest -q"},
27 merge=True,
28 )
29 read_result = await registry.execute("project_memory_read", section="all")
30 directive_result = await registry.execute(
31 "project_memory_add_directive",
32 directive="Use uv, never pip.",
33 priority="high",
34 )
35
36 assert not write_result.is_error
37 assert not read_result.is_error
38 assert not directive_result.is_error
39 memory = json.loads(read_result.output)
40 assert memory["techStack"] == "Python"
41 assert registry.get("project_memory_read").required_permission == PermissionMode.READ_ONLY
42 assert registry.get("project_memory_write").required_permission == PermissionMode.WORKSPACE_WRITE
43
44
45 @pytest.mark.asyncio
46 async def test_notepad_tools_round_trip(temp_dir: Path) -> None:
47 registry = create_default_registry(temp_dir)
48
49 await registry.execute("notepad_write_priority", content="Keep Loader focused on runtime quality.")
50 await registry.execute("notepad_write_working", content="Audit session resume behavior.")
51 await registry.execute("notepad_write_manual", content="Never delete refs without asking.")
52 read_result = await registry.execute("notepad_read", section="all")
53
54 assert not read_result.is_error
55 assert "Keep Loader focused on runtime quality." in read_result.output
56 assert "Audit session resume behavior." in read_result.output
57 assert "Never delete refs without asking." in read_result.output
58
59
60 @pytest.mark.asyncio
61 async def test_memory_lifecycle_hook_mirrors_directives_into_notepad(temp_dir: Path) -> None:
62 registry = create_default_registry(temp_dir)
63 policy = build_permission_policy(
64 active_mode=PermissionMode.WORKSPACE_WRITE,
65 workspace_root=temp_dir,
66 tool_requirements=registry.get_tool_requirements(),
67 )
68 executor = ToolExecutor(
69 registry,
70 RuntimeTracer(),
71 policy,
72 hooks=HookManager([MemoryLifecycleHook()]),
73 )
74
75 outcome = await executor.execute_tool_call(
76 ToolCall(
77 id="directive-1",
78 name="project_memory_add_directive",
79 arguments={
80 "directive": "Use uv, never pip.",
81 "priority": "high",
82 },
83 ),
84 source="native",
85 skip_confirmation=True,
86 )
87
88 assert outcome.state == ToolExecutionState.EXECUTED
89 notepad = (temp_dir / ".loader" / "notepad.md").read_text()
90 assert "Remembered directive [high]: Use uv, never pip." in notepad
91
92
93 @pytest.mark.asyncio
94 async def test_definition_of_done_summary_is_captured_in_project_memory(
95 temp_dir: Path,
96 ) -> None:
97 target = temp_dir / "memory-proof.txt"
98 backend = ScriptedBackend(
99 completions=[
100 CompletionResponse(
101 content="I'll create the file.",
102 tool_calls=[
103 ToolCall(
104 id="write-1",
105 name="write",
106 arguments={"file_path": str(target), "content": "memory proof\n"},
107 )
108 ],
109 ),
110 CompletionResponse(content="The file is in place."),
111 ]
112 )
113
114 await run_scenario(
115 "Create memory-proof.txt in the workspace root.",
116 backend,
117 config=AgentConfig(auto_context=False, stream=False),
118 project_root=temp_dir,
119 )
120
121 memory = json.loads((temp_dir / ".loader" / "project-memory.json").read_text())
122 notes = memory.get("notes", [])
123 assert any(
124 note.get("category") == "definition_of_done"
125 and "Verification:" in note.get("content", "")
126 for note in notes
127 )