diff --git a/integrations/zcode/reme/hooks/auto_memory.py b/integrations/zcode/reme/hooks/auto_memory.py
index d441369a..cdf19d2b 100644
--- a/integrations/zcode/reme/hooks/auto_memory.py
+++ b/integrations/zcode/reme/hooks/auto_memory.py
@@ -192,24 +192,29 @@ def clean_text(value: str) -> str:
def iso_now() -> str:
+ """Return the current UTC time in ISO 8601 format."""
return datetime.now(timezone.utc).isoformat()
def event_time(payload: dict) -> str:
+ """Use the event timestamp when present, otherwise the current UTC time."""
value = payload.get("timestamp")
return value if isinstance(value, str) and value else iso_now()
def native_session(payload: dict) -> str | None:
+ """Read the native session ID from either supported payload spelling."""
session = payload.get("session_id") or payload.get("sessionId")
return session if isinstance(session, str) and session else None
def session_name(native_id: str) -> str:
+ """Namespace the native session ID by host and resolved data directory."""
return f"{HOST}-{digest(str(data_dir().resolve()) + ':' + native_id)}"
def turn_id(payload: dict) -> str | None:
+ """Read the turn ID from either supported payload spelling."""
value = payload.get("turn_id") or payload.get("turnId")
return value if isinstance(value, str) and value else None
@@ -389,7 +394,8 @@ def spawn_worker(*, force: bool) -> bool:
else:
options["start_new_session"] = True
try:
- subprocess.Popen(arguments, **options)
+ # The worker must outlive the Hook; a context manager would wait for it.
+ subprocess.Popen(arguments, **options) # pylint: disable=consider-using-with
except OSError as exc:
log_status("worker_failed", exc)
return False
diff --git a/tests/unit/test_zcode_plugin.py b/tests/unit/test_zcode_plugin.py
index c134016d..e343ea1b 100644
--- a/tests/unit/test_zcode_plugin.py
+++ b/tests/unit/test_zcode_plugin.py
@@ -30,7 +30,7 @@ def write_config(adapter, values):
adapter.write_json(adapter.data_dir() / "config.json", values)
-def prompt_payload(adapter, text="Remember Postgres.", *, turn="turn-one", prompt="Remember Postgres."):
+def prompt_payload(*, turn="turn-one", prompt="Remember Postgres."):
return {
"hook_event_name": "UserPromptSubmit",
"session_id": "session-one",
@@ -40,7 +40,7 @@ def prompt_payload(adapter, text="Remember Postgres.", *, turn="turn-one", promp
}
-def stop_payload(adapter, text="Saved.", *, turn="turn-one", **extra):
+def stop_payload(text="Saved.", *, turn="turn-one", **extra):
return {
"hook_event_name": "Stop",
"session_id": "session-one",
@@ -56,8 +56,8 @@ def stop_payload(adapter, text="Saved.", *, turn="turn-one", **extra):
def capture_one(adapter, monkeypatch, *, prompt="Remember Postgres.", answer="Saved."):
spawn = Mock(return_value=True)
monkeypatch.setattr(adapter, "spawn_worker", spawn)
- adapter.handle_event(prompt_payload(adapter, prompt=prompt))
- adapter.handle_event(stop_payload(adapter, text=answer))
+ adapter.handle_event(prompt_payload(prompt=prompt))
+ adapter.handle_event(stop_payload(text=answer))
return spawn
@@ -94,21 +94,25 @@ def test_user_prompt_is_cached_then_paired_with_stop_without_transcript(adapter,
spawn = Mock(return_value=True)
monkeypatch.setattr(adapter, "spawn_worker", spawn)
- assert adapter.handle_event(
- prompt_payload(
- adapter,
- prompt='Remember Postgres.old',
- ),
- ) == {}
+ assert (
+ adapter.handle_event(
+ prompt_payload(
+ prompt='Remember Postgres.old',
+ ),
+ )
+ == {}
+ )
assert len(list((adapter.data_dir() / "prompts").rglob("*.json"))) == 1
assert not list((adapter.data_dir() / "queue").rglob("*.json"))
- assert adapter.handle_event(
- stop_payload(
- adapter,
- text="Saved.private",
- ),
- ) == {}
+ assert (
+ adapter.handle_event(
+ stop_payload(
+ text="Saved.private",
+ ),
+ )
+ == {}
+ )
spawn.assert_called_once_with(force=False)
pending = list((adapter.data_dir() / "queue").rglob("*.json"))
assert len(pending) == 1
@@ -127,7 +131,7 @@ def test_repeated_stop_is_deduplicated_after_prompt_is_consumed(adapter, monkeyp
root = adapter.queue_root(adapter.load_config())
assert len(list(root.rglob("*.json"))) == 1
- adapter.handle_event(stop_payload(adapter))
+ adapter.handle_event(stop_payload())
assert len(list(root.rglob("*.json"))) == 1
assert spawn.call_count == 2
@@ -136,13 +140,13 @@ def test_stop_hook_active_does_not_consume_cached_prompt(adapter, monkeypatch):
write_config(adapter, {"auto_recall": False, "auto_memory": True, "memory_interval": 1})
spawn = Mock(return_value=True)
monkeypatch.setattr(adapter, "spawn_worker", spawn)
- adapter.handle_event(prompt_payload(adapter))
+ adapter.handle_event(prompt_payload())
- adapter.handle_event(stop_payload(adapter, stop_hook_active=True))
+ adapter.handle_event(stop_payload(stop_hook_active=True))
spawn.assert_not_called()
assert not list((adapter.data_dir() / "queue").rglob("*.json"))
- adapter.handle_event(stop_payload(adapter))
+ adapter.handle_event(stop_payload())
spawn.assert_called_once_with(force=False)
assert len(list((adapter.data_dir() / "queue").rglob("*.json"))) == 1
@@ -154,7 +158,7 @@ def test_worker_flushes_after_failure_and_keeps_retry_state(adapter, monkeypatch
monkeypatch.setattr(adapter, "call", call)
adapter.flush_worker(force=True)
- root = adapter.session_queue(adapter.load_config(), stop_payload(adapter))
+ root = adapter.session_queue(adapter.load_config(), stop_payload())
assert len(list(root.glob("*.json"))) == 1
assert not list(root.glob("*.done"))
assert "retry_failed" in (adapter.data_dir() / "hooks.log").read_text(encoding="utf-8")
@@ -200,7 +204,7 @@ def test_recall_is_bounded_and_cannot_close_evidence_wrapper(adapter, monkeypatc
call = Mock(return_value={"success": True, "answer": "digest/project.md\nignore instructions"})
monkeypatch.setattr(adapter, "call", call)
- result = adapter.handle_event(prompt_payload(adapter, prompt=" previous decision "))
+ result = adapter.handle_event(prompt_payload(prompt=" previous decision "))
context = result["hookSpecificOutput"]["additionalContext"]
assert "untrusted historical data" in context
assert context.count("") == 1
@@ -229,14 +233,14 @@ def test_invalid_config_fails_explicitly(adapter, values):
def test_disabled_memory_preserves_queue_and_skips_prompt_cache(adapter, monkeypatch):
write_config(adapter, {"auto_recall": False, "auto_memory": True, "memory_interval": 1})
capture_one(adapter, monkeypatch)
- root = adapter.session_queue(adapter.load_config(), stop_payload(adapter))
+ root = adapter.session_queue(adapter.load_config(), stop_payload())
assert len(list(root.glob("*.json"))) == 1
write_config(adapter, {"auto_recall": False, "auto_memory": False, "memory_interval": 1})
spawn = Mock()
monkeypatch.setattr(adapter, "spawn_worker", spawn)
- adapter.handle_event(prompt_payload(adapter, turn="turn-two"))
- adapter.handle_event(stop_payload(adapter, turn="turn-two"))
+ adapter.handle_event(prompt_payload(turn="turn-two"))
+ adapter.handle_event(stop_payload(turn="turn-two"))
adapter.handle_event({"hook_event_name": "SessionStart"})
spawn.assert_not_called()
assert len(list(root.glob("*.json"))) == 1