92 lines
2.5 KiB
Python
92 lines
2.5 KiB
Python
"""
|
|
SessionStart hook - injects knowledge base context into every conversation.
|
|
|
|
This is the "context injection" layer. When Claude Code starts a session,
|
|
this hook reads the knowledge base index and recent daily log, then injects
|
|
them as additional context so Claude always "remembers" what it has learned.
|
|
|
|
Configure in .claude/settings.json:
|
|
{
|
|
"hooks": {
|
|
"SessionStart": [{
|
|
"matcher": "",
|
|
"command": "uv run python hooks/session-start.py"
|
|
}]
|
|
}
|
|
}
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
|
|
# Paths relative to project root
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
KNOWLEDGE_DIR = ROOT / "knowledge"
|
|
DAILY_DIR = ROOT / "daily"
|
|
INDEX_FILE = KNOWLEDGE_DIR / "index.md"
|
|
|
|
MAX_CONTEXT_CHARS = 20_000
|
|
MAX_LOG_LINES = 30
|
|
|
|
|
|
def get_recent_log() -> str:
|
|
"""Read the most recent daily log (today or yesterday)."""
|
|
today = datetime.now(timezone.utc).astimezone()
|
|
|
|
for offset in range(2):
|
|
date = today - timedelta(days=offset)
|
|
log_path = DAILY_DIR / f"{date.strftime('%Y-%m-%d')}.md"
|
|
if log_path.exists():
|
|
lines = log_path.read_text(encoding="utf-8").splitlines()
|
|
# Return last N lines to keep context small
|
|
recent = lines[-MAX_LOG_LINES:] if len(lines) > MAX_LOG_LINES else lines
|
|
return "\n".join(recent)
|
|
|
|
return "(no recent daily log)"
|
|
|
|
|
|
def build_context() -> str:
|
|
"""Assemble the context to inject into the conversation."""
|
|
parts = []
|
|
|
|
# Today's date
|
|
today = datetime.now(timezone.utc).astimezone()
|
|
parts.append(f"## Today\n{today.strftime('%A, %B %d, %Y')}")
|
|
|
|
# Knowledge base index (the core retrieval mechanism)
|
|
if INDEX_FILE.exists():
|
|
index_content = INDEX_FILE.read_text(encoding="utf-8")
|
|
parts.append(f"## Knowledge Base Index\n\n{index_content}")
|
|
else:
|
|
parts.append("## Knowledge Base Index\n\n(empty - no articles compiled yet)")
|
|
|
|
# Recent daily log
|
|
recent_log = get_recent_log()
|
|
parts.append(f"## Recent Daily Log\n\n{recent_log}")
|
|
|
|
context = "\n\n---\n\n".join(parts)
|
|
|
|
# Truncate if too long
|
|
if len(context) > MAX_CONTEXT_CHARS:
|
|
context = context[:MAX_CONTEXT_CHARS] + "\n\n...(truncated)"
|
|
|
|
return context
|
|
|
|
|
|
def main():
|
|
context = build_context()
|
|
|
|
output = {
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "SessionStart",
|
|
"additionalContext": context,
|
|
}
|
|
}
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|