feat(phased-execution): push each phase commit after it is made
The harness now pushes the phase commit right after committing it (PHASE_PUSH=1 default, new push_phase in lib.sh): upstream when set, else 'git push -u <first remote> <branch>'; no remote = skip with a notice. A push failure keeps the commit local, prints the same loud ERROR contract as a commit failure, and stops the run — the next phase's push sweeps the unpushed commit in. PHASE_PUSH=0 opts out with a loud notice. SKILL.md (Commits + config table + exit codes) and the executor prompts document the new behavior.
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
# phase and is the commit point. Children never commit — by default
|
||||
# (PHASE_COMMIT=1) the harness makes ONE atomic commit per completed phase
|
||||
# (code changes + the file move + the executor reports), scoped so the
|
||||
# owner's pre-existing worktree changes are left alone (see commit_phase).
|
||||
# owner's pre-existing worktree changes are left alone, and pushes it when
|
||||
# the repo has a remote (PHASE_PUSH=1; see commit_phase / push_phase).
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
EXECUTOR_PROMPT_FILE="$SKILL_DIR/assets/executor-prompt.md"
|
||||
@@ -303,12 +304,13 @@ run_validation() {
|
||||
bash .agents/validate.sh >"$1" 2>&1
|
||||
}
|
||||
|
||||
# --- phase commit -------------------------------------------------------------
|
||||
# --- phase commit + push ------------------------------------------------------
|
||||
# Commit ownership: child executors NEVER commit (their prompts forbid it,
|
||||
# overriding any project instruction to commit per task/phase). By default
|
||||
# (PHASE_COMMIT=1) the harness therefore makes ONE atomic commit per
|
||||
# completed phase — the phase's code changes, the todo→complete file move,
|
||||
# and the executor reports, together — at the phase's commit point.
|
||||
# and the executor reports, together — at the phase's commit point, and
|
||||
# pushes it right after (PHASE_PUSH=1, default — see push_phase).
|
||||
#
|
||||
# Staging is scoped so the owner's unrelated work is not swept in:
|
||||
# stage = (everything dirty now)
|
||||
@@ -334,9 +336,11 @@ phase_dirty_snapshot() {
|
||||
# Returns 0 on success, when there is nothing left to commit, or outside a
|
||||
# git work tree. Returns 1 (after printing a ✗ ERROR block with the git
|
||||
# error and the hand-fix command) when the phase's own artifacts could not
|
||||
# be committed. The phase stays COMPLETE either way — the work passed
|
||||
# validation; the caller stops the run so a commit miss is visible, never
|
||||
# retried as a task and never swept into a later phase's commit.
|
||||
# be committed, or the push of the phase commit failed (the commit is local
|
||||
# and safe — the next phase's push sweeps it in once the push works). The
|
||||
# phase stays COMPLETE either way — the work passed validation; the caller
|
||||
# stops the run so a commit/push miss is visible, never retried as a task
|
||||
# and never swept into a later phase's commit.
|
||||
commit_phase() {
|
||||
local unit="$1" report="${2:-}"
|
||||
local phase pre curf paths lits moved staged addout commitout subject msgf
|
||||
@@ -441,6 +445,49 @@ commit_phase() {
|
||||
fi
|
||||
rm -f "$msgf" "$pre"
|
||||
echo " (committed: $(git log -1 --oneline))"
|
||||
# Hand the commit to the remote (PHASE_PUSH, default on). A push failure
|
||||
# returns 1 so the run stops — same contract as a commit failure.
|
||||
if ! push_phase "$phase"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# push_phase <phase>
|
||||
# Pushes the phase commit made by commit_phase (PHASE_PUSH=1, default).
|
||||
# No remote configured → notice and success (the commit stays local).
|
||||
# Branch with an upstream → `git push`; without → `git push -u <first
|
||||
# remote> <branch>`. On failure prints a ✗ ERROR block with the git error
|
||||
# and the hand-fix command and returns 1 — the commit is local and safe,
|
||||
# and the next phase's push sweeps the unpushed commit in once the push
|
||||
# works.
|
||||
push_phase() {
|
||||
local phase="$1" remote branch cmd pushout
|
||||
[[ "${PHASE_PUSH:-1}" == "1" ]] || { echo " (PHASE_PUSH=0 — commit not pushed, left local)"; return 0; }
|
||||
remote="$(git remote 2>/dev/null | head -n1)"
|
||||
if [[ -z "$remote" ]]; then
|
||||
echo " (no git remote configured — skipping push)"
|
||||
return 0
|
||||
fi
|
||||
branch="$(git symbolic-ref --short -q HEAD || true)"
|
||||
if [[ -z "$branch" ]]; then
|
||||
echo "✗ ERROR: phase push for $phase — detached HEAD, nothing to push." >&2
|
||||
echo " the phase commit is local; push by hand once you are on a branch." >&2
|
||||
return 1
|
||||
fi
|
||||
if git rev-parse --abbrev-ref --disambiguate '@{u}' >/dev/null 2>&1; then
|
||||
cmd=(git push)
|
||||
else
|
||||
cmd=(git push -u "$remote" "$branch")
|
||||
fi
|
||||
if ! pushout="$("${cmd[@]}" 2>&1)"; then
|
||||
echo "✗ ERROR: phase push for $phase FAILED — the phase is committed LOCALLY but NOT PUSHED." >&2
|
||||
printf '%s\n' "$pushout" | sed 's/^/ /' >&2
|
||||
echo " finish by hand: ${cmd[*]}" >&2
|
||||
echo " re-running will NOT re-push it now — the next phase's push sweeps it in once the push works; fix the push first." >&2
|
||||
return 1
|
||||
fi
|
||||
echo " (pushed: $(git log -1 --oneline))"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -482,8 +529,8 @@ notify_task() {
|
||||
# execute_unit <unit>
|
||||
# Returns 0 and moves the unit to complete/ on success; returns 1 after
|
||||
# MAX_FIX_ATTEMPTS failed attempts (unit file is left in todo/), or 1 when
|
||||
# the phase commit fails at a phase's commit point (unit stays in
|
||||
# complete/ — the work is done; the commit must be finished by hand).
|
||||
# the phase commit or its push fails at a phase's commit point (unit stays
|
||||
# in complete/ — the work is done; the commit/push must be finished by hand).
|
||||
execute_unit() {
|
||||
local unit="$1"
|
||||
local base attempt=1 errors=""
|
||||
@@ -544,7 +591,7 @@ execute_unit() {
|
||||
# The work is done and validated; the unit stays in complete/ and
|
||||
# re-running will NOT re-execute this phase. Stop the run so the
|
||||
# commit miss is visible — commit_phase printed the hand-fix.
|
||||
echo " phase work is UNCOMMITTED — fix the commit first (see above); re-running continues at the next phase" >&2
|
||||
echo " phase commit/push did not complete — fix it first (see above); re-running continues at the next phase" >&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user