From cappy-toolkit
Full CI/CD pipeline for CAPPY ecosystem — local dev → GitLab (Sentinel gate) → GitHub prod (3 repos). Use whenever deploying binaries, plugin files, or the pattern database. Handles build, test, codesign, atomic replacement, and git fan-out. Use this skill when the user says "deploy", "release", "push to prod", "ship it", or asks to update the binary, plugin, or database.
npx claudepluginhub thelightarchitect/cappy-toolkit --plugin cappy-toolkitThis skill uses the workspace's default tool permissions.
<!-- Copyright (C) 2025-2026 Kevin Francis Tan (github.com/theLightArchitect) | SPDX-License-Identifier: AGPL-3.0-or-later -->
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Version: 3.0.0 Purpose: Full CI/CD pipeline: local dev → GitLab (test) → GitHub prod (3 repos) Author: Kevin Francis Tan Updated: 2026-03-13
Safe build-test-deploy pipeline for the CAPPY ecosystem. Supports three deployment targets that fan out from a single GitLab monorepo to three separate GitHub prod repos.
Local Dev → GitLab (test) → GitHub Prod
├── cappy-tac-toolkit (plugin)
├── cappy-mcp-server (showcase, no source)
└── cappy-cache (pattern DB)
| Argument | Description |
|---|---|
| (none) | Full pipeline: build binary, test, deploy binary |
--build-only | Build release binary only, no deploy |
--deploy-only | Deploy existing release binary (skip build/test) |
--skip-tests | Build and deploy without running tests |
--dry-run | Show what would happen without executing |
--plugin | Deploy plugin files only (no binary build) |
--db | Deploy pattern database only |
--all | Full ecosystem deploy: binary + plugin + database |
# Binary (MCP Server)
DEV_DIR=~/Desktop/Builders/mcp-cappy-dev
BINARY=$DEV_DIR/target/release/cappy-core
RUNTIME=~/.cappy/bin/cappy-core
# Plugin (TAC Toolkit) — source and cache
PLUGIN_SRC=~/Desktop/Builders/cappy-plugin
PLUGIN_VERSION=$(python3 -c "import json; d=json.load(open('$PLUGIN_SRC/.claude-plugin/plugin.json')); print(d['version'])")
PLUGIN_CACHE=~/.claude/plugins/cache/cappy-local/cappy-toolkit/$PLUGIN_VERSION
PLUGIN_CACHE_BIN=$PLUGIN_CACHE/bin/cappy-core
# Expected MCP protocol version (update when Claude Code upgrades MCP client)
EXPECTED_MCP_PROTOCOL="2025-06-18"
# Remotes
GITLAB_REMOTE=gitlab
GITHUB_REMOTE=github
# Prod repos (GitHub Enterprise)
GITHUB_TOOLKIT=git@panwgithub.paloaltonetworks.local:kevtan/cappy-tac-toolkit.git
GITHUB_MCP=git@panwgithub.paloaltonetworks.local:kevtan/cappy-mcp-server.git
GITHUB_CACHE=git@panwgithub.paloaltonetworks.local:kevtan/cappy-cache.git
cargo test passes (binary changes)cargo clippy clean — no warnings (binary changes).DS_Store files stagedname field: lowercase + hyphens, 3-50 charsmodel field: one of inherit, sonnet, opus, haikucolor field: one of blue, cyan, green, yellow, magenta, reddescription includes <example> blockssubagent_type references use namespaced format: cappy-toolkit:cappyplugin.json does NOT contain "agents" field (causes validation error)allowed-tools in agent frontmatterThese checks prevent broken MCP deployments. All must pass before any binary deploy.
Protocol version check: Binary responds with $EXPECTED_MCP_PROTOCOL
ACTUAL=$(echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"gate","version":"1.0"}},"id":1}' \
| $BINARY mcp-server 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['result']['protocolVersion'])")
[ "$ACTUAL" = "$EXPECTED_MCP_PROTOCOL" ] && echo "PASS: $ACTUAL" || echo "FAIL: got $ACTUAL, expected $EXPECTED_MCP_PROTOCOL"
Root cause of 2026-03-13 outage: Binary was built with hardcoded 2024-11-05. After Claude Code upgraded its MCP client to require 2025-06-18, the server was silently rejected. Fix: update src/mcp.rs and src/gateway/mcp_protocol.rs before rebuilding.
Cache version alignment: plugin.json version matches the versioned cache directory
PLUGIN_VER=$(python3 -c "import json; print(json.load(open('$PLUGIN_SRC/.claude-plugin/plugin.json'))['version'])")
CACHE_DIR=~/.claude/plugins/cache/cappy-local/cappy-toolkit/$PLUGIN_VER
[ -d "$CACHE_DIR" ] && echo "PASS: cache dir $CACHE_DIR exists" || echo "FAIL: $CACHE_DIR missing — create it or align plugin.json version"
Root cause of 2026-03-13 outage: Binary deployed to 2.0.0/bin/ but plugin.json said 3.0.0. Claude Code resolved ${CLAUDE_PLUGIN_ROOT} to the versioned directory from plugin.json, not the cache directory name. Fix: always deploy binary to ~/.claude/plugins/cache/cappy-local/cappy-toolkit/{plugin.json version}/bin/.
Binary presence at versioned cache path:
[ -f "$PLUGIN_CACHE_BIN" ] && echo "PASS: binary present at $PLUGIN_CACHE_BIN" || echo "FAIL: binary missing from cache"
tools/list responds: Binary must expose at least 1 tool
echo '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' | $BINARY mcp-server 2>/dev/null | python3 -c "import json,sys; r=json.load(sys.stdin); print(f'PASS: {len(r[\"result\"][\"tools\"])} tools')"
Codesign valid: codesign -v $BINARY returns exit 0
.exe, .jar, .zip, .tar, .gz, .dmg.gitignore covers bin/, target/, .env, .DS_Storecappy-mcp-server prod repo contains NO Rust sourcecappy-mcp-server prod repo contains NO binaries in gitcode.pan.run, no internal JIRA)# --- Standard pre-flight ---
ls "$DEV_DIR/Cargo.toml"
ls "$PLUGIN_SRC/.claude-plugin/plugin.json"
ps aux | grep 'cappy-core mcp-server' | grep -v grep
cd "$PLUGIN_SRC" && git status --short
# --- MCP Gate: protocol version (against current deployed binary) ---
CURRENT_PROTO=$(echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"gate","version":"1.0"}},"id":1}' \
| $RUNTIME mcp-server 2>/dev/null \
| python3 -c "import json,sys; print(json.load(sys.stdin)['result']['protocolVersion'])" 2>/dev/null)
echo "Current runtime protocol: $CURRENT_PROTO (expected: $EXPECTED_MCP_PROTOCOL)"
# --- MCP Gate: cache version alignment ---
PLUGIN_VER=$(python3 -c "import json; print(json.load(open('$PLUGIN_SRC/.claude-plugin/plugin.json'))['version'])")
PLUGIN_CACHE=~/.claude/plugins/cache/cappy-local/cappy-toolkit/$PLUGIN_VER
echo "Plugin version: $PLUGIN_VER"
echo "Cache dir exists: $([ -d $PLUGIN_CACHE ] && echo YES || echo NO — WILL NEED TO CREATE)"
echo "Cache bin exists: $([ -f $PLUGIN_CACHE/bin/cappy-core ] && echo YES || echo MISSING)"
# --- Plugin validation ---
grep -q '^name: cappy$' "$PLUGIN_SRC/agents/CAPPY.md" && echo "PASS: agent name" || echo "FAIL: agent name"
grep -q '^model: sonnet$' "$PLUGIN_SRC/agents/CAPPY.md" && echo "PASS: model" || echo "FAIL: model"
grep -q '<example>' "$PLUGIN_SRC/agents/CAPPY.md" && echo "PASS: examples" || echo "FAIL: examples"
grep -rn 'subagent_type.*"cappy",' "$PLUGIN_SRC" --include='*.md' | grep -v 'cappy-toolkit:cappy' | grep -v 'deploy' && echo "FAIL: bare subagent_type" || echo "PASS: subagent_type"
grep -q '"agents"' "$PLUGIN_SRC/.claude-plugin/plugin.json" && echo "FAIL: agents field in plugin.json" || echo "PASS: no agents field"
Report all findings. If any MCP Gate check FAILS, block the deploy and show the fix procedure from the checklist.
cd "$DEV_DIR"
cargo build --release 2>&1
Gate: Build must succeed with exit code 0.
IMPORTANT — After Build, Verify Source Protocol Version:
grep "protocolVersion" "$DEV_DIR/src/mcp.rs"
grep "MCP_VERSION" "$DEV_DIR/src/gateway/mcp_protocol.rs"
Both must show 2025-06-18. If either shows an older version, the binary will fail to connect after deploy. Fix before proceeding.
cd "$DEV_DIR"
cargo test 2>&1
cargo clippy 2>&1
Gate: All tests pass AND clippy clean. Skip with --skip-tests.
Run all MCP Gate checks against the newly built binary before it touches production:
NEW_BINARY="$DEV_DIR/target/release/cappy-core"
# Protocol version
PROTO=$(echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"gate","version":"1.0"}},"id":1}' \
| $NEW_BINARY mcp-server 2>/dev/null \
| python3 -c "import json,sys; print(json.load(sys.stdin)['result']['protocolVersion'])" 2>/dev/null)
[ "$PROTO" = "$EXPECTED_MCP_PROTOCOL" ] && echo "PASS: protocol $PROTO" || { echo "FAIL: protocol $PROTO — DO NOT DEPLOY"; exit 1; }
# Tool count
TOOLS=$(echo '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' \
| $NEW_BINARY mcp-server 2>/dev/null \
| python3 -c "import json,sys; print(len(json.load(sys.stdin)['result']['tools']))" 2>/dev/null)
echo "Tools registered: $TOOLS"
[ "${TOOLS:-0}" -gt 0 ] && echo "PASS: tools/list" || echo "FAIL: no tools registered"
If protocol check fails: abort. Do NOT deploy. Fix the version in src/mcp.rs + src/gateway/mcp_protocol.rs, rebuild, recheck.
MANDATORY. Present to user:
Ask: "Deploy to production? Confirm targets:"
$RUNTIME (atomic mv) + $PLUGIN_CACHE_BIN (versioned cache)plugin/v3.0.0 then GitHub cappy-tac-toolkit/maincappy-cache/mainIf user declines, stop.
CRITICAL: Never cp over a running binary. Always cp-to-temp + mv.
NEW_BINARY="$DEV_DIR/target/release/cappy-core"
# Codesign
codesign -s - -f "$NEW_BINARY"
codesign -v "$NEW_BINARY" && echo "PASS: signed" || { echo "FAIL: codesign failed"; exit 1; }
# Atomic deploy to runtime
cp "$NEW_BINARY" "$RUNTIME.new" && mv "$RUNTIME.new" "$RUNTIME"
# Ensure versioned cache bin directory exists
mkdir -p "$PLUGIN_CACHE/bin"
# Atomic deploy to plugin cache (VERSIONED PATH — must match plugin.json version)
cp "$NEW_BINARY" "$PLUGIN_CACHE_BIN.new" && mv "$PLUGIN_CACHE_BIN.new" "$PLUGIN_CACHE_BIN"
# Verify codesign survived deploy
codesign -v "$RUNTIME" && echo "PASS: runtime signed" || echo "FAIL: runtime signature invalid"
codesign -v "$PLUGIN_CACHE_BIN" && echo "PASS: cache binary signed" || echo "FAIL: cache binary signature invalid"
# Verify sizes match
ls -lh "$NEW_BINARY" "$RUNTIME" "$PLUGIN_CACHE_BIN"
cd "$PLUGIN_SRC"
git add -A
git status
# User reviews staged changes
git commit -m "<message>"
cd "$PLUGIN_SRC"
git push gitlab main:plugin/v3.0.0
Gate: GitLab Sentinel must pass. If secrets detected, abort. Fix and retry.
After GitLab passes:
# Sync plugin source → versioned cache (exclude bin/ and databases/ — managed separately)
rsync -av --delete \
--exclude='.git' \
--exclude='.mcp.json' \
--exclude='bin/' \
--exclude='databases/' \
--exclude='archive/' \
"$PLUGIN_SRC/" "$PLUGIN_CACHE/"
# IMPORTANT: rsync excludes bin/ — redeploy binary to cache after every sync
cp "$RUNTIME" "$PLUGIN_CACHE_BIN.new" && mv "$PLUGIN_CACHE_BIN.new" "$PLUGIN_CACHE_BIN"
echo "Binary redeployed after rsync: $(ls -lh $PLUGIN_CACHE_BIN)"
# Plugin → cappy-tac-toolkit (GitHub)
cd "$PLUGIN_SRC"
git push github main
NOTE: rsync always excludes bin/ — this is intentional (binary managed separately). After every rsync, immediately redeploy the binary to the cache. This prevents the "missing binary" class of failures.
After all deploys complete, run the full MCP reconnect verification:
# 1. Verify binary at both paths
echo "=== Binary Verification ==="
ls -lh "$RUNTIME" "$PLUGIN_CACHE_BIN"
shasum -a 256 "$RUNTIME" "$PLUGIN_CACHE_BIN"
# 2. Full MCP smoke test — runtime binary
echo "=== MCP Smoke Test (runtime) ==="
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"gate","version":"1.0"}},"id":1}' \
| "$RUNTIME" mcp-server 2>/dev/null
# 3. Full MCP smoke test — plugin cache binary
echo "=== MCP Smoke Test (plugin cache) ==="
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"gate","version":"1.0"}},"id":1}' \
| "$PLUGIN_CACHE_BIN" mcp-server 2>/dev/null
# 4. SHA256 match between both paths
SUM_RUNTIME=$(shasum -a 256 "$RUNTIME" | cut -d' ' -f1)
SUM_CACHE=$(shasum -a 256 "$PLUGIN_CACHE_BIN" | cut -d' ' -f1)
[ "$SUM_RUNTIME" = "$SUM_CACHE" ] && echo "PASS: SHA256 match" || echo "FAIL: binary mismatch"
Then instruct the user to:
/reload-plugins in Claude Code/mcp in Claude Codeplugin:cappy-toolkit:cappy shows connected (not failed)If MCP fails to reconnect:
grep protocolVersion src/mcp.rs — must be 2025-06-18ls -la ~/.claude/plugins/cache/cappy-local/cappy-toolkit/{version}/bin/codesign -v $PLUGIN_CACHE_BINxattr -l $PLUGIN_CACHE_BIN## Deploy Report
### Phase 1: Pre-flight
- Dev dir: EXISTS
- MCP runtime protocol: 2025-06-18 ✓
- Plugin version: 3.0.0
- Cache dir aligned: ~/.../3.0.0/ EXISTS ✓
- Cache binary: EXISTS ✓
- Agent frontmatter: PASS
- subagent_type consistency: PASS
- No agents field in plugin.json: PASS
### Phase 2-3: Build + Test
- Status: SUCCESS / WARNINGS: X / CLIPPY: CLEAN
- Tests: X passed, 0 failed
### Phase 4: MCP Smoke Test (new binary)
- Protocol version: 2025-06-18 PASS
- tools/list: X tools PASS
- Codesign: PASS
### Phase 6: Binary Deploy
- Runtime: ~/.cappy/bin/cappy-core (XX MB) — atomic PASS
- Plugin cache: ~/.../3.0.0/bin/cappy-core (XX MB) — atomic PASS
- SHA256: runtime == cache PASS
- Codesign (post-deploy): PASS
### Phase 7: Git Deploy
- GitLab Sentinel: PASS
- GitHub cappy-tac-toolkit: PUSHED → main
### Phase 8: MCP Reconnect
- /reload-plugins: [AWAIT USER CONFIRMATION]
- /mcp plugin:cappy-toolkit:cappy: [AWAIT USER CONFIRMATION]
| Error | Action |
|---|---|
| Protocol version mismatch | Update src/mcp.rs + src/gateway/mcp_protocol.rs to 2025-06-18, rebuild |
| Cache dir missing | mkdir -p ~/.claude/plugins/cache/cappy-local/cappy-toolkit/{version}/bin |
| Binary missing from cache | cp ~/.cappy/bin/cappy-core {cache}/bin/cappy-core (always after rsync) |
| Build fails | Abort. Show errors. Do not deploy. |
| Tests fail | Abort. Show failed tests. Do not deploy. |
| GitLab Sentinel rejects | Abort. Fix secret/file violation. |
mv fails | Report error. Check disk space. |
| SHA256 mismatch | CRITICAL: Report immediately. Do not proceed. |
| MCP fails after deploy | Run diagnostic sequence from Phase 8. |
Symptoms: /mcp showed "Failed to reconnect to plugin:cappy-toolkit:cappy"
Root Cause 1 — Protocol version: Binary hardcoded 2024-11-05 in src/mcp.rs and src/gateway/mcp_protocol.rs. Claude Code upgraded MCP client to require 2025-06-18. Server silently rejected — no error in logs, just reconnect failure.
Root Cause 2 — Cache path mismatch: Binary deployed to 2.0.0/bin/ but plugin.json said version 3.0.0. Claude Code resolves ${CLAUDE_PLUGIN_ROOT} to the versioned directory from plugin.json, not the cache directory label.
Prevention: Both issues are now covered by the MCP Gate checklist (Phase 1 and Phase 4).
Version: 3.0.0 Updated: 2026-03-13