From 75f71226c84f7fda165f5daef82985dee8004968 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 15:43:43 +0000
Subject: [PATCH 1/4] Initial plan
From 3ac2e856ef8d67a84eaa48e2cc529e0f8e0ea8e4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 16:03:41 +0000
Subject: [PATCH 2/4] feat: create architecture-guardian agentic workflow
Adds .github/workflows/architecture-guardian.md and the compiled
.lock.yml to enforce code structure discipline on every PR and push
to main.
The workflow:
- Detects files exceeding 500/1000 lines (WARNING/BLOCKER)
- Detects functions exceeding 80 lines (WARNING)
- Detects high public export count >10 (INFO)
- Detects circular imports/dependency cycles (BLOCKER)
- Posts structured PR review grouped by severity
- Fails PR check on BLOCKER violations
- Thresholds configurable via .architecture.yml
Closes #25378"
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7af87394-21f4-4209-ba3d-375346b57b4a
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../workflows/architecture-guardian.lock.yml | 1268 +++++++++++++++++
.github/workflows/architecture-guardian.md | 393 +++++
2 files changed, 1661 insertions(+)
create mode 100644 .github/workflows/architecture-guardian.lock.yml
create mode 100644 .github/workflows/architecture-guardian.md
diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml
new file mode 100644
index 0000000000..f46cbcfe7e
--- /dev/null
+++ b/.github/workflows/architecture-guardian.lock.yml
@@ -0,0 +1,1268 @@
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6b77dd0a19000d8d6f30e1a5af237d801e62f04da762b7ccffd87c4175511d65","strict":true,"agent_id":"copilot"}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"}]}
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# 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/
+#
+# Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main
+#
+# Secrets used:
+# - GH_AW_GITHUB_MCP_SERVER_TOKEN
+# - GH_AW_GITHUB_TOKEN
+# - GITHUB_TOKEN
+#
+# Custom actions used:
+# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+
+name: "Architecture Guardian"
+"on":
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ push:
+ branches:
+ - main
+ 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 }}-${{ github.event.pull_request.number || github.ref || github.run_id }}"
+ cancel-in-progress: true
+
+run-name: "Architecture Guardian"
+
+jobs:
+ activation:
+ needs: pre_activation
+ if: >
+ needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)
+ runs-on: ubuntu-slim
+ permissions:
+ actions: read
+ contents: read
+ outputs:
+ body: ${{ steps.sanitized.outputs.body }}
+ comment_id: ""
+ comment_repo: ""
+ lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ text: ${{ steps.sanitized.outputs.text }}
+ title: ${{ steps.sanitized.outputs.title }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }}
+ - 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 || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.21"
+ GH_AW_INFO_AGENT_VERSION: "1.0.21"
+ GH_AW_INFO_WORKFLOW_NAME: "Architecture Guardian"
+ 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.25.16"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_COMPILED_STRICT: "true"
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .github
+ .agents
+ actions/setup
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ - name: Check workflow lock file
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "architecture-guardian.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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Compute current body text
+ id: sanitized
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/compute_text.cjs');
+ await main();
+ - 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_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ 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_65cb4b79140ac0f2_EOF'
+
+ GH_AW_PROMPT_65cb4b79140ac0f2_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/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_65cb4b79140ac0f2_EOF'
+
+ Tools: add_comment, submit_pull_request_review, missing_tool, missing_data, noop
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_65cb4b79140ac0f2_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ cat << 'GH_AW_PROMPT_65cb4b79140ac0f2_EOF'
+
+ {{#runtime-import .github/workflows/architecture-guardian.md}}
+ GH_AW_PROMPT_65cb4b79140ac0f2_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ 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/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ 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_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_NAME: process.env.GH_AW_GITHUB_EVENT_NAME,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED
+ }
+ });
+ - 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: activation
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/github_rate_limits.jsonl
+ if-no-files-found: ignore
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ copilot-requests: write
+ pull-requests: read
+ 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_WORKFLOW_ID_SANITIZED: architectureguardian
+ outputs:
+ 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 }}
+ inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
+ model: ${{ needs.activation.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ - name: Set runtime paths
+ id: set-runtime-paths
+ run: |
+ echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT"
+ echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT"
+ echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT"
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ 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: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ github.event.pull_request || github.event.issue.pull_request
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh 1.0.21
+ env:
+ GH_HOST: github.com
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.16
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.16 ghcr.io/github/gh-aw-firewall/squid:0.25.16 ghcr.io/github/gh-aw-mcpg:v0.2.16 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_dc06f30be9a0fc2c_EOF'
+ {"add_comment":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_dc06f30be9a0fc2c_EOF
+ - name: Write Safe Outputs Tools
+ env:
+ GH_AW_TOOLS_META_JSON: |
+ {
+ "description_suffixes": {
+ "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.",
+ "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted."
+ },
+ "repo_params": {},
+ "dynamic_tools": []
+ }
+ GH_AW_VALIDATION_JSON: |
+ {
+ "add_comment": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "item_number": {
+ "issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ }
+ }
+ },
+ "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
+ }
+ }
+ },
+ "submit_pull_request_review": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "event": {
+ "type": "string",
+ "enum": [
+ "APPROVE",
+ "REQUEST_CHANGES",
+ "COMMENT"
+ ]
+ }
+ }
+ }
+ }
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/generate_safe_outputs_tools.cjs');
+ await main();
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ 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 }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ 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"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.16'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_1fa16f742618a0cf_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.32.0",
+ "env": {
+ "GITHUB_HOST": "\${GITHUB_SERVER_URL}",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "repos,pull_requests"
+ },
+ "guard-policies": {
+ "allow-only": {
+ "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
+ "repos": "$GITHUB_MCP_GUARD_REPOS"
+ }
+ }
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ },
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ]
+ }
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ GH_AW_MCP_CONFIG_1fa16f742618a0cf_EOF
+ - name: Download activation artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
+ - name: Clean git credentials
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool shell(awk:*)
+ # --allow-tool shell(cat)
+ # --allow-tool shell(cat:*)
+ # --allow-tool shell(date)
+ # --allow-tool shell(echo)
+ # --allow-tool shell(find:*)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(grep:*)
+ # --allow-tool shell(head)
+ # --allow-tool shell(head:*)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(node:*)
+ # --allow-tool shell(pwd)
+ # --allow-tool shell(python3:*)
+ # --allow-tool shell(sed:*)
+ # --allow-tool shell(sort)
+ # --allow-tool shell(sort:*)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(uniq)
+ # --allow-tool shell(wc)
+ # --allow-tool shell(wc:*)
+ # --allow-tool shell(yq)
+ # --allow-tool web_fetch
+ # --allow-tool write
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains 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 --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.16 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(node:*)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3:*)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool web_fetch --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ github.token }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_PHASE: agent
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_VERSION: dev
+ 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]
+ S2STOKENS: true
+ XDG_CONFIG_HOME: /home/runner
+ - name: Detect inference access error
+ id: detect-inference-error
+ if: always()
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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);
+ 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ 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/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ id: parse-mcp-gateway
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/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: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
+ - name: Parse token usage for step summary
+ if: always()
+ continue-on-error: true
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/parse_token_usage.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: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ 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/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
+ if-no-files-found: ignore
+ - name: Upload firewall audit logs
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: firewall-audit-logs
+ path: |
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/sandbox/firewall/audit/
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ if: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ concurrency:
+ group: "gh-aw-conclusion-architecture-guardian"
+ cancel-in-progress: false
+ 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: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ - 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: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ 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"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+ - name: Record missing tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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: "Architecture Guardian"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Record incomplete
+ id: report_incomplete
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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: "Architecture Guardian"
+ 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);
+ 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ 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: "architecture-guardian"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
+ GH_AW_GROUP_REPORTS: "false"
+ GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
+ GH_AW_TIMEOUT_MINUTES: "20"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+
+ detection:
+ needs:
+ - activation
+ - agent
+ if: >
+ always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true')
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ copilot-requests: write
+ outputs:
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ - 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ # --- Threat Detection ---
+ - name: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.16 ghcr.io/github/gh-aw-firewall/squid:0.25.16
+ - 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 configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.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
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ 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
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ done
+ for f in /tmp/gh-aw/aw-*.bundle; do
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Architecture Guardian"
+ WORKFLOW_DESCRIPTION: "Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ 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/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: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh 1.0.21
+ env:
+ GH_HOST: github.com
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.16
+ - name: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.16 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ github.token }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PHASE: detection
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_VERSION: dev
+ 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]
+ S2STOKENS: true
+ XDG_CONFIG_HOME: /home/runner
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ 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()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ 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/parse_threat_detection_results.cjs');
+ await main();
+
+ pre_activation:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
+ matched_command: ''
+ setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ - name: Check team membership for workflow
+ id: check_membership
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_REQUIRED_ROLES: "admin,maintainer,write"
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ 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/check_membership.cjs');
+ await main();
+
+ safe_outputs:
+ needs:
+ - activation
+ - agent
+ - detection
+ if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ timeout-minutes: 15
+ env:
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/architecture-guardian"
+ GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
+ GH_AW_WORKFLOW_ID: "architecture-guardian"
+ GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ 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 }}
+ comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }}
+ comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ 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: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: github/gh-aw
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ id: setup
+ uses: ./actions/setup
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ - 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: Configure GH_HOST for enterprise compatibility
+ id: ghes-host-config
+ shell: bash
+ run: |
+ # 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ 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: "{\"add_comment\":{\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"max\":1}}"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Upload Safe Output Items
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: safe-output-items
+ path: /tmp/gh-aw/safe-output-items.jsonl
+ if-no-files-found: ignore
+
diff --git a/.github/workflows/architecture-guardian.md b/.github/workflows/architecture-guardian.md
new file mode 100644
index 0000000000..c3520faff3
--- /dev/null
+++ b/.github/workflows/architecture-guardian.md
@@ -0,0 +1,393 @@
+---
+name: Architecture Guardian
+description: Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+permissions:
+ contents: read
+ pull-requests: read
+engine: copilot
+tools:
+ github:
+ toolsets: [repos, pull_requests]
+ bash:
+ - "find:*"
+ - "wc:*"
+ - "grep:*"
+ - "cat:*"
+ - "head:*"
+ - "awk:*"
+ - "sed:*"
+ - "sort:*"
+ - "python3:*"
+ - "node:*"
+ edit:
+ web-fetch:
+safe-outputs:
+ submit-pull-request-review:
+ max: 1
+ add-comment:
+ max: 1
+ noop:
+ messages:
+ footer: "> 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}"
+ run-started: "🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}..."
+ run-success: "✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋"
+ run-failure: "🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown..."
+timeout-minutes: 20
+features:
+ copilot-requests: true
+---
+
+# Architecture Guardian
+
+You are the Architecture Guardian, a code quality agent that enforces structural discipline in the codebase. Your mission is to prevent "spaghetti code" by detecting structural violations before they accumulate.
+
+## Current Context
+
+- **Repository**: ${{ github.repository }}
+- **Event**: ${{ github.event_name }}
+- **Run ID**: ${{ github.run_id }}
+- **PR Number**: ${{ github.event.pull_request.number }}
+
+## Step 1: Load Configuration
+
+Read the `.architecture.yml` configuration file if it exists. This file contains configurable thresholds for the analysis.
+
+```bash
+cat .architecture.yml 2>/dev/null || echo "No .architecture.yml found, using defaults"
+```
+
+**Default thresholds** (used when `.architecture.yml` is absent or a value is missing):
+
+| Threshold | Default | Config Key |
+|-----------|---------|------------|
+| File size BLOCKER | 1000 lines | `thresholds.file_lines_blocker` |
+| File size WARNING | 500 lines | `thresholds.file_lines_warning` |
+| Function size | 80 lines | `thresholds.function_lines` |
+| Max public exports | 10 | `thresholds.max_exports` |
+
+Parse the YAML values if the file exists. Fall back to defaults for any missing key.
+
+## Step 2: Identify Files to Analyze
+
+Determine which files to scan based on the event type:
+
+### For Pull Requests
+
+Fetch the list of changed files in this PR using the GitHub tools (`pull_request_read` with method `get_files`). Focus on source files in the PR diff.
+
+Alternatively, use git:
+```bash
+git fetch origin ${{ github.event.pull_request.base.sha }} 2>/dev/null || true
+git diff --name-only ${{ github.event.pull_request.base.sha }}...HEAD 2>/dev/null || git diff --name-only HEAD~1 2>/dev/null || true
+```
+
+### For Push to Main
+
+Scan all source files in the repository:
+```bash
+git diff --name-only HEAD~1 2>/dev/null || find . -type f \( -name "*.py" -o -name "*.rs" \) ! -path "./.git/*" ! -path "./node_modules/*" ! -path "./target/*" | head -200
+```
+
+Filter to supported languages:
+- **Python**: `*.py` files
+- **Rust**: `*.rs` files
+
+## Step 3: Run Structural Analysis
+
+For each relevant source file, perform the following checks. Collect all violations in a structured list.
+
+### Check 1: File Size
+
+Count lines in each file:
+
+```bash
+wc -l 2>/dev/null
+```
+
+Classify:
+- Lines > `thresholds.file_lines_blocker` (default 1000) → **BLOCKER**
+- Lines > `thresholds.file_lines_warning` (default 500) → **WARNING**
+
+### Check 2: Function/Method Size (Python)
+
+Use a Python script to parse function sizes:
+
+```bash
+python3 - <<'PYEOF'
+import ast, sys, os
+
+def analyze_functions(filepath):
+ try:
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
+ source = f.read()
+ tree = ast.parse(source, filename=filepath)
+ except SyntaxError as e:
+ print(f"PARSE_ERROR:{filepath}:{e}", flush=True)
+ return
+
+ for node in ast.walk(tree):
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ start = node.lineno
+ end = node.end_lineno if hasattr(node, 'end_lineno') else node.lineno
+ length = end - start + 1
+ print(f"FUNC:{filepath}:{node.name}:{start}:{length}", flush=True)
+
+for path in sys.argv[1:]:
+ analyze_functions(path)
+PYEOF
+```
+
+Alternatively, approximate with grep:
+
+```bash
+# Approximate Python function line counts
+grep -n "^def \|^ def \|^async def \|^ async def "
+```
+
+Functions exceeding `thresholds.function_lines` (default 80) → **WARNING**
+
+### Check 3: Function/Method Size (Rust)
+
+Approximate Rust function sizes using line counting between `fn` keywords:
+
+```bash
+grep -n "^\s*pub fn \|^\s*fn \|^\s*pub async fn \|^\s*async fn "
+```
+
+Count lines between consecutive function definitions to estimate function length. Functions exceeding `thresholds.function_lines` (default 80) → **WARNING**
+
+### Check 4: High Public Export Count (Python)
+
+Count public exports (non-underscore names) in Python files:
+
+```bash
+python3 - <<'PYEOF'
+import ast, sys
+
+def count_exports(filepath):
+ try:
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
+ source = f.read()
+ tree = ast.parse(source, filename=filepath)
+ except SyntaxError:
+ return
+
+ exports = []
+ for node in ast.walk(tree):
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
+ if not node.name.startswith('_'):
+ exports.append(node.name)
+
+ count = len(exports)
+ if count > 0:
+ print(f"EXPORTS:{filepath}:{count}:{','.join(exports[:20])}", flush=True)
+
+for path in sys.argv[1:]:
+ count_exports(path)
+PYEOF
+```
+
+Files with more than `thresholds.max_exports` (default 10) public names → **INFO**
+
+### Check 5: Circular Imports / Dependency Cycles (Python)
+
+Detect circular imports using a simple reachability analysis:
+
+```bash
+python3 - <<'PYEOF'
+import ast, sys, os
+from collections import defaultdict, deque
+
+def find_imports(filepath, root):
+ try:
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
+ source = f.read()
+ tree = ast.parse(source, filename=filepath)
+ except Exception:
+ return []
+
+ imports = []
+ pkg = os.path.dirname(os.path.relpath(filepath, root))
+ for node in ast.walk(tree):
+ if isinstance(node, ast.ImportFrom) and node.module:
+ parts = node.module.split('.')
+ candidate = os.path.join(root, *parts) + '.py'
+ if os.path.exists(candidate):
+ imports.append(os.path.relpath(candidate, root))
+ elif isinstance(node, ast.Import):
+ for alias in node.names:
+ parts = alias.name.split('.')
+ candidate = os.path.join(root, *parts) + '.py'
+ if os.path.exists(candidate):
+ imports.append(os.path.relpath(candidate, root))
+ return imports
+
+root = sys.argv[1] if len(sys.argv) > 1 else '.'
+files = [os.path.relpath(p, root) for p in sys.argv[2:]]
+
+graph = defaultdict(list)
+for f in files:
+ abs_path = os.path.join(root, f)
+ for dep in find_imports(abs_path, root):
+ graph[f].append(dep)
+
+def find_cycle(start, graph):
+ visited = set()
+ path = []
+ def dfs(node):
+ if node in path:
+ idx = path.index(node)
+ return path[idx:]
+ if node in visited:
+ return None
+ visited.add(node)
+ path.append(node)
+ for nbr in graph.get(node, []):
+ result = dfs(nbr)
+ if result:
+ return result
+ path.pop()
+ return None
+ return dfs(start)
+
+seen_cycles = set()
+for f in files:
+ cycle = find_cycle(f, graph)
+ if cycle:
+ key = frozenset(cycle)
+ if key not in seen_cycles:
+ seen_cycles.add(key)
+ print(f"CYCLE:{' -> '.join(cycle + [cycle[0]])}", flush=True)
+
+PYEOF
+```
+
+Circular dependency cycles → **BLOCKER**
+
+## Step 4: Classify Violations by Severity
+
+Group all findings into three severity tiers:
+
+### BLOCKER (fails the PR check)
+- Circular import / dependency cycles between modules
+- Files exceeding 1000 lines (configurable)
+
+### WARNING (allows merge but posts warning)
+- Files exceeding 500 lines (configurable)
+- Functions/methods exceeding 80 lines (configurable)
+
+### INFO (informational only)
+- Files with more than 10 public exports (configurable)
+
+## Step 5: Generate AI Refactoring Suggestions
+
+For each **BLOCKER** and **WARNING** violation, generate a concise refactoring suggestion that explains:
+
+1. **What the violation is** — e.g., "`src/utils.py` has 1,247 lines"
+2. **Why it's a problem** — e.g., "Large files are harder to navigate, review, and maintain"
+3. **A concrete plan to fix it** — e.g., "Extract the `DataProcessor` class into `src/data_processor.py` and move the `FileUtils` helpers into `src/file_utils.py`"
+
+Use your knowledge of software architecture best practices. Be specific and actionable.
+
+For **INFO** violations, provide a brief note about the high export count and suggest whether the module might benefit from splitting.
+
+## Step 6: Post Report
+
+### If NO violations are found
+
+Call the `noop` safe-output tool:
+
+```json
+{"noop": {"message": "No architecture violations found. All files are within configured thresholds."}}
+```
+
+### If violations are found on a Pull Request
+
+Submit a pull request review with a structured comment. Use `submit-pull-request-review` with event type:
+- `REQUEST_CHANGES` if there are any **BLOCKER** violations
+- `COMMENT` if there are only **WARNING** or **INFO** violations
+
+**Review body format**:
+
+```markdown
+## 🏛️ Architecture Guardian Report
+
+> Automated architecture scan for this pull request.
+
+### Summary
+
+| Severity | Count |
+|----------|-------|
+| 🚨 BLOCKER | N |
+| ⚠️ WARNING | N |
+| ℹ️ INFO | N |
+
+---
+
+### 🚨 BLOCKER Violations
+
+> These violations **must be resolved** before merging.
+
+#### [Violation Title]
+
+**File**: `path/to/file.py`
+**Issue**: [Description of the problem]
+**Why it matters**: [Explanation]
+**Suggested fix**: [Concrete refactoring plan]
+
+---
+
+### ⚠️ WARNING Violations
+
+> These violations are strongly recommended to fix, but will not block the merge.
+
+#### [Violation Title]
+
+**File**: `path/to/file.py` | **Function**: `function_name` | **Lines**: N
+**Issue**: [Description]
+**Suggested fix**: [Concrete refactoring plan]
+
+---
+
+### ℹ️ INFO Violations
+
+> Informational findings. Consider addressing in future refactoring.
+
+- `path/to/file.py`: N public exports — consider splitting into focused modules
+
+---
+
+### Configuration
+
+Thresholds from `.architecture.yml` (or defaults):
+- File size BLOCKER: N lines
+- File size WARNING: N lines
+- Function size: N lines
+- Max public exports: N
+
+> 🏛️ *To configure thresholds, add a `.architecture.yml` file to the repository root.*
+```
+
+### If violations are found on a Push to Main
+
+Post a comment to the last commit or create a summary using `add-comment`.
+
+## Step 7: Fail if BLOCKER Violations Exist
+
+After posting the report:
+
+- If there are **BLOCKER** violations AND this is a pull request: submit the review with `REQUEST_CHANGES` event — this marks the PR check as requiring changes.
+- If there are **no** BLOCKER violations: submit as `COMMENT` or call `noop`.
+
+**Important**: If no action is needed after completing your analysis, you **MUST** call the `noop` safe-output tool with a brief explanation. Failing to call any safe-output tool is the most common cause of safe-output workflow failures.
+
+```json
+{"noop": {"message": "No action needed: [brief explanation of what was analyzed and why]"}}
+```
From 875afbde57719107e535bbbd76303ce8d42f7f2b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 16:15:01 +0000
Subject: [PATCH 3/4] feat: change architecture-guardian to daily weekday
schedule with 24h lookback
- Trigger changed from pull_request+push to daily schedule on weekdays (~2 PM UTC)
- Workflow now analyzes commits from the last 24 hours instead of PR diffs
- safe-outputs changed from submit-pull-request-review to create-issue
- Added skip-if-match to avoid creating duplicate issues
- Added tracker-id for workflow identity tracking
- Updated prompt: Step 2 now uses git log --since="24 hours ago"
- Updated prompt: Step 6 now creates a GitHub issue with action checklist
- Removed PR-specific language (REQUEST_CHANGES review, base SHA diff)
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ea35747c-7522-4c93-84e4-d44eadc4bb90
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../workflows/architecture-guardian.lock.yml | 180 +++++++++---------
.github/workflows/architecture-guardian.md | 108 +++++------
2 files changed, 145 insertions(+), 143 deletions(-)
diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml
index f46cbcfe7e..6ef5868616 100644
--- a/.github/workflows/architecture-guardian.lock.yml
+++ b/.github/workflows/architecture-guardian.lock.yml
@@ -1,5 +1,5 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6b77dd0a19000d8d6f30e1a5af237d801e62f04da762b7ccffd87c4175511d65","strict":true,"agent_id":"copilot"}
-# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"}]}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"463cff10841281bd7b33edb216c1b58977401c882fb000751d839e09733d2330","strict":true,"agent_id":"copilot"}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"}]}
# ___ _ _
# / _ \ | | (_)
# | |_| | __ _ ___ _ __ | |_ _ ___
@@ -22,9 +22,10 @@
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
-# Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main
+# Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies
#
# Secrets used:
+# - GH_AW_AGENT_TOKEN
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -37,14 +38,10 @@
name: "Architecture Guardian"
"on":
- pull_request:
- types:
- - opened
- - synchronize
- - reopened
- push:
- branches:
- - main
+ schedule:
+ - cron: "5 14 * * 1-5"
+ # Friendly format: daily around 14:00 on weekdays (scattered)
+ # skip-if-match: is:issue is:open in:title "[architecture-guardian]" # Skip-if-match processed as search check in pre-activation job
workflow_dispatch:
inputs:
aw_context:
@@ -56,29 +53,24 @@ name: "Architecture Guardian"
permissions: {}
concurrency:
- group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}"
- cancel-in-progress: true
+ group: "gh-aw-${{ github.workflow }}"
run-name: "Architecture Guardian"
jobs:
activation:
needs: pre_activation
- if: >
- needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)
+ if: needs.pre_activation.outputs.activated == 'true'
runs-on: ubuntu-slim
permissions:
actions: read
contents: read
outputs:
- body: ${{ steps.sanitized.outputs.body }}
comment_id: ""
comment_repo: ""
lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
model: ${{ steps.generate_aw_info.outputs.model }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
- text: ${{ steps.sanitized.outputs.text }}
- title: ${{ steps.sanitized.outputs.title }}
steps:
- name: Checkout actions folder
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -140,15 +132,6 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
await main();
- - name: Compute current body text
- id: sanitized
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- 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/compute_text.cjs');
- await main();
- name: Create prompt with built-in context
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
@@ -157,8 +140,6 @@ jobs:
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
@@ -167,16 +148,16 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_65cb4b79140ac0f2_EOF'
+ cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
- GH_AW_PROMPT_65cb4b79140ac0f2_EOF
+ GH_AW_PROMPT_1efb64fabf1df90f_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/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_65cb4b79140ac0f2_EOF'
+ cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
- Tools: add_comment, submit_pull_request_review, missing_tool, missing_data, noop
+ Tools: create_issue, missing_tool, missing_data, noop
The following GitHub context information is available for this workflow:
@@ -206,20 +187,17 @@ jobs:
{{/if}}
- GH_AW_PROMPT_65cb4b79140ac0f2_EOF
+ GH_AW_PROMPT_1efb64fabf1df90f_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_65cb4b79140ac0f2_EOF'
+ cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
{{#runtime-import .github/workflows/architecture-guardian.md}}
- GH_AW_PROMPT_65cb4b79140ac0f2_EOF
+ GH_AW_PROMPT_1efb64fabf1df90f_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
with:
@@ -236,8 +214,6 @@ jobs:
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_NAME: ${{ github.event_name }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
@@ -258,8 +234,6 @@ jobs:
GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
- GH_AW_GITHUB_EVENT_NAME: process.env.GH_AW_GITHUB_EVENT_NAME,
- GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_BASE_SHA,
GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
@@ -293,9 +267,11 @@ jobs:
needs: activation
runs-on: ubuntu-latest
permissions:
+ actions: read
contents: read
copilot-requests: write
- pull-requests: read
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
@@ -393,23 +369,22 @@ jobs:
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_dc06f30be9a0fc2c_EOF'
- {"add_comment":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_dc06f30be9a0fc2c_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_b87c517c3419b095_EOF'
+ {"create_issue":{"assignees":["copilot"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_b87c517c3419b095_EOF
- name: Write Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
{
"description_suffixes": {
- "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.",
- "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted."
+ "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Assignees [\"copilot\"] will be automatically assigned."
},
"repo_params": {},
"dynamic_tools": []
}
GH_AW_VALIDATION_JSON: |
{
- "add_comment": {
+ "create_issue": {
"defaultMax": 1,
"fields": {
"body": {
@@ -418,12 +393,27 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
- "item_number": {
+ "labels": {
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "parent": {
"issueOrPRNumber": true
},
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "temporary_id": {
+ "type": "string"
+ },
+ "title": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
}
}
},
@@ -499,24 +489,6 @@ jobs:
"maxLength": 1024
}
}
- },
- "submit_pull_request_review": {
- "defaultMax": 1,
- "fields": {
- "body": {
- "type": "string",
- "sanitize": true,
- "maxLength": 65000
- },
- "event": {
- "type": "string",
- "enum": [
- "APPROVE",
- "REQUEST_CHANGES",
- "COMMENT"
- ]
- }
- }
}
}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -594,7 +566,7 @@ jobs:
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.16'
mkdir -p /home/runner/.copilot
- cat << GH_AW_MCP_CONFIG_1fa16f742618a0cf_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_8c3dc814275924bf_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -604,7 +576,7 @@ jobs:
"GITHUB_HOST": "\${GITHUB_SERVER_URL}",
"GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
"GITHUB_READ_ONLY": "1",
- "GITHUB_TOOLSETS": "repos,pull_requests"
+ "GITHUB_TOOLSETS": "repos"
},
"guard-policies": {
"allow-only": {
@@ -635,7 +607,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_1fa16f742618a0cf_EOF
+ GH_AW_MCP_CONFIG_8c3dc814275924bf_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@@ -655,12 +627,14 @@ jobs:
# --allow-tool shell(date)
# --allow-tool shell(echo)
# --allow-tool shell(find:*)
+ # --allow-tool shell(git diff:*)
+ # --allow-tool shell(git log:*)
+ # --allow-tool shell(git show:*)
# --allow-tool shell(grep)
# --allow-tool shell(grep:*)
# --allow-tool shell(head)
# --allow-tool shell(head:*)
# --allow-tool shell(ls)
- # --allow-tool shell(node:*)
# --allow-tool shell(pwd)
# --allow-tool shell(python3:*)
# --allow-tool shell(sed:*)
@@ -671,7 +645,6 @@ jobs:
# --allow-tool shell(wc)
# --allow-tool shell(wc:*)
# --allow-tool shell(yq)
- # --allow-tool web_fetch
# --allow-tool write
timeout-minutes: 20
run: |
@@ -679,7 +652,7 @@ jobs:
touch /tmp/gh-aw/agent-step-summary.md
# shellcheck disable=SC1003
sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains 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 --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.16 --skip-pull --enable-api-proxy \
- -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(node:*)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3:*)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool web_fetch --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(git diff:*)'\'' --allow-tool '\''shell(git log:*)'\'' --allow-tool '\''shell(git show:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3:*)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_GITHUB_TOKEN: ${{ github.token }}
@@ -867,9 +840,7 @@ jobs:
runs-on: ubuntu-slim
permissions:
contents: read
- discussions: write
issues: write
- pull-requests: write
concurrency:
group: "gh-aw-conclusion-architecture-guardian"
cancel-in-progress: false
@@ -914,6 +885,7 @@ jobs:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX: "1"
GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ GH_AW_TRACKER_ID: "architecture-guardian"
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"
@@ -931,6 +903,7 @@ jobs:
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: "Architecture Guardian"
+ GH_AW_TRACKER_ID: "architecture-guardian"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -945,6 +918,7 @@ jobs:
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: "Architecture Guardian"
+ GH_AW_TRACKER_ID: "architecture-guardian"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -959,14 +933,17 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ GH_AW_TRACKER_ID: "architecture-guardian"
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: "architecture-guardian"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ GH_AW_ASSIGN_COPILOT_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.assign_copilot_failure_count }}
+ GH_AW_ASSIGN_COPILOT_ERRORS: ${{ needs.safe_outputs.outputs.assign_copilot_errors }}
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
- GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"footerWorkflowRecompile\":\"\\u003e 🛠️ *Workflow maintenance by [{workflow_name}]({run_url}) for {repository}*\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
GH_AW_GROUP_REPORTS: "false"
GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
GH_AW_TIMEOUT_MINUTES: "20"
@@ -1067,7 +1044,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
WORKFLOW_NAME: "Architecture Guardian"
- WORKFLOW_DESCRIPTION: "Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main"
+ WORKFLOW_DESCRIPTION: "Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
with:
script: |
@@ -1139,12 +1116,11 @@ jobs:
await main();
pre_activation:
- if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id
runs-on: ubuntu-slim
permissions:
contents: read
outputs:
- activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' }}
matched_command: ''
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
steps:
@@ -1173,6 +1149,19 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs');
await main();
+ - name: Check skip-if-match query
+ id: check_skip_if_match
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SKIP_QUERY: "is:issue is:open in:title \"[architecture-guardian]\""
+ GH_AW_WORKFLOW_NAME: "Architecture Guardian"
+ GH_AW_SKIP_MAX_MATCHES: "1"
+ 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/check_skip_if_match.cjs');
+ await main();
safe_outputs:
needs:
@@ -1183,25 +1172,26 @@ jobs:
runs-on: ubuntu-slim
permissions:
contents: read
- discussions: write
issues: write
- pull-requests: write
timeout-minutes: 15
env:
GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/architecture-guardian"
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}\",\"footerWorkflowRecompile\":\"\\u003e 🛠️ *Workflow maintenance by [{workflow_name}]({run_url}) for {repository}*\",\"runStarted\":\"🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...\",\"runSuccess\":\"✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋\",\"runFailure\":\"🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...\"}"
+ GH_AW_TRACKER_ID: "architecture-guardian"
GH_AW_WORKFLOW_ID: "architecture-guardian"
GH_AW_WORKFLOW_NAME: "Architecture Guardian"
outputs:
+ assign_copilot_errors: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_errors }}
+ assign_copilot_failure_count: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_failure_count }}
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 }}
- comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }}
- comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
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_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }}
+ created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_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:
@@ -1250,7 +1240,9 @@ jobs:
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: "{\"add_comment\":{\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"max\":1}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_ASSIGN_COPILOT: "true"
+ GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -1258,6 +1250,20 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
await main();
+ - name: Assign Copilot to created issues
+ id: assign_copilot_to_created_issues
+ if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != ''
+ continue-on-error: true
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }}
+ with:
+ github-token: ${{ secrets.GH_AW_AGENT_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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/assign_copilot_to_created_issues.cjs');
+ await main();
- name: Upload Safe Output Items
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
diff --git a/.github/workflows/architecture-guardian.md b/.github/workflows/architecture-guardian.md
index c3520faff3..c60033aec6 100644
--- a/.github/workflows/architecture-guardian.md
+++ b/.github/workflows/architecture-guardian.md
@@ -1,21 +1,22 @@
---
name: Architecture Guardian
-description: Enforces code structure discipline by scanning for large files, oversized functions, high export counts, and circular dependencies on every PR and push to main
+description: Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies
on:
- pull_request:
- types: [opened, synchronize, reopened]
- push:
- branches:
- - main
+ schedule: "daily around 14:00 on weekdays" # ~2 PM UTC, weekdays only
workflow_dispatch:
+ skip-if-match: 'is:issue is:open in:title "[architecture-guardian]"'
permissions:
contents: read
- pull-requests: read
+ actions: read
engine: copilot
+tracker-id: architecture-guardian
tools:
github:
- toolsets: [repos, pull_requests]
+ toolsets: [repos]
bash:
+ - "git log:*"
+ - "git diff:*"
+ - "git show:*"
- "find:*"
- "wc:*"
- "grep:*"
@@ -25,17 +26,18 @@ tools:
- "sed:*"
- "sort:*"
- "python3:*"
- - "node:*"
edit:
- web-fetch:
safe-outputs:
- submit-pull-request-review:
- max: 1
- add-comment:
+ create-issue:
+ expires: 2d
+ title-prefix: "[architecture-guardian] "
+ labels: [architecture, automated-analysis, cookie]
+ assignees: copilot
max: 1
noop:
messages:
footer: "> 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}"
+ footer-workflow-recompile: "> 🛠️ *Workflow maintenance by [{workflow_name}]({run_url}) for {repository}*"
run-started: "🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}..."
run-success: "✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋"
run-failure: "🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown..."
@@ -43,17 +45,15 @@ timeout-minutes: 20
features:
copilot-requests: true
---
-
# Architecture Guardian
-You are the Architecture Guardian, a code quality agent that enforces structural discipline in the codebase. Your mission is to prevent "spaghetti code" by detecting structural violations before they accumulate.
+You are the Architecture Guardian, a code quality agent that enforces structural discipline in the codebase. Your mission is to prevent "spaghetti code" by detecting structural violations in commits landed in the last 24 hours before they accumulate.
## Current Context
- **Repository**: ${{ github.repository }}
-- **Event**: ${{ github.event_name }}
+- **Analysis Period**: Last 24 hours
- **Run ID**: ${{ github.run_id }}
-- **PR Number**: ${{ github.event.pull_request.number }}
## Step 1: Load Configuration
@@ -74,30 +74,27 @@ cat .architecture.yml 2>/dev/null || echo "No .architecture.yml found, using def
Parse the YAML values if the file exists. Fall back to defaults for any missing key.
-## Step 2: Identify Files to Analyze
+## Step 2: Identify Files Changed in the Last 24 Hours
-Determine which files to scan based on the event type:
+Use git to find commits from the last 24 hours and the files they touched:
-### For Pull Requests
+```bash
+git log --since="24 hours ago" --oneline --name-only
+```
-Fetch the list of changed files in this PR using the GitHub tools (`pull_request_read` with method `get_files`). Focus on source files in the PR diff.
+Collect the unique set of changed source files:
-Alternatively, use git:
```bash
-git fetch origin ${{ github.event.pull_request.base.sha }} 2>/dev/null || true
-git diff --name-only ${{ github.event.pull_request.base.sha }}...HEAD 2>/dev/null || git diff --name-only HEAD~1 2>/dev/null || true
+git log --since="24 hours ago" --name-only --pretty=format: | sort -u | grep -E '\.(py|rs)$'
```
-### For Push to Main
+If no Python or Rust files were changed in the last 24 hours, call the `noop` tool and stop:
-Scan all source files in the repository:
-```bash
-git diff --name-only HEAD~1 2>/dev/null || find . -type f \( -name "*.py" -o -name "*.rs" \) ! -path "./.git/*" ! -path "./node_modules/*" ! -path "./target/*" | head -200
+```json
+{"noop": {"message": "No Python or Rust source files changed in the last 24 hours. Architecture scan skipped."}}
```
-Filter to supported languages:
-- **Python**: `*.py` files
-- **Rust**: `*.rs` files
+Exclude generated files, test fixtures, and vendor directories (e.g., `node_modules/`, `target/`, `.git/`).
## Step 3: Run Structural Analysis
@@ -275,11 +272,11 @@ Circular dependency cycles → **BLOCKER**
Group all findings into three severity tiers:
-### BLOCKER (fails the PR check)
+### BLOCKER (critical — must be addressed promptly)
- Circular import / dependency cycles between modules
- Files exceeding 1000 lines (configurable)
-### WARNING (allows merge but posts warning)
+### WARNING (should be addressed soon)
- Files exceeding 500 lines (configurable)
- Functions/methods exceeding 80 lines (configurable)
@@ -305,24 +302,25 @@ For **INFO** violations, provide a brief note about the high export count and su
Call the `noop` safe-output tool:
```json
-{"noop": {"message": "No architecture violations found. All files are within configured thresholds."}}
+{"noop": {"message": "No architecture violations found in the last 24 hours. All changed files are within configured thresholds."}}
```
-### If violations are found on a Pull Request
-
-Submit a pull request review with a structured comment. Use `submit-pull-request-review` with event type:
-- `REQUEST_CHANGES` if there are any **BLOCKER** violations
-- `COMMENT` if there are only **WARNING** or **INFO** violations
+### If violations are found
-**Review body format**:
+Create an issue with a structured report. Only create ONE issue (the `max: 1` limit applies and an existing open issue skips the run via `skip-if-match`).
-```markdown
-## 🏛️ Architecture Guardian Report
+**Issue title**: Architecture Violations Detected — [DATE]
-> Automated architecture scan for this pull request.
+**Issue body format**:
+```markdown
### Summary
+- **Analysis Period**: Last 24 hours
+- **Files Analyzed**: [NUMBER]
+- **Total Violations**: [NUMBER]
+- **Date**: [DATE]
+
| Severity | Count |
|----------|-------|
| 🚨 BLOCKER | N |
@@ -333,11 +331,12 @@ Submit a pull request review with a structured comment. Use `submit-pull-request
### 🚨 BLOCKER Violations
-> These violations **must be resolved** before merging.
+> These violations indicate serious structural problems that require prompt attention.
#### [Violation Title]
**File**: `path/to/file.py`
+**Commit**: [sha] — [commit message]
**Issue**: [Description of the problem]
**Why it matters**: [Explanation]
**Suggested fix**: [Concrete refactoring plan]
@@ -346,11 +345,12 @@ Submit a pull request review with a structured comment. Use `submit-pull-request
### ⚠️ WARNING Violations
-> These violations are strongly recommended to fix, but will not block the merge.
+> These violations should be addressed soon to prevent further structural debt.
#### [Violation Title]
**File**: `path/to/file.py` | **Function**: `function_name` | **Lines**: N
+**Commit**: [sha] — [commit message]
**Issue**: [Description]
**Suggested fix**: [Concrete refactoring plan]
@@ -372,19 +372,15 @@ Thresholds from `.architecture.yml` (or defaults):
- Function size: N lines
- Max public exports: N
-> 🏛️ *To configure thresholds, add a `.architecture.yml` file to the repository root.*
-```
-
-### If violations are found on a Push to Main
+### Action Checklist
-Post a comment to the last commit or create a summary using `add-comment`.
+- [ ] Review all BLOCKER violations and plan refactoring
+- [ ] Address WARNING violations in upcoming PRs
+- [ ] Consider splitting INFO modules if they grow further
+- [ ] Close this issue once all violations are resolved
-## Step 7: Fail if BLOCKER Violations Exist
-
-After posting the report:
-
-- If there are **BLOCKER** violations AND this is a pull request: submit the review with `REQUEST_CHANGES` event — this marks the PR check as requiring changes.
-- If there are **no** BLOCKER violations: submit as `COMMENT` or call `noop`.
+> 🏛️ *To configure thresholds, add a `.architecture.yml` file to the repository root.*
+```
**Important**: If no action is needed after completing your analysis, you **MUST** call the `noop` safe-output tool with a brief explanation. Failing to call any safe-output tool is the most common cause of safe-output workflow failures.
From 97cf245d11bb0b211bc1dfa551af6389dcb391fb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 16:50:11 +0000
Subject: [PATCH 4/4] fix: update architecture-guardian to analyze Go and
JavaScript files
- File filter changed from .py/.rs to .go/.js/.cjs/.mjs
- Check 2 (function size) rewritten for Go: uses grep + awk on func declarations
- Check 3 (function size) rewritten for JavaScript: uses grep on function declarations
- Check 4 (public exports) rewritten for Go: greps exported identifiers (uppercase),
and module.exports / export for JS files
- Check 5 (import cycles) rewritten for Go: uses go list/go build to detect cycles,
plus grep-based JS circular require detection
- Removed python3 from bash tool allowlist (no longer needed)
- Updated all examples and descriptions to reference .go files and Go/JS idioms
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b6d21b68-d7cd-444e-aa9d-372c363a73d5
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../workflows/architecture-guardian.lock.yml | 29 ++-
.github/workflows/architecture-guardian.md | 194 +++++-------------
2 files changed, 71 insertions(+), 152 deletions(-)
diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml
index 6ef5868616..20185215a5 100644
--- a/.github/workflows/architecture-guardian.lock.yml
+++ b/.github/workflows/architecture-guardian.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"463cff10841281bd7b33edb216c1b58977401c882fb000751d839e09733d2330","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"280f3237541c8d47c13a209b5c3e5e89458c4d9d8aceaffb8de082f9642f5cce","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"}]}
# ___ _ _
# / _ \ | | (_)
@@ -22,7 +22,7 @@
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
-# Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies
+# Daily analysis of commits from the last 24 hours to detect code structure violations in Go and JavaScript files, such as large files, oversized functions, high export counts, and import cycles
#
# Secrets used:
# - GH_AW_AGENT_TOKEN
@@ -148,14 +148,14 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
+ cat << 'GH_AW_PROMPT_27d2d38655bd3e86_EOF'
- GH_AW_PROMPT_1efb64fabf1df90f_EOF
+ GH_AW_PROMPT_27d2d38655bd3e86_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/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
+ cat << 'GH_AW_PROMPT_27d2d38655bd3e86_EOF'
Tools: create_issue, missing_tool, missing_data, noop
@@ -187,12 +187,12 @@ jobs:
{{/if}}
- GH_AW_PROMPT_1efb64fabf1df90f_EOF
+ GH_AW_PROMPT_27d2d38655bd3e86_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_1efb64fabf1df90f_EOF'
+ cat << 'GH_AW_PROMPT_27d2d38655bd3e86_EOF'
{{#runtime-import .github/workflows/architecture-guardian.md}}
- GH_AW_PROMPT_1efb64fabf1df90f_EOF
+ GH_AW_PROMPT_27d2d38655bd3e86_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -369,9 +369,9 @@ jobs:
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_b87c517c3419b095_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_9252b8521cdae542_EOF'
{"create_issue":{"assignees":["copilot"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_b87c517c3419b095_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_9252b8521cdae542_EOF
- name: Write Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -566,7 +566,7 @@ jobs:
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.16'
mkdir -p /home/runner/.copilot
- cat << GH_AW_MCP_CONFIG_8c3dc814275924bf_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_58a1fc569cec0f1f_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -607,7 +607,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_8c3dc814275924bf_EOF
+ GH_AW_MCP_CONFIG_58a1fc569cec0f1f_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@@ -636,7 +636,6 @@ jobs:
# --allow-tool shell(head:*)
# --allow-tool shell(ls)
# --allow-tool shell(pwd)
- # --allow-tool shell(python3:*)
# --allow-tool shell(sed:*)
# --allow-tool shell(sort)
# --allow-tool shell(sort:*)
@@ -652,7 +651,7 @@ jobs:
touch /tmp/gh-aw/agent-step-summary.md
# shellcheck disable=SC1003
sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains 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 --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.16 --skip-pull --enable-api-proxy \
- -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(git diff:*)'\'' --allow-tool '\''shell(git log:*)'\'' --allow-tool '\''shell(git show:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3:*)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(git diff:*)'\'' --allow-tool '\''shell(git log:*)'\'' --allow-tool '\''shell(git show:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_GITHUB_TOKEN: ${{ github.token }}
@@ -1044,7 +1043,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
WORKFLOW_NAME: "Architecture Guardian"
- WORKFLOW_DESCRIPTION: "Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies"
+ WORKFLOW_DESCRIPTION: "Daily analysis of commits from the last 24 hours to detect code structure violations in Go and JavaScript files, such as large files, oversized functions, high export counts, and import cycles"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
with:
script: |
diff --git a/.github/workflows/architecture-guardian.md b/.github/workflows/architecture-guardian.md
index c60033aec6..f8cd6801cd 100644
--- a/.github/workflows/architecture-guardian.md
+++ b/.github/workflows/architecture-guardian.md
@@ -1,6 +1,6 @@
---
name: Architecture Guardian
-description: Daily analysis of commits from the last 24 hours to detect code structure violations such as large files, oversized functions, high export counts, and circular dependencies
+description: Daily analysis of commits from the last 24 hours to detect code structure violations in Go and JavaScript files, such as large files, oversized functions, high export counts, and import cycles
on:
schedule: "daily around 14:00 on weekdays" # ~2 PM UTC, weekdays only
workflow_dispatch:
@@ -25,7 +25,6 @@ tools:
- "awk:*"
- "sed:*"
- "sort:*"
- - "python3:*"
edit:
safe-outputs:
create-issue:
@@ -85,16 +84,16 @@ git log --since="24 hours ago" --oneline --name-only
Collect the unique set of changed source files:
```bash
-git log --since="24 hours ago" --name-only --pretty=format: | sort -u | grep -E '\.(py|rs)$'
+git log --since="24 hours ago" --name-only --pretty=format: | sort -u | grep -E '\.(go|js|cjs|mjs)$'
```
-If no Python or Rust files were changed in the last 24 hours, call the `noop` tool and stop:
+If no Go or JavaScript files were changed in the last 24 hours, call the `noop` tool and stop:
```json
-{"noop": {"message": "No Python or Rust source files changed in the last 24 hours. Architecture scan skipped."}}
+{"noop": {"message": "No Go or JavaScript source files changed in the last 24 hours. Architecture scan skipped."}}
```
-Exclude generated files, test fixtures, and vendor directories (e.g., `node_modules/`, `target/`, `.git/`).
+Exclude generated files, test fixtures, and vendor directories (e.g., `node_modules/`, `vendor/`, `.git/`, `*_test.go`).
## Step 3: Run Structural Analysis
@@ -112,158 +111,79 @@ Classify:
- Lines > `thresholds.file_lines_blocker` (default 1000) → **BLOCKER**
- Lines > `thresholds.file_lines_warning` (default 500) → **WARNING**
-### Check 2: Function/Method Size (Python)
+### Check 2: Function Size (Go)
-Use a Python script to parse function sizes:
+Find Go function declarations and estimate sizes by counting lines between consecutive `func` markers:
```bash
-python3 - <<'PYEOF'
-import ast, sys, os
-
-def analyze_functions(filepath):
- try:
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
- source = f.read()
- tree = ast.parse(source, filename=filepath)
- except SyntaxError as e:
- print(f"PARSE_ERROR:{filepath}:{e}", flush=True)
- return
-
- for node in ast.walk(tree):
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
- start = node.lineno
- end = node.end_lineno if hasattr(node, 'end_lineno') else node.lineno
- length = end - start + 1
- print(f"FUNC:{filepath}:{node.name}:{start}:{length}", flush=True)
-
-for path in sys.argv[1:]:
- analyze_functions(path)
-PYEOF
+# List all func declaration line numbers in a Go file
+grep -n "^func "
```
-Alternatively, approximate with grep:
+Use the line numbers to estimate each function's length. For a more precise count, use `awk`:
```bash
-# Approximate Python function line counts
-grep -n "^def \|^ def \|^async def \|^ async def "
+awk '/^func /{if(start>0) print name, NR-start; name=$0; start=NR} END{if(start>0) print name, NR-start+1}'
```
Functions exceeding `thresholds.function_lines` (default 80) → **WARNING**
-### Check 3: Function/Method Size (Rust)
+### Check 3: Function Size (JavaScript / CommonJS)
-Approximate Rust function sizes using line counting between `fn` keywords:
+Approximate function sizes in `.js` / `.cjs` / `.mjs` files using grep:
```bash
-grep -n "^\s*pub fn \|^\s*fn \|^\s*pub async fn \|^\s*async fn "
+# List function declaration line numbers
+grep -n "^function \|^const .* = function\|^const .* = ("
```
-Count lines between consecutive function definitions to estimate function length. Functions exceeding `thresholds.function_lines` (default 80) → **WARNING**
+Count lines between consecutive function declarations to estimate length. Functions exceeding `thresholds.function_lines` (default 80) → **WARNING**
-### Check 4: High Public Export Count (Python)
+### Check 4: High Public Export Count (Go)
-Count public exports (non-underscore names) in Python files:
+In Go, exported identifiers start with an uppercase letter. Count exported top-level functions, types, variables, and constants:
```bash
-python3 - <<'PYEOF'
-import ast, sys
-
-def count_exports(filepath):
- try:
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
- source = f.read()
- tree = ast.parse(source, filename=filepath)
- except SyntaxError:
- return
-
- exports = []
- for node in ast.walk(tree):
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
- if not node.name.startswith('_'):
- exports.append(node.name)
-
- count = len(exports)
- if count > 0:
- print(f"EXPORTS:{filepath}:{count}:{','.join(exports[:20])}", flush=True)
-
-for path in sys.argv[1:]:
- count_exports(path)
-PYEOF
+# Count exported top-level declarations in a Go file
+grep -c "^func [A-Z]\|^type [A-Z]\|^var [A-Z]\|^const [A-Z]"
+
+# List them for reporting
+grep -n "^func [A-Z]\|^type [A-Z]\|^var [A-Z]\|^const [A-Z]"
+```
+
+For JavaScript files, count module-level exports:
+
+```bash
+# CommonJS
+grep -c "^module\.exports\|^exports\."
+
+# ES modules
+grep -c "^export "
```
Files with more than `thresholds.max_exports` (default 10) public names → **INFO**
-### Check 5: Circular Imports / Dependency Cycles (Python)
+### Check 5: Import Cycles (Go)
+
+Go's toolchain detects import cycles at build time. Use `go list` to surface any cycles across all packages:
+
+```bash
+go list ./... 2>&1 | grep -i "import cycle\|cycle not allowed"
+```
+
+Alternatively, use `go build` which also reports cycles:
+
+```bash
+go build ./... 2>&1 | grep -i "import cycle\|cycle not allowed"
+```
+
+Any output from these commands indicates a circular import dependency → **BLOCKER**
-Detect circular imports using a simple reachability analysis:
+For JavaScript files, detect circular `require()` chains by grepping for cross-file imports and checking for mutual dependencies:
```bash
-python3 - <<'PYEOF'
-import ast, sys, os
-from collections import defaultdict, deque
-
-def find_imports(filepath, root):
- try:
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
- source = f.read()
- tree = ast.parse(source, filename=filepath)
- except Exception:
- return []
-
- imports = []
- pkg = os.path.dirname(os.path.relpath(filepath, root))
- for node in ast.walk(tree):
- if isinstance(node, ast.ImportFrom) and node.module:
- parts = node.module.split('.')
- candidate = os.path.join(root, *parts) + '.py'
- if os.path.exists(candidate):
- imports.append(os.path.relpath(candidate, root))
- elif isinstance(node, ast.Import):
- for alias in node.names:
- parts = alias.name.split('.')
- candidate = os.path.join(root, *parts) + '.py'
- if os.path.exists(candidate):
- imports.append(os.path.relpath(candidate, root))
- return imports
-
-root = sys.argv[1] if len(sys.argv) > 1 else '.'
-files = [os.path.relpath(p, root) for p in sys.argv[2:]]
-
-graph = defaultdict(list)
-for f in files:
- abs_path = os.path.join(root, f)
- for dep in find_imports(abs_path, root):
- graph[f].append(dep)
-
-def find_cycle(start, graph):
- visited = set()
- path = []
- def dfs(node):
- if node in path:
- idx = path.index(node)
- return path[idx:]
- if node in visited:
- return None
- visited.add(node)
- path.append(node)
- for nbr in graph.get(node, []):
- result = dfs(nbr)
- if result:
- return result
- path.pop()
- return None
- return dfs(start)
-
-seen_cycles = set()
-for f in files:
- cycle = find_cycle(f, graph)
- if cycle:
- key = frozenset(cycle)
- if key not in seen_cycles:
- seen_cycles.add(key)
- print(f"CYCLE:{' -> '.join(cycle + [cycle[0]])}", flush=True)
-
-PYEOF
+# Find all require() calls pointing to local modules
+grep -rn "require('\.\./\|\./" --include="*.js" --include="*.cjs"
```
Circular dependency cycles → **BLOCKER**
@@ -273,7 +193,7 @@ Circular dependency cycles → **BLOCKER**
Group all findings into three severity tiers:
### BLOCKER (critical — must be addressed promptly)
-- Circular import / dependency cycles between modules
+- Circular import / dependency cycles between Go packages
- Files exceeding 1000 lines (configurable)
### WARNING (should be addressed soon)
@@ -287,9 +207,9 @@ Group all findings into three severity tiers:
For each **BLOCKER** and **WARNING** violation, generate a concise refactoring suggestion that explains:
-1. **What the violation is** — e.g., "`src/utils.py` has 1,247 lines"
+1. **What the violation is** — e.g., "`pkg/workflow/compiler.go` has 1,247 lines"
2. **Why it's a problem** — e.g., "Large files are harder to navigate, review, and maintain"
-3. **A concrete plan to fix it** — e.g., "Extract the `DataProcessor` class into `src/data_processor.py` and move the `FileUtils` helpers into `src/file_utils.py`"
+3. **A concrete plan to fix it** — e.g., "Extract the expression-extraction logic into `pkg/workflow/expression_extraction.go` and move YAML helpers into `pkg/workflow/compiler_yaml.go`"
Use your knowledge of software architecture best practices. Be specific and actionable.
@@ -335,7 +255,7 @@ Create an issue with a structured report. Only create ONE issue (the `max: 1` li
#### [Violation Title]
-**File**: `path/to/file.py`
+**File**: `path/to/file.go`
**Commit**: [sha] — [commit message]
**Issue**: [Description of the problem]
**Why it matters**: [Explanation]
@@ -349,7 +269,7 @@ Create an issue with a structured report. Only create ONE issue (the `max: 1` li
#### [Violation Title]
-**File**: `path/to/file.py` | **Function**: `function_name` | **Lines**: N
+**File**: `path/to/file.go` | **Function**: `FunctionName` | **Lines**: N
**Commit**: [sha] — [commit message]
**Issue**: [Description]
**Suggested fix**: [Concrete refactoring plan]
@@ -360,7 +280,7 @@ Create an issue with a structured report. Only create ONE issue (the `max: 1` li
> Informational findings. Consider addressing in future refactoring.
-- `path/to/file.py`: N public exports — consider splitting into focused modules
+- `path/to/file.go`: N exported identifiers — consider splitting into focused packages or sub-packages
---