From afe7f9b416f5201c89fcc4f26c525f90fe661883 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 31 Jan 2026 09:57:13 +0900 Subject: [PATCH 1/5] refactor(me): move docker-sandbox skill from root to me plugin The docker-sandbox skill was previously located in /skills/docker-sandbox, which is not a valid location for Claude to recognize skills. Skills must be located within a plugin directory (plugins/{name}/skills/). This commit moves the docker-sandbox skill into the me plugin so it can be properly discovered and used by Claude Code. Co-Authored-By: Claude --- .../me/skills}/docker-sandbox/SKILL.md | 0 .../me/skills}/docker-sandbox/docker/.gitkeep | 0 .../skills}/docker-sandbox/docker/Dockerfile | 0 .../docker-sandbox/scripts/run-docker-test.sh | 0 .../me/skills/docker-sandbox/tests}/.gitkeep | 0 .../tests/example-command-test.yaml | 0 .../tests/example-hook-test.yaml | 0 .../tests/example-skill-test.yaml | 0 .../scripts/lib/docker-helpers.sh | 100 -------------- .../scripts/lib/test-helpers.sh | 126 ------------------ skills/docker-sandbox/tests/.gitkeep | 0 11 files changed, 226 deletions(-) rename {skills => plugins/me/skills}/docker-sandbox/SKILL.md (100%) rename {skills => plugins/me/skills}/docker-sandbox/docker/.gitkeep (100%) rename {skills => plugins/me/skills}/docker-sandbox/docker/Dockerfile (100%) rename {skills => plugins/me/skills}/docker-sandbox/scripts/run-docker-test.sh (100%) rename {skills/docker-sandbox/scripts/lib => plugins/me/skills/docker-sandbox/tests}/.gitkeep (100%) rename {skills => plugins/me/skills}/docker-sandbox/tests/example-command-test.yaml (100%) rename {skills => plugins/me/skills}/docker-sandbox/tests/example-hook-test.yaml (100%) rename {skills => plugins/me/skills}/docker-sandbox/tests/example-skill-test.yaml (100%) delete mode 100755 skills/docker-sandbox/scripts/lib/docker-helpers.sh delete mode 100755 skills/docker-sandbox/scripts/lib/test-helpers.sh delete mode 100644 skills/docker-sandbox/tests/.gitkeep diff --git a/skills/docker-sandbox/SKILL.md b/plugins/me/skills/docker-sandbox/SKILL.md similarity index 100% rename from skills/docker-sandbox/SKILL.md rename to plugins/me/skills/docker-sandbox/SKILL.md diff --git a/skills/docker-sandbox/docker/.gitkeep b/plugins/me/skills/docker-sandbox/docker/.gitkeep similarity index 100% rename from skills/docker-sandbox/docker/.gitkeep rename to plugins/me/skills/docker-sandbox/docker/.gitkeep diff --git a/skills/docker-sandbox/docker/Dockerfile b/plugins/me/skills/docker-sandbox/docker/Dockerfile similarity index 100% rename from skills/docker-sandbox/docker/Dockerfile rename to plugins/me/skills/docker-sandbox/docker/Dockerfile diff --git a/skills/docker-sandbox/scripts/run-docker-test.sh b/plugins/me/skills/docker-sandbox/scripts/run-docker-test.sh similarity index 100% rename from skills/docker-sandbox/scripts/run-docker-test.sh rename to plugins/me/skills/docker-sandbox/scripts/run-docker-test.sh diff --git a/skills/docker-sandbox/scripts/lib/.gitkeep b/plugins/me/skills/docker-sandbox/tests/.gitkeep similarity index 100% rename from skills/docker-sandbox/scripts/lib/.gitkeep rename to plugins/me/skills/docker-sandbox/tests/.gitkeep diff --git a/skills/docker-sandbox/tests/example-command-test.yaml b/plugins/me/skills/docker-sandbox/tests/example-command-test.yaml similarity index 100% rename from skills/docker-sandbox/tests/example-command-test.yaml rename to plugins/me/skills/docker-sandbox/tests/example-command-test.yaml diff --git a/skills/docker-sandbox/tests/example-hook-test.yaml b/plugins/me/skills/docker-sandbox/tests/example-hook-test.yaml similarity index 100% rename from skills/docker-sandbox/tests/example-hook-test.yaml rename to plugins/me/skills/docker-sandbox/tests/example-hook-test.yaml diff --git a/skills/docker-sandbox/tests/example-skill-test.yaml b/plugins/me/skills/docker-sandbox/tests/example-skill-test.yaml similarity index 100% rename from skills/docker-sandbox/tests/example-skill-test.yaml rename to plugins/me/skills/docker-sandbox/tests/example-skill-test.yaml diff --git a/skills/docker-sandbox/scripts/lib/docker-helpers.sh b/skills/docker-sandbox/scripts/lib/docker-helpers.sh deleted file mode 100755 index 875405f0..00000000 --- a/skills/docker-sandbox/scripts/lib/docker-helpers.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -# docker-helpers.sh: Docker lifecycle management functions for Claude Code testing -set -euo pipefail - -# Check Docker availability and daemon -check_docker_available() { - command -v docker &>/dev/null && docker ps &>/dev/null -} - -# Build Claude Code test image -build_claude_image() { - local image_name="${1:-claude-test}" - local docker_context="${2:-./docker}" - echo "Building Docker image: $image_name from $docker_context" - docker build -t "$image_name" "$docker_context" -} - -# Create and start container -create_container() { - local container_name="$1" - local image_name="$2" - local oauth_token="$3" - local workspace="${4:-$(pwd)}" - - if [ -z "$oauth_token" ] || [ "$oauth_token" = "null" ]; then - echo "Error: OAuth token is empty" >&2 - return 1 - fi - - docker run -d \ - --name "$container_name" \ - -e CLAUDE_CODE_OAUTH_TOKEN="$oauth_token" \ - -v "$workspace:/workspace" \ - -w /workspace \ - "$image_name" \ - sleep infinity -} - -# Execute command in container (output to stdout) -exec_in_container() { - local container_name="$1" - shift - docker exec "$container_name" "$@" -} - -# Execute command in container (capture output) -exec_in_container_capture() { - local container_name="$1" - shift - docker exec "$container_name" "$@" 2>&1 -} - -# Wait for Claude Code CLI to be ready -wait_for_claude_ready() { - local container_name="$1" - local timeout="${2:-30}" - local elapsed=0 - - echo "Waiting for Claude Code to be ready (timeout: ${timeout}s)..." >&2 - - while [ $elapsed -lt $timeout ]; do - if docker exec "$container_name" bash -c "command -v claude" >/dev/null 2>&1; then - echo "Claude Code is ready" >&2 - return 0 - fi - sleep 1 - elapsed=$((elapsed + 1)) - done - - echo "Timeout waiting for Claude Code in container" >&2 - return 1 -} - -# Get container logs -get_container_logs() { - local container_name="$1" - docker logs "$container_name" 2>&1 -} - -# Cleanup container -cleanup_container() { - local container_name="$1" - if container_exists "$container_name"; then - echo "Cleaning up container: $container_name" >&2 - docker stop "$container_name" &>/dev/null || true - docker rm "$container_name" &>/dev/null || true - fi -} - -# Check if container exists -container_exists() { - local container_name="$1" - docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$" -} - -# Check if container is running -container_running() { - local container_name="$1" - docker ps --format '{{.Names}}' | grep -q "^${container_name}$" -} diff --git a/skills/docker-sandbox/scripts/lib/test-helpers.sh b/skills/docker-sandbox/scripts/lib/test-helpers.sh deleted file mode 100755 index 56075e70..00000000 --- a/skills/docker-sandbox/scripts/lib/test-helpers.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -# test-helpers.sh: Test execution and verification functions -set -euo pipefail - -# Global variables for test data -TEST_NAME="" -TEST_TARGET_TYPE="" -TEST_TARGET_NAME="" -TEST_PROMPT="" -TEST_TIMEOUT=60 -TEST_EXPECTED_CONTAINS=() - -# Load YAML test definition -load_test_definition() { - local test_file="$1" - - if [ ! -f "$test_file" ]; then - echo "Error: Test file not found: $test_file" >&2 - return 1 - fi - - # Parse YAML using grep/sed - TEST_NAME=$(grep '^name:' "$test_file" | sed 's/name: *//; s/"//g; s/\r//') - TEST_TARGET_TYPE=$(grep -A2 '^test_target:' "$test_file" | grep 'type:' | sed 's/.*type: *//; s/\r//') - TEST_TARGET_NAME=$(grep -A2 '^test_target:' "$test_file" | grep 'name:' | sed 's/.*name: *//; s/\r//') - # shellcheck disable=SC2034 # Used by run-docker-test.sh - TEST_PROMPT=$(grep -A1 '^input:' "$test_file" | grep 'prompt:' | sed 's/.*prompt: *//; s/"//g; s/\r//') - TEST_TIMEOUT=$(grep '^timeout:' "$test_file" | awk '{print $2}') - TEST_TIMEOUT=${TEST_TIMEOUT:-60} - - # Parse expected_output.contains (multi-line) - local in_contains=0 - while IFS= read -r line; do - if echo "$line" | grep -q 'expected_output:'; then - in_contains=1 - continue - fi - if [ "$in_contains" = "1" ]; then - if echo "$line" | grep -q 'contains:'; then - continue - fi - if echo "$line" | grep -qE '^\s+-\s+(.*)'; then - local value - value=$(echo "$line" | sed 's/^\s*-\s*//; s/"//g; s/\r//') - if [ -n "$value" ]; then - TEST_EXPECTED_CONTAINS+=("$value") - fi - elif echo "$line" | grep -qE '^\s+[a-z_]+:'; then - # New key, stop parsing contains - break - fi - fi - done < "$test_file" - - echo "Loaded test: $TEST_NAME" >&2 - echo " Target: $TEST_TARGET_TYPE/$TEST_TARGET_NAME" >&2 - echo " Timeout: ${TEST_TIMEOUT}s" >&2 -} - -# Verify output contains expected strings -verify_contains() { - local output="$1" - shift - local expected_strings=("$@") - local failed=0 - - for str in "${expected_strings[@]}"; do - if ! echo "$output" | grep -qF "$str"; then - echo " Expected to contain: $str" >&2 - failed=1 - else - echo " Found: $str" >&2 - fi - done - - return $failed -} - -# Verify file exists in container -verify_file_exists() { - local container_name="$1" - local file_path="$2" - - docker exec "$container_name" test -f "$file_path" -} - -# Verify file contains content -verify_file_contains() { - local container_name="$1" - local file_path="$2" - local expected_content="$3" - - local output - output=$(docker exec "$container_name" cat "$file_path" 2>/dev/null || echo "") - echo "$output" | grep -qF "$expected_content" -} - -# Measure execution time -measure_time() { - local start_time - start_time=$(date +%s) - "$@" - local end_time - end_time=$(date +%s) - echo $((end_time - start_time)) -} - -# Report test result -report_result() { - local test_name="$1" - local status="$2" - local duration="${3:-0}" - - if [ "$status" = "pass" ]; then - echo "PASS: $test_name (${duration}s)" - else - echo "FAIL: $test_name (${duration}s)" - fi -} - -# Skip test with message -skip() { - local message="$1" - echo "SKIP: $message" >&2 - exit 0 -} diff --git a/skills/docker-sandbox/tests/.gitkeep b/skills/docker-sandbox/tests/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 8143c645b115a8de292b60613f5d44433ab854ef Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 31 Jan 2026 09:58:49 +0900 Subject: [PATCH 2/5] refactor(me): rename docker-sandbox to claude-isolated-test Rename the skill to better reflect its purpose: testing Claude Code components in isolated Docker environments. The new name emphasizes the goal (isolated testing) rather than the implementation (Docker). Co-Authored-By: Claude --- .../skills/{docker-sandbox => claude-isolated-test}/SKILL.md | 4 ++-- .../{docker-sandbox => claude-isolated-test}/docker/.gitkeep | 0 .../docker/Dockerfile | 0 .../scripts/run-docker-test.sh | 0 .../{docker-sandbox => claude-isolated-test}/tests/.gitkeep | 0 .../tests/example-command-test.yaml | 0 .../tests/example-hook-test.yaml | 0 .../tests/example-skill-test.yaml | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/SKILL.md (99%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/docker/.gitkeep (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/docker/Dockerfile (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/scripts/run-docker-test.sh (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/tests/.gitkeep (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/tests/example-command-test.yaml (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/tests/example-hook-test.yaml (100%) rename plugins/me/skills/{docker-sandbox => claude-isolated-test}/tests/example-skill-test.yaml (100%) diff --git a/plugins/me/skills/docker-sandbox/SKILL.md b/plugins/me/skills/claude-isolated-test/SKILL.md similarity index 99% rename from plugins/me/skills/docker-sandbox/SKILL.md rename to plugins/me/skills/claude-isolated-test/SKILL.md index de1443bf..9d5ff475 100644 --- a/plugins/me/skills/docker-sandbox/SKILL.md +++ b/plugins/me/skills/claude-isolated-test/SKILL.md @@ -1,9 +1,9 @@ --- -name: docker-sandbox +name: claude-isolated-test description: Use when testing Claude Code components (SKILLs, Commands, Hooks) in isolated Docker environments, needing reproducible test conditions, or testing without polluting local environment --- -# Docker Sandbox Testing +# Claude Isolated Testing ## Overview diff --git a/plugins/me/skills/docker-sandbox/docker/.gitkeep b/plugins/me/skills/claude-isolated-test/docker/.gitkeep similarity index 100% rename from plugins/me/skills/docker-sandbox/docker/.gitkeep rename to plugins/me/skills/claude-isolated-test/docker/.gitkeep diff --git a/plugins/me/skills/docker-sandbox/docker/Dockerfile b/plugins/me/skills/claude-isolated-test/docker/Dockerfile similarity index 100% rename from plugins/me/skills/docker-sandbox/docker/Dockerfile rename to plugins/me/skills/claude-isolated-test/docker/Dockerfile diff --git a/plugins/me/skills/docker-sandbox/scripts/run-docker-test.sh b/plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh similarity index 100% rename from plugins/me/skills/docker-sandbox/scripts/run-docker-test.sh rename to plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh diff --git a/plugins/me/skills/docker-sandbox/tests/.gitkeep b/plugins/me/skills/claude-isolated-test/tests/.gitkeep similarity index 100% rename from plugins/me/skills/docker-sandbox/tests/.gitkeep rename to plugins/me/skills/claude-isolated-test/tests/.gitkeep diff --git a/plugins/me/skills/docker-sandbox/tests/example-command-test.yaml b/plugins/me/skills/claude-isolated-test/tests/example-command-test.yaml similarity index 100% rename from plugins/me/skills/docker-sandbox/tests/example-command-test.yaml rename to plugins/me/skills/claude-isolated-test/tests/example-command-test.yaml diff --git a/plugins/me/skills/docker-sandbox/tests/example-hook-test.yaml b/plugins/me/skills/claude-isolated-test/tests/example-hook-test.yaml similarity index 100% rename from plugins/me/skills/docker-sandbox/tests/example-hook-test.yaml rename to plugins/me/skills/claude-isolated-test/tests/example-hook-test.yaml diff --git a/plugins/me/skills/docker-sandbox/tests/example-skill-test.yaml b/plugins/me/skills/claude-isolated-test/tests/example-skill-test.yaml similarity index 100% rename from plugins/me/skills/docker-sandbox/tests/example-skill-test.yaml rename to plugins/me/skills/claude-isolated-test/tests/example-skill-test.yaml From 02da1ca31af2320c56d112d3847a84fd173892a4 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 31 Jan 2026 09:59:35 +0900 Subject: [PATCH 3/5] fix(claude-isolated-test): update remaining docker-sandbox references - Fix comparison table header in SKILL.md - Fix hardcoded path in run-docker-test.sh (use relative path) Co-Authored-By: Claude --- plugins/me/skills/claude-isolated-test/SKILL.md | 2 +- .../me/skills/claude-isolated-test/scripts/run-docker-test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/me/skills/claude-isolated-test/SKILL.md b/plugins/me/skills/claude-isolated-test/SKILL.md index 9d5ff475..d691a5c1 100644 --- a/plugins/me/skills/claude-isolated-test/SKILL.md +++ b/plugins/me/skills/claude-isolated-test/SKILL.md @@ -418,7 +418,7 @@ ENTRYPOINT ["/usr/bin/env", "bash"] ## Comparison with tmux-testing -| Feature | docker-sandbox | tmux-testing | +| Feature | claude-isolated-test | tmux-testing | |---------|----------------|--------------| | **Isolation** | Full OS isolation | Process isolation | | **TTY** | No (unless combined) | Yes | diff --git a/plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh b/plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh index ece73e2a..fa2dbb20 100755 --- a/plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh +++ b/plugins/me/skills/claude-isolated-test/scripts/run-docker-test.sh @@ -61,7 +61,7 @@ fi # 2. Check if image exists, build if needed if ! docker image inspect "$IMAGE_NAME" &>/dev/null; then echo "Image not found. Building $IMAGE_NAME..." >&2 - build_claude_image "$IMAGE_NAME" "$PROJECT_ROOT/skills/docker-sandbox/docker" + build_claude_image "$IMAGE_NAME" "$SCRIPT_DIR/../docker" fi # 3. Get OAuth token from Keychain (macOS) or environment From 42836b3ec4f16aa9b6069d821400f5efd42a29f2 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 31 Jan 2026 10:03:06 +0900 Subject: [PATCH 4/5] fix(tests): update test file for claude-isolated-test rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename test file: docker-sandbox.bats → claude-isolated-test.bats - Update path: skills/docker-sandbox → plugins/me/skills/claude-isolated-test - Update variable: DOCKER_SANDBOX_DIR → CLAUDE_ISOLATED_TEST_DIR Co-Authored-By: Claude --- ...sandbox.bats => claude-isolated-test.bats} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename tests/{docker-sandbox.bats => claude-isolated-test.bats} (79%) diff --git a/tests/docker-sandbox.bats b/tests/claude-isolated-test.bats similarity index 79% rename from tests/docker-sandbox.bats rename to tests/claude-isolated-test.bats index 44a25f08..a838a642 100755 --- a/tests/docker-sandbox.bats +++ b/tests/claude-isolated-test.bats @@ -1,12 +1,12 @@ #!/usr/bin/env bats -# docker-sandbox.bats: Tests for docker-sandbox skill +# claude-isolated-test.bats: Tests for claude-isolated-test skill load 'helpers/bats_helper.bash' setup() { # Source the helper libraries - DOCKER_SANDBOX_DIR="${BATS_TEST_DIRNAME}/../skills/docker-sandbox" - export DOCKER_SANDBOX_DIR + CLAUDE_ISOLATED_TEST_DIR="${BATS_TEST_DIRNAME}/../plugins/me/skills/claude-isolated-test" + export CLAUDE_ISOLATED_TEST_DIR } teardown() { @@ -20,7 +20,7 @@ teardown() { skip "Docker not available" fi - source "$DOCKER_SANDBOX_DIR/scripts/lib/docker-helpers.sh" + source "$CLAUDE_ISOLATED_TEST_DIR/scripts/lib/docker-helpers.sh" run check_docker_available [ "$status" -eq 0 ] @@ -34,7 +34,7 @@ teardown() { } export -f docker - source "$DOCKER_SANDBOX_DIR/scripts/lib/docker-helpers.sh" + source "$CLAUDE_ISOLATED_TEST_DIR/scripts/lib/docker-helpers.sh" run check_docker_available [ "$status" -ne 0 ] @@ -48,7 +48,7 @@ teardown() { skip "Docker not available" fi - source "$DOCKER_SANDBOX_DIR/scripts/lib/docker-helpers.sh" + source "$CLAUDE_ISOLATED_TEST_DIR/scripts/lib/docker-helpers.sh" local container_name="claude-test-bats-$$-$RANDOM" local test_token="test-token-12345" @@ -72,7 +72,7 @@ teardown() { } @test "test-helpers.sh: verify_contains checks all expected strings" { - source "$DOCKER_SANDBOX_DIR/scripts/lib/test-helpers.sh" + source "$CLAUDE_ISOLATED_TEST_DIR/scripts/lib/test-helpers.sh" local output="This is a test output with multiple lines" @@ -84,7 +84,7 @@ teardown() { } @test "run-docker-test.sh: script exists and is executable" { - local script="$DOCKER_SANDBOX_DIR/scripts/run-docker-test.sh" + local script="$CLAUDE_ISOLATED_TEST_DIR/scripts/run-docker-test.sh" [ -f "$script" ] [ -x "$script" ] @@ -95,7 +95,7 @@ teardown() { skip "Docker not available" fi - local script="$DOCKER_SANDBOX_DIR/scripts/run-docker-test.sh" + local script="$CLAUDE_ISOLATED_TEST_DIR/scripts/run-docker-test.sh" run "$script" [ "$status" -ne 0 ] @@ -107,7 +107,7 @@ teardown() { skip "Docker not available" fi - local script="$DOCKER_SANDBOX_DIR/scripts/run-docker-test.sh" + local script="$CLAUDE_ISOLATED_TEST_DIR/scripts/run-docker-test.sh" run "$script" "/nonexistent/test.yaml" [ "$status" -ne 0 ] From 6364b2e719bca93dd1e9bd8664a59cc31ffb0f1c Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 31 Jan 2026 10:05:26 +0900 Subject: [PATCH 5/5] fix(ci): add missing lib/ directory and update gitignore - Add scripts/lib/ directory with helper scripts that were missed in rename - Update .gitignore: change lib/ to */lib/ to allow exception patterns to work This fixes CI failures where docker-helpers.sh and test-helpers.sh could not be found. Co-Authored-By: Claude --- .gitignore | 3 +- .../claude-isolated-test/scripts/lib/.gitkeep | 0 .../scripts/lib/docker-helpers.sh | 100 ++++++++++++++ .../scripts/lib/test-helpers.sh | 126 ++++++++++++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 plugins/me/skills/claude-isolated-test/scripts/lib/.gitkeep create mode 100755 plugins/me/skills/claude-isolated-test/scripts/lib/docker-helpers.sh create mode 100755 plugins/me/skills/claude-isolated-test/scripts/lib/test-helpers.sh diff --git a/.gitignore b/.gitignore index 26173e4d..7f1d4526 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,8 @@ dist/ downloads/ eggs/ .eggs/ -lib/ +# lib/ is ignored but allow specific paths (exceptions below) +*/lib/ lib64/ # Exception: Include plugin and skill script libraries diff --git a/plugins/me/skills/claude-isolated-test/scripts/lib/.gitkeep b/plugins/me/skills/claude-isolated-test/scripts/lib/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/plugins/me/skills/claude-isolated-test/scripts/lib/docker-helpers.sh b/plugins/me/skills/claude-isolated-test/scripts/lib/docker-helpers.sh new file mode 100755 index 00000000..875405f0 --- /dev/null +++ b/plugins/me/skills/claude-isolated-test/scripts/lib/docker-helpers.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# docker-helpers.sh: Docker lifecycle management functions for Claude Code testing +set -euo pipefail + +# Check Docker availability and daemon +check_docker_available() { + command -v docker &>/dev/null && docker ps &>/dev/null +} + +# Build Claude Code test image +build_claude_image() { + local image_name="${1:-claude-test}" + local docker_context="${2:-./docker}" + echo "Building Docker image: $image_name from $docker_context" + docker build -t "$image_name" "$docker_context" +} + +# Create and start container +create_container() { + local container_name="$1" + local image_name="$2" + local oauth_token="$3" + local workspace="${4:-$(pwd)}" + + if [ -z "$oauth_token" ] || [ "$oauth_token" = "null" ]; then + echo "Error: OAuth token is empty" >&2 + return 1 + fi + + docker run -d \ + --name "$container_name" \ + -e CLAUDE_CODE_OAUTH_TOKEN="$oauth_token" \ + -v "$workspace:/workspace" \ + -w /workspace \ + "$image_name" \ + sleep infinity +} + +# Execute command in container (output to stdout) +exec_in_container() { + local container_name="$1" + shift + docker exec "$container_name" "$@" +} + +# Execute command in container (capture output) +exec_in_container_capture() { + local container_name="$1" + shift + docker exec "$container_name" "$@" 2>&1 +} + +# Wait for Claude Code CLI to be ready +wait_for_claude_ready() { + local container_name="$1" + local timeout="${2:-30}" + local elapsed=0 + + echo "Waiting for Claude Code to be ready (timeout: ${timeout}s)..." >&2 + + while [ $elapsed -lt $timeout ]; do + if docker exec "$container_name" bash -c "command -v claude" >/dev/null 2>&1; then + echo "Claude Code is ready" >&2 + return 0 + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + + echo "Timeout waiting for Claude Code in container" >&2 + return 1 +} + +# Get container logs +get_container_logs() { + local container_name="$1" + docker logs "$container_name" 2>&1 +} + +# Cleanup container +cleanup_container() { + local container_name="$1" + if container_exists "$container_name"; then + echo "Cleaning up container: $container_name" >&2 + docker stop "$container_name" &>/dev/null || true + docker rm "$container_name" &>/dev/null || true + fi +} + +# Check if container exists +container_exists() { + local container_name="$1" + docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$" +} + +# Check if container is running +container_running() { + local container_name="$1" + docker ps --format '{{.Names}}' | grep -q "^${container_name}$" +} diff --git a/plugins/me/skills/claude-isolated-test/scripts/lib/test-helpers.sh b/plugins/me/skills/claude-isolated-test/scripts/lib/test-helpers.sh new file mode 100755 index 00000000..56075e70 --- /dev/null +++ b/plugins/me/skills/claude-isolated-test/scripts/lib/test-helpers.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# test-helpers.sh: Test execution and verification functions +set -euo pipefail + +# Global variables for test data +TEST_NAME="" +TEST_TARGET_TYPE="" +TEST_TARGET_NAME="" +TEST_PROMPT="" +TEST_TIMEOUT=60 +TEST_EXPECTED_CONTAINS=() + +# Load YAML test definition +load_test_definition() { + local test_file="$1" + + if [ ! -f "$test_file" ]; then + echo "Error: Test file not found: $test_file" >&2 + return 1 + fi + + # Parse YAML using grep/sed + TEST_NAME=$(grep '^name:' "$test_file" | sed 's/name: *//; s/"//g; s/\r//') + TEST_TARGET_TYPE=$(grep -A2 '^test_target:' "$test_file" | grep 'type:' | sed 's/.*type: *//; s/\r//') + TEST_TARGET_NAME=$(grep -A2 '^test_target:' "$test_file" | grep 'name:' | sed 's/.*name: *//; s/\r//') + # shellcheck disable=SC2034 # Used by run-docker-test.sh + TEST_PROMPT=$(grep -A1 '^input:' "$test_file" | grep 'prompt:' | sed 's/.*prompt: *//; s/"//g; s/\r//') + TEST_TIMEOUT=$(grep '^timeout:' "$test_file" | awk '{print $2}') + TEST_TIMEOUT=${TEST_TIMEOUT:-60} + + # Parse expected_output.contains (multi-line) + local in_contains=0 + while IFS= read -r line; do + if echo "$line" | grep -q 'expected_output:'; then + in_contains=1 + continue + fi + if [ "$in_contains" = "1" ]; then + if echo "$line" | grep -q 'contains:'; then + continue + fi + if echo "$line" | grep -qE '^\s+-\s+(.*)'; then + local value + value=$(echo "$line" | sed 's/^\s*-\s*//; s/"//g; s/\r//') + if [ -n "$value" ]; then + TEST_EXPECTED_CONTAINS+=("$value") + fi + elif echo "$line" | grep -qE '^\s+[a-z_]+:'; then + # New key, stop parsing contains + break + fi + fi + done < "$test_file" + + echo "Loaded test: $TEST_NAME" >&2 + echo " Target: $TEST_TARGET_TYPE/$TEST_TARGET_NAME" >&2 + echo " Timeout: ${TEST_TIMEOUT}s" >&2 +} + +# Verify output contains expected strings +verify_contains() { + local output="$1" + shift + local expected_strings=("$@") + local failed=0 + + for str in "${expected_strings[@]}"; do + if ! echo "$output" | grep -qF "$str"; then + echo " Expected to contain: $str" >&2 + failed=1 + else + echo " Found: $str" >&2 + fi + done + + return $failed +} + +# Verify file exists in container +verify_file_exists() { + local container_name="$1" + local file_path="$2" + + docker exec "$container_name" test -f "$file_path" +} + +# Verify file contains content +verify_file_contains() { + local container_name="$1" + local file_path="$2" + local expected_content="$3" + + local output + output=$(docker exec "$container_name" cat "$file_path" 2>/dev/null || echo "") + echo "$output" | grep -qF "$expected_content" +} + +# Measure execution time +measure_time() { + local start_time + start_time=$(date +%s) + "$@" + local end_time + end_time=$(date +%s) + echo $((end_time - start_time)) +} + +# Report test result +report_result() { + local test_name="$1" + local status="$2" + local duration="${3:-0}" + + if [ "$status" = "pass" ]; then + echo "PASS: $test_name (${duration}s)" + else + echo "FAIL: $test_name (${duration}s)" + fi +} + +# Skip test with message +skip() { + local message="$1" + echo "SKIP: $message" >&2 + exit 0 +}