Skip to content

feat: post-compact refresh trigger#30

Merged
justi merged 3 commits intomainfrom
feat/post-compact-trigger
Apr 27, 2026
Merged

feat: post-compact refresh trigger#30
justi merged 3 commits intomainfrom
feat/post-compact-trigger

Conversation

@justi
Copy link
Copy Markdown
Owner

@justi justi commented Apr 27, 2026

Summary

Adds the third refresh trigger that was previously deferred as an open question in CLAUDE.md: fire immediately after a /compact (or AutoCompact) event.

Wire-up:

  • cmd_mark_compact — new PostCompact hook entry point. Writes .claude/revive-compact.signal (timestamp), exits 0 silently, mkdir best-effort. Same silent-failure contract as cmd_refresh.
  • cadence_gate — now checks for the signal first. If present, deletes it, logs post-compact: forcing emit, emits regardless of counter / time-gap. Counter still ticks so subsequent calls resume normal cadence.
  • cmd_install_hook — now wires both UserPromptSubmit → revive refresh AND PostCompact → revive mark-compact. Refactored upsert into an inner helper so idempotency works for both.

Why post-compact specifically

Agents lose most of their context after /compact. That is exactly when re-injecting the brief gives the highest ROI — waiting for the next 5-prompt boundary throws that window away. Confirmed feasibility via claude-code-guide: PostCompact is a documented hook event with manual / auto matchers.

Test plan

  • 8 new tests covering signal lifecycle (write, consume, counter advance), silent-failure contract, idempotence for both hooks, help-text surface
  • bats tests/ — 125/125 pass
  • shellcheck bin/revive install.sh clean
  • Live smoke: revive mark-compact writes signal → revive refresh emits brief and removes signal
  • revive doctor still all-green

Migration

Existing users: revive install-hook is idempotent — re-running it adds the PostCompact entry next to the existing UserPromptSubmit entry without duplication.

🤖 Generated with Claude Code

The third documented refresh trigger (alongside the every-5-prompts
counter and the >10-min gap) was deferred in CLAUDE.md as an open
question. Claude Code's hook system actually exposes `PostCompact`
with a JSON payload — feasibility is no longer a question.

Wire-up:

- New `cmd_mark_compact` writes `.claude/revive-compact.signal` with
  a unix timestamp. Like `cmd_refresh`, it is on the hook hot path:
  always exits 0, fails silently, mkdir best-effort.
- `cadence_gate` checks for the signal first. If present, it removes
  the signal, logs `post-compact: forcing emit`, and emits regardless
  of counter or time-gap. The counter still ticks so subsequent calls
  resume normal cadence.
- `cmd_install_hook` now also wires `PostCompact` → `revive mark-compact`
  alongside the existing `UserPromptSubmit` → `revive refresh` entry.
  Refactored the upsert into a small inner helper so both events go
  through the same idempotency check.

Why post-compact specifically: agents lose most of their context when
the user runs `/compact` (or AutoCompact fires). That is precisely
when re-injecting a brief produces the highest ROI — waiting for the
next 5-prompt boundary throws that opportunity away.

8 new tests cover signal lifecycle (write, consume, counter advance),
the silent-failure contract, install-hook idempotence for both hooks,
and the help-text surface. 125 / 125 pass; shellcheck clean.
Copilot AI review requested due to automatic review settings April 27, 2026 06:27
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “post-compact” refresh trigger to revive so that immediately after a Claude Code /compact (or AutoCompact), the next revive refresh bypasses cadence and emits the brief.

Changes:

  • Introduces a mark-compact subcommand that writes a .claude/revive-compact.signal file.
  • Updates cadence_gate to detect/consume the signal and force an immediate emit.
  • Extends install-hook to wire both UserPromptSubmit and PostCompact, plus adds Bats coverage for the new behavior.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated no comments.

File Description
bin/revive Adds compact signal + mark-compact, forces cadence override when signal exists, and installs the new PostCompact hook.
tests/revive.bats Adds tests for signal lifecycle, forced refresh behavior, and install-hook idempotency across both hook events.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Codex flagged that doctor only validates `UserPromptSubmit`, so an
upgraded install that hasn't re-run `install-hook` would silently
miss the new `PostCompact` wire-up — `/compact` refresh would never
fire and doctor would still say "all checks passed".

Refactored the hook-check into an inner helper `_doctor_check_hook`
that takes (event, command-pattern). Calls it twice — once for
UserPromptSubmit/refresh, once for PostCompact/mark-compact. Same
jq-then-grep fallback semantics as before.

