Skip to content

Commit 8b6bb09

Browse files
authored
feat(log): per-line cwd + source-aware emit label (#33)
* feat(log): per-line cwd + source-aware emit label Two annoyances surfaced when dogfooding v0.2.0 across multiple projects: 1. Lines in the global `~/.context-revive/hook.log` did not carry any project context. With revive wired in 5+ repos, the same `post-compact: forcing emit` message could be from any of them — un-debuggable. 2. The forcing-emit log line said `post-compact:` even when the trigger was `/clear`, because both paths shared one signal file and `cadence_gate` had no idea which command wrote it. Fixes: - `log()` now prepends `[<cwd>]` after the timestamp, with a tilde substitution for paths under $HOME. A subtle bash gotcha: `${VAR/old/\~}` leaks the backslash; bash does NOT tilde-expand inside parameter-substitution replacements, so the literal `~` is correct (and was wrong on my first attempt — caught in smoke before commit). - `cmd_mark_compact` and `cmd_mark_clear` write `compact` / `clear` to the signal file (was: a unix timestamp, never read). The signal filename stays `revive-compact.signal` to keep the on-disk path stable across upgrades. - `cadence_gate` reads the payload, sanitizes via `tr -cd '[:lower:]'`, and emits either `post-compact:` or `post-clear:` accordingly. Unknown / malformed content falls back to a neutral `post-context-loss:` label. 4 new tests cover: cwd field present in log, compact label, clear label, malformed payload → neutral label without crashing. * test(log): hold cwd-format regex in a variable for portability The inline `[[ "$output" =~ \[[^]]+\]\ \[[^]]+\]\ post-compact ]]` passed on macOS bats but failed on Ubuntu bats in CI — bash 3.2 vs 5.x differ on how literal `\[` inside the pattern is parsed. The canonical workaround is to bind the pattern to a local variable and reference it as `$re`. No content change to the assertion. * fix(log): match $HOME on path boundary only (codex P2) `${PWD/#$HOME/~}` did a string-prefix rewrite without a path boundary check, so a checkout in `$HOME-something/...` (e.g. HOME=/Users/alice and PWD=/Users/alice-work/project) would log as `~-work/project` — wrong project name in a feature that exists specifically to disambiguate projects. Replace with a case statement that fires only on the exact $HOME match or the `$HOME/` prefix; falls through to the absolute path otherwise. New test pins this: stages a sibling worktree at `$WORKDIR-other` with HOME=$WORKDIR, runs mark-compact, and asserts the log line does NOT contain the `~-other` splice artefact. * test(log): clean up compact-source test setup (copilot) Replaced the convoluted `printf > .claude/... 2>/dev/null || mkdir -p .claude && printf > .claude/...` with the same two-line pattern the sibling clear-source and garbage-payload tests already use. Bash redirect failures land on the original stderr before `2>/dev/null` is applied, so the old form would have leaked an error noise even when functionally correct.
1 parent 0379a3e commit 8b6bb09

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

bin/revive

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,18 @@ cmd_version() { echo "revive $VERSION"; }
5353

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

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

454471
cmd_mark_clear() {
455472
mkdir -p "$(dirname "$COMPACT_SIGNAL")" 2>/dev/null || return 0
456-
date +%s > "$COMPACT_SIGNAL" 2>/dev/null || return 0
473+
printf 'clear\n' > "$COMPACT_SIGNAL" 2>/dev/null || return 0
457474
log "post-clear signal written: $COMPACT_SIGNAL"
458475
return 0
459476
}

tests/revive.bats

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,3 +1570,64 @@ JSON
15701570
[[ "$output" == *"PostCompact hook installed"* ]] || return 1
15711571
[[ "$output" == *"SessionStart(clear) hook installed"* ]] || return 1
15721572
}
1573+
1574+
# --- log enrichments ---
1575+
1576+
@test "log lines carry the cwd field for multi-project scanability" {
1577+
# mark-compact always logs (refresh on a fresh repo doesn't, since the
1578+
# counter-1 emit path doesn't go through log()). The mark-compact line
1579+
# must carry [<cwd>] so a global hook.log collected from many projects
1580+
# stays disambiguable.
1581+
mkdir -p .claude
1582+
"$REVIVE" mark-compact
1583+
run cat "$HOME/.context-revive/hook.log"
1584+
[[ "$output" == *"signal written"* ]] || return 1
1585+
# Format: [timestamp] [cwd] message — must have TWO bracketed fields
1586+
# before the message body. Regex held in a variable to avoid the bash
1587+
# =~ backslash-quoting differences between macOS bash 3.2 and CI's
1588+
# bash 5.x (literal \[ inside the inline pattern is unportable).
1589+
local re='\[[^]]+\] \[[^]]+\] post-compact'
1590+
[[ "$output" =~ $re ]] || return 1
1591+
}
1592+
1593+
@test "refresh log message reflects compact source" {
1594+
mkdir -p .claude
1595+
printf 'compact\n' > .claude/revive-compact.signal
1596+
"$REVIVE" refresh >/dev/null
1597+
run cat "$HOME/.context-revive/hook.log"
1598+
[[ "$output" == *"post-compact: forcing emit"* ]] || return 1
1599+
}
1600+
1601+
@test "refresh log message reflects clear source" {
1602+
mkdir -p .claude
1603+
printf 'clear\n' > .claude/revive-compact.signal
1604+
"$REVIVE" refresh >/dev/null
1605+
run cat "$HOME/.context-revive/hook.log"
1606+
[[ "$output" == *"post-clear: forcing emit"* ]] || return 1
1607+
[[ "$output" != *"post-compact: forcing emit"* ]] || return 1
1608+
}
1609+
1610+
@test "refresh handles a malformed signal payload with neutral label" {
1611+
mkdir -p .claude
1612+
# Garbage content — must NOT mislabel as compact/clear, must NOT crash.
1613+
printf '\x00\x01garbage\n' > .claude/revive-compact.signal
1614+
run "$REVIVE" refresh
1615+
[ "$status" -eq 0 ] || return 1
1616+
run cat "$HOME/.context-revive/hook.log"
1617+
[[ "$output" == *"post-context-loss: forcing emit"* ]] || return 1
1618+
}
1619+
1620+
@test "log cwd prefix-match respects path boundary (codex P2)" {
1621+
# When HOME is `/tmp/h` and PWD is `/tmp/h-other/project`, the lazy
1622+
# `${PWD/#$HOME/~}` would mis-emit `~-other/project` and the global
1623+
# log would attribute the message to the wrong project. The case-based
1624+
# rewrite must only fire on `$HOME` or `$HOME/...`.
1625+
mkdir -p "$WORKDIR-other/project/.claude"
1626+
cd "$WORKDIR-other/project"
1627+
HOME="$WORKDIR" "$REVIVE" mark-compact
1628+
run cat "$WORKDIR/.context-revive/hook.log"
1629+
[[ "$output" == *"signal written"* ]] || { rm -rf "$WORKDIR-other"; return 1; }
1630+
# The log line must NOT contain the spliced `~-other` artefact.
1631+
[[ "$output" != *"~-other"* ]] || { rm -rf "$WORKDIR-other"; return 1; }
1632+
rm -rf "$WORKDIR-other"
1633+
}

0 commit comments

Comments
 (0)