diff --git a/.changeset/bold-jays-cheer.md b/.changeset/bold-jays-cheer.md new file mode 100644 index 000000000..83e6f9e40 --- /dev/null +++ b/.changeset/bold-jays-cheer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2981 +--- +**`execute-phase` now warns when local commits are ahead of origin** — forking the phase branch from `origin/$DEFAULT_BRANCH` silently missed unpushed local commits (e.g. plan/research docs). A loud WARNING now names the divergence before the fork. (#2639) diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index 8e69bdeee..97c24f5b5 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -301,7 +301,9 @@ else else git switch --quiet "$DEFAULT_BRANCH" 2>/dev/null && git merge --ff-only --quiet "origin/$DEFAULT_BRANCH" 2>/dev/null || true fi - # Pinned base (#2916); --no-track (#2498) so default autoSetupMerge doesn't wire upstream to origin/$DEFAULT_BRANCH. + # Pinned base (#2916); --no-track (#2498). #2639: warn if local ahead of origin. + AHEAD=$(git rev-list --count "origin/$DEFAULT_BRANCH..$DEFAULT_BRANCH" 2>/dev/null || echo 0) + [ "$AHEAD" != "0" ] && [ -n "$AHEAD" ] && echo "WARNING: $DEFAULT_BRANCH is $AHEAD ahead of origin — '$BRANCH_NAME' won't include those commits (#2639)." >&2 git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH" --no-track \ || { echo "ERROR: Could not create '$BRANCH_NAME' from origin/$DEFAULT_BRANCH (#2916)." >&2; exit 1; } fi diff --git a/tests/emitted-drift-acks/2930-fragmentize-execute-phase-markers.json b/tests/emitted-drift-acks/2930-fragmentize-execute-phase-markers.json index 22988e762..4bfaedce3 100644 --- a/tests/emitted-drift-acks/2930-fragmentize-execute-phase-markers.json +++ b/tests/emitted-drift-acks/2930-fragmentize-execute-phase-markers.json @@ -1,6 +1,6 @@ { "version": 1, "paths": { - "execute-phase.md": "#2930 (epic #1671 Phase 3): pilots the in-file `` marker grammar by wrapping the --wave/gap-closure/regression-gate branch sections (partial-wave, gap-closure-artifacts, regression-gate) in marker pairs, proving the composeWorkflow seam runs at install time before per-runtime rewrites. Retargeted from plan-phase.md (chore/2930 review): plan-phase.md sits only 36 B under the ADR-857 Phase-6 PRE_PHASE6 gate (tests/phase6-capstone-conformance.test.cjs) and cannot absorb marker overhead, so the maintainer retargeted the pilot to execute-phase.md, which has 728 B of headroom under its own PRE_PHASE6 cap. SOURCE grows by exactly 275 marker bytes (6 marker lines); the EMITTED artifact composeWorkflow produces at install is byte-identical to the pre-#2930 file (markers are stripped, never shipped). See .gsd/phase/chore-2930-fragmentize-xl-workflow/40-design.md 'Known limits' item 5." + "execute-phase.md": "#2930 (epic #1671 Phase 3): pilots the in-file `` marker grammar by wrapping the --wave/gap-closure/regression-gate branch sections (partial-wave, gap-closure-artifacts, regression-gate) in marker pairs, proving the composeWorkflow seam runs at install time before per-runtime rewrites. Retargeted from plan-phase.md (chore/2930 review): plan-phase.md sits only 36 B under the ADR-857 Phase-6 PRE_PHASE6 gate (tests/phase6-capstone-conformance.test.cjs) and cannot absorb marker overhead, so the maintainer retargeted the pilot to execute-phase.md, which has 728 B of headroom under its own PRE_PHASE6 cap. SOURCE grows by exactly 275 marker bytes (6 marker lines); the EMITTED artifact composeWorkflow produces at install is byte-identical to the pre-#2930 file (markers are stripped, never shipped). See .gsd/phase/chore-2930-fragmentize-xl-workflow/40-design.md 'Known limits' item 5. #2639: handle_branching now warns when local is ahead of origin (+376 B condensed one-line WARNING + rev-list --count check)." } } diff --git a/tests/execute-phase-branch-base-ahead-warning.test.cjs b/tests/execute-phase-branch-base-ahead-warning.test.cjs new file mode 100644 index 000000000..7f41bc1d5 --- /dev/null +++ b/tests/execute-phase-branch-base-ahead-warning.test.cjs @@ -0,0 +1,37 @@ +// allow-test-rule: source-text-is-the-product (see #2639) +// gsd-core/workflows/execute-phase.md is the deployed CI contract; asserting +// that handle_branching warns when local is ahead of origin is only expressible +// against the workflow text. + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const WORKFLOW = path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase.md'); + +describe('#2639 — handle_branching warns when local is ahead of origin', () => { + const text = fs.existsSync(WORKFLOW) ? fs.readFileSync(WORKFLOW, 'utf8') : ''; + + test('the fork block checks for local-ahead-of-origin before branching', () => { + assert.ok(text.length > 0, 'execute-phase.md must exist'); + // The fix adds a `git rev-list --count origin/$DEFAULT_BRANCH..$DEFAULT_BRANCH` + // check before the `git checkout -b` fork, with a WARNING to stderr. + assert.ok( + /rev-list.*--count.*DEFAULT_BRANCH/i.test(text), + 'handle_branching must check if local $DEFAULT_BRANCH is ahead of origin before forking (git rev-list --count) — without this, unpushed local commits are silently missing from the phase branch (#2639)', + ); + }); + + test('the warning names the divergence and advises the user', () => { + assert.ok(text.length > 0, 'execute-phase.md must exist'); + // The warning must mention "ahead" and "unpushed" so the user understands + // their commits won't be on the phase branch. + assert.ok( + /ahead.*origin.*DEFAULT_BRANCH/i.test(text) || /unpushed/i.test(text), + 'handle_branching must warn that local commits are ahead/unpushed when forking from origin (#2639)', + ); + }); +});