Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions hooks/loop-codex-stop-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,37 @@ COMPLETED_ITERATIONS=$((CURRENT_ROUND + 1))
PREV_ROUND=$(( CURRENT_ROUND > 0 ? CURRENT_ROUND - 1 : 0 ))
PREV_PREV_ROUND=$(( CURRENT_ROUND > 1 ? CURRENT_ROUND - 2 : 0 ))

# Integral component: accumulated commit history and recent round references
# Validate BASE_COMMIT is an ancestor of HEAD (not just a valid object) before using it in git log
if [[ -n "$BASE_COMMIT" ]] && git -C "$PROJECT_ROOT" merge-base --is-ancestor "$BASE_COMMIT" HEAD 2>/dev/null; then
COMMIT_HISTORY=$(git -C "$PROJECT_ROOT" log --oneline --no-decorate --reverse "$BASE_COMMIT"..HEAD 2>/dev/null | tail -80)
else
COMMIT_HISTORY=$(git -C "$PROJECT_ROOT" log --oneline --no-decorate --reverse -30 2>/dev/null)
# Annotate so Codex knows this is not the full loop history
[[ -n "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(base commit unavailable, showing recent branch commits)
${COMMIT_HISTORY}"
fi
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

RECENT_ROUND_FILES=""
for (( r = CURRENT_ROUND - 1; r >= 0 && r >= CURRENT_ROUND - 3; r-- )); do
RECENT_ROUND_FILES+="- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-summary.md
- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-review-result.md
"
done
[[ -z "$RECENT_ROUND_FILES" ]] && RECENT_ROUND_FILES="(first round, no prior history)"

COMMIT_HISTORY_SECTION_FALLBACK="## Development History (Integral Context)
\`\`\`
${COMMIT_HISTORY}
\`\`\`
### Recent Round Files
Read these files before conducting your review to understand the trajectory of work:
${RECENT_ROUND_FILES}"
COMMIT_HISTORY_SECTION=$(load_and_render_safe "$TEMPLATE_DIR" "codex/commit-history-section.md" "$COMMIT_HISTORY_SECTION_FALLBACK" \
"COMMIT_HISTORY=$COMMIT_HISTORY" \
"RECENT_ROUND_FILES=$RECENT_ROUND_FILES")

# Build the review prompt
FULL_ALIGNMENT_FALLBACK="# Full Alignment Review (Round {{CURRENT_ROUND}})

Expand All @@ -973,6 +1004,8 @@ Review Claude's work against the plan and goal tracker. Check all goals are bein
## Claude's Summary
{{SUMMARY_CONTENT}}

{{COMMIT_HISTORY_SECTION}}

{{GOAL_TRACKER_UPDATE_SECTION}}

Write your review to {{REVIEW_RESULT_FILE}}. End with COMPLETE if done, or list issues."
Expand All @@ -984,6 +1017,8 @@ Review Claude's work for this round.
## Claude's Summary
{{SUMMARY_CONTENT}}

{{COMMIT_HISTORY_SECTION}}

{{GOAL_TRACKER_UPDATE_SECTION}}

Write your review to {{REVIEW_RESULT_FILE}}. End with COMPLETE if done, or list issues."
Expand All @@ -997,6 +1032,7 @@ if [[ "$FULL_ALIGNMENT_CHECK" == "true" ]]; then
"GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
"DOCS_PATH=$DOCS_PATH" \
"GOAL_TRACKER_UPDATE_SECTION=$GOAL_TRACKER_UPDATE_SECTION" \
"COMMIT_HISTORY_SECTION=$COMMIT_HISTORY_SECTION" \
"COMPLETED_ITERATIONS=$COMPLETED_ITERATIONS" \
"LOOP_TIMESTAMP=$LOOP_TIMESTAMP" \
"PREV_ROUND=$PREV_ROUND" \
Expand All @@ -1013,6 +1049,7 @@ else
"GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
"DOCS_PATH=$DOCS_PATH" \
"GOAL_TRACKER_UPDATE_SECTION=$GOAL_TRACKER_UPDATE_SECTION" \
"COMMIT_HISTORY_SECTION=$COMMIT_HISTORY_SECTION" \
"COMPLETED_ITERATIONS=$COMPLETED_ITERATIONS" \
"LOOP_TIMESTAMP=$LOOP_TIMESTAMP" \
"PREV_ROUND=$PREV_ROUND" \
Expand Down
12 changes: 12 additions & 0 deletions prompt-template/codex/commit-history-section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Development History (Integral Context)

Accumulated commits since loop start (oldest first):
```
{{COMMIT_HISTORY}}
```

### Recent Round Files
Read these files before conducting your review to understand the trajectory of work:
{{RECENT_ROUND_FILES}}

Use this history to identify patterns across rounds: recurring issues, stalled progress, or drift from the mainline objective. Weight recent rounds more heavily but watch for systemic trends in the full commit log.
2 changes: 2 additions & 0 deletions prompt-template/codex/full-alignment-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ You MUST read this plan file first to understand the full scope of work before c
<!-- CLAUDE's WORK SUMMARY END -->
---

{{COMMIT_HISTORY_SECTION}}

## Part 1: Goal Tracker Audit (MANDATORY)

Read @{{GOAL_TRACKER_FILE}} and verify:
Expand Down
2 changes: 2 additions & 0 deletions prompt-template/codex/regular-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Below is Claude's summary of the work completed:
<!-- CLAUDE's WORK SUMMARY END -->
---

{{COMMIT_HISTORY_SECTION}}

## Part 1: Implementation Review

- Your task is to conduct a deep critical review, focusing on finding implementation issues and identifying gaps between "plan-design" and actual implementation.
Expand Down
271 changes: 271 additions & 0 deletions tests/test-commit-history-section.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#!/usr/bin/env bash
#
# Test script for the Integral (I) component: commit-history-section
#
# Validates:
# 1. Round 0: "(no commits yet)" and "(first round, no prior history)"
# 2. Round 2+: commit log and round file references rendered correctly
# 3. Corrupted BASE_COMMIT: graceful fallback with annotation
# 4. Template missing: fallback renders the full section including round files
#

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
source "$SCRIPT_DIR/test-helpers.sh"
source "$PROJECT_ROOT/hooks/lib/template-loader.sh"

TEMPLATE_DIR="$PROJECT_ROOT/prompt-template"

echo "========================================"
echo "Testing commit-history-section (I component)"
echo "========================================"
echo ""

# ========================================
# Setup: create a temporary git repo
# ========================================
setup_test_dir
init_test_git_repo "$TEST_DIR/repo"

# ========================================
# Test 1: Round 0 - no commits since base, first round
# ========================================
echo "Test 1: Round 0 - no commits, first round"

CURRENT_ROUND=0
BASE_COMMIT=$(git -C "$TEST_DIR/repo" rev-parse HEAD)

# No commits since BASE_COMMIT..HEAD (same commit)
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse "$BASE_COMMIT"..HEAD 2>/dev/null | tail -80)
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

RECENT_ROUND_FILES=""
LOOP_TIMESTAMP="2026-01-01_00-00-00"
for (( r = CURRENT_ROUND - 1; r >= 0 && r >= CURRENT_ROUND - 3; r-- )); do
RECENT_ROUND_FILES+="- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-summary.md
- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-review-result.md
"
done
[[ -z "$RECENT_ROUND_FILES" ]] && RECENT_ROUND_FILES="(first round, no prior history)"

RESULT=$(load_and_render_safe "$TEMPLATE_DIR" "codex/commit-history-section.md" "FALLBACK" \
"COMMIT_HISTORY=$COMMIT_HISTORY" \
"RECENT_ROUND_FILES=$RECENT_ROUND_FILES")

if echo "$RESULT" | grep -q "(no commits yet)" && echo "$RESULT" | grep -q "(first round, no prior history)"; then
pass "Round 0 shows correct placeholders"
else
fail "Round 0 placeholders" "(no commits yet) and (first round, no prior history)" "$RESULT"
fi

# ========================================
# Test 2: Round 3 - with commits and round history
# ========================================
echo ""
echo "Test 2: Round 3 - commits and round file references"

# Make some commits
cd "$TEST_DIR/repo"
echo "feat1" > feat1.txt && git add feat1.txt && git commit -q -m "feat: add feature 1"
echo "feat2" > feat2.txt && git add feat2.txt && git commit -q -m "feat: add feature 2"
echo "fix1" > fix1.txt && git add fix1.txt && git commit -q -m "fix: resolve bug in feature 1"
cd - > /dev/null

CURRENT_ROUND=3
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse "$BASE_COMMIT"..HEAD 2>/dev/null | tail -80)
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

RECENT_ROUND_FILES=""
for (( r = CURRENT_ROUND - 1; r >= 0 && r >= CURRENT_ROUND - 3; r-- )); do
RECENT_ROUND_FILES+="- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-summary.md
- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-review-result.md
"
done
[[ -z "$RECENT_ROUND_FILES" ]] && RECENT_ROUND_FILES="(first round, no prior history)"

RESULT=$(load_and_render_safe "$TEMPLATE_DIR" "codex/commit-history-section.md" "FALLBACK" \
"COMMIT_HISTORY=$COMMIT_HISTORY" \
"RECENT_ROUND_FILES=$RECENT_ROUND_FILES")

HAS_COMMITS=true
HAS_ROUNDS=true

echo "$RESULT" | grep -q "feat: add feature 1" || HAS_COMMITS=false
echo "$RESULT" | grep -q "feat: add feature 2" || HAS_COMMITS=false
echo "$RESULT" | grep -q "fix: resolve bug in feature 1" || HAS_COMMITS=false

echo "$RESULT" | grep -q "round-2-summary.md" || HAS_ROUNDS=false
echo "$RESULT" | grep -q "round-1-summary.md" || HAS_ROUNDS=false
echo "$RESULT" | grep -q "round-0-summary.md" || HAS_ROUNDS=false
echo "$RESULT" | grep -q "round-2-review-result.md" || HAS_ROUNDS=false

if [[ "$HAS_COMMITS" == "true" ]]; then
pass "Round 3 shows all 3 commits"
else
fail "Round 3 commits" "3 commit messages" "$RESULT"
fi

if [[ "$HAS_ROUNDS" == "true" ]]; then
pass "Round 3 shows round 0-2 file references"
else
fail "Round 3 round files" "round-0/1/2 summary and review files" "$RESULT"
fi

# ========================================
# Test 3: Corrupted BASE_COMMIT - nonexistent object
# ========================================
echo ""
echo "Test 3: Corrupted BASE_COMMIT graceful fallback"

BAD_COMMIT="deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"

# Simulate the exact logic from the stop hook (merge-base --is-ancestor)
if [[ -n "$BAD_COMMIT" ]] && git -C "$TEST_DIR/repo" merge-base --is-ancestor "$BAD_COMMIT" HEAD 2>/dev/null; then
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse "$BAD_COMMIT"..HEAD 2>/dev/null | tail -80)
else
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse -30 2>/dev/null)
[[ -n "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(base commit unavailable, showing recent branch commits)
${COMMIT_HISTORY}"
fi
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

if echo "$COMMIT_HISTORY" | grep -q "base commit unavailable"; then
pass "Corrupted BASE_COMMIT triggers annotation"
else
fail "Corrupted BASE_COMMIT annotation" "base commit unavailable" "$COMMIT_HISTORY"
fi

if echo "$COMMIT_HISTORY" | grep -q "feat: add feature"; then
pass "Corrupted BASE_COMMIT still shows recent commits"
else
fail "Corrupted BASE_COMMIT recent commits" "recent branch commits" "$COMMIT_HISTORY"
fi

# Verify no crash (we got here = no set -e crash)
pass "Corrupted BASE_COMMIT did not crash (set -e safe)"

# ========================================
# Test 3b: Valid but unrelated commit (not ancestor of HEAD)
# ========================================
echo ""
echo "Test 3b: Valid but unrelated BASE_COMMIT (orphan branch)"

# Create an orphan branch with its own commit, then switch back
cd "$TEST_DIR/repo"
ORIG_BRANCH=$(git rev-parse --abbrev-ref HEAD)
git checkout -q --orphan orphan-test
echo "orphan" > orphan.txt && git add orphan.txt && git commit -q -m "orphan commit"
ORPHAN_COMMIT=$(git rev-parse HEAD)
git checkout -q "$ORIG_BRANCH"
cd - > /dev/null

# ORPHAN_COMMIT exists but is NOT an ancestor of HEAD
if [[ -n "$ORPHAN_COMMIT" ]] && git -C "$TEST_DIR/repo" merge-base --is-ancestor "$ORPHAN_COMMIT" HEAD 2>/dev/null; then
COMMIT_HISTORY="should not reach here"
else
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse -30 2>/dev/null)
[[ -n "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(base commit unavailable, showing recent branch commits)
${COMMIT_HISTORY}"
fi
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

if echo "$COMMIT_HISTORY" | grep -q "base commit unavailable"; then
pass "Unrelated valid commit triggers annotation"
else
fail "Unrelated valid commit annotation" "base commit unavailable" "$COMMIT_HISTORY"
fi

# ========================================
# Test 4: Missing template - fallback renders full section
# ========================================
echo ""
echo "Test 4: Missing template fallback renders full section"

CURRENT_ROUND=2
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse "$BASE_COMMIT"..HEAD 2>/dev/null | tail -80)

RECENT_ROUND_FILES=""
for (( r = CURRENT_ROUND - 1; r >= 0 && r >= CURRENT_ROUND - 3; r-- )); do
RECENT_ROUND_FILES+="- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-summary.md
- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-review-result.md
"
done

# Use the exact fallback format from the stop hook
COMMIT_HISTORY_SECTION_FALLBACK="## Development History (Integral Context)
\`\`\`
${COMMIT_HISTORY}
\`\`\`
### Recent Round Files
Read these files before conducting your review to understand the trajectory of work:
${RECENT_ROUND_FILES}"

# Point to a non-existent template to force fallback
RESULT=$(load_and_render_safe "$TEMPLATE_DIR" "codex/non-existent-template.md" "$COMMIT_HISTORY_SECTION_FALLBACK" \
"COMMIT_HISTORY=$COMMIT_HISTORY" \
"RECENT_ROUND_FILES=$RECENT_ROUND_FILES")

FALLBACK_OK=true
echo "$RESULT" | grep -q "Development History" || FALLBACK_OK=false
echo "$RESULT" | grep -q "feat: add feature 1" || FALLBACK_OK=false
echo "$RESULT" | grep -q "Recent Round Files" || FALLBACK_OK=false
echo "$RESULT" | grep -q "round-1-summary.md" || FALLBACK_OK=false
echo "$RESULT" | grep -q "round-0-review-result.md" || FALLBACK_OK=false
echo "$RESULT" | grep -q "Read these files" || FALLBACK_OK=false

if [[ "$FALLBACK_OK" == "true" ]]; then
pass "Fallback renders full section with commits, round files, and directive"
else
fail "Fallback full section" "commits + round files + directive" "$RESULT"
fi

# ========================================
# Test 5: Round 1 - only 1 prior round (boundary)
# ========================================
echo ""
echo "Test 5: Round 1 - only 1 prior round"

CURRENT_ROUND=1
RECENT_ROUND_FILES=""
for (( r = CURRENT_ROUND - 1; r >= 0 && r >= CURRENT_ROUND - 3; r-- )); do
RECENT_ROUND_FILES+="- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-summary.md
- @.humanize/rlcr/${LOOP_TIMESTAMP}/round-${r}-review-result.md
"
done
[[ -z "$RECENT_ROUND_FILES" ]] && RECENT_ROUND_FILES="(first round, no prior history)"

if echo "$RECENT_ROUND_FILES" | grep -q "round-0-summary.md" && \
! echo "$RECENT_ROUND_FILES" | grep -q "round-1-"; then
pass "Round 1 references only round 0"
else
fail "Round 1 boundary" "only round-0 references" "$RECENT_ROUND_FILES"
fi

# ========================================
# Test 6: Empty BASE_COMMIT (legacy loop)
# ========================================
echo ""
echo "Test 6: Empty BASE_COMMIT fallback"

EMPTY_BASE=""
if [[ -n "$EMPTY_BASE" ]] && git -C "$TEST_DIR/repo" merge-base --is-ancestor "$EMPTY_BASE" HEAD 2>/dev/null; then
COMMIT_HISTORY="should not reach here"
else
COMMIT_HISTORY=$(git -C "$TEST_DIR/repo" log --oneline --no-decorate --reverse -30 2>/dev/null)
[[ -n "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(base commit unavailable, showing recent branch commits)
${COMMIT_HISTORY}"
fi
[[ -z "$COMMIT_HISTORY" ]] && COMMIT_HISTORY="(no commits yet)"

if echo "$COMMIT_HISTORY" | grep -q "base commit unavailable"; then
pass "Empty BASE_COMMIT triggers annotation"
else
fail "Empty BASE_COMMIT annotation" "base commit unavailable" "$COMMIT_HISTORY"
fi

# ========================================
# Summary
# ========================================
print_test_summary "Commit History Section (I Component) Tests"
Loading