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() {
|
build_context() {
|
||||||
local ctx=""
|
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 ─────────────────────────────────────────────
|
# ── No starter chosen yet ─────────────────────────────────────────────
|
||||||
if [[ "$(buddymon_starter_chosen)" == "false" ]]; then
|
if [[ "$(buddymon_starter_chosen)" == "false" ]]; then
|
||||||
ctx="## 🐾 Buddymon — First Encounter!\n\n"
|
ctx="## 🐾 Buddymon — First Encounter!\n\n"
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,63 @@ print('\n'.join(lines))
|
||||||
PYEOF
|
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
|
# Clean up this session's state file — each session is ephemeral
|
||||||
rm -f "${SESSION_FILE}"
|
rm -f "${SESSION_FILE}"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue