loop-codex-stop-hook.sh 85.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
#!/usr/bin/env bash
#
# Stop Hook for RLCR loop
#
# Intercepts Claude's exit attempts and uses Codex to review work.
# If Codex doesn't confirm completion, blocks exit and feeds review back.
#
# State directory: .humanize/rlcr/<timestamp>/
# State file: state.md (current_round, max_iterations, codex config)
# Summary file: round-N-summary.md (Claude's work summary)
# Review prompt: round-N-review-prompt.md (prompt sent to Codex)
# Review result: round-N-review-result.md (Codex's review)
#

set -euo pipefail

# ========================================
# Default Configuration
# ========================================

# DEFAULT_CODEX_MODEL and DEFAULT_CODEX_EFFORT are provided by loop-common.sh (sourced below)
DEFAULT_CODEX_TIMEOUT=5400

# ========================================
# Read Hook Input
# ========================================

HOOK_INPUT=$(cat)

# NOTE: We intentionally do NOT check stop_hook_active here.
# For iterative loops, stop_hook_active will be true when Claude is continuing
# from a previous blocked stop. We WANT to run Codex review each iteration.
# Loop termination is controlled by:
# - No active loop directory (no state.md) -> exit early below
# - Codex outputs MARKER_COMPLETE -> allow exit
# - current_round >= max_iterations -> allow exit

# ========================================
# Find Active Loop
# ========================================

# Source shared loop functions and template loader
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
source "$SCRIPT_DIR/lib/loop-common.sh"

PROJECT_ROOT="$(resolve_project_root)" || exit 0
LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"

# Source portable timeout wrapper for git operations
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
source "$PLUGIN_ROOT/scripts/portable-timeout.sh"

# Source methodology analysis library
source "$SCRIPT_DIR/lib/methodology-analysis.sh"

# Default timeout for git operations (30 seconds)
GIT_TIMEOUT=30

# Template directory is set by loop-common.sh via template-loader.sh

# Extract session_id from hook input for session-aware loop filtering
HOOK_SESSION_ID=$(extract_session_id "$HOOK_INPUT")

LOOP_DIR=$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID" true)

# If no active loop (or session_id mismatch), allow exit
if [[ -z "$LOOP_DIR" ]]; then
    exit 0
fi

# ========================================
# Background-Task Guards
# ========================================
# Delegates to handle_bg_task_short_circuit (hooks/lib/loop-bg-tasks.sh),
# which runs four cohesive guards in order:
#   1. Ambiguous-caller marker guard (no session_id + marker present)
#   2. Cross-session parked-loop guard (foreign session walking in)
#   3. Pending-bg short-circuit (this session has async work in flight)
#   4. Same-session stale-marker cleanup (bg work just finished)
# When any guard short-circuits, it emits the appropriate JSON on stdout
# and `exit 0`s directly; we never return from that call. When no guard
# fires we continue into the normal gate logic below.
handle_bg_task_short_circuit "$LOOP_DIR" "$HOOK_INPUT" "$HOOK_SESSION_ID"

# ========================================
# Detect Loop Phase: Normal or Finalize
# ========================================
# Normal loop: state.md exists
# Finalize Phase: finalize-state.md exists (after Codex COMPLETE, before final completion)

STATE_FILE=$(resolve_active_state_file "$LOOP_DIR")
if [[ -z "$STATE_FILE" ]]; then
    # No state file found, allow exit
    exit 0
fi

IS_FINALIZE_PHASE=false
[[ "$STATE_FILE" == *"/finalize-state.md" ]] && IS_FINALIZE_PHASE=true

IS_METHODOLOGY_ANALYSIS_PHASE=false
[[ "$STATE_FILE" == *"/methodology-analysis-state.md" ]] && IS_METHODOLOGY_ANALYSIS_PHASE=true

# ========================================
# Parse State File (using shared function)
# ========================================

# First extract raw frontmatter to check which fields are actually present
# This prevents silently using defaults for missing critical fields
RAW_FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE" 2>/dev/null || echo "")

# Check if critical fields are present before parsing (which applies defaults)
RAW_CURRENT_ROUND=$(echo "$RAW_FRONTMATTER" | grep "^current_round:" || true)
RAW_MAX_ITERATIONS=$(echo "$RAW_FRONTMATTER" | grep "^max_iterations:" || true)
RAW_FULL_REVIEW_ROUND=$(echo "$RAW_FRONTMATTER" | grep "^full_review_round:" || true)
RAW_BITLESSON_REQUIRED=$(echo "$RAW_FRONTMATTER" | grep "^bitlesson_required:" || true)
RAW_BITLESSON_FILE=$(echo "$RAW_FRONTMATTER" | grep "^bitlesson_file:" || true)
RAW_BITLESSON_ALLOW_EMPTY_NONE=$(echo "$RAW_FRONTMATTER" | grep "^bitlesson_allow_empty_none:" || true)

# Use tolerant parsing to extract values
# Note: parse_state_file applies defaults for missing current_round/max_iterations
if ! parse_state_file "$STATE_FILE" 2>/dev/null; then
    echo "Warning: parse_state_file returned non-zero, proceeding to schema validation" >&2
fi

# Map STATE_* variables to local names for backward compatibility
PLAN_TRACKED="$STATE_PLAN_TRACKED"
START_BRANCH="$STATE_START_BRANCH"
BASE_BRANCH="${STATE_BASE_BRANCH:-}"
BASE_COMMIT="${STATE_BASE_COMMIT:-}"
PLAN_FILE="$STATE_PLAN_FILE"
CURRENT_ROUND="$STATE_CURRENT_ROUND"
MAX_ITERATIONS="$STATE_MAX_ITERATIONS"
PUSH_EVERY_ROUND="$STATE_PUSH_EVERY_ROUND"
FULL_REVIEW_ROUND="${STATE_FULL_REVIEW_ROUND:-5}"
REVIEW_STARTED="$STATE_REVIEW_STARTED"
CODEX_EXEC_MODEL="${STATE_CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
CODEX_EXEC_EFFORT="${STATE_CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
CODEX_REVIEW_MODEL="$CODEX_EXEC_MODEL"
CODEX_REVIEW_EFFORT="high"
CODEX_TIMEOUT="${STATE_CODEX_TIMEOUT:-${CODEX_TIMEOUT:-$DEFAULT_CODEX_TIMEOUT}}"
ASK_CODEX_QUESTION="${STATE_ASK_CODEX_QUESTION:-false}"
AGENT_TEAMS="${STATE_AGENT_TEAMS:-false}"
PRIVACY_MODE="${STATE_PRIVACY_MODE:-true}"
BITLESSON_REQUIRED="false"
if [[ -n "$RAW_BITLESSON_REQUIRED" ]]; then
    BITLESSON_REQUIRED=$(echo "$RAW_BITLESSON_REQUIRED" | sed 's/^bitlesson_required:[[:space:]]*//' | tr -d ' "')
fi
BITLESSON_FILE_REL=".humanize/bitlesson.md"
if [[ -n "$RAW_BITLESSON_FILE" ]]; then
    BITLESSON_FILE_REL=$(echo "$RAW_BITLESSON_FILE" | sed 's/^bitlesson_file:[[:space:]]*//' | sed 's/^"//; s/"$//')
