diff --git a/.github/workflows/advanced-copilot-cli-sync.lock.yml b/.github/workflows/advanced-copilot-cli-sync.lock.yml
new file mode 100644
index 00000000..e0bdae8b
--- /dev/null
+++ b/.github/workflows/advanced-copilot-cli-sync.lock.yml
@@ -0,0 +1,1695 @@
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"84d335b3a809f564fbd2f8edd493ebe6d52f770e32ef0ec29ad37b4c1216b594","body_hash":"286af4ebf9cdaad5ea54400415ed98d2c244c019fd888e06a2450f1a54ab104d","compiler_version":"v0.84.3","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.77"}}
+# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c863074b673419603d146aab585e2986ef08deec","version":"v0.84.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]}
+# This file was automatically generated by gh-aw (v0.84.3). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
+#
+# Weekly check for updates to github-samples/advanced-copilot-cli. Opens a PR to keep the Learning Hub mirror aligned when substantive upstream course changes are detected.
+#
+# Secrets used:
+# - COPILOT_GITHUB_TOKEN
+# - GH_AW_CI_TRIGGER_TOKEN
+# - GH_AW_GITHUB_MCP_SERVER_TOKEN
+# - GH_AW_GITHUB_TOKEN
+# - GITHUB_TOKEN
+#
+# Custom actions used:
+# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
+# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+# - github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+#
+# Container images used:
+# - ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1
+# - ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d
+# - ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00
+# - ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196
+# - ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520
+
+name: "Advanced Copilot CLI Content Sync"
+on:
+ schedule:
+ - cron: "39 14 * * 4" # Friendly format: weekly (scattered)
+ workflow_dispatch:
+ inputs:
+ aw_context:
+ default: ""
+ description: "Agent caller context (used internally by Agentic Workflows)."
+ required: false
+ type: string
+
+permissions: {}
+
+concurrency:
+ group: "gh-aw-${{ github.workflow }}"
+
+run-name: "Advanced Copilot CLI Content Sync"
+
+jobs:
+ activation:
+ runs-on: ubuntu-slim
+ permissions:
+ actions: read
+ contents: read
+ env:
+ GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
+ daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
+ engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
+ lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }}
+ setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
+ setup-span-id: ${{ steps.setup.outputs.span-id }}
+ setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Generate agentic run info
+ id: generate_aw_info
+ 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 || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AGENT_VERSION: "1.0.77"
+ GH_AW_INFO_CLI_VERSION: "v0.84.3"
+ GH_AW_INFO_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_INFO_EXPERIMENTAL: "false"
+ GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
+ GH_AW_INFO_STAGED: "false"
+ GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
+ GH_AW_INFO_FIREWALL_ENABLED: "true"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_COMPILED_STRICT: "true"
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Restore daily AIC usage cache
+ id: restore-daily-aic-cache
+ if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
+ continue-on-error: true
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: agentic-workflow-usage-advancedcopilotclisync-${{ github.run_id }}
+ restore-keys: agentic-workflow-usage-advancedcopilotclisync-
+ path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
+ - name: Restore daily AIC usage cache (artifact fallback)
+ id: restore-daily-aic-cache-fallback
+ if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }}
+ GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }}
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ 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/restore_aic_usage_cache_fallback.cjs');
+ await main();
+ - name: Check daily workflow token guardrail
+ id: daily-effective-workflow-guardrail
+ if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_ID: "advanced-copilot-cli-sync"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }}
+ GH_AW_HAS_SLASH_COMMAND: "false"
+ GH_AW_HAS_LABEL_COMMAND: "false"
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ 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/check_daily_aic_workflow_guardrail.cjs');
+ await main();
+ - name: Check for OAuth tokens
+ id: check-oauth-tokens
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .github
+ .agents
+ .antigravity
+ .claude
+ .codex
+ .gemini
+ .opencode
+ .pi
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ - name: Save agent config folders for base branch restoration
+ env:
+ GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
+ GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ - name: Check workflow lock file
+ id: check-lock-file
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_WORKFLOW_FILE: "advanced-copilot-cli-sync.lock.yml"
+ GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
+ 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/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Check compile-agentic version
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_COMPILED_VERSION: "v0.84.3"
+ 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/check_version_updates.cjs');
+ await main();
+ - name: Log runtime features
+ if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ # poutine:ignore untrusted_checkout_exec
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
+ {
+ cat << 'GH_AW_PROMPT_3142e03166035497_EOF'
+
+ GH_AW_PROMPT_3142e03166035497_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_3142e03166035497_EOF'
+
+ Tools: create_pull_request, missing_tool, missing_data, noop
+ GH_AW_PROMPT_3142e03166035497_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
+ cat << 'GH_AW_PROMPT_3142e03166035497_EOF'
+
+ GH_AW_PROMPT_3142e03166035497_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
+ cat << 'GH_AW_PROMPT_3142e03166035497_EOF'
+
+ The following GitHub context information is available for this workflow:
+ {{#if github.actor}}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if github.repository}}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if github.workspace}}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
+ - **issue-number**: #__GH_AW_EXPR_802A9F6A__
+ {{/if}}
+ {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
+ - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
+ {{/if}}
+ {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
+ - **pull-request-number**: #__GH_AW_EXPR_463A214A__
+ {{/if}}
+ {{#if github.event.comment.id || github.aw.context.comment_id}}
+ - **comment-id**: __GH_AW_EXPR_FF1D34CE__
+ {{/if}}
+ {{#if github.run_id}}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_3142e03166035497_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ cat << 'GH_AW_PROMPT_3142e03166035497_EOF'
+
+ {{#runtime-import .github/workflows/advanced-copilot-cli-sync.md}}
+ GH_AW_PROMPT_3142e03166035497_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ENGINE_ID: "copilot"
+ 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/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ALLOWED_EXTENSIONS: ''
+ GH_AW_CACHE_DESCRIPTION: ''
+ GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/'
+ GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
+ GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ 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: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+
+ const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS,
+ GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION,
+ GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR,
+ GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A,
+ GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A,
+ GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A,
+ GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE,
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ 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
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Upload activation artifact
+ if: success()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: activation
+ include-hidden-files: true
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/models.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/aw-prompts/prompt-template.txt
+ /tmp/gh-aw/aw-prompts/prompt-import-tree.json
+ /tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/base
+ /tmp/gh-aw/.github/agents
+ /tmp/gh-aw/.github/skills
+ if-no-files-found: ignore
+ retention-days: 1
+
+ agent:
+ needs: activation
+ if: needs.activation.outputs.daily_ai_credits_exceeded != 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ copilot-requests: write
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ queue: max
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ GH_AW_WORKFLOW_ID_SANITIZED: advancedcopilotclisync
+ outputs:
+ agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }}
+ ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }}
+ aic: ${{ steps.parse-mcp-gateway.outputs.aic }}
+ ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }}
+ cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }}
+ cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }}
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
+ inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
+ invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
+ mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
+ model: ${{ needs.activation.outputs.model }}
+ model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
+ setup-span-id: ${{ steps.setup.outputs.span-id }}
+ setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Set runtime paths
+ id: set-runtime-paths
+ run: |
+ {
+ echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
+ echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
+ echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
+ } >> "$GITHUB_OUTPUT"
+ - name: Checkout repository
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh"
+ - name: Configure gh CLI for GitHub Enterprise
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh"
+ env:
+ GH_TOKEN: ${{ github.token }}
+ - name: Download activation artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
+ # Cache memory file share configuration from frontmatter processed below
+ - name: Create cache-memory directory
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh"
+ - name: Restore cache-memory file share data
+ id: restore_cache_memory_0
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
+ path: /tmp/gh-aw/cache-memory
+ restore-keys: |
+ memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-
+ - name: Setup cache-memory git repository
+ env:
+ GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory
+ GH_AW_MIN_INTEGRITY: none
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh"
+ - name: Configure Git credentials
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request'
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/checkout_pr_branch.cjs');
+ await main();
+ - name: Install GitHub Copilot CLI
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
+ env:
+ GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.84.3
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.43 --rootless
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ with:
+ script: |
+ const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Restore agent config folders from base branch
+ if: steps.checkout-pr.outcome == 'success'
+ env:
+ GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
+ GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
+ - name: Restore inline sub-agents from activation artifact
+ env:
+ GH_AW_SUB_AGENT_DIR: ".github/agents"
+ GH_AW_SUB_AGENT_EXT: ".agent.md"
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh"
+ - name: Restore inline skills from activation artifact
+ env:
+ GH_AW_SKILL_DIR: ".github/skills"
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
+ - name: Download container images
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1 ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00 ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520
+ - name: Generate 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_de9e9697a4e82056_EOF'
+ {"create_pull_request":{"base_branch":"main","labels":["automated-update","learning-hub","advanced-copilot-cli"],"max":1,"max_patch_files":100,"max_patch_size":4096,"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","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[bot] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_de9e9697a4e82056_EOF
+ - name: Generate Safe Outputs Tools
+ env:
+ GH_AW_TOOLS_META_JSON: |
+ {
+ "description_suffixes": {
+ "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[bot] \". Labels [\"automated-update\" \"learning-hub\" \"advanced-copilot-cli\"] will be automatically added."
+ },
+ "repo_params": {},
+ "dynamic_tools": []
+ }
+ GH_AW_VALIDATION_JSON: |
+ {
+ "create_pull_request": {
+ "defaultMax": 1,
+ "fields": {
+ "base": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "branch": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "draft": {
+ "type": "boolean"
+ },
+ "labels": {
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ },
+ "title": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ },
+ "report_incomplete": {
+ "defaultMax": 5,
+ "fields": {
+ "details": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 1024
+ }
+ }
+ }
+ }
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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/generate_safe_outputs_tools.cjs');
+ await main();
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }}
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
+ GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
+ GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="8080"
+ export MCP_GATEWAY_DOMAIN="awmg-mcpg"
+ export MCP_GATEWAY_HOST_DOMAIN="localhost"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
+ MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
+ source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/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 DOCKER_HOST=unix:///var/run/docker.sock -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_POLICY_ALLOW_CREATE_PULL_REQUEST -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 GH_AW_SINK_VISIBILITY -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 RUNNER_TEMP -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 -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.7'
+
+ mkdir -p "$HOME/.copilot"
+ GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
+ cat << GH_AW_MCP_CONFIG_803efe5dd620c71c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ {
+ "mcpServers": {
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v1.8.0",
+ "env": {
+ "GITHUB_FEATURES": "fields_param",
+ "GITHUB_HOST": "${GITHUB_SERVER_URL}",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "repos"
+ },
+ "guard-policies": {
+ "allow-only": {
+ "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
+ "repos": "$GITHUB_MCP_GUARD_REPOS"
+ }
+ }
+ },
+ "safeoutputs": {
+ "type": "stdio",
+ "container": "ghcr.io/github/gh-aw-node",
+ "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"],
+ "args": ["-w", "\${GITHUB_WORKSPACE}"],
+ "entrypoint": "sh",
+ "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"],
+ "env": {
+ "DEBUG": "*",
+ "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}",
+ "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
+ "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
+ "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
+ "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}",
+ "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
+ "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
+ "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
+ "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
+ "GITHUB_SHA": "\${GITHUB_SHA}",
+ "GITHUB_TOKEN": "\${GITHUB_TOKEN}",
+ "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}",
+ "RUNNER_TEMP": "\${RUNNER_TEMP}"
+ },
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ],
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
+ }
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
+ "startupTimeout": 120
+ }
+ }
+ GH_AW_MCP_CONFIG_803efe5dd620c71c_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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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 credentials
+ continue-on-error: true
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh"
+ - name: Audit pre-agent workspace
+ id: pre_agent_audit
+ continue-on-error: true
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh"
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ mkdir -p "$HOME/.copilot"
+ printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
+ export XDG_CONFIG_HOME="$HOME"
+ export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ touch /tmp/gh-aw/agent-step-summary.md
+ GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
+ export GH_AW_NODE_BIN
+ export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
+ (umask 177 && touch /tmp/gh-aw/agent-stdio.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"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\",\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/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 || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; 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 /tmp/gh-aw/cache-memory/ --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:
+ AWF_REFLECT_ENABLED: 1
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
+ COPILOT_GITHUB_TOKEN: ${{ github.token }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_LLM_PROVIDER: github
+ GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
+ GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
+ 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_TIMEOUT_MINUTES: 20
+ GH_AW_VERSION: v0.84.3
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ RUNNER_TEMP: ${{ runner.temp }}
+ S2STOKENS: true
+ TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
+ - name: Detect agent errors
+ if: always()
+ id: detect-agent-errors
+ continue-on-error: true
+ run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ - name: Configure Git credentials
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh"
+ - name: Stop MCP Gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Append agent step summary
+ if: always()
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh"
+ - name: Copy Safe Outputs
+ if: always()
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ run: |
+ mkdir -p /tmp/gh-aw
+ cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
+ - name: Ingest agent output
+ id: collect_output
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "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,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,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,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"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ 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/collect_ndjson_output.cjs');
+ await main();
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ 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_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ id: parse-mcp-gateway
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless
+ - name: Parse token usage for step summary
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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_token_usage.cjs');
+ await main();
+ - name: Print AWF reflect summary
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ 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/awf_reflect_summary.cjs');
+ await main();
+ - name: Write agent output placeholder if missing
+ if: always()
+ run: |
+ if [ ! -f /tmp/gh-aw/agent_output.json ]; then
+ echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
+ fi
+ - name: Commit cache-memory changes
+ if: always()
+ env:
+ GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/commit_cache_memory_git.sh"
+ - name: Check cache-memory git integrity
+ if: always()
+ continue-on-error: true
+ env:
+ GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh"
+ - name: Upload cache-memory data as artifact
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: always()
+ with:
+ name: cache-memory
+ include-hidden-files: true
+ path: /tmp/gh-aw/cache-memory
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/agent_usage.json
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/pre-agent-audit.txt
+ /tmp/gh-aw/agent/
+ /tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/safeoutputs.jsonl
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/aw-*.patch
+ /tmp/gh-aw/aw-*.bundle
+ /tmp/gh-aw/awf-config.json
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/sandbox/firewall/audit/
+ /tmp/gh-aw/sandbox/firewall/awf-reflect.json
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ - update_cache_memory
+ if: >
+ always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
+ needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ concurrency:
+ group: "gh-aw-conclusion-advanced-copilot-cli-sync"
+ cancel-in-progress: false
+ queue: max
+ env:
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ outputs:
+ incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }}
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ id: setup-agent-output-env
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ - name: Download safe outputs items manifest
+ id: download-safe-outputs-manifest
+ if: always()
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: safe-outputs-items
+ path: /tmp/gh-aw/
+ - name: Collect usage artifact files
+ if: always()
+ continue-on-error: true
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
+ - name: Upload usage artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: usage
+ path: |
+ /tmp/gh-aw/usage/aw_info.json
+ /tmp/gh-aw/usage/aw-info.jsonl
+ /tmp/gh-aw/usage/agent_usage.json
+ /tmp/gh-aw/usage/agent_usage.jsonl
+ /tmp/gh-aw/usage/detection_usage.jsonl
+ /tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/github_rate_limits.jsonl
+ /tmp/gh-aw/usage/agent/token_usage.jsonl
+ /tmp/gh-aw/usage/detection/token_usage.jsonl
+ /tmp/gh-aw/usage/activity/summary.json
+ if-no-files-found: ignore
+ - name: Restore daily AIC usage cache
+ id: restore-daily-aic-cache-conclusion
+ if: always()
+ continue-on-error: true
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: agentic-workflow-usage-advancedcopilotclisync-${{ github.run_id }}
+ restore-keys: agentic-workflow-usage-advancedcopilotclisync-
+ path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
+ - name: Write daily AIC usage cache entry
+ id: write-daily-aic-cache
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ await main();
+ - name: Save daily AIC usage cache
+ id: save-daily-aic-cache
+ if: always()
+ continue-on-error: true
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: agentic-workflow-usage-advancedcopilotclisync-${{ github.run_id }}
+ path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
+ - name: Upload daily AIC usage cache artifact
+ id: upload-daily-aic-cache
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: aic-usage-cache
+ path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
+ if-no-files-found: ignore
+ retention-days: 7
+ - name: Process no-op messages
+ id: noop
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_AIC: ${{ needs.agent.outputs.aic }}
+ GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
+ GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
+ GH_AW_WORKFLOW_ID: "advanced-copilot-cli-sync"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/handle_noop_message.cjs');
+ await main();
+ - name: Log detection run
+ id: detection_runs
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
+ GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/handle_detection_runs.cjs');
+ await main();
+ - name: Record missing tool
+ id: missing_tool
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/missing_tool.cjs');
+ await main();
+ - name: Record incomplete
+ id: report_incomplete
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true"
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/report_incomplete_handler.cjs');
+ await main();
+ - name: Handle agent failure
+ id: handle_agent_failure
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "advanced-copilot-cli-sync"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
+ GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }}
+ GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }}
+ GH_AW_AIC: ${{ needs.agent.outputs.aic }}
+ GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
+ GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
+ GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ 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_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ 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 }}
+ GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
+ GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
+ GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }}
+ GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }}
+ GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }}
+ GH_AW_GROUP_REPORTS: "false"
+ GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
+ GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true"
+ GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true"
+ GH_AW_TIMEOUT_MINUTES: "20"
+ GH_AW_CACHE_MEMORY_ENABLED: "true"
+ GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }}
+ GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/handle_agent_failure.cjs');
+ await main();
+
+ detection:
+ needs:
+ - activation
+ - agent
+ if: always() && needs.agent.result != 'skipped'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ copilot-requests: write
+ env:
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ outputs:
+ aic: ${{ steps.parse_detection_token_usage.outputs.aic }}
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_reason: ${{ steps.detection_conclusion.outputs.reason }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ id: setup-agent-output-env
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ - name: Checkout repository for patch context
+ if: needs.agent.outputs.has_patch == 'true'
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ # --- Threat Detection ---
+ - name: Clean stale firewall files from agent artifact
+ run: |
+ 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.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1 ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d
+ - name: Check if detection needed
+ id: detection_guard
+ if: always()
+ env:
+ OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ run: |
+ if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
+ echo "run_detection=true" >> "$GITHUB_OUTPUT"
+ echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
+ else
+ echo "run_detection=false" >> "$GITHUB_OUTPUT"
+ echo "Detection skipped: no agent outputs or patches to analyze"
+ fi
+ - 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"
+ rm -f "$HOME/.copilot/mcp-config.json"
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ rm -f /tmp/gh-aw/agent_usage.json
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
+ echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
+ fi
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ if [ -f "$f" ]; then
+ cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ fi
+ done
+ for f in /tmp/gh-aw/aw-*.bundle; do
+ if [ -f "$f" ]; then
+ cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ fi
+ done
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ - name: Setup threat detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ WORKFLOW_DESCRIPTION: "Weekly check for updates to github-samples/advanced-copilot-cli. Opens a PR to keep the Learning Hub mirror aligned when substantive upstream course changes are detected."
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ 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/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Setup Node.js
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: '24'
+ package-manager-cache: false
+ - name: Install GitHub Copilot CLI
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
+ env:
+ GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.84.3
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.43
+ - 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
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ mkdir -p "$HOME/.copilot"
+ printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
+ export XDG_CONFIG_HOME="$HOME"
+ touch /tmp/gh-aw/agent-step-summary.md
+ GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
+ export GH_AW_NODE_BIN
+ export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/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 || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; 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:
+ AWF_REFLECT_ENABLED: 1
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
+ COPILOT_GITHUB_TOKEN: ${{ github.token }}
+ COPILOT_MODEL: detection
+ GH_AW_LLM_PROVIDER: github
+ GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
+ GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
+ GH_AW_PHASE: detection
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_TIMEOUT_MINUTES: 20
+ GH_AW_VERSION: v0.84.3
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ RUNNER_TEMP: ${{ runner.temp }}
+ S2STOKENS: true
+ TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ 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_token_usage.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+ - name: Parse and conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ with:
+ script: |
+ 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 detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
+ 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 && !detectionExecutionFailed) {
+ 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:
+ - activation
+ - agent
+ - detection
+ if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ timeout-minutes: 45
+ env:
+ GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }}
+ GH_AW_AIC: ${{ needs.agent.outputs.aic }}
+ GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/advanced-copilot-cli-sync"
+ GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
+ GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
+ GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
+ GH_AW_WORKFLOW_ID: "advanced-copilot-cli-sync"
+ GH_AW_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/advanced-copilot-cli-sync.md"
+ outputs:
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }}
+ created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ id: setup-agent-output-env
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ - name: Download patch artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Checkout repository
+ if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request')
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: true
+ token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ - name: Configure Git credentials
+ if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request')
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
+ - name: Configure GH_HOST for enterprise compatibility
+ id: ghes-host-config
+ shell: bash
+ run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
+ # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
+ # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
+ GH_HOST="${GITHUB_SERVER_URL#https://}"
+ GH_HOST="${GH_HOST#http://}"
+ echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
+ GH_AW_ALLOWED_DOMAINS: "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,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,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,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"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"main\",\"labels\":[\"automated-update\",\"learning-hub\",\"advanced-copilot-cli\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"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\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[bot] \"},\"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 }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ 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/process_safe_outputs.cjs');
+ await main();
+ - name: Upload Safe Outputs Items
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: safe-outputs-items
+ path: |
+ /tmp/gh-aw/safe-output-items.jsonl
+ /tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/process-safe-outputs.stdout.log
+ /tmp/gh-aw/process-safe-outputs.stderr.log
+ if-no-files-found: ignore
+
+ update_cache_memory:
+ needs:
+ - activation
+ - agent
+ - detection
+ if: always() && needs.detection.result == 'success' && needs.agent.result == 'success'
+ runs-on: ubuntu-slim
+ permissions:
+ actions: write
+ env:
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ GH_AW_WORKFLOW_ID_SANITIZED: advancedcopilotclisync
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@c863074b673419603d146aab585e2986ef08deec # v0.84.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Advanced Copilot CLI Content Sync"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/advanced-copilot-cli-sync.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.77"
+ GH_AW_INFO_AWF_VERSION: "v0.27.43"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download cache-memory artifact (default)
+ id: download_cache_default
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ continue-on-error: true
+ with:
+ name: cache-memory
+ path: /tmp/gh-aw/cache-memory
+ - name: Check if cache-memory folder has content (default)
+ id: check_cache_default
+ shell: bash
+ run: |
+ if [ -d "/tmp/gh-aw/cache-memory" ] && [ "$(ls -A /tmp/gh-aw/cache-memory 2>/dev/null)" ]; then
+ echo "has_content=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "has_content=false" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Save cache-memory to cache (default)
+ if: steps.check_cache_default.outputs.has_content == 'true'
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+ with:
+ key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
+ path: /tmp/gh-aw/cache-memory
diff --git a/.github/workflows/advanced-copilot-cli-sync.md b/.github/workflows/advanced-copilot-cli-sync.md
new file mode 100644
index 00000000..1b9408aa
--- /dev/null
+++ b/.github/workflows/advanced-copilot-cli-sync.md
@@ -0,0 +1,149 @@
+---
+name: "Advanced Copilot CLI Content Sync"
+description: "Weekly check for updates to github-samples/advanced-copilot-cli. Opens a PR to keep the Learning Hub mirror aligned when substantive upstream course changes are detected."
+on:
+ schedule: weekly
+permissions:
+ contents: read
+ copilot-requests: write
+tools:
+ github:
+ toolsets: [repos]
+ cache-memory: true
+safe-outputs:
+ create-pull-request:
+ labels: [automated-update, learning-hub, advanced-copilot-cli]
+ title-prefix: "[bot] "
+ base-branch: main
+ allowed-files:
+ - "website/src/content/docs/learning-hub/advanced-copilot-cli/**"
+ - "website/public/images/learning-hub/advanced-copilot-cli/**"
+ - "website/astro.config.mjs"
+ - "website/src/content/docs/learning-hub/index.md"
+---
+
+# Advanced Copilot CLI Content Sync
+
+You are a documentation sync agent for the **awesome-copilot** Learning Hub. Your job is to check whether the upstream source repository [`github-samples/advanced-copilot-cli`](https://github.com/github-samples/advanced-copilot-cli) has received any meaningful updates since your last run, and — if it has — update the Learning Hub mirror so it stays aligned with the upstream course.
+
+## Step 1 — Determine what's new in the upstream repo
+
+1. Read `cache-memory` and look for a file named `advanced-copilot-cli-sync-state.json`. It may contain:
+ - `last_synced_sha` — the most recent commit SHA you processed on your previous run
+ - `last_synced_at` — a filesystem-safe timestamp in the format `YYYY-MM-DD-HH-MM-SS`
+
+2. Use GitHub tools to fetch recent commits from `github-samples/advanced-copilot-cli` (default branch):
+ - If `last_synced_sha` exists, list commits **since that SHA** (stop once you reach it).
+ - If no cached state exists, list commits from the **past 7 days**.
+
+3. Identify which files changed across those commits. Focus on:
+ - Markdown files under `content/` — the course module content
+ - Supporting assets referenced by the course material, especially screenshots and GIFs under `content/images/`
+ - Any configuration or metadata files that materially affect the course content or navigation
+
+4. If **no commits** were found since the last sync, stop here and call the `noop` safe output with a message like: "No new commits found in `github-samples/advanced-copilot-cli` since last sync (``). No action needed." Then update the cache with the latest SHA.
+
+## Step 2 — Read the changed upstream content
+
+For each file that changed in the upstream repo, use GitHub tools to fetch the **current file contents** from `github-samples/advanced-copilot-cli`. Pay close attention to:
+
+- New sections, commands, flags, or concepts introduced
+- Renamed or restructured sections
+- Deprecated commands or workflows that have been removed
+- Updated screenshots, GIFs, image references, or code examples
+- Links to new official documentation or resources
+
+## Step 3 — Compare against the local Learning Hub content
+
+Read the local files in the canonical Learning Hub course folder `website/src/content/docs/learning-hub/advanced-copilot-cli/`:
+
+```
+website/src/content/docs/learning-hub/advanced-copilot-cli/
+├── index.md
+├── 00-prerequisites.md
+├── 01-working-with-copilot-cli.md
+├── 02-building-ai-infrastructure.md
+├── 03-test-suite-remote-delegation.md
+├── 04-lifecycle-hooks.md
+├── 05-add-feature-barcode.md
+├── 06-modernize-apps.md
+├── 07-manage-infrastructure.md
+└── 08-wrap-up.md
+```
+
+The upstream module files under `content/` map to local pages by matching filename, so `content/00-prerequisites.md` mirrors `00-prerequisites.md`, and so on. The upstream `README.md` maps to the local `index.md` overview page.
+
+Also inspect local course assets in `website/public/images/learning-hub/advanced-copilot-cli/` when upstream changes touch screenshots, banners, or GIFs.
+
+If the upstream changes alter course structure or navigation, you may also need to inspect:
+
+- `website/astro.config.mjs`
+- `website/src/content/docs/learning-hub/index.md`
+
+Map the upstream changes to the relevant local file(s). Ask yourself:
+
+- Is the local mirror missing any upstream content, structure, assignments, examples, or visuals?
+- Is any existing Learning Hub content now outdated or incorrect based on upstream changes?
+- Do local route rewrites, repo-link rewrites, or asset paths need updating so the mirrored pages still work on the website?
+- Do the Astro frontmatter fields (especially `lastUpdated`) need updating because the mirrored page changed?
+
+If the local content is already fully consistent with the upstream changes — or the upstream changes are non-substantive (e.g., only CI config, typo fixes, or internal tooling changes) — stop here and call the `noop` safe output with a brief explanation. Still update the cache with the latest commit SHA.
+
+## Step 4 — Update the Learning Hub files
+
+For each local file that needs updating:
+
+1. Edit the relevant local docs, assets, and supporting navigation files so the website remains a **source-faithful mirror** of the upstream course:
+ - Add or update missing concepts, commands, flags, steps, assignments, demos, and visuals
+ - Correct or remove outdated information
+ - Localize newly added screenshots or GIFs into `website/public/images/learning-hub/advanced-copilot-cli/`
+ - Bump the `lastUpdated` frontmatter field to today's date (`YYYY-MM-DD`) for any page whose mirrored content changed
+
+2. Keep a **mirror-first** approach:
+ - Preserve upstream wording, headings, section order, assignments, and overall module flow as closely as practical
+ - Do not summarize, reinterpret, or "website-optimize" the course into a different learning experience
+ - Only adapt what the website requires: Astro frontmatter, route-safe internal links, GitHub repo links, local asset paths, and minor HTML/CSS hooks needed for presentation
+ - The upstream module files carry no Astro frontmatter and use reference-style navigation and cross-module links; when mirroring, add the Astro frontmatter and rewrite reference-style links that point at sibling module files (for example `[next-lesson]: ./01-working-with-copilot-cli.md`) into route-safe links to the mirrored page (for example `[next-lesson]: ../01-working-with-copilot-cli/`)
+ - Rewrite local image references (for example ``) to the localized website path `/images/learning-hub/advanced-copilot-cli/`
+ - Convert repo-root relative links that are invalid on the published website into absolute links to `https://github.com/github-samples/advanced-copilot-cli` (use `/tree/main/...` for directories and `/blob/main/...` for files)
+
+3. If upstream adds, removes, or renames major sections or modules:
+ - Create, delete, or rename the corresponding markdown files in `website/src/content/docs/learning-hub/advanced-copilot-cli/`
+ - Update `website/astro.config.mjs` if the sidebar module list must change
+ - Update `website/src/content/docs/learning-hub/index.md` only if the landing page's course entry must change
+
+## Step 5 — Update the sync state cache
+
+Before opening the PR, write an updated `advanced-copilot-cli-sync-state.json` to `cache-memory` with:
+
+```json
+{
+ "last_synced_sha": "",
+ "last_synced_at": "",
+ "files_reviewed": [""],
+ "files_updated": [""]
+}
+```
+
+## Step 6 — Open a pull request
+
+Create a pull request with your changes using the `create-pull-request` safe output. Use `main` as the base branch for all work related to this workflow. The PR body must include:
+
+1. **What changed upstream** — a concise summary of the commits and file changes found in `github-samples/advanced-copilot-cli`
+2. **What was updated locally** — list each mirrored Learning Hub file or asset you edited and what changed
+3. **Source links** — links to the relevant upstream commits or files
+4. A note that the markdown body of this workflow can be edited directly on GitHub.com without recompilation
+
+If there is nothing to change after your analysis, do **not** open a PR. Instead, call the `noop` safe output.
+
+## Guidelines
+
+- The canonical course content lives in `website/src/content/docs/learning-hub/advanced-copilot-cli/`; do not recreate legacy duplicates elsewhere
+- Prefer changes within the course docs and `website/public/images/learning-hub/advanced-copilot-cli/`
+- Only edit `website/astro.config.mjs` or `website/src/content/docs/learning-hub/index.md` when upstream course structure or navigation truly requires it
+- Preserve existing frontmatter fields; only update `lastUpdated` and `description` if genuinely warranted
+- Keep the course source-faithful; avoid summaries or interpretive rewrites
+- Use `main` as the base branch for any branch or PR created by this workflow
+- Do not auto-merge; the PR is for human review
+- If you are uncertain whether an upstream change warrants a Learning Hub update, err on the side of creating the PR — a human reviewer can always decline
+- Always call either `create-pull-request` or `noop` at the end of your run so the workflow clearly signals its outcome
diff --git a/website/astro.config.mjs b/website/astro.config.mjs
index 6c11bef2..80e63448 100644
--- a/website/astro.config.mjs
+++ b/website/astro.config.mjs
@@ -154,6 +154,24 @@ export default defineConfig({
"learning-hub/cli-for-beginners/07-putting-it-all-together",
],
},
+ {
+ label: "Advanced Copilot CLI",
+ items: [
+ {
+ label: "Overview",
+ link: "/learning-hub/advanced-copilot-cli/",
+ },
+ "learning-hub/advanced-copilot-cli/00-prerequisites",
+ "learning-hub/advanced-copilot-cli/01-working-with-copilot-cli",
+ "learning-hub/advanced-copilot-cli/02-building-ai-infrastructure",
+ "learning-hub/advanced-copilot-cli/03-test-suite-remote-delegation",
+ "learning-hub/advanced-copilot-cli/04-lifecycle-hooks",
+ "learning-hub/advanced-copilot-cli/05-add-feature-barcode",
+ "learning-hub/advanced-copilot-cli/06-modernize-apps",
+ "learning-hub/advanced-copilot-cli/07-manage-infrastructure",
+ "learning-hub/advanced-copilot-cli/08-wrap-up",
+ ],
+ },
{
label: "Copilot Workshops",
items: [
diff --git a/website/public/images/learning-hub/advanced-copilot-cli/03-copilot-work-surfaces.png b/website/public/images/learning-hub/advanced-copilot-cli/03-copilot-work-surfaces.png
new file mode 100644
index 00000000..e18b7b91
Binary files /dev/null and b/website/public/images/learning-hub/advanced-copilot-cli/03-copilot-work-surfaces.png differ
diff --git a/website/public/images/learning-hub/advanced-copilot-cli/03-delegation-handoff-flow.png b/website/public/images/learning-hub/advanced-copilot-cli/03-delegation-handoff-flow.png
new file mode 100644
index 00000000..f90b03e8
Binary files /dev/null and b/website/public/images/learning-hub/advanced-copilot-cli/03-delegation-handoff-flow.png differ
diff --git a/website/public/images/learning-hub/advanced-copilot-cli/03-remote-control-flow.png b/website/public/images/learning-hub/advanced-copilot-cli/03-remote-control-flow.png
new file mode 100644
index 00000000..02744540
Binary files /dev/null and b/website/public/images/learning-hub/advanced-copilot-cli/03-remote-control-flow.png differ
diff --git a/website/public/images/learning-hub/advanced-copilot-cli/03-test-backed-workflow.png b/website/public/images/learning-hub/advanced-copilot-cli/03-test-backed-workflow.png
new file mode 100644
index 00000000..69143bde
Binary files /dev/null and b/website/public/images/learning-hub/advanced-copilot-cli/03-test-backed-workflow.png differ
diff --git a/website/public/images/learning-hub/advanced-copilot-cli/03-test-evidence-loop.png b/website/public/images/learning-hub/advanced-copilot-cli/03-test-evidence-loop.png
new file mode 100644
index 00000000..d0280da3
Binary files /dev/null and b/website/public/images/learning-hub/advanced-copilot-cli/03-test-evidence-loop.png differ
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/00-prerequisites.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/00-prerequisites.md
new file mode 100644
index 00000000..d6f8ea63
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/00-prerequisites.md
@@ -0,0 +1,109 @@
+---
+title: '00 · Prerequisites'
+description: 'Set up your Codespaces-based environment and prerequisites for the Advanced GitHub Copilot CLI course.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 0 — Prerequisites and environment setup
+
+| | [Next: Getting started with Copilot CLI →][next-lesson] |
+|:--|--:|
+
+The first step to using any tool or beginning development on a new project is installation. In our case, we'll need:
+
+- Copilot CLI
+- A new instance of the project
+- SDKs, frameworks and libraries installed
+
+To streamline the last bullet point (local installation of SDKs, frameworks and libraries), this course is built to use [Codespaces][codespaces-docs]. Codespaces are cloud-based containers which are configured using a dev container definition. Once launched, you can interact with your codespace through an instance of VS Code running in your browser, so no local installation is required!
+
+> [!IMPORTANT]
+> The content in this course will always use codespaces as the interface for the lab. You can run the course locally by using a [dev container running locally in VS Code][vscode-devcontainers], or installing all the necessary software on your system. The "green path" is to work through the content through codespaces, which will be our focus.
+
+## Scenario
+
+You've recently joined Contoso, and your first assignment is as part of the team developing AssetTrack, an internal asset tracking application. The first step before you can begin creating code is to get a copy of the project and the necessary tooling installed. To streamline the process you'll do everything via Codespaces.
+
+In this lesson, you will:
+
+- create a new repository based on the template for AssetTrack.
+- open the project in Codespaces.
+- install and authenticate Copilot CLI.
+- verify installation of Copilot CLI.
+- run AssetTrack.
+
+## Create a new instance of AssetTrack
+
+When doing standard development, the first step is often to fork or clone the repository you'll be contributing to. For our course, since you'll be working through the exercises on your own, you'll grab a separate copy of the project. You'll do this by creating a new instance of the repository by using a [template repository][github-template-docs] on your own personal GitHub account.
+
+1. In your browser, navigate to [https://github.com/github-samples/contoso-inventory](https://github.com/github-samples/contoso-inventory).
+2. Select **Use this template**.
+3. Select **Create a new repository**.
+4. Under **Owner**, select your personal GitHub account.
+5. For **Repository name**, enter `AssetTrack`.
+6. Leave the remaining options at their defaults.
+7. Select **Create repository**.
+8. Once the new repository has been created, select the **Code** button.
+9. Switch to the **Codespaces** tab.
+10. Select **Create codespace on main**.
+
+> [!NOTE]
+> The first launch of the codespace will take a few minutes. AssetTrack uses a custom devcontainer that includes the runtimes for all four stacks (Java, Node/Astro, .NET, Python/FastAPI), and the container image needs to be built the first time the codespace starts. Subsequent launches will be much faster.
+
+## Launch AssetTrack
+
+Before pointing Copilot CLI at the codebase, you need to know the app runs in its current state. That way, when something breaks later in the course, you can be confident it was a change you (or the agent) made — not a pre-existing problem with your environment. AssetTrack spans four stacks (Java, Astro/TypeScript, .NET, and two FastAPI services), but it's wired up so a single `npm run dev` command starts everything together and the Astro frontend proxies the rest. Running it once now gives you a known-good baseline and the URL you'll keep open throughout the course.
+
+1. In your codespace, press ctrl+` to open the integrated terminal.
+2. Run `npm run dev`.
+3. Wait for the terminal to display a forwarded link to `localhost:4321`.
+4. Select the displayed link to open AssetTrack in a new browser tab.
+5. Confirm the AssetTrack dashboard renders.
+
+## Install and authenticate Copilot CLI
+
+Copilot CLI is the primary tool you'll spend the rest of the course driving, so the final setup step is to get it installed, signed in, and verified inside the codespace. You'll do this in a second terminal so the app keeps running undisturbed in the first one — that side-by-side layout (app on the left, agent on the right) is the workflow you'll use for every exercise that follows. Authenticating once now means later modules can jump straight into prompting instead of stopping to handle a login flow.
+
+1. In your codespace, press ctrl+shift+` to open a new terminal.
+2. Install Copilot CLI by following the [official install instructions][copilot-cli-install].
+3. Run `copilot` to start the CLI.
+4. Follow the prompts to sign in with your GitHub account and authenticate.
+5. Once you reach the prompt, enter `hello` and press Enter.
+6. Confirm Copilot CLI responds.
+
+## Summary
+
+With the environment in place, you're ready to start driving Copilot CLI against a real codebase. You created your own copy of AssetTrack from a template, launched it in a Codespace backed by the course's custom devcontainer, confirmed the app boots end-to-end, and got Copilot CLI installed and authenticated in a second terminal. That side-by-side setup — app running in one terminal, agent in another — is the workflow every remaining module builds on.
+
+In this lesson, you:
+
+- created a new repository based on the template for AssetTrack.
+- opened the project in Codespaces.
+- installed and authenticated Copilot CLI.
+- verified installation of Copilot CLI.
+- ran AssetTrack.
+
+Next, [start a real conversation with the codebase][next-lesson].
+
+## Resources
+
+- [Copilot CLI documentation][copilot-cli-docs]
+- [GitHub Copilot CLI repository][copilot-cli-repo]
+- [Legacy app: `github-samples/contoso-inventory`][contoso-inventory]
+- [GitHub Codespaces overview][codespaces-docs]
+
+---
+
+| | [Next: Getting started with Copilot CLI →][next-lesson] |
+|:--|--:|
+
+[next-lesson]: ../01-working-with-copilot-cli/
+[contoso-inventory]: https://github.com/github-samples/contoso-inventory
+[copilot-cli-docs]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli
+[copilot-cli-install]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#installing-copilot-cli
+[copilot-cli-repo]: https://github.com/github/copilot-cli
+[codespaces-docs]: https://docs.github.com/codespaces/overview
+[vscode-devcontainers]: https://code.visualstudio.com/docs/devcontainers/containers
+[github-template-docs]: https://docs.github.com/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/01-working-with-copilot-cli.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/01-working-with-copilot-cli.md
new file mode 100644
index 00000000..53c7954d
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/01-working-with-copilot-cli.md
@@ -0,0 +1,324 @@
+---
+title: '01 · Working with Copilot CLI'
+description: 'Understand the agent model, how the Copilot CLI harness works under the hood, and the everyday mechanics of models and permissions.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 1 — Working with Copilot CLI
+
+| [← Previous: Prerequisites and environment setup][previous-lesson] | [Next: Building an AI infrastructure →][next-lesson] |
+|:--|--:|
+
+As we begin exploring deeper concepts in Copilot CLI, it helps to know how things work internally. This module grounds you in the agent model, walks through what's happening under the hood when Copilot is "thinking," and gets you comfortable with the core mechanics — picking a model and granting permissions.
+
+## What you will learn
+
+In this lesson, you will learn:
+
+- how requests are processed by AI agents, including GitHub Copilot CLI.
+- what an AI agent is and how it differs from chatting with an AI tool.
+- how the Copilot CLI harness works under the hood.
+- the core mechanics you'll use every day.
+
+## Scenario
+
+You're a developer who recently joined Contoso Industries and inherited **AssetTrack** — an internal asset-tracking application built on Java, Astro/TypeScript with React islands, .NET, and FastAPI. The app has incomplete documentation, many files, and a non-trivial bug list. Before changing anything, you want to understand what Copilot CLI is doing on your behalf, and develop the muscle memory for steering it.
+
+> [!NOTE]
+> Starting state: this is the first module, so there's no catch-up branch — work from the default branch of the AssetTrack fork you created in the [prerequisites][previous-lesson].
+
+## Tech topics
+
+This module covers two foundational concepts:
+
+- **Understanding AI agents** — how requests are processed, what makes an agent different from a chat tool, and what context Copilot uses to ground its responses.
+- **Copilot CLI under the hood** — the harness, the tool surface, and the permission model.
+
+## Understanding AI agents
+
+Any prompt to GitHub Copilot, including Copilot CLI, goes through a set of steps before a response is generated. While much of this happens behind the scenes, you have several inputs into this flow. Ensuring Copilot has the right context at the right time is key to success with Copilot. At a high level, it follows a general flow:
+
+- Understand the prompt.
+- Analyze the context.
+- With the context, determine the user's intent.
+- Generate a response.
+- Apply filters.
+- Send response to user.
+
+Let's briefly walk through each of these.
+
+> [!IMPORTANT]
+> The flow highlighted above provides an overview of the approach Copilot will typically take. Internally, it may iterate between multiple steps as it works on providing the solution for the provided prompt. For example, it may generate an initial response, discover it doesn't meet the needs, and return back to analyzing context to improve its response.
+
+### Understand the prompt
+
+The prompt you send to Copilot is certainly the most obvious part of the flow with Copilot CLI. It's what you type into the dialog box, and what devs typically focus on when first working with an AI assistant. A good prompt should contain:
+
+- What you're looking to build
+- Why you're trying to build it
+- How you're trying to build it
+
+Short, single sentence prompts similar to "Add a filter to the assets list page" contain too much ambiguity. Ambiguity leads to code that, while technically accurate, doesn't genuinely meet the requirements set forth in the project. A more detailed prompt gives Copilot a better starting point when determining the appropriate strategy for generating its response:
+
+```
+Add a filter to the assets list page. Operators should be able to filter by category (a dropdown list) and availability (a toggle with the heading of "In stock"). Create the React island for the filter UI, the .NET endpoint on assets-svc that backs it, and the necessary tests on both sides.
+```
+
+> [!NOTE]
+> Large language models (LLMs) process and generate text as tokens. In the prompt above, it's very likely every word would be a single token as they're all relatively common in English and development. The one exception to this is *availability*, which might be broken down into its root *available* and the suffix *ility*. This type of breakdown allows for better understanding of the word and its meaning in context with the rest of the prompt. By and large you don't need to consider tokenization of prompts, responses, or other text considered by Copilot, but it can be helpful to better understand how Copilot is working on your request.
+
+### Analyze the context
+
+Context is key throughout much of life, and when working with AI. While the more robust prompt in the section above provides a lot of direction to Copilot, there's still quite a bit that needs to be understood to ensure the response generated genuinely meets the needs of the project. Some questions that need to be answered include:
+
+- Which Astro / React conventions are in play on the frontend? Is the filter expected to be an island or part of a server-rendered page?
+- What tools are we using for tests? For unit tests? Are we using end to end tests? And, if so, what framework is being used there?
+- Where's the assets list page in the project?
+- Where's the `assets-svc` endpoint defined, and what's the existing .NET convention for query parameters and validation?
+- What conventions are followed? Are there lint rules?
+
+We always need to ensure Copilot is able to find the correct answers to these questions. Copilot is able to explore the project to find and follow existing patterns. But this approach can be inefficient on larger projects, especially when you may have portions of your codebase which don't follow the guidelines set forth by your team.
+
+This is where your AI infrastructure - your instructions files, agent skills, custom agents and MCP servers - helps guide Copilot by providing curated context it can use when generating responses. Between your prompt, your code, and your project's AI infrastructure, Copilot will have the understanding of how to work in your environment. You'll build out that infrastructure starting in [Module 2][next-lesson].
+
+### Determine the user's intent
+
+You'll notice that determining the intent of your prompt is the third step in this flow. This might feel a bit curious. After all, shouldn't this be the first thing Copilot does? But as highlighted previously, even our relatively detailed prompt instructing Copilot of what to build and how to build it left a fair amount of ambiguity. Only after examining the prompt and the context is Copilot able to build out an effective plan for fulfilling the request.
+
+At this point, Copilot may ask follow-up questions depending on its level of certainty with the approach it's about to take to fulfilling the request. As always, you can always add direction to your prompt or other forms of context (e.g. your instructions files) to be more likely to ask clarifying questions.
+
+> [!NOTE]
+> Copilot will often automatically create a plan when approaching a task. If you wish to formalize this step, and iterate on the plan before asking Copilot to begin building the solution, you can use [`/plan` mode][plan-mode].
+
+### Generate a response
+
+It's now time for Copilot to begin generating a response! This could include creating code or determining a particular task should be run, like calling a skill or running tests. This could be considered the draft version of the response Copilot generates, as there's still one more step before it actually performs any actions.
+
+### Apply filters
+
+Built into Copilot are various filters, including responsible AI usage, a [light security filter][security-filter], and, if enabled, [filtering code that matches publicly available code][public-code-filter]. This ensures responsible use of Copilot, and further improves the quality of code.
+
+> [!NOTE]
+> The security filter built into GitHub Copilot is not built as a replacement for proper security reviews, including human and automated tool reviews.
+
+### Send response to the user
+
+Now it's time for Copilot to do its work! After running through all of the above, Copilot begins generating the necessary code and performing the required tasks. During this process it will determine if any changes to the content or its approach need to be made, potentially returning to earlier steps in the flow.
+
+Throughout the entire process, you can send additional messages to Copilot to steer it or further refine your request. Copilot will consider those prompts, again running through the same flow as needed.
+
+And, from here, you will iterate! You'll validate the code and the completed operations, ensuring everything looks good. You'll make additional requests, run more tests, create commits, pull requests, and your standard developer flow.
+
+## Copilot CLI under the hood
+
+An AI agent is, in concrete terms, an LLM that runs in a loop, picks tools to call, observes the results, and iterates autonomously or as directed. Unlike a standard chat request of Copilot which reads input and generates code, Copilot agents can run scripts, access files, and perform other, potentially dangerous, operations. As a result, Copilot will always ask for permission before performing any tasks - including just launching the tool. [Tools available to Copilot CLI][available-tools] include:
+
+| Tool | Description |
+|---|---|
+| `view` | Read a file or list a directory's contents. |
+| `edit` | Modify an existing file via string replacement. |
+| `create` | Create a new file. |
+| `bash` / `powershell` | Execute shell commands in your local environment. |
+| `grep` (or `rg`) | Search file contents for text or patterns. |
+| `glob` | Find files matching a name or path pattern. |
+| `web_fetch` | Fetch and parse content from a URL. |
+| `task` | Spawn a subagent (e.g., `explore`, `general-purpose`) to handle a focused piece of work in its own context. |
+| `skill` | Invoke a custom skill that bundles instructions or scripts for a specialized job. |
+| MCP server tools | Tools provided by built-in or configured MCP servers — e.g., the GitHub MCP server for issues and pull requests. |
+
+### Managing permissions
+
+When you start Copilot CLI for the first time in a folder, Copilot will prompt you for read access to the folder. You can choose to deny permissions (which will cause Copilot to exit), to allow for that session, or to approve and save that choice for all future sessions. In addition to access to the folder, Copilot will request permissions before running any potentially unsafe operations. You can choose to allow or deny these calls individually, approve for the current session, or to always allow the tool.
+
+> [!NOTE]
+> A [session][session-docs] is the conversation between launching `copilot` and exiting. Sessions are persisted, so you can pick one back up later with `/resume` or `copilot --continue`. Note that "approve for the rest of this session" approvals only apply to the current run; they reset when you exit and resume.
+
+Copilot offers many [options to control permissions][permissions-docs], including:
+
+| Slash command | CLI switch | Description |
+|---|---|---|
+| `/model [MODEL]`, `/models` | `--model=MODEL` | Display available models, or switch the active model for the session. |
+| `/add-dir PATH` | `--add-dir=PATH` | Grant the agent file access to an additional directory beyond the launch folder. |
+| `/list-dirs` | — | Display the directories currently allowed for file access. |
+| `/cwd [PATH]`, `/cd [PATH]` | — | Change (or display) the working directory without restarting the session. |
+| — | `--allow-tool=TOOL` | Pre-approve specific tools (e.g., `shell(git:*)`, `write`, `MyMCP`) so they run without prompting. |
+| — | `--deny-tool=TOOL` | Block specific tools entirely; deny rules win over allow rules. |
+| `/reset-allowed-tools` | — | Clear all session-level tool approvals you've granted. |
+| `/allow-all`, `/yolo` | `--allow-all`, `--yolo` | Enable all permissions — tools, paths, and URLs. Use with care. |
+| — | `--allow-all-tools` | Auto-approve every tool call (required for programmatic / scripted runs), but paths must still be approved. |
+| — | `--allow-all-paths` | Skip path verification and allow access to any file location. |
+| — | `--allow-url=URL` / `--deny-url=URL` | Allow or block specific URLs/domains for `web_fetch` and shell network calls. |
+
+> [!WARNING]
+> Enabling all tools (commonly referred to as **YOLO mode**) gives Copilot unrestricted ability to read, modify, and execute files, run shell commands, and call out to MCP servers without asking. A misinterpreted prompt or a prompt-injection attack via fetched content can result in data loss, leaked secrets, or destructive commands. Only use YOLO mode in [trusted, sandboxed environments][risk-mitigation] such as a container or disposable VM, and never in a directory containing credentials or unreviewed code.
+
+## Exercise: Explore the project using Copilot CLI
+
+As you likely expected, there's quite a bit going on behind the scenes with Copilot CLI, and a host of options we have for controlling how it behaves. Let's make a couple of requests of Copilot CLI, focusing on how Copilot fulfills the requests we make of it and the tools it calls.
+
+1. Return to your codespace. If you already closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then select your existing codespace.
+2. Open a terminal window by selecting Ctrl + `.
+3. Start Copilot CLI by running the following command in the terminal window:
+
+ ```bash
+ copilot
+ ```
+
+4. When prompted to trust the folder, select **2. Yes, and remember this folder for future sessions.**
+
+> [!NOTE]
+> If you choose to approve access for all future sessions, the folder is listed in Copilot's local configuration, stored in `~/.copilot/config.json` (a hidden directory in your home folder) by default.
+
+5. If prompted to determine [session sync][session-sync], use the right arrow to highlight **This repository** and select Enter.
+6. Display the list of models available to you by entering the following switch and selecting Enter:
+
+ ```
+ /models
+ ```
+
+7. Make note of the list of available models. Note that this list will vary depending on the current plan for Copilot you have access to and, if on a business account, what your administrators have chosen to make available.
+8. Select **auto** from the list and select Enter.
+9. Send the following prompt to Copilot CLI to ask about the project:
+
+ ```
+ Tell me about this project
+ ```
+
+10. Make note of the tool calls to **Read** to read files and **List directory** to explore the available files. Because you already granted permissions to Copilot to read the folder in the prior step, these are run without having to ask for permissions.
+11. Send the following prompt to list any GitHub issues filed for the repo:
+
+ ```
+ Are there any issues currently open on this GitHub repo?
+ ```
+
+12. Note that again Copilot didn't ask permissions. This is because the read-only MCP server is automatically built into Copilot CLI. Also note the call to **MCP:github-mcp-server** took place automatically, as Copilot determined it was the correct tool to call.
+13. Send the following prompt to create issues based on the todos in the README file:
+
+ ```
+ Can you create a set of short issues based on the todos you see in the readme?
+ ```
+
+ Because you are now requesting Copilot make changes to your repository in the form of creating issues, it now asks for permissions. Also note the heading for the dialog, which says **Permission request (2 remaining)**.
+
+ Selecting **Yes** will allow for the first call only, and the next two will require separate approvals. **Yes, and don't ask again for `gh issue` in this repo (*path*)** will allow Copilot CLI to always call the `gh issue` CLI tool for this repository.
+
+> [!IMPORTANT]
+> Ensure you always consider the implications of granting Copilot or any AI tool permissions to perform actions on your behalf.
+
+14. For purposes of this exercise, select **2** by cursoring down to option 2 to allow Copilot CLI to call `gh issue` for this repository, then select Enter.
+15. Copilot CLI creates the issues!
+16. Display the summaries of the newly created issues by sending the following prompt to Copilot CLI:
+
+ ```
+ Show me summaries of the issues on this repository
+ ```
+
+17. Again, because Copilot CLI has read access via the GitHub MCP server, it automatically pulls down the issue summaries from the repository.
+18. Revoke Copilot's ability to create issues by using the following switch and selecting Enter:
+
+ ```
+ /reset-allowed-tools
+ ```
+
+19. Attempt to create another issue by sending the following prompt to Copilot CLI:
+
+ ```
+ Create an issue to add dark mode to the application.
+ ```
+
+20. Note how Copilot CLI prompts you for permissions to perform the action. Select Esc twice to exit out of the prompt.
+
+You explored how Copilot CLI uses tools behind the scenes, and how it requests permissions. You also saw how you can both grant and revoke permissions for Copilot CLI.
+
+## Exercise: Improve documentation
+
+As with many (most?) applications, documentation is lacking in AssetTrack. There's missing documentation and some that's even incorrect. This not only causes challenges when you or your team members look to make updates to the codebase, it also impacts Copilot's ability to generate quality code. Let's perform an audit of our documentation in the project, identify key areas for improvement, and ask Copilot to make the necessary changes.
+
+1. With Copilot CLI still running in your codespace, ask Copilot to tour the repo and produce a structured summary. Send the following prompt:
+
+ ```
+ Tour the repo and give me a structured summary: each stack's purpose, modules, entry points, data layer, templates, scripts, tech-debt areas, and unenforced rules.
+ ```
+
+2. Review the output. Copilot will read across the codebase using the `view` and `grep` tools as you saw previously.
+3. Ask Copilot to identify what's missing or wrong in the current `README.md`:
+
+ ```
+ Compare what you just found against the current README.md. What's missing, outdated, or wrong?
+ ```
+
+4. Have Copilot draft updates to `README.md` (and any supplemental docs you decide are warranted — `ARCHITECTURE.md`, per-stack READMEs under `services/*`, etc.):
+
+ ```
+ Draft updates to README.md (and add ARCHITECTURE.md if it helps) reflecting what you found. If there's any ambiguity, please ask me for follow-up information.
+ ```
+
+5. Review the changes by opening the diffs inside of Copilot CLI by using the slash command `/diff`.
+6. Confirm the updates match the current structure of the project.
+
+> [!IMPORTANT]
+> Always review documentation generated by an AI tool against the actual code. A confidently wrong README is worse than no README.
+
+7. Create a new branch with the updates by prompting Copilot:
+
+ ```
+ Create a new branch called docs-update, and a short commit message for the changes.
+ ```
+
+8. Have Copilot create a pull request (PR) by using the following prompt:
+
+ ```
+ Create a PR from the new branch to main. Create a descriptive comment describing the docs updates.
+ ```
+
+9. In a new browser tab, open your repository.
+10. Select **Pull requests** to open the list of pull requests.
+11. Select the PR you just generated.
+12. Select **Merge** to merge the pull request.
+
+
+## Summary
+
+Copilot CLI isn't a chat box bolted onto your terminal — it's an agent running in a loop, pulling in context from your prompt, your code, and your AI infrastructure, then picking tools to act on your behalf. In this module you saw that flow end-to-end: how a prompt is interpreted, where context comes from, what the harness exposes as tools, and how the permission model keeps you in control of what runs. You then put it to work by exploring AssetTrack, calling the GitHub MCP server, managing approvals, and shipping a real documentation update through a branch and pull request.
+
+In this lesson, you learned:
+
+- how requests are processed by AI agents, including GitHub Copilot CLI.
+- what an AI agent is and how it differs from chatting with an AI tool.
+- how the Copilot CLI harness works under the hood.
+- the core mechanics you'll use every day.
+
+Next, you'll **build the AI infrastructure** — codify what you just documented as `copilot-instructions.md` and scoped `.instructions` files, then add a custom agent and an imported skill in [Module 2][next-lesson].
+
+## Resources
+
+- [About GitHub Copilot CLI][copilot-cli-docs]
+- [Using GitHub Copilot CLI][copilot-cli-howto]
+- [Tool availability values][available-tools]
+- [Tool permission patterns][permissions-docs]
+- [Resume an interactive session][session-docs]
+- [Session sync (chronicle)][session-sync]
+- [Use plan mode][plan-mode]
+- [Risk mitigation and YOLO mode][risk-mitigation]
+- [Security measures for GitHub Copilot CLI][security-filter]
+- [Public code filtering][public-code-filter]
+
+---
+
+| [← Previous: Prerequisites and environment setup][previous-lesson] | [Next: Building an AI infrastructure →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../00-prerequisites/
+[next-lesson]: ../02-building-ai-infrastructure/
+[copilot-cli-docs]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli
+[copilot-cli-howto]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli
+[plan-mode]: https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli/overview#use-plan-mode
+[public-code-filter]: https://docs.github.com/copilot/responsible-use/copilot-cli#public-code
+[security-filter]: https://docs.github.com/copilot/responsible-use/copilot-cli#security-measures-for-github-copilot-cli
+[available-tools]: https://docs.github.com/copilot/reference/copilot-cli-reference/cli-command-reference#tool-availability-values
+[permissions-docs]: https://docs.github.com/copilot/reference/copilot-cli-reference/cli-command-reference#tool-permission-patterns
+[session-docs]: https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli/overview#resume-an-interactive-session
+[session-sync]: https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle
+[risk-mitigation]: https://docs.github.com/copilot/concepts/agents/copilot-cli/about-copilot-cli#risk-mitigation
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/02-building-ai-infrastructure.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/02-building-ai-infrastructure.md
new file mode 100644
index 00000000..6b0787ee
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/02-building-ai-infrastructure.md
@@ -0,0 +1,274 @@
+---
+title: '02 · Building an AI Infrastructure Foundation'
+description: 'Build reusable AI infrastructure — custom instructions, a custom agent, and contribution standards — for a brownfield repo.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 2 — Building an AI infrastructure foundation
+
+| [← Previous: Working with Copilot CLI][previous-lesson] | [Next: Enhancing the test suite with remote and delegation →][next-lesson] |
+|:--|--:|
+
+Every time you start a fresh Copilot CLI session, the agent only sees your raw files in the working directory. Without shared instructions, guidelines and codified conventions, you have to keep re-explaining your stacks and re-establishing your coding standards. That repetition makes sessions much slower and produces inconsistent output, so in this module, we build the **AI infrastructure** that makes future interactions with Copilot faster and more accurate.
+
+## What you will learn
+
+By the end of this module you will be able to:
+
+- Generate baseline & path-scoped instructions for Copilot
+- Understand where Copilot CLI looks for and applies different customizations including instructions, skills and agents
+- Create a custom agent with a clear persona, scope and well-defined rules of engagement
+- Bootstrap contribution standards to be observed by every AI-influenced change
+
+## Scenario
+
+Contoso has a set of best practices that need to be followed in every code change: stack-specific conventions, accessibility requirements (the organization is moving toward WCAG 2.2 AA), and a hard rule that every AI-generated change must flow through an issue and a pull request - no direct commits to main.
+
+> [!NOTE]
+> Starting state: your fork has the documentation updates from [Module 1][previous-lesson] merged. If you're jumping in here, check out the catch-up branch that holds them:
+>
+> ```bash
+> git checkout start-of-module-02
+> ```
+
+## Add custom instructions to Copilot CLI
+
+Previously, you watched Copilot rediscover AssetTrack from scratch and updated the docs. Now you'll codify what Copilot needs to know about *how* the team writes code in general and on each stack.
+
+Having proper documentation helps Copilot *understand* your project. **Custom instructions** go further by telling Copilot *how to behave* when working in your project. With every Copilot interaction, you want your coding standards, style and business rules to be automatically reflected in the agent's responses. Custom instructions are the persistent, version-controlled files that encode those standards and shape every response the agent generates.
+
+### Where Copilot CLI looks for instructions
+
+Copilot CLI loads instructions from several sources and combines them. The sources are:
+
+- `.github/copilot-instructions.md` - for rules that apply to every session within the context of the repository
+- `.github/instructions/**/*.instructions.md` - for rules that apply to a specific area of the codebase following a defined path pattern
+- `~/.copilot/copilot-instructions.md` - for user-level rules that apply to every Copilot CLI session across repositories on your machine
+- `AGENTS.md`, and its sibling files like `CLAUDE.md` / `GEMINI.md` - for agent-level instructions that apply whenever the corresponding agent is active
+
+This layering is intentional as they all serve different purposes, but can also result in conflicting rules if not managed carefully. Copilot combines instructions when all sources are present but given the non-deterministic nature of language models, its choice of instruction mix might not always be predictable.
+
+Knowing where the agent looks for instructions is the first debugging step when you notice Copilot ignoring rules. Run `/instructions` to see exactly what's loaded in the current session and update your files accordingly.
+
+### Agent-generated baseline instructions
+
+For brownfield projects like AssetTrack, you can use the agent itself to generate a baseline `copilot-instructions.md` that captures the conventions and patterns it observes in the code. The built-in `/init` command asks Copilot to scan the repository, inspect code, build files and existing docs, then produces a baseline set of instructions.
+
+> [!CAUTION]
+> The output of `/init` is a **starting point**, not a finished artifact. It will miss nuances, include things that are too vague to be useful and sometimes get conventions wrong, depending on the quality of the codebase. Always review and refine the generated file.
+
+### Path-scoped instructions
+
+Repository-wide instructions cover broad rules that apply everywhere. But AssetTrack runs across four stacks:
+- Java for the workforce, audit and auth services,
+- Astro/TypeScript with React islands for the frontend,
+- .NET for the asset service,
+- Python for reporting and notification services,
+
+and each stack has rules the others don't share. For example, Astro components would need accessibility rules that don't make sense to the backend, which would in turn need SQL-safety rules. FastAPI services are to follow modern-Python conventions that aren't relevant in sessions targeting .NET context.
+
+Path-scoped instruction files let you apply targeted rules by specifying an `applyTo` glob pattern that matches the relevant files. This way you can have a clean separation of concerns, applying the right rules to each stack without cluttering the global instructions.
+
+## Exercise 1: Create instruction files for AssetTrack
+
+Let's start by capturing existing conventions and patterns in the codebase in a baseline instructions file, then adding path-scoped instruction files for each stack.
+
+### Generate the baseline instructions
+
+1. Return to your codespace. If you closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then reopen your existing codespace.
+2. Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`) and select **Chat: New Copilot CLI Session to the side**
+3. If prompted, trust the project folder by selecting **Yes, and remember this folder for future sessions**.
+4. Run `/models`, select **Auto** from the list and **Enter**
+5. Run `/init`.
+
+ Copilot scans the repository and generates a `.github/copilot-instructions.md` file. You'll see the agent reviewing available docs, reading through the code, it may also try to run build and test-related commands, then draft the instructions file based on its findings.
+
+6. Open `.github/copilot-instructions.md` and review the generated content. What you need to be asking yourself as you review is *"If Copilot followed these instructions exactly, would it produce better, acceptable code?"* If the answer is "no" or "maybe", revise the instructions removing anything that isn't helpful or accurate, clarify what is too vague and add any important rules or conventions the agent missed.
+
+> [!TIP]
+> Re-run `/init` when the instructions start drifting significantly from reality either after a major restructure, after adopting a new framework or when Copilot consistently produces code that violates your current conventions. You can also run it frequently as a diagnostic tool to see what Copilot *thinks* your conventions are and if you are not happy with the "perceptions", treat that as a sign you need to improve on your code quality and consistency.
+
+### Create scoped instructions
+
+Let's start with our Java parts of the codebase.
+
+1. Create a new file - `.github/instructions/java.instructions.md`, and paste in the following content:
+
+ ```markdown
+ ---
+ applyTo: "services/**/*.java"
+ description: This file describes instructions for Java code style and best practices for the project.
+ ---
+
+ - Follow standard naming conventions — Classes in PascalCase, methods/variables in camelCase, constants in UPPER_SNAKE_CASE, packages in lowercase.
+ - Program to interfaces, not implementations — Declare variables/parameters using the most general interface type (e.g., List list = new ArrayList<>()) to enable flexibility and loose coupling.
+ - Favor composition over inheritance — Prefer delegating to component objects rather than extending classes; inheritance breaks encapsulation and creates fragile hierarchies.
+ - Handle exceptions properly — Never swallow exceptions with empty catch blocks, use checked exceptions for recoverable conditions and runtime exceptions for programming errors, and always clean up resources with try-with-resources.
+ - Adhere to SOLID principles — Especially Single Responsibility (one reason to change per class) and Dependency Inversion (depend on abstractions); foundational for maintainable OO design.
+ - Make classes immutable whenever possible — Mark fields private final, avoid setters, and don't expose internal mutable state; immutable objects are inherently thread-safe and easier to reason about.
+ ```
+
+ The instruction file has 2 distinct sections: the **YAML frontmatter** at the top, which sets the `applyTo` glob to match all Java files under `services/` and adds a description, and the **body** - which lists the instructions for Java code in AssetTrack.
+
+
+2. Spot-check the scoped instructions with a stack-specific prompt:
+
+ ```text
+ Refactor the notification logic in AssignmentService into a separate component that properly handles failures instead of swallowing exceptions. Show me the new interface, implementation class and how you'd update AssignmentService to use it - don't apply it
+ ```
+
+ Notice the tool call to **Read** on `java.instructions.md` before the agent writes its proposal and the code should reflect the specific rules you set for Java code.
+
+ We'll take a different approach for the Astro/React instructions, where instead of manually authoring the rules, you'll ask Copilot to generate them.
+
+3. In the Copilot CLI, run `/new` to reset the session context, then prompt Copilot to create the Astro instructions file:
+
+ ```text
+ Generate a path-specific instruction file for only the Astro + TypeScript React frontend portions of this mixed-language microservices application. Use proper `.instructions.md` format with YAML frontmatter - `applyTo` single glob expression targeting frontend Astro/TS/TSX files only, and accurate description and should be optimized for high quality future code updates. Define concise engineering standards for Astro + React architecture, strict TypeScript, accessibility, styling consistency, performant data fetching, error/loading states, testing, maintainability, minimal dependencies, clean incremental changes etc.
+
+ ```
+
+ Your checklist for this step is to:
+
+ - [ ] Ensure the generated file has valid YAML frontmatter with an `applyTo` glob that matches only the Astro/React files (for example `services/web/src/**/*.{astro,ts,tsx}`)
+ - [ ] Ensure the instructions are specific, actionable and enforceable - not just vague or aspirational. (Make necessary edits as you see fit)
+
+4. Optionally create two more instruction files for the .NET and FastAPI services, or any other areas of the codebase you want to target with specific rules.
+
+
+5. Confirm all instruction files are loaded by running `/instructions`. You should see your instruction files with the option to enable or disable each one.
+
+6. Create a new branch and commit the instruction files. Don't push yet:
+
+ ```text
+ Create a new branch called add-ai-infrastructure and commit the instruction files with a descriptive commit message.
+ ```
+
+## Custom agents in Copilot CLI
+
+Instructions files apply *passively* and take effect for every session that touches the matched paths. Custom agents on the other hand, are configured **personas** that can be explicitly invoked when the workflow needs to take advantage of their specialized knowledge. In the Copilot CLI environment, you can either: -
+
+- Run a custom agent as the default, or
+- Go through the main agent, which interacts with custom agents following a hierarchical structure, recognizing them as subagents that have their own isolated loop and context window separate from its own.
+
+Think of custom agents as specialized workers your main agent can offload scoped & focused tasks to and only expects the final output. In other words, if your main agent decides to invoke a custom agent, what it does in between is a "black box" to the main agent, and is not included in the main agent's context window.
+
+When thinking about creating a custom agent, aim for a well-defined, recurring and narrowly-scoped task that benefits from domain-expert level behavior, a restricted toolset and strict rules of engagement. This design pattern goes a long way in preventing it from straying into areas it shouldn't be working on.
+
+Custom agents live in `.github/agents/` (for repo-scoped) or `~/.copilot/agents/` (for user-scoped), and are invoked via `/agent`.
+
+## Exercise 2: Create an Accessibility Expert custom agent
+
+Now let's add a reusable `Accessibility Expert` custom agent and use it against the frontend code. Instead of designing the agent from scratch, you'll reuse one from the Awesome GitHub Copilot repo, a community-curated collection of copilot customizations that are ready to drop in.
+
+1. Browse the [Awesome GitHub Copilot][awesome-copilot] website and in the search bar, type "accessibility" to find related customizations.
+
+2. Select the **Agent** filter, and find the `Accessibility Expert - Agent`. Select it.
+
+3. This previews the agent definition file that you can quickly review to confirm it fits your use case.
+
+4. At the top of the preview page, select **Render** first, then **Copy** and move back to your codespace.
+
+5. Create a new file - `.github/agents/accessibility-expert.agent.md` - and paste the copied content into it.
+
+> [!NOTE]
+> Tools and toolsets are updated frequently, so you might notice some of the tools mentioned in the agent definition file are no longer available or have changed names. If that's the case, select **Configure Tools ...** right above the `tools: ...` definition in the agent file, and select the **Built-in** and **GitHub MCP** checkboxes. This will update the agent definition with the current tool names and you can always adjust the allowlist accordingly.
+>
+> Ensure you enable the GitHub tools for a later exercise in this module.
+
+6. If you exited the previous Copilot CLI session, restart it or reset to a clean conversation with `/new`.
+
+7. Confirm Copilot discovers the new agent with `/agent`. You should see `Accessibility Expert` in the list. Exit the agent menu for now with `Esc`.
+
+### Use the agent to make accessibility improvements
+
+1. Ask Copilot to work with the Accessibility Expert agent to produce an accessibility report for the Astro frontend with recommendations
+
+ ```text
+ Work with the the accessibility expert to review the Astro frontend code and produce an accessibility report with specific recommendations for improvements based on WCAG 2.2 AA standards.
+ ```
+
+ Notice that the main agent passes the task to the Accessibility Expert agent, which then finds the custom instructions for Astro/React you created earlier, tracks the relevant files and produces a report with specific, actionable recommendations that reference WCAG success criteria and specific selectors in the code.
+
+ If the agent fails to reference the instructions you created, this creates an opportunity to improve its behavior by adding a rule in the agent definition file that explicitly instructs it to always check for relevant instruction files in the repository and apply them when working on tasks that match the scope.
+
+2. We'll leave this session and come back to it at a later exercise in this module, but before you do, **run `/rename` to rename the session to "Accessibility Report"** so you can easily identify it later when you return to it.
+
+3. Start a new session with `/new`, commit the agent file to `add-ai-infrastructure`, push and open a PR.
+
+## Agent skills
+
+Custom agents introduce *specialized personas*. **Agent skills** change what Copilot *knows* to do. A skill is a packaged capability, could include an instruction set, optional scripts and resources - that the agent can invoke **at runtime** when the task matches its trigger. Skills live in `.copilot/skills/` (for repo-scoped) or `~/.copilot/skills/` (for user-scoped) and in Copilot CLI, you use `/skills` to view and manage them.
+
+The new AI infrastructure for Contoso is coming together nicely, but there's one more piece to add. Now that you have a baseline for how copilot should approach making updates locally, we want to bootstrap the contribution standards that should be followed to land these updates through channels that integrate with the team's existing workflows for enhanced collaboration, human-in-the-loop review and auditability.
+
+We'll import a skill that encodes the standard contribution flow: file an issue following a standard template, create a branch from `main`, make and push changes, open a PR with the right template and link the issue. For the skill's output to be reviewable, the issue and PR templates need to exist first. When invoked, `make-repo-contribution` walks through the contribution flow as a unit instead of improvising it each time.
+
+## Exercise 3: Import and use the `make-repo-contribution` skill
+
+This last exercise guides you to install the `make-repo-contribution` skill and use it to land the changes so far.
+
+1. Browse the [Awesome GitHub Copilot][awesome-copilot] website and in the search bar, type "contribution" to find related customizations.
+
+2. Select the **Skill** filter and find the `Make Repo Contribution - Skill`. Select it.
+
+3. A skill can be a single `SKILL.md` file or a collection of files with additional scripts and assets. At the top of the preview page, select **SKILL.md** to view the other files included in the skill. You should see the `SKILL.md` file, a `assets/issue-template.md` file and a `assets/pr-template.md` file.
+
+4. Select **Download** to get the full skill package and move back to your codespace.
+
+5. Create a new folder - `.github/skills/` and move the downloaded skill folder into it, so the structure looks like `.github/skills/make-repo-contribution/` with the three files inside.
+
+6. Restart the Copilot CLI session with `/restart` to load the new skill.
+
+7. Confirm the skill is loaded with `/skills list`, then exit the skills menu for now.
+
+### Make a contribution with the skill
+
+Let's bring it all together now. You'll use the `make-repo-contribution` skill to open a new issue for the recommendations from the report you generated with the Accessibility Expert agent, then implement the change and open a PR through the same skill.
+
+1. In the CLI, run `/resume` and select the Accessibility Report session you created in Exercise 2.
+
+2. Use `/agent` to switch to the Accessibility Expert agent, then run the following prompt:
+
+ ```text
+ The accessibility report you generated has some great recommendations. Pick one that you think would have a high impact but is not too complex to implement, create an issue then go ahead and implement it. Remember to follow the relevant custom instructions in the repo and when you're ready, commit and open a PR following the contribution standards we established.
+ ```
+
+ Observe as the agent works on the implementation and automatically invokes the `make-repo-contribution` skill when ready to land the change. The agent should first create an issue using the provided template, then commit the change, push to a new branch and open a PR linking the issue, all through the skill's workflow.
+
+> [!NOTE]
+> Compare what just happened to the manual push and PR you opened at the end of Exercise 2. The skill enforced the same steps with issue, branch, commit, PR, linked issue - but as a single, consistent unit. For Contoso, that means every AI-driven contribution follows the same auditable flow regardless of who (or what) triggered it.
+
+3. Navigate to your repository on GitHub.com, open each PR, review the changes and merge both into `main`.
+
+## Summary
+
+Together these files form the **AI infrastructure** for AssetTrack. Every future Copilot CLI session and every exercise in the rest of this course benefits from the context you've established here. Instructions are durable, version-controlled, reviewable in pull requests and shareable across the entire team.
+
+In this module, you learned:
+
+- How to generate baseline instructions with `/init`
+- How to create path-scoped instruction files targeting specific stacks with relevant rules
+- To import custom agents from Awesome Copilot, a community-curated repo of ready-to-use customizations - and how to use them
+
+Next, you'll close the loop on accessibility with **Playwright tests** and offload the broader test backfill via `/remote` and `/delegate` in [Module 3][next-lesson].
+
+## Resources
+
+- [Using Copilot CLI][copilot-cli-docs]
+- [Awesome GitHub Copilot - community-curated collection of Copilot customizations][awesome-copilot]
+- [CLI Command reference][commands-reference]
+- [Comparing GitHub Copilot CLI customization features][cli-customization-comparison]
+
+
+| [← Previous: Working with Copilot CLI][previous-lesson] | [Next: Enhancing the test suite with remote and delegation →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../01-working-with-copilot-cli/
+[next-lesson]: ../03-test-suite-remote-delegation/
+[copilot-cli-docs]: https://docs.github.com/copilot/how-tos/copilot-cli
+[awesome-copilot]: https://awesome-copilot.github.com/
+[commands-reference]: https://docs.github.com/copilot/reference/copilot-cli-reference/cli-command-reference
+[cli-customization-comparison]: https://docs.github.com/copilot/concepts/agents/copilot-cli/comparing-cli-features
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/03-test-suite-remote-delegation.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/03-test-suite-remote-delegation.md
new file mode 100644
index 00000000..4123f6be
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/03-test-suite-remote-delegation.md
@@ -0,0 +1,234 @@
+---
+title: '03 · Test Suite, Remote & Delegation'
+description: 'Turn accessibility checks into a Playwright-backed feedback loop, steer sessions with /remote, and offload work with /delegate.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 3 — Enhancing the test suite with remote and delegation
+
+| [← Previous: Building an AI infrastructure foundation][previous-lesson] | [Next: Shaping Copilot CLI's lifecycle with hooks →][next-lesson] |
+|:--|--:|
+
+The accessibility and contribution infrastructure from [Module 2][m02] is useful only if the team can prove it keeps working. This module turns the first accessibility checks into a Playwright-backed feedback loop, uses `/remote` to steer the active CLI session remotely, and hands a bounded test backfill to Copilot cloud agent with `/delegate`.
+
+> [!NOTE]
+> If you're jumping straight to this module without finishing the earlier ones, check out the `start-of-module-03` catch-up branch. It holds the accessibility and contribution infrastructure the earlier modules produced, which the exercises here assume is already in place.
+>
+> 1. Follow the [course prerequisites][prerequisites] to create your AssetTrack repository from the [`github-samples/contoso-inventory`][contoso-inventory] template. Ensure that you select **Include all branches** when you create it so the catch-up branches come along.
+> 2. In your codespace terminal, check out the catch-up branch:
+>
+> ```bash
+> git checkout start-of-module-03
+> ```
+>
+> This branch contains the completed AI infrastructure that this module builds on.
+
+## What you will learn
+
+By the end of this module you will be able to:
+
+- Decide which test work belongs in a local Copilot CLI session and which work is safe to delegate.
+- Start a Playwright browser test suite for AssetTrack's web UI.
+- Use test output to separate setup issues from real accessibility gaps.
+- Steer an active Copilot CLI session from GitHub.com or GitHub Mobile with `/remote`.
+- Hand scoped work to Copilot cloud agent with `/delegate`.
+- Write and review a delegation brief before trusting an agent-created pull request.
+
+## Scenario
+
+AssetTrack already has a few backend smoke tests, but the UI has no browser coverage yet. That makes accessibility work hard to trust. You'll use Copilot CLI to scaffold a small Playwright suite, read the first failures carefully, and make only the changes the evidence supports. Once the local foundation is reviewable, you'll write a delegation brief and send the broader test backfill to Copilot cloud agent.
+
+
+
+## Local, remote, and delegated work
+
+The same Copilot CLI logic can run in three places. Choosing the right one for a given task is the core skill of this module. Each surface trades immediacy for reach.
+
+- A local Copilot CLI session is best for exploration, test setup, debugging, and judgment calls. You approve tool calls, inspect diffs, and decide whether a failure is a test bug, an app gap, or an environment issue.
+- `/remote` gives you access to your Copilot CLI session from GitHub.com or GitHub Mobile, allowing you to control the same active CLI session running in your terminal or codespace. It does not move execution to a hosted runner.
+- `/delegate` sends scoped work to Copilot cloud agent, which works from GitHub state, creates commits, and opens a pull request.
+
+As a general rule, keep ambiguous work local and delegate only bounded work you can describe with files, commands, constraints, and PR expectations. Remember that the cloud agent sees pushed branches and repository files, not the uncommitted changes in your terminal. Commit and push before you delegate.
+
+
+
+## Exercise 1: Start the Playwright foundation locally
+
+Playwright is a browser-automation framework that drives a real browser to exercise your UI the way a user would. Let's start by creating the first browser test signal for the AssetTrack UI and using it to validate the accessibility work from the previous module. You'll touch `playwright.config.ts`, accessibility specs under `tests/playwright/`, and root `package.json` and lockfile updates — plus narrow Astro accessibility files only if the tests prove a real gap.
+
+The goal is a tight evidence loop: scaffold tests, run them, classify each failure, and fix only what the evidence supports.
+
+
+
+1. Return to your codespace. If you closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then reopen your existing codespace.
+2. Open a terminal in the codespace. If you don't already have a terminal available, open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P) and select **Terminal: Create New Terminal**.
+3. Run `npm run install:all` to ensure dependencies are up to date.
+4. Start the app with `npm run dev`, open `http://localhost:4321` or the forwarded Codespaces URL, and confirm the AssetTrack UI loads.
+5. Stop the dev server by pressing ctrl+C in the terminal.
+6. If you don't have a Copilot CLI session already running, type `copilot` in the terminal to start a new session.
+7. Ask for a read-only test inventory:
+
+ ```text
+ Inspect this repository's current test setup. Summarize what test frameworks already exist, which services have tests, which services are missing tests, and where a Playwright browser test suite should live. Do not edit files yet.
+ ```
+
+8. Review the answer. Copilot should find xUnit tests under `services/assets-svc/Tests/`, a Spring Boot context test under `services/workforce-svc/src/test/`, an intentionally empty `services/reporting-svc/tests/` folder, and no Playwright setup for `services/web`.
+9. Ask Copilot to scaffold the Playwright foundation:
+
+ ```text
+ Add a minimal Playwright browser test setup for the AssetTrack web UI.
+
+ Requirements:
+ - Put the Playwright config at the repository root as playwright.config.ts.
+ - Put tests under tests/playwright/.
+ - Add npm scripts at the repository root for test:e2e and test:e2e:ui.
+ - Use the existing npm run dev command as the Playwright webServer command.
+ - Target http://localhost:4321 as the base URL.
+ - Use Chromium only for now so the course exercise stays fast.
+ - Add or update a `.gitignore` so generated Playwright output (`test-results/` and `playwright-report/`) is not committed.
+ - Do not change production application code in this step.
+ - After editing, run the install or test command needed to verify the setup. A normal first setup may add @playwright/test and run npx playwright install chromium. In Codespaces, npx playwright install --with-deps chromium may be needed.
+ ```
+
+10. Before running the full app, ask Copilot to run `npx playwright test --list` and confirm the tests are discovered.
+11. Ask Copilot to add focused accessibility checks for the dashboard landmarks, active navigation state, asset form labels, asset list filters, and keyboard access to the Assets link (reachable by Tab and activated by Enter). Require Playwright role and label locators such as `getByRole` and `getByLabel`; avoid CSS selectors unless there is no accessible alternative (for example, use `getByRole('heading', { level: 1 })` rather than `locator('h1')`). Because these locators resolve elements through the accessibility tree, using them doubles as an accessibility check: if a role or label locator can't find an element, that missing role or label is often the gap itself. Keep the checks focused and standards-correct: links are activated with `Enter`, not `Space`, and a button inside a form already submits, so don't require an explicit `type="submit"`.
+12. Run the Playwright suite and ask for evidence in a predictable shape:
+
+ ```text
+ Run the Playwright tests and summarize the result. If any tests fail, classify each failure as one of: test bug, app accessibility gap, or environment/startup issue. Include the command run, how many tests were found, the pass/fail count, each failure category, and the next action you recommend. Do not change production code yet.
+ ```
+
+13. If the setup is broken, fix only `playwright.config.ts`, `tests/playwright/**`, and package files. If the failure proves a real accessibility gap, switch to the `Accessibility Expert` agent from Module 2 and make the narrowest app fix. The agent applies the path-scoped Astro accessibility instructions you added in Module 2, so the fix follows those rules and WCAG 2.2 AA. If Copilot does not automatically switch to the right agent, run `/agent`, select `Accessibility Expert`, then send the prompt.
+14. Review the diff, but don't commit yet — you'll commit and push this foundation in Exercise 3. The local result should be a Playwright foundation, a test result, and maybe a small accessibility fix backed by that result.
+
+When you're done, `npx playwright test --list` discovers the browser tests under `tests/playwright/`, `npm run test:e2e` runs against the configured web server, any production code change is traceable to a failing accessibility test, and generated folders such as `test-results/` and `playwright-report/` are cleaned or ignored before commit.
+
+## Remote control with `/remote`
+
+`/remote` creates a GitHub.com session link for the same Copilot CLI session already running locally or in a codespace. It is good for approving prompts from another tab or device, watching longer validation commands, and keeping a session moving while the original environment stays online. It does not run commands in GitHub Actions or move your work to Copilot cloud agent.
+
+A few things can get in the way: the account or organization may have remote sessions disabled, the current folder may not be a GitHub repository, or the original machine may stop before the command finishes. The `/keep-alive busy` command can help prevent supported environments from idling during longer work; if your CLI version does not support it, keep the codespace or machine active using your instructor's environment guidance.
+
+
+
+## Exercise 2: Steer the test session with `/remote`
+
+Now let's prove you can control the active Copilot CLI session from GitHub while the Playwright validation runs. No source files are required.
+
+1. In the same Copilot CLI session, enter `/remote on`.
+2. Open the GitHub.com link Copilot provides and sign in with the same GitHub account that started the CLI session.
+3. Confirm the remote page shows the current prompt history and accepts a new message.
+4. From the remote UI, send:
+
+ ```text
+ Run the Playwright suite again. If the app needs to start first, use the existing Playwright webServer configuration. Summarize any failures as test bug, app accessibility gap, or environment/startup issue.
+ ```
+
+5. Respond to permission prompts from the remote UI.
+6. If remote sessions are disabled, record that limitation and continue with the delegation exercise.
+7. When finished, enter `/remote off`. You can also enter `/remote` without an argument to check the current status.
+
+When you're done, the remote UI shows the same conversation as the terminal session, a prompt sent from GitHub.com or GitHub Mobile runs in the original CLI session, and the Playwright summary includes the command run, pass/fail count, failure classification, and recommended next action.
+
+## Delegating work with `/delegate`
+
+`/delegate` sends a task to Copilot cloud agent, which works asynchronously on GitHub, creates commits, and opens a pull request. Writing the brief into the repository first means reviewers can see exactly what the agent was asked to do, and the agent can work from a durable artifact instead of relying only on a chat prompt.
+
+A useful delegation task spells out the primary goal, secondary goals, the files and folders in scope, the files and folders out of scope, the commands to run, what to do if production behavior blocks the task, and the expected PR title and description. Before committing or pushing the local foundation, ask Copilot for `git status` and a diff summary — don't delegate from a dirty working tree unless you understand what the cloud agent can and cannot see. When the PR lands, treat the cloud agent like a teammate: review file scope, test evidence, production code changes, skipped tests, and known limitations before merging.
+
+
+
+## Exercise 3: Delegate the test backfill
+
+Finally, let's create a reviewable handoff for Copilot cloud agent and use it to expand the test suite through a draft PR. You'll add `docs/delegations/test-backfill.md`, push a branch such as `test-suite-foundation`, and end with a delegated draft PR adding tests under `tests/playwright/`, `services/assets-svc/Tests/`, and `services/reporting-svc/tests/`.
+
+1. Ask Copilot CLI to create `docs/delegations/test-backfill.md` with a brief for Copilot cloud agent that captures the primary goal:
+
+ - Expand the Playwright accessibility coverage started locally.
+ - Keep using role and label locators where possible.
+ - Cover dashboard, navigation, asset list filters, and new asset form behaviors.
+
+2. Add the secondary goal to the brief:
+
+ - Add xUnit backfill coverage for `services/assets-svc` covering create, read, update, delete, search, stats-by-status, and not-found edge cases.
+ - Add pytest backfill coverage for `services/reporting-svc` covering warranty-expiring reports, utilization reports, and CSV import behavior.
+
+3. Add the constraints to the brief:
+
+ - Do not change production application code.
+ - Do not add new backend framework dependencies unless required for the test framework already implied by the service.
+ - Prefer isolated test data and temporary SQLite databases.
+ - Mock cross-service HTTP calls (for example, reporting-svc's calls to assets-svc) instead of requiring live services.
+ - If you add a test-only dependency such as a mocking library, declare it in the service's dependency manifest so the tests run from a clean install.
+ - If a real production bug blocks a test, document it in the PR instead of fixing it.
+ - Include exact commands and results in the PR description.
+ - Follow the Module 2 contribution standard: open an issue using the repository's issue template, link it from the pull request, and use the pull request template.
+
+4. Ask for a read-only checkpoint before any write operations:
+
+ ```text
+ Show me the current git status and summarize the files changed for the Playwright setup, accessibility follow-up if any, and delegation brief. Do not commit or push yet.
+ ```
+
+5. Clean or ignore generated Playwright artifacts such as `test-results/` and `playwright-report/`.
+6. After reviewing the diff, land the work following the Module 2 contribution standard — no direct commits to `main`. Create and push a branch such as `test-suite-foundation`, driving the issue → branch → PR flow with the `make-repo-contribution` skill so the change stays auditable. Use a commit like `test: add Playwright accessibility foundation`, a second commit like `fix: address accessibility gaps found by Playwright` only if a narrow app fix was needed, and `docs: add test backfill delegation brief` for the brief.
+7. Confirm `docs/delegations/test-backfill.md` exists on the pushed branch in GitHub before delegating.
+8. Use `/delegate` with both the branch reference and a short inline summary:
+
+ ```text
+ /delegate Use the pushed test-suite-foundation branch and docs/delegations/test-backfill.md as the source of truth. If the branch context does not include that file, use this brief summary: primary goal, expand Playwright accessibility coverage under tests/playwright using role and label locators for dashboard, navigation, asset list filters, and new asset form behaviors; secondary goal, add xUnit coverage for services/assets-svc covering create, read, update, delete, search, stats-by-status, and not-found edge cases, and add pytest coverage for services/reporting-svc covering warranty-expiring reports, utilization reports, and CSV import behavior. Keep production application code unchanged. If production behavior blocks a test, document the gap in the PR instead of fixing it. Following the repository's contribution standard, open an issue for this backfill and a draft pull request titled "Add test suite backfill" that links the issue and uses the repository's issue and pull request templates, and include the commands you ran plus their results in the PR description.
+ ```
+
+9. If `/delegate` is unavailable or blocked by permissions, keep the pushed branch and delegation brief as the handoff artifact.
+10. When the draft PR is ready, ask Copilot CLI to summarize changed files, tests added, commands reported by the agent, production code changes, and known limitations.
+11. Review the PR. If it uses CSS selectors where role or label locators would work, changes production code without evidence, omits command output, leaves out a required coverage area such as utilization reports or CSV import, skips the linked issue or the repository's pull request template, or ignores the brief, leave a specific revision comment.
+12. After the delegated PR adds the backfill, run or verify the relevant commands:
+
+ ```bash
+ npm run test:e2e
+ dotnet test services/assets-svc/Tests/AssetsService.Tests.csproj
+ cd services/reporting-svc && pytest
+ ```
+
+When you're done, the pushed branch contains the local Playwright foundation and `docs/delegations/test-backfill.md`, the delegated PR references the brief, links an issue, and includes exact command output, the new tests live under `tests/playwright/`, `services/assets-svc/Tests/`, and `services/reporting-svc/tests/`, and production code is unchanged unless the PR clearly explains why a test-backed fix was necessary.
+
+## Summary
+
+You should now have:
+
+- A Playwright foundation for the AssetTrack UI.
+- A validation result for the Module 2 accessibility work.
+- Any narrow accessibility fix that was justified by a failing browser test.
+- A delegation brief at `docs/delegations/test-backfill.md`.
+- A pushed handoff branch and, when `/delegate` is available, a draft PR from Copilot cloud agent expanding the test suite.
+- A clearer sense of when to keep Copilot CLI work local, steer it remotely, or delegate it to cloud agent.
+
+Next, you'll use the test commands created here to shape Copilot CLI's lifecycle with hooks so tests, builds, and lint checks run automatically as Copilot edits the project in [Module 4][next-lesson].
+
+## Resources
+
+- [Steer a Copilot CLI session remotely][remote-docs]
+- [Delegate tasks to Copilot cloud agent][delegate-docs]
+- [About Copilot cloud agent][cloud-agent]
+- [Playwright getting started][playwright]
+- [Playwright locators][playwright-locators]
+- [Accessible name and description computation][accessible-name]
+
+---
+
+| [← Previous: Building an AI infrastructure foundation][previous-lesson] | [Next: Shaping Copilot CLI's lifecycle with hooks →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../02-building-ai-infrastructure/
+[next-lesson]: ../04-lifecycle-hooks/
+[m02]: ../02-building-ai-infrastructure/
+[prerequisites]: ../00-prerequisites/
+[contoso-inventory]: https://github.com/github-samples/contoso-inventory
+[remote-docs]: https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli/steer-remotely
+[delegate-docs]: https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli/delegate-tasks-to-cca
+[cloud-agent]: https://docs.github.com/copilot/concepts/agents/cloud-agent/about-cloud-agent
+[playwright]: https://playwright.dev/docs/intro
+[playwright-locators]: https://playwright.dev/docs/locators
+[accessible-name]: https://www.w3.org/TR/accname-1.2/
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/04-lifecycle-hooks.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/04-lifecycle-hooks.md
new file mode 100644
index 00000000..6da0b76d
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/04-lifecycle-hooks.md
@@ -0,0 +1,367 @@
+---
+title: '04 · Shaping the Lifecycle with Hooks'
+description: 'Wire deterministic lifecycle hooks so tests, lint, and build feedback flow back into the agent automatically.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 4 — Shaping Copilot CLI's lifecycle with hooks
+
+| [← Previous: Enhancing the test suite with remote and delegation][previous-lesson] | [Next: Adding a new feature →][next-lesson] |
+|:--|--:|
+
+The AI layer of the agent loop is **probabilistic**. It may or may not remember to run tests, lint or build after a change. Hooks are the **deterministic** layer underneath it, hosting shell commands the harness runs at defined lifecycle points regardless of what the model decides, feeding structured output back into the conversation so the next turn starts from reality rather than assumption.
+
+## What you will learn
+
+- How hooks fit into the agent execution loop and why they complement the already existing AI infrastructure from [Module 2][m02].
+- The hook event model: which events fire when, what payloads they receive and what output they expect.
+- Hook types: `command`, `http` and `prompt` - and when to reach for each.
+- Scoping hooks to specific locations and tool calls.
+- How to wire up a `postToolUse` hook that runs the right tests after each file edit and injects the results back into the agent's context.
+
+
+## Scenario
+
+> [!NOTE]
+> **Starting state**: the AI infrastructure - instructions, custom agents, skills from [Module 2][m02], and the Playwright test scaffold from [Module 3][previous-lesson] are in place on your fork. If you're jumping in here, check out the catch-up branch that holds them:
+>
+> ```bash
+> git checkout start-of-module-04
+> ```
+
+At the end of [Module 3][previous-lesson] you delegated the test backfill to the Copilot cloud agent. Those tests now exist, but they run only when you explicitly ask, or when CI picks them up on a push. There is nothing that automatically runs the right checks right after the agent edits a file, while it still has context on what it just changed. Hooks close that gap.
+
+## Lifecycle hooks in Copilot CLI
+
+### What a hook is
+
+A hook is a JSON-declared shell command, HTTP call or prompt that the Copilot CLI harness invokes synchronously at a named point in the agent loop. Hooks run **outside the AI model** - they are not prompts and the model does not decide when or whether they fire. The harness fires them unconditionally when a trigger event occurs, collects their stdout and injects it back into the agent's context before the next turn.
+
+Your instructions tell the model how to behave, while your hooks enforce behavior regardless of what the model does. This distinction matters. A `postToolUse` hook that runs `pytest` after every Python file edit is not a suggestion - it runs every single time.
+
+Consider hooks for checks that are:
+
+- **Fast:** hooks run synchronously, so a slow hook stalls the agent. *Rule of thumb - Anything over ~30 seconds belongs in CI or a delegated job, not a hook.*
+- **Deterministic:** the output should be stable for the same input. A hook that flakes trains the agent (and you) to discount its output.
+- **Objective:** pass/fail, lint errors, type errors. Anything requiring judgment (code review, architecture decisions) should stay in the conversation, not in a hook.
+
+### Types of hooks
+
+Each hook entry declares a `type` field which can either be:
+
+- `command` - runs a shell command locally. Provide `bash` and optionally `powershell` for cross-platform support. This is the most common type.
+- `http` - POSTs the event payload as JSON to a URL. Useful for audit trails, webhook-triggered CI or external governance systems without a local script. HTTPS is required by default, but you can allow HTTP for localhost by setting `COPILOT_HOOK_ALLOW_LOCALHOST=1`.
+- `prompt` - auto-submits a text string or slash command into the session on `sessionStart`. Only fires on new interactive sessions (not on resume or in non-interactive `-p` mode).
+
+## How hooks receive events and return output
+
+The harness emits named events across the session lifecycle and your hook configuration maps event names to arrays of hook entries.
+
+| Event | When it fires | What your hook can do |
+|---|---|---|
+| `sessionStart` | New or resumed session begins | Inject context (`additionalContext`) e.g., current branch, open issues |
+| `sessionEnd` | Session terminates | Logging, cleanup, notifications |
+| `userPromptSubmitted` | User submits a prompt | Intercept and short-circuit with a direct `response`, bypassing the model |
+| `preToolUse` | Before a tool call executes | Allow, deny or modify the tool's arguments |
+| `postToolUse` | After a tool call completes **successfully** | Modify the tool result seen by the LLM or append `additionalContext` |
+| `postToolUseFailure` | After a tool call fails | Provide recovery guidance via `additionalContext` |
+| `agentStop` | The agent finishes a turn | Block the turn from closing, e.g., force another turn with a `reason` |
+| `subagentStart` | A subagent is spawned | Inject context into the subagent's prompt |
+| `subagentStop` | A subagent completes | Block and force another subagent turn |
+| `permissionRequest` | CLI prompts the user for tool approval | Programmatically allow or deny |
+
+For the test-and-lint feedback loop in this module, the events that matter most are:
+
+- `postToolUse` - to run checks after each file edit and feed results back to the agent loop
+- `agentStop` - to optionally block the agent from finishing a turn if checks are red).
+
+### Reading hook input and writing hook output
+
+Every hook entry of type `command` receives the full event payload as **JSON on stdin**. Your script reads it with `INPUT=$(cat)` and extracts fields with `jq`. The exact schema is event-specific, but all payloads share the common fields `sessionId`, `timestamp` and `cwd`.
+
+For `postToolUse` - the event you'll use most for validation in this module - the payload is:
+
+```typescript
+{
+ sessionId: string;
+ timestamp: number;
+ cwd: string;
+ toolName: string;
+ toolArgs: unknown; // the exact args the tool was called with (e.g., { path: "...", new_str: "..." } for edit)
+ toolResult: {
+ resultType: "success";
+ textResultForLlm: string;
+ };
+}
+```
+
+Your script communicates back to the harness by writing a single JSON object to **stdout**. For `postToolUse`, the schema is:
+
+```typescript
+{
+ modifiedResult?: {
+ resultType: "success";
+ textResultForLlm: string; // replaces the tool result the LLM sees
+ };
+ additionalContext?: string; // appended to what the LLM sees
+}
+```
+
+For `agentStop`, the output schema is different:
+
+```typescript
+{
+ decision: "allow" | "block";
+ reason?: string; // required when decision is "block" — becomes the prompt for the forced next turn
+}
+```
+
+A `"block"` decision tells the harness to open another agent turn automatically, with `reason` as the injected prompt. This is how you will implement *"if tests are red, the agent must address them before it can stop."*
+
+### Exit codes and fail-open vs. fail-closed
+
+Exit codes are how a command hook communicates its own health back to the harness, separate from the JSON it writes to stdout. Every command hook produces an exit code, and the harness uses it to decide whether to treat the hook as successful, warn or fail, before it ever looks at the hook's output.
+
+| Exit code | Meaning |
+|---|---|
+| `0` | Success - stdout parsed as hook output if present |
+| `2` | Warning - `stderr` surfaced, run continues |
+| Other non-zero | Hook failure logged, run continues (fail-open) |
+
+Most hook failures are **fail-open** - a crash, timeout or non-zero exit is logged and the agent continues. `preToolUse` is the deliberate exception. Because it sits in front of every tool call as a security gate, a command hook that crashes, times out or exits non-zero (other than exit 2) **denies the tool call.** Here, the harness refuses to let a broken guard silently become no guard at all.
+
+> [!NOTE]
+> HTTP `preToolUse` hooks behave differently. If the request fails, either due to network error, timeout or non-2xx, the harness treats the hook as if it never ran and the normal permission flow decides instead.
+>
+> A broken local script is a bug you control and can fix, but a network outage is not, and it shouldn't silently block every tool call the agent tries to make.
+
+### Scoping hooks with `matcher`
+
+Every hook entry can include a `matcher` field - a regular expression matched against the tool name. Without a matcher, the hook fires for every tool call in that event, but with one, it fires only when the tool name matches.
+
+For a `postToolUse` hook that should only fire when the agent edits or creates a file:
+
+```json
+{
+ "type": "command",
+ "matcher": "edit|create",
+ "bash": ".github/hooks/scripts/test-router.sh",
+ "timeoutSec": 60
+}
+```
+
+### Hook configuration files
+
+Hooks are declared in JSON files and require no explicit registration step. All you need to do is drop a correctly structured file in the right directory and restart Copilot CLI - it loads on startup.
+
+The harness loads hooks from four sources, in order. Hooks from all sources for the same event are **merged** (not overwritten):
+
+1. **Policy hooks:** Machine-wide hooks installed by enterprise IT administrators. They load before everything else, cannot be disabled by `disableAllHooks` and end users cannot modify or override them.
+2. **User hooks:** `~/.copilot/hooks/*.json` (or `%USERPROFILE%\.copilot\hooks\` on Windows). Personal hooks stored on your local machine, not version-controlled and not shared with teammates. This is ideal for desktop notifications, personal audit logging or any preference you don't want to impose on the team.
+3. **Repository hooks:** `.github/hooks/*.json`. Checked into source control so every team member gets them automatically on clone. This is also the **only** source the cloud agent loads since user files and plugins do not exist in the cloud sandbox.
+4. **Plugin hooks:** Declared inside each installed Copilot CLI plugin's own directory and loaded automatically alongside other sources when a plugin is installed.
+
+Every hook file must declare `"version": 1` at the top level:
+
+```json
+{
+ "version": 1,
+ "hooks": {
+ "postToolUse": [],
+ "agentStop": []
+ }
+}
+```
+
+> [!NOTE]
+> When running Copilot CLI in non-interactive prompt mode, repository hooks are **disabled by default**. Enable them with `GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS=true` if you need hooks to fire in CI or scripted runs.
+
+## Exercise 1: Wire up `after-edit` hooks for AssetTrack
+
+AssetTrack's checks span four different stacks - .NET, Java, Python, TypeScript, so "run the tests" means a different command depending on which file the agent just touched and that's an easy step to forget mid-session. That's exactly the kind of deterministic, no-judgment-required work you read about above, so instead of relying on the model to remember, you'll build:
+
+- a `postToolUse` hook that:
+ - inspects which file was just edited,
+ - routes to the right test runner for that stack,
+ - feeds the output back as `additionalContext` so the next agent turn begins with the actual test result.
+
+- A second hook - `agentStop`, that blocks the agent from finishing a turn if any stack's checks are red.
+
+1. Return to your codespace and open a terminal.
+
+2. Create the directory for the hook scripts:
+
+ ```bash
+ mkdir -p .github/hooks/scripts
+ ```
+
+3. Download `.github/hooks/scripts/test-router.sh`, which reads the edited file path from the `postToolUse` payload, runs the right test runner for that stack and emits the result as `additionalContext`. The same script also handles `agentStop` by looking at changed files and blocking only when the relevant stack's tests fail:
+
+ ```bash
+ curl -o .github/hooks/scripts/test-router.sh \
+ https://raw.githubusercontent.com/github-samples/advanced-copilot-cli/main/assets/04/.github/hooks/scripts/test-router.sh
+ ```
+
+4. Make the script executable:
+
+ ```bash
+ chmod +x .github/hooks/scripts/test-router.sh
+ ```
+
+5. Create the hook configuration file `.github/hooks/hooks.json` and paste in the following content. This declares the `postToolUse` and `agentStop` hooks, both of which call the same script you just downloaded:
+
+ ```json
+ {
+ "version": 1,
+ "hooks": {
+ "postToolUse": [
+ {
+ "type": "command",
+ "matcher": "edit|create",
+ "bash": ".github/hooks/scripts/test-router.sh",
+ "timeoutSec": 60
+ }
+ ],
+ "agentStop": [
+ {
+ "type": "command",
+ "bash": ".github/hooks/scripts/test-router.sh",
+ "timeoutSec": 90
+ }
+ ]
+ }
+ }
+ ```
+
+## Exercise 2: Verify the hooks load
+
+1. Restart Copilot CLI to pick up the new configuration, then load the environment customizations with:
+
+ ```text
+ /env
+ ```
+
+ You should see your `postToolUse` and `agentStop` entries listed under Hooks.
+
+2. Test the hook script in isolation before relying on it in a session. From the repository root, pipe a synthetic payload and confirm the output is valid JSON:
+
+ ```bash
+ echo "{\"toolName\":\"edit\",\"toolArgs\":{\"path\":\"$PWD/services/reporting-svc/app/main.py\",\"old_str\":\"\",\"new_str\":\"\"},\"toolResult\":{\"resultType\":\"success\",\"textResultForLlm\":\"edited\"}}" \
+ | .github/hooks/scripts/test-router.sh | jq .
+ ```
+
+ pytest runs and the script emits one JSON object:
+
+ ```text
+ {
+ "additionalContext": "Hook check for services/reporting-svc/app/main.py passed.\nStack: Python\nCommand: cd services/reporting-svc && pytest\nExit code: 0\n..."
+ }
+ ```
+
+ The `additionalContext` value is what matters: it contains the command exit code, and last 40 lines of test output. That output is exactly what the harness appends to the tool result before the model reads it on its next turn.
+
+3. Test the `agentStop` path before making any stack changes. The hook files themselves do not map to a test stack, so it should allow the turn to finish:
+
+ ```bash
+ echo '{}' | .github/hooks/scripts/test-router.sh | jq .
+ ```
+
+ Expected output:
+
+ ```json
+ {
+ "decision": "allow"
+ }
+ ```
+
+4. The hook infrastructure is ready. Commit the hook config and script on this branch.
+
+ *Create a new branch called add-lifecycle-hooks, commit the `.github/hooks/` directory with a message explaining what the hooks do and why, then push and open a PR.*
+
+> [!NOTE]
+> The PR at this point contains only the hook infrastructure. The code changes in the next exercise are temporary. You will revert them before merging.
+
+## Exercise 3: Close the feedback loop on a real change
+
+With hooks wired up, prove the loop end-to-end: a change goes in, the `postToolUse` hook fires and reports results, and if the `agentStop` gate catches a failure, the agent addresses it before finishing the turn.
+
+1. Start a new Copilot CLI session (or run `/new` to reset context). Confirm the hooks are active with `/env`.
+
+2. Ask Copilot to add a small testable method. Pick the stack that matches your background.
+
+ *For Java:*
+
+ ```text
+ In AssignmentService, add a private helper method isOverdue(LocalDate dueDate) that returns true if dueDate is before today. Add a unit test for it in AssignmentServiceTest — no public API changes.
+ ```
+
+ *For C# (.NET):*
+
+ ```text
+ In the assets service, add a private helper method IsWarrantyExpiring(DateTime expiryDate) that returns true if expiryDate is within 30 days from today. Add a unit test for it — no public API changes.
+ ```
+
+ *Or for Python:*
+
+ ```text
+ In the reporting service, add a helper function days_until_expiry(expiry_date: date) -> int that returns the number of days until a warranty expires. Add a pytest test for it.
+ ```
+
+ Watch the agent's tool calls. After the `edit` or `create` call that writes the test file, the agent's next turn should include the hook output in its visible context.
+
+3. Confirm the test output surfaced - ask the agent directly:
+
+ ```text
+ What did the test run report?
+ ```
+
+ The agent should be able to quote or summarize the test output from the `additionalContext` the hook injected.
+
+4. Now deliberately break the assertion to trigger the `agentStop` gate. Ask Copilot to introduce a broken test, (purely for demonstration purposes):
+
+ ```text
+ Change the assertion in the test you just added so it asserts the wrong expected value — make it definitely fail.
+ ```
+
+5. Watch what happens when the agent tries to finish its turn. The `agentStop` path in `test-router.sh` runs, detects the test failure and returns `"decision": "block"` with the failing command output in the reason.
+
+ The harness opens a new agent turn automatically with that reason as the prompt. The agent should propose a fix without you typing anything. *If the agent reports green tests but the gate still blocks, ask it to quote the block reason - the command, exit code and output in that reason can be treated as the source of truth.*
+
+6. Confirm the loop closes - the agent fixes the assertion, the tests pass on the next `postToolUse` hook fire and the `agentStop` path returns `"decision": "allow"`.
+
+7. Revert the example changes so the branch stays clean for the PR, then confirm tests are green:
+
+ ```text
+ Revert the method and test you added, then confirm all tests pass.
+ ```
+
+## Summary
+
+You've now:
+
+- Explored hooks as the deterministic enforcement layer underneath the probabilistic model layer - they run unconditionally, feed structured output back via stdin/stdout JSON contracts and can block or modify agent actions.
+- Authored a `postToolUse` hook scoped to file edits that dispatches to the right test runner per stack and injects results as `additionalContext`.
+- Wired an `agentStop` gate that blocks the agent from finishing a turn while tests are red and forces a self-correction loop.
+
+Next, you'll put all of the infrastructure to work by adding a real **new feature** (barcode / QR support) from `/plan` through to merged PR in [Module 5][next-lesson].
+
+## Resources
+
+- [Using hooks in Copilot CLI][use-hooks]
+- [Hooks reference][hooks-reference]
+- [Community hook patterns on Awesome GitHub Copilot][awesome-copilot-hooks]
+
+---
+
+| [← Previous: Enhancing the test suite with remote and delegation][previous-lesson] | [Next: Adding a new feature →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../03-test-suite-remote-delegation/
+[next-lesson]: ../05-add-feature-barcode/
+[m02]: ../02-building-ai-infrastructure/
+[test-router-script]: https://github.com/github-samples/advanced-copilot-cli/blob/main/assets/04/.github/hooks/scripts/test-router.sh
+[use-hooks]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/use-hooks
+[hooks-reference]: https://docs.github.com/copilot/reference/hooks-reference
+[awesome-copilot-hooks]: https://awesome-copilot.github.com/learning-hub/automating-with-hooks/
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/05-add-feature-barcode.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/05-add-feature-barcode.md
new file mode 100644
index 00000000..a903dbd6
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/05-add-feature-barcode.md
@@ -0,0 +1,282 @@
+---
+title: '05 · Adding a Feature: Barcode Support'
+description: 'Plan and build a new feature end to end with /research, /plan, rubber-duck critique, /fleet, and a QA custom agent.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 5 — Adding a new feature: barcode support
+
+| [← Previous: Shaping Copilot CLI's lifecycle with hooks][previous-lesson] | [Next: Modernizing apps with Copilot CLI →][next-lesson] |
+|:--|--:|
+
+Adding a greenfield feature inside a brownfield app is a real test of an agentic workflow. It takes forethought: finding the services that already exist, scoping the change, making the updates, and making sure nothing else breaks. An open-ended prompt like "add barcode support" is exactly the kind of request that sends an agent wandering across services, inventing schema, and producing a diff no one can review.
+
+The antidote isn't a trick; it's a loop you run every time you work with an agent — research, spec and plan, build, QA, then iterate. This module walks that loop end to end, using QR code support for AssetTrack as the forcing function for `/research`, `/plan` with a rubber-duck critique, `/fleet`, and a quality assurance custom agent you build yourself.
+
+> [!NOTE]
+> **Starting state**: instructions, custom agents (including the `Accessibility Expert` from [Module 2][m02]), and the tests from [Modules 2–3][m02] are in place, and the hooks from [Module 4][m04] are wired up. If you're jumping in here, check out the catch-up branch that holds them:
+>
+> ```bash
+> git checkout start-of-module-05
+> ```
+>
+> This module registers the Playwright MCP server and has you build a quality assurance custom agent, then **modifies application code** in `assets-svc` and `web` — so work on a feature branch such as `feat/barcode-support`.
+
+## What you will learn
+
+- The repeatable loop for working with an agent — research, spec and plan, build, QA, iterate — and how this module makes each step concrete.
+- How to build a custom quality assurance agent from a spec, register the Playwright MCP server so it can drive the running app, and compose it with the `Accessibility Expert` from Module 2.
+- How to use `/research` to choose a library before committing to an approach, and how to treat the resulting report as evidence rather than as a decision.
+- How to use `/plan` and plan mode to produce a written, bounded plan before any code changes, and how a rubber-duck critique catches the gaps a first plan always hides.
+- How `/fleet` runs subagents in parallel: when that genuinely accelerates the work, when it backfires, and how to keep the combined diff reviewable.
+
+## Scenario
+
+AssetTrack tracks physical hardware — laptops, monitors, phones, badges — and every asset already carries a unique asset tag such as `CON-LPT-001`. Operators today read those tags by eye and type them in. Contoso wants each asset to carry a scannable QR code so that, in a later phase, an operator can point a phone at an asset and pull up its record. In this module you'll find a library to generate those codes, plan the updates it requires, and add generation to the application — leaving the scanning phase for later.
+
+## The agentic loop
+
+Whether you're fixing a bug or adding a feature that spans several services, the standard approach without AI is:
+
+1. **Research** — understand the problem and the realistic options before committing to an approach.
+2. **Spec and plan** — turn that understanding into a written, bounded plan you could hand to someone else.
+3. **Build** — implement the plan, ideally in independent slices that can move in parallel.
+4. **QA** — prove the result against the plan: it runs, it's tested, it's reviewed, it's accessible.
+5. **Iterate** — feed what QA finds back into the work until the gate passes.
+
+The steps don't change when introducing AI agents. If anything, it becomes more important. Spending the time up front to determine what needs to be done, then following it with validation that everything was done correctly, is key to success.
+
+> [!NOTE]
+> For our exercise, we're going to start by building out our quality assurance custom agent. Then you'll use it at the end of the module to validate the work done to add the required feature.
+
+## Build the quality gate first
+
+You met custom agents in [Module 2][m02], where you imported the `Accessibility Expert` from a community catalog. Here you'll go the other way and build one to a precise spec, because the checks you want are specific to this feature and this app. A custom agent is a configured persona of Copilot — a name, a description, a restricted toolset, and instructions — that runs in its own isolated context and is invoked deliberately with `/agent`.
+
+The quality assurance agent needs to do five things:
+
+1. read the spec and review the research to determine what needed to be done,
+2. explore the running app to confirm the new functionality actually exists,
+3. run the tests,
+4. review the code on the branch,
+5. confirm the new UI is accessible.
+
+Two of those need help from outside the agent's own reasoning. Exploring the running app means controlling a real browser, which the agent can't do on its own — so you'll give it access to the **Playwright MCP server**, a tool surface that lets Copilot navigate and read pages in a live browser. (MCP servers extend what Copilot can do; [Module 6][next-lesson] goes deeper, but you only need this one here.) And the accessibility check already has an owner — the `Accessibility Expert` agent from Module 2 — so rather than re-teach that knowledge, the quality assurance agent hands the new UI to it and treats a failed confirmation as a finding like any other.
+
+## Exercise 1: Register the Playwright MCP server
+
+Let's start by registering the Playwright MCP server for the quality assurance agent to be able to use later.
+
+> [!NOTE]
+> You'll start Copilot CLI with `--yolo`, which auto-approves every edit, command, and tool call so the work doesn't stop for permission on each step — useful once the `/fleet` build is running. That's appropriate here because a codespace is the kind of sandboxed, disposable container that [Module 1][m01] called out as the right home for YOLO mode. Treat it as the exception: on your own machine, or anywhere near real credentials or unreviewed code, start Copilot with plain `copilot` and approve actions deliberately.
+
+1. Return to your codespace. If you closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then reopen your existing codespace.
+2. Open a terminal window by selecting Ctrl + `.
+3. Create and switch to a feature branch with `git switch -c feat/barcode-support`.
+4. Start Copilot CLI in YOLO mode from the repository root:
+
+ ```bash
+ copilot --yolo
+ ```
+
+5. In Copilot CLI, run the command `/mcp add` to open the MCP registration panel.
+6. Fill out the form, using Tab to move between fields, with the following information:
+ - **Server Name**: `playwright`
+ - **Server type**: **STDIO**
+ - **Command**: `npx @playwright/mcp@latest`
+7. Select Ctrl+S to save.
+
+You've now added the Playwright MCP server to Copilot's toolset!
+
+## Exercise 2: Build a Quality assurance agent
+
+Now let's create a `Quality assurance` custom agent whose instructions encode the five checks above. The agent has nothing to inspect yet. You're defining the finish line before the race starts; it does its real work in the final exercise, once research, planning, and the build have given it something to gate.
+
+1. Ask Copilot to create the Quality assurance agent definition. Describe the five responsibilities and let it write the file:
+
+ ```text
+ Create a custom agent at .github/agents/qa.agent.md named "Quality assurance" that acts as a quality gate for a feature branch. Give it permissions to read and shell access, the Playwright MCP server, and the ability to invoke other agents. Its instructions should have it, in order: (1) read the feature's research report and plan; (2) use the Playwright MCP server to open the running app and confirm the new functionality actually exists in the UI; (3) run the project's test suites and report the results; (4) review the code changes on the current branch against the plan; and (5) hand any new or changed UI to the Accessibility Expert agent to confirm accessibility, treating a failed confirmation as a finding. It should finish by reporting what it covered, what it couldn't verify, and every gap it found.
+ ```
+
+2. Open `.github/agents/qa.agent.md` and review what Copilot wrote. Confirm the five steps are present and in order, that the toolset includes the Playwright MCP server and the ability to call another agent, and tighten any instruction that reads as vague.
+3. Reset to a clean conversation with `/new` so Copilot picks up the new agent, then confirm with `/agent`. You should see `Quality assurance` in the list. Exit the agent menu with `Esc`.
+
+When you're done, `.github/agents/qa.agent.md` defines a `Quality assurance` agent that lists in `/agent`, encodes the five checks in order, and is wired to both the Playwright MCP server and the `Accessibility Expert`. It has nothing to gate yet — you'll point it at the finished feature in the last exercise.
+
+## Choosing a barcode library with `/research`
+
+Taking on a new library dependency is not something to improvise. These choices carry consequences that outlive the afternoon you spend on them: the license has to be compatible with the project, the library has to be actively maintained, and — the trap that catches most teams — it must not drag in native dependencies that break inside the production environment. A library that renders by leaning on the host operating system's graphics stack will pass on your laptop and fail in the deployed container. This is precisely the kind of question `/research` exists to answer.
+
+A good research prompt names the decision and its constraints rather than asking for a favorite. It states the use case (generate a QR image for an asset, server-side, in the .NET `assets-svc`), the runtime reality (the service runs in a Linux container, so no native graphics dependencies), and the decision criteria (a permissive license, active maintenance, a pure-managed rendering path). A weak prompt — "what's the best QR library?" — gives the agent nothing to optimize against, and the report drifts toward generic popularity rankings.
+
+Treat the report the way you'd read a colleague's recommendation: check that the cited sources are real and current, that it names a specific library and version, and that the integration sketch points at the actual service. The report is evidence for a decision you make, not the decision itself.
+
+## Exercise 3: Use `/research` to choose a QR library
+
+Now let's use `/research` to determine which library fits best for server-side QR generation in `assets-svc`, with the Linux-container constraint front and center. You'll review the report, and save it locally so it becomes an asset in the repository. This will aid future code generation and other developers on your team, as it will explain what was selected and why it was selected.
+
+1. In the same Copilot CLI session, run `/research` with a prompt that names the use case, the runtime constraint, and the decision criteria:
+
+ ```text
+ /research Research a QR code generation library for AssetTrack's assets service (services/assets-svc, .NET 10). I need to generate a QR code image for an asset on the server. The service runs in a Linux container, so the library must not depend on native OS graphics such as System.Drawing. Compare the realistic options and recommend one. For each option, report the license, a maintenance signal (last release and activity), whether rendering is pure-managed, and the output formats it supports (PNG and SVG). End with a recommendation and a short integration sketch for services/assets-svc. Cite your sources.
+ ```
+
+> [!NOTE]
+> The research will take several minutes as it searches the internet and considers options.
+
+2. Once the report is generated, ask Copilot to save the report to a folder named `reports` and a file named `qr-code-research.md` by using the following prompt:
+
+ ```
+ Save the report to reports/qr-code-research.md
+ ```
+
+3. In your codespace, open the file `reports/qr-code-research.md`. Review the report, reading through the information Copilot collected. The exact libraries it will return will vary, but you may see the following:
+
+ - Net.Codecrete.QrCodeGenerator
+ - QRCoder
+ - ZXing.Net
+
+4. Based on the information provided, select the library you think works best. In the prompt examples below, we'll use `Net.Codecrete.QrCodeGenerator`, but if there's a different one you like you're free to use it!
+
+You now have a full report with the various options available to you, comparisons between them, that's now an asset that can be reviewed in the future.
+
+## Planning the feature with `/plan` and rubber-duck critique
+
+A written plan is the cheapest place to catch a mistake. Once an agent starts editing files and running tools, a wrong assumption costs real time to unwind; in a plan, it costs a sentence. That economy is the whole argument for planning first, and it's why this stage produces a plan and nothing else — no edits yet.
+
+The plan for QR support has more substance than "generate an image," because the decision to store the code rather than regenerate it on every request introduces a genuine data change. Persisting the QR payload means the feature naturally separates into waves. The first is a schema change in `assets-svc`: a new column on the assets table, populated when an asset is created or updated, plus a migration. The second is the API: a new `GET /assets/{id}/qr` endpoint that renders the stored payload into an image using the library research settled on. The third is the UI: surfacing the code on the asset detail page at `services/web/src/pages/assets/[id].astro`, with the field threaded through the TypeScript model in between.
+
+The most valuable part of this stage is the critique. A first plan always reads as complete and almost always hides a gap, and rubber-ducking it — asking Copilot to argue against its own plan, ideally with a different model — is how that gap surfaces before it becomes a bug. Here the gap is specific: the QR payload is a deep-link URL that needs the asset's id, but the id doesn't exist until after the row is inserted. A plan that says "generate the payload on insert" is subtly wrong, and the fix — populate on write and backfill the rows that already exist — is exactly the kind of thing a critique catches.
+
+## Exercise 4: Plan the feature with `/plan` and a rubber-duck critique
+
+Now let's turn the library choice into a bounded, multi-wave plan, saving it to `docs/plans/qr-support.md` — still with no application code changed. The plan is finished when a teammate, handed it, would produce the same diff you would.
+
+> [!NOTE]
+> Because Copilot is probabilistic rather than deterministic, the exact plan generated and flow you experience may vary. The steps below provide guidance on what to expect, but you will need to adjust based on the exact path Copilot takes.
+
+1. In the same session, select Shift+Tab until Copilot CLI is in **plan** mode.
+2. Ask for a plan that covers the whole feature by using the following prompt:
+
+ ```text
+ /plan QR code support for AssetTrack using the library Net.Codecrete.QrCodeGenerator. The QR payload is a deep-link URL to the asset's detail page. Cover: a schema change in services/assets-svc to store the payload, populating it when an asset is created or updated, a new GET /assets/{id}/qr endpoint that renders the stored payload as an SVG image, surfacing the QR code on the asset detail page in services/web, and tests across each layer.
+ ```
+
+> [!NOTE]
+> Copilot will likely ask follow-up questions as it builds the plan. Use your best judgement and the following guidance to answer the questions:
+>
+> 1. Because the website won't actually be deployed, it's OK to accept `http://localhost:4321` as the target URL base.
+> 2. Use server-side rendering whenever possible.
+> 3. Expose payload as JSON for flexibility.
+
+3. Review the plan. Look for anything that might be vague or otherwise confusing. If you're not sure how you'd follow the plan, the same would hold true for the agent. As needed, cursor down to the bottom option, which will read something like `Suggest changes`, and request any necessary updates.
+
+Once you're satisfied with the plan, it's still a best practice to have a review of it. Copilot can do this for you through rubber ducking. Let's ask Copilot to perform a rubber duck review!
+
+4. In Copilot CLI, cursor down to `Suggest changes`.
+5. Use the following prompt to request the rubber duck review:
+
+ ```text
+ Rubber duck this plan with a new model and agent. Have the rubber duck look for what's missing, what breaks existing behavior, and what would a reviewer reject? Pay attention to the QR payload: it's a deep-link URL that needs the asset id. Does the plan handle assets that already exist in the database?
+ ```
+
+6. Copilot will rubber duck the plan, identifying potential gaps and any other updates. Follow the prompts from Copilot, answering questions based on the approach you'd like to take to adding the functionality.
+7. Once you're happy with the plan, select the prompt that says something similar to `Exit plan mode and I will prompt myself.`
+8. Save the plan so it becomes a referenceable asset — and so `/fleet` and the Quality assurance agent can read it later. Ask Copilot to write it to `docs/plans/qr-support.md`:
+
+ ```text
+ Save the plan to docs/plans/qr-support.md
+ ```
+
+With the help of Copilot, and a rubber duck, you now have a good plan to implement the feature.
+
+## Executing in parallel with `/fleet`
+
+With a bounded plan in hand, the implementation is ready to run — and because the plan split cleanly, parts of it can run at the same time. `/fleet` spins up several subagents at once, each owning an independent slice of the work. The qualifier that matters is independent. Parallelism pays off when slices don't touch each other's inputs: separate files, separate test suites, a server change and a UI change that meet only at a stable contract. It backfires on cross-cutting work, where one agent's output is another agent's input and running them together just produces conflicts.
+
+The QR plan is a good fit because its waves were drawn along those lines. The schema-and-API work in `assets-svc` and the UI work in `services/web` meet only at the shape of the `GET /assets/{id}/qr` endpoint, so once that contract is fixed, the two can proceed in parallel. Each subagent produces its own diff stream, which is what keeps the parallelism from turning into a tangle — you review each slice on its own terms rather than trying to make sense of one merged blast of changes.
+
+This is also where the discipline of the earlier stages pays off. The reason these slices can run in parallel without stepping on each other is that the research settled the library and the plan drew clean seams. `/fleet` is fast here not because parallelism is inherently fast, but because the work was shaped to be parallelizable.
+
+## Exercise 5: Build the waves in parallel with `/fleet`
+
+Now let's implement the plan with `/fleet`. Copilot will look at the plan and what needs to be done, then divide the work appropriately.
+
+Let's tell Copilot to use a fleet of agents, and to divide the work between the frontend and backend, bringing everything together when each separate component is complete. Also provide guidance to run tests and make the necessary fixes.
+
+1. Send the following prompt to launch the fleet of agents:
+
+> [!IMPORTANT]
+> Type the `/fleet` command manually and paste the rest of the prompt. This will ensure fleet mode is properly enabled.
+
+ ```
+ /fleet Implement the plan we just created. Divide the work between the backend and frontend. Ensure we have tests each step of the way, and that they pass. Bring everything together into one agent as necessary.
+ ```
+
+Copilot will get to work implementing the feature!
+
+> [!NOTE]
+> Building this feature will take several minutes to complete.
+
+When you're done, the feature is built and compiles: an asset has a stored `qr_payload`, existing assets were backfilled, `GET /assets/{id}/qr` returns an image, and the asset detail page renders the QR card — each wave arriving as its own reviewable diff. What you don't yet have is anyone vouching for it. That's the next stage.
+
+## Running the gate with your Quality assurance agent
+
+Parallel speed has a cost: several subagents each moved fast on their own slice, and no single pass has looked at the whole result with testing in mind. That's the QA step of the loop, and the agent you built in Exercise 2 is what performs it. Point it at the branch and it works through its five checks: it reads the research and plan, drives the running app through the Playwright MCP server to confirm the QR code actually appears, runs the tests, reviews the diff against the plan, and hands the new asset detail UI to the `Accessibility Expert` for a verdict.
+
+Whatever it surfaces is the start of the fifth step — iterate. A gate is only useful if you act on it: you fold its findings back into the code and run it again, until the feature clears it. One agent owns "is the promised behavior covered and working," and it delegates "is the new surface accessible" to the agent that already owns that question.
+
+## Exercise 6: Run the Quality assurance agent you built
+
+Finally, let's run the `Quality assurance` agent over the `/fleet` build, then iterate on what it finds. The agent will drive the running app, so make sure AssetTrack is up — `npm run dev`, with the UI reachable at `http://localhost:4321` — in the terminal where you've kept the app running.
+
+> [!NOTE]
+> Custom agents start with a fresh context. This means they'll only have the information you pass to it or they discover on their own. That can be advantageous in many scenarios, including performing a review. Having a Quality assurance agent do its review knowing the approach that was taken could lead to it taking shortcuts, assuming work was completed that wasn't actually done. By starting from scratch, only knowing what was supposed to be done, means it will naively explore, ask questions, and report back anything that doesn't align with the original goals.
+
+1. Switch to the agent with `/agent`, select `Quality assurance`, then run it over the branch:
+
+ ```text
+ Review the QR code feature on this branch against reports/qr-code-research.md and docs/plans/qr-support.md. Work through your full checklist: confirm the QR code renders in the running app, run the tests, review the diff, and confirm the new asset detail UI with the Accessibility Expert. Report what you covered, anything you couldn't verify, and every gap you found.
+ ```
+
+2. Watch the agent work through its checks. It should open the app in a browser through the Playwright MCP server to confirm the QR code is really there, run the test suites, review the changes, and delegate the UI accessibility check to the `Accessibility Expert`.
+3. Iterate on the findings. For each gap the agent reports, ask Copilot to fix it, then run the `Quality assurance` agent again. Repeat until the gate comes back clean.
+4. Commit the feature, along with any tests the gate added, once it passes.
+
+When you're done, the `Quality assurance` agent has confirmed the QR code renders in the running app, the tests pass, the diff matches the plan, and the new asset detail UI carries an accessibility sign-off — with every gap it found either fixed or recorded rather than silently skipped.
+
+## Summary
+
+QR support is a small feature, but it's a real one: it changes the database, adds an API endpoint and a dependency, threads a field through to the UI, and carries tests across every layer it touches. Walking the loop end to end, you:
+
+- Built a `Quality assurance` custom agent first — your definition of "done" — wired to the Playwright MCP server and composed with the `Accessibility Expert` from Module 2.
+- Used `/research` to choose a QR library you can defend, with the container constraint front and center and citations to back the choice.
+- Used `/plan` and a rubber-duck critique to turn that choice into a bounded, multi-wave plan — and caught the deep-link backfill problem before it became a bug.
+- Used `/fleet` to build the independent waves in parallel, then integrated a set of independently reviewable diffs.
+- Ran the Quality assurance agent as the gate, then iterated on its findings until the feature passed.
+
+The throughline is the loop itself: research and planning make the parallel build safe, and a quality gate you defined up front catches what speed alone would miss. Next, you'll apply the same research-and-plan instincts to a larger problem — modernizing AssetTrack's older services — with `/research`, `/lsp`, MCP servers, and per-stack migrator agents in [Module 6][next-lesson].
+
+## Resources
+
+- [Plan mode in Copilot CLI][copilot-plan]
+- [About custom agents in Copilot CLI][copilot-agents]
+- [Adding MCP servers to Copilot CLI][copilot-mcp]
+- [Playwright MCP server][playwright-mcp]
+
+---
+
+| [← Previous: Shaping Copilot CLI's lifecycle with hooks][previous-lesson] | [Next: Modernizing apps with Copilot CLI →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../04-lifecycle-hooks/
+[next-lesson]: ../06-modernize-apps/
+[m01]: ../01-working-with-copilot-cli/
+[m02]: ../02-building-ai-infrastructure/
+[m04]: ../04-lifecycle-hooks/
+[copilot-plan]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli
+[copilot-agents]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli
+[copilot-mcp]: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
+[playwright-mcp]: https://github.com/microsoft/playwright-mcp
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/06-modernize-apps.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/06-modernize-apps.md
new file mode 100644
index 00000000..17652195
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/06-modernize-apps.md
@@ -0,0 +1,365 @@
+---
+title: '06 · Modernizing Apps'
+description: 'Give Copilot better signal with LSP and MCP servers and drive a defensible modernization with per-stack migrator agents.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 6 — Modernizing apps with Copilot CLI
+
+| [← Previous: Adding a new feature][previous-lesson] | [Next: Managing Copilot's infrastructure →][next-lesson] |
+|:--|--:|
+
+Modernizing a brownfield service is a research problem before it's a coding problem, and an orchestration problem once the research is done. Then, the cycle continues.
+
+The temptation with an agent is to point it at an old service and say "upgrade this" — the same open-ended prompt that [sent Copilot wandering][m05] on the barcode feature. The discipline that made the barcode feature work applies here too: assess the ground truth, plan against real sources, build a safety net, migrate in small reviewable steps, validate each one, and write down what you learned so the next upgrade is faster.
+
+This module walks that loop against AssetTrack's oldest service. You'll give Copilot better signal with a language server and a documentation MCP server, use `/research` to produce a migration plan you can defend, protect the change with tests, and drive the upgrade with a custom migrator agent. Along the way you'll build reusable assets — an agent, a saved plan, a playbook, an extended test hook — so the second legacy service costs a fraction of the first.
+
+## What you will learn
+
+By the end of this module you will be able to:
+
+- Explain why a standard modernization process still applies when an agent does the work, and how to keep an upgrade small and reviewable.
+- Configure a Language Server Protocol (LSP) server so Copilot reasons about Java from the compiler instead of from text search.
+- Register a documentation MCP server so Copilot grounds framework claims in first-party docs rather than stale training data.
+- Use `/research` to produce a citation-backed migration plan and save it as a reusable asset.
+- Establish a test safety net that gives fast signal when a change breaks behavior, so the upgrade can move quickly.
+- Author a custom migrator agent that executes the upgrade in reviewable phases, and capture a playbook that accelerates the next service.
+
+## Scenario
+
+AssetTrack is a polyglot system, and two of its Java services are a generation behind. `audit-svc` and `auth-svc` both run on Java 17 and Spring Boot 3.5 — a supported but no-longer-current generation — while the platform's target is the current supported generation, Java 21 and Spring Boot 4.1. The services aren't vulnerable today: their dependencies are pinned to CVE-clean versions, and the hard rule is that no branch of this upgrade may ship a known-vulnerable package. But staying a generation back means missing the current framework's defaults, its language and runtime features, and the security posture that comes from tracking the latest supported line. The mandate is to bring them forward onto the current runtime and framework generation without ever regressing that clean dependency baseline.
+
+You won't do both at once. You'll modernize `audit-svc` first because it's the smaller, better-bounded service — it has no security-token code and no third-party JSON or JWT dependencies — so the upgrade is close to mechanical and you can learn the shape of the work with fewer moving parts. Then you'll turn what you learn into assets — a migrator agent and a written playbook — that make `auth-svc` a repeat rather than a fresh start. `auth-svc` is where the real dependency-security lesson lives: its `jjwt` token library drags a vulnerable transitive Jackson 2 under Spring Boot 4, and clearing that is the centerpiece of the second upgrade. `workforce-svc` already runs on Java 21, so the runtime jump is well-trodden ground; the Spring Boot 4 major is new ground for all of them.
+
+> [!NOTE]
+> **Starting state**: your fork has the AI infrastructure from earlier modules in place — custom instructions, the `Accessibility Expert` agent, the [`make-repo-contribution` skill][m02] you built earlier, and the [lifecycle hooks][m04] you added. If you're jumping in here, check out the catch-up branch that holds them:
+>
+> ```bash
+> git checkout start-of-module-06
+> ```
+>
+> This module changes production code in `audit-svc` and `auth-svc`; you'll modernize both services, then ship the whole thing as a single pull request at the end with the `make-repo-contribution` skill, which creates the branch and opens the PR for you.
+
+## Modernization is still a process, even with an agent
+
+The reason "upgrade this service" fails as a prompt is the same reason it fails as a plan for a human: an upgrade is a sequence of decisions, and skipping the decisions doesn't make them go away — it just moves them into the diff where they're expensive to find. A framework major that raises the minimum runtime, a namespace that gets renamed out from under your imports, a dependency that no longer resolves: each is a fork in the road, and an agent with no plan takes them at random.
+
+The loop that keeps that under control is the ordinary one that careful teams already use for a risky upgrade, and an agent doesn't change it:
+
+1. **Assess** — establish what the service actually is today: its runtime, framework version, dependencies, and the shape of its code.
+2. **Plan** — turn the target into a sequence of small, ordered steps, grounded in the real migration guides rather than guesses.
+3. **Protect** — put a test safety net in place so you can tell immediately when a change alters behavior.
+4. **Migrate** — apply one step at a time, keeping each change small enough to review.
+5. **Validate** — build and test after every step so a regression surfaces against the step that caused it, not ten steps later.
+6. **Document** — capture the decisions and the recipe so the next service reuses the thinking instead of rediscovering it.
+
+Agents change the speed of each step, not the steps themselves. An agent can assess a service and apply a mechanical migration far faster than doing it by hand — which makes the plan, the safety net, and the validation matter more than ever, because a fast agent makes a wrong assumption expensive just as fast. This is also the shape of the purpose-built modernization tooling you'll meet at the end of the module — GitHub's own Java modernization agent runs an assess-plan-execute pipeline, which is the same loop productized. Learning it by hand first is what lets you drive that tooling well later.
+
+> [!NOTE]
+> In this module you'll be modernizing Java code to newer versions. Modernizing an entire application is an entire course unto itself, so our goal here isn't to teach the finer points of the process, but rather highlight how the standard flow is augmented with GitHub Copilot.
+>
+> Additionally, there is a purpose-built version of a Copilot CLI plugin, [GitHub Copilot modernization][copilot-modernization-plugin]. It installs from a marketplace and runs a `modernize` agent through a standard process.
+>
+> We'll be walking through the process by hand, using research, custom agents, and tests. Basically, you can think of this as doing the math by hand to see the numbers, then you can reach for a calculator in the future.
+
+## Model choice during the upgrade process
+
+GitHub Copilot provides access to numerous models of various levels, including the ability to bring your own key. This allows you to choose the right model for the job.
+
+As you work through the application modernization flow, you'll move back and forth between higher-end tasks, like planning and research, to lower-end tasks, like writing code and generating tests. With `/model` you can switch to an appropriate model depending on the needs of the current ask, both saving time and reducing the credit usage necessary to complete the operation. For example, you might choose Claude Opus 4.8 to build the plan, then assign an agent using MAI-Flash-1 to implement the code as defined in the plan.
+
+> [!NOTE]
+> During our exploration here we're going to use `auto` as our model choice, which allows Copilot to choose the model it thinks is most appropriate for the task. This is both to streamline the lesson and to allow for completion of the course with reduced credit usage. When it comes time to being complex operations on your production codebase, you can deploy various strategies to choose the right model at the right time.
+
+## Giving Copilot better signal: LSP and documentation MCP
+
+Before Copilot makes upgrade changes, it needs a strong signal about both the code it is changing and the framework it is targeting. It should understand the code the way a compiler does, and understand the target framework the way its maintainers document it. Two extension points provide that signal.
+
+A **[Language Server Protocol (LSP)][copilot-cli-lsp-concept]** server gives Copilot structured code intelligence — go-to-definition, find-references, hover types, workspace symbol search — from the language's own analyzer rather than from text matching. On a modernization that precision matters: when Copilot needs to know every caller of a method whose signature changed, or whether a renamed symbol is fully rewired, an LSP answers from the compiler's model of the code, and it does so with compact structured results instead of reading whole files into the conversation. Copilot CLI uses a configured LSP automatically whenever one is available for the language in play.
+
+2. **An MCP server** extends what Copilot can *do*; a documentation MCP server points that extension at first-party docs. [Model Context Protocol (MCP)][mcp-concept] is an open standard for giving a model access to external tools and data, and Copilot CLI ships with the GitHub MCP server built in. For a Spring Boot major upgrade the documentation that matters is the frameworks' own, and the most direct way to reach it is [GitMCP][gitmcp], an open-source server that turns any public GitHub repository into a documentation surface. Point it at `spring-projects/spring-boot` and Copilot reads Spring's own docs straight from the source, with no account or API key — and because it's open source you can self-host it. Pointing Copilot at a live docs surface is what keeps its framework claims tied to current guidance instead of whatever version happened to be current when its training data was frozen. For a framework major upgrade where the whole point is that things changed, that freshness is the difference between advice you can trust and advice you have to re-verify by hand.
+
+### Where LSP and MCP configuration live
+
+Copilot CLI loads LSP configuration from, in priority order, a project-level `.github/lsp.json`, any installed plugins, and a user-level `~/.copilot/lsp-config.json`. A project-level file is the right choice for a shared course repo because it travels with the clone, so every contributor gets the same code intelligence — as long as the language server itself is installed in their environment. Each entry maps a language server to the file extensions it handles. MCP servers are configured separately and added with the `/mcp add` command inside a session, or with `copilot mcp add` from your shell.
+
+## Exercise 1: Configure a Java LSP and a documentation MCP server
+
+You'll give Copilot structured intelligence for AssetTrack's Java code and a first-party documentation surface for the framework research that follows. The fastest way to set up an LSP correctly is the `lsp-setup` skill, which detects your OS, installs the right server, and writes the configuration for you.
+
+### Install the Java language server
+
+Start with the code signal: install the Eclipse JDT language server through the `lsp-setup` skill and commit its configuration so the whole team shares the same view of the code.
+
+1. Return to your codespace. If you closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then reopen your existing codespace.
+2. Open a terminal by selecting Ctrl + \`, then start Copilot CLI from the repository root by running `copilot --yolo`.
+3. If prompted, trust the project folder by selecting **Yes, and remember this folder for future sessions**.
+4. Run `/models`, select **Auto** from the list, and select Enter.
+5. Ask Copilot to install the skill directly from the [Awesome GitHub Copilot][awesome-copilot] collection by entering the prompt:
+
+ ```text
+ Install the lsp-setup skill from awesome-copilot
+ ```
+
+ Copilot fetches the skill and writes it into the project skills directory as `.github/skills/lsp-setup/`.
+6. Start a new chat with `/new` so Copilot loads the newly installed skill. Confirm it's available by running `/skills list` — you should see `lsp-setup` in the list — then start the setup by entering the prompt:
+
+ ```text
+ setup lsp
+ ```
+
+7. When asked which language, choose **Java**. When asked about scope, choose the repository-level configuration so it's written to `.github/lsp.json` and shared with the team. The exact prompts vary depending on the approach Copilot takes, so follow the remaining prompts and use your best judgment to install the server.
+
+8. When setup finishes, load the configuration by running `/lsp reload`, then confirm the server starts by running `/lsp test java`. You should see the `java` server start and report ready.
+
+ The skill writes a `.github/lsp.json` that registers the [Eclipse JDT Language Server (`jdtls`)][jdtls] for Java. It looks like this:
+
+ ```json
+ {
+ "lspServers": {
+ "java": {
+ "command": "jdtls",
+ "args": [],
+ "fileExtensions": {
+ ".java": "java"
+ }
+ }
+ }
+ }
+ ```
+
+> [!NOTE]
+> The committed `.github/lsp.json` tells Copilot how to launch `jdtls`, but each environment still needs the server installed — that's what the `lsp-setup` skill does. A teammate who clones the repo runs the same skill once to install `jdtls` locally. It runs on the Java 21 JDK the AssetTrack devcontainer provides, which both builds `audit-svc` and `auth-svc` — they target Java 17 — and analyzes their source without trouble.
+
+9. To complete the installation, exit Copilot CLI by using the command `/exit`, then `/exit` again, then re-open Copilot by running `copilot --yolo`.
+
+With `.github/lsp.json` committed and `jdtls` installed, every contributor now gets the same compiler-backed view of AssetTrack's Java code.
+
+### Register a documentation MCP server
+
+Now add the framework signal: register GitMCP, pointed at Spring Boot's own repository, so Copilot can read first-party documentation while it works.
+
+1. In the same session, run `/mcp add` to open the registration form.
+2. Complete the form for the GitMCP documentation server, using Tab to move between fields:
+ - **Server Name**: `gitmcp-spring-boot`
+ - **Server type**: **HTTP**
+ - **URL**: `https://gitmcp.io/spring-projects/spring-boot`
+3. Select Ctrl + S to save, then Esc to leave the form.
+4. Confirm the server loaded by running `/mcp list` and checking that `gitmcp-spring-boot` appears alongside the built-in GitHub server.
+5. Select Esc to exit the MCP configuration screen.
+
+> [!TIP]
+> GitMCP serves whatever documentation a repository publishes — a project's `llms.txt` if it has one, otherwise its in-repo reference docs and README — so it's only as good as the source repo. If an answer comes back thin, point it at a more documentation-rich repository, add a second GitMCP server for another library (for example the Jackson repository, which matters for the Spring Boot 4 JSON change), or use the dynamic `https://gitmcp.io/docs` endpoint and name the repository in your prompt.
+
+GitMCP for Spring Boot is now registered, so Copilot can pull Spring Boot's own documentation on demand as it plans and applies the upgrade.
+
+Both the language server and the documentation MCP server are part of AssetTrack's AI infrastructure from now on.
+
+## Planning the migration with `/research`
+
+A framework major upgrade is exactly the kind of decision `/research` exists to support: it needs current, cross-referenced sources, and the cost of getting it wrong is high. The [`/research`][copilot-research] slash command runs a specialized agent that investigates your codebase, relevant GitHub repositories, and the web, then produces a cited Markdown report. Unlike a normal chat answer, the report is saved to disk, so it becomes an artifact you can review, save into the repo, and hand to a teammate.
+
+A good research prompt for a migration names the service, the current stack, the target, and the specific decisions the plan has to make — the runtime jump, the framework version, the namespace change, the data-access approach — and asks for phases and risks rather than a yes/no answer. A weak prompt ("how do I upgrade this?") gives the agent nothing to optimize against, and the report drifts toward a generic checklist. Because the research agent works autonomously and documents its assumptions rather than stopping to ask, the more constraints you give it up front, the more useful the report.
+
+Treat the result as evidence, not as the decision. The upgrade touches real things: Spring Boot 4 builds on [Spring Framework 7][spring-framework-7] and raises the minimum Java version, so the runtime and the framework move together; Spring Boot 4 defaults to [Jackson 3][jackson-3] — the new `tools.jackson` namespace — and no longer manages Jackson 2, so anything still resolving a Jackson 2 artifact needs a deliberate decision rather than a silent transitive pull; and the target is the current framework generation, not a data-layer rewrite — so keep `audit-svc`'s working `JdbcTemplate` code in this upgrade and record the larger move to [Spring Data JPA][spring-data-jpa] as a separate follow-on rather than smuggling a rewrite into a framework bump. A good report will surface each of these with a source; your job is to confirm the sources are real and current before you let the plan drive code.
+
+> [!NOTE]
+> The riskiest part of a major upgrade is often the changes in default behavior, not renames and versions. A new major version can silently alter how the framework behaves out of the box, so code that never changed starts doing something different, and the failure shows up at runtime with no obvious link to anything you edited. That's why default behavior belongs in your research up front: a shifted default is far cheaper to surface in a report and hand to the agent as an explicit constraint than to debug in a failing service later. A research pass that digs to this depth takes real time and reads dozens of sources, which is exactly why the plan in the next exercise was produced once and captured as a reusable asset rather than regenerated live.
+
+## Exercise 2: Get a research-backed migration plan for audit-svc
+
+The plan you'll drive the rest of the module from was produced by a real `/research` run — the research prompt in the tip below, pointed at `audit-svc` — and then trimmed so Copilot can consult it without re-reading a 700-line report every time it needs the recipe. It's a representative artifact: citation-backed, phased, and grounded in the actual `contoso-inventory` source. You'll pull it into the repo now so it becomes the first reusable asset of the modernization, then verify it before it drives any code.
+
+1. In your main session, have Copilot find the representative plan in the course repository and save it into your repo. Copilot CLI ships with the GitHub MCP server, so it can locate the file by repository and name rather than a brittle raw URL:
+
+ ```text
+ Using the built-in GitHub MCP server, find the audit-svc migration plan in the github-samples/advanced-copilot-cli repository — it's the resource file under content/resources for modernizing audit-svc. Read its contents and save them to docs/modernization/audit-svc-plan.md in this repo. Save it as-is — don't summarize or reformat it.
+ ```
+
+2. Open `docs/modernization/audit-svc-plan.md` and read the phases. Check it against the ground truth so you trust it before it drives code: it names a specific Spring Boot 4.1 parent rather than a generic "4.x", it accounts for the Jackson 2→3 default that Spring Boot 4 brings, and it keeps the existing `JdbcTemplate` data access rather than folding a JPA rewrite into the upgrade. Verify a couple of the cited links resolve. This is the contract the migrator agent works against, so if any phase reads too vaguely to hand to a teammate, ask Copilot to deepen it — ambiguity here becomes drift later.
+
+> [!TIP]
+> Want to see how the plan was produced? Generate your own! Type `/research`, then paste the prompt that names the service, the current and target stacks, and the decisions the plan must make. Do keep in mind it will take roughly 30 minutes for the research to complete.
+>
+> ```text
+> I need to modernize the audit-svc service in this repo. It currently runs on Java 17 and Spring Boot 3.5.16 with raw JDBC (JdbcTemplate) over SQLite, with jackson-bom and log4j2 pinned to CVE-clean versions. The target is the current supported generation: Java 21 and Spring Boot 4.1 (Spring Framework 7). Produce a phased migration plan. For each phase cover: the exact changes (the spring-boot-starter-parent version bump to the latest Spring Boot 4.1, the java.version bump from 17 to 21, how the Spring Boot 4 default of Jackson 3 affects this service, and confirmation that the existing JdbcTemplate data access stays as-is for this upgrade with any move to Spring Data JPA called out only as an optional follow-on), the order to do them in, how to validate each phase, and the risks. A hard constraint: no phase may introduce a known-vulnerable dependency. Cite the official Spring Boot 4 migration guide and Jackson 3 sources.
+> ```
+>
+> The research runs for several minutes while the agent reads the code and the official Spring Boot and Jackson guides on the web — it won't stop to ask questions, it records its findings in the report. When it finishes, save it with `/share file research docs/modernization/audit-svc-plan.md`. Your report will be longer and more heavily footnoted than the trimmed version you downloaded, but the decisions and the shape will be the same.
+
+## Creating a test safety net
+
+A modernization is a unique kind of code update: the whole point is that behavior stays the same while the framework underneath it migrates completely. That's exactly the situation tests were built for. A suite that pins the current behavior turns every phase of the upgrade into a yes/no question, "Did the parent bump, the base-image change, or a moved import alter what the service does?", and answers it in seconds instead of in a production incident weeks later. Without that signal you're reading diffs and hoping; with it, you can let a fast agent make sweeping changes and know immediately when one lands wrong.
+
+Two layers of tests catch different failures, and a real upgrade wants both:
+
+- **Regression tests at the service** — unit and integration tests that exercise `audit-svc`'s endpoints and its data access. These are the inner loop: they run in seconds after every phase and pinpoint the exact change that broke a behavior, so a regression is fixed against the phase that caused it rather than untangled ten phases later.
+- **End-to-end tests across the system** — a [Playwright suite you built earlier][m03] that drives AssetTrack through the UI. A framework major can leave a service's own tests green while breaking how it integrates with the rest of the system; the e2e layer is the outer net that catches a contract that shifted at the seams.
+
+The catch is that `audit-svc` has no tests today, which is common for exactly the services most in need of modernizing. So the first move isn't the upgrade — it's building enough of a net to make the upgrade observable. You capture the current behavior *before* you change the framework, so the tests describe "what `audit-svc` does on Spring Boot 3.5," and then hold that description constant as you move it to 4.1. Tests written after an upgrade only prove the new code is self-consistent; tests written before it prove you didn't change behavior on the way.
+
+> [!TIP]
+> Writing characterization tests against the old version first is what makes a modernization safe to hand to an agent at all. The tests become the specification the agent has to keep satisfying, which turns "trust me, the upgrade is fine" into a green suite anyone can rerun.
+
+## Exercise 3: Build a test safety net for audit-svc
+
+Now put that into practice: capture `audit-svc`'s current behavior in tests *before* you touch the framework, so the upgrade has a baseline to be measured against.
+
+1. Still in your main session, have Copilot add the tests and run them, so the safety net is green before anything changes:
+
+ ```text
+ audit-svc has no tests. Before we modernize it, add a safety net that captures its current behavior. Add a Spring Boot context-load test like workforce-svc's WorkforceApplicationTests, plus integration tests that exercise the AuditController endpoints and the AuditRepository queries and assert the current JSON responses. Configure the tests to use an isolated temporary SQLite database instead of the real AUDIT_DB_PATH so they don't touch /data/audit.db. Then run them with mvn test from services/audit-svc and confirm they pass on the current Spring Boot 3.5 code before we change anything.
+ ```
+
+2. Confirm Copilot reports the suite passing against the *old* version. This is the baseline: if the upgrade changes behavior, these are the tests that will tell you.
+
+When you're done, `audit-svc` has a green safety net on Spring Boot 3.5 — the behavioral baseline the migrator agent has to keep satisfying phase by phase.
+
+## Driving the upgrade with a custom migrator agent
+
+You could run the migration from your main session, but a modernization is a good candidate for a dedicated custom agent, because a written scope and a small tool surface keep the work reviewable and repeatable. A migrator agent whose instructions pin it to one service — its source, its build, and the repo wiring that service depends on — stays out of the frontend and the other services, and because its behavior is written down, you can point it at the next legacy service without re-explaining the job.
+
+One caveat, stated honestly: that scope is an instruction the agent follows, not a sandbox. A custom agent's tools control the *kinds* of actions it can take — read, edit, run commands — but `edit` and `execute` still reach the whole repository. The agent keeps to the service it's migrating and that service's wiring because its instructions say so. That is exactly why the approval gate matters, and why the finishing edits it makes to shared files, like the test router and `package.json`, are ones to read most closely.
+
+The migrator's instructions encode the process, not a specific service: read the saved plan, apply one phase at a time, run the service's tests after each phase, stop after each phase and wait for your approval before starting the next, and report rather than pressing on when a phase fails. Once the phases are approved and green it finishes the job the way a careful person would — running the end-to-end suite as a final gate, wiring the service into the repo's test router and dev script, and writing a status report — so what comes out the other end is a fully migrated service, not just an edited `pom.xml`. That approval gate is what keeps a fast agent honest: validation between phases is where a framework upgrade catches its own mistakes, and it only helps if you read the diff before waving the agent on.
+
+## Exercise 4: Modernize audit-svc with the migrator agent
+
+You'll author a `Java migrator` custom agent, run it through the saved plan one reviewable phase at a time, and capture what you learned so the next service reuses it.
+
+### Author the migrator agent
+
+Now encode the process itself in a reusable agent whose written scope keeps the migration reviewable and lets you rerun it on the next service.
+
+1. Ask Copilot to write the agent definition from the migration plan you saved, so the process the research captured becomes the agent's instructions instead of a list you re-describe by hand:
+
+ ```text
+ Read docs/modernization/audit-svc-plan.md, then create a custom agent at .github/agents/java-migrator.agent.md named "Java migrator" with read, edit, and execute tools that follows that plan to modernize one service and its wiring at a time — never another service or the frontend. It works one phase at a time, running the service's Maven tests after each phase and stopping for my approval before the next, then runs the Playwright end-to-end suite (npm run test:e2e) as a final gate. Finish with a testing status report confirming both layers pass on the target stack.
+ ```
+
+2. Open `.github/agents/java-migrator.agent.md` and review it. Confirm the scope (one service plus its wiring, never another service's source or the frontend), the read/edit/execute tools, the phase-by-phase rule that stops for your approval and runs the service's tests between phases, the finishing work it does once the phases are green — the end-to-end gate, the test-router route, and the dev-script cleanup — and the closing testing status report that confirms both the unit and end-to-end suites passed on the updated framework and runtime.
+3. Reset to a clean conversation with `/new` so Copilot picks up the new agent, then confirm it with `/agent`. You should see `Java migrator` in the list. Select Esc to exit the agent menu.
+
+The `Java migrator` agent now exists as a repository scoped asset — one you'll use to drive your migrations.
+
+### Run the migration phase by phase
+
+Drive the upgrade through the saved plan, approving one phase at a time and reading the diffs — watching most closely where the agent touches shared files. The agent runs the migration end to end; your job is the review at each gate.
+
+1. Switch to the agent with `/agent` and select `Java migrator`, and select Enter.
+2. Tell the agent to perform the upgrade of audit-svc by using the following prompt:
+
+ ```text
+ Modernize services/audit-svc following the guidelines provided in the agent, and give me a final status report at the end. The baseline test suite from the previous exercise is already in place, so confirm it passes and start from the toolchain phase.
+ ```
+
+ Watch the agent work the loop: it bumps the `spring-boot-starter-parent` to Spring Boot `4.1.0` and the `java.version` from `17` to `21`, re-points the `jackson-bom` currency pin at a CVE-clean Jackson 3 (Boot 4.1.0 otherwise resolves a still-vulnerable Jackson `3.1.4`), and builds and tests after each phase. When the LSP is active, notice that it locates callers and symbols precisely rather than grepping.
+
+3. Read the agent's testing status report. It should name the stack the service now targets — Java 21 and Spring Boot 4.1.0 — and confirm both layers ran and passed against it: the per-phase `audit-svc` unit and integration tests, and the final end-to-end suite.
+
+### Capture the playbook and update the agent
+
+As highlighted previously, app modernization follows a cycle of research, coding and orchestration. As you complete one cycle, learnings should be documented and updates made to the tools used. Now that you've updated the first service, let's document any learnings, and ensure the agent is updated to reflect any needed changes. Writing docs and editing the agent definition are outside the migrator's scope, so switch back to your default agent with `/agent` before you start.
+
+1. Turn what you just learned into a short playbook so the next service reuses the recipe. Ask Copilot to write it from the actual work, not from theory, by sending the following prompt:
+
+ ```text
+ Using the the learnings and process we just followed, let's create an updated migration-playbook.md file that will supersede the original. Bring over anything applicable generalized from the original research, and any lessons from the upgrade you just performed.
+ ```
+
+2. Ask Copilot to update the agent with any learnings it has that might improve the process by using the following prompt:
+
+ ```text
+ Let's perform a similar update to the agent definition as well. Identify any areas of the agent's definition that could be improved upon.
+ ```
+
+3. Review the newly generated `migration-playbook.md` and updated `java-migrator.agent.md` files, making any changes you believe are necessary for clarity's sake.
+
+When you're done, `audit-svc` runs on Java 21 and Spring Boot 4.1, its tests are green, and it has moved a full generation forward from Java 17 / Spring Boot 3.5 — and you've produced four assets that outlast this one service: the `Java migrator` agent, the saved plan, the playbook, and a test router that now guards the modernized service.
+
+## Exercise 5: Reuse the assets on the next service
+
+Modernizing the first service was the expensive part. The second one is where building assets pays off: the same agent and the same playbook are most of the work already done — and because the playbook already captured the recipe, you don't need a second research pass to rediscover it. `auth-svc` starts from the same Java 17 / Spring Boot 3.5 baseline as `audit-svc`, but it carries this module's real dependency-security lesson: it issues JWTs with the `jjwt` library, and under Spring Boot 4 `jjwt`'s serializer drags in a vulnerable transitive Jackson 2 — exactly the dependency the framework upgrade stops managing for you. Clearing it without regressing the clean baseline is the substance of this upgrade: bump `jjwt` from `0.11.5` to `0.12.7` (its `-api`, `-impl`, and `-gson` artifacts) and swap the `jjwt-jackson` serializer for `jjwt-gson`, whose CVE-clean Gson dependency (`2.13.2`) removes Jackson 2 from `auth-svc` entirely.
+
+> [!TIP]
+> That difference is here on purpose. `auth-svc` isn't a clean re-run of `audit-svc`, and that's the point. It leans on an extra third-party library for its login tokens, and that library's transitive dependencies react to the Spring Boot 4 upgrade in a way `audit-svc`'s never do — small, realistic ways that no two services are ever quite alike. When the vulnerable-Jackson pull trips a build or a dependency check, you're watching the safety net do its job, not the agent coming apart. A red signal is a precise instruction you hand back to the agent — "this library pulled a vulnerable transitive," "swap the serializer to jjwt-gson" — and it adapts. That loop, where good prep plus a red result sharpens the next instruction, is exactly how better inputs produce better AI outcomes. Expect a little iteration on the second service, and read it as the process working rather than the agent misbehaving.
+
+1. Start a new session in Copilot to load the changes we just made by using the `/new` command.
+2. Build the safety net first, the same way you did for `audit-svc`. Have Copilot write the tests and confirm they pass against the current version so you have a baseline before anything changes:
+
+ ```text
+ Add safety net tests for auth-svc: a context-load test plus integration tests for the TokenController that assert a token is issued and validates, using an isolated temporary database. Run them with mvn test and confirm they pass on the current version.
+ ```
+
+3. Enable the Java migrator agent by using the `/agent` command in Copilot, selecting `java-migrator`, then selecting Enter.
+4. Run the newly updated agent and playbook to modernize `auth-svc` by using the following prompt:
+
+ ```text
+ Modernize services/auth-svc following the guidelines provided in the agent, and give me a final status report at the end. The baseline test suite from the previous exercise is already in place, so confirm it passes and start from the toolchain phase.
+ ```
+
+ The agent performs the upgrade, following the steps lessons from your research and the first migration process.
+
+5. Once the work is complete, ask Copilot to create a PR with your new agent, playbook, and newly upgraded services by using the following prompt:
+
+ ```text
+ Group all the changes we made into logically grouped commits. Then create a new pull request with a summary of what we built.
+ ```
+
+When you're done, both legacy services are on the modern stack, and the second upgrade reused the migrator agent, the playbook, and the router pattern instead of rediscovering them — the payoff of starting small, learning, and documenting rather than writing one-off prompts. That's how the process compounds.
+
+## Summary
+
+You modernized AssetTrack's oldest service without letting the agent improvise, by running the same process a careful engineer would. In this module, you:
+
+- Configured a Java LSP so Copilot reasoned about the code from the compiler, and registered a GitMCP documentation MCP server pointed at Spring Boot's own repository so its framework claims came from current, first-party docs.
+- Used `/research` to produce a citation-backed, phased migration plan and saved it into the repo as a reusable asset.
+- Built a test safety net that captured the service's behavior before the upgrade, giving fast signal at every phase and an end-to-end check across the system.
+- Built a scoped `Java migrator` agent that applied the upgrade in reviewable phases, validated by its own unit and end-to-end tests.
+- Reused the agent, a captured playbook, and an extended test router to modernize the second service faster than the first, then shipped both upgrades as a single reviewed pull request.
+
+The throughline is that AI changes the cost of each step, not the need for the steps. Assess, plan, protect, migrate, validate, and document is what keeps a fast agent producing an upgrade you can review — and the assets you build doing it are what make the next one cheap. Next, you'll take the agents, skills, and MCP servers you've built and distribute them across the whole organization [in the next module][next-lesson].
+
+> [!NOTE]
+> Now that you've run the loop by hand, the [GitHub Copilot modernization plugin][copilot-modernization-plugin] maps onto it almost one for one — same discipline, more of it automated. Its **assessment** stage is the code-and-docs signal you stood up with the LSP and the documentation MCP; its **planning** stage is the phased, citation-backed plan you produced with `/research` and saved into the repo; its **execution** stage is the scoped migrator agent applying reviewable phases. The self-verification its executors run — build, test, and validate with retry — is your test safety net, and the per-phase commits it preserves for review are your captured playbook. It even generalizes the scope and guardrails you hand-wrote into your agent into a reusable *rulebook*, so target stacks, upgrade standards, and compliance policies apply to every plan it generates. The calculator runs the same six steps you just did by hand — the difference is it runs them across a fleet of services at once, which is exactly why knowing the numbers first is what lets you trust the result.
+
+## Resources
+
+- [Adding LSP servers for GitHub Copilot CLI][copilot-cli-lsp-add]
+- [Eclipse JDT Language Server (`jdtls`)][jdtls]
+- [Adding MCP servers for GitHub Copilot CLI][copilot-mcp]
+- [Researching with GitHub Copilot CLI][copilot-research]
+- [Creating custom agents for GitHub Copilot CLI][copilot-agents-create]
+- [Spring Boot 4.0 migration guide][spring-boot-4-migration]
+- [Spring Framework 7 reference][spring-framework-7]
+- [Jackson 3][jackson-3]
+- [GitHub Copilot modernization plugin][copilot-modernization-plugin]
+
+---
+
+| [← Previous: Adding a new feature][previous-lesson] | [Next: Managing Copilot's infrastructure →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../05-add-feature-barcode/
+[next-lesson]: ../07-manage-infrastructure/
+[m02]: ../02-building-ai-infrastructure/
+[m03]: ../03-test-suite-remote-delegation/
+[m04]: ../04-lifecycle-hooks/
+[m05]: ../05-add-feature-barcode/
+[copilot-cli-lsp-concept]: https://docs.github.com/copilot/concepts/agents/copilot-cli/lsp-servers
+[mcp-concept]: https://docs.github.com/copilot/concepts/context/mcp
+[gitmcp]: https://github.com/idosal/git-mcp
+[awesome-copilot]: https://awesome-copilot.github.com/
+[jdtls]: https://github.com/eclipse-jdtls/eclipse.jdt.ls
+[copilot-research]: https://docs.github.com/copilot/concepts/agents/copilot-cli/research
+[jackson-3]: https://github.com/FasterXML/jackson#jackson-30
+[spring-framework-7]: https://docs.spring.io/spring-framework/reference/7.0/index.html
+[spring-data-jpa]: https://spring.io/guides/gs/accessing-data-jpa/
+[copilot-modernization-plugin]: https://github.com/microsoft/github-copilot-modernization
+[copilot-cli-lsp-add]: https://docs.github.com/copilot/how-tos/copilot-cli/set-up-copilot-cli/add-lsp-servers
+[copilot-mcp]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
+[copilot-agents-create]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli
+[spring-boot-4-migration]: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/07-manage-infrastructure.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/07-manage-infrastructure.md
new file mode 100644
index 00000000..fce864ac
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/07-manage-infrastructure.md
@@ -0,0 +1,304 @@
+---
+title: '07 · Managing Copilot''s Infrastructure'
+description: 'Scale your AI infrastructure with a custom MCP server, a plugin, and enterprise-level distribution.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 7 — Managing Copilot's infrastructure
+
+| [← Previous: Modernizing apps with Copilot CLI][previous-lesson] | [Next: Wrap-up →][next-lesson] |
+|:--|--:|
+
+Everything so far has been scoped to a single repository, or to you as the individual developer. The instructions, custom agents, agent skills, and lifecycle hooks you committed to AssetTrack do travel to anyone who clones it — but they stop at that repo's boundary. The MCP configuration was saved to your user settings. In practice, though, there are always rules, agents, skills, and shared resources that shouldn't belong to single repository; they encode how the whole organization works, and every developer at Contoso should get them automatically. This module moves that setup to the enterprise level: a custom MCP server that exposes a shared resource, a plugin that bundles your AI infrastructure into a single installable unit, and enterprise standards that push it to every developer without anyone cloning a repo or copying a file.
+
+## What you will learn
+
+By the end of this module you will be able to:
+
+- Explain when a custom MCP server is the right way to extend Copilot, and add one to a session.
+- Author a custom MCP server that exposes AssetTrack's databases as a live, read-only schema catalog.
+- Package your custom agents, agent skills, hooks, and MCP configuration into a plugin that installs in one command.
+- Distribute your setup at the enterprise level with a marketplace, enterprise-managed plugin standards, and enterprise custom agents.
+
+## Scenario
+
+The accessibility upgrade, the test backfill, the hooks, the barcode feature, and the modernization all worked because you had the right AI infrastructure. A new engineer who clones AssetTrack inherits the repo-scoped pieces (instructions, agents, skills, and hooks), but the user-scoped setup on your machine doesn't come with the clone, and none of it reaches Contoso's *other* repositories and teams. The consistency you fought for inside AssetTrack stops at its edge, and every other team is starting from scratch.
+
+Closing that gap means lifting the setup out of AssetTrack and up to where the whole organization can reach it: AssetTrack's databases exposed as a shared schema catalog through an MCP server, the surrounding AI infrastructure packaged so it installs in a single step, and enterprise standards that put the setup in front of every Contoso developer the moment they authenticate — no cloning, no copying.
+
+> [!NOTE]
+> Starting state: your fork has a working set of AI infrastructure in place — custom instructions, custom agents, agent skills, lifecycle hooks, a Playwright test suite, and the modernized barcode feature, all committed to AssetTrack. If you're jumping in here, check out the catch-up branch that holds them:
+>
+> ```bash
+> git checkout start-of-module-07
+> ```
+>
+> The plugin and MCP work in this module targets your fork only, but the patterns are written for org-wide rollout.
+
+## Custom MCP servers as shared infrastructure
+
+As we explored in an earlier module, [Model Context Protocol (MCP)][mcp-concept] is an open standard for giving a model access to external tools and data. Copilot CLI ships with the GitHub MCP server built in, and you can add others, such as [Context7][context7-mcp] and [Microsoft Learn][mslearn-mcp] documentation servers. Those are servers publicly published. Your organization can also create and publish a *custom* MCP server to expose a resource specific to your team.
+
+Custom MCP servers are powerful because they allow developers to stay in the zone rather than opening a separate tool. You can use them to provide Copilot resources it would otherwise lack, such as how internal libraries work, the service catalog that only lives in an internal portal, or the operational database behind a SQL client.
+
+AssetTrack is split across services that each own their own database — assets in one, employees and their assignments in another, audit events in a third. That layout is normal for microservices, but it means no single place tells you where a given piece of data lives. When an engineer sets out to add a feature — say, a service that returns the most recent events for a piece of hardware by employee — the first question is *which databases hold that data?*, and today the only way to answer is to read through every service or ask whoever remembers. That knowledge lives in people's heads instead of in a tool Copilot can reach.
+
+A custom MCP server can turn that scattered knowledge into a live catalog. Pointed at the databases, it introspects them on demand and exposes read-only tools — `list_databases`, `get_schema`, and `find_data` — that let Copilot discover which service owns which tables and columns. Ask where hardware, employees, and events live, and Copilot answers from the catalog: assets in the assets service, assignments and employees in the workforce service, events in the audit service, along with the columns that join them. Because the tools only ever read schema, never rows, the server is a safe surface that reflects the databases exactly as they are — with no schema files to write or keep in sync.
+
+### Where MCP configuration lives
+
+Copilot CLI merges MCP configuration from several locations:
+
+- `~/.copilot/mcp-config.json` — user-level servers available in every session on your machine. This is where `/mcp add` and `copilot mcp add` write.
+- A `.mcp.json` file committed to the repo root — workspace servers shared with everyone who clones it. Workspace servers load only when the working directory is trusted.
+- A `.mcp.json` file inside a plugin — configuration that travels with the plugin, which is how you'll distribute the AssetTrack catalog's server entry later in this module.
+
+Each entry is one of two kinds: a **local** server that Copilot launches as a subprocess and talks to over `stdin`/`stdout`, or a **remote** server that Copilot connects to at an HTTP URL. The catalog you build in this module is a remote HTTP server — a shape that later lets the whole organization point at one shared endpoint instead of each developer running their own copy.
+
+> [!NOTE]
+> We'll explore creating plugins later in this module.
+
+If your organization or enterprise has configured an [MCP registry URL and allowlist policy][add-mcp-servers], those settings apply to Copilot CLI as well, and only permitted servers can run.
+
+## Exercise 1: Build the AssetTrack schema catalog MCP server
+
+You'll scaffold an MCP server that introspects AssetTrack's service databases and serves them as a read-only schema catalog over HTTP, register it by URL, and confirm Copilot uses it to answer where data lives across the services.
+
+### Start the databases and scaffold the server
+
+1. Return to your codespace. If you closed it, navigate to your repository on GitHub.com, select **Code** > **Codespaces**, then reopen your existing codespace.
+2. Open a terminal by selecting Ctrl + \`, then start AssetTrack once so each service creates and seeds its database. Run `npm run dev` and leave it running. Each service's `dev:*` script creates its SQLite file under `services//data/`.
+3. Open a second terminal by selecting Ctrl + Shift + \`, then start Copilot CLI from the repository root by running `copilot --yolo`.
+4. If prompted, trust the project folder by selecting **Yes, and remember this folder for future sessions**.
+5. Run `/models`, select **Auto** from the list, and select Enter.
+6. Ask Copilot to scaffold the catalog server, keeping it read-only and pointed at the dev databases:
+
+ ```text
+ Build an MCP server in a new mcp-servers/assettrack-catalog/ folder that gives Copilot a read-only schema catalog of AssetTrack's databases. It should introspect the databases live and expose the schema through a few tools.
+
+ Requirements:
+ - Serve it over HTTP using the MCP SDK's Streamable HTTP transport at /mcp, on the port from the PORT env var (default 5010).
+ - Find the dev SQLite databases under services/*/data/ starting from the ASSETTRACK_REPO_ROOT env var (default to the current working directory), and figure out which service owns each one from its path.
+ - Read the schema live from each database rather than storing it, and open them read-only.
+ - Expose three tools and no write tools: list_databases, get_schema (optionally for one database), and find_data to search tables and columns for a term.
+ - Wire it into the repo's npm run dev by adding a dev:catalog script and including it in the root concurrently command so it starts alongside the services.
+ ```
+
+> [!NOTE]
+> You used plan mode in earlier modules to think through larger changes before writing any code, and it would work well here too. We're keeping this prompt short and direct so you can stay focused on the MCP concepts rather than the build itself.
+
+7. Review the generated code before running anything. Confirm it opens each database read-only, introspects live (no hard-coded or stored schema), exposes only the three read tools, and reads the repository root from the environment rather than hard-coding a path.
+
+### Run and register the server
+
+1. The catalog now starts with the rest of AssetTrack. Stop the `npm run dev` you left running by selecting Ctrl + C, then run `npm run dev` again so `concurrently` picks up the new `dev:catalog` process. The catalog comes up alongside the services and listens at `http://localhost:5010/mcp`.
+
+2. Register the server with the current session. In the CLI, run `/mcp add` and complete the form with the following values:
+
+ - Server Name: `assettrack-catalog`
+ - Server Type: `HTTP` (the Streamable HTTP transport; choose `SSE` only for a legacy server)
+ - URL: `http://localhost:5010/mcp`
+ - Tools: `*`
+
+ The server is available immediately without restarting the CLI.
+
+ > [!TIP]
+ > To register it without the form, use `copilot mcp add` with the HTTP transport (it writes to `~/.copilot/mcp-config.json`):
+ >
+ > ```bash
+ > copilot mcp add --transport http assettrack-catalog http://localhost:5010/mcp
+ > ```
+
+3. Select Ctrl + S to save the configuration.
+4. Select Esc to exit the configuration screen.
+5. Confirm the server loaded by running `/mcp` and checking that `assettrack-catalog` appears with its three tools.
+6. Start a new session by using the command `/new` and selecting Enter.:
+7. Test the new MCP server by using the following prompt:
+
+ ```text
+ Where is the data for hardware events by employee stored, and what tables/columns relate hardware, employees, assignments, and recent events?
+ ```
+
+ Watch the tool calls: Copilot should call `find_data` and `get_schema` and answer from the catalog — events from `audit_events` in the audit service, hardware from `assets` in the assets service, and the employee link from `assignments` and `employees` in the workforce service — instead of guessing or grepping the codebase.
+
+With the catalog registered, "where does this data live?" is now a first-class tool call in every session. Instead of reading through the services or asking whoever remembers, Copilot reads the live schema across AssetTrack's databases and points you — and any new service you build — at exactly the right source.
+
+### Commit and merge the changes
+
+With our new MCP server created, let's create a pull request (PR) so it becomes part of our project!
+
+1. Use the `/new` prompt in Copilot to create a new session.
+2. Use the following prompt to tell Copilot to create a new branch, a commit and a PR, and to merge the PR when done:
+
+ ```
+ We just defined a new MCP server. Can you please create a new branch called add-mcp-server, generate a short commit message, then create the PR. Once the CI completes for the PR, go ahead and merge it.
+ ```
+
+Copilot will get to work on creating the PR and merging it. This will take just a couple of minutes to complete.
+
+## Plugins: bundling AI infrastructure
+
+A [plugin][copilot-plugins] is a single installable package that extends Copilot — including Copilot CLI and the Copilot cloud agent — with reusable components. A plugin can contain any combination of:
+
+- Custom agents — `*.agent.md` files in `agents/`
+- Agent skills — skill subdirectories in `skills/`, each with a `SKILL.md`
+- Hooks — a `hooks.json` file
+- MCP server configurations — a `.mcp.json` file, exactly the shape you saw in Exercise 1
+- LSP server configurations — an `lsp.json` file
+
+Every plugin has a `plugin.json` manifest at its root that names the plugin and points to the components it provides. A typical layout:
+
+```text
+contoso-assettrack-plugin/
+├── plugin.json # Required manifest
+├── agents/ # Custom agents
+│ └── accessibility-expert.agent.md
+├── skills/ # Agent skills
+│ └── make-repo-contribution/
+│ └── SKILL.md
+├── hooks/ # Lifecycle hooks that run checks after Copilot edits files
+│ ├── hooks.json
+│ └── scripts/
+│ └── test-router.sh
+└── .mcp.json # MCP config pointing at the schema catalog's HTTP URL
+```
+
+You install a plugin from a marketplace with `copilot plugin install PLUGIN-NAME@MARKETPLACE-NAME` (or the `/plugin install` slash command), or enable it declaratively through the `enabledPlugins` field of a user-level `~/.copilot/settings.json` or repository-level `.github/copilot/settings.json` file. A marketplace is just a registry of plugins — it can live in a GitHub repository, another Git host, or a local or shared file system — so even while you're developing a plugin, you register a marketplace (a local folder works fine) and install from it. Because a plugin's components are cached after install, you re-run the install command to pick up edits.
+
+## Exercise 2: Package the AssetTrack AI infrastructure as a plugin
+
+You'll gather the agents, skills, hooks, and MCP configuration built across the course into one plugin, then register your repo as a marketplace and install it from there — the same path a teammate would take to get everything in a single command.
+
+### Assemble the plugin
+
+Let's build the plugin!
+
+1. Ask Copilot to build the whole plugin by gathering the various agents, skills, and hooks you've worked with thus far:
+
+ ```text
+ Create a plugin named contoso-assettrack-plugin in a new folder of the same name, with a plugin.json manifest that lists me as the author. Include the following:
+ - All the agents, skills, and hooks defined in this repo, copied into the plugin so it is self-contained
+ - A .mcp.json that registers an MCP server named assettrack-catalog pointing at http://localhost:5010/mcp
+ ```
+
+2. Review what Copilot produced before going further. Two things matter most: the components were copied into the plugin (not referenced from `.github/...`), so it's self-contained and installs from anywhere; and any path the hooks use is plugin-relative with `${PLUGIN_ROOT}`, which the CLI expands to the installed plugin's directory — so a hook that used a `.github/...` path in the repo should now call `"${PLUGIN_ROOT}/hooks/scripts/test-router.sh"`. The `.mcp.json` only references the catalog server's URL; it bundles no server code, because the catalog runs as a separate HTTP process.
+
+### Register your repo as a marketplace and install the plugin
+
+Copilot CLI installs plugins from a marketplace, and a marketplace is just a repository with a `marketplace.json` manifest that lists which plugins it holds. Your fork already contains the plugin, so instead of standing up a second repo you'll turn the fork itself into the marketplace by adding one manifest.
+
+> [!NOTE]
+> This is the one shortcut we're taking: using the AssetTrack repo itself as the marketplace. It keeps the exercise to a single repo. In a real organization you'd usually give the marketplace its own repository so plugin distribution isn't tied to one product's codebase. The `marketplace.json` and the commands are identical regardless of what repository is being used.
+
+1. Ask Copilot to add the marketplace manifest to your repo:
+
+ ```text
+ Add a plugin marketplace manifest to this repo at .github/plugin/marketplace.json. Name the marketplace contoso-marketplace and list the contoso-assettrack-plugin with its name, description, and version, using the contoso-assettrack-plugin folder at the repo root as its source. Follow the marketplace.json format in the GitHub Copilot CLI plugin docs.
+ ```
+
+With the marketplace created, add it from the Copilot CLI session you're already using so you can test the plugin without leaving the flow.
+
+1. Register your repo as a marketplace by pointing at the repository root, then confirm it's known and browse its plugins:
+
+ ```text
+ /plugin marketplace add .
+ /plugin marketplace list
+ /plugin marketplace browse contoso-marketplace
+ ```
+
+2. Install the plugin from the marketplace by its `name@marketplace` identifier:
+
+ ```text
+ /plugin install contoso-assettrack-plugin@contoso-marketplace
+ ```
+
+### Test the plugin in action
+
+With the plugin installed, do a little spot-checking in the same Copilot CLI session to ensure everything is installed and behaving as expected.
+
+1. Use the command `/mcp list` to list all MCP servers. You should see both assettrack-catalog MCP servers, with a note the one from the plugin is being used.
+2. Use the command `/agent` to list all installed agents. You should see the Accessibility agent with a mark that it's from a plugin.
+3. Select Esc to exit the agents list screen.
+4. Use the command `/skills list` to list all installed skills. You should see a group of available skills highlighted from the plugin.
+
+The AssetTrack AI infrastructure is now a single unit. Everything you built piece by piece across the course — the accessibility agent, the contribution skill, the hooks, and the schema catalog MCP server — installs with one plugin, which is exactly what makes it something you can hand to the rest of Contoso.
+
+## Distributing at the enterprise level
+
+Installing from your own marketplace solves the problem for you; solving it for everyone is a distribution problem. This can be tackled in three steps:
+
+1. host the marketplace where the whole organization can reach it,
+2. make the plugin install automatically through enterprise-managed plugin standards,
+3. and govern org-wide personas through enterprise custom agents.|
+
+These are driven from the enterprise's `.github-private` repository, so their rules are version-controlled and reviewed like any other change.
+
+The first step is hosting. The marketplace you created is defined by the `marketplace.json` file already committed to your repo, listing which plugins exist and where to find them. Because a [plugin marketplace][plugins-marketplace] can live in a repository, another Git host, or a shared file path, you make it organization-wide simply by pushing that repo so any developer can add it with `copilot plugin marketplace add OWNER/REPO`.
+
+Hosting the marketplace makes the plugin available, but installing it still requires action. [Enterprise-managed plugin standards][enterprise-plugin-standards] close that gap. An enterprise owner adds a `managed-settings.json` file to `copilot/managed-settings.json` in the `.github-private` repository, and Copilot applies it to everyone on the enterprise's plan. Two keys do the work — `enabledPlugins` installs (or blocks) a plugin automatically, keyed as `PLUGIN-NAME@MARKETPLACE-NAME`, while `extraKnownMarketplaces` / `strictKnownMarketplaces` control which marketplaces developers may install from. The [enterprise managed settings reference][enterprise-managed-settings-reference] lists the rest.
+
+[Custom agents have a built-in enterprise configuration option][enterprise-custom-agents] that needs no plugin at all, with the ability to define them in a `.github-private` repo for the entire enterprise. An enterprise owner points the **Custom agents** setting (under the enterprise's **AI controls**) at one organization's [`.github-private` repository][github-private-repo], and every `*.agent.md` profile in that repository's `agents/` directory becomes available to everyone on the enterprise's Copilot plan, Copilot CLI included, without those users needing access to the repository. A ruleset keeps edits in check — enterprise owners commit directly while everyone else proposes changes through pull requests — and [agent names are deduplicated across levels][custom-agents-config], so a personal or repository-level agent of the same name wins.
+
+> [!TIP]
+> In practice you'll use both. Enterprise custom agents are the right tool for a governed set of personas — the Contoso accessibility reviewer, a compliance bot — that should be available org-wide from one reviewed repository. A plugin, set as a default through `enabledPlugins`, is the right tool when you need to ship a whole bundle at once: agents plus skills, hooks, and the AssetTrack schema catalog MCP server. Either way, a new Contoso engineer signs in and the standard setup is simply there — no cloning, no copying, no manual install.
+
+## Summary
+
+You've turned one developer's setup into team-wide capability. In this module, you:
+
+- Built a custom MCP server that exposes AssetTrack's databases as a live, read-only schema catalog.
+- Bundled your custom agents, agent skills, hooks, and MCP server into an installable plugin.
+- Registered your repo as a marketplace and installed the plugin from it, then planned an enterprise rollout using enterprise-managed plugin standards and enterprise custom agents, so the whole setup arrives automatically when a teammate authenticates.
+
+AI infrastructure is only complete when it's available across the entirety of your organization. The key to productivity with Copilot is ensuring the same toolsets — from MCP servers to custom agents to agent skills and more — are always discoverable and callable, no matter which engineer opens a session.
+
+Wrap up the course in [Module 8][next-lesson].
+
+## Resources
+
+- [About GitHub Copilot plugins][copilot-plugins]
+- [Creating a plugin for GitHub Copilot CLI][plugins-creating]
+- [GitHub Copilot CLI plugin reference][plugin-reference]
+- [Finding and installing plugins for GitHub Copilot CLI][plugins-finding-installing]
+- [Creating a plugin marketplace for GitHub Copilot CLI][plugins-marketplace]
+- [About enterprise-managed plugin standards][enterprise-plugin-standards]
+- [Preparing to use custom agents in your enterprise][enterprise-custom-agents]
+- [Custom agents configuration reference][custom-agents-config]
+- [Configuring enterprise-managed settings][configure-enterprise-managed-settings]
+- [Enterprise managed settings reference][enterprise-managed-settings-reference]
+- [Adding MCP servers for GitHub Copilot CLI][add-mcp-servers]
+- [About Model Context Protocol (MCP)][mcp-concept]
+- [Copilot CLI command reference][commands-reference]
+- [Comparing GitHub Copilot CLI customization features][cli-customization-comparison]
+
+---
+
+| [← Previous: Modernizing apps with Copilot CLI][previous-lesson] | [Next: Wrap-up →][next-lesson] |
+|:--|--:|
+
+[previous-lesson]: ../06-modernize-apps/
+[next-lesson]: ../08-wrap-up/
+[m06]: ../06-modernize-apps/
+[m02]: ../02-building-ai-infrastructure/
+[m04]: ../04-lifecycle-hooks/
+[mcp-concept]: https://docs.github.com/copilot/concepts/context/mcp
+[context7-mcp]: https://github.com/upstash/context7
+[mslearn-mcp]: https://github.com/microsoftdocs/mcp
+[copilot-plugins]: https://docs.github.com/copilot/concepts/agents/about-plugins
+[plugins-creating]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/plugins-creating
+[plugin-reference]: https://docs.github.com/copilot/reference/copilot-cli-reference/cli-plugin-reference
+[plugins-finding-installing]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-installing
+[plugins-marketplace]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/plugins-marketplace
+[enterprise-plugin-standards]: https://docs.github.com/copilot/concepts/agents/about-enterprise-plugin-standards
+[configure-enterprise-managed-settings]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings
+[enterprise-managed-settings-reference]: https://docs.github.com/copilot/reference/enterprise-managed-settings-reference
+[add-mcp-servers]: https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
+[commands-reference]: https://docs.github.com/copilot/reference/copilot-cli-reference/cli-command-reference
+[cli-customization-comparison]: https://docs.github.com/copilot/concepts/agents/copilot-cli/comparing-cli-features
+[copilot-plugins-repo]: https://github.com/github/copilot-plugins
+[awesome-copilot]: https://github.com/github/awesome-copilot
+[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents
+[github-private-repo]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/create-github-private-repo
+[custom-agents-config]: https://docs.github.com/copilot/reference/custom-agents-configuration
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/08-wrap-up.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/08-wrap-up.md
new file mode 100644
index 00000000..afb611a5
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/08-wrap-up.md
@@ -0,0 +1,86 @@
+---
+title: '08 · Wrap-up & Next Steps'
+description: 'Recap the AI infrastructure you built across the course and where to go next with Copilot CLI.'
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Module 8 — Wrap-up and next steps
+
+| [← Previous: Managing Copilot's infrastructure][previous-lesson] | |
+|:--|--:|
+
+You've worked Copilot CLI through a brownfield, multi-stack codebase, codified your team's conventions, built reusable skills and custom agents, wired up lifecycle hooks, registered LSP and MCP servers, modernized a stack, and packaged the whole setup as a plugin. This module is a quick recap and a pointer to where to go next.
+
+## What you will learn
+
+- A consolidated view of the AI infrastructure you built across the course.
+- How the patterns generalize beyond AssetTrack.
+- Where to go next with Copilot CLI.
+
+## Recap
+
+Talking points:
+
+- **Working with Copilot CLI** ([Module 1][m01]) — the harness, agent loop, tool surface, permission model.
+- **Building AI infrastructure foundation** ([Module 2][m02]) — exploring a brownfield project, filling doc gaps, generating `copilot-instructions.md` with `/init`, scoped `.instructions` files, the `Accessibility Expert` custom agent, and the imported `make-repo-contribution` skill.
+- **Enhancing the test suite with remote and delegation** ([Module 3][m03]) — Playwright tests locally, `/remote` against a hosted environment, `/delegate` to the Copilot cloud agent, reviewing the resulting PR.
+- **Shaping Copilot CLI's lifecycle with hooks** ([Module 4][m04]) — wiring lifecycle hooks so tests, lint, and build feedback flow back to the agent automatically.
+- **Adding a new feature** ([Module 5][m05]) — `/research` for the library choice, `/plan` + rubber-duck, QA + accessibility custom agents, `/fleet` for parallel execution.
+- **Modernizing apps with Copilot CLI** ([Module 6][m06]) — `/lsp` across stacks, MCP servers as documentation surfaces, `/research` for a citation-backed plan, per-stack migrator agents driving the upgrade.
+- **Managing Copilot's infrastructure** ([Module 7][m07]) — enterprise custom agents, plugins for distribution, and a custom MCP server exposing the inventory database safely.
+
+## Generalizing beyond AssetTrack
+
+Talking points:
+
+- The same pattern (instructions → custom agents → skills → hooks → LSP / MCP → plugins → research + plan + fleet) applies to any brownfield repo. Walk the learner through how to bootstrap each piece on their own codebase.
+- Common variations:
+ - A team conventions `copilot-instructions.md` shared across many repos.
+ - User-level skills for tasks that recur outside any one project (e.g., "rewrite this commit message in our team's house style").
+ - Custom agents for compliance-sensitive areas (security review, license audit).
+ - Custom MCP servers for the resources your team queries most: databases, internal APIs, feature flags.
+- Knowing when **not** to use any of this — for genuinely one-off, exploratory work, a plain CLI session is still the right tool.
+
+## Suggested next steps
+
+Talking points:
+
+- Apply the instructions / custom agents / skills / hooks / MCP / plugin pattern to a real repo at work. Start with `copilot-instructions.md`.
+- Share one custom agent or skill with your team. See how the conversation changes when everyone has the same one available.
+- Explore the [MCP registry][mcp-registry] for a server that fits your team's external tools — or write a small one targeting an internal resource.
+- Subscribe to the [GitHub Changelog][changelog] — Copilot CLI features evolve fast.
+
+## Further reading
+
+- [Copilot CLI documentation][copilot-cli-docs]
+- [GitHub Copilot best practices][copilot-best-practices]
+- [Model Context Protocol introduction][mcp-intro]
+- [Legacy app: `github-samples/contoso-inventory`][contoso-inventory]
+
+## Resources
+
+- [Copilot CLI documentation][copilot-cli-docs]
+- [GitHub Copilot Changelog][changelog]
+- [MCP registry][mcp-registry]
+
+---
+
+| [← Previous: Managing Copilot's infrastructure][previous-lesson] | |
+|:--|--:|
+
+[previous-lesson]: ../07-manage-infrastructure/
+[m01]: ../01-working-with-copilot-cli/
+[m02]: ../02-building-ai-infrastructure/
+[m03]: ../03-test-suite-remote-delegation/
+[m04]: ../04-lifecycle-hooks/
+[m05]: ../05-add-feature-barcode/
+[m06]: ../06-modernize-apps/
+[m07]: ../07-manage-infrastructure/
+[copilot-cli-docs]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli
+[copilot-best-practices]: https://docs.github.com/copilot/get-started/best-practices
+[mcp-intro]: https://modelcontextprotocol.io/introduction
+[mcp-registry]: https://github.com/mcp
+[contoso-inventory]: https://github.com/github-samples/contoso-inventory
+[changelog]: https://github.blog/changelog/label/copilot/
diff --git a/website/src/content/docs/learning-hub/advanced-copilot-cli/index.md b/website/src/content/docs/learning-hub/advanced-copilot-cli/index.md
new file mode 100644
index 00000000..11963f9d
--- /dev/null
+++ b/website/src/content/docs/learning-hub/advanced-copilot-cli/index.md
@@ -0,0 +1,90 @@
+---
+title: "Advanced GitHub Copilot CLI"
+description: "A source-faithful mirror of the companion Advanced GitHub Copilot CLI course."
+authors:
+ - GitHub Copilot Learning Hub Team
+lastUpdated: 2026-08-26
+---
+
+# Advanced GitHub Copilot CLI
+
+> **✨ Take GitHub Copilot CLI beyond the basics and use it for real-world brownfield work.**
+
+A hands-on course for experienced developers who are ready to use GitHub Copilot CLI as their primary agent surface — building reusable AI infrastructure (custom instructions, custom agents, agent skills, lifecycle hooks, LSP, and MCP integrations) on top of an existing multi-stack legacy codebase.
+
+> ⚠️ **Note**: Because GitHub Copilot, and generative AI at large, is probabilistic rather than deterministic, the exact code, files changed, and outputs may vary between runs. You may notice slight differences between what's described here and what you see in your terminal. This is expected.
+
+## Who this course is for
+
+You're already comfortable with Copilot in an IDE and with the basics of Copilot CLI (running `copilot`, having a chat, accepting an edit). You want to:
+
+- Use Copilot CLI as your primary agent surface, not as a fallback when you're away from your editor.
+- Codify your team's conventions so Copilot follows them automatically.
+- Build reusable skills and custom agents instead of re-prompting from scratch every session.
+- Extend Copilot CLI with LSP, MCP, and lifecycle hooks, and distribute it all as a plugin your team installs in one shot.
+
+This course assumes you are familiar with Copilot CLI, GitHub flow (issues and pull requests), and using VS Code or a similar IDE, and that you have written software in one or more languages.
+
+> ✅ **You don't need every language.** The scenario app uses Python, Java, TypeScript, and C#, but familiarity with all of them is **not** required. One of the core tasks is asking Copilot about the project and how it works.
+
+## The scenario
+
+You've inherited **AssetTrack** at **Contoso Industries** — an internal asset-tracking application built across **Java**, **Astro/TypeScript with React islands**, **.NET**, and **FastAPI**. It's a brownfield app like many others: incomplete documentation, a long bug list, and the usual rough edges that come from years of accumulated tech decisions and tech debt.
+
+You'll work the legacy app from [`github-samples/contoso-inventory`](https://github.com/github-samples/contoso-inventory) throughout the course, using Copilot CLI to understand it, extend it, and modernize it.
+
+## 🎯 What You'll Learn
+
+Across the seven core modules of this course (plus a prerequisites module and a wrap-up) you will:
+
+- Understand what an AI agent is and how the Copilot CLI harness works under the hood, including how to control models, permissions, and modes — then use Copilot CLI to explore the repo and fill the obvious documentation gaps.
+- Build the AI infrastructure for a brownfield repo: generate `copilot-instructions.md` with `/init`, add path-scoped `.instructions` files, author a custom agent for accessibility, and import the `make-repo-contribution` skill so every Copilot contribution flows through issues and PRs.
+- Validate accessibility upgrades with Playwright tests, drive a session against a hosted environment with `/remote`, and offload bounded test work to the Copilot cloud agent with `/delegate`.
+- Wire lifecycle **hooks** so tests, lint, and build feedback flow back to the agent automatically.
+- Plan and execute a new feature (barcode support) with `/research`, `/plan`, rubber-duck critique, QA + accessibility custom agents, and `/fleet` parallel subagents.
+- Give Copilot better signal with LSP servers across stacks, a documentation MCP server, and `/research` — then drive modernization with per-stack migrator agents.
+- Scale your AI infrastructure: package it as a plugin, build a custom MCP server exposing AssetTrack's database safely, and reason about enterprise-tier custom agents.
+
+## 📚 Course Structure
+
+Each module builds on the ones before it, but every module's exercises include a starting-state note so you can drop in if you need to.
+
+| Module | Title | What You'll Do |
+| :----: | ----- | -------------- |
+| 00 | [Prerequisites and environment setup](./00-prerequisites/) | Get your Codespaces-based environment ready |
+| 01 | [Working with Copilot CLI](./01-working-with-copilot-cli/) | Learn the agent model, harness, models, and permissions |
+| 02 | [Building an AI infrastructure foundation](./02-building-ai-infrastructure/) | Custom instructions, a custom agent, and contribution standards |
+| 03 | [Enhancing the test suite with remote and delegation](./03-test-suite-remote-delegation/) | Playwright tests, `/remote`, and `/delegate` |
+| 04 | [Shaping Copilot CLI's lifecycle with hooks](./04-lifecycle-hooks/) | Deterministic lifecycle hooks feeding the agent |
+| 05 | [Adding a new feature: barcode support](./05-add-feature-barcode/) | `/research`, `/plan`, rubber-duck, `/fleet`, and a QA agent |
+| 06 | [Modernizing apps with Copilot CLI](./06-modernize-apps/) | LSP and MCP signal plus per-stack migrator agents |
+| 07 | [Managing Copilot's infrastructure](./07-manage-infrastructure/) | Custom MCP server, plugin packaging, and enterprise distribution |
+| 08 | [Wrap-up and next steps](./08-wrap-up/) | Recap and where to go next |
+
+Head to [Module 0: Prerequisites and environment setup](./00-prerequisites/) to get started.
+
+## 🌿 Jumping into a module: catch-up branches
+
+Each module assumes the cumulative output of every earlier module. If you skip ahead, check out the matching `start-of-module-N` catch-up branch on your AssetTrack repository before you start. Each branch holds the state a learner has after finishing every module before `N`, so `start-of-module-03` gives you everything the first two modules produce.
+
+Create your AssetTrack repository from the [`github-samples/contoso-inventory`](https://github.com/github-samples/contoso-inventory) template with **Include all branches** selected so the catch-up branches come along, then check out the branch for the module you're starting (Module 3 shown):
+
+```bash
+git checkout start-of-module-03
+```
+
+Module 1 starts from the pristine fork, so it has no catch-up branch, and there is no branch after Module 7 because that module's work targets your fork only.
+
+## 📋 GitHub Copilot CLI Command Reference
+
+The **[GitHub Copilot CLI command reference](https://docs.github.com/copilot/reference/copilot-cli-reference/cli-command-reference)** helps you find commands and keyboard shortcuts to use Copilot CLI effectively.
+
+## 🙋 Getting Help
+
+- 🐛 **Found a bug?** [Open an issue](https://github.com/github-samples/advanced-copilot-cli/issues)
+- 🤝 **Want to contribute?** See [`CONTRIBUTING.md`](https://github.com/github-samples/advanced-copilot-cli/blob/main/CONTRIBUTING.md)
+- 📚 **Official Docs:** [GitHub Copilot CLI documentation](https://docs.github.com/copilot/concepts/agents/about-copilot-cli)
+
+## License
+
+This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](https://github.com/github-samples/advanced-copilot-cli/blob/main/LICENSE) file for the full terms.
diff --git a/website/src/content/docs/learning-hub/index.md b/website/src/content/docs/learning-hub/index.md
index 17dac9de..736a5125 100644
--- a/website/src/content/docs/learning-hub/index.md
+++ b/website/src/content/docs/learning-hub/index.md
@@ -14,7 +14,7 @@ New to GitHub Copilot? Start here to understand the tools available to you.
**Canvases**: Learn [Working with Canvas Extensions](working-with-canvas-extensions/) to create and evolve interactive canvases with `/create-canvas`.
-**Terminal**: Looking for a guided path into GitHub Copilot from the terminal? Explore the [Copilot CLI for Beginners](cli-for-beginners/) with a text-based experience or the [YouTube video series](https://www.youtube.com/watch?v=BDxRhhs36ns&list=PL0lo9MOBetEHvO-spzKBAITkkTqv4RvNl).
+**Terminal**: Looking for a guided path into GitHub Copilot from the terminal? Explore the [Copilot CLI for Beginners](cli-for-beginners/) with a text-based experience or the [YouTube video series](https://www.youtube.com/watch?v=BDxRhhs36ns&list=PL0lo9MOBetEHvO-spzKBAITkkTqv4RvNl). Ready to go further? The [Advanced GitHub Copilot CLI](advanced-copilot-cli/) course takes you into real-world brownfield work — building reusable AI infrastructure, hooks, LSP and MCP integrations, and plugins on top of a legacy multi-stack app.
**Workshop**: Prefer to learn by building? Work through [Hands-on with GitHub Copilot's agents](copilot-workshops/) — a hands-on workshop with four harnesses (VS Code, Copilot CLI, Copilot app, and cloud agent) built around a shared Tailspin Toys backlog.