Skip to content

Automate integration-batch conflict recovery (union stub merges, snapshot refresh, lockfile regeneration)#599

Closed
Copilot wants to merge 5 commits intomainfrom
copilot/add-scripted-conflict-resolution
Closed

Automate integration-batch conflict recovery (union stub merges, snapshot refresh, lockfile regeneration)#599
Copilot wants to merge 5 commits intomainfrom
copilot/add-scripted-conflict-resolution

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 19, 2026

This PR moves integration batching merge/conflict handling into a dedicated script and adds deterministic auto-recovery for the two dominant conflict classes (stub-command files and snapshots), plus unconditional package-lock.json regeneration on lockfile conflicts.

  • Scripted merge engine for integration batching

    • Added .github/integration/scripts/merge_prs.sh and replaced inline merge logic in integration-batch.yml.
    • Script merges selected PR branches into integration and emits a single-line JSON summary consumed by workflow outputs, PR body sections, and label/comment logic.
  • Union conflict auto-resolution for stub command files

    • Auto-generates union allowlist from src/cli/**:
      • include files containing const program = new FrodoStubCommand(
      • exclude files containing const program = new FrodoCommand(
    • Applies manual overrides via:
      • .github/integration/union-allowlist-extra.txt
      • .github/integration/union-blocklist.txt
    • Configures union merge driver only for allowlisted files using .git/info/attributes + merge.union.driver true.
  • Snapshot conflict recovery with targeted updates

    • For conflicts in *.snap / __snapshots__/, resolves merge state enough to continue, derives test path patterns from snapshot stems, dedupes, and runs:
      • npm run test:update <pattern>
    • Commits updated snapshots on integration for focused maintainer review.
  • Lockfile conflict policy: never fail batching

    • If package-lock.json is conflicted, merge continues and lockfile is always regenerated with:
      • npm i --package-lock-only
    • Regenerated lockfile is committed on integration.
  • Workflow/output/documentation updates

    • integration-batch.yml now consumes script JSON (merged, skipped, snapshot patterns/files, lockfile-regenerated flag) and uses it for summaries + non-dry-run labeling/commenting.
    • Added empty override files under .github/integration/.
    • Updated docs/INTEGRATION.md to document union/snapshot/lockfile auto-resolution behavior.

Example invocation used by the workflow:

summary="$(bash .github/integration/scripts/merge_prs.sh \
  --selected-json "$selected" \
  --dry-run "$dry_run")"

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/rockcarver/frodo-cli/releases
    • Triggering command: /opt/hostedtoolcache/node/24.14.1/x64/bin/node /opt/hostedtoolcache/node/24.14.1/x64/bin/node --no-warnings --enable-source-maps --experimental-loader file:///home/REDACTED/work/frodo-cli/frodo-cli/dist/loader.cjs /home/REDACTED/work/frodo-cli/frodo-cli/dist/app.cjs shell ash credential.usernnode (http block)
    • Triggering command: /opt/hostedtoolcache/node/24.14.1/x64/bin/node /opt/hostedtoolcache/node/24.14.1/x64/bin/node --no-warnings --enable-source-maps --experimental-loader file:///home/REDACTED/work/frodo-cli/frodo-cli/dist/loader.cjs /home/REDACTED/work/frodo-cli/frodo-cli/dist/app.cjs shell p/bin/sh commit.gpgsign (http block)
    • Triggering command: /opt/hostedtoolcache/node/24.14.1/x64/bin/node /opt/hostedtoolcache/node/24.14.1/x64/bin/node --no-warnings --enable-source-maps --experimental-loader file:///home/REDACTED/work/frodo-cli/frodo-cli/dist/loader.cjs /home/REDACTED/work/frodo-cli/frodo-cli/dist/app.cjs shell tnet/tools/sh core.hooksPath (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Create a PR in rockcarver/frodo-cli implementing scripted conflict resolution for the integration batching workflow, including automatic package-lock regeneration.

Confirmed decisions:

  • Helper script(s) must live under .github/integration/scripts/ so npm/binary packaging doesn't include them.
  • package-lock.json conflicts should never fail batching; always auto-recover by regenerating lockfile with npm i --package-lock-only and committing the result on integration.

Implement:

  1. Add .github/integration/scripts/merge_prs.sh:

    • Merges selected PR branches into integration.
    • Auto-generates union allowlist by scanning src/cli/**:
      • include files containing const program = new FrodoStubCommand(
      • exclude files containing const program = new FrodoCommand(
    • Apply manual overrides from:
      • .github/integration/union-allowlist-extra.txt
      • .github/integration/union-blocklist.txt
    • Configure git union merge driver only for allowlisted paths via .git/info/attributes and merge.union.driver true.
    • If merge conflicts:
      • If all conflicts are in union allowlist -> auto-resolve (accept both) and continue.
      • If conflicts include snapshot files (**/*.snap or __snapshots__) -> complete merge (resolve snapshot files enough to continue), then run targeted npm run test:update <pattern> where pattern is derived from snapshot filename stem; dedupe patterns.
      • If package-lock.json is conflicted -> always auto-recover by regenerating using npm i --package-lock-only (regardless of other conflict types, as long as remaining conflicts are auto-resolvable) and commit.
      • Otherwise -> abort merge for that PR and mark it skipped with reason.
    • After all merges, if snapshot update patterns were run, commit updated snapshots with clear commit message.
    • Emit a single-line JSON summary describing merged PRs, skipped PRs, snapshot patterns run, snapshot files changed, and whether lockfile was regenerated.
    • Support dry-run mode (do not push, do not label/comment), but still perform merges locally and compute summary.
  2. Add empty override files:

    • .github/integration/union-allowlist-extra.txt
    • .github/integration/union-blocklist.txt
  3. Update .github/workflows/integration-batch.yml:

    • Replace inline merge logic with invocation of the script.
    • Consume script JSON output (compact) for summaries and for labeling/commenting when not dry-run.
    • Keep existing semantics for selecting PRs (integration-batch label etc.), skipping drafts/already integrated, and check-run readiness selection.
  4. Document briefly:

    • Update docs/INTEGRATION.md (or create if missing) to mention new auto-resolution behaviors: union for stub commands, snapshot auto-update, and automatic lockfile regeneration.

