Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions bin/revive
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,18 @@ cmd_version() { echo "revive $VERSION"; }

log() {
mkdir -p "$LOG_DIR" 2>/dev/null || return 0
printf '[%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$*" \
# Tilde-substitute $HOME so multi-project logs stay scannable. Match
# only on a path boundary — `${PWD/#$HOME/~}` would happily rewrite
# `/Users/alice-work/project` to `~-work/project` when HOME is
# `/Users/alice` (codex P2). Use case to require either exact match
# or `$HOME/` prefix.
local cwd_short
case "$PWD" in
"$HOME") cwd_short="~" ;;
"$HOME"/*) cwd_short="~${PWD#"$HOME"}" ;;
*) cwd_short="$PWD" ;;
esac
printf '[%s] [%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$cwd_short" "$*" \
>> "$LOG_FILE" 2>/dev/null || true
}

Expand Down Expand Up @@ -411,8 +422,14 @@ cadence_gate() {
# most of its context — that's exactly when re-injecting the brief gives
# the highest ROI, so bypass cadence and emit immediately.
if [[ -f "$COMPACT_SIGNAL" ]]; then
# The signal file payload identifies the trigger ("compact" / "clear").
# Sanitize to a-z only — defense-in-depth in case content was mangled.
# Empty or unknown content falls back to a neutral label.
local source
source=$(head -1 "$COMPACT_SIGNAL" 2>/dev/null | tr -cd '[:lower:]')
[[ "$source" == compact || "$source" == clear ]] || source="context-loss"
rm -f "$COMPACT_SIGNAL" 2>/dev/null || true
log "post-compact: forcing emit (counter=$n)"
log "post-${source}: forcing emit (counter=$n)"
emit=1
elif (( n == 1 )); then emit=1
elif (( n % REFRESH_EVERY == 0 )); then emit=1
Expand Down Expand Up @@ -446,14 +463,14 @@ cmd_show() {
# path and must never break a Claude Code session.
cmd_mark_compact() {
mkdir -p "$(dirname "$COMPACT_SIGNAL")" 2>/dev/null || return 0
date +%s > "$COMPACT_SIGNAL" 2>/dev/null || return 0
printf 'compact\n' > "$COMPACT_SIGNAL" 2>/dev/null || return 0
log "post-compact signal written: $COMPACT_SIGNAL"
return 0
}

cmd_mark_clear() {
mkdir -p "$(dirname "$COMPACT_SIGNAL")" 2>/dev/null || return 0
date +%s > "$COMPACT_SIGNAL" 2>/dev/null || return 0
printf 'clear\n' > "$COMPACT_SIGNAL" 2>/dev/null || return 0
log "post-clear signal written: $COMPACT_SIGNAL"
return 0
}
Expand Down
61 changes: 61 additions & 0 deletions tests/revive.bats
Original file line number Diff line number Diff line change
Expand Up @@ -1570,3 +1570,64 @@ JSON
[[ "$output" == *"PostCompact hook installed"* ]] || return 1
[[ "$output" == *"SessionStart(clear) hook installed"* ]] || return 1
}

# --- log enrichments ---

@test "log lines carry the cwd field for multi-project scanability" {
# mark-compact always logs (refresh on a fresh repo doesn't, since the
# counter-1 emit path doesn't go through log()). The mark-compact line
# must carry [<cwd>] so a global hook.log collected from many projects
# stays disambiguable.
mkdir -p .claude
"$REVIVE" mark-compact
run cat "$HOME/.context-revive/hook.log"
[[ "$output" == *"signal written"* ]] || return 1
# Format: [timestamp] [cwd] message — must have TWO bracketed fields
# before the message body. Regex held in a variable to avoid the bash
# =~ backslash-quoting differences between macOS bash 3.2 and CI's
# bash 5.x (literal \[ inside the inline pattern is unportable).
local re='\[[^]]+\] \[[^]]+\] post-compact'
[[ "$output" =~ $re ]] || return 1
}

@test "refresh log message reflects compact source" {
mkdir -p .claude
printf 'compact\n' > .claude/revive-compact.signal
"$REVIVE" refresh >/dev/null
run cat "$HOME/.context-revive/hook.log"
[[ "$output" == *"post-compact: forcing emit"* ]] || return 1
}

@test "refresh log message reflects clear source" {
mkdir -p .claude
printf 'clear\n' > .claude/revive-compact.signal
"$REVIVE" refresh >/dev/null
run cat "$HOME/.context-revive/hook.log"
[[ "$output" == *"post-clear: forcing emit"* ]] || return 1
[[ "$output" != *"post-compact: forcing emit"* ]] || return 1
}

@test "refresh handles a malformed signal payload with neutral label" {
mkdir -p .claude
# Garbage content — must NOT mislabel as compact/clear, must NOT crash.
printf '\x00\x01garbage\n' > .claude/revive-compact.signal
run "$REVIVE" refresh
[ "$status" -eq 0 ] || return 1
run cat "$HOME/.context-revive/hook.log"
[[ "$output" == *"post-context-loss: forcing emit"* ]] || return 1
}

@test "log cwd prefix-match respects path boundary (codex P2)" {
# When HOME is `/tmp/h` and PWD is `/tmp/h-other/project`, the lazy
# `${PWD/#$HOME/~}` would mis-emit `~-other/project` and the global
# log would attribute the message to the wrong project. The case-based
# rewrite must only fire on `$HOME` or `$HOME/...`.
mkdir -p "$WORKDIR-other/project/.claude"
cd "$WORKDIR-other/project"
HOME="$WORKDIR" "$REVIVE" mark-compact
run cat "$WORKDIR/.context-revive/hook.log"
[[ "$output" == *"signal written"* ]] || { rm -rf "$WORKDIR-other"; return 1; }
# The log line must NOT contain the spliced `~-other` artefact.
[[ "$output" != *"~-other"* ]] || { rm -rf "$WORKDIR-other"; return 1; }
rm -rf "$WORKDIR-other"
}
Loading