Skip to content

feat(apm): add reusable APM sync workflow + onboarding - #194

Merged
DevSecNinja merged 6 commits into
mainfrom
feat/apm-sync-reusable
Jun 21, 2026
Merged

feat(apm): add reusable APM sync workflow + onboarding#194
DevSecNinja merged 6 commits into
mainfrom
feat/apm-sync-reusable

Conversation

@DevSecNinja

Copy link
Copy Markdown
Owner

What

Adds a central apm-sync reusable workflow so any DevSecNinja repo can keep its installed APM primitives (prompts, instructions, agents, hooks) up to date with their upstream packages — the agentic-primitive counterpart to config-sync (which only distributes static files).

  • .github/workflows/apm-sync.yml — reusable (workflow_call).
  • docs/apm-sync-onboarding.md — adoption guide (mirrors release-please-onboarding.md), linked from README.

How it works

On each run (scheduled or workflow_dispatch in the caller):

  1. Installs a pinnedapm CLI (curl -sSL https://aka.ms/apm-unix | sh, VERSION pinned).
  2. Runs apm update --yes — re-resolves every dep in apm.yml to its latest matching ref, rewrites apm.lock.yaml, and redeploys compiled primitives into the harness dirs (.github/, .claude/, …).
  3. Opens/force-updates a chore: sync APM primitives PR via the existing actions/open-pr composite. Branch protection respected — nothing auto-merges.
  4. No-op when the caller has no apm.yml.

Conventions followed

  • apm-version is required: true — caller owns the version (ADR 0001). # renovate: comments on both the reusable ref and the apm version.
  • Optional app-id + app-private-key open the PR via the GitHub App so required CI fires on it — same pattern/secrets as the release-please reusable (RELEASE_PLEASE_APP_ID / RELEASE_PLEASE_APP_PRIVATE_KEY); falls back to GITHUB_TOKEN.
  • harden-runner (audit egress), pinned action SHAs, least-privilege permissions.

The bigger picture

This pairs with ai-toolkit#21, which ships the org conventions as an APM instruction primitive. Once both merge, a repo runs apm install DevSecNinja/ai-toolkit --target copilot once, and this workflow keeps it current automatically — replacing the closed config-sync approach (#193) for agentic content while config-sync keeps owning static config.

Validation note

I authored this via API and can't run actionlint/CI from here. The workflow follows the repo's existing reusable conventions; please let the lint workflow run on the PR. One thing worth a reviewer's eye: the exact apm update redeploy semantics in a headless CI checkout (e.g. whether a repo with zero harness dirs needs targets: in apm.yml) — the onboarding doc tells consumers to pin --target at install time, which covers the common case.

Add a reusable workflow that refreshes installed APM primitives in the
calling repo (`apm update`) and opens a PR with the result via the central
open-pr composite — the agentic-primitive counterpart to config-sync.
- apm-version is a required input (caller owns the version, per ADR 0001).
- Optional app-id/app-private-key open the PR via a GitHub App so required
CI fires, mirroring the release-please reusable; falls back to
GITHUB_TOKEN otherwise.
- No-op when the calling repo has no apm.yml.
Document how repos adopt the apm-sync reusable workflow: prerequisites
(one-time apm install, App auth), a caller template, inputs, run behavior,
and troubleshooting. Mirrors the release-please onboarding structure.
Comment thread.github/workflows/apm-sync.yml Fixed
zizmor flags the checkout step for not setting persist-credentials: false,
but the open-pr step force-pushes the sync branch using the token checkout
writes into the remote URL. Disabling it would break PR creation without
adding isolation (the job runs no untrusted code). Mirror config-sync.yml's
`# zizmor: ignore[artipacked]` suppression with a justification.
@DevSecNinja

Copy link
Copy Markdown
OwnerAuthor

Addressed the zizmor artipacked finding in 287e04c.

This is a justified suppression rather than a real vulnerability: the Open sync PR step runs actions/open-pr, which does git push --force-with-lease using the bearer token actions/checkout writes into the remote URL. Setting persist-credentials: false would break the PR push without adding isolation — this job runs no untrusted code (it only installs the pinned apm CLI and runs apm update over the repo's own committed apm.yml/apm.lock.yaml).

Mirrored the exact pattern already used in config-sync.yml: a # zizmor: ignore[artipacked] comment on the checkout step with an inline justification. This is consistent with how the other PR-opening reusable in this repo handles the same scanner finding.

Apply yamlfmt (body: |- block scalar) and dprint (markdown table column
alignment) auto-formatting to the apm-sync workflow and onboarding doc so
the central lint reusable passes.
Dogfooding `apm install` into this repo committed apm.yml, apm.lock.yaml
and APM-deployed prompt/instruction markdown under the harness dirs. Those
are tool-generated/vendored content — yamllint/yamlfmt reject APM's 2-space
sequence indentation, and dprint rejects the verbatim prompt bodies
(`•` bullets, `3)` lists). Reformatting them is pointless (clobbered on the
next `apm install`) and for prompt bodies actively harmful.
Exclude APM-managed paths from the central synced linter configs, the same
way CHANGELOG.md is already excluded from dprint:
- yamllint `ignore` + yamlfmt `exclude`: apm.yml, apm.lock.yaml, apm_modules/
- dprint `excludes`: apm_modules/ and the APM harness output dirs
(.github/prompts, .github/instructions, .github/chatmodes, .claude,
.cursor, .opencode, .gemini, .windsurf, .kiro, .agents)
This unblocks any repo that adopts APM, not just this one.
@DevSecNinja

Copy link
Copy Markdown
OwnerAuthor

Lint is green now. Root cause was bigger than my new files:

What actually failed: this repo already dogfoods apm install DevSecNinja/ai-toolkit (committed to main), and the APM-deployed content violates the org linters:

  • apm.yml / apm.lock.yaml → yamllint + yamlfmt (APM emits 2-space block-sequence indentation; the config expects 4).
  • .github/prompts/*.prompt.md (deployed from ai-toolkit) → dprint (verbatim prompt bodies use bullets and 3) lists).

So this was a pre-existing main failure that every PR's CI trips on, not something this branch introduced.

Fix (folded into this PR): exclude APM-managed/generated paths from the central synced linter configs — the same treatment CHANGELOG.md already gets in dprint. Reformatting these is pointless (clobbered on the next apm install) and for prompt bodies actively harmful.

  • config-sync/files/.yamllint.yamlignore + .yamlfmt.yamlexclude: apm.yml, apm.lock.yaml, apm_modules/
  • config-sync/files/dprint.jsonexcludes: apm_modules/ + the APM harness output dirs (.github/prompts, .github/instructions, .github/chatmodes, .claude, .cursor, .opencode, .gemini, .windsurf, .kiro, .agents)

Since these are the central config-sync sources, the exclusions propagate to every repo that adopts APM — so this is the general fix, not a one-off. I verified all three linters pass CI-style (find … | xargs yamllint/yamlfmt, dprint check) before pushing.

Tradeoff to flag: excluding .github/prompts/** from dprint also means hand-authored prompts in that dir are no longer markdown-formatted (APM-deployed and hand-authored prompts share the directory). You accepted this; noting it for the record.

Also fixed in this PR: the zizmor artipacked finding (justified suppression mirroring config-sync.yml) and the formatting of my own two files.

@DevSecNinja
DevSecNinja merged commit 2fe813b into mainJun 21, 2026
16 checks passed
@DevSecNinja
DevSecNinja deleted the feat/apm-sync-reusable branch June 21, 2026 14:18
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@DevSecNinja@github-advanced-security