pub fn phase_commit_count(
project_root: &Path,
git_flow: &GitFlowConfig,
phase: PhaseId,
) -> Option<u32>Expand description
Commits on the phase’s feature branch that are not on develop.
Derives the branch name from git_flow.feature_prefix and the zero-padded
phase, verifies the branch exists with rev-parse --verify, and on
success counts {git_flow.develop}..{branch} with rev-list --count.
This is the single implementation of that count — evaluate_layer2,
evaluate_layer3 and pipeline_outcomes::handle_validate_outcome’s
forward-progress check all call it rather than each re-deriving the branch
name and re-running the same two git commands, which is what made the
counts able to silently diverge before this extraction. That claim was
aspirational until 35-01: evaluate_layer3 carried its own inline
rev-list --count with an independent copy of the lossy zero collapse, and
deleting it is what makes “single implementation” true.
Must be called with the main project_root, never a worktree path — git
worktrees share refs and the object database, so a commit made inside a
linked worktree is immediately visible to a count run from the main
checkout, which is the property every caller already relies on.
The return distinguishes a MEASUREMENT from a measurement FAILURE, which
is the whole point of the Option (999.77 / D-08, A-06):
Some(n)— git ran and reported a real number. This includesSome(0)for a branch that genuinely does not exist yet, which is normal on a phase’s first Validate and is a real observation, not a failure to observe.None— the count could not be established: either thegitchild could not be executed at all (.output()returnedErr), or it ran but produced stdout that does not parse as au32. A-06 splits only the ran/did-not-run axis; the unparseable case is mapped toNonehere because the child produced no usable count, and reporting a forged zero for it would recreate exactly the hazard this signature removes.
The two consumers now handle None distinctly, and neither collapses it
to zero. pipeline_outcomes::handle_validate_outcome treats an
unmeasurable cycle as not-progress and leaves its persisted baseline
untouched, so the next real measurement still compares against the last
real observation. evaluate_layer2 returns Ok(None) and falls through
to evaluate_layer3, which classifies an unmeasurable count as
AgentStatus::Unknown rather than asserting the negative that no work
was done.
§Changed in v2.5.0 — breaking
The return type was u32 before this release; it is now Option<u32>
(999.77 / 999.87). A call site updating from the old form must decide which
of the two states it means, because the old type conflated them:
Some(0)— git RAN and the branch genuinely has no commits. This is the old0in its legitimate sense, and is normal on a phase’s first Validate.None— no count was established at all. This is the case the old signature could not express, and.unwrap_or(0)is precisely the wrong way to restore it: collapsing it back to zero is the defect this change exists to remove. A transientgitfailure then reads as “no work done”, which forged aconsecutive_failuresbaseline reset (999.77) and made the result cascade classify a successful agent asFailed(999.87).
The enumeration of this and every other public-surface change in the release
is in CHANGELOG.md under 2.5.0.