diff --git a/.github/workflows/update-agent-skills.yaml b/.github/workflows/update-agent-skills.yaml index c228e55..feb1393 100644 --- a/.github/workflows/update-agent-skills.yaml +++ b/.github/workflows/update-agent-skills.yaml @@ -10,12 +10,18 @@ on: options: - all - agentic-engineering + - engineering-practices schedule: - cron: "0 6 * * *" -permissions: - contents: write - pull-requests: write +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: @@ -40,6 +46,9 @@ jobs: update: needs: scope + permissions: + 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 7194e09..b5a4290 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,9 +143,13 @@ 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. +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/docs/scripts.md b/docs/scripts.md index 3cbcf0c..6a81c13 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 bafc844..885a47a 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 8980397..fa2d4ee 100644 --- a/scripts/skill-update-scope.test.sh +++ b/scripts/skill-update-scope.test.sh @@ -12,15 +12,38 @@ 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") +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 + .jobs."bump-versions".permissions == {contents:"read"})' > /dev/null +printf 'skill update scopes: PASS (declared selections, canonical branch and fail-closed dispatch)\n'