v1.6.0 — per-agent souls #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint and validate | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| markdown-lint: | |
| name: Markdown lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install markdownlint-cli | |
| run: npm install -g markdownlint-cli@0.41.0 | |
| - name: Run markdownlint | |
| run: markdownlint '**/*.md' --ignore node_modules --ignore .obsidian --config .markdownlint.jsonc | |
| frontmatter-check: | |
| name: Frontmatter present on all nodes | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check every reference node and subagent starts with frontmatter | |
| run: | | |
| set -e | |
| missing=0 | |
| for dir in skills/design-engineering/references skills/design-engineering/agents; do | |
| for f in $(find "$dir" -name '*.md'); do | |
| first_line=$(head -n 1 "$f") | |
| if [ "$first_line" != "---" ]; then | |
| echo "::error file=$f::Missing YAML frontmatter (file must start with ---)" | |
| missing=$((missing + 1)) | |
| fi | |
| done | |
| done | |
| if [ $missing -gt 0 ]; then | |
| echo "::error::$missing file(s) missing frontmatter" | |
| exit 1 | |
| fi | |
| echo "All reference nodes and subagents have frontmatter." | |
| skill-structure: | |
| name: Skill structure | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Verify required files exist | |
| run: | | |
| set -e | |
| required=( | |
| "skills/design-engineering/SKILL.md" | |
| "skills/design-engineering/agents/README.md" | |
| "skills/design-engineering/agents/ui-reviewer.md" | |
| "skills/design-engineering/agents/motion-auditor.md" | |
| "skills/design-engineering/agents/anti-pattern-scanner.md" | |
| "skills/design-engineering/agents/agentation-fix-loop.md" | |
| "skills/design-engineering/agents/design-md-consumer.md" | |
| "skills/design-engineering/agents/pov-curator.md" | |
| "AGENTS.md" | |
| "SOUL.md" | |
| "README.md" | |
| "LICENSE" | |
| "CONTRIBUTING.md" | |
| "CODE_OF_CONDUCT.md" | |
| "SECURITY.md" | |
| "spec/agent-skills-spec.md" | |
| "template/TEMPLATE.md" | |
| ".claude-plugin/marketplace.json" | |
| ) | |
| for f in "${required[@]}"; do | |
| if [ ! -f "$f" ]; then | |
| echo "::error file=$f::Required file missing" | |
| exit 1 | |
| fi | |
| done | |
| echo "All required files present." | |
| - name: SKILL.md description starts with 'Load when' | |
| run: | | |
| desc=$(awk '/^description:/{flag=1; sub(/^description: /, ""); print; next} flag && /^[a-z]+:/ {flag=0} flag {print}' skills/design-engineering/SKILL.md) | |
| if ! echo "$desc" | head -n 1 | grep -qiE '^["\'']?(load when|use when)'; then | |
| echo "::warning file=skills/design-engineering/SKILL.md::Description should start with 'Load when...' or 'Use when...' per Perplexity's skill-building guide" | |
| else | |
| echo "Description starts with 'Load when' / 'Use when' — OK." | |
| fi | |
| - name: Skill name matches folder name | |
| run: | | |
| name=$(grep -m 1 '^name:' skills/design-engineering/SKILL.md | awk '{print $2}' | tr -d '"') | |
| if [ "$name" != "design-engineering" ]; then | |
| echo "::error::SKILL.md name ($name) does not match folder name (design-engineering)" | |
| exit 1 | |
| fi | |
| echo "Name matches folder name." | |
| - name: Subagent frontmatter fields | |
| run: | | |
| set -e | |
| missing=0 | |
| for f in $(find skills/design-engineering/agents -name '*.md' -not -name 'README.md'); do | |
| base=$(basename "$f" .md) | |
| # Each subagent must declare: name, description, tools, model | |
| for field in name description tools model; do | |
| if ! grep -qE "^${field}:" "$f"; then | |
| echo "::error file=$f::Subagent missing required frontmatter field: $field" | |
| missing=$((missing + 1)) | |
| fi | |
| done | |
| # Subagent name must match filename | |
| agent_name=$(grep -m 1 '^name:' "$f" | awk '{print $2}' | tr -d '"') | |
| if [ "$agent_name" != "$base" ]; then | |
| echo "::error file=$f::Subagent name ($agent_name) does not match filename ($base)" | |
| missing=$((missing + 1)) | |
| fi | |
| done | |
| if [ $missing -gt 0 ]; then | |
| echo "::error::$missing subagent frontmatter error(s)" | |
| exit 1 | |
| fi | |
| echo "All subagents have required frontmatter (name, description, tools, model) and names match filenames." | |
| - name: Subagent soul block | |
| run: | | |
| set -e | |
| missing=0 | |
| for f in $(find skills/design-engineering/agents -name '*.md' -not -name 'README.md'); do | |
| if ! grep -qE '^## Soul' "$f"; then | |
| echo "::error file=$f::Subagent missing required ## Soul section" | |
| missing=$((missing + 1)) | |
| fi | |
| done | |
| if [ $missing -gt 0 ]; then | |
| echo "::error::$missing subagent(s) missing ## Soul section" | |
| exit 1 | |
| fi | |
| echo "All subagents carry a ## Soul section." | |
| link-check: | |
| name: Internal wikilink check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Verify every [[wikilink]] in skill content resolves to a file | |
| run: | | |
| set -e | |
| # Collect basenames of reference files AND subagents (the resolvable wikilink targets). | |
| # Also include "SKILL" so cross-refs from subagents/README to the main SKILL.md resolve. | |
| basenames=$( { find skills/design-engineering/references skills/design-engineering/agents -name '*.md' -exec basename {} .md \; ; echo SKILL ; } ) | |
| # Descriptive uses that aren't real links (allow-list) | |
| allowlist=$(printf '%s\n' wikilinks node-name name foo bar wikilink-1 wikilink-2) | |
| unresolved=0 | |
| # Only check actual skill content — not docs (CONTRIBUTING/README/AGENTS/spec/template) | |
| for f in $(find skills/design-engineering -name '*.md'); do | |
| # Strip code fences and inline backticks so descriptive [[wikilinks]] in code don't false-positive. | |
| stripped=$(awk 'BEGIN{infence=0} /^```/{infence=!infence; next} !infence' "$f" | sed 's/`[^`]*`//g') | |
| for link in $(echo "$stripped" | grep -oE '\[\[[a-zA-Z0-9_-]+\]\]' | sed 's/\[\[//;s/\]\]//' | sort -u); do | |
| if echo "$allowlist" | grep -qx "$link"; then | |
| continue | |
| fi | |
| if ! echo "$basenames" | grep -qx "$link"; then | |
| echo "::error file=$f::Unresolved wikilink: [[$link]]" | |
| unresolved=$((unresolved + 1)) | |
| fi | |
| done | |
| done | |
| if [ $unresolved -gt 0 ]; then | |
| echo "::error::$unresolved unresolved wikilink(s)" | |
| exit 1 | |
| fi | |
| echo "All wikilinks in skill content resolve." |