Skip to content

Commit 201c46b

Browse files
jpheinclaude
andcommitted
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>
1 parent bb7ed80 commit 201c46b

3 files changed

Lines changed: 37 additions & 8 deletions

File tree

mempalace/hooks_cli.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ def _parse_harness_input(data: dict, harness: str) -> dict:
126126
}
127127

128128

129+
def _wing_from_transcript_path(transcript_path: str) -> str:
130+
"""Derive a project wing name from a Claude Code transcript path.
131+
132+
Claude Code stores transcripts at:
133+
~/.claude/projects/-home-<user>-Projects-<project>/session.jsonl
134+
We extract <project> as the wing name. Falls back to "sessions".
135+
"""
136+
match = re.search(r"-Projects-([^/]+?)(?:/|$)", transcript_path)
137+
if match:
138+
return match.group(1).lower().replace(" ", "_")
139+
return "sessions"
140+
141+
129142
def hook_stop(data: dict, harness: str):
130143
"""Stop hook: block every N messages for auto-save."""
131144
parsed = _parse_harness_input(data, harness)
@@ -167,7 +180,9 @@ def hook_stop(data: dict, harness: str):
167180
# Optional: auto-ingest if MEMPAL_DIR is set
168181
_maybe_auto_ingest()
169182

170-
_output({"decision": "block", "reason": STOP_BLOCK_REASON})
183+
project_wing = _wing_from_transcript_path(transcript_path)
184+
reason = STOP_BLOCK_REASON + f" Write diary entry to wing={project_wing}."
185+
_output({"decision": "block", "reason": reason})
171186
else:
172187
_output({})
173188

mempalace/mcp_server.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,10 @@ def tool_kg_stats():
539539
# ==================== AGENT DIARY ====================
540540

541541

542-
def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
542+
def tool_diary_write(agent_name: str, entry: str, topic: str = "general", wing: str = ""):
543543
"""
544-
Write a diary entry for this agent. Each agent gets its own wing
545-
with a diary room. Entries are timestamped and accumulate over time.
544+
Write a diary entry for this agent. Entries are timestamped and
545+
accumulate over time in a diary room.
546546
547547
This is the agent's personal journal — observations, thoughts,
548548
what it worked on, what it noticed, what it thinks matters.
@@ -553,7 +553,10 @@ def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
553553
except ValueError as e:
554554
return {"success": False, "error": str(e)}
555555

556-
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
556+
if wing:
557+
wing = wing.lower().replace(" ", "_")
558+
else:
559+
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
557560
room = "diary"
558561
col = _get_collection(create=True)
559562
if not col:
@@ -605,12 +608,15 @@ def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
605608
return {"success": False, "error": str(e)}
606609

607610

608-
def tool_diary_read(agent_name: str, last_n: int = 10):
611+
def tool_diary_read(agent_name: str, last_n: int = 10, wing: str = ""):
609612
"""
610613
Read an agent's recent diary entries. Returns the last N entries
611614
in chronological order — the agent's personal journal.
612615
"""
613-
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
616+
if wing:
617+
wing = wing.lower().replace(" ", "_")
618+
else:
619+
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
614620
col = _get_collection()
615621
if not col:
616622
return _no_palace()
@@ -884,6 +890,10 @@ def tool_diary_read(agent_name: str, last_n: int = 10):
884890
"type": "string",
885891
"description": "Topic tag (optional, default: general)",
886892
},
893+
"wing": {
894+
"type": "string",
895+
"description": "Target wing for this diary entry (optional). If omitted, uses wing_{agent_name}. Use this to write diary entries to a project wing instead of an agent-specific wing.",
896+
},
887897
},
888898
"required": ["agent_name", "entry"],
889899
},
@@ -902,6 +912,10 @@ def tool_diary_read(agent_name: str, last_n: int = 10):
902912
"type": "integer",
903913
"description": "Number of recent entries to read (default: 10)",
904914
},
915+
"wing": {
916+
"type": "string",
917+
"description": "Wing to read diary entries from (optional). If omitted, reads from wing_{agent_name}.",
918+
},
905919
},
906920
"required": ["agent_name"],
907921
},

tests/test_hooks_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def test_stop_hook_blocks_at_interval(tmp_path):
169169
state_dir=tmp_path,
170170
)
171171
assert result["decision"] == "block"
172-
assert result["reason"] == STOP_BLOCK_REASON
172+
assert result["reason"].startswith(STOP_BLOCK_REASON)
173173

174174

175175
def test_stop_hook_tracks_save_point(tmp_path):

0 commit comments

Comments
 (0)