Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
# CODEOWNERS documentation : https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners

* @ContentSquare/tag-functions
* @ContentSquare/tag-functions @ContentSquare/mobile

# GitHub Copilot agents folder ownership
.github/agents/* @ContentSquare/tag-functions
.github/agents/* @ContentSquare/tag-functions

# TAG TEAM

**/contentsquare-web*/ @ContentSquare/tag-functions
**/contentsquare-web*/ @ContentSquare/tag-functions

# MCP TEAM

plugins/contentsquare/* @ContentSquare/mcp-and-a2a
plugins/contentsquare/* @ContentSquare/mcp-and-a2a

# MOBILE TEAM

**/contentsquare-android*/ @ContentSquare/mobile
**/contentsquare-ios*/ @ContentSquare/mobile
**/contentsquare-reactnative*/ @ContentSquare/mobile
**/contentsquare-flutter*/ @ContentSquare/mobile
76 changes: 76 additions & 0 deletions .github/workflows/sync-skill.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Sync Skill

# Reusable workflow: mirrors a caller's authored skill into skills/, regenerates
# the plugin copies, and opens/updates one PR per stack.

on:
workflow_call:
inputs:
repo:
description: Source repo slug (owner/name)
required: true
type: string
sha:
description: Merged commit SHA on the source repo's default branch
required: true
type: string
compare_url:
description: Source diff URL (optional)
required: false
type: string
default: ""
skills_path:
description: Skills root inside the source repo
required: false
type: string
default: ai/.agents/skills
secrets:
token:
description: Token with read on the source repo and write access here.
required: true

concurrency:
group: sync-skill-${{ inputs.repo }}
cancel-in-progress: false

jobs:
sync:
runs-on: ubuntu24-large
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout agents repo
uses: ContentSquare/actions-checkout@approved-v6
with:
repository: ContentSquare/agents
ref: main
token: ${{ secrets.token }}
fetch-depth: 0

- name: Checkout source SDK repo (skills only)
uses: ContentSquare/actions-checkout@approved-v6
with:
repository: ${{ inputs.repo }}
ref: ${{ inputs.sha }}
token: ${{ secrets.token }}
path: _source
sparse-checkout: ${{ inputs.skills_path }}
sparse-checkout-cone-mode: false
fetch-depth: 1

- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Sync skill and open/update PR
env:
GH_TOKEN: ${{ secrets.token }}
SOURCE_REPO: ${{ inputs.repo }}
SOURCE_SHA: ${{ inputs.sha }}
COMPARE_URL: ${{ inputs.compare_url }}
SKILLS_PATH: ${{ inputs.skills_path }}
SOURCE_DIR: _source
SERVER_URL: ${{ github.server_url }}
run: ./scripts/sync-skill-from-source.sh
122 changes: 122 additions & 0 deletions scripts/sync-skill-from-source.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
#
# Mirror an authored skill from a source checkout into skills/<skill>/, regenerate
# the generated plugin copies, and open or update one PR per stack.
#
# Env (required): SOURCE_REPO, SOURCE_SHA, SOURCE_DIR, GH_TOKEN
# Env (optional): SKILLS_PATH, COMPARE_URL, SERVER_URL
set -euo pipefail

SOURCE_REPO="${SOURCE_REPO:?SOURCE_REPO is required}"
SOURCE_SHA="${SOURCE_SHA:?SOURCE_SHA is required}"
SOURCE_DIR="${SOURCE_DIR:?SOURCE_DIR is required}"
SKILLS_PATH="${SKILLS_PATH:-ai/.agents/skills}"
SERVER_URL="${SERVER_URL:-https://github.com}"

SHORT_SHA="${SOURCE_SHA:0:7}"
COMMIT_URL="$SERVER_URL/$SOURCE_REPO/commit/$SOURCE_SHA"
DIFF_URL="${COMPARE_URL:-$COMMIT_URL}"

src_root="$SOURCE_DIR/$SKILLS_PATH"
if [ ! -d "$src_root" ]; then
echo "::notice::No skills directory at '$src_root' in ${SOURCE_REPO}@${SHORT_SHA} — nothing to sync."
exit 0
fi

# Skill folder name -> stack (used as the PR branch key).
skill_to_stack() {
case "$1" in
contentsquare-flutter-sdk) echo "flutter" ;;
contentsquare-ios-sdk) echo "ios" ;;
contentsquare-android-sdk) echo "android" ;;
contentsquare-reactnative-sdk) echo "reactnative" ;;
contentsquare-web-tag-install) echo "web" ;;
*) printf '%s' "${1#contentsquare-}" | sed 's/-sdk$//' ;;
esac
}

git fetch --quiet origin main

synced_any=false
shopt -s nullglob
for skill_src in "$src_root"/*/; do
skill="$(basename "$skill_src")"
stack="$(skill_to_stack "$skill")"
branch="auto/skill-sync-$stack"
dest="skills/$skill"

echo "::group::Syncing skill '$skill' (stack: $stack)"

# Reuse the branch while its PR is open; else branch fresh from main.
reuse=false
if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
state="$(gh pr view "$branch" --json state --jq '.state' 2>/dev/null || echo '')"
if [ "$state" = "OPEN" ]; then
reuse=true
fi
fi

if [ "$reuse" = true ]; then
echo "Open PR found for '$branch' — continuing it."
git fetch --quiet origin "$branch"
git checkout -B "$branch" "origin/$branch"
else
echo "No open PR for '$branch' — branching fresh from main."
git checkout -B "$branch" origin/main
fi

# Mirror source, then regenerate the plugin copies.
rm -rf "${dest:?}"
mkdir -p "$dest"
cp -a "${skill_src}." "$dest/"
find "$dest" -name '.DS_Store' -delete
./scripts/sync-plugin.sh >/dev/null

git add -A
if git diff --cached --quiet; then
echo "No content changes for '$skill' — skipping."
echo "::endgroup::"
continue
fi

echo "Source: ${SOURCE_REPO}@${SHORT_SHA} · diff: ${DIFF_URL}"

git commit --quiet -m "chore(skills): update $skill skill"

if [ "$reuse" = true ]; then
git push --force-with-lease origin "$branch"
else
git push --force origin "$branch"
fi

synced_at="$(date -u '+%Y-%m-%d %H:%M UTC')"

if [ "$reuse" = true ]; then
gh pr comment "$branch" --body "🤖 Synced newer upstream changes to the \`$skill\` skill ($synced_at)."
echo "Updated existing PR for '$branch'."
else
body_file="$(mktemp)"
cat > "$body_file" <<EOF
## 🤖 Automated skill sync

Updates the \`$skill\` skill and its generated plugin copies from the upstream
source. Opened and updated automatically; while open, new changes are pushed here
instead of opening another PR. Don't hand-edit these files — edit the upstream
source and the change flows back here.
EOF
gh pr create \
--base main \
--head "$branch" \
--title "chore(skills): update $stack skill" \
--body-file "$body_file"
rm -f "$body_file"
echo "Opened new PR for '$branch'."
fi

synced_any=true
echo "::endgroup::"
done

if [ "$synced_any" = false ]; then
echo "::notice::No skill changes required a PR for ${SOURCE_REPO}@${SHORT_SHA}."
fi