diff --git a/.github/workflows/merge-forward-skills.yml b/.github/workflows/merge-forward-skills.yml new file mode 100644 index 0000000..5b6d7df --- /dev/null +++ b/.github/workflows/merge-forward-skills.yml @@ -0,0 +1,200 @@ +name: Sync upstream & merge-forward skill branches + +on: + # Triggered by upstream repo via repository_dispatch + repository_dispatch: + types: [upstream-main-updated] + # Fallback: run on 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 + +jobs: + sync-and-merge: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_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/qwibitai/nanoclaw.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 + echo "::error::Failed to merge upstream/main into fork main — conflicts detected" + git merge --abort + echo "synced=false" >> "$GITHUB_OUTPUT" + echo "sync_failed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Validate build + 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: | + 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 + echo "::warning::Merge conflict in $BRANCH" + git merge --abort + FAILED="$FAILED $SKILL_NAME" + continue + 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 `qwibitai/nanoclaw` 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'] + });