Upstream sync check #1
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: Upstream sync check | |
| on: | |
| schedule: | |
| - cron: '0 8 * * 1' # Weekly on Monday | |
| workflow_dispatch: | |
| jobs: | |
| check-motoko: | |
| name: Check caffeinelabs/motoko | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Create GitHub App Token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| id: app-token | |
| with: | |
| client-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_CLIENT_ID }} | |
| private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} | |
| - name: Get latest motoko release tag | |
| id: latest | |
| run: | | |
| TAG=$(gh release view --repo caffeinelabs/motoko --json tagName -q .tagName) | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Get current pinned tag | |
| id: current | |
| run: | | |
| TAG=$(grep 'Tag:' skills/motoko/SKILL.md | head -1 | sed 's/.*Tag: \([^ ]*\).*/\1/') | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: Check if update needed | |
| id: check | |
| run: | | |
| LATEST="${{ steps.latest.outputs.tag }}" | |
| CURRENT="${{ steps.current.outputs.tag }}" | |
| BRANCH="infra/sync-motoko-${LATEST}" | |
| if [ "$LATEST" = "$CURRENT" ]; then | |
| echo "needed=false" >> $GITHUB_OUTPUT | |
| echo "Already at latest: $CURRENT" | |
| elif git ls-remote --exit-code origin "refs/heads/${BRANCH}" > /dev/null 2>&1; then | |
| echo "needed=false" >> $GITHUB_OUTPUT | |
| echo "Branch $BRANCH already exists — PR likely open, skipping" | |
| else | |
| echo "needed=true" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| echo "New release: $LATEST (current: $CURRENT)" | |
| fi | |
| - name: Resolve commit SHA for new release | |
| if: steps.check.outputs.needed == 'true' | |
| id: sha | |
| run: | | |
| TAG="${{ steps.latest.outputs.tag }}" | |
| RESULT=$(curl -sf "https://api.github.com/repos/caffeinelabs/motoko/git/ref/tags/${TAG}" \ | |
| -H "Authorization: Bearer $GH_TOKEN" | \ | |
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['object']['sha'], d['object']['type'])") | |
| OBJ_SHA=$(echo "$RESULT" | awk '{print $1}') | |
| OBJ_TYPE=$(echo "$RESULT" | awk '{print $2}') | |
| if [ "$OBJ_TYPE" = "tag" ]; then | |
| COMMIT=$(curl -sf "https://api.github.com/repos/caffeinelabs/motoko/git/tags/${OBJ_SHA}" \ | |
| -H "Authorization: Bearer $GH_TOKEN" | \ | |
| python3 -c "import sys,json; print(json.load(sys.stdin)['object']['sha'])") | |
| else | |
| COMMIT="$OBJ_SHA" | |
| fi | |
| echo "commit=$COMMIT" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Fetch upstream files and build diff | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| SHA="${{ steps.sha.outputs.commit }}" | |
| CURRENT="${{ steps.current.outputs.tag }}" | |
| LATEST="${{ steps.latest.outputs.tag }}" | |
| declare -A UPSTREAM_TO_LOCAL=( | |
| ["writing-motoko"]="motoko" | |
| ["migrating-motoko"]="migrating-motoko" | |
| ["migrating-motoko-enhanced"]="migrating-motoko-enhanced" | |
| ) | |
| { | |
| echo "## Upstream diff: caffeinelabs/motoko \`${CURRENT}\` → \`${LATEST}\`" | |
| echo "" | |
| echo "Commit: [\`${SHA:0:12}\`](https://github.com/caffeinelabs/motoko/commit/${SHA})" | |
| echo "" | |
| echo "**Review instructions:** check which sections are listed as \`owned by icskills\` in each" | |
| echo "skill's upstream comment block before applying changes. Do NOT overwrite those sections." | |
| echo "" | |
| } > /tmp/pr-body.md | |
| for upstream_name in "writing-motoko" "migrating-motoko" "migrating-motoko-enhanced"; do | |
| local_name="${UPSTREAM_TO_LOCAL[$upstream_name]}" | |
| curl -sf "https://raw.githubusercontent.com/caffeinelabs/motoko/${SHA}/.agents/skills/${upstream_name}/SKILL.md" \ | |
| > /tmp/upstream-${upstream_name}.md 2>/dev/null || { | |
| echo "(skill not found at this path)" > /tmp/upstream-${upstream_name}.md | |
| } | |
| DIFF=$(diff skills/${local_name}/SKILL.md /tmp/upstream-${upstream_name}.md || true) | |
| if [ -n "$DIFF" ]; then | |
| { | |
| echo "### \`${local_name}\` ← upstream \`${upstream_name}\`" | |
| echo "" | |
| echo '<details><summary>Show diff</summary>' | |
| echo "" | |
| echo '```diff' | |
| echo "$DIFF" | |
| echo '```' | |
| echo "" | |
| echo '</details>' | |
| echo "" | |
| } >> /tmp/pr-body.md | |
| else | |
| echo "### \`${local_name}\` — no changes" >> /tmp/pr-body.md | |
| echo "" >> /tmp/pr-body.md | |
| fi | |
| done | |
| - name: Create sync PR | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| BRANCH="${{ steps.check.outputs.branch }}" | |
| git config user.name "pr-automation-bot-public[bot]" | |
| git config user.email "pr-automation-bot-public[bot]@users.noreply.github.com" | |
| git checkout -b "$BRANCH" | |
| git commit --allow-empty -m "chore: upstream sync check — caffeinelabs/motoko ${{ steps.latest.outputs.tag }}" | |
| git push -u origin "$BRANCH" | |
| gh pr create \ | |
| --title "chore: sync check — caffeinelabs/motoko ${{ steps.latest.outputs.tag }}" \ | |
| --body-file /tmp/pr-body.md | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| check-mops: | |
| name: Check caffeinelabs/mops | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Create GitHub App Token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| id: app-token | |
| with: | |
| client-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_CLIENT_ID }} | |
| private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} | |
| - name: Get latest mops release tag | |
| id: latest | |
| run: | | |
| TAG=$(gh release list --repo caffeinelabs/mops --limit 100 --json tagName --jq '[.[] | select(.tagName | startswith("cli-"))] | first | .tagName') | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Get current pinned tag | |
| id: current | |
| run: | | |
| TAG=$(grep 'Tag:' skills/mops-cli/SKILL.md | head -1 | sed 's/.*Tag: \([^ ]*\).*/\1/') | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: Check if update needed | |
| id: check | |
| run: | | |
| LATEST="${{ steps.latest.outputs.tag }}" | |
| CURRENT="${{ steps.current.outputs.tag }}" | |
| BRANCH="infra/sync-mops-${LATEST}" | |
| if [ "$LATEST" = "$CURRENT" ]; then | |
| echo "needed=false" >> $GITHUB_OUTPUT | |
| echo "Already at latest: $CURRENT" | |
| elif git ls-remote --exit-code origin "refs/heads/${BRANCH}" > /dev/null 2>&1; then | |
| echo "needed=false" >> $GITHUB_OUTPUT | |
| echo "Branch $BRANCH already exists — PR likely open, skipping" | |
| else | |
| echo "needed=true" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| echo "New release: $LATEST (current: $CURRENT)" | |
| fi | |
| - name: Resolve commit SHA for new release | |
| if: steps.check.outputs.needed == 'true' | |
| id: sha | |
| run: | | |
| TAG="${{ steps.latest.outputs.tag }}" | |
| RESULT=$(curl -sf "https://api.github.com/repos/caffeinelabs/mops/git/ref/tags/${TAG}" \ | |
| -H "Authorization: Bearer $GH_TOKEN" | \ | |
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['object']['sha'], d['object']['type'])") | |
| OBJ_SHA=$(echo "$RESULT" | awk '{print $1}') | |
| OBJ_TYPE=$(echo "$RESULT" | awk '{print $2}') | |
| if [ "$OBJ_TYPE" = "tag" ]; then | |
| COMMIT=$(curl -sf "https://api.github.com/repos/caffeinelabs/mops/git/tags/${OBJ_SHA}" \ | |
| -H "Authorization: Bearer $GH_TOKEN" | \ | |
| python3 -c "import sys,json; print(json.load(sys.stdin)['object']['sha'])") | |
| else | |
| COMMIT="$OBJ_SHA" | |
| fi | |
| echo "commit=$COMMIT" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Fetch upstream file and build diff | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| SHA="${{ steps.sha.outputs.commit }}" | |
| CURRENT="${{ steps.current.outputs.tag }}" | |
| LATEST="${{ steps.latest.outputs.tag }}" | |
| curl -sf "https://raw.githubusercontent.com/caffeinelabs/mops/${SHA}/.agents/skills/mops-cli/SKILL.md" \ | |
| > /tmp/upstream-mops-cli.md 2>/dev/null || { | |
| echo "(skill not found at this path)" > /tmp/upstream-mops-cli.md | |
| } | |
| DIFF=$(diff skills/mops-cli/SKILL.md /tmp/upstream-mops-cli.md || true) | |
| { | |
| echo "## Upstream diff: caffeinelabs/mops \`${CURRENT}\` → \`${LATEST}\`" | |
| echo "" | |
| echo "Commit: [\`${SHA:0:12}\`](https://github.com/caffeinelabs/mops/commit/${SHA})" | |
| echo "" | |
| echo "**Review instructions:** check which sections are listed as \`owned by icskills\` in the" | |
| echo "skill's upstream comment block before applying changes. Do NOT overwrite those sections." | |
| echo "" | |
| echo "### \`mops-cli\`" | |
| echo "" | |
| if [ -n "$DIFF" ]; then | |
| echo '<details><summary>Show diff</summary>' | |
| echo "" | |
| echo '```diff' | |
| echo "$DIFF" | |
| echo '```' | |
| echo "" | |
| echo '</details>' | |
| else | |
| echo "No changes." | |
| fi | |
| } > /tmp/pr-body.md | |
| - name: Create sync PR | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| BRANCH="${{ steps.check.outputs.branch }}" | |
| git config user.name "pr-automation-bot-public[bot]" | |
| git config user.email "pr-automation-bot-public[bot]@users.noreply.github.com" | |
| git checkout -b "$BRANCH" | |
| git commit --allow-empty -m "chore: upstream sync check — caffeinelabs/mops ${{ steps.latest.outputs.tag }}" | |
| git push -u origin "$BRANCH" | |
| gh pr create \ | |
| --title "chore: sync check — caffeinelabs/mops ${{ steps.latest.outputs.tag }}" \ | |
| --body-file /tmp/pr-body.md | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} |