Merge pull request #30 from boxlite-ai/codex/capture-stdin-before-run… #88
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync upstream & merge-forward skill branches | |
| on: | |
| # Triggered by upstream repo via repository_dispatch | |
| repository_dispatch: | |
| types: [upstream-main-updated] | |
| # Fallback: run on a schedule in case dispatch isn't configured | |
| schedule: | |
| - cron: '0 */6 * * *' # every 6 hours | |
| # Also run when fork's main is pushed directly | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| issues: write | |
| concurrency: | |
| group: fork-sync | |
| cancel-in-progress: true | |
| jobs: | |
| sync-and-merge: | |
| if: github.repository != 'boxlite-ai/agentlite' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/create-github-app-token@v1 | |
| id: app-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Sync with upstream main | |
| id: sync | |
| run: | | |
| # Add upstream remote | |
| git remote add upstream https://github.com/boxlite-ai/agentlite.git | |
| git fetch upstream main | |
| # Check if upstream has new commits | |
| if git merge-base --is-ancestor upstream/main HEAD; then | |
| echo "Already up to date with upstream main." | |
| echo "synced=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Merge upstream main into fork's main | |
| if ! git merge upstream/main --no-edit; then | |
| # Auto-resolve trivial conflicts (lockfile, badge, package.json version) | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| AUTO_RESOLVABLE=true | |
| for f in $CONFLICTED; do | |
| case "$f" in | |
| package-lock.json|package.json|repo-tokens/badge.svg|.github/workflows/*) | |
| git checkout --theirs "$f" | |
| git add "$f" | |
| ;; | |
| .env.example) | |
| # Keep fork's channel-specific env vars | |
| git checkout --ours "$f" | |
| git add "$f" | |
| ;; | |
| *) | |
| AUTO_RESOLVABLE=false | |
| ;; | |
| esac | |
| done | |
| if [ "$AUTO_RESOLVABLE" = false ]; then | |
| echo "::error::Failed to merge upstream/main into fork main — non-trivial conflicts detected" | |
| git merge --abort | |
| echo "synced=false" >> "$GITHUB_OUTPUT" | |
| echo "sync_failed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git commit --no-edit | |
| echo "Auto-resolved lockfile/badge/version conflicts" | |
| fi | |
| # Regenerate lockfile to match merged package.json | |
| npm ci | |
| if ! npm run build; then | |
| echo "::error::Build failed after merging upstream/main" | |
| git reset --hard "origin/main" | |
| echo "synced=false" >> "$GITHUB_OUTPUT" | |
| echo "sync_failed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if ! npm test 2>/dev/null; then | |
| echo "::error::Tests failed after merging upstream/main" | |
| git reset --hard "origin/main" | |
| echo "synced=false" >> "$GITHUB_OUTPUT" | |
| echo "sync_failed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git push origin main | |
| echo "synced=true" >> "$GITHUB_OUTPUT" | |
| - name: Merge main into skill branches | |
| id: merge | |
| run: | | |
| # Re-fetch to pick up any changes pushed since job start | |
| git fetch origin | |
| FAILED="" | |
| SUCCEEDED="" | |
| # List all remote skill branches | |
| SKILL_BRANCHES=$(git branch -r --list 'origin/skill/*' | sed 's|origin/||' | xargs) | |
| if [ -z "$SKILL_BRANCHES" ]; then | |
| echo "No skill branches found." | |
| exit 0 | |
| fi | |
| for BRANCH in $SKILL_BRANCHES; do | |
| SKILL_NAME=$(echo "$BRANCH" | sed 's|skill/||') | |
| echo "" | |
| echo "=== Processing $BRANCH ===" | |
| git checkout -B "$BRANCH" "origin/$BRANCH" | |
| if ! git merge main --no-edit; then | |
| # Auto-resolve trivial conflicts | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| CAN_AUTO=true | |
| for f in $CONFLICTED; do | |
| case "$f" in | |
| package-lock.json|package.json|repo-tokens/badge.svg) | |
| git checkout --theirs "$f" | |
| git add "$f" | |
| ;; | |
| *) | |
| CAN_AUTO=false | |
| ;; | |
| esac | |
| done | |
| if [ "$CAN_AUTO" = false ]; then | |
| echo "::warning::Merge conflict in $BRANCH" | |
| git merge --abort | |
| FAILED="$FAILED $SKILL_NAME" | |
| continue | |
| fi | |
| git commit --no-edit | |
| fi | |
| # Check if there's anything new to push | |
| if git diff --quiet "origin/$BRANCH"; then | |
| echo "$BRANCH is already up to date with main." | |
| SUCCEEDED="$SUCCEEDED $SKILL_NAME" | |
| continue | |
| fi | |
| npm ci | |
| if ! npm run build; then | |
| echo "::warning::Build failed for $BRANCH" | |
| git reset --hard "origin/$BRANCH" | |
| FAILED="$FAILED $SKILL_NAME" | |
| continue | |
| fi | |
| if ! npm test 2>/dev/null; then | |
| echo "::warning::Tests failed for $BRANCH" | |
| git reset --hard "origin/$BRANCH" | |
| FAILED="$FAILED $SKILL_NAME" | |
| continue | |
| fi | |
| git push origin "$BRANCH" | |
| SUCCEEDED="$SUCCEEDED $SKILL_NAME" | |
| echo "$BRANCH merged and pushed successfully." | |
| done | |
| echo "" | |
| echo "=== Results ===" | |
| echo "Succeeded: $SUCCEEDED" | |
| echo "Failed: $FAILED" | |
| echo "failed=$FAILED" >> "$GITHUB_OUTPUT" | |
| echo "succeeded=$SUCCEEDED" >> "$GITHUB_OUTPUT" | |
| - name: Open issue for upstream sync failure | |
| if: steps.sync.outputs.sync_failed == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Upstream sync failed — merge conflict or build failure`, | |
| body: [ | |
| 'The automated sync with `boxlite-ai/agentlite` main failed.', | |
| '', | |
| 'This usually means upstream made changes that conflict with this fork\'s channel code.', | |
| '', | |
| 'To resolve manually:', | |
| '```bash', | |
| 'git fetch upstream main', | |
| 'git merge upstream/main', | |
| '# resolve conflicts', | |
| 'npm run build && npm test', | |
| 'git push', | |
| '```', | |
| ].join('\n'), | |
| labels: ['upstream-sync'] | |
| }); | |
| - name: Open issue for failed skill merges | |
| if: steps.merge.outputs.failed != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const failed = '${{ steps.merge.outputs.failed }}'.trim().split(/\s+/); | |
| const body = [ | |
| `The merge-forward workflow failed to merge \`main\` into the following skill branches:`, | |
| '', | |
| ...failed.map(s => `- \`skill/${s}\`: merge conflict, build failure, or test failure`), | |
| '', | |
| 'Please resolve manually:', | |
| '```bash', | |
| ...failed.map(s => [ | |
| `git checkout skill/${s}`, | |
| `git merge main`, | |
| `# resolve conflicts, then: git push`, | |
| '' | |
| ]).flat(), | |
| '```', | |
| ].join('\n'); | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Merge-forward failed for ${failed.length} skill branch(es)`, | |
| body, | |
| labels: ['skill-maintenance'] | |
| }); |