From 182d65e02a1dbdf96dbef9c4cf3fd99c36150ac2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 07:03:35 +0000 Subject: [PATCH 1/4] Initial plan From ad874617d437cadbb88e236a4fdfab22c17e7f6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 07:09:46 +0000 Subject: [PATCH 2/4] chore: initial plan for no-json-stringify-error scope extension Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-token-audit.lock.yml | 2 +- .github/workflows/agentic-token-optimizer.lock.yml | 2 +- .github/workflows/agentic-token-trend-audit.lock.yml | 2 +- .github/workflows/architecture-guardian.lock.yml | 2 +- .github/workflows/auto-triage-issues.lock.yml | 2 +- .github/workflows/aw-failure-investigator.lock.yml | 2 +- .github/workflows/ci-doctor.lock.yml | 2 +- .github/workflows/cli-consistency-checker.lock.yml | 2 +- .github/workflows/code-simplifier.lock.yml | 2 +- .github/workflows/contribution-check.lock.yml | 2 +- .github/workflows/copilot-centralization-drilldown.lock.yml | 2 +- .github/workflows/copilot-pr-nlp-analysis.lock.yml | 2 +- .github/workflows/daily-ambient-context-optimizer.lock.yml | 2 +- .github/workflows/daily-community-attribution.lock.yml | 2 +- .github/workflows/daily-multi-device-docs-tester.lock.yml | 2 +- .github/workflows/daily-security-observability.lock.yml | 2 +- .github/workflows/daily-spdd-spec-planner.lock.yml | 2 +- .github/workflows/dataflow-pr-discussion-dataset.lock.yml | 2 +- .github/workflows/delight.lock.yml | 2 +- .github/workflows/dependabot-burner.lock.yml | 2 +- .github/workflows/design-decision-gate.lock.yml | 2 +- .github/workflows/designer-drift-audit.lock.yml | 2 +- .github/workflows/docs-noob-tester.lock.yml | 2 +- .github/workflows/eslint-monster.lock.yml | 2 +- .github/workflows/glossary-maintainer.lock.yml | 2 +- .github/workflows/gpclean.lock.yml | 2 +- .github/workflows/impeccable-skills-reviewer.lock.yml | 2 +- .github/workflows/issue-arborist.lock.yml | 2 +- .github/workflows/lint-monster.lock.yml | 2 +- .github/workflows/linter-miner.lock.yml | 2 +- .github/workflows/mattpocock-skills-reviewer.lock.yml | 2 +- .github/workflows/mergefest.lock.yml | 2 +- .github/workflows/objective-impact-report.lock.yml | 2 +- .github/workflows/pr-description-caveman.lock.yml | 2 +- .github/workflows/prompt-clustering-analysis.lock.yml | 2 +- .github/workflows/release.lock.yml | 2 +- .github/workflows/repository-quality-improver.lock.yml | 2 +- .github/workflows/schema-consistency-checker.lock.yml | 2 +- .github/workflows/spec-extractor.lock.yml | 2 +- .github/workflows/stale-repo-identifier.lock.yml | 2 +- .github/workflows/static-analysis-report.lock.yml | 2 +- .github/workflows/step-name-alignment.lock.yml | 2 +- .github/workflows/test-quality-sentinel.lock.yml | 2 +- .github/workflows/uk-ai-operational-resilience.lock.yml | 2 +- .github/workflows/unbloat-docs.lock.yml | 2 +- .github/workflows/weekly-blog-post-writer.lock.yml | 2 +- .github/workflows/workflow-health-manager.lock.yml | 4 ++-- .github/workflows/workflow-skill-extractor.lock.yml | 2 +- 48 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index dfc3946ad98..de2da2d2e6a 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -525,7 +525,7 @@ jobs: - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Download agentic workflow logs - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\n# Download last 24 hours of agentic workflow logs as JSON\n# Allow partial results — gh aw logs streams incrementally, so even if\n# it hits an API rate limit partway through, the JSON written so far is\n# still valid and should be processed by the agent.\nLOGS_EXIT=0\ngh aw logs \\\n --start-date -1d \\\n --json \\\n -c 100 \\\n > /tmp/gh-aw/token-audit/workflow-logs.json || LOGS_EXIT=$?\n\nif [ -s /tmp/gh-aw/token-audit/workflow-logs.json ]; then\n TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/workflow-logs.json)\n echo \"✅ Downloaded $TOTAL agentic workflow runs (last 24 hours)\"\n if [ \"$LOGS_EXIT\" -ne 0 ]; then\n echo \"⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)\"\n fi\nelse\n echo \"❌ No log data downloaded (exit code $LOGS_EXIT)\"\n echo '{\"runs\":[],\"summary\":{}}' > /tmp/gh-aw/token-audit/workflow-logs.json\nfi\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\n# Download last 24 hours of agentic workflow logs as JSON\n# Allow partial results — gh aw logs streams incrementally, so even if\n# it hits an API rate limit partway through, the JSON written so far is\n# still valid and should be processed by the agent.\nLOGS_EXIT=0\ngh aw logs \\\n --start-date -1d \\\n --json \\\n -c 100 \\\n > /tmp/gh-aw/token-audit/workflow-logs.json || LOGS_EXIT=$?\n\nif [ -s /tmp/gh-aw/token-audit/workflow-logs.json ]; then\n TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/workflow-logs.json)\n echo \"✅ Downloaded $TOTAL agentic workflow runs (last 24 hours)\"\n if [ \"$LOGS_EXIT\" -ne 0 ]; then\n echo \"⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)\"\n fi\nelse\n echo \"❌ No log data downloaded (exit code $LOGS_EXIT)\"\n echo '{\"runs\":[],\"summary\":{}}' > /tmp/gh-aw/token-audit/workflow-logs.json\nfi" # Repo memory git-based storage configuration from frontmatter processed below - name: Clone repo-memory branch (default) diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index 248cace1314..3b6906eb720 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -483,7 +483,7 @@ jobs: - name: Aggregate top workflows by AIC usage run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\njq '{\n generated_at: (now | todateiso8601),\n window_days: 7,\n top_workflows: (\n [.runs[]\n | select(.status == \"completed\")\n | {\n workflow_name: .workflow_name,\n aic: (.aic // 0),\n raw_tokens: (.token_usage // 0),\n turns: (.turns // 0),\n action_minutes: (.action_minutes // 0)\n }\n ]\n | group_by(.workflow_name)\n | map({\n workflow_name: .[0].workflow_name,\n run_count: length,\n total_aic: (map(.aic) | add),\n avg_aic: ((map(.aic) | add) / length),\n total_raw_tokens: (map(.raw_tokens) | add),\n total_turns: (map(.turns) | add),\n total_action_minutes: (map(.action_minutes) | add)\n })\n | sort_by(.total_aic)\n | reverse\n | .[:10]\n )\n}' /tmp/gh-aw/token-audit/all-runs.json > /tmp/gh-aw/token-audit/top-workflows.json\n\necho \"✅ Generated top workflow summary at /tmp/gh-aw/token-audit/top-workflows.json\"\njq '.top_workflows' /tmp/gh-aw/token-audit/top-workflows.json\n" - name: Load optimization history - run: "set -euo pipefail\n\nOPT_LOG=\"/tmp/gh-aw/repo-memory/default/optimization-log.json\"\nif [ -f \"$OPT_LOG\" ]; then\n echo \"✅ Previous optimizations:\"\n jq -r '.[] | \"\\(.date): \\(.workflow_name)\"' \"$OPT_LOG\"\nelse\n echo \"ℹ️ No previous optimization history found.\"\nfi\n" + run: "set -euo pipefail\n\nOPT_LOG=\"/tmp/gh-aw/repo-memory/default/optimization-log.json\"\nif [ -f \"$OPT_LOG\" ]; then\n echo \"✅ Previous optimizations:\"\n jq -r '.[] | \"\\(.date): \\(.workflow_name)\"' \"$OPT_LOG\"\nelse\n echo \"ℹ️ No previous optimization history found.\"\nfi" # Repo memory git-based storage configuration from frontmatter processed below - name: Clone repo-memory branch (default) diff --git a/.github/workflows/agentic-token-trend-audit.lock.yml b/.github/workflows/agentic-token-trend-audit.lock.yml index f040ef04ab9..fbf66fe3cc4 100644 --- a/.github/workflows/agentic-token-trend-audit.lock.yml +++ b/.github/workflows/agentic-token-trend-audit.lock.yml @@ -509,7 +509,7 @@ jobs: DATE_RANGE_INPUT: ${{ github.event.inputs.date_range }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Download agentic workflow logs - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\nDATE_RANGE=\"$DATE_RANGE_INPUT\"\nif [[ \"$DATE_RANGE\" != *\"..\"* ]]; then\n echo \"❌ Invalid date_range input: $DATE_RANGE\"\n echo \"Expected format: .. (for example 2026-05-01..2026-05-31 or -30d..now)\"\n exit 1\nfi\n\nSTART_DATE=\"${DATE_RANGE%%..*}\"\nEND_DATE=\"${DATE_RANGE##*..}\"\n\n# Download logs for the requested range as JSON.\n# Allow partial results — gh aw logs streams incrementally, so even if\n# it hits an API rate limit partway through, the JSON written so far is\n# still valid and should be processed by the agent.\nLOGS_EXIT=0\ngh aw logs \\\n --start-date \"$START_DATE\" \\\n --end-date \"$END_DATE\" \\\n --json \\\n -c 500 \\\n > /tmp/gh-aw/token-audit/workflow-logs.json || LOGS_EXIT=$?\n\nif [ -s /tmp/gh-aw/token-audit/workflow-logs.json ]; then\n TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/workflow-logs.json)\n echo \"✅ Downloaded $TOTAL agentic workflow runs ($START_DATE to $END_DATE)\"\n if [ \"$LOGS_EXIT\" -ne 0 ]; then\n echo \"⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)\"\n fi\nelse\n echo \"❌ No log data downloaded (exit code $LOGS_EXIT)\"\n echo '{\"runs\":[],\"summary\":{}}' > /tmp/gh-aw/token-audit/workflow-logs.json\nfi\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\nDATE_RANGE=\"$DATE_RANGE_INPUT\"\nif [[ \"$DATE_RANGE\" != *\"..\"* ]]; then\n echo \"❌ Invalid date_range input: $DATE_RANGE\"\n echo \"Expected format: .. (for example 2026-05-01..2026-05-31 or -30d..now)\"\n exit 1\nfi\n\nSTART_DATE=\"${DATE_RANGE%%..*}\"\nEND_DATE=\"${DATE_RANGE##*..}\"\n\n# Download logs for the requested range as JSON.\n# Allow partial results — gh aw logs streams incrementally, so even if\n# it hits an API rate limit partway through, the JSON written so far is\n# still valid and should be processed by the agent.\nLOGS_EXIT=0\ngh aw logs \\\n --start-date \"$START_DATE\" \\\n --end-date \"$END_DATE\" \\\n --json \\\n -c 500 \\\n > /tmp/gh-aw/token-audit/workflow-logs.json || LOGS_EXIT=$?\n\nif [ -s /tmp/gh-aw/token-audit/workflow-logs.json ]; then\n TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/workflow-logs.json)\n echo \"✅ Downloaded $TOTAL agentic workflow runs ($START_DATE to $END_DATE)\"\n if [ \"$LOGS_EXIT\" -ne 0 ]; then\n echo \"⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)\"\n fi\nelse\n echo \"❌ No log data downloaded (exit code $LOGS_EXIT)\"\n echo '{\"runs\":[],\"summary\":{}}' > /tmp/gh-aw/token-audit/workflow-logs.json\nfi" - name: Configure Git credentials env: diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml index ad86986d5e6..6b51ec42799 100644 --- a/.github/workflows/architecture-guardian.lock.yml +++ b/.github/workflows/architecture-guardian.lock.yml @@ -526,7 +526,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: Collect architecture metrics - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# Read thresholds from .architecture.yml or use defaults\nFILE_LINES_BLOCKER=1000\nFILE_LINES_WARNING=500\nFUNCTION_LINES=80\nMAX_EXPORTS=10\n\nif [ -f .architecture.yml ]; then\n b=$(grep -E '^\\s*file_lines_blocker:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n w=$(grep -E '^\\s*file_lines_warning:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n f=$(grep -E '^\\s*function_lines:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n e=$(grep -E '^\\s*max_exports:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n [[ -n \"${b:-}\" && \"$b\" =~ ^[0-9]+$ ]] && FILE_LINES_BLOCKER=$b\n [[ -n \"${w:-}\" && \"$w\" =~ ^[0-9]+$ ]] && FILE_LINES_WARNING=$w\n [[ -n \"${f:-}\" && \"$f\" =~ ^[0-9]+$ ]] && FUNCTION_LINES=$f\n [[ -n \"${e:-}\" && \"$e\" =~ ^[0-9]+$ ]] && MAX_EXPORTS=$e\nfi\n\n# Get changed Go/JS files in last 24 hours, excluding tests and vendor paths\nCHANGED_FILES=$(git log --since=\"24 hours ago\" --name-only --pretty=format: \\\n | sort -u \\\n | grep -E '\\.(go|js|cjs|mjs)$' \\\n | grep -vE '(node_modules/|vendor/|\\.git/|_test\\.go$)' \\\n | while IFS= read -r f; do [ -f \"$f\" ] && echo \"$f\"; done \\\n || true)\n\nif [ -z \"$CHANGED_FILES\" ]; then\n jq -n \\\n --argjson blocker \"$FILE_LINES_BLOCKER\" \\\n --argjson warning \"$FILE_LINES_WARNING\" \\\n --argjson func_lines \"$FUNCTION_LINES\" \\\n --argjson max_exports \"$MAX_EXPORTS\" \\\n '{noop: true, thresholds: {file_lines_blocker: $blocker, file_lines_warning: $warning, function_lines: $func_lines, max_exports: $max_exports}, files: [], import_cycles: \"\"}' \\\n > /tmp/gh-aw/agent/arch-metrics.json\n echo \"No changed Go/JS files found in the last 24 hours.\"\n exit 0\nfi\n\n# Build file metrics array\nFILES_JSON=\"[]\"\nwhile IFS= read -r FILE; do\n [ -z \"$FILE\" ] && continue\n LINES=$(wc -l < \"$FILE\" 2>/dev/null | tr -d ' ' || echo 0)\n EXT=\"${FILE##*.}\"\n\n if [[ \"$EXT\" == \"go\" ]]; then\n # Function sizes: \"func declaration\\tline_count\" per function\n # Pattern matches both regular functions (^func Name) and receiver methods (^func (r *T) Name)\n FUNC_DATA=$(awk '/^func /{if(start>0 && name!=\"\") printf \"%s\\t%d\\n\", name, NR-start; name=$0; start=NR} END{if(start>0 && name!=\"\") printf \"%s\\t%d\\n\", name, NR-start+1}' \"$FILE\" 2>/dev/null | head -50 || true)\n # Export count and names (top-level exported identifiers start with uppercase)\n EXPORT_COUNT=$(grep -cE \"^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]\" \"$FILE\" 2>/dev/null || echo 0)\n EXPORT_NAMES=$(grep -nE \"^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]\" \"$FILE\" 2>/dev/null | head -20 || true)\n else\n # JS/CJS/MJS: capture named functions, arrow functions, and class methods\n FUNC_DATA=$(grep -nE \"^function |^const [a-zA-Z_$][a-zA-Z0-9_$]* = (function|\\(|async \\(|async function)|^(export (default )?function|export const [a-zA-Z_$][a-zA-Z0-9_$]* =)|^[a-zA-Z_$][a-zA-Z0-9_$]*\\s*\\([^)]*\\)\\s*\\{\" \"$FILE\" 2>/dev/null | head -50 || true)\n CJS_COUNT=$(grep -cE \"^module\\.exports|^exports\\.\" \"$FILE\" 2>/dev/null || echo 0)\n ESM_COUNT=$(grep -cE \"^export \" \"$FILE\" 2>/dev/null || echo 0)\n EXPORT_COUNT=$((CJS_COUNT + ESM_COUNT))\n EXPORT_NAMES=$(grep -nE \"^export |^module\\.exports|^exports\\.\" \"$FILE\" 2>/dev/null | head -20 || true)\n fi\n\n FILES_JSON=$(jq \\\n --arg file \"$FILE\" \\\n --argjson lines \"$LINES\" \\\n --argjson exports \"$EXPORT_COUNT\" \\\n --arg func_data \"${FUNC_DATA:-}\" \\\n --arg export_names \"${EXPORT_NAMES:-}\" \\\n '. + [{file: $file, lines: $lines, export_count: $exports, func_data: $func_data, export_names: $export_names}]' \\\n <<< \"$FILES_JSON\")\ndone <<< \"$CHANGED_FILES\"\n\n# Check Go import cycles once across all packages\n# Note: go list may also emit errors for syntax issues; grep filters to only cycle errors\nIMPORT_CYCLES=$(go list ./... 2>&1 | grep -iE \"import cycle|cycle not allowed\" || true)\n\njq -n \\\n --argjson blocker \"$FILE_LINES_BLOCKER\" \\\n --argjson warning \"$FILE_LINES_WARNING\" \\\n --argjson func_lines \"$FUNCTION_LINES\" \\\n --argjson max_exports \"$MAX_EXPORTS\" \\\n --argjson files \"$FILES_JSON\" \\\n --arg import_cycles \"$IMPORT_CYCLES\" \\\n '{noop: false, thresholds: {file_lines_blocker: $blocker, file_lines_warning: $warning, function_lines: $func_lines, max_exports: $max_exports}, files: $files, import_cycles: $import_cycles}' \\\n > /tmp/gh-aw/agent/arch-metrics.json\n\nFILE_COUNT=$(echo \"$CHANGED_FILES\" | wc -l | tr -d ' ')\necho \"✅ Pre-computed metrics for $FILE_COUNT file(s) → /tmp/gh-aw/agent/arch-metrics.json\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# Read thresholds from .architecture.yml or use defaults\nFILE_LINES_BLOCKER=1000\nFILE_LINES_WARNING=500\nFUNCTION_LINES=80\nMAX_EXPORTS=10\n\nif [ -f .architecture.yml ]; then\n b=$(grep -E '^\\s*file_lines_blocker:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n w=$(grep -E '^\\s*file_lines_warning:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n f=$(grep -E '^\\s*function_lines:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n e=$(grep -E '^\\s*max_exports:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '\"' | head -1 || true)\n [[ -n \"${b:-}\" && \"$b\" =~ ^[0-9]+$ ]] && FILE_LINES_BLOCKER=$b\n [[ -n \"${w:-}\" && \"$w\" =~ ^[0-9]+$ ]] && FILE_LINES_WARNING=$w\n [[ -n \"${f:-}\" && \"$f\" =~ ^[0-9]+$ ]] && FUNCTION_LINES=$f\n [[ -n \"${e:-}\" && \"$e\" =~ ^[0-9]+$ ]] && MAX_EXPORTS=$e\nfi\n\n# Get changed Go/JS files in last 24 hours, excluding tests and vendor paths\nCHANGED_FILES=$(git log --since=\"24 hours ago\" --name-only --pretty=format: \\\n | sort -u \\\n | grep -E '\\.(go|js|cjs|mjs)$' \\\n | grep -vE '(node_modules/|vendor/|\\.git/|_test\\.go$)' \\\n | while IFS= read -r f; do [ -f \"$f\" ] && echo \"$f\"; done \\\n || true)\n\nif [ -z \"$CHANGED_FILES\" ]; then\n jq -n \\\n --argjson blocker \"$FILE_LINES_BLOCKER\" \\\n --argjson warning \"$FILE_LINES_WARNING\" \\\n --argjson func_lines \"$FUNCTION_LINES\" \\\n --argjson max_exports \"$MAX_EXPORTS\" \\\n '{noop: true, thresholds: {file_lines_blocker: $blocker, file_lines_warning: $warning, function_lines: $func_lines, max_exports: $max_exports}, files: [], import_cycles: \"\"}' \\\n > /tmp/gh-aw/agent/arch-metrics.json\n echo \"No changed Go/JS files found in the last 24 hours.\"\n exit 0\nfi\n\n# Build file metrics array\nFILES_JSON=\"[]\"\nwhile IFS= read -r FILE; do\n [ -z \"$FILE\" ] && continue\n LINES=$(wc -l < \"$FILE\" 2>/dev/null | tr -d ' ' || echo 0)\n EXT=\"${FILE##*.}\"\n\n if [[ \"$EXT\" == \"go\" ]]; then\n # Function sizes: \"func declaration\\tline_count\" per function\n # Pattern matches both regular functions (^func Name) and receiver methods (^func (r *T) Name)\n FUNC_DATA=$(awk '/^func /{if(start>0 && name!=\"\") printf \"%s\\t%d\\n\", name, NR-start; name=$0; start=NR} END{if(start>0 && name!=\"\") printf \"%s\\t%d\\n\", name, NR-start+1}' \"$FILE\" 2>/dev/null | head -50 || true)\n # Export count and names (top-level exported identifiers start with uppercase)\n EXPORT_COUNT=$(grep -cE \"^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]\" \"$FILE\" 2>/dev/null || echo 0)\n EXPORT_NAMES=$(grep -nE \"^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]\" \"$FILE\" 2>/dev/null | head -20 || true)\n else\n # JS/CJS/MJS: capture named functions, arrow functions, and class methods\n FUNC_DATA=$(grep -nE \"^function |^const [a-zA-Z_$][a-zA-Z0-9_$]* = (function|\\(|async \\(|async function)|^(export (default )?function|export const [a-zA-Z_$][a-zA-Z0-9_$]* =)|^[a-zA-Z_$][a-zA-Z0-9_$]*\\s*\\([^)]*\\)\\s*\\{\" \"$FILE\" 2>/dev/null | head -50 || true)\n CJS_COUNT=$(grep -cE \"^module\\.exports|^exports\\.\" \"$FILE\" 2>/dev/null || echo 0)\n ESM_COUNT=$(grep -cE \"^export \" \"$FILE\" 2>/dev/null || echo 0)\n EXPORT_COUNT=$((CJS_COUNT + ESM_COUNT))\n EXPORT_NAMES=$(grep -nE \"^export |^module\\.exports|^exports\\.\" \"$FILE\" 2>/dev/null | head -20 || true)\n fi\n\n FILES_JSON=$(jq \\\n --arg file \"$FILE\" \\\n --argjson lines \"$LINES\" \\\n --argjson exports \"$EXPORT_COUNT\" \\\n --arg func_data \"${FUNC_DATA:-}\" \\\n --arg export_names \"${EXPORT_NAMES:-}\" \\\n '. + [{file: $file, lines: $lines, export_count: $exports, func_data: $func_data, export_names: $export_names}]' \\\n <<< \"$FILES_JSON\")\ndone <<< \"$CHANGED_FILES\"\n\n# Check Go import cycles once across all packages\n# Note: go list may also emit errors for syntax issues; grep filters to only cycle errors\nIMPORT_CYCLES=$(go list ./... 2>&1 | grep -iE \"import cycle|cycle not allowed\" || true)\n\njq -n \\\n --argjson blocker \"$FILE_LINES_BLOCKER\" \\\n --argjson warning \"$FILE_LINES_WARNING\" \\\n --argjson func_lines \"$FUNCTION_LINES\" \\\n --argjson max_exports \"$MAX_EXPORTS\" \\\n --argjson files \"$FILES_JSON\" \\\n --arg import_cycles \"$IMPORT_CYCLES\" \\\n '{noop: false, thresholds: {file_lines_blocker: $blocker, file_lines_warning: $warning, function_lines: $func_lines, max_exports: $max_exports}, files: $files, import_cycles: $import_cycles}' \\\n > /tmp/gh-aw/agent/arch-metrics.json\n\nFILE_COUNT=$(echo \"$CHANGED_FILES\" | wc -l | tr -d ' ')\necho \"✅ Pre-computed metrics for $FILE_COUNT file(s) → /tmp/gh-aw/agent/arch-metrics.json\"" - name: Configure Git credentials env: diff --git a/.github/workflows/auto-triage-issues.lock.yml b/.github/workflows/auto-triage-issues.lock.yml index 69a39074b87..64ed523e7b4 100644 --- a/.github/workflows/auto-triage-issues.lock.yml +++ b/.github/workflows/auto-triage-issues.lock.yml @@ -499,7 +499,7 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Fetch unlabeled issues - run: | + run: |- mkdir -p /tmp/gh-aw/agent gh api "repos/github/gh-aw/issues?state=open&labels=&per_page=30" \ --jq '[.[] | select(.labels | length == 0) | {number: .number, title: .title, body: .body}]' \ diff --git a/.github/workflows/aw-failure-investigator.lock.yml b/.github/workflows/aw-failure-investigator.lock.yml index acdf9ef612f..16c18f61439 100644 --- a/.github/workflows/aw-failure-investigator.lock.yml +++ b/.github/workflows/aw-failure-investigator.lock.yml @@ -556,7 +556,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Deterministic pre-fetch for failure analysis - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/failure-investigator\npython3 - <<'PY'\nimport json\nimport os\nimport subprocess\nfrom datetime import datetime, timedelta, timezone\nfrom pathlib import Path\nfrom urllib.parse import urlencode\n\nREPO = os.environ[\"GITHUB_REPOSITORY\"]\nOUT = \"/tmp/gh-aw/agent/failure-investigator/prefetch.json\"\nTRACKER_ID = \"aw-failure-investigator\"\nLOOKBACK_HOURS = 6\nFAILURE_CONCLUSIONS = {\"failure\", \"timed_out\", \"startup_failure\", \"cancelled\"}\nMAX_DISCOVERY_PAGES = 20\n# Most dominant signatures appear in the final 30-60 lines.\nMAX_LOG_TAIL_LINES = 50\n# Deep-dive budget: investigate at most this many distinct failed runs.\nMAX_FAILURES_TO_DETAIL = 5\nAGENTIC_WORKFLOW_PATHS = {\n f\".github/workflows/{path.name}\"\n for path in Path(\".github/workflows\").glob(\"*.lock.yml\")\n}\n\ndef cmd_display(args):\n return \" \".join(args)\n\ndef run_json(args):\n try:\n out = subprocess.check_output(args, text=True, stderr=subprocess.STDOUT)\n return json.loads(out)\n except subprocess.CalledProcessError as error:\n print(f\"Warning: command failed: {cmd_display(args)}\")\n print(error.output)\n return None\n except json.JSONDecodeError as error:\n print(f\"Warning: non-JSON output from command: {cmd_display(args)} ({error})\")\n return None\n except OSError as error:\n print(f\"Warning: could not execute command: {cmd_display(args)} ({error})\")\n return None\n\ndef run_text(args):\n try:\n return subprocess.check_output(args, text=True, stderr=subprocess.STDOUT)\n except subprocess.CalledProcessError as error:\n print(f\"Warning: command failed: {cmd_display(args)}\")\n print(error.output)\n return \"\"\n except OSError as error:\n print(f\"Warning: could not execute command: {cmd_display(args)} ({error})\")\n return \"\"\n\ndef run_api_json(endpoint, params):\n query = urlencode(params)\n return run_json([\"gh\", \"api\", f\"{endpoint}?{query}\"])\n\ndef is_failure_conclusion(conclusion):\n return (conclusion or \"\").lower() in FAILURE_CONCLUSIONS\n\ndef normalize_workflow_path(path):\n return (path or \"\").split(\"@\", 1)[0]\n\ndef is_agentic_workflow_path(path):\n workflow_path = normalize_workflow_path(path)\n if AGENTIC_WORKFLOW_PATHS:\n return workflow_path in AGENTIC_WORKFLOW_PATHS\n print(\"Warning: no local .lock.yml workflows found; falling back to workflow path suffix matching\")\n return workflow_path.endswith(\".lock.yml\")\n\ndef isoformat_z(dt):\n return dt.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace(\"+00:00\", \"Z\")\n\ndef list_failed_agentic_runs():\n created_since = isoformat_z(datetime.now(timezone.utc) - timedelta(hours=LOOKBACK_HOURS))\n page = 1\n failed_runs = []\n\n while True:\n response = run_api_json(\n f\"repos/{REPO}/actions/runs\",\n {\n \"exclude_pull_requests\": \"true\",\n \"status\": \"completed\",\n \"created\": f\">={created_since}\",\n \"per_page\": \"100\",\n \"page\": str(page),\n },\n ) or {}\n workflow_runs = response.get(\"workflow_runs\") or []\n if not workflow_runs:\n break\n\n for run in workflow_runs:\n workflow_path = normalize_workflow_path(run.get(\"path\"))\n if not is_agentic_workflow_path(workflow_path):\n continue\n if not is_failure_conclusion(run.get(\"conclusion\")):\n continue\n\n failed_runs.append(\n {\n \"run_id\": run.get(\"id\"),\n \"workflow_name\": run.get(\"name\"),\n \"workflow_path\": workflow_path,\n \"created_at\": run.get(\"created_at\"),\n \"status\": run.get(\"status\"),\n \"conclusion\": run.get(\"conclusion\"),\n \"url\": run.get(\"html_url\"),\n }\n )\n\n if len(workflow_runs) < 100:\n break\n if page >= MAX_DISCOVERY_PAGES:\n print(f\"Warning: reached pagination cap ({MAX_DISCOVERY_PAGES} pages) while listing workflow runs\")\n break\n page += 1\n\n failed_runs.sort(key=lambda run: run.get(\"created_at\") or \"\", reverse=True)\n return failed_runs\n\nfailed_runs = list_failed_agentic_runs()\n\n# Cap the number of runs to detail so the payload stays compact.\nfailure_details = []\nfor run in failed_runs[:MAX_FAILURES_TO_DETAIL]:\n run_id = run.get(\"run_id\")\n if not run_id:\n continue\n\n run_view = run_json(\n [\n \"gh\",\n \"run\",\n \"view\",\n str(run_id),\n \"--repo\",\n REPO,\n \"--json\",\n \"databaseId,url,name,workflowName,createdAt,conclusion,status,jobs\",\n ]\n )\n if not run_view:\n continue\n\n failed_job_names = []\n failed_steps = []\n truncated_error_logs = []\n agent_job_conclusion = None\n for job in run_view.get(\"jobs\", []):\n job_name = job.get(\"name\")\n job_conclusion = (job.get(\"conclusion\") or \"\").lower()\n if (job_name or \"\").lower() == \"agent\":\n agent_job_conclusion = job_conclusion or None\n\n if is_failure_conclusion(job_conclusion):\n if job_name:\n failed_job_names.append(job_name)\n for step in job.get(\"steps\", []):\n if is_failure_conclusion(step.get(\"conclusion\")):\n failed_steps.append(\n {\n \"job_id\": job.get(\"databaseId\"),\n \"job_name\": job_name,\n \"step_name\": step.get(\"name\"),\n }\n )\n\n job_id = job.get(\"databaseId\")\n if job_id:\n log_text = run_text(\n [\n \"gh\",\n \"run\",\n \"view\",\n str(run_id),\n \"--repo\",\n REPO,\n \"--job\",\n str(job_id),\n \"--log-failed\",\n ]\n )\n if log_text:\n tail_lines = log_text.splitlines()[-MAX_LOG_TAIL_LINES:]\n truncated_error_logs.append(\n {\n \"job_id\": job_id,\n \"job_name\": job_name,\n \"line_count\": len(tail_lines),\n \"tail_lines\": \"\\n\".join(tail_lines),\n }\n )\n\n failure_details.append(\n {\n \"run_id\": run_id,\n \"workflow_name\": run_view.get(\"workflowName\") or run_view.get(\"name\"),\n \"workflow_path\": run.get(\"workflow_path\"),\n \"url\": run_view.get(\"url\"),\n \"created_at\": run_view.get(\"createdAt\"),\n \"status\": run_view.get(\"status\"),\n \"conclusion\": run_view.get(\"conclusion\"),\n \"failed_job_names\": sorted(set(failed_job_names)),\n \"agent_job_conclusion\": agent_job_conclusion,\n \"failed_steps\": failed_steps,\n \"truncated_error_logs\": truncated_error_logs,\n }\n )\n\nexisting_tracking_issues = run_json(\n [\n \"gh\",\n \"issue\",\n \"list\",\n \"--repo\",\n REPO,\n \"--state\",\n \"open\",\n \"--search\",\n f\"gh-aw-tracker-id: {TRACKER_ID}\",\n \"--limit\",\n \"100\",\n \"--json\",\n \"number,title,state,url,labels,createdAt,updatedAt\",\n ]\n) or []\n\npayload = {\n \"generated_at\": datetime.now(timezone.utc).isoformat(),\n \"repository\": REPO,\n \"lookback_window\": f\"{LOOKBACK_HOURS}h\",\n \"failed_run_ids\": [run.get(\"run_id\") for run in failed_runs if run.get(\"run_id\")],\n \"failures\": failure_details,\n \"existing_tracking_issues\": existing_tracking_issues,\n}\n\nwith open(OUT, \"w\", encoding=\"utf-8\") as f:\n json.dump(payload, f, indent=2)\n f.write(\"\\n\")\n\nprint(f\"Wrote deterministic prefetch payload to {OUT}\")\nprint(f\"Failed runs in payload: {len(payload['failed_run_ids'])}\")\nprint(f\"Existing tracking issues in payload: {len(existing_tracking_issues)}\")\nPY\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/failure-investigator\npython3 - <<'PY'\nimport json\nimport os\nimport subprocess\nfrom datetime import datetime, timedelta, timezone\nfrom pathlib import Path\nfrom urllib.parse import urlencode\n\nREPO = os.environ[\"GITHUB_REPOSITORY\"]\nOUT = \"/tmp/gh-aw/agent/failure-investigator/prefetch.json\"\nTRACKER_ID = \"aw-failure-investigator\"\nLOOKBACK_HOURS = 6\nFAILURE_CONCLUSIONS = {\"failure\", \"timed_out\", \"startup_failure\", \"cancelled\"}\nMAX_DISCOVERY_PAGES = 20\n# Most dominant signatures appear in the final 30-60 lines.\nMAX_LOG_TAIL_LINES = 50\n# Deep-dive budget: investigate at most this many distinct failed runs.\nMAX_FAILURES_TO_DETAIL = 5\nAGENTIC_WORKFLOW_PATHS = {\n f\".github/workflows/{path.name}\"\n for path in Path(\".github/workflows\").glob(\"*.lock.yml\")\n}\n\ndef cmd_display(args):\n return \" \".join(args)\n\ndef run_json(args):\n try:\n out = subprocess.check_output(args, text=True, stderr=subprocess.STDOUT)\n return json.loads(out)\n except subprocess.CalledProcessError as error:\n print(f\"Warning: command failed: {cmd_display(args)}\")\n print(error.output)\n return None\n except json.JSONDecodeError as error:\n print(f\"Warning: non-JSON output from command: {cmd_display(args)} ({error})\")\n return None\n except OSError as error:\n print(f\"Warning: could not execute command: {cmd_display(args)} ({error})\")\n return None\n\ndef run_text(args):\n try:\n return subprocess.check_output(args, text=True, stderr=subprocess.STDOUT)\n except subprocess.CalledProcessError as error:\n print(f\"Warning: command failed: {cmd_display(args)}\")\n print(error.output)\n return \"\"\n except OSError as error:\n print(f\"Warning: could not execute command: {cmd_display(args)} ({error})\")\n return \"\"\n\ndef run_api_json(endpoint, params):\n query = urlencode(params)\n return run_json([\"gh\", \"api\", f\"{endpoint}?{query}\"])\n\ndef is_failure_conclusion(conclusion):\n return (conclusion or \"\").lower() in FAILURE_CONCLUSIONS\n\ndef normalize_workflow_path(path):\n return (path or \"\").split(\"@\", 1)[0]\n\ndef is_agentic_workflow_path(path):\n workflow_path = normalize_workflow_path(path)\n if AGENTIC_WORKFLOW_PATHS:\n return workflow_path in AGENTIC_WORKFLOW_PATHS\n print(\"Warning: no local .lock.yml workflows found; falling back to workflow path suffix matching\")\n return workflow_path.endswith(\".lock.yml\")\n\ndef isoformat_z(dt):\n return dt.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace(\"+00:00\", \"Z\")\n\ndef list_failed_agentic_runs():\n created_since = isoformat_z(datetime.now(timezone.utc) - timedelta(hours=LOOKBACK_HOURS))\n page = 1\n failed_runs = []\n\n while True:\n response = run_api_json(\n f\"repos/{REPO}/actions/runs\",\n {\n \"exclude_pull_requests\": \"true\",\n \"status\": \"completed\",\n \"created\": f\">={created_since}\",\n \"per_page\": \"100\",\n \"page\": str(page),\n },\n ) or {}\n workflow_runs = response.get(\"workflow_runs\") or []\n if not workflow_runs:\n break\n\n for run in workflow_runs:\n workflow_path = normalize_workflow_path(run.get(\"path\"))\n if not is_agentic_workflow_path(workflow_path):\n continue\n if not is_failure_conclusion(run.get(\"conclusion\")):\n continue\n\n failed_runs.append(\n {\n \"run_id\": run.get(\"id\"),\n \"workflow_name\": run.get(\"name\"),\n \"workflow_path\": workflow_path,\n \"created_at\": run.get(\"created_at\"),\n \"status\": run.get(\"status\"),\n \"conclusion\": run.get(\"conclusion\"),\n \"url\": run.get(\"html_url\"),\n }\n )\n\n if len(workflow_runs) < 100:\n break\n if page >= MAX_DISCOVERY_PAGES:\n print(f\"Warning: reached pagination cap ({MAX_DISCOVERY_PAGES} pages) while listing workflow runs\")\n break\n page += 1\n\n failed_runs.sort(key=lambda run: run.get(\"created_at\") or \"\", reverse=True)\n return failed_runs\n\nfailed_runs = list_failed_agentic_runs()\n\n# Cap the number of runs to detail so the payload stays compact.\nfailure_details = []\nfor run in failed_runs[:MAX_FAILURES_TO_DETAIL]:\n run_id = run.get(\"run_id\")\n if not run_id:\n continue\n\n run_view = run_json(\n [\n \"gh\",\n \"run\",\n \"view\",\n str(run_id),\n \"--repo\",\n REPO,\n \"--json\",\n \"databaseId,url,name,workflowName,createdAt,conclusion,status,jobs\",\n ]\n )\n if not run_view:\n continue\n\n failed_job_names = []\n failed_steps = []\n truncated_error_logs = []\n agent_job_conclusion = None\n for job in run_view.get(\"jobs\", []):\n job_name = job.get(\"name\")\n job_conclusion = (job.get(\"conclusion\") or \"\").lower()\n if (job_name or \"\").lower() == \"agent\":\n agent_job_conclusion = job_conclusion or None\n\n if is_failure_conclusion(job_conclusion):\n if job_name:\n failed_job_names.append(job_name)\n for step in job.get(\"steps\", []):\n if is_failure_conclusion(step.get(\"conclusion\")):\n failed_steps.append(\n {\n \"job_id\": job.get(\"databaseId\"),\n \"job_name\": job_name,\n \"step_name\": step.get(\"name\"),\n }\n )\n\n job_id = job.get(\"databaseId\")\n if job_id:\n log_text = run_text(\n [\n \"gh\",\n \"run\",\n \"view\",\n str(run_id),\n \"--repo\",\n REPO,\n \"--job\",\n str(job_id),\n \"--log-failed\",\n ]\n )\n if log_text:\n tail_lines = log_text.splitlines()[-MAX_LOG_TAIL_LINES:]\n truncated_error_logs.append(\n {\n \"job_id\": job_id,\n \"job_name\": job_name,\n \"line_count\": len(tail_lines),\n \"tail_lines\": \"\\n\".join(tail_lines),\n }\n )\n\n failure_details.append(\n {\n \"run_id\": run_id,\n \"workflow_name\": run_view.get(\"workflowName\") or run_view.get(\"name\"),\n \"workflow_path\": run.get(\"workflow_path\"),\n \"url\": run_view.get(\"url\"),\n \"created_at\": run_view.get(\"createdAt\"),\n \"status\": run_view.get(\"status\"),\n \"conclusion\": run_view.get(\"conclusion\"),\n \"failed_job_names\": sorted(set(failed_job_names)),\n \"agent_job_conclusion\": agent_job_conclusion,\n \"failed_steps\": failed_steps,\n \"truncated_error_logs\": truncated_error_logs,\n }\n )\n\nexisting_tracking_issues = run_json(\n [\n \"gh\",\n \"issue\",\n \"list\",\n \"--repo\",\n REPO,\n \"--state\",\n \"open\",\n \"--search\",\n f\"gh-aw-tracker-id: {TRACKER_ID}\",\n \"--limit\",\n \"100\",\n \"--json\",\n \"number,title,state,url,labels,createdAt,updatedAt\",\n ]\n) or []\n\npayload = {\n \"generated_at\": datetime.now(timezone.utc).isoformat(),\n \"repository\": REPO,\n \"lookback_window\": f\"{LOOKBACK_HOURS}h\",\n \"failed_run_ids\": [run.get(\"run_id\") for run in failed_runs if run.get(\"run_id\")],\n \"failures\": failure_details,\n \"existing_tracking_issues\": existing_tracking_issues,\n}\n\nwith open(OUT, \"w\", encoding=\"utf-8\") as f:\n json.dump(payload, f, indent=2)\n f.write(\"\\n\")\n\nprint(f\"Wrote deterministic prefetch payload to {OUT}\")\nprint(f\"Failed runs in payload: {len(payload['failed_run_ids'])}\")\nprint(f\"Existing tracking issues in payload: {len(existing_tracking_issues)}\")\nPY" # Cache configuration from frontmatter processed below - name: Failure investigator prefetch diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 3bd3507d4c3..0e5ca299442 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -587,7 +587,7 @@ jobs: REPO: ${{ github.repository }} if: github.event_name == 'pull_request' name: Fetch PR check run status - run: "set -e\nPR_DIR=\"/tmp/gh-aw/agent/ci-doctor/pr\"\nmkdir -p \"$PR_DIR\"\n\necho \"=== CI Doctor: Fetching check runs for PR #$PR_NUMBER (SHA: $HEAD_SHA) ===\"\n\n# Fetch all check runs for the PR head commit (paginated to handle >30 jobs)\ngh api --paginate \"repos/$REPO/commits/$HEAD_SHA/check-runs\" \\\n --jq '.check_runs[] | {id:.id, name:.name, status:.status, conclusion:.conclusion, html_url:.html_url}' \\\n | jq -s '.' \\\n > \"$PR_DIR/check-runs.json\"\n\nTOTAL=$(jq 'length' \"$PR_DIR/check-runs.json\")\nFAILED=$(jq '[.[] | select(.conclusion == \"failure\" or .conclusion == \"cancelled\" or .conclusion == \"timed_out\")] | length' \"$PR_DIR/check-runs.json\")\necho \"Found $TOTAL check run(s), $FAILED failing\"\n\n# Isolate the failing check runs\njq '[.[] | select(.conclusion == \"failure\" or .conclusion == \"cancelled\" or .conclusion == \"timed_out\")]' \\\n \"$PR_DIR/check-runs.json\" > \"$PR_DIR/failed-checks.json\"\n\n# Write a human-readable summary\nSUMMARY_FILE=\"$PR_DIR/summary.txt\"\n{\n echo \"=== CI Doctor PR Pre-Analysis ===\"\n echo \"PR: #$PR_NUMBER\"\n echo \"HEAD SHA: $HEAD_SHA\"\n echo \"Total check runs: $TOTAL\"\n echo \"Failing check runs: $FAILED\"\n echo \"\"\n echo \"All checks ($PR_DIR/check-runs.json):\"\n jq -r '.[] | \" \\(.conclusion // .status): \\(.name)\"' \"$PR_DIR/check-runs.json\"\n echo \"\"\n if [ \"$FAILED\" -gt 0 ]; then\n echo \"Failing checks ($PR_DIR/failed-checks.json):\"\n jq -r '.[] | \" - \\(.name) [\\(.conclusion)]: \\(.html_url)\"' \"$PR_DIR/failed-checks.json\"\n fi\n} | tee \"$SUMMARY_FILE\"\n\necho \"\"\necho \"✅ PR pre-analysis complete. Agent should start with $SUMMARY_FILE\"\n" + run: "set -e\nPR_DIR=\"/tmp/gh-aw/agent/ci-doctor/pr\"\nmkdir -p \"$PR_DIR\"\n\necho \"=== CI Doctor: Fetching check runs for PR #$PR_NUMBER (SHA: $HEAD_SHA) ===\"\n\n# Fetch all check runs for the PR head commit (paginated to handle >30 jobs)\ngh api --paginate \"repos/$REPO/commits/$HEAD_SHA/check-runs\" \\\n --jq '.check_runs[] | {id:.id, name:.name, status:.status, conclusion:.conclusion, html_url:.html_url}' \\\n | jq -s '.' \\\n > \"$PR_DIR/check-runs.json\"\n\nTOTAL=$(jq 'length' \"$PR_DIR/check-runs.json\")\nFAILED=$(jq '[.[] | select(.conclusion == \"failure\" or .conclusion == \"cancelled\" or .conclusion == \"timed_out\")] | length' \"$PR_DIR/check-runs.json\")\necho \"Found $TOTAL check run(s), $FAILED failing\"\n\n# Isolate the failing check runs\njq '[.[] | select(.conclusion == \"failure\" or .conclusion == \"cancelled\" or .conclusion == \"timed_out\")]' \\\n \"$PR_DIR/check-runs.json\" > \"$PR_DIR/failed-checks.json\"\n\n# Write a human-readable summary\nSUMMARY_FILE=\"$PR_DIR/summary.txt\"\n{\n echo \"=== CI Doctor PR Pre-Analysis ===\"\n echo \"PR: #$PR_NUMBER\"\n echo \"HEAD SHA: $HEAD_SHA\"\n echo \"Total check runs: $TOTAL\"\n echo \"Failing check runs: $FAILED\"\n echo \"\"\n echo \"All checks ($PR_DIR/check-runs.json):\"\n jq -r '.[] | \" \\(.conclusion // .status): \\(.name)\"' \"$PR_DIR/check-runs.json\"\n echo \"\"\n if [ \"$FAILED\" -gt 0 ]; then\n echo \"Failing checks ($PR_DIR/failed-checks.json):\"\n jq -r '.[] | \" - \\(.name) [\\(.conclusion)]: \\(.html_url)\"' \"$PR_DIR/failed-checks.json\"\n fi\n} | tee \"$SUMMARY_FILE\"\n\necho \"\"\necho \"✅ PR pre-analysis complete. Agent should start with $SUMMARY_FILE\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 930d93b1c18..08666ca4fd4 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -530,7 +530,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Build CLI and pre-collect help output - run: "set -euo pipefail\ncd /home/runner/work/gh-aw/gh-aw\nmake build\n\noutput_dir=\"/tmp/gh-aw/agent/help-output\"\nmkdir -p \"${output_dir}\"\nextract_commands='\n /^[[:space:]]+[[:alnum:]_-]+([[:space:]]|$)/ {\n cmd=$1\n gsub(/:$/, \"\", cmd)\n if (cmd != \"\" && cmd != \"Commands\") print cmd\n }\n'\n\n./gh-aw --help > \"${output_dir}/main.txt\"\nmapfile -t top_commands < <(awk \"${extract_commands}\" \"${output_dir}/main.txt\" | sort -u)\n\nfor cmd in \"${top_commands[@]}\"; do\n if ! ./gh-aw \"$cmd\" --help > \"${output_dir}/${cmd}.txt\" 2>&1; then\n echo \"warning: failed to collect help for '${cmd}'\" >&2\n continue\n fi\n mapfile -t subcommands < <(awk \"${extract_commands}\" \"${output_dir}/${cmd}.txt\" | sort -u)\n for sub in \"${subcommands[@]}\"; do\n if ! ./gh-aw \"$cmd\" \"$sub\" --help > \"${output_dir}/${cmd}-${sub}.txt\" 2>&1; then\n echo \"warning: failed to collect help for '${cmd} ${sub}'\" >&2\n fi\n done\ndone\n\nshopt -s nullglob\nhelp_files=(\"${output_dir}\"/*.txt)\nif [ ${#help_files[@]} -eq 0 ]; then\n echo \"No help output files were generated\" >&2\n exit 1\nfi\ncat \"${help_files[@]}\" > /tmp/gh-aw/agent/all-help.txt\nwc -l /tmp/gh-aw/agent/all-help.txt | awk '{print \"Pre-collected help lines:\", $1}'\n" + run: "set -euo pipefail\ncd /home/runner/work/gh-aw/gh-aw\nmake build\n\noutput_dir=\"/tmp/gh-aw/agent/help-output\"\nmkdir -p \"${output_dir}\"\nextract_commands='\n /^[[:space:]]+[[:alnum:]_-]+([[:space:]]|$)/ {\n cmd=$1\n gsub(/:$/, \"\", cmd)\n if (cmd != \"\" && cmd != \"Commands\") print cmd\n }\n'\n\n./gh-aw --help > \"${output_dir}/main.txt\"\nmapfile -t top_commands < <(awk \"${extract_commands}\" \"${output_dir}/main.txt\" | sort -u)\n\nfor cmd in \"${top_commands[@]}\"; do\n if ! ./gh-aw \"$cmd\" --help > \"${output_dir}/${cmd}.txt\" 2>&1; then\n echo \"warning: failed to collect help for '${cmd}'\" >&2\n continue\n fi\n mapfile -t subcommands < <(awk \"${extract_commands}\" \"${output_dir}/${cmd}.txt\" | sort -u)\n for sub in \"${subcommands[@]}\"; do\n if ! ./gh-aw \"$cmd\" \"$sub\" --help > \"${output_dir}/${cmd}-${sub}.txt\" 2>&1; then\n echo \"warning: failed to collect help for '${cmd} ${sub}'\" >&2\n fi\n done\ndone\n\nshopt -s nullglob\nhelp_files=(\"${output_dir}\"/*.txt)\nif [ ${#help_files[@]} -eq 0 ]; then\n echo \"No help output files were generated\" >&2\n exit 1\nfi\ncat \"${help_files[@]}\" > /tmp/gh-aw/agent/all-help.txt\nwc -l /tmp/gh-aw/agent/all-help.txt | awk '{print \"Pre-collected help lines:\", $1}'" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index c79d3d5242a..ed42078ff2c 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -501,7 +501,7 @@ jobs: EXPR_GITHUB_REPOSITORY: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Prepare workflow history summary (deterministic) - run: "set -euo pipefail\n: \"${GH_TOKEN:?GH_TOKEN is required for gh CLI queries}\"\nmkdir -p /tmp/gh-aw/agent/code-simplifier\n\ngh api \"repos/$EXPR_GITHUB_REPOSITORY/actions/workflows/code-simplifier.lock.yml/runs?per_page=30\" \\\n > /tmp/gh-aw/agent/code-simplifier/workflow-runs.json || echo '{\"workflow_runs\":[]}' > /tmp/gh-aw/agent/code-simplifier/workflow-runs.json\n\njq '\n .workflow_runs // []\n | {\n sample_size: length,\n success_count: (map(select(.conclusion == \"success\")) | length),\n failure_count: (map(select(.conclusion == \"failure\")) | length),\n cancelled_count: (map(select(.conclusion == \"cancelled\")) | length),\n recent_failures: (map(select(.conclusion == \"failure\")) | map({id,created_at,html_url}) | .[0:5]),\n deterministic_candidates: [\n {\n name: \"recent PR and commit discovery\",\n move_to: \"steps\",\n reason: \"This data is fetched every run and can be pre-computed once in deterministic shell steps.\"\n },\n {\n name: \"changed-file filtering\",\n move_to: \"steps\",\n reason: \"Extension and test/generated-file filtering is deterministic and should not consume AI tokens.\"\n },\n {\n name: \"workflow run history aggregation\",\n move_to: \"steps\",\n reason: \"Historical run metadata can be summarized via jq before entering agent context.\"\n }\n ]\n }\n' /tmp/gh-aw/agent/code-simplifier/workflow-runs.json > /tmp/gh-aw/agent/code-simplifier/history-summary.json\n" + run: "set -euo pipefail\n: \"${GH_TOKEN:?GH_TOKEN is required for gh CLI queries}\"\nmkdir -p /tmp/gh-aw/agent/code-simplifier\n\ngh api \"repos/$EXPR_GITHUB_REPOSITORY/actions/workflows/code-simplifier.lock.yml/runs?per_page=30\" \\\n > /tmp/gh-aw/agent/code-simplifier/workflow-runs.json || echo '{\"workflow_runs\":[]}' > /tmp/gh-aw/agent/code-simplifier/workflow-runs.json\n\njq '\n .workflow_runs // []\n | {\n sample_size: length,\n success_count: (map(select(.conclusion == \"success\")) | length),\n failure_count: (map(select(.conclusion == \"failure\")) | length),\n cancelled_count: (map(select(.conclusion == \"cancelled\")) | length),\n recent_failures: (map(select(.conclusion == \"failure\")) | map({id,created_at,html_url}) | .[0:5]),\n deterministic_candidates: [\n {\n name: \"recent PR and commit discovery\",\n move_to: \"steps\",\n reason: \"This data is fetched every run and can be pre-computed once in deterministic shell steps.\"\n },\n {\n name: \"changed-file filtering\",\n move_to: \"steps\",\n reason: \"Extension and test/generated-file filtering is deterministic and should not consume AI tokens.\"\n },\n {\n name: \"workflow run history aggregation\",\n move_to: \"steps\",\n reason: \"Historical run metadata can be summarized via jq before entering agent context.\"\n }\n ]\n }\n' /tmp/gh-aw/agent/code-simplifier/workflow-runs.json > /tmp/gh-aw/agent/code-simplifier/history-summary.json" - name: Configure Git credentials env: diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index 0112e0d7c6c..094049c3de1 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -497,7 +497,7 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Fetch and filter PRs - run: | + run: |- # Fetch open PRs from the target repository opened in the last 24 hours SINCE=$(date -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \ || date -v-24H '+%Y-%m-%dT%H:%M:%SZ') diff --git a/.github/workflows/copilot-centralization-drilldown.lock.yml b/.github/workflows/copilot-centralization-drilldown.lock.yml index 4cb96926c59..910370bd650 100644 --- a/.github/workflows/copilot-centralization-drilldown.lock.yml +++ b/.github/workflows/copilot-centralization-drilldown.lock.yml @@ -460,7 +460,7 @@ jobs: SAMPLE_PROMPT: ${{ inputs.sample_prompt }} TARGET_KIND: ${{ inputs.target_kind }} name: Normalize candidate input - run: "set -euo pipefail\nGH_AW_SAFE_OUTPUTS=\"${GH_AW_SAFE_OUTPUTS:-${RUNNER_TEMP:-/tmp}/gh-aw/safeoutputs/outputs.jsonl}\"\nmkdir -p /tmp/gh-aw/data\n\nif [ -n \"${CANDIDATE_JSON}\" ]; then\n printf '%s' \"$CANDIDATE_JSON\" | jq '.' > /tmp/gh-aw/data/candidate-raw.json\nelse\n jq -n \\\n --arg title \"$CANDIDATE_TITLE\" \\\n --arg recommendation_kind \"$RECOMMENDATION_KIND\" \\\n --arg sample_prompt \"$SAMPLE_PROMPT\" \\\n --arg evidence_summary \"$EVIDENCE_SUMMARY\" \\\n '{\n title: $title,\n recommendation_kind: $recommendation_kind,\n sample_prompt: (if $sample_prompt == \"\" then null else $sample_prompt end),\n evidence_summary: (if $evidence_summary == \"\" then null else $evidence_summary end)\n }' > /tmp/gh-aw/data/candidate-raw.json\nfi\n\ncandidate_title=\"$(jq -r '.title // empty' /tmp/gh-aw/data/candidate-raw.json)\"\nsample_prompt=\"$(jq -r '.sample_prompt // empty' /tmp/gh-aw/data/candidate-raw.json)\"\nevidence_summary=\"$(jq -r '.evidence_summary // empty' /tmp/gh-aw/data/candidate-raw.json)\"\n\nif [ -z \"$candidate_title\" ]; then\n printf '%s\\n' '{\"type\":\"noop\",\"message\":\"No candidate title was provided for drilldown.\"}' >> \"$GH_AW_SAFE_OUTPUTS\"\n exit 0\nfi\n\nslug=\"$(printf '%s' \"$candidate_title\" \\\n | tr '[:upper:]' '[:lower:]' \\\n | sed 's/[^a-z0-9]/-/g' \\\n | sed 's/-\\{2,\\}/-/g' \\\n | sed 's/^-//' \\\n | sed 's/-$//' \\\n | cut -c1-48)\"\n\nif [ -z \"$slug\" ]; then\n slug=\"candidate\"\nfi\n\nresolved_target_kind=\"$TARGET_KIND\"\nif [ \"$resolved_target_kind\" = \"\" ] || [ \"$resolved_target_kind\" = \"auto\" ]; then\n case \"$RECOMMENDATION_KIND\" in\n continuous_workflow)\n resolved_target_kind=\"workflow\"\n ;;\n shared_prompt_or_chatops)\n resolved_target_kind=\"shared_prompt\"\n ;;\n keep_ad_hoc_but_standardize)\n resolved_target_kind=\"playbook\"\n ;;\n *)\n resolved_target_kind=\"workflow\"\n ;;\n esac\nfi\n\nsample_len=${#sample_prompt}\nevidence_len=${#evidence_summary}\ncandidate_strength=\"weak\"\nif [ \"$sample_len\" -ge 24 ] || [ \"$evidence_len\" -ge 40 ] || [ -n \"$CANDIDATE_JSON\" ]; then\n candidate_strength=\"strong\"\nfi\n\njq -n \\\n --arg title \"$candidate_title\" \\\n --arg slug \"$slug\" \\\n --arg recommendation_kind \"$RECOMMENDATION_KIND\" \\\n --arg resolved_target_kind \"$resolved_target_kind\" \\\n --arg sample_prompt \"$sample_prompt\" \\\n --arg evidence_summary \"$evidence_summary\" \\\n --arg candidate_strength \"$candidate_strength\" \\\n --slurpfile raw /tmp/gh-aw/data/candidate-raw.json '\n {\n title: $title,\n slug: $slug,\n recommendation_kind: $recommendation_kind,\n resolved_target_kind: $resolved_target_kind,\n candidate_strength: $candidate_strength,\n sample_prompt: (if $sample_prompt == \"\" then null else $sample_prompt end),\n evidence_summary: (if $evidence_summary == \"\" then null else $evidence_summary end),\n raw_candidate: $raw[0]\n }\n ' > /tmp/gh-aw/data/candidate.json\n\njq -n \\\n --arg resolved_target_kind \"$resolved_target_kind\" \\\n --arg slug \"$slug\" '\n {\n target_path: (\n if $resolved_target_kind == \"workflow\" then \".github/workflows/\" + $slug + \".md\"\n elif $resolved_target_kind == \"shared_prompt\" then \".github/workflows/shared/\" + $slug + \".md\"\n else \".github/workflows/shared/\" + $slug + \"-playbook.md\"\n end\n ),\n trigger_hint: (\n if $resolved_target_kind == \"workflow\" then \"Prefer workflow_dispatch first; choose schedule or event triggers only when repeated evidence justifies automation.\"\n elif $resolved_target_kind == \"shared_prompt\" then \"Prefer a reusable prompt or shared workflow component before full automation.\"\n else \"Keep this human-in-the-loop and produce a reusable playbook or prompt template.\"\n end\n ),\n implementation_bias: \"Prefer the smallest durable artifact that reduces repeated prompting without widening scope.\",\n report_style: \"Use issue sections with visible summary and one fenced draft block.\"\n }\n ' > /tmp/gh-aw/data/derived-plan.json\n" + run: "set -euo pipefail\nGH_AW_SAFE_OUTPUTS=\"${GH_AW_SAFE_OUTPUTS:-${RUNNER_TEMP:-/tmp}/gh-aw/safeoutputs/outputs.jsonl}\"\nmkdir -p /tmp/gh-aw/data\n\nif [ -n \"${CANDIDATE_JSON}\" ]; then\n printf '%s' \"$CANDIDATE_JSON\" | jq '.' > /tmp/gh-aw/data/candidate-raw.json\nelse\n jq -n \\\n --arg title \"$CANDIDATE_TITLE\" \\\n --arg recommendation_kind \"$RECOMMENDATION_KIND\" \\\n --arg sample_prompt \"$SAMPLE_PROMPT\" \\\n --arg evidence_summary \"$EVIDENCE_SUMMARY\" \\\n '{\n title: $title,\n recommendation_kind: $recommendation_kind,\n sample_prompt: (if $sample_prompt == \"\" then null else $sample_prompt end),\n evidence_summary: (if $evidence_summary == \"\" then null else $evidence_summary end)\n }' > /tmp/gh-aw/data/candidate-raw.json\nfi\n\ncandidate_title=\"$(jq -r '.title // empty' /tmp/gh-aw/data/candidate-raw.json)\"\nsample_prompt=\"$(jq -r '.sample_prompt // empty' /tmp/gh-aw/data/candidate-raw.json)\"\nevidence_summary=\"$(jq -r '.evidence_summary // empty' /tmp/gh-aw/data/candidate-raw.json)\"\n\nif [ -z \"$candidate_title\" ]; then\n printf '%s\\n' '{\"type\":\"noop\",\"message\":\"No candidate title was provided for drilldown.\"}' >> \"$GH_AW_SAFE_OUTPUTS\"\n exit 0\nfi\n\nslug=\"$(printf '%s' \"$candidate_title\" \\\n | tr '[:upper:]' '[:lower:]' \\\n | sed 's/[^a-z0-9]/-/g' \\\n | sed 's/-\\{2,\\}/-/g' \\\n | sed 's/^-//' \\\n | sed 's/-$//' \\\n | cut -c1-48)\"\n\nif [ -z \"$slug\" ]; then\n slug=\"candidate\"\nfi\n\nresolved_target_kind=\"$TARGET_KIND\"\nif [ \"$resolved_target_kind\" = \"\" ] || [ \"$resolved_target_kind\" = \"auto\" ]; then\n case \"$RECOMMENDATION_KIND\" in\n continuous_workflow)\n resolved_target_kind=\"workflow\"\n ;;\n shared_prompt_or_chatops)\n resolved_target_kind=\"shared_prompt\"\n ;;\n keep_ad_hoc_but_standardize)\n resolved_target_kind=\"playbook\"\n ;;\n *)\n resolved_target_kind=\"workflow\"\n ;;\n esac\nfi\n\nsample_len=${#sample_prompt}\nevidence_len=${#evidence_summary}\ncandidate_strength=\"weak\"\nif [ \"$sample_len\" -ge 24 ] || [ \"$evidence_len\" -ge 40 ] || [ -n \"$CANDIDATE_JSON\" ]; then\n candidate_strength=\"strong\"\nfi\n\njq -n \\\n --arg title \"$candidate_title\" \\\n --arg slug \"$slug\" \\\n --arg recommendation_kind \"$RECOMMENDATION_KIND\" \\\n --arg resolved_target_kind \"$resolved_target_kind\" \\\n --arg sample_prompt \"$sample_prompt\" \\\n --arg evidence_summary \"$evidence_summary\" \\\n --arg candidate_strength \"$candidate_strength\" \\\n --slurpfile raw /tmp/gh-aw/data/candidate-raw.json '\n {\n title: $title,\n slug: $slug,\n recommendation_kind: $recommendation_kind,\n resolved_target_kind: $resolved_target_kind,\n candidate_strength: $candidate_strength,\n sample_prompt: (if $sample_prompt == \"\" then null else $sample_prompt end),\n evidence_summary: (if $evidence_summary == \"\" then null else $evidence_summary end),\n raw_candidate: $raw[0]\n }\n ' > /tmp/gh-aw/data/candidate.json\n\njq -n \\\n --arg resolved_target_kind \"$resolved_target_kind\" \\\n --arg slug \"$slug\" '\n {\n target_path: (\n if $resolved_target_kind == \"workflow\" then \".github/workflows/\" + $slug + \".md\"\n elif $resolved_target_kind == \"shared_prompt\" then \".github/workflows/shared/\" + $slug + \".md\"\n else \".github/workflows/shared/\" + $slug + \"-playbook.md\"\n end\n ),\n trigger_hint: (\n if $resolved_target_kind == \"workflow\" then \"Prefer workflow_dispatch first; choose schedule or event triggers only when repeated evidence justifies automation.\"\n elif $resolved_target_kind == \"shared_prompt\" then \"Prefer a reusable prompt or shared workflow component before full automation.\"\n else \"Keep this human-in-the-loop and produce a reusable playbook or prompt template.\"\n end\n ),\n implementation_bias: \"Prefer the smallest durable artifact that reduces repeated prompting without widening scope.\",\n report_style: \"Use issue sections with visible summary and one fenced draft block.\"\n }\n ' > /tmp/gh-aw/data/derived-plan.json" - name: Configure Git credentials env: diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 67ff903d5b0..947b21d9824 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -537,7 +537,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Fetch PR comments for detailed analysis - run: "# Create comments directory\nmkdir -p /tmp/gh-aw/agent/pr-comments\n\n# Fetch detailed comments for each PR from the pre-fetched data\nPR_COUNT=$(jq 'length' /tmp/gh-aw/agent/pr-data/copilot-prs.json)\necho \"Fetching comments for $PR_COUNT PRs...\"\n\njq -r '.[].number' /tmp/gh-aw/agent/pr-data/copilot-prs.json | while read -r PR_NUM; do\n echo \"Fetching comments for PR #${PR_NUM}\"\n gh pr view \"${PR_NUM}\" \\\n --json comments,reviews,reviewComments \\\n > \"/tmp/gh-aw/agent/pr-comments/pr-${PR_NUM}.json\" 2>/dev/null || echo \"{}\" > \"/tmp/gh-aw/agent/pr-comments/pr-${PR_NUM}.json\"\n sleep 0.5 # Rate limiting\ndone\n\necho \"Comment data saved to /tmp/gh-aw/agent/pr-comments/\"\n" + run: "# Create comments directory\nmkdir -p /tmp/gh-aw/agent/pr-comments\n\n# Fetch detailed comments for each PR from the pre-fetched data\nPR_COUNT=$(jq 'length' /tmp/gh-aw/agent/pr-data/copilot-prs.json)\necho \"Fetching comments for $PR_COUNT PRs...\"\n\njq -r '.[].number' /tmp/gh-aw/agent/pr-data/copilot-prs.json | while read -r PR_NUM; do\n echo \"Fetching comments for PR #${PR_NUM}\"\n gh pr view \"${PR_NUM}\" \\\n --json comments,reviews,reviewComments \\\n > \"/tmp/gh-aw/agent/pr-comments/pr-${PR_NUM}.json\" 2>/dev/null || echo \"{}\" > \"/tmp/gh-aw/agent/pr-comments/pr-${PR_NUM}.json\"\n sleep 0.5 # Rate limiting\ndone\n\necho \"Comment data saved to /tmp/gh-aw/agent/pr-comments/\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/daily-ambient-context-optimizer.lock.yml b/.github/workflows/daily-ambient-context-optimizer.lock.yml index a32ef0424a2..70f2f6fa5ca 100644 --- a/.github/workflows/daily-ambient-context-optimizer.lock.yml +++ b/.github/workflows/daily-ambient-context-optimizer.lock.yml @@ -521,7 +521,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} - script: "const fs = require('fs');\n\nconst { owner, repo } = context.repo;\nconst OPTIMIZER_PATTERNS = [\n 'copilot/ambient-context',\n 'copilot/daily-ambient-context',\n 'ambient-context-optim',\n];\n\nconst cutoff7d = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n\nconst allPrs = await github.paginate(github.rest.pulls.list, {\n owner, repo, state: 'all', per_page: 100,\n});\n\nconst optPrs7d = allPrs.filter((pr) => {\n const branch = pr.head?.ref || '';\n return (\n OPTIMIZER_PATTERNS.some((p) => branch.includes(p)) &&\n new Date(pr.created_at) >= cutoff7d\n );\n});\n\nconst merged7d = optPrs7d.filter((pr) => pr.merged_at);\nconst closed7d = optPrs7d.filter((pr) => !pr.merged_at && pr.closed_at);\nconst totalSettled = merged7d.length + closed7d.length;\n\n// close_rate is null when sample < 3 to avoid misleading zero reads\nconst closeRate = totalSettled >= 3 ? closed7d.length / totalSettled : null;\nconst autoPause = closeRate !== null && closeRate >= 0.30;\n\nconst rateData = {\n merged_7d: merged7d.length,\n closed_7d: closed7d.length,\n total_settled_7d: totalSettled,\n close_rate: closeRate !== null ? Math.round(closeRate * 1000) / 1000 : null,\n auto_pause: autoPause,\n note: 'close_rate is null when total_settled_7d < 3 (insufficient data for auto-pause)',\n};\n\nfs.writeFileSync('/tmp/gh-aw/ambient-context/pr-close-rate.json', JSON.stringify(rateData, null, 2));\n\nconst rateStr = closeRate !== null ? `${Math.round(closeRate * 100)}%` : 'N/A (< 3 settled PRs)';\ncore.info(`PR close-rate (7d): ${rateStr} (${closed7d.length} closed, ${merged7d.length} merged)${autoPause ? ' — AUTO-PAUSE ACTIVE' : ''}`);\n" + script: "const fs = require('fs');\n\nconst { owner, repo } = context.repo;\nconst OPTIMIZER_PATTERNS = [\n 'copilot/ambient-context',\n 'copilot/daily-ambient-context',\n 'ambient-context-optim',\n];\n\nconst cutoff7d = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n\nconst allPrs = await github.paginate(github.rest.pulls.list, {\n owner, repo, state: 'all', per_page: 100,\n});\n\nconst optPrs7d = allPrs.filter((pr) => {\n const branch = pr.head?.ref || '';\n return (\n OPTIMIZER_PATTERNS.some((p) => branch.includes(p)) &&\n new Date(pr.created_at) >= cutoff7d\n );\n});\n\nconst merged7d = optPrs7d.filter((pr) => pr.merged_at);\nconst closed7d = optPrs7d.filter((pr) => !pr.merged_at && pr.closed_at);\nconst totalSettled = merged7d.length + closed7d.length;\n\n// close_rate is null when sample < 3 to avoid misleading zero reads\nconst closeRate = totalSettled >= 3 ? closed7d.length / totalSettled : null;\nconst autoPause = closeRate !== null && closeRate >= 0.30;\n\nconst rateData = {\n merged_7d: merged7d.length,\n closed_7d: closed7d.length,\n total_settled_7d: totalSettled,\n close_rate: closeRate !== null ? Math.round(closeRate * 1000) / 1000 : null,\n auto_pause: autoPause,\n note: 'close_rate is null when total_settled_7d < 3 (insufficient data for auto-pause)',\n};\n\nfs.writeFileSync('/tmp/gh-aw/ambient-context/pr-close-rate.json', JSON.stringify(rateData, null, 2));\n\nconst rateStr = closeRate !== null ? `${Math.round(closeRate * 100)}%` : 'N/A (< 3 settled PRs)';\ncore.info(`PR close-rate (7d): ${rateStr} (${closed7d.length} closed, ${merged7d.length} merged)${autoPause ? ' — AUTO-PAUSE ACTIVE' : ''}`);" - name: Configure Git credentials env: diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index a840bc33df7..a9a473a9f14 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -546,7 +546,7 @@ jobs: name: Compute deterministic attributions (Tier 0, 1, 2) run: "# Tier 0: COMPLETED issues (direct contributions, no PR needed)\njq '[.[] | select(.stateReason == \"COMPLETED\") |\n . + {tier: 0, attribution_type: \"direct issue\", closing_prs: []}]' \\\n /tmp/gh-aw/agent/community-data/community_issues.json \\\n > /tmp/gh-aw/agent/community-data/tier0_attributed.json\n\nT0=$(jq length /tmp/gh-aw/agent/community-data/tier0_attributed.json)\necho \"Tier 0 (direct issue — COMPLETED): $T0\"\n\n# Tier 1: native GitHub close references (exclude Tier 0 issues)\njq --slurpfile issues /tmp/gh-aw/agent/community-data/community_issues.json \\\n --slurpfile t0 /tmp/gh-aw/agent/community-data/tier0_attributed.json '\n ($t0[0] | map(.number) | map(tostring)) as $t0_keys |\n ($issues[0] | map(.number | tostring)) as $issue_keys |\n to_entries |\n map(select(\n .key as $k |\n ($issue_keys | index($k) != null) and\n ($t0_keys | index($k) == null)\n )) |\n map(.key as $k | .value as $prs |\n ($issues[0] | map(select(.number | tostring == $k))[0]) +\n {tier: 1, attribution_type: \"resolved by PR\", closing_prs: $prs}\n )\n' /tmp/gh-aw/agent/community-data/closing_refs_by_issue.json \\\n > /tmp/gh-aw/agent/community-data/tier1_attributed.json 2>/dev/null \\\n || echo \"[]\" > /tmp/gh-aw/agent/community-data/tier1_attributed.json\n\nT1=$(jq length /tmp/gh-aw/agent/community-data/tier1_attributed.json)\necho \"Tier 1 (native close refs): $T1\"\n\n# Tier 2: PR body keyword matching (exclude Tier 0 and Tier 1 issues)\nKW_ISSUES=$(jq -r '.[].body // \"\"' /tmp/gh-aw/agent/community-data/pull_requests.json \\\n | grep -oP '(?i)(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s*(?:github/gh-aw#|#)\\K[0-9]+' 2>/dev/null \\\n | sort -u | jq -R 'tonumber' | jq -s 'sort | unique' 2>/dev/null \\\n || echo \"[]\")\n\njq --argjson kw \"$KW_ISSUES\" \\\n --slurpfile t0 /tmp/gh-aw/agent/community-data/tier0_attributed.json \\\n --slurpfile t1 /tmp/gh-aw/agent/community-data/tier1_attributed.json '\n ($t0[0] | map(.number)) as $t0_nums |\n ($t1[0] | map(.number)) as $t1_nums |\n [.[] |\n select(\n .number as $n |\n ($kw | index($n) != null) and\n ($t0_nums | index($n) == null) and\n ($t1_nums | index($n) == null)\n ) |\n . + {tier: 2, attribution_type: \"resolved by PR\", closing_prs: []}\n ]\n' /tmp/gh-aw/agent/community-data/community_issues.json \\\n > /tmp/gh-aw/agent/community-data/tier2_attributed.json 2>/dev/null \\\n || echo \"[]\" > /tmp/gh-aw/agent/community-data/tier2_attributed.json\n\nT2=$(jq length /tmp/gh-aw/agent/community-data/tier2_attributed.json)\necho \"Tier 2 (PR body keywords): $T2\"\n\n# Combine Tier 0 + 1 + 2 into pre_attributed.json\njq -n \\\n --slurpfile t0 /tmp/gh-aw/agent/community-data/tier0_attributed.json \\\n --slurpfile t1 /tmp/gh-aw/agent/community-data/tier1_attributed.json \\\n --slurpfile t2 /tmp/gh-aw/agent/community-data/tier2_attributed.json \\\n '$t0[0] + $t1[0] + $t2[0]' \\\n > /tmp/gh-aw/agent/community-data/pre_attributed.json\n\nTOTAL=$(jq length /tmp/gh-aw/agent/community-data/pre_attributed.json)\necho \"\"\necho \"Pre-attributed: $TOTAL issues (Tier 0: $T0, Tier 1: $T1, Tier 2: $T2)\"\n\n# Compute Tier 3+ candidates (closed in window, not yet pre-attributed)\njq --slurpfile pre /tmp/gh-aw/agent/community-data/pre_attributed.json '\n ($pre[0] | map(.number)) as $attributed |\n [.[] | select(.number as $n | $attributed | index($n) == null)]\n' /tmp/gh-aw/agent/community-data/community_issues_closed_in_window.json \\\n > /tmp/gh-aw/agent/community-data/tier3_candidates.json 2>/dev/null \\\n || echo \"[]\" > /tmp/gh-aw/agent/community-data/tier3_candidates.json\n\nT3=$(jq length /tmp/gh-aw/agent/community-data/tier3_candidates.json)\necho \"Tier 3+ candidates (agent lookup needed): $T3\"\n\n# Cap Tier 3 to 5 per run — prevents runaway API call loops when there are\n# many unlinked issues. Remaining candidates will be processed in future runs.\njq '.[0:5]' /tmp/gh-aw/agent/community-data/tier3_candidates.json \\\n > /tmp/gh-aw/agent/community-data/tier3_candidates_capped.json\nT3_CAPPED=$(jq length /tmp/gh-aw/agent/community-data/tier3_candidates_capped.json)\nif [ \"$T3_CAPPED\" -lt \"$T3\" ]; then\n echo \"⚠ Capped Tier 3 lookups: processing $T3_CAPPED of $T3 candidates this run\"\nfi\necho \"\"\necho \"Data available in /tmp/gh-aw/agent/community-data/:\"\necho \" pre_attributed.json — Tier 0+1+2 confirmed attributions\"\necho \" tier3_candidates_capped.json — up to 5 issues needing Tier 3 agent lookup (this run)\"\necho \" tier3_candidates.json — full list of Tier 3+ candidates (for reference)\"\n" - name: Format attribution data for agent - run: "DATA_DIR=/tmp/gh-aw/agent/community-data\n\n# Group Tier 0-2 attributions by author for agent-ready consumption.\n# Produces a structured JSON the agent can read with `cat` — no jq needed.\njq '\n group_by(.author.login) |\n sort_by(.[0].author.login | ascii_downcase) |\n map({\n author: .[0].author.login,\n count: length,\n issues: (sort_by(-.number))\n })\n' \"$DATA_DIR/pre_attributed.json\" \\\n > \"$DATA_DIR/attribution_by_author.json\" 2>/dev/null \\\n || echo \"[]\" > \"$DATA_DIR/attribution_by_author.json\"\n\nAUTHOR_COUNT=$(jq length \"$DATA_DIR/attribution_by_author.json\")\nISSUE_COUNT=$(jq length \"$DATA_DIR/pre_attributed.json\")\necho \"✓ Grouped: $AUTHOR_COUNT authors, $ISSUE_COUNT issues\"\n\n# Generate the pre-formatted README community section (Tier 0-2 only).\n# The agent reads this file directly, appends Tier 3 results, and inserts\n# the result into README.md — no bash data-processing required.\n{\n REPO_URL=\"https://github.com/${GITHUB_REPOSITORY:-github/gh-aw}\"\n SEARCH_BASE=\"${REPO_URL}/issues?q=is%3Aissue+is%3Aclosed+label%3Acommunity+author%3A\"\n echo \"## 🌍 Community Contributions\"\n echo \"\"\n echo \"Community members whose issues were resolved — updated automatically.\"\n echo \"\"\n jq -r --arg base \"$SEARCH_BASE\" '\n .[] |\n \"[@\\(.author) (\\(.count))](\\($base + .author))\"\n ' \"$DATA_DIR/attribution_by_author.json\"\n echo \"\"\n} > \"$DATA_DIR/readme_community_section_tier012.md\"\n\necho \"✓ Generated readme_community_section_tier012.md\"\necho \"\"\necho \"Data available in $DATA_DIR/:\"\necho \" attribution_by_author.json — Tier 0-2 issues grouped by author (agent-ready)\"\necho \" readme_community_section_tier012.md — pre-formatted README section (Tier 0-2 only)\"\n" + run: "DATA_DIR=/tmp/gh-aw/agent/community-data\n\n# Group Tier 0-2 attributions by author for agent-ready consumption.\n# Produces a structured JSON the agent can read with `cat` — no jq needed.\njq '\n group_by(.author.login) |\n sort_by(.[0].author.login | ascii_downcase) |\n map({\n author: .[0].author.login,\n count: length,\n issues: (sort_by(-.number))\n })\n' \"$DATA_DIR/pre_attributed.json\" \\\n > \"$DATA_DIR/attribution_by_author.json\" 2>/dev/null \\\n || echo \"[]\" > \"$DATA_DIR/attribution_by_author.json\"\n\nAUTHOR_COUNT=$(jq length \"$DATA_DIR/attribution_by_author.json\")\nISSUE_COUNT=$(jq length \"$DATA_DIR/pre_attributed.json\")\necho \"✓ Grouped: $AUTHOR_COUNT authors, $ISSUE_COUNT issues\"\n\n# Generate the pre-formatted README community section (Tier 0-2 only).\n# The agent reads this file directly, appends Tier 3 results, and inserts\n# the result into README.md — no bash data-processing required.\n{\n REPO_URL=\"https://github.com/${GITHUB_REPOSITORY:-github/gh-aw}\"\n SEARCH_BASE=\"${REPO_URL}/issues?q=is%3Aissue+is%3Aclosed+label%3Acommunity+author%3A\"\n echo \"## 🌍 Community Contributions\"\n echo \"\"\n echo \"Community members whose issues were resolved — updated automatically.\"\n echo \"\"\n jq -r --arg base \"$SEARCH_BASE\" '\n .[] |\n \"[@\\(.author) (\\(.count))](\\($base + .author))\"\n ' \"$DATA_DIR/attribution_by_author.json\"\n echo \"\"\n} > \"$DATA_DIR/readme_community_section_tier012.md\"\n\necho \"✓ Generated readme_community_section_tier012.md\"\necho \"\"\necho \"Data available in $DATA_DIR/:\"\necho \" attribution_by_author.json — Tier 0-2 issues grouped by author (agent-ready)\"\necho \" readme_community_section_tier012.md — pre-formatted README section (Tier 0-2 only)\"" # Repo memory git-based storage configuration from frontmatter processed below - name: Clone wiki-memory branch (default) diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index eca14f3a017..16cc64a7a7c 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -576,7 +576,7 @@ jobs: - env: EXPR_GITHUB_RUN_ID: ${{ github.run_id }} name: Wait for server readiness - run: "PID_FILE=\"/tmp/gh-aw/agent/docs-server-$EXPR_GITHUB_RUN_ID.pid\"\nLOG_FILE=\"/tmp/gh-aw/agent/docs-server-$EXPR_GITHUB_RUN_ID.log\"\nMAX_WAIT=135 # Maximum 135 seconds wait time\nWAITED=0\nuntil curl -sf http://localhost:4321/gh-aw/ > /dev/null 2>&1; do\n # Check if the server process has already died\n if [ -f \"$PID_FILE\" ] && ! kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then\n echo \"::error::Documentation server process died before becoming ready. Server log:\"\n cat \"$LOG_FILE\"\n exit 1\n fi\n WAITED=$((WAITED + 3))\n if [ $WAITED -ge $MAX_WAIT ]; then\n echo \"::error::Documentation server did not start after ${MAX_WAIT}s. Server log:\"\n cat \"$LOG_FILE\"\n exit 1\n fi\n echo \"Waiting for server... ($WAITED/${MAX_WAIT}s)\"\n sleep 3\ndone\necho \"Server ready at http://localhost:4321/gh-aw/!\"\n" + run: "PID_FILE=\"/tmp/gh-aw/agent/docs-server-$EXPR_GITHUB_RUN_ID.pid\"\nLOG_FILE=\"/tmp/gh-aw/agent/docs-server-$EXPR_GITHUB_RUN_ID.log\"\nMAX_WAIT=135 # Maximum 135 seconds wait time\nWAITED=0\nuntil curl -sf http://localhost:4321/gh-aw/ > /dev/null 2>&1; do\n # Check if the server process has already died\n if [ -f \"$PID_FILE\" ] && ! kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then\n echo \"::error::Documentation server process died before becoming ready. Server log:\"\n cat \"$LOG_FILE\"\n exit 1\n fi\n WAITED=$((WAITED + 3))\n if [ $WAITED -ge $MAX_WAIT ]; then\n echo \"::error::Documentation server did not start after ${MAX_WAIT}s. Server log:\"\n cat \"$LOG_FILE\"\n exit 1\n fi\n echo \"Waiting for server... ($WAITED/${MAX_WAIT}s)\"\n sleep 3\ndone\necho \"Server ready at http://localhost:4321/gh-aw/!\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/daily-security-observability.lock.yml b/.github/workflows/daily-security-observability.lock.yml index 384569f9a99..f7b2d9430ff 100644 --- a/.github/workflows/daily-security-observability.lock.yml +++ b/.github/workflows/daily-security-observability.lock.yml @@ -578,7 +578,7 @@ jobs: - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Download integrity-filtered logs - run: "mkdir -p /tmp/gh-aw/agent/integrity\nmkdir -p /tmp/gh-aw/cache-memory/security-observability\n\nCACHE_FILE=/tmp/gh-aw/cache-memory/security-observability/filtered-logs.snapshot.json\nRUN_FILE=/tmp/gh-aw/agent/integrity/filtered-logs.json\nFRESH_LOGS=/tmp/gh-aw/agent/integrity/filtered-logs.fresh.json\nEMPTY_DATA='{\"runs\":[],\"summary\":{\"total_runs\":0}}'\nNOW_EPOCH=$(date +%s)\nMAX_CACHE_AGE_SECONDS=$((7 * 24 * 60 * 60))\n\n# Warm start from cached 7-day snapshot when available and fresh.\nif [ -f \"$CACHE_FILE\" ] && jq -e '.runs and .updated_at' \"$CACHE_FILE\" > /dev/null 2>&1; then\n cache_updated_at=$(jq -r '.updated_at' \"$CACHE_FILE\")\n cache_updated_epoch=$(\n date -d \"$cache_updated_at\" +%s 2>/dev/null \\\n || date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$cache_updated_at\" +%s 2>/dev/null \\\n || echo 0\n )\n cache_age_seconds=$((NOW_EPOCH - cache_updated_epoch))\n if [ \"$cache_updated_epoch\" -gt 0 ] && [ \"$cache_age_seconds\" -le \"$MAX_CACHE_AGE_SECONDS\" ]; then\n jq '{runs: (.runs // []), summary: (.summary // {\"total_runs\": 0})}' \"$CACHE_FILE\" > \"$RUN_FILE\"\n echo \"✅ Warm cache restored (${cache_age_seconds}s old)\"\n else\n echo \"⚠️ Cache snapshot is stale (${cache_age_seconds}s old); starting fresh\"\n fi\nfi\n\n# Download logs filtered to only runs with DIFC integrity-filtered events.\n# --artifacts mcp: only download the MCP gateway log artifact (sufficient for DIFC checking).\n# --timeout 8: cap execution at 8 minutes to prevent runaway downloads.\ngh aw logs --filtered-integrity --start-date -7d --json -c 200 \\\n --artifacts mcp --timeout 8 \\\n > \"$FRESH_LOGS\" || true\n\n# Validate JSON output and fall back to an empty dataset on failure\nif ! jq -e '.runs' \"$FRESH_LOGS\" > /dev/null 2>&1; then\n echo \"⚠️ No valid logs produced; continuing with empty dataset\"\n echo \"$EMPTY_DATA\" > \"$FRESH_LOGS\"\nfi\n\n# Merge warm-start and fresh runs; fresh entries override warm-cache entries with the same run_id.\nif [ -f \"$RUN_FILE\" ] && jq -e '.runs' \"$RUN_FILE\" > /dev/null 2>&1; then\n jq -s '\n {\n runs: (\n ((.[0].runs // []) + (.[1].runs // []))\n | sort_by(.run_id)\n | group_by(.run_id)\n | map(.[-1])\n ),\n summary: {\n total_runs: (\n ((.[0].runs // []) + (.[1].runs // []) | sort_by(.run_id) | group_by(.run_id) | length)\n )\n }\n }\n ' \"$RUN_FILE\" \"$FRESH_LOGS\" > \"$RUN_FILE.merged\"\n mv \"$RUN_FILE.merged\" \"$RUN_FILE\"\nelse\n mv \"$FRESH_LOGS\" \"$RUN_FILE\"\nfi\n\ncount=$(jq '.runs | length' \"$RUN_FILE\" 2>/dev/null || echo 0)\necho \"✅ Downloaded $count runs with integrity-filtered events\"\n\n# Persist updated 7-day snapshot back to cache-memory every run.\njq -n \\\n --arg updated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --slurpfile payload \"$RUN_FILE\" \\\n '{\n updated_at: $updated_at,\n runs: ($payload[0].runs // []),\n summary: {\n total_runs: (($payload[0].runs // []) | length)\n }\n }' > \"$CACHE_FILE\"\n" + run: "mkdir -p /tmp/gh-aw/agent/integrity\nmkdir -p /tmp/gh-aw/cache-memory/security-observability\n\nCACHE_FILE=/tmp/gh-aw/cache-memory/security-observability/filtered-logs.snapshot.json\nRUN_FILE=/tmp/gh-aw/agent/integrity/filtered-logs.json\nFRESH_LOGS=/tmp/gh-aw/agent/integrity/filtered-logs.fresh.json\nEMPTY_DATA='{\"runs\":[],\"summary\":{\"total_runs\":0}}'\nNOW_EPOCH=$(date +%s)\nMAX_CACHE_AGE_SECONDS=$((7 * 24 * 60 * 60))\n\n# Warm start from cached 7-day snapshot when available and fresh.\nif [ -f \"$CACHE_FILE\" ] && jq -e '.runs and .updated_at' \"$CACHE_FILE\" > /dev/null 2>&1; then\n cache_updated_at=$(jq -r '.updated_at' \"$CACHE_FILE\")\n cache_updated_epoch=$(\n date -d \"$cache_updated_at\" +%s 2>/dev/null \\\n || date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$cache_updated_at\" +%s 2>/dev/null \\\n || echo 0\n )\n cache_age_seconds=$((NOW_EPOCH - cache_updated_epoch))\n if [ \"$cache_updated_epoch\" -gt 0 ] && [ \"$cache_age_seconds\" -le \"$MAX_CACHE_AGE_SECONDS\" ]; then\n jq '{runs: (.runs // []), summary: (.summary // {\"total_runs\": 0})}' \"$CACHE_FILE\" > \"$RUN_FILE\"\n echo \"✅ Warm cache restored (${cache_age_seconds}s old)\"\n else\n echo \"⚠️ Cache snapshot is stale (${cache_age_seconds}s old); starting fresh\"\n fi\nfi\n\n# Download logs filtered to only runs with DIFC integrity-filtered events.\n# --artifacts mcp: only download the MCP gateway log artifact (sufficient for DIFC checking).\n# --timeout 8: cap execution at 8 minutes to prevent runaway downloads.\ngh aw logs --filtered-integrity --start-date -7d --json -c 200 \\\n --artifacts mcp --timeout 8 \\\n > \"$FRESH_LOGS\" || true\n\n# Validate JSON output and fall back to an empty dataset on failure\nif ! jq -e '.runs' \"$FRESH_LOGS\" > /dev/null 2>&1; then\n echo \"⚠️ No valid logs produced; continuing with empty dataset\"\n echo \"$EMPTY_DATA\" > \"$FRESH_LOGS\"\nfi\n\n# Merge warm-start and fresh runs; fresh entries override warm-cache entries with the same run_id.\nif [ -f \"$RUN_FILE\" ] && jq -e '.runs' \"$RUN_FILE\" > /dev/null 2>&1; then\n jq -s '\n {\n runs: (\n ((.[0].runs // []) + (.[1].runs // []))\n | sort_by(.run_id)\n | group_by(.run_id)\n | map(.[-1])\n ),\n summary: {\n total_runs: (\n ((.[0].runs // []) + (.[1].runs // []) | sort_by(.run_id) | group_by(.run_id) | length)\n )\n }\n }\n ' \"$RUN_FILE\" \"$FRESH_LOGS\" > \"$RUN_FILE.merged\"\n mv \"$RUN_FILE.merged\" \"$RUN_FILE\"\nelse\n mv \"$FRESH_LOGS\" \"$RUN_FILE\"\nfi\n\ncount=$(jq '.runs | length' \"$RUN_FILE\" 2>/dev/null || echo 0)\necho \"✅ Downloaded $count runs with integrity-filtered events\"\n\n# Persist updated 7-day snapshot back to cache-memory every run.\njq -n \\\n --arg updated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --slurpfile payload \"$RUN_FILE\" \\\n '{\n updated_at: $updated_at,\n runs: ($payload[0].runs // []),\n summary: {\n total_runs: (($payload[0].runs // []) | length)\n }\n }' > \"$CACHE_FILE\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/daily-spdd-spec-planner.lock.yml b/.github/workflows/daily-spdd-spec-planner.lock.yml index c30b8925fa7..df68af6c566 100644 --- a/.github/workflows/daily-spdd-spec-planner.lock.yml +++ b/.github/workflows/daily-spdd-spec-planner.lock.yml @@ -482,7 +482,7 @@ jobs: - env: GH_TOKEN: ${{ github.token }} name: Copy OpenSPDD prompts - run: "set -euo pipefail\n# Resolve the latest OpenSPDD main ref each run via authenticated GitHub API.\n# This intentionally tracks upstream prompt updates while avoiding unauthenticated rate limits.\nOPENSPDD_REF=\"$(gh api repos/gszhangwei/open-spdd/commits/main --jq .sha)\"\ntest -n \"${OPENSPDD_REF}\" || { echo \"::error::Failed to resolve OpenSPDD main ref\"; exit 1; }\nPROMPTS_DIR=\"${GITHUB_WORKSPACE}/.github/copilot-prompts\"\nmkdir -p \"${PROMPTS_DIR}\"\nfor PROMPT in spdd-analysis spdd-reasons-canvas spdd-generate spdd-sync; do\n gh api \\\n -H \"Accept: application/vnd.github.raw\" \\\n \"repos/gszhangwei/open-spdd/contents/.cursor/commands/${PROMPT}.md?ref=${OPENSPDD_REF}\" \\\n > \"${PROMPTS_DIR}/${PROMPT}.md\"\n test -s \"${PROMPTS_DIR}/${PROMPT}.md\" || { echo \"::error::Failed to download ${PROMPT}.md\"; exit 1; }\ndone\n" + run: "set -euo pipefail\n# Resolve the latest OpenSPDD main ref each run via authenticated GitHub API.\n# This intentionally tracks upstream prompt updates while avoiding unauthenticated rate limits.\nOPENSPDD_REF=\"$(gh api repos/gszhangwei/open-spdd/commits/main --jq .sha)\"\ntest -n \"${OPENSPDD_REF}\" || { echo \"::error::Failed to resolve OpenSPDD main ref\"; exit 1; }\nPROMPTS_DIR=\"${GITHUB_WORKSPACE}/.github/copilot-prompts\"\nmkdir -p \"${PROMPTS_DIR}\"\nfor PROMPT in spdd-analysis spdd-reasons-canvas spdd-generate spdd-sync; do\n gh api \\\n -H \"Accept: application/vnd.github.raw\" \\\n \"repos/gszhangwei/open-spdd/contents/.cursor/commands/${PROMPT}.md?ref=${OPENSPDD_REF}\" \\\n > \"${PROMPTS_DIR}/${PROMPT}.md\"\n test -s \"${PROMPTS_DIR}/${PROMPT}.md\" || { echo \"::error::Failed to download ${PROMPT}.md\"; exit 1; }\ndone" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index abc815c7377..44b80660eb5 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -738,7 +738,7 @@ jobs: GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - name: Fetch merged PRs - run: | + run: |- bash "${RUNNER_TEMP}/gh-aw/actions/install_gh_cli.sh" # Fetch up to 500 merged PRs — title, body, metadata diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 307ba303ca6..aa8848c86aa 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -555,7 +555,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Sample files and load memory - run: "mkdir -p /tmp/gh-aw/agent\n# Sample documentation files (eliminates agent exploratory find turns)\nfind docs/src/content/docs \\( -name '*.md' -o -name '*.mdx' \\) | shuf -n 2 > /tmp/gh-aw/agent/doc-samples.txt\n# Sample workflows with messages (pre-compute instead of agent grep)\ngrep -rl \"messages:\" .github/workflows/ --include=\"*.md\" | shuf -n 2 > /tmp/gh-aw/agent/workflow-samples.txt\n# Sample validation files\nfind pkg -name '*validation*.go' | shuf -n 1 > /tmp/gh-aw/agent/validation-sample.txt || echo \"No validation files found\" > /tmp/gh-aw/agent/validation-sample.txt\n# Load historical memory (eliminates agent memory-read turns)\ncat memory/delight/previous-findings.json 2>/dev/null > /tmp/gh-aw/agent/previous-findings.json || echo \"[]\" > /tmp/gh-aw/agent/previous-findings.json\ncat memory/delight/improvement-themes.json 2>/dev/null > /tmp/gh-aw/agent/improvement-themes.json || echo \"[]\" > /tmp/gh-aw/agent/improvement-themes.json\n" + run: "mkdir -p /tmp/gh-aw/agent\n# Sample documentation files (eliminates agent exploratory find turns)\nfind docs/src/content/docs \\( -name '*.md' -o -name '*.mdx' \\) | shuf -n 2 > /tmp/gh-aw/agent/doc-samples.txt\n# Sample workflows with messages (pre-compute instead of agent grep)\ngrep -rl \"messages:\" .github/workflows/ --include=\"*.md\" | shuf -n 2 > /tmp/gh-aw/agent/workflow-samples.txt\n# Sample validation files\nfind pkg -name '*validation*.go' | shuf -n 1 > /tmp/gh-aw/agent/validation-sample.txt || echo \"No validation files found\" > /tmp/gh-aw/agent/validation-sample.txt\n# Load historical memory (eliminates agent memory-read turns)\ncat memory/delight/previous-findings.json 2>/dev/null > /tmp/gh-aw/agent/previous-findings.json || echo \"[]\" > /tmp/gh-aw/agent/previous-findings.json\ncat memory/delight/improvement-themes.json 2>/dev/null > /tmp/gh-aw/agent/improvement-themes.json || echo \"[]\" > /tmp/gh-aw/agent/improvement-themes.json" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index 442b31a476b..8b5501b7208 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -552,7 +552,7 @@ jobs: name: Prefetch dependabot burner context uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - script: "const fs = require('fs');\nconst path = require('path');\n\nconst manifestTargets = new Set([\n '.github/workflows/package.json',\n '.github/workflows/package-lock.json',\n '.github/workflows/requirements.txt',\n '.github/workflows/go.mod',\n]);\nconst objective = (process.env.BURN_OBJECTIVE || '').trim() || 'Close grouped Dependabot PRs for generated workflow manifests by updating source workflow markdown and recompiling in one replacement PR.';\nconst outPath = '/tmp/gh-aw/agent/dependabot-burner/context.json';\n\nfunction parseBumpTitle(title) {\n const match = String(title || '').match(/^Bump\\s+(.+?)\\s+from\\s+([^\\s]+)\\s+to\\s+([^\\s]+)$/i);\n if (!match) {\n return {\n dependency_name: String(title || '').trim(),\n current_version: '',\n target_version: '',\n title_parse_mode: 'fallback',\n };\n }\n return {\n dependency_name: match[1],\n current_version: match[2],\n target_version: match[3],\n title_parse_mode: 'parsed',\n };\n}\n\nfunction normalizeManifestFamily(filename) {\n if (filename.includes('package')) {\n return 'npm';\n }\n if (filename.endsWith('requirements.txt')) {\n return 'pip';\n }\n if (filename.endsWith('go.mod')) {\n return 'go';\n }\n return 'other';\n}\n\nfunction summarizeFamilies(files) {\n return [...new Set((files || []).map(normalizeManifestFamily))].sort();\n}\n\nfunction getTriggerPRNumber() {\n if (context.payload.pull_request?.number) {\n return Number(context.payload.pull_request.number);\n }\n if (context.payload.issue?.pull_request && context.payload.issue?.number) {\n return Number(context.payload.issue.number);\n }\n return null;\n}\n\nasync function loadPullFiles(pullNumber) {\n const files = await github.paginate(github.rest.pulls.listFiles, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: pullNumber,\n per_page: 100,\n });\n return files.map((file) => file.filename).filter((filename) => manifestTargets.has(filename));\n}\n\nasync function listOpenDependabotPRs() {\n const pulls = await github.paginate(github.rest.pulls.list, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n state: 'open',\n per_page: 100,\n });\n\n const candidates = [];\n for (const pull of pulls) {\n const author = pull.user?.login || '';\n if (author !== 'dependabot[bot]' && author !== 'app/dependabot') {\n continue;\n }\n\n const manifestFiles = await loadPullFiles(pull.number);\n if (manifestFiles.length === 0) {\n continue;\n }\n\n const parsed = parseBumpTitle(pull.title);\n candidates.push({\n number: pull.number,\n title: pull.title,\n dependency_name: parsed.dependency_name,\n current_version: parsed.current_version,\n target_version: parsed.target_version,\n title_parse_mode: parsed.title_parse_mode,\n manifest_files: manifestFiles,\n manifest_families: summarizeFamilies(manifestFiles),\n created_at: pull.created_at,\n updated_at: pull.updated_at,\n url: pull.html_url,\n });\n }\n\n return candidates.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());\n}\n\nasync function listRecentClosedBurnerPRs() {\n const pulls = await github.paginate(github.rest.pulls.list, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n state: 'closed',\n per_page: 100,\n });\n\n return pulls\n .filter((pull) => pull.title?.startsWith('[dependabot-burner] ') && !pull.merged_at)\n .slice(0, 20)\n .map((pull) => ({\n number: pull.number,\n title: pull.title,\n body: pull.body || '',\n url: pull.html_url,\n closed_at: pull.closed_at,\n created_at: pull.created_at,\n }))\n .sort((a, b) => new Date(b.closed_at || b.created_at).getTime() - new Date(a.closed_at || a.created_at).getTime());\n}\n\nconst triggerPRNumber = getTriggerPRNumber();\nconst openPRs = await listOpenDependabotPRs();\nconst recentFailedBurns = await listRecentClosedBurnerPRs();\n\nlet triggerPR = openPRs.find((pull) => pull.number === triggerPRNumber) || null;\nlet selectionReason = triggerPRNumber ? 'slash-command-trigger-not-in-scope' : 'bundle-all-open-manifest-prs';\nlet selectedPRs = openPRs;\n\nif (triggerPRNumber) {\n if (!triggerPR) {\n const manifestFiles = await loadPullFiles(triggerPRNumber);\n if (manifestFiles.length > 0) {\n const pull = await github.rest.pulls.get({\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: triggerPRNumber,\n });\n const parsed = parseBumpTitle(pull.data.title);\n triggerPR = {\n number: pull.data.number,\n title: pull.data.title,\n dependency_name: parsed.dependency_name,\n current_version: parsed.current_version,\n target_version: parsed.target_version,\n title_parse_mode: parsed.title_parse_mode,\n manifest_files: manifestFiles,\n manifest_families: summarizeFamilies(manifestFiles),\n created_at: pull.data.created_at,\n updated_at: pull.data.updated_at,\n url: pull.data.html_url,\n };\n }\n }\n\n if (triggerPR) {\n const triggerFiles = new Set(triggerPR.manifest_files || []);\n selectedPRs = openPRs.filter((pull) => {\n if (pull.number === triggerPR.number) {\n return true;\n }\n return (pull.manifest_files || []).some((file) => triggerFiles.has(file));\n });\n selectionReason = 'slash-command-similar-prs';\n } else {\n selectedPRs = [];\n }\n}\n\nconst payload = {\n objective,\n trigger_event: context.eventName,\n trigger_pr_number: triggerPRNumber,\n trigger_pr: triggerPR,\n selection_reason: selectionReason,\n open_pr_count: openPRs.length,\n selected_batch_pr_numbers: selectedPRs.map((pull) => pull.number),\n selected_batch_dependencies: selectedPRs.map((pull) => ({\n pr_number: pull.number,\n dependency_name: pull.dependency_name,\n current_version: pull.current_version,\n target_version: pull.target_version,\n title_parse_mode: pull.title_parse_mode,\n manifest_files: pull.manifest_files,\n manifest_families: pull.manifest_families,\n title: pull.title,\n url: pull.url,\n })),\n related_prs: triggerPRNumber ? selectedPRs.filter((pull) => pull.number !== triggerPRNumber) : [],\n recent_failed_burns: recentFailedBurns,\n};\n\nfs.mkdirSync(path.dirname(outPath), { recursive: true });\nfs.writeFileSync(outPath, JSON.stringify(payload, null, 2) + '\\n', 'utf8');\nconsole.log(JSON.stringify(payload, null, 2));\n" + script: "const fs = require('fs');\nconst path = require('path');\n\nconst manifestTargets = new Set([\n '.github/workflows/package.json',\n '.github/workflows/package-lock.json',\n '.github/workflows/requirements.txt',\n '.github/workflows/go.mod',\n]);\nconst objective = (process.env.BURN_OBJECTIVE || '').trim() || 'Close grouped Dependabot PRs for generated workflow manifests by updating source workflow markdown and recompiling in one replacement PR.';\nconst outPath = '/tmp/gh-aw/agent/dependabot-burner/context.json';\n\nfunction parseBumpTitle(title) {\n const match = String(title || '').match(/^Bump\\s+(.+?)\\s+from\\s+([^\\s]+)\\s+to\\s+([^\\s]+)$/i);\n if (!match) {\n return {\n dependency_name: String(title || '').trim(),\n current_version: '',\n target_version: '',\n title_parse_mode: 'fallback',\n };\n }\n return {\n dependency_name: match[1],\n current_version: match[2],\n target_version: match[3],\n title_parse_mode: 'parsed',\n };\n}\n\nfunction normalizeManifestFamily(filename) {\n if (filename.includes('package')) {\n return 'npm';\n }\n if (filename.endsWith('requirements.txt')) {\n return 'pip';\n }\n if (filename.endsWith('go.mod')) {\n return 'go';\n }\n return 'other';\n}\n\nfunction summarizeFamilies(files) {\n return [...new Set((files || []).map(normalizeManifestFamily))].sort();\n}\n\nfunction getTriggerPRNumber() {\n if (context.payload.pull_request?.number) {\n return Number(context.payload.pull_request.number);\n }\n if (context.payload.issue?.pull_request && context.payload.issue?.number) {\n return Number(context.payload.issue.number);\n }\n return null;\n}\n\nasync function loadPullFiles(pullNumber) {\n const files = await github.paginate(github.rest.pulls.listFiles, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: pullNumber,\n per_page: 100,\n });\n return files.map((file) => file.filename).filter((filename) => manifestTargets.has(filename));\n}\n\nasync function listOpenDependabotPRs() {\n const pulls = await github.paginate(github.rest.pulls.list, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n state: 'open',\n per_page: 100,\n });\n\n const candidates = [];\n for (const pull of pulls) {\n const author = pull.user?.login || '';\n if (author !== 'dependabot[bot]' && author !== 'app/dependabot') {\n continue;\n }\n\n const manifestFiles = await loadPullFiles(pull.number);\n if (manifestFiles.length === 0) {\n continue;\n }\n\n const parsed = parseBumpTitle(pull.title);\n candidates.push({\n number: pull.number,\n title: pull.title,\n dependency_name: parsed.dependency_name,\n current_version: parsed.current_version,\n target_version: parsed.target_version,\n title_parse_mode: parsed.title_parse_mode,\n manifest_files: manifestFiles,\n manifest_families: summarizeFamilies(manifestFiles),\n created_at: pull.created_at,\n updated_at: pull.updated_at,\n url: pull.html_url,\n });\n }\n\n return candidates.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());\n}\n\nasync function listRecentClosedBurnerPRs() {\n const pulls = await github.paginate(github.rest.pulls.list, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n state: 'closed',\n per_page: 100,\n });\n\n return pulls\n .filter((pull) => pull.title?.startsWith('[dependabot-burner] ') && !pull.merged_at)\n .slice(0, 20)\n .map((pull) => ({\n number: pull.number,\n title: pull.title,\n body: pull.body || '',\n url: pull.html_url,\n closed_at: pull.closed_at,\n created_at: pull.created_at,\n }))\n .sort((a, b) => new Date(b.closed_at || b.created_at).getTime() - new Date(a.closed_at || a.created_at).getTime());\n}\n\nconst triggerPRNumber = getTriggerPRNumber();\nconst openPRs = await listOpenDependabotPRs();\nconst recentFailedBurns = await listRecentClosedBurnerPRs();\n\nlet triggerPR = openPRs.find((pull) => pull.number === triggerPRNumber) || null;\nlet selectionReason = triggerPRNumber ? 'slash-command-trigger-not-in-scope' : 'bundle-all-open-manifest-prs';\nlet selectedPRs = openPRs;\n\nif (triggerPRNumber) {\n if (!triggerPR) {\n const manifestFiles = await loadPullFiles(triggerPRNumber);\n if (manifestFiles.length > 0) {\n const pull = await github.rest.pulls.get({\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: triggerPRNumber,\n });\n const parsed = parseBumpTitle(pull.data.title);\n triggerPR = {\n number: pull.data.number,\n title: pull.data.title,\n dependency_name: parsed.dependency_name,\n current_version: parsed.current_version,\n target_version: parsed.target_version,\n title_parse_mode: parsed.title_parse_mode,\n manifest_files: manifestFiles,\n manifest_families: summarizeFamilies(manifestFiles),\n created_at: pull.data.created_at,\n updated_at: pull.data.updated_at,\n url: pull.data.html_url,\n };\n }\n }\n\n if (triggerPR) {\n const triggerFiles = new Set(triggerPR.manifest_files || []);\n selectedPRs = openPRs.filter((pull) => {\n if (pull.number === triggerPR.number) {\n return true;\n }\n return (pull.manifest_files || []).some((file) => triggerFiles.has(file));\n });\n selectionReason = 'slash-command-similar-prs';\n } else {\n selectedPRs = [];\n }\n}\n\nconst payload = {\n objective,\n trigger_event: context.eventName,\n trigger_pr_number: triggerPRNumber,\n trigger_pr: triggerPR,\n selection_reason: selectionReason,\n open_pr_count: openPRs.length,\n selected_batch_pr_numbers: selectedPRs.map((pull) => pull.number),\n selected_batch_dependencies: selectedPRs.map((pull) => ({\n pr_number: pull.number,\n dependency_name: pull.dependency_name,\n current_version: pull.current_version,\n target_version: pull.target_version,\n title_parse_mode: pull.title_parse_mode,\n manifest_files: pull.manifest_files,\n manifest_families: pull.manifest_families,\n title: pull.title,\n url: pull.url,\n })),\n related_prs: triggerPRNumber ? selectedPRs.filter((pull) => pull.number !== triggerPRNumber) : [],\n recent_failed_burns: recentFailedBurns,\n};\n\nfs.mkdirSync(path.dirname(outPath), { recursive: true });\nfs.writeFileSync(outPath, JSON.stringify(payload, null, 2) + '\\n', 'utf8');\nconsole.log(JSON.stringify(payload, null, 2));" # Cache configuration from frontmatter processed below - name: Dependabot burner selection context diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index df307f82470..45dcbd4276d 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -572,7 +572,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} name: Pre-fetch ADR gate PR context - run: "set -euo pipefail\n\nif [ \"$EXPR_GITHUB_EVENT_NAME\" = \"workflow_dispatch\" ] && [ -z \"${PR_NUMBER:-}\" ]; then\n echo \"::error::workflow_dispatch requires inputs.pr_number\"\n exit 1\nfi\n\nmkdir -p /tmp/gh-aw/agent\n\ngh pr view \"$PR_NUMBER\" \\\n --repo \"$EXPR_GITHUB_REPOSITORY\" \\\n --json number,title,body,labels,baseRefName,headRefName,author,url \\\n > /tmp/gh-aw/agent/pr.json\n\ngh api --paginate \"repos/$EXPR_GITHUB_REPOSITORY/pulls/$PR_NUMBER/files?per_page=100\" \\\n --jq '.[]' | jq -s '.' > /tmp/gh-aw/agent/pr-files.json\n\nFILE_COUNT=$(jq 'length' /tmp/gh-aw/agent/pr-files.json)\n\nif [ \"$FILE_COUNT\" -gt 300 ]; then\n echo \"::warning::PR has $FILE_COUNT changed files (exceeds the 300-file GitHub diff API limit). Skipping full diff; file listing is available in pr-files.json.\"\n printf '# Diff unavailable: PR has %s changed files (exceeds the 300-file GitHub diff API limit).\\n# Use pr-files.json for the full file listing instead.\\n' \"$FILE_COUNT\" \\\n > /tmp/gh-aw/agent/pr.diff\nelse\n gh pr diff \"$PR_NUMBER\" \\\n --repo \"$EXPR_GITHUB_REPOSITORY\" \\\n > /tmp/gh-aw/agent/pr.diff\nfi\n\nif [ -f \"$EXPR_GITHUB_WORKSPACE/.design-gate.yml\" ]; then\n cp \"$EXPR_GITHUB_WORKSPACE/.design-gate.yml\" /tmp/gh-aw/agent/design-gate-config.yml\n HAS_CUSTOM_CONFIG=true\nelse\n echo \"No .design-gate.yml found — using defaults\" > /tmp/gh-aw/agent/design-gate-config.yml\n HAS_CUSTOM_CONFIG=false\nfi\n\nBUSINESS_ADDITIONS_DEFAULT=$(jq '[.[] | select(.filename | test(\"^(src|lib|pkg|internal|app|core|domain|services|api)/\")) | .additions] | add // 0' /tmp/gh-aw/agent/pr-files.json)\nHAS_IMPLEMENTATION_LABEL=$(jq '[.labels[]?.name] | index(\"implementation\") != null' /tmp/gh-aw/agent/pr.json)\n\njq -n \\\n --argjson default_business_additions \"$BUSINESS_ADDITIONS_DEFAULT\" \\\n --argjson has_implementation_label \"$HAS_IMPLEMENTATION_LABEL\" \\\n --argjson has_custom_config \"$HAS_CUSTOM_CONFIG\" \\\n --arg pr_number \"$PR_NUMBER\" \\\n --arg threshold \"100\" \\\n --argjson file_count \"$FILE_COUNT\" \\\n --argjson diff_available \"$(jq -n --argjson fc \"$FILE_COUNT\" 'if $fc <= 300 then true else false end')\" \\\n '{\n pr_number: ($pr_number | tonumber),\n threshold: ($threshold | tonumber),\n has_custom_config: $has_custom_config,\n has_implementation_label: $has_implementation_label,\n default_business_additions: $default_business_additions,\n requires_adr_by_default_volume: ($default_business_additions > ($threshold | tonumber)),\n file_count: $file_count,\n diff_available: $diff_available\n }' > /tmp/gh-aw/agent/adr-prefetch-summary.json\n" + run: "set -euo pipefail\n\nif [ \"$EXPR_GITHUB_EVENT_NAME\" = \"workflow_dispatch\" ] && [ -z \"${PR_NUMBER:-}\" ]; then\n echo \"::error::workflow_dispatch requires inputs.pr_number\"\n exit 1\nfi\n\nmkdir -p /tmp/gh-aw/agent\n\ngh pr view \"$PR_NUMBER\" \\\n --repo \"$EXPR_GITHUB_REPOSITORY\" \\\n --json number,title,body,labels,baseRefName,headRefName,author,url \\\n > /tmp/gh-aw/agent/pr.json\n\ngh api --paginate \"repos/$EXPR_GITHUB_REPOSITORY/pulls/$PR_NUMBER/files?per_page=100\" \\\n --jq '.[]' | jq -s '.' > /tmp/gh-aw/agent/pr-files.json\n\nFILE_COUNT=$(jq 'length' /tmp/gh-aw/agent/pr-files.json)\n\nif [ \"$FILE_COUNT\" -gt 300 ]; then\n echo \"::warning::PR has $FILE_COUNT changed files (exceeds the 300-file GitHub diff API limit). Skipping full diff; file listing is available in pr-files.json.\"\n printf '# Diff unavailable: PR has %s changed files (exceeds the 300-file GitHub diff API limit).\\n# Use pr-files.json for the full file listing instead.\\n' \"$FILE_COUNT\" \\\n > /tmp/gh-aw/agent/pr.diff\nelse\n gh pr diff \"$PR_NUMBER\" \\\n --repo \"$EXPR_GITHUB_REPOSITORY\" \\\n > /tmp/gh-aw/agent/pr.diff\nfi\n\nif [ -f \"$EXPR_GITHUB_WORKSPACE/.design-gate.yml\" ]; then\n cp \"$EXPR_GITHUB_WORKSPACE/.design-gate.yml\" /tmp/gh-aw/agent/design-gate-config.yml\n HAS_CUSTOM_CONFIG=true\nelse\n echo \"No .design-gate.yml found — using defaults\" > /tmp/gh-aw/agent/design-gate-config.yml\n HAS_CUSTOM_CONFIG=false\nfi\n\nBUSINESS_ADDITIONS_DEFAULT=$(jq '[.[] | select(.filename | test(\"^(src|lib|pkg|internal|app|core|domain|services|api)/\")) | .additions] | add // 0' /tmp/gh-aw/agent/pr-files.json)\nHAS_IMPLEMENTATION_LABEL=$(jq '[.labels[]?.name] | index(\"implementation\") != null' /tmp/gh-aw/agent/pr.json)\n\njq -n \\\n --argjson default_business_additions \"$BUSINESS_ADDITIONS_DEFAULT\" \\\n --argjson has_implementation_label \"$HAS_IMPLEMENTATION_LABEL\" \\\n --argjson has_custom_config \"$HAS_CUSTOM_CONFIG\" \\\n --arg pr_number \"$PR_NUMBER\" \\\n --arg threshold \"100\" \\\n --argjson file_count \"$FILE_COUNT\" \\\n --argjson diff_available \"$(jq -n --argjson fc \"$FILE_COUNT\" 'if $fc <= 300 then true else false end')\" \\\n '{\n pr_number: ($pr_number | tonumber),\n threshold: ($threshold | tonumber),\n has_custom_config: $has_custom_config,\n has_implementation_label: $has_implementation_label,\n default_business_additions: $default_business_additions,\n requires_adr_by_default_volume: ($default_business_additions > ($threshold | tonumber)),\n file_count: $file_count,\n diff_available: $diff_available\n }' > /tmp/gh-aw/agent/adr-prefetch-summary.json" - name: Configure Git credentials env: diff --git a/.github/workflows/designer-drift-audit.lock.yml b/.github/workflows/designer-drift-audit.lock.yml index d4a8dd3625c..8d1696dff2c 100644 --- a/.github/workflows/designer-drift-audit.lock.yml +++ b/.github/workflows/designer-drift-audit.lock.yml @@ -453,7 +453,7 @@ jobs: - name: Extract designer file metadata run: "# Skill file\nSKILL=\".github/skills/agentic-workflow-designer/SKILL.md\"\nif [ -f \"$SKILL\" ]; then\n {\n echo \"=== SKILL.md ===\"\n grep -n '^##' \"$SKILL\" | head -50\n echo \"---\"\n # Decision heuristic tables\n grep -E '^\\|.*\\|' \"$SKILL\" | grep -ivE '^\\|[\\s-]+\\|' | head -60\n echo \"---\"\n # Explicit references to .github/aw/ files\n grep -n '\\.github/aw/' \"$SKILL\" || true\n echo \"===\"\n } > /tmp/gh-aw/data/designer-skill.txt\nfi\n\n# Agent file\nAGENT=\".github/agents/interactive-agent-designer.agent.md\"\nif [ -f \"$AGENT\" ]; then\n {\n echo \"=== interactive-agent-designer.agent.md ===\"\n grep -n '^##' \"$AGENT\" | head -50\n echo \"---\"\n grep -E '^\\|.*\\|' \"$AGENT\" | grep -ivE '^\\|[\\s-]+\\|' | head -60\n echo \"---\"\n grep -n '\\.github/aw/' \"$AGENT\" || true\n echo \"===\"\n } > /tmp/gh-aw/data/designer-agent.txt\nfi\n" - name: Collect recent changes to reference docs - run: "# Commits in the last 7 days that touched reference docs\ngit log --oneline --since=\"7 days ago\" -- \\\n .github/aw/syntax.md \\\n .github/aw/safe-outputs.md \\\n .github/aw/network.md \\\n .github/aw/patterns.md \\\n .github/aw/subagents.md \\\n .github/aw/token-optimization.md \\\n .github/aw/triggers.md \\\n .github/aw/create-agentic-workflow.md \\\n > /tmp/gh-aw/data/recent-ref-commits.txt 2>/dev/null || true\n\n# Commits in the last 7 days that touched designer files\ngit log --oneline --since=\"7 days ago\" -- \\\n .github/skills/agentic-workflow-designer/SKILL.md \\\n .github/agents/interactive-agent-designer.agent.md \\\n > /tmp/gh-aw/data/recent-designer-commits.txt 2>/dev/null || true\n\n# New or changed safe output types (from validation config in Go)\ngrep -oP '\"(\\w+)\":\\s*\\{' pkg/workflow/safe_outputs_validation_config.go \\\n | sed 's/\": {//' | tr -d '\"' | sort \\\n > /tmp/gh-aw/data/all-safe-output-types.txt 2>/dev/null || true\n" + run: "# Commits in the last 7 days that touched reference docs\ngit log --oneline --since=\"7 days ago\" -- \\\n .github/aw/syntax.md \\\n .github/aw/safe-outputs.md \\\n .github/aw/network.md \\\n .github/aw/patterns.md \\\n .github/aw/subagents.md \\\n .github/aw/token-optimization.md \\\n .github/aw/triggers.md \\\n .github/aw/create-agentic-workflow.md \\\n > /tmp/gh-aw/data/recent-ref-commits.txt 2>/dev/null || true\n\n# Commits in the last 7 days that touched designer files\ngit log --oneline --since=\"7 days ago\" -- \\\n .github/skills/agentic-workflow-designer/SKILL.md \\\n .github/agents/interactive-agent-designer.agent.md \\\n > /tmp/gh-aw/data/recent-designer-commits.txt 2>/dev/null || true\n\n# New or changed safe output types (from validation config in Go)\ngrep -oP '\"(\\w+)\":\\s*\\{' pkg/workflow/safe_outputs_validation_config.go \\\n | sed 's/\": {//' | tr -d '\"' | sort \\\n > /tmp/gh-aw/data/all-safe-output-types.txt 2>/dev/null || true" - name: Configure Git credentials env: diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index 2fa3bdc6938..8189b40dc2a 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -557,7 +557,7 @@ jobs: - name: Wait for server readiness run: "MAX_WAIT=135 # 45 attempts × 3s = 135s max wait\nWAITED=0\nuntil (echo > /dev/tcp/127.0.0.1/4321) > /dev/null 2>&1; do\n # Check if the server process has already died\n if [ -f /tmp/gh-aw/agent/server.pid ] && ! kill -0 \"$(cat /tmp/gh-aw/agent/server.pid)\" 2>/dev/null; then\n echo \"::error::Documentation server process died before opening port 4321. Server log:\"\n cat /tmp/gh-aw/agent/preview.log\n exit 1\n fi\n WAITED=$((WAITED + 3))\n if [ $WAITED -ge $MAX_WAIT ]; then\n echo \"::error::Documentation server port 4321 did not open after ${MAX_WAIT}s. Server log:\"\n cat /tmp/gh-aw/agent/preview.log\n exit 1\n fi\n echo \"Waiting for docs port... ($WAITED/${MAX_WAIT}s)\"\n sleep 3\ndone\nWAITED=0\nuntil curl -sf http://localhost:4321/gh-aw/ > /dev/null 2>&1; do\n # Check if the server process has already died\n if [ -f /tmp/gh-aw/agent/server.pid ] && ! kill -0 \"$(cat /tmp/gh-aw/agent/server.pid)\" 2>/dev/null; then\n echo \"::error::Documentation server process died before becoming ready. Server log:\"\n cat /tmp/gh-aw/agent/preview.log\n exit 1\n fi\n WAITED=$((WAITED + 3))\n if [ $WAITED -ge $MAX_WAIT ]; then\n echo \"::error::Documentation server did not start after ${MAX_WAIT}s. Server log:\"\n cat /tmp/gh-aw/agent/preview.log\n exit 1\n fi\n echo \"Waiting for server... ($WAITED/${MAX_WAIT}s)\"\n sleep 3\ndone\necho \"Server ready at http://localhost:4321/gh-aw/!\"\n" - name: Write server URL for agent - run: "mkdir -p /tmp/gh-aw/agent\necho \"http://localhost:4321/gh-aw/\" > /tmp/gh-aw/agent/server-url.txt\necho \"Server URL: http://localhost:4321/gh-aw/\"\n" + run: "mkdir -p /tmp/gh-aw/agent\necho \"http://localhost:4321/gh-aw/\" > /tmp/gh-aw/agent/server-url.txt\necho \"Server URL: http://localhost:4321/gh-aw/\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/eslint-monster.lock.yml b/.github/workflows/eslint-monster.lock.yml index 4a833cb5297..4f0ea2bbfd4 100644 --- a/.github/workflows/eslint-monster.lock.yml +++ b/.github/workflows/eslint-monster.lock.yml @@ -473,7 +473,7 @@ jobs: GH_TOKEN: ${{ github.token }} - id: eslint_scan name: Run ESLint factory pre-check - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nrm -f /tmp/gh-aw/agent/lint-clean.flag\nREPO_ROOT=\"$(pwd)\"\n\ncd eslint-factory\nnpm ci > /tmp/gh-aw/agent/eslint-factory.log 2>&1\n\nif npm run lint:setup-js >> /tmp/gh-aw/agent/eslint-factory.log 2>&1; then\n : > /tmp/gh-aw/agent/eslint-diagnostics.txt\n : > /tmp/gh-aw/agent/skill-index.txt\n touch /tmp/gh-aw/agent/lint-clean.flag\n exit 0\nfi\n\ngrep -E '^[^:]+:[0-9]+:[0-9]+:' /tmp/gh-aw/agent/eslint-factory.log > /tmp/gh-aw/agent/eslint-diagnostics.txt || true\ndiag_count=$(wc -l < /tmp/gh-aw/agent/eslint-diagnostics.txt | tr -d ' ')\nif [ \"${diag_count}\" -eq 0 ]; then\n grep -E '^[[:space:]]*[^[:space:]].*$' /tmp/gh-aw/agent/eslint-factory.log | head -n 80 > /tmp/gh-aw/agent/eslint-diagnostics.txt || true\nfi\n\nfind \"${REPO_ROOT}/.github/skills\" -maxdepth 6 -name 'SKILL.md' | sort > /tmp/gh-aw/agent/skill-index.txt\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nrm -f /tmp/gh-aw/agent/lint-clean.flag\nREPO_ROOT=\"$(pwd)\"\n\ncd eslint-factory\nnpm ci > /tmp/gh-aw/agent/eslint-factory.log 2>&1\n\nif npm run lint:setup-js >> /tmp/gh-aw/agent/eslint-factory.log 2>&1; then\n : > /tmp/gh-aw/agent/eslint-diagnostics.txt\n : > /tmp/gh-aw/agent/skill-index.txt\n touch /tmp/gh-aw/agent/lint-clean.flag\n exit 0\nfi\n\ngrep -E '^[^:]+:[0-9]+:[0-9]+:' /tmp/gh-aw/agent/eslint-factory.log > /tmp/gh-aw/agent/eslint-diagnostics.txt || true\ndiag_count=$(wc -l < /tmp/gh-aw/agent/eslint-diagnostics.txt | tr -d ' ')\nif [ \"${diag_count}\" -eq 0 ]; then\n grep -E '^[[:space:]]*[^[:space:]].*$' /tmp/gh-aw/agent/eslint-factory.log | head -n 80 > /tmp/gh-aw/agent/eslint-diagnostics.txt || true\nfi\n\nfind \"${REPO_ROOT}/.github/skills\" -maxdepth 6 -name 'SKILL.md' | sort > /tmp/gh-aw/agent/skill-index.txt" - name: Configure Git credentials env: diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index af19e7091e7..f56aa642481 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -534,7 +534,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: Fetch recent changes - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# Determine scan scope: Monday = full weekly scan, other weekdays = daily\nDAY=$(date +%u)\nif [ \"$DAY\" -eq 1 ]; then\n SINCE=\"7 days ago\"\n SCOPE=\"weekly\"\nelse\n SINCE=\"24 hours ago\"\n SCOPE=\"daily\"\nfi\n\necho \"Scan scope: $SCOPE (since: $SINCE)\"\n\n# Fetch recent commits (all files) — includes file names for context\ngit log --since=\"$SINCE\" --oneline --name-only \\\n > /tmp/gh-aw/agent/recent-commits.txt\n\n# Fetch commits that touched docs\ngit log --since=\"$SINCE\" --name-only \\\n --format=\"%H %s\" -- 'docs/**/*.md' 'docs/**/*.mdx' \\\n > /tmp/gh-aw/agent/doc-changes.txt\n\necho \"Recent commits: $(wc -l < /tmp/gh-aw/agent/recent-commits.txt)\"\necho \"Doc file changes: $(wc -l < /tmp/gh-aw/agent/doc-changes.txt)\"\necho \"$SCOPE\" > /tmp/gh-aw/agent/scan-scope.txt\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# Determine scan scope: Monday = full weekly scan, other weekdays = daily\nDAY=$(date +%u)\nif [ \"$DAY\" -eq 1 ]; then\n SINCE=\"7 days ago\"\n SCOPE=\"weekly\"\nelse\n SINCE=\"24 hours ago\"\n SCOPE=\"daily\"\nfi\n\necho \"Scan scope: $SCOPE (since: $SINCE)\"\n\n# Fetch recent commits (all files) — includes file names for context\ngit log --since=\"$SINCE\" --oneline --name-only \\\n > /tmp/gh-aw/agent/recent-commits.txt\n\n# Fetch commits that touched docs\ngit log --since=\"$SINCE\" --name-only \\\n --format=\"%H %s\" -- 'docs/**/*.md' 'docs/**/*.mdx' \\\n > /tmp/gh-aw/agent/doc-changes.txt\n\necho \"Recent commits: $(wc -l < /tmp/gh-aw/agent/recent-commits.txt)\"\necho \"Doc file changes: $(wc -l < /tmp/gh-aw/agent/doc-changes.txt)\"\necho \"$SCOPE\" > /tmp/gh-aw/agent/scan-scope.txt" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/gpclean.lock.yml b/.github/workflows/gpclean.lock.yml index fb5a0f15a3d..e9a78be3a2f 100644 --- a/.github/workflows/gpclean.lock.yml +++ b/.github/workflows/gpclean.lock.yml @@ -529,7 +529,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Download SBOM from GitHub Dependency Graph API - run: "set -e\necho \"📦 Downloading SBOM from GitHub Dependency Graph API...\"\n\n# Download SBOM using gh CLI (requires contents: read permission)\ngh api \\\n -H \"Accept: application/vnd.github+json\" \\\n -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n \"/repos/$GITHUB_REPOSITORY/dependency-graph/sbom\" \\\n > /tmp/gh-aw/agent/sbom.json\n\necho \"✅ SBOM downloaded successfully to /tmp/gh-aw/agent/sbom.json\"\n\n# Show SBOM summary\nif command -v jq &> /dev/null; then\n PACKAGE_COUNT=$(jq '.sbom.packages | length' /tmp/gh-aw/agent/sbom.json 2>/dev/null || echo \"unknown\")\n echo \"📊 SBOM contains ${PACKAGE_COUNT} packages\"\nfi\n" + run: "set -e\necho \"📦 Downloading SBOM from GitHub Dependency Graph API...\"\n\n# Download SBOM using gh CLI (requires contents: read permission)\ngh api \\\n -H \"Accept: application/vnd.github+json\" \\\n -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n \"/repos/$GITHUB_REPOSITORY/dependency-graph/sbom\" \\\n > /tmp/gh-aw/agent/sbom.json\n\necho \"✅ SBOM downloaded successfully to /tmp/gh-aw/agent/sbom.json\"\n\n# Show SBOM summary\nif command -v jq &> /dev/null; then\n PACKAGE_COUNT=$(jq '.sbom.packages | length' /tmp/gh-aw/agent/sbom.json 2>/dev/null || echo \"unknown\")\n echo \"📊 SBOM contains ${PACKAGE_COUNT} packages\"\nfi" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/impeccable-skills-reviewer.lock.yml b/.github/workflows/impeccable-skills-reviewer.lock.yml index 95827d1eea9..5c3bcf85714 100644 --- a/.github/workflows/impeccable-skills-reviewer.lock.yml +++ b/.github/workflows/impeccable-skills-reviewer.lock.yml @@ -558,7 +558,7 @@ jobs: PR_DIFF_MAX_LINES: "3000" PR_NUMBER: ${{ github.event.pull_request.number }} name: Pre-fetch PR diff - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n{ gh pr diff \"$PR_NUMBER\" --repo $EXPR_GITHUB_REPOSITORY \\\n --exclude '**/*.lock.yml' \\\n --exclude '**/generated/**' \\\n --exclude '**/dist/**' \\\n --exclude '**/build/**' \\\n || true; } | head -n \"${PR_DIFF_MAX_LINES}\" > /tmp/gh-aw/agent/pr-diff.patch\nLINES=$(wc -l < /tmp/gh-aw/agent/pr-diff.patch)\ngh pr view \"$PR_NUMBER\" \\\n --repo $EXPR_GITHUB_REPOSITORY \\\n --json number,title,body,headRefName,additions,deletions,changedFiles,files \\\n > /tmp/gh-aw/agent/pr-meta.json\necho \"Pre-fetched PR diff (${LINES} lines) and metadata\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n{ gh pr diff \"$PR_NUMBER\" --repo $EXPR_GITHUB_REPOSITORY \\\n --exclude '**/*.lock.yml' \\\n --exclude '**/generated/**' \\\n --exclude '**/dist/**' \\\n --exclude '**/build/**' \\\n || true; } | head -n \"${PR_DIFF_MAX_LINES}\" > /tmp/gh-aw/agent/pr-diff.patch\nLINES=$(wc -l < /tmp/gh-aw/agent/pr-diff.patch)\ngh pr view \"$PR_NUMBER\" \\\n --repo $EXPR_GITHUB_REPOSITORY \\\n --json number,title,body,headRefName,additions,deletions,changedFiles,files \\\n > /tmp/gh-aw/agent/pr-meta.json\necho \"Pre-fetched PR diff (${LINES} lines) and metadata\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index 0ad951403dc..d34c6dee11b 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -531,7 +531,7 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Fetch issues - run: | + run: |- # Create output directory mkdir -p /tmp/gh-aw/agent/issues-data diff --git a/.github/workflows/lint-monster.lock.yml b/.github/workflows/lint-monster.lock.yml index ca9fe669bcd..63ed27361c8 100644 --- a/.github/workflows/lint-monster.lock.yml +++ b/.github/workflows/lint-monster.lock.yml @@ -468,7 +468,7 @@ jobs: GH_TOKEN: ${{ github.token }} - id: lint_scan name: Run custom lint pre-check - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nrm -f /tmp/gh-aw/agent/lint-clean.flag\n\nif make golint-custom > /tmp/gh-aw/agent/golint-custom.log 2>&1; then\n : > /tmp/gh-aw/agent/lint-diagnostics.txt\n : > /tmp/gh-aw/agent/skill-index.txt\n touch /tmp/gh-aw/agent/lint-clean.flag\n exit 0\nfi\n\ngrep -E '^[^:]+:[0-9]+:[0-9]+:' /tmp/gh-aw/agent/golint-custom.log > /tmp/gh-aw/agent/lint-diagnostics.txt || true\ndiag_count=$(wc -l < /tmp/gh-aw/agent/lint-diagnostics.txt | tr -d ' ')\nif [ \"${diag_count}\" -eq 0 ]; then\n grep -E '^[[:space:]]*[^[:space:]].*$' /tmp/gh-aw/agent/golint-custom.log | head -n 50 > /tmp/gh-aw/agent/lint-diagnostics.txt || true\n diag_count=$(wc -l < /tmp/gh-aw/agent/lint-diagnostics.txt | tr -d ' ')\nfi\n\nfind .github/skills -maxdepth 6 -name 'SKILL.md' | sort > /tmp/gh-aw/agent/skill-index.txt\necho \"Lint diagnostics captured: ${diag_count}\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nrm -f /tmp/gh-aw/agent/lint-clean.flag\n\nif make golint-custom > /tmp/gh-aw/agent/golint-custom.log 2>&1; then\n : > /tmp/gh-aw/agent/lint-diagnostics.txt\n : > /tmp/gh-aw/agent/skill-index.txt\n touch /tmp/gh-aw/agent/lint-clean.flag\n exit 0\nfi\n\ngrep -E '^[^:]+:[0-9]+:[0-9]+:' /tmp/gh-aw/agent/golint-custom.log > /tmp/gh-aw/agent/lint-diagnostics.txt || true\ndiag_count=$(wc -l < /tmp/gh-aw/agent/lint-diagnostics.txt | tr -d ' ')\nif [ \"${diag_count}\" -eq 0 ]; then\n grep -E '^[[:space:]]*[^[:space:]].*$' /tmp/gh-aw/agent/golint-custom.log | head -n 50 > /tmp/gh-aw/agent/lint-diagnostics.txt || true\n diag_count=$(wc -l < /tmp/gh-aw/agent/lint-diagnostics.txt | tr -d ' ')\nfi\n\nfind .github/skills -maxdepth 6 -name 'SKILL.md' | sort > /tmp/gh-aw/agent/skill-index.txt\necho \"Lint diagnostics captured: ${diag_count}\"" - name: Configure Git credentials env: diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index 2730b9f8134..a0b85e60737 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -566,7 +566,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Preload linter source and cache context - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n: > /tmp/gh-aw/agent/linters-src.txt\nwhile IFS= read -r -d '' file; do\n printf '\\n===== FILE: %s =====\\n' \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\n cat \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\ndone < <(find pkg/linters -type f -name '*.go' -print0 | sort -z)\ncat .github/skills/go-linters/SKILL.md > /tmp/gh-aw/agent/go-linters-skill.txt\n\nprior_file=\"\"\nif [ -d /tmp/gh-aw/cache-memory ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters.json' | sort | head -n 1 || true)\"\n if [ -z \"${prior_file}\" ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters' | sort | head -n 1 || true)\"\n fi\nfi\nif [ -n \"${prior_file}\" ] && [ -f \"${prior_file}\" ]; then\n cp \"${prior_file}\" /tmp/gh-aw/agent/prior-linters.json\nelse\n echo \"[]\" > /tmp/gh-aw/agent/prior-linters.json\nfi\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n: > /tmp/gh-aw/agent/linters-src.txt\nwhile IFS= read -r -d '' file; do\n printf '\\n===== FILE: %s =====\\n' \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\n cat \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\ndone < <(find pkg/linters -type f -name '*.go' -print0 | sort -z)\ncat .github/skills/go-linters/SKILL.md > /tmp/gh-aw/agent/go-linters-skill.txt\n\nprior_file=\"\"\nif [ -d /tmp/gh-aw/cache-memory ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters.json' | sort | head -n 1 || true)\"\n if [ -z \"${prior_file}\" ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters' | sort | head -n 1 || true)\"\n fi\nfi\nif [ -n \"${prior_file}\" ] && [ -f \"${prior_file}\" ]; then\n cp \"${prior_file}\" /tmp/gh-aw/agent/prior-linters.json\nelse\n echo \"[]\" > /tmp/gh-aw/agent/prior-linters.json\nfi" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5 diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index 3cabcf0a1ae..926908661cb 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -705,7 +705,7 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} name: Pre-fetch PR diff - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n{ gh pr diff \"$PR_NUMBER\" --repo $EXPR_GITHUB_REPOSITORY \\\n --exclude '**/*.lock.yml' \\\n --exclude '**/generated/**' \\\n --exclude '**/dist/**' \\\n --exclude '**/build/**' \\\n || true; } | head -n 3000 > /tmp/gh-aw/agent/pr-diff.patch\nLINES=$(wc -l < /tmp/gh-aw/agent/pr-diff.patch)\ngh pr view \"$PR_NUMBER\" \\\n --repo $EXPR_GITHUB_REPOSITORY \\\n --json number,title,body,headRefName,additions,deletions,changedFiles,files \\\n > /tmp/gh-aw/agent/pr-meta.json\necho \"Pre-fetched PR diff (${LINES} lines) and metadata\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n{ gh pr diff \"$PR_NUMBER\" --repo $EXPR_GITHUB_REPOSITORY \\\n --exclude '**/*.lock.yml' \\\n --exclude '**/generated/**' \\\n --exclude '**/dist/**' \\\n --exclude '**/build/**' \\\n || true; } | head -n 3000 > /tmp/gh-aw/agent/pr-diff.patch\nLINES=$(wc -l < /tmp/gh-aw/agent/pr-diff.patch)\ngh pr view \"$PR_NUMBER\" \\\n --repo $EXPR_GITHUB_REPOSITORY \\\n --json number,title,body,headRefName,additions,deletions,changedFiles,files \\\n > /tmp/gh-aw/agent/pr-meta.json\necho \"Pre-fetched PR diff (${LINES} lines) and metadata\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index b450c0e9e14..fd32f482178 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -535,7 +535,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials - run: "git config user.name \"github-actions[bot]\"\ngit config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n# Create .gitignore to exclude workflow YAML files\ncat > /tmp/gh-aw/agent/merge-gitignore << 'EOF'\n# Exclude all .yml files in .github/workflows/\n.github/workflows/*.yml\nEOF\n" + run: "git config user.name \"github-actions[bot]\"\ngit config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n# Create .gitignore to exclude workflow YAML files\ncat > /tmp/gh-aw/agent/merge-gitignore << 'EOF'\n# Exclude all .yml files in .github/workflows/\n.github/workflows/*.yml\nEOF" - name: Configure Git credentials env: diff --git a/.github/workflows/objective-impact-report.lock.yml b/.github/workflows/objective-impact-report.lock.yml index 4be7bdba8b7..de8be6fa7d6 100644 --- a/.github/workflows/objective-impact-report.lock.yml +++ b/.github/workflows/objective-impact-report.lock.yml @@ -522,7 +522,7 @@ jobs: name: Prepare safe-output issue evaluations uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - script: "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst { execFileSync } = require(\"child_process\");\nconst {\n evaluateItem,\n normalizeOutcome,\n readJSONL,\n} = require(path.join(process.env.GITHUB_WORKSPACE || process.cwd(), \"actions/setup/js/evaluate_outcomes.cjs\"));\n\nconst DATA_DIR = \"/tmp/gh-aw/agent/objective-impact-report\";\nconst RUNS_DIR = path.join(DATA_DIR, \"safe-output-runs\");\nconst OUTPUT_JSONL = path.join(DATA_DIR, \"safe-output-issue-evaluations.jsonl\");\nconst OUTPUT_SUMMARY = path.join(DATA_DIR, \"safe-output-issue-summary.json\");\n\nfunction readJSON(filePath, fallback) {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch {\n return fallback;\n }\n}\n\nfunction writeFileAtomic(filePath, content) {\n const baseName = path.basename(filePath);\n const parentDir = path.dirname(filePath);\n const tmpDir = fs.mkdtempSync(path.join(parentDir, `.${baseName}.tmp-`));\n const tmpFile = path.join(tmpDir, baseName);\n try {\n try {\n fs.writeFileSync(tmpFile, content);\n } catch (err) {\n throw new Error(`failed to write temp file ${tmpFile}`, { cause: err });\n }\n try {\n fs.renameSync(tmpFile, filePath);\n } catch (err) {\n throw new Error(`failed to rename temp file ${tmpFile} to ${filePath}`, { cause: err });\n }\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n\nfunction writeJSON(filePath, value) {\n writeFileAtomic(filePath, JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nfunction gh(args) {\n try {\n return execFileSync(\"gh\", args, { encoding: \"utf8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n } catch {\n return null;\n }\n}\n\nfunction ensureIssueURL(item, repo) {\n if (item.url || typeof item.number !== \"number\" || !repo) {\n return item;\n }\n return {\n ...item,\n url: `https://github.com/${repo}/issues/${item.number}`,\n };\n}\n\nfunction loadRuns() {\n const workflowLogs = readJSON(path.join(DATA_DIR, \"workflow-logs.json\"), {});\n const runs = Array.isArray(workflowLogs.runs) ? workflowLogs.runs : [];\n return runs\n .map(run => ({\n id: Number(run.id ?? run.databaseId ?? 0),\n workflow_name: run.workflow_name || run.workflowName || \"\",\n aic: run.aic ?? null,\n created_at: run.created_at || run.createdAt || \"\",\n status: run.status || \"\",\n conclusion: run.conclusion || \"\",\n url: run.html_url || run.url || \"\",\n }))\n .filter(run => Number.isInteger(run.id) && run.id > 0);\n}\n\nfunction loadManifest(runDir) {\n const manifestPath = path.join(runDir, \"safe-output-items.jsonl\");\n if (!fs.existsSync(manifestPath)) return [];\n return readJSONL(manifestPath);\n}\n\nfunction downloadManifest(repo, runId, runDir) {\n fs.mkdirSync(runDir, { recursive: true });\n const manifestPath = path.join(runDir, \"safe-output-items.jsonl\");\n if (fs.existsSync(manifestPath) && fs.statSync(manifestPath).size > 0) {\n return true;\n }\n const result = gh([\"run\", \"download\", String(runId), \"--repo\", repo, \"--name\", \"safe-outputs-items\", \"--dir\", runDir]);\n return result !== null && fs.existsSync(manifestPath) && fs.statSync(manifestPath).size > 0;\n}\n\nfunction main() {\n const repo = process.env.EXPR_GITHUB_REPOSITORY || process.env.GITHUB_REPOSITORY || \"\";\n if (!repo) {\n console.error(\"EXPR_GITHUB_REPOSITORY or GITHUB_REPOSITORY is required\");\n process.exit(1);\n }\n\n fs.mkdirSync(DATA_DIR, { recursive: true });\n fs.mkdirSync(RUNS_DIR, { recursive: true });\n\n const runs = loadRuns();\n const rows = [];\n\n for (const run of runs) {\n const runDir = path.join(RUNS_DIR, `run-${run.id}`);\n if (!downloadManifest(repo, run.id, runDir)) {\n continue;\n }\n\n const items = loadManifest(runDir)\n .filter(item => item && (item.type === \"create_issue\" || item.type === \"close_issue\"))\n .map(item => ensureIssueURL(item, item.repo || repo));\n\n for (const item of items) {\n const evalResult = evaluateItem(item, repo);\n const normalized = normalizeOutcome(evalResult.result, evalResult.detail);\n rows.push({\n run_id: run.id,\n workflow_name: run.workflow_name,\n workflow_aic: run.aic,\n workflow_run_created_at: run.created_at,\n workflow_run_url: run.url,\n type: item.type,\n repo: item.repo || repo,\n number: typeof item.number === \"number\" ? item.number : null,\n url: item.url || \"\",\n timestamp: item.timestamp || \"\",\n result: evalResult.result,\n detail: evalResult.detail,\n outcome_status: normalized.outcome_status,\n evidence_strength: normalized.evidence_strength,\n signal: normalized.signal,\n resolution_sec: evalResult.resolution_sec,\n pending_age_sec: evalResult.pending_age_sec,\n comments: evalResult.comments,\n reactions_total: evalResult.reactions_total,\n reactions_positive: evalResult.reactions_positive,\n reactions_negative: evalResult.reactions_negative,\n zero_touch: evalResult.zero_touch,\n });\n }\n }\n\n writeFileAtomic(OUTPUT_JSONL, rows.map(row => JSON.stringify(row)).join(\"\\n\") + (rows.length > 0 ? \"\\n\" : \"\"));\n\n const summary = {\n total_issue_outcomes: rows.length,\n create_issue_count: rows.filter(row => row.type === \"create_issue\").length,\n close_issue_count: rows.filter(row => row.type === \"close_issue\").length,\n accepted_count: rows.filter(row => row.outcome_status === \"accepted\").length,\n rejected_count: rows.filter(row => row.outcome_status === \"rejected\").length,\n pending_count: rows.filter(row => row.outcome_status === \"pending\").length,\n ignored_count: rows.filter(row => row.outcome_status === \"ignored\").length,\n unknown_count: rows.filter(row => row.outcome_status === \"unknown\").length,\n distinct_workflows: [...new Set(rows.map(row => row.workflow_name).filter(Boolean))].length,\n distinct_runs_with_issue_outcomes: [...new Set(rows.map(row => row.run_id))].length,\n };\n writeJSON(OUTPUT_SUMMARY, summary);\n}\n\nmain();\n" + script: "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst { execFileSync } = require(\"child_process\");\nconst {\n evaluateItem,\n normalizeOutcome,\n readJSONL,\n} = require(path.join(process.env.GITHUB_WORKSPACE || process.cwd(), \"actions/setup/js/evaluate_outcomes.cjs\"));\n\nconst DATA_DIR = \"/tmp/gh-aw/agent/objective-impact-report\";\nconst RUNS_DIR = path.join(DATA_DIR, \"safe-output-runs\");\nconst OUTPUT_JSONL = path.join(DATA_DIR, \"safe-output-issue-evaluations.jsonl\");\nconst OUTPUT_SUMMARY = path.join(DATA_DIR, \"safe-output-issue-summary.json\");\n\nfunction readJSON(filePath, fallback) {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch {\n return fallback;\n }\n}\n\nfunction writeFileAtomic(filePath, content) {\n const baseName = path.basename(filePath);\n const parentDir = path.dirname(filePath);\n const tmpDir = fs.mkdtempSync(path.join(parentDir, `.${baseName}.tmp-`));\n const tmpFile = path.join(tmpDir, baseName);\n try {\n try {\n fs.writeFileSync(tmpFile, content);\n } catch (err) {\n throw new Error(`failed to write temp file ${tmpFile}`, { cause: err });\n }\n try {\n fs.renameSync(tmpFile, filePath);\n } catch (err) {\n throw new Error(`failed to rename temp file ${tmpFile} to ${filePath}`, { cause: err });\n }\n } finally {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n }\n}\n\nfunction writeJSON(filePath, value) {\n writeFileAtomic(filePath, JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nfunction gh(args) {\n try {\n return execFileSync(\"gh\", args, { encoding: \"utf8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n } catch {\n return null;\n }\n}\n\nfunction ensureIssueURL(item, repo) {\n if (item.url || typeof item.number !== \"number\" || !repo) {\n return item;\n }\n return {\n ...item,\n url: `https://github.com/${repo}/issues/${item.number}`,\n };\n}\n\nfunction loadRuns() {\n const workflowLogs = readJSON(path.join(DATA_DIR, \"workflow-logs.json\"), {});\n const runs = Array.isArray(workflowLogs.runs) ? workflowLogs.runs : [];\n return runs\n .map(run => ({\n id: Number(run.id ?? run.databaseId ?? 0),\n workflow_name: run.workflow_name || run.workflowName || \"\",\n aic: run.aic ?? null,\n created_at: run.created_at || run.createdAt || \"\",\n status: run.status || \"\",\n conclusion: run.conclusion || \"\",\n url: run.html_url || run.url || \"\",\n }))\n .filter(run => Number.isInteger(run.id) && run.id > 0);\n}\n\nfunction loadManifest(runDir) {\n const manifestPath = path.join(runDir, \"safe-output-items.jsonl\");\n if (!fs.existsSync(manifestPath)) return [];\n return readJSONL(manifestPath);\n}\n\nfunction downloadManifest(repo, runId, runDir) {\n fs.mkdirSync(runDir, { recursive: true });\n const manifestPath = path.join(runDir, \"safe-output-items.jsonl\");\n if (fs.existsSync(manifestPath) && fs.statSync(manifestPath).size > 0) {\n return true;\n }\n const result = gh([\"run\", \"download\", String(runId), \"--repo\", repo, \"--name\", \"safe-outputs-items\", \"--dir\", runDir]);\n return result !== null && fs.existsSync(manifestPath) && fs.statSync(manifestPath).size > 0;\n}\n\nfunction main() {\n const repo = process.env.EXPR_GITHUB_REPOSITORY || process.env.GITHUB_REPOSITORY || \"\";\n if (!repo) {\n console.error(\"EXPR_GITHUB_REPOSITORY or GITHUB_REPOSITORY is required\");\n process.exit(1);\n }\n\n fs.mkdirSync(DATA_DIR, { recursive: true });\n fs.mkdirSync(RUNS_DIR, { recursive: true });\n\n const runs = loadRuns();\n const rows = [];\n\n for (const run of runs) {\n const runDir = path.join(RUNS_DIR, `run-${run.id}`);\n if (!downloadManifest(repo, run.id, runDir)) {\n continue;\n }\n\n const items = loadManifest(runDir)\n .filter(item => item && (item.type === \"create_issue\" || item.type === \"close_issue\"))\n .map(item => ensureIssueURL(item, item.repo || repo));\n\n for (const item of items) {\n const evalResult = evaluateItem(item, repo);\n const normalized = normalizeOutcome(evalResult.result, evalResult.detail);\n rows.push({\n run_id: run.id,\n workflow_name: run.workflow_name,\n workflow_aic: run.aic,\n workflow_run_created_at: run.created_at,\n workflow_run_url: run.url,\n type: item.type,\n repo: item.repo || repo,\n number: typeof item.number === \"number\" ? item.number : null,\n url: item.url || \"\",\n timestamp: item.timestamp || \"\",\n result: evalResult.result,\n detail: evalResult.detail,\n outcome_status: normalized.outcome_status,\n evidence_strength: normalized.evidence_strength,\n signal: normalized.signal,\n resolution_sec: evalResult.resolution_sec,\n pending_age_sec: evalResult.pending_age_sec,\n comments: evalResult.comments,\n reactions_total: evalResult.reactions_total,\n reactions_positive: evalResult.reactions_positive,\n reactions_negative: evalResult.reactions_negative,\n zero_touch: evalResult.zero_touch,\n });\n }\n }\n\n writeFileAtomic(OUTPUT_JSONL, rows.map(row => JSON.stringify(row)).join(\"\\n\") + (rows.length > 0 ? \"\\n\" : \"\"));\n\n const summary = {\n total_issue_outcomes: rows.length,\n create_issue_count: rows.filter(row => row.type === \"create_issue\").length,\n close_issue_count: rows.filter(row => row.type === \"close_issue\").length,\n accepted_count: rows.filter(row => row.outcome_status === \"accepted\").length,\n rejected_count: rows.filter(row => row.outcome_status === \"rejected\").length,\n pending_count: rows.filter(row => row.outcome_status === \"pending\").length,\n ignored_count: rows.filter(row => row.outcome_status === \"ignored\").length,\n unknown_count: rows.filter(row => row.outcome_status === \"unknown\").length,\n distinct_workflows: [...new Set(rows.map(row => row.workflow_name).filter(Boolean))].length,\n distinct_runs_with_issue_outcomes: [...new Set(rows.map(row => row.run_id))].length,\n };\n writeJSON(OUTPUT_SUMMARY, summary);\n}\n\nmain();" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/pr-description-caveman.lock.yml b/.github/workflows/pr-description-caveman.lock.yml index e3611bb4129..e63c783d20b 100644 --- a/.github/workflows/pr-description-caveman.lock.yml +++ b/.github/workflows/pr-description-caveman.lock.yml @@ -480,7 +480,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} name: Fetch and chunk PR diff - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/chunks\n\nEXCLUSIONS=(\n ':!*.lock.yml' ':!*.lock' ':!*-lock.json' ':!yarn.lock'\n ':!go.sum' ':!go.mod'\n ':!*.generated.*' ':!generated/**' ':!vendor/**'\n ':!dist/**' ':!*.min.js' ':!*.min.css'\n)\n\n# Diff stat (always small — safe to capture in full)\ngit diff \"$BASE_SHA\"...\"$HEAD_SHA\" -- \"${EXCLUSIONS[@]}\" --stat \\\n > /tmp/gh-aw/agent/diff-stat.txt 2>&1 || true\n\n# Commit log\ngit log --oneline \"$BASE_SHA\"..\"$HEAD_SHA\" \\\n > /tmp/gh-aw/agent/commits.txt 2>&1 || true\n\n# Full diff split into 400-line chunks\n# split -l produces chunk_000, chunk_001, ...\ngit diff \"$BASE_SHA\"...\"$HEAD_SHA\" -- \"${EXCLUSIONS[@]}\" \\\n | split -l 400 - /tmp/gh-aw/agent/chunks/chunk_ 2>/dev/null || true\n\n# Record chunk manifest and count so the agent can process deterministically\nls /tmp/gh-aw/agent/chunks/ > /tmp/gh-aw/agent/chunk-manifest.txt\nwc -l < /tmp/gh-aw/agent/chunk-manifest.txt > /tmp/gh-aw/agent/chunk-count.txt\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/chunks\n\nEXCLUSIONS=(\n ':!*.lock.yml' ':!*.lock' ':!*-lock.json' ':!yarn.lock'\n ':!go.sum' ':!go.mod'\n ':!*.generated.*' ':!generated/**' ':!vendor/**'\n ':!dist/**' ':!*.min.js' ':!*.min.css'\n)\n\n# Diff stat (always small — safe to capture in full)\ngit diff \"$BASE_SHA\"...\"$HEAD_SHA\" -- \"${EXCLUSIONS[@]}\" --stat \\\n > /tmp/gh-aw/agent/diff-stat.txt 2>&1 || true\n\n# Commit log\ngit log --oneline \"$BASE_SHA\"..\"$HEAD_SHA\" \\\n > /tmp/gh-aw/agent/commits.txt 2>&1 || true\n\n# Full diff split into 400-line chunks\n# split -l produces chunk_000, chunk_001, ...\ngit diff \"$BASE_SHA\"...\"$HEAD_SHA\" -- \"${EXCLUSIONS[@]}\" \\\n | split -l 400 - /tmp/gh-aw/agent/chunks/chunk_ 2>/dev/null || true\n\n# Record chunk manifest and count so the agent can process deterministically\nls /tmp/gh-aw/agent/chunks/ > /tmp/gh-aw/agent/chunk-manifest.txt\nwc -l < /tmp/gh-aw/agent/chunk-manifest.txt > /tmp/gh-aw/agent/chunk-count.txt" - name: Configure Git credentials env: diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index a267142e5d6..3eab6c36254 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -569,7 +569,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Download workflow logs for PR analysis - run: "# Create logs directory\nmkdir -p /tmp/gh-aw/agent/workflow-logs\n\necho \"Downloading workflow logs to extract turn counts...\"\n\n# Download logs for the last 30 days of copilot workflows\n# This will give us the aw_info.json which contains turn counts\n./gh-aw logs --engine copilot --start-date -30d -o /tmp/gh-aw/agent/workflow-logs\n\n# Verify logs were downloaded\necho \"Downloaded workflow logs:\"\nfind /tmp/gh-aw/agent/workflow-logs -maxdepth 1 -ls\n" + run: "# Create logs directory\nmkdir -p /tmp/gh-aw/agent/workflow-logs\n\necho \"Downloading workflow logs to extract turn counts...\"\n\n# Download logs for the last 30 days of copilot workflows\n# This will give us the aw_info.json which contains turn counts\n./gh-aw logs --engine copilot --start-date -30d -o /tmp/gh-aw/agent/workflow-logs\n\n# Verify logs were downloaded\necho \"Downloaded workflow logs:\"\nfind /tmp/gh-aw/agent/workflow-logs -maxdepth 1 -ls" # Cache configuration from frontmatter processed below - name: Save prompt clustering data to cache diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index d06c96072a6..cbe60cf6d1e 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -502,7 +502,7 @@ jobs: RELEASE_ID: ${{ needs.release.outputs.release_id }} RELEASE_TAG: ${{ needs.config.outputs.release_tag }} name: Setup release environment - run: "set -e\nmkdir -p /tmp/gh-aw/agent/release-data\nmkdir -p /tmp/gh-aw/agent/community-data\n# Copy community issues from the agent/community-data path (written by community-attribution import step)\ncp /tmp/gh-aw/agent/community-data/community_issues.json /tmp/gh-aw/agent/community-data/community_issues.json 2>/dev/null || echo \"[]\" > /tmp/gh-aw/agent/community-data/community_issues.json\n\n# Use the release ID and tag from the release job\necho \"Release ID from release job: $RELEASE_ID\"\necho \"Release tag from release job: $RELEASE_TAG\"\n\necho \"Processing release: $RELEASE_TAG\"\necho \"RELEASE_TAG=$RELEASE_TAG\" >> \"$GITHUB_ENV\"\n\n# Get the current release information\n# Use release ID to fetch release data\ngh api \"/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID\" > /tmp/gh-aw/agent/release-data/current_release.json\necho \"✓ Fetched current release information\"\n\n# Get the previous release to determine the range\nPREV_RELEASE_TAG=$(gh release list --limit 2 --json tagName --jq '.[1].tagName // empty')\n\nif [ -z \"$PREV_RELEASE_TAG\" ]; then\n echo \"No previous release found. This appears to be the first release.\"\n echo \"PREV_RELEASE_TAG=\" >> \"$GITHUB_ENV\"\n touch /tmp/gh-aw/agent/release-data/pull_requests.json\n echo \"[]\" > /tmp/gh-aw/agent/release-data/pull_requests.json\nelse\n echo \"Previous release: $PREV_RELEASE_TAG\"\n echo \"PREV_RELEASE_TAG=$PREV_RELEASE_TAG\" >> \"$GITHUB_ENV\"\n \n # Get commits between releases\n echo \"Fetching commits between $PREV_RELEASE_TAG and $RELEASE_TAG...\"\n git fetch --unshallow 2>/dev/null || git fetch --depth=1000\n \n # Get all merged PRs between the two releases (include closingIssuesReferences for attribution)\n echo \"Fetching pull requests merged between releases...\"\n PREV_PUBLISHED_AT=$(gh release view \"$PREV_RELEASE_TAG\" --json publishedAt --jq .publishedAt)\n CURR_PUBLISHED_AT=$(gh release view \"$RELEASE_TAG\" --json publishedAt --jq .publishedAt)\n gh pr list \\\n --state merged \\\n --limit 1000 \\\n --json number,title,author,labels,mergedAt,url,body,closingIssuesReferences \\\n --jq \"[.[] | select(.mergedAt >= \\\"$PREV_PUBLISHED_AT\\\" and .mergedAt <= \\\"$CURR_PUBLISHED_AT\\\")]\" \\\n > /tmp/gh-aw/agent/release-data/pull_requests.json\n \n PR_COUNT=$(jq length \"/tmp/gh-aw/agent/release-data/pull_requests.json\")\n echo \"✓ Fetched $PR_COUNT pull requests\"\nfi\n\n# Build closing references index from GitHub-native closingIssuesReferences\n# Maps each closed issue number -> list of PR numbers that directly close it\necho \"Building closing references index from GitHub-native PR links...\"\n# Use a nested reduce so the outer body always returns the accumulator,\n# even when closingIssuesReferences is empty (avoids jq setting acc to null).\njq '\n reduce .[] as $pr (\n {};\n reduce ($pr.closingIssuesReferences // [])[] as $issue (\n .;\n ($issue.number | tostring) as $key |\n .[$key] = (.[$key] // []) + [$pr.number]\n )\n )\n' /tmp/gh-aw/agent/release-data/pull_requests.json \\\n > /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json 2>/dev/null \\\n || echo \"{}\" > /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json\n# Also expose to community-data dir so shared attribution strategy can reference it\ncp /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json /tmp/gh-aw/agent/community-data/closing_refs_by_issue.json\ncp /tmp/gh-aw/agent/release-data/pull_requests.json /tmp/gh-aw/agent/community-data/pull_requests.json\n\nDIRECT_CLOSE_COUNT=$(jq 'keys | length' /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json)\necho \"✓ Found $DIRECT_CLOSE_COUNT issues with GitHub-native closing PR references\"\n\n# Find community issues closed during this release window (candidates for attribution review)\nif [ -n \"$PREV_PUBLISHED_AT\" ]; then\n jq --arg prev \"$PREV_PUBLISHED_AT\" --arg curr \"$CURR_PUBLISHED_AT\" \\\n '[.[] | select(.closedAt != null and .closedAt >= $prev and .closedAt <= $curr)]' \\\n /tmp/gh-aw/agent/community-data/community_issues.json \\\n > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json 2>/dev/null \\\n || echo \"[]\" > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json\n \n CLOSED_IN_WINDOW=$(jq length /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json)\n echo \"✓ Found $CLOSED_IN_WINDOW community issues closed in this release window\"\nelse\n echo \"[]\" > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json\nfi\n\n# Get the CHANGELOG.md content around this version\nif [ -f \"CHANGELOG.md\" ]; then\n cp CHANGELOG.md /tmp/gh-aw/agent/release-data/CHANGELOG.md\n echo \"✓ Copied CHANGELOG.md for reference\"\nfi\n\n# List documentation files for linking\nfind docs -type f -name \"*.md\" 2>/dev/null > /tmp/gh-aw/agent/release-data/docs_files.txt || echo \"No docs directory found\"\n\necho \"✓ Setup complete.\"\necho \" Release data: /tmp/gh-aw/agent/release-data/ (current_release.json, pull_requests.json,\"\necho \" closing_refs_by_issue.json, community_issues_closed_in_window.json,\"\necho \" CHANGELOG.md (if exists), docs_files.txt)\"\necho \" Community data: /tmp/gh-aw/agent/community-data/ (community_issues.json,\"\necho \" closing_refs_by_issue.json, pull_requests.json)\"\n" + run: "set -e\nmkdir -p /tmp/gh-aw/agent/release-data\nmkdir -p /tmp/gh-aw/agent/community-data\n# Copy community issues from the agent/community-data path (written by community-attribution import step)\ncp /tmp/gh-aw/agent/community-data/community_issues.json /tmp/gh-aw/agent/community-data/community_issues.json 2>/dev/null || echo \"[]\" > /tmp/gh-aw/agent/community-data/community_issues.json\n\n# Use the release ID and tag from the release job\necho \"Release ID from release job: $RELEASE_ID\"\necho \"Release tag from release job: $RELEASE_TAG\"\n\necho \"Processing release: $RELEASE_TAG\"\necho \"RELEASE_TAG=$RELEASE_TAG\" >> \"$GITHUB_ENV\"\n\n# Get the current release information\n# Use release ID to fetch release data\ngh api \"/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID\" > /tmp/gh-aw/agent/release-data/current_release.json\necho \"✓ Fetched current release information\"\n\n# Get the previous release to determine the range\nPREV_RELEASE_TAG=$(gh release list --limit 2 --json tagName --jq '.[1].tagName // empty')\n\nif [ -z \"$PREV_RELEASE_TAG\" ]; then\n echo \"No previous release found. This appears to be the first release.\"\n echo \"PREV_RELEASE_TAG=\" >> \"$GITHUB_ENV\"\n touch /tmp/gh-aw/agent/release-data/pull_requests.json\n echo \"[]\" > /tmp/gh-aw/agent/release-data/pull_requests.json\nelse\n echo \"Previous release: $PREV_RELEASE_TAG\"\n echo \"PREV_RELEASE_TAG=$PREV_RELEASE_TAG\" >> \"$GITHUB_ENV\"\n \n # Get commits between releases\n echo \"Fetching commits between $PREV_RELEASE_TAG and $RELEASE_TAG...\"\n git fetch --unshallow 2>/dev/null || git fetch --depth=1000\n \n # Get all merged PRs between the two releases (include closingIssuesReferences for attribution)\n echo \"Fetching pull requests merged between releases...\"\n PREV_PUBLISHED_AT=$(gh release view \"$PREV_RELEASE_TAG\" --json publishedAt --jq .publishedAt)\n CURR_PUBLISHED_AT=$(gh release view \"$RELEASE_TAG\" --json publishedAt --jq .publishedAt)\n gh pr list \\\n --state merged \\\n --limit 1000 \\\n --json number,title,author,labels,mergedAt,url,body,closingIssuesReferences \\\n --jq \"[.[] | select(.mergedAt >= \\\"$PREV_PUBLISHED_AT\\\" and .mergedAt <= \\\"$CURR_PUBLISHED_AT\\\")]\" \\\n > /tmp/gh-aw/agent/release-data/pull_requests.json\n \n PR_COUNT=$(jq length \"/tmp/gh-aw/agent/release-data/pull_requests.json\")\n echo \"✓ Fetched $PR_COUNT pull requests\"\nfi\n\n# Build closing references index from GitHub-native closingIssuesReferences\n# Maps each closed issue number -> list of PR numbers that directly close it\necho \"Building closing references index from GitHub-native PR links...\"\n# Use a nested reduce so the outer body always returns the accumulator,\n# even when closingIssuesReferences is empty (avoids jq setting acc to null).\njq '\n reduce .[] as $pr (\n {};\n reduce ($pr.closingIssuesReferences // [])[] as $issue (\n .;\n ($issue.number | tostring) as $key |\n .[$key] = (.[$key] // []) + [$pr.number]\n )\n )\n' /tmp/gh-aw/agent/release-data/pull_requests.json \\\n > /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json 2>/dev/null \\\n || echo \"{}\" > /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json\n# Also expose to community-data dir so shared attribution strategy can reference it\ncp /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json /tmp/gh-aw/agent/community-data/closing_refs_by_issue.json\ncp /tmp/gh-aw/agent/release-data/pull_requests.json /tmp/gh-aw/agent/community-data/pull_requests.json\n\nDIRECT_CLOSE_COUNT=$(jq 'keys | length' /tmp/gh-aw/agent/release-data/closing_refs_by_issue.json)\necho \"✓ Found $DIRECT_CLOSE_COUNT issues with GitHub-native closing PR references\"\n\n# Find community issues closed during this release window (candidates for attribution review)\nif [ -n \"$PREV_PUBLISHED_AT\" ]; then\n jq --arg prev \"$PREV_PUBLISHED_AT\" --arg curr \"$CURR_PUBLISHED_AT\" \\\n '[.[] | select(.closedAt != null and .closedAt >= $prev and .closedAt <= $curr)]' \\\n /tmp/gh-aw/agent/community-data/community_issues.json \\\n > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json 2>/dev/null \\\n || echo \"[]\" > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json\n \n CLOSED_IN_WINDOW=$(jq length /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json)\n echo \"✓ Found $CLOSED_IN_WINDOW community issues closed in this release window\"\nelse\n echo \"[]\" > /tmp/gh-aw/agent/release-data/community_issues_closed_in_window.json\nfi\n\n# Get the CHANGELOG.md content around this version\nif [ -f \"CHANGELOG.md\" ]; then\n cp CHANGELOG.md /tmp/gh-aw/agent/release-data/CHANGELOG.md\n echo \"✓ Copied CHANGELOG.md for reference\"\nfi\n\n# List documentation files for linking\nfind docs -type f -name \"*.md\" 2>/dev/null > /tmp/gh-aw/agent/release-data/docs_files.txt || echo \"No docs directory found\"\n\necho \"✓ Setup complete.\"\necho \" Release data: /tmp/gh-aw/agent/release-data/ (current_release.json, pull_requests.json,\"\necho \" closing_refs_by_issue.json, community_issues_closed_in_window.json,\"\necho \" CHANGELOG.md (if exists), docs_files.txt)\"\necho \" Community data: /tmp/gh-aw/agent/community-data/ (community_issues.json,\"\necho \" closing_refs_by_issue.json, pull_requests.json)\"" - name: Configure Git credentials env: diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 02b41ff9441..f100c388288 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -493,7 +493,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: Collect quality metrics - run: "mkdir -p /tmp/gh-aw/agent\n{\n echo \"## Focus Area History\"\n if [ -f /tmp/gh-aw/cache-memory-focus-areas/history.json ]; then\n cat /tmp/gh-aw/cache-memory-focus-areas/history.json\n else\n echo '{\"runs\":[],\"recent_areas\":[],\"statistics\":{\"total_runs\":0,\"custom_rate\":0,\"reuse_rate\":0,\"unique_areas_explored\":0}}'\n fi\n\n echo \"\"\n echo \"## Code Metrics\"\n echo \"### Largest Go source files (top 20)\"\n find . -type f -name \"*.go\" ! -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | sort -rn | head -21 | tail -20\n\n echo \"### Test ratio\"\n TEST_LOC=$(find . -type f -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')\n SRC_LOC=$(find . -type f -name \"*.go\" ! -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')\n echo \"Test LOC: $TEST_LOC | Source LOC: $SRC_LOC\"\n\n echo \"### Directory file counts\"\n for dir in cmd pkg docs .github; do\n if [ -d \"$dir\" ]; then\n echo \"$dir: $(find \"$dir\" -type f | wc -l) files\"\n fi\n done\n\n echo \"### TODO/FIXME count\"\n grep -r \"TODO\\|FIXME\" --include=\"*.go\" --include=\"*.cjs\" . 2>/dev/null | wc -l\n\n echo \"### README size\"\n wc -l README.md 2>/dev/null || echo \"No README.md\"\n} > /tmp/gh-aw/agent/analysis-context.md\necho \"✅ Quality metrics collected → /tmp/gh-aw/agent/analysis-context.md\"\n" + run: "mkdir -p /tmp/gh-aw/agent\n{\n echo \"## Focus Area History\"\n if [ -f /tmp/gh-aw/cache-memory-focus-areas/history.json ]; then\n cat /tmp/gh-aw/cache-memory-focus-areas/history.json\n else\n echo '{\"runs\":[],\"recent_areas\":[],\"statistics\":{\"total_runs\":0,\"custom_rate\":0,\"reuse_rate\":0,\"unique_areas_explored\":0}}'\n fi\n\n echo \"\"\n echo \"## Code Metrics\"\n echo \"### Largest Go source files (top 20)\"\n find . -type f -name \"*.go\" ! -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | sort -rn | head -21 | tail -20\n\n echo \"### Test ratio\"\n TEST_LOC=$(find . -type f -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')\n SRC_LOC=$(find . -type f -name \"*.go\" ! -name \"*_test.go\" ! -path \"./.git/*\" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')\n echo \"Test LOC: $TEST_LOC | Source LOC: $SRC_LOC\"\n\n echo \"### Directory file counts\"\n for dir in cmd pkg docs .github; do\n if [ -d \"$dir\" ]; then\n echo \"$dir: $(find \"$dir\" -type f | wc -l) files\"\n fi\n done\n\n echo \"### TODO/FIXME count\"\n grep -r \"TODO\\|FIXME\" --include=\"*.go\" --include=\"*.cjs\" . 2>/dev/null | wc -l\n\n echo \"### README size\"\n wc -l README.md 2>/dev/null || echo \"No README.md\"\n} > /tmp/gh-aw/agent/analysis-context.md\necho \"✅ Quality metrics collected → /tmp/gh-aw/agent/analysis-context.md\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory (focus-areas) diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 6b5c2951ca7..30e66705b4f 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -569,7 +569,7 @@ jobs: GH_AW_SKILL_DIR: ".pi/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Precompute schema analysis data - run: "set -e\nmkdir -p /tmp/gh-aw/agent\n\necho \"=== Extracting schema fields ===\"\n\n# 1. All top-level fields in the main JSON schema\nSCHEMA_FIELDS=$(jq -r '.properties | keys[]' pkg/parser/schemas/main_workflow_schema.json 2>/dev/null | sort -u || echo \"\")\n\n# 2. yaml-tagged struct fields in pkg/parser/*.go\nPARSER_YAML_FIELDS=$(grep -rh 'yaml:\"' pkg/parser/*.go 2>/dev/null \\\n | grep -o 'yaml:\"[^\"]*\"' \\\n | sed 's/yaml:\"//;s/\"//' \\\n | sed 's/,omitempty//' \\\n | sed 's/,.*$//' \\\n | grep -v '^-$' \\\n | grep -v '^$' \\\n | sort -u || echo \"\")\n\n# 3. yaml-tagged struct fields in pkg/workflow/*.go\nWORKFLOW_YAML_FIELDS=$(grep -rh 'yaml:\"' pkg/workflow/*.go 2>/dev/null \\\n | grep -o 'yaml:\"[^\"]*\"' \\\n | sed 's/yaml:\"//;s/\"//' \\\n | sed 's/,omitempty//' \\\n | sed 's/,.*$//' \\\n | grep -v '^-$' \\\n | grep -v '^$' \\\n | sort -u || echo \"\")\n\n# 4. Top-level frontmatter keys actually used in workflow .md files\nUSED_FIELDS=$(grep -rh '^[a-z][a-z0-9_-]*:' .github/workflows/*.md 2>/dev/null \\\n | sed 's/:.*//' \\\n | grep -v '^#' \\\n | sort -u || echo \"\")\n\n# 5. Schema field types for all top-level fields\nFIELD_TYPES=$(jq -r '.properties | to_entries[] |\n \"\\(.key): \\(.value.type // (.value.anyOf // .value.oneOf // [] | map(.type // \"complex\") | unique | join(\"|\")) // \"complex\")\"' \\\n pkg/parser/schemas/main_workflow_schema.json 2>/dev/null | sort || echo \"\")\n\n# 6. Fields in schema but absent as yaml tags in parser structs\nIN_SCHEMA_NOT_PARSER=$(comm -23 \\\n <(echo \"$SCHEMA_FIELDS\") \\\n <(echo \"$PARSER_YAML_FIELDS\" | sort -u) 2>/dev/null || echo \"\")\n\n# 7. yaml tags in parser structs absent from schema\nIN_PARSER_NOT_SCHEMA=$(comm -23 \\\n <(echo \"$PARSER_YAML_FIELDS\" | sort -u) \\\n <(echo \"$SCHEMA_FIELDS\") 2>/dev/null || echo \"\")\n\n# 8. Fields in schema but absent from workflow compiler structs\nIN_SCHEMA_NOT_WORKFLOW=$(comm -23 \\\n <(echo \"$SCHEMA_FIELDS\") \\\n <(echo \"$WORKFLOW_YAML_FIELDS\" | sort -u) 2>/dev/null || echo \"\")\n\n# 9. Fields used in actual workflow .md files but not in schema\nIN_USED_NOT_SCHEMA=$(comm -23 \\\n <(echo \"$USED_FIELDS\" | sort -u) \\\n <(echo \"$SCHEMA_FIELDS\") 2>/dev/null || echo \"\")\n\n# Write JSON output\njq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg schema_fields \"$SCHEMA_FIELDS\" \\\n --arg parser_yaml_fields \"$PARSER_YAML_FIELDS\" \\\n --arg workflow_yaml_fields \"$WORKFLOW_YAML_FIELDS\" \\\n --arg used_in_workflows \"$USED_FIELDS\" \\\n --arg field_types \"$FIELD_TYPES\" \\\n --arg in_schema_not_parser \"$IN_SCHEMA_NOT_PARSER\" \\\n --arg in_parser_not_schema \"$IN_PARSER_NOT_SCHEMA\" \\\n --arg in_schema_not_workflow \"$IN_SCHEMA_NOT_WORKFLOW\" \\\n --arg in_used_not_schema \"$IN_USED_NOT_SCHEMA\" \\\n '{\n generated_at: $generated_at,\n schema_fields: ($schema_fields | split(\"\\n\") | map(select(. != \"\"))),\n parser_yaml_fields: ($parser_yaml_fields | split(\"\\n\") | map(select(. != \"\"))),\n workflow_yaml_fields: ($workflow_yaml_fields | split(\"\\n\") | map(select(. != \"\"))),\n used_in_workflows: ($used_in_workflows | split(\"\\n\") | map(select(. != \"\"))),\n field_types: ($field_types | split(\"\\n\") | map(select(. != \"\"))),\n field_gaps: {\n in_schema_not_parser: ($in_schema_not_parser | split(\"\\n\") | map(select(. != \"\"))),\n in_parser_not_schema: ($in_parser_not_schema | split(\"\\n\") | map(select(. != \"\"))),\n in_schema_not_workflow: ($in_schema_not_workflow | split(\"\\n\") | map(select(. != \"\"))),\n in_used_not_schema: ($in_used_not_schema | split(\"\\n\") | map(select(. != \"\")))\n }\n }' > /tmp/gh-aw/agent/schema-diff.json\n\necho \"✓ Schema diff written to /tmp/gh-aw/agent/schema-diff.json\"\necho \"Summary:\"\njq '{\n schema_field_count: (.schema_fields | length),\n parser_yaml_field_count: (.parser_yaml_fields | length),\n workflow_yaml_field_count: (.workflow_yaml_fields | length),\n gaps: {\n in_schema_not_parser: (.field_gaps.in_schema_not_parser | length),\n in_parser_not_schema: (.field_gaps.in_parser_not_schema | length),\n in_schema_not_workflow: (.field_gaps.in_schema_not_workflow | length),\n in_used_not_schema: (.field_gaps.in_used_not_schema | length)\n }\n}' /tmp/gh-aw/agent/schema-diff.json\n\necho \"=== AWF config source drift pre-check (gh-aw-firewall) ===\"\nAWF_SNAPSHOT_DIR=/tmp/gh-aw/cache-memory/awf-config-sources\nmkdir -p \"$AWF_SNAPSHOT_DIR\"\nAWF_CANONICAL_FETCH_DEGRADED=false\nAWF_USING_SNAPSHOT=false\nAWF_FETCH_FAILED_SOURCES=\"\"\n\nfetch_awf_source() {\n local source_path=\"$1\"\n local target_path=\"$2\"\n if ! gh api -H \"Accept: application/vnd.github.raw\" \"/repos/github/gh-aw-firewall/contents/${source_path}\" > \"$target_path\"; then\n AWF_CANONICAL_FETCH_DEGRADED=true\n AWF_FETCH_FAILED_SOURCES=\"${AWF_FETCH_FAILED_SOURCES}${source_path}\\n\"\n rm -f \"$target_path\"\n return 1\n fi\n}\n\nif [ -n \"${GH_TOKEN:-${GITHUB_TOKEN:-}}\" ]; then\n fetch_awf_source docs/awf-config.schema.json /tmp/gh-aw/agent/awf-config.schema.json || true\n fetch_awf_source src/awf-config-schema.json /tmp/gh-aw/agent/awf-config-runtime.schema.json || true\n fetch_awf_source docs/awf-config-spec.md /tmp/gh-aw/agent/awf-config-spec.md || true\nelse\n AWF_CANONICAL_FETCH_DEGRADED=true\n AWF_FETCH_FAILED_SOURCES=\"docs/awf-config.schema.json\\nsrc/awf-config-schema.json\\ndocs/awf-config-spec.md\\n\"\n echo \"⚠️ AWF canonical source fetch degraded: GH_TOKEN/GITHUB_TOKEN is not set\"\nfi\n\nfor source_path in docs/awf-config.schema.json src/awf-config-schema.json docs/awf-config-spec.md; do\n source_file=$(basename \"$source_path\")\n if [ \"$source_path\" = \"src/awf-config-schema.json\" ]; then\n target_path=/tmp/gh-aw/agent/awf-config-runtime.schema.json\n source_file=awf-config-runtime.schema.json\n else\n target_path=/tmp/gh-aw/agent/\"$source_file\"\n fi\n\n if [ ! -s \"$target_path\" ] && [ -s \"$AWF_SNAPSHOT_DIR/$source_file\" ]; then\n cp \"$AWF_SNAPSHOT_DIR/$source_file\" \"$target_path\"\n AWF_USING_SNAPSHOT=true\n echo \"⚠️ Using last-known AWF snapshot for $source_path\"\n fi\ndone\n\nif [ -s /tmp/gh-aw/agent/awf-config.schema.json ] && [ -s /tmp/gh-aw/agent/awf-config-runtime.schema.json ] && [ -s /tmp/gh-aw/agent/awf-config-spec.md ]; then\n cp /tmp/gh-aw/agent/awf-config.schema.json \"$AWF_SNAPSHOT_DIR/awf-config.schema.json\"\n cp /tmp/gh-aw/agent/awf-config-runtime.schema.json \"$AWF_SNAPSHOT_DIR/awf-config-runtime.schema.json\"\n cp /tmp/gh-aw/agent/awf-config-spec.md \"$AWF_SNAPSHOT_DIR/awf-config-spec.md\"\n\n jq -r '.properties | keys[]' /tmp/gh-aw/agent/awf-config.schema.json | sort -u \\\n > /tmp/gh-aw/agent/awf-config-top-level.txt\n jq -r '.properties | keys[]' /tmp/gh-aw/agent/awf-config-runtime.schema.json | sort -u \\\n > /tmp/gh-aw/agent/awf-config-runtime-top-level.txt\n rg --no-heading --no-filename 'apiProxy|container|sandbox|auth|network' pkg/workflow actions/setup \\\n | head -200 > /tmp/gh-aw/agent/awf-config-ghaw-refs.txt || true\n\n FAILED_SOURCES_JSON=$(printf \"%b\" \"$AWF_FETCH_FAILED_SOURCES\" | sed '/^$/d' | sort -u | jq -Rsc 'split(\"\\n\") | map(select(length > 0))')\n jq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg spec_path \"docs/awf-config-spec.md\" \\\n --arg schema_path \"docs/awf-config.schema.json\" \\\n --arg runtime_schema_path \"src/awf-config-schema.json\" \\\n --arg top_level_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-top-level.txt | tr -d ' ')\" \\\n --arg runtime_top_level_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-runtime-top-level.txt | tr -d ' ')\" \\\n --arg refs_sample_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-ghaw-refs.txt | tr -d ' ')\" \\\n --arg degraded \"$AWF_CANONICAL_FETCH_DEGRADED\" \\\n --arg using_snapshot \"$AWF_USING_SNAPSHOT\" \\\n --argjson failed_sources \"$FAILED_SOURCES_JSON\" \\\n '{\n generated_at: $generated_at,\n source_repo: \"github/gh-aw-firewall\",\n canonical_spec: $spec_path,\n canonical_schema: $schema_path,\n canonical_runtime_schema: $runtime_schema_path,\n top_level_property_count: ($top_level_count | tonumber),\n runtime_top_level_property_count: ($runtime_top_level_count | tonumber),\n ghaw_reference_sample_count: ($refs_sample_count | tonumber),\n degraded: ($degraded == \"true\"),\n using_snapshot: ($using_snapshot == \"true\"),\n failed_sources: $failed_sources\n }' > /tmp/gh-aw/agent/awf-config-drift.json\n if [ \"$AWF_CANONICAL_FETCH_DEGRADED\" = true ]; then\n echo \"⚠️ AWF canonical source fetch degraded; continuing in non-fatal mode\"\n else\n echo \"✓ AWF config source pre-check artifacts written under /tmp/gh-aw/agent/\"\n fi\nelse\n AWF_CANONICAL_FETCH_DEGRADED=true\n FAILED_SOURCES_JSON=$(printf \"%b\" \"$AWF_FETCH_FAILED_SOURCES\" | sed '/^$/d' | sort -u | jq -Rsc 'split(\"\\n\") | map(select(length > 0))')\n jq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --argjson failed_sources \"$FAILED_SOURCES_JSON\" \\\n '{\n generated_at: $generated_at,\n source_repo: \"github/gh-aw-firewall\",\n degraded: true,\n warning: \"canonical source retrieval failed; skipping destructive AWF drift actions\",\n failed_sources: $failed_sources\n }' > /tmp/gh-aw/agent/awf-config-drift.json\n echo \"⚠️ AWF canonical source fetch failed; run marked degraded (non-fatal)\"\nfi\n" + run: "set -e\nmkdir -p /tmp/gh-aw/agent\n\necho \"=== Extracting schema fields ===\"\n\n# 1. All top-level fields in the main JSON schema\nSCHEMA_FIELDS=$(jq -r '.properties | keys[]' pkg/parser/schemas/main_workflow_schema.json 2>/dev/null | sort -u || echo \"\")\n\n# 2. yaml-tagged struct fields in pkg/parser/*.go\nPARSER_YAML_FIELDS=$(grep -rh 'yaml:\"' pkg/parser/*.go 2>/dev/null \\\n | grep -o 'yaml:\"[^\"]*\"' \\\n | sed 's/yaml:\"//;s/\"//' \\\n | sed 's/,omitempty//' \\\n | sed 's/,.*$//' \\\n | grep -v '^-$' \\\n | grep -v '^$' \\\n | sort -u || echo \"\")\n\n# 3. yaml-tagged struct fields in pkg/workflow/*.go\nWORKFLOW_YAML_FIELDS=$(grep -rh 'yaml:\"' pkg/workflow/*.go 2>/dev/null \\\n | grep -o 'yaml:\"[^\"]*\"' \\\n | sed 's/yaml:\"//;s/\"//' \\\n | sed 's/,omitempty//' \\\n | sed 's/,.*$//' \\\n | grep -v '^-$' \\\n | grep -v '^$' \\\n | sort -u || echo \"\")\n\n# 4. Top-level frontmatter keys actually used in workflow .md files\nUSED_FIELDS=$(grep -rh '^[a-z][a-z0-9_-]*:' .github/workflows/*.md 2>/dev/null \\\n | sed 's/:.*//' \\\n | grep -v '^#' \\\n | sort -u || echo \"\")\n\n# 5. Schema field types for all top-level fields\nFIELD_TYPES=$(jq -r '.properties | to_entries[] |\n \"\\(.key): \\(.value.type // (.value.anyOf // .value.oneOf // [] | map(.type // \"complex\") | unique | join(\"|\")) // \"complex\")\"' \\\n pkg/parser/schemas/main_workflow_schema.json 2>/dev/null | sort || echo \"\")\n\n# 6. Fields in schema but absent as yaml tags in parser structs\nIN_SCHEMA_NOT_PARSER=$(comm -23 \\\n <(echo \"$SCHEMA_FIELDS\") \\\n <(echo \"$PARSER_YAML_FIELDS\" | sort -u) 2>/dev/null || echo \"\")\n\n# 7. yaml tags in parser structs absent from schema\nIN_PARSER_NOT_SCHEMA=$(comm -23 \\\n <(echo \"$PARSER_YAML_FIELDS\" | sort -u) \\\n <(echo \"$SCHEMA_FIELDS\") 2>/dev/null || echo \"\")\n\n# 8. Fields in schema but absent from workflow compiler structs\nIN_SCHEMA_NOT_WORKFLOW=$(comm -23 \\\n <(echo \"$SCHEMA_FIELDS\") \\\n <(echo \"$WORKFLOW_YAML_FIELDS\" | sort -u) 2>/dev/null || echo \"\")\n\n# 9. Fields used in actual workflow .md files but not in schema\nIN_USED_NOT_SCHEMA=$(comm -23 \\\n <(echo \"$USED_FIELDS\" | sort -u) \\\n <(echo \"$SCHEMA_FIELDS\") 2>/dev/null || echo \"\")\n\n# Write JSON output\njq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg schema_fields \"$SCHEMA_FIELDS\" \\\n --arg parser_yaml_fields \"$PARSER_YAML_FIELDS\" \\\n --arg workflow_yaml_fields \"$WORKFLOW_YAML_FIELDS\" \\\n --arg used_in_workflows \"$USED_FIELDS\" \\\n --arg field_types \"$FIELD_TYPES\" \\\n --arg in_schema_not_parser \"$IN_SCHEMA_NOT_PARSER\" \\\n --arg in_parser_not_schema \"$IN_PARSER_NOT_SCHEMA\" \\\n --arg in_schema_not_workflow \"$IN_SCHEMA_NOT_WORKFLOW\" \\\n --arg in_used_not_schema \"$IN_USED_NOT_SCHEMA\" \\\n '{\n generated_at: $generated_at,\n schema_fields: ($schema_fields | split(\"\\n\") | map(select(. != \"\"))),\n parser_yaml_fields: ($parser_yaml_fields | split(\"\\n\") | map(select(. != \"\"))),\n workflow_yaml_fields: ($workflow_yaml_fields | split(\"\\n\") | map(select(. != \"\"))),\n used_in_workflows: ($used_in_workflows | split(\"\\n\") | map(select(. != \"\"))),\n field_types: ($field_types | split(\"\\n\") | map(select(. != \"\"))),\n field_gaps: {\n in_schema_not_parser: ($in_schema_not_parser | split(\"\\n\") | map(select(. != \"\"))),\n in_parser_not_schema: ($in_parser_not_schema | split(\"\\n\") | map(select(. != \"\"))),\n in_schema_not_workflow: ($in_schema_not_workflow | split(\"\\n\") | map(select(. != \"\"))),\n in_used_not_schema: ($in_used_not_schema | split(\"\\n\") | map(select(. != \"\")))\n }\n }' > /tmp/gh-aw/agent/schema-diff.json\n\necho \"✓ Schema diff written to /tmp/gh-aw/agent/schema-diff.json\"\necho \"Summary:\"\njq '{\n schema_field_count: (.schema_fields | length),\n parser_yaml_field_count: (.parser_yaml_fields | length),\n workflow_yaml_field_count: (.workflow_yaml_fields | length),\n gaps: {\n in_schema_not_parser: (.field_gaps.in_schema_not_parser | length),\n in_parser_not_schema: (.field_gaps.in_parser_not_schema | length),\n in_schema_not_workflow: (.field_gaps.in_schema_not_workflow | length),\n in_used_not_schema: (.field_gaps.in_used_not_schema | length)\n }\n}' /tmp/gh-aw/agent/schema-diff.json\n\necho \"=== AWF config source drift pre-check (gh-aw-firewall) ===\"\nAWF_SNAPSHOT_DIR=/tmp/gh-aw/cache-memory/awf-config-sources\nmkdir -p \"$AWF_SNAPSHOT_DIR\"\nAWF_CANONICAL_FETCH_DEGRADED=false\nAWF_USING_SNAPSHOT=false\nAWF_FETCH_FAILED_SOURCES=\"\"\n\nfetch_awf_source() {\n local source_path=\"$1\"\n local target_path=\"$2\"\n if ! gh api -H \"Accept: application/vnd.github.raw\" \"/repos/github/gh-aw-firewall/contents/${source_path}\" > \"$target_path\"; then\n AWF_CANONICAL_FETCH_DEGRADED=true\n AWF_FETCH_FAILED_SOURCES=\"${AWF_FETCH_FAILED_SOURCES}${source_path}\\n\"\n rm -f \"$target_path\"\n return 1\n fi\n}\n\nif [ -n \"${GH_TOKEN:-${GITHUB_TOKEN:-}}\" ]; then\n fetch_awf_source docs/awf-config.schema.json /tmp/gh-aw/agent/awf-config.schema.json || true\n fetch_awf_source src/awf-config-schema.json /tmp/gh-aw/agent/awf-config-runtime.schema.json || true\n fetch_awf_source docs/awf-config-spec.md /tmp/gh-aw/agent/awf-config-spec.md || true\nelse\n AWF_CANONICAL_FETCH_DEGRADED=true\n AWF_FETCH_FAILED_SOURCES=\"docs/awf-config.schema.json\\nsrc/awf-config-schema.json\\ndocs/awf-config-spec.md\\n\"\n echo \"⚠️ AWF canonical source fetch degraded: GH_TOKEN/GITHUB_TOKEN is not set\"\nfi\n\nfor source_path in docs/awf-config.schema.json src/awf-config-schema.json docs/awf-config-spec.md; do\n source_file=$(basename \"$source_path\")\n if [ \"$source_path\" = \"src/awf-config-schema.json\" ]; then\n target_path=/tmp/gh-aw/agent/awf-config-runtime.schema.json\n source_file=awf-config-runtime.schema.json\n else\n target_path=/tmp/gh-aw/agent/\"$source_file\"\n fi\n\n if [ ! -s \"$target_path\" ] && [ -s \"$AWF_SNAPSHOT_DIR/$source_file\" ]; then\n cp \"$AWF_SNAPSHOT_DIR/$source_file\" \"$target_path\"\n AWF_USING_SNAPSHOT=true\n echo \"⚠️ Using last-known AWF snapshot for $source_path\"\n fi\ndone\n\nif [ -s /tmp/gh-aw/agent/awf-config.schema.json ] && [ -s /tmp/gh-aw/agent/awf-config-runtime.schema.json ] && [ -s /tmp/gh-aw/agent/awf-config-spec.md ]; then\n cp /tmp/gh-aw/agent/awf-config.schema.json \"$AWF_SNAPSHOT_DIR/awf-config.schema.json\"\n cp /tmp/gh-aw/agent/awf-config-runtime.schema.json \"$AWF_SNAPSHOT_DIR/awf-config-runtime.schema.json\"\n cp /tmp/gh-aw/agent/awf-config-spec.md \"$AWF_SNAPSHOT_DIR/awf-config-spec.md\"\n\n jq -r '.properties | keys[]' /tmp/gh-aw/agent/awf-config.schema.json | sort -u \\\n > /tmp/gh-aw/agent/awf-config-top-level.txt\n jq -r '.properties | keys[]' /tmp/gh-aw/agent/awf-config-runtime.schema.json | sort -u \\\n > /tmp/gh-aw/agent/awf-config-runtime-top-level.txt\n rg --no-heading --no-filename 'apiProxy|container|sandbox|auth|network' pkg/workflow actions/setup \\\n | head -200 > /tmp/gh-aw/agent/awf-config-ghaw-refs.txt || true\n\n FAILED_SOURCES_JSON=$(printf \"%b\" \"$AWF_FETCH_FAILED_SOURCES\" | sed '/^$/d' | sort -u | jq -Rsc 'split(\"\\n\") | map(select(length > 0))')\n jq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg spec_path \"docs/awf-config-spec.md\" \\\n --arg schema_path \"docs/awf-config.schema.json\" \\\n --arg runtime_schema_path \"src/awf-config-schema.json\" \\\n --arg top_level_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-top-level.txt | tr -d ' ')\" \\\n --arg runtime_top_level_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-runtime-top-level.txt | tr -d ' ')\" \\\n --arg refs_sample_count \"$(wc -l < /tmp/gh-aw/agent/awf-config-ghaw-refs.txt | tr -d ' ')\" \\\n --arg degraded \"$AWF_CANONICAL_FETCH_DEGRADED\" \\\n --arg using_snapshot \"$AWF_USING_SNAPSHOT\" \\\n --argjson failed_sources \"$FAILED_SOURCES_JSON\" \\\n '{\n generated_at: $generated_at,\n source_repo: \"github/gh-aw-firewall\",\n canonical_spec: $spec_path,\n canonical_schema: $schema_path,\n canonical_runtime_schema: $runtime_schema_path,\n top_level_property_count: ($top_level_count | tonumber),\n runtime_top_level_property_count: ($runtime_top_level_count | tonumber),\n ghaw_reference_sample_count: ($refs_sample_count | tonumber),\n degraded: ($degraded == \"true\"),\n using_snapshot: ($using_snapshot == \"true\"),\n failed_sources: $failed_sources\n }' > /tmp/gh-aw/agent/awf-config-drift.json\n if [ \"$AWF_CANONICAL_FETCH_DEGRADED\" = true ]; then\n echo \"⚠️ AWF canonical source fetch degraded; continuing in non-fatal mode\"\n else\n echo \"✓ AWF config source pre-check artifacts written under /tmp/gh-aw/agent/\"\n fi\nelse\n AWF_CANONICAL_FETCH_DEGRADED=true\n FAILED_SOURCES_JSON=$(printf \"%b\" \"$AWF_FETCH_FAILED_SOURCES\" | sed '/^$/d' | sort -u | jq -Rsc 'split(\"\\n\") | map(select(length > 0))')\n jq -n \\\n --arg generated_at \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --argjson failed_sources \"$FAILED_SOURCES_JSON\" \\\n '{\n generated_at: $generated_at,\n source_repo: \"github/gh-aw-firewall\",\n degraded: true,\n warning: \"canonical source retrieval failed; skipping destructive AWF drift actions\",\n failed_sources: $failed_sources\n }' > /tmp/gh-aw/agent/awf-config-drift.json\n echo \"⚠️ AWF canonical source fetch failed; run marked degraded (non-fatal)\"\nfi" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index 433d4a21177..95b59a460c2 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -564,7 +564,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Collect package analysis data - run: "set -e\nPACKAGES=(agentdrain cli console constants envutil fileutil gitutil logger parser repoutil semverutil sliceutil stringutil styles testutil timeutil tty types typeutil workflow)\nTOTAL=${#PACKAGES[@]}\nCACHE_DIR=/tmp/gh-aw/cache-memory/spec-extractor\nCONTEXT=/tmp/gh-aw/agent/pkg-context.md\n\n# Initialize or load rotation state\nmkdir -p \"$CACHE_DIR/extractions\"\nif [ -f \"$CACHE_DIR/rotation.json\" ]; then\n LAST_INDEX=$(python3 -c \"import json; d=json.load(open('$CACHE_DIR/rotation.json')); print(d.get('last_index', 0))\" 2>/dev/null || echo 0)\nelse\n printf '{\"last_index\":0,\"last_packages\":[],\"last_run\":\"\",\"total_packages\":%d}\\n' \"$TOTAL\" > \"$CACHE_DIR/rotation.json\"\n LAST_INDEX=0\nfi\n\n# Select next 4 packages using round-robin\nPKG0=\"${PACKAGES[$((LAST_INDEX % TOTAL))]}\"\nPKG1=\"${PACKAGES[$(((LAST_INDEX + 1) % TOTAL))]}\"\nPKG2=\"${PACKAGES[$(((LAST_INDEX + 2) % TOTAL))]}\"\nPKG3=\"${PACKAGES[$(((LAST_INDEX + 3) % TOTAL))]}\"\nNEXT_INDEX=$(((LAST_INDEX + 4) % TOTAL))\nSELECTED=(\"$PKG0\" \"$PKG1\" \"$PKG2\" \"$PKG3\")\n\necho \"Selected packages: ${SELECTED[*]} (last_index=$LAST_INDEX, next=$NEXT_INDEX)\"\n\n# Collect analysis data for each package into the context file\n{\n echo \"# Package Analysis Context\"\n echo \"\"\n echo \"**Run date**: $(date -u +%Y-%m-%d)\"\n echo \"**Rotation last_index (current)**: $LAST_INDEX\"\n echo \"**Selected packages**: ${SELECTED[*]}\"\n echo \"**Next last_index (save this after run)**: $NEXT_INDEX\"\n echo \"\"\n\n for PKG in \"${SELECTED[@]}\"; do\n echo \"---\"\n echo \"\"\n echo \"## Package: \\`$PKG\\`\"\n echo \"\"\n\n echo \"### Source Files\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f 2>/dev/null | sort || true\n echo '```'\n\n echo \"### Line Counts\"\n echo '```'\n wc -l pkg/\"$PKG\"/*.go 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Functions\"\n echo '```'\n grep -rn \"^func [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Types\"\n echo '```'\n grep -rn \"^type [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Constants\"\n echo '```'\n grep -rn \"^const [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Variables\"\n echo '```'\n grep -rn \"^var [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Package Doc Comments (first 30 lines per file)\"\n for f in pkg/\"$PKG\"/*.go; do\n [ -f \"$f\" ] || continue\n echo \"#### $f\"\n echo '```go'\n head -n 30 \"$f\" 2>/dev/null || true\n echo '```'\n done\n\n echo \"### Imports\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f | xargs grep -h \"import\" 2>/dev/null | sort -u || true\n echo '```'\n\n echo \"### Existing README.md\"\n echo '```markdown'\n cat \"pkg/$PKG/README.md\" 2>/dev/null || echo \"No existing README.md\"\n echo '```'\n\n echo \"### Recent Git History (30 days)\"\n echo '```'\n git log --oneline --since='30 days ago' -- \"pkg/$PKG\" 2>/dev/null || true\n echo '```'\n echo \"\"\n done\n} > \"$CONTEXT\"\necho \"Context file written to $CONTEXT ($(wc -l < \"$CONTEXT\") lines)\"\n" + run: "set -e\nPACKAGES=(agentdrain cli console constants envutil fileutil gitutil logger parser repoutil semverutil sliceutil stringutil styles testutil timeutil tty types typeutil workflow)\nTOTAL=${#PACKAGES[@]}\nCACHE_DIR=/tmp/gh-aw/cache-memory/spec-extractor\nCONTEXT=/tmp/gh-aw/agent/pkg-context.md\n\n# Initialize or load rotation state\nmkdir -p \"$CACHE_DIR/extractions\"\nif [ -f \"$CACHE_DIR/rotation.json\" ]; then\n LAST_INDEX=$(python3 -c \"import json; d=json.load(open('$CACHE_DIR/rotation.json')); print(d.get('last_index', 0))\" 2>/dev/null || echo 0)\nelse\n printf '{\"last_index\":0,\"last_packages\":[],\"last_run\":\"\",\"total_packages\":%d}\\n' \"$TOTAL\" > \"$CACHE_DIR/rotation.json\"\n LAST_INDEX=0\nfi\n\n# Select next 4 packages using round-robin\nPKG0=\"${PACKAGES[$((LAST_INDEX % TOTAL))]}\"\nPKG1=\"${PACKAGES[$(((LAST_INDEX + 1) % TOTAL))]}\"\nPKG2=\"${PACKAGES[$(((LAST_INDEX + 2) % TOTAL))]}\"\nPKG3=\"${PACKAGES[$(((LAST_INDEX + 3) % TOTAL))]}\"\nNEXT_INDEX=$(((LAST_INDEX + 4) % TOTAL))\nSELECTED=(\"$PKG0\" \"$PKG1\" \"$PKG2\" \"$PKG3\")\n\necho \"Selected packages: ${SELECTED[*]} (last_index=$LAST_INDEX, next=$NEXT_INDEX)\"\n\n# Collect analysis data for each package into the context file\n{\n echo \"# Package Analysis Context\"\n echo \"\"\n echo \"**Run date**: $(date -u +%Y-%m-%d)\"\n echo \"**Rotation last_index (current)**: $LAST_INDEX\"\n echo \"**Selected packages**: ${SELECTED[*]}\"\n echo \"**Next last_index (save this after run)**: $NEXT_INDEX\"\n echo \"\"\n\n for PKG in \"${SELECTED[@]}\"; do\n echo \"---\"\n echo \"\"\n echo \"## Package: \\`$PKG\\`\"\n echo \"\"\n\n echo \"### Source Files\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f 2>/dev/null | sort || true\n echo '```'\n\n echo \"### Line Counts\"\n echo '```'\n wc -l pkg/\"$PKG\"/*.go 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Functions\"\n echo '```'\n grep -rn \"^func [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Types\"\n echo '```'\n grep -rn \"^type [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Constants\"\n echo '```'\n grep -rn \"^const [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Variables\"\n echo '```'\n grep -rn \"^var [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Package Doc Comments (first 30 lines per file)\"\n for f in pkg/\"$PKG\"/*.go; do\n [ -f \"$f\" ] || continue\n echo \"#### $f\"\n echo '```go'\n head -n 30 \"$f\" 2>/dev/null || true\n echo '```'\n done\n\n echo \"### Imports\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f | xargs grep -h \"import\" 2>/dev/null | sort -u || true\n echo '```'\n\n echo \"### Existing README.md\"\n echo '```markdown'\n cat \"pkg/$PKG/README.md\" 2>/dev/null || echo \"No existing README.md\"\n echo '```'\n\n echo \"### Recent Git History (30 days)\"\n echo '```'\n git log --oneline --since='30 days ago' -- \"pkg/$PKG\" 2>/dev/null || true\n echo '```'\n echo \"\"\n done\n} > \"$CONTEXT\"\necho \"Context file written to $CONTEXT ($(wc -l < \"$CONTEXT\") lines)\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5 diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 3824e5ff292..3465903f59d 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -575,7 +575,7 @@ jobs: NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt ORGANIZATION: ${{ env.ORGANIZATION }} - name: Save stale repos output - run: | + run: |- mkdir -p /tmp/gh-aw/agent/stale-repos-data echo "$INACTIVE_REPOS" > /tmp/gh-aw/agent/stale-repos-data/inactive-repos.json echo "Stale repositories data saved" diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index b3603ded51f..dc2512f46d8 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -541,7 +541,7 @@ jobs: - name: Verify static analysis tools run: "set -e\necho \"Verifying static analysis tools are available...\"\n\n# Verify zizmor\necho \"Testing zizmor...\"\ndocker run --rm ghcr.io/zizmorcore/zizmor:latest --version || echo \"Warning: zizmor version check failed\"\n\n# Verify poutine\necho \"Testing poutine...\"\ndocker run --rm ghcr.io/boostsecurityio/poutine:latest --version || echo \"Warning: poutine version check failed\"\n\n# Verify runner-guard\necho \"Testing runner-guard...\"\ndocker run --rm ghcr.io/vigilant-llc/runner-guard:latest --version || echo \"Warning: runner-guard version check failed\"\n\necho \"Static analysis tools verification complete\"\n" - name: Run compile with security tools - run: "set -e\necho \"Running gh aw compile with security tools to download Docker images...\"\n\n# Run compile with all security scanner flags to download Docker images\n# Store the output in a file for inspection\n\"$GITHUB_WORKSPACE/gh-aw\" compile --zizmor --poutine --actionlint --runner-guard 2>&1 | tee /tmp/gh-aw/agent/compile-output.txt\n\necho \"Compile with security tools completed\"\necho \"Output saved to /tmp/gh-aw/agent/compile-output.txt\"\n" + run: "set -e\necho \"Running gh aw compile with security tools to download Docker images...\"\n\n# Run compile with all security scanner flags to download Docker images\n# Store the output in a file for inspection\n\"$GITHUB_WORKSPACE/gh-aw\" compile --zizmor --poutine --actionlint --runner-guard 2>&1 | tee /tmp/gh-aw/agent/compile-output.txt\n\necho \"Compile with security tools completed\"\necho \"Output saved to /tmp/gh-aw/agent/compile-output.txt\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/step-name-alignment.lock.yml b/.github/workflows/step-name-alignment.lock.yml index 4c88a31a24d..6c89ee64f04 100644 --- a/.github/workflows/step-name-alignment.lock.yml +++ b/.github/workflows/step-name-alignment.lock.yml @@ -480,7 +480,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: Build step alignment manifest - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\nMANIFEST_JSONL=\"/tmp/gh-aw/agent/step-alignment-input.jsonl\"\nMANIFEST_JSON=\"/tmp/gh-aw/agent/step-alignment-input.json\"\n: > \"$MANIFEST_JSONL\"\n\nwhile IFS= read -r workflow_file; do\n yq -o=json \\\n '.jobs | to_entries[] | .value.steps[]? | {\"workflow_file\": \"'\"$workflow_file\"'\", \"step_name\": (.name // \"\"), \"action_uses\": (.uses // \"\")}' \\\n \"$workflow_file\" >> \"$MANIFEST_JSONL\"\ndone < <(find .github/workflows -name \"*.lock.yml\" -type f | sort)\n\njq -s '.' \"$MANIFEST_JSONL\" > \"$MANIFEST_JSON\"\nrm -f \"$MANIFEST_JSONL\"\n\necho \"Wrote $(jq 'length' \"$MANIFEST_JSON\") step records to $MANIFEST_JSON\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\nMANIFEST_JSONL=\"/tmp/gh-aw/agent/step-alignment-input.jsonl\"\nMANIFEST_JSON=\"/tmp/gh-aw/agent/step-alignment-input.json\"\n: > \"$MANIFEST_JSONL\"\n\nwhile IFS= read -r workflow_file; do\n yq -o=json \\\n '.jobs | to_entries[] | .value.steps[]? | {\"workflow_file\": \"'\"$workflow_file\"'\", \"step_name\": (.name // \"\"), \"action_uses\": (.uses // \"\")}' \\\n \"$workflow_file\" >> \"$MANIFEST_JSONL\"\ndone < <(find .github/workflows -name \"*.lock.yml\" -type f | sort)\n\njq -s '.' \"$MANIFEST_JSONL\" > \"$MANIFEST_JSON\"\nrm -f \"$MANIFEST_JSONL\"\n\necho \"Wrote $(jq 'length' \"$MANIFEST_JSON\") step records to $MANIFEST_JSON\"" # Cache memory file share configuration from frontmatter processed below - name: Create cache-memory directory diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index 0c6d1dd379d..aa3d110da05 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -574,7 +574,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} name: Pre-fetch PR data - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# PR metadata\ngh pr view \"$PR_NUMBER\" \\\n --json files,additions,deletions,baseRefName,headRefName \\\n > /tmp/gh-aw/agent/pr-meta.json\n\n# List of changed test files\ngh pr diff \"$PR_NUMBER\" \\\n --name-only | grep -E '(_test\\.go|\\.test\\.cjs|\\.test\\.js)$' \\\n > /tmp/gh-aw/agent/test-files.txt || true\n\n# Diff for test files only; capped at 40 KB to control cache token costs\nif [ -s /tmp/gh-aw/agent/test-files.txt ]; then\n # shellcheck disable=SC2046\n gh pr diff \"$PR_NUMBER\" \\\n -- $(tr '\\n' ' ' < /tmp/gh-aw/agent/test-files.txt) \\\n | head -c 40000 \\\n > /tmp/gh-aw/agent/test-diff.txt 2>/dev/null || true\nelse\n touch /tmp/gh-aw/agent/test-diff.txt\nfi\n\ngit diff \"$EXPR_GITHUB_EVENT_PULL_REQUEST_BASE_SHA...HEAD\" --numstat \\\n > /tmp/gh-aw/agent/diff-numstat.txt 2>/dev/null || true\n\n# Extract new/modified test function signatures from the diff\nif [ -s /tmp/gh-aw/agent/test-diff.txt ]; then\n grep -E \"^\\+func Test\" /tmp/gh-aw/agent/test-diff.txt \\\n > /tmp/gh-aw/agent/go-new-test-funcs.txt || true\n grep -E \"^\\+(it|test|describe)\\(\" /tmp/gh-aw/agent/test-diff.txt \\\n > /tmp/gh-aw/agent/js-new-test-funcs.txt || true\n # Check for new Go test files missing mandatory build tags\n git diff \"$EXPR_GITHUB_EVENT_PULL_REQUEST_BASE_SHA...HEAD\" \\\n --diff-filter=A --name-only 2>/dev/null \\\n | grep '_test\\.go$' | while read -r f; do\n if ! head -1 \"$f\" | grep -qE '^//go:build'; then\n echo \"MISSING BUILD TAG: $f\"\n fi\n done > /tmp/gh-aw/agent/missing-build-tags.txt || true\n # Go test structural stats (assertions, error checks, table-driven, forbidden mocks)\n awk '\n /^\\+func Test/ {\n if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks\n match($0, /func (Test[^(]+)/, arr); test_name=arr[1]; assertions=0; errors=0; table_driven=0; forbidden_mocks=0\n }\n test_name && /^\\+.*(assert\\.|require\\.)/ { assertions++ }\n test_name && /^\\+.*t\\.(Error|Errorf|Fatal|Fatalf)\\(/ { assertions++; errors++ }\n test_name && /^\\+.*(assert\\.Error|require\\.Error|assert\\.NoError|require\\.NoError)/ { errors++ }\n test_name && /^\\+.*t\\.Run\\(/ { table_driven++ }\n test_name && /^\\+.*(gomock\\.|testify\\/mock|\\.EXPECT\\(\\)|\\.On\\(|\\.Return\\()/ { forbidden_mocks++ }\n test_name && /^\\+\\}$/ { print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks; test_name=\"\" }\n END { if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks }\n ' /tmp/gh-aw/agent/test-diff.txt > /tmp/gh-aw/agent/go-test-stats.txt || true\n # JS test structural stats (assertions, error matchers, vi.* mocks)\n awk '\n /^\\+(it|test)\\(/ {\n if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks\n match($0, /(it|test)\\([\"\\047]([^\"\\047]+)/, arr); test_name=arr[2]; assertions=0; errors=0; mocks=0\n }\n test_name && /^\\+.*expect\\(/ { assertions++ }\n test_name && /^\\+.*(\\.toThrow|\\.rejects|\\.toThrowError)/ { errors++ }\n test_name && /^\\+.*(vi\\.mock|vi\\.spyOn|vi\\.fn)/ { mocks++ }\n test_name && /^\\+\\}\\)/ { print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks; test_name=\"\" }\n END { if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks }\n ' /tmp/gh-aw/agent/test-diff.txt > /tmp/gh-aw/agent/js-test-stats.txt || true\nelse\n touch /tmp/gh-aw/agent/go-new-test-funcs.txt \\\n /tmp/gh-aw/agent/js-new-test-funcs.txt \\\n /tmp/gh-aw/agent/missing-build-tags.txt \\\n /tmp/gh-aw/agent/go-test-stats.txt \\\n /tmp/gh-aw/agent/js-test-stats.txt\nfi\n\necho \"Pre-fetched $(grep -c . /tmp/gh-aw/agent/test-files.txt || echo 0) test files\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n# PR metadata\ngh pr view \"$PR_NUMBER\" \\\n --json files,additions,deletions,baseRefName,headRefName \\\n > /tmp/gh-aw/agent/pr-meta.json\n\n# List of changed test files\ngh pr diff \"$PR_NUMBER\" \\\n --name-only | grep -E '(_test\\.go|\\.test\\.cjs|\\.test\\.js)$' \\\n > /tmp/gh-aw/agent/test-files.txt || true\n\n# Diff for test files only; capped at 40 KB to control cache token costs\nif [ -s /tmp/gh-aw/agent/test-files.txt ]; then\n # shellcheck disable=SC2046\n gh pr diff \"$PR_NUMBER\" \\\n -- $(tr '\\n' ' ' < /tmp/gh-aw/agent/test-files.txt) \\\n | head -c 40000 \\\n > /tmp/gh-aw/agent/test-diff.txt 2>/dev/null || true\nelse\n touch /tmp/gh-aw/agent/test-diff.txt\nfi\n\ngit diff \"$EXPR_GITHUB_EVENT_PULL_REQUEST_BASE_SHA...HEAD\" --numstat \\\n > /tmp/gh-aw/agent/diff-numstat.txt 2>/dev/null || true\n\n# Extract new/modified test function signatures from the diff\nif [ -s /tmp/gh-aw/agent/test-diff.txt ]; then\n grep -E \"^\\+func Test\" /tmp/gh-aw/agent/test-diff.txt \\\n > /tmp/gh-aw/agent/go-new-test-funcs.txt || true\n grep -E \"^\\+(it|test|describe)\\(\" /tmp/gh-aw/agent/test-diff.txt \\\n > /tmp/gh-aw/agent/js-new-test-funcs.txt || true\n # Check for new Go test files missing mandatory build tags\n git diff \"$EXPR_GITHUB_EVENT_PULL_REQUEST_BASE_SHA...HEAD\" \\\n --diff-filter=A --name-only 2>/dev/null \\\n | grep '_test\\.go$' | while read -r f; do\n if ! head -1 \"$f\" | grep -qE '^//go:build'; then\n echo \"MISSING BUILD TAG: $f\"\n fi\n done > /tmp/gh-aw/agent/missing-build-tags.txt || true\n # Go test structural stats (assertions, error checks, table-driven, forbidden mocks)\n awk '\n /^\\+func Test/ {\n if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks\n match($0, /func (Test[^(]+)/, arr); test_name=arr[1]; assertions=0; errors=0; table_driven=0; forbidden_mocks=0\n }\n test_name && /^\\+.*(assert\\.|require\\.)/ { assertions++ }\n test_name && /^\\+.*t\\.(Error|Errorf|Fatal|Fatalf)\\(/ { assertions++; errors++ }\n test_name && /^\\+.*(assert\\.Error|require\\.Error|assert\\.NoError|require\\.NoError)/ { errors++ }\n test_name && /^\\+.*t\\.Run\\(/ { table_driven++ }\n test_name && /^\\+.*(gomock\\.|testify\\/mock|\\.EXPECT\\(\\)|\\.On\\(|\\.Return\\()/ { forbidden_mocks++ }\n test_name && /^\\+\\}$/ { print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks; test_name=\"\" }\n END { if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"table_driven=\" table_driven, \"forbidden_mocks=\" forbidden_mocks }\n ' /tmp/gh-aw/agent/test-diff.txt > /tmp/gh-aw/agent/go-test-stats.txt || true\n # JS test structural stats (assertions, error matchers, vi.* mocks)\n awk '\n /^\\+(it|test)\\(/ {\n if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks\n match($0, /(it|test)\\([\"\\047]([^\"\\047]+)/, arr); test_name=arr[2]; assertions=0; errors=0; mocks=0\n }\n test_name && /^\\+.*expect\\(/ { assertions++ }\n test_name && /^\\+.*(\\.toThrow|\\.rejects|\\.toThrowError)/ { errors++ }\n test_name && /^\\+.*(vi\\.mock|vi\\.spyOn|vi\\.fn)/ { mocks++ }\n test_name && /^\\+\\}\\)/ { print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks; test_name=\"\" }\n END { if (test_name) print test_name, \"assertions=\" assertions, \"errors=\" errors, \"mocks=\" mocks }\n ' /tmp/gh-aw/agent/test-diff.txt > /tmp/gh-aw/agent/js-test-stats.txt || true\nelse\n touch /tmp/gh-aw/agent/go-new-test-funcs.txt \\\n /tmp/gh-aw/agent/js-new-test-funcs.txt \\\n /tmp/gh-aw/agent/missing-build-tags.txt \\\n /tmp/gh-aw/agent/go-test-stats.txt \\\n /tmp/gh-aw/agent/js-test-stats.txt\nfi\n\necho \"Pre-fetched $(grep -c . /tmp/gh-aw/agent/test-files.txt || echo 0) test files\"" - name: Configure Git credentials env: diff --git a/.github/workflows/uk-ai-operational-resilience.lock.yml b/.github/workflows/uk-ai-operational-resilience.lock.yml index 83c3409c359..4b44a0f0085 100644 --- a/.github/workflows/uk-ai-operational-resilience.lock.yml +++ b/.github/workflows/uk-ai-operational-resilience.lock.yml @@ -544,7 +544,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} LOOKBACK_DAYS: ${{ github.event.inputs.lookback_days || '7' }} name: Pre-compute recent changes governance context - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\nREPO=\"${GITHUB_REPOSITORY}\"\nDAYS=\"${LOOKBACK_DAYS}\"\nif ! [[ \"$DAYS\" =~ ^[0-9]+$ ]]; then DAYS=7; fi\nif [ \"$DAYS\" -lt 1 ]; then DAYS=1; fi\nif [ \"$DAYS\" -gt 30 ]; then DAYS=30; fi\n\nSINCE=\"$(date -u -d \"-${DAYS} days\" +%Y-%m-%dT%H:%M:%SZ)\"\necho \"$SINCE\" >/tmp/gh-aw/agent/since.txt\n\ngh api --paginate \"repos/${REPO}/commits?since=${SINCE}&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/recent-commits.json\ngh api --paginate \"repos/${REPO}/issues?state=open&labels=security&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-security-issues.json\ngh api --paginate \"repos/${REPO}/code-scanning/alerts?state=open&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-code-scanning-alerts.json || echo \"[]\" >/tmp/gh-aw/agent/open-code-scanning-alerts.json\ngh api --paginate \"repos/${REPO}/secret-scanning/alerts?state=open&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-secret-scanning-alerts.json || echo \"[]\" >/tmp/gh-aw/agent/open-secret-scanning-alerts.json\n\njq -r '\n [.[].commit.message]\n | map(select(type==\"string\"))\n | map(\n if test(\"security|vuln|cve|patch|auth|secret|token|permissions|hardening\"; \"i\")\n then .\n else empty\n end\n )' /tmp/gh-aw/agent/recent-commits.json > /tmp/gh-aw/agent/security-signal-commits.json\n\n{\n echo \"## UK AI Governance Pre-compute\"\n echo \"- Repository: ${REPO}\"\n echo \"- Lookback days: ${DAYS}\"\n echo \"- Since: ${SINCE}\"\n echo \"- Commits in window: $(jq 'length' /tmp/gh-aw/agent/recent-commits.json)\"\n echo \"- Security-signal commits: $(jq 'length' /tmp/gh-aw/agent/security-signal-commits.json)\"\n echo \"- Open security issues: $(jq 'length' /tmp/gh-aw/agent/open-security-issues.json)\"\n echo \"- Open code scanning alerts: $(jq 'length' /tmp/gh-aw/agent/open-code-scanning-alerts.json)\"\n echo \"- Open secret scanning alerts: $(jq 'length' /tmp/gh-aw/agent/open-secret-scanning-alerts.json)\"\n} > /tmp/gh-aw/agent/uk-ai-governance-context.md\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\nREPO=\"${GITHUB_REPOSITORY}\"\nDAYS=\"${LOOKBACK_DAYS}\"\nif ! [[ \"$DAYS\" =~ ^[0-9]+$ ]]; then DAYS=7; fi\nif [ \"$DAYS\" -lt 1 ]; then DAYS=1; fi\nif [ \"$DAYS\" -gt 30 ]; then DAYS=30; fi\n\nSINCE=\"$(date -u -d \"-${DAYS} days\" +%Y-%m-%dT%H:%M:%SZ)\"\necho \"$SINCE\" >/tmp/gh-aw/agent/since.txt\n\ngh api --paginate \"repos/${REPO}/commits?since=${SINCE}&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/recent-commits.json\ngh api --paginate \"repos/${REPO}/issues?state=open&labels=security&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-security-issues.json\ngh api --paginate \"repos/${REPO}/code-scanning/alerts?state=open&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-code-scanning-alerts.json || echo \"[]\" >/tmp/gh-aw/agent/open-code-scanning-alerts.json\ngh api --paginate \"repos/${REPO}/secret-scanning/alerts?state=open&per_page=100\" | jq -s 'add // []' > /tmp/gh-aw/agent/open-secret-scanning-alerts.json || echo \"[]\" >/tmp/gh-aw/agent/open-secret-scanning-alerts.json\n\njq -r '\n [.[].commit.message]\n | map(select(type==\"string\"))\n | map(\n if test(\"security|vuln|cve|patch|auth|secret|token|permissions|hardening\"; \"i\")\n then .\n else empty\n end\n )' /tmp/gh-aw/agent/recent-commits.json > /tmp/gh-aw/agent/security-signal-commits.json\n\n{\n echo \"## UK AI Governance Pre-compute\"\n echo \"- Repository: ${REPO}\"\n echo \"- Lookback days: ${DAYS}\"\n echo \"- Since: ${SINCE}\"\n echo \"- Commits in window: $(jq 'length' /tmp/gh-aw/agent/recent-commits.json)\"\n echo \"- Security-signal commits: $(jq 'length' /tmp/gh-aw/agent/security-signal-commits.json)\"\n echo \"- Open security issues: $(jq 'length' /tmp/gh-aw/agent/open-security-issues.json)\"\n echo \"- Open code scanning alerts: $(jq 'length' /tmp/gh-aw/agent/open-code-scanning-alerts.json)\"\n echo \"- Open secret scanning alerts: $(jq 'length' /tmp/gh-aw/agent/open-secret-scanning-alerts.json)\"\n} > /tmp/gh-aw/agent/uk-ai-governance-context.md" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index de8aaf12c94..a533fc9776c 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -635,7 +635,7 @@ jobs: GH_AW_SKILL_DIR: ".pi/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Pre-flight checks - run: "mkdir -p /tmp/gh-aw/agent\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Write a heartbeat timestamp so the cache always has fresh content to save,\n# even on noop runs where the agent writes nothing to the cache directory.\necho \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > /tmp/gh-aw/cache-memory/last-run.txt\n\n# Check 1: verify docs directory structure exists\nDIR_COUNT=$(find docs/src/content/docs -maxdepth 1 -type d 2>/dev/null | wc -l)\nif [ \"$DIR_COUNT\" -eq 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight failed: docs/src/content/docs directory not found — documentation structure is missing or repository is not set up correctly.\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# Check 2: count editable markdown files\nTOTAL=$(find docs/src/content/docs -path '*/blog*' -prune \\\n -o -name '*.md' -type f ! -name 'frontmatter-full.md' -print \\\n | xargs grep -rL 'disable-agentic-editing: true' 2>/dev/null \\\n | wc -l)\nif [ \"$TOTAL\" -eq 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight failed: no editable markdown files found in docs/src/content/docs (all files may be protected or excluded).\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# Check 3: count uncleaned candidates (not cleaned in the past 7 days)\nRECENT_CUTOFF=$(date -d '7 days ago' '+%Y-%m-%d' 2>/dev/null \\\n || date -v-7d '+%Y-%m-%d' 2>/dev/null \\\n || echo \"0000-00-00\")\n\n# Expiration check: if the most recent cleanup entry is older than 14 days the\n# cache has gone cold (e.g. GitHub Actions evicted the 7-day cache entry).\n# Reset cleaned-files.txt so every file is eligible again and stale \"already\n# cleaned\" claims are not silently reused.\nCACHE_FILE=\"/tmp/gh-aw/cache-memory/cleaned-files.txt\"\nSTALE_CUTOFF=$(date -d '14 days ago' '+%Y-%m-%d' 2>/dev/null \\\n || date -v-14d '+%Y-%m-%d' 2>/dev/null \\\n || echo \"0000-00-00\")\nLATEST_ENTRY=$(awk 'NF>0{print $1}' \"$CACHE_FILE\" 2>/dev/null | sort | tail -1)\nif [ -n \"$LATEST_ENTRY\" ] && [ \"$LATEST_ENTRY\" \\< \"$STALE_CUTOFF\" ]; then\n echo \"Cache expiration: most recent entry $LATEST_ENTRY predates $STALE_CUTOFF — resetting cleaned-files.txt\"\n > \"$CACHE_FILE\"\nfi\n\nCLEANED=$(awk -v cutoff=\"$RECENT_CUTOFF\" \\\n 'NF>0 && $1>=cutoff{count++} END{print count+0}' \\\n \"$CACHE_FILE\" 2>/dev/null || echo \"0\")\nUNCLEANED=$(( TOTAL - CLEANED ))\nif [ \"$UNCLEANED\" -le 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight check: all eligible documentation files were cleaned recently — nothing to do this run.\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# All checks passed — write candidate file list and preflight result\nfind docs/src/content/docs -path '*/blog*' -prune \\\n -o -name '*.md' -type f ! -name 'frontmatter-full.md' -print \\\n | xargs grep -rL 'disable-agentic-editing: true' 2>/dev/null \\\n > /tmp/gh-aw/agent/candidate-files.txt\nprintf '{\"pass\":true,\"reason\":\"All pre-flight checks passed. %d uncleaned candidates available.\",\"uncleaned\":%d,\"total\":%d}\\n' \\\n \"$UNCLEANED\" \"$UNCLEANED\" \"$TOTAL\" \\\n > /tmp/gh-aw/agent/preflight.json\n\necho \"Pre-flight passed: $UNCLEANED uncleaned candidates out of $TOTAL eligible files\"\necho \"Candidate files written to /tmp/gh-aw/agent/candidate-files.txt\"\n" + run: "mkdir -p /tmp/gh-aw/agent\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Write a heartbeat timestamp so the cache always has fresh content to save,\n# even on noop runs where the agent writes nothing to the cache directory.\necho \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > /tmp/gh-aw/cache-memory/last-run.txt\n\n# Check 1: verify docs directory structure exists\nDIR_COUNT=$(find docs/src/content/docs -maxdepth 1 -type d 2>/dev/null | wc -l)\nif [ \"$DIR_COUNT\" -eq 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight failed: docs/src/content/docs directory not found — documentation structure is missing or repository is not set up correctly.\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# Check 2: count editable markdown files\nTOTAL=$(find docs/src/content/docs -path '*/blog*' -prune \\\n -o -name '*.md' -type f ! -name 'frontmatter-full.md' -print \\\n | xargs grep -rL 'disable-agentic-editing: true' 2>/dev/null \\\n | wc -l)\nif [ \"$TOTAL\" -eq 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight failed: no editable markdown files found in docs/src/content/docs (all files may be protected or excluded).\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# Check 3: count uncleaned candidates (not cleaned in the past 7 days)\nRECENT_CUTOFF=$(date -d '7 days ago' '+%Y-%m-%d' 2>/dev/null \\\n || date -v-7d '+%Y-%m-%d' 2>/dev/null \\\n || echo \"0000-00-00\")\n\n# Expiration check: if the most recent cleanup entry is older than 14 days the\n# cache has gone cold (e.g. GitHub Actions evicted the 7-day cache entry).\n# Reset cleaned-files.txt so every file is eligible again and stale \"already\n# cleaned\" claims are not silently reused.\nCACHE_FILE=\"/tmp/gh-aw/cache-memory/cleaned-files.txt\"\nSTALE_CUTOFF=$(date -d '14 days ago' '+%Y-%m-%d' 2>/dev/null \\\n || date -v-14d '+%Y-%m-%d' 2>/dev/null \\\n || echo \"0000-00-00\")\nLATEST_ENTRY=$(awk 'NF>0{print $1}' \"$CACHE_FILE\" 2>/dev/null | sort | tail -1)\nif [ -n \"$LATEST_ENTRY\" ] && [ \"$LATEST_ENTRY\" \\< \"$STALE_CUTOFF\" ]; then\n echo \"Cache expiration: most recent entry $LATEST_ENTRY predates $STALE_CUTOFF — resetting cleaned-files.txt\"\n > \"$CACHE_FILE\"\nfi\n\nCLEANED=$(awk -v cutoff=\"$RECENT_CUTOFF\" \\\n 'NF>0 && $1>=cutoff{count++} END{print count+0}' \\\n \"$CACHE_FILE\" 2>/dev/null || echo \"0\")\nUNCLEANED=$(( TOTAL - CLEANED ))\nif [ \"$UNCLEANED\" -le 0 ]; then\n echo '{\"pass\":false,\"reason\":\"Pre-flight check: all eligible documentation files were cleaned recently — nothing to do this run.\"}' \\\n > /tmp/gh-aw/agent/preflight.json\n exit 0\nfi\n\n# All checks passed — write candidate file list and preflight result\nfind docs/src/content/docs -path '*/blog*' -prune \\\n -o -name '*.md' -type f ! -name 'frontmatter-full.md' -print \\\n | xargs grep -rL 'disable-agentic-editing: true' 2>/dev/null \\\n > /tmp/gh-aw/agent/candidate-files.txt\nprintf '{\"pass\":true,\"reason\":\"All pre-flight checks passed. %d uncleaned candidates available.\",\"uncleaned\":%d,\"total\":%d}\\n' \\\n \"$UNCLEANED\" \"$UNCLEANED\" \"$TOTAL\" \\\n > /tmp/gh-aw/agent/preflight.json\n\necho \"Pre-flight passed: $UNCLEANED uncleaned candidates out of $TOTAL eligible files\"\necho \"Candidate files written to /tmp/gh-aw/agent/candidate-files.txt\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 896aead4e69..d992b9bd3de 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -583,7 +583,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Pre-fetch release and merged PR data if: ${{ needs.activation.outputs.prefetch_strategy == 'eager' }} - run: | + run: |- set -euo pipefail mkdir -p /tmp/gh-aw/agent diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 6c1a64a7b82..dfc54330fcf 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -490,7 +490,7 @@ jobs: - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Build Inventory - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n# Run compilation validation and capture output\ngh aw compile --validate > /tmp/gh-aw/agent/compile-validate.txt 2>&1 || true\n# List executable workflow files (exclude shared/ subdirectory)\nls .github/workflows/*.md 2>/dev/null > /tmp/gh-aw/agent/workflow-list.txt || true\necho \"Inventory complete: $(wc -l < /tmp/gh-aw/agent/workflow-list.txt | tr -d ' ') workflows found\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n# Run compilation validation and capture output\ngh aw compile --validate > /tmp/gh-aw/agent/compile-validate.txt 2>&1 || true\n# List executable workflow files (exclude shared/ subdirectory)\nls .github/workflows/*.md 2>/dev/null > /tmp/gh-aw/agent/workflow-list.txt || true\necho \"Inventory complete: $(wc -l < /tmp/gh-aw/agent/workflow-list.txt | tr -d ' ') workflows found\"" # Repo memory git-based storage configuration from frontmatter processed below - name: Clone repo-memory branch (default) @@ -559,7 +559,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Load Metrics - run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nMETRICS_FILE=\"/tmp/gh-aw/repo-memory/default/metrics/latest.json\"\nif [ -f \"$METRICS_FILE\" ]; then\n jq '[.workflow_runs | to_entries[]\n | select(.value.success_rate < 0.8)]\n | sort_by(.value.success_rate) | .[0:20]' \\\n \"$METRICS_FILE\" > /tmp/gh-aw/agent/failing-workflows.json 2>/dev/null \\\n || echo '[]' > /tmp/gh-aw/agent/failing-workflows.json\nelse\n echo '[]' > /tmp/gh-aw/agent/failing-workflows.json\nfi\necho \"Metrics loaded: $(jq 'length' /tmp/gh-aw/agent/failing-workflows.json) failing workflows (<80% success)\"\n" + run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\nMETRICS_FILE=\"/tmp/gh-aw/repo-memory/default/metrics/latest.json\"\nif [ -f \"$METRICS_FILE\" ]; then\n jq '[.workflow_runs | to_entries[]\n | select(.value.success_rate < 0.8)]\n | sort_by(.value.success_rate) | .[0:20]' \\\n \"$METRICS_FILE\" > /tmp/gh-aw/agent/failing-workflows.json 2>/dev/null \\\n || echo '[]' > /tmp/gh-aw/agent/failing-workflows.json\nelse\n echo '[]' > /tmp/gh-aw/agent/failing-workflows.json\nfi\necho \"Metrics loaded: $(jq 'length' /tmp/gh-aw/agent/failing-workflows.json) failing workflows (<80% success)\"" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.24@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.24@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.24@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b ghcr.io/github/gh-aw-firewall/squid:0.27.24@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485 ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 diff --git a/.github/workflows/workflow-skill-extractor.lock.yml b/.github/workflows/workflow-skill-extractor.lock.yml index ec5683faf20..9a7bd276653 100644 --- a/.github/workflows/workflow-skill-extractor.lock.yml +++ b/.github/workflows/workflow-skill-extractor.lock.yml @@ -475,7 +475,7 @@ jobs: - name: Build workflow index uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - script: "const fs = require('fs');\nconst path = require('path');\n\nconst workflowDir = '.github/workflows';\nconst entries = fs.readdirSync(workflowDir, { withFileTypes: true });\nconst index = [];\n\nfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n if (!entry.isFile() || !entry.name.endsWith('.md') || entry.name.startsWith('.')) {\n continue;\n }\n\n const workflowPath = path.join(workflowDir, entry.name);\n const content = fs.readFileSync(workflowPath, 'utf8');\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n const frontmatter = frontmatterMatch ? frontmatterMatch[1] : '';\n\n const imports = Array.from(frontmatter.matchAll(/^\\s*-\\s+(shared\\/\\S+)/gm), (m) => m[1]);\n let engine = null;\n const frontmatterLines = frontmatter.split('\\n');\n let inEngineBlock = false;\n\n for (const line of frontmatterLines) {\n if (!inEngineBlock) {\n if (/^engine:\\s*$/.test(line)) {\n inEngineBlock = true;\n }\n continue;\n }\n\n if (!/^[ \\t]/.test(line)) {\n break;\n }\n\n const engineIDMatch = line.match(/^\\s*id:\\s*(\\S+)/);\n if (engineIDMatch) {\n engine = engineIDMatch[1];\n break;\n }\n }\n\n index.push({\n file: entry.name,\n path: workflowPath,\n imports,\n engine,\n has_github_tools: frontmatter.includes('github:'),\n has_safe_outputs: frontmatter.includes('safe-outputs:'),\n frontmatter_preview: frontmatter.slice(0, 400)\n });\n}\n\nfs.mkdirSync('/tmp/gh-aw/agent', { recursive: true });\nfs.writeFileSync('/tmp/gh-aw/agent/workflow-index.json', JSON.stringify(index, null, 2) + '\\n', 'utf8');\ncore.info(`Indexed ${index.length} workflows`);\n" + script: "const fs = require('fs');\nconst path = require('path');\n\nconst workflowDir = '.github/workflows';\nconst entries = fs.readdirSync(workflowDir, { withFileTypes: true });\nconst index = [];\n\nfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n if (!entry.isFile() || !entry.name.endsWith('.md') || entry.name.startsWith('.')) {\n continue;\n }\n\n const workflowPath = path.join(workflowDir, entry.name);\n const content = fs.readFileSync(workflowPath, 'utf8');\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n const frontmatter = frontmatterMatch ? frontmatterMatch[1] : '';\n\n const imports = Array.from(frontmatter.matchAll(/^\\s*-\\s+(shared\\/\\S+)/gm), (m) => m[1]);\n let engine = null;\n const frontmatterLines = frontmatter.split('\\n');\n let inEngineBlock = false;\n\n for (const line of frontmatterLines) {\n if (!inEngineBlock) {\n if (/^engine:\\s*$/.test(line)) {\n inEngineBlock = true;\n }\n continue;\n }\n\n if (!/^[ \\t]/.test(line)) {\n break;\n }\n\n const engineIDMatch = line.match(/^\\s*id:\\s*(\\S+)/);\n if (engineIDMatch) {\n engine = engineIDMatch[1];\n break;\n }\n }\n\n index.push({\n file: entry.name,\n path: workflowPath,\n imports,\n engine,\n has_github_tools: frontmatter.includes('github:'),\n has_safe_outputs: frontmatter.includes('safe-outputs:'),\n frontmatter_preview: frontmatter.slice(0, 400)\n });\n}\n\nfs.mkdirSync('/tmp/gh-aw/agent', { recursive: true });\nfs.writeFileSync('/tmp/gh-aw/agent/workflow-index.json', JSON.stringify(index, null, 2) + '\\n', 'utf8');\ncore.info(`Indexed ${index.length} workflows`);" - name: Configure Git credentials env: From eea6eb7c81810a7898e561b7f73ac7145df1cdbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 07:18:51 +0000 Subject: [PATCH 3/4] feat(eslint-factory): extend no-json-stringify-error to cover .then(_, onRejected) and pin scope boundaries Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- eslint-factory/README.md | 19 +++++ .../src/rules/no-json-stringify-error.test.ts | 69 +++++++++++++++++++ .../src/rules/no-json-stringify-error.ts | 26 +++++-- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/eslint-factory/README.md b/eslint-factory/README.md index a0d170f6376..9a83eec7690 100644 --- a/eslint-factory/README.md +++ b/eslint-factory/README.md @@ -16,6 +16,25 @@ This project hosts custom ESLint linters for `/actions/setup/js`. ## Rules +### `no-json-stringify-error` + +Disallow `JSON.stringify()` on caught error variables. `Error` properties (`message`, `stack`, etc.) are non-enumerable, so `JSON.stringify(err)` silently produces `{}`. + +**Detected scopes:** +- `try { } catch (err) { }` — catch-clause bindings. +- `p.catch(err => ...)` — inline arrow or function callbacks passed as the first argument to `.catch()`. +- `p.then(onFulfilled, err => ...)` — inline rejection handlers passed as the **second** argument to `.then()`, which are semantically equivalent to `.catch()`. + +**Out of scope:** named-reference handlers such as `p.catch(handler)` or `p.then(ok, handler)` — the rule does not follow references across files or scopes. + +Flagged forms: +- `JSON.stringify(err)` where `err` is a catch-clause or inline rejection-handler parameter. +- `JSON.stringify(err, null, 2)` (with replacer/space arguments). + +Safe alternatives: +- `getErrorMessage(err)` from `error_helpers.cjs` (auto-suggested fix). +- `JSON.stringify({ message: err.message, stack: err.stack })` — explicitly serializing safe string properties. + ### `prefer-number-isnan` Prefer `Number.isNaN()` over global `isNaN()` to avoid silent coercion of non-numeric inputs. diff --git a/eslint-factory/src/rules/no-json-stringify-error.test.ts b/eslint-factory/src/rules/no-json-stringify-error.test.ts index 0b58fd18d2f..43daa5d3ff6 100644 --- a/eslint-factory/src/rules/no-json-stringify-error.test.ts +++ b/eslint-factory/src/rules/no-json-stringify-error.test.ts @@ -183,6 +183,75 @@ describe("no-json-stringify-error", () => { }); }); + it("valid: p.catch(handler) named-reference is not flagged (static cross-reference out of scope)", () => { + cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { + valid: [`function handler(err) { JSON.stringify(err); } p.catch(handler);`], + invalid: [], + }); + }); + + it("valid: p.then(ok, handler) named-reference rejection handler is not flagged", () => { + cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { + valid: [`function onRejected(err) { JSON.stringify(err); } p.then(ok, onRejected);`], + invalid: [], + }); + }); + + it("invalid: JSON.stringify(err) in .then() second-argument rejection handler (arrow) is flagged", () => { + cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { + valid: [], + invalid: [ + { + code: `p.then(result => result, err => core.error(JSON.stringify(err)));`, + errors: [ + { + messageId: "jsonStringifyError", + data: { errorVar: "err" }, + suggestions: [ + { + messageId: "useGetErrorMessage", + data: { errorVar: "err" }, + output: `p.then(result => result, err => core.error(getErrorMessage(err)));`, + }, + ], + }, + ], + }, + ], + }); + }); + + it("invalid: JSON.stringify(err) in .then() second-argument rejection handler (function) is flagged", () => { + cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { + valid: [], + invalid: [ + { + code: `p.then(null, function(err) { core.error(JSON.stringify(err)); });`, + errors: [ + { + messageId: "jsonStringifyError", + data: { errorVar: "err" }, + suggestions: [ + { + messageId: "useGetErrorMessage", + data: { errorVar: "err" }, + output: `p.then(null, function(err) { core.error(getErrorMessage(err)); });`, + }, + ], + }, + ], + }, + ], + }); + }); + + it("valid: first argument of .then() (onFulfilled) is not treated as a rejection handler", () => { + cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { + valid: [`p.then(result => JSON.stringify(result));`, `p.then(function(result) { JSON.stringify(result); });`], + invalid: [], + }); + }); + it("invalid: works with ES module syntax", () => { esmRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { valid: [], diff --git a/eslint-factory/src/rules/no-json-stringify-error.ts b/eslint-factory/src/rules/no-json-stringify-error.ts index 6da57870020..52ac78a7f1a 100644 --- a/eslint-factory/src/rules/no-json-stringify-error.ts +++ b/eslint-factory/src/rules/no-json-stringify-error.ts @@ -8,9 +8,15 @@ interface ErrorScope { } /** - * Returns true when the function is passed directly as the first argument to a - * `.catch()` call. Named-reference handlers (for example `p.catch(handler)`) - * are intentionally out of scope. + * Returns true when the function node is a rejection-handler passed inline to + * a promise method: + * - First argument of `.catch(fn)` — the canonical rejection handler. + * - Second argument of `.then(onFulfilled, onRejected)` — semantically + * equivalent to `.catch(onRejected)`. + * + * Named-reference handlers (for example `p.catch(handler)`) are intentionally + * out of scope because the rule cannot statically follow the reference to + * inspect its parameter list without cross-file analysis. */ function isCatchCallback(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): boolean { const parent = node.parent; @@ -18,7 +24,12 @@ function isCatchCallback(node: TSESTree.ArrowFunctionExpression | TSESTree.Funct const callee = parent.callee; if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false; const prop = callee.property; - return prop.type === AST_NODE_TYPES.Identifier && prop.name === "catch" && parent.arguments[0] === node; + if (prop.type !== AST_NODE_TYPES.Identifier) return false; + // .catch(fn) — first argument + if (prop.name === "catch" && parent.arguments[0] === node) return true; + // .then(onFulfilled, onRejected) — second argument is the rejection handler + if (prop.name === "then" && parent.arguments[1] === node) return true; + return false; } export const noJsonStringifyErrorRule = createRule({ @@ -27,7 +38,10 @@ export const noJsonStringifyErrorRule = createRule({ type: "problem", hasSuggestions: true, docs: { - description: "Disallow JSON.stringify() on caught error variables — Error properties (message, stack, etc.) are non-enumerable and produce {} silently", + description: + "Disallow JSON.stringify() on caught error variables — Error properties (message, stack, etc.) are non-enumerable and produce {} silently. " + + "Detected scopes: try/catch bindings, .catch(fn) inline callbacks, and .then(onFulfilled, onRejected) inline rejection handlers. " + + "Named-reference handlers (e.g. p.catch(handler)) are out of scope because the rule cannot statically follow the reference.", }, schema: [], messages: { @@ -87,7 +101,7 @@ export const noJsonStringifyErrorRule = createRule({ scopeStack.pop(); }, - // Track .catch() callback parameters + // Track .catch() callback parameters and .then(_, onRejected) rejection handler parameters ArrowFunctionExpression: enterFunction, "ArrowFunctionExpression:exit": exitFunction, FunctionExpression: enterFunction, From c916cb07059e309db5e96ff54f1711c60cb5bdc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 08:18:31 +0000 Subject: [PATCH 4/4] Address eslint-factory review feedback Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../src/rules/no-json-stringify-error.test.ts | 38 +++++++++++++++++-- .../src/rules/no-json-stringify-error.ts | 16 ++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/eslint-factory/src/rules/no-json-stringify-error.test.ts b/eslint-factory/src/rules/no-json-stringify-error.test.ts index 43daa5d3ff6..76100f6b42e 100644 --- a/eslint-factory/src/rules/no-json-stringify-error.test.ts +++ b/eslint-factory/src/rules/no-json-stringify-error.test.ts @@ -183,9 +183,9 @@ describe("no-json-stringify-error", () => { }); }); - it("valid: p.catch(handler) named-reference is not flagged (static cross-reference out of scope)", () => { + it("valid: p.catch(handler) named-reference is not flagged", () => { cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { - valid: [`function handler(err) { JSON.stringify(err); } p.catch(handler);`], + valid: [`const handler = err => JSON.stringify(err); p.catch(handler);`], invalid: [], }); }); @@ -217,6 +217,22 @@ describe("no-json-stringify-error", () => { }, ], }, + { + code: `p.then(null, err => core.error(JSON.stringify(err, null, 2)));`, + errors: [ + { + messageId: "jsonStringifyError", + data: { errorVar: "err" }, + suggestions: [ + { + messageId: "useGetErrorMessage", + data: { errorVar: "err" }, + output: `p.then(null, err => core.error(getErrorMessage(err)));`, + }, + ], + }, + ], + }, ], }); }); @@ -247,7 +263,7 @@ describe("no-json-stringify-error", () => { it("valid: first argument of .then() (onFulfilled) is not treated as a rejection handler", () => { cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, { - valid: [`p.then(result => JSON.stringify(result));`, `p.then(function(result) { JSON.stringify(result); });`], + valid: [`p.then(result => JSON.stringify(result));`, `p.then(function(result) { JSON.stringify(result); });`, `p.then(err => core.info(JSON.stringify(err)));`], invalid: [], }); }); @@ -272,6 +288,22 @@ describe("no-json-stringify-error", () => { }, ], }, + { + code: `p.then(ok, err => console.error(JSON.stringify(err)));`, + errors: [ + { + messageId: "jsonStringifyError", + data: { errorVar: "err" }, + suggestions: [ + { + messageId: "useGetErrorMessage", + data: { errorVar: "err" }, + output: `p.then(ok, err => console.error(getErrorMessage(err)));`, + }, + ], + }, + ], + }, ], }); }); diff --git a/eslint-factory/src/rules/no-json-stringify-error.ts b/eslint-factory/src/rules/no-json-stringify-error.ts index 52ac78a7f1a..5ba4e56773e 100644 --- a/eslint-factory/src/rules/no-json-stringify-error.ts +++ b/eslint-factory/src/rules/no-json-stringify-error.ts @@ -8,17 +8,17 @@ interface ErrorScope { } /** - * Returns true when the function node is a rejection-handler passed inline to + * Returns true when the function node is an inline rejection handler passed to * a promise method: * - First argument of `.catch(fn)` — the canonical rejection handler. - * - Second argument of `.then(onFulfilled, onRejected)` — semantically - * equivalent to `.catch(onRejected)`. + * - Second argument of `.then(onFulfilled, onRejected)` — the rejection-handler + * position, analogous to `.catch(fn)`. * * Named-reference handlers (for example `p.catch(handler)`) are intentionally * out of scope because the rule cannot statically follow the reference to - * inspect its parameter list without cross-file analysis. + * inspect its parameter list or body. */ -function isCatchCallback(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): boolean { +function isInlineRejectionHandler(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): boolean { const parent = node.parent; if (!parent || parent.type !== AST_NODE_TYPES.CallExpression) return false; const callee = parent.callee; @@ -69,7 +69,7 @@ export const noJsonStringifyErrorRule = createRule({ } function enterFunction(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): void { - if (isCatchCallback(node)) { + if (isInlineRejectionHandler(node)) { const params = node.params; if (params.length === 1 && params[0].type === AST_NODE_TYPES.Identifier) { scopeStack.push({ varName: params[0].name, isSentinel: false }); @@ -78,7 +78,7 @@ export const noJsonStringifyErrorRule = createRule({ scopeStack.push({ varName: "", isSentinel: true }); } } else { - // Non-.catch() function: sentinel to avoid false positives from shadowed names + // Non-rejection-handler function: sentinel to avoid false positives from shadowed names scopeStack.push({ varName: "", isSentinel: true }); } } @@ -101,7 +101,7 @@ export const noJsonStringifyErrorRule = createRule({ scopeStack.pop(); }, - // Track .catch() callback parameters and .then(_, onRejected) rejection handler parameters + // Track inline rejection-handler parameters from .catch(fn) and .then(_, fn) ArrowFunctionExpression: enterFunction, "ArrowFunctionExpression:exit": exitFunction, FunctionExpression: enterFunction,