Deliverable: Open a PR with these changes.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Yes, implement your suggestions.

Assistant: I’m blocked from pushing the change via automation in this turn, but the fix is straightforward and you (or I, once tool access is available again) can apply it as a small edit to .github/workflows/integration-batch.yml.

What to change (selection criteria)

Right now the workflow likely does something like:

  • get head_sha
  • call the legacy combined status endpoint for that SHA
  • require state == success

That misses GitHub Actions “checks”. Replace that with check-runs evaluation:

Drop-in replacement for the “Filter to PRs with successful …” step

In that step’s loop (per PR), replace the status call with:

checks_json="$(gh api -H "Accept: application/vnd.github+json" "/repos/$REPO/commits/$head_sha/check-runs")"
total="$(echo "$checks_json" | jq -r '.total_count')"

ready="false"
reason=""

if [ "$total" -eq 0 ]; then
  ready="false"
  reason="no check runs found"
else
  incomplete="$(echo "$checks_json" | jq '[.check_runs[] | select(.status != "completed")] | length')"
  bad="$(echo "$checks_json" | jq '[.check_runs[] | select(.status=="completed") | select(.conclusion != "success" and .conclusion != "skipped" and .conclusion != "neutral")] | length')"

  if [ "$incomplete" -gt 0 ]; then
    ready="false"
    reason="checks pending"
  elif [ "$bad" -gt 0 ]; then
    ready="false"
    reason="checks not successful"
  else
    ready="true"
  fi
fi

if [ "$ready" = "true" ]; then
  selected="$(echo "$selected" | jq --argjson n "$pr" --arg t "$title" '. + [{"number":$n,"title":$t}]')"
