Files
Your Name b1b26691a9 Memory: auto-scaffold category dirs for fresh projects
- memory-lifecycle.sh cmd_store: mkdir -p the category dir instead of
  failing when memory/<category>/ does not exist yet
- cmd_recall (and list via delegation): missing category prints
  'No entries' with rc 0, consistent with search/sessions/cleanup
- tests: T13-T16 fresh-project regression tests (store creates dir,
  recall/list/sessions treat missing memory as empty) in
  test-memory-system.sh; all 8 suites pass
2026-09-08 11:24:46 -04:00

241 lines
6.3 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# memory-lifecycle.sh — Manage project memory: recall, store, list, search
#
# Usage:
# memory-lifecycle.sh recall <category> [query] — search memory for relevant entries
# memory-lifecycle.sh store <category> <file> — add or update a memory entry
# memory-lifecycle.sh list <category> — list entries in a category
# memory-lifecycle.sh search <query> — full-text search across all memory
# memory-lifecycle.sh sessions — list active/interrupted sessions
# memory-lifecycle.sh cleanup — archive old completed sessions
#
# Categories: decisions, lessons, failures, architecture, sessions
#
# This script provides deterministic memory operations for the agent team.
# It does NOT do semantic search — that is the Orchestrator's responsibility
# using agent reasoning over the recalled entries.
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd -P)"
MEMORY_DIR="${OPENCODE_MEMORY_DIR:-$PROJECT_ROOT/memory}"
usage() {
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
exit 2
}
# --- recall -----------------------------------------------------------------
cmd_recall() {
local category="$1" query="${2:-}"
local dir="$MEMORY_DIR/$category"
if [ ! -d "$dir" ]; then
echo "=== Memory recall: $category ==="
echo " No entries in $category"
exit 0
fi
echo "=== Memory recall: $category ==="
if [ -n "$query" ]; then
echo "Query: $query"
echo "---"
# Search for matching entries (case-insensitive grep)
local found=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
[ "$(basename "$f")" = "README.md" ] && continue
if grep -qiF "$query" "$f" 2>/dev/null; then
echo " FOUND: $(basename "$f")"
# Show matching lines with context
grep -iF "$query" "$f" | head -5 | sed 's/^/ /'
found=$((found + 1))
fi
done
if [ "$found" = "0" ]; then
echo " No entries matching '$query' in $category"
else
echo " ---"
echo " $found matching entr(y/ies)"
fi
else
# List all entries
local count=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
[ "$(basename "$f")" = "README.md" ] && continue
echo " $(basename "$f")"
count=$((count + 1))
done
if [ "$count" = "0" ]; then
echo " No entries in $category"
else
echo " ---"
echo " $count entr(y/ies)"
fi
fi
}
# --- store ------------------------------------------------------------------
cmd_store() {
local category="$1" file="$2"
local dir="$MEMORY_DIR/$category"
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
fi
if [ ! -f "$file" ]; then
echo "ERROR: source file '$file' does not exist" >&2
exit 1
fi
local basename
basename="$(basename "$file")"
local dest="$dir/$basename"
if [ -f "$dest" ]; then
echo "UPDATED: $category/$basename"
else
echo "CREATED: $category/$basename"
fi
cp -p "$file" "$dest"
}
# --- list -------------------------------------------------------------------
cmd_list() {
local category="$1"
cmd_recall "$category" ""
}
# --- search -----------------------------------------------------------------
cmd_search() {
local query="$1"
echo "=== Full-text memory search: '$query' ==="
local found=0
for category in decisions lessons failures architecture sessions; do
local dir="$MEMORY_DIR/$category"
[ -d "$dir" ] || continue
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
[ "$(basename "$f")" = "README.md" ] && continue
if grep -qiF "$query" "$f" 2>/dev/null; then
echo " $category/$(basename "$f")"
found=$((found + 1))
fi
done
done
if [ "$found" = "0" ]; then
echo " No entries matching '$query'"
else
echo " ---"
echo " $found matching entr(y/ies) across all categories"
fi
}
# --- sessions ---------------------------------------------------------------
cmd_sessions() {
echo "=== Active/Interrupted Sessions ==="
local dir="$MEMORY_DIR/sessions"
local count=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
[ "$(basename "$f")" = "README.md" ] && continue
local status
status=$(grep -m1 "^Status:" "$f" 2>/dev/null | sed 's/^Status: *//' || echo "unknown")
if [ "$status" = "active" ] || [ "$status" = "interrupted" ]; then
local title
title=$(grep -m1 "^# " "$f" 2>/dev/null | sed 's/^# //' || echo "$(basename "$f")")
echo " [$status] $(basename "$f"): $title"
count=$((count + 1))
fi
done
if [ "$count" = "0" ]; then
echo " No active or interrupted sessions"
fi
}
# --- cleanup ----------------------------------------------------------------
cmd_cleanup() {
echo "=== Session cleanup ==="
local dir="$MEMORY_DIR/sessions"
local archived=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
[ "$(basename "$f")" = "README.md" ] && continue
local status
status=$(grep -m1 "^Status:" "$f" 2>/dev/null | sed 's/^Status: *//' || echo "unknown")
if [ "$status" = "completed" ]; then
# Check if older than 7 days
local file_age
file_age=$(( ($(date +%s) - $(stat -c %Y "$f" 2>/dev/null || echo 0)) / 86400 ))
if [ "$file_age" -gt 7 ]; then
local dest="$dir/archive"
mkdir -p "$dest"
mv "$f" "$dest/"
echo " Archived: $(basename "$f") (age: ${file_age}d)"
archived=$((archived + 1))
fi
fi
done
if [ "$archived" = "0" ]; then
echo " No sessions to archive"
else
echo " Archived $archived session(s)"
fi
}
# --- main -------------------------------------------------------------------
[ $# -ge 1 ] || usage
CMD="$1"; shift
case "$CMD" in
recall)
[ $# -ge 1 ] || { echo "ERROR: recall requires a category" >&2; usage; }
cmd_recall "$1" "${2:-}"
;;
store)
[ $# -ge 2 ] || { echo "ERROR: store requires a category and file" >&2; usage; }
cmd_store "$1" "$2"
;;
list)
[ $# -ge 1 ] || { echo "ERROR: list requires a category" >&2; usage; }
cmd_list "$1"
;;
search)
[ $# -ge 1 ] || { echo "ERROR: search requires a query" >&2; usage; }
cmd_search "$1"
;;
sessions)
cmd_sessions
;;
cleanup)
cmd_cleanup
;;
-h|--help)
usage
;;
*)
echo "ERROR: unknown command: $CMD" >&2
usage
;;
esac