Files
Eddie fb6e3f1c2b fix(scripts): adversarial audit — 15 bugs across generators, validators, review tooling (#85)
* fix(scripts): adversarial audit fixes across generators, validators, review tooling

Round-1 adversarial subagent review found 13 confirmed bugs across the
plugin-marketplace scripts. All verified fixed:

Critical:
- review-skill.sh: ((VAR++)) returned 1 under set -e when counter was 0,
  aborting the script on the first finding and making the entire report
  unreachable for any non-perfect skill. Add || true.
- review-skill.sh: date -d is GNU-only; on macOS every skill with
  last_verified was falsely flagged as ~20500 days stale. Use BSD date -jf
  with GNU fallback.

Important:
- validate-frontmatter.sh: set -e + validate_skill '$file' aborted on the
  first failing skill; summary never printed. Guard with || true
  (CRITICAL_COUNT already tracks failures).
- validate-json-schemas.sh: plugin_count=1 when zero files found
  (echo '' | wc -l); report falsely showed 'Total: 1'. Explicit empty guard.
- generate-marketplace.sh: version sync read .plugins[0] (always the
  alphabetically-first plugin), masking drift instead of detecting it.
  Now asserts all plugin versions agree; fails loudly on drift.
- baseline-audit-all.sh: wrote to planning/ which doesn't exist; died before
  processing any skill. mkdir -p the output dir.
- check-versions.sh: head -n -1 is GNU-only; silently zeroed the dep list
  on macOS. Use portable sed '$d'.
- sync-plugins.sh: codex loop counted updates even when jq/mv failed, no
  validation before mv, no .tmp cleanup. Now validates + cleans up + counts
  only on success. Corrected misleading 'lockstep' comment (version-only).
- generate-marketplace.sh: heredoc interpolated raw values into JSON (latent
  injection if a description gained a quote/backslash/newline). Build each
  entry with jq -nc --arg.
- fix-frontmatter.mjs: closing --- swallowed when description was last field;
  1-space indent emitted invalid >- content; merged unrelated lists;
  falsely re-folded already-valid descriptions (non-idempotent, churned 25
  files). Added block-scalar idempotency guard, non-blank-continuation
  check, delimiter stop, per-list indent scoping.

Minor/hygiene:
- Align divergent version defaults (1.0.0 vs 3.0.0) across codex/marketplace
  generators.
- Add pipefail to generate-marketplace.sh + generate-codex-manifests.sh.

* fix(scripts): round-2 adversarial findings — propagate ((VAR++)) fix + harden date parse

Round-2 adversarial review (on commit 154847fb) found two remaining
Important issues in the same bug families:

- baseline-audit-all.sh: the ((VAR++)) counters (COMPLETED, CRITICAL_SKILLS,
  HIGH_SKILLS, MEDIUM_SKILLS, CLEAN_SKILLS) had no || true guard, so under
  set -euo pipefail the audit aborted on the first skill — the same bug class
  fixed in review-skill.sh but not propagated to this caller. Also default
  `days=0` so a missing grep match can't throw 'integer expression expected'.
- review-skill.sh: the BSD/GNU date fallback left verified_epoch=0 when the
  last_verified value was a quoted scalar or non-YYYY-MM-DD format, silently
  flagging the skill as ~20,500 days stale (the exact false-positive the fix
  targeted). Now strips surrounding quotes, and if neither parser recognizes
  the value, skips the staleness check instead of computing a bogus age.

Both verified: baseline-audit-all now runs all 182 skills to completion;
review-skill date path handles quoted/ISO/unrecognized values correctly.

* fix(fix-frontmatter): cover all YAML block-scalar headers; preserve blank lines on list break

Addresses CodeRabbit review on PR #85:

- Idempotency regex only matched >| with optional chomping (-/+). YAML also
  allows indentation indicators (|2, >3) and header comments (| # c), plus
  any-order indicator combos (|3+, >2-). Now parses the header token
  (before any comment) against the full grammar, leaving all valid
  block-scalar headers untouched.
- fixListIndentation dropped buffered blank lines when the list block broke
  on a different-indentation line: the blanks were consumed (j advanced)
  but never pushed to out. Buffer them as pendingBlanks and push on the
  break path so trailing blanks survive in the output.

Verified: 15-case regex test all-correct; idempotent on clean repo (0 files);
validate-frontmatter + validate-json-schemas still green.
2026-08-08 16:16:10 +02:00

174 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# baseline-audit-all.sh - Run quick automated checks on all skills
# Generates summary data for SKILLS_REVIEW_PROGRESS.md
# Usage: ./scripts/baseline-audit-all.sh
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Skills live under plugins/<plugin>/skills/<skill>/ and .agents/skills/<skill>/
# (the repo was restructured away from a flat top-level skills/ directory).
PLUGINS_DIR="$REPO_ROOT/plugins"
AGENTS_SKILLS_DIR="$REPO_ROOT/.agents/skills"
REVIEW_SCRIPT="$REPO_ROOT/scripts/review-skill.sh"
OUTPUT_FILE="$REPO_ROOT/planning/baseline-audit-results.txt"
# List all skill names (one per SKILL.md) across the supported layouts.
all_skill_names() {
find "$PLUGINS_DIR" -mindepth 4 -maxdepth 4 -type f -name "SKILL.md" \
-path "*/skills/*/SKILL.md" 2>/dev/null \
| sed 's#.*/skills/##; s#/SKILL\.md$##' | sort -u
find "$AGENTS_SKILLS_DIR" -mindepth 2 -maxdepth 2 -type f -name "SKILL.md" 2>/dev/null \
| sed 's#.*/skills/##; s#/SKILL\.md$##' | sort -u
}
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Counters
TOTAL_SKILLS=0
COMPLETED=0
CRITICAL_SKILLS=0
HIGH_SKILLS=0
MEDIUM_SKILLS=0
CLEAN_SKILLS=0
# Check review script exists
if [ ! -x "$REVIEW_SCRIPT" ]; then
echo -e "${RED}Error: review-skill.sh not found or not executable${NC}"
exit 1
fi
echo "════════════════════════════════════════════════════════════"
echo " BASELINE AUDIT: All Skills"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Starting baseline audit..."
echo "Output will be saved to: $OUTPUT_FILE"
echo ""
# Ensure the output directory exists (planning/ is not guaranteed to be
# present; without this the `cat >` below fails under `set -e` before any
# skill is processed).
mkdir -p "$(dirname "$OUTPUT_FILE")"
# Initialize output file
cat > "$OUTPUT_FILE" << 'EOF'
# Baseline Audit Results
# Generated: $(date +"%Y-%m-%d %H:%M:%S")
#
# Format: SKILL_NAME | CRITICAL | HIGH | MEDIUM | LOW | STATUS | NOTES
#
EOF
# Get all skill names (across plugins/ and .agents/skills/)
SKILLS=($(all_skill_names))
TOTAL_SKILLS=${#SKILLS[@]}
echo "Found $TOTAL_SKILLS skills to audit"
echo ""
# Process each skill
for skill in "${SKILLS[@]}"; do
# `((VAR++))` returns exit status 1 when VAR is 0; under `set -e` this would
# abort the audit on the first skill (same bug class fixed in review-skill.sh).
((COMPLETED++)) || true
echo -ne "[$COMPLETED/$TOTAL_SKILLS] Checking $skill..."
# Run review script in quick mode, capture output
output=$("$REVIEW_SCRIPT" "$skill" --quick 2>&1 || true)
# Extract issue counts
critical=$(echo "$output" | grep -o "🔴 CRITICAL ([0-9]*)" | grep -o "[0-9]*" || echo "0")
high=$(echo "$output" | grep -o "🟡 HIGH ([0-9]*)" | grep -o "[0-9]*" || echo "0")
medium=$(echo "$output" | grep -o "🟠 MEDIUM ([0-9]*)" | grep -o "[0-9]*" || echo "0")
low=$(echo "$output" | grep -o "🟢 LOW ([0-9]*)" | grep -o "[0-9]*" || echo "0")
# Determine status
status="CLEAN"
priority="🟢 Low"
if [ "$critical" -gt 0 ]; then
((CRITICAL_SKILLS++)) || true
status="CRITICAL"
priority="🔴 Critical"
echo -e " ${RED}CRITICAL${NC} (${critical} critical, ${high} high, ${medium} medium)"
elif [ "$high" -gt 0 ]; then
((HIGH_SKILLS++)) || true
status="HIGH"
priority="🟡 High"
echo -e " ${YELLOW}HIGH${NC} (${high} high, ${medium} medium)"
elif [ "$medium" -gt 0 ]; then
((MEDIUM_SKILLS++)) || true
status="MEDIUM"
priority="🟠 Medium"
echo -e " ${BLUE}MEDIUM${NC} (${medium} medium, ${low} low)"
else
((CLEAN_SKILLS++)) || true
echo -e " ${GREEN}CLEAN${NC}"
fi
# Extract specific issues for notes
notes=""
if echo "$output" | grep -q "Missing YAML frontmatter"; then
notes="$notes NO_YAML"
fi
if echo "$output" | grep -q "Name mismatch"; then
notes="$notes NAME_MISMATCH"
fi
if echo "$output" | grep -q "Last verified.*days ago"; then
# Default to 0 so a missing/empty match doesn't make `[ "" -gt 90 ]` throw
# "integer expression expected" under set -e/u and abort the audit.
days=$(echo "$output" | grep -o "Last verified [0-9]* days ago" | grep -o "[0-9]*" || echo "0")
[ -z "$days" ] && days=0
if [ "$days" -gt 90 ]; then
notes="$notes STALE_${days}d"
fi
fi
if echo "$output" | grep -q "hasn't been verified"; then
notes="$notes NO_VERIFICATION"
fi
# Write to output file
echo "$skill|$critical|$high|$medium|$low|$status|$priority|$notes" >> "$OUTPUT_FILE"
done
echo ""
echo "════════════════════════════════════════════════════════════"
echo " BASELINE AUDIT COMPLETE"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Summary:"
echo " Total Skills: $TOTAL_SKILLS"
echo " 🔴 Critical: $CRITICAL_SKILLS (immediate attention)"
echo " 🟡 High: $HIGH_SKILLS (fix soon)"
echo " 🟠 Medium: $MEDIUM_SKILLS (minor issues)"
echo " 🟢 Clean: $CLEAN_SKILLS (no issues found)"
echo ""
echo "Results saved to: $OUTPUT_FILE"
echo ""
echo "Next steps:"
echo "1. Review $OUTPUT_FILE for detailed breakdown"
echo "2. Update planning/SKILLS_REVIEW_PROGRESS.md"
echo "3. Prioritize critical/high issues"
echo "4. Begin systematic reviews"
echo ""
# Generate summary statistics
echo "## Summary Statistics" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
echo "- Total Skills: $TOTAL_SKILLS" >> "$OUTPUT_FILE"
echo "- Critical Priority: $CRITICAL_SKILLS" >> "$OUTPUT_FILE"
echo "- High Priority: $HIGH_SKILLS" >> "$OUTPUT_FILE"
echo "- Medium Priority: $MEDIUM_SKILLS" >> "$OUTPUT_FILE"
echo "- Clean: $CLEAN_SKILLS" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
exit 0