feat: progress-aware commit-recovery cap (no longer block on 2nd attempt)

The existing commit-recovery path blocked stories on the 2nd consecutive
exit-without-commit. For long sweep refactors (e.g. story 997, the typed
retries payload migration), claude-code's session-length boundary
naturally terminates the coder mid-sweep before it can commit — even
though substantial file-edit progress is being made each session. The
old cap-of-1 misclassified normal mid-flight progress as 'agent declined
to commit'.

New behaviour:
- Each commit-recovery respawn captures a worktree-diff byte-length
  fingerprint (git diff master | wc -c).
- If the fingerprint differs from the previous attempt the agent made
  file-edit progress, the no-progress counter resets to 1.
- If the fingerprint is byte-identical (no new edits between exits),
  increment the no-progress counter.
- Block only when the counter reaches NO_PROGRESS_CAP (3) — i.e. three
  consecutive respawns where the agent did literally nothing.

Adds ContentKey::CommitRecoveryDiffFingerprint to store the prior
fingerprint. Updates the existing block-test to reflect the new cap
semantics; existing 'first respawn issued' test continues to pass.

All 2935 tests pass.
This commit is contained in:
Timmy
2026-05-14 11:24:02 +01:00
parent 5e5c5a0e08
commit bab337b289
3 changed files with 100 additions and 32 deletions
+12 -1
View File
@@ -24,8 +24,16 @@ pub enum ContentKey<'a> {
MergeMasterSpawnCount(&'a str),
/// Evidence that `run_tests` passed during an agent session.
RunTestsOk(&'a str),
/// Flag indicating a commit-recovery respawn is in progress.
/// Flag indicating a commit-recovery respawn is in progress. Stored as
/// a decimal string counting consecutive respawns that made NO file-edit
/// progress (worktree diff byte-identical to the previous attempt). Reset
/// to "1" whenever a respawn produces a different diff fingerprint.
CommitRecoveryPending(&'a str),
/// Worktree diff byte-length captured at the last commit-recovery respawn
/// trigger. Used to detect whether the agent made any file-edit progress
/// between consecutive session-boundary-clean exits. Same byte length on
/// two consecutive attempts → no progress → increment CommitRecoveryPending.
CommitRecoveryDiffFingerprint(&'a str),
/// Flag indicating a merge gate fixup coder session is in progress.
///
/// Set when the merge gate fails with a self-evident-fix class of failure
@@ -57,6 +65,9 @@ impl<'a> ContentKey<'a> {
ContentKey::MergeMasterSpawnCount(id) => format!("{id}:mergemaster_spawn_count"),
ContentKey::RunTestsOk(id) => format!("{id}:run_tests_ok"),
ContentKey::CommitRecoveryPending(id) => format!("{id}:commit_recovery_pending"),
ContentKey::CommitRecoveryDiffFingerprint(id) => {
format!("{id}:commit_recovery_diff_fingerprint")
}
ContentKey::MergeFixupPending(id) => format!("{id}:merge_fixup_pending"),
ContentKey::MergeFailureKind(id) => format!("{id}:merge_failure_kind"),
ContentKey::MergeSuccess(id) => format!("{id}:merge_success"),