diff --git a/.github/workflows/sync-agent-skills.yml b/.github/workflows/sync-agent-skills.yml index 3c96c5e..f9c7e19 100644 --- a/.github/workflows/sync-agent-skills.yml +++ b/.github/workflows/sync-agent-skills.yml @@ -15,7 +15,8 @@ name: Sync agent skills # * Settings > Actions > General > "Allow GitHub Actions to create and approve pull requests". # * Recommended: a GitHub App (owned by the org, not a person) installed on this repo with # Contents + Pull requests: write. Set its client ID as the repository VARIABLE -# SYNC_APP_CLIENT_ID and its private key as the repository SECRET SYNC_APP_PRIVATE_KEY. The job +# SYNC_APP_CLIENT_ID and its private key as the SECRET SYNC_APP_PRIVATE_KEY of the "skills-sync" +# environment (restricted to the main branch, so a feature-branch workflow cannot read it). The job # then mints a short-lived token per run, and the sync PR gets the normal "Build and Deploy" # checks. Without the app it falls back to GITHUB_TOKEN: PRs opened that way do not trigger # other workflows, so this job runs the spell check and the site build itself and opens the PR @@ -39,6 +40,8 @@ concurrency: jobs: sync: runs-on: ubuntu-latest + # The app's private key lives in this environment, which only the main branch may use. + environment: skills-sync env: SYNC_APP_CLIENT_ID: ${{ vars.SYNC_APP_CLIENT_ID }} SYNC_APP_PRIVATE_KEY: ${{ secrets.SYNC_APP_PRIVATE_KEY }} diff --git a/scripts/mirror-skills.mjs b/scripts/mirror-skills.mjs index eff2744..b31a617 100644 --- a/scripts/mirror-skills.mjs +++ b/scripts/mirror-skills.mjs @@ -191,8 +191,15 @@ function main() { changed++; } for (const p of orphans) { rmSync(p); changed++; } - if (!localSource && flag('--update')) writeFileSync(PIN_PATH, JSON.stringify({ ref }, null, 2) + '\n'); - console.log(`Mirrored ${files.size} skills from bugsee-for-ai@${ref.slice(0, 7)}; ${changed} file${changed === 1 ? '' : 's'} changed.`); + // Move the pin only when the generated output actually changed. The older pin stays valid (it + // produces identical files), and a bugsee-for-ai commit that touches no skill content (CI, + // README, ...) must not open a sync PR whose only change is the pin. + const pinned = JSON.parse(readFileSync(PIN_PATH, 'utf8')).ref; + const updating = !localSource && flag('--update'); + if (updating && changed > 0) writeFileSync(PIN_PATH, JSON.stringify({ ref }, null, 2) + '\n'); + const kept = updating && changed === 0 && ref !== pinned; + console.log(`Mirrored ${files.size} skills from bugsee-for-ai@${ref.slice(0, 7)}; ${changed} file${changed === 1 ? '' : 's'} changed.` + + (kept ? ` No skill content changed since @${pinned.slice(0, 7)}; pin kept.` : '')); } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();