|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Regression tests for summary placeholder gating in loop-codex-stop-hook.sh. |
| 4 | +# |
| 5 | +# Round summary files are pre-created as scaffold targets. The stop hook must |
| 6 | +# reject a scaffold summary before running Codex, otherwise a contract-only |
| 7 | +# round can be reviewed as if implementation work was attempted. |
| 8 | + |
| 9 | +set -euo pipefail |
| 10 | + |
| 11 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 12 | +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 13 | +source "$SCRIPT_DIR/test-helpers.sh" |
| 14 | + |
| 15 | +STOP_HOOK="$PROJECT_ROOT/hooks/loop-codex-stop-hook.sh" |
| 16 | + |
| 17 | +setup_test_dir |
| 18 | +export XDG_CACHE_HOME="$TEST_DIR/.cache" |
| 19 | +mkdir -p "$XDG_CACHE_HOME" |
| 20 | + |
| 21 | +setup_mock_codex() { |
| 22 | + mkdir -p "$TEST_DIR/bin" |
| 23 | + cat > "$TEST_DIR/bin/codex" << 'EOF' |
| 24 | +#!/usr/bin/env bash |
| 25 | +if [[ -n "${MOCK_CODEX_MARKER:-}" ]]; then |
| 26 | + : > "$MOCK_CODEX_MARKER" |
| 27 | +fi |
| 28 | +echo "Mainline Progress Verdict: STALLED" |
| 29 | +echo "Final Decision: NOT COMPLETE" |
| 30 | +exit 0 |
| 31 | +EOF |
| 32 | + chmod +x "$TEST_DIR/bin/codex" |
| 33 | + export PATH="$TEST_DIR/bin:$PATH" |
| 34 | +} |
| 35 | + |
| 36 | +create_loop_fixture() { |
| 37 | + local repo_dir="$1" |
| 38 | + local summary_body="$2" |
| 39 | + |
| 40 | + init_test_git_repo "$repo_dir" |
| 41 | + printf 'plans/\n' > "$repo_dir/.gitignore" |
| 42 | + git -C "$repo_dir" add .gitignore |
| 43 | + git -C "$repo_dir" commit -q -m "Add gitignore" |
| 44 | + |
| 45 | + mkdir -p "$repo_dir/plans" |
| 46 | + cat > "$repo_dir/plans/plan.md" << 'EOF' |
| 47 | +# Test Plan |
| 48 | +
|
| 49 | +Complete real implementation work. |
| 50 | +EOF |
| 51 | + |
| 52 | + local loop_dir="$repo_dir/.humanize/rlcr/2026-03-01_00-00-00" |
| 53 | + mkdir -p "$loop_dir" |
| 54 | + cp "$repo_dir/plans/plan.md" "$loop_dir/plan.md" |
| 55 | + |
| 56 | + local branch base_commit |
| 57 | + branch=$(git -C "$repo_dir" rev-parse --abbrev-ref HEAD) |
| 58 | + base_commit=$(git -C "$repo_dir" rev-parse HEAD) |
| 59 | + |
| 60 | + cat > "$loop_dir/state.md" << EOF |
| 61 | +--- |
| 62 | +current_round: 0 |
| 63 | +max_iterations: 42 |
| 64 | +codex_model: gpt-5.5 |
| 65 | +codex_effort: high |
| 66 | +codex_timeout: 60 |
| 67 | +push_every_round: false |
| 68 | +full_review_round: 5 |
| 69 | +plan_file: "plans/plan.md" |
| 70 | +plan_tracked: false |
| 71 | +start_branch: $branch |
| 72 | +base_branch: $branch |
| 73 | +base_commit: $base_commit |
| 74 | +review_started: false |
| 75 | +ask_codex_question: false |
| 76 | +agent_teams: false |
| 77 | +bitlesson_required: false |
| 78 | +mainline_stall_count: 0 |
| 79 | +last_mainline_verdict: unknown |
| 80 | +drift_status: normal |
| 81 | +--- |
| 82 | +EOF |
| 83 | + |
| 84 | + printf '%s\n' "$summary_body" > "$loop_dir/round-0-summary.md" |
| 85 | + |
| 86 | + cat > "$loop_dir/round-0-contract.md" << 'EOF' |
| 87 | +# Round 0 Contract |
| 88 | +
|
| 89 | +- Mainline Objective: Complete real implementation work. |
| 90 | +- Target ACs: AC-1. |
| 91 | +- Blocking: none. |
| 92 | +- Queued: none. |
| 93 | +- Success Criteria: concrete implementation evidence exists. |
| 94 | +EOF |
| 95 | + |
| 96 | + cat > "$loop_dir/goal-tracker.md" << 'EOF' |
| 97 | +# Goal Tracker |
| 98 | +## IMMUTABLE SECTION |
| 99 | +### Ultimate Goal |
| 100 | +Complete real implementation work. |
| 101 | +### Acceptance Criteria |
| 102 | +- AC-1: Work evidence exists. |
| 103 | +--- |
| 104 | +## MUTABLE SECTION |
| 105 | +### Plan Version: 1 (Updated: Round 0) |
| 106 | +#### Active Tasks |
| 107 | +| Task | Target AC | Status | Tag | Owner | Notes | |
| 108 | +|------|-----------|--------|-----|-------|-------| |
| 109 | +EOF |
| 110 | + |
| 111 | + echo "$loop_dir" |
| 112 | +} |
| 113 | + |
| 114 | +run_stop_hook() { |
| 115 | + local repo_dir="$1" |
| 116 | + printf '{"hook_event_name":"Stop","cwd":"%s","session_id":"test-session","transcript_path":null}\n' "$repo_dir" \ |
| 117 | + | CLAUDE_PROJECT_DIR="$repo_dir" "$STOP_HOOK" |
| 118 | +} |
| 119 | + |
| 120 | +setup_mock_codex |
| 121 | + |
| 122 | +echo "==========================================" |
| 123 | +echo "Summary Placeholder Gate Tests" |
| 124 | +echo "==========================================" |
| 125 | + |
| 126 | +# Test 1: scaffold summary blocks before Codex runs. |
| 127 | +repo1="$TEST_DIR/repo-placeholder" |
| 128 | +marker1="$TEST_DIR/codex-ran-placeholder" |
| 129 | +summary_placeholder='# Round 0 Summary |
| 130 | +
|
| 131 | +## Work Completed |
| 132 | +- [Describe what was implemented in this phase] |
| 133 | +
|
| 134 | +## Files Changed |
| 135 | +- [List created/modified files] |
| 136 | +
|
| 137 | +## Validation |
| 138 | +- [List tests/commands run and outcomes]' |
| 139 | +create_loop_fixture "$repo1" "$summary_placeholder" >/dev/null |
| 140 | +export MOCK_CODEX_MARKER="$marker1" |
| 141 | +output1=$(run_stop_hook "$repo1") |
| 142 | +unset MOCK_CODEX_MARKER |
| 143 | +msg1=$(printf '%s' "$output1" | jq -r '.systemMessage // empty') |
| 144 | +reason1=$(printf '%s' "$output1" | jq -r '.reason // empty') |
| 145 | +if [[ "$msg1" == "Loop: Summary file still contains placeholders for round 0" ]] && \ |
| 146 | + [[ "$reason1" == *"Work Summary Still Placeholder"* ]] && \ |
| 147 | + [[ ! -e "$marker1" ]]; then |
| 148 | + pass "placeholder summary is blocked before Codex runs" |
| 149 | +else |
| 150 | + fail "placeholder summary is blocked before Codex runs" "placeholder block without Codex marker" "msg=$msg1 marker=$([[ -e "$marker1" ]] && echo yes || echo no)" |
| 151 | +fi |
| 152 | + |
| 153 | +# Test 2: concrete summary reaches Codex review path. |
| 154 | +repo2="$TEST_DIR/repo-concrete" |
| 155 | +marker2="$TEST_DIR/codex-ran-concrete" |
| 156 | +summary_concrete='# Round 0 Summary |
| 157 | +
|
| 158 | +## Work Completed |
| 159 | +- Implemented the target change and captured validation evidence. |
| 160 | +
|
| 161 | +## Files Changed |
| 162 | +- hooks/loop-codex-stop-hook.sh |
| 163 | +
|
| 164 | +## Validation |
| 165 | +- bash -n hooks/loop-codex-stop-hook.sh: passed |
| 166 | +
|
| 167 | +## Remaining Items |
| 168 | +- None.' |
| 169 | +create_loop_fixture "$repo2" "$summary_concrete" >/dev/null |
| 170 | +export MOCK_CODEX_MARKER="$marker2" |
| 171 | +output2=$(run_stop_hook "$repo2") |
| 172 | +unset MOCK_CODEX_MARKER |
| 173 | +if [[ -e "$marker2" ]] && [[ "$output2" == *"Codex found issues"* || "$output2" == *"Mainline Progress Verdict"* ]]; then |
| 174 | + pass "concrete summary reaches Codex review" |
| 175 | +else |
| 176 | + fail "concrete summary reaches Codex review" "Codex marker created" "marker=$([[ -e "$marker2" ]] && echo yes || echo no) output=$output2" |
| 177 | +fi |
| 178 | + |
| 179 | +print_test_summary "Summary Placeholder Gate Test Summary" |
0 commit comments