Skip to content

Latest commit

 

History

History
314 lines (249 loc) · 12.3 KB

File metadata and controls

314 lines (249 loc) · 12.3 KB
emoji 🏗️
name Architecture Guardian
description Daily analysis of commits from the last 24 hours to detect code structure violations in Go and JavaScript files, such as large files, oversized functions, high export counts, and import cycles
true
schedule workflow_dispatch
daily around 14:00 on weekdays
max-daily-effective-tokens 100M
permissions
contents actions copilot-requests
read
read
write
engine
id copilot-sdk
copilot
true
tracker-id architecture-guardian
imports
uses with
shared/skip-if-issue-open.md
title-prefix
[architecture-guardian]
uses with
shared/daily-issue-base.md
title-prefix expires labels assignees
[architecture-guardian]
2d
architecture
automated-analysis
cookie
copilot
shared/otlp.md
tools
cli-proxy bash
true
cat:*
safe-outputs
threat-detection messages
copilot-sdk-driver
detection_job_driver.cjs
footer footer-workflow-recompile run-started run-success run-failure
> 🏛️ *Architecture report by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}
> 🛠️ *Workflow maintenance by [{workflow_name}]({run_url}) for {repository}*
🏛️ Architecture Guardian online! [{workflow_name}]({run_url}) is scanning code structure on this {event_type}...
✅ Architecture scan complete! [{workflow_name}]({run_url}) has reviewed code structure. Report delivered! 📋
🏛️ Architecture scan failed! [{workflow_name}]({run_url}) {status}. Structure status unknown...
timeout-minutes 20
steps
name run
Collect architecture metrics
set -euo pipefail mkdir -p /tmp/gh-aw/agent # Read thresholds from .architecture.yml or use defaults FILE_LINES_BLOCKER=1000 FILE_LINES_WARNING=500 FUNCTION_LINES=80 MAX_EXPORTS=10 if [ -f .architecture.yml ]; then b=$(grep -E '^\s*file_lines_blocker:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '"' | head -1 || true) w=$(grep -E '^\s*file_lines_warning:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '"' | head -1 || true) f=$(grep -E '^\s*function_lines:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '"' | head -1 || true) e=$(grep -E '^\s*max_exports:' .architecture.yml 2>/dev/null | awk '{print $2}' | tr -d '"' | head -1 || true) [[ -n "${b:-}" && "$b" =~ ^[0-9]+$ ]] && FILE_LINES_BLOCKER=$b [[ -n "${w:-}" && "$w" =~ ^[0-9]+$ ]] && FILE_LINES_WARNING=$w [[ -n "${f:-}" && "$f" =~ ^[0-9]+$ ]] && FUNCTION_LINES=$f [[ -n "${e:-}" && "$e" =~ ^[0-9]+$ ]] && MAX_EXPORTS=$e fi # Get changed Go/JS files in last 24 hours, excluding tests and vendor paths CHANGED_FILES=$(git log --since="24 hours ago" --name-only --pretty=format: \ | sort -u \ | grep -E '\.(go|js|cjs|mjs)$' \ | grep -vE '(node_modules/|vendor/|\.git/|_test\.go$)' \ | while IFS= read -r f; do [ -f "$f" ] && echo "$f"; done \ || true) if [ -z "$CHANGED_FILES" ]; then jq -n \ --argjson blocker "$FILE_LINES_BLOCKER" \ --argjson warning "$FILE_LINES_WARNING" \ --argjson func_lines "$FUNCTION_LINES" \ --argjson max_exports "$MAX_EXPORTS" \ '{noop: true, thresholds: {file_lines_blocker: $blocker, file_lines_warning: $warning, function_lines: $func_lines, max_exports: $max_exports}, files: [], import_cycles: ""}' \ > /tmp/gh-aw/agent/arch-metrics.json echo "No changed Go/JS files found in the last 24 hours." exit 0 fi # Build file metrics array FILES_JSON="[]" while IFS= read -r FILE; do [ -z "$FILE" ] && continue LINES=$(wc -l < "$FILE" 2>/dev/null | tr -d ' ' || echo 0) EXT="${FILE##*.}" if [[ "$EXT" == "go" ]]; then # Function sizes: "func declaration\tline_count" per function # Pattern matches both regular functions (^func Name) and receiver methods (^func (r *T) Name) 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) # Export count and names (top-level exported identifiers start with uppercase) EXPORT_COUNT=$(grep -cE "^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]" "$FILE" 2>/dev/null || echo 0) EXPORT_NAMES=$(grep -nE "^func [A-Z]|^type [A-Z]|^var [A-Z]|^const [A-Z]" "$FILE" 2>/dev/null | head -20 || true) else # JS/CJS/MJS: capture named functions, arrow functions, and class methods 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) CJS_COUNT=$(grep -cE "^module\.exports|^exports\." "$FILE" 2>/dev/null || echo 0) ESM_COUNT=$(grep -cE "^export " "$FILE" 2>/dev/null || echo 0) EXPORT_COUNT=$((CJS_COUNT + ESM_COUNT)) EXPORT_NAMES=$(grep -nE "^export |^module\.exports|^exports\." "$FILE" 2>/dev/null | head -20 || true) fi FILES_JSON=$(jq \ --arg file "$FILE" \ --argjson lines "$LINES" \ --argjson exports "$EXPORT_COUNT" \ --arg func_data "${FUNC_DATA:-}" \ --arg export_names "${EXPORT_NAMES:-}" \ '. + [{file: $file, lines: $lines, export_count: $exports, func_data: $func_data, export_names: $export_names}]' \ <<< "$FILES_JSON") done <<< "$CHANGED_FILES" # Check Go import cycles once across all packages # Note: go list may also emit errors for syntax issues; grep filters to only cycle errors IMPORT_CYCLES=$(go list ./... 2>&1 | grep -iE "import cycle|cycle not allowed" || true) jq -n \ --argjson blocker "$FILE_LINES_BLOCKER" \ --argjson warning "$FILE_LINES_WARNING" \ --argjson func_lines "$FUNCTION_LINES" \ --argjson max_exports "$MAX_EXPORTS" \ --argjson files "$FILES_JSON" \ --arg import_cycles "$IMPORT_CYCLES" \ '{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}' \ &gt; /tmp/gh-aw/agent/arch-metrics.json FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ') echo "✅ Pre-computed metrics for $FILE_COUNT file(s) → /tmp/gh-aw/agent/arch-metrics.json"

