Skip to content

bump to v2.1.24

bump to v2.1.24 #348

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Build
run: go build ./...
- name: Vet
run: go vet ./...
- name: Test
run: go test ./... -v -race -count=1
- name: Check formatting
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "Files not formatted:"
echo "$unformatted"
exit 1
fi
hook-smoke-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# The devkit-guard shell wrappers exec the Go engine binary.
# Provide Go so hooks_test.sh can build it on first run (the
# script auto-builds if $CLAUDE_PLUGIN_ROOT/bin/devkit-engine is
# missing) — without this, every "expected exit 2" fixture would
# silently fall through the no-binary path and fail.
- uses: actions/setup-go@v5
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Run hook smoke tests
run: bash hooks/hooks_test.sh
# Catches the class of bug where the shipped marketplace tree is
# missing something the plugin needs at runtime. PR #52 shipped a
# plugin.json pointing at bin/devkit, but bin/ was gitignored — the
# plugin installed but the MCP server silently failed to start.
# This job runs in a clean dir with no local build artifacts.
fresh-install-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Bootstrap wrapper must exist and be executable
run: |
test -f bin/devkit || { echo "FATAL: bin/devkit wrapper missing from repo"; exit 1; }
test -x bin/devkit || { echo "FATAL: bin/devkit not executable"; exit 1; }
- name: Wrapper must not contain devkit-engine binary (that is gitignored)
run: |
if [ -e bin/devkit-engine ]; then
echo "FATAL: bin/devkit-engine should not be tracked in git (it is the cached binary)"
exit 1
fi
- name: plugin.json mcpServers must point at MCPB bundle
run: |
ref=$(jq -r '.mcpServers' .claude-plugin/plugin.json)
expected='./devkit.mcpb'
if [ "$ref" != "$expected" ]; then
echo "FATAL: plugin.json mcpServers is '$ref', expected '$expected'"
exit 1
fi
- name: MCPB bundle must exist and contain launcher stubs
run: |
test -f devkit.mcpb || { echo "FATAL: devkit.mcpb bundle missing from repo root"; exit 1; }
entries=$(unzip -Z1 devkit.mcpb)
for required in manifest.json server/devkit server/devkit.exe; do
if ! printf '%s\n' "$entries" | grep -qx "$required"; then
echo "FATAL: devkit.mcpb missing required entry '$required'"
printf 'bundle contents:\n%s\n' "$entries"
exit 1
fi
done
unzip -p devkit.mcpb manifest.json | jq -e \
'.server.mcp_config.platform_overrides.win32.command == "${__dirname}/server/devkit.exe"' \
> /dev/null || {
echo "FATAL: mcpb manifest.json platform_overrides.win32.command is not the expected Windows launcher"
exit 1
}
- name: Bundled server/devkit.exe must be a real PE binary
run: |
magic=$(unzip -p devkit.mcpb server/devkit.exe | head -c 2 | xxd -p)
if [ "$magic" != "4d5a" ]; then
echo "FATAL: server/devkit.exe in bundle is not a PE binary (MZ header missing; got '$magic')"
echo "Did a probe stub or wrong-architecture binary get committed?"
exit 1
fi
- name: Bundled server/devkit must have a shell shebang
run: |
first=$(unzip -p devkit.mcpb server/devkit | head -c 2)
if [ "$first" != "#!" ]; then
echo "FATAL: server/devkit in bundle has no shebang; got '$first'"
exit 1
fi
- name: shellcheck wrappers
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
shellcheck bin/devkit bin/mcpb-build mcpb/server/devkit
- name: Wrapper --help must exit with a download attempt (no local engine)
run: |
# With no local bin/devkit-engine and no network cache, the wrapper
# should try to download and either succeed or fail loudly. It must
# NOT silently pass with exit 0 and no output.
set +e
output=$(./bin/devkit --version 2>&1)
exit_code=$?
set -e
echo "wrapper exit=$exit_code"
echo "wrapper output: $output"
# The wrapper must either succeed (downloaded + ran --version) or
# emit a clear error. An empty output with exit 0 would be the silent
# failure class we're guarding against.
if [ $exit_code -eq 0 ] && [ -z "$output" ]; then
echo "FATAL: wrapper exited 0 with no output — silent failure class bug"
exit 1
fi
mcpb-launcher-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: mcpb/launcher
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: mcpb/launcher/go.mod
- name: Vet
run: go vet ./...
- name: Test
run: go test ./... -v -count=1
- name: Check formatting
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "Files not formatted:"
echo "$unformatted"
exit 1
fi
# Guards against the class of bug where mcpb/launcher/main.go,
# mcpb/manifest.json, or mcpb/server/devkit are edited but devkit.mcpb is
# not rebuilt — CI would otherwise greenlight a shipped bundle with stale
# runtime behavior.
#
# devkit.mcpb.sources.json is a sidecar manifest that records the sha256
# of each source file at bundle-build time. This job re-computes each
# hash and compares. We don't byte-compare the cross-compiled .exe
# because Go cross-builds aren't byte-identical across host OSes even
# with -trimpath (linker build ID leaks host state) — the sidecar is
# the portable equivalent.
mcpb-bundle-integrity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sources sidecar must exist
run: |
test -f devkit.mcpb.sources.json || {
echo "FATAL: devkit.mcpb.sources.json is missing; did you rebuild the bundle with bin/mcpb-build?"
exit 1
}
- name: Source file hashes must match sidecar
run: |
set -eo pipefail
stale=0
# Read the tracked file list from the sidecar itself so there's
# one source of truth — bin/mcpb-build defines what's tracked,
# CI re-verifies each entry.
files=$(jq -r 'keys[]' devkit.mcpb.sources.json)
if [ -z "$files" ]; then
echo "FATAL: devkit.mcpb.sources.json is empty or invalid"
exit 1
fi
while IFS= read -r file; do
want=$(jq -r --arg f "$file" '.[$f]' devkit.mcpb.sources.json)
if [ ! -f "$file" ]; then
echo "FATAL: sidecar references $file but the file is missing"
stale=1
continue
fi
have=$(sha256sum "$file" | awk '{print $1}')
if [ "$want" != "$have" ]; then
echo "FATAL: $file has been edited since devkit.mcpb was built"
echo " sidecar sha256: $want"
echo " current sha256: $have"
stale=1
fi
done <<< "$files"
if [ "$stale" != "0" ]; then
echo ""
echo "Rebuild with: bin/mcpb-build"
exit 1
fi
- name: Bundled manifest.json must match source
run: |
unzip -p devkit.mcpb manifest.json > /tmp/bundled-manifest.json
if ! diff -u mcpb/manifest.json /tmp/bundled-manifest.json; then
echo "FATAL: devkit.mcpb manifest.json has drifted from mcpb/manifest.json"
exit 1
fi
- name: Bundled server/devkit must match source
run: |
unzip -p devkit.mcpb server/devkit > /tmp/bundled-proxy.sh
if ! diff -u mcpb/server/devkit /tmp/bundled-proxy.sh; then
echo "FATAL: devkit.mcpb server/devkit has drifted from mcpb/server/devkit"
exit 1
fi
validate-counts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate README and ROADMAP counts
run: |
set -uo pipefail
ERRORS=""
CHECKS=0
# Verify directories exist
for dir in commands skills workflows hooks; do
if [ ! -d "$dir" ]; then
echo "FATAL: $dir/ directory missing"
exit 1
fi
done
# Count actual files
CMD_COUNT=$(set -- commands/*.md; echo $#)
SKILL_COUNT=$(find skills -maxdepth 2 -name 'SKILL.md' | wc -l | tr -d ' ')
WORKFLOW_COUNT=$(set -- workflows/*.yml; echo $#)
HOOK_COUNT=$(python3 -c "
import sys, json
data = json.load(open('hooks/hooks.json'))
scripts = set()
for event in data.get('hooks', {}).values():
for entry in event:
for h in entry.get('hooks', []):
cmd = h.get('command', '')
scripts.add(cmd.split('/')[-1])
print(len(scripts))
") || { echo "FATAL: failed to parse hooks/hooks.json"; exit 1; }
# Validate counts are non-zero
for var_name in CMD_COUNT SKILL_COUNT WORKFLOW_COUNT HOOK_COUNT; do
val=$(eval echo \$$var_name)
if [ -z "$val" ] || [ "$val" = "0" ]; then
echo "FATAL: $var_name is empty or zero ($val)"
exit 1
fi
done
echo "Actual counts: commands=$CMD_COUNT skills=$SKILL_COUNT workflows=$WORKFLOW_COUNT hooks=$HOOK_COUNT"
# Check README and ROADMAP
for file in README.md ROADMAP.md; do
if [ ! -f "$file" ]; then continue; fi
# Commands
if grep -qE "\*\*[0-9]+ slash commands\*\*" "$file"; then
DOC_CMD=$(grep -m1 -oE '\*\*[0-9]+ slash commands\*\*' "$file" | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$DOC_CMD" != "$CMD_COUNT" ]; then
ERRORS="$ERRORS\n$file: says $DOC_CMD commands, actual is $CMD_COUNT"
fi
fi
# Skills
if grep -qE "\*\*[0-9]+ context-activated skills\*\*" "$file"; then
DOC_SKILL=$(grep -m1 -oE '\*\*[0-9]+ context-activated skills\*\*' "$file" | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$DOC_SKILL" != "$SKILL_COUNT" ]; then
ERRORS="$ERRORS\n$file: says $DOC_SKILL skills, actual is $SKILL_COUNT"
fi
fi
# Workflows
if grep -qE "\*\*[0-9]+ YAML workflows\*\*" "$file"; then
DOC_WF=$(grep -m1 -oE '\*\*[0-9]+ YAML workflows\*\*' "$file" | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$DOC_WF" != "$WORKFLOW_COUNT" ]; then
ERRORS="$ERRORS\n$file: says $DOC_WF workflows, actual is $WORKFLOW_COUNT"
fi
fi
# Hooks
if grep -qE "\*\*[0-9]+ hooks\*\*" "$file"; then
DOC_HOOK=$(grep -m1 -oE '\*\*[0-9]+ hooks\*\*' "$file" | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$DOC_HOOK" != "$HOOK_COUNT" ]; then
ERRORS="$ERRORS\n$file: says $DOC_HOOK hooks, actual is $HOOK_COUNT"
fi
fi
done
# Check README repo structure comments
if grep -qE '# [0-9]+ slash commands' README.md; then
TREE_CMD=$(grep -m1 -oE '# [0-9]+ slash commands' README.md | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$TREE_CMD" != "$CMD_COUNT" ]; then
ERRORS="$ERRORS\nREADME.md tree: says $TREE_CMD commands, actual is $CMD_COUNT"
fi
fi
if grep -qE '# [0-9]+ context-activated skills' README.md; then
TREE_SKILL=$(grep -m1 -oE '# [0-9]+ context-activated skills' README.md | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$TREE_SKILL" != "$SKILL_COUNT" ]; then
ERRORS="$ERRORS\nREADME.md tree: says $TREE_SKILL skills, actual is $SKILL_COUNT"
fi
fi
if grep -qE '# [0-9]+ YAML workflow' README.md; then
TREE_WF=$(grep -m1 -oE '# [0-9]+ YAML workflow' README.md | grep -oE '[0-9]+' | head -1)
CHECKS=$((CHECKS + 1))
if [ "$TREE_WF" != "$WORKFLOW_COUNT" ]; then
ERRORS="$ERRORS\nREADME.md tree: says $TREE_WF workflows, actual is $WORKFLOW_COUNT"
fi
fi
# Ensure at least some counts were validated
if [ "$CHECKS" -lt 1 ]; then
echo "FATAL: no count patterns found in README.md or ROADMAP.md — expected at least one"
exit 1
fi
if [ -n "$ERRORS" ]; then
echo ""
echo "COUNT MISMATCHES FOUND:"
printf '%b\n' "$ERRORS"
echo ""
echo "Fix the counts in README.md and/or ROADMAP.md to match actual file counts."
exit 1
else
echo "All counts match ($CHECKS checks passed)."
fi