Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
},
"metadata": {
"description": "AI-driven development toolkit for TDD and SDD workflows, providing comprehensive command templates and agents to enhance developer productivity with Claude Code",
"version": "1.1.1"
"version": "1.2.0"
},
"plugins": [
{
"name": "tsumiki",
"source": "./",
"description": "AI-driven development toolkit for TDD and SDD workflows, providing comprehensive command templates and agents to enhance developer productivity with Claude Code",
"version": "1.1.1",
"version": "1.2.0",
"author": {
"name": "makoto kuroeda",
"email": "kuroeda.makoto@classmethod.jp"
Expand Down
5 changes: 3 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tsumiki",
"version": "1.1.1",
"version": "1.2.0",
"description": "AI-driven development toolkit for TDD and SDD workflows, providing comprehensive command templates and agents to enhance developer productivity with Claude Code",
"author": {
"name": "makoto kuroeda",
Expand All @@ -10,5 +10,6 @@
"repository": "https://github.com/classmethod/tsumiki",
"license": "MIT",
"keywords": ["ai-development", "sdd", "tdd"],
"commands": "./commands/"
"commands": "./commands/",
"skills": "./skills/"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ dist/

# Others
tmp/

# Version management
.tool-versions
19 changes: 18 additions & 1 deletion MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ docs/rule/kairo/requirements/ # kairo-requirements専用ルール

これらのディレクトリ内の `.md` ファイルは、コマンド実行時にコンテキストとして自動読み込みされます。

#### --permission-mode  bypassPermissions の利用

可能な限り隔離された環境を用意する方法について *サンプル* を公開しました
利用についてはマニュアル https://code.claude.com/docs/en/permissions#permission-modes を必ず参照してください
claude code をホストのsandbox環境下で利用し、ファイルの変更など一部のコマンド以外全てを docker内で実行する *サンプル* を用意しています
claude_docker/ ディレクトリを参考に構築してください(pluginとしては扱わないようにしてます)
仕組みは、hooksを利用してtool実行のうち許可されてないものは全てdocker上で動作するコマンドを書き換えています

テスト済みの動作環境は、Mac/RancherDesktop(docker)/go です
※これはtsumikiの長時間実行の試験をする目的で作成されました。実プロジェクトでの適用についてはそれぞれのプロジェクトで判断・変更してください

改善方法についてのPRをお待ちしてます

### TDDコマンド

TASK作成時に `TDD` と判定している場合で個別にTDDプロセスを実行したい場合は、以下のコマンドを順次実行できます:
Expand Down Expand Up @@ -152,7 +165,11 @@ Kairoは以下を生成します:

# 特定のタスクのみ実装
/tsumiki:kairo-implement タスクファイル名 TASK番号
# "TASK-101を実装してください"

# タスク範囲を指定して実装 タスクディレクトリ名 開始TASK番号 終了TASK番号
/tsumiki:kairo-loop
(実行中にcompactが発動しても安定して「長時間処理」が可能です

```

Kairoは各タスクに対して内部的にTDDコマンドを使用して以下のプロセスを実行します:
Expand Down
139 changes: 139 additions & 0 deletions claude_docker/hooks/docker-auto-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/bin/bash
# docker-auto-wrapper.sh
# Claude CodeのPreToolUseフック:ほぼすべてのBashコマンドをDockerコンテナ内で自動実行

set -e

# ログディレクトリの作成
LOG_DIR="/tmp/claude"
mkdir -p "$LOG_DIR" 2>/dev/null || true

# デバッグログ用環境変数(DOCKER_HOOK_DEBUG=1 で有効化)
debug_log() {
local msg="$*"
if [[ "${DOCKER_HOOK_DEBUG:-0}" == "1" ]]; then
echo "[DEBUG] $msg" >&2
# デバッグモード時のみファイルにも出力
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $msg" >> "$LOG_DIR/hook-debug.log" 2>/dev/null || true
fi
}

# 標準入力からJSONを読み取る
INPUT=$(cat)
debug_log "Input received"

# デバッグモード時のみ実際の入力を保存
if [[ "${DOCKER_HOOK_DEBUG:-0}" == "1" ]]; then
echo "$INPUT" > "$LOG_DIR/hook-input-last.json" 2>/dev/null || true
fi

# コマンドを抽出
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
if [[ $? -ne 0 ]]; then
debug_log "ERROR: Failed to parse JSON with jq"
exit 0
fi
debug_log "Command: $COMMAND"

# コマンドが空の場合はそのまま実行
if [ -z "$COMMAND" ]; then
debug_log "Empty command, skipping"
exit 0
fi

# コマンドの最初の単語を抽出(複数のパターンに対応)
# パイプ、リダイレクト、コマンド置換を考慮して抽出
# 重要: 複数行コマンドの場合は最初の行の最初の単語のみを取得
CMD_NAME=$(echo "$COMMAND" | head -1 | sed 's/|.*//' | sed 's/>.*//' | sed 's/<.*//' | sed 's/;.*//' | sed 's/&&.*//' | awk '{print $1}' | tr -d ' \t')

# パス情報を削除(例: "/usr/local/bin/go" → "go")
CMD_BASE=$(basename "$CMD_NAME" 2>/dev/null || echo "$CMD_NAME")
debug_log "CMD_NAME after extraction: [$CMD_NAME]"
debug_log "CMD_BASE after basename: [$CMD_BASE]"
debug_log "Extracted command base: $CMD_BASE"

# 最優先: Git/Docker関連コマンドは必ずホストで実行
# Sandbox無効時でも確実に除外するため、最初にチェック
CRITICAL_HOST_COMMANDS=("git" "docker" "docker-compose")
debug_log "Checking CRITICAL_HOST_COMMANDS for: [$CMD_BASE]"
for critical_cmd in "${CRITICAL_HOST_COMMANDS[@]}"; do
debug_log " Comparing: [$CMD_BASE] == [$critical_cmd]"
if [[ "$CMD_BASE" == "$critical_cmd" ]]; then
debug_log " MATCH! Critical host command detected: $CMD_BASE - running on host"
exit 0
fi
done
debug_log "No critical command match found"

# ホストで実行すべきコマンドのホワイトリスト
# これらのコマンドはDockerコンテナ内で実行しても意味がない、または実行できない
HOST_ONLY_COMMANDS=(
"cd" # シェル組み込みコマンド
"pushd" # シェル組み込みコマンド
"popd" # シェル組み込みコマンド
"export" # 環境変数設定
"source" # スクリプト読み込み
"." # source のエイリアス
"alias" # エイリアス定義
"unalias" # エイリアス解除
"hash" # コマンドハッシュテーブル
"type" # コマンドタイプ確認
"which" # コマンドパス確認(場合により)
"date" # 日時取得(ホストの日時を使用)
)

# ホワイトリストに含まれているかチェック
for host_cmd in "${HOST_ONLY_COMMANDS[@]}"; do
if [[ "$CMD_BASE" == "$host_cmd" ]]; then
debug_log "Host-only command detected: $CMD_BASE - running on host"
exit 0
fi
done

# それ以外のすべてのコマンドをDockerコンテナ内で実行
debug_log "Wrapping command for Docker execution"

# プロジェクトルートを確定(スクリプトの 2 階層上 = .claude/hooks/ の親の親)
PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
CONTAINER_NAME="go-app"

# コンテナ状態を確認(失敗時は空文字になる)
CONTAINER_STATUS=$(docker inspect --format '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) || true
debug_log "Container status: [$CONTAINER_STATUS]"

if [[ "$CONTAINER_STATUS" == "running" ]]; then
# 起動中のコンテナで実行(高速)
WRAPPED_COMMAND="docker-compose -f \"$COMPOSE_FILE\" exec -T app sh -c $(printf '%q' "$COMMAND")"
debug_log "using exec (container running)"
else
# コンテナを自動起動(db の healthcheck 完了まで待機、60 秒タイムアウト)
docker-compose -f "$COMPOSE_FILE" up -d --wait --wait-timeout 60 app >&2 && UP_OK=true || UP_OK=false

if [[ "$UP_OK" == "true" ]]; then
WRAPPED_COMMAND="docker-compose -f \"$COMPOSE_FILE\" exec -T app sh -c $(printf '%q' "$COMMAND")"
debug_log "using exec (container started)"
else
# 起動失敗時は従来の run --rm にフォールバック
WRAPPED_COMMAND="docker-compose -f \"$COMPOSE_FILE\" run --rm -T app sh -c $(printf '%q' "$COMMAND")"
debug_log "using run --rm (fallback)"
fi
fi
debug_log "Wrapped command: $WRAPPED_COMMAND"

# JSONを出力して、コマンドを変更
jq -n \
--arg cmd "$WRAPPED_COMMAND" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": $cmd
},
"permissionDecisionReason": "Automatically wrapped to run in Docker container"
}
}'

debug_log "Hook completed successfully"
exit 0
102 changes: 102 additions & 0 deletions claude_docker/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"excludedCommands": ["docker", "docker-compose", "git"],
"allowUnsandboxedCommands": true,
"filesystem": {
"write": {
"allowOnly": [
"/tmp/claude",
"/private/tmp/claude",
"/private/tmp/zsh*"
]
}
},
"network": {
"allowedDomains": [
"proxy.golang.org",
"*.pkg.go.dev",
"github.com",
"*.docker.io",
"*.dockerhub.com",
"registry.hub.docker.com"
],
"allowUnixSockets": [
"/var/run/docker.sock",
"~/.rd/docker.sock"
],
"allowLocalBinding": true
}
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/docker-auto-wrapper.sh",
"timeout": 90
}
]
}
]
},
"permissions": {
"deny": [
"Bash(git push --force*)",
"Bash(git push -f *)",
"Bash(git push * -f*)",
"Bash(git push *--force*)",
"Bash(git reset --hard*)",
"Bash(git branch -D*)",
"Bash(git push * --delete*)",
"Bash(git push --delete*)",
"Bash(git clean -f*)",
"Bash(git checkout .)",
"Bash(git restore .)",
"Bash(git push *)",
"Bash(git commit --amend*)",
"Bash(git rebase*)",
"Bash(git merge*)",
"Bash(git stash drop*)",
"Bash(git tag -d*)",
"Bash(docker-compose down -v*)",
"Bash(docker-compose down *-v*)",
"Bash(docker-compose down*--volumes*)",
"Bash(docker system prune*)",
"Bash(docker volume rm*)",
"Bash(docker volume prune*)",
"Bash(docker rm -f *)",
"Bash(docker container prune*)",
"Bash(docker image prune -a*)",
"Bash(docker rmi *-f*)",
"Bash(docker network prune*)",
"Bash(docker-compose kill*)",
"Bash(docker stop *)",
"Bash(docker kill *)",
"Bash(rm *go.mod*)",
"Bash(rm *go.sum*)",
"Bash(rm *docker-compose.yml*)",
"Bash(rm *Dockerfile*)",
"Bash(rm *.claude/settings.json*)",
"Bash(rm *.claude/hooks/*)",
"Bash(rm *CLAUDE.md*)",
"Bash(rm -rf .git*)",
"Bash(find * -delete*)",
"Bash(find * -exec rm*)",
"Bash(*| sh)",
"Bash(*| bash)",
"Bash(*| sh *)",
"Bash(*| bash *)",
"Edit(.env)",
"Write(.env)",
"Edit(go.mod)",
"Edit(go.sum)",
"Edit(docker-compose.yml)",
"Edit(.claude/settings.json)",
"Write(.claude/settings.json)"
]
}
}
11 changes: 11 additions & 0 deletions claude_docker/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"sandbox": {
"network": {
"allowUnixSockets": [
"/var/run/docker.sock",
"/Users/USER_NAME/.rd/docker.sock"
]
},
"filesystem": {}
}
}
Loading