fi
if [[ -z "$BITLESSON_FILE_REL" ]] || \
   [[ ! "$BITLESSON_FILE_REL" =~ ^[a-zA-Z0-9._/-]+$ ]] || \
   [[ "$BITLESSON_FILE_REL" = /* ]] || \
   [[ "$BITLESSON_FILE_REL" =~ (^|/)\.\.(/|$) ]]; then
    BITLESSON_FILE_REL=".humanize/bitlesson.md"
fi
BITLESSON_FILE="$PROJECT_ROOT/$BITLESSON_FILE_REL"
BITLESSON_ALLOW_EMPTY_NONE="true"
if [[ -n "$RAW_BITLESSON_ALLOW_EMPTY_NONE" ]]; then
    BITLESSON_ALLOW_EMPTY_NONE=$(echo "$RAW_BITLESSON_ALLOW_EMPTY_NONE" | sed 's/^bitlesson_allow_empty_none:[[:space:]]*//' | tr -d ' "')
fi
if [[ "${HUMANIZE_ALLOW_EMPTY_BITLESSON_NONE:-}" == "true" ]]; then
    BITLESSON_ALLOW_EMPTY_NONE="true"
fi
if [[ "$BITLESSON_ALLOW_EMPTY_NONE" != "true" && "$BITLESSON_ALLOW_EMPTY_NONE" != "false" ]]; then
    BITLESSON_ALLOW_EMPTY_NONE="true"
fi
MAINLINE_STALL_COUNT="${STATE_MAINLINE_STALL_COUNT:-0}"
LAST_MAINLINE_VERDICT="${STATE_LAST_MAINLINE_VERDICT:-$MAINLINE_VERDICT_UNKNOWN}"
DRIFT_STATUS="${STATE_DRIFT_STATUS:-$DRIFT_STATUS_NORMAL}"
# Re-validate Codex Model and Effort for YAML safety (in case state.md was manually edited)
# Use same validation patterns as setup-rlcr-loop.sh
if [[ ! "$CODEX_EXEC_MODEL" =~ ^[a-zA-Z0-9._-]+$ ]]; then
    echo "Error: Invalid codex_model in state file: $CODEX_EXEC_MODEL" >&2
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_UNEXPECTED"
    exit 0
fi
if [[ ! "$CODEX_EXEC_EFFORT" =~ ^(xhigh|high|medium|low)$ ]]; then
    echo "Error: Invalid codex effort in state file: $CODEX_EXEC_EFFORT" >&2
    echo "  Must be one of: xhigh, high, medium, low" >&2
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_UNEXPECTED"
    exit 0
fi

# Validate critical fields were actually present (not just defaulted)
# This prevents silently treating a truncated state file as round 0
if [[ -z "$RAW_CURRENT_ROUND" ]]; then
    echo "Error: State file missing required field: current_round" >&2
    echo "  State file may be truncated or corrupted" >&2
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_UNEXPECTED"
    exit 0
fi
if [[ -z "$RAW_MAX_ITERATIONS" ]]; then
    echo "Error: State file missing required field: max_iterations" >&2
    echo "  State file may be truncated or corrupted" >&2
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_UNEXPECTED"
    exit 0
fi

# Validate numeric fields
if [[ ! "$CURRENT_ROUND" =~ ^[0-9]+$ ]]; then
    echo "Warning: State file corrupted (current_round not numeric), stopping loop" >&2
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_UNEXPECTED"
    exit 0
fi

if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
    echo "Warning: State file corrupted (max_iterations not numeric), using default" >&2
    MAX_ITERATIONS=84
fi

if [[ ! "$MAINLINE_STALL_COUNT" =~ ^[0-9]+$ ]]; then
    echo "Warning: Invalid mainline_stall_count '$MAINLINE_STALL_COUNT', defaulting to 0" >&2
    MAINLINE_STALL_COUNT=0
fi
LAST_MAINLINE_VERDICT=$(normalize_mainline_progress_verdict "$LAST_MAINLINE_VERDICT")
DRIFT_STATUS=$(normalize_drift_status "$DRIFT_STATUS")

# ========================================
# Quick-check 0: Schema Validation (v1.1.2+ fields)
# ========================================
# If schema is outdated, terminate loop as unexpected

if [[ -z "$PLAN_TRACKED" || -z "$START_BRANCH" ]]; then
    REASON="RLCR loop state file is missing required fields (plan_tracked or start_branch).

This indicates the loop was started with an older version of humanize.

**Options:**
1. Cancel the loop: \`/humanize:cancel-rlcr-loop\`
2. Update humanize plugin to version 1.1.2+
3. Restart the RLCR loop with the updated plugin"
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - state schema outdated" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

# ========================================
# Quick-check 0.1: Schema Validation (v1.5.0+ fields)
# ========================================
# Validate review_started and base_branch fields for v1.5.0+ state files

if [[ -z "$REVIEW_STARTED" || ( "$REVIEW_STARTED" != "true" && "$REVIEW_STARTED" != "false" ) ]]; then
    REASON="RLCR loop state file is missing or has invalid review_started field.

This indicates the loop was started with an older version of humanize (pre-1.5.0).

**Options:**
1. Cancel the loop: \`/humanize:cancel-rlcr-loop\`
2. Update humanize plugin to version 1.5.0+
3. Restart the RLCR loop with the updated plugin"
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - state schema outdated (missing review_started)" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

if [[ -z "$BASE_BRANCH" ]]; then
    REASON="RLCR loop state file is missing base_branch field.

This indicates the loop was started with an older version of humanize (pre-1.5.0).

**Options:**
1. Cancel the loop: \`/humanize:cancel-rlcr-loop\`
2. Update humanize plugin to version 1.5.0+
3. Restart the RLCR loop with the updated plugin"
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - state schema outdated (missing base_branch)" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

# ========================================
# Quick-check 0.2: Schema Warning (v1.5.2+ fields)
# ========================================
# Warn about missing full_review_round field (introduced in v1.5.2)
# This is a non-blocking warning - we continue with default value (5)

if [[ -z "$RAW_FULL_REVIEW_ROUND" ]]; then
    echo "Note: State file missing full_review_round field (introduced in v1.5.2)." >&2
    echo "  Using default value: 5 (Full Alignment Checks at rounds 4, 9, 14, ...)" >&2
    echo "  To use configurable Full Alignment Check intervals, upgrade to humanize v1.5.2+" >&2
    echo "  and restart the RLCR loop with --full-review-round <N> option." >&2
fi

# ========================================
# Quick-check 0.5: Branch Consistency
# ========================================

# Use || GIT_EXIT_CODE=$? to prevent set -e from aborting on non-zero exit
CURRENT_BRANCH=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null) || GIT_EXIT_CODE=$?
GIT_EXIT_CODE=${GIT_EXIT_CODE:-0}
if [[ $GIT_EXIT_CODE -ne 0 || -z "$CURRENT_BRANCH" ]]; then
    REASON="Git operation failed or timed out.

Cannot verify branch consistency. This may indicate:
- Git is not responding
- Repository is in an invalid state
- Network issues (if remote operations are involved)

Please check git status manually and try again."
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - git operation failed" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

if [[ -n "$START_BRANCH" && "$CURRENT_BRANCH" != "$START_BRANCH" ]]; then
    REASON="Git branch changed during RLCR loop.

Started on: $START_BRANCH
Current: $CURRENT_BRANCH

Branch switching is not allowed. Switch back to $START_BRANCH or cancel the loop."
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - branch changed" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

# ========================================
# Quick-check 0.6: Plan File Integrity
# ========================================
# Skip this check in Review Phase (review_started=true)
# In review phase, the plan file is no longer needed - only code review matters.
# This is especially important for skip-impl mode where no real plan file exists.

if [[ "$REVIEW_STARTED" == "true" ]]; then
    echo "Review phase: skipping plan file integrity check (plan no longer needed)" >&2
else

BACKUP_PLAN="$LOOP_DIR/plan.md"
FULL_PLAN_PATH="$PROJECT_ROOT/$PLAN_FILE"

# Check backup exists
if [[ ! -f "$BACKUP_PLAN" ]]; then
    REASON="Plan file backup not found in loop directory.

Please copy the plan file to the loop directory:
  cp \"$FULL_PLAN_PATH\" \"$BACKUP_PLAN\"

This backup is required for plan integrity verification."
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - plan backup missing" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

# Check original plan file still matches backup
if [[ ! -f "$FULL_PLAN_PATH" ]]; then
    REASON="Project plan file has been deleted.

Original: $PLAN_FILE
Backup available at: $BACKUP_PLAN

You can restore from backup if needed. Plan file modifications are not allowed during RLCR loop."
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - plan file deleted" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

# Check plan file integrity
# For tracked files: check both git status (uncommitted) AND content diff (committed changes)
# For gitignored files: check content diff only
if [[ "$PLAN_TRACKED" == "true" ]]; then
    # Tracked file: first check git status for uncommitted changes
    PLAN_GIT_STATUS=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" status --porcelain "$PLAN_FILE" 2>/dev/null || echo "")
    if [[ -n "$PLAN_GIT_STATUS" ]]; then
        REASON="Plan file has uncommitted modifications.

File: $PLAN_FILE
Status: $PLAN_GIT_STATUS

This RLCR loop was started with --track-plan-file. Plan file modifications are not allowed during the loop."
        jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - plan file modified (uncommitted)" \
            '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
        exit 0
    fi
fi

# Plan changes are now allowed: plan.md is a symlink to the original, so this diff always passes
if ! diff -q "$FULL_PLAN_PATH" "$BACKUP_PLAN" &>/dev/null; then
    FALLBACK="# Plan File Modified

The plan file \`$PLAN_FILE\` has been modified since the RLCR loop started.

**Modifying plan files is forbidden during an active RLCR loop.**

If you need to change the plan:
1. Cancel the current loop: \`/humanize:cancel-rlcr-loop\`
2. Update the plan file
3. Start a new loop: \`/humanize:start-rlcr-loop $PLAN_FILE\`

Backup available at: \`$BACKUP_PLAN\`"
    REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/plan-file-modified.md" "$FALLBACK" \
        "PLAN_FILE=$PLAN_FILE" \
        "BACKUP_PATH=$BACKUP_PLAN")
    jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - plan file modified" \
        '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
    exit 0
fi

fi  # End of REVIEW_STARTED != true check for plan file integrity

# ========================================
# Quick Check: Are All Tasks Completed?
# ========================================
# Before running expensive Codex review, check if Claude still has
# incomplete tasks. If yes, block immediately and tell Claude to finish.
# Supports both legacy TodoWrite and new Task system (TaskCreate/TaskUpdate).

TODO_CHECKER="$SCRIPT_DIR/check-todos-from-transcript.py"

if [[ -f "$TODO_CHECKER" ]]; then
    # Pass hook input to the task checker
    TODO_RESULT=$(echo "$HOOK_INPUT" | python3 "$TODO_CHECKER" 2>&1) || TODO_EXIT=$?
    TODO_EXIT=${TODO_EXIT:-0}

    if [[ "$TODO_EXIT" -eq 2 ]]; then
        # Parse error - block and surface the error
        REASON="Task checker encountered a parse error.

Error: $TODO_RESULT

This may indicate an issue with the hook input or transcript format.
Please try again or cancel the loop if this persists."
        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Blocked - task checker parse error" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi

    if [[ "$TODO_EXIT" -eq 1 ]]; then
        # Incomplete tasks found - block immediately without Codex review
        # Extract the incomplete task list from the result
        INCOMPLETE_LIST=$(echo "$TODO_RESULT" | tail -n +2)

        FALLBACK="# Incomplete Tasks

Complete these tasks before exiting:

{{INCOMPLETE_LIST}}"
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/incomplete-todos.md" "$FALLBACK" \
            "INCOMPLETE_LIST=$INCOMPLETE_LIST")

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Blocked - incomplete tasks detected, please finish all tasks first" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi
fi

# ========================================
# Helper: Clean Up Stale index.lock
# ========================================
# git status (and other git commands) temporarily create .git/index.lock
# while refreshing the index. If a git process is killed mid-operation
# (e.g., by a timeout wrapper), the lock file can be left behind,
# causing subsequent git add/commit to fail with:
#   fatal: Unable to create '.git/index.lock': File exists.
# This helper removes the stale lock so Claude's commit won't fail.
cleanup_stale_index_lock() {
    # Resolve the git dir relative to PROJECT_ROOT, not the hook's cwd, so
    # that index.lock cleanup targets the correct repo even when the hook
    # executes from a plugin/cache directory rather than the project root.
    local project_root="${1:-$PROJECT_ROOT}"
    local git_dir
    git_dir=$(git -C "$project_root" rev-parse --git-dir 2>/dev/null) || return 0
    # git rev-parse --git-dir may return a relative path; make it absolute.
    if [[ "$git_dir" != /* ]]; then
        git_dir="$project_root/$git_dir"
    fi
    if [[ -f "$git_dir/index.lock" ]]; then
        echo "Removing stale $git_dir/index.lock" >&2
        rm -f "$git_dir/index.lock"
    fi
}

# ========================================
# Cache Git Status Output
# ========================================
# Cache git status output to avoid calling it multiple times.
# Used by both large file check and git clean check below.
# IMPORTANT: Fail-closed on git failures to prevent bypassing checks.

GIT_STATUS_CACHED=""
GIT_IS_REPO=false

if command -v git &>/dev/null && run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" rev-parse --git-dir &>/dev/null 2>&1; then
    GIT_IS_REPO=true
    # Capture exit code to detect timeout/failure - do NOT use || echo "" which would fail-open
    GIT_STATUS_EXIT=0
    GIT_STATUS_CACHED=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" status --porcelain 2>/dev/null) || GIT_STATUS_EXIT=$?

    if [[ $GIT_STATUS_EXIT -ne 0 ]]; then
        # Git status failed or timed out - fail-closed by blocking exit
        # The timed-out git status may have left a stale index.lock
        cleanup_stale_index_lock
        FALLBACK="# Git Status Failed

Git status operation failed or timed out (exit code {{GIT_STATUS_EXIT}}).

Cannot verify repository state. Please check git status manually and try again."
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/git-status-failed.md" "$FALLBACK" \
            "GIT_STATUS_EXIT=$GIT_STATUS_EXIT")
        jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - git status failed (exit $GIT_STATUS_EXIT)" \
            '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
        exit 0
    fi
fi

# ========================================
# Quick Check: Large File Detection
# ========================================
# Check if any tracked or new files exceed the line limit.
# Large files should be split into smaller modules.

MAX_LINES=2000

if [[ "$GIT_IS_REPO" == "true" ]]; then
    LARGE_FILES=""

    while IFS= read -r line; do
        # Skip empty lines
        if [ -z "$line" ]; then
            continue
        fi

        # Extract filename (skip first 3 chars: "XY ")
        filename="${line#???}"

        # Handle renames: "old -> new" format
        case "$filename" in
            *" -> "*) filename="${filename##* -> }" ;;
        esac

        # Resolve filename relative to PROJECT_ROOT (git status --porcelain
        # returns project-relative paths, but the hook may run from a
        # different working directory).
        filename="$PROJECT_ROOT/$filename"

        # Skip deleted files
        if [ ! -f "$filename" ]; then
            continue
        fi

        # Get file extension and convert to lowercase
        ext="${filename##*.}"
        ext_lower=$(to_lower "$ext")

        # Determine file type based on extension
        case "$ext_lower" in
            py|js|ts|tsx|jsx|java|c|cpp|cc|cxx|h|hpp|cs|go|rs|rb|php|swift|kt|kts|scala|sh|bash|zsh)
                file_type="code"
                ;;
            md|rst|txt|adoc|asciidoc)
                file_type="documentation"
                ;;
            *)
                continue
                ;;
        esac

        # Count lines and trim whitespace (portable across shells)
        line_count=$(wc -l < "$filename" 2>/dev/null | tr -d ' ') || continue

        # Validate line_count is numeric before comparison
        [[ "$line_count" =~ ^[0-9]+$ ]] || continue

        if [ "$line_count" -gt "$MAX_LINES" ]; then
            LARGE_FILES="${LARGE_FILES}
- \`${filename}\`: ${line_count} lines (${file_type} file)"
        fi
    done <<< "$GIT_STATUS_CACHED"

    if [ -n "$LARGE_FILES" ]; then
        FALLBACK="# Large Files Detected

Files exceeding {{MAX_LINES}} lines:

{{LARGE_FILES}}

Split these into smaller modules before continuing."
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/large-files.md" "$FALLBACK" \
            "MAX_LINES=$MAX_LINES" \
            "LARGE_FILES=$LARGE_FILES")

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Blocked - large files detected (>${MAX_LINES} lines), please split into smaller modules" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi
fi

# ========================================
# Methodology Analysis Phase Completion Handler
# ========================================
# When in methodology analysis phase, check if the analysis is done.
# If done, rename state to the original exit reason's terminal state.
# If not done, block and ask Claude to complete the analysis.
# All other checks (summary, bitlesson, goal tracker, max iterations) are skipped.
# IMPORTANT: This MUST run before the git-clean check, because methodology
# artifacts (.humanize/rlcr/...) may make the working tree appear dirty
# if .humanize is tracked, which would block exit before reaching this handler.

if [[ "$IS_METHODOLOGY_ANALYSIS_PHASE" == "true" ]]; then
    if complete_methodology_analysis; then
        # Before allowing the terminal state transition, re-verify the
        # working tree is clean. The main git-clean gate below is skipped
        # in the methodology branch, so without this check, tracked edits
        # made during the analysis phase (e.g. post-signoff source
        # modifications) could slip through unreviewed as soon as the
        # completion marker appears.
        #
        # Apply the same .humanize/ untracked exclusion the main gate uses
        # so methodology-artifact writes under .humanize/rlcr/... do not
        # themselves trip the check.
        if [[ "$GIT_IS_REPO" == "true" ]]; then
            HUMANIZE_UNTRACKED_PATTERN='^\?\? \.humanize[-/]'
            GIT_STATUS_FOR_BLOCK=$(echo "$GIT_STATUS_CACHED" | grep -vE "$HUMANIZE_UNTRACKED_PATTERN" || true)
            if [[ -n "$GIT_STATUS_FOR_BLOCK" ]]; then
                cleanup_stale_index_lock
                FALLBACK="# Git Not Clean

Methodology analysis is complete, but the working tree still has uncommitted changes:

{{GIT_ISSUES}}

Please commit all changes before allowing the loop to exit.
{{SPECIAL_NOTES}}"
                REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/git-not-clean.md" "$FALLBACK" \
                    "GIT_ISSUES=uncommitted changes after methodology analysis" \
                    "SPECIAL_NOTES=")

                jq -n \
                    --arg reason "$REASON" \
                    --arg msg "Loop: Blocked - uncommitted changes detected after methodology analysis, please commit first" \
                    '{
                        "decision": "block",
                        "reason": $reason,
                        "systemMessage": $msg
                    }'
                exit 0
            fi
        fi
        # Analysis complete and tree clean, allow exit
        exit 0
    else
        # Analysis not yet complete, block
        block_methodology_analysis_incomplete
        exit 0
    fi
fi

# ========================================
# Quick Check: Git Clean and Pushed?
# ========================================
# Before running expensive Codex review, check if all changes have been
# committed and pushed. This ensures work is properly saved.

# Use cached git status from above
if [[ "$GIT_IS_REPO" == "true" ]]; then
    GIT_ISSUES=""
    SPECIAL_NOTES=""

    if git_has_tracked_humanize_state "$PROJECT_ROOT"; then
        cleanup_stale_index_lock
        REASON=$(git_tracked_humanize_blocked_message)

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Blocked - tracked Humanize state detected, remove it from git first" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi

    # Check for uncommitted changes (staged or unstaged) using cached status.
    # Exclude untracked .humanize/ paths and .humanize-* dash-separated legacy
    # variants from the dirty determination because local plugin state under
    # .humanize/ (.humanize/bitlesson.md, config.json, rlcr/) is intentionally
    # untracked.
    HUMANIZE_UNTRACKED_PATTERN='^\?\? \.humanize[-/]'
    GIT_STATUS_FOR_BLOCK=$(echo "$GIT_STATUS_CACHED" | grep -vE "$HUMANIZE_UNTRACKED_PATTERN" || true)
    if [[ -n "$GIT_STATUS_FOR_BLOCK" ]]; then
        GIT_ISSUES="uncommitted changes"

        # Check for special cases in untracked files (use original status for notes)
        UNTRACKED=$(echo "$GIT_STATUS_CACHED" | grep '^??' || true)

        # Check if .humanize/ or .humanize-* dash-separated legacy variants are untracked.
        if echo "$UNTRACKED" | grep -qE "$HUMANIZE_UNTRACKED_PATTERN"; then
            HUMANIZE_LOCAL_NOTE=$(load_template "$TEMPLATE_DIR" "block/git-not-clean-humanize-local.md" 2>/dev/null)
            if [[ -z "$HUMANIZE_LOCAL_NOTE" ]]; then
                HUMANIZE_LOCAL_NOTE="Note: .humanize/ and .humanize-* directories are intentionally untracked."
            fi
            SPECIAL_NOTES="$SPECIAL_NOTES$HUMANIZE_LOCAL_NOTE"
        fi

        # Check for other untracked files (potential artifacts)
        OTHER_UNTRACKED=$(echo "$UNTRACKED" | grep -vE "$HUMANIZE_UNTRACKED_PATTERN" || true)
        if [[ -n "$OTHER_UNTRACKED" ]]; then
            UNTRACKED_NOTE=$(load_template "$TEMPLATE_DIR" "block/git-not-clean-untracked.md" 2>/dev/null)
            if [[ -z "$UNTRACKED_NOTE" ]]; then
                UNTRACKED_NOTE="Review untracked files - add to .gitignore or commit them."
            fi
            SPECIAL_NOTES="$SPECIAL_NOTES$UNTRACKED_NOTE"
        fi
    fi

    # Block if there are uncommitted changes
    if [[ -n "$GIT_ISSUES" ]]; then
        # Clean up stale index.lock before Claude attempts git add/commit
        cleanup_stale_index_lock
        # Git has uncommitted changes - block and remind Claude to commit
        FALLBACK="# Git Not Clean

Detected: {{GIT_ISSUES}}

Please commit all changes before exiting.
{{SPECIAL_NOTES}}"
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/git-not-clean.md" "$FALLBACK" \
            "GIT_ISSUES=$GIT_ISSUES" \
            "SPECIAL_NOTES=$SPECIAL_NOTES")

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Blocked - $GIT_ISSUES detected, please commit first" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi

    # ========================================
    # Check Unpushed Commits (only when push_every_round is true)
    # ========================================

    if [[ "$PUSH_EVERY_ROUND" == "true" ]]; then
        # Check if local branch is ahead of remote (unpushed commits)
        GIT_AHEAD=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" status -sb 2>/dev/null | grep -o 'ahead [0-9]*' || true)
        if [[ -n "$GIT_AHEAD" ]]; then
            AHEAD_COUNT=$(echo "$GIT_AHEAD" | grep -o '[0-9]*')
            CURRENT_BRANCH=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")

            FALLBACK="# Unpushed Commits

You have {{AHEAD_COUNT}} unpushed commit(s) on branch {{CURRENT_BRANCH}}.

Please push before exiting."
            REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/unpushed-commits.md" "$FALLBACK" \
                "AHEAD_COUNT=$AHEAD_COUNT" \
                "CURRENT_BRANCH=$CURRENT_BRANCH")

            jq -n \
                --arg reason "$REASON" \
                --arg msg "Loop: Blocked - $AHEAD_COUNT unpushed commit(s) detected, please push first" \
                '{
                    "decision": "block",
                    "reason": $reason,
                    "systemMessage": $msg
                }'
            exit 0
        fi
    fi
fi

# ========================================
# Check Summary File Exists
# ========================================

# In Finalize Phase, expect finalize-summary.md instead of round-N-summary.md
if [[ "$IS_FINALIZE_PHASE" == "true" ]]; then
    SUMMARY_FILE="$LOOP_DIR/finalize-summary.md"
    ROUND_CONTRACT_FILE=""
else
    SUMMARY_FILE="$LOOP_DIR/round-${CURRENT_ROUND}-summary.md"
    ROUND_CONTRACT_FILE="$LOOP_DIR/round-${CURRENT_ROUND}-contract.md"
fi

if [[ ! -f "$SUMMARY_FILE" ]]; then
    # Summary file doesn't exist - Claude didn't write it
    # Block exit and remind Claude to write summary

    FALLBACK="# Work Summary Missing

Please write your work summary to: {{SUMMARY_FILE}}"
    REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/work-summary-missing.md" "$FALLBACK" \
        "SUMMARY_FILE=$SUMMARY_FILE")

    if [[ "$IS_FINALIZE_PHASE" == "true" ]]; then
        SYSTEM_MSG="Loop: Finalize Phase - summary file missing"
    else
        SYSTEM_MSG="Loop: Summary file missing for round $CURRENT_ROUND"
    fi

    jq -n \
        --arg reason "$REASON" \
        --arg msg "$SYSTEM_MSG" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
fi

# Check Round Contract Exists
# ========================================

# Only enforce round contract when anti-drift is active (drift_status present in raw state).
# Legacy loops that pre-date the anti-drift feature will not have this field.
RAW_DRIFT_STATUS=$(echo "$RAW_FRONTMATTER" | grep "^drift_status:" || true)
if [[ "$IS_FINALIZE_PHASE" != "true" ]] && [[ -n "$RAW_DRIFT_STATUS" ]]; then
    if [[ ! -f "$ROUND_CONTRACT_FILE" ]]; then
        FALLBACK="# Round Contract Missing

Before trying to exit, write the current round contract to: {{ROUND_CONTRACT_FILE}}

The round contract must restate:
- The single mainline objective for this round
- The target ACs
- Which side issues are truly blocking
- Which side issues are queued and out of scope
- The success criteria for this round"
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/round-contract-missing.md" "$FALLBACK" \
            "ROUND_CONTRACT_FILE=$ROUND_CONTRACT_FILE")

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Round contract missing for round $CURRENT_ROUND" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi
fi

# ========================================
# Check BitLesson Delta Section (all non-finalize rounds)
# ========================================

if [[ "$IS_FINALIZE_PHASE" != "true" ]] && [[ "$BITLESSON_REQUIRED" == "true" ]]; then
    BITLESSON_DELTA_RESULT=$(bash "$PLUGIN_ROOT/scripts/bitlesson-validate-delta.sh" \
        --summary-file "$SUMMARY_FILE" \
        --bitlesson-file "$BITLESSON_FILE" \
        --bitlesson-relpath "$BITLESSON_FILE_REL" \
        --allow-empty-none "$BITLESSON_ALLOW_EMPTY_NONE" \
        --template-dir "$TEMPLATE_DIR" \
        --current-round "$CURRENT_ROUND") || {
        echo "Error: bitlesson-validate-delta.sh failed" >&2
        exit 1
    }
    if [[ -n "$BITLESSON_DELTA_RESULT" ]]; then
        echo "$BITLESSON_DELTA_RESULT"
        exit 0
    fi
fi

# ========================================
# Check Goal Tracker Initialization (Round 0 only, skip in Finalize Phase)
# ========================================

GOAL_TRACKER_FILE="$LOOP_DIR/goal-tracker.md"

# Skip this check in Finalize Phase, Review Phase, or when review_started is already true (skip-impl mode)
# - Finalize Phase: goal tracker was already initialized before COMPLETE
# - Review Phase: later rounds may update only the mutable section, so Round 0 placeholder checks no longer apply
if [[ "$IS_FINALIZE_PHASE" != "true" ]] && [[ "$REVIEW_STARTED" != "true" ]] && [[ "$CURRENT_ROUND" -eq 0 ]] && [[ -f "$GOAL_TRACKER_FILE" ]]; then
    # Check if goal-tracker.md still contains placeholder text
    # Extract each section and check for generic placeholder pattern within that section
    # This avoids coupling to specific placeholder wording and prevents false positives
    # from stray mentions of placeholder text elsewhere in the file

    HAS_GOAL_PLACEHOLDER=false
    HAS_AC_PLACEHOLDER=false
    HAS_TASKS_PLACEHOLDER=false

    # Extract Ultimate Goal section (### Ultimate Goal to next heading)
    # Use awk to extract lines between start and end patterns, excluding end pattern
    GOAL_SECTION=$(awk '/^### Ultimate Goal/{found=1; next} /^##/{found=0} found' "$GOAL_TRACKER_FILE" 2>/dev/null)
    # Check for generic placeholder pattern "[To be " within this section
    if echo "$GOAL_SECTION" | grep -qE '\[To be [a-z]'; then
        HAS_GOAL_PLACEHOLDER=true
    fi

    # Extract Acceptance Criteria section (### Acceptance Criteria to next heading)
    AC_SECTION=$(awk '/^### Acceptance Criteria/{found=1; next} /^##/{found=0} found' "$GOAL_TRACKER_FILE" 2>/dev/null)
    # Check for generic placeholder pattern "[To be " within this section
    if echo "$AC_SECTION" | grep -qE '\[To be [a-z]'; then
        HAS_AC_PLACEHOLDER=true
    fi

    # Extract Active Tasks section (#### Active Tasks to next heading or EOF)
    # Active Tasks is a level-4 heading, so match any ## or higher
    TASKS_SECTION=$(awk '/^#### Active Tasks/{found=1; next} /^##/{found=0} found' "$GOAL_TRACKER_FILE" 2>/dev/null)
    # Check for generic placeholder pattern "[To be " within this section
    if echo "$TASKS_SECTION" | grep -qE '\[To be [a-z]'; then
        HAS_TASKS_PLACEHOLDER=true
    fi

    # Build list of missing items
    MISSING_ITEMS=""
    if [[ "$HAS_GOAL_PLACEHOLDER" == "true" ]]; then
        MISSING_ITEMS="$MISSING_ITEMS
- **Ultimate Goal**: Still contains placeholder text"
    fi
    if [[ "$HAS_AC_PLACEHOLDER" == "true" ]]; then
        MISSING_ITEMS="$MISSING_ITEMS
- **Acceptance Criteria**: Still contains placeholder text"
    fi
    if [[ "$HAS_TASKS_PLACEHOLDER" == "true" ]]; then
        MISSING_ITEMS="$MISSING_ITEMS
- **Active Tasks**: Still contains placeholder text"
    fi

    if [[ -n "$MISSING_ITEMS" ]]; then
        FALLBACK="# Goal Tracker Not Initialized

Please fill in the Goal Tracker ({{GOAL_TRACKER_FILE}}):
{{MISSING_ITEMS}}"
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/goal-tracker-not-initialized.md" "$FALLBACK" \
            "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
            "MISSING_ITEMS=$MISSING_ITEMS")

        jq -n \
            --arg reason "$REASON" \
            --arg msg "Loop: Goal Tracker not initialized in Round 0" \
            '{
                "decision": "block",
                "reason": $reason,
                "systemMessage": $msg
            }'
        exit 0
    fi
fi

# ========================================
# Check Max Iterations (skip in Finalize Phase - already post-COMPLETE)
# ========================================

NEXT_ROUND=$((CURRENT_ROUND + 1))

# Skip max iterations check in Finalize Phase or Review Phase
# - Finalize Phase: already received COMPLETE from codex
# - Review Phase: must continue until [P?] issues are cleared, regardless of iteration count
if [[ "$IS_FINALIZE_PHASE" != "true" ]] && [[ "$REVIEW_STARTED" != "true" ]] && [[ $NEXT_ROUND -gt $MAX_ITERATIONS ]]; then
    echo "RLCR loop did not complete, but reached max iterations ($MAX_ITERATIONS). Exiting." >&2
    # Try to enter methodology analysis phase before final exit
    if enter_methodology_analysis_phase "maxiter" "Reached max iterations ($MAX_ITERATIONS) without completion"; then
        exit 0
    fi
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_MAXITER"
    exit 0
fi

# ========================================
# Finalize Phase Completion (skip Codex review)
# ========================================
# If we're in Finalize Phase and all checks have passed, complete the loop
# No Codex review is performed - this is the final step after Codex already confirmed COMPLETE

if [[ "$IS_FINALIZE_PHASE" == "true" ]]; then
    echo "Finalize Phase complete. All checks passed." >&2
    # Try to enter methodology analysis phase before final exit
    if enter_methodology_analysis_phase "complete" "All acceptance criteria met and code review passed"; then
        exit 0
    fi
    # Methodology analysis skipped or already done - proceed with normal exit
    mv "$STATE_FILE" "$LOOP_DIR/complete-state.md"
    echo "State preserved as: $LOOP_DIR/complete-state.md" >&2
    exit 0
fi

# ========================================
# Docs Path (static default)
# ========================================

DOCS_PATH="docs"

# ========================================
# Build Codex Review Prompt
# ========================================

PROMPT_FILE="$LOOP_DIR/round-${CURRENT_ROUND}-prompt.md"
REVIEW_PROMPT_FILE="$LOOP_DIR/round-${CURRENT_ROUND}-review-prompt.md"
REVIEW_RESULT_FILE="$LOOP_DIR/round-${CURRENT_ROUND}-review-result.md"

SUMMARY_CONTENT=$(cat "$SUMMARY_FILE")

# Shared prompt section for Goal Tracker Update Requests (used in both Full Alignment and Regular reviews)
GOAL_TRACKER_SECTION_FALLBACK="## Goal Tracker Updates
If Claude's summary includes a Goal Tracker Update Request section, apply the requested changes to {{GOAL_TRACKER_FILE}}."
GOAL_TRACKER_UPDATE_SECTION=$(load_and_render_safe "$TEMPLATE_DIR" "codex/goal-tracker-update-section.md" "$GOAL_TRACKER_SECTION_FALLBACK" \
    "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE")

# Determine if this is a Full Alignment Check round (every FULL_REVIEW_ROUND rounds)
# Full Alignment Checks occur at rounds (N-1), (2N-1), (3N-1), etc. where N=FULL_REVIEW_ROUND
# Validate FULL_REVIEW_ROUND is a positive integer (default to 5 if invalid/corrupted)
if ! [[ "$FULL_REVIEW_ROUND" =~ ^[0-9]+$ ]] || [[ "$FULL_REVIEW_ROUND" -lt 2 ]]; then
    echo "Warning: Invalid full_review_round value '$FULL_REVIEW_ROUND', defaulting to 5" >&2
    FULL_REVIEW_ROUND=5
fi
FULL_ALIGNMENT_CHECK=false
if [[ $((CURRENT_ROUND % FULL_REVIEW_ROUND)) -eq $((FULL_REVIEW_ROUND - 1)) ]]; then
    FULL_ALIGNMENT_CHECK=true
fi

# Calculate derived values for templates
LOOP_TIMESTAMP=$(basename "$LOOP_DIR")
COMPLETED_ITERATIONS=$((CURRENT_ROUND + 1))
# Clamp previous round indices to 0 minimum to avoid negative file references
# This can happen with --full-review-round 2 where first alignment check is at 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}})

Review Claude's work against the plan and goal tracker. Check all goals are being met.

## 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."

REGULAR_REVIEW_FALLBACK="# Code Review (Round {{CURRENT_ROUND}})

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."

if [[ "$FULL_ALIGNMENT_CHECK" == "true" ]]; then
    # Full Alignment Check prompt
    load_and_render_safe "$TEMPLATE_DIR" "codex/full-alignment-review.md" "$FULL_ALIGNMENT_FALLBACK" \
        "CURRENT_ROUND=$CURRENT_ROUND" \
        "PLAN_FILE=$PLAN_FILE" \
        "SUMMARY_CONTENT=$SUMMARY_CONTENT" \
        "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" \
        "PREV_PREV_ROUND=$PREV_PREV_ROUND" \
        "REVIEW_RESULT_FILE=$REVIEW_RESULT_FILE" > "$REVIEW_PROMPT_FILE"

else
    # Regular review prompt with goal alignment section
    load_and_render_safe "$TEMPLATE_DIR" "codex/regular-review.md" "$REGULAR_REVIEW_FALLBACK" \
        "CURRENT_ROUND=$CURRENT_ROUND" \
        "PLAN_FILE=$PLAN_FILE" \
        "PROMPT_FILE=$PROMPT_FILE" \
        "SUMMARY_CONTENT=$SUMMARY_CONTENT" \
        "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" \
        "PREV_PREV_ROUND=$PREV_PREV_ROUND" \
        "REVIEW_RESULT_FILE=$REVIEW_RESULT_FILE" > "$REVIEW_PROMPT_FILE"
fi

# ========================================
# Shared Setup: Cache Directory and Codex Arguments
# ========================================
# Initialize these before the REVIEW_STARTED guard so they are available in both
# impl phase (codex exec) and review phase (codex review)

# First, check if Codex CLI exists
if ! command -v codex >/dev/null 2>&1; then
    REASON="# Codex CLI Not Found

The 'codex' CLI is not installed or not in PATH.
RLCR loop requires it to perform reviews.

**To fix:**
1. Install Codex CLI: https://github.com/openai/codex
2. Retry the exit

Or use \`/cancel-rlcr-loop\` to end the loop."

    cat <<EOF
{
    "decision": "block",
    "reason": $(echo "$REASON" | jq -Rs .)
}
EOF
    exit 0
fi

# Debug log files go to XDG_CACHE_HOME/humanize/<project-path>/<timestamp>/ to avoid polluting project dir
# Respects XDG_CACHE_HOME for testability in restricted environments (falls back to $HOME/.cache)
# This prevents Claude and Codex from reading these debug files during their work
# The project path is sanitized to replace problematic characters with '-'
# LOOP_TIMESTAMP already set above via basename "$LOOP_DIR"
# Sanitize project root path: replace / and other problematic chars with -
# This matches Claude Code's convention (e.g., /home/sihao/github.com/foo -> -home-sihao-github-com-foo)
SANITIZED_PROJECT_PATH=$(echo "$PROJECT_ROOT" | sed 's/[^a-zA-Z0-9._-]/-/g' | sed 's/--*/-/g')
CACHE_BASE="${XDG_CACHE_HOME:-$HOME/.cache}"
CACHE_DIR="$CACHE_BASE/humanize/$SANITIZED_PROJECT_PATH/$LOOP_TIMESTAMP"
mkdir -p "$CACHE_DIR"

# portable-timeout.sh already sourced above

# Disable native hooks for nested Codex reviewer calls to prevent Stop-hook recursion.
# Probe whether the installed Codex CLI supports --disable; cache the result per loop
# so older builds do not fail with an unknown-argument error.
CODEX_DISABLE_HOOKS_ARGS=()
_CODEX_FEATURE_CACHE="$CACHE_DIR/.codex-disable-hooks-supported"
if [[ -f "$_CODEX_FEATURE_CACHE" ]]; then
    [[ "$(cat "$_CODEX_FEATURE_CACHE")" == "yes" ]] && CODEX_DISABLE_HOOKS_ARGS=(--disable hooks)
elif codex --help 2>&1 | grep -q -- '--disable'; then
    CODEX_DISABLE_HOOKS_ARGS=(--disable hooks)
    echo "yes" > "$_CODEX_FEATURE_CACHE" 2>/dev/null
else
    echo "no" > "$_CODEX_FEATURE_CACHE" 2>/dev/null
fi

# Build command arguments for summary review (codex exec)
CODEX_EXEC_ARGS=("-m" "$CODEX_EXEC_MODEL")
if [[ -n "$CODEX_EXEC_EFFORT" ]]; then
    CODEX_EXEC_ARGS+=("-c" "model_reasoning_effort=${CODEX_EXEC_EFFORT}")
fi

CODEX_AUTO_FLAG="--full-auto"
if [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "true" ]] || [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "1" ]]; then
    CODEX_AUTO_FLAG="--dangerously-bypass-approvals-and-sandbox"
fi
CODEX_EXEC_ARGS+=("$CODEX_AUTO_FLAG" "-C" "$PROJECT_ROOT")

# Build Codex command arguments for codex review
CODEX_REVIEW_ARGS=("-c" "model=${CODEX_REVIEW_MODEL}" "-c" "review_model=${CODEX_REVIEW_MODEL}")
if [[ -n "$CODEX_REVIEW_EFFORT" ]]; then
    CODEX_REVIEW_ARGS+=("-c" "model_reasoning_effort=${CODEX_REVIEW_EFFORT}")
fi

# ========================================
# Helper Functions for Code Review Phase
# ========================================

# Run code review and save debug files
# Arguments: $1=round_number
# Sets: CODEX_REVIEW_EXIT_CODE, CODEX_REVIEW_LOG_FILE
# Returns: exit code from the configured review CLI
run_codex_code_review() {
    local round="$1"
    local timestamp
    timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)

    # Determine review base: prefer BASE_COMMIT (captured at loop start) over BASE_BRANCH
    # Using the fixed commit SHA prevents comparing a branch to itself when working on main,
    # as the branch ref advances with each commit but the captured SHA stays fixed
    local review_base="${BASE_COMMIT:-$BASE_BRANCH}"
    local review_base_type="branch"
    if [[ -n "$BASE_COMMIT" ]]; then
        review_base_type="commit"
    fi

    CODEX_REVIEW_CMD_FILE="$CACHE_DIR/round-${round}-codex-review.cmd"
    CODEX_REVIEW_LOG_FILE="$CACHE_DIR/round-${round}-codex-review.log"
    local prompt_file="$LOOP_DIR/round-${round}-review-prompt.md"

    # Create audit prompt file describing the code review invocation
    local prompt_fallback="# Code Review Phase - Round ${round}

This file documents the code review invocation for audit purposes.
Provider: codex

## Review Configuration
- Base Branch: ${BASE_BRANCH}
- Base Commit: ${BASE_COMMIT:-N/A}
- Review Base (${review_base_type}): ${review_base}
- Review Round: ${round}
- Timestamp: ${timestamp}
"
    load_and_render_safe "$TEMPLATE_DIR" "codex/code-review-phase.md" "$prompt_fallback" \
        "REVIEW_ROUND=$round" \
        "BASE_BRANCH=$BASE_BRANCH" \
        "BASE_COMMIT=${BASE_COMMIT:-N/A}" \
        "REVIEW_BASE=$review_base" \
        "REVIEW_BASE_TYPE=$review_base_type" \
        "TIMESTAMP=$timestamp" > "$prompt_file"

    echo "Code review prompt (audit) saved to: $prompt_file" >&2

    {
        echo "# Code review invocation debug info"
        echo "# Timestamp: $timestamp"
        echo "# Working directory: $PROJECT_ROOT"
        echo "# Base branch: $BASE_BRANCH"
        echo "# Base commit: ${BASE_COMMIT:-N/A}"
        echo "# Review base ($review_base_type): $review_base"
        echo "# Timeout: $CODEX_TIMEOUT seconds"
        echo ""
        echo "codex review ${CODEX_DISABLE_HOOKS_ARGS[*]} --base $review_base ${CODEX_REVIEW_ARGS[*]}"
    } > "$CODEX_REVIEW_CMD_FILE"

    echo "Code review command saved to: $CODEX_REVIEW_CMD_FILE" >&2
    echo "Running codex review with timeout ${CODEX_TIMEOUT}s in $PROJECT_ROOT (base: $review_base)..." >&2

    CODEX_REVIEW_EXIT_CODE=0
    (cd "$PROJECT_ROOT" && run_with_timeout "$CODEX_TIMEOUT" codex review "${CODEX_DISABLE_HOOKS_ARGS[@]}" --base "$review_base" "${CODEX_REVIEW_ARGS[@]}") \
        > "$CODEX_REVIEW_LOG_FILE" 2>&1 || CODEX_REVIEW_EXIT_CODE=$?

    echo "Code review exit code: $CODEX_REVIEW_EXIT_CODE" >&2
    echo "Code review log saved to: $CODEX_REVIEW_LOG_FILE" >&2

    return "$CODEX_REVIEW_EXIT_CODE"
}

# Note: detect_review_issues() is defined in loop-common.sh and sourced above

# Run code review and handle the result
# Arguments: $1=round_number, $2=success_system_message
# This function consolidates the common pattern of:
#   1. Running codex review (no prompt - uses --base only)
#   2. Checking results and handling outcomes
# On success (no issues), calls enter_finalize_phase and exits
# On issues found, calls continue_review_loop_with_issues and exits
# On failure, calls block_review_failure and exits
#
# Round numbering: After COMPLETE at round N, all review phase files use round N+1
# The caller passes CURRENT_ROUND + 1 as the round_number parameter
run_and_handle_code_review() {
    local round="$1"
    local success_msg="$2"

    echo "Running codex review against base branch: $BASE_BRANCH..." >&2

    # Run codex review using helper function
    # IMPORTANT: Review failure is a blocking error - do NOT skip to finalize
    if ! run_codex_code_review "$round"; then
        block_review_failure "$round" "Codex review command failed" "$CODEX_REVIEW_EXIT_CODE"
    fi

    # Check both stdout and result file for [P0-9] issues (plan requirement)
    # detect_review_issues returns: 0=issues found, 1=no issues, 2=stdout missing (hard error)
    local merged_content=""
    local detect_exit=0
    merged_content=$(detect_review_issues "$round") || detect_exit=$?

    if [[ "$detect_exit" -eq 2 ]]; then
        # Stdout missing/empty is a hard error - block and require retry
        block_review_failure "$round" "Codex review produced no stdout output" "N/A"
    elif [[ "$detect_exit" -eq 0 ]] && [[ -n "$merged_content" ]]; then
        # Issues found - continue review loop
        continue_review_loop_with_issues "$round" "$merged_content"
    else
        # No issues found (exit code 1) - proceed to finalize
        echo "Code review passed with no issues. Proceeding to finalize phase." >&2
        enter_finalize_phase "" "$success_msg"
    fi
}

# Enter finalize phase with appropriate prompt
# Arguments: $1=skip_reason (empty if not skipped), $2=system_message
enter_finalize_phase() {
    local skip_reason="$1"
    local system_msg="$2"

    mv "$STATE_FILE" "$LOOP_DIR/finalize-state.md"
    echo "State file renamed to: $LOOP_DIR/finalize-state.md" >&2

    local finalize_summary_file="$LOOP_DIR/finalize-summary.md"
    local finalize_prompt

    if [[ -n "$skip_reason" ]]; then
        local fallback="# Finalize Phase (Review Skipped)

**Warning**: Code review was skipped due to: {{REVIEW_SKIP_REASON}}

The implementation could not be fully validated. You are now in the **Finalize Phase**.

## Important Notice
Since the code review was skipped, please manually verify your changes before finalizing:
1. Review your code changes for any obvious issues
2. Run any available tests to verify correctness
3. Check for common code quality issues

## Simplification (Optional)
If time permits, use the \`code-simplifier:code-simplifier\` agent via the Task tool to simplify and refactor your code. Focus more on changes between branch from {{BASE_BRANCH}} to {{START_BRANCH}}.

## Constraints
- Must NOT change existing functionality
- Must NOT fail existing tests
- Must NOT introduce new bugs
- Only perform functionality-equivalent code refactoring and simplification

## Before Exiting
1. Complete all todos
2. Commit your changes
3. Write your finalize summary to: {{FINALIZE_SUMMARY_FILE}}"

        finalize_prompt=$(load_and_render_safe "$TEMPLATE_DIR" "claude/finalize-phase-skipped-prompt.md" "$fallback" \
            "FINALIZE_SUMMARY_FILE=$finalize_summary_file" \
            "PLAN_FILE=$PLAN_FILE" \
            "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
            "REVIEW_SKIP_REASON=$skip_reason" \
            "BASE_BRANCH=$BASE_BRANCH" \
            "START_BRANCH=$START_BRANCH")
    else
        local fallback="# Finalize Phase

Codex review has passed. The implementation is complete.

You are now in the **Finalize Phase**. Use the \`code-simplifier:code-simplifier\` agent via the Task tool to simplify and refactor your code.

## Constraints
- Must NOT change existing functionality
- Must NOT fail existing tests
- Must NOT introduce new bugs
- Only perform functionality-equivalent code refactoring and simplification

## Focus
Focus on the code changes made during this RLCR session. Focus more on changes between branch from {{BASE_BRANCH}} to {{START_BRANCH}}.

## Before Exiting
1. Complete all todos
2. Commit your changes
3. Write your finalize summary to: {{FINALIZE_SUMMARY_FILE}}"

        finalize_prompt=$(load_and_render_safe "$TEMPLATE_DIR" "claude/finalize-phase-prompt.md" "$fallback" \
            "FINALIZE_SUMMARY_FILE=$finalize_summary_file" \
            "PLAN_FILE=$PLAN_FILE" \
            "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
            "BASE_BRANCH=$BASE_BRANCH" \
            "START_BRANCH=$START_BRANCH")
    fi

    jq -n \
        --arg reason "$finalize_prompt" \
        --arg msg "$system_msg" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
}

# Append task tag routing reminder to follow-up prompts.
# Arguments: $1=prompt_file_path
append_task_tag_routing_note() {
    local prompt_file="$1"

    cat >> "$prompt_file" << 'ROUTING_EOF'

## Task Tag Routing Reminder

Follow the plan's per-task routing tags strictly:
- `coding` task -> Claude executes directly
- `analyze` task -> execute via `/humanize:ask-codex`, then integrate the result
- Keep Goal Tracker Active Tasks columns `Tag` and `Owner` aligned with execution
ROUTING_EOF
}

# Stop the loop when mainline progress has stalled for too many consecutive rounds.
# Arguments: $1=stall_count, $2=last_verdict
stop_for_mainline_drift() {
    local stall_count="$1"
    local last_verdict="$2"

    upsert_state_fields "$STATE_FILE" \
        "${FIELD_MAINLINE_STALL_COUNT}=${stall_count}" \
        "${FIELD_LAST_MAINLINE_VERDICT}=${last_verdict}" \
        "${FIELD_DRIFT_STATUS}=${DRIFT_STATUS_REPLAN_REQUIRED}"

    local fallback="# Mainline Drift Circuit Breaker

The RLCR loop has been stopped because the mainline failed to advance for {{STALL_COUNT}} consecutive implementation rounds.

- Last mainline verdict: {{LAST_VERDICT}}
- Drift status: replan_required

This loop should not continue automatically. Revisit the original plan, recover the round contract, and restart with a narrower mainline objective."
    local reason
    reason=$(load_and_render_safe "$TEMPLATE_DIR" "block/mainline-drift-stop.md" "$fallback" \
        "STALL_COUNT=$stall_count" \
        "LAST_VERDICT=$last_verdict" \
        "PLAN_FILE=$PLAN_FILE")

    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_STOP"

    jq -n \
        --arg reason "$reason" \
        --arg msg "Loop: Stopped - mainline drift circuit breaker triggered" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
}

# Block exit when implementation review output omits the required mainline verdict.
# Arguments: $1=review_result_file, $2=review_prompt_file
block_missing_mainline_verdict() {
    local review_result_file="$1"
    local review_prompt_file="$2"

    local fallback="# Mainline Verdict Missing

The implementation review output is missing the required line:

\`Mainline Progress Verdict: ADVANCED / STALLED / REGRESSED\`

Humanize cannot safely update drift state or choose the correct next-round prompt without this verdict.

Retry the exit so Codex reruns the implementation review.

Files:
- Review result: {{REVIEW_RESULT_FILE}}
- Review prompt: {{REVIEW_PROMPT_FILE}}"
    local reason
    reason=$(load_and_render_safe "$TEMPLATE_DIR" "block/mainline-verdict-missing.md" "$fallback" \
        "REVIEW_RESULT_FILE=$review_result_file" \
        "REVIEW_PROMPT_FILE=$review_prompt_file")

    jq -n \
        --arg reason "$reason" \
        --arg msg "Loop: Blocked - implementation review missing Mainline Progress Verdict" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
}

# Continue review loop when issues are found
# Arguments: $1=round_number, $2=review_content
continue_review_loop_with_issues() {
    local round="$1"
    local review_content="$2"

    echo "Code review found issues. Continuing review loop..." >&2

    # Update round number in state file
    local temp_file="${STATE_FILE}.tmp.$$"
    sed "s/^current_round: .*/current_round: $round/" "$STATE_FILE" > "$temp_file"
    mv "$temp_file" "$STATE_FILE"

    # Build review-fix prompt for Claude
    local next_prompt_file="$LOOP_DIR/round-${round}-prompt.md"
    local next_summary_file="$LOOP_DIR/round-${round}-summary.md"
    if [[ ! -f "$next_summary_file" ]]; then
        cat > "$next_summary_file" << EOF
# Review Round $round Summary

## Work Completed
- [Describe what was implemented in this phase]

## Files Changed
- [List created/modified files]

## Validation
- [List tests/commands run and outcomes]

## Remaining Items
- [List unresolved items, if any]

## BitLesson Delta
- Action: none|add|update
- Lesson ID(s): NONE
- Notes: [what changed and why]
EOF
    fi
    local next_contract_file="$LOOP_DIR/round-${round}-contract.md"

    local fallback="# Code Review Findings

You are in the **Review Phase** of the RLCR loop. Codex has performed a code review and found issues.

## Review Results

{{REVIEW_CONTENT}}

## Instructions

1. Re-anchor on the original plan and current goal tracker before changing code
2. Refresh the round contract at {{ROUND_CONTRACT_FILE}}
3. Address only the issues that are truly blocking the current mainline objective or code-review acceptance
4. Record non-blocking follow-up items as queued, not as the main goal
5. Commit your changes after fixing the issues
6. Write your summary to: {{SUMMARY_FILE}}"

    load_and_render_safe "$TEMPLATE_DIR" "claude/review-phase-prompt.md" "$fallback" \
        "REVIEW_CONTENT=$review_content" \
        "SUMMARY_FILE=$next_summary_file" \
        "BITLESSON_FILE=$BITLESSON_FILE" \
        "PLAN_FILE=$PLAN_FILE" \
        "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
        "ROUND_CONTRACT_FILE=$next_contract_file" \
        "CURRENT_ROUND=$round" > "$next_prompt_file"
    if [[ "$BITLESSON_REQUIRED" == "true" ]] && ! grep -q 'bitlesson-selector' "$next_prompt_file"; then
        cat >> "$next_prompt_file" << EOF

## BitLesson Selection (REQUIRED FOR EACH FIX TASK)

Before implementing each fix task, you MUST:

1. Read @$BITLESSON_FILE
2. Run \`bitlesson-selector\` for each fix task/sub-task to select relevant lesson IDs
3. Follow the selected lesson IDs (or \`NONE\`) during implementation

Reference: @$BITLESSON_FILE
EOF
    fi
    append_task_tag_routing_note "$next_prompt_file"

    jq -n \
        --arg reason "$(cat "$next_prompt_file")" \
        --arg msg "Loop: Review Phase Round $round - Fix code review issues" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
}

# Block exit when codex review fails or produces no output
# This is a hard error - the review phase cannot be skipped
# Arguments: $1=round_number, $2=failure_reason, $3=exit_code (optional)
block_review_failure() {
    local round="$1"
    local failure_reason="$2"
    local exit_code="${3:-unknown}"

    echo "ERROR: Codex review failed. Blocking exit and requiring retry." >&2

    local stderr_content=""
    local stderr_file="$CACHE_DIR/round-${round}-codex-review.log"
    if [[ -f "$stderr_file" ]]; then
        stderr_content=$(tail -50 "$stderr_file" 2>/dev/null || echo "(unable to read stderr)")
    fi

    local fallback="# Codex Review Failed

The code review could not be completed. This is a blocking error that requires retry.

## Error Details

**Reason**: {{FAILURE_REASON}}
**Round**: {{ROUND_NUMBER}}
**Base Branch**: {{BASE_BRANCH}}
**Exit Code**: {{EXIT_CODE}}

## What Happened

The \`codex review\` command failed to produce valid output. This can occur due to:
- Network connectivity issues
- Codex service timeout or unavailability
- Invalid review configuration
- Internal Codex errors

## Required Action

**You must retry the exit.** The review phase cannot be skipped - the loop must continue until code review passes with no \`[P0-9]\` issues found.

Steps to retry:
1. Ensure your changes are committed
2. Write your summary to the expected file
3. Attempt to exit again

If this error persists, consider canceling and restarting the loop: \`/humanize:cancel-rlcr-loop\`

## Debug Information

Stderr (last 50 lines):
\`\`\`
{{STDERR_CONTENT}}
\`\`\`"

    local reason
    reason=$(load_and_render_safe "$TEMPLATE_DIR" "block/codex-review-failed.md" "$fallback" \
        "FAILURE_REASON=$failure_reason" \
        "ROUND_NUMBER=$round" \
        "BASE_BRANCH=$BASE_BRANCH" \
        "EXIT_CODE=$exit_code" \
        "STDERR_CONTENT=$stderr_content" \
        "REVIEW_RESULT_FILE=$LOOP_DIR/round-${round}-review-result.md" \
        "CODEX_CMD_FILE=$CACHE_DIR/round-${round}-codex-review.cmd" \
        "CODEX_LOG_FILE=$CACHE_DIR/round-${round}-codex-review.log")

    jq -n \
        --arg reason "$reason" \
        --arg msg "Loop: Blocked - Codex review failed, retry required" \
        '{
            "decision": "block",
            "reason": $reason,
            "systemMessage": $msg
        }'
    exit 0
}

# ========================================
# Run Codex Review (Implementation Phase Only)
# ========================================
# Skip the summary review when in review phase - review phase uses codex review instead

if [[ "$REVIEW_STARTED" == "true" ]]; then
    echo "In review phase - skipping codex exec summary review, will run codex review instead..." >&2
    # Jump directly to Review Phase section below (after the COMPLETE/STOP handling)
else

echo "Running summary review for round $CURRENT_ROUND via codex..." >&2

CODEX_CMD_FILE="$CACHE_DIR/round-${CURRENT_ROUND}-codex-run.cmd"
CODEX_STDOUT_FILE="$CACHE_DIR/round-${CURRENT_ROUND}-codex-run.out"
CODEX_STDERR_FILE="$CACHE_DIR/round-${CURRENT_ROUND}-codex-run.log"

# Save the command for debugging
CODEX_PROMPT_CONTENT=$(cat "$REVIEW_PROMPT_FILE")
{
    echo "# Codex invocation debug info"
    echo "# Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo "# Working directory: $PROJECT_ROOT"
    echo "# Timeout: $CODEX_TIMEOUT seconds"
    echo ""
    echo "codex exec ${CODEX_DISABLE_HOOKS_ARGS[*]} ${CODEX_EXEC_ARGS[*]} \"<prompt>\""
    echo ""
    echo "# Prompt content:"
    echo "$CODEX_PROMPT_CONTENT"
} > "$CODEX_CMD_FILE"

echo "Codex command saved to: $CODEX_CMD_FILE" >&2
echo "Running summary review with timeout ${CODEX_TIMEOUT}s..." >&2

CODEX_EXIT_CODE=0
printf '%s' "$CODEX_PROMPT_CONTENT" | run_with_timeout "$CODEX_TIMEOUT" codex exec "${CODEX_DISABLE_HOOKS_ARGS[@]}" "${CODEX_EXEC_ARGS[@]}" - \
    > "$CODEX_STDOUT_FILE" 2> "$CODEX_STDERR_FILE" || CODEX_EXIT_CODE=$?

echo "Codex exit code: $CODEX_EXIT_CODE" >&2
echo "Codex stdout saved to: $CODEX_STDOUT_FILE" >&2
echo "Codex stderr saved to: $CODEX_STDERR_FILE" >&2

# ========================================
# Check Codex Execution Result
# ========================================

# Helper function to print Codex failure and block exit for retry
# Uses JSON output with exit 0 (per Claude Code hooks spec) instead of exit 2
codex_failure_exit() {
    local error_type="$1"
    local details="$2"

    REASON="# Codex Review Failed

**Error Type:** $error_type

$details

**Debug files:**
- Command: $CODEX_CMD_FILE
- Stdout: $CODEX_STDOUT_FILE
- Stderr: $CODEX_STDERR_FILE

Please retry or use \`/cancel-rlcr-loop\` to end the loop."

    cat <<EOF
{
    "decision": "block",
    "reason": $(echo "$REASON" | jq -Rs .)
}
EOF
    exit 0
}

# Check 1: Codex exit code indicates failure
if [[ "$CODEX_EXIT_CODE" -ne 0 ]]; then
    STDERR_CONTENT=""
    if [[ -f "$CODEX_STDERR_FILE" ]]; then
        STDERR_CONTENT=$(tail -30 "$CODEX_STDERR_FILE" 2>/dev/null || echo "(unable to read stderr)")
    fi

    codex_failure_exit "Non-zero exit code ($CODEX_EXIT_CODE)" \
"Codex exited with code $CODEX_EXIT_CODE.
This may indicate:
  - Invalid arguments or configuration
  - Authentication failure
  - Network issues
  - Prompt format issues (e.g., multiline handling)

Stderr output (last 30 lines):
$STDERR_CONTENT"
fi

# Check if Codex created the review result file (it should write to workspace)
# If not, check if it wrote to stdout
if [[ ! -f "$REVIEW_RESULT_FILE" ]]; then
    # Codex might have written output to stdout instead
    if [[ -s "$CODEX_STDOUT_FILE" ]]; then
        echo "Codex output found in stdout, copying to review result file..." >&2
        if ! cp "$CODEX_STDOUT_FILE" "$REVIEW_RESULT_FILE" 2>/dev/null; then
            codex_failure_exit "Failed to copy stdout to review result file" \
"Codex wrote output to stdout but copying to review file failed.
Source: $CODEX_STDOUT_FILE
Target: $REVIEW_RESULT_FILE

This may indicate permission issues or disk space problems.
Check if the loop directory is writable."
        fi
    fi
fi

# Check 2: Review result file still doesn't exist
if [[ ! -f "$REVIEW_RESULT_FILE" ]]; then
    STDERR_CONTENT=""
    if [[ -f "$CODEX_STDERR_FILE" ]]; then
        STDERR_CONTENT=$(tail -30 "$CODEX_STDERR_FILE" 2>/dev/null || echo "(no stderr output)")
    fi

    STDOUT_CONTENT=""
    if [[ -f "$CODEX_STDOUT_FILE" ]]; then
        STDOUT_CONTENT=$(tail -30 "$CODEX_STDOUT_FILE" 2>/dev/null || echo "(no stdout output)")
    fi

    codex_failure_exit "Review result file not created" \
"Expected file: $REVIEW_RESULT_FILE
Codex completed (exit code 0) but did not create the review result file.

This may indicate:
  - Codex did not understand the prompt
  - Codex wrote to wrong path
  - Workspace/permission issues

Stdout (last 30 lines):
$STDOUT_CONTENT

Stderr (last 30 lines):
$STDERR_CONTENT"
fi

# Check 3: Review result file is empty
if [[ ! -s "$REVIEW_RESULT_FILE" ]]; then
    codex_failure_exit "Review result file is empty" \
"File exists but is empty: $REVIEW_RESULT_FILE
Codex created the file but wrote no content.

This may indicate Codex encountered an internal error."
fi

# Read the review result
REVIEW_CONTENT=$(cat "$REVIEW_RESULT_FILE")

# Check if the last non-empty line is exactly "COMPLETE" or "STOP"
# The word must be on its own line to avoid false positives like "CANNOT COMPLETE"
# Use strict matching: only whitespace before/after the word is allowed
LAST_LINE=$(echo "$REVIEW_CONTENT" | grep -v '^[[:space:]]*$' | tail -1)
LAST_LINE_TRIMMED=$(echo "$LAST_LINE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

NEXT_MAINLINE_STALL_COUNT="$MAINLINE_STALL_COUNT"
NEXT_LAST_MAINLINE_VERDICT="$LAST_MAINLINE_VERDICT"
NEXT_DRIFT_STATUS="$DRIFT_STATUS"
DRIFT_REPLAN_REQUIRED=false
MAINLINE_DRIFT_STOP=false

if [[ "$REVIEW_STARTED" != "true" ]]; then
    EXTRACTED_MAINLINE_VERDICT=$(extract_mainline_progress_verdict "$REVIEW_CONTENT")

    if [[ "$LAST_LINE_TRIMMED" != "$MARKER_STOP" ]] && [[ "$EXTRACTED_MAINLINE_VERDICT" == "$MAINLINE_VERDICT_UNKNOWN" ]]; then
        echo "Implementation review output is missing Mainline Progress Verdict. Blocking exit for safety." >&2
        block_missing_mainline_verdict "$REVIEW_RESULT_FILE" "$REVIEW_PROMPT_FILE"
    fi

    case "$EXTRACTED_MAINLINE_VERDICT" in
        "$MAINLINE_VERDICT_ADVANCED")
            NEXT_MAINLINE_STALL_COUNT=0
            NEXT_LAST_MAINLINE_VERDICT="$MAINLINE_VERDICT_ADVANCED"
            NEXT_DRIFT_STATUS="$DRIFT_STATUS_NORMAL"
            ;;
        "$MAINLINE_VERDICT_STALLED"|"$MAINLINE_VERDICT_REGRESSED")
            NEXT_MAINLINE_STALL_COUNT=$((MAINLINE_STALL_COUNT + 1))
            NEXT_LAST_MAINLINE_VERDICT="$EXTRACTED_MAINLINE_VERDICT"
            if [[ "$NEXT_MAINLINE_STALL_COUNT" -ge 2 ]]; then
                NEXT_DRIFT_STATUS="$DRIFT_STATUS_REPLAN_REQUIRED"
                DRIFT_REPLAN_REQUIRED=true
            else
                NEXT_DRIFT_STATUS="$DRIFT_STATUS_NORMAL"
            fi
            if [[ "$NEXT_MAINLINE_STALL_COUNT" -ge 3 ]]; then
                MAINLINE_DRIFT_STOP=true
            fi
            ;;
        *)
            :
            ;;
    esac

    if [[ "$LAST_LINE_TRIMMED" == "$MARKER_COMPLETE" ]]; then
        NEXT_MAINLINE_STALL_COUNT=0
        NEXT_LAST_MAINLINE_VERDICT="$MAINLINE_VERDICT_ADVANCED"
        NEXT_DRIFT_STATUS="$DRIFT_STATUS_NORMAL"
        DRIFT_REPLAN_REQUIRED=false
        MAINLINE_DRIFT_STOP=false
    fi
fi

# Handle COMPLETE - enter Review Phase or Finalize Phase
if [[ "$LAST_LINE_TRIMMED" == "$MARKER_COMPLETE" ]]; then
    # In review phase, COMPLETE signal is ignored - only absence of [P0-9] triggers finalize
    if [[ "$REVIEW_STARTED" == "true" ]]; then
        echo "COMPLETE signal ignored in review phase. Codex review determines exit." >&2
        # Fall through to continue with codex review logic below
    else
        # Implementation phase complete - transition to review phase
        # Max iterations check
        if [[ $CURRENT_ROUND -ge $MAX_ITERATIONS ]]; then
            echo "Codex review passed but at max iterations ($MAX_ITERATIONS). Terminating as MAXITER." >&2
            if enter_methodology_analysis_phase "maxiter" "Codex confirmed COMPLETE but at max iterations ($MAX_ITERATIONS)"; then
                exit 0
            fi
            end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_MAXITER"
            exit 0
        fi

        # Initialize skip tracking variables before any skip paths
        REVIEW_SKIPPED=""
        REVIEW_SKIP_REASON=""

        # Check if base_branch is available for code review
        if [[ -z "$BASE_BRANCH" ]]; then
            echo "Warning: No base_branch configured, skipping code review phase." >&2
            REVIEW_SKIPPED="true"
            REVIEW_SKIP_REASON="No base_branch configured for code review"
        else
            echo "Implementation complete. Entering Review Phase..." >&2

            # Update state to indicate review phase has started and clear drift counters.
            upsert_state_fields "$STATE_FILE" \
                "${FIELD_REVIEW_STARTED}=true" \
                "${FIELD_MAINLINE_STALL_COUNT}=0" \
                "${FIELD_LAST_MAINLINE_VERDICT}=${MAINLINE_VERDICT_ADVANCED}" \
                "${FIELD_DRIFT_STATUS}=${DRIFT_STATUS_NORMAL}"
            REVIEW_STARTED="true"

            # Create marker file to validate review phase was properly entered
            # Also record which round build finished for monitor display
            echo "build_finish_round=$CURRENT_ROUND" > "$LOOP_DIR/.review-phase-started"

            # Run code review and handle results (may exit on issues/failure/success)
            # Pass CURRENT_ROUND + 1 so all review phase files use the next round number
            echo "Implementation complete. Running initial code review..." >&2
            run_and_handle_code_review "$((CURRENT_ROUND + 1))" "Loop: Finalize Phase - Simplify and refactor code before completion"
        fi
    fi
fi

fi  # End of implementation phase codex exec block (skipped when review_started is true)

# ========================================
# Review Phase: Run Code Review (when review_started is true)
# ========================================
# When in review phase, we need to run codex review on every exit attempt
# The loop continues until no [P0-9] patterns are found in the review output

if [[ "$REVIEW_STARTED" == "true" && -n "$BASE_BRANCH" ]]; then
    # Validate that review phase was properly entered (marker file must exist)
    # This prevents manual toggle attacks where someone edits state.md directly
    if [[ ! -f "$LOOP_DIR/.review-phase-started" ]]; then
        REASON="Review phase state inconsistency detected.

The state file indicates review_started=true, but no review phase marker exists.
This can happen if the state file was manually edited.

**To fix:**
Reset the state by canceling and restarting the loop.

Use \`/humanize:cancel-rlcr-loop\` to end this loop."
        jq -n --arg reason "$REASON" --arg msg "Loop: Blocked - invalid review phase state" \
            '{"decision": "block", "reason": $reason, "systemMessage": $msg}'
        exit 0
    fi

    echo "Review Phase: Running code review..." >&2

    # Run code review and handle results (may exit on issues/failure/success)
    # Pass CURRENT_ROUND + 1 so all review phase files use the next round number
    run_and_handle_code_review "$((CURRENT_ROUND + 1))" "Loop: Finalize Phase - Code review passed"
fi

if [[ "$MAINLINE_DRIFT_STOP" == "true" ]] && [[ "$LAST_LINE_TRIMMED" != "$MARKER_STOP" ]] && [[ "$LAST_LINE_TRIMMED" != "$MARKER_COMPLETE" ]]; then
    echo "Mainline progress stalled for $NEXT_MAINLINE_STALL_COUNT consecutive rounds. Triggering drift circuit breaker." >&2
    stop_for_mainline_drift "$NEXT_MAINLINE_STALL_COUNT" "$NEXT_LAST_MAINLINE_VERDICT"
fi

# Handle STOP - circuit breaker triggered
if [[ "$LAST_LINE_TRIMMED" == "$MARKER_STOP" ]]; then
    echo "" >&2
    echo "========================================" >&2
    if [[ "$FULL_ALIGNMENT_CHECK" == "true" ]]; then
        echo "CIRCUIT BREAKER TRIGGERED" >&2
        echo "========================================" >&2
        echo "Codex detected development stagnation during Full Alignment Check (Round $CURRENT_ROUND)." >&2
        echo "The loop has been stopped to prevent further unproductive iterations." >&2
        echo "" >&2
        echo "Review the historical round files in .humanize/rlcr/$(basename "$LOOP_DIR")/ to understand what went wrong." >&2
        echo "Consider:" >&2
        echo "  - Revisiting the original plan for clarity" >&2
        echo "  - Breaking down the task into smaller pieces" >&2
        echo "  - Manually addressing the blocking issues" >&2
    else
        echo "UNEXPECTED CIRCUIT BREAKER" >&2
        echo "========================================" >&2
        echo "Codex output STOP during a non-alignment round (Round $CURRENT_ROUND)." >&2
        echo "This is unusual - STOP is normally only expected during Full Alignment Checks (every $FULL_REVIEW_ROUND rounds)." >&2
        echo "Honoring the STOP request and terminating the loop." >&2
        echo "" >&2
        echo "Review the review result to understand why Codex requested an early stop:" >&2
        echo "  $REVIEW_RESULT_FILE" >&2
    fi
    echo "========================================" >&2
    # Try to enter methodology analysis phase before final exit
    if enter_methodology_analysis_phase "stop" "Circuit breaker triggered - stagnation detected at round $CURRENT_ROUND"; then
        exit 0
    fi
    end_loop "$LOOP_DIR" "$STATE_FILE" "$EXIT_STOP"
    exit 0
fi

# ========================================
# Review Found Issues - Continue Loop
# ========================================

# Update state file for next round
upsert_state_fields "$STATE_FILE" \
    "${FIELD_CURRENT_ROUND}=${NEXT_ROUND}" \
    "${FIELD_MAINLINE_STALL_COUNT}=${NEXT_MAINLINE_STALL_COUNT}" \
    "${FIELD_LAST_MAINLINE_VERDICT}=${NEXT_LAST_MAINLINE_VERDICT}" \
    "${FIELD_DRIFT_STATUS}=${NEXT_DRIFT_STATUS}"

# Create next round prompt
NEXT_PROMPT_FILE="$LOOP_DIR/round-${NEXT_ROUND}-prompt.md"
NEXT_SUMMARY_FILE="$LOOP_DIR/round-${NEXT_ROUND}-summary.md"
if [[ ! -f "$NEXT_SUMMARY_FILE" ]]; then
    cat > "$NEXT_SUMMARY_FILE" << EOF
# Round $NEXT_ROUND Summary

## Work Completed
- [Describe what was implemented in this phase]

## Files Changed
- [List created/modified files]

## Validation
- [List tests/commands run and outcomes]

## Remaining Items
- [List unresolved items, if any]

## BitLesson Delta
- Action: none|add|update
- Lesson ID(s): NONE
- Notes: [what changed and why]
EOF
fi
NEXT_CONTRACT_FILE="$LOOP_DIR/round-${NEXT_ROUND}-contract.md"

# Build the next round prompt from templates
NEXT_ROUND_FALLBACK="# Next Round Instructions

Review the feedback below and address all issues.

Before executing tasks in this round:
1. Read @{{BITLESSON_FILE}}
2. Run \`bitlesson-selector\` for each task/sub-task
3. Follow selected lesson IDs (or \`NONE\`)

## Codex Review
{{REVIEW_CONTENT}}

Reference: {{PLAN_FILE}}, {{GOAL_TRACKER_FILE}}, {{ROUND_CONTRACT_FILE}}, {{BITLESSON_FILE}}"
DRIFT_REPLAN_FALLBACK="# Drift Recovery Required

The mainline has not advanced for {{STALL_COUNT}} consecutive implementation rounds.

Last mainline verdict: {{LAST_MAINLINE_VERDICT}}

Before writing code:
- Re-read @{{PLAN_FILE}}
- Re-read @{{GOAL_TRACKER_FILE}}
- Re-read the recent round summaries and review results
- Rewrite @{{ROUND_CONTRACT_FILE}} with a recovery-focused mainline objective

Do not spend this round clearing queued work. Recover mainline progress first.

## Codex Review
{{REVIEW_CONTENT}}"

if [[ "$DRIFT_REPLAN_REQUIRED" == "true" ]]; then
    load_and_render_safe "$TEMPLATE_DIR" "claude/drift-replan-prompt.md" "$DRIFT_REPLAN_FALLBACK" \
        "PLAN_FILE=$PLAN_FILE" \
        "REVIEW_CONTENT=$REVIEW_CONTENT" \
        "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
        "BITLESSON_FILE=$BITLESSON_FILE" \
        "ROUND_CONTRACT_FILE=$NEXT_CONTRACT_FILE" \
        "CURRENT_ROUND=$NEXT_ROUND" \
        "STALL_COUNT=$NEXT_MAINLINE_STALL_COUNT" \
        "LAST_MAINLINE_VERDICT=$NEXT_LAST_MAINLINE_VERDICT" > "$NEXT_PROMPT_FILE"
else
    load_and_render_safe "$TEMPLATE_DIR" "claude/next-round-prompt.md" "$NEXT_ROUND_FALLBACK" \
        "PLAN_FILE=$PLAN_FILE" \
        "REVIEW_CONTENT=$REVIEW_CONTENT" \
        "GOAL_TRACKER_FILE=$GOAL_TRACKER_FILE" \
        "BITLESSON_FILE=$BITLESSON_FILE" \
        "ROUND_CONTRACT_FILE=$NEXT_CONTRACT_FILE" \
        "CURRENT_ROUND=$NEXT_ROUND" \
        "STALL_COUNT=$NEXT_MAINLINE_STALL_COUNT" \
        "LAST_MAINLINE_VERDICT=$NEXT_LAST_MAINLINE_VERDICT" > "$NEXT_PROMPT_FILE"
fi

if [[ "$DRIFT_REPLAN_REQUIRED" == "true" ]] && [[ "$BITLESSON_REQUIRED" == "true" ]] && ! grep -q 'bitlesson-selector' "$NEXT_PROMPT_FILE"; then
    cat >> "$NEXT_PROMPT_FILE" << EOF

## BitLesson Selection (REQUIRED FOR EACH TASK)

Before executing each task or sub-task, you MUST:

1. Read @$BITLESSON_FILE
2. Run \`bitlesson-selector\` for each task/sub-task to select relevant lesson IDs
3. Follow the selected lesson IDs (or \`NONE\`) during implementation

Reference: @$BITLESSON_FILE
EOF
fi

if [[ "$AGENT_TEAMS" == "true" ]]; then
    ENFORCEMENT_BLOCK="**Delegation Warning**: Do NOT implement code yourself in Agent Teams mode; delegate all coding tasks to team members."

    TEMP_PROMPT_FILE="${NEXT_PROMPT_FILE}.tmp.$$"
    awk -v enforcement="$ENFORCEMENT_BLOCK" '
        BEGIN { injected = 0 }
        !injected && /^## Original Implementation Plan/ {
            print ""
            print enforcement
            print ""
            injected = 1
        }
        { print }
        END {
            if (!injected) {
                print ""
                print enforcement
                print ""
            }
        }
    ' "$NEXT_PROMPT_FILE" > "$TEMP_PROMPT_FILE"
    mv "$TEMP_PROMPT_FILE" "$NEXT_PROMPT_FILE"
fi

# Check for Open Questions in review content and inject notice if enabled
# Detection: line containing "Open Question" substring with total length < 40 chars
if [[ "$ASK_CODEX_QUESTION" == "true" ]]; then
    HAS_OPEN_QUESTION=false
    while IFS= read -r line; do
        if [[ ${#line} -lt 40 ]] && echo "$line" | grep -q "Open Question"; then
            HAS_OPEN_QUESTION=true
            break
        fi
    done < "$REVIEW_RESULT_FILE"

    if [[ "$HAS_OPEN_QUESTION" == "true" ]]; then
        echo "Detected Open Question(s) in Codex review - injecting AskUserQuestion notice" >&2
        OPEN_QUESTION_NOTICE=$(load_template "$TEMPLATE_DIR" "claude/open-question-notice.md" 2>/dev/null)
        if [[ -z "$OPEN_QUESTION_NOTICE" ]]; then
            OPEN_QUESTION_NOTICE="**IMPORTANT**: Codex has found Open Question(s). You must use \`AskUserQuestion\` to clarify those questions with user first, before proceeding to resolve any other Codex's findings."
        fi
        # Insert notice between "<!-- CODEX's REVIEW RESULT  END  -->" line + "---" line and "## Goal Tracker Reference"
        TEMP_PROMPT_FILE="${NEXT_PROMPT_FILE}.tmp.$$"
        awk -v notice="$OPEN_QUESTION_NOTICE" '
            /<!-- CODEX.*REVIEW RESULT.*END.*-->/ {
                print
                getline
                if (/^---/) {
                    print
                    print ""
                    print notice
                    next
                }
            }
            { print }
        ' "$NEXT_PROMPT_FILE" > "$TEMP_PROMPT_FILE"
        mv "$TEMP_PROMPT_FILE" "$NEXT_PROMPT_FILE"
    fi
fi

# Add special instructions for post-Full Alignment Check rounds
if [[ "$FULL_ALIGNMENT_CHECK" == "true" ]]; then
    POST_ALIGNMENT=$(load_template "$TEMPLATE_DIR" "claude/post-alignment-action-items.md" 2>/dev/null)
    if [[ -n "$POST_ALIGNMENT" ]]; then
        echo "$POST_ALIGNMENT" >> "$NEXT_PROMPT_FILE"
    fi
fi

# Add footer with commit/summary instructions
FOOTER_FALLBACK="## Before Exiting
Commit your changes and write summary to {{NEXT_SUMMARY_FILE}}"
load_and_render_safe "$TEMPLATE_DIR" "claude/next-round-footer.md" "$FOOTER_FALLBACK" \
    "NEXT_SUMMARY_FILE=$NEXT_SUMMARY_FILE" >> "$NEXT_PROMPT_FILE"
append_task_tag_routing_note "$NEXT_PROMPT_FILE"

# Add push instruction only if push_every_round is true
if [[ "$PUSH_EVERY_ROUND" == "true" ]]; then
    PUSH_NOTE=$(load_template "$TEMPLATE_DIR" "claude/push-every-round-note.md" 2>/dev/null)
    if [[ -z "$PUSH_NOTE" ]]; then
        PUSH_NOTE="Also push your changes after committing."
    fi
    echo "$PUSH_NOTE" >> "$NEXT_PROMPT_FILE"
fi

# Add goal tracker update request template
GOAL_UPDATE_REQUEST=$(load_template "$TEMPLATE_DIR" "claude/goal-tracker-update-request.md" 2>/dev/null)
if [[ -z "$GOAL_UPDATE_REQUEST" ]]; then
    GOAL_UPDATE_REQUEST="Include a Goal Tracker Update Request section in your summary if needed."
fi
echo "$GOAL_UPDATE_REQUEST" >> "$NEXT_PROMPT_FILE"

# Add agent-teams continuation instructions (only during implementation phase, not review phase)
# Loads both continuation header and shared core template for full team leader guidance
if [[ "$AGENT_TEAMS" == "true" ]] && [[ "$REVIEW_STARTED" != "true" ]]; then
    AGENT_TEAMS_CONTINUE=$(load_template "$TEMPLATE_DIR" "claude/agent-teams-continue.md" 2>/dev/null)
    AGENT_TEAMS_CORE=$(load_template "$TEMPLATE_DIR" "claude/agent-teams-core.md" 2>/dev/null)
    if [[ -n "$AGENT_TEAMS_CONTINUE" ]] && [[ -n "$AGENT_TEAMS_CORE" ]]; then
        echo "" >> "$NEXT_PROMPT_FILE"
        echo "$AGENT_TEAMS_CONTINUE" >> "$NEXT_PROMPT_FILE"
        echo "" >> "$NEXT_PROMPT_FILE"
        echo "$AGENT_TEAMS_CORE" >> "$NEXT_PROMPT_FILE"
    else
        cat >> "$NEXT_PROMPT_FILE" << 'AGENT_TEAMS_FALLBACK_EOF'

## Agent Teams Continuation

Continue using **Agent Teams mode** as the **Team Leader**.
Split remaining work among team members and coordinate their efforts.
Do NOT do implementation work yourself - delegate all coding to team members.
AGENT_TEAMS_FALLBACK_EOF
    fi
fi

# Build system message
SYSTEM_MSG="Loop: Round $NEXT_ROUND/$MAX_ITERATIONS - Codex found issues to address"
if [[ "$DRIFT_REPLAN_REQUIRED" == "true" ]]; then
    SYSTEM_MSG="Loop: Round $NEXT_ROUND/$MAX_ITERATIONS - Mainline drift detected, replan required"
fi

# Block exit and send review feedback
jq -n \
    --arg reason "$(cat "$NEXT_PROMPT_FILE")" \
    --arg msg "$SYSTEM_MSG" \
    '{
        "decision": "block",
        "reason": $reason,
        "systemMessage": $msg
    }'

exit 0