From 5d531eecfb0b53bc56430714a2ed8e0b9d0d3ff5 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 16:18:52 +0200 Subject: [PATCH 1/6] fix(marketplace): address PR #1038 review comments + docs refresh Twelve findings from the copilot-pull-request-reviewer pass on PR #1038. Code fixes (in src/): - Remove unused DEPRECATION_MESSAGE import in commands/marketplace.py - Remove unused LOCAL_SOURCE_RE import in marketplace/yml_editor.py - _has_marketplace_block() now raises MarketplaceYmlError on YAML parse errors and OS read errors instead of swallowing them as 'no config' -- fixes a misleading message on malformed apm.yml. - migrate_marketplace_yml() validates that apm.yml round-trips to a mapping; empty apm.yml now treated as an empty mapping (CommentedMap) so the marketplace block can still be inserted. - _is_apm_yml_with_marketplace() now requires the marketplace value itself to be a mapping; previously a non-dict value would crash _get_marketplace_container() callers on .get() access. - 'apm marketplace init' applies the same empty-vs-non-mapping guard on apm.yml round-trip; non-mapping top level is a hard error, empty file is treated as an empty mapping. - 'apm init --marketplace' no longer derives marketplace owner from the project name (which produced misleading github.com/ URLs); the template's acme-org placeholder is used instead. - _check_gitignore_for_marketplace_json warning text refreshed: 'Both apm.yml and the generated marketplace.json must be tracked'. - Renamed test_source_dot_traversal to test_local_source_accepted (the behavior changed at fold time). - init_template.py module docstring now describes both renderers. - test_apm_yml_marketplace_loader.py docstring corrected: strict-key enforcement is inside the marketplace block only. Regression tests (tests/unit/marketplace/test_review_fixes.py, +12): - malformed apm.yml surfaces a clear MarketplaceYmlError - migrate rejects list/scalar top level, accepts empty file - _is_apm_yml_with_marketplace rejects non-mapping marketplace values - 'apm marketplace init' rejects non-mapping apm.yml, accepts empty Docs (delivered by doc-writer agent): - Full rewrite of docs/src/content/docs/guides/marketplace-authoring.md around the apm.yml block; cites microsoft/azure-skills as the byte-for-byte build proof. Adds local-path packages section and a migration section. - One-line fix in guides/marketplaces.md (marketplace.yml -> apm.yml). - reference/cli-commands.md: rewrote init/build/outdated/check/doctor blurbs, added 'apm marketplace migrate' reference, added '--marketplace' flag to 'apm init' options/examples. - reference/manifest-schema.md: added optional 'marketplace:' to the top-level shape with a pointer to the authoring guide. - packages/apm-guide/.apm/skills/apm-usage/commands.md and package-authoring.md: refreshed authoring tables and shape; called out experimental gate and deprecation. - CHANGELOG.md: Added/Changed/Deprecated entries under [Unreleased] citing #1038. Validation: - 6757 unit tests pass (6745 prior + 12 new regression). - Real-world build proof: cloned microsoft/azure-skills, appended a marketplace: block to its apm.yml derived from the hand-authored marketplace.json, ran 'apm marketplace build', and diffed -- byte- for-byte identical (sha256 02f76bfc...). Closes review of #1038. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 9 + .../docs/guides/marketplace-authoring.md | 282 +++++++++++------- docs/src/content/docs/guides/marketplaces.md | 2 +- .../content/docs/reference/cli-commands.md | 71 +++-- .../content/docs/reference/manifest-schema.md | 3 + .../.apm/skills/apm-usage/commands.md | 21 +- .../skills/apm-usage/package-authoring.md | 95 ++++-- src/apm_cli/commands/init.py | 6 +- src/apm_cli/commands/marketplace.py | 23 +- src/apm_cli/marketplace/init_template.py | 10 +- src/apm_cli/marketplace/migration.py | 41 ++- src/apm_cli/marketplace/yml_editor.py | 10 +- .../test_apm_yml_marketplace_loader.py | 2 +- tests/unit/marketplace/test_review_fixes.py | 140 +++++++++ tests/unit/marketplace/test_yml_schema.py | 2 +- 15 files changed, 535 insertions(+), 182 deletions(-) create mode 100644 tests/unit/marketplace/test_review_fixes.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b047cfc96..be76a8bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **[Experimental] Marketplace authoring folded into `apm.yml`.** A new top-level `marketplace:` block carries `owner`, `build`, `packages` and optional `name`/`description`/`version` overrides (inherited from apm.yml top level when omitted). `apm marketplace init` now adds the block to `apm.yml` (scaffolding `apm.yml` if absent) instead of writing a standalone `marketplace.yml`. `apm marketplace build`, `check`, `outdated`, `doctor`, `publish`, and `package add|set|remove` all read the block. Validation evidence: `microsoft/azure-skills` -- a real-world repo with a hand-authored `.claude-plugin/marketplace.json` -- builds byte-for-byte identical output from its `apm.yml`. (#1038) +- **[Experimental] `apm marketplace migrate`** -- one-shot consolidation of a legacy `marketplace.yml` into `apm.yml`'s `marketplace:` block. Accepts `--force`/`--yes`/`-y` as aliases and `--dry-run` for preview. Inheritable fields are dropped from the block when they match the apm.yml top level and emitted as overrides when they differ. (#1038) +- **[Experimental] `apm init --marketplace`** -- seeds a fresh `apm.yml` with a `marketplace:` authoring block at project-creation time. Equivalent shortcut to `apm init` followed by `apm marketplace init`. (#1038) +- **Local-path package sources for marketplace authoring.** `source: ./path/to/dir` is now first-class in the `marketplace.packages` list; previously rejected by the source regex. Local entries skip git resolution and are emitted to `marketplace.json` as plain string sources (matches Anthropic's local-source convention). (#1038) - Codex CLI MCP config is now project-local (`.codex/config.toml`) during project installs and gated to active project targets; Codex user-scope primitive deployment is also supported. (#803) - **Dev Container Feature** `ghcr.io/microsoft/apm/apm-cli` -- one-line install of the APM CLI into any `devcontainer.json`, GitHub Codespace, or JetBrains Gateway workspace. Supports a `version` option (`latest` or pinned semver), declares `installsAfter` for the official Python feature, handles PEP 668 on Ubuntu 24.04+. Ships with 37 bats unit tests and a 6-distro Docker integration matrix (Ubuntu 24.04, Ubuntu 22.04, Debian 12, Alpine 3.20, Fedora 41, plus Python-feature combo). (#861) - `shared/apm.md` gh-aw workflow gains an `apps:` array input for cross-org private packages: each entry mints its own GitHub App installation token via `actions/create-github-app-token` and packs only its declared packages, with a matrix fan-out one replica per credential group. The single-app top-level form (`app-id`, `private-key`, `owner`, `repositories`) shipped earlier in this cycle is preserved as the canonical shorthand for one-org users; `apps[]` is purely additive. Multi-bundle restore uses the `bundles-file:` input from `microsoft/apm-action@v1.5.0` (microsoft/apm-action#30, microsoft/apm-action#29). @@ -16,10 +20,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **[Experimental] Marketplace authoring surface consolidated to `apm.yml`.** The publisher-side `marketplace.yml` was folded into the consumer manifest under a new `marketplace:` block. Authoring commands no longer create or expect a standalone `marketplace.yml` -- they read and write the block in `apm.yml`. The compiled artefact moves to `.claude-plugin/marketplace.json` (Anthropic-canonical location). Empty `tags:` and inherited top-level `description`/`version` are now omitted from the generated `marketplace.json` to match the canonical hand-authored shape (e.g. microsoft/azure-skills). (#1038) - **Manifest contract: invalid `target:` values now raise a parse error.** Previously, an unknown token (or a CSV string like `target: opencode,claude,copilot,agents` instead of the YAML list `target: [opencode, claude, copilot, agents]`) was silently ignored, leaving `apm install` and `apm compile` to exit 0 while deploying nothing. The shared parser used by `--target` now also validates `apm.yml`'s `target:`, so the same input resolves the same way at every entry point. **Migration:** three previously-silent inputs now fail loud -- (1) unknown tokens (`target: bogus` -> fix the typo), (2) empty values (`target: ""`, `target: []` -> remove the line if you meant auto-detect), (3) `all` mixed with other targets (`target: [all, claude]` -> use `all` alone). Omitting `target:` entirely still triggers auto-detection. (#820) - Rename `DownloadStrategyManager` to `DownloadDelegate` to better reflect Facade/Delegate pattern (#918) - Fix incorrect double-checked locking in marketplace registry `_load()` -- hold lock across full check+read+set (#918) +### Deprecated + +- **Standalone `marketplace.yml` for marketplace authoring.** Loading still works for one release with a one-line deprecation warning; both files present at once is a hard error pointing at `apm marketplace migrate`. Slated for removal in the next minor release. Migrate with `apm marketplace migrate --yes`. (#1038) + ### Fixed - `apm install` and `apm compile` no longer exit 0 with success messages when `target:` in `apm.yml` is a CSV string -- the value now parses identically to the same input on `--target`, and zero-target resolution surfaces a warning instead of a silent no-op. (#820) diff --git a/docs/src/content/docs/guides/marketplace-authoring.md b/docs/src/content/docs/guides/marketplace-authoring.md index ff5a98623..dbb4d400a 100644 --- a/docs/src/content/docs/guides/marketplace-authoring.md +++ b/docs/src/content/docs/guides/marketplace-authoring.md @@ -1,30 +1,20 @@ --- title: "Authoring a marketplace" -description: Create and maintain an APM marketplace that stays in sync with Anthropic's marketplace.json standard. +description: Author an APM marketplace from a single apm.yml and compile it to Anthropic's marketplace.json. sidebar: order: 6 --- This guide is for **marketplace maintainers** -- the people who curate a set of plugin packages for their team or organisation. If you are a consumer installing plugins from an existing marketplace, see the [Marketplaces guide](../marketplaces/) instead. -APM gives you a two-file authoring model: +APM uses a single source-of-truth model: -- `marketplace.yml` -- source of truth, hand-edited, expressive (version ranges, tag patterns, prereleases). -- `marketplace.json` -- compiled artefact, byte-for-byte compliant with Anthropic's `marketplace.json` standard, consumed by Claude Code, Copilot CLI, and APM itself. +- `apm.yml` -- your project manifest with a top-level `marketplace:` block. Hand-edited. +- `.claude-plugin/marketplace.json` -- compiled artefact, byte-for-byte compliant with Anthropic's `marketplace.json` standard, consumed by Claude Code, Copilot CLI, and APM itself. -Both files are committed to git. `marketplace.yml` is edited; `marketplace.json` is regenerated with `apm marketplace build`. +Both files are committed to git. `apm.yml` is edited; `marketplace.json` is regenerated with `apm marketplace build`. -## Anthropic compliance - -`marketplace.json` produced by `apm marketplace build` conforms to [Anthropic's marketplace.json specification](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces). The compiler follows three rules: - -1. **`plugins:` is emitted verbatim.** APM does not rename, reorder, or decorate plugin entries. The Anthropic-defined key name (`plugins`) is used as-is. -2. **`metadata:` is an opaque pass-through.** Whatever you put under `metadata:` in `marketplace.yml` is copied byte-for-byte into `marketplace.json`, preserving key casing (for example, `pluginRoot` stays `pluginRoot`). This means extensions to Anthropic's schema (new metadata fields) usually do not need an APM code change. -3. **APM-only fields are stripped at compile time.** The `build:` block, per-package `version` ranges, `tagPattern` overrides, and `includePrerelease` flags live only in `marketplace.yml`. They never leak into `marketplace.json`. - -APM does not emit a `versions[]` array. Each compiled plugin has exactly one resolved `source.ref` -- the latest commit SHA (or explicit ref) that satisfies the declared range at build time. Consumers pin to that single resolved ref. - -:::caution[Experimental Feature] +:::caution[Experimental] Marketplace authoring commands are behind an experimental flag. Enable it once before following this guide: ```bash @@ -37,78 +27,131 @@ See [Experimental Flags](../../reference/experimental/) for details. ## Quickstart ```bash -# 1. Scaffold a marketplace.yml -apm marketplace init +# 1. From an empty directory or an existing project +apm marketplace init # adds 'marketplace:' to apm.yml + # (creates apm.yml if absent) -# 2. Edit marketplace.yml -- add your packages, owner, metadata -$EDITOR marketplace.yml +# 2. Edit the marketplace block: owner, packages, optional metadata +$EDITOR apm.yml -# 3. Compile to marketplace.json +# 3. Compile to .claude-plugin/marketplace.json apm marketplace build # 4. Commit BOTH files -git add marketplace.yml marketplace.json +git add apm.yml .claude-plugin/marketplace.json git commit -m "Initial marketplace" git push ``` -Consumers now register your repository with `apm marketplace add /` and install packages from it. +`apm init --marketplace` is the equivalent shortcut when starting a brand-new project: it scaffolds `apm.yml` with the `marketplace:` block already in place. -## The marketplace.yml schema +Consumers register your repository with `apm marketplace add /` and install packages from it. -Full example: +## Real-world example: microsoft/azure-skills + +The `microsoft/azure-skills` repository ships an `apm.yml` plus a hand-authored `.claude-plugin/marketplace.json`. Running `apm marketplace build` against its `apm.yml` produces a byte-for-byte identical `marketplace.json` -- proof that the apm.yml `marketplace:` block fully expresses the Anthropic shape. ```yaml -name: my-marketplace -description: Curated plugins for the acme-org engineering team -version: 1.2.0 +# apm.yml +name: azure-skills +version: 1.0.0 +description: Microsoft Azure MCP and Skills integration + +marketplace: + owner: + name: Microsoft + url: https://www.microsoft.com + packages: + - name: azure + description: Microsoft Azure MCP integration + source: ./.github/plugins/azure-skills + homepage: https://github.com/microsoft/azure-skills +``` + +Note three things: -owner: - name: acme-org - url: https://github.com/acme-org - email: maintainers@acme-org.example +- No `name`, `description`, or `version` inside `marketplace:` -- they are inherited from the `apm.yml` top level. +- `source: ./.github/plugins/azure-skills` is a local-path package: the plugin lives in this same repo. +- No `tags:` -- empty/absent tags are omitted from `marketplace.json` to match Anthropic's canonical shape. -# APM-only: stripped from marketplace.json at compile time. -build: - tagPattern: "v{version}" +## Anthropic compliance -# Pass-through: copied verbatim into marketplace.json. -metadata: - homepage: https://example.com/plugins - pluginRoot: ./plugins +`marketplace.json` produced by `apm marketplace build` conforms to [Anthropic's marketplace.json specification](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces). The compiler follows three rules: -packages: - - name: example-package - description: Example package consumers will see - source: acme-org/example-package - version: "^1.0.0" - - - name: monorepo-tool - description: Package that lives in a subdirectory - source: acme-org/monorepo - subdir: tools/monorepo-tool - version: "~2.3.0" - tagPattern: "monorepo-tool-v{version}" +1. **`plugins:` is emitted verbatim.** APM does not rename, reorder, or decorate plugin entries. +2. **`metadata:` is an opaque pass-through.** Whatever you put under `marketplace.metadata:` in `apm.yml` is copied byte-for-byte into `marketplace.json`, preserving key casing (for example, `pluginRoot` stays `pluginRoot`). +3. **APM-only fields are stripped at compile time.** The `build:` block, per-package `version` ranges, `tag_pattern` overrides, and `include_prerelease` flags live only in `apm.yml`. They never leak into `marketplace.json`. - - name: pinned-package - description: Pinned to an explicit ref - source: acme-org/pinned-package - ref: 3f2a9b1c +APM does not emit a `versions[]` array. Each compiled plugin has exactly one resolved `source.ref` -- the latest commit SHA (or explicit ref) that satisfies the declared range at build time. Empty `tags:` and inherited `description`/`version` are omitted from output. + +## The `marketplace:` schema + +Full example with both remote and local packages: + +```yaml +name: my-project +version: 1.2.0 +description: Curated plugins for the acme-org engineering team + +marketplace: + # Optional overrides. Omit to inherit from apm.yml top level. + # name: my-marketplace + # description: ... + # version: 1.2.0 + + owner: + name: acme-org + url: https://github.com/acme-org + email: maintainers@acme-org.example + + # APM-only: stripped from marketplace.json at compile time. + build: + tagPattern: "v{version}" + + # Pass-through: copied verbatim into marketplace.json. + metadata: + homepage: https://example.com/plugins + pluginRoot: ./plugins + + packages: + - name: example-package + description: Example package consumers will see + source: acme-org/example-package + version: "^1.0.0" + + - name: monorepo-tool + description: Package that lives in a subdirectory + source: acme-org/monorepo + subdir: tools/monorepo-tool + version: "~2.3.0" + tag_pattern: "monorepo-tool-v{version}" + + - name: pinned-package + description: Pinned to an explicit ref + source: acme-org/pinned-package + ref: 3f2a9b1c + + - name: local-tool + description: Plugin shipped alongside this repo + source: ./plugins/local-tool + version: 0.1.0 ``` -### Top-level fields +### Fields inside `marketplace:` | Field | Required | Description | |-------|----------|-------------| -| `name` | yes | Marketplace identifier. | -| `description` | yes | One-line summary shown to consumers. | -| `version` | yes | Semver of the marketplace itself. Bump on release. | +| `name` | no | Override the `apm.yml` top-level `name`. Inherited when omitted. | +| `description` | no | Override the top-level `description`. Inherited when omitted. | +| `version` | no | Override the top-level `version`. Inherited when omitted. | | `owner` | yes | Mapping with `name` (required), optional `url`, `email`. | -| `output` | no | Output path for the compiled file. Defaults to `marketplace.json`. | +| `output` | no | Output path. Defaults to `.claude-plugin/marketplace.json`. | | `build` | no | APM-only build options. See below. | | `metadata` | no | Opaque pass-through copied into `marketplace.json`. | | `packages` | no | List of package entries. | +When `name`/`description`/`version` are inherited (not overridden), they are also omitted from the generated `marketplace.json` top level so the artefact stays stable across unrelated bumps to `apm.yml`. + ### The `build` block (APM-only) | Field | Default | Description | @@ -122,27 +165,55 @@ Stripped from `marketplace.json` at compile time. | Field | Required | Description | |-------|----------|-------------| | `name` | yes | Plugin name consumers will install. Unique within the marketplace. | -| `source` | yes | `/` shape, e.g. `acme-org/example-package`. Resolves to a git remote. | +| `source` | yes | Either `/` (remote) or `./path/to/dir` (local-path package in this repo). | | `description` | no | Pass-through to `marketplace.json`. | -| `tags` | no | Pass-through list of strings. | -| `version` | conditional | Semver range (see below). Either `version` or `ref` must be set. | -| `ref` | conditional | Explicit SHA, tag, or branch. Takes precedence over `version`. | -| `subdir` | no | Subdirectory within the repo. Validated against path traversal. | +| `homepage` | no | Pass-through URL. | +| `tags` | no | Pass-through list of strings. Omitted from output when empty. | +| `version` | conditional | Semver range (see below). Either `version` or `ref` must be set for remote sources. Local sources may set `version` to seed the compiled output. | +| `ref` | conditional | Explicit SHA, tag, or branch. Takes precedence over `version`. Remote sources only. | +| `subdir` | no | Subdirectory within a remote repo. Validated against path traversal. | | `tag_pattern` | no | Per-package override of `build.tagPattern`. | | `include_prerelease` | no | Include semver pre-release tags in range resolution. Defaults to `false`. | -Unknown keys at any level raise a schema error rather than being silently ignored. +Unknown keys inside `marketplace:` raise a schema error rather than being silently ignored. + +### Local-path packages + +When `source` starts with `./`, the entry is a local-path package: APM does not run `git ls-remote`, does not resolve a SHA, and emits the path verbatim into `marketplace.json` as a plain string source. Use this for plugins that ship in the same repository as the marketplace itself (the azure-skills pattern). + +```yaml +packages: + - name: local-tool + source: ./plugins/local-tool + description: Vendored alongside this marketplace + version: 0.1.0 +``` ### `.gitignore` -Both `marketplace.yml` and `marketplace.json` must be tracked. `apm marketplace init` warns if your `.gitignore` would exclude `marketplace.json`. If you use a generic `*.json` rule, add an explicit unignore: +Both `apm.yml` and the generated `.claude-plugin/marketplace.json` must be tracked. `apm marketplace init` warns if your `.gitignore` would exclude the generated file. If you use a generic `*.json` rule, add an explicit unignore: ```gitignore # .gitignore *.json -!marketplace.json +!.claude-plugin/marketplace.json ``` +## Migrating from `marketplace.yml` + +Earlier APM versions stored the same configuration in a standalone `marketplace.yml`. That file is now deprecated. APM still loads it for one release, prints a deprecation warning, and exits with an error if both files are present at once. + +Run the one-shot migration: + +```bash +apm marketplace migrate # preview the new apm.yml block +apm marketplace migrate --yes # apply: rewrite apm.yml, delete marketplace.yml +``` + +Flags: `--dry-run` shows the diff without writing; `--force`, `--yes`, and `-y` are accepted as equivalent overrides for an existing `marketplace:` block in `apm.yml`. + +After migration, commit `apm.yml` (and the deleted `marketplace.yml`) to record the consolidation. + ## Version ranges APM uses npm-compatible semver ranges. The most common forms: @@ -159,7 +230,7 @@ APM uses npm-compatible semver ranges. The most common forms: Pre-release tags (for example `1.2.0-beta.1`) are excluded by default. Set `include_prerelease: true` on the entry, or pass `--include-prerelease` to the build command, to include them. -Pin to a non-semver ref when you need exact reproducibility across a range the upstream does not tag cleanly: +Pin to a non-semver ref when you need exact reproducibility: ```yaml packages: @@ -172,7 +243,7 @@ packages: ## Managing plugins -Three subcommands let you manage `marketplace.yml` entries without hand-editing YAML. +Three subcommands let you manage entries in `marketplace.packages` without hand-editing YAML. ### Adding a package @@ -182,7 +253,7 @@ apm marketplace package add microsoft/apm-sample-package \ --description "Sample package" ``` -`package add` takes a `/` source, derives the package name from the repo, and appends an entry to `packages:`. Pass `--name` to override the derived name, `--subdir` for monorepo paths, `--tag-pattern` for non-default tag layouts, or `--tags` to attach metadata tags. By default the command verifies the source is reachable via `git ls-remote`; pass `--no-verify` to skip that check. +`package add` takes a `/` source, derives the package name from the repo, and appends an entry to `marketplace.packages` in `apm.yml`. Pass `--name` to override the derived name, `--subdir` for monorepo paths, `--tag-pattern` for non-default tag layouts, or `--tags` to attach metadata tags. By default the command verifies the source is reachable via `git ls-remote`; pass `--no-verify` to skip that check. `--version` and `--ref` are mutually exclusive -- use `--ref` to pin an exact SHA, tag, or branch instead of a semver range. @@ -192,7 +263,7 @@ apm marketplace package add microsoft/apm-sample-package \ apm marketplace package set apm-sample-package --version ">=2.0.0" ``` -`package set` takes the package name (not the source) and updates the specified fields in place. Any option accepted by `package add` (except `--name`) can be passed to `package set`. +`package set` takes the package name (not the source) and updates the specified fields in place. ### Removing a package @@ -200,11 +271,11 @@ apm marketplace package set apm-sample-package --version ">=2.0.0" apm marketplace package remove apm-sample-package --yes ``` -`package remove` drops the named entry from `packages:`. Without `--yes` the command prompts for confirmation. +`package remove` drops the named entry. Without `--yes` the command prompts for confirmation. ## The build flow -`apm marketplace build` reads `marketplace.yml`, runs `git ls-remote` against each package source, picks the best-matching ref for each entry, and writes `marketplace.json` atomically (temp file plus rename). +`apm marketplace build` reads `apm.yml`, resolves each remote package against `git ls-remote`, leaves local-path packages untouched, and writes `.claude-plugin/marketplace.json` atomically (temp file plus rename). ``` apm marketplace build @@ -223,23 +294,22 @@ apm marketplace build |------|---------| | `0` | Build succeeded; `marketplace.json` written (or previewed). | | `1` | Build error -- network failure, ref not found, no tag matches the range, etc. | -| `2` | Schema error in `marketplace.yml`. | +| `2` | Schema error in the `marketplace:` block. | ### What the compiler does -1. Parses and validates `marketplace.yml`. Unknown keys or invalid semver is a schema error (exit 2). -2. For each package: runs `git ls-remote`, enumerates tags and branches, filters by the entry's tag pattern, resolves the version range, picks the highest match. -3. Walks `metadata:` unchanged into the output. -4. Emits `plugins:` with the Anthropic key name; each entry carries the resolved `source` (with `ref` and SHA) plus any pass-through fields (`description`, `tags`). -5. Writes the file atomically. +1. Parses and validates the `marketplace:` block. Unknown keys or invalid semver is a schema error (exit 2). +2. For each remote package: runs `git ls-remote`, enumerates tags and branches, filters by the entry's tag pattern, resolves the version range, picks the highest match. +3. For each local-path package: emits the path verbatim, no resolution. +4. Walks `metadata:` unchanged into the output. +5. Emits `plugins:` with the Anthropic key name; each entry carries the resolved `source` plus any pass-through fields. Inherited top-level fields and empty `tags:` are omitted. +6. Writes the file atomically. ## Checking and troubleshooting -Two commands cover diagnosis. - ### `apm marketplace check` -Validates the yml schema and verifies every entry is resolvable. Use it in CI before publishing. +Validates the schema and verifies every entry is resolvable. Use it in CI before publishing. ```bash apm marketplace check @@ -250,7 +320,7 @@ Exit code is non-zero when any entry is unreachable, a ref does not exist, or no ### `apm marketplace doctor` -Checks the environment -- git version, network reachability of common hosts, `gh` CLI presence, git authentication, and whether `marketplace.yml` is present and parses. +Checks the environment -- git version, network reachability of common hosts, `gh` CLI presence, git authentication, and whether the project's marketplace config is present and parses. ```bash apm marketplace doctor @@ -262,8 +332,9 @@ Run it first when `build` or `publish` fails in an unfamiliar environment. | Symptom | Cause | Fix | |---------|-------|-----| -| `'packages[0].source' must match '/' shape` | `source` is a full URL or contains a path. | Use `owner/repo` and put path under `subdir:`. | -| `No tag matching '^1.0.0'` | No published tags satisfy the range under your tag pattern. | Loosen the range, check `tagPattern`, or pin with `ref:`. | +| `Both apm.yml ... and marketplace.yml exist` | Legacy file lingered after edits to apm.yml. | Run `apm marketplace migrate --yes` (or delete `marketplace.yml` if apm.yml is already the source of truth). | +| `'packages[0].source' must match ...` | `source` is a full URL or contains a path. | Use `owner/repo`, or `./path` for a local entry, and put repo paths under `subdir:`. | +| `No tag matching '^1.0.0'` | No published tags satisfy the range under your tag pattern. | Loosen the range, check `tag_pattern`, or pin with `ref:`. | | `Ref 'main' not found` | Branch or tag does not exist upstream. | Verify with `git ls-remote `. | | `Pre-release tags skipped` | Latest published tag is a pre-release. | Set `include_prerelease: true` on the entry or pass `--include-prerelease`. | | `No cached refs (offline)` | First-ever `--offline` build. | Run once online to populate the cache, then retry offline. | @@ -278,7 +349,7 @@ export GITHUB_HOST=github.company.com apm marketplace build ``` -Token resolution and metadata fetch use the same host, so existing auth configuration (see [Authentication](../../getting-started/authentication/)) works automatically. `git ls-remote` calls are authenticated with the resolved token, so private GHES repos work without a separate git credential helper. `type: url` sources accept Git-style repository URLs as input, including HTTPS and SSH forms, but APM resolves auth and metadata against `GITHUB_HOST`. In practice, the URL host is ignored unless it matches `GITHUB_HOST`, so do not rely on `type: url` for true cross-host resolution. +Token resolution and metadata fetch use the same host, so existing auth configuration (see [Authentication](../../getting-started/authentication/)) works automatically. `git ls-remote` calls are authenticated with the resolved token, so private GHES repos work without a separate git credential helper. ## Discovering upgrades @@ -292,7 +363,7 @@ apm marketplace outdated --offline Output columns: package, current version, declared range, latest in range, latest overall. Packages whose "latest overall" exceeds "latest in range" need a **manual range bump** (for example, widening `^1.0.0` to `^2.0.0`) before a new build will pick them up. This is intentional -- major-version bumps are a maintainer decision. -Packages pinned with `ref:` show `--` in the range columns; `outdated` cannot reason about them. +Packages pinned with `ref:` and local-path packages show `--` in the range columns; `outdated` cannot reason about them. ## Publishing to consumers @@ -344,10 +415,10 @@ Output shows per-target status: updated, unchanged, failed. PR URLs are printed |------|---------| | `--targets PATH` | Use a custom targets file (default `./consumer-targets.yml`). | | `--dry-run` | Preview; no push, no PR. | -| `--no-pr` | Push the branch to each target but skip PR creation (useful when `gh` is unavailable or you use another PR workflow). | +| `--no-pr` | Push the branch to each target but skip PR creation. | | `--draft` | Open PRs as drafts. | -| `--allow-downgrade` | Allow pushing a lower version than the target currently references. Off by default to prevent accidental regressions. | -| `--allow-ref-change` | Allow switching ref types (for example, branch to SHA). Off by default. | +| `--allow-downgrade` | Allow pushing a lower version than the target currently references. | +| `--allow-ref-change` | Allow switching ref types (for example, branch to SHA). | | `--parallel N` | Maximum concurrent targets. Default `4`. | | `--yes`, `-y` | Skip interactive confirmation (required for non-interactive CI). | | `-v`, `--verbose` | Per-target detail. | @@ -363,12 +434,13 @@ Publish runs append to `.apm/publish-state.json`, which records the history of r Projects that prefix tags with a package name (common in monorepos) need a per-entry pattern: ```yaml -packages: - - name: ui-components - source: acme-org/frontend-monorepo - subdir: packages/ui-components - version: "^3.0.0" - tag_pattern: "ui-components-v{version}" +marketplace: + packages: + - name: ui-components + source: acme-org/frontend-monorepo + subdir: packages/ui-components + version: "^3.0.0" + tag_pattern: "ui-components-v{version}" ``` The `{name}` placeholder resolves to the package entry's `name`, so you can also write `tag_pattern: "{name}-v{version}"` and reuse a single `build.tagPattern`. @@ -378,19 +450,16 @@ The `{name}` placeholder resolves to the package entry's `name`, so you can also Set `include_prerelease: true` on the package entry, or pass `--include-prerelease` to `build` and `outdated` for the whole marketplace: ```yaml -packages: - - name: example-package - source: acme-org/example-package - version: ">=1.0.0-0" - include_prerelease: true +marketplace: + packages: + - name: example-package + source: acme-org/example-package + version: ">=1.0.0-0" + include_prerelease: true ``` Note the `-0` pre-release suffix on the range -- it makes the lower bound inclusive of pre-releases. -### PR body is wrong -- how do I re-run safely? - -Close the incorrect PR, fix `marketplace.yml` or the targets file, rebuild, and re-run `apm marketplace publish`. The command is idempotent on identical inputs: if the target branch already carries the expected change, the target is reported as "unchanged". If you need to force a fresh PR on a target that currently has a different ref than expected, pass `--allow-ref-change`. - ### Can I use a non-GitHub host? Not in the first release. `apm marketplace publish` uses the `gh` CLI and assumes GitHub for PR creation. You can still `build` and `check` against any git remote that speaks `git ls-remote` over HTTPS or SSH; only the `publish` step is GitHub-specific. For non-GitHub consumers, run `publish --no-pr` and drive the PR creation through your own tooling. @@ -399,4 +468,5 @@ Not in the first release. `apm marketplace publish` uses the `gh` CLI and assume - [Marketplaces guide](../marketplaces/) -- consumer-side: registering and installing from a marketplace. - [CLI command reference](../../reference/cli-commands/) -- authoritative options for every `apm marketplace` subcommand. +- [Manifest schema](../../reference/manifest-schema/) -- the `apm.yml` shape including the `marketplace:` block. - [Plugins guide](../plugins/) -- what a plugin is and how consumers install one. diff --git a/docs/src/content/docs/guides/marketplaces.md b/docs/src/content/docs/guides/marketplaces.md index 71a1ea77e..2ea137a86 100644 --- a/docs/src/content/docs/guides/marketplaces.md +++ b/docs/src/content/docs/guides/marketplaces.md @@ -275,7 +275,7 @@ apm marketplace package add acme/monorepo --subdir plugins/formatter --name form ### Ref auto-resolution -Mutable git refs (`HEAD`, branch names) are automatically resolved to concrete 40-character SHAs before being stored in `marketplace.yml`. This ensures supply-chain safety -- the entry always pins to an immutable commit. +Mutable git refs (`HEAD`, branch names) are automatically resolved to concrete 40-character SHAs before being stored in `apm.yml`. This ensures supply-chain safety -- the entry always pins to an immutable commit. **Default behaviour (no `--ref`):** When neither `--version` nor `--ref` is provided, the current `HEAD` SHA is pinned automatically: diff --git a/docs/src/content/docs/reference/cli-commands.md b/docs/src/content/docs/reference/cli-commands.md index aa7617c87..732971f3e 100644 --- a/docs/src/content/docs/reference/cli-commands.md +++ b/docs/src/content/docs/reference/cli-commands.md @@ -36,6 +36,7 @@ apm init [PROJECT_NAME] [OPTIONS] **Options:** - `-y, --yes` - Skip interactive prompts and use auto-detected defaults - `--plugin` - Initialize as a plugin authoring project (creates `plugin.json` + `apm.yml` with `devDependencies`) +- `--marketplace` - Seed `apm.yml` with a `marketplace:` authoring block (requires `apm experimental enable marketplace-authoring`). See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). **Examples:** ```bash @@ -53,6 +54,9 @@ apm init my-project --yes # Initialize a plugin authoring project apm init my-plugin --plugin + +# Initialize a project that also publishes a marketplace +apm init my-marketplace --marketplace ``` **Behavior:** @@ -1257,32 +1261,61 @@ apm marketplace validate acme-plugins apm marketplace validate acme-plugins --verbose ``` -#### `apm marketplace init` - Scaffold a marketplace.yml +#### `apm marketplace init` - Add a marketplace block to apm.yml -Create a richly-commented `marketplace.yml` in the current directory. The scaffold is valid against the schema and ready to be edited. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). +Add a `marketplace:` block to the project's `apm.yml`. If `apm.yml` is absent, a minimal one is scaffolded first. The block is richly commented and ready to be edited. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). ```bash apm marketplace init [OPTIONS] ``` **Options:** -- `--force` - Overwrite an existing `marketplace.yml` +- `--force` - Overwrite an existing `marketplace:` block in `apm.yml` - `--no-gitignore-check` - Skip the `.gitignore` staleness check +- `--name TEXT` - Marketplace/package name (defaults to `my-marketplace` when scaffolding apm.yml) +- `--owner TEXT` - Owner name for the marketplace block - `-v, --verbose` - Show detailed output **Exit codes:** -- `0` - Scaffold written -- `1` - File already exists (without `--force`) or write failure +- `0` - Block written +- `1` - Block already exists (without `--force`) or write failure **Examples:** ```bash apm marketplace init -apm marketplace init --force +apm marketplace init --force --owner acme-org ``` -#### `apm marketplace build` - Compile marketplace.yml +`apm init --marketplace` is the equivalent shortcut at project-creation time: it seeds a fresh `apm.yml` with the `marketplace:` block already in place. + +#### `apm marketplace migrate` - Fold marketplace.yml into apm.yml + +One-shot conversion of a legacy standalone `marketplace.yml` into the `marketplace:` block of `apm.yml`. Inheritable fields (`name`, `description`, `version`) are dropped from the block when they match `apm.yml`'s top-level values, and emitted as overrides when they differ. The legacy `marketplace.yml` is deleted on success. + +```bash +apm marketplace migrate [OPTIONS] +``` + +**Options:** +- `--force`, `--yes`, `-y` - Overwrite an existing `marketplace:` block in `apm.yml` (the three flags are aliases) +- `--dry-run` - Print the proposed change without writing +- `-v, --verbose` - Show detailed output + +**Exit codes:** +- `0` - Migration applied (or dry run complete) +- `1` - Migration failed (legacy file missing, conflict without `--force`, write failure) + +**Examples:** +```bash +apm marketplace migrate --dry-run +apm marketplace migrate --yes +``` + +#### `apm marketplace build` - Compile the marketplace block + +Resolve all package version ranges against the source repositories and write an Anthropic-compliant `.claude-plugin/marketplace.json`. APM-only fields (`build:`, version ranges, tag patterns) are stripped; `metadata:` is passed through verbatim. Inherited top-level fields and empty `tags:` are omitted from the output. -Resolve all package version ranges against the source repositories and write an Anthropic-compliant `marketplace.json`. APM-only fields (`build:`, version ranges, tag patterns) are stripped; `metadata:` is passed through verbatim. +Reads from `apm.yml`'s `marketplace:` block by default; falls back to a legacy `marketplace.yml` (with a deprecation warning) when no block is present. Errors out when both are present at once. ```bash apm marketplace build [OPTIONS] @@ -1297,11 +1330,11 @@ apm marketplace build [OPTIONS] **Exit codes:** - `0` - Build succeeded (or dry run complete) - `1` - Build error (network failure, unresolvable ref, no matching tag) -- `2` - Schema error in `marketplace.yml` +- `2` - Schema error in the `marketplace:` block **Examples:** ```bash -# Compile marketplace.yml -> marketplace.json +# Compile to .claude-plugin/marketplace.json apm marketplace build # Preview without writing @@ -1313,7 +1346,7 @@ apm marketplace build --offline #### `apm marketplace outdated` - Report available upgrades -List packages in `marketplace.yml` whose source repositories have newer tags available. Range-aware: distinguishes "latest in range" (picked up by next `build`) from "latest overall" (requires a manual range bump). +List packages in the `marketplace:` block whose source repositories have newer tags available. Range-aware: distinguishes "latest in range" (picked up by next `build`) from "latest overall" (requires a manual range bump). Local-path packages and `ref:`-pinned entries show `--` in the range columns. ```bash apm marketplace outdated [OPTIONS] @@ -1327,7 +1360,7 @@ apm marketplace outdated [OPTIONS] **Exit codes:** - `0` - Report rendered (even if upgrades are available) - `1` - Unable to query refs -- `2` - Schema error in `marketplace.yml` +- `2` - Schema error in the `marketplace:` block **Examples:** ```bash @@ -1335,9 +1368,9 @@ apm marketplace outdated apm marketplace outdated --include-prerelease ``` -#### `apm marketplace check` - Validate marketplace.yml entries +#### `apm marketplace check` - Validate marketplace entries -Validate the `marketplace.yml` schema and verify that every package entry is resolvable (ref exists, at least one tag satisfies the range). Intended for CI use before publishing. +Validate the `marketplace:` schema and verify that every package entry is resolvable (ref exists, at least one tag satisfies the range). Intended for CI use before publishing. ```bash apm marketplace check [OPTIONS] @@ -1350,7 +1383,7 @@ apm marketplace check [OPTIONS] **Exit codes:** - `0` - All entries OK - `1` - One or more entries are unreachable or unresolvable -- `2` - Schema error in `marketplace.yml` +- `2` - Schema error in the `marketplace:` block **Examples:** ```bash @@ -1360,7 +1393,7 @@ apm marketplace check --offline #### `apm marketplace doctor` - Environment diagnostics -Check git, network reachability, authentication, `gh` CLI availability, and the presence of `marketplace.yml`. Run this first when `build` or `publish` fails in an unfamiliar environment. +Check git, network reachability, authentication, `gh` CLI availability, and the presence of a marketplace config (in `apm.yml` or legacy `marketplace.yml`). Run this first when `build` or `publish` fails in an unfamiliar environment. ```bash apm marketplace doctor [OPTIONS] @@ -1418,7 +1451,7 @@ Run history and PR URLs are recorded in `.apm/publish-state.json` so re-runs can #### `apm marketplace package add` - Add a package entry -Add a package entry to `marketplace.yml`. +Add a package entry to the `marketplace.packages` list in `apm.yml`. ```bash apm marketplace package add SOURCE [OPTIONS] @@ -1456,7 +1489,7 @@ apm marketplace package add acme/code-review --ref abc123...40chars \ #### `apm marketplace package set` - Update a package entry -Update fields on an existing package entry in `marketplace.yml`. +Update fields on an existing package entry in the `marketplace.packages` list of `apm.yml`. ```bash apm marketplace package set NAME [OPTIONS] @@ -1491,7 +1524,7 @@ apm marketplace package set code-review --description "Updated review skill" #### `apm marketplace package remove` - Remove a package entry -Remove a package entry from `marketplace.yml`. +Remove a package entry from the `marketplace.packages` list in `apm.yml`. ```bash apm marketplace package remove NAME [OPTIONS] diff --git a/docs/src/content/docs/reference/manifest-schema.md b/docs/src/content/docs/reference/manifest-schema.md index 55107d9ea..17f874e1d 100644 --- a/docs/src/content/docs/reference/manifest-schema.md +++ b/docs/src/content/docs/reference/manifest-schema.md @@ -57,8 +57,11 @@ devDependencies: mcp: > compilation: policy: +marketplace: # OPTIONAL; marketplace authoring ``` +`marketplace:` is the source for `apm marketplace build` and is OPTIONAL. Repositories that do not publish a marketplace omit it entirely. The block, its schema, and the build flow are documented in the [Authoring a marketplace guide](../../guides/marketplace-authoring/). Within `marketplace:`, the inheritable fields `name`, `description`, and `version` default to the top-level values above and SHOULD be omitted unless an override is required. + --- ## 3. Top-Level Fields diff --git a/packages/apm-guide/.apm/skills/apm-usage/commands.md b/packages/apm-guide/.apm/skills/apm-usage/commands.md index 1eaa36c4f..27b151185 100644 --- a/packages/apm-guide/.apm/skills/apm-usage/commands.md +++ b/packages/apm-guide/.apm/skills/apm-usage/commands.md @@ -4,7 +4,7 @@ | Command | Purpose | Key flags | |---------|---------|-----------| -| `apm init [NAME]` | Initialize a new APM project | `-y` skip prompts, `--plugin` authoring mode | +| `apm init [NAME]` | Initialize a new APM project | `-y` skip prompts, `--plugin` plugin authoring mode, `--marketplace` seed apm.yml with a `marketplace:` block (experimental) | ## Dependency management @@ -66,17 +66,24 @@ ## Marketplace authoring (experimental) +> **Authoring is gated** behind `apm experimental enable marketplace-authoring`. Consumer commands (add, list, browse, update, remove, validate, search) are always available. +> +> Source of truth is the `marketplace:` block in `apm.yml`. The legacy standalone `marketplace.yml` is deprecated and slated for removal next minor; use `apm marketplace migrate` to fold it in. + | Command | Purpose | Key flags | |---------|---------|-----------| -| `apm marketplace init` | Scaffold `marketplace.yml` in CWD | `--force`, `--no-gitignore-check` | -| `apm marketplace build` | Compile `marketplace.yml` to Anthropic-compliant `marketplace.json` | `--dry-run`, `--offline`, `--include-prerelease`, `-v` | +| `apm marketplace init` | Add a `marketplace:` block to `apm.yml` (scaffolds `apm.yml` if absent) | `--force`, `--no-gitignore-check`, `--name`, `--owner` | +| `apm marketplace migrate` | Fold a legacy `marketplace.yml` into `apm.yml`'s `marketplace:` block; deletes `marketplace.yml` on success | `--force`/`--yes`/`-y`, `--dry-run`, `-v` | +| `apm marketplace build` | Compile `marketplace:` to Anthropic-compliant `.claude-plugin/marketplace.json` | `--dry-run`, `--offline`, `--include-prerelease`, `-v` | | `apm marketplace outdated` | Report upgradable packages, range-aware | `--offline`, `--include-prerelease`, `-v` | -| `apm marketplace check` | Validate yml and verify refs resolve | `--offline`, `-v` | -| `apm marketplace doctor` | Diagnose git, network, auth, yml readiness | `-v` | +| `apm marketplace check` | Validate the `marketplace:` block and verify refs resolve | `--offline`, `-v` | +| `apm marketplace doctor` | Diagnose git, network, auth, and marketplace config readiness | `-v` | | `apm marketplace publish` | Open PRs on consumer repos from `consumer-targets.yml` | `--targets PATH`, `--dry-run`, `--no-pr`, `--draft`, `--allow-downgrade`, `--allow-ref-change`, `--parallel N`, `-y` | -| `apm marketplace package add ` | Add a package entry to `marketplace.yml` | `--name`, `--version`, `--ref` (mutable refs auto-resolved to SHA), `-d`/`--description`, `-s`/`--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease`, `--no-verify` | +| `apm marketplace package add ` | Add a package entry to `marketplace.packages` (source accepts `owner/repo` or `./path`) | `--name`, `--version`, `--ref` (mutable refs auto-resolved to SHA), `-d`/`--description`, `-s`/`--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease`, `--no-verify` | | `apm marketplace package set ` | Update fields on an existing package entry | `--version`, `--ref` (mutable refs auto-resolved to SHA), `--description`, `--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease` | -| `apm marketplace package remove ` | Remove a package entry from `marketplace.yml` | `--yes` | +| `apm marketplace package remove ` | Remove a package entry from `marketplace.packages` | `--yes` | + +`apm init --marketplace` is the equivalent shortcut at project-creation time -- it seeds a fresh `apm.yml` with the `marketplace:` block already in place (also gated by the experimental flag). ## MCP servers diff --git a/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md b/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md index d4df08241..f9802d7ce 100644 --- a/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md +++ b/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md @@ -196,61 +196,96 @@ apm install org/my-package#v1.0.0 ## Marketplace authoring A **marketplace** is a curated index of packages (plugins) that consumers -install via `apm install @`. Maintainers author a -`marketplace.yml` source file and compile it to an Anthropic-compliant -`marketplace.json` with `apm marketplace build`. Both files are committed. +install via `apm install @`. Maintainers declare the +marketplace in a `marketplace:` block inside `apm.yml` and compile it to +an Anthropic-compliant `.claude-plugin/marketplace.json` with +`apm marketplace build`. Both files are committed. + +Authoring is gated behind `apm experimental enable marketplace-authoring`. ### When to run `apm marketplace init` - The user is setting up a new marketplace repository. - The user wants to convert an ad-hoc list of plugins into a proper index. -Do NOT run `init` inside an existing package directory; a marketplace -repository is a separate repo whose job is to list plugins, not to be one. +`apm marketplace init` adds a `marketplace:` block to the project's +`apm.yml` (and scaffolds a minimal `apm.yml` first if needed). Use +`apm init --marketplace` instead when starting a brand-new project that +will publish its own marketplace. -### marketplace.yml shape +### apm.yml `marketplace:` block ```yaml -name: my-marketplace -description: Short summary +name: my-project version: 0.1.0 -owner: - name: acme-org - url: https://github.com/acme-org -build: # APM-only, stripped at compile time - tagPattern: "v{version}" -metadata: # pass-through, copied verbatim to marketplace.json - homepage: https://example.com -packages: - - name: example-package - description: What this package does - source: acme-org/example-package # / - version: "^1.0.0" # semver range OR 'ref:' below - # ref: 3f2a9b1c # explicit SHA/tag/branch; overrides version - # subdir: tools/x # optional subdirectory - # tag_pattern: "{name}-v{version}" # optional per-package override - # include_prerelease: false # optional +description: Short summary + +marketplace: + # name / description / version inherit from apm.yml top level + # (omit unless you need to override). + owner: + name: acme-org + url: https://github.com/acme-org + build: # APM-only, stripped at compile time + tagPattern: "v{version}" + metadata: # pass-through, copied verbatim + homepage: https://example.com + packages: + - name: example-package + description: What this package does + source: acme-org/example-package # owner/repo (remote) + version: "^1.0.0" # semver range OR 'ref:' below + # ref: 3f2a9b1c # explicit SHA/tag/branch + # subdir: tools/x # optional subdirectory + # tag_pattern: "{name}-v{version}" # optional per-package override + # include_prerelease: false # optional + + - name: local-tool + description: Plugin shipped alongside this repo + source: ./plugins/local-tool # local path (no remote fetch) + version: 0.1.0 ``` Schema rules: -- `name`, `description`, `version`, `owner.name` are required. -- Each package needs either `version` (a semver range) or `ref` (explicit). +- `owner.name` is required. `name`, `description`, `version` are + optional inside the block (inherited from apm.yml top level). +- Each remote package needs either `version` or `ref`. - `ref` takes precedence over `version`. +- `source: ./...` marks a local-path package: skips git resolution, + emits the path verbatim into `marketplace.json`. - Unknown keys raise a schema error -- do not invent fields. ### Build semantics -`apm marketplace build` runs `git ls-remote` against each package source, -picks the highest tag satisfying the range (under the applicable -`tagPattern`), and writes `marketplace.json`. The compiler: +`apm marketplace build` runs `git ls-remote` against each remote package +source, picks the highest tag satisfying the range (under the applicable +`tagPattern`), leaves local-path entries untouched, and writes +`.claude-plugin/marketplace.json`. The compiler: 1. Emits `plugins:` verbatim (Anthropic's key name). 2. Copies `metadata:` byte-for-byte. 3. Strips `build:`, per-package `version`, `tag_pattern`, `include_prerelease`. -4. Does not emit `versions[]` -- each plugin carries a single resolved ref. +4. Omits empty `tags:` and inherited top-level `description`/`version` + from the output (matches Anthropic's canonical hand-authored shape, + e.g. microsoft/azure-skills). +5. Does not emit `versions[]` -- each plugin carries a single resolved ref. Exit codes: `0` success, `1` build error, `2` schema error. +### Migrating from legacy `marketplace.yml` + +Earlier APM versions stored this configuration in a standalone +`marketplace.yml`. That file is deprecated and slated for removal next +minor. Run the one-shot migration: + +```bash +apm marketplace migrate --dry-run # preview the apm.yml change +apm marketplace migrate --yes # apply: rewrite apm.yml, delete marketplace.yml +``` + +`--force`, `--yes`, and `-y` are equivalent. Both files present at once +is a hard error -- run `migrate` to consolidate. + ### Full guide See [docs/guides/marketplace-authoring](../../../../../docs/src/content/docs/guides/marketplace-authoring.md) diff --git a/src/apm_cli/commands/init.py b/src/apm_cli/commands/init.py index 5f82f645c..ce8ef2924 100644 --- a/src/apm_cli/commands/init.py +++ b/src/apm_cli/commands/init.py @@ -123,7 +123,11 @@ def init(ctx, project_name, yes, plugin, marketplace_flag, verbose): existing = apm_yml_path.read_text(encoding="utf-8") if not existing.endswith("\n"): existing += "\n" - block = render_marketplace_block(owner=config.get("name")) + # Owner is intentionally left to the template default + # (acme-org placeholder). Deriving it from the project + # name produced misleading https://github.com/ + # URLs; the user is expected to edit the placeholder. + block = render_marketplace_block() apm_yml_path.write_text(existing + "\n" + block, encoding="utf-8") except OSError as exc: logger.warning( diff --git a/src/apm_cli/commands/marketplace.py b/src/apm_cli/commands/marketplace.py index 1706d8069..8a2f98516 100644 --- a/src/apm_cli/commands/marketplace.py +++ b/src/apm_cli/commands/marketplace.py @@ -40,7 +40,6 @@ from ..marketplace.ref_resolver import RefResolver, RemoteRef from ..marketplace.semver import SemVer, parse_semver, satisfies_range from ..marketplace.migration import ( - DEPRECATION_MESSAGE, ConfigSource, detect_config_source, load_marketplace_config, @@ -281,8 +280,22 @@ def init(force, no_gitignore_check, name, owner, verbose): logger.error(f"Failed to parse apm.yml: {exc}", symbol="error") sys.exit(1) - if isinstance(data, dict) and "marketplace" in data and \ - data["marketplace"] is not None and not force: + # An empty apm.yml round-trips to None; treat it as an empty + # mapping so the marketplace block can still be inserted. + # A non-mapping top level (list, scalar) is a hard error. + if data is None: + from ruamel.yaml.comments import CommentedMap + data = CommentedMap() + elif not isinstance(data, dict): + logger.error( + "apm.yml must be a YAML mapping at the top level " + f"(got {type(data).__name__}).", + symbol="error", + ) + sys.exit(1) + + if "marketplace" in data and data["marketplace"] is not None \ + and not force: logger.warning( "apm.yml already has a 'marketplace:' block. Use --force to overwrite.", symbol="warning", @@ -355,8 +368,8 @@ def _check_gitignore_for_marketplace_json(logger): if stripped in patterns: logger.warning( "Your .gitignore ignores marketplace.json. " - "Both marketplace.yml and marketplace.json must be tracked " - "in git. Remove the .gitignore rule.", + "Both apm.yml and the generated marketplace.json must be " + "tracked in git. Remove the .gitignore rule.", symbol="warning", ) return diff --git a/src/apm_cli/marketplace/init_template.py b/src/apm_cli/marketplace/init_template.py index 4a108ca5c..ebdcb1d2a 100644 --- a/src/apm_cli/marketplace/init_template.py +++ b/src/apm_cli/marketplace/init_template.py @@ -1,7 +1,11 @@ -"""Template renderer for ``apm marketplace init``. +"""Template renderers for marketplace authoring scaffolds. -Produces a richly-commented ``marketplace.yml`` scaffold that is valid -against :func:`~apm_cli.marketplace.yml_schema.load_marketplace_yml`. +Two renderers ship in this module: + +* :func:`render_marketplace_yml_template` -- legacy ``marketplace.yml`` + scaffold, retained for one release while the deprecation runs out. +* :func:`render_marketplace_block` -- the apm.yml ``marketplace:`` block + used by ``apm marketplace init`` and ``apm init --marketplace``. """ from __future__ import annotations diff --git a/src/apm_cli/marketplace/migration.py b/src/apm_cli/marketplace/migration.py index a5eba6238..5e155b8a5 100644 --- a/src/apm_cli/marketplace/migration.py +++ b/src/apm_cli/marketplace/migration.py @@ -61,16 +61,36 @@ class ConfigSource(enum.Enum): def _has_marketplace_block(apm_yml_path: Path) -> bool: - """Return ``True`` when *apm_yml_path* exists and has ``marketplace:``.""" + """Return ``True`` when *apm_yml_path* has a non-null ``marketplace:``. + + Missing files and valid YAML without a top-level ``marketplace`` block + return ``False``. Read failures and YAML parse errors raise + :class:`MarketplaceYmlError` so callers do not mistake a malformed + ``apm.yml`` for an absent marketplace configuration (which would + surface a misleading "no marketplace config" message instead of the + real parse error). + """ if not apm_yml_path.exists(): return False try: text = apm_yml_path.read_text(encoding="utf-8") + except OSError as exc: + raise MarketplaceYmlError( + f"Could not read {apm_yml_path.name}: {exc}" + ) from exc + + try: data = yaml.safe_load(text) - except (OSError, yaml.YAMLError): - return False - return isinstance(data, dict) and "marketplace" in data and \ - data["marketplace"] is not None + except yaml.YAMLError as exc: + raise MarketplaceYmlError( + f"Invalid YAML in {apm_yml_path.name}: {exc}" + ) from exc + + return ( + isinstance(data, dict) + and "marketplace" in data + and data["marketplace"] is not None + ) def detect_config_source(project_root: Path) -> ConfigSource: @@ -231,6 +251,17 @@ def migrate_marketplace_yml( apm_text = apm_path.read_text(encoding="utf-8") apm_data = rt.load(apm_text) + if apm_data is None: + # Empty apm.yml: round-trip with an empty mapping so we can + # still insert the marketplace block. + from ruamel.yaml.comments import CommentedMap + apm_data = CommentedMap() + elif not isinstance(apm_data, dict): + raise MarketplaceYmlError( + "apm.yml must be a YAML mapping at the top level " + f"(got {type(apm_data).__name__}). Cannot migrate." + ) + if "marketplace" in apm_data and apm_data["marketplace"] is not None: if not force: raise MarketplaceYmlError( diff --git a/src/apm_cli/marketplace/yml_editor.py b/src/apm_cli/marketplace/yml_editor.py index dea5fc1cf..0b22592f2 100644 --- a/src/apm_cli/marketplace/yml_editor.py +++ b/src/apm_cli/marketplace/yml_editor.py @@ -24,7 +24,6 @@ from ._io import atomic_write from .errors import MarketplaceYmlError from .yml_schema import ( - LOCAL_SOURCE_RE, SOURCE_RE, load_marketplace_from_apm_yml, load_marketplace_yml, @@ -71,12 +70,17 @@ def _is_apm_yml_with_marketplace(data) -> bool: The legacy ``marketplace.yml`` shape has marketplace fields (``owner``, ``packages``) at the root; the apm.yml shape nests them under ``marketplace:``. We pick whichever shape the file actually has. + + Requires the ``marketplace`` value itself to be a mapping; otherwise + downstream callers (e.g. :func:`_get_marketplace_container`) would + return a non-dict and crash on ``container.get(...)``. """ if not isinstance(data, dict): return False - if "marketplace" not in data or data["marketplace"] is None: + block = data.get("marketplace") + if block is None: return False - return True + return isinstance(block, dict) def _get_marketplace_container(data): diff --git a/tests/unit/marketplace/test_apm_yml_marketplace_loader.py b/tests/unit/marketplace/test_apm_yml_marketplace_loader.py index 7fca529f2..f5168cbaa 100644 --- a/tests/unit/marketplace/test_apm_yml_marketplace_loader.py +++ b/tests/unit/marketplace/test_apm_yml_marketplace_loader.py @@ -2,7 +2,7 @@ Covers inheritance of name/description/version from the apm.yml top level, override semantics inside the marketplace block, and rejection -of unknown keys at both levels. +of unknown keys within the marketplace block. """ from __future__ import annotations diff --git a/tests/unit/marketplace/test_review_fixes.py b/tests/unit/marketplace/test_review_fixes.py new file mode 100644 index 000000000..282de82b2 --- /dev/null +++ b/tests/unit/marketplace/test_review_fixes.py @@ -0,0 +1,140 @@ +"""Regression tests for PR #1038 review-comment fixes. + +Covers the edge cases flagged by copilot-pull-request-reviewer: +- malformed apm.yml surfaces a clear error instead of "no marketplace config" +- empty / non-mapping apm.yml does not crash migrate or init +- ``_is_apm_yml_with_marketplace`` rejects non-mapping marketplace values +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from apm_cli.commands.marketplace import marketplace +from apm_cli.marketplace.errors import MarketplaceYmlError +from apm_cli.marketplace.migration import ( + _has_marketplace_block, + detect_config_source, + migrate_marketplace_yml, +) +from apm_cli.marketplace.yml_editor import _is_apm_yml_with_marketplace + + +# --------------------------------------------------------------------------- +# r3: malformed apm.yml surfaces a clear error +# --------------------------------------------------------------------------- + + +class TestMalformedApmYmlSurfaced: + def test_yaml_parse_error_raises_marketplace_error(self, tmp_path: Path): + bad = tmp_path / "apm.yml" + bad.write_text("name: app\nversion: : :\n", encoding="utf-8") + with pytest.raises(MarketplaceYmlError, match="Invalid YAML"): + _has_marketplace_block(bad) + + def test_unreadable_apm_yml_raises_marketplace_error( + self, tmp_path: Path, monkeypatch + ): + bad = tmp_path / "apm.yml" + bad.write_text("name: app\n", encoding="utf-8") + + def boom(*a, **kw): + raise OSError("permission denied") + + monkeypatch.setattr(Path, "read_text", boom) + with pytest.raises(MarketplaceYmlError, match="Could not read"): + _has_marketplace_block(bad) + + def test_detect_config_source_propagates_parse_error(self, tmp_path: Path): + bad = tmp_path / "apm.yml" + bad.write_text("name: app\nversion: : :\n", encoding="utf-8") + with pytest.raises(MarketplaceYmlError): + detect_config_source(tmp_path) + + +# --------------------------------------------------------------------------- +# r4: migrate handles empty / non-mapping apm.yml +# --------------------------------------------------------------------------- + + +def _legacy_yml() -> str: + return ( + "name: mp\n" + "version: 0.1.0\n" + "description: legacy mp\n" + "owner:\n name: acme\n" + "packages: []\n" + ) + + +class TestMigrateNonMappingApmYml: + def test_migrate_rejects_list_top_level(self, tmp_path: Path): + (tmp_path / "apm.yml").write_text("- one\n- two\n", encoding="utf-8") + (tmp_path / "marketplace.yml").write_text(_legacy_yml(), encoding="utf-8") + with pytest.raises(MarketplaceYmlError, match="must be a YAML mapping"): + migrate_marketplace_yml(tmp_path) + + def test_migrate_rejects_scalar_top_level(self, tmp_path: Path): + (tmp_path / "apm.yml").write_text("just-a-string\n", encoding="utf-8") + (tmp_path / "marketplace.yml").write_text(_legacy_yml(), encoding="utf-8") + with pytest.raises(MarketplaceYmlError, match="must be a YAML mapping"): + migrate_marketplace_yml(tmp_path) + + def test_migrate_handles_empty_apm_yml(self, tmp_path: Path): + (tmp_path / "apm.yml").write_text("", encoding="utf-8") + (tmp_path / "marketplace.yml").write_text(_legacy_yml(), encoding="utf-8") + # Should succeed: empty apm.yml round-trips to an empty mapping. + diff = migrate_marketplace_yml(tmp_path) + assert "marketplace" in (tmp_path / "apm.yml").read_text(encoding="utf-8") + assert diff # diff is a non-empty unified-diff string + + +# --------------------------------------------------------------------------- +# r5: _is_apm_yml_with_marketplace rejects non-mapping marketplace values +# --------------------------------------------------------------------------- + + +class TestIsApmYmlWithMarketplaceTightened: + def test_marketplace_value_must_be_mapping(self): + # Scalar marketplace value is not a valid block; helper must say no. + data = {"name": "app", "marketplace": "not-a-block"} + assert _is_apm_yml_with_marketplace(data) is False + + def test_marketplace_list_value_rejected(self): + data = {"name": "app", "marketplace": [1, 2, 3]} + assert _is_apm_yml_with_marketplace(data) is False + + def test_marketplace_mapping_accepted(self): + data = {"name": "app", "marketplace": {"owner": {"name": "acme"}}} + assert _is_apm_yml_with_marketplace(data) is True + + def test_missing_or_null_block_rejected(self): + assert _is_apm_yml_with_marketplace({"name": "app"}) is False + assert _is_apm_yml_with_marketplace({"marketplace": None}) is False + + +# --------------------------------------------------------------------------- +# r6: marketplace init handles empty / non-mapping apm.yml +# --------------------------------------------------------------------------- + + +class TestMarketplaceInitNonMappingApmYml: + def test_init_rejects_list_top_level(self, tmp_path: Path, monkeypatch): + runner = CliRunner() + monkeypatch.chdir(tmp_path) + (tmp_path / "apm.yml").write_text("- one\n", encoding="utf-8") + result = runner.invoke(marketplace, ["init"]) + assert result.exit_code == 1 + assert "must be a YAML mapping" in result.output + + def test_init_handles_empty_apm_yml(self, tmp_path: Path, monkeypatch): + runner = CliRunner() + monkeypatch.chdir(tmp_path) + (tmp_path / "apm.yml").write_text("", encoding="utf-8") + result = runner.invoke(marketplace, ["init"]) + assert result.exit_code == 0, result.output + text = (tmp_path / "apm.yml").read_text(encoding="utf-8") + assert "marketplace:" in text diff --git a/tests/unit/marketplace/test_yml_schema.py b/tests/unit/marketplace/test_yml_schema.py index 008dc4995..993b6c209 100644 --- a/tests/unit/marketplace/test_yml_schema.py +++ b/tests/unit/marketplace/test_yml_schema.py @@ -686,7 +686,7 @@ def test_owner_not_a_mapping(self, tmp_path: Path): with pytest.raises(MarketplaceYmlError, match="owner"): load_marketplace_yml(yml) - def test_source_dot_traversal(self, tmp_path: Path): + def test_local_source_accepted(self, tmp_path: Path): """Local-path source './acme' is now valid (no version/ref needed).""" content = _minimal_yml( packages=( From e471f192442f598a283d576f08f1a55869d5aab0 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 16:33:57 +0200 Subject: [PATCH 2/6] ci: recompile gh-aw workflows to v0.71.2 Picks up the AW_APM_PACKAGES JSON-array fix from gh-aw v0.71.2 (shared/apm.md realignment in github/gh-aw#29002), which caused the PR Review Panel run on PR #1042 to fail at the 'Validate downloaded bundles match matrix manifest' step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/aw/actions-lock.json | 12 +- .github/workflows/agentics-maintenance.yml | 36 ++--- .../cli-consistency-checker.lock.yml | 120 ++++++++++------ .github/workflows/daily-doc-updater.lock.yml | 124 +++++++++++------ .../workflows/daily-test-improver.lock.yml | 128 +++++++++++------ .github/workflows/pr-review-panel.lock.yml | 128 +++++++++++------ .github/workflows/triage-panel.lock.yml | 130 +++++++++++------- 7 files changed, 434 insertions(+), 244 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index ae984cb26..af13fe770 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -25,15 +25,15 @@ "version": "v7", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup-cli@v0.71.1": { + "github/gh-aw-actions/setup-cli@v0.71.2": { "repo": "github/gh-aw-actions/setup-cli", - "version": "v0.71.1", - "sha": "239aec45b78c8799417efdd5bc6d8cc036629ec1" + "version": "v0.71.2", + "sha": "ab8940d1df237fc4fae4ab8091e7ba66ada55a55" }, - "github/gh-aw-actions/setup@v0.71.1": { + "github/gh-aw-actions/setup@v0.71.2": { "repo": "github/gh-aw-actions/setup", - "version": "v0.71.1", - "sha": "239aec45b78c8799417efdd5bc6d8cc036629ec1" + "version": "v0.71.2", + "sha": "ab8940d1df237fc4fae4ab8091e7ba66ada55a55" }, "github/gh-aw/actions/setup@v0.50.6": { "repo": "github/gh-aw/actions/setup", diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index d7b4d5932..3268d1d9d 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -12,7 +12,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.71.1). DO NOT EDIT. +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.71.2). DO NOT EDIT. # # To regenerate this workflow, run: # gh aw compile @@ -92,7 +92,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -130,7 +130,7 @@ jobs: actions: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -159,7 +159,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -174,9 +174,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup-cli@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: - version: v0.71.1 + version: v0.71.2 - name: Run operation uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -204,7 +204,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -250,7 +250,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -294,7 +294,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -309,9 +309,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup-cli@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: - version: v0.71.1 + version: v0.71.2 - name: Create missing labels uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -340,7 +340,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -355,9 +355,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup-cli@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: - version: v0.71.1 + version: v0.71.2 - name: Restore activity report logs cache id: activity_report_logs_cache @@ -437,7 +437,7 @@ jobs: issues: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -474,7 +474,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -489,9 +489,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup-cli@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: - version: v0.71.1 + version: v0.71.2 - name: Validate workflows and file issue on findings uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 56a5005b5..052023385 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5a9d052a38597a88a66140c25dc36adf95f169ba6aede00fd0fc1607bd4868e5","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5a9d052a38597a88a66140c25dc36adf95f169ba6aede00fd0fc1607bd4868e5","compiler_version":"v0.71.2","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ab8940d1df237fc4fae4ab8091e7ba66ada55a55","version":"v0.71.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29","digest":"sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29","digest":"sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29","digest":"sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.2). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -36,14 +36,14 @@ # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0 -# - ghcr.io/github/github-mcp-server:v1.0.2 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 +# - ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "CLI Consistency Checker" @@ -83,7 +83,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -92,17 +92,17 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.36" + GH_AW_INFO_AGENT_VERSION: "1.0.36" + GH_AW_INFO_CLI_VERSION: "v0.71.2" GH_AW_INFO_WORKFLOW_NAME: "CLI Consistency Checker" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.29" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -153,7 +153,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.71.2" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -187,6 +187,9 @@ jobs: Tools: create_issue, missing_tool, missing_data, noop + GH_AW_PROMPT_7f2a6c65259dd107_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_7f2a6c65259dd107_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -244,6 +247,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -262,7 +266,8 @@ jobs: GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -319,7 +324,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -370,11 +375,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -397,7 +402,7 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -600,7 +605,7 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.1' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -609,7 +614,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -646,6 +651,20 @@ jobs: } } GH_AW_MCP_CONFIG_2f5ac48e458e535a_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -660,8 +679,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -671,7 +690,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -856,7 +875,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -954,6 +973,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" @@ -983,7 +1003,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1013,7 +1033,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 - name: Check if detection needed id: detection_guard if: always() @@ -1028,7 +1048,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1072,13 +1092,14 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 @@ -1089,8 +1110,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -1098,7 +1119,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1122,16 +1143,33 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() + continue-on-error: true uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } safe_outputs: needs: @@ -1151,7 +1189,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.36" GH_AW_WORKFLOW_ID: "cli-consistency-checker" GH_AW_WORKFLOW_NAME: "CLI Consistency Checker" outputs: @@ -1166,7 +1204,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 354d63948..20d26fffa 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7dff95974a58d1cda6cfbfefa43b0e949237feb436374a1af76834272c07523a","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","CREATE_PR_PAT","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7dff95974a58d1cda6cfbfefa43b0e949237feb436374a1af76834272c07523a","compiler_version":"v0.71.2","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","CREATE_PR_PAT","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ab8940d1df237fc4fae4ab8091e7ba66ada55a55","version":"v0.71.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29","digest":"sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29","digest":"sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29","digest":"sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.2). DO NOT EDIT. # # To update this file, edit githubnext/agentics/workflows/daily-doc-updater.md@b87234850bf9664d198f28a02df0f937d0447295 and run: # gh aw compile @@ -40,14 +40,14 @@ # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0 -# - ghcr.io/github/github-mcp-server:v1.0.2 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 +# - ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Daily Documentation Updater" @@ -88,7 +88,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -97,17 +97,17 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.36" + GH_AW_INFO_AGENT_VERSION: "1.0.36" + GH_AW_INFO_CLI_VERSION: "v0.71.2" GH_AW_INFO_WORKFLOW_NAME: "Daily Documentation Updater" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","node","python","rust","java"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.29" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -158,7 +158,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.71.2" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -195,6 +195,9 @@ jobs: cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat << 'GH_AW_PROMPT_d8180d8f6736e660_EOF' + GH_AW_PROMPT_d8180d8f6736e660_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_d8180d8f6736e660_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -253,6 +256,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -271,7 +275,8 @@ jobs: GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -328,7 +333,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -379,11 +384,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -406,14 +411,14 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7fbc604f6aa7572c_EOF' - {"create_pull_request":{"auto_merge":true,"draft":false,"expires":48,"labels":["documentation","automation"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/"],"reviewers":["copilot"],"title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + {"create_pull_request":{"auto_merge":true,"draft":false,"expires":48,"labels":["documentation","automation"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"reviewers":["copilot"],"title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_7fbc604f6aa7572c_EOF - name: Write Safe Outputs Tools env: @@ -617,7 +622,7 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.1' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -626,7 +631,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -663,6 +668,20 @@ jobs: } } GH_AW_MCP_CONFIG_80fdafd984f93f67_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -677,8 +696,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -688,7 +707,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -875,7 +894,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -983,6 +1002,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} @@ -1014,7 +1034,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1044,7 +1064,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 - name: Check if detection needed id: detection_guard if: always() @@ -1059,7 +1079,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1103,13 +1123,14 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 @@ -1120,8 +1141,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -1129,7 +1150,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1153,16 +1174,33 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() + continue-on-error: true uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } safe_outputs: needs: @@ -1183,7 +1221,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.36" GH_AW_WORKFLOW_ID: "daily-doc-updater" GH_AW_WORKFLOW_NAME: "Daily Documentation Updater" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-doc-updater.md@b87234850bf9664d198f28a02df0f937d0447295" @@ -1200,7 +1238,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1264,7 +1302,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"auto_merge\":true,\"draft\":false,\"expires\":48,\"labels\":[\"documentation\",\"automation\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\",\".githooks/\",\".husky/\"],\"reviewers\":[\"copilot\"],\"title_prefix\":\"[docs] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"auto_merge\":true,\"draft\":false,\"expires\":48,\"labels\":[\"documentation\",\"automation\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"reviewers\":[\"copilot\"],\"title_prefix\":\"[docs] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} GITHUB_TOKEN: ${{ secrets.CREATE_PR_PAT }} with: diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index dc6c081b2..265af7915 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d35699dc0ea071e6e8866c1fa72deccb81156b9e572ef3949bca0ab17eab0b6a","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","CREATE_PR_PAT","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d35699dc0ea071e6e8866c1fa72deccb81156b9e572ef3949bca0ab17eab0b6a","compiler_version":"v0.71.2","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","CREATE_PR_PAT","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ab8940d1df237fc4fae4ab8091e7ba66ada55a55","version":"v0.71.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29","digest":"sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29","digest":"sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29","digest":"sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.2). DO NOT EDIT. # # To update this file, edit githubnext/agentics/workflows/daily-test-improver.md@b87234850bf9664d198f28a02df0f937d0447295 and run: # gh aw compile @@ -48,14 +48,14 @@ # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0 -# - ghcr.io/github/github-mcp-server:v1.0.2 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 +# - ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Daily Test Improver" @@ -131,7 +131,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -141,17 +141,17 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.36" + GH_AW_INFO_AGENT_VERSION: "1.0.36" + GH_AW_INFO_CLI_VERSION: "v0.71.2" GH_AW_INFO_WORKFLOW_NAME: "Daily Test Improver" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","node","python","rust","java"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.29" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -215,7 +215,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.71.2" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,6 +281,9 @@ jobs: cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" cat << 'GH_AW_PROMPT_d3a5e29c049960b9_EOF' + GH_AW_PROMPT_d3a5e29c049960b9_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_d3a5e29c049960b9_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -350,6 +353,7 @@ jobs: GH_AW_GITHUB_SERVER_URL: ${{ github.server_url }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_MEMORY_BRANCH_NAME: 'memory/daily-test-improver' GH_AW_MEMORY_CONSTRAINTS: "\n\n**Constraints:**\n- **Max File Size**: 10240 bytes (0.01 MB) per file\n- **Max File Count**: 100 files per commit\n- **Max Patch Size**: 10240 bytes (10 KB) total per push (max: 100 KB)\n" GH_AW_MEMORY_DESCRIPTION: '' @@ -380,6 +384,7 @@ jobs: GH_AW_GITHUB_SERVER_URL: process.env.GH_AW_GITHUB_SERVER_URL, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_MEMORY_BRANCH_NAME: process.env.GH_AW_MEMORY_BRANCH_NAME, GH_AW_MEMORY_CONSTRAINTS: process.env.GH_AW_MEMORY_CONSTRAINTS, GH_AW_MEMORY_DESCRIPTION: process.env.GH_AW_MEMORY_DESCRIPTION, @@ -440,7 +445,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -501,11 +506,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -528,14 +533,14 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_06d219a5154f9c3f_EOF' - {"add_comment":{"hide_older_comments":true,"max":1,"target":"*"},"create_issue":{"labels":["automation","testing"],"max":1},"create_pull_request":{"draft":true,"labels":["automation","testing"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/"],"title_prefix":"[Test Improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/"],"target":"*"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} + {"add_comment":{"hide_older_comments":true,"max":1,"target":"*"},"create_issue":{"labels":["automation","testing"],"max":1},"create_pull_request":{"draft":true,"labels":["automation","testing"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"title_prefix":"[Test Improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} GH_AW_SAFE_OUTPUTS_CONFIG_06d219a5154f9c3f_EOF - name: Write Safe Outputs Tools env: @@ -871,7 +876,7 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.1' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -880,7 +885,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -917,6 +922,20 @@ jobs: } } GH_AW_MCP_CONFIG_d491f7b2025ca149_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -931,8 +950,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -942,7 +961,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1141,7 +1160,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1249,6 +1268,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} @@ -1303,7 +1323,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1333,7 +1353,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 - name: Check if detection needed id: detection_guard if: always() @@ -1348,7 +1368,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1392,13 +1412,14 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 @@ -1409,8 +1430,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -1418,7 +1439,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1442,16 +1463,33 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() + continue-on-error: true uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } pre_activation: if: "(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment') && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/test-assist ') || startsWith(github.event.issue.body, '/test-assist\n') || github.event.issue.body == '/test-assist') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/test-assist ') || startsWith(github.event.comment.body, '/test-assist\n') || github.event.comment.body == '/test-assist') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/test-assist ') || startsWith(github.event.comment.body, '/test-assist\n') || github.event.comment.body == '/test-assist') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/test-assist ') || startsWith(github.event.comment.body, '/test-assist\n') || github.event.comment.body == '/test-assist') || github.event_name == 'pull_request' && (startsWith(github.event.pull_request.body, '/test-assist ') || startsWith(github.event.pull_request.body, '/test-assist\n') || github.event.pull_request.body == '/test-assist') || github.event_name == 'discussion' && (startsWith(github.event.discussion.body, '/test-assist ') || startsWith(github.event.discussion.body, '/test-assist\n') || github.event.discussion.body == '/test-assist') || github.event_name == 'discussion_comment' && (startsWith(github.event.comment.body, '/test-assist ') || startsWith(github.event.comment.body, '/test-assist\n') || github.event.comment.body == '/test-assist')) || (!(github.event_name == 'issues')) && (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'pull_request')) && (!(github.event_name == 'pull_request_review_comment')) && (!(github.event_name == 'discussion')) && (!(github.event_name == 'discussion_comment'))" @@ -1463,7 +1501,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1498,7 +1536,7 @@ jobs: - detection if: > always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result != 'skipped' + needs.agent.result == 'success' runs-on: ubuntu-slim permissions: contents: write @@ -1512,7 +1550,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1584,7 +1622,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.36" GH_AW_WORKFLOW_ID: "daily-test-improver" GH_AW_WORKFLOW_NAME: "Daily Test Improver" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-test-improver.md@b87234850bf9664d198f28a02df0f937d0447295" @@ -1607,7 +1645,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1671,7 +1709,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,*.pythonhosted.org,*.vsblob.vsassets.io,adoptium.net,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,bun.sh,cdn.azul.com,cdn.jsdelivr.net,central.sonatype.com,ci.dot.net,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,deb.nodesource.com,deno.land,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,esm.sh,files.pythonhosted.org,ge.spockframework.org,get.pnpm.io,github.com,googleapis.deno.dev,googlechromelabs.github.io,gradle.org,host.docker.internal,index.crates.io,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo.yarnpkg.com,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request\":{\"draft\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\",\".githooks/\",\".husky/\"],\"title_prefix\":\"[Test Improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\",\".githooks/\",\".husky/\"],\"target\":\"*\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request\":{\"draft\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"title_prefix\":\"[Test Improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} GITHUB_TOKEN: ${{ secrets.CREATE_PR_PAT }} with: diff --git a/.github/workflows/pr-review-panel.lock.yml b/.github/workflows/pr-review-panel.lock.yml index f428ba8dc..179f774fe 100644 --- a/.github/workflows/pr-review-panel.lock.yml +++ b/.github/workflows/pr-review-panel.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"92695581e85ba85ce1995d6e9a826783dcf60b1f7cd1c06c5785b8f7ee37eda0","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_PLUGINS_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"},{"repo":"microsoft/apm-action","sha":"454b8a1d279376a47df8bb8d525ec076ca0fcef7","version":"v1.5.0"},{"repo":"ruby/setup-ruby","sha":"0cb964fd540e0a24c900370abf38a33466142735","version":"v1.305.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"92695581e85ba85ce1995d6e9a826783dcf60b1f7cd1c06c5785b8f7ee37eda0","compiler_version":"v0.71.2","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_PLUGINS_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"ab8940d1df237fc4fae4ab8091e7ba66ada55a55","version":"v0.71.2"},{"repo":"microsoft/apm-action","sha":"454b8a1d279376a47df8bb8d525ec076ca0fcef7","version":"v1.5.0"},{"repo":"ruby/setup-ruby","sha":"c4e5b1316158f92e3d49443a9d58b31d25ac0f8f","version":"v1.306.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29","digest":"sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29","digest":"sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29","digest":"sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.2). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -43,16 +43,16 @@ # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 # - microsoft/apm-action@454b8a1d279376a47df8bb8d525ec076ca0fcef7 # v1.5.0 -# - ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0 +# - ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0 -# - ghcr.io/github/github-mcp-server:v1.0.2 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 +# - ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "PR Review Panel" @@ -108,7 +108,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -118,17 +118,17 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.36" + GH_AW_INFO_AGENT_VERSION: "1.0.36" + GH_AW_INFO_CLI_VERSION: "v0.71.2" GH_AW_INFO_WORKFLOW_NAME: "PR Review Panel" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.29" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -179,7 +179,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.71.2" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -225,6 +225,9 @@ jobs: Tools: add_comment(max:2), add_labels, remove_labels, missing_tool, missing_data, noop + GH_AW_PROMPT_2afa3c5792fcd6cb_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_2afa3c5792fcd6cb_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -288,6 +291,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | @@ -309,6 +313,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); @@ -367,7 +372,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -385,7 +390,7 @@ jobs: with: persist-credentials: false - name: Setup Ruby - uses: ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 with: ruby-version: '3.3' - name: Create gh-aw temp directory @@ -441,11 +446,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -468,7 +473,7 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -700,7 +705,7 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.1' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -709,7 +714,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -746,6 +751,20 @@ jobs: } } GH_AW_MCP_CONFIG_75facca82ff1fd27_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -760,8 +779,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -771,7 +790,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1090,7 +1109,7 @@ jobs: AW_APM_LEGACY_OWNER: ${{ github.aw.import-inputs.owner }} AW_APM_LEGACY_PRIVATE_KEY: ${{ github.aw.import-inputs.private-key }} AW_APM_LEGACY_REPOS: ${{ github.aw.import-inputs.repositories }} - AW_APM_PACKAGES: "[microsoft/apm#main]" + AW_APM_PACKAGES: "[\"microsoft/apm#main\"]" conclusion: needs: @@ -1120,7 +1139,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1218,6 +1237,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" @@ -1247,7 +1267,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1277,7 +1297,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 - name: Check if detection needed id: detection_guard if: always() @@ -1292,7 +1312,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1336,13 +1356,14 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 @@ -1353,8 +1374,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -1362,7 +1383,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1386,19 +1407,36 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() + continue-on-error: true uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } pre_activation: - if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'panel-review' }} + if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'panel-review' runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} @@ -1407,7 +1445,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1444,7 +1482,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.36" GH_AW_WORKFLOW_ID: "pr-review-panel" GH_AW_WORKFLOW_NAME: "PR Review Panel" outputs: @@ -1459,7 +1497,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} diff --git a/.github/workflows/triage-panel.lock.yml b/.github/workflows/triage-panel.lock.yml index c125e8d86..ced41708f 100644 --- a/.github/workflows/triage-panel.lock.yml +++ b/.github/workflows/triage-panel.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8c3da1c544f941fd3b29c5fe60edb77f5a4b7b39160438ae57a86b6ceef374f5","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_PLUGINS_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"},{"repo":"microsoft/apm-action","sha":"454b8a1d279376a47df8bb8d525ec076ca0fcef7","version":"v1.5.0"},{"repo":"ruby/setup-ruby","sha":"0cb964fd540e0a24c900370abf38a33466142735","version":"v1.305.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8c3da1c544f941fd3b29c5fe60edb77f5a4b7b39160438ae57a86b6ceef374f5","compiler_version":"v0.71.2","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_PLUGINS_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"ab8940d1df237fc4fae4ab8091e7ba66ada55a55","version":"v0.71.2"},{"repo":"microsoft/apm-action","sha":"454b8a1d279376a47df8bb8d525ec076ca0fcef7","version":"v1.5.0"},{"repo":"ruby/setup-ruby","sha":"c4e5b1316158f92e3d49443a9d58b31d25ac0f8f","version":"v1.306.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29","digest":"sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29","digest":"sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29","digest":"sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.2). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -43,16 +43,16 @@ # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 # - microsoft/apm-action@454b8a1d279376a47df8bb8d525ec076ca0fcef7 # v1.5.0 -# - ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0 +# - ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0 -# - ghcr.io/github/github-mcp-server:v1.0.2 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 +# - ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Triage Panel" @@ -115,7 +115,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -125,17 +125,17 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.36" + GH_AW_INFO_AGENT_VERSION: "1.0.36" + GH_AW_INFO_CLI_VERSION: "v0.71.2" GH_AW_INFO_WORKFLOW_NAME: "Triage Panel" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.29" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -186,7 +186,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.71.2" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -233,6 +233,9 @@ jobs: Tools: add_comment(max:12), add_labels(max:70), remove_labels(max:12), assign_milestone(max:12), dispatch_workflow(max:10), missing_tool, missing_data, noop + GH_AW_PROMPT_6f96760dced7e8ea_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_6f96760dced7e8ea_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -299,6 +302,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | @@ -321,6 +325,7 @@ jobs: GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); @@ -379,7 +384,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -397,7 +402,7 @@ jobs: with: persist-credentials: false - name: Setup Ruby - uses: ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 with: ruby-version: '3.3' - name: Create gh-aw temp directory @@ -453,11 +458,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -480,7 +485,7 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -748,7 +753,7 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.1' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -757,7 +762,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -794,6 +799,20 @@ jobs: } } GH_AW_MCP_CONFIG_40d3a7868f7beed9_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -808,8 +827,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -819,7 +838,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1138,7 +1157,7 @@ jobs: AW_APM_LEGACY_OWNER: ${{ github.aw.import-inputs.owner }} AW_APM_LEGACY_PRIVATE_KEY: ${{ github.aw.import-inputs.private-key }} AW_APM_LEGACY_REPOS: ${{ github.aw.import-inputs.repositories }} - AW_APM_PACKAGES: "[microsoft/apm#main]" + AW_APM_PACKAGES: "[\"microsoft/apm#main\"]" conclusion: needs: @@ -1169,7 +1188,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1267,6 +1286,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" @@ -1296,7 +1316,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1326,7 +1346,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.29@sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.29@sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6 ghcr.io/github/gh-aw-firewall/squid:0.25.29@sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53 - name: Check if detection needed id: detection_guard if: always() @@ -1341,7 +1361,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1385,13 +1405,14 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.36 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 @@ -1402,8 +1423,8 @@ jobs: export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.29,squid=sha256:8a71ad9e40454051672312917e51567abfb8251d7c294d086c48f63d84e4cb53,agent=sha256:e68f37e36962dcb3f3d1de680a49bc2302cefd001b941a7dc377155ec7ce42f4,agent-act=sha256:97b4cc14dc2123a45b9d5b9927489f66882dec5857de6afc0e5bab257be92ef1,api-proxy=sha256:d1219e4110684402aabbeb5a43858f26790c9d0be210581cf3f7a521bd2c87b6,cli-proxy=sha256:29917488eb90a01ff9544ffeeb5cc26434a8ea16d69ae8972f5f6be0e567e276 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -1411,7 +1432,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.71.2 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1435,24 +1456,41 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() + continue-on-error: true uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } pre_activation: if: > - ${{ github.event_name != 'issues' + github.event_name != 'issues' || (github.event.label.name == 'status/needs-triage' && github.event.issue.user.type != 'Bot' && github.event.issue.locked != true - && github.event.issue.state == 'open') }} + && github.event.issue.state == 'open') runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} @@ -1461,7 +1499,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1499,7 +1537,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.36" GH_AW_WORKFLOW_ID: "triage-panel" GH_AW_WORKFLOW_NAME: "Triage Panel" outputs: @@ -1515,7 +1553,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@ab8940d1df237fc4fae4ab8091e7ba66ada55a55 # v0.71.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} From a322bfd067accff159b690134f1e2498ddf15c98 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 16:53:48 +0200 Subject: [PATCH 3/6] fix(marketplace): address remaining 4 review-bot comments on PR #1038 - migration.py: wrap ruamel apm.yml load; raise typed MarketplaceYmlError("apm.yml is malformed: ...") instead of leaking ruamel.yaml.YAMLError to the caller. Mirrors the existing legacy marketplace.yml error path. - init.py: when 'apm init --marketplace' is invoked but the marketplace_authoring experimental flag is disabled, append the block (option b -- lower friction, harmless if unused) and emit a CommandLogger.warning() pointing at the flag name and enablement command. - yml_editor.py: add 'data: object' type hint to _is_apm_yml_with_marketplace() to satisfy the project-wide type-hint requirement. - CHANGELOG.md: condense Unreleased marketplace entries to one line per entry per Keep a Changelog convention; strip nested bullets and prose. Tests: - test_migrate_with_malformed_apm_yml_raises_typed_error - TestInitMarketplaceFlagWarnsWhenExperimentalDisabled ::test_warns_with_experimental_flag_name Full unit suite: 6759 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 12 ++--- src/apm_cli/commands/init.py | 9 ++++ src/apm_cli/marketplace/migration.py | 10 +++- src/apm_cli/marketplace/yml_editor.py | 2 +- tests/unit/marketplace/test_review_fixes.py | 59 +++++++++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6224c8c58..776e0a90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **[Experimental] Marketplace authoring folded into `apm.yml`.** A new top-level `marketplace:` block carries `owner`, `build`, `packages` and optional `name`/`description`/`version` overrides (inherited from apm.yml top level when omitted). `apm marketplace init` now adds the block to `apm.yml` (scaffolding `apm.yml` if absent) instead of writing a standalone `marketplace.yml`. `apm marketplace build`, `check`, `outdated`, `doctor`, `publish`, and `package add|set|remove` all read the block. Validation evidence: `microsoft/azure-skills` -- a real-world repo with a hand-authored `.claude-plugin/marketplace.json` -- builds byte-for-byte identical output from its `apm.yml`. (#1038) -- **[Experimental] `apm marketplace migrate`** -- one-shot consolidation of a legacy `marketplace.yml` into `apm.yml`'s `marketplace:` block. Accepts `--force`/`--yes`/`-y` as aliases and `--dry-run` for preview. Inheritable fields are dropped from the block when they match the apm.yml top level and emitted as overrides when they differ. (#1038) -- **[Experimental] `apm init --marketplace`** -- seeds a fresh `apm.yml` with a `marketplace:` authoring block at project-creation time. Equivalent shortcut to `apm init` followed by `apm marketplace init`. (#1038) -- **Local-path package sources for marketplace authoring.** `source: ./path/to/dir` is now first-class in the `marketplace.packages` list; previously rejected by the source regex. Local entries skip git resolution and are emitted to `marketplace.json` as plain string sources (matches Anthropic's local-source convention). (#1038) +- `marketplace:` block in `apm.yml` unifies catalog authoring with the manifest. (#1038) +- `apm marketplace migrate` -- one-shot consolidation of legacy `marketplace.yml` into `apm.yml`. (#1038) +- `apm init --marketplace` -- seeds a fresh `apm.yml` with a `marketplace:` authoring block. (#1038) +- Local-path package sources (`source: ./path/to/dir`) are first-class in `marketplace.packages`. (#1038) - Codex CLI MCP config is now project-local (`.codex/config.toml`) during project installs and gated to active project targets; Codex user-scope primitive deployment is also supported. (#803) - **Dev Container Feature** `ghcr.io/microsoft/apm/apm-cli` -- one-line install of the APM CLI into any `devcontainer.json`, GitHub Codespace, or JetBrains Gateway workspace. Supports a `version` option (`latest` or pinned semver), declares `installsAfter` for the official Python feature, handles PEP 668 on Ubuntu 24.04+. Ships with 37 bats unit tests and a 6-distro Docker integration matrix (Ubuntu 24.04, Ubuntu 22.04, Debian 12, Alpine 3.20, Fedora 41, plus Python-feature combo). (#861) - `shared/apm.md` gh-aw workflow gains an `apps:` array input for cross-org private packages: each entry mints its own GitHub App installation token via `actions/create-github-app-token` and packs only its declared packages, with a matrix fan-out one replica per credential group. The single-app top-level form (`app-id`, `private-key`, `owner`, `repositories`) shipped earlier in this cycle is preserved as the canonical shorthand for one-org users; `apps[]` is purely additive. Multi-bundle restore uses the `bundles-file:` input from `microsoft/apm-action@v1.5.0` (microsoft/apm-action#30, microsoft/apm-action#29). @@ -20,14 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **[Experimental] Marketplace authoring surface consolidated to `apm.yml`.** The publisher-side `marketplace.yml` was folded into the consumer manifest under a new `marketplace:` block. Authoring commands no longer create or expect a standalone `marketplace.yml` -- they read and write the block in `apm.yml`. The compiled artefact moves to `.claude-plugin/marketplace.json` (Anthropic-canonical location). Empty `tags:` and inherited top-level `description`/`version` are now omitted from the generated `marketplace.json` to match the canonical hand-authored shape (e.g. microsoft/azure-skills). (#1038) +- `apm marketplace build` reads from `apm.yml` when a `marketplace:` block is present; output moves to `.claude-plugin/marketplace.json`. (#1038) - **Manifest contract: invalid `target:` values now raise a parse error.** Previously, an unknown token (or a CSV string like `target: opencode,claude,copilot,agents` instead of the YAML list `target: [opencode, claude, copilot, agents]`) was silently ignored, leaving `apm install` and `apm compile` to exit 0 while deploying nothing. The shared parser used by `--target` now also validates `apm.yml`'s `target:`, so the same input resolves the same way at every entry point. **Migration:** three previously-silent inputs now fail loud -- (1) unknown tokens (`target: bogus` -> fix the typo), (2) empty values (`target: ""`, `target: []` -> remove the line if you meant auto-detect), (3) `all` mixed with other targets (`target: [all, claude]` -> use `all` alone). Omitting `target:` entirely still triggers auto-detection. (#820) - Rename `DownloadStrategyManager` to `DownloadDelegate` to better reflect Facade/Delegate pattern (#918) - Fix incorrect double-checked locking in marketplace registry `_load()` -- hold lock across full check+read+set (#918) ### Deprecated -- **Standalone `marketplace.yml` for marketplace authoring.** Loading still works for one release with a one-line deprecation warning; both files present at once is a hard error pointing at `apm marketplace migrate`. Slated for removal in the next minor release. Migrate with `apm marketplace migrate --yes`. (#1038) +- Standalone `marketplace.yml` (still loaded; emits a deprecation warning; slated for removal in v0.13). (#1038) ### Fixed diff --git a/src/apm_cli/commands/init.py b/src/apm_cli/commands/init.py index ce8ef2924..a3416a4fa 100644 --- a/src/apm_cli/commands/init.py +++ b/src/apm_cli/commands/init.py @@ -117,6 +117,7 @@ def init(ctx, project_name, yes, plugin, marketplace_flag, verbose): # Append marketplace authoring block when requested. if marketplace_flag: + from ..core.experimental import is_enabled from ..marketplace.init_template import render_marketplace_block apm_yml_path = Path.cwd() / APM_YML_FILENAME try: @@ -135,6 +136,14 @@ def init(ctx, project_name, yes, plugin, marketplace_flag, verbose): symbol="warning", ) + if not is_enabled("marketplace_authoring"): + logger.warning( + "Marketplace authoring is gated behind the " + "'marketplace_authoring' experimental flag. " + "Enable it with: apm experimental enable marketplace-authoring", + symbol="warning", + ) + logger.success("APM project initialized successfully!") # Display created file info diff --git a/src/apm_cli/marketplace/migration.py b/src/apm_cli/marketplace/migration.py index 5e155b8a5..31f5cd7b1 100644 --- a/src/apm_cli/marketplace/migration.py +++ b/src/apm_cli/marketplace/migration.py @@ -249,7 +249,15 @@ def migrate_marketplace_yml( # Load apm.yml round-trip so we can safely insert the new key. apm_text = apm_path.read_text(encoding="utf-8") - apm_data = rt.load(apm_text) + try: + apm_data = rt.load(apm_text) + except Exception as exc: # ruamel.yaml.YAMLError and parser subclasses + from ruamel.yaml import YAMLError + if not isinstance(exc, YAMLError): + raise + raise MarketplaceYmlError( + f"apm.yml is malformed: {exc}" + ) from exc if apm_data is None: # Empty apm.yml: round-trip with an empty mapping so we can diff --git a/src/apm_cli/marketplace/yml_editor.py b/src/apm_cli/marketplace/yml_editor.py index 0b22592f2..b17f9cded 100644 --- a/src/apm_cli/marketplace/yml_editor.py +++ b/src/apm_cli/marketplace/yml_editor.py @@ -64,7 +64,7 @@ def _dump_rt(data) -> str: return stream.getvalue() -def _is_apm_yml_with_marketplace(data) -> bool: +def _is_apm_yml_with_marketplace(data: object) -> bool: """Detect an apm.yml file that hosts a ``marketplace:`` block. The legacy ``marketplace.yml`` shape has marketplace fields (``owner``, diff --git a/tests/unit/marketplace/test_review_fixes.py b/tests/unit/marketplace/test_review_fixes.py index 282de82b2..5e13e1afd 100644 --- a/tests/unit/marketplace/test_review_fixes.py +++ b/tests/unit/marketplace/test_review_fixes.py @@ -138,3 +138,62 @@ def test_init_handles_empty_apm_yml(self, tmp_path: Path, monkeypatch): assert result.exit_code == 0, result.output text = (tmp_path / "apm.yml").read_text(encoding="utf-8") assert "marketplace:" in text + + +# --------------------------------------------------------------------------- +# Followup: migrate -- malformed apm.yml raises typed MarketplaceYmlError +# (instead of leaking a raw ruamel.yaml.YAMLError) +# --------------------------------------------------------------------------- + + +class TestMigrateMalformedApmYmlTyped: + def test_migrate_with_malformed_apm_yml_raises_typed_error( + self, tmp_path: Path + ): + # apm.yml passes the up-front existence check but is unparseable. + (tmp_path / "apm.yml").write_text( + "name: app\nversion: : :\n", encoding="utf-8" + ) + (tmp_path / "marketplace.yml").write_text( + _legacy_yml(), encoding="utf-8" + ) + with pytest.raises(MarketplaceYmlError, match="apm.yml is malformed"): + migrate_marketplace_yml(tmp_path) + + +# --------------------------------------------------------------------------- +# Followup: apm init --marketplace warns when experimental flag is disabled +# --------------------------------------------------------------------------- + + +class TestInitMarketplaceFlagWarnsWhenExperimentalDisabled: + def test_warns_with_experimental_flag_name( + self, tmp_path: Path, monkeypatch + ): + from unittest.mock import patch as _patch + + from apm_cli.commands.init import init + + runner = CliRunner() + monkeypatch.chdir(tmp_path) + + # Force marketplace_authoring=False even though the autouse + # marketplace fixture flips it to True for everything else. + def _disabled(name: str) -> bool: + if name == "marketplace_authoring": + return False + from apm_cli.core.experimental import is_enabled as real_is + return real_is(name) + + with _patch( + "apm_cli.core.experimental.is_enabled", side_effect=_disabled + ): + result = runner.invoke(init, ["my-proj", "--yes", "--marketplace"]) + + assert result.exit_code == 0, result.output + # Warning text must mention the experimental flag name so the + # user knows what to enable. + assert "marketplace_authoring" in result.output + # And block was still appended (option b: lower friction). + text = (tmp_path / "my-proj" / "apm.yml").read_text(encoding="utf-8") + assert "marketplace:" in text From 33f29ec35baacb84ec8222a6e09b55e97c094e5c Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 17:23:02 +0200 Subject: [PATCH 4/6] docs(marketplace): teach unified 'apm pack' workflow - Rewrite marketplace authoring guide to use 'apm pack' and the apm.yml marketplace: block as the single source of truth. - Update CLI command reference: remove 'apm marketplace build' entry, refresh 'apm pack' flag table, refresh 'apm marketplace init'. - Update apm-usage skill (commands.md) to match. - Remove all references to the marketplace_authoring experimental flag. Closes part of #722. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../docs/guides/marketplace-authoring.md | 234 +++++++++--------- .../content/docs/reference/cli-commands.md | 109 ++++---- .../content/docs/reference/experimental.md | 1 - .../content/docs/reference/manifest-schema.md | 2 +- .../.apm/skills/apm-usage/authentication.md | 2 +- .../.apm/skills/apm-usage/commands.md | 29 +-- .../skills/apm-usage/package-authoring.md | 54 ++-- 7 files changed, 212 insertions(+), 219 deletions(-) diff --git a/docs/src/content/docs/guides/marketplace-authoring.md b/docs/src/content/docs/guides/marketplace-authoring.md index dbb4d400a..5beb62f33 100644 --- a/docs/src/content/docs/guides/marketplace-authoring.md +++ b/docs/src/content/docs/guides/marketplace-authoring.md @@ -1,55 +1,46 @@ --- title: "Authoring a marketplace" -description: Author an APM marketplace from a single apm.yml and compile it to Anthropic's marketplace.json. +description: Author an APM marketplace from a single apm.yml and build it with apm pack. sidebar: order: 6 --- -This guide is for **marketplace maintainers** -- the people who curate a set of plugin packages for their team or organisation. If you are a consumer installing plugins from an existing marketplace, see the [Marketplaces guide](../marketplaces/) instead. +This guide is for **marketplace maintainers** -- people who curate a set of plugin packages for their team or organisation. If you are a consumer installing plugins from an existing marketplace, see the [Marketplaces guide](../marketplaces/) instead. APM uses a single source-of-truth model: -- `apm.yml` -- your project manifest with a top-level `marketplace:` block. Hand-edited. -- `.claude-plugin/marketplace.json` -- compiled artefact, byte-for-byte compliant with Anthropic's `marketplace.json` standard, consumed by Claude Code, Copilot CLI, and APM itself. +- `apm.yml` -- your project manifest, hand-edited. A top-level `marketplace:` block declares the marketplace. +- `.claude-plugin/marketplace.json` -- compiled artefact, byte-for-byte compliant with [Anthropic's marketplace.json specification](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces). Consumed by Claude Code, Copilot CLI, and APM itself. -Both files are committed to git. `apm.yml` is edited; `marketplace.json` is regenerated with `apm marketplace build`. - -:::caution[Experimental] -Marketplace authoring commands are behind an experimental flag. Enable it once before following this guide: - -```bash -apm experimental enable marketplace-authoring -``` - -See [Experimental Flags](../../reference/experimental/) for details. -::: +Both files are committed. `apm.yml` is edited; `marketplace.json` is regenerated by `apm pack`. ## Quickstart ```bash -# 1. From an empty directory or an existing project -apm marketplace init # adds 'marketplace:' to apm.yml - # (creates apm.yml if absent) +# 1. Add a marketplace block to your existing apm.yml +apm marketplace init -# 2. Edit the marketplace block: owner, packages, optional metadata +# 2. Edit the block in apm.yml -- describe each plugin under marketplace.plugins $EDITOR apm.yml -# 3. Compile to .claude-plugin/marketplace.json -apm marketplace build +# 3. Build the marketplace +apm pack -# 4. Commit BOTH files +# 4. Commit both files git add apm.yml .claude-plugin/marketplace.json git commit -m "Initial marketplace" git push ``` -`apm init --marketplace` is the equivalent shortcut when starting a brand-new project: it scaffolds `apm.yml` with the `marketplace:` block already in place. +`apm marketplace init` appends a richly commented `marketplace:` block to your existing `apm.yml` and creates an empty `.claude-plugin/` directory. It does NOT create a standalone `marketplace.yml`. If your project has no `apm.yml` yet, run `apm init` first. + +`apm pack` is the universal build verb. When `apm.yml` contains a `marketplace:` block, it writes `.claude-plugin/marketplace.json`. When `apm.yml` also contains `dependencies:`, it writes a bundle to `./build//` in the same run. The manifest drives what gets produced -- there is no separate "marketplace build" command. Consumers register your repository with `apm marketplace add /` and install packages from it. ## Real-world example: microsoft/azure-skills -The `microsoft/azure-skills` repository ships an `apm.yml` plus a hand-authored `.claude-plugin/marketplace.json`. Running `apm marketplace build` against its `apm.yml` produces a byte-for-byte identical `marketplace.json` -- proof that the apm.yml `marketplace:` block fully expresses the Anthropic shape. +The `microsoft/azure-skills` repository ships an `apm.yml` plus a hand-authored `.claude-plugin/marketplace.json`. Running `apm pack` against its `apm.yml` produces a byte-for-byte identical `marketplace.json` -- proof that the `marketplace:` block fully expresses the Anthropic shape. ```yaml # apm.yml @@ -61,9 +52,9 @@ marketplace: owner: name: Microsoft url: https://www.microsoft.com - packages: + plugins: - name: azure - description: Microsoft Azure MCP integration + description: Microsoft Azure MCP integration for cloud resource management source: ./.github/plugins/azure-skills homepage: https://github.com/microsoft/azure-skills ``` @@ -71,22 +62,19 @@ marketplace: Note three things: - No `name`, `description`, or `version` inside `marketplace:` -- they are inherited from the `apm.yml` top level. -- `source: ./.github/plugins/azure-skills` is a local-path package: the plugin lives in this same repo. +- `source: ./.github/plugins/azure-skills` is a local-path entry: the plugin lives in this same repo. - No `tags:` -- empty/absent tags are omitted from `marketplace.json` to match Anthropic's canonical shape. -## Anthropic compliance +Build it: -`marketplace.json` produced by `apm marketplace build` conforms to [Anthropic's marketplace.json specification](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces). The compiler follows three rules: - -1. **`plugins:` is emitted verbatim.** APM does not rename, reorder, or decorate plugin entries. -2. **`metadata:` is an opaque pass-through.** Whatever you put under `marketplace.metadata:` in `apm.yml` is copied byte-for-byte into `marketplace.json`, preserving key casing (for example, `pluginRoot` stays `pluginRoot`). -3. **APM-only fields are stripped at compile time.** The `build:` block, per-package `version` ranges, `tag_pattern` overrides, and `include_prerelease` flags live only in `apm.yml`. They never leak into `marketplace.json`. - -APM does not emit a `versions[]` array. Each compiled plugin has exactly one resolved `source.ref` -- the latest commit SHA (or explicit ref) that satisfies the declared range at build time. Empty `tags:` and inherited `description`/`version` are omitted from output. +``` +$ apm pack +[+] Built marketplace.json (1 plugins) -> .claude-plugin/marketplace.json +``` ## The `marketplace:` schema -Full example with both remote and local packages: +Full example with both remote and local plugins: ```yaml name: my-project @@ -113,22 +101,22 @@ marketplace: homepage: https://example.com/plugins pluginRoot: ./plugins - packages: - - name: example-package - description: Example package consumers will see - source: acme-org/example-package + plugins: + - name: example-plugin + description: Example plugin consumers will see + source: acme-org/example-plugin version: "^1.0.0" - name: monorepo-tool - description: Package that lives in a subdirectory + description: Plugin that lives in a subdirectory source: acme-org/monorepo subdir: tools/monorepo-tool version: "~2.3.0" tag_pattern: "monorepo-tool-v{version}" - - name: pinned-package + - name: pinned-plugin description: Pinned to an explicit ref - source: acme-org/pinned-package + source: acme-org/pinned-plugin ref: 3f2a9b1c - name: local-tool @@ -145,10 +133,9 @@ marketplace: | `description` | no | Override the top-level `description`. Inherited when omitted. | | `version` | no | Override the top-level `version`. Inherited when omitted. | | `owner` | yes | Mapping with `name` (required), optional `url`, `email`. | -| `output` | no | Output path. Defaults to `.claude-plugin/marketplace.json`. | | `build` | no | APM-only build options. See below. | | `metadata` | no | Opaque pass-through copied into `marketplace.json`. | -| `packages` | no | List of package entries. | +| `plugins` | no | List of plugin entries. | When `name`/`description`/`version` are inherited (not overridden), they are also omitted from the generated `marketplace.json` top level so the artefact stays stable across unrelated bumps to `apm.yml`. @@ -160,29 +147,29 @@ When `name`/`description`/`version` are inherited (not overridden), they are als Stripped from `marketplace.json` at compile time. -### Package entries +### Plugin entries | Field | Required | Description | |-------|----------|-------------| | `name` | yes | Plugin name consumers will install. Unique within the marketplace. | -| `source` | yes | Either `/` (remote) or `./path/to/dir` (local-path package in this repo). | +| `source` | yes | Either `/` (remote) or `./path/to/dir` (local-path entry in this repo). | | `description` | no | Pass-through to `marketplace.json`. | | `homepage` | no | Pass-through URL. | | `tags` | no | Pass-through list of strings. Omitted from output when empty. | | `version` | conditional | Semver range (see below). Either `version` or `ref` must be set for remote sources. Local sources may set `version` to seed the compiled output. | | `ref` | conditional | Explicit SHA, tag, or branch. Takes precedence over `version`. Remote sources only. | | `subdir` | no | Subdirectory within a remote repo. Validated against path traversal. | -| `tag_pattern` | no | Per-package override of `build.tagPattern`. | +| `tag_pattern` | no | Per-plugin override of `build.tagPattern`. | | `include_prerelease` | no | Include semver pre-release tags in range resolution. Defaults to `false`. | Unknown keys inside `marketplace:` raise a schema error rather than being silently ignored. -### Local-path packages +### Local-path entries -When `source` starts with `./`, the entry is a local-path package: APM does not run `git ls-remote`, does not resolve a SHA, and emits the path verbatim into `marketplace.json` as a plain string source. Use this for plugins that ship in the same repository as the marketplace itself (the azure-skills pattern). +When `source` starts with `./`, the entry is a local-path plugin: APM does not run `git ls-remote`, does not resolve a SHA, and emits the path verbatim into `marketplace.json` as a plain string source. Use this for plugins that ship in the same repository as the marketplace itself (the azure-skills pattern). ```yaml -packages: +plugins: - name: local-tool source: ./plugins/local-tool description: Vendored alongside this marketplace @@ -199,9 +186,62 @@ Both `apm.yml` and the generated `.claude-plugin/marketplace.json` must be track !.claude-plugin/marketplace.json ``` +## The build flow + +`apm pack` reads `apm.yml`, resolves each remote plugin against `git ls-remote`, leaves local-path entries untouched, and writes `.claude-plugin/marketplace.json` atomically (temp file plus rename). + +``` +$ apm pack +``` + +Marketplace-relevant flags: + +| Flag | Description | +|------|-------------| +| `--dry-run` | Resolve and print the result table, but do not write `marketplace.json`. | +| `--offline` | Use only cached refs; fail entries that need a fresh `git ls-remote`. | +| `--include-prerelease` | Allow pre-release tags to satisfy every range (overrides per-entry flag). | +| `--marketplace-output PATH` | Override the output path. Default: `.claude-plugin/marketplace.json`. | +| `-v`, `--verbose` | Include per-entry resolution detail. | + +`apm pack` also accepts bundle flags (`--format`, `--target`, `--archive`, `-o`, `--force`); they are silent no-ops in a marketplace-only project. See [`apm pack` reference](../../reference/cli-commands/#apm-pack---pack-distributable-artifacts) for the full list. + +The default output path matches Anthropic's convention -- Claude Code reads `.claude-plugin/marketplace.json` from the repo root. Override with `--marketplace-output PATH` only when you need to inspect a build without touching the committed file: + +```bash +apm pack --marketplace-output ./build/marketplace.json --dry-run +``` + +### What the compiler does + +1. Parses and validates the `marketplace:` block. Unknown keys or invalid semver is a schema error (exit 2). +2. For each remote plugin: runs `git ls-remote`, enumerates tags and branches, filters by the entry's tag pattern, resolves the version range, picks the highest match. +3. For each local-path plugin: emits the path verbatim, no resolution. +4. Walks `metadata:` unchanged into the output. +5. Emits `plugins:` with the Anthropic key name; each entry carries the resolved `source` plus any pass-through fields. Inherited top-level fields and empty `tags:` are omitted. +6. Writes the file atomically. + +### Exit codes + +| Code | Meaning | +|------|---------| +| `0` | Build succeeded; `marketplace.json` written (or previewed). | +| `1` | Build error -- network failure, ref not found, no tag matches the range, etc. | +| `2` | Schema error in the `marketplace:` block. | + +### Anthropic compliance + +`marketplace.json` produced by `apm pack` follows three rules: + +1. **`plugins:` is emitted verbatim.** APM does not rename, reorder, or decorate plugin entries. +2. **`metadata:` is an opaque pass-through.** Whatever you put under `marketplace.metadata:` in `apm.yml` is copied byte-for-byte into `marketplace.json`, preserving key casing (for example, `pluginRoot` stays `pluginRoot`). +3. **APM-only fields are stripped at compile time.** The `build:` block, per-plugin `version` ranges, `tag_pattern` overrides, and `include_prerelease` flags live only in `apm.yml`. They never leak into `marketplace.json`. + +APM does not emit a `versions[]` array. Each compiled plugin has exactly one resolved `source.ref` -- the latest commit SHA (or explicit ref) that satisfies the declared range at build time. Empty `tags:` and inherited `description`/`version` are omitted from output. + ## Migrating from `marketplace.yml` -Earlier APM versions stored the same configuration in a standalone `marketplace.yml`. That file is now deprecated. APM still loads it for one release, prints a deprecation warning, and exits with an error if both files are present at once. +Earlier APM versions stored this configuration in a standalone `marketplace.yml`. That file is deprecated. APM still reads it (with a warning) when no `marketplace:` block is present in `apm.yml`, but `apm marketplace init` no longer creates one. Both files present at once is a hard error. Run the one-shot migration: @@ -210,9 +250,7 @@ apm marketplace migrate # preview the new apm.yml block apm marketplace migrate --yes # apply: rewrite apm.yml, delete marketplace.yml ``` -Flags: `--dry-run` shows the diff without writing; `--force`, `--yes`, and `-y` are accepted as equivalent overrides for an existing `marketplace:` block in `apm.yml`. - -After migration, commit `apm.yml` (and the deleted `marketplace.yml`) to record the consolidation. +`--force`, `--yes`, and `-y` are equivalent overrides for an existing `marketplace:` block in `apm.yml`. After migration, commit `apm.yml` (and the deleted `marketplace.yml`). ## Version ranges @@ -228,14 +266,14 @@ APM uses npm-compatible semver ranges. The most common forms: | `1.x` or `1.*` | Any 1.y.z. | | `>=1.2.0 <2.0.0` | AND-combination. | -Pre-release tags (for example `1.2.0-beta.1`) are excluded by default. Set `include_prerelease: true` on the entry, or pass `--include-prerelease` to the build command, to include them. +Pre-release tags (for example `1.2.0-beta.1`) are excluded by default. Set `include_prerelease: true` on the entry, or pass `--include-prerelease` to `apm pack`, to include them. Pin to a non-semver ref when you need exact reproducibility: ```yaml -packages: - - name: pinned-package - source: acme-org/pinned-package +plugins: + - name: pinned-plugin + source: acme-org/pinned-plugin ref: 3f2a9b1cdeadbeef # SHA, tag, or branch -- overrides version ranges ``` @@ -243,9 +281,9 @@ packages: ## Managing plugins -Three subcommands let you manage entries in `marketplace.packages` without hand-editing YAML. +Three subcommands let you manage entries in `marketplace.plugins` without hand-editing YAML. -### Adding a package +### Adding a plugin ```bash apm marketplace package add microsoft/apm-sample-package \ @@ -253,19 +291,19 @@ apm marketplace package add microsoft/apm-sample-package \ --description "Sample package" ``` -`package add` takes a `/` source, derives the package name from the repo, and appends an entry to `marketplace.packages` in `apm.yml`. Pass `--name` to override the derived name, `--subdir` for monorepo paths, `--tag-pattern` for non-default tag layouts, or `--tags` to attach metadata tags. By default the command verifies the source is reachable via `git ls-remote`; pass `--no-verify` to skip that check. +`package add` takes a `/` source, derives the plugin name from the repo, and appends an entry to `marketplace.plugins` in `apm.yml`. Pass `--name` to override the derived name, `--subdir` for monorepo paths, `--tag-pattern` for non-default tag layouts, or `--tags` to attach metadata tags. By default the command verifies the source is reachable via `git ls-remote`; pass `--no-verify` to skip that check. `--version` and `--ref` are mutually exclusive -- use `--ref` to pin an exact SHA, tag, or branch instead of a semver range. -### Updating a package +### Updating a plugin ```bash apm marketplace package set apm-sample-package --version ">=2.0.0" ``` -`package set` takes the package name (not the source) and updates the specified fields in place. +`package set` takes the plugin name (not the source) and updates the specified fields in place. -### Removing a package +### Removing a plugin ```bash apm marketplace package remove apm-sample-package --yes @@ -273,38 +311,6 @@ apm marketplace package remove apm-sample-package --yes `package remove` drops the named entry. Without `--yes` the command prompts for confirmation. -## The build flow - -`apm marketplace build` reads `apm.yml`, resolves each remote package against `git ls-remote`, leaves local-path packages untouched, and writes `.claude-plugin/marketplace.json` atomically (temp file plus rename). - -``` -apm marketplace build -``` - -| Flag | Description | -|------|-------------| -| `--dry-run` | Resolve and print the result table, but do not write `marketplace.json`. | -| `--offline` | Use only cached refs; fail entries that need a fresh `git ls-remote`. | -| `--include-prerelease` | Allow pre-release tags to satisfy every range (overrides per-entry flag). | -| `-v`, `--verbose` | Include per-entry resolution detail. | - -### Exit codes - -| Code | Meaning | -|------|---------| -| `0` | Build succeeded; `marketplace.json` written (or previewed). | -| `1` | Build error -- network failure, ref not found, no tag matches the range, etc. | -| `2` | Schema error in the `marketplace:` block. | - -### What the compiler does - -1. Parses and validates the `marketplace:` block. Unknown keys or invalid semver is a schema error (exit 2). -2. For each remote package: runs `git ls-remote`, enumerates tags and branches, filters by the entry's tag pattern, resolves the version range, picks the highest match. -3. For each local-path package: emits the path verbatim, no resolution. -4. Walks `metadata:` unchanged into the output. -5. Emits `plugins:` with the Anthropic key name; each entry carries the resolved `source` plus any pass-through fields. Inherited top-level fields and empty `tags:` are omitted. -6. Writes the file atomically. - ## Checking and troubleshooting ### `apm marketplace check` @@ -326,14 +332,14 @@ Checks the environment -- git version, network reachability of common hosts, `gh apm marketplace doctor ``` -Run it first when `build` or `publish` fails in an unfamiliar environment. +Run it first when `apm pack` or `publish` fails in an unfamiliar environment. ### Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `Both apm.yml ... and marketplace.yml exist` | Legacy file lingered after edits to apm.yml. | Run `apm marketplace migrate --yes` (or delete `marketplace.yml` if apm.yml is already the source of truth). | -| `'packages[0].source' must match ...` | `source` is a full URL or contains a path. | Use `owner/repo`, or `./path` for a local entry, and put repo paths under `subdir:`. | +| `'plugins[0].source' must match ...` | `source` is a full URL or contains a path. | Use `owner/repo`, or `./path` for a local entry, and put repo paths under `subdir:`. | | `No tag matching '^1.0.0'` | No published tags satisfy the range under your tag pattern. | Loosen the range, check `tag_pattern`, or pin with `ref:`. | | `Ref 'main' not found` | Branch or tag does not exist upstream. | Verify with `git ls-remote `. | | `Pre-release tags skipped` | Latest published tag is a pre-release. | Set `include_prerelease: true` on the entry or pass `--include-prerelease`. | @@ -342,18 +348,18 @@ Run it first when `build` or `publish` fails in an unfamiliar environment. ### GitHub Enterprise Server -`apm marketplace build` respects the `GITHUB_HOST` environment variable. Set it before building to resolve packages from a GHES instance: +`apm pack` respects the `GITHUB_HOST` environment variable. Set it before building to resolve plugins from a GHES instance: ```bash export GITHUB_HOST=github.company.com -apm marketplace build +apm pack ``` Token resolution and metadata fetch use the same host, so existing auth configuration (see [Authentication](../../getting-started/authentication/)) works automatically. `git ls-remote` calls are authenticated with the resolved token, so private GHES repos work without a separate git credential helper. ## Discovering upgrades -`apm marketplace outdated` compares the currently resolved version of each package (as captured in `marketplace.json`) against the latest tag available in the source repo. +`apm marketplace outdated` compares the currently resolved version of each plugin (as captured in `marketplace.json`) against the latest tag available in the source repo. ```bash apm marketplace outdated @@ -361,9 +367,9 @@ apm marketplace outdated --include-prerelease apm marketplace outdated --offline ``` -Output columns: package, current version, declared range, latest in range, latest overall. Packages whose "latest overall" exceeds "latest in range" need a **manual range bump** (for example, widening `^1.0.0` to `^2.0.0`) before a new build will pick them up. This is intentional -- major-version bumps are a maintainer decision. +Output columns: plugin, current version, declared range, latest in range, latest overall. Plugins whose "latest overall" exceeds "latest in range" need a **manual range bump** (for example, widening `^1.0.0` to `^2.0.0`) before the next `apm pack` will pick them up. This is intentional -- major-version bumps are a maintainer decision. -Packages pinned with `ref:` and local-path packages show `--` in the range columns; `outdated` cannot reason about them. +Plugins pinned with `ref:` and local-path entries show `--` in the range columns; `outdated` cannot reason about them. ## Publishing to consumers @@ -371,7 +377,7 @@ Packages pinned with `ref:` and local-path packages show `--` in the range colum You need: -1. A built `marketplace.json` on the current branch (run `apm marketplace build` first). +1. A built `marketplace.json` on the current branch (run `apm pack` first). 2. A `consumer-targets.yml` file listing the repos to update. 3. The [`gh` CLI](https://cli.github.com/) authenticated against GitHub (unless you use `--no-pr`). @@ -431,11 +437,11 @@ Publish runs append to `.apm/publish-state.json`, which records the history of r ### Custom tag pattern -Projects that prefix tags with a package name (common in monorepos) need a per-entry pattern: +Projects that prefix tags with a plugin name (common in monorepos) need a per-entry pattern: ```yaml marketplace: - packages: + plugins: - name: ui-components source: acme-org/frontend-monorepo subdir: packages/ui-components @@ -443,17 +449,17 @@ marketplace: tag_pattern: "ui-components-v{version}" ``` -The `{name}` placeholder resolves to the package entry's `name`, so you can also write `tag_pattern: "{name}-v{version}"` and reuse a single `build.tagPattern`. +The `{name}` placeholder resolves to the plugin entry's `name`, so you can also write `tag_pattern: "{name}-v{version}"` and reuse a single `build.tagPattern`. ### Pre-release tags are being skipped -Set `include_prerelease: true` on the package entry, or pass `--include-prerelease` to `build` and `outdated` for the whole marketplace: +Set `include_prerelease: true` on the entry, or pass `--include-prerelease` to `apm pack` and `apm marketplace outdated` for the whole marketplace: ```yaml marketplace: - packages: - - name: example-package - source: acme-org/example-package + plugins: + - name: example-plugin + source: acme-org/example-plugin version: ">=1.0.0-0" include_prerelease: true ``` @@ -462,11 +468,11 @@ Note the `-0` pre-release suffix on the range -- it makes the lower bound inclus ### Can I use a non-GitHub host? -Not in the first release. `apm marketplace publish` uses the `gh` CLI and assumes GitHub for PR creation. You can still `build` and `check` against any git remote that speaks `git ls-remote` over HTTPS or SSH; only the `publish` step is GitHub-specific. For non-GitHub consumers, run `publish --no-pr` and drive the PR creation through your own tooling. +Not in the first release. `apm marketplace publish` uses the `gh` CLI and assumes GitHub for PR creation. You can still pack and `check` against any git remote that speaks `git ls-remote` over HTTPS or SSH; only `publish` is GitHub-specific. For non-GitHub consumers, run `publish --no-pr` and drive PR creation through your own tooling. ## Related reading - [Marketplaces guide](../marketplaces/) -- consumer-side: registering and installing from a marketplace. -- [CLI command reference](../../reference/cli-commands/) -- authoritative options for every `apm marketplace` subcommand. +- [CLI command reference](../../reference/cli-commands/) -- authoritative options for `apm pack` and every `apm marketplace` subcommand. - [Manifest schema](../../reference/manifest-schema/) -- the `apm.yml` shape including the `marketplace:` block. - [Plugins guide](../plugins/) -- what a plugin is and how consumers install one. diff --git a/docs/src/content/docs/reference/cli-commands.md b/docs/src/content/docs/reference/cli-commands.md index 732971f3e..4ca1a4163 100644 --- a/docs/src/content/docs/reference/cli-commands.md +++ b/docs/src/content/docs/reference/cli-commands.md @@ -36,7 +36,7 @@ apm init [PROJECT_NAME] [OPTIONS] **Options:** - `-y, --yes` - Skip interactive prompts and use auto-detected defaults - `--plugin` - Initialize as a plugin authoring project (creates `plugin.json` + `apm.yml` with `devDependencies`) -- `--marketplace` - Seed `apm.yml` with a `marketplace:` authoring block (requires `apm experimental enable marketplace-authoring`). See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). +- `--marketplace` - Seed `apm.yml` with a `marketplace:` authoring block. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). **Examples:** ```bash @@ -556,51 +556,73 @@ apm policy status --policy-source ./draft-policy.yml apm policy status --check ``` -### `apm pack` - Create a portable bundle +### `apm pack` - Pack distributable artifacts -Create a self-contained bundle from installed APM dependencies using the `deployed_files` recorded in `apm.lock.yaml` as the source of truth. +Pack distributable artifacts from your APM project. The manifest drives what gets produced: + +- `dependencies:` block in `apm.yml` -> bundle (directory or `.tar.gz`) +- `marketplace:` block in `apm.yml` -> `.claude-plugin/marketplace.json` +- both blocks present -> both artifacts in a single run + +The lockfile (`apm.lock.yaml`) pins bundle contents. An enriched copy is embedded in each bundle. ```bash apm pack [OPTIONS] ``` **Options:** -- `-o, --output PATH` - Output directory (default: `./build`) -- `-t, --target [copilot|vscode|claude|cursor|codex|opencode|gemini|all]` - Filter files by target. Accepts comma-separated values for multiple targets (e.g., `-t claude,copilot`). Auto-detects from `apm.yml` if not specified. `vscode` is an alias for `copilot` -- `--archive` - Produce a `.tar.gz` archive instead of a directory -- `--dry-run` - List files that would be packed without writing anything -- `--format [apm|plugin]` - Bundle format (default: `apm`). `plugin` produces a standalone plugin directory with `plugin.json` -- `--force` - On collision (plugin format), last writer wins instead of first +- `-o, --output PATH` - Bundle output directory (default: `./build`). Does not affect `marketplace.json` path. +- `-t, --target [copilot|vscode|claude|cursor|codex|opencode|gemini|all]` - Filter bundle files by target. Accepts comma-separated values (e.g., `-t claude,copilot`). Auto-detects from `apm.yml` if omitted. `vscode` is an alias for `copilot`. No-op for marketplace output. +- `--archive` - Produce a `.tar.gz` archive instead of a directory. Bundle only. +- `--format [apm|plugin]` - Bundle format (default: `apm`). `plugin` produces a standalone plugin directory with `plugin.json`. No-op for marketplace output. +- `--force` - On collision (plugin format), last writer wins instead of first. Bundle only. +- `--dry-run` - Preview outputs without writing anything. +- `--offline` - Marketplace: use cached refs only (skip `git ls-remote`). +- `--include-prerelease` - Marketplace: allow pre-release tags to satisfy version ranges. +- `--marketplace-output PATH` - Marketplace: override the output path (default: `.claude-plugin/marketplace.json`). +- `-v, --verbose` - Detailed output from every producer. + +Flags whose scope does not match the detected outputs are silent no-ops, not errors. CI scripts can pass `--offline` unconditionally even when some projects only produce a bundle. + +**Exit codes:** +- `0` - Success +- `1` - Build or runtime error (network failure, ref not found, no tag matches a range, etc.) +- `2` - Schema validation error in `apm.yml` **Examples:** ```bash -# Pack to ./build/-/ +# Bundle only (apm.yml has dependencies:, no marketplace:) apm pack +apm pack --target claude --archive +apm pack --format plugin -o ./dist -# Pack as a .tar.gz archive -apm pack --archive - -# Pack only VS Code / Copilot files -apm pack --target vscode - -# Export as a standalone plugin directory -apm pack --format plugin +# Marketplace only (apm.yml has marketplace:, no dependencies:) +apm pack +apm pack --offline --dry-run -# Preview what would be packed -apm pack --dry-run +# Both blocks present -- one command, both artifacts +apm pack +apm pack --archive --offline -# Custom output directory -apm pack -o dist/ +# Override marketplace.json path (rare; default matches Anthropic spec) +apm pack --marketplace-output ./build/marketplace.json ``` -**Behavior:** +**Bundle behaviour:** - Reads `apm.lock.yaml` to enumerate all `deployed_files` from installed dependencies - Scans files for hidden Unicode characters before bundling — warns if findings are detected (non-blocking; consumers are protected by `apm install`/`apm unpack` which block on critical) - Copies files preserving directory structure - Writes an enriched `apm.lock.yaml` inside the bundle with a `pack:` metadata section (the project's own `apm.lock.yaml` is never modified) - **Plugin format** (`--format plugin`): Remaps `.apm/` content into plugin-native paths (`agents/`, `skills/`, `commands/`, etc.), generates or updates `plugin.json`, merges hooks into a single `hooks.json`. `devDependencies` are also excluded from plugin bundles. See [Pack & Distribute](../../guides/pack-distribute/#plugin-format) for the full mapping table -**Target filtering:** +**Marketplace behaviour:** +- Reads the `marketplace:` block from `apm.yml` (falls back to legacy `marketplace.yml` with a deprecation warning when no block is present; both files present is a hard error) +- Resolves each remote plugin's version range against `git ls-remote`; emits local-path entries verbatim +- Writes `.claude-plugin/marketplace.json` atomically -- this is where Claude Code reads the file from the repo root +- Creates `.claude-plugin/` if absent; never scaffolds other files there +- See the [Authoring a marketplace guide](../../guides/marketplace-authoring/) for the full schema and workflow + +**Bundle target filtering:** | Target | Includes paths starting with | |--------|------------------------------| @@ -1263,7 +1285,7 @@ apm marketplace validate acme-plugins --verbose #### `apm marketplace init` - Add a marketplace block to apm.yml -Add a `marketplace:` block to the project's `apm.yml`. If `apm.yml` is absent, a minimal one is scaffolded first. The block is richly commented and ready to be edited. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). +Add a `marketplace:` block to the project's `apm.yml`. If `apm.yml` is absent, a minimal one is scaffolded first. The block is richly commented and ready to be edited. Build the marketplace with [`apm pack`](#apm-pack---pack-distributable-artifacts). See the [Authoring a marketplace guide](../../guides/marketplace-authoring/). ```bash apm marketplace init [OPTIONS] @@ -1311,39 +1333,6 @@ apm marketplace migrate --dry-run apm marketplace migrate --yes ``` -#### `apm marketplace build` - Compile the marketplace block - -Resolve all package version ranges against the source repositories and write an Anthropic-compliant `.claude-plugin/marketplace.json`. APM-only fields (`build:`, version ranges, tag patterns) are stripped; `metadata:` is passed through verbatim. Inherited top-level fields and empty `tags:` are omitted from the output. - -Reads from `apm.yml`'s `marketplace:` block by default; falls back to a legacy `marketplace.yml` (with a deprecation warning) when no block is present. Errors out when both are present at once. - -```bash -apm marketplace build [OPTIONS] -``` - -**Options:** -- `--dry-run` - Resolve and print the result table, but do not write `marketplace.json` -- `--offline` - Use cached refs only (no `git ls-remote` calls) -- `--include-prerelease` - Allow pre-release tags to satisfy ranges -- `-v, --verbose` - Per-entry resolution detail - -**Exit codes:** -- `0` - Build succeeded (or dry run complete) -- `1` - Build error (network failure, unresolvable ref, no matching tag) -- `2` - Schema error in the `marketplace:` block - -**Examples:** -```bash -# Compile to .claude-plugin/marketplace.json -apm marketplace build - -# Preview without writing -apm marketplace build --dry-run - -# Offline build against cached refs -apm marketplace build --offline -``` - #### `apm marketplace outdated` - Report available upgrades List packages in the `marketplace:` block whose source repositories have newer tags available. Range-aware: distinguishes "latest in range" (picked up by next `build`) from "latest overall" (requires a manual range bump). Local-path packages and `ref:`-pinned entries show `--` in the range columns. @@ -1393,7 +1382,7 @@ apm marketplace check --offline #### `apm marketplace doctor` - Environment diagnostics -Check git, network reachability, authentication, `gh` CLI availability, and the presence of a marketplace config (in `apm.yml` or legacy `marketplace.yml`). Run this first when `build` or `publish` fails in an unfamiliar environment. +Check git, network reachability, authentication, `gh` CLI availability, and the presence of a marketplace config (in `apm.yml` or legacy `marketplace.yml`). Run this first when `apm pack` or `publish` fails in an unfamiliar environment. ```bash apm marketplace doctor [OPTIONS] @@ -1414,7 +1403,7 @@ apm marketplace doctor --verbose #### `apm marketplace publish` - Open PRs on consumer repositories -Drive the compiled `marketplace.json` out to consumer repositories listed in a `consumer-targets.yml` file, opening a pull request on each. Requires an authenticated `gh` CLI unless `--no-pr` is used. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/#publishing-to-consumers) for the full workflow. +Drive the compiled `marketplace.json` out to consumer repositories listed in a `consumer-targets.yml` file, opening a pull request on each. Requires an authenticated `gh` CLI unless `--no-pr` is used. Run `apm pack` first to (re)build `marketplace.json`. See the [Authoring a marketplace guide](../../guides/marketplace-authoring/#publishing-to-consumers) for the full workflow. ```bash apm marketplace publish [OPTIONS] diff --git a/docs/src/content/docs/reference/experimental.md b/docs/src/content/docs/reference/experimental.md index c49ddfdd3..4e75ff147 100644 --- a/docs/src/content/docs/reference/experimental.md +++ b/docs/src/content/docs/reference/experimental.md @@ -171,7 +171,6 @@ apm experimental reset verbose-version |-----------------------|----------------------------------------------------------------------------------| | `verbose-version` | Show Python version, platform, and install path in `apm --version`. | | `copilot-cowork` | Deploy APM skills to Microsoft 365 Copilot Cowork via OneDrive. | -| `marketplace-authoring`| Enable marketplace authoring commands (init, build, publish, etc.). | New flags are proposed via [CONTRIBUTING.md](https://github.com/microsoft/apm/blob/main/CONTRIBUTING.md#how-to-add-an-experimental-feature-flag) and graduate to default when stable. See the contributor recipe for the full lifecycle. See also: [Cowork integration](../integrations/copilot-cowork/). diff --git a/docs/src/content/docs/reference/manifest-schema.md b/docs/src/content/docs/reference/manifest-schema.md index 17f874e1d..d43f3914b 100644 --- a/docs/src/content/docs/reference/manifest-schema.md +++ b/docs/src/content/docs/reference/manifest-schema.md @@ -60,7 +60,7 @@ policy: marketplace: # OPTIONAL; marketplace authoring ``` -`marketplace:` is the source for `apm marketplace build` and is OPTIONAL. Repositories that do not publish a marketplace omit it entirely. The block, its schema, and the build flow are documented in the [Authoring a marketplace guide](../../guides/marketplace-authoring/). Within `marketplace:`, the inheritable fields `name`, `description`, and `version` default to the top-level values above and SHOULD be omitted unless an override is required. +`marketplace:` is the source for `apm pack`'s marketplace output and is OPTIONAL. Repositories that do not publish a marketplace omit it entirely. The block, its schema, and the build flow are documented in the [Authoring a marketplace guide](../../guides/marketplace-authoring/). Within `marketplace:`, the inheritable fields `name`, `description`, and `version` default to the top-level values above and SHOULD be omitted unless an override is required. --- diff --git a/packages/apm-guide/.apm/skills/apm-usage/authentication.md b/packages/apm-guide/.apm/skills/apm-usage/authentication.md index 13b89459a..27a7d3f05 100644 --- a/packages/apm-guide/.apm/skills/apm-usage/authentication.md +++ b/packages/apm-guide/.apm/skills/apm-usage/authentication.md @@ -86,7 +86,7 @@ modified -- on failure you see `No files were modified`. export GITHUB_HOST=github.company.com export GITHUB_APM_PAT_MYORG=ghp_ghes_token apm install myorg/internal-package # resolves to github.company.com -apm marketplace build # also resolves to github.company.com +apm pack # marketplace.json also resolves against github.company.com ``` ## GHE Cloud data residency (*.ghe.com) diff --git a/packages/apm-guide/.apm/skills/apm-usage/commands.md b/packages/apm-guide/.apm/skills/apm-usage/commands.md index 27b151185..020ecf227 100644 --- a/packages/apm-guide/.apm/skills/apm-usage/commands.md +++ b/packages/apm-guide/.apm/skills/apm-usage/commands.md @@ -4,7 +4,7 @@ | Command | Purpose | Key flags | |---------|---------|-----------| -| `apm init [NAME]` | Initialize a new APM project | `-y` skip prompts, `--plugin` plugin authoring mode, `--marketplace` seed apm.yml with a `marketplace:` block (experimental) | +| `apm init [NAME]` | Initialize a new APM project | `-y` skip prompts, `--plugin` plugin authoring mode, `--marketplace` seed apm.yml with a `marketplace:` block | ## Dependency management @@ -45,18 +45,16 @@ | Command | Purpose | Key flags | |---------|---------|-----------| -| `apm pack` | Bundle package for distribution | `-o PATH`, `-t TARGET`, `--archive`, `--dry-run`, `--format [apm\|plugin]`, `--force` | +| `apm pack` | Build distributable artifacts (bundle and/or marketplace.json -- driven by `apm.yml`) | `-o PATH`, `-t TARGET`, `--archive`, `--dry-run`, `--format [apm\|plugin]`, `--force`, `--offline`, `--include-prerelease`, `--marketplace-output PATH` | | `apm unpack BUNDLE` | Extract a bundle | `-o PATH`, `--skip-verify`, `--force`, `--dry-run` | -## Marketplace (experimental — authoring only) - -> **Authoring commands gated behind `apm experimental enable marketplace-authoring`**. Consumer commands (add, list, browse, update, remove, validate, search) are always available. +## Marketplace (consumer) | Command | Purpose | Key flags | |---------|---------|-----------| | `apm marketplace add OWNER/REPO` | Register a marketplace | `-n NAME`, `-b BRANCH`, `--host HOST` | | `apm marketplace list` | List registered marketplaces | -- | -| `apm marketplace browse NAME` | Browse marketplace packages | -- | +| `apm marketplace browse NAME` | Browse marketplace plugins | -- | | `apm marketplace update [NAME]` | Update marketplace index | -- | | `apm marketplace remove NAME` | Remove a marketplace | `-y` skip confirm | | `apm marketplace validate NAME` | Validate marketplace manifest | `--check-refs`, `-v` | @@ -64,26 +62,23 @@ | `apm install NAME@MKT[#ref]` | Install from marketplace | Optional `#ref` override | | `apm view NAME@MARKETPLACE` | View marketplace plugin info | -- | -## Marketplace authoring (experimental) +## Marketplace authoring -> **Authoring is gated** behind `apm experimental enable marketplace-authoring`. Consumer commands (add, list, browse, update, remove, validate, search) are always available. -> -> Source of truth is the `marketplace:` block in `apm.yml`. The legacy standalone `marketplace.yml` is deprecated and slated for removal next minor; use `apm marketplace migrate` to fold it in. +> Source of truth is the `marketplace:` block in `apm.yml`. `apm pack` produces `.claude-plugin/marketplace.json` whenever that block is present. The legacy standalone `marketplace.yml` is deprecated -- use `apm marketplace migrate` to fold it in. | Command | Purpose | Key flags | |---------|---------|-----------| -| `apm marketplace init` | Add a `marketplace:` block to `apm.yml` (scaffolds `apm.yml` if absent) | `--force`, `--no-gitignore-check`, `--name`, `--owner` | +| `apm marketplace init` | Append a `marketplace:` block to `apm.yml` and create `.claude-plugin/` | `--force`, `--no-gitignore-check`, `--name`, `--owner` | | `apm marketplace migrate` | Fold a legacy `marketplace.yml` into `apm.yml`'s `marketplace:` block; deletes `marketplace.yml` on success | `--force`/`--yes`/`-y`, `--dry-run`, `-v` | -| `apm marketplace build` | Compile `marketplace:` to Anthropic-compliant `.claude-plugin/marketplace.json` | `--dry-run`, `--offline`, `--include-prerelease`, `-v` | -| `apm marketplace outdated` | Report upgradable packages, range-aware | `--offline`, `--include-prerelease`, `-v` | +| `apm marketplace outdated` | Report upgradable plugins, range-aware | `--offline`, `--include-prerelease`, `-v` | | `apm marketplace check` | Validate the `marketplace:` block and verify refs resolve | `--offline`, `-v` | | `apm marketplace doctor` | Diagnose git, network, auth, and marketplace config readiness | `-v` | | `apm marketplace publish` | Open PRs on consumer repos from `consumer-targets.yml` | `--targets PATH`, `--dry-run`, `--no-pr`, `--draft`, `--allow-downgrade`, `--allow-ref-change`, `--parallel N`, `-y` | -| `apm marketplace package add ` | Add a package entry to `marketplace.packages` (source accepts `owner/repo` or `./path`) | `--name`, `--version`, `--ref` (mutable refs auto-resolved to SHA), `-d`/`--description`, `-s`/`--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease`, `--no-verify` | -| `apm marketplace package set ` | Update fields on an existing package entry | `--version`, `--ref` (mutable refs auto-resolved to SHA), `--description`, `--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease` | -| `apm marketplace package remove ` | Remove a package entry from `marketplace.packages` | `--yes` | +| `apm marketplace package add ` | Add a plugin entry to `marketplace.plugins` (source accepts `owner/repo` or `./path`) | `--name`, `--version`, `--ref` (mutable refs auto-resolved to SHA), `-d`/`--description`, `-s`/`--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease`, `--no-verify` | +| `apm marketplace package set ` | Update fields on an existing plugin entry | `--version`, `--ref` (mutable refs auto-resolved to SHA), `--description`, `--subdir`, `--tag-pattern`, `--tags`, `--include-prerelease` | +| `apm marketplace package remove ` | Remove a plugin entry from `marketplace.plugins` | `--yes` | -`apm init --marketplace` is the equivalent shortcut at project-creation time -- it seeds a fresh `apm.yml` with the `marketplace:` block already in place (also gated by the experimental flag). +To build the marketplace, run `apm pack` (it reads `apm.yml` and writes `.claude-plugin/marketplace.json` whenever the `marketplace:` block is present). `apm init --marketplace` is the equivalent shortcut at project-creation time -- it seeds a fresh `apm.yml` with the `marketplace:` block already in place. ## MCP servers diff --git a/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md b/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md index f9802d7ce..3e50bd4b6 100644 --- a/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md +++ b/packages/apm-guide/.apm/skills/apm-usage/package-authoring.md @@ -195,23 +195,20 @@ apm install org/my-package#v1.0.0 ## Marketplace authoring -A **marketplace** is a curated index of packages (plugins) that consumers -install via `apm install @`. Maintainers declare the -marketplace in a `marketplace:` block inside `apm.yml` and compile it to -an Anthropic-compliant `.claude-plugin/marketplace.json` with -`apm marketplace build`. Both files are committed. - -Authoring is gated behind `apm experimental enable marketplace-authoring`. +A **marketplace** is a curated index of plugins that consumers install via +`apm install @`. Maintainers declare the marketplace in a +`marketplace:` block inside `apm.yml`; running `apm pack` builds an +Anthropic-compliant `.claude-plugin/marketplace.json`. Both files are committed. ### When to run `apm marketplace init` - The user is setting up a new marketplace repository. - The user wants to convert an ad-hoc list of plugins into a proper index. -`apm marketplace init` adds a `marketplace:` block to the project's -`apm.yml` (and scaffolds a minimal `apm.yml` first if needed). Use -`apm init --marketplace` instead when starting a brand-new project that -will publish its own marketplace. +`apm marketplace init` appends a `marketplace:` block to the project's +`apm.yml` and creates `.claude-plugin/`. It does NOT scaffold a standalone +`marketplace.yml`. Use `apm init --marketplace` when starting a brand-new +project that will publish its own marketplace. ### apm.yml `marketplace:` block @@ -230,14 +227,14 @@ marketplace: tagPattern: "v{version}" metadata: # pass-through, copied verbatim homepage: https://example.com - packages: - - name: example-package - description: What this package does - source: acme-org/example-package # owner/repo (remote) + plugins: + - name: example-plugin + description: What this plugin does + source: acme-org/example-plugin # owner/repo (remote) version: "^1.0.0" # semver range OR 'ref:' below # ref: 3f2a9b1c # explicit SHA/tag/branch # subdir: tools/x # optional subdirectory - # tag_pattern: "{name}-v{version}" # optional per-package override + # tag_pattern: "{name}-v{version}" # optional per-plugin override # include_prerelease: false # optional - name: local-tool @@ -249,34 +246,41 @@ marketplace: Schema rules: - `owner.name` is required. `name`, `description`, `version` are optional inside the block (inherited from apm.yml top level). -- Each remote package needs either `version` or `ref`. +- Each remote plugin needs either `version` or `ref`. - `ref` takes precedence over `version`. -- `source: ./...` marks a local-path package: skips git resolution, +- `source: ./...` marks a local-path entry: skips git resolution, emits the path verbatim into `marketplace.json`. - Unknown keys raise a schema error -- do not invent fields. ### Build semantics -`apm marketplace build` runs `git ls-remote` against each remote package -source, picks the highest tag satisfying the range (under the applicable -`tagPattern`), leaves local-path entries untouched, and writes -`.claude-plugin/marketplace.json`. The compiler: +`apm pack` runs `git ls-remote` against each remote plugin source, picks the +highest tag satisfying the range (under the applicable `tagPattern`), leaves +local-path entries untouched, and writes `.claude-plugin/marketplace.json`. +The compiler: 1. Emits `plugins:` verbatim (Anthropic's key name). 2. Copies `metadata:` byte-for-byte. -3. Strips `build:`, per-package `version`, `tag_pattern`, `include_prerelease`. +3. Strips `build:`, per-plugin `version`, `tag_pattern`, `include_prerelease`. 4. Omits empty `tags:` and inherited top-level `description`/`version` from the output (matches Anthropic's canonical hand-authored shape, e.g. microsoft/azure-skills). 5. Does not emit `versions[]` -- each plugin carries a single resolved ref. +`apm pack` also produces a bundle if `apm.yml` declares `dependencies:`. With +only a `marketplace:` block present, bundle flags (`--archive`, `-o`, `--format`, +`--target`, `--force`) are silent no-ops. + +Marketplace-relevant flags on `apm pack`: `--dry-run`, `--offline`, +`--include-prerelease`, `--marketplace-output PATH`, `-v`. + Exit codes: `0` success, `1` build error, `2` schema error. ### Migrating from legacy `marketplace.yml` Earlier APM versions stored this configuration in a standalone -`marketplace.yml`. That file is deprecated and slated for removal next -minor. Run the one-shot migration: +`marketplace.yml`. That file is deprecated; `apm marketplace init` no longer +creates one. Run the one-shot migration: ```bash apm marketplace migrate --dry-run # preview the apm.yml change From dd5f558ba37fb058344f73a448ba77ca4912e5a8 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 17:26:10 +0200 Subject: [PATCH 5/6] feat(pack): unify apm pack to produce bundle and marketplace.json Reads apm.yml and detects which artifacts to produce based on the presence of 'dependencies:' (bundle) and 'marketplace:' (marketplace.json) blocks. A single 'apm pack' invocation now replaces the legacy 'apm marketplace build' subcommand. Changes: - New BuildOrchestrator (src/apm_cli/core/build_orchestrator.py) with pluggable ArtifactProducer protocol and BundleProducer + MarketplaceProducer implementations. - pack command gains --offline, --include-prerelease, and --marketplace-output flags. Help text documents exit codes. - 'apm marketplace build' is hard-removed: invoking it exits 2 with a one-line migration message. - 'marketplace_authoring' experimental flag deleted (GA). - 'apm marketplace init' and 'apm init --marketplace' next-step hints now point at 'apm pack'. - 'apm marketplace publish' error wording updated. - New tests: 14 orchestrator unit tests, 9 pack integration tests, and one byte-for-byte snapshot test against microsoft/azure-skills@bef1f05 (sha256 02f76bfc0e5bbf7fdf1de1dda1f84c4da6e986913b6647973c0ffe39c1d5003b). - Stale tests removed: test_marketplace_build.py, test_marketplace_gating.py, and the marketplace_authoring experimental-flag class. - CHANGELOG updated under Added / Changed / Removed. Validation: - 6706 unit + console tests pass (uv run pytest tests/unit tests/test_console.py) - 10 new integration tests pass - azure-skills snapshot proof matches byte-for-byte Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 12 +- src/apm_cli/commands/init.py | 9 - src/apm_cli/commands/marketplace.py | 193 +------- src/apm_cli/commands/marketplace_plugin.py | 2 - src/apm_cli/commands/pack.py | 224 ++++++--- src/apm_cli/core/build_orchestrator.py | 274 +++++++++++ src/apm_cli/core/experimental.py | 6 - src/apm_cli/marketplace/init_template.py | 4 +- .../.claude-plugin/marketplace.json | 15 + tests/fixtures/azure-skills/apm.yml | 13 + .../test_azure_skills_marketplace.py | 58 +++ tests/integration/test_pack_unified.py | 250 ++++++++++ tests/unit/commands/conftest.py | 49 +- tests/unit/commands/test_marketplace_build.py | 452 ------------------ .../unit/commands/test_marketplace_gating.py | 271 ----------- tests/unit/commands/test_marketplace_init.py | 2 +- .../unit/commands/test_marketplace_publish.py | 2 +- tests/unit/core/__init__.py | 0 tests/unit/core/test_build_orchestrator.py | 192 ++++++++ tests/unit/marketplace/conftest.py | 38 +- tests/unit/marketplace/test_review_fixes.py | 34 +- 21 files changed, 1015 insertions(+), 1085 deletions(-) create mode 100644 src/apm_cli/core/build_orchestrator.py create mode 100644 tests/fixtures/azure-skills/.claude-plugin/marketplace.json create mode 100644 tests/fixtures/azure-skills/apm.yml create mode 100644 tests/integration/test_azure_skills_marketplace.py create mode 100644 tests/integration/test_pack_unified.py delete mode 100644 tests/unit/commands/test_marketplace_build.py delete mode 100644 tests/unit/commands/test_marketplace_gating.py create mode 100644 tests/unit/core/__init__.py create mode 100644 tests/unit/core/test_build_orchestrator.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 776e0a90f..2f84cf5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Unified `apm pack` entrypoint**: a single command now reads `apm.yml` and produces both bundle (`./build//`) and `.claude-plugin/marketplace.json` when the corresponding blocks are present. Three new flags: `--offline` (forbid network calls during marketplace resolution), `--include-prerelease` (allow non-stable git refs), and `--marketplace-output PATH` (override the marketplace.json destination). (#722) - `marketplace:` block in `apm.yml` unifies catalog authoring with the manifest. (#1038) - `apm marketplace migrate` -- one-shot consolidation of legacy `marketplace.yml` into `apm.yml`. (#1038) - `apm init --marketplace` -- seeds a fresh `apm.yml` with a `marketplace:` authoring block. (#1038) @@ -20,11 +21,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- `apm marketplace build` reads from `apm.yml` when a `marketplace:` block is present; output moves to `.claude-plugin/marketplace.json`. (#1038) +- `apm marketplace init` and `apm init --marketplace` next-step hints now point at `apm pack` (was `apm marketplace build`). (#722) - **Manifest contract: invalid `target:` values now raise a parse error.** Previously, an unknown token (or a CSV string like `target: opencode,claude,copilot,agents` instead of the YAML list `target: [opencode, claude, copilot, agents]`) was silently ignored, leaving `apm install` and `apm compile` to exit 0 while deploying nothing. The shared parser used by `--target` now also validates `apm.yml`'s `target:`, so the same input resolves the same way at every entry point. **Migration:** three previously-silent inputs now fail loud -- (1) unknown tokens (`target: bogus` -> fix the typo), (2) empty values (`target: ""`, `target: []` -> remove the line if you meant auto-detect), (3) `all` mixed with other targets (`target: [all, claude]` -> use `all` alone). Omitting `target:` entirely still triggers auto-detection. (#820) - Rename `DownloadStrategyManager` to `DownloadDelegate` to better reflect Facade/Delegate pattern (#918) - Fix incorrect double-checked locking in marketplace registry `_load()` -- hold lock across full check+read+set (#918) +### Removed + +- **`apm marketplace build` command removed.** `apm pack` now produces marketplace.json directly. Invoking the old subcommand exits 2 with a one-line migration message pointing at `apm pack`. (#722) +- **`marketplace_authoring` experimental flag removed.** Marketplace authoring (init, package add, validate, publish, etc.) is now generally available with no opt-in. (#722) + ### Deprecated - Standalone `marketplace.yml` (still loaded; emits a deprecation warning; slated for removal in v0.13). (#1038) @@ -33,8 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `apm install` and `apm compile` no longer exit 0 with success messages when `target:` in `apm.yml` is a CSV string -- the value now parses identically to the same input on `--target`, and zero-target resolution surfaces a warning instead of a silent no-op. (#820) - Remove redundant `seen` set from `_scan_patterns()` discovery walk (#918) -- `apm marketplace build` now respects `GITHUB_HOST` for GitHub Enterprise repos -- ref resolution, token lookup, and metadata fetch all use the configured host instead of hardcoded `github.com`. `git ls-remote` is authenticated so private repos work without separate credential setup. (#1008) -- `apm marketplace build` now accepts multiple Git URL forms (GitHub, GHES, GitLab, Bitbucket, ADO, SSH) for `type: url` parsing via `DependencyReference.parse()`. Host resolution is still driven by `GITHUB_HOST`, so non-`github.com` hosts require `GITHUB_HOST` to be set accordingly. (#1008) +- `apm pack` (marketplace producer) now respects `GITHUB_HOST` for GitHub Enterprise repos -- ref resolution, token lookup, and metadata fetch all use the configured host instead of hardcoded `github.com`. `git ls-remote` is authenticated so private repos work without separate credential setup. (#1008) +- `apm pack` (marketplace producer) now accepts multiple Git URL forms (GitHub, GHES, GitLab, Bitbucket, ADO, SSH) for `type: url` parsing via `DependencyReference.parse()`. Host resolution is still driven by `GITHUB_HOST`, so non-`github.com` hosts require `GITHUB_HOST` to be set accordingly. (#1008) - **ADO Entra ID auth path no longer silently fails.** Bearer tokens from `az account get-access-token` are now correctly plumbed through validation (auth scheme, git env). Auth failures raise a typed `AuthenticationError` with an actionable 4-case diagnostic instead of the ambiguous "not accessible or doesn't exist" message. `apm install --update` runs a pre-flight auth check before modifying any files -- on failure it aborts with "No files were modified". (#1015) - Correct targeting of compiled artifacts so GEMINI.md is only created if requested (#1019) diff --git a/src/apm_cli/commands/init.py b/src/apm_cli/commands/init.py index a3416a4fa..ce8ef2924 100644 --- a/src/apm_cli/commands/init.py +++ b/src/apm_cli/commands/init.py @@ -117,7 +117,6 @@ def init(ctx, project_name, yes, plugin, marketplace_flag, verbose): # Append marketplace authoring block when requested. if marketplace_flag: - from ..core.experimental import is_enabled from ..marketplace.init_template import render_marketplace_block apm_yml_path = Path.cwd() / APM_YML_FILENAME try: @@ -136,14 +135,6 @@ def init(ctx, project_name, yes, plugin, marketplace_flag, verbose): symbol="warning", ) - if not is_enabled("marketplace_authoring"): - logger.warning( - "Marketplace authoring is gated behind the " - "'marketplace_authoring' experimental flag. " - "Enable it with: apm experimental enable marketplace-authoring", - symbol="warning", - ) - logger.success("APM project initialized successfully!") # Display created file info diff --git a/src/apm_cli/commands/marketplace.py b/src/apm_cli/commands/marketplace.py index 8a2f98516..dcedbce97 100644 --- a/src/apm_cli/commands/marketplace.py +++ b/src/apm_cli/commands/marketplace.py @@ -70,22 +70,25 @@ class MarketplaceGroup(click.Group): """Custom group that organises commands by audience.""" _consumer_commands = ["add", "list", "browse", "update", "remove", "validate"] - _authoring_commands = ["init", "build", "check", "outdated", "doctor", "publish", "package"] - - @staticmethod - def _authoring_visible() -> bool: - """Return True when authoring commands should appear in ``--help``.""" - try: - from ..core.experimental import is_enabled - - return is_enabled("marketplace_authoring") - except Exception: # noqa: BLE001 -- fail-open UI visibility check - return True # fail open — show commands if flag check fails + _authoring_commands = ["init", "check", "outdated", "doctor", "publish", "package"] + + def get_command(self, ctx, cmd_name): + # The 'build' subcommand was removed in favour of the unified + # 'apm pack' entrypoint. Surface a hard error with a migration + # hint rather than silently aliasing. + if cmd_name == "build": + raise click.UsageError( + "'apm marketplace build' was removed. Use 'apm pack' instead.\n" + "marketplace.json is now produced by 'apm pack' when " + "apm.yml has a 'marketplace:' block." + ) + return super().get_command(ctx, cmd_name) def format_commands(self, ctx, formatter): - sections = [("Consumer commands", self._consumer_commands)] - if self._authoring_visible(): - sections.append(("Authoring commands", self._authoring_commands)) + sections = [ + ("Consumer commands", self._consumer_commands), + ("Authoring commands", self._authoring_commands), + ] for section_name, cmd_names in sections: commands = [] @@ -187,30 +190,6 @@ def _find_duplicate_names(yml): return f"Duplicate names: {', '.join(duplicates)}" return "" -def _require_authoring_flag(): - """Exit with enablement hint if marketplace-authoring flag is disabled.""" - from ..core.experimental import is_enabled - - if not is_enabled("marketplace_authoring"): - _rich_warning( - "Marketplace authoring commands are experimental.", - symbol="warning", - ) - _rich_info( - "Enable with: apm experimental enable marketplace-authoring", - symbol="info", - ) - _rich_info( - "Learn more: apm experimental list", - symbol="info", - ) - _rich_info( - "Docs: https://microsoft.github.io/apm/guides/marketplace-authoring/", - symbol="info", - ) - sys.exit(1) - - @click.group(cls=MarketplaceGroup, help="Manage marketplaces for discovery and governance") @click.pass_context def marketplace(ctx): @@ -239,7 +218,6 @@ def marketplace(ctx): @click.option("--verbose", "-v", is_flag=True, help="Show detailed output") def init(force, no_gitignore_check, name, owner, verbose): """Scaffold a 'marketplace:' block in apm.yml (creates apm.yml if absent).""" - _require_authoring_flag() from ..marketplace.init_template import render_marketplace_block logger = CommandLogger("marketplace-init", verbose=verbose) @@ -330,7 +308,7 @@ def init(force, no_gitignore_check, name, owner, verbose): next_steps = [ "Edit the 'marketplace:' block in apm.yml to add your packages", - "Run 'apm marketplace build' to generate .claude-plugin/marketplace.json", + "Run 'apm pack' to generate .claude-plugin/marketplace.json", "Commit BOTH apm.yml and the generated marketplace.json", ] @@ -877,134 +855,6 @@ def validate(name, check_refs, verbose): # --------------------------------------------------------------------------- -@marketplace.command(help="Build marketplace.json from marketplace.yml") -@click.option("--dry-run", is_flag=True, help="Preview without writing marketplace.json") -@click.option("--offline", is_flag=True, help="Use cached refs only (no network)") -@click.option( - "--include-prerelease", is_flag=True, help="Include prerelease versions" -) -@click.option("--verbose", "-v", is_flag=True, help="Show detailed output") -def build(dry_run, offline, include_prerelease, verbose): - """Resolve packages and compile marketplace.json.""" - _require_authoring_flag() - logger = CommandLogger("marketplace-build", verbose=verbose) - - project_root, _config = _load_config_or_exit(logger) - - # Pick the right path for the builder constructor (shape-aware lazy load). - apm_path = project_root / "apm.yml" - legacy_path = project_root / "marketplace.yml" - yml_path = apm_path if _config.source_path == apm_path or \ - (apm_path.exists() and not legacy_path.exists()) else legacy_path - - try: - opts = BuildOptions( - dry_run=dry_run, - offline=offline, - include_prerelease=include_prerelease, - ) - builder = MarketplaceBuilder(yml_path, options=opts) - report = builder.build() - except MarketplaceYmlError as exc: - logger.error(f"marketplace config error: {exc}", symbol="error") - sys.exit(2) - except BuildError as exc: - _render_build_error(logger, exc) - logger.verbose_detail(traceback.format_exc()) - sys.exit(1) - except Exception as e: # noqa: BLE001 -- top-level command catch-all - logger.error(f"Build failed: {e}", symbol="error") - logger.verbose_detail(traceback.format_exc()) - sys.exit(1) - - # Render results table - _render_build_table(logger, report) - - # Surface duplicate-name warnings from the builder - for warn_msg in report.warnings: - logger.warning(warn_msg, symbol="warning") - - if dry_run: - logger.progress( - "Dry run -- marketplace.json not written", symbol="info" - ) - else: - logger.success( - f"Built marketplace.json ({len(report.resolved)} packages)", - symbol="check", - ) - - -def _render_build_error(logger, exc): - """Render a BuildError with actionable hints.""" - if isinstance(exc, GitLsRemoteError): - logger.error(exc.summary_text, symbol="error") - if exc.hint: - logger.progress(f"Hint: {exc.hint}", symbol="info") - elif isinstance(exc, NoMatchingVersionError): - logger.error(str(exc), symbol="error") - logger.progress( - "Check that your version range matches published tags.", - symbol="info", - ) - elif isinstance(exc, RefNotFoundError): - logger.error(str(exc), symbol="error") - logger.progress( - "Verify the ref is spelled correctly and the remote is reachable.", - symbol="info", - ) - elif isinstance(exc, HeadNotAllowedError): - logger.error(str(exc), symbol="error") - elif isinstance(exc, OfflineMissError): - logger.error(str(exc), symbol="error") - logger.progress( - "Run a build online first to populate the cache.", - symbol="info", - ) - else: - logger.error(f"Build failed: {exc}", symbol="error") - - -def _render_build_table(logger, report): - """Render the resolved-packages table (Rich with colorama fallback).""" - console = _get_console() - if not console: - # Colorama fallback - for pkg in report.resolved: - sha_short = pkg.sha[:8] if pkg.sha else "--" - ref_kind = "tag" if not pkg.ref.startswith("refs/heads/") else "branch" - logger.tree_item( - f" [+] {pkg.name} {pkg.ref} {sha_short} ({ref_kind})" - ) - return - - from rich.table import Table - from rich.text import Text - - table = Table( - title="Resolved Packages", - show_header=True, - header_style="bold cyan", - border_style="cyan", - ) - table.add_column("Status", style="green", no_wrap=True, width=6) - table.add_column("Package", style="bold white", no_wrap=True) - table.add_column("Version", style="cyan") - table.add_column("Commit", style="dim") - table.add_column("Ref Kind", style="white") - - for pkg in report.resolved: - sha_short = pkg.sha[:8] if pkg.sha else "--" - # Determine ref kind - ref_kind = "tag" - if pkg.ref and not parse_semver(pkg.ref.lstrip("vV")): - ref_kind = "ref" - table.add_row(Text("[+]"), pkg.name, pkg.ref, sha_short, ref_kind) - - console.print() - console.print(table) - - # --------------------------------------------------------------------------- # marketplace outdated # --------------------------------------------------------------------------- @@ -1018,7 +868,6 @@ def _render_build_table(logger, report): @click.option("--verbose", "-v", is_flag=True, help="Show detailed output") def outdated(offline, include_prerelease, verbose): """Compare installed versions against latest available tags.""" - _require_authoring_flag() logger = CommandLogger("marketplace-outdated", verbose=verbose) _, yml = _load_config_or_exit(logger) @@ -1275,7 +1124,6 @@ def _render_outdated_table(logger, rows): @click.option("--verbose", "-v", is_flag=True, help="Show detailed output") def check(offline, verbose): """Validate marketplace.yml and check each entry is resolvable.""" - _require_authoring_flag() logger = CommandLogger("marketplace-check", verbose=verbose) _, yml = _load_config_or_exit(logger) @@ -1454,7 +1302,6 @@ def _render_check_table(logger, results): @click.option("--verbose", "-v", is_flag=True, help="Show detailed output") def doctor(verbose): """Check git, network, auth, and marketplace.yml readiness.""" - _require_authoring_flag() logger = CommandLogger("marketplace-doctor", verbose=verbose) checks = [] @@ -1780,7 +1627,6 @@ def publish( verbose, ): """Publish marketplace updates to consumer repositories.""" - _require_authoring_flag() logger = CommandLogger("marketplace-publish", verbose=verbose) # ------------------------------------------------------------------ @@ -1794,7 +1640,7 @@ def publish( mkt_json_path = Path.cwd() / "marketplace.json" if not mkt_json_path.exists(): logger.error( - "marketplace.json not found. Run 'apm marketplace build' first.", + "marketplace.json not found. Run 'apm pack' first.", symbol="error", ) sys.exit(1) @@ -2252,7 +2098,6 @@ def search(expression, limit, verbose): @click.option("--verbose", "-v", is_flag=True, help="Show detailed output") def migrate(force, dry_run, verbose): """One-shot conversion from legacy marketplace.yml to apm.yml block.""" - _require_authoring_flag() logger = CommandLogger("marketplace-migrate", verbose=verbose) project_root = Path.cwd() diff --git a/src/apm_cli/commands/marketplace_plugin.py b/src/apm_cli/commands/marketplace_plugin.py index b1a040f65..d285ca990 100644 --- a/src/apm_cli/commands/marketplace_plugin.py +++ b/src/apm_cli/commands/marketplace_plugin.py @@ -234,9 +234,7 @@ def _resolve_ref( @click.group(help="Manage packages in marketplace.yml (add, set, remove)") def package(): """Add, update, or remove packages in marketplace.yml.""" - from ..commands.marketplace import _require_authoring_flag - _require_authoring_flag() # ------------------------------------------------------------------- diff --git a/src/apm_cli/commands/pack.py b/src/apm_cli/commands/pack.py index 5b40d072f..187c99901 100644 --- a/src/apm_cli/commands/pack.py +++ b/src/apm_cli/commands/pack.py @@ -5,96 +5,208 @@ import click -from ..bundle.packer import pack_bundle from ..bundle.unpacker import unpack_bundle +from ..core.build_orchestrator import ( + BuildError, + BuildOptions, + BuildOrchestrator, + OutputKind, +) from ..core.command_logger import CommandLogger from ..core.target_detection import TargetParamType -@click.command(name="pack", help="Create a self-contained bundle from installed dependencies") +_PACK_HELP = """\ +Pack distributable artifacts from your APM project. + +Reads apm.yml to decide what to produce: + + dependencies: block -> bundle (directory or .tar.gz) + marketplace: block -> .claude-plugin/marketplace.json + both blocks present -> both artifacts + +The lockfile (apm.lock.yaml) pins bundle contents. An enriched copy +is embedded in each bundle. + +Examples: + + # Bundle only (most common -- just dependencies: in apm.yml): + apm pack + apm pack --target claude --archive + apm pack --format plugin -o ./dist + + # Marketplace only (marketplace: in apm.yml, no dependencies:): + apm pack + apm pack --offline --dry-run + + # Both (apm.yml has dependencies: AND marketplace: blocks): + apm pack + apm pack --archive --offline + + # Override marketplace.json location: + apm pack --marketplace-output ./build/marketplace.json + +Exit codes: + 0 Success + 1 Build or runtime error + 2 Manifest schema validation error +""" + + +@click.command(name="pack", help=_PACK_HELP) @click.option( "--format", "fmt", type=click.Choice(["apm", "plugin"]), default="apm", - help="Bundle format", + help="Bundle format: 'apm' (default) for standard bundles, 'plugin' for standalone plugin directories with plugin.json.", ) @click.option( "--target", "-t", type=TargetParamType(), default=None, - help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified", + help="Target platform (comma-separated for multiple, e.g. claude,copilot). Use 'all' for every target. Auto-detects if not specified.", ) -@click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive") +@click.option("--archive", is_flag=True, default=False, help="Produce a .tar.gz archive instead of a directory.") @click.option( "-o", "--output", type=click.Path(), default="./build", - help="Output directory (default: ./build)", + help="Bundle output directory (default: ./build).", ) @click.option("--dry-run", is_flag=True, default=False, help="Show what would be packed without writing") -@click.option("--force", is_flag=True, default=False, help="On collision, last writer wins") -@click.option("--verbose", "-v", is_flag=True, help="Show detailed packing information") +@click.option("--force", is_flag=True, default=False, help="On collision (plugin format), last writer wins.") +@click.option("--verbose", "-v", is_flag=True, help="Show detailed packing information.") +@click.option( + "--offline", + is_flag=True, + default=False, + help="Marketplace: use cached refs, skip network.", +) +@click.option( + "--include-prerelease", + is_flag=True, + default=False, + help="Marketplace: include pre-release version tags.", +) +@click.option( + "--marketplace-output", + "marketplace_output", + type=click.Path(), + default=None, + help="Marketplace: override output path (default: .claude-plugin/marketplace.json).", +) @click.pass_context -def pack_cmd(ctx, fmt, target, archive, output, dry_run, force, verbose): - """Create a self-contained APM bundle.""" +def pack_cmd( + ctx, + fmt, + target, + archive, + output, + dry_run, + force, + verbose, + offline, + include_prerelease, + marketplace_output, +): + """Pack APM artifacts: bundle and/or marketplace.json.""" logger = CommandLogger("pack", verbose=verbose, dry_run=dry_run) + project_root = Path(".").resolve() + options = BuildOptions( + project_root=project_root, + apm_yml_path=project_root / "apm.yml", + bundle_format=fmt, + bundle_target=target, + bundle_archive=archive, + bundle_output=Path(output), + bundle_force=force, + marketplace_offline=offline, + marketplace_include_prerelease=include_prerelease, + marketplace_output=Path(marketplace_output) if marketplace_output else None, + dry_run=dry_run, + verbose=verbose, + ) + try: - result = pack_bundle( - project_root=Path("."), - output_dir=Path(output), - fmt=fmt, - target=target, - archive=archive, - dry_run=dry_run, - force=force, - logger=logger, - ) + result = BuildOrchestrator().run(options, logger=logger) + except BuildError as exc: + raise click.ClickException(str(exc)) - mapping_summary = _mapping_summary(result.path_mappings) + for sub in result.producer_results: + if sub.kind is OutputKind.BUNDLE: + _render_bundle_result(logger, sub.payload, fmt, target, dry_run) + elif sub.kind is OutputKind.MARKETPLACE: + _render_marketplace_result(logger, sub.payload, dry_run, sub.warnings) - if dry_run: - if result.mapped_count: - logger.dry_run_notice( - f"Would remap {result.mapped_count} file(s){mapping_summary}" - ) - for mapped, original in result.path_mappings.items(): - logger.verbose_detail(f" {original} -> {mapped}") - if result.files: - logger.dry_run_notice( - f"Would pack {len(result.files)} file(s) -> {result.bundle_path}" - ) - for f in result.files: - logger.tree_item(f" {f}") - else: - _warn_empty(logger, target, result) - return - if result.mapped_count: - logger.progress( - f"Mapped {result.mapped_count} file(s){mapping_summary}" +def _render_bundle_result(logger, pack_result, fmt, target, dry_run): + """Mirror the legacy ``apm pack`` output for the bundle producer.""" + if pack_result is None: + return + + mapping_summary = _mapping_summary(pack_result.path_mappings) + + if dry_run: + if pack_result.mapped_count: + logger.dry_run_notice( + f"Would remap {pack_result.mapped_count} file(s){mapping_summary}" ) - for mapped, original in result.path_mappings.items(): + for mapped, original in pack_result.path_mappings.items(): logger.verbose_detail(f" {original} -> {mapped}") - - if not result.files: - _warn_empty(logger, target, result) + if pack_result.files: + logger.dry_run_notice( + f"Would pack {len(pack_result.files)} file(s) -> {pack_result.bundle_path}" + ) + for f in pack_result.files: + logger.tree_item(f" {f}") else: - logger.success(f"Packed {len(result.files)} file(s) -> {result.bundle_path}") - for f in result.files: - logger.verbose_detail(f" {f}") - if fmt == "plugin": - logger.progress( - "Plugin bundle ready -- contains plugin.json and " - "plugin-native directories (agents/, skills/, commands/, ...). " - "No APM-specific files included." - ) + _warn_empty(logger, target, pack_result) + return - except (FileNotFoundError, ValueError) as exc: - logger.error(str(exc)) - sys.exit(1) + if pack_result.mapped_count: + logger.progress( + f"Mapped {pack_result.mapped_count} file(s){mapping_summary}" + ) + for mapped, original in pack_result.path_mappings.items(): + logger.verbose_detail(f" {original} -> {mapped}") + + if not pack_result.files: + _warn_empty(logger, target, pack_result) + else: + logger.success( + f"Packed {len(pack_result.files)} file(s) -> {pack_result.bundle_path}" + ) + for f in pack_result.files: + logger.verbose_detail(f" {f}") + if fmt == "plugin": + logger.progress( + "Plugin bundle ready -- contains plugin.json and " + "plugin-native directories (agents/, skills/, commands/, ...). " + "No APM-specific files included." + ) + + +def _render_marketplace_result(logger, report, dry_run, extra_warnings=None): + """Render the marketplace producer's report (one-liner summary).""" + if report is None: + return + for warn_msg in (extra_warnings or []): + logger.warning(warn_msg) + for warn_msg in report.warnings: + logger.warning(warn_msg) + if dry_run or report.dry_run: + logger.dry_run_notice( + f"Would write marketplace.json ({len(report.resolved)} package(s)) " + f"-> {report.output_path}" + ) + return + logger.success( + f"Built marketplace.json ({len(report.resolved)} package(s)) " + f"-> {report.output_path}" + ) @click.command(name="unpack", help="Extract an APM bundle into the current project") diff --git a/src/apm_cli/core/build_orchestrator.py b/src/apm_cli/core/build_orchestrator.py new file mode 100644 index 000000000..892e650c2 --- /dev/null +++ b/src/apm_cli/core/build_orchestrator.py @@ -0,0 +1,274 @@ +"""Unified artifact production -- bundle + marketplace.json from one entrypoint. + +The :class:`BuildOrchestrator` inspects ``apm.yml`` and runs whichever +producers are applicable: + +* ``dependencies:`` block -> :class:`BundleProducer` -> ``./build//`` +* ``marketplace:`` block -> :class:`MarketplaceProducer` -> ``.claude-plugin/marketplace.json`` + +Producers are thin adapters around the existing +:func:`apm_cli.bundle.packer.pack_bundle` and +:class:`apm_cli.marketplace.builder.MarketplaceBuilder` -- the orchestrator +adds no new build logic, only routing. +""" + +from __future__ import annotations + +import enum +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Protocol, Sequence + +import yaml + + +class OutputKind(enum.Enum): + """Kinds of artifacts that ``apm pack`` can produce.""" + + BUNDLE = "bundle" + MARKETPLACE = "marketplace" + + +@dataclass +class BuildOptions: + """Knobs collected from ``apm pack`` flags and passed to producers.""" + + project_root: Path + apm_yml_path: Path + # Bundle-only options + bundle_format: str = "apm" + bundle_target: Any = None + bundle_archive: bool = False + bundle_output: Path | None = None + bundle_force: bool = False + # Marketplace-only options + marketplace_offline: bool = False + marketplace_include_prerelease: bool = False + marketplace_output: Path | None = None + # Common options + dry_run: bool = False + verbose: bool = False + + +@dataclass +class ProducerResult: + """One producer's contribution to the overall build.""" + + kind: OutputKind + outputs: list[Path] = field(default_factory=list) + warnings: list[str] = field(default_factory=list) + payload: Any = None + + +@dataclass +class BuildResult: + """Aggregated outputs and warnings from every producer that ran.""" + + outputs: list[Path] = field(default_factory=list) + warnings: list[str] = field(default_factory=list) + producer_results: list[ProducerResult] = field(default_factory=list) + + +class BuildError(Exception): + """User-facing build error. The CLI maps this to exit code 1.""" + + +class ArtifactProducer(Protocol): + """Protocol that every concrete producer must implement.""" + + kind: OutputKind + + def produce(self, options: BuildOptions, logger: Any) -> ProducerResult: ... + + +# --------------------------------------------------------------------------- +# Bundle producer -- thin adapter around bundle.packer.pack_bundle +# --------------------------------------------------------------------------- + + +class BundleProducer: + """Produce an APM bundle (or plugin bundle) from the lockfile.""" + + kind = OutputKind.BUNDLE + + def produce(self, options: BuildOptions, logger: Any) -> ProducerResult: + from ..bundle.packer import pack_bundle + + output_dir = options.bundle_output or (options.project_root / "build") + try: + pack_result = pack_bundle( + project_root=options.project_root, + output_dir=output_dir, + fmt=options.bundle_format, + target=options.bundle_target, + archive=options.bundle_archive, + dry_run=options.dry_run, + force=options.bundle_force, + logger=logger, + ) + except (FileNotFoundError, ValueError) as exc: + raise BuildError(str(exc)) from exc + + outputs: list[Path] = [] + if pack_result.bundle_path is not None: + outputs.append(Path(pack_result.bundle_path)) + return ProducerResult( + kind=OutputKind.BUNDLE, + outputs=outputs, + payload=pack_result, + ) + + +# --------------------------------------------------------------------------- +# Marketplace producer -- thin adapter around MarketplaceBuilder +# --------------------------------------------------------------------------- + + +class MarketplaceProducer: + """Produce ``.claude-plugin/marketplace.json`` from the marketplace block.""" + + kind = OutputKind.MARKETPLACE + + def produce(self, options: BuildOptions, logger: Any) -> ProducerResult: + from ..marketplace.builder import ( + BuildOptions as MktBuildOptions, + MarketplaceBuilder, + ) + from ..marketplace.errors import BuildError as MktBuildError + from ..marketplace.migration import ( + ConfigSource, + detect_config_source, + load_marketplace_config, + ) + from ..marketplace.yml_schema import MarketplaceYmlError + + warnings: list[str] = [] + + def _warn(msg: str) -> None: + warnings.append(msg) + + project_root = options.project_root + try: + source = detect_config_source(project_root) + config = load_marketplace_config(project_root, warn_callback=_warn) + except MarketplaceYmlError as exc: + raise BuildError(f"marketplace config error: {exc}") from exc + + # Resolve which on-disk yml the builder should bind to (purely + # cosmetic -- the from_config path uses the loaded config object). + if source == ConfigSource.LEGACY_YML: + yml_for_builder = project_root / "marketplace.yml" + else: + yml_for_builder = project_root / "apm.yml" + + # Determine the output override: explicit flag wins; otherwise + # legacy marketplace.yml keeps writing to ./marketplace.json (the + # value baked into the legacy config), and apm.yml keeps writing + # to .claude-plugin/marketplace.json (also the config default). + output_override: Path | None = None + if options.marketplace_output is not None: + output_override = options.marketplace_output + + mkt_opts = MktBuildOptions( + dry_run=options.dry_run, + offline=options.marketplace_offline, + include_prerelease=options.marketplace_include_prerelease, + output_override=output_override, + ) + builder = MarketplaceBuilder.from_config( + config, project_root=project_root, options=mkt_opts + ) + # Bind the synthetic yml path to the actual on-disk file when it + # exists so any downstream diagnostics report a real location. + builder._yml_path = yml_for_builder # noqa: SLF001 -- intentional + + try: + report = builder.build() + except MktBuildError as exc: + raise BuildError(str(exc)) from exc + + outputs: list[Path] = [] + if report.output_path is not None: + outputs.append(Path(report.output_path)) + warnings.extend(report.warnings) + return ProducerResult( + kind=OutputKind.MARKETPLACE, + outputs=outputs, + warnings=warnings, + payload=report, + ) + + +# --------------------------------------------------------------------------- +# Output detection +# --------------------------------------------------------------------------- + + +def detect_outputs(apm_yml_path: Path) -> set[OutputKind]: + """Inspect ``apm.yml`` (and a sibling legacy ``marketplace.yml``) and + return the set of producers that should run. + """ + + out: set[OutputKind] = set() + data: dict | None = None + if apm_yml_path.is_file(): + try: + with open(apm_yml_path, encoding="utf-8") as handle: + loaded = yaml.safe_load(handle) + except yaml.YAMLError as exc: + raise BuildError(f"Failed to parse {apm_yml_path}: {exc}") from exc + if loaded is not None and not isinstance(loaded, dict): + raise BuildError( + f"{apm_yml_path} must be a YAML mapping at the top level." + ) + data = loaded or {} + + if data and data.get("dependencies"): + out.add(OutputKind.BUNDLE) + if data and data.get("marketplace"): + out.add(OutputKind.MARKETPLACE) + + legacy = apm_yml_path.parent / "marketplace.yml" + if legacy.is_file(): + out.add(OutputKind.MARKETPLACE) + + return out + + +# --------------------------------------------------------------------------- +# Orchestrator +# --------------------------------------------------------------------------- + + +class BuildOrchestrator: + """Pick the right producers for an apm.yml and run them in order.""" + + def __init__( + self, + producers: Sequence[ArtifactProducer] | None = None, + ) -> None: + self._producers: list[ArtifactProducer] = ( + list(producers) + if producers is not None + else [BundleProducer(), MarketplaceProducer()] + ) + + def run(self, options: BuildOptions, logger: Any = None) -> BuildResult: + outputs_needed = detect_outputs(options.apm_yml_path) + if not outputs_needed: + raise BuildError( + "apm.yml has neither 'dependencies:' nor 'marketplace:' " + "block. Nothing to pack. Add dependencies via " + "'apm install ' or scaffold a marketplace block " + "with 'apm marketplace init'." + ) + + result = BuildResult() + for producer in self._producers: + if producer.kind not in outputs_needed: + continue + sub = producer.produce(options, logger) + result.outputs.extend(sub.outputs) + result.warnings.extend(sub.warnings) + result.producer_results.append(sub) + return result diff --git a/src/apm_cli/core/experimental.py b/src/apm_cli/core/experimental.py index 1f97d3132..f1566deac 100644 --- a/src/apm_cli/core/experimental.py +++ b/src/apm_cli/core/experimental.py @@ -70,12 +70,6 @@ class ExperimentalFlag: "See https://microsoft.github.io/apm/integrations/copilot-cowork/" ), ), - "marketplace_authoring": ExperimentalFlag( - name="marketplace_authoring", - description="Enable marketplace authoring commands (init, build, publish, etc.).", - default=False, - hint="Run 'apm marketplace --help' to see available commands.", - ), } diff --git a/src/apm_cli/marketplace/init_template.py b/src/apm_cli/marketplace/init_template.py index ebdcb1d2a..02d3536dd 100644 --- a/src/apm_cli/marketplace/init_template.py +++ b/src/apm_cli/marketplace/init_template.py @@ -17,7 +17,7 @@ # APM marketplace descriptor # # This file (marketplace.yml) is the SOURCE for your marketplace. -# Run 'apm marketplace build' to compile it to marketplace.json. +# Run 'apm pack' to compile it to marketplace.json. # Both files must be committed to the repository. # # For the full schema, see: @@ -88,7 +88,7 @@ def render_marketplace_yml_template( _MARKETPLACE_BLOCK_TEMPLATE = """\ # Marketplace authoring config (APM-only). -# Run 'apm marketplace build' to compile this block to .claude-plugin/marketplace.json. +# Run 'apm pack' to compile this block to .claude-plugin/marketplace.json. # # Top-level 'name', 'description', and 'version' are inherited from # the project (above) by default. Override them inside this block when diff --git a/tests/fixtures/azure-skills/.claude-plugin/marketplace.json b/tests/fixtures/azure-skills/.claude-plugin/marketplace.json new file mode 100644 index 000000000..aef2ea36b --- /dev/null +++ b/tests/fixtures/azure-skills/.claude-plugin/marketplace.json @@ -0,0 +1,15 @@ +{ + "name": "azure-skills", + "owner": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "plugins": [ + { + "name": "azure", + "description": "Microsoft Azure MCP integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Claude Code.", + "source": "./.github/plugins/azure-skills", + "homepage": "https://github.com/microsoft/azure-skills" + } + ] +} diff --git a/tests/fixtures/azure-skills/apm.yml b/tests/fixtures/azure-skills/apm.yml new file mode 100644 index 000000000..4d92478fc --- /dev/null +++ b/tests/fixtures/azure-skills/apm.yml @@ -0,0 +1,13 @@ +name: azure-skills +version: 1.0.0 +description: Microsoft Azure MCP and Skills integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from your development environment. + +marketplace: + owner: + name: Microsoft + url: https://www.microsoft.com + packages: + - name: azure + description: Microsoft Azure MCP integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Claude Code. + source: ./.github/plugins/azure-skills + homepage: https://github.com/microsoft/azure-skills diff --git a/tests/integration/test_azure_skills_marketplace.py b/tests/integration/test_azure_skills_marketplace.py new file mode 100644 index 000000000..66b44f838 --- /dev/null +++ b/tests/integration/test_azure_skills_marketplace.py @@ -0,0 +1,58 @@ +"""Byte-for-byte snapshot test against microsoft/azure-skills. + +The snapshot in ``tests/fixtures/azure-skills/`` was captured from +microsoft/azure-skills@bef1f05. This test asserts that running +``apm pack`` on the captured ``apm.yml`` produces a marketplace.json +whose SHA-256 matches the one shipped in that repo. + +Marker: ``integration`` so it can be excluded from quick unit runs. +""" + +from __future__ import annotations + +import hashlib +import shutil +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from apm_cli.commands.pack import pack_cmd + + +FIXTURES = Path(__file__).parent.parent / "fixtures" / "azure-skills" +EXPECTED_SHA256 = "02f76bfc0e5bbf7fdf1de1dda1f84c4da6e986913b6647973c0ffe39c1d5003b" + + +@pytest.mark.integration +def test_azure_skills_marketplace_byte_for_byte(tmp_path, monkeypatch): + apm_src = FIXTURES / "apm.yml" + expected_src = FIXTURES / ".claude-plugin" / "marketplace.json" + assert apm_src.exists(), f"snapshot apm.yml missing at {apm_src}" + assert expected_src.exists(), f"snapshot marketplace.json missing at {expected_src}" + + # Sanity-check the snapshot itself matches the documented hash. If + # this fails, the fixture has drifted and needs to be re-captured. + snapshot_sha = hashlib.sha256(expected_src.read_bytes()).hexdigest() + assert snapshot_sha == EXPECTED_SHA256, ( + f"fixture marketplace.json SHA-256 drifted: {snapshot_sha}" + ) + + # Stage the apm.yml in a clean tempdir + shutil.copy2(apm_src, tmp_path / "apm.yml") + monkeypatch.chdir(tmp_path) + + runner = CliRunner() + result = runner.invoke(pack_cmd, []) + assert result.exit_code == 0, result.output + + out = tmp_path / ".claude-plugin" / "marketplace.json" + assert out.exists(), "pack did not write .claude-plugin/marketplace.json" + + actual_sha = hashlib.sha256(out.read_bytes()).hexdigest() + assert actual_sha == EXPECTED_SHA256, ( + "Generated marketplace.json drifted from azure-skills snapshot:\n" + f" expected: {EXPECTED_SHA256}\n" + f" actual: {actual_sha}\n" + f" generated content:\n{out.read_text(encoding='utf-8')}" + ) diff --git a/tests/integration/test_pack_unified.py b/tests/integration/test_pack_unified.py new file mode 100644 index 000000000..281929e27 --- /dev/null +++ b/tests/integration/test_pack_unified.py @@ -0,0 +1,250 @@ +"""Integration tests for the unified ``apm pack`` entrypoint. + +Covers the matrix of bundle / marketplace / both / neither outputs plus +flag overrides and the hard-error path for the removed +``apm marketplace build`` subcommand. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest +import yaml +from click.testing import CliRunner + +from apm_cli.commands.marketplace import marketplace +from apm_cli.commands.pack import pack_cmd + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +_LOCKFILE_TEMPLATE = """\ +lockfile_version: '1' +generated_at: '2025-01-01T00:00:00+00:00' +dependencies: [] +""" + + +def _write_apm_yml(root: Path, body: str) -> None: + (root / "apm.yml").write_text(body, encoding="utf-8") + + +def _write_minimal_lockfile(root: Path) -> None: + """Write an empty but well-formed apm.lock.yaml so pack_bundle works.""" + (root / "apm.lock.yaml").write_text(_LOCKFILE_TEMPLATE, encoding="utf-8") + + +def _write_marketplace_block_yml(root: Path, *, package_name: str = "azure") -> None: + """Write an apm.yml with a marketplace block targeting a local source.""" + plugin_dir = root / ".github" / "plugins" / package_name + plugin_dir.mkdir(parents=True, exist_ok=True) + _write_apm_yml( + root, + f"""\ +name: pack-test +version: 1.0.0 +description: pack integration test fixture + +marketplace: + owner: + name: Tester + url: https://example.com + packages: + - name: {package_name} + description: Local package fixture for pack integration tests + source: ./.github/plugins/{package_name} + homepage: https://example.com +""", + ) + + +@pytest.fixture +def runner(): + return CliRunner() + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +class TestPackUnified: + def test_pack_bundle_only(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _write_apm_yml( + tmp_path, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm: []\n", + ) + _write_minimal_lockfile(tmp_path) + + result = runner.invoke(pack_cmd, []) + + assert result.exit_code == 0, result.output + # Bundle directory is created under ./build (empty bundle is fine) + assert (tmp_path / "build").exists() + # Marketplace.json should NOT be created + assert not (tmp_path / ".claude-plugin" / "marketplace.json").exists() + + def test_pack_marketplace_only(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _write_marketplace_block_yml(tmp_path) + + result = runner.invoke(pack_cmd, []) + + assert result.exit_code == 0, result.output + out = tmp_path / ".claude-plugin" / "marketplace.json" + assert out.exists() + data = json.loads(out.read_text(encoding="utf-8")) + assert data["name"] == "pack-test" + assert data["plugins"][0]["name"] == "azure" + # No bundle directory should appear + assert not (tmp_path / "build").exists() + + def test_pack_both(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + # Add both blocks + plugin_dir = tmp_path / ".github" / "plugins" / "azure" + plugin_dir.mkdir(parents=True) + _write_apm_yml( + tmp_path, + """\ +name: pack-test +version: 1.0.0 +description: y +dependencies: + apm: [] + +marketplace: + owner: + name: Tester + url: https://example.com + packages: + - name: azure + description: x + source: ./.github/plugins/azure +""", + ) + _write_minimal_lockfile(tmp_path) + + result = runner.invoke(pack_cmd, []) + + assert result.exit_code == 0, result.output + assert (tmp_path / "build").exists() + assert (tmp_path / ".claude-plugin" / "marketplace.json").exists() + + def test_pack_neither_errors(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _write_apm_yml(tmp_path, "name: x\nversion: 0.1.0\ndescription: y\n") + + result = runner.invoke(pack_cmd, []) + + assert result.exit_code == 1 + assert "Nothing to pack" in result.output + + def test_pack_marketplace_output_override(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _write_marketplace_block_yml(tmp_path) + + out_path = tmp_path / "out" / "m.json" + result = runner.invoke( + pack_cmd, ["--marketplace-output", str(out_path)] + ) + + assert result.exit_code == 0, result.output + assert out_path.exists() + # Default location should NOT be written when overridden + assert not (tmp_path / ".claude-plugin" / "marketplace.json").exists() + + def test_pack_legacy_marketplace_yml(self, runner, tmp_path, monkeypatch): + """Legacy standalone marketplace.yml still produces marketplace.json.""" + monkeypatch.chdir(tmp_path) + plugin_dir = tmp_path / ".github" / "plugins" / "azure" + plugin_dir.mkdir(parents=True) + # apm.yml has neither dependencies nor marketplace blocks + _write_apm_yml( + tmp_path, "name: x\nversion: 0.1.0\ndescription: y\n" + ) + (tmp_path / "marketplace.yml").write_text( + """\ +name: legacy +version: 0.1.0 +description: y +owner: + name: Tester + url: https://example.com +packages: + - name: azure + description: x + source: ./.github/plugins/azure +""", + encoding="utf-8", + ) + + result = runner.invoke(pack_cmd, []) + + assert result.exit_code == 0, result.output + # Legacy default path is ./marketplace.json (kept by yml_schema) + assert (tmp_path / "marketplace.json").exists() + # Deprecation warning should fire + assert "marketplace.yml" in result.output.lower() + + def test_pack_dry_run_marketplace(self, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + _write_marketplace_block_yml(tmp_path) + + result = runner.invoke(pack_cmd, ["--dry-run"]) + + assert result.exit_code == 0, result.output + assert not (tmp_path / ".claude-plugin" / "marketplace.json").exists() + + def test_pack_plugin_format_with_marketplace(self, runner, tmp_path, monkeypatch): + """--format plugin still triggers marketplace producer.""" + monkeypatch.chdir(tmp_path) + plugin_dir = tmp_path / ".github" / "plugins" / "azure" + plugin_dir.mkdir(parents=True) + _write_apm_yml( + tmp_path, + """\ +name: pack-test +version: 1.0.0 +description: y +dependencies: + apm: [] + +marketplace: + owner: + name: Tester + url: https://example.com + packages: + - name: azure + description: x + source: ./.github/plugins/azure +""", + ) + _write_minimal_lockfile(tmp_path) + + result = runner.invoke(pack_cmd, ["--format", "plugin"]) + + assert result.exit_code == 0, result.output + # Marketplace.json must be written regardless of bundle format + assert (tmp_path / ".claude-plugin" / "marketplace.json").exists() + + +# --------------------------------------------------------------------------- +# Removed `apm marketplace build` subcommand +# --------------------------------------------------------------------------- + + +class TestMarketplaceBuildSubcommandRemoved: + def test_marketplace_build_subcommand_errors(self, runner): + result = runner.invoke(marketplace, ["build"]) + # Click maps UsageError to exit code 2. + assert result.exit_code == 2 + assert "apm pack" in result.output + assert "was removed" in result.output diff --git a/tests/unit/commands/conftest.py b/tests/unit/commands/conftest.py index 1c535af1a..1540a443e 100644 --- a/tests/unit/commands/conftest.py +++ b/tests/unit/commands/conftest.py @@ -1,46 +1,7 @@ -"""Shared fixtures for ``tests/unit/commands/``.""" +"""Shared fixtures for ``tests/unit/commands/``. -from __future__ import annotations - -from unittest.mock import patch - -import pytest - - -# Cache the *real* is_enabled so we can delegate non-marketplace flags. -from apm_cli.core.experimental import is_enabled as _real_is_enabled - - -def _marketplace_enabled_is_enabled(name: str) -> bool: - """Stub that forces ``marketplace_authoring`` to True.""" - if name == "marketplace_authoring": - return True - return _real_is_enabled(name) +The ``marketplace_authoring`` experimental flag was removed when marketplace +authoring went GA -- this conftest no longer patches the flag. +""" - -@pytest.fixture(autouse=True) -def _enable_marketplace_flag(request): - """Pre-enable the ``marketplace_authoring`` experimental flag. - - The marketplace group callback guards execution behind this flag. - All *existing* marketplace tests need the flag enabled so they - exercise the subcommand logic rather than hitting the guard. - - Only applies to test modules whose name contains "marketplace" - (excluding ``test_marketplace_gating`` which tests disabled state). - - Patches ``is_enabled`` at the source module so it survives any - config-cache isolation performed by individual tests. - """ - module_name = request.module.__name__ - is_marketplace_test = "marketplace" in module_name - is_gating_test = "gating" in module_name - - if is_marketplace_test and not is_gating_test: - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=_marketplace_enabled_is_enabled, - ): - yield - else: - yield +from __future__ import annotations diff --git a/tests/unit/commands/test_marketplace_build.py b/tests/unit/commands/test_marketplace_build.py deleted file mode 100644 index a131dc6df..000000000 --- a/tests/unit/commands/test_marketplace_build.py +++ /dev/null @@ -1,452 +0,0 @@ -"""Tests for ``apm marketplace build`` subcommand.""" - -from __future__ import annotations - -import json -import textwrap -from pathlib import Path -from unittest.mock import MagicMock, patch - -import pytest -from click.testing import CliRunner - -from apm_cli.commands.marketplace import marketplace -from apm_cli.marketplace.builder import BuildOptions, BuildReport, ResolvedPackage -from apm_cli.marketplace.errors import ( - BuildError, - GitLsRemoteError, - HeadNotAllowedError, - MarketplaceYmlError, - NoMatchingVersionError, - OfflineMissError, - RefNotFoundError, -) - - -# --------------------------------------------------------------------------- -# Helpers / fixtures -# --------------------------------------------------------------------------- - -_SHA_A = "a" * 40 -_SHA_B = "b" * 40 - -_BASIC_YML = textwrap.dedent("""\ - name: test-marketplace - description: Test marketplace - version: 1.0.0 - owner: - name: Test Owner - packages: - - name: pkg-alpha - source: acme-org/pkg-alpha - version: "^1.0.0" - description: Alpha package - tags: [testing] - - name: pkg-beta - source: acme-org/pkg-beta - version: "~2.0.0" - tags: [utility] -""") - - -def _make_report( - resolved=None, errors=(), dry_run=False, - unchanged=0, added=2, updated=0, removed=0, -): - """Build a fake BuildReport.""" - if resolved is None: - resolved = ( - ResolvedPackage( - name="pkg-alpha", - source_repo="acme-org/pkg-alpha", - subdir=None, - ref="v1.2.0", - sha=_SHA_A, - requested_version="^1.0.0", - tags=("testing",), - is_prerelease=False, - ), - ResolvedPackage( - name="pkg-beta", - source_repo="acme-org/pkg-beta", - subdir="src/plugin", - ref="v2.0.1", - sha=_SHA_B, - requested_version="~2.0.0", - tags=("utility",), - is_prerelease=False, - ), - ) - return BuildReport( - resolved=resolved, - errors=errors, - warnings=(), - unchanged_count=unchanged, - added_count=added, - updated_count=updated, - removed_count=removed, - output_path=Path("marketplace.json"), - dry_run=dry_run, - ) - - -@pytest.fixture -def runner(): - return CliRunner() - - -@pytest.fixture -def yml_cwd(tmp_path, monkeypatch): - """Set CWD to tmp_path and write a valid marketplace.yml.""" - monkeypatch.chdir(tmp_path) - (tmp_path / "marketplace.yml").write_text(_BASIC_YML, encoding="utf-8") - return tmp_path - - -# --------------------------------------------------------------------------- -# Happy path -# --------------------------------------------------------------------------- - - -class TestBuildHappyPath: - """build command -- success scenarios.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_basic_build_success(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 0 - assert "Built marketplace.json" in result.output - assert "2 packages" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_build_table_contains_package_names(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 0 - assert "pkg-alpha" in result.output - assert "pkg-beta" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_build_table_contains_version_refs(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - result = runner.invoke(marketplace, ["build"]) - assert "v1.2.0" in result.output - assert "v2.0.1" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_build_table_shows_sha_prefix(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - result = runner.invoke(marketplace, ["build"]) - assert _SHA_A[:8] in result.output - assert _SHA_B[:8] in result.output - - -# --------------------------------------------------------------------------- -# Dry-run -# --------------------------------------------------------------------------- - - -class TestBuildDryRun: - """build --dry-run scenarios.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_dry_run_message(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(dry_run=True) - - result = runner.invoke(marketplace, ["build", "--dry-run"]) - assert result.exit_code == 0 - assert "Dry run" in result.output - assert "not written" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_dry_run_no_built_message(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(dry_run=True) - - result = runner.invoke(marketplace, ["build", "--dry-run"]) - assert "Built marketplace.json" not in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_dry_run_passes_option_to_builder(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(dry_run=True) - - runner.invoke(marketplace, ["build", "--dry-run"]) - opts = MockBuilder.call_args[1].get("options") or MockBuilder.call_args[0][1] - assert opts.dry_run is True - - -# --------------------------------------------------------------------------- -# Flag forwarding -# --------------------------------------------------------------------------- - - -class TestBuildFlags: - """Verify CLI flags are forwarded to BuildOptions.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_offline_flag(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - runner.invoke(marketplace, ["build", "--offline"]) - opts = MockBuilder.call_args[1].get("options") or MockBuilder.call_args[0][1] - assert opts.offline is True - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_include_prerelease_flag(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - runner.invoke(marketplace, ["build", "--include-prerelease"]) - opts = MockBuilder.call_args[1].get("options") or MockBuilder.call_args[0][1] - assert opts.include_prerelease is True - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_verbose_flag(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report() - - result = runner.invoke(marketplace, ["build", "--verbose"]) - assert result.exit_code == 0 - - -# --------------------------------------------------------------------------- -# Missing / bad marketplace.yml -# --------------------------------------------------------------------------- - - -class TestBuildMissingYml: - """build command -- no marketplace.yml.""" - - def test_missing_yml_exits_1(self, runner, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "No marketplace config" in result.output - - def test_missing_yml_suggests_init(self, runner, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - result = runner.invoke(marketplace, ["build"]) - assert "init" in result.output - - -class TestBuildSchemaError: - """build command -- invalid marketplace.yml.""" - - def test_schema_error_exits_2(self, runner, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - (tmp_path / "marketplace.yml").write_text("not: valid\n", encoding="utf-8") - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 2 - assert "config error" in result.output.lower() or \ - "schema error" in result.output.lower() or \ - "required" in result.output.lower() or \ - "unknown" in result.output.lower() - - def test_bad_yaml_syntax_exits_2(self, runner, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - (tmp_path / "marketplace.yml").write_text(":\n - !!invalid", encoding="utf-8") - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 2 - - -# --------------------------------------------------------------------------- -# Build errors -# --------------------------------------------------------------------------- - - -class TestBuildErrors: - """build command -- BuildError subclass handling.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_no_matching_version_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = NoMatchingVersionError( - "pkg-alpha", "^1.0.0" - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "pkg-alpha" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_ref_not_found_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = RefNotFoundError( - "pkg-alpha", "v99.0.0", "acme-org/pkg-alpha" - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "not found" in result.output.lower() - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_git_ls_remote_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = GitLsRemoteError( - package="pkg-alpha", - summary="Authentication failed", - hint="Check your GITHUB_TOKEN", - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "Authentication failed" in result.output - assert "GITHUB_TOKEN" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_offline_miss_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = OfflineMissError( - package="pkg-alpha", remote="acme-org/pkg-alpha" - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "offline" in result.output.lower() or "cache" in result.output.lower() - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_head_not_allowed_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = HeadNotAllowedError( - package="pkg-alpha", ref="main" - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "main" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_generic_build_error(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = BuildError( - "Something unexpected", package="pkg-alpha" - ) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - - -# --------------------------------------------------------------------------- -# Empty packages -# --------------------------------------------------------------------------- - - -class TestBuildEdgeCases: - """Edge cases for the build command.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_empty_packages_list(self, MockBuilder, runner, yml_cwd): - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(resolved=(), added=0) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 0 - assert "0 packages" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_single_package(self, MockBuilder, runner, yml_cwd): - single = ( - ResolvedPackage( - name="only-one", - source_repo="acme-org/only-one", - subdir=None, - ref="v3.0.0", - sha=_SHA_A, - requested_version="^3.0.0", - tags=(), - is_prerelease=False, - ), - ) - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(resolved=single, added=1) - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 0 - assert "only-one" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_prerelease_package(self, MockBuilder, runner, yml_cwd): - pre = ( - ResolvedPackage( - name="beta-pkg", - source_repo="acme-org/beta-pkg", - subdir=None, - ref="v2.0.0-rc.1", - sha=_SHA_A, - requested_version="^2.0.0", - tags=(), - is_prerelease=True, - ), - ) - mock_inst = MockBuilder.return_value - mock_inst.build.return_value = _make_report(resolved=pre, added=1) - - result = runner.invoke(marketplace, ["build", "--include-prerelease"]) - assert result.exit_code == 0 - assert "v2.0.0-rc.1" in result.output - - -# --------------------------------------------------------------------------- -# Verbose traceback (L3) -# --------------------------------------------------------------------------- - - -class TestBuildVerboseTraceback: - """build --verbose -- traceback on unexpected failure.""" - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_verbose_shows_traceback_on_unexpected_error( - self, MockBuilder, runner, yml_cwd - ): - """When --verbose is passed and build raises an unexpected error, - stderr should contain the full traceback.""" - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = RuntimeError("unexpected internal failure") - - result = runner.invoke(marketplace, ["build", "--verbose"]) - assert result.exit_code == 1 - assert "Traceback" in result.output - assert "unexpected internal failure" in result.output - - @patch("apm_cli.commands.marketplace.MarketplaceBuilder") - def test_no_traceback_without_verbose(self, MockBuilder, runner, yml_cwd): - """Without --verbose the traceback is suppressed.""" - mock_inst = MockBuilder.return_value - mock_inst.build.side_effect = RuntimeError("unexpected internal failure") - - result = runner.invoke(marketplace, ["build"]) - assert result.exit_code == 1 - assert "Traceback" not in result.output - assert "Build failed" in result.output - - -# --------------------------------------------------------------------------- -# GHE host support -# --------------------------------------------------------------------------- - - -class TestBuildGHEHost: - """build command -- GHE / custom host scenarios.""" - - def test_build_ghe_host_env( - self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path - ) -> None: - """MarketplaceBuilder respects GITHUB_HOST for token resolution.""" - monkeypatch.setenv("GITHUB_HOST", "corp.ghe.com") - from apm_cli.marketplace.builder import MarketplaceBuilder, BuildOptions - yml_path = tmp_path / "marketplace.yml" - yml_path.write_text("name: test\noutput: marketplace.json\npackages: []\n") - builder = MarketplaceBuilder(yml_path) - assert builder._host == "corp.ghe.com" diff --git a/tests/unit/commands/test_marketplace_gating.py b/tests/unit/commands/test_marketplace_gating.py deleted file mode 100644 index 6705f9e2f..000000000 --- a/tests/unit/commands/test_marketplace_gating.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Tests for marketplace experimental flag gating. - -Verifies: - - ``marketplace_authoring`` flag is registered in the ``FLAGS`` registry - - Consumer commands (add, list, browse, update, remove, validate) work - WITHOUT the flag enabled - - Authoring commands (init, build, check, outdated, doctor, publish, package) - are blocked when the flag is disabled, with an enablement message - - Authoring commands proceed when the flag is enabled - -Note: The directory-level conftest patches ``is_enabled`` to return True -for ``marketplace_authoring`` (so existing marketplace subcommand tests pass). -Tests here that need the flag *disabled* wrap their assertions in an -explicit ``patch`` context manager that overrides the conftest mock. -""" - -from __future__ import annotations - -from typing import Any -from unittest.mock import patch - -from apm_cli.core.experimental import is_enabled as _real_is_enabled - -import pytest -from click.testing import CliRunner - - -# --------------------------------------------------------------------------- -# Flag registration (uses the real FLAGS dict -- unaffected by is_enabled mock) -# --------------------------------------------------------------------------- - - -class TestMarketplaceFlagRegistration: - """Verify the marketplace_authoring flag exists with correct metadata.""" - - def test_marketplace_flag_in_registry(self) -> None: - """marketplace_authoring is a registered ExperimentalFlag.""" - from apm_cli.core.experimental import FLAGS - - assert "marketplace_authoring" in FLAGS - - def test_flag_default_is_false(self) -> None: - """Flag ships disabled by default.""" - from apm_cli.core.experimental import FLAGS - - flag = FLAGS["marketplace_authoring"] - assert flag.default is False - - def test_flag_name_matches_key(self) -> None: - """Registry key matches the flag's .name attribute.""" - from apm_cli.core.experimental import FLAGS - - flag = FLAGS["marketplace_authoring"] - assert flag.name == "marketplace_authoring" - - def test_flag_has_hint(self) -> None: - """Flag provides a post-enable hint.""" - from apm_cli.core.experimental import FLAGS - - flag = FLAGS["marketplace_authoring"] - assert flag.hint is not None - assert "marketplace" in flag.hint.lower() - - def test_flag_description_mentions_authoring(self) -> None: - """Flag description is scoped to authoring commands only.""" - from apm_cli.core.experimental import FLAGS - - flag = FLAGS["marketplace_authoring"] - assert "authoring" in flag.description.lower() - - -# --------------------------------------------------------------------------- -# Consumer commands: always available (no flag required) -# --------------------------------------------------------------------------- - - -class TestConsumerCommandsUngated: - """Consumer commands must work without marketplace_authoring enabled.""" - - @pytest.mark.parametrize("subcmd", ["add", "list", "browse", "update", "remove", "validate"]) - def test_consumer_command_reachable_when_flag_disabled(self, subcmd: str) -> None: - """Consumer subcommands are not blocked by the authoring flag.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, [subcmd, "--help"]) - - # --help should succeed (exit 0) and NOT show the experimental - # gating message -- the command is reachable. - assert result.exit_code == 0 - assert "experimental" not in result.output.lower() - - def test_marketplace_help_works_when_flag_disabled(self) -> None: - """``marketplace --help`` shows consumer section without the flag.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, ["--help"]) - - assert result.exit_code == 0 - assert "Consumer commands" in result.output - - def test_marketplace_help_hides_authoring_when_flag_disabled(self) -> None: - """``marketplace --help`` omits authoring section when flag is off.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, ["--help"]) - - assert result.exit_code == 0 - assert "Authoring commands" not in result.output - - @pytest.mark.parametrize("subcmd", ["init", "build", "check", "outdated", "doctor", "publish", "package"]) - def test_authoring_commands_hidden_from_help_when_flag_disabled(self, subcmd: str) -> None: - """Individual authoring command names are absent from --help when flag is off.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, ["--help"]) - - assert result.exit_code == 0 - # Each authoring command name should not appear as a listed subcommand - # (it may appear in the group description; check the commands section) - lines = result.output.split("\n") - command_lines = [ - line for line in lines - if line.strip().startswith(subcmd) - ] - assert not command_lines, ( - f"Authoring command '{subcmd}' should be hidden from --help " - f"when flag is disabled, but found: {command_lines}" - ) - - -# --------------------------------------------------------------------------- -# Authoring commands: blocked without the flag -# --------------------------------------------------------------------------- - - -class TestAuthoringCommandsGated: - """Authoring commands must be blocked when the flag is disabled.""" - - @pytest.mark.parametrize("subcmd", ["init", "build", "check", "outdated", "doctor", "publish"]) - def test_authoring_command_blocked_when_disabled(self, subcmd: str) -> None: - """Authoring subcommand exits with enablement hint when flag off.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, [subcmd]) - - assert result.exit_code != 0 - assert "experimental" in result.output.lower() - assert "apm experimental enable marketplace-authoring" in result.output - - def test_package_subgroup_blocked_when_disabled(self) -> None: - """``marketplace package`` exits with enablement hint when flag off.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, ["package", "add", "x/y"]) - - assert result.exit_code != 0 - assert "experimental" in result.output.lower() - assert "apm experimental enable marketplace-authoring" in result.output - - @pytest.mark.parametrize("subcmd", ["init", "build", "check", "outdated", "doctor", "publish"]) - def test_authoring_guard_message_includes_learn_more(self, subcmd: str) -> None: - """Guard message includes 'apm experimental list' for discoverability.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: False, - ): - result = runner.invoke(marketplace, [subcmd]) - - assert "apm experimental list" in result.output - - -# --------------------------------------------------------------------------- -# Authoring commands: accessible when the flag IS enabled -# --------------------------------------------------------------------------- - - -class TestAuthoringCommandsEnabled: - """Authoring commands proceed normally when the flag is enabled.""" - - @pytest.fixture(autouse=True) - def _enable_flag(self): - """Enable marketplace_authoring for this class's tests.""" - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=lambda name: True if name == "marketplace_authoring" else _real_is_enabled(name), - ): - yield - - @pytest.mark.parametrize("subcmd", ["init", "build", "check", "outdated", "doctor", "publish"]) - def test_authoring_command_help_reachable_when_enabled(self, subcmd: str) -> None: - """Authoring subcommand --help works when flag is enabled.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - result = runner.invoke(marketplace, [subcmd, "--help"]) - - assert result.exit_code == 0 - assert "experimental" not in result.output.lower() - - def test_package_subgroup_help_reachable_when_enabled(self) -> None: - """``marketplace package --help`` works when flag is enabled.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - result = runner.invoke(marketplace, ["package", "--help"]) - - assert result.exit_code == 0 - assert "experimental" not in result.output.lower() - - def test_marketplace_help_shows_both_sections_when_enabled(self) -> None: - """``marketplace --help`` shows Consumer and Authoring sections when flag on.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - result = runner.invoke(marketplace, ["--help"]) - - assert result.exit_code == 0 - assert "Consumer commands" in result.output - assert "Authoring commands" in result.output - - @pytest.mark.parametrize("subcmd", ["init", "build", "check", "outdated", "doctor", "publish", "package"]) - def test_authoring_commands_listed_in_help_when_enabled(self, subcmd: str) -> None: - """Authoring command names appear in --help when flag is on.""" - from apm_cli.commands.marketplace import marketplace - - runner = CliRunner() - result = runner.invoke(marketplace, ["--help"]) - - assert result.exit_code == 0 - lines = result.output.split("\n") - command_lines = [ - line for line in lines - if line.strip().startswith(subcmd) - ] - assert command_lines, ( - f"Authoring command '{subcmd}' should be visible in --help " - f"when flag is enabled" - ) diff --git a/tests/unit/commands/test_marketplace_init.py b/tests/unit/commands/test_marketplace_init.py index 56ecfa6e5..17a9eb8c8 100644 --- a/tests/unit/commands/test_marketplace_init.py +++ b/tests/unit/commands/test_marketplace_init.py @@ -67,7 +67,7 @@ def test_next_steps_shown(self, runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) result = runner.invoke(marketplace, ["init"]) assert result.exit_code == 0 - assert "apm marketplace build" in result.output + assert "apm pack" in result.output # --------------------------------------------------------------------------- diff --git a/tests/unit/commands/test_marketplace_publish.py b/tests/unit/commands/test_marketplace_publish.py index 44a65ecda..4069b88b1 100644 --- a/tests/unit/commands/test_marketplace_publish.py +++ b/tests/unit/commands/test_marketplace_publish.py @@ -316,7 +316,7 @@ def test_missing_marketplace_json_exit_1(self, runner, tmp_path, monkeypatch): result = runner.invoke(marketplace, ["publish", "--yes"]) assert result.exit_code == 1 assert "marketplace.json not found" in result.output - assert "apm marketplace build" in result.output + assert "apm pack" in result.output def test_marketplace_yml_schema_error_exit_2(self, runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) diff --git a/tests/unit/core/__init__.py b/tests/unit/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/core/test_build_orchestrator.py b/tests/unit/core/test_build_orchestrator.py new file mode 100644 index 000000000..df9df122f --- /dev/null +++ b/tests/unit/core/test_build_orchestrator.py @@ -0,0 +1,192 @@ +"""Unit tests for ``apm_cli.core.build_orchestrator``.""" + +from __future__ import annotations + +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from apm_cli.core.build_orchestrator import ( + ArtifactProducer, + BuildError, + BuildOptions, + BuildOrchestrator, + BuildResult, + OutputKind, + ProducerResult, + detect_outputs, +) + + +def _write(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + + +# --------------------------------------------------------------------------- +# detect_outputs +# --------------------------------------------------------------------------- + + +class TestDetectOutputs: + def test_dependencies_only_returns_bundle(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm:\n - owner/repo\n", + ) + assert detect_outputs(apm) == {OutputKind.BUNDLE} + + def test_marketplace_only_returns_marketplace(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "marketplace:\n owner:\n name: o\n", + ) + assert detect_outputs(apm) == {OutputKind.MARKETPLACE} + + def test_both_blocks_present(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm:\n - owner/repo\n" + "marketplace:\n owner:\n name: o\n", + ) + assert detect_outputs(apm) == {OutputKind.BUNDLE, OutputKind.MARKETPLACE} + + def test_neither_block_returns_empty(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(apm, "name: x\nversion: 0.1.0\ndescription: y\n") + assert detect_outputs(apm) == set() + + def test_legacy_marketplace_yml_triggers_marketplace(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(apm, "name: x\nversion: 0.1.0\ndescription: y\n") + _write(tmp_path / "marketplace.yml", "name: m\nversion: 0.1.0\ndescription: y\n") + assert detect_outputs(apm) == {OutputKind.MARKETPLACE} + + def test_missing_apm_yml_with_legacy_marketplace_yml(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(tmp_path / "marketplace.yml", "name: m\n") + # apm.yml does not exist + assert detect_outputs(apm) == {OutputKind.MARKETPLACE} + + def test_invalid_yaml_raises_build_error(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(apm, "name: : :\n") + with pytest.raises(BuildError, match="Failed to parse"): + detect_outputs(apm) + + def test_non_mapping_top_level_raises(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(apm, "- a\n- b\n") + with pytest.raises(BuildError, match="must be a YAML mapping"): + detect_outputs(apm) + + +# --------------------------------------------------------------------------- +# BuildOrchestrator +# --------------------------------------------------------------------------- + + +def _make_producer(kind: OutputKind, output_path: Path) -> ArtifactProducer: + producer = MagicMock(spec=["kind", "produce"]) + producer.kind = kind + producer.produce.return_value = ProducerResult(kind=kind, outputs=[output_path]) + return producer + + +class TestBuildOrchestrator: + def test_runs_only_bundle_when_only_dependencies(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm:\n - owner/repo\n", + ) + bp = _make_producer(OutputKind.BUNDLE, tmp_path / "build") + mp = _make_producer(OutputKind.MARKETPLACE, tmp_path / "m.json") + opts = BuildOptions(project_root=tmp_path, apm_yml_path=apm) + + result = BuildOrchestrator(producers=[bp, mp]).run(opts) + + bp.produce.assert_called_once() + mp.produce.assert_not_called() + assert result.outputs == [tmp_path / "build"] + + def test_runs_only_marketplace_when_only_marketplace(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "marketplace:\n owner:\n name: o\n", + ) + bp = _make_producer(OutputKind.BUNDLE, tmp_path / "build") + mp = _make_producer(OutputKind.MARKETPLACE, tmp_path / "m.json") + opts = BuildOptions(project_root=tmp_path, apm_yml_path=apm) + + result = BuildOrchestrator(producers=[bp, mp]).run(opts) + + bp.produce.assert_not_called() + mp.produce.assert_called_once() + assert result.outputs == [tmp_path / "m.json"] + + def test_runs_both_when_both_present(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm:\n - owner/repo\n" + "marketplace:\n owner:\n name: o\n", + ) + bp = _make_producer(OutputKind.BUNDLE, tmp_path / "build") + mp = _make_producer(OutputKind.MARKETPLACE, tmp_path / "m.json") + opts = BuildOptions(project_root=tmp_path, apm_yml_path=apm) + + result = BuildOrchestrator(producers=[bp, mp]).run(opts) + + bp.produce.assert_called_once() + mp.produce.assert_called_once() + assert set(result.outputs) == {tmp_path / "build", tmp_path / "m.json"} + + def test_raises_build_error_when_neither_block_present(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write(apm, "name: x\nversion: 0.1.0\ndescription: y\n") + opts = BuildOptions(project_root=tmp_path, apm_yml_path=apm) + + with pytest.raises(BuildError, match="Nothing to pack"): + BuildOrchestrator().run(opts) + + def test_collects_warnings_from_all_producers(self, tmp_path: Path): + apm = tmp_path / "apm.yml" + _write( + apm, + "name: x\nversion: 0.1.0\ndescription: y\n" + "dependencies:\n apm:\n - owner/repo\n" + "marketplace:\n owner:\n name: o\n", + ) + bp = MagicMock(spec=["kind", "produce"]) + bp.kind = OutputKind.BUNDLE + bp.produce.return_value = ProducerResult( + kind=OutputKind.BUNDLE, outputs=[], warnings=["b-warn"] + ) + mp = MagicMock(spec=["kind", "produce"]) + mp.kind = OutputKind.MARKETPLACE + mp.produce.return_value = ProducerResult( + kind=OutputKind.MARKETPLACE, outputs=[], warnings=["m-warn"] + ) + opts = BuildOptions(project_root=tmp_path, apm_yml_path=apm) + + result = BuildOrchestrator(producers=[bp, mp]).run(opts) + + assert result.warnings == ["b-warn", "m-warn"] + + def test_default_producers_are_bundle_and_marketplace(self): + orch = BuildOrchestrator() + kinds = [p.kind for p in orch._producers] + assert OutputKind.BUNDLE in kinds + assert OutputKind.MARKETPLACE in kinds diff --git a/tests/unit/marketplace/conftest.py b/tests/unit/marketplace/conftest.py index f3a7c9084..12c12ef99 100644 --- a/tests/unit/marketplace/conftest.py +++ b/tests/unit/marketplace/conftest.py @@ -1,35 +1,7 @@ -"""Shared fixtures for ``tests/unit/marketplace/``.""" - -from __future__ import annotations - -from unittest.mock import patch - -import pytest - - -# Cache the *real* is_enabled so we can delegate non-marketplace flags. -from apm_cli.core.experimental import is_enabled as _real_is_enabled +"""Shared fixtures for ``tests/unit/marketplace/``. +The ``marketplace_authoring`` experimental flag was removed when marketplace +authoring went GA -- this conftest no longer patches the flag. +""" -def _marketplace_enabled_is_enabled(name: str) -> bool: - """Stub that forces ``marketplace_authoring`` to True.""" - if name == "marketplace_authoring": - return True - return _real_is_enabled(name) - - -@pytest.fixture(autouse=True) -def _enable_marketplace_flag(): - """Pre-enable the ``marketplace_authoring`` experimental flag. - - Tests in this directory that invoke the ``marketplace`` Click group - need the flag enabled so the group callback does not exit early. - - Patches ``is_enabled`` at the source module so it survives any - config-cache isolation performed by individual tests. - """ - with patch( - "apm_cli.core.experimental.is_enabled", - side_effect=_marketplace_enabled_is_enabled, - ): - yield +from __future__ import annotations diff --git a/tests/unit/marketplace/test_review_fixes.py b/tests/unit/marketplace/test_review_fixes.py index 5e13e1afd..146fcb82e 100644 --- a/tests/unit/marketplace/test_review_fixes.py +++ b/tests/unit/marketplace/test_review_fixes.py @@ -165,35 +165,7 @@ def test_migrate_with_malformed_apm_yml_raises_typed_error( # Followup: apm init --marketplace warns when experimental flag is disabled # --------------------------------------------------------------------------- +# Removed: the ``marketplace_authoring`` experimental flag was deleted when +# marketplace authoring went GA. ``apm init --marketplace`` now appends the +# block unconditionally, so the disabled-flag warning case no longer exists. -class TestInitMarketplaceFlagWarnsWhenExperimentalDisabled: - def test_warns_with_experimental_flag_name( - self, tmp_path: Path, monkeypatch - ): - from unittest.mock import patch as _patch - - from apm_cli.commands.init import init - - runner = CliRunner() - monkeypatch.chdir(tmp_path) - - # Force marketplace_authoring=False even though the autouse - # marketplace fixture flips it to True for everything else. - def _disabled(name: str) -> bool: - if name == "marketplace_authoring": - return False - from apm_cli.core.experimental import is_enabled as real_is - return real_is(name) - - with _patch( - "apm_cli.core.experimental.is_enabled", side_effect=_disabled - ): - result = runner.invoke(init, ["my-proj", "--yes", "--marketplace"]) - - assert result.exit_code == 0, result.output - # Warning text must mention the experimental flag name so the - # user knows what to enable. - assert "marketplace_authoring" in result.output - # And block was still appended (option b: lower friction). - text = (tmp_path / "my-proj" / "apm.yml").read_text(encoding="utf-8") - assert "marketplace:" in text From 2b70e1e981ccfa6cd95a33ea03b2e5cce1d4379e Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Wed, 29 Apr 2026 17:34:50 +0200 Subject: [PATCH 6/6] docs(changelog): condense apm pack entry to one line Per copilot-pull-request-reviewer comment on PR #1042: Keep a Changelog entries should be one concise line per PR. The previous entry (418 chars, multi-clause) is condensed to 165 chars matching the convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f84cf5bc..38b11d843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **Unified `apm pack` entrypoint**: a single command now reads `apm.yml` and produces both bundle (`./build//`) and `.claude-plugin/marketplace.json` when the corresponding blocks are present. Three new flags: `--offline` (forbid network calls during marketplace resolution), `--include-prerelease` (allow non-stable git refs), and `--marketplace-output PATH` (override the marketplace.json destination). (#722) +- `apm pack` produces `.claude-plugin/marketplace.json` when `apm.yml` has a `marketplace:` block; new flags `--offline`, `--include-prerelease`, `--marketplace-output PATH`. (#722) - `marketplace:` block in `apm.yml` unifies catalog authoring with the manifest. (#1038) - `apm marketplace migrate` -- one-shot consolidation of legacy `marketplace.yml` into `apm.yml`. (#1038) - `apm init --marketplace` -- seeds a fresh `apm.yml` with a `marketplace:` authoring block. (#1038)