fix(linkedin-studio): rebuild dead structural lint to real v3.1 layout
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a61b818578
commit
2f2df94acb
1 changed files with 92 additions and 153 deletions
|
|
@ -1,16 +1,28 @@
|
|||
#!/bin/bash
|
||||
# LinkedIn Studio Plugin — Structure Validator
|
||||
# Validates file existence, frontmatter format, and router completeness
|
||||
# Validates the REAL v3.1 layout: registration counts (derived dynamically),
|
||||
# frontmatter shape, hook drift, and plugin.json fields. Counts are asserted
|
||||
# against the declared contract below, which is kept in sync with the
|
||||
# CLAUDE.md "## Agents (N)" / "## Commands (N)" headers (cross-checked here)
|
||||
# and the STATE.md "Telling" block. Adding or removing an agent, command,
|
||||
# reference, or skill breaks the count-equality and fails the lint — this is
|
||||
# the registration guard that gates the remediation plan's later steps.
|
||||
#
|
||||
# The stat-consistency grep (one magnitude per algorithm effect across the
|
||||
# tree) is added in remediation Step 3, after reconciliation makes it pass.
|
||||
# The version-consistency grep is added in Step 21.
|
||||
#
|
||||
# Usage: bash scripts/test-runner.sh
|
||||
# bash 3.2-safe: plain arrays only, no `declare -A`, no `mapfile`/`readarray`.
|
||||
|
||||
set -e
|
||||
|
||||
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$PLUGIN_ROOT"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
WARN=0
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
|
|
@ -20,6 +32,14 @@ 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)); }
|
||||
|
||||
# --- Declared registration contract (the "Telling" block) ---
|
||||
# Source of truth: CLAUDE.md headers + STATE.md Telling. Bump these together
|
||||
# with the files when adding/removing an agent, command, reference, or skill.
|
||||
EXPECT_AGENTS=19
|
||||
EXPECT_COMMANDS=26
|
||||
EXPECT_REFS=25
|
||||
EXPECT_SKILLS=6
|
||||
|
||||
echo "================================================"
|
||||
echo "LinkedIn Studio Plugin — Structure Validator"
|
||||
echo "Plugin root: $PLUGIN_ROOT"
|
||||
|
|
@ -29,8 +49,8 @@ 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
|
||||
for f in ".claude-plugin/plugin.json" "CLAUDE.md" "CHANGELOG.md" "README.md" "config/REMEMBER.template.md"; do
|
||||
if [ -f "$f" ]; then
|
||||
pass "$f exists"
|
||||
else
|
||||
fail "$f MISSING"
|
||||
|
|
@ -39,200 +59,119 @@ done
|
|||
|
||||
echo ""
|
||||
|
||||
# --- Section 2: Agent Files ---
|
||||
echo "--- Agent Files ---"
|
||||
# --- Section 2: Registration Counts (dynamic) ---
|
||||
echo "--- Registration Counts ---"
|
||||
|
||||
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"
|
||||
)
|
||||
AGENTS=$(ls agents/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
COMMANDS=$(ls commands/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
REFS=$(ls references/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
SKILLS=$(ls skills/*/SKILL.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
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
|
||||
assert_count() {
|
||||
# $1 label, $2 actual, $3 expected
|
||||
if [ "$2" -eq "$3" ]; then
|
||||
pass "$1: $2 (expected $3)"
|
||||
else
|
||||
fail "$1: $2 (expected $3) — registration drift"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_count "agents/*.md" "$AGENTS" "$EXPECT_AGENTS"
|
||||
assert_count "commands/*.md" "$COMMANDS" "$EXPECT_COMMANDS"
|
||||
assert_count "references/*.md" "$REFS" "$EXPECT_REFS"
|
||||
assert_count "skills/*/SKILL.md" "$SKILLS" "$EXPECT_SKILLS"
|
||||
|
||||
# Cross-check the CLAUDE.md declared headers against the contract (doc-drift guard)
|
||||
DOC_AGENTS=$(grep -oE '^## Agents \([0-9]+\)' CLAUDE.md | grep -oE '[0-9]+' | head -1)
|
||||
DOC_COMMANDS=$(grep -oE '^## Commands \([0-9]+\)' CLAUDE.md | grep -oE '[0-9]+' | head -1)
|
||||
if [ "$DOC_AGENTS" = "$EXPECT_AGENTS" ]; then
|
||||
pass "CLAUDE.md '## Agents ($DOC_AGENTS)' matches contract"
|
||||
else
|
||||
fail "CLAUDE.md agents header ($DOC_AGENTS) != contract ($EXPECT_AGENTS)"
|
||||
fi
|
||||
if [ "$DOC_COMMANDS" = "$EXPECT_COMMANDS" ]; then
|
||||
pass "CLAUDE.md '## Commands ($DOC_COMMANDS)' matches contract"
|
||||
else
|
||||
fail "CLAUDE.md commands header ($DOC_COMMANDS) != contract ($EXPECT_COMMANDS)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Section 3: Agent Frontmatter ---
|
||||
echo "--- Agent Frontmatter ---"
|
||||
|
||||
for f in agents/*.md; do
|
||||
if head -1 "$f" | grep -q "^---"; then
|
||||
if grep -q "^name:" "$f" && grep -q "^description:" "$f"; then
|
||||
pass "$f (frontmatter OK)"
|
||||
else
|
||||
fail "$f (no YAML frontmatter)"
|
||||
fail "$f (missing name:/description:)"
|
||||
fi
|
||||
else
|
||||
fail "$f MISSING"
|
||||
fail "$f (no YAML frontmatter)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Section 3: Command Files ---
|
||||
echo "--- Command Files ---"
|
||||
# --- Section 4: Command Frontmatter ---
|
||||
echo "--- Command Frontmatter ---"
|
||||
|
||||
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
|
||||
for f in commands/*.md; do
|
||||
if head -1 "$f" | grep -q "^---"; then
|
||||
if grep -q "^name:" "$f" && grep -q "^description:" "$f"; then
|
||||
pass "$f (frontmatter OK)"
|
||||
else
|
||||
fail "$f (no YAML frontmatter)"
|
||||
fail "$f (missing name:/description:)"
|
||||
fi
|
||||
else
|
||||
fail "$f MISSING"
|
||||
fail "$f (no YAML frontmatter)"
|
||||
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-studio" "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 ---
|
||||
# --- Section 5: Hook Configuration (drift) ---
|
||||
echo "--- Hook Configuration ---"
|
||||
|
||||
HOOKS_FILE="$PLUGIN_ROOT/hooks/hooks.json"
|
||||
if [ -f "$HOOKS_FILE" ]; then
|
||||
if [ -f "hooks/hooks.json" ]; 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"
|
||||
if python3 hooks/scripts/compile-hooks.py --check >/dev/null 2>&1; then
|
||||
pass "hooks.json matches compiled template (no drift)"
|
||||
else
|
||||
fail "hooks.json is INVALID JSON"
|
||||
fail "hooks.json DRIFT — run: python3 hooks/scripts/compile-hooks.py"
|
||||
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 ---
|
||||
# --- Section 6: 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:
|
||||
with open('.claude-plugin/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')
|
||||
required = ['name', 'version', 'description']
|
||||
missing = [field for field in required if field not in data]
|
||||
if missing:
|
||||
print('Missing fields:', missing)
|
||||
sys.exit(1)
|
||||
print(f'Version: {data[\"version\"]}')
|
||||
print('Version:', data['version'])
|
||||
" 2>/dev/null; then
|
||||
pass "plugin.json structure valid"
|
||||
pass "plugin.json structure valid (name/version/description)"
|
||||
else
|
||||
fail "plugin.json structure invalid"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Section 8: Router Completeness ---
|
||||
echo "--- Router Completeness ---"
|
||||
# --- Section 7: Analytics Source ---
|
||||
echo "--- Analytics Source ---"
|
||||
|
||||
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
|
||||
if [ -f "scripts/analytics/src/cli.ts" ]; then
|
||||
pass "scripts/analytics/src/cli.ts exists"
|
||||
else
|
||||
fail "scripts/analytics/src/cli.ts MISSING"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue