674 lines
24 KiB
Bash
Executable File
674 lines
24 KiB
Bash
Executable File
#!/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/memory"
|
|
cat > "$sb/.claude/plugins/memory/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
|