Skip to content

Commit fd8c813

Browse files
authored
feat(init): auto-update .gitignore + tighter tagline (#31)
* docs(readme): tighten tagline to name the actual problem "Your side project died at 80%. Bring it back in one prompt." was cryptic — readers had to guess what 80% referred to (project abandonment? completion?), and nothing in the line connected to the actual problem the tool solves (agent context rot in long sessions). Replace with the literal failure mode users hit in practice: "Your agent forgot the ADR you wrote 30 prompts ago." Names the pain directly, no metaphor decoding required. The next paragraph already explains the mechanism, so the tagline can stay terse. * feat(init): auto-update .gitignore so static.md is trackable A common onboarding stumble: user follows the README, runs `revive init`, then `git add .revive/static.md` — git silently skips the file because their .gitignore has `.revive/*` (or similar) and no exception. The "checked in" workflow the README documents quietly fails. `cmd_init` now calls `maybe_update_gitignore` after writing static.md. Logic: - Skip silently if not in a git repo, no .gitignore exists, or `git check-ignore` says the file is already trackable. - Otherwise append a small canonical block: .revive/* !.revive/static.md with a header comment explaining what gets tracked vs not. - Re-run `git check-ignore`. If a directory-level `.revive/` rule still suppresses the un-ignore (git semantics: `!` cannot rescue a file inside a fully-ignored directory), print a warning telling the user how to fix it. 4 new tests cover: no .gitignore (no-op), `.revive/*` (append + verify trackable), already-correct (idempotent — line count unchanged), `.revive/` directory rule (warning emitted, exit 0 preserved). * fix(init/gitignore): handle broad dot-dir + read-only cases (codex) Two issues codex flagged on the original gitignore-auto patch: [P2] Broad dot-dir rules left static.md untrackable - A repo with `.revive/`, `.revive`, or even `.*` / `.*/` already ignores the parent directory. Git semantics: `!` cannot rescue a file inside a fully-ignored directory. - The canonical block now leads with `!.revive/` to un-ignore the parent first, then re-ignores contents with `.revive/*`, then re-includes static.md with `!.revive/static.md`. - The "warns when a directory-level .revive/ rule blocks" test flipped to "un-ignores even with a directory-level rule" — the former failure mode no longer happens. Added a parallel test for `.*` to cover the broad-rule case. [P3] Read-only .gitignore aborted `revive init` - Under `set -e`, `cat >> .gitignore` on a read-only file killed cmd_init even though the gitignore update is best-effort. - Pre-check writability with `[[ -w .gitignore ]]`. If not writable, print the exact lines the user needs to add and return 0 — static.md is already created at this point. - New test exercises the read-only path: chmod -w .gitignore, trap restores write perm so teardown doesn't break.
1 parent 23535be commit fd8c813

3 files changed

Lines changed: 118 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# context-revive
22

3-
> Your side project died at 80%. Bring it back in one prompt.
3+
> Your agent forgot the ADR you wrote 30 prompts ago.
44
55
**Re-inject a dense, deterministic project brief into Claude Code every few
66
prompts, before context rot degrades the agent.** You edit `.revive/static.md`

bin/revive

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,57 @@ cmd_init() {
697697
else
698698
echo "created: $STATIC_FILE"
699699
fi
700+
701+
maybe_update_gitignore
702+
}
703+
704+
# If $STATIC_FILE would currently be gitignored, append the canonical
705+
# exception block to .gitignore so the file can actually be tracked. Stays
706+
# silent when not in a git repo, when there's no .gitignore yet, or when
707+
# static.md is already trackable. If a directory-level `.revive/` rule
708+
# blocks the un-ignore from taking effect, surface a clear warning instead
709+
# of silently leaving the file untracked.
710+
maybe_update_gitignore() {
711+
git rev-parse --git-dir >/dev/null 2>&1 || return 0
712+
[[ -f .gitignore ]] || return 0
713+
git check-ignore -q "$STATIC_FILE" 2>/dev/null || return 0
714+
715+
# Best-effort: a read-only .gitignore must not abort `init`. Surface a
716+
# warning with the exact lines the user needs to add, and continue.
717+
if [[ ! -w .gitignore ]]; then
718+
cat >&2 <<MSG
719+
warning: .gitignore is not writable — skipping auto-update. Add manually:
720+
!.revive/
721+
.revive/*
722+
!.revive/static.md
723+
or run \`git add -f $STATIC_FILE\`.
724+
MSG
725+
return 0
726+
fi
727+
728+
# The block has to un-ignore the parent directory first: git semantics
729+
# don't let \`!\` rescue a file inside a fully-ignored directory. Putting
730+
# \`!.revive/\` first covers \`.revive/\`, \`.revive\`, and broad rules like
731+
# \`.*\` / \`.*/\`. Then we re-ignore the directory contents and re-include
732+
# only static.md.
733+
cat >> .gitignore <<'GITIGNORE'
734+
735+
# revive: .revive/static.md is the curated STATIC layer (checked in);
736+
# other .revive/ contents are local runtime state.
737+
!.revive/
738+
.revive/*
739+
!.revive/static.md
740+
GITIGNORE
741+
742+
if git check-ignore -q "$STATIC_FILE" 2>/dev/null; then
743+
cat >&2 <<MSG
744+
warning: $STATIC_FILE is still gitignored after appending the canonical
745+
exception block. Inspect with \`git check-ignore -v $STATIC_FILE\` to see
746+
which rule is overriding, or run \`git add -f $STATIC_FILE\`.
747+
MSG
748+
else
749+
echo "updated: .gitignore (un-ignored $STATIC_FILE)"
750+
fi
700751
}
701752

702753
# Build a prompt that a coding agent (Claude Code / Cursor / Aider / any

tests/revive.bats

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,3 +1430,69 @@ JSON
14301430
run "$REVIVE" help
14311431
[[ "$output" == *"mark-compact"* ]] || return 1
14321432
}
1433+
1434+
# --- init: gitignore auto-update ---
1435+
1436+
@test "init does nothing to .gitignore when there is none" {
1437+
run "$REVIVE" init
1438+
[ "$status" -eq 0 ] || return 1
1439+
[ ! -f .gitignore ] || return 1
1440+
}
1441+
1442+
@test "init appends exception when .revive/* is gitignored without exception" {
1443+
printf '.revive/*\n' > .gitignore
1444+
run "$REVIVE" init
1445+
[ "$status" -eq 0 ] || return 1
1446+
[[ "$output" == *"un-ignored"* ]] || return 1
1447+
run cat .gitignore
1448+
[[ "$output" == *"!.revive/static.md"* ]] || return 1
1449+
# static.md is now actually trackable
1450+
run git check-ignore -q .revive/static.md
1451+
[ "$status" -ne 0 ] || return 1
1452+
}
1453+
1454+
@test "init leaves .gitignore alone when static.md is already trackable" {
1455+
printf '.revive/*\n!.revive/static.md\n' > .gitignore
1456+
local before
1457+
before=$(wc -l < .gitignore)
1458+
run "$REVIVE" init
1459+
[ "$status" -eq 0 ] || return 1
1460+
local after
1461+
after=$(wc -l < .gitignore)
1462+
[ "$before" -eq "$after" ] || return 1
1463+
}
1464+
1465+
@test "init un-ignores static.md even with a directory-level .revive/ rule" {
1466+
# Codex P2 regression check: a `.revive/` rule used to leave static.md
1467+
# untrackable. The canonical block now leads with `!.revive/` so the
1468+
# un-ignore works regardless.
1469+
printf '.revive/\n' > .gitignore
1470+
run "$REVIVE" init
1471+
[ "$status" -eq 0 ] || return 1
1472+
[[ "$output" == *"un-ignored"* ]] || return 1
1473+
run git check-ignore -q .revive/static.md
1474+
[ "$status" -ne 0 ] || return 1
1475+
}
1476+
1477+
@test "init un-ignores static.md against a broad dot-dir rule (.*)" {
1478+
# Common case: repos that ignore all dot-directories with `.*`. Without
1479+
# the leading `!.revive/`, the file-level exception cannot rescue
1480+
# static.md from the broader directory ignore.
1481+
printf '.*\n' > .gitignore
1482+
run "$REVIVE" init
1483+
[ "$status" -eq 0 ] || return 1
1484+
run git check-ignore -q .revive/static.md
1485+
[ "$status" -ne 0 ] || return 1
1486+
}
1487+
1488+
@test "init does not abort when .gitignore is read-only" {
1489+
# Codex P3: `cat >> .gitignore` under `set -e` would terminate cmd_init
1490+
# if the file is read-only. Best-effort path must warn and continue.
1491+
printf '.revive/*\n' > .gitignore
1492+
chmod -w .gitignore
1493+
trap 'chmod +w .gitignore || true' RETURN
1494+
run "$REVIVE" init
1495+
[ "$status" -eq 0 ] || return 1
1496+
[[ "$output" == *"not writable"* ]] || return 1
1497+
[ -f .revive/static.md ] || return 1
1498+
}

0 commit comments

Comments
 (0)