@@ -617,15 +617,17 @@ function main(): SyncResult {
617617 }
618618 }
619619
620- // Update sync marker only when NOTHING had local modifications (no
621- // merge conflicts AND no auto-merged-with-local-mods files). If any file
622- // had local mods — even an auto-merged one — keep the old sha so the
623- // next run still has the correct base context for 3-way merging until
624- // a human has reviewed/merged the needs-review PR.
620+ // Update sync marker whenever all files were successfully resolved:
621+ // clean transforms, clean auto-merges, or no changes at all. A successful
622+ // 3-way auto-merge means the file IS resolved — its content on disk
623+ // reflects the merged state, so the next run should treat HEAD as the
624+ // new base. Only outstanding merge conflicts (upstream-wins written to
625+ // PR branch only) or deletions (not auto-applied) keep the old sha so
626+ // future runs still flag them until a human merges the needs-review PR.
625627 if (
626628 ! dryRun &&
627- result . needsReview . length === 0 &&
628629 result . mergeConflict . length === 0 &&
630+ result . deleted . length === 0 &&
629631 ( result . copied . length > 0 ||
630632 result . transformed . length > 0 ||
631633 result . autoMerged . length > 0 )
@@ -678,14 +680,16 @@ const hasAutoApplied =
678680 result . copied . length > 0 ||
679681 result . transformed . length > 0 ||
680682 result . autoMerged . length > 0 ;
681- // Any file that had showcase-local modifications (needsReview) must route
682- // through push_and_pr so a human can review — even if the 3-way merge was
683- // clean (autoMerged). Silent auto-merging files with local mods would
684- // clobber intentional showcase divergence.
683+ // Only files that still need human attention force push_and_pr:
684+ // - mergeConflict: 3-way merge failed, upstream-wins content staged to PR
685+ // branch, human must reconcile
686+ // - deleted: files gone on main, not auto-deleted in showcase, human decides
687+ // Auto-merged files (clean 3-way merge) are considered RESOLVED — they go
688+ // through the auto_push fast path and the marker advances. `needsReview`
689+ // is the superset tracker (local-mods detected pre-merge) and is NOT a
690+ // gating condition on its own.
685691const hasReviewItems =
686- result . mergeConflict . length > 0 ||
687- result . needsReview . length > 0 ||
688- result . deleted . length > 0 ;
692+ result . mergeConflict . length > 0 || result . deleted . length > 0 ;
689693
690694if ( ! hasAutoApplied && ! hasReviewItems ) {
691695 process . exit ( 2 ) ;
@@ -720,10 +724,18 @@ if (!hasAutoApplied && !hasReviewItems) {
720724 // ROOT makes the contract explicit: whoever invokes the script picks
721725 // where these artifacts land, and the workflow invokes from repo root.
722726 const artifactDir = process . cwd ( ) ;
723- fs . writeFileSync (
724- path . join ( artifactDir , "review-items.txt" ) ,
725- reviewLines . join ( "\n" ) + "\n" ,
726- ) ;
727+ const reviewItemsPath = path . join ( artifactDir , "review-items.txt" ) ;
728+ fs . writeFileSync ( reviewItemsPath , reviewLines . join ( "\n" ) + "\n" ) ;
729+ // Emit the absolute path to GITHUB_OUTPUT so the workflow's PR-body and
730+ // Slack payload steps can read the file without hardcoding a location.
731+ // (Downstream steps use `steps.sync.outputs.review_items_file`.) Skipped
732+ // outside CI — process.env.GITHUB_OUTPUT is unset for local runs.
733+ if ( process . env . GITHUB_OUTPUT ) {
734+ fs . appendFileSync (
735+ process . env . GITHUB_OUTPUT ,
736+ `review_items_file=${ reviewItemsPath } \n` ,
737+ ) ;
738+ }
727739 // Persist the conflict manifest so the workflow can apply upstream-wins
728740 // content to the PR branch (NOT the current worktree — see
729741 // conflictManifest comment above).
0 commit comments