From 24751f95d36bd0013baba6485f198986fb179970 Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Sun, 19 Jul 2026 09:41:52 -0400 Subject: [PATCH 1/7] Add APM manifest as alternate install path for the plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add apm.yml declaring actions-migrator v1.2.0 as an APM package (targets: copilot). Schema mirrors github/expert-services-agent-skill PR #3. - Add README 'Option C — Install via APM' section with a 'Why APM complements the plugin' comparison table explaining that plugin and APM install solve different problems and are additive (control plane vs lockfile provenance / content scan / SBOM). - Ignore APM-generated output (apm_modules/, build/, .agents/); the committed source of truth remains plugin/ and agents/. apm.lock.yaml is intentionally omitted from this PR — it should be generated by running 'apm install' in a follow-up commit once a maintainer has the APM CLI available. No behavior change to the plugin runtime. Additive only. --- .gitignore | 7 ++++++- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ apm.yml | 31 +++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 apm.yml diff --git a/.gitignore b/.gitignore index e016fd2..7d68343 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,9 @@ ENV/ .env.*.local # Scratch folder -scratch/ \ No newline at end of file +scratch/ +# APM: generated / compiled output — produced on demand by `apm install` / `apm pack`. +# The committed source of truth is the primitives under `plugin/` and `agents/` plus `apm.yml`. +apm_modules/ +build/ +.agents/ diff --git a/README.md b/README.md index c515947..05de1dc 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,50 @@ cd actions-migrations-via-copilot copilot plugin install ./plugin ``` +### Option C — Install via APM (pinned, hashed, auditable) *(experimental)* + +Use this when you need reproducible installs with lockfile-pinned versions, content-integrity scanning, SBOM export, or air-gapped operation — typical for regulated industries. + +Install the [APM CLI](https://microsoft.github.io/apm/) once: + +```bash +curl -sSL https://aka.ms/apm-unix | sh # macOS / Linux +# irm https://aka.ms/apm-windows | iex # Windows +``` + +Add the dependency to your project's `apm.yml`: + +```yaml +dependencies: + apm: + - github/actions-migrations-via-copilot#v1.2.0 +``` + +Then run: + +```bash +apm install +``` + +APM deploys the agents and skills into your repo, pins the resolved sources and content hashes in `apm.lock.yaml`, and content-scans every primitive before it reaches disk. + +#### Why APM complements the plugin + +Plugin install and APM install solve different problems and are additive — most enterprises will use both. + +| Concern | Plugin (Options A/B) | APM (Option C) | +| --- | --- | --- | +| Control plane | GitHub Copilot admin UI + `.github-private` settings | `apm-policy.yml` in a policy repo (tighten-only inheritance) | +| Version resolution | Whatever `main` (or the marketplace pointer) resolves to at runtime | Semver tag pinned in `apm.yml`, hash pinned in `apm.lock.yaml` | +| Content integrity | — | Hidden-Unicode / prompt-injection scan on every install | +| Tampering detection | — | `apm audit --ci` diffs on-disk primitives against lockfile hashes | +| Audit trail | Enterprise audit log | Lockfile diff on every PR; `apm lock export --format cyclonedx\|spdx` (SBOM) | +| Air-gapped / restricted network | Depends on marketplace reachability | Files land in the consumer repo — no runtime fetch | +| Non-Copilot harnesses (Claude, Cursor, Codex, ...) | — | Same manifest deploys to every supported harness | +| Zero-touch enablement | Admin flips a toggle in the enterprise console | Consumer commits `apm.yml` and runs `apm install` (or CI does) | + +**Recommended pattern:** use plugin install as the primary, frictionless path (GitHub-native, admin-toggleable); layer APM on top for regulated or air-gapped repos that need lockfile provenance, content-integrity scanning, or SBOMs. Both paths ship the same agents and skills — only the delivery and governance surface differs. + --- ## How Migration Works diff --git a/apm.yml b/apm.yml new file mode 100644 index 0000000..4edd4cf --- /dev/null +++ b/apm.yml @@ -0,0 +1,31 @@ +name: actions-migrator +version: 1.2.0 +description: Enterprise-grade GitHub Copilot agents that migrate CI/CD pipelines + from Jenkins, Azure DevOps, CircleCI, GitLab, Travis CI, Bamboo, Bitbucket + Pipelines, and Drone CI to GitHub Actions. +author: GitHub Professional Services +repository: https://github.com/github/actions-migrations-via-copilot +homepage: https://github.com/github/actions-migrations-via-copilot +license: MIT +keywords: +- github-actions +- ci-cd +- migration +- jenkins +- azure-devops +- circleci +- gitlab +- travis-ci +- bamboo +- bitbucket +- drone-ci +- reusable-workflows +targets: +- copilot +includes: auto +dependencies: + apm: [] + mcp: [] +devDependencies: + apm: [] +scripts: {} From 15e077ada4cbbd844e1eaa5e497c9253a2c5b69e Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Sun, 19 Jul 2026 09:49:17 -0400 Subject: [PATCH 2/7] Trim README: drop 'Why APM complements the plugin' table Rationale lives in the PR description; README stays focused on install instructions. --- README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/README.md b/README.md index 05de1dc..aea229b 100644 --- a/README.md +++ b/README.md @@ -124,23 +124,6 @@ apm install APM deploys the agents and skills into your repo, pins the resolved sources and content hashes in `apm.lock.yaml`, and content-scans every primitive before it reaches disk. -#### Why APM complements the plugin - -Plugin install and APM install solve different problems and are additive — most enterprises will use both. - -| Concern | Plugin (Options A/B) | APM (Option C) | -| --- | --- | --- | -| Control plane | GitHub Copilot admin UI + `.github-private` settings | `apm-policy.yml` in a policy repo (tighten-only inheritance) | -| Version resolution | Whatever `main` (or the marketplace pointer) resolves to at runtime | Semver tag pinned in `apm.yml`, hash pinned in `apm.lock.yaml` | -| Content integrity | — | Hidden-Unicode / prompt-injection scan on every install | -| Tampering detection | — | `apm audit --ci` diffs on-disk primitives against lockfile hashes | -| Audit trail | Enterprise audit log | Lockfile diff on every PR; `apm lock export --format cyclonedx\|spdx` (SBOM) | -| Air-gapped / restricted network | Depends on marketplace reachability | Files land in the consumer repo — no runtime fetch | -| Non-Copilot harnesses (Claude, Cursor, Codex, ...) | — | Same manifest deploys to every supported harness | -| Zero-touch enablement | Admin flips a toggle in the enterprise console | Consumer commits `apm.yml` and runs `apm install` (or CI does) | - -**Recommended pattern:** use plugin install as the primary, frictionless path (GitHub-native, admin-toggleable); layer APM on top for regulated or air-gapped repos that need lockfile provenance, content-integrity scanning, or SBOMs. Both paths ship the same agents and skills — only the delivery and governance surface differs. - --- ## How Migration Works From a90b9ebb88d4655172516eb44b5daad077bb0e2c Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Mon, 20 Jul 2026 16:19:07 -0400 Subject: [PATCH 3/7] apm.yml: declare explicit includes for plugin/ layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit APM's default includes: auto looks for primitives at .apm/ or root conventional paths (agents/, skills/). Our primitives live under plugin/agents/ and plugin/skills/ — an explicit includes list points APM there without moving files or introducing duplication. Per microsoft/apm docs (apm pack reference), an explicit includes list is exhaustive and overrides implicit discovery. Confirmed with @danielmeppiel that plugin-native paths are supported. --- apm.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apm.yml b/apm.yml index 4edd4cf..df7bb73 100644 --- a/apm.yml +++ b/apm.yml @@ -22,7 +22,9 @@ keywords: - reusable-workflows targets: - copilot -includes: auto +includes: +- plugin/agents +- plugin/skills dependencies: apm: [] mcp: [] From 53f93e03b72ef7769a5a50d25dc03acfbebc608c Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Mon, 20 Jul 2026 21:16:12 -0400 Subject: [PATCH 4/7] Revert "apm.yml: declare explicit includes for plugin/ layout" This reverts commit a90b9ebb88d4655172516eb44b5daad077bb0e2c. --- apm.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apm.yml b/apm.yml index df7bb73..4edd4cf 100644 --- a/apm.yml +++ b/apm.yml @@ -22,9 +22,7 @@ keywords: - reusable-workflows targets: - copilot -includes: -- plugin/agents -- plugin/skills +includes: auto dependencies: apm: [] mcp: [] From 33d7d809dd423f3deaa54f23e11decb2657230c2 Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Mon, 20 Jul 2026 21:16:26 -0400 Subject: [PATCH 5/7] README: install via /plugin virtual path (Plugin collection layout) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit APM's install-time validator requires the target path to be a valid package shape (.apm/, top-level skills/, curated aggregator, plugin.json, or SKILL.md). Our root has apm.yml only, so pointing consumers at the whole repo fails validation. Pointing at /plugin (the subdirectory containing plugin.json) resolves as APM's 'Plugin collection' package type — APM dissects plugin.json and maps the declared agents/ and skills/ directories into the consumer's runtime paths. No producer refactor needed. Verified end-to-end: apm install materialized 9 agents + 11 skills, wrote apm.lock.yaml with resolved commit + per-file hashes. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aea229b..dfcab2e 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,11 @@ Add the dependency to your project's `apm.yml`: ```yaml dependencies: apm: - - github/actions-migrations-via-copilot#v1.2.0 + - github/actions-migrations-via-copilot/plugin#v1.2.0 ``` +The `/plugin` suffix is a **virtual path** — it points APM at the [`plugin/`](plugin/) subdirectory where `plugin.json` lives, so APM treats this repo as a *Plugin collection* package type ([APM docs](https://microsoft.github.io/apm/reference/package-types/#plugin-collection-pluginjson)) and dissects `plugin.json` to install the agents and skills. + Then run: ```bash From d6a95bb8a0bb76b6cc1458c3f5300970f83b0199 Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Fri, 24 Jul 2026 11:32:53 -0400 Subject: [PATCH 6/7] README: split Windows install into a PowerShell code block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on README.md:108. Previously the Windows install line was inside the Bash block prefixed with '#', so it rendered as a shell comment — copy-paste would run nothing on Windows. Now it lives in a separate PowerShell code block that Windows users can copy directly. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfcab2e..ef66401 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,13 @@ Use this when you need reproducible installs with lockfile-pinned versions, cont Install the [APM CLI](https://microsoft.github.io/apm/) once: ```bash -curl -sSL https://aka.ms/apm-unix | sh # macOS / Linux -# irm https://aka.ms/apm-windows | iex # Windows +# macOS / Linux +curl -sSL https://aka.ms/apm-unix | sh +``` + +```powershell +# Windows +irm https://aka.ms/apm-windows | iex ``` Add the dependency to your project's `apm.yml`: From b75b28125255a987161d3eb9151f27b9a974064a Mon Sep 17 00:00:00 2001 From: Alex Demichieli Date: Fri, 24 Jul 2026 11:40:42 -0400 Subject: [PATCH 7/7] README: clarify APM's air-gap benefit (offline runtime, not install) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on README.md:102. The prior 'air-gapped operation' phrasing implied install itself could work offline, but 'apm install' requires network to fetch the CLI and the pinned package. APM's actual benefit is offline *runtime* — once installed, all primitives are on disk and the agent never fetches from a marketplace or plugin cache at execution time. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef66401..295531e 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ copilot plugin install ./plugin ### Option C — Install via APM (pinned, hashed, auditable) *(experimental)* -Use this when you need reproducible installs with lockfile-pinned versions, content-integrity scanning, SBOM export, or air-gapped operation — typical for regulated industries. +Use this when you need reproducible installs with lockfile-pinned versions, content-integrity scanning, SBOM export, or **offline agent runtime** — typical for regulated industries. Install itself still requires network access to fetch the CLI and the pinned package, but once `apm install` resolves the dependency, all primitives land on disk in your repo — so the agent's runtime never fetches from a marketplace or plugin cache. Install the [APM CLI](https://microsoft.github.io/apm/) once: