From 1743be46c0fa96e5e2eaf05472bc15bf72987671 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Fri, 23 Oct 2026 04:15:19 +0200 Subject: [PATCH 1/2] ci: scope engineering-practice skill updates --- .github/workflows/update-agent-skills.yaml | 8 ++++--- AGENTS.md | 10 +++++---- docs/scripts.md | 1 + scripts/skill-update-scope.sh | 1 + scripts/skill-update-scope.test.sh | 25 ++++++++++++++++++---- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/.github/workflows/update-agent-skills.yaml b/.github/workflows/update-agent-skills.yaml index c228e552..5c6491b2 100644 --- a/.github/workflows/update-agent-skills.yaml +++ b/.github/workflows/update-agent-skills.yaml @@ -10,12 +10,11 @@ on: options: - all - agentic-engineering + - engineering-practices schedule: - cron: "0 6 * * *" -permissions: - contents: write - pull-requests: write +permissions: {} jobs: scope: @@ -40,6 +39,9 @@ jobs: update: needs: scope + permissions: + contents: write + pull-requests: write uses: devantler-tech/.github/.github/workflows/update-agent-skills.yaml@e271b7ff25d4ce97b067612e7ba0c5d657b6a17a # v6.1.0 with: dir: ${{ needs.scope.outputs.dir }} diff --git a/AGENTS.md b/AGENTS.md index 7194e096..a97b09d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -128,8 +128,8 @@ which records the true upstream in the skill's `metadata.github-*` frontmatter ( `anthropics/skills`, our own sibling [`devantler-tech/agent-skills`](https://github.com/devantler-tech/agent-skills), …), each tracked independently. The daily [`update-agent-skills.yaml`](.github/workflows/update-agent-skills.yaml) workflow runs -[`gh skill update --all`](https://github.com/devantler-tech/actions/tree/main/update-agent-skills) via -the [`update-agent-skills`](https://github.com/devantler-tech/actions/blob/main/.github/workflows/update-agent-skills.yaml) +[`gh skill update --all`](https://github.com/devantler-tech/.github/tree/main/actions/update-agent-skills) via +the [`update-agent-skills`](https://github.com/devantler-tech/.github/blob/main/.github/workflows/update-agent-skills.yaml) reusable workflow and opens one PR per drifted skill, from `deps/agent-skills-update-`, so a blocked update holds back only its own skill — **no lockfile, no sync bot, no custom metadata.** Never hand-edit anything inside a bundled skill — not the `SKILL.md`, and not the `references/`, `scripts/` and `assets/` files beside it, which are equally the upstream's and equally @@ -143,8 +143,10 @@ distributed notice. A changed notice or incomplete read refuses validation; refr a reviewed curation change rather than replacing the notice silently. Run `bash scripts/check-matt-skill-license.test.sh` for its offline source and notice regressions. For a portfolio-owned repair, dispatch `update-agent-skills.yaml` with -`scope=agentic-engineering` to update only that plugin's skills through the same programmed -PRs. The default `all` scope and scheduled updates cover the full catalogue. Unsupported +`scope=agentic-engineering` or `scope=engineering-practices` to update only the selected plugin's +skills through the same programmed, per-skill PRs. Each scope retains the canonical branch +identity and existing one-skill edit boundary. The default `all` scope and scheduled updates +cover the full catalogue. Unsupported scope observations refuse before the updater starts. `guard-bundled-skill-edits.sh` covers the rest of the tree: a PR that changes any file inside a synced skill fails and names the upstream to fix it in, so the edit is refused at review instead of diff --git a/docs/scripts.md b/docs/scripts.md index 3cbcf0cd..6a81c132 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -8,6 +8,7 @@ complete inventory; read a script's header for its arguments and safeguards. | Check a change | Follow the [validation steps](validation.md), including dependency installation | | Update versions and release notes | Use [bump-plugin-version.sh](../scripts/bump-plugin-version.sh) and add a matching changelog entry; skill syncs use [plugin-changelog.sh write](../scripts/plugin-changelog.sh) | | Refresh generated resource digests | [refresh-desired-state-digests.sh](../scripts/refresh-desired-state-digests.sh) | +| Synchronize one curated plugin | Dispatch [Update Agent Skills](../.github/workflows/update-agent-skills.yaml) with `agentic-engineering` or `engineering-practices`; [skill-update-scope.sh](../scripts/skill-update-scope.sh) resolves only declared selections before the normal per-skill updater runs. Default and scheduled `all` updates still cover the full catalogue. | | Prepare or inspect a marketplace release | [Release guide](marketplace-releases.md) and [prepare-marketplace-release.sh](../scripts/prepare-marketplace-release.sh) | | Propose or publish a marketplace release | [Release guide](marketplace-releases.md), which explains the separate opt-in steps and recovery | | Recheck open pull requests after changing CI | [recheck-open-prs.sh](../scripts/recheck-open-prs.sh) | diff --git a/scripts/skill-update-scope.sh b/scripts/skill-update-scope.sh index bafc8440..885a47ae 100644 --- a/scripts/skill-update-scope.sh +++ b/scripts/skill-update-scope.sh @@ -6,5 +6,6 @@ refuse() { printf 'skill update scope: unsupported dispatch\n' >&2; exit 2; } case "$1:${2:-all}" in schedule:all|workflow_dispatch:all) printf 'dir=plugins\nbranch=deps/agent-skills-update\n' ;; workflow_dispatch:agentic-engineering) printf 'dir=plugins/agentic-engineering/skills\nbranch=deps/agent-skills-update-agentic-engineering-skills\n' ;; + workflow_dispatch:engineering-practices) printf 'dir=plugins/engineering-practices/skills\nbranch=deps/agent-skills-update-engineering-practices-skills\n' ;; *) refuse ;; esac diff --git a/scripts/skill-update-scope.test.sh b/scripts/skill-update-scope.test.sh index 89803972..901d5e1e 100644 --- a/scripts/skill-update-scope.test.sh +++ b/scripts/skill-update-scope.test.sh @@ -12,15 +12,32 @@ bash "$here/skill-update-scope.sh" workflow_dispatch all > "$work/out" [[ $(cat "$work/out") == $'dir=plugins\nbranch=deps/agent-skills-update' ]] bash "$here/skill-update-scope.sh" workflow_dispatch agentic-engineering > "$work/out" [[ $(cat "$work/out") == $'dir=plugins/agentic-engineering/skills\nbranch=deps/agent-skills-update-agentic-engineering-skills' ]] -for scope in unknown ../outside $'agentic-engineering\ndir=plugins'; do +bash "$here/skill-update-scope.sh" workflow_dispatch engineering-practices > "$work/out" +[[ $(cat "$work/out") == $'dir=plugins/engineering-practices/skills\nbranch=deps/agent-skills-update-engineering-practices-skills' ]] +# A scoped producer must still create the canonical per-skill branch accepted +# by the existing actor-and-one-skill guard, not a broader exemption. +branch=$(sed -n 's/^branch=//p' "$work/out") +[[ "$branch-ways-of-working" == deps/agent-skills-update-engineering-practices-skills-ways-of-working ]] +for scope in unknown ../outside $'agentic-engineering\ndir=plugins' $'engineering-practices\nbranch=other'; do rc=0 bash "$here/skill-update-scope.sh" workflow_dispatch "$scope" > "$work/out" 2> "$work/err" || rc=$? [[ $rc == 2 && ! -s $work/out && -s $work/err ]] done rc=0 -bash "$here/skill-update-scope.sh" schedule agentic-engineering > "$work/out" 2> "$work/err" || rc=$? -[[ $rc == 2 && ! -s $work/out ]] +for scope in agentic-engineering engineering-practices; do + rc=0 + bash "$here/skill-update-scope.sh" schedule "$scope" > "$work/out" 2> "$work/err" || rc=$? + [[ $rc == 2 && ! -s $work/out ]] +done rc=0 bash "$here/skill-update-scope.sh" pull_request all > "$work/out" 2> "$work/err" || rc=$? [[ $rc == 2 && ! -s $work/out ]] -printf 'skill update scopes: PASS (9 cases)\n' +options=$(yq -o=json '.on.workflow_dispatch.inputs.scope' "$here/../.github/workflows/update-agent-skills.yaml") +printf '%s\n' "$options" | jq -se 'length == 1 and (.[0] | .type == "choice" and .default == "all" and + (.options | sort) == ["agentic-engineering","all","engineering-practices"])' > /dev/null +workflow=$(yq -o=json '.' "$here/../.github/workflows/update-agent-skills.yaml") +printf '%s\n' "$workflow" | jq -se 'length == 1 and (.[0] | + .permissions == {} and .jobs.scope.permissions == {contents:"read"} and + .jobs.update.permissions == {contents:"write","pull-requests":"write"} and + .jobs."bump-versions".permissions == {contents:"read"})' > /dev/null +printf 'skill update scopes: PASS (declared selections, canonical branch and fail-closed dispatch)\n' From df8f35aab4342fec1ac9d9636a797fabe1d645d2 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Fri, 23 Oct 2026 09:15:17 +0200 Subject: [PATCH 2/2] ci: serialize scoped skill updates through follow-ups --- .github/workflows/update-agent-skills.yaml | 11 +++++++++-- AGENTS.md | 2 ++ scripts/skill-update-scope.test.sh | 6 ++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-agent-skills.yaml b/.github/workflows/update-agent-skills.yaml index 5c6491b2..feb1393a 100644 --- a/.github/workflows/update-agent-skills.yaml +++ b/.github/workflows/update-agent-skills.yaml @@ -16,6 +16,13 @@ on: permissions: {} +# All scopes can update the same per-skill branches. Keep their digest/version +# follow-ups inside the same non-cancelling queue as the updater itself. +concurrency: + group: agent-plugins-skill-sync-${{ github.repository }} + cancel-in-progress: false + queue: max + jobs: scope: name: Resolve the requested skill scope @@ -40,8 +47,8 @@ jobs: update: needs: scope permissions: - contents: write - pull-requests: write + contents: write # The shared updater writes the signed skill-sync commits. + pull-requests: write # The shared updater opens per-skill update proposals. uses: devantler-tech/.github/.github/workflows/update-agent-skills.yaml@e271b7ff25d4ce97b067612e7ba0c5d657b6a17a # v6.1.0 with: dir: ${{ needs.scope.outputs.dir }} diff --git a/AGENTS.md b/AGENTS.md index a97b09d1..b5a4290b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -148,6 +148,8 @@ skills through the same programmed, per-skill PRs. Each scope retains the canoni identity and existing one-skill edit boundary. The default `all` scope and scheduled updates cover the full catalogue. Unsupported scope observations refuse before the updater starts. +All scopes share one non-cancelling workflow queue, including their digest, version and release-note +follow-ups, so overlapping selections cannot rewrite the same update branch concurrently. `guard-bundled-skill-edits.sh` covers the rest of the tree: a PR that changes any file inside a synced skill fails and names the upstream to fix it in, so the edit is refused at review instead of being silently reverted by the next sync. The programmed sync PR is exempt for its own skill, a wholly new skill diff --git a/scripts/skill-update-scope.test.sh b/scripts/skill-update-scope.test.sh index 901d5e1e..fa2d4ee1 100644 --- a/scripts/skill-update-scope.test.sh +++ b/scripts/skill-update-scope.test.sh @@ -36,6 +36,12 @@ options=$(yq -o=json '.on.workflow_dispatch.inputs.scope' "$here/../.github/work printf '%s\n' "$options" | jq -se 'length == 1 and (.[0] | .type == "choice" and .default == "all" and (.options | sort) == ["agentic-engineering","all","engineering-practices"])' > /dev/null workflow=$(yq -o=json '.' "$here/../.github/workflows/update-agent-skills.yaml") +if ! printf '%s\n' "$workflow" | jq -se 'length == 1 and (.[0].concurrency == { + group:"agent-plugins-skill-sync-${{ github.repository }}", + "cancel-in-progress":false, queue:"max"})' > /dev/null; then + printf 'skill update scopes: require one non-cancelling queue across all scopes and follow-up jobs\n' >&2 + exit 1 +fi printf '%s\n' "$workflow" | jq -se 'length == 1 and (.[0] | .permissions == {} and .jobs.scope.permissions == {contents:"read"} and .jobs.update.permissions == {contents:"write","pull-requests":"write"} and