feat: session handoff — persist last-session summary across CC restarts
session-stop.sh writes ~/.claude/buddymon/handoff.json with: buddy id, XP earned, commit count, languages touched, caught monsters, challenge state, any active encounter, and manual notes (for future /buddymon note). session-start.sh reads handoff.json on next session start, injects a '📬 From your last session' block into additionalContext, then removes the file so it only fires once. Closes #1 on Circuit-Forge/buddymon.
This commit is contained in:
parent
c85bade62f
commit
caa655ab9a
2 changed files with 128 additions and 0 deletions
|
|
@ -39,6 +39,77 @@ CATALOG="${PLUGIN_ROOT}/lib/catalog.json"
|
|||
build_context() {
|
||||
local ctx=""
|
||||
|
||||
# ── Session handoff from previous session ──────────────────────────────
|
||||
local handoff_file="${BUDDYMON_DIR}/handoff.json"
|
||||
if [[ -f "${handoff_file}" ]]; then
|
||||
local handoff_block
|
||||
handoff_block=$(python3 << PYEOF
|
||||
import json, os
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
h = json.load(open('${handoff_file}'))
|
||||
except Exception:
|
||||
print('')
|
||||
exit()
|
||||
|
||||
buddy_id = h.get('buddy_id')
|
||||
if not buddy_id:
|
||||
print('')
|
||||
exit()
|
||||
|
||||
import json as _json
|
||||
catalog_file = '${CATALOG}'
|
||||
try:
|
||||
catalog = _json.load(open(catalog_file))
|
||||
b = (catalog.get('buddymon', {}).get(buddy_id)
|
||||
or catalog.get('evolutions', {}).get(buddy_id) or {})
|
||||
display = b.get('display', buddy_id)
|
||||
except Exception:
|
||||
display = buddy_id
|
||||
|
||||
lines = [f"### 📬 From your last session ({h.get('date', '?')}) — {display}"]
|
||||
|
||||
xp = h.get('xp_earned', 0)
|
||||
commits = h.get('commits', 0)
|
||||
langs = h.get('languages', [])
|
||||
caught = h.get('caught', [])
|
||||
|
||||
if xp:
|
||||
lines.append(f"- Earned **{xp} XP**")
|
||||
if commits:
|
||||
lines.append(f"- Made **{commits} commit{'s' if commits != 1 else ''}**")
|
||||
if langs:
|
||||
lines.append(f"- Languages touched: {', '.join(langs)}")
|
||||
if caught:
|
||||
lines.append(f"- Caught: {', '.join(caught)}")
|
||||
|
||||
challenge = h.get('challenge')
|
||||
challenge_completed = h.get('challenge_completed', False)
|
||||
if challenge:
|
||||
status = "✅ completed" if challenge_completed else "⏳ still in progress"
|
||||
lines.append(f"- Challenge **{challenge.get('name','?')}** — {status}")
|
||||
|
||||
enc = h.get('active_encounter')
|
||||
if enc:
|
||||
lines.append(f"- ⚠️ **Unresolved encounter carried over:** {enc.get('display', '?')} (strength: {enc.get('current_strength', 100)}%)")
|
||||
|
||||
notes = h.get('notes', [])
|
||||
if notes:
|
||||
lines.append("**Notes:**")
|
||||
for n in notes:
|
||||
lines.append(f" · {n}")
|
||||
|
||||
print('\n'.join(lines))
|
||||
PYEOF
|
||||
)
|
||||
if [[ -n "${handoff_block}" ]]; then
|
||||
ctx+="${handoff_block}\n\n"
|
||||
fi
|
||||
# Archive handoff — consumed for this session
|
||||
rm -f "${handoff_file}"
|
||||
fi
|
||||
|
||||
# ── No starter chosen yet ─────────────────────────────────────────────
|
||||
if [[ "$(buddymon_starter_chosen)" == "false" ]]; then
|
||||
ctx="## 🐾 Buddymon — First Encounter!\n\n"
|
||||
|
|
|
|||
|
|
@ -126,6 +126,63 @@ print('\n'.join(lines))
|
|||
PYEOF
|
||||
)
|
||||
|
||||
# Write handoff.json for next session to pick up
|
||||
python3 << PYEOF
|
||||
import json, os
|
||||
from datetime import datetime, timezone
|
||||
|
||||
session_state_file = '${SESSION_FILE}'
|
||||
session_file = '${BUDDYMON_DIR}/session.json'
|
||||
encounters_file = '${BUDDYMON_DIR}/encounters.json'
|
||||
handoff_file = '${BUDDYMON_DIR}/handoff.json'
|
||||
|
||||
session_state = {}
|
||||
try:
|
||||
session_state = json.load(open(session_state_file))
|
||||
except Exception:
|
||||
pass
|
||||
session_data = {}
|
||||
try:
|
||||
session_data = json.load(open(session_file))
|
||||
except Exception:
|
||||
pass
|
||||
encounters = {}
|
||||
try:
|
||||
encounters = json.load(open(encounters_file))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Collect caught monsters from this session
|
||||
caught_this_session = [
|
||||
e.get('display', e.get('id', '?'))
|
||||
for e in encounters.get('history', [])
|
||||
if e.get('outcome') == 'caught'
|
||||
and e.get('timestamp', '') >= datetime.now(timezone.utc).strftime('%Y-%m-%d')
|
||||
]
|
||||
|
||||
# Carry over any existing handoff notes (user-added via /buddymon note)
|
||||
existing = {}
|
||||
try:
|
||||
existing = json.load(open(handoff_file))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
handoff = {
|
||||
"date": datetime.now(timezone.utc).strftime('%Y-%m-%d'),
|
||||
"buddy_id": session_state.get('buddymon_id'),
|
||||
"xp_earned": session_state.get('session_xp', 0),
|
||||
"commits": session_data.get('commits_this_session', 0),
|
||||
"languages": session_data.get('languages_seen', []),
|
||||
"caught": caught_this_session,
|
||||
"challenge": session_state.get('challenge'),
|
||||
"challenge_completed": session_data.get('challenge_completed', False),
|
||||
"active_encounter": encounters.get('active_encounter'),
|
||||
"notes": existing.get('notes', []), # preserve any manual notes
|
||||
}
|
||||
|
||||
json.dump(handoff, open(handoff_file, 'w'), indent=2)
|
||||
PYEOF
|
||||
|
||||
# Clean up this session's state file — each session is ephemeral
|
||||
rm -f "${SESSION_FILE}"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue