#!/usr/bin/env bash
# memory-load.sh  -  v15.10
#
# Reads the per-repo memory at $PROJECT_ROOT/.multi-agent/memory/ and prints
# a compact markdown block suitable for Phase 0 context injection. Falls back
# to an empty line and exit 0 if memory is absent or disabled.
#
# Usage: memory-load.sh <project-root> [task-text]
#
# With task text, the pointer lines are ranked against it (BM25 + recency, via
# scripts/_retrieval.mjs) and the most relevant are printed. Without it the
# index order is kept, which is what every existing caller gets.
#
# The cap used to be a bare `head -30`. That is a truncation, not a summary: the
# thirty-first pointer was invisible however precisely it matched the task, and
# the block got less useful the longer a repo was worked on - the opposite of
# what accumulated memory is for.
#
# Honors `prefs.global.perRepoMemory`. When the pref is off OR the prefs file
# cannot be located, exits silently (non-zero ignored by caller).

set -euo pipefail

PROJECT_ROOT="${1:?usage: memory-load.sh <project-root> [task-text]}"
TASK_TEXT="${2:-}"
MAX_POINTERS="${MEMORY_LOAD_MAX:-30}"
MEM_DIR="$PROJECT_ROOT/.multi-agent/memory"
MEM_INDEX="$MEM_DIR/MEMORY.md"
RETRIEVAL="$(cd "$(dirname "$0")" && pwd)/_retrieval.mjs"

# Pref-gate: locate the preferences file (home-dir first, then XDG config).
# The canonical name written by the installer is multi-agent-preferences.json;
# the bare preferences.json names are kept only as legacy fallbacks.
prefs_file=""
for candidate in \
  "$HOME/.claude/multi-agent-preferences.json" \
  "$HOME/.config/multi-agent-pipeline/multi-agent-preferences.json" \
  "$HOME/.claude/preferences.json" \
  "$HOME/.config/multi-agent-pipeline/preferences.json"
do
  [ -f "$candidate" ] && { prefs_file="$candidate"; break; }
done

if [ -n "$prefs_file" ] && command -v jq >/dev/null 2>&1; then
  enabled=$(jq -r '.global.perRepoMemory // false' "$prefs_file" 2>/dev/null || echo "false")
  [ "$enabled" = "true" ] || exit 0
else
  # No prefs file or no jq  -  treat as opt-out to avoid surprising new users.
  exit 0
fi

[ -f "$MEM_INDEX" ] || exit 0

# Index lines are written from memory bodies, which are authored text. A line
# containing the words `</repo-memory>` would make the reader see the block end
# early, with everything after it reading as though it were outside. Neutralise
# the delimiter on the way out; nothing else is touched.
defang() { sed -e 's|<repo-memory>|\&lt;repo-memory\&gt;|g' -e 's|</repo-memory>|\&lt;/repo-memory\&gt;|g'; }

# Emit the Phase 0 injection block. Individual memory files are NOT inlined -
# a pointer line names its file, and the caller reads it on demand when the
# pointer looks relevant.
printf '<repo-memory path="%s">\n' "$MEM_DIR"

if [ -n "$TASK_TEXT" ] && [ -f "$RETRIEVAL" ] && command -v node >/dev/null 2>&1; then
  # Rank the pointer lines against the task. Any failure here (no node, a bad
  # ranking, an unreadable index) falls back to index order rather than
  # dropping the block: a worse-ordered memory beats no memory.
  MEMORY_LOAD_TASK="$TASK_TEXT" MEMORY_LOAD_MAX="$MAX_POINTERS" \
  MEMORY_LOAD_RETRIEVAL="$RETRIEVAL" MEMORY_LOAD_INDEX="$MEM_INDEX" \
    node --input-type=module -e '
      import { readFileSync } from "node:fs";
      import { pathToFileURL } from "node:url";
      const { bm25 } = await import(pathToFileURL(process.env.MEMORY_LOAD_RETRIEVAL).href);
      const max = Number(process.env.MEMORY_LOAD_MAX) || 30;
      const raw = readFileSync(process.env.MEMORY_LOAD_INDEX, "utf8").replace(/\n$/, "").split("\n");
      const bullets = raw.map((line, i) => ({ line, i })).filter((x) => /^\s*-\s/.test(x.line));
      if (bullets.length === 0) {
        process.stdout.write(raw.slice(0, max).join("\n") + "\n");
      } else {
        const head = raw.slice(0, bullets[0].i).filter((l) => l.trim() !== "");
        const ranked = bm25(
          process.env.MEMORY_LOAD_TASK,
          bullets.map((x) => ({ line: x.line })),
        ).filter((r) => r.score > 0);
        const chosen = ranked.length ? ranked : bullets.map((_, index) => ({ index }));
        const picked = chosen
          .slice(0, max)
          .map((r) => bullets[r.index].i)
          .sort((a, b) => a - b);
        process.stdout.write([...head, ...picked.map((i) => raw[i])].join("\n") + "\n");
      }
    ' 2>/dev/null | defang || head -"$MAX_POINTERS" "$MEM_INDEX" | defang
else
  head -"$MAX_POINTERS" "$MEM_INDEX" | defang
fi

printf '</repo-memory>\n'

exit 0
