cc-os/plugins/os-vault/tests/generate-fixtures.sh

674 lines
24 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# generate-fixtures.sh — Run all scenarios and write golden fixtures to tests/golden/
# Run from any directory. Uses absolute paths throughout.
# SAFETY: Verifies real memsearch HEAD is unchanged after session-end scenarios.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
# Set HOOKS_DIR to override, e.g. for Python port replay:
# HOOKS_DIR=/path/to/python-hooks bash generate-fixtures.sh --replay-dir /tmp/out
HOOKS_DIR="${HOOKS_DIR:-$(cd "$SCRIPT_DIR/../hooks" && pwd -P)}"
GOLDEN_DIR="$SCRIPT_DIR/golden"
INPUTS_DIR="$SCRIPT_DIR/inputs"
REPLAY_DIR=""
EXPECTED_HEAD="15804ed63ebf6400f22d719ee102857985f9a9d9"
# ---------------------------------------------------------------------------
# Parse arguments
# ---------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--replay-dir) REPLAY_DIR="$2"; shift 2 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done
# If --replay-dir is set, write there instead of golden/
if [[ -n "$REPLAY_DIR" ]]; then
mkdir -p "$REPLAY_DIR"
GOLDEN_DIR="$REPLAY_DIR"
fi
# ---------------------------------------------------------------------------
# Safety: record real memsearch HEAD
# ---------------------------------------------------------------------------
REAL_HEAD_BEFORE=$(git -C /home/jared/.memsearch rev-parse HEAD 2>/dev/null || echo "NO_REAL_REPO")
echo "=== SAFETY CHECK: real HEAD before = $REAL_HEAD_BEFORE ==="
# In replay mode, skip the strict hash check (HEAD may have advanced since fixtures were made).
# The safety invariant is before==after, not before==EXPECTED_HEAD.
if [[ -z "$REPLAY_DIR" && "$REAL_HEAD_BEFORE" != "$EXPECTED_HEAD" ]]; then
echo "ERROR: HEAD mismatch before run. Expected $EXPECTED_HEAD, got $REAL_HEAD_BEFORE" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Pre-run global cleanup of /tmp stale flags
# ---------------------------------------------------------------------------
cleanup_tmp_flags() {
rm -f /tmp/memory-context-injected-golden-sess-{003,004,005,006,006b,006c,007,008}
rm -f /tmp/claude-vault-touched-golden-sess-{006,006b,006c,007,008}
}
cleanup_tmp_flags
mkdir -p "$GOLDEN_DIR"
# ---------------------------------------------------------------------------
# Normalization helper — takes sandbox path as $1, reads stdin
# ---------------------------------------------------------------------------
normalize() {
local sandbox_path="$1"
sed \
-e "s|[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}T[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}Z|__TIMESTAMP__|g" \
-e "s|[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}|__DATE__|g" \
-e "s|${sandbox_path}|__SANDBOX__|g" \
-e "s|cwd=[^ ]*|cwd=__CWD__|g" \
-e "s|/home/jared|__REAL_HOME__|g"
}
# ---------------------------------------------------------------------------
# Build a fresh sandbox for a scenario
# Returns sandbox path in SANDBOX (exported)
# ---------------------------------------------------------------------------
make_sandbox() {
local scenario="$1"
local sb
sb=$(mktemp -d)
sb=$(cd "$sb" && pwd -P)
mkdir -p "$sb/.claude/plugins/os-vault"
cat > "$sb/.claude/plugins/os-vault/config.yaml" <<YAML
vault_path: ${sb}/vault
graphify_output_dir: ${sb}/vault/graphify-out
ollama_model: qwen25-coder-7b-16k
stale_threshold_days: 7
memsearch_dir: /home/jared/.memsearch
YAML
mkdir -p "$sb/vault/journal"
mkdir -p "$sb/.cache/graphify"
mkdir -p "$sb/bin"
# Graphify shim — log path baked in
cat > "$sb/bin/graphify" <<BASH
#!/usr/bin/env bash
echo "[fake-graphify] args: \$@" >> "${sb}/graphify-shim.log"
exit 0
BASH
chmod +x "$sb/bin/graphify"
# Git shim — log path BAKED IN (absolute, not env-var-only) for safety in subshells
# Emits NOTHING to stdout/stderr
cat > "$sb/bin/git" <<BASH
#!/usr/bin/env bash
for arg in "\$@"; do
if [ "\$arg" = "/home/jared/.memsearch" ]; then
echo "[git-shim] BLOCKED: git \$@" >> "${sb}/git-shim.log"
exit 0
fi
done
exec /usr/bin/git "\$@"
BASH
chmod +x "$sb/bin/git"
echo "$sb"
}
# ---------------------------------------------------------------------------
# Helper: write all fixture files for a scenario
# ---------------------------------------------------------------------------
write_fixture() {
local scenario="$1"
local sb="$2"
local stdout_content="$3"
local exit_code="$4"
local orig_input="$5"
local sdir="$GOLDEN_DIR/$scenario"
mkdir -p "$sdir/sideeffects"
echo "$stdout_content" | normalize "$sb" > "$sdir/stdout.txt"
echo "$exit_code" > "$sdir/exit_code.txt"
cp "$orig_input" "$sdir/input.json"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-start-fresh
# ---------------------------------------------------------------------------
run_session_start_fresh() {
echo ""
echo "--- session-start-fresh ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-start-fresh")
local sdir="$GOLDEN_DIR/session-start-fresh"
mkdir -p "$sdir/sideeffects"
# Setup: stamp file exists (fresh mtime) → rebuild NOT needed
mkdir -p "$sb/.cache/graphify"
touch "$sb/.cache/graphify/vault-rebuild.stamp"
# Record log offset
local log_offset=0
[[ -f /tmp/memory-hook.log ]] && log_offset=$(wc -c < /tmp/memory-hook.log)
# Expand __SANDBOX__ in input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-start-fresh.json" > "$input_expanded"
# Run hook
local stdout exit_code=0
stdout=$(HOME="$sb" PATH="$sb/bin:$PATH" GRAPHIFY_SHIM_LOG="$sb/graphify-shim.log" \
bash "$HOOKS_DIR/session-start.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Write main fixtures
write_fixture "session-start-fresh" "$sb" "$stdout" "$exit_code" "$INPUTS_DIR/session-start-fresh.json"
# Side effect: memory-hook.log delta
if [[ -f /tmp/memory-hook.log ]]; then
dd if=/tmp/memory-hook.log bs=1 skip="$log_offset" 2>/dev/null \
| normalize "$sb" > "$sdir/sideeffects/memory-hook-log-delta.txt"
else
echo "(no log entries)" > "$sdir/sideeffects/memory-hook-log-delta.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "log delta: $(cat "$sdir/sideeffects/memory-hook-log-delta.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-start-stale
# ---------------------------------------------------------------------------
run_session_start_stale() {
echo ""
echo "--- session-start-stale ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-start-stale")
local sdir="$GOLDEN_DIR/session-start-stale"
mkdir -p "$sdir/sideeffects"
# Setup: NO stamp file → rebuild needed
# (we do NOT create vault-rebuild.stamp)
# Record log offset
local log_offset=0
[[ -f /tmp/memory-hook.log ]] && log_offset=$(wc -c < /tmp/memory-hook.log)
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-start-stale.json" > "$input_expanded"
# Run hook
local stdout exit_code=0
stdout=$(HOME="$sb" PATH="$sb/bin:$PATH" GRAPHIFY_SHIM_LOG="$sb/graphify-shim.log" \
bash "$HOOKS_DIR/session-start.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Poll for graphify shim log (background nohup'd call)
for i in $(seq 1 10); do
[[ -f "$sb/graphify-shim.log" ]] && break
sleep 0.5
done
# Write main fixtures
write_fixture "session-start-stale" "$sb" "$stdout" "$exit_code" "$INPUTS_DIR/session-start-stale.json"
# Side effect: memory-hook.log delta
if [[ -f /tmp/memory-hook.log ]]; then
dd if=/tmp/memory-hook.log bs=1 skip="$log_offset" 2>/dev/null \
| normalize "$sb" > "$sdir/sideeffects/memory-hook-log-delta.txt"
else
echo "(no log entries)" > "$sdir/sideeffects/memory-hook-log-delta.txt"
fi
# Side effect: graphify shim args
if [[ -f "$sb/graphify-shim.log" ]]; then
normalize "$sb" < "$sb/graphify-shim.log" > "$sdir/sideeffects/graphify-shim-args.txt"
else
echo "(graphify not invoked within 5s)" > "$sdir/sideeffects/graphify-shim-args.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "log delta: $(cat "$sdir/sideeffects/memory-hook-log-delta.txt")"
echo "graphify shim: $(cat "$sdir/sideeffects/graphify-shim-args.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-context-first-with-graph
# ---------------------------------------------------------------------------
run_session_context_first_with_graph() {
echo ""
echo "--- session-context-first-with-graph ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-context-first-with-graph")
local sdir="$GOLDEN_DIR/session-context-first-with-graph"
mkdir -p "$sdir/sideeffects"
# Setup: create a real git repo with graph.json
mkdir -p "$sb/vault-project/graphify-out"
echo '{"nodes":[],"edges":[]}' > "$sb/vault-project/graphify-out/graph.json"
/usr/bin/git init "$sb/vault-project" >/dev/null 2>&1
/usr/bin/git -C "$sb/vault-project" config user.email "test@test.com"
/usr/bin/git -C "$sb/vault-project" config user.name "Test"
/usr/bin/git -C "$sb/vault-project" commit --allow-empty -m "init" >/dev/null 2>&1
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-context-first-with-graph.json" > "$input_expanded"
# Run hook from vault-project cwd
local stdout exit_code=0
stdout=$(cd "$sb/vault-project" && HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/session-context.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Write main fixtures
write_fixture "session-context-first-with-graph" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/session-context-first-with-graph.json"
# Side effect: flag file created?
if [[ -f /tmp/memory-context-injected-golden-sess-003 ]]; then
echo "yes" > "$sdir/sideeffects/flag-file-created.txt"
else
echo "no" > "$sdir/sideeffects/flag-file-created.txt"
fi
echo "stdout (normalized): $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "flag created: $(cat "$sdir/sideeffects/flag-file-created.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-context-reentry
# ---------------------------------------------------------------------------
run_session_context_reentry() {
echo ""
echo "--- session-context-reentry ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-context-reentry")
local sdir="$GOLDEN_DIR/session-context-reentry"
mkdir -p "$sdir/sideeffects"
# Setup: flag file already exists (reentry)
touch /tmp/memory-context-injected-golden-sess-004
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-context-reentry.json" > "$input_expanded"
# Run hook from /tmp
local stdout exit_code=0
stdout=$(cd /tmp && HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/session-context.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Write main fixtures
write_fixture "session-context-reentry" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/session-context-reentry.json"
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-context-no-graph
# ---------------------------------------------------------------------------
run_session_context_no_graph() {
echo ""
echo "--- session-context-no-graph ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-context-no-graph")
local sdir="$GOLDEN_DIR/session-context-no-graph"
mkdir -p "$sdir/sideeffects"
# Setup: git repo but NO graphify-out/graph.json
mkdir -p "$sb/noproject"
/usr/bin/git init "$sb/noproject" >/dev/null 2>&1
/usr/bin/git -C "$sb/noproject" config user.email "test@test.com"
/usr/bin/git -C "$sb/noproject" config user.name "Test"
/usr/bin/git -C "$sb/noproject" commit --allow-empty -m "init" >/dev/null 2>&1
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-context-no-graph.json" > "$input_expanded"
# Run hook from noproject cwd
local stdout exit_code=0
stdout=$(cd "$sb/noproject" && HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/session-context.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Write main fixtures
write_fixture "session-context-no-graph" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/session-context-no-graph.json"
echo "stdout (normalized): $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: post-tool-use-vault-md
# ---------------------------------------------------------------------------
run_post_tool_use_vault_md() {
echo ""
echo "--- post-tool-use-vault-md ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "post-tool-use-vault-md")
local sdir="$GOLDEN_DIR/post-tool-use-vault-md"
mkdir -p "$sdir/sideeffects"
# Setup: fresh stamp + vault notes dir
touch "$sb/.cache/graphify/vault-rebuild.stamp"
mkdir -p "$sb/vault/notes"
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/post-tool-use-vault-md.json" > "$input_expanded"
# Run hook
local stdout exit_code=0
stdout=$(HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/post-tool-use-write.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Write main fixtures
write_fixture "post-tool-use-vault-md" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/post-tool-use-vault-md.json"
# Side effect: touch file content
if [[ -f /tmp/claude-vault-touched-golden-sess-006 ]]; then
normalize "$sb" < /tmp/claude-vault-touched-golden-sess-006 \
> "$sdir/sideeffects/touch-file.txt"
else
echo "(not created)" > "$sdir/sideeffects/touch-file.txt"
fi
# Side effect: post-tool-use.log
if [[ -f "$sb/.cache/graphify/post-tool-use.log" ]]; then
normalize "$sb" < "$sb/.cache/graphify/post-tool-use.log" \
> "$sdir/sideeffects/post-tool-use-log.txt"
else
echo "(not created)" > "$sdir/sideeffects/post-tool-use-log.txt"
fi
# Side effect: stamp deleted?
if [[ ! -f "$sb/.cache/graphify/vault-rebuild.stamp" ]]; then
echo "yes" > "$sdir/sideeffects/stamp-deleted.txt"
else
echo "no" > "$sdir/sideeffects/stamp-deleted.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "touch-file: $(cat "$sdir/sideeffects/touch-file.txt")"
echo "log: $(cat "$sdir/sideeffects/post-tool-use-log.txt")"
echo "stamp-deleted: $(cat "$sdir/sideeffects/stamp-deleted.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: post-tool-use-non-md
# ---------------------------------------------------------------------------
run_post_tool_use_non_md() {
echo ""
echo "--- post-tool-use-non-md ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "post-tool-use-non-md")
local sdir="$GOLDEN_DIR/post-tool-use-non-md"
mkdir -p "$sdir/sideeffects"
mkdir -p "$sb/vault/notes"
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/post-tool-use-non-md.json" > "$input_expanded"
# Run hook
local stdout exit_code=0
stdout=$(HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/post-tool-use-write.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
write_fixture "post-tool-use-non-md" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/post-tool-use-non-md.json"
# Confirm touch file and log were NOT created (no-op)
if [[ -f /tmp/claude-vault-touched-golden-sess-006b ]]; then
echo "UNEXPECTED: touch file was created" > "$sdir/sideeffects/noops.txt"
else
echo "ok: no touch file (non-md guard fired)" > "$sdir/sideeffects/noops.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "noops: $(cat "$sdir/sideeffects/noops.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: post-tool-use-outside-vault
# ---------------------------------------------------------------------------
run_post_tool_use_outside_vault() {
echo ""
echo "--- post-tool-use-outside-vault ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "post-tool-use-outside-vault")
local sdir="$GOLDEN_DIR/post-tool-use-outside-vault"
mkdir -p "$sdir/sideeffects"
# Expand input (no __SANDBOX__ in this one but run through sed anyway)
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/post-tool-use-outside-vault.json" > "$input_expanded"
# Run hook
local stdout exit_code=0
stdout=$(HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/post-tool-use-write.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
write_fixture "post-tool-use-outside-vault" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/post-tool-use-outside-vault.json"
if [[ -f /tmp/claude-vault-touched-golden-sess-006c ]]; then
echo "UNEXPECTED: touch file was created" > "$sdir/sideeffects/noops.txt"
else
echo "ok: no touch file (outside-vault guard fired)" > "$sdir/sideeffects/noops.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "noops: $(cat "$sdir/sideeffects/noops.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-end-with-touches
# SAFETY: real HEAD must be verified after this run.
# ---------------------------------------------------------------------------
run_session_end_with_touches() {
echo ""
echo "--- session-end-with-touches ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-end-with-touches")
local sdir="$GOLDEN_DIR/session-end-with-touches"
mkdir -p "$sdir/sideeffects"
# Git shim preflight
echo " [preflight] Testing git shim..."
"$sb/bin/git" -C /home/jared/.memsearch status >/dev/null 2>/dev/null
if [[ -f "$sb/git-shim.log" ]]; then
echo " [preflight] PASS — shim intercepted /home/jared/.memsearch call"
cat "$sb/git-shim.log"
# Clear the preflight entry so fixture only captures hook-time calls
rm -f "$sb/git-shim.log"
else
echo " [preflight] WARNING — shim log not found; proceeding anyway"
fi
# Setup: touch file with two paths
printf '%s\n%s\n' "${sb}/vault/notes/test.md" "${sb}/vault/notes/other.md" \
> /tmp/claude-vault-touched-golden-sess-007
# Ensure journal dir exists
mkdir -p "$sb/vault/journal"
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-end-with-touches.json" > "$input_expanded"
# Run hook from /tmp (avoids git rev-parse returning cc-os repo as project)
local stdout exit_code=0
stdout=$(cd /tmp && HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/session-end.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# IMMEDIATE HEAD CHECK after first session-end run
local head_after
head_after=$(git -C /home/jared/.memsearch rev-parse HEAD 2>/dev/null || echo "NO_REAL_REPO")
echo " [safety] HEAD after session-end-with-touches: $head_after"
if [[ "$head_after" != "$REAL_HEAD_BEFORE" ]]; then
echo "SAFETY BREACH: memsearch HEAD changed! $head_after != $REAL_HEAD_BEFORE" >&2
rm -rf "$sb"
exit 99
fi
echo " [safety] PASS"
write_fixture "session-end-with-touches" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/session-end-with-touches.json"
# Side effect: journal content (find by glob, not recomputing date)
local journal_file
journal_file=$(ls "$sb/vault/journal/"*.md 2>/dev/null | head -1)
if [[ -n "$journal_file" && -f "$journal_file" ]]; then
normalize "$sb" < "$journal_file" > "$sdir/sideeffects/journal.txt"
else
echo "(journal file not created)" > "$sdir/sideeffects/journal.txt"
fi
# Side effect: git shim log
if [[ -f "$sb/git-shim.log" ]]; then
normalize "$sb" < "$sb/git-shim.log" > "$sdir/sideeffects/git-shim-log.txt"
else
echo "(no git shim log — memsearch block not triggered?)" > "$sdir/sideeffects/git-shim-log.txt"
fi
# Side effect: touch file deleted?
if [[ ! -f /tmp/claude-vault-touched-golden-sess-007 ]]; then
echo "deleted" > "$sdir/sideeffects/touch-file-deleted.txt"
else
echo "NOT deleted (unexpected)" > "$sdir/sideeffects/touch-file-deleted.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "journal (first 10 lines):"
head -10 "$sdir/sideeffects/journal.txt" || true
echo "git-shim-log: $(cat "$sdir/sideeffects/git-shim-log.txt")"
echo "touch-file-deleted: $(cat "$sdir/sideeffects/touch-file-deleted.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# SCENARIO: session-end-no-touches
# ---------------------------------------------------------------------------
run_session_end_no_touches() {
echo ""
echo "--- session-end-no-touches ---"
cleanup_tmp_flags
local sb
sb=$(make_sandbox "session-end-no-touches")
local sdir="$GOLDEN_DIR/session-end-no-touches"
mkdir -p "$sdir/sideeffects"
# Setup: NO touch file for golden-sess-008
mkdir -p "$sb/vault/journal"
# Expand input
local input_expanded="$sb/input-expanded.json"
sed "s|__SANDBOX__|${sb}|g" "$INPUTS_DIR/session-end-no-touches.json" > "$input_expanded"
# Run hook from /tmp
local stdout exit_code=0
stdout=$(cd /tmp && HOME="$sb" PATH="$sb/bin:$PATH" \
bash "$HOOKS_DIR/session-end.sh" < "$input_expanded" 2>/dev/null) || exit_code=$?
# Safety check
local head_after
head_after=$(git -C /home/jared/.memsearch rev-parse HEAD 2>/dev/null || echo "NO_REAL_REPO")
echo " [safety] HEAD after session-end-no-touches: $head_after"
if [[ "$head_after" != "$REAL_HEAD_BEFORE" ]]; then
echo "SAFETY BREACH: memsearch HEAD changed!" >&2
rm -rf "$sb"
exit 99
fi
echo " [safety] PASS"
write_fixture "session-end-no-touches" "$sb" "$stdout" "$exit_code" \
"$INPUTS_DIR/session-end-no-touches.json"
# Side effect: journal content
local journal_file
journal_file=$(ls "$sb/vault/journal/"*.md 2>/dev/null | head -1)
if [[ -n "$journal_file" && -f "$journal_file" ]]; then
normalize "$sb" < "$journal_file" > "$sdir/sideeffects/journal.txt"
else
echo "(journal file not created)" > "$sdir/sideeffects/journal.txt"
fi
# Side effect: git shim log
if [[ -f "$sb/git-shim.log" ]]; then
normalize "$sb" < "$sb/git-shim.log" > "$sdir/sideeffects/git-shim-log.txt"
else
echo "(no git shim log — memsearch block not triggered?)" > "$sdir/sideeffects/git-shim-log.txt"
fi
echo "stdout: $(cat "$sdir/stdout.txt")"
echo "exit_code: $exit_code"
echo "journal (first 10 lines):"
head -10 "$sdir/sideeffects/journal.txt" || true
echo "git-shim-log: $(cat "$sdir/sideeffects/git-shim-log.txt")"
rm -rf "$sb"
}
# ---------------------------------------------------------------------------
# Run all scenarios
# ---------------------------------------------------------------------------
echo "=== Generating golden fixtures ==="
run_session_start_fresh
run_session_start_stale
run_session_context_first_with_graph
run_session_context_reentry
run_session_context_no_graph
run_post_tool_use_vault_md
run_post_tool_use_non_md
run_post_tool_use_outside_vault
run_session_end_with_touches # safety-critical — HEAD checked immediately after
run_session_end_no_touches
# ---------------------------------------------------------------------------
# Final safety check
# ---------------------------------------------------------------------------
echo ""
echo "=== FINAL SAFETY CHECK ==="
REAL_HEAD_AFTER=$(git -C /home/jared/.memsearch rev-parse HEAD 2>/dev/null || echo "NO_REAL_REPO")
echo "BEFORE: $REAL_HEAD_BEFORE"
echo "AFTER: $REAL_HEAD_AFTER"
if [[ "$REAL_HEAD_AFTER" == "$REAL_HEAD_BEFORE" ]]; then
echo "SAFETY CHECK PASSED — real memsearch HEAD unchanged"
else
echo "SAFETY CHECK FAILED — HEAD CHANGED from $REAL_HEAD_BEFORE to $REAL_HEAD_AFTER"
exit 1
fi
echo ""
echo "=== All fixtures written to $GOLDEN_DIR ==="
find "$GOLDEN_DIR" -type f | sort