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 }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd77deecb..38b11d843 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+- `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)
+- 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).
@@ -16,16 +21,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- `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)
+
### 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)
- 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/docs/src/content/docs/guides/marketplace-authoring.md b/docs/src/content/docs/guides/marketplace-authoring.md
index ff5a98623..5beb62f33 100644
--- a/docs/src/content/docs/guides/marketplace-authoring.md
+++ b/docs/src/content/docs/guides/marketplace-authoring.md
@@ -1,113 +1,143 @@
---
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 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 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, 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. `marketplace.yml` is edited; `marketplace.json` is regenerated with `apm marketplace build`.
+Both files are committed. `apm.yml` is edited; `marketplace.json` is regenerated by `apm pack`.
-## 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:
+## Quickstart
-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`.
+```bash
+# 1. Add a marketplace block to your existing apm.yml
+apm marketplace init
-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.
+# 2. Edit the block in apm.yml -- describe each plugin under marketplace.plugins
+$EDITOR apm.yml
-:::caution[Experimental Feature]
-Marketplace authoring commands are behind an experimental flag. Enable it once before following this guide:
+# 3. Build the marketplace
+apm pack
-```bash
-apm experimental enable marketplace-authoring
+# 4. Commit both files
+git add apm.yml .claude-plugin/marketplace.json
+git commit -m "Initial marketplace"
+git push
```
-See [Experimental Flags](../../reference/experimental/) for details.
-:::
+`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.
-## Quickstart
+`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.
-```bash
-# 1. Scaffold a marketplace.yml
-apm marketplace init
+Consumers register your repository with `apm marketplace add /` and install packages from it.
-# 2. Edit marketplace.yml -- add your packages, owner, metadata
-$EDITOR marketplace.yml
+## Real-world example: microsoft/azure-skills
-# 3. Compile to marketplace.json
-apm marketplace build
+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.
-# 4. Commit BOTH files
-git add marketplace.yml marketplace.json
-git commit -m "Initial marketplace"
-git push
+```yaml
+# 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
+ plugins:
+ - name: azure
+ description: Microsoft Azure MCP integration for cloud resource management
+ source: ./.github/plugins/azure-skills
+ homepage: https://github.com/microsoft/azure-skills
```
-Consumers now register your repository with `apm marketplace add /` and install packages from it.
+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 entry: the plugin lives in this same repo.
+- No `tags:` -- empty/absent tags are omitted from `marketplace.json` to match Anthropic's canonical shape.
+
+Build it:
+
+```
+$ apm pack
+[+] Built marketplace.json (1 plugins) -> .claude-plugin/marketplace.json
+```
-## The marketplace.yml schema
+## The `marketplace:` schema
-Full example:
+Full example with both remote and local plugins:
```yaml
-name: my-marketplace
-description: Curated plugins for the acme-org engineering team
+name: my-project
version: 1.2.0
+description: Curated plugins for the acme-org engineering team
-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"
- tagPattern: "monorepo-tool-v{version}"
-
- - name: pinned-package
- description: Pinned to an explicit ref
- source: acme-org/pinned-package
- ref: 3f2a9b1c
+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
+
+ plugins:
+ - name: example-plugin
+ description: Example plugin consumers will see
+ source: acme-org/example-plugin
+ version: "^1.0.0"
+
+ - name: monorepo-tool
+ 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-plugin
+ description: Pinned to an explicit ref
+ source: acme-org/pinned-plugin
+ 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`. |
| `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`.
### The `build` block (APM-only)
@@ -117,32 +147,111 @@ packages:
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 | `/` shape, e.g. `acme-org/example-package`. Resolves to a git remote. |
+| `source` | yes | Either `/` (remote) or `./path/to/dir` (local-path entry 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. |
-| `tag_pattern` | no | Per-package override of `build.tagPattern`. |
+| `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-plugin 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 entries
+
+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
+plugins:
+ - 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
+```
+
+## 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 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:
+
+```bash
+apm marketplace migrate # preview the new apm.yml block
+apm marketplace migrate --yes # apply: rewrite apm.yml, delete marketplace.yml
```
+`--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
APM uses npm-compatible semver ranges. The most common forms:
@@ -157,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 across a range the upstream does not tag cleanly:
+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
```
@@ -172,9 +281,9 @@ packages:
## Managing plugins
-Three subcommands let you manage `marketplace.yml` entries 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 \
@@ -182,64 +291,31 @@ 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 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. Any option accepted by `package add` (except `--name`) can be passed to `package set`.
+`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
```
-`package remove` drops the named entry from `packages:`. 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
-```
-
-| 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 `marketplace.yml`. |
-
-### 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.
+`package remove` drops the named entry. Without `--yes` the command prompts for confirmation.
## 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,20 +326,21 @@ 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
```
-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 |
|---------|-------|-----|
-| `'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). |
+| `'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`. |
| `No cached refs (offline)` | First-ever `--offline` build. | Run once online to populate the cache, then retry offline. |
@@ -271,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. `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
-`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
@@ -290,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:` 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
@@ -300,7 +377,7 @@ Packages pinned with `ref:` show `--` in the range columns; `outdated` cannot re
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`).
@@ -344,10 +421,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. |
@@ -360,43 +437,42 @@ 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
-packages:
- - name: ui-components
- source: acme-org/frontend-monorepo
- subdir: packages/ui-components
- version: "^3.0.0"
- tag_pattern: "ui-components-v{version}"
+marketplace:
+ plugins:
+ - 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`.
+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
-packages:
- - name: example-package
- source: acme-org/example-package
- version: ">=1.0.0-0"
- include_prerelease: true
+marketplace:
+ plugins:
+ - name: example-plugin
+ source: acme-org/example-plugin
+ 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.
+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/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..4ca1a4163 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. 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:**
@@ -552,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
+
+Pack distributable artifacts from your APM project. The manifest drives what gets produced:
-Create a self-contained bundle from installed APM dependencies using the `deployed_files` recorded in `apm.lock.yaml` as the source of truth.
+- `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 |
|--------|------------------------------|
@@ -1257,63 +1283,59 @@ 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. 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]
```
**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.
-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.
+#### `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 build [OPTIONS]
+apm marketplace migrate [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
+- `--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` - Build succeeded (or dry run complete)
-- `1` - Build error (network failure, unresolvable ref, no matching tag)
-- `2` - Schema error in `marketplace.yml`
+- `0` - Migration applied (or dry run complete)
+- `1` - Migration failed (legacy file missing, conflict without `--force`, write failure)
**Examples:**
```bash
-# Compile marketplace.yml -> marketplace.json
-apm marketplace build
-
-# Preview without writing
-apm marketplace build --dry-run
-
-# Offline build against cached refs
-apm marketplace build --offline
+apm marketplace migrate --dry-run
+apm marketplace migrate --yes
```
#### `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 +1349,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 +1357,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 +1372,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 +1382,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 `apm pack` or `publish` fails in an unfamiliar environment.
```bash
apm marketplace doctor [OPTIONS]
@@ -1381,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]
@@ -1418,7 +1440,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 +1478,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 +1513,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/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 55107d9ea..d43f3914b 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 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.
+
---
## 3. Top-Level Fields
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 1eaa36c4f..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` 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 |
## 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,19 +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
+
+> 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` | 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 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 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 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.yml` | `--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 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` |
+
+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 d4df08241..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,62 +195,101 @@ 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.
+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.
-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` 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.
-### 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
+ 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-plugin 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 plugin needs either `version` or `ref`.
- `ref` takes precedence over `version`.
+- `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 package source,
-picks the highest tag satisfying the range (under the applicable
-`tagPattern`), and writes `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`.
-4. Does not emit `versions[]` -- each plugin carries a single resolved ref.
+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; `apm marketplace init` no longer
+creates one. 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..dcedbce97 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,
@@ -71,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 = []
@@ -188,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):
@@ -240,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)
@@ -281,8 +258,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",
@@ -317,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",
]
@@ -355,8 +346,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
@@ -864,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
# ---------------------------------------------------------------------------
@@ -1005,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)
@@ -1262,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)
@@ -1441,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 = []
@@ -1767,7 +1627,6 @@ def publish(
verbose,
):
"""Publish marketplace updates to consumer repositories."""
- _require_authoring_flag()
logger = CommandLogger("marketplace-publish", verbose=verbose)
# ------------------------------------------------------------------
@@ -1781,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)
@@ -2239,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 4a108ca5c..02d3536dd 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
@@ -13,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:
@@ -84,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/src/apm_cli/marketplace/migration.py b/src/apm_cli/marketplace/migration.py
index a5eba6238..31f5cd7b1 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:
@@ -229,7 +249,26 @@ 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
+ # 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:
diff --git a/src/apm_cli/marketplace/yml_editor.py b/src/apm_cli/marketplace/yml_editor.py
index dea5fc1cf..b17f9cded 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,
@@ -65,18 +64,23 @@ 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``,
``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/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_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..146fcb82e
--- /dev/null
+++ b/tests/unit/marketplace/test_review_fixes.py
@@ -0,0 +1,171 @@
+"""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
+
+
+# ---------------------------------------------------------------------------
+# 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
+# ---------------------------------------------------------------------------
+
+# 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.
+
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=(