Files
ruvnet--RuView/plugins/ruview/scripts/smoke.sh
T
ruv 8ff7c2c35a feat(plugins): RuView Claude Code + Codex marketplace plugin
Add `plugins/ruview` — an end-to-end toolkit for working with RuView
(WiFi-DensePose) from Claude Code, mirrored as Codex prompts.

Marketplace: `plugins/.claude-plugin/marketplace.json` (one plugin, `ruview`).

Skills (9): ruview-quickstart, ruview-hardware-setup, ruview-configure,
ruview-applications, ruview-model-training, ruview-advanced-sensing,
ruview-cli-api, ruview-mmwave, ruview-verify — shell-first (cargo / python /
idf.py / docker / node), no claude-flow MCP dependency.

Commands (7): /ruview-start, /ruview-flash, /ruview-provision, /ruview-app,
/ruview-train, /ruview-advanced, /ruview-verify.

Agents (3): ruview-onboarding-guide, ruview-config-engineer,
ruview-training-engineer.

Codex mirror: codex/AGENTS.md + codex/README.md + codex/prompts/*.md (full
command parity, enforced by scripts/smoke.sh).

Docs: docs/adrs/0001-ruview-plugin-contract.md (Proposed). Verification:
scripts/smoke.sh (13 structural checks). Provisioning docs reflect the full
`provision.py` flag set (TDM mesh, edge tiers, vitals, hop channels, Cognitum
Seed, swarm intervals) and the issue #391 NVS-namespace-replace gotcha.

Verified: `claude plugin validate` (plugin + marketplace), loads via
`claude --plugin-dir`, smoke 13/13, and confirmed against an attached ESP32-S3
on COM8 running the RuView CSI firmware (live adaptive_ctrl + csi_collector
serial output).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-11 17:39:16 -04:00

100 lines
4.8 KiB
Bash

#!/usr/bin/env bash
# Structural smoke test for the `ruview` Claude Code plugin.
# Run from anywhere: bash plugins/ruview/scripts/smoke.sh
set -u
# Resolve plugin root (this file lives in <root>/scripts/smoke.sh)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
MARKET="$(cd "$ROOT/.." && pwd)/.claude-plugin/marketplace.json"
PASS=0
FAIL=0
ok() { echo " PASS $1"; PASS=$((PASS+1)); }
bad() { echo " FAIL $1"; FAIL=$((FAIL+1)); }
has() { grep -q "$1" "$2" 2>/dev/null; }
echo "ruview plugin smoke test"
echo "root: $ROOT"
echo
# 1. marketplace.json exists and lists the ruview plugin
if [ -f "$MARKET" ] && has '"ruview"' "$MARKET"; then ok "marketplace.json present and lists 'ruview'"; else bad "marketplace.json missing or does not list 'ruview' ($MARKET)"; fi
# 2. plugin.json exists with required fields
PJ="$ROOT/.claude-plugin/plugin.json"
if [ -f "$PJ" ] && has '"name"' "$PJ" && has '"description"' "$PJ" && has '"version"' "$PJ"; then ok "plugin.json has name/description/version"; else bad "plugin.json missing or incomplete"; fi
# 3. plugin.json has keywords
if has '"keywords"' "$PJ"; then ok "plugin.json has keywords"; else bad "plugin.json missing keywords"; fi
# 4. plugin.json does NOT enumerate skills/commands/agents (auto-discovered)
if has '"skills"' "$PJ" || has '"commands"' "$PJ" || has '"agents"' "$PJ"; then bad "plugin.json must NOT contain skills/commands/agents arrays"; else ok "plugin.json does not enumerate skills/commands/agents"; fi
# 5. every skill has SKILL.md with name + description + allowed-tools, and no wildcard tools
SKILL_OK=1
for d in "$ROOT"/skills/*/; do
[ -d "$d" ] || continue
f="$d/SKILL.md"
if [ ! -f "$f" ]; then bad "missing $f"; SKILL_OK=0; continue; fi
has '^name:' "$f" || { bad "$f missing 'name:'"; SKILL_OK=0; }
has '^description:' "$f" || { bad "$f missing 'description:'"; SKILL_OK=0; }
has '^allowed-tools:' "$f" || { bad "$f missing 'allowed-tools:'"; SKILL_OK=0; }
if grep -E '^allowed-tools:.*(\*|\ball tools\b)' "$f" >/dev/null 2>&1; then bad "$f uses wildcard tools"; SKILL_OK=0; fi
done
[ "$SKILL_OK" = 1 ] && ok "all skills have valid frontmatter, no wildcard tools"
# 6. expected skills present
EXPECTED_SKILLS="ruview-quickstart ruview-hardware-setup ruview-configure ruview-applications ruview-model-training ruview-advanced-sensing ruview-cli-api ruview-mmwave ruview-verify"
SKILLS_PRESENT=1
for s in $EXPECTED_SKILLS; do
[ -f "$ROOT/skills/$s/SKILL.md" ] || { bad "expected skill missing: $s"; SKILLS_PRESENT=0; }
done
[ "$SKILLS_PRESENT" = 1 ] && ok "expected skill set present ($(echo $EXPECTED_SKILLS | wc -w) skills)"
# 7. every command has a description in frontmatter
CMD_OK=1
for f in "$ROOT"/commands/*.md; do
[ -f "$f" ] || { bad "no command files found"; CMD_OK=0; break; }
has '^description:' "$f" || { bad "$f missing 'description:'"; CMD_OK=0; }
done
[ "$CMD_OK" = 1 ] && ok "all commands have a description"
# 8. every agent has name + description + model
AG_OK=1
for f in "$ROOT"/agents/*.md; do
[ -f "$f" ] || { bad "no agent files found"; AG_OK=0; break; }
has '^name:' "$f" || { bad "$f missing 'name:'"; AG_OK=0; }
has '^description:' "$f" || { bad "$f missing 'description:'"; AG_OK=0; }
has '^model:' "$f" || { bad "$f missing 'model:'"; AG_OK=0; }
done
[ "$AG_OK" = 1 ] && ok "all agents have name/description/model"
# 9. README has Compatibility + Namespace coordination
RM="$ROOT/README.md"
if has '## Compatibility' "$RM" && has 'Namespace coordination' "$RM"; then ok "README has Compatibility + Namespace coordination"; else bad "README missing Compatibility or Namespace coordination section"; fi
# 10. ADR-0001 exists with Status: Proposed
ADR="$ROOT/docs/adrs/0001-ruview-plugin-contract.md"
if [ -f "$ADR" ] && grep -qi 'Status:.*Proposed' "$ADR"; then ok "ADR-0001 present with Status: Proposed"; else bad "ADR-0001 missing or not 'Proposed'"; fi
# 11. Codex mirror present
if [ -f "$ROOT/codex/AGENTS.md" ] && ls "$ROOT"/codex/prompts/*.md >/dev/null 2>&1; then ok "Codex mirror present (AGENTS.md + prompts/)"; else bad "Codex mirror missing"; fi
# 11b. command <-> Codex prompt parity
PARITY=1
for f in "$ROOT"/commands/*.md; do
[ -f "$f" ] || continue
base="$(basename "$f")"
[ -f "$ROOT/codex/prompts/$base" ] || { bad "no Codex prompt for command $base"; PARITY=0; }
done
[ "$PARITY" = 1 ] && ok "every command has a matching Codex prompt"
# 12. no skills/commands/agents accidentally placed inside .claude-plugin/
if ls "$ROOT"/.claude-plugin/skills "$ROOT"/.claude-plugin/commands "$ROOT"/.claude-plugin/agents >/dev/null 2>&1; then bad "skills/commands/agents must not live under .claude-plugin/"; else ok ".claude-plugin/ contains only plugin.json"; fi
echo
echo "----------------------------------------"
echo "PASS: $PASS FAIL: $FAIL"
[ "$FAIL" -eq 0 ] || exit 1