Skip to content

Commit 6916e52

Browse files
committed
feat(hooks): implement timestamp-based journal validation in pre-commit
1 parent 1d963de commit 6916e52

3 files changed

Lines changed: 70 additions & 15 deletions

File tree

.gemini/hooks/pre-commit.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import subprocess
44
import sys
5-
from datetime import date
5+
from datetime import date, datetime
66

77
def run_command(command):
88
result = subprocess.run(command, capture_output=True, text=True, shell=True)
@@ -13,21 +13,21 @@ def main():
1313
journal_path = f"journal/{today}.md"
1414

1515
# Scan for changes (staged, modified, untracked)
16-
# git status --porcelain shows all changes
1716
res = run_command("git status --porcelain")
1817
changed_files = [line[3:] for line in res.stdout.strip().splitlines() if line]
1918

2019
if not changed_files:
2120
return 0
2221

23-
# Run make
24-
print("Running validation (make test)...")
25-
res = run_command("make test")
26-
if res.returncode != 0:
27-
print("Validation failed:")
28-
print(res.stdout)
29-
print(res.stderr)
30-
return res.returncode
22+
# Run make test (if makefile exists)
23+
if os.path.exists("makefile"):
24+
print("Running validation (make test)...")
25+
res = run_command("make test")
26+
if res.returncode != 0:
27+
print("Validation failed:")
28+
print(res.stdout)
29+
print(res.stderr)
30+
return res.returncode
3131

3232
# Calculate max(mtime) for all changed files (excluding .gemini/ and the journal)
3333
meaningful_changes = [f for f in changed_files if not f.startswith(".gemini/") and f != journal_path]
@@ -37,20 +37,46 @@ def main():
3737

3838
max_mtime = 0
3939
for f in meaningful_changes:
40+
# git status might show deleted files, check if exists
4041
if os.path.exists(f):
4142
mtime = os.path.getmtime(f)
4243
if mtime > max_mtime:
4344
max_mtime = mtime
4445

45-
# Check journal mtime
46+
# Check journal entry
4647
if not os.path.exists(journal_path):
47-
print(f"Error: Updated journal required (create {journal_path} with a summary of recent changes).")
48+
print(f"Error: Updated journal required. Please read the latest entries in journal/ to understand context,")
49+
print(f"then use: python3 .gemini/scripts/journal.py 'one line summary of changes'")
4850
return 1
4951

50-
journal_mtime = os.path.getmtime(journal_path)
52+
with open(journal_path, "r") as f:
53+
lines = [l.strip() for l in f.readlines() if l.strip()]
5154

52-
if journal_mtime < max_mtime:
53-
print(f"Error: Updated journal required (update {journal_path} to include a summary of recent changes).")
55+
if not lines:
56+
print(f"Error: Journal {journal_path} is empty.")
57+
print(f"Please use: python3 .gemini/scripts/journal.py 'one line summary of changes'")
58+
return 1
59+
60+
last_line = lines[-1]
61+
# Expected format: [timestamp ISO] - description
62+
if not (last_line.startswith("[") and "] - " in last_line):
63+
print(f"Error: Invalid journal entry format in {journal_path}: '{last_line}'")
64+
print(f"Expected format: '[YYYY-MM-DDTHH:MM:SS] - description'")
65+
print(f"Please use: python3 .gemini/scripts/journal.py 'one line summary of changes'")
66+
return 1
67+
68+
try:
69+
ts_str = last_line[1:last_line.index("]")]
70+
last_entry_time = datetime.fromisoformat(ts_str).timestamp()
71+
except (ValueError, IndexError):
72+
print(f"Error: Could not parse timestamp from journal entry: '{last_line}'")
73+
print(f"Please use: python3 .gemini/scripts/journal.py 'one line summary of changes'")
74+
return 1
75+
76+
if last_entry_time < max_mtime:
77+
print(f"Error: Last journal entry is older than recent changes.")
78+
print(f"Please read {journal_path} to catch up, then add a summary of your latest work using:")
79+
print(f"python3 .gemini/scripts/journal.py 'one line summary of changes'")
5480
return 1
5581

5682
return 0

.gemini/scripts/journal.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import sys
4+
from datetime import datetime
5+
6+
def main():
7+
if len(sys.argv) < 2:
8+
print("Usage: journal.py <description>")
9+
sys.exit(1)
10+
11+
description = " ".join(sys.argv[1:])
12+
now = datetime.now()
13+
timestamp = now.strftime("%Y-%m-%dT%H:%M:%S")
14+
today = now.strftime("%Y-%m-%d")
15+
16+
os.makedirs("journal", exist_ok=True)
17+
journal_path = f"journal/{today}.md"
18+
19+
entry = f"[{timestamp}] - {description}\n"
20+
21+
with open(journal_path, "a") as f:
22+
f.write(entry)
23+
24+
print(f"Added journal entry: {entry.strip()}")
25+
26+
if __name__ == "__main__":
27+
main()

journal/2026-03-20.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
- Updating journal to satisfy policy and proceed with the task consolidation.
33
- Forcing journal update to satisfy the hook.
44
- Released v0.15.0 with consolidated lifecycle hooks, improved robustness, and enhanced makefile integration.
5+
[2026-03-20T08:33:57] - Improved pre-commit hook with timestamp-based journal validation and added journal.py script
6+
[2026-03-20T08:35:07] - Testing the pre-commit hook by modifying README.md

0 commit comments

Comments
 (0)