fix: add wing param to diary_write/diary_read, derive from transcript path (#659)
* fix: add wing param to diary_write/diary_read, derive from transcript path
Without a wing override, all diary entries from the stop hook land in
wing_session-hook regardless of which project the session is in, making
per-project diary search impossible.
- tool_diary_write(): add optional `wing` param; sanitize and use it when
provided, fall back to wing_{agent_name} when omitted
- tool_diary_read(): add optional `wing` param for filtering by target wing
- TOOLS dict: expose `wing` in input_schema for both diary tools
- hooks_cli: add _wing_from_transcript_path() helper that extracts the
project name from Claude Code paths like
~/.claude/projects/-home-jp-Projects-kiyo-xhci-fix/... → kiyo-xhci-fix
- hook_stop: derive project wing and append wing= hint to block reason so
Claude writes diary entries to the correct per-project wing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: sanitize wing param, cross-platform paths, tighten test assertions
Addresses Copilot review feedback on #659.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wing_ prefix + agent filter on diary_read
Addresses bensig's 2-issue review on this PR.
1. _wing_from_transcript_path() was returning bare project names
(e.g. "myproject") while all existing wings follow the wing_*
convention from AAAK_SPEC. Entries landed in wing="myproject"
while diary_read defaulted to wing="wing_<agent_name>" —
orphaning every diary entry written by the stop hook. Now
returns "wing_<project>" and falls back to "wing_sessions".
2. tool_diary_read() did not include agent_name in the ChromaDB
where filter when a custom wing was provided — any caller with
a shared wing could read entries written by other agents.
Add {"agent": agent_name} to the $and clause. Also flagged by
Qudo and left unresolved until now.
Tests updated to expect the wing_ prefix (6 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-1
@@ -20,6 +20,7 @@ from mempalace.hooks_cli import (
|
||||
_parse_harness_input,
|
||||
_sanitize_session_id,
|
||||
_validate_transcript_path,
|
||||
_wing_from_transcript_path,
|
||||
hook_stop,
|
||||
hook_session_start,
|
||||
hook_precompact,
|
||||
@@ -233,7 +234,27 @@ def test_stop_hook_saves_silently_at_interval(tmp_path):
|
||||
# Saves silently — systemMessage notification with themes, no block
|
||||
assert result["systemMessage"].startswith("\u2726 15 memories woven into the palace")
|
||||
assert "hooks" in result["systemMessage"]
|
||||
mock_save.assert_called_once_with(str(transcript), "test", toast=False)
|
||||
# tmp_path has no "-Projects-" segment, so _wing_from_transcript_path falls back to "wing_sessions"
|
||||
mock_save.assert_called_once_with(str(transcript), "test", wing="wing_sessions", toast=False)
|
||||
|
||||
|
||||
def test_stop_hook_derives_wing_from_transcript_path(tmp_path):
|
||||
"""When transcript path looks like a Claude Code path, wing is derived from it."""
|
||||
project_dir = tmp_path / ".claude" / "projects" / "-home-jp-Projects-myproject"
|
||||
project_dir.mkdir(parents=True)
|
||||
transcript = project_dir / "session.jsonl"
|
||||
_write_transcript(
|
||||
transcript,
|
||||
[{"message": {"role": "user", "content": f"msg {i}"}} for i in range(SAVE_INTERVAL)],
|
||||
)
|
||||
save_result = {"count": 15, "themes": []}
|
||||
with patch("mempalace.hooks_cli._save_diary_direct", return_value=save_result) as mock_save:
|
||||
_capture_hook_output(
|
||||
hook_stop,
|
||||
{"session_id": "test", "stop_hook_active": False, "transcript_path": str(transcript)},
|
||||
state_dir=tmp_path,
|
||||
)
|
||||
mock_save.assert_called_once_with(str(transcript), "test", wing="wing_myproject", toast=False)
|
||||
|
||||
|
||||
def test_stop_hook_tracks_save_point(tmp_path):
|
||||
@@ -281,6 +302,28 @@ def test_precompact_allows(tmp_path):
|
||||
assert result == {}
|
||||
|
||||
|
||||
# --- _wing_from_transcript_path ---
|
||||
|
||||
|
||||
def test_wing_from_transcript_path_extracts_project():
|
||||
path = "/home/jp/.claude/projects/-home-jp-Projects-memorypalace/session.jsonl"
|
||||
assert _wing_from_transcript_path(path) == "wing_memorypalace"
|
||||
|
||||
|
||||
def test_wing_from_transcript_path_fallback():
|
||||
assert _wing_from_transcript_path("/some/random/path.jsonl") == "wing_sessions"
|
||||
|
||||
|
||||
def test_wing_from_transcript_path_windows_backslashes():
|
||||
path = "C:\\Users\\jp\\.claude\\projects\\-home-jp-Projects-myapp\\session.jsonl"
|
||||
assert _wing_from_transcript_path(path) == "wing_myapp"
|
||||
|
||||
|
||||
def test_wing_from_transcript_path_lowercases():
|
||||
path = "/home/jp/.claude/projects/-home-jp-Projects-MyProject/session.jsonl"
|
||||
assert _wing_from_transcript_path(path) == "wing_myproject"
|
||||
|
||||
|
||||
# --- _log ---
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user