Problem
A sync whose upstream range touches only consumer-owned / carved-out paths
still produces a commit -- and, on a protected branch, a branch + PR whose entire
diff is a one-line syncedAt bump in .sdlc-ai-sync.json.
This is the same empty-PR complaint as #224. The guard added by #230 only covers
the case where the anchor already equals the upstream head:
$syncStateChanged = ($ops.Count -gt 0) -or ($mergedPaths.Count -gt 0) -or ($anchorSha -ne $upstreamHead)
When the anchor is behind the head but every upstream commit in the range
touched a carved-out path (run.ps1, run.Tests.ps1,
.github/workflows/copilot-setup-steps.yml, .github/agents/tests/), the third
clause is $true, so the state file is rewritten, the tree goes dirty, and an
empty sync commit/PR is manufactured.
Reproduced live in a consuming repo: anchor 42d3b44, upstream head 5264227.
The two intervening upstream PRs (#232, #234) touched only run.ps1 /
run.Tests.ps1, both consumer-owned. Files to update: 0, yet a real run would
open a content-free PR.
Second defect -- dishonest message
Write-SyncPlan prints the zero-op line unconditionally:
Files to update: 0 (already at upstream 5264227 -- nothing to sync)
The consumer is not at 5264227; its anchor is 42d3b44. Two upstream
commits exist, they simply carry no managed-path changes. The message should not
claim an anchor position that is false.
Proposed fix
- Drop the
($anchorSha -ne $upstreamHead) clause so a zero-op sync never
rewrites the state file. This is safe: the anchor feeds only
Test-LocalDriftOnManagedPaths and Get-UpstreamOps, both of which compare
managed-path content. Zero ops means no managed path differs between anchor
and head, so an older anchor yields identical results -- it just re-diffs a
slightly wider (and still cheap) range until real managed content lands.
- Make the zero-op message distinguish "anchor == head" from "upstream moved but
nothing managed changed."
Acceptance criteria
Problem
A sync whose upstream range touches only consumer-owned / carved-out paths
still produces a commit -- and, on a protected branch, a branch + PR whose entire
diff is a one-line
syncedAtbump in.sdlc-ai-sync.json.This is the same empty-PR complaint as #224. The guard added by #230 only covers
the case where the anchor already equals the upstream head:
When the anchor is behind the head but every upstream commit in the range
touched a carved-out path (
run.ps1,run.Tests.ps1,.github/workflows/copilot-setup-steps.yml,.github/agents/tests/), the thirdclause is
$true, so the state file is rewritten, the tree goes dirty, and anempty sync commit/PR is manufactured.
Reproduced live in a consuming repo: anchor
42d3b44, upstream head5264227.The two intervening upstream PRs (#232, #234) touched only
run.ps1/run.Tests.ps1, both consumer-owned.Files to update: 0, yet a real run wouldopen a content-free PR.
Second defect -- dishonest message
Write-SyncPlanprints the zero-op line unconditionally:The consumer is not at
5264227; its anchor is42d3b44. Two upstreamcommits exist, they simply carry no managed-path changes. The message should not
claim an anchor position that is false.
Proposed fix
($anchorSha -ne $upstreamHead)clause so a zero-op sync neverrewrites the state file. This is safe: the anchor feeds only
Test-LocalDriftOnManagedPathsandGet-UpstreamOps, both of which comparemanaged-path content. Zero ops means no managed path differs between anchor
and head, so an older anchor yields identical results -- it just re-diffs a
slightly wider (and still cheap) range until real managed content lands.
nothing managed changed."
Acceptance criteria
syncedAtuntouched.