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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions hooks/loop-codex-stop-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,25 @@ COMMIT_HISTORY_SECTION=$(load_and_render_safe "$TEMPLATE_DIR" "codex/commit-hist
"COMMIT_HISTORY=$COMMIT_HISTORY" \
"RECENT_ROUND_FILES=$RECENT_ROUND_FILES")

# ========================================
# Build the Session Invariants block
# ========================================
# Enumerates the loop's session-wide byte-locks and immutables so the
# reviewer prompt can route findings whose only fix mutates a locked
# artifact through the canonical cancel/amend/restart path rather than
# re-issuing the same critique each round. This addresses the
# stagnation-class methodology gap where a reviewer kept demanding
# edits to a plan file the loop forbids in-place edits to.
SESSION_INVARIANTS=""
if [[ "$PLAN_TRACKED" == "true" ]]; then
SESSION_INVARIANTS+="- **Plan file byte-lock**: \`${PLAN_FILE}\` is byte-locked for the entire session via \`--track-plan-file\`. The stop hook enforces byte-identity against the snapshot taken at session open. Any finding whose only fix is to edit this file's prose CANNOT be addressed in-loop and MUST be tagged \`out-of-loop\` rather than \`must-fix\`.
"
fi
SESSION_INVARIANTS+="- **Working branch fixed**: the working branch \`${START_BRANCH}\` is constant across all rounds. Findings whose only fix requires a branch switch CANNOT be addressed in-loop.
- **Witness lattice / approved-path discipline**: methodology-level constants and seals declared in the project's CLAUDE.md / AGENTS.md (e.g., sealed traits, approved-path factories, schema freezes) are session invariants. Findings whose only fix mutates one of these constants CANNOT be addressed in-loop without an explicit successor ADR or plan amendment.

**Canonical resolution for invariant-locked findings**: \`/humanize:cancel-rlcr-loop\`, then amend the locked artifact in a fresh commit, then \`/humanize:start-rlcr-loop --track-plan-file <plan>\` to restart. The implementer cannot do this from inside the loop."

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

Expand Down Expand Up @@ -1100,6 +1119,7 @@ if [[ "$FULL_ALIGNMENT_CHECK" == "true" ]]; then
"DOCS_PATH=$DOCS_PATH" \
"GOAL_TRACKER_UPDATE_SECTION=$GOAL_TRACKER_UPDATE_SECTION" \
"COMMIT_HISTORY_SECTION=$COMMIT_HISTORY_SECTION" \
"SESSION_INVARIANTS=$SESSION_INVARIANTS" \
"COMPLETED_ITERATIONS=$COMPLETED_ITERATIONS" \
"LOOP_TIMESTAMP=$LOOP_TIMESTAMP" \
"PREV_ROUND=$PREV_ROUND" \
Expand All @@ -1117,6 +1137,7 @@ else
"DOCS_PATH=$DOCS_PATH" \
"GOAL_TRACKER_UPDATE_SECTION=$GOAL_TRACKER_UPDATE_SECTION" \
"COMMIT_HISTORY_SECTION=$COMMIT_HISTORY_SECTION" \
"SESSION_INVARIANTS=$SESSION_INVARIANTS" \
"COMPLETED_ITERATIONS=$COMPLETED_ITERATIONS" \
"LOOP_TIMESTAMP=$LOOP_TIMESTAMP" \
"PREV_ROUND=$PREV_ROUND" \
Expand Down
22 changes: 22 additions & 0 deletions prompt-template/claude/finalize-phase-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,25 @@ Your summary should include:
- Files modified during the Finalize Phase
- Confirmation that tests still pass
- Any notes about the refactoring decisions

## Required: Outcome Classification

The **first content line** of your finalize summary MUST be one of these three classifications, formatted exactly as shown:

```
Outcome: no-op (already-minimal)
```
```
Outcome: cosmetic (formatting only)
```
```
Outcome: substantive (logic edits)
```

Pick the one that matches what actually happened:

- **`no-op (already-minimal)`** — the code was already at minimal complexity for its constraints; refactor agent (or you) made no edits. This is a positive signal — it means the prior rounds did not ship over-complex artifacts. Common in re-acceptance sessions where the substantive work landed in a prior session.
- **`cosmetic (formatting only)`** — only formatting / whitespace / comment-only changes. No logic touched.
- **`substantive (logic edits)`** — actual logic changes were made (extracted helpers, consolidated branches, removed dead code, etc.). For sessions whose Codex review approved COMPLETE before Finalize, a `substantive` outcome warrants a one-sentence justification of why the prior rounds shipped non-minimal artifacts.

Why this classification matters: it lets future audits and methodology analyses tell at a glance whether the Finalize Phase added real value, was a no-op (the expected outcome in well-shaped rounds), or surfaced complexity the implementation rounds left behind. A `no-op` outcome is **not failure** — it is positive evidence that the prior rounds' exit point was already at local minimum complexity.
24 changes: 24 additions & 0 deletions prompt-template/claude/next-round-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,27 @@ If you cannot safely reconcile the tracker yourself, include an optional "Goal T
- blocking side issues
- queued side issues
- Only mainline gaps and blocking side issues should drive the next code changes

## Optional: Blocked By Methodology Invariant Block

If a Codex finding's only fix would mutate a session-byte-locked artifact (e.g., a plan file under `--track-plan-file`, a sealed witness lattice, a frozen wire-protocol), you cannot address it from inside the loop. Re-running the round shape over and over will not unstick it; the methodology will eventually trigger the stagnation circuit breaker.

When you recognise this class of impasse, include the following block in your round summary so the reviewer treats it as a high-confidence signal rather than re-issuing the same critique:

```markdown
## Blocked By Methodology Invariant