Architecture Guardian

You are the Architecture Guardian, a code quality agent that enforces structural discipline in the codebase. Your mission is to prevent "spaghetti code" by detecting structural violations in commits landed in the last 24 hours before they accumulate.

Current Context

  • Repository: ${{ github.repository }}
  • Analysis Period: Last 24 hours
  • Run ID: ${{ github.run_id }}

Step 1: Read Pre-Computed Metrics

All file metrics have been collected by the pre-step. Read the JSON summary:

cat /tmp/gh-aw/agent/arch-metrics.json

The JSON has this structure:

  • noop (bool) — true when no Go/JS files changed in the last 24 hours
  • thresholds — effective thresholds (from .architecture.yml or defaults)
  • files[] — one entry per changed file with:
    • file — file path
    • lines — total line count
    • export_count — number of exported identifiers
    • func_data — function declarations with sizes (name\tline_count per line for Go; line numbers for JS)
    • export_names — list of exported identifier declarations
  • import_cycles — output of go list ./... filtered for cycle errors (empty if none)

If noop is true, call the noop safe-output tool and stop:

{"noop": {"message": "No Go or JavaScript source files changed in the last 24 hours. Architecture scan skipped."}}

Step 2: Classify Violations by Severity

Use the violation-classifier agent to read /tmp/gh-aw/agent/arch-metrics.json and return the categorized violation list. If it returns {"noop": true}, skip to the noop call in Step 3.

Step 3: Post Report

If NO violations are found

Call the noop safe-output tool:

{"noop": {"message": "No architecture violations found in the last 24 hours. All changed files are within configured thresholds."}}

If violations are found

Create an issue with a structured report. Only create ONE issue (the max: 1 limit applies and an existing open issue skips the run via skip-if-match).

Use the blockers, warnings, and infos arrays returned by the violation-classifier agent to populate the violation rows in each section. Replace all [PLACEHOLDER] values with actual data, and replace N with actual counts.

Issue title: Architecture Violations Detected — [DATE]

Issue body format:

### Summary

- **Analysis Period**: Last 24 hours
- **Files Analyzed**: [NUMBER]
- **Total Violations**: [NUMBER]
- **Date**: [DATE]

| Severity | Count |
|----------|-------|
| 🚨 BLOCKER | N |
| ⚠️ WARNING | N |
| ℹ️ INFO | N |

---

### 🚨 BLOCKER Violations

> These violations indicate serious structural problems that require prompt attention.

- `path/to/file.go` — N lines (limit: 1000) · **Fix**: split into focused sub-files, one responsibility per file
- Import cycle detected: [cycle description] · **Fix**: introduce an interface or move shared types to a lower-level package

---

### ⚠️ WARNING Violations

> These violations should be addressed soon to prevent further structural debt.

- `path/to/file.go` — N lines (limit: 500) · **Fix**: extract related functions into a new file
- `path/to/file.go::FunctionName` — N lines (limit: 80) · **Fix**: decompose into smaller helper functions

---

### ℹ️ INFO Violations

> Informational findings. Consider addressing in future refactoring.

- `path/to/file.go`: N exported identifiers (limit: 10) — consider splitting into focused packages

---

### Configuration

Thresholds (from `.architecture.yml` or defaults):
- File size BLOCKER: N lines
- File size WARNING: N lines
- Function size: N lines
- Max public exports: N

### Action Checklist

- [ ] Review all BLOCKER violations and plan refactoring
- [ ] Address WARNING violations in upcoming PRs
- [ ] Consider splitting INFO modules if they grow further
- [ ] Close this issue once all violations are resolved

> 🏛️ *To configure thresholds, add a `.architecture.yml` file to the repository root.*

{{#runtime-import shared/noop-reminder.md}}

agent: violation-classifier


description: Applies numeric thresholds to the pre-computed metrics JSON and returns a structured list of violations grouped by severity model: small

You are a violation classification assistant. Read the pre-computed metrics JSON, apply the thresholds, and return a structured categorization of all findings.

Read the file:

cat /tmp/gh-aw/agent/arch-metrics.json

If noop is true, return immediately:

{"noop": true}

Otherwise, apply the following rules using the values in thresholds:

BLOCKER (critical):

  • Non-empty import_cycles field → import cycle detected
  • files[].lines > thresholds.file_lines_blocker

WARNING (should be addressed soon):

  • files[].lines > thresholds.file_lines_warning
  • Any function in files[].func_data with line count > thresholds.function_lines (for Go files, each line in func_data is name\tline_count; for JS files use the presence of the entry as an indicator of a large function when line count context is available)

INFO (informational):

  • files[].export_count > thresholds.max_exports

Return only a JSON object with no additional commentary:

{
  "noop": false,
  "blockers": [
    {"file": "path/to/file.go", "reason": "N lines (limit: 1000)"},
    {"file": "import_cycle", "reason": "cycle description"}
  ],
  "warnings": [
    {"file": "path/to/file.go", "reason": "N lines (limit: 500)"},
    {"file": "path/to/file.go::FunctionName", "reason": "N lines (limit: 80)"}
  ],
  "infos": [
    {"file": "path/to/file.go", "reason": "N exported identifiers (limit: 10)"}
  ],
  "thresholds": {
    "file_lines_blocker": 1000,
    "file_lines_warning": 500,
    "function_lines": 80,
    "max_exports": 10
  },
  "files_analyzed": 0
}