diff --git a/.github/workflows/lint-agents.yml b/.github/workflows/lint-agents.yml new file mode 100644 index 0000000..8baa37f --- /dev/null +++ b/.github/workflows/lint-agents.yml @@ -0,0 +1,44 @@ +name: Lint Agent Files + +on: + pull_request: + paths: + - 'design/**' + - 'engineering/**' + - 'marketing/**' + - 'product/**' + - 'project-management/**' + - 'testing/**' + - 'support/**' + - 'spatial-computing/**' + - 'specialized/**' + +jobs: + lint: + name: Validate agent frontmatter and structure + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed agent files + id: changed + run: | + FILES=$(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- \ + 'design/*.md' 'engineering/*.md' 'marketing/*.md' 'product/*.md' \ + 'project-management/*.md' 'testing/*.md' 'support/*.md' \ + 'spatial-computing/*.md' 'specialized/*.md') + echo "files=$FILES" >> "$GITHUB_OUTPUT" + if [ -z "$FILES" ]; then + echo "No agent files changed." + else + echo "Changed files:" + echo "$FILES" + fi + + - name: Run agent linter + if: steps.changed.outputs.files != '' + run: | + chmod +x scripts/lint-agents.sh + ./scripts/lint-agents.sh ${{ steps.changed.outputs.files }} diff --git a/scripts/lint-agents.sh b/scripts/lint-agents.sh new file mode 100755 index 0000000..3ba121c --- /dev/null +++ b/scripts/lint-agents.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# +# Validates agent markdown files: +# 1. YAML frontmatter must exist with name, description, color (ERROR) +# 2. Recommended sections checked but only warned (WARN) +# 3. File must have meaningful content +# +# Usage: ./scripts/lint-agents.sh [file ...] +# If no files given, scans all agent directories. + +set -euo pipefail + +AGENT_DIRS=( + design + engineering + marketing + product + project-management + testing + support + spatial-computing + specialized +) + +REQUIRED_FRONTMATTER=("name" "description" "color") +RECOMMENDED_SECTIONS=("Identity" "Core Mission" "Critical Rules") + +errors=0 +warnings=0 + +lint_file() { + local file="$1" + + # 1. Check frontmatter delimiters + local first_line + first_line=$(head -1 "$file") + if [[ "$first_line" != "---" ]]; then + echo "ERROR $file: missing frontmatter opening ---" + ((errors++)) + return + fi + + # Extract frontmatter (between first and second ---) + local frontmatter + frontmatter=$(awk 'NR==1{next} /^---$/{exit} {print}' "$file") + + if [[ -z "$frontmatter" ]]; then + echo "ERROR $file: empty or malformed frontmatter" + ((errors++)) + return + fi + + # 2. Check required frontmatter fields + for field in "${REQUIRED_FRONTMATTER[@]}"; do + if ! echo "$frontmatter" | grep -qE "^${field}:"; then + echo "ERROR $file: missing frontmatter field '${field}'" + ((errors++)) + fi + done + + # 3. Check recommended sections (warn only) + local body + body=$(awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' "$file") + + for section in "${RECOMMENDED_SECTIONS[@]}"; do + if ! echo "$body" | grep -qi "$section"; then + echo "WARN $file: missing recommended section '${section}'" + ((warnings++)) + fi + done + + # 4. Check file has meaningful content + if [[ $(echo "$body" | wc -w) -lt 50 ]]; then + echo "WARN $file: body seems very short (< 50 words)" + ((warnings++)) + fi +} + +# Collect files to lint +files=() +if [[ $# -gt 0 ]]; then + files=("$@") +else + for dir in "${AGENT_DIRS[@]}"; do + if [[ -d "$dir" ]]; then + while IFS= read -r f; do + files+=("$f") + done < <(find "$dir" -maxdepth 1 -name "*.md" -type f | sort) + fi + done +fi + +if [[ ${#files[@]} -eq 0 ]]; then + echo "No agent files found." + exit 1 +fi + +echo "Linting ${#files[@]} agent files..." +echo "" + +for file in "${files[@]}"; do + lint_file "$file" +done + +echo "" +echo "Results: ${errors} error(s), ${warnings} warning(s) in ${#files[@]} files." + +if [[ $errors -gt 0 ]]; then + echo "FAILED: fix the errors above before merging." + exit 1 +else + echo "PASSED" + exit 0 +fi