diff --git a/README.md b/README.md index 7ab166b..5615ce6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # context-revive -> Your side project died at 80%. Bring it back in one prompt. +> Your agent forgot the ADR you wrote 30 prompts ago. **Re-inject a dense, deterministic project brief into Claude Code every few prompts, before context rot degrades the agent.** You edit `.revive/static.md` diff --git a/bin/revive b/bin/revive index 0f9b6f8..866e705 100755 --- a/bin/revive +++ b/bin/revive @@ -697,6 +697,57 @@ cmd_init() { else echo "created: $STATIC_FILE" fi + + maybe_update_gitignore +} + +# If $STATIC_FILE would currently be gitignored, append the canonical +# exception block to .gitignore so the file can actually be tracked. Stays +# silent when not in a git repo, when there's no .gitignore yet, or when +# static.md is already trackable. If a directory-level `.revive/` rule +# blocks the un-ignore from taking effect, surface a clear warning instead +# of silently leaving the file untracked. +maybe_update_gitignore() { + git rev-parse --git-dir >/dev/null 2>&1 || return 0 + [[ -f .gitignore ]] || return 0 + git check-ignore -q "$STATIC_FILE" 2>/dev/null || return 0 + + # Best-effort: a read-only .gitignore must not abort `init`. Surface a + # warning with the exact lines the user needs to add, and continue. + if [[ ! -w .gitignore ]]; then + cat >&2 <> .gitignore <<'GITIGNORE' + +# revive: .revive/static.md is the curated STATIC layer (checked in); +# other .revive/ contents are local runtime state. +!.revive/ +.revive/* +!.revive/static.md +GITIGNORE + + if git check-ignore -q "$STATIC_FILE" 2>/dev/null; then + cat >&2 < .gitignore + run "$REVIVE" init + [ "$status" -eq 0 ] || return 1 + [[ "$output" == *"un-ignored"* ]] || return 1 + run cat .gitignore + [[ "$output" == *"!.revive/static.md"* ]] || return 1 + # static.md is now actually trackable + run git check-ignore -q .revive/static.md + [ "$status" -ne 0 ] || return 1 +} + +@test "init leaves .gitignore alone when static.md is already trackable" { + printf '.revive/*\n!.revive/static.md\n' > .gitignore + local before + before=$(wc -l < .gitignore) + run "$REVIVE" init + [ "$status" -eq 0 ] || return 1 + local after + after=$(wc -l < .gitignore) + [ "$before" -eq "$after" ] || return 1 +} + +@test "init un-ignores static.md even with a directory-level .revive/ rule" { + # Codex P2 regression check: a `.revive/` rule used to leave static.md + # untrackable. The canonical block now leads with `!.revive/` so the + # un-ignore works regardless. + printf '.revive/\n' > .gitignore + run "$REVIVE" init + [ "$status" -eq 0 ] || return 1 + [[ "$output" == *"un-ignored"* ]] || return 1 + run git check-ignore -q .revive/static.md + [ "$status" -ne 0 ] || return 1 +} + +@test "init un-ignores static.md against a broad dot-dir rule (.*)" { + # Common case: repos that ignore all dot-directories with `.*`. Without + # the leading `!.revive/`, the file-level exception cannot rescue + # static.md from the broader directory ignore. + printf '.*\n' > .gitignore + run "$REVIVE" init + [ "$status" -eq 0 ] || return 1 + run git check-ignore -q .revive/static.md + [ "$status" -ne 0 ] || return 1 +} + +@test "init does not abort when .gitignore is read-only" { + # Codex P3: `cat >> .gitignore` under `set -e` would terminate cmd_init + # if the file is read-only. Best-effort path must warn and continue. + printf '.revive/*\n' > .gitignore + chmod -w .gitignore + trap 'chmod +w .gitignore || true' RETURN + run "$REVIVE" init + [ "$status" -eq 0 ] || return 1 + [[ "$output" == *"not writable"* ]] || return 1 + [ -f .revive/static.md ] || return 1 +}