New test "doctor warns when only UserPromptSubmit is wired (upgrade
gap)" reproduces the exact scenario codex called out: hand-craft a
settings.json with just the legacy hook, confirm doctor surfaces the
missing PostCompact entry.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/revive.bats Outdated
Comment on lines +1374 to +1376
chmod -w .
run "$REVIVE" mark-compact
chmod +w .
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test relies on chmod -w . to make the directory unwritable, but it doesn’t assert that the chmod calls succeeded. If chmod fails (platform/FS quirks), the test can pass while not actually exercising the silent-failure path. Add || return 1 (or equivalent) after the permission changes (and ideally ensure permissions are restored via a trap) to avoid false positives.

Suggested change
chmod -w .
run "$REVIVE" mark-compact
chmod +w .
trap 'chmod +w . || true' RETURN
chmod -w . || return 1
run "$REVIVE" mark-compact

Copilot uses AI. Check for mistakes.
The silent-failure test could pass without exercising the
silent-failure path if `chmod -w .` itself failed (e.g., FS
quirk on CI). Add `|| return 1` so the test fails fast, and a
RETURN trap so the write permission is restored even if a later
assertion bails — otherwise a leaked read-only WORKDIR breaks
teardown.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@justi justi merged commit 23535be into main Apr 27, 2026
7 checks passed
@justi justi deleted the feat/post-compact-trigger branch April 27, 2026 07:05
justi added a commit that referenced this pull request Apr 27, 2026
* docs(readme): document the post-compact refresh trigger

Cadence section now lists four triggers (was three): first prompt,
every-N counter, time-gap, and the new post-compact override.
Quick start mentions install-hook now wires both UserPromptSubmit
and PostCompact.

* feat: refresh trigger after `/clear`

Sibling to the post-compact trigger from #30. `/clear` wipes more
than `/compact` (full conversation reset), but the recovery path is
identical — re-inject the brief on the next prompt, bypassing
cadence. Wired through Claude Code's `SessionStart` hook with
`matcher: "clear"`, the documented dedicated signal.

Implementation:
- New `cmd_mark_clear` writes the same `.claude/revive-compact.signal`
  as `cmd_mark_compact`. Two commands so settings.json reads naturally
  and hook.log distinguishes the source.
- `cmd_install_hook` now wires three hooks. Refactored the upsert
  helper to take an optional `matcher`, so SessionStart{clear} stays
  distinct from any user-added SessionStart{startup} or {resume} entry
  (idempotency key: event + matcher + command).
- `cmd_doctor` adds a third hook check via the existing
  `_doctor_check_hook` helper (`SessionStart` + `revive mark-clear`).
- README cadence section gains trigger #5; install-hook line now
  lists all three events.

6 new tests cover: signal write, help-text surface, settings.json
shape (matcher field present), 3-hook idempotency, doctor warn on
missing SessionStart, doctor green path with all three hooks.

* release: v0.2.0

Headline: context-loss recovery. Refresh now fires immediately after
`/compact` (#30) and `/clear` — the two moments when an agent has
just lost most of its working memory and the brief gives the highest
ROI. Three hook events wired by `install-hook`: UserPromptSubmit,
PostCompact, SessionStart(matcher=clear).

Also since v0.1.19:
- `revive init` auto-fixes `.gitignore` so static.md is trackable (#31)
- README tagline rewrite (#31)

* fix(doctor): validate SessionStart matcher (codex P3)

`_doctor_check_hook` accepted any SessionStart entry pointing at
`revive mark-clear` regardless of its `matcher` value. A user who
hand-wired SessionStart{startup} → mark-clear (or no matcher at
all) would pass doctor, but `/clear` would never trigger that
hook. Codex flagged the false-positive on the v0.2.0 PR.

Two changes:

1. The helper takes an optional 3rd arg `matcher`. When set, the
   jq query also asserts `(.matcher // "") == $m` so only the
   intended entry counts. Doctor labels include the matcher in
   parens — `SessionStart(clear) hook installed`.

2. The grep fallback used to override a legitimate "no match"
   from jq because it ran on any non-zero jq exit. Distinguish
   jq's exit codes:
     - 0 → match;
     - 1 → legitimate no-match (trust it, skip grep);
     - ≥2 → jq error or absent → grep fallback (best-effort,
            can't tie matcher to command across JSON lines).
   Without that, the matcher check would have been silently
   defeated whenever jq returned 1 and grep happened to find
   the command string anywhere in the file.

Also: `set -e` interaction. The naive `jq ...; rc=$?` pattern
would abort the function on jq's non-zero exit before the
assignment. Wrapping in `if jq ...; then rc=0; else rc=$?; fi`
preserves the code.

Tests:
- "doctor warns when SessionStart entry has wrong matcher" — new
  regression test for codex P3, hand-crafts settings.json with
  matcher=startup pointing at mark-clear, asserts the warning.
- Existing missing-SessionStart and all-three-hooks tests
  updated to expect the `(clear)` label.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants