- analytics-interpreter absorbs performance-reporter (interpret/report modes, identical data sources): mode-selector + both output templates kept inline. - engagement-coach absorbs comment-strategist (5x5x5 + first-hour + CEA commenting + target selection + scoring + quality scorecard + daily routine). Self-ref at engagement-coach.md:24 rewritten — target is now in-file. Model upgraded from haiku to sonnet (absorbed deeper work); tools union: Read, Glob, WebSearch. - 7 ref-files reconciled: commands/linkedin.md (router rules merged), skills/linkedin-analytics (row dropped), skills/linkedin-thought-leadership (2 rows merged), skills/linkedin-networking (row dropped), CLAUDE.md (agents table 16→14, merged rows), README.md (agents table, flow diagram, intent table, analytics consumers line), references/glossary.md (3 'Used in' refs), scripts/test-runner.sh (EXPECTED_AGENTS list reconciled to current 14 — also closed lingering S5/S6 gaps for fact-checker/persona-reviewer/ video-scripter, removed already-deleted content-tracker/personalization-scorer), docs/agents-capability-matrix.md (full restructure: header count 16→14, agent table, capability grid columns + capabilities, pipeline diagram, intent table, model selection table — fixed STATE residual #1 on the tracker/pers-scorer stale columns in the same pass). - Q2 decision (video-scripter → content-repurposer?): KEEP separate. Distinct invocation paths (/linkedin:video vs format conversion), distinct outputs (timed video script with pacing+captions vs format-translation artifact), and newsletter.md already uses content-repurposer for prose drafting independently of video-scripter. Net agents/ 16→14. - agents/README.md dropped from Files (moved to docs/agents-capability-matrix.md in S14); literal Verify exits 2 on missing path (logged), corrected Verify passes 4/4 predicates. Manifest audit: 2/2 expected paths exist, 13 'CEA' occurrences in engagement-coach.md. - gitleaks: clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
258 lines
7 KiB
Bash
Executable file
258 lines
7 KiB
Bash
Executable file
#!/bin/bash
|
|
# LinkedIn Thought Leadership Plugin — Structure Validator
|
|
# Validates file existence, frontmatter format, and router completeness
|
|
# Usage: bash scripts/test-runner.sh
|
|
|
|
set -e
|
|
|
|
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
# Color output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
pass() { echo -e "${GREEN}✓${NC} $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo -e "${RED}✗${NC} $1"; FAIL=$((FAIL + 1)); }
|
|
warn() { echo -e "${YELLOW}⚠${NC} $1"; WARN=$((WARN + 1)); }
|
|
|
|
echo "================================================"
|
|
echo "LinkedIn Thought Leadership Plugin — Structure Validator"
|
|
echo "Plugin root: $PLUGIN_ROOT"
|
|
echo "================================================"
|
|
echo ""
|
|
|
|
# --- Section 1: Core Files ---
|
|
echo "--- Core Files ---"
|
|
|
|
for f in ".claude-plugin/plugin.json" "CLAUDE.md" "CHANGELOG.md" "docs/DEVELOPMENT-LOG.md" "README.md" "config/REMEMBER.template.md"; do
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
pass "$f exists"
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 2: Agent Files ---
|
|
echo "--- Agent Files ---"
|
|
|
|
EXPECTED_AGENTS=(
|
|
"engagement-coach" "content-optimizer" "strategy-advisor" "analytics-interpreter"
|
|
"content-planner" "network-builder" "content-repurposer" "trend-spotter"
|
|
"voice-trainer" "differentiation-checker" "post-feedback-monitor" "video-scripter"
|
|
"fact-checker" "persona-reviewer"
|
|
)
|
|
|
|
for agent in "${EXPECTED_AGENTS[@]}"; do
|
|
f="agents/${agent}.md"
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
# Check for YAML frontmatter
|
|
if head -1 "$PLUGIN_ROOT/$f" | grep -q "^---"; then
|
|
# Check for required fields
|
|
if grep -q "^name:" "$PLUGIN_ROOT/$f" && grep -q "^model:" "$PLUGIN_ROOT/$f" && grep -q "^color:" "$PLUGIN_ROOT/$f"; then
|
|
pass "$f (frontmatter OK)"
|
|
else
|
|
warn "$f (missing frontmatter fields)"
|
|
fi
|
|
else
|
|
fail "$f (no YAML frontmatter)"
|
|
fi
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 3: Command Files ---
|
|
echo "--- Command Files ---"
|
|
|
|
EXPECTED_COMMANDS=(
|
|
"linkedin" "linkedin:setup" "linkedin:post" "linkedin:quick" "linkedin:templates"
|
|
"linkedin:pipeline" "linkedin:batch" "linkedin:analyze" "linkedin:audit"
|
|
"linkedin:import" "linkedin:report" "linkedin:strategy" "linkedin:authority"
|
|
"linkedin:competitive" "linkedin:monetize" "linkedin:profile"
|
|
"linkedin:collab" "linkedin:speaking" "linkedin:multiplatform"
|
|
"linkedin:ab-test"
|
|
)
|
|
|
|
for cmd in "${EXPECTED_COMMANDS[@]}"; do
|
|
f="commands/${cmd}.md"
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
if head -1 "$PLUGIN_ROOT/$f" | grep -q "^---"; then
|
|
if grep -q "^name:" "$PLUGIN_ROOT/$f" && grep -q "^description:" "$PLUGIN_ROOT/$f"; then
|
|
pass "$f (frontmatter OK)"
|
|
else
|
|
warn "$f (missing frontmatter fields)"
|
|
fi
|
|
else
|
|
fail "$f (no YAML frontmatter)"
|
|
fi
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 4: Reference Files ---
|
|
echo "--- Reference Files ---"
|
|
|
|
EXPECTED_REFS=(
|
|
"engagement-frameworks" "collaborations-guide" "algorithm-signals-reference"
|
|
"linkedin-growth-playbook-2025-2026" "opportunity-generation"
|
|
"linkedin-formats" "ai-content-framework" "articles-strategy-guide"
|
|
"first-comment-strategy" "poll-strategy-guide" "newsletter-strategy-guide"
|
|
"linkedin-visual-style" "growth-roadmaps"
|
|
"thought-leadership-angles" "low-frequency-posting-strategy"
|
|
"url-processing-templates" "linkedin-monetization-strategies"
|
|
"troubleshooting-guide" "glossary" "ab-testing-framework"
|
|
)
|
|
|
|
for ref in "${EXPECTED_REFS[@]}"; do
|
|
f="references/${ref}.md"
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
pass "$f exists"
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 5: Skill Files ---
|
|
echo "--- Skill Files ---"
|
|
|
|
for skill in "linkedin-thought-leadership" "linkedin-content-creation" "linkedin-analytics" "linkedin-strategy" "linkedin-networking" "linkedin-voice"; do
|
|
f="skills/${skill}.md"
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
pass "$f exists"
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 6: Hook Configuration ---
|
|
echo "--- Hook Configuration ---"
|
|
|
|
HOOKS_FILE="$PLUGIN_ROOT/hooks/hooks.json"
|
|
if [ -f "$HOOKS_FILE" ]; then
|
|
pass "hooks/hooks.json exists"
|
|
# Validate JSON
|
|
if python3 -c "import json; json.load(open('$HOOKS_FILE'))" 2>/dev/null; then
|
|
pass "hooks.json is valid JSON"
|
|
else
|
|
fail "hooks.json is INVALID JSON"
|
|
fi
|
|
else
|
|
fail "hooks/hooks.json MISSING"
|
|
fi
|
|
|
|
# Check hook prompt files
|
|
for prompt in "content-quality-gate" "voice-guardian" "state-update-reminder" "post-creation-automation"; do
|
|
f="hooks/prompts/${prompt}.md"
|
|
if [ -f "$PLUGIN_ROOT/$f" ]; then
|
|
pass "$f exists"
|
|
else
|
|
fail "$f MISSING"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# --- Section 7: Plugin.json Validation ---
|
|
echo "--- Plugin.json Validation ---"
|
|
|
|
PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json"
|
|
if python3 -c "
|
|
import json, sys
|
|
with open('$PLUGIN_JSON') as f:
|
|
data = json.load(f)
|
|
required = ['name', 'version', 'auto_discover', 'description']
|
|
for field in required:
|
|
if field not in data:
|
|
print(f'Missing field: {field}')
|
|
sys.exit(1)
|
|
if data.get('auto_discover') != True:
|
|
print('auto_discover is not true')
|
|
sys.exit(1)
|
|
print(f'Version: {data[\"version\"]}')
|
|
" 2>/dev/null; then
|
|
pass "plugin.json structure valid"
|
|
else
|
|
fail "plugin.json structure invalid"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# --- Section 8: Router Completeness ---
|
|
echo "--- Router Completeness ---"
|
|
|
|
ROUTER="$PLUGIN_ROOT/commands/linkedin.md"
|
|
if [ -f "$ROUTER" ]; then
|
|
# Check that key commands are mentioned in router
|
|
for cmd in "linkedin:setup" "linkedin:post" "linkedin:quick" "linkedin:report" "linkedin:import" "linkedin:ab-test" "linkedin:collab" "linkedin:pipeline" "linkedin:batch"; do
|
|
if grep -q "$cmd" "$ROUTER"; then
|
|
pass "Router references $cmd"
|
|
else
|
|
fail "Router MISSING reference to $cmd"
|
|
fi
|
|
done
|
|
|
|
# Check that key agents are mentioned
|
|
for agent in "engagement-coach" "content-optimizer" "network-builder" "post-feedback-monitor" "personalization-scorer"; do
|
|
if grep -q "$agent" "$ROUTER"; then
|
|
pass "Router references $agent"
|
|
else
|
|
fail "Router MISSING reference to $agent"
|
|
fi
|
|
done
|
|
else
|
|
fail "Router file MISSING"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# --- Section 9: Analytics Structure ---
|
|
echo "--- Analytics Structure ---"
|
|
|
|
for d in "scripts/analytics/src" "assets/analytics"; do
|
|
if [ -d "$PLUGIN_ROOT/$d" ]; then
|
|
pass "$d/ directory exists"
|
|
else
|
|
fail "$d/ directory MISSING"
|
|
fi
|
|
done
|
|
|
|
if [ -f "$PLUGIN_ROOT/scripts/analytics/src/cli.ts" ]; then
|
|
pass "scripts/analytics/src/cli.ts exists"
|
|
else
|
|
fail "scripts/analytics/src/cli.ts MISSING"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# --- Summary ---
|
|
echo "================================================"
|
|
echo "RESULTS"
|
|
echo "================================================"
|
|
echo -e "${GREEN}Passed: $PASS${NC}"
|
|
echo -e "${RED}Failed: $FAIL${NC}"
|
|
echo -e "${YELLOW}Warnings: $WARN${NC}"
|
|
echo ""
|
|
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo -e "${GREEN}All structural checks passed!${NC}"
|
|
exit 0
|
|
else
|
|
echo -e "${RED}$FAIL check(s) failed. Review above.${NC}"
|
|
exit 1
|
|
fi
|