- Invariant: <invariant-name> (e.g., "plan-file-byte-lock", "witness-lattice-seven-impl-seal", "bus-v1-byte-freeze")
- Findings blocked:
- <one-line description of finding 1>
- <one-line description of finding 2>
- Canonical resolution: <e.g., "cancel/amend/restart with the locked artifact amended off-loop">
- Why I cannot act in-loop: <one-sentence explanation citing the relevant byte-lock or seal>
```

When this block is present, the reviewer is asked to:
1. Acknowledge the block instead of re-issuing the listed findings as `must-fix`.
2. Tag the listed findings as `out-of-loop` rather than `must-fix`.
3. Recommend the canonical resolution path.

Use this block conservatively. It is the implementer's escape hatch when the methodology's invariants prevent in-round action — it is NOT a way to defer ordinary follow-up work. Each finding in the block must be one that cannot be addressed without amending a byte-locked artifact.
12 changes: 12 additions & 0 deletions prompt-template/codex/full-alignment-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ Queued Side Issues: N

The `Mainline Progress Verdict` line is mandatory. If you omit it, the Humanize stop hook will block the round and require the review to be rerun.

### Out-of-Loop Findings (Loop-Aware Classification)

If a finding's only fix would mutate a session-byte-locked artifact (see Session Invariants below), tag it `out-of-loop` rather than `must-fix` and recommend the canonical resolution path (cancel/amend/restart) instead of asking the implementer to address it in the next round. Re-issuing the same critique each round will not unstick the loop and will trigger the stagnation circuit breaker after a few iterations.

### Session Invariants

The implementer is operating under the following session-wide invariants:

{{SESSION_INVARIANTS}}

If your top-priority finding falls into the `out-of-loop` class, say so explicitly in your review and stop demanding the implementer act on it in-loop. The next state-changing action must come from outside the current loop session.

## Part 3: Implementation Review

- Conduct a deep critical review of the implementation
Expand Down
29 changes: 29 additions & 0 deletions prompt-template/codex/regular-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,35 @@ You MUST classify your findings into these lanes:
- **Mainline Gaps**: plan-derived work or AC progress that is missing, incomplete, or regressing
- **Blocking Side Issues**: bugs or implementation issues that block the current mainline objective from succeeding safely
- **Queued Side Issues**: valid non-blocking follow-up issues that should be documented but must NOT take over the next round
- **Out-of-Loop Findings**: findings whose only fix would mutate a session-byte-locked artifact (see Session Invariants below). Tag these `out-of-loop` rather than `must-fix` and recommend the canonical resolution path (cancel/amend/restart) instead of asking the implementer to address them in the next round.

### Session Invariants (Loop-Aware)

The implementer is operating under the following session-wide invariants. **Findings whose only fix would mutate a byte-locked artifact MUST be tagged `out-of-loop` rather than `must-fix`** — re-issuing the same critique each round will not unstick the loop and will trigger the stagnation circuit breaker after a few iterations. Route invariant-locked findings to the canonical resolution path instead of demanding in-round fixes.

{{SESSION_INVARIANTS}}

If your top-priority finding falls into the `out-of-loop` class, say so explicitly in your review and stop demanding the implementer act on it in-loop. The next state-changing action must come from outside the current loop session.

### Implementer's `## Blocked By Methodology Invariant` Block

The implementer's summary may contain an optional `## Blocked By Methodology Invariant` block listing findings the implementer believes cannot be addressed in-loop because of a session invariant. The block has this shape:

```markdown
## Blocked By Methodology Invariant

- Invariant: <invariant-name>
- Findings blocked: <list>
- Canonical resolution: <usually cancel/amend/restart>
- Why I cannot act in-loop: <one-sentence explanation>
```

When this block is present:
1. **Verify the implementer's claim**: confirm the listed findings are genuinely byte-locked behind the named invariant. If the implementer is using the block to defer ordinary follow-up work that they could in fact address in-loop, push back on that — only true invariant-locked findings belong here.
2. **For verified-blocked findings**: tag them `out-of-loop` in your review rather than `must-fix`, acknowledge the implementer's correct identification of the impasse class, and recommend the canonical resolution. Do NOT re-issue them as round-N+1 mainline gaps.
3. **For findings the implementer wrongly classified as blocked**: leave them in the appropriate `must-fix` lane and explain why they are in fact addressable in-loop.

This signal exists to break the stagnation pattern where the reviewer keeps demanding edits the implementer is structurally forbidden from making. When the implementer correctly identifies that pattern via this block, your job is to validate it and route to the canonical resolution, not to litigate it for another round.

Also include a one-line verdict:
```
Expand Down
103 changes: 102 additions & 1 deletion scripts/setup-rlcr-loop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -814,11 +814,55 @@ if [[ -z "$BASE_COMMIT" ]]; then
fi
echo "Base commit SHA captured: $BASE_COMMIT" >&2

# ========================================
# Detect inherited-delta session
# ========================================
# A new session is "inherited-delta" when the most recent prior session in
# .humanize/rlcr/ was working from a different base_commit than the one
# captured above — i.e., commits landed between the prior session's start
# and this session's start (whether by the prior session's own work, by
# off-loop amendments, or both). Re-acceptance sessions on prior work that
# hit the stagnation circuit breaker are the canonical example.
#
# Persisting this signal at session open lets the round-0 contract +
# the reviewer prompt route the session correctly without relying on
# the implementer's narration.
LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"

INHERITED_DELTA="false"
PRIOR_TIMESTAMP=""
PRIOR_BASE_COMMIT=""
PRIOR_EXIT_STATE="unknown"

if [[ -d "$LOOP_BASE_DIR" ]]; then
# Get the most recent prior session dir (any name; sorted lexicographically
# because session timestamps are zero-padded and sortable as strings).
PRIOR_SESSION_DIR=$(find "$LOOP_BASE_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | tail -1 || true)
if [[ -n "$PRIOR_SESSION_DIR" ]] && [[ -d "$PRIOR_SESSION_DIR" ]]; then
PRIOR_TIMESTAMP=$(basename "$PRIOR_SESSION_DIR")
# Read base_commit from whichever state file exists (state, finalize, complete, methodology).
for state_candidate in state.md finalize-state.md complete-state.md methodology-analysis-state.md; do
prior_state="$PRIOR_SESSION_DIR/$state_candidate"
if [[ -f "$prior_state" ]]; then
PRIOR_BASE_COMMIT=$(awk -F': *' '/^base_commit:/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$prior_state" | tr -d '"' | tr -d "'")
PRIOR_EXIT_STATE="${state_candidate%.md}"
break
fi
done

if [[ -n "$PRIOR_BASE_COMMIT" ]] && [[ "$PRIOR_BASE_COMMIT" != "$BASE_COMMIT" ]]; then
INHERITED_DELTA="true"
Comment on lines +853 to +854
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect inherited delta from prior session HEAD

Set INHERITED_DELTA using more than PRIOR_BASE_COMMIT != BASE_COMMIT: both values are base-branch SHAs captured at session start, so this misses the common case where new commits were added on the working branch between sessions while the base branch did not move. In that scenario the comparison stays equal, inherited_delta is incorrectly left false, and session-lineage.md is never generated even though the new session is actually re-accepting inherited work.

Useful? React with 👍 / 👎.

echo "Inherited-delta session detected: prior session $PRIOR_TIMESTAMP started at $PRIOR_BASE_COMMIT, this session starts at $BASE_COMMIT" >&2
fi
fi
fi

# ========================================
# Setup State Directory
# ========================================

LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"
# LOOP_BASE_DIR was already defined above for the inherited-delta detection;
# reusing here without redefinition.

# Create timestamp for this loop session
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
Expand Down Expand Up @@ -902,10 +946,67 @@ bitlesson_allow_empty_none: $BITLESSON_ALLOW_EMPTY_NONE
mainline_stall_count: 0
last_mainline_verdict: unknown
drift_status: normal
inherited_delta: $INHERITED_DELTA
started_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
---
EOF

# Generate session-lineage.md when this session opens with inherited delta
# from a prior session. The file gives future audits + the round-0 contract
# a machine-readable record of where the substantive work came from, instead
# of relying on the implementer's narrative across artifacts.
if [[ "$INHERITED_DELTA" == "true" ]]; then
INHERITED_COMMIT_LOG=""
if commit_range_log=$(run_with_timeout "$GIT_TIMEOUT" git -C "$PROJECT_ROOT" log --oneline "${PRIOR_BASE_COMMIT}..${BASE_COMMIT}" 2>/dev/null); then
INHERITED_COMMIT_LOG="$commit_range_log"
fi
if [[ -z "$INHERITED_COMMIT_LOG" ]]; then
INHERITED_COMMIT_LOG="(no commit subjects available — base commits are not in a single ancestry chain on this branch, or git log timed out)"
fi

cat > "$LOOP_DIR/session-lineage.md" << LINEAGE_EOF
# Session Lineage — inherited-delta session

This session opened with substantive work already at HEAD, inherited from a prior RLCR session
plus any off-loop commits that landed between sessions. The methodology classifies this as an
\`inherited-delta\` session: the round-0 contract should declare which prior-session changes are
being re-presented and which off-loop edits occurred, rather than re-running an
implementation-grade round shape over a thin re-acceptance task.

## Prior session

- Timestamp: \`$PRIOR_TIMESTAMP\`
- Base commit at prior start: \`$PRIOR_BASE_COMMIT\`
- Exit state file: \`$PRIOR_EXIT_STATE.md\` (whichever state file was found in the prior session directory)

## This session

- Base commit at start: \`$BASE_COMMIT\`
- Started at: $(date -u +%Y-%m-%dT%H:%M:%SZ)

## Inherited commits

Commit range \`${PRIOR_BASE_COMMIT}..${BASE_COMMIT}\` (between prior session base and this session base):

\`\`\`
$INHERITED_COMMIT_LOG
\`\`\`

## Why a new session is needed (please fill in)

[Stub — the implementer should record why this is a new session rather than a continuation of
the prior session. Common reasons:
- Prior session hit the stagnation circuit breaker (cancel/amend/restart canonical resolution).
- Off-loop plan amendment (commit between sessions edited a byte-locked artifact).
- New acceptance criteria; the prior session closed cleanly but new scope landed.
- Prior session ended via max-iteration cap.

Replace this block with one paragraph describing the actual reason. Keep it brief — the audit
trail benefits from concise prose.]
LINEAGE_EOF
echo "session-lineage.md generated for inherited-delta session" >&2
fi

# Create signal file for PostToolUse hook to record session_id
# The hook will read the session_id from its JSON input and patch state.md
# Format: line 1 = state file path, line 2 = command marker for verification
Expand Down
80 changes: 80 additions & 0 deletions tests/test-blocked-by-invariant-block.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
#
# Test that the implementer prompt documents the optional
# Blocked By Methodology Invariant block, and the reviewer prompt
# documents how to recognise + route it.
#
# Positive Test Cases:
# - T-POS-1: next-round prompt documents the optional block + format
# - T-POS-2: next-round prompt lists the four required block fields
# - T-POS-3: next-round prompt warns against using block for ordinary follow-up
# - T-POS-4: regular-review prompt instructs reviewer to recognise the block
# - T-POS-5: regular-review prompt instructs reviewer to verify-then-route
# - T-POS-6: regular-review prompt instructs reviewer to push back on misuse
#

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
TESTS_PASSED=0
TESTS_FAILED=0

pass() { echo -e "${GREEN}PASS${NC}: $1"; TESTS_PASSED=$((TESTS_PASSED + 1)); }
fail() { echo -e "${RED}FAIL${NC}: $1"; echo " $2"; TESTS_FAILED=$((TESTS_FAILED + 1)); }

NEXT_ROUND="$PROJECT_ROOT/prompt-template/claude/next-round-prompt.md"
REGULAR="$PROJECT_ROOT/prompt-template/codex/regular-review.md"

# T-POS-1: next-round prompt documents the optional block
if grep -qF "## Blocked By Methodology Invariant" "$NEXT_ROUND"; then
pass "T-POS-1: next-round prompt names the optional block"
else
fail "T-POS-1: next-round prompt missing block name" "expected literal '## Blocked By Methodology Invariant' heading"
fi

# T-POS-2: four required block fields
for field in "Invariant:" "Findings blocked:" "Canonical resolution:" "Why I cannot act in-loop:"; do
if grep -qF "$field" "$NEXT_ROUND"; then
pass "T-POS-2: block field documented: $field"
else
fail "T-POS-2: block field missing: $field" "expected literal '$field' in template"
fi
done

# T-POS-3: misuse warning
if grep -qiE "use this block conservatively|NOT a way to defer|conservatively" "$NEXT_ROUND"; then
pass "T-POS-3: next-round warns against block misuse"
else
fail "T-POS-3: misuse warning missing" "expected language warning the implementer not to abuse the block"
fi

# T-POS-4: reviewer recognises the block
if grep -qF "## Blocked By Methodology Invariant" "$REGULAR"; then
pass "T-POS-4: regular-review references the block"
else
fail "T-POS-4: regular-review missing block reference" "expected '## Blocked By Methodology Invariant' in template"
fi

# T-POS-5: reviewer verify-then-route language
if grep -qiE "verify the implementer.s claim|confirm the listed findings" "$REGULAR"; then
pass "T-POS-5: reviewer instructed to verify-then-route"
else
fail "T-POS-5: verify-then-route guidance missing" "expected verification step in reviewer prompt"
fi

# T-POS-6: push back on misuse
if grep -qiE "push back|wrongly classified|leave them in" "$REGULAR"; then
pass "T-POS-6: reviewer instructed to push back on misuse"
else
fail "T-POS-6: push-back guidance missing" "expected explicit push-back language for false-blocked findings"
fi

echo ""
echo "Total: $TESTS_PASSED passed, $TESTS_FAILED failed"
[[ "$TESTS_FAILED" -eq 0 ]] || exit 1
exit 0
Loading