else
  skipped="$(echo "$skipped" | jq --argjson n "$pr" --arg t "$title" --arg r "$reason" '. + [{"number":$n,"title":$t,"reason":$r}]')"
fi

And keep your existing “write outputs as compact JSON” lines, e.g.:

echo "selected=$(echo "$selected" | jq -c .)" >> "$GITHUB_OUTPUT"
echo "skipped=$(echo "$skipped" | jq -c .)" >> "$GITHUB_OUTPUT"

Why this fixes it

The PR UI “green checks” are based on check-runs, not n...

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Add scripted conflict resolution for integration batching workflow Automate integration-batch conflict recovery (union stub merges, snapshot refresh, lockfile regeneration) Apr 19, 2026
Copilot AI requested a review from vscheuber April 19, 2026 20:12
@vscheuber vscheuber requested a review from Copilot April 19, 2026 20:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the integration batching workflow to use a dedicated merge/conflict-recovery script, aiming to automatically resolve the most common conflict cases (stub command unions, snapshot updates, and lockfile regeneration) to keep batching moving reliably.

Changes:

  • Added .github/integration/scripts/merge_prs.sh to perform merges and attempt deterministic auto-recovery for union/snapshot/lockfile conflicts, emitting a JSON summary.
  • Updated integration-batch.yml to invoke the script and use its JSON output for workflow outputs, PR body content, and labeling/commenting.
  • Documented the new auto-recovery behaviors and added empty allowlist/blocklist override files under .github/integration/.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/INTEGRATION.md Documents merge auto-recovery behavior (union, snapshots, lockfile regeneration) for the batching workflow.
.github/workflows/integration-batch.yml Replaces inline merge logic with a scripted merge engine and consumes script JSON outputs for downstream steps.
.github/integration/union-blocklist.txt Adds an empty manual blocklist file for union-merge auto-resolution overrides.
.github/integration/union-allowlist-extra.txt Adds an empty manual allowlist extension file for union-merge auto-resolution overrides.
.github/integration/scripts/merge_prs.sh Implements the merge engine, conflict classification, auto-resolution, snapshot refresh, lockfile regeneration, and JSON summary emission.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +134
for pr in $(echo "$SELECTED_JSON" | jq -r '.[].number'); do
title="$(echo "$SELECTED_JSON" | jq -r ".[] | select(.number==$pr) | .title")"
git fetch origin "pull/$pr/head:pr-$pr"

if git merge --no-ff "pr-$pr" -m "Merge PR #$pr: $title"; then
merged="$(echo "$merged" | jq --argjson n "$pr" --arg t "$title" '. + [{"number":$n,"title":$t}]')"
continue
fi
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge_prs.sh is invoked via command substitution in the workflow and expected to emit only compact JSON on stdout, but this script runs git/npm commands that write progress/conflict output to stdout as well. That will pollute the captured summary and make jq -c . fail in the workflow. Redirect all non-JSON logging to stderr (or run git/npm in quiet mode), and ensure stdout prints exactly one JSON line (e.g., by sending JSON to a dedicated FD and exec 1>&2 for logs).

Copilot uses AI. Check for mistakes.
fi
for pattern in "${deduped_patterns[@]}"; do
[ -z "$pattern" ] && continue
npm run test:update "$pattern"
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm run test:update "$pattern" will not reliably pass $pattern through to Jest; npm run requires -- to forward positional args to the underlying command. As written, the snapshot refresh is likely to update all snapshots (or ignore the pattern), which defeats the “targeted update” behavior described in the PR. Use npm run test:update -- "$pattern" (and ensure the Jest invocation uses the arg as a test path/pattern).

Suggested change
npm run test:update "$pattern"
npm run test:update -- "$pattern"

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +260
if [ -s "$snapshot_patterns_file" ]; then
mapfile -t deduped_patterns < <(sort -u "$snapshot_patterns_file")
if [ "${#deduped_patterns[@]}" -gt 0 ] && [ "$npm_ci_done" = 'false' ]; then
npm ci
npm_ci_done='true'
fi
for pattern in "${deduped_patterns[@]}"; do
[ -z "$pattern" ] && continue
npm run test:update "$pattern"
snapshot_patterns="$(echo "$snapshot_patterns" | jq --arg p "$pattern" '. + [$p]')"
done

mapfile -t snapshot_files_changed_arr < <(
{
git diff --name-only
git diff --name-only --cached
} | sort -u | grep -E '(^|/)(__snapshots__/|.*\.snap$)' || true
)
if [ "${#snapshot_files_changed_arr[@]}" -gt 0 ]; then
git add -- "${snapshot_files_changed_arr[@]}"
git commit -m "test: update integration snapshots"
fi
fi

if [ "$lockfile_regen_commit_needed" = 'true' ]; then
if [ "$npm_ci_done" = 'false' ]; then
npm ci
npm_ci_done='true'
fi
npm i --package-lock-only
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script claims lockfile conflicts should “never fail batching”, but it runs npm ci before lockfile regeneration (both in the snapshot-update block and again in the lockfile-regeneration block). If package-lock.json is conflicted or temporarily inconsistent with merged package.json, npm ci can fail and abort the whole run due to set -e. Regenerate package-lock.json first (or skip npm ci entirely for the lockfile-regeneration path) so lockfile conflicts can’t break the batch.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +107
summary="$(bash .github/integration/scripts/merge_prs.sh --selected-json "$selected" --dry-run "$dry_run")"
summary="$(echo "$summary" | jq -c .)"
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step captures the script output into summary and immediately parses it as JSON. If the script writes any non-JSON logs to stdout (e.g., from git merge/npm), jq -c . will fail and the workflow will stop. Consider defensively capturing only the last line (or switching to a delimiter-based output from the script) in addition to updating the script to keep stdout JSON-only.

Suggested change
summary="$(bash .github/integration/scripts/merge_prs.sh --selected-json "$selected" --dry-run "$dry_run")"
summary="$(echo "$summary" | jq -c .)"
summary_output="$(bash .github/integration/scripts/merge_prs.sh --selected-json "$selected" --dry-run "$dry_run")"
summary="$(printf '%s\n' "$summary_output" | awk 'NF { line = $0 } END { print line }')"
summary="$(printf '%s\n' "$summary" | jq -c .)"

Copilot uses AI. Check for mistakes.
Comment thread docs/INTEGRATION.md
- union auto-resolution is applied only to an allowlist of `src/cli/**` command files that instantiate `FrodoStubCommand` (excluding files that instantiate `FrodoCommand`), plus manual overrides from:
- `.github/integration/union-allowlist-extra.txt`
- `.github/integration/union-blocklist.txt`
- snapshot conflicts (`*.snap` and `__snapshots__/`) are auto-resolved enough to complete the merge, then targeted snapshot refresh runs via `npm run test:update <pattern>` where `<pattern>` is derived from conflicted snapshot filenames
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc shows npm run test:update <pattern>, but npm run only forwards arguments to the underlying Jest command when they come after --. To reflect the actual invocation (and avoid confusion when running manually), document this as npm run test:update -- <pattern>.

Suggested change
- snapshot conflicts (`*.snap` and `__snapshots__/`) are auto-resolved enough to complete the merge, then targeted snapshot refresh runs via `npm run test:update <pattern>` where `<pattern>` is derived from conflicted snapshot filenames
- snapshot conflicts (`*.snap` and `__snapshots__/`) are auto-resolved enough to complete the merge, then targeted snapshot refresh runs via `npm run test:update -- <pattern>` where `<pattern>` is derived from conflicted snapshot filenames

Copilot uses AI. Check for mistakes.
@vscheuber
Copy link
Copy Markdown
Contributor

Superseded by #600

@vscheuber vscheuber closed this Apr 19, 2026
vscheuber added a commit that referenced this pull request Apr 19, 2026
…r-package-lock

Integration batch: auto-recover `package-lock.json` conflicts via lockfile regeneration (supersedes #599)
@vscheuber vscheuber deleted the copilot/add-scripted-conflict-resolution branch April 20, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants