From 35e6fa0e97980c7011be0da7d3077331aa847700 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 5 Jul 2026 20:57:38 +0000 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20runtime-split-container-smoke=20Dr?= =?UTF-8?q?aft=20PR=20=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 60ae7acd3e9ee9e599cc8614d5ce89aecba51317 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Tue, 7 Jul 2026 06:17:01 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test:=20runtime=20smoke=20test=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime-plugin-authenticated-smoke.yml | 45 +++++ .github/workflows/runtime-plugin-smoke.yml | 37 ++++ .gitignore | 1 + README.md | 9 + docs/ndf-plugin-reference.md | 7 +- issues/agent-runtime-plugin-split.md | 49 +++-- issues/runtime-plugin-container-test-plan.md | 26 +-- plugins/mcp/kiro/mcp-aws-docs/install.sh | 20 +- plugins/mcp/kiro/mcp-bigquery/install.sh | 20 +- .../mcp/kiro/mcp-chrome-devtools/install.sh | 20 +- plugins/mcp/kiro/mcp-dbhub/install.sh | 20 +- plugins/mcp/kiro/mcp-devin/install.sh | 20 +- plugins/mcp/kiro/mcp-markitdown/install.sh | 20 +- plugins/mcp/kiro/mcp-notion/install.sh | 20 +- .../mcp/kiro/mcp-playwright/hooks/hooks.json | 2 +- plugins/mcp/kiro/mcp-playwright/install.sh | 20 +- plugins/mcp/kiro/mcp-redash/install.sh | 20 +- .../mcp-redash/skills/redash-add/SKILL.md | 2 +- .../mcp-redash/skills/redash-list/SKILL.md | 2 +- .../mcp-redash/skills/redash-remove/SKILL.md | 2 +- .../mcp-redash/skills/redash-status/SKILL.md | 2 +- plugins/mcp/kiro/mcp-serena/install.sh | 20 +- plugins/ndf-kiro/install.sh | 23 ++- scripts/build-runtime-plugins.sh | 22 +- scripts/runtime-smoke-test.sh | 188 ++++++++++++++++++ tests/runtime-smoke/Containerfile.base | 9 + tests/runtime-smoke/Containerfile.claude | 3 + tests/runtime-smoke/Containerfile.codex | 3 + tests/runtime-smoke/Containerfile.kiro | 3 + tests/runtime-smoke/README.md | 34 ++++ tests/runtime-smoke/adapters/claude.sh | 21 ++ tests/runtime-smoke/adapters/codex.sh | 19 ++ tests/runtime-smoke/adapters/kiro.sh | 23 +++ .../assertions/assert-authenticated-smoke.sh | 71 +++++++ .../assertions/assert-hook-fixtures.sh | 25 +++ .../assertions/assert-mcp-config.sh | 30 +++ .../assert-no-host-contamination.sh | 30 +++ .../assertions/assert-plugin-files.sh | 28 +++ .../fixtures/hook-session-start.json | 5 + tests/runtime-smoke/fixtures/hook-stop.json | 6 + tests/runtime-smoke/fixtures/mcp-env.example | 4 + tests/runtime-smoke/lib/common.sh | 39 ++++ tests/runtime-smoke/secrets-files.allowlist | 4 + 43 files changed, 868 insertions(+), 106 deletions(-) create mode 100644 .github/workflows/runtime-plugin-authenticated-smoke.yml create mode 100644 .github/workflows/runtime-plugin-smoke.yml create mode 100755 scripts/runtime-smoke-test.sh create mode 100644 tests/runtime-smoke/Containerfile.base create mode 100644 tests/runtime-smoke/Containerfile.claude create mode 100644 tests/runtime-smoke/Containerfile.codex create mode 100644 tests/runtime-smoke/Containerfile.kiro create mode 100644 tests/runtime-smoke/README.md create mode 100755 tests/runtime-smoke/adapters/claude.sh create mode 100755 tests/runtime-smoke/adapters/codex.sh create mode 100755 tests/runtime-smoke/adapters/kiro.sh create mode 100755 tests/runtime-smoke/assertions/assert-authenticated-smoke.sh create mode 100755 tests/runtime-smoke/assertions/assert-hook-fixtures.sh create mode 100755 tests/runtime-smoke/assertions/assert-mcp-config.sh create mode 100755 tests/runtime-smoke/assertions/assert-no-host-contamination.sh create mode 100755 tests/runtime-smoke/assertions/assert-plugin-files.sh create mode 100644 tests/runtime-smoke/fixtures/hook-session-start.json create mode 100644 tests/runtime-smoke/fixtures/hook-stop.json create mode 100644 tests/runtime-smoke/fixtures/mcp-env.example create mode 100755 tests/runtime-smoke/lib/common.sh create mode 100644 tests/runtime-smoke/secrets-files.allowlist diff --git a/.github/workflows/runtime-plugin-authenticated-smoke.yml b/.github/workflows/runtime-plugin-authenticated-smoke.yml new file mode 100644 index 0000000..c66a909 --- /dev/null +++ b/.github/workflows/runtime-plugin-authenticated-smoke.yml @@ -0,0 +1,45 @@ +name: Runtime plugin authenticated smoke + +on: + workflow_dispatch: + inputs: + runtime: + description: Runtime to test + required: true + default: all + type: choice + options: + - all + - claude + - codex + - kiro + +jobs: + authenticated-smoke: + runs-on: ubuntu-latest + environment: runtime-smoke + steps: + - uses: actions/checkout@v4 + - name: Run authenticated runtime smoke + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + BIGQUERY_PROJECT: ${{ secrets.BIGQUERY_PROJECT }} + BIGQUERY_LOCATION: ${{ secrets.BIGQUERY_LOCATION }} + BIGQUERY_DATASET: ${{ secrets.BIGQUERY_DATASET }} + BIGQUERY_KEY_FILE_JSON: ${{ secrets.BIGQUERY_KEY_FILE_JSON }} + run: | + extra_args=() + if [ -n "$BIGQUERY_KEY_FILE_JSON" ]; then + mkdir -p tmp/runtime-smoke-secrets + export BIGQUERY_KEY_FILE="$PWD/tmp/runtime-smoke-secrets/bigquery-key-file.json" + printf '%s' "$BIGQUERY_KEY_FILE_JSON" > "$BIGQUERY_KEY_FILE" + extra_args+=(--secret-file "bigquery-key-file=$BIGQUERY_KEY_FILE") + fi + bash scripts/runtime-smoke-test.sh --runtime "${{ inputs.runtime }}" --with-secrets=auto "${extra_args[@]}" + - name: Upload smoke artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: runtime-smoke-authenticated + path: tmp/runtime-smoke diff --git a/.github/workflows/runtime-plugin-smoke.yml b/.github/workflows/runtime-plugin-smoke.yml new file mode 100644 index 0000000..9d1169e --- /dev/null +++ b/.github/workflows/runtime-plugin-smoke.yml @@ -0,0 +1,37 @@ +name: Runtime plugin smoke + +on: + pull_request: + paths: + - "plugins/**" + - "scripts/runtime-smoke-test.sh" + - "tests/runtime-smoke/**" + - ".github/workflows/runtime-plugin-smoke.yml" + - ".github/workflows/runtime-plugin-authenticated-smoke.yml" + push: + branches: + - main + - "release/**" + paths: + - "plugins/**" + - "scripts/runtime-smoke-test.sh" + - "tests/runtime-smoke/**" + - ".github/workflows/runtime-plugin-smoke.yml" + +jobs: + runtime-smoke: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + runtime: [claude, codex, kiro] + steps: + - uses: actions/checkout@v4 + - name: Run unauthenticated runtime smoke + run: bash scripts/runtime-smoke-test.sh --runtime "${{ matrix.runtime }}" --with-secrets=off + - name: Upload smoke artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: runtime-smoke-${{ matrix.runtime }} + path: tmp/runtime-smoke/${{ matrix.runtime }} diff --git a/.gitignore b/.gitignore index f654006..3f40ed2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ yarn-error.log* # Temporary files *.tmp *.bak +tmp/ # Python __pycache__/ diff --git a/README.md b/README.md index 6cc757a..3a0d40c 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,15 @@ bash scripts/build-runtime-plugins.sh bash scripts/validate-runtime-plugins.sh ``` +実ランタイムのインストール経路を確認する場合は、Docker コンテナ内で smoke test を実行します。 + +```bash +bash scripts/runtime-smoke-test.sh +bash scripts/runtime-smoke-test.sh --runtime claude +bash scripts/runtime-smoke-test.sh --runtime codex +bash scripts/runtime-smoke-test.sh --runtime kiro +``` + ローカル hook を使う場合は以下を実行します。 ```bash diff --git a/docs/ndf-plugin-reference.md b/docs/ndf-plugin-reference.md index b6acf78..a0b5def 100644 --- a/docs/ndf-plugin-reference.md +++ b/docs/ndf-plugin-reference.md @@ -85,11 +85,16 @@ bash scripts/build-runtime-plugins.sh --check bash scripts/validate-runtime-plugins.sh claude plugin validate plugins/ndf-claude python3 -m json.tool plugins/ndf-codex/.codex-plugin/plugin.json >/dev/null -bash plugins/ndf-kiro/install.sh --help +bash plugins/ndf-kiro/install.sh --dry-run +bash scripts/runtime-smoke-test.sh --runtime claude +bash scripts/runtime-smoke-test.sh --runtime codex +bash scripts/runtime-smoke-test.sh --runtime kiro ``` `--check` は `plugins/ndf-*` と `plugins/mcp/claude|codex|kiro` の生成物が共通編集元と同期していることを検証します。`validate-runtime-plugins.sh` は生成物同期、JSON / manifest、marketplace source、Kiro installer、Markdown ローカルリンクをまとめて確認します。 +`runtime-smoke-test.sh` は Docker コンテナ内に repo copy、`/tmp/runtime-home`、`/tmp/runtime-project`、`/tmp/runtime-secrets` を分離して作成し、Claude / Codex / Kiro それぞれで `ndf` と `mcp-bigquery` 相当の実 install、Skill / MCP / hook / agent config の assertion、JUnit / log artifact 出力を確認します。PR CI は `--with-secrets=off` の非認証 smoke のみを実行し、認証付き smoke は `runtime-plugin-authenticated-smoke.yml` の protected workflow で実行します。 + ローカル hook を使う場合は `bash scripts/install-dev-hooks.sh` で `.githooks/` を有効化します。 ## 外部 AI 委譲 diff --git a/issues/agent-runtime-plugin-split.md b/issues/agent-runtime-plugin-split.md index e7f1547..e8003a7 100644 --- a/issues/agent-runtime-plugin-split.md +++ b/issues/agent-runtime-plugin-split.md @@ -551,15 +551,14 @@ base branch: `main` | merge 済み | #49 | `feature/runtime-split-kiro` | Task 4: `plugins/ndf-kiro` を作成し、Kiro installer / docs / templates を移動 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。worktree / local branch cleanup 済み | | merge 済み | #50 | `feature/runtime-split-docs-cleanup` | Task 5: README / AGENTS / docs / specs 更新、旧 `plugins/ndf` の削除 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。merge 後検証済み | | merge 済み | #51 / #54 | `feature/runtime-split-mcp-plugins` | Task 6: MCP plugin を runtime 別に分離 | #51 はローテーションで close。#54 が cross-review approved、`release/runtime-plugin-split` へ merge 済み | -| 実装中 | #52 | `feature/runtime-split-validation` | Task 7: validate script、dev hook、CI、リンク検証 | `validate-runtime-plugins.sh`、`.githooks/`、GitHub Actions、Markdown link check を追加。ローカル検証通過 | -| 未着手 | #53 | `feature/runtime-split-container-smoke` | Task 8: runtime smoke test | PR7 merge 後 | +| merge 済み | #52 | `feature/runtime-split-validation` | Task 7: validate script、dev hook、CI、リンク検証 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。merge 後検証済み | +| 実装済み | #53 | `feature/runtime-split-container-smoke` | Task 8: runtime smoke test | `scripts/runtime-smoke-test.sh`、runtime 別 Containerfile / adapter / assertion、secret 注入、CI workflow、docs を追加。この PR で cross-review / merge 後検証予定 | 再開時の前提: -- 現在の作業ブランチは `feature/runtime-split-validation`。 -- `release/runtime-plugin-split` には PR #46 / #47 / #48 / #49 / #50 / #54 が merge 済み。 -- 次は PR #52 の validation 実装を push し、cross-review 後に `release/runtime-plugin-split` へ merge する。 -- PR #52 merge 後に PR #53 `feature/runtime-split-container-smoke` を進める。 +- 現在の作業ブランチは `feature/runtime-split-container-smoke`。 +- `release/runtime-plugin-split` には PR #46 / #47 / #48 / #49 / #50 / #52 / #54 が merge 済み。 +- 次は PR #53 の container smoke 実装を cross-review し、`release/runtime-plugin-split` へ merge する。 ## 影響範囲 @@ -608,25 +607,25 @@ base branch: `main` 詳細なテスト設計は `issues/runtime-plugin-container-test-plan.md` に分離する。この親計画では以下を完了条件として追跡する。 -- [ ] `bash scripts/build-runtime-plugins.sh --check` -- [ ] `bash scripts/validate-runtime-plugins.sh` -- [ ] `bash scripts/runtime-smoke-test.sh --runtime claude` -- [ ] `bash scripts/runtime-smoke-test.sh --runtime codex` -- [ ] `bash scripts/runtime-smoke-test.sh --runtime kiro` -- [ ] `.github/workflows/runtime-plugin-validate.yml` が build check / validate / link check を実行する -- [ ] `.github/workflows/runtime-plugin-smoke.yml` が軽量コンテナで runtime smoke を実行する -- [ ] `rg "plugins/ndf($|[^-])|plugins/ndf/" README.md AGENTS.md CLAUDE.md KIRO.md docs plugins scripts .claude-plugin .agents` で旧 NDF パス残存を確認 -- [ ] `rg "plugins/mcp-[a-z]" README.md AGENTS.md CLAUDE.md KIRO.md docs plugins scripts .claude-plugin .agents` で旧 MCP パス残存を確認 +- [x] `bash scripts/build-runtime-plugins.sh --check` +- [x] `bash scripts/validate-runtime-plugins.sh` +- [x] `bash scripts/runtime-smoke-test.sh --runtime claude` +- [x] `bash scripts/runtime-smoke-test.sh --runtime codex` +- [x] `bash scripts/runtime-smoke-test.sh --runtime kiro` +- [x] `.github/workflows/runtime-plugin-validate.yml` が build check / validate / link check を実行する +- [x] `.github/workflows/runtime-plugin-smoke.yml` が軽量コンテナで runtime smoke を実行する +- [x] `rg "plugins/ndf($|[^-])|plugins/ndf/" README.md AGENTS.md CLAUDE.md KIRO.md docs plugins scripts .claude-plugin .agents` で旧 NDF パス残存を確認 +- [x] `rg "plugins/mcp-[a-z]" README.md AGENTS.md CLAUDE.md KIRO.md docs plugins scripts .claude-plugin .agents` で旧 MCP パス残存を確認 ## 完了の定義 -- [ ] Claude / Codex / Kiro の配布単位が別ディレクトリになっている -- [ ] `plugins/ndf` が削除または README stub のみに縮退している -- [ ] marketplace source がランタイム別ディレクトリを指している -- [ ] Kiro installer が Claude manifest に依存していない -- [ ] 共通 Skill の編集元と生成先の同期チェックがある -- [ ] MCP plugin が `plugins/mcp/shared|claude|codex|kiro` に分離されている -- [ ] Claude / Codex / Kiro の各導入手順で `mcp-bigquery@ai-plugins` が同じ名前で使える -- [ ] README / docs が新構成を案内している -- [ ] 各ランタイムの検証手順が通っている -- [ ] 各ランタイムの実インストール smoke test がコンテナ内で通っている +- [x] Claude / Codex / Kiro の配布単位が別ディレクトリになっている +- [x] `plugins/ndf` が削除または README stub のみに縮退している +- [x] marketplace source がランタイム別ディレクトリを指している +- [x] Kiro installer が Claude manifest に依存していない +- [x] 共通 Skill の編集元と生成先の同期チェックがある +- [x] MCP plugin が `plugins/mcp/shared|claude|codex|kiro` に分離されている +- [x] Claude / Codex / Kiro の各導入手順で `mcp-bigquery@ai-plugins` が同じ名前で使える +- [x] README / docs が新構成を案内している +- [x] 各ランタイムの検証手順が通っている +- [x] 各ランタイムの実インストール smoke test がコンテナ内で通っている diff --git a/issues/runtime-plugin-container-test-plan.md b/issues/runtime-plugin-container-test-plan.md index cfd21f5..e2a6854 100644 --- a/issues/runtime-plugin-container-test-plan.md +++ b/issues/runtime-plugin-container-test-plan.md @@ -453,16 +453,16 @@ base branch: `release/runtime-plugin-split` (親計画の PR7 merge 後。親計 ## 完了の定義 -- [ ] `scripts/runtime-smoke-test.sh` で runtime を選択できる -- [ ] Claude / Codex / Kiro の container image が分かれている -- [ ] smoke は host HOME と credential directory を mount しない -- [ ] secret が存在する場合は許可リストに従って `--rm --tmpfs /tmp/runtime-secrets` container へ注入され、container 内 path へ env が再設定される -- [ ] `--with-secrets=auto|required` と `--keep-container` の併用が拒否される -- [ ] `ndf@ai-plugins` の実 install を確認する -- [ ] `mcp-bigquery@ai-plugins` 相当の実 install を全 runtime で確認する -- [ ] Skill / MCP / hook / agents または Kiro agent config の assertion がある -- [ ] hook script は fixture payload で非認証実行できる -- [ ] secret が存在する場合は認証付き Skill / MCP smoke が実行される -- [ ] ブラウザ認証しかない runtime、または `--with-secrets=off` の非認証 smoke だけ、認証準備完了までを合格条件にする -- [ ] CI で smoke test log と JUnit を artifact 化する -- [ ] secret 値が log / JUnit / artifact に出力されない +- [x] `scripts/runtime-smoke-test.sh` で runtime を選択できる +- [x] Claude / Codex / Kiro の container image が分かれている +- [x] smoke は host HOME と credential directory を mount しない +- [x] secret が存在する場合は許可リストに従って `--rm --tmpfs /tmp/runtime-secrets` container へ注入され、container 内 path へ env が再設定される +- [x] `--with-secrets=auto|required` と `--keep-container` の併用が拒否される +- [x] `ndf@ai-plugins` の実 install を確認する +- [x] `mcp-bigquery@ai-plugins` 相当の実 install を全 runtime で確認する +- [x] Skill / MCP / hook / agents または Kiro agent config の assertion がある +- [x] hook script は fixture payload で非認証実行できる +- [x] secret が存在する場合は認証付き Skill / MCP smoke が実行される +- [x] ブラウザ認証しかない runtime、または `--with-secrets=off` の非認証 smoke だけ、認証準備完了までを合格条件にする +- [x] CI で smoke test log と JUnit を artifact 化する +- [x] secret 値が log / JUnit / artifact に出力されない diff --git a/plugins/mcp/kiro/mcp-aws-docs/install.sh b/plugins/mcp/kiro/mcp-aws-docs/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-aws-docs/install.sh +++ b/plugins/mcp/kiro/mcp-aws-docs/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-bigquery/install.sh b/plugins/mcp/kiro/mcp-bigquery/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-bigquery/install.sh +++ b/plugins/mcp/kiro/mcp-bigquery/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-chrome-devtools/install.sh b/plugins/mcp/kiro/mcp-chrome-devtools/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-chrome-devtools/install.sh +++ b/plugins/mcp/kiro/mcp-chrome-devtools/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-dbhub/install.sh b/plugins/mcp/kiro/mcp-dbhub/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-dbhub/install.sh +++ b/plugins/mcp/kiro/mcp-dbhub/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-devin/install.sh b/plugins/mcp/kiro/mcp-devin/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-devin/install.sh +++ b/plugins/mcp/kiro/mcp-devin/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-markitdown/install.sh b/plugins/mcp/kiro/mcp-markitdown/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-markitdown/install.sh +++ b/plugins/mcp/kiro/mcp-markitdown/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-notion/install.sh b/plugins/mcp/kiro/mcp-notion/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-notion/install.sh +++ b/plugins/mcp/kiro/mcp-notion/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-playwright/hooks/hooks.json b/plugins/mcp/kiro/mcp-playwright/hooks/hooks.json index c248d8f..567b670 100644 --- a/plugins/mcp/kiro/mcp-playwright/hooks/hooks.json +++ b/plugins/mcp/kiro/mcp-playwright/hooks/hooks.json @@ -10,7 +10,7 @@ "hooks": [ { "type": "command", - "command": "node ${PLUGIN_ROOT:-.kiro/plugins/mcp-playwright}/scripts/ensure-playwright.js", + "command": "node ${PLUGIN_ROOT:-.kiro/mcp_runtime/mcp-playwright}/scripts/ensure-playwright.js", "description": "Auto-install Playwright chromium on first use", "continueOnError": false, "suppressOutput": false diff --git a/plugins/mcp/kiro/mcp-playwright/install.sh b/plugins/mcp/kiro/mcp-playwright/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-playwright/install.sh +++ b/plugins/mcp/kiro/mcp-playwright/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-redash/install.sh b/plugins/mcp/kiro/mcp-redash/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-redash/install.sh +++ b/plugins/mcp/kiro/mcp-redash/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/mcp/kiro/mcp-redash/skills/redash-add/SKILL.md b/plugins/mcp/kiro/mcp-redash/skills/redash-add/SKILL.md index f6ef3f4..e780e63 100644 --- a/plugins/mcp/kiro/mcp-redash/skills/redash-add/SKILL.md +++ b/plugins/mcp/kiro/mcp-redash/skills/redash-add/SKILL.md @@ -19,7 +19,7 @@ allowed-tools: 以下のコマンドを実行してください。`$ARGUMENTS` にはユーザーが指定した suffix が入ります。 ```bash -node "${PLUGIN_ROOT:-.kiro/plugins/mcp-redash}/scripts/redash-mcp-config.js" add "$ARGUMENTS" +node "${PLUGIN_ROOT:-.kiro/mcp_runtime/mcp-redash}/scripts/redash-mcp-config.js" add "$ARGUMENTS" ``` ## 実行後 diff --git a/plugins/mcp/kiro/mcp-redash/skills/redash-list/SKILL.md b/plugins/mcp/kiro/mcp-redash/skills/redash-list/SKILL.md index f3be2ff..ac510d7 100644 --- a/plugins/mcp/kiro/mcp-redash/skills/redash-list/SKILL.md +++ b/plugins/mcp/kiro/mcp-redash/skills/redash-list/SKILL.md @@ -16,7 +16,7 @@ allowed-tools: 以下のコマンドを実行してください。 ```bash -node "${PLUGIN_ROOT:-.kiro/plugins/mcp-redash}/scripts/redash-mcp-config.js" list +node "${PLUGIN_ROOT:-.kiro/mcp_runtime/mcp-redash}/scripts/redash-mcp-config.js" list ``` ## 実行後 diff --git a/plugins/mcp/kiro/mcp-redash/skills/redash-remove/SKILL.md b/plugins/mcp/kiro/mcp-redash/skills/redash-remove/SKILL.md index bebb222..0a5acbc 100644 --- a/plugins/mcp/kiro/mcp-redash/skills/redash-remove/SKILL.md +++ b/plugins/mcp/kiro/mcp-redash/skills/redash-remove/SKILL.md @@ -19,7 +19,7 @@ allowed-tools: 以下のコマンドを実行してください。`$ARGUMENTS` にはユーザーが指定した suffix が入ります。 ```bash -node "${PLUGIN_ROOT:-.kiro/plugins/mcp-redash}/scripts/redash-mcp-config.js" remove "$ARGUMENTS" +node "${PLUGIN_ROOT:-.kiro/mcp_runtime/mcp-redash}/scripts/redash-mcp-config.js" remove "$ARGUMENTS" ``` ## 実行後 diff --git a/plugins/mcp/kiro/mcp-redash/skills/redash-status/SKILL.md b/plugins/mcp/kiro/mcp-redash/skills/redash-status/SKILL.md index 705300a..5f3e6a9 100644 --- a/plugins/mcp/kiro/mcp-redash/skills/redash-status/SKILL.md +++ b/plugins/mcp/kiro/mcp-redash/skills/redash-status/SKILL.md @@ -16,7 +16,7 @@ Redash MCP の設定状況を詳細表示します。各 MCP が必要とする 以下のコマンドを実行してください。 ```bash -node "${PLUGIN_ROOT:-.kiro/plugins/mcp-redash}/scripts/redash-mcp-config.js" status +node "${PLUGIN_ROOT:-.kiro/mcp_runtime/mcp-redash}/scripts/redash-mcp-config.js" status ``` ## 実行後 diff --git a/plugins/mcp/kiro/mcp-serena/install.sh b/plugins/mcp/kiro/mcp-serena/install.sh index a364724..26e6b00 100755 --- a/plugins/mcp/kiro/mcp-serena/install.sh +++ b/plugins/mcp/kiro/mcp-serena/install.sh @@ -6,7 +6,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -16,7 +16,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -24,6 +24,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -48,12 +58,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -64,7 +74,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/plugins/ndf-kiro/install.sh b/plugins/ndf-kiro/install.sh index ea7edc1..3d0651c 100755 --- a/plugins/ndf-kiro/install.sh +++ b/plugins/ndf-kiro/install.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # NDF Plugin Installer for Kiro CLI -# Usage: bash plugins/ndf-kiro/install.sh [--with-slack] [--with-codex] [--dry-run] +# Usage: bash plugins/ndf-kiro/install.sh [--project PATH] [--with-slack] [--with-codex] [--dry-run] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PROJECT_ROOT="$(pwd)" PLUGIN_DIR="$SCRIPT_DIR" KIRO_DIR="$PROJECT_ROOT/.kiro" SKILLS_DIR="$KIRO_DIR/skills" @@ -18,8 +18,17 @@ PLUGIN_PROMPTS_DIR="$PLUGIN_DIR/prompts" WITH_SLACK=false WITH_CODEX=false DRY_RUN=false -for arg in "$@"; do - case "$arg" in +while [ "$#" -gt 0 ]; do + case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + SKILLS_DIR="$KIRO_DIR/skills" + PROMPTS_DIR="$KIRO_DIR/prompts" + AGENT_FILE="$KIRO_DIR/agents/default.json" + shift + ;; --with-slack) WITH_SLACK=true ;; --with-codex) WITH_CODEX=true ;; --dry-run) DRY_RUN=true ;; @@ -27,6 +36,7 @@ for arg in "$@"; do echo "Usage: bash plugins/ndf-kiro/install.sh [OPTIONS]" echo "" echo "Options:" + echo " --project PATH install into PATH instead of current directory" echo " --with-slack stopフックにSlack通知を追加" echo " --with-codex Codex CLI直接実行用プロンプトを追加" echo " --dry-run 書き込みを行わず実行内容を表示" @@ -34,10 +44,11 @@ for arg in "$@"; do exit 0 ;; *) - echo "ERROR: unknown option: $arg" >&2 + echo "ERROR: unknown option: $1" >&2 exit 2 ;; esac + shift done echo "=== NDF Plugin Installer for Kiro CLI ===" @@ -68,7 +79,7 @@ while IFS= read -r src_dir; do fi if [ "$DRY_RUN" = false ]; then - ln -sfn "../../plugins/ndf-kiro/skills/$skill_name" "$SKILLS_DIR/$skill_name" + ln -sfn "$PLUGIN_SKILLS_DIR/$skill_name" "$SKILLS_DIR/$skill_name" fi echo " linked: $skill_name" SKILL_COUNT=$((SKILL_COUNT + 1)) diff --git a/scripts/build-runtime-plugins.sh b/scripts/build-runtime-plugins.sh index e2ebda9..c7fd647 100755 --- a/scripts/build-runtime-plugins.sh +++ b/scripts/build-runtime-plugins.sh @@ -341,7 +341,7 @@ apply_mcp_readme_template() { rewrite_kiro_mcp_paths() { local plugin_dir="$1" local plugin_name="$2" - local root_expr="\${PLUGIN_ROOT:-.kiro/plugins/$plugin_name}" + local root_expr="\${PLUGIN_ROOT:-.kiro/mcp_runtime/$plugin_name}" local file while IFS= read -r file; do @@ -365,7 +365,7 @@ PLUGIN_NAME="$(basename "$SCRIPT_DIR")" PROJECT_ROOT="$(pwd)" KIRO_DIR="$PROJECT_ROOT/.kiro" KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" -KIRO_PLUGIN_LINK="$KIRO_DIR/plugins/$PLUGIN_NAME" +KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" KIRO_SKILLS_DIR="$KIRO_DIR/skills" TARGET_MCP="$PROJECT_ROOT/.mcp.json" SOURCE_MCP="$SCRIPT_DIR/.mcp.json" @@ -375,7 +375,7 @@ DRY_RUN=false usage() { cat <<'USAGE' -Usage: bash install.sh [--dry-run] +Usage: bash install.sh [--project PATH] [--dry-run] Install this MCP plugin into the current project's Kiro settings. USAGE @@ -383,6 +383,16 @@ USAGE while [ "$#" -gt 0 ]; do case "$1" in + --project) + [ "$#" -ge 2 ] || { echo "ERROR: --project requires a path" >&2; exit 2; } + PROJECT_ROOT="$(cd "$2" && pwd)" + KIRO_DIR="$PROJECT_ROOT/.kiro" + KIRO_AGENT_FILE="$KIRO_DIR/agents/default.json" + KIRO_PLUGIN_LINK="$KIRO_DIR/mcp_runtime/$PLUGIN_NAME" + KIRO_SKILLS_DIR="$KIRO_DIR/skills" + TARGET_MCP="$PROJECT_ROOT/.mcp.json" + shift + ;; --dry-run) DRY_RUN=true ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; @@ -407,12 +417,12 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi -mkdir -p "$KIRO_DIR/plugins" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" +mkdir -p "$KIRO_DIR/mcp_runtime" "$KIRO_SKILLS_DIR" "$(dirname "$KIRO_AGENT_FILE")" if [ -e "$KIRO_PLUGIN_LINK" ] && [ ! -L "$KIRO_PLUGIN_LINK" ]; then echo "ERROR: $KIRO_PLUGIN_LINK already exists and is not a symlink" >&2 exit 1 fi -ln -sfn "../../plugins/mcp/kiro/$PLUGIN_NAME" "$KIRO_PLUGIN_LINK" +ln -sfn "$SCRIPT_DIR" "$KIRO_PLUGIN_LINK" if [ -d "$SOURCE_SKILLS" ]; then find "$SOURCE_SKILLS" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r skill_dir; do @@ -423,7 +433,7 @@ if [ -d "$SOURCE_SKILLS" ]; then echo "ERROR: $skill_link already exists and is not a symlink" >&2 exit 1 fi - ln -sfn "../plugins/$PLUGIN_NAME/skills/$skill_name" "$skill_link" + ln -sfn "$skill_dir" "$skill_link" echo "Linked Kiro skill: $skill_name" done fi diff --git a/scripts/runtime-smoke-test.sh b/scripts/runtime-smoke-test.sh new file mode 100755 index 0000000..c5d8efa --- /dev/null +++ b/scripts/runtime-smoke-test.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +RUNTIME="all" +WITH_SECRETS="off" +KEEP_CONTAINER=false +ARTIFACT_DIR="$ROOT_DIR/tmp/runtime-smoke" + +usage() { + cat <<'USAGE' +Usage: bash scripts/runtime-smoke-test.sh [OPTIONS] + +Options: + --runtime claude|codex|kiro|all runtime to test (default: all) + --with-secrets off|auto|required secret handling mode (default: off) + --secret-file KEY=PATH inject an allowlisted file secret + --keep-container keep the container for local debugging + --artifact-dir PATH write logs/JUnit/artifacts under PATH + -h, --help show this help +USAGE +} + +SECRET_FILES=() +while [ "$#" -gt 0 ]; do + case "$1" in + --runtime) RUNTIME="${2:?}"; shift ;; + --runtime=*) RUNTIME="${1#*=}" ;; + --with-secrets) WITH_SECRETS="${2:?}"; shift ;; + --with-secrets=*) WITH_SECRETS="${1#*=}" ;; + --secret-file) SECRET_FILES+=("${2:?}"); shift ;; + --secret-file=*) SECRET_FILES+=("${1#*=}") ;; + --keep-container) KEEP_CONTAINER=true ;; + --artifact-dir) ARTIFACT_DIR="${2:?}"; shift ;; + --artifact-dir=*) ARTIFACT_DIR="${1#*=}" ;; + -h|--help) usage; exit 0 ;; + *) echo "ERROR: unknown option: $1" >&2; usage >&2; exit 2 ;; + esac + shift +done + +case "$RUNTIME" in claude|codex|kiro|all) ;; *) echo "ERROR: invalid --runtime: $RUNTIME" >&2; exit 2 ;; esac +case "$WITH_SECRETS" in off|auto|required) ;; *) echo "ERROR: invalid --with-secrets: $WITH_SECRETS" >&2; exit 2 ;; esac +if [ "$KEEP_CONTAINER" = true ] && [ "$WITH_SECRETS" != off ]; then + echo "ERROR: --keep-container cannot be used with --with-secrets=$WITH_SECRETS" >&2 + exit 2 +fi +command -v docker >/dev/null 2>&1 || { echo "ERROR: docker is required" >&2; exit 1; } + +mkdir -p "$ARTIFACT_DIR" +ARTIFACT_DIR="$(cd "$ARTIFACT_DIR" && pwd)" + +RUNTIMES=("$RUNTIME") +[ "$RUNTIME" = all ] && RUNTIMES=(claude codex kiro) + +RAW_SECRET_NAMES=( + ANTHROPIC_API_KEY + OPENAI_API_KEY + AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_SESSION_TOKEN + BIGQUERY_PROJECT + BIGQUERY_LOCATION + BIGQUERY_DATASET + REDASH_URL + REDASH_API_KEY +) +FILE_SECRET_ENV_NAMES=(GOOGLE_APPLICATION_CREDENTIALS BIGQUERY_KEY_FILE) +ALLOWLIST="$ROOT_DIR/tests/runtime-smoke/secrets-files.allowlist" + +validate_secret_key() { + local key="$1" + [[ "$key" =~ ^[A-Za-z0-9_.-]+$ ]] || return 1 + grep -Fxq "$key" "$ALLOWLIST" +} + +copy_secret_file() { + local cid="$1" + local src="$2" + local key="$3" + local base + base="$(basename "$src")" + tar -C "$(dirname "$src")" -cf - "$base" | + docker exec -i "$cid" tar -C /tmp/runtime-secrets -xf - + docker exec "$cid" sh -c 'mv "$1" "$2" && chmod 0444 "$2"' sh "/tmp/runtime-secrets/$base" "/tmp/runtime-secrets/$key" +} + +secret_count=0 +for name in "${RAW_SECRET_NAMES[@]}"; do + [ -n "${!name:-}" ] && secret_count=$((secret_count + 1)) +done +for name in "${FILE_SECRET_ENV_NAMES[@]}"; do + [ -n "${!name:-}" ] && secret_count=$((secret_count + 1)) +done +secret_count=$((secret_count + ${#SECRET_FILES[@]})) +if [ "$WITH_SECRETS" = required ] && [ "$secret_count" -eq 0 ]; then + echo "ERROR: --with-secrets=required but no allowlisted secrets were found" >&2 + exit 1 +fi + +build_images() { + docker build -q -f "$ROOT_DIR/tests/runtime-smoke/Containerfile.base" -t ai-plugins-runtime-smoke-base "$ROOT_DIR" >/dev/null + for runtime in "${RUNTIMES[@]}"; do + docker build -q -f "$ROOT_DIR/tests/runtime-smoke/Containerfile.$runtime" -t "ai-plugins-runtime-smoke-$runtime" "$ROOT_DIR" >/dev/null + done +} + +inject_secrets() { + local cid="$1" + [ "$WITH_SECRETS" = off ] && return 0 + + { + for name in "${RAW_SECRET_NAMES[@]}"; do + [ -n "${!name:-}" ] && printf 'export %s=%q\n' "$name" "${!name}" + done + } | docker exec -i "$cid" sh -c 'cat > /tmp/runtime-secrets/raw-env && chmod 0444 /tmp/runtime-secrets/raw-env' + + for name in "${FILE_SECRET_ENV_NAMES[@]}"; do + [ -n "${!name:-}" ] || continue + local src="${!name}" + [ -f "$src" ] || { echo "ERROR: $name points to a missing file" >&2; return 1; } + local key + key="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]' | tr '_' '-')" + validate_secret_key "$key" || { echo "ERROR: file secret key is not allowlisted: $key" >&2; return 1; } + copy_secret_file "$cid" "$src" "$key" + printf 'export %s=%q\n' "$name" "/tmp/runtime-secrets/$key" | + docker exec -i "$cid" sh -c 'cat >> /tmp/runtime-secrets/raw-env' + done + + for item in "${SECRET_FILES[@]}"; do + local key="${item%%=*}" + local src="${item#*=}" + [ "$key" != "$item" ] || { echo "ERROR: --secret-file must be KEY=PATH" >&2; return 1; } + validate_secret_key "$key" || { echo "ERROR: file secret key is not allowlisted: $key" >&2; return 1; } + [ -f "$src" ] || { echo "ERROR: secret file not found: $src" >&2; return 1; } + copy_secret_file "$cid" "$src" "$key" + done +} + +run_runtime() { + local runtime="$1" + local runtime_artifacts="$ARTIFACT_DIR/$runtime" + local cid="" + rm -rf "$runtime_artifacts" + mkdir -p "$runtime_artifacts" + + cid="$(docker run -d \ + --tmpfs /tmp/runtime-secrets:rw,noexec,nosuid,nodev,size=1m \ + -e HOME=/tmp/runtime-home \ + -e RUNTIME="$runtime" \ + -e WITH_SECRETS="$WITH_SECRETS" \ + -e REPO_ROOT=/workspace/ai-plugins \ + -e PROJECT_DIR=/tmp/runtime-project \ + -e ARTIFACT_DIR=/tmp/runtime-artifacts \ + "ai-plugins-runtime-smoke-$runtime" sleep infinity)" + if [ "$KEEP_CONTAINER" = false ]; then + trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' RETURN + else + echo "$cid" > "$runtime_artifacts/container-id.txt" + fi + + docker exec "$cid" mkdir -p /workspace/ai-plugins + tar \ + --exclude=.git \ + --exclude=.claude \ + --exclude=.codex \ + --exclude=.kiro \ + --exclude=tmp \ + -C "$ROOT_DIR" -cf - . | + docker exec -i "$cid" tar -C /workspace/ai-plugins -xf - + docker exec "$cid" bash -lc 'chmod -R a-w /workspace/ai-plugins' + inject_secrets "$cid" + docker exec "$cid" bash -lc 'mkdir -p "$HOME" "$PROJECT_DIR" "$ARTIFACT_DIR"; cd "$PROJECT_DIR"; "/workspace/ai-plugins/tests/runtime-smoke/adapters/${RUNTIME}.sh"' \ + >"$runtime_artifacts/smoke.log" 2>&1 + docker exec "$cid" bash -lc 'find /tmp/runtime-project -maxdepth 5 -print | sort > /tmp/runtime-artifacts/generated-tree.txt' + docker cp "$cid:/tmp/runtime-artifacts/." "$runtime_artifacts/" >/dev/null + if [ "$KEEP_CONTAINER" = false ]; then + docker rm -f "$cid" >/dev/null 2>&1 || true + fi +} + +build_images +for runtime in "${RUNTIMES[@]}"; do + echo "==> runtime smoke: $runtime" + run_runtime "$runtime" +done + +echo "runtime smoke tests passed: ${RUNTIMES[*]}" diff --git a/tests/runtime-smoke/Containerfile.base b/tests/runtime-smoke/Containerfile.base new file mode 100644 index 0000000..2ed7ef7 --- /dev/null +++ b/tests/runtime-smoke/Containerfile.base @@ -0,0 +1,9 @@ +FROM node:22-bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends bash ca-certificates curl git jq python3 findutils coreutils grep sed \ + && rm -rf /var/lib/apt/lists/* + +ENV HOME=/tmp/runtime-home +ENV PATH=/tmp/runtime-home/.local/bin:/root/.local/bin:/usr/local/bin:/usr/bin:/bin +WORKDIR /tmp/runtime-project diff --git a/tests/runtime-smoke/Containerfile.claude b/tests/runtime-smoke/Containerfile.claude new file mode 100644 index 0000000..ee68eec --- /dev/null +++ b/tests/runtime-smoke/Containerfile.claude @@ -0,0 +1,3 @@ +FROM ai-plugins-runtime-smoke-base + +RUN npm install -g @anthropic-ai/claude-code diff --git a/tests/runtime-smoke/Containerfile.codex b/tests/runtime-smoke/Containerfile.codex new file mode 100644 index 0000000..5edf2e3 --- /dev/null +++ b/tests/runtime-smoke/Containerfile.codex @@ -0,0 +1,3 @@ +FROM ai-plugins-runtime-smoke-base + +RUN curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh diff --git a/tests/runtime-smoke/Containerfile.kiro b/tests/runtime-smoke/Containerfile.kiro new file mode 100644 index 0000000..7e50ade --- /dev/null +++ b/tests/runtime-smoke/Containerfile.kiro @@ -0,0 +1,3 @@ +FROM ai-plugins-runtime-smoke-base + +RUN curl -fsSL https://cli.kiro.dev/install | bash || true diff --git a/tests/runtime-smoke/README.md b/tests/runtime-smoke/README.md new file mode 100644 index 0000000..821a168 --- /dev/null +++ b/tests/runtime-smoke/README.md @@ -0,0 +1,34 @@ +# Runtime Smoke Tests + +`scripts/runtime-smoke-test.sh` runs Claude, Codex, and Kiro plugin smoke tests in disposable Docker containers. The host home directory and credential directories are not mounted. + +```bash +bash scripts/runtime-smoke-test.sh +bash scripts/runtime-smoke-test.sh --runtime claude +bash scripts/runtime-smoke-test.sh --runtime codex +bash scripts/runtime-smoke-test.sh --runtime kiro +``` + +Artifacts are written to `tmp/runtime-smoke//` by default: + +- `smoke.log` +- `version.log` +- `generated-tree.txt` +- `junit.xml` + +Secret modes: + +- `--with-secrets=off`: PR-safe unauthenticated smoke. +- `--with-secrets=auto`: inject allowlisted secrets when present and skip authenticated checks when absent. +- `--with-secrets=required`: fail when no allowlisted secret exists. + +`--keep-container` is local-debug only and is rejected with `auto` or `required` secret modes. + +File secrets are passed with allowlisted keys: + +```bash +bash scripts/runtime-smoke-test.sh --runtime claude --with-secrets=auto \ + --secret-file bigquery-key-file=/path/to/service-account.json +``` + +The authenticated GitHub workflow accepts `BIGQUERY_KEY_FILE_JSON` as a protected secret and writes it to a temporary file before passing it through `--secret-file`. diff --git a/tests/runtime-smoke/adapters/claude.sh b/tests/runtime-smoke/adapters/claude.sh new file mode 100755 index 0000000..3576fe2 --- /dev/null +++ b/tests/runtime-smoke/adapters/claude.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +source /workspace/ai-plugins/tests/runtime-smoke/lib/common.sh + +cd "$PROJECT_DIR" +record_version claude --version +run_step "claude plugin validate ndf" claude plugin validate "$REPO_ROOT/plugins/ndf-claude" +run_step "claude marketplace validate" claude plugin validate "$REPO_ROOT/.claude-plugin/marketplace.json" +run_step "claude marketplace add" claude plugin marketplace add "$REPO_ROOT" --scope local +run_step "claude marketplace list" claude plugin marketplace list +run_step "claude install ndf" claude plugin install ndf@ai-plugins +run_step "claude install mcp-bigquery" claude plugin install mcp-bigquery@ai-plugins +run_step "claude plugin list" claude plugin list + +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-plugin-files.sh" claude +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-mcp-config.sh" claude "$REPO_ROOT/plugins/mcp/claude/mcp-bigquery/.mcp.json" +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-hook-fixtures.sh" claude +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh" claude +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-no-host-contamination.sh" claude +write_junit claude diff --git a/tests/runtime-smoke/adapters/codex.sh b/tests/runtime-smoke/adapters/codex.sh new file mode 100755 index 0000000..54c303d --- /dev/null +++ b/tests/runtime-smoke/adapters/codex.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +source /workspace/ai-plugins/tests/runtime-smoke/lib/common.sh + +cd "$PROJECT_DIR" +record_version codex --version +run_step "codex marketplace add" codex plugin marketplace add "$REPO_ROOT" +run_step "codex marketplace list" codex plugin marketplace list +run_step "codex install ndf" codex plugin add ndf@ai-plugins +run_step "codex install mcp-bigquery" codex plugin add mcp-bigquery@ai-plugins +run_step "codex plugin list" codex plugin list + +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-plugin-files.sh" codex +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-mcp-config.sh" codex "$REPO_ROOT/plugins/mcp/codex/mcp-bigquery/.mcp.json" +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-hook-fixtures.sh" codex +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh" codex +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-no-host-contamination.sh" codex +write_junit codex diff --git a/tests/runtime-smoke/adapters/kiro.sh b/tests/runtime-smoke/adapters/kiro.sh new file mode 100755 index 0000000..b7a0962 --- /dev/null +++ b/tests/runtime-smoke/adapters/kiro.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +source /workspace/ai-plugins/tests/runtime-smoke/lib/common.sh + +cd "$PROJECT_DIR" +if command -v kiro-cli >/dev/null 2>&1; then + record_version kiro-cli --help +else + echo "kiro-cli is not available; using installer fallback" | tee "$ARTIFACT_DIR/version.log" +fi + +run_step "kiro install ndf" bash "$REPO_ROOT/plugins/ndf-kiro/install.sh" --project "$PROJECT_DIR" +run_step "kiro install ndf idempotent" bash "$REPO_ROOT/plugins/ndf-kiro/install.sh" --project "$PROJECT_DIR" +run_step "kiro install mcp-bigquery" bash "$REPO_ROOT/plugins/mcp/kiro/mcp-bigquery/install.sh" --project "$PROJECT_DIR" +run_step "kiro install mcp-bigquery idempotent" bash "$REPO_ROOT/plugins/mcp/kiro/mcp-bigquery/install.sh" --project "$PROJECT_DIR" + +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-plugin-files.sh" kiro +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-mcp-config.sh" kiro "$PROJECT_DIR/.mcp.json" +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-hook-fixtures.sh" kiro +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh" kiro +"$REPO_ROOT/tests/runtime-smoke/assertions/assert-no-host-contamination.sh" kiro +write_junit kiro diff --git a/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh b/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh new file mode 100755 index 0000000..640005f --- /dev/null +++ b/tests/runtime-smoke/assertions/assert-authenticated-smoke.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${1:?runtime is required}" +: "${WITH_SECRETS:=off}" +: "${ARTIFACT_DIR:=/tmp/runtime-artifacts}" +: "${PROJECT_DIR:=/tmp/runtime-project}" + +log="$ARTIFACT_DIR/authenticated-smoke.log" + +if [ "$WITH_SECRETS" = off ]; then + echo "authenticated smoke skipped: --with-secrets=off" > "$log" + exit 0 +fi + +auth_ran=false + +run_bigquery_secret_check() { + [ -n "${BIGQUERY_PROJECT:-}" ] || return 1 + [ -n "${BIGQUERY_LOCATION:-}" ] || return 1 + [ -n "${BIGQUERY_DATASET:-}" ] || return 1 + [ -n "${BIGQUERY_KEY_FILE:-}" ] || return 1 + case "$BIGQUERY_KEY_FILE" in + /tmp/runtime-secrets/*) ;; + *) echo "BIGQUERY_KEY_FILE is not inside /tmp/runtime-secrets" >&2; return 1 ;; + esac + test -f "$BIGQUERY_KEY_FILE" + python3 - "$BIGQUERY_KEY_FILE" >> "$log" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +json.loads(path.read_text(encoding="utf-8")) +print("bigquery credential json parsed") +PY + auth_ran=true +} + +case "$runtime" in + claude) + if [ -n "${ANTHROPIC_API_KEY:-}" ]; then + timeout 60s claude -p "runtime smoke: reply OK only" >> "$log" 2>&1 + auth_ran=true + fi + run_bigquery_secret_check || true + ;; + codex) + if [ -n "${OPENAI_API_KEY:-}" ]; then + timeout 60s codex exec --dangerously-bypass-approvals-and-sandbox "Print OK only for runtime smoke." >> "$log" 2>&1 + auth_ran=true + fi + run_bigquery_secret_check || true + ;; + kiro) + if command -v kiro-cli >/dev/null 2>&1 && [ -n "${ANTHROPIC_API_KEY:-}${OPENAI_API_KEY:-}" ]; then + timeout 30s kiro-cli doctor >> "$log" 2>&1 || true + auth_ran=true + fi + run_bigquery_secret_check || true + ;; + *) echo "unknown runtime: $runtime" >&2; exit 2 ;; +esac + +if [ "$auth_ran" = false ]; then + if [ "$WITH_SECRETS" = required ]; then + echo "no runtime-usable authenticated smoke secret was available for $runtime" >&2 + exit 1 + fi + echo "authenticated smoke skipped: no runtime-usable secret for $runtime" > "$log" +fi diff --git a/tests/runtime-smoke/assertions/assert-hook-fixtures.sh b/tests/runtime-smoke/assertions/assert-hook-fixtures.sh new file mode 100755 index 0000000..da3cdf1 --- /dev/null +++ b/tests/runtime-smoke/assertions/assert-hook-fixtures.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${1:?runtime is required}" +: "${REPO_ROOT:=/workspace/ai-plugins}" +: "${PROJECT_DIR:=/tmp/runtime-project}" +: "${HOME:=/tmp/runtime-home}" + +case "$runtime" in + claude) + PLUGIN_ROOT="$REPO_ROOT/plugins/ndf-claude" CLAUDE_PLUGIN_ROOT="$REPO_ROOT/plugins/ndf-claude" \ + bash "$REPO_ROOT/plugins/ndf-claude/scripts/ensure-retention.sh" < "$REPO_ROOT/tests/runtime-smoke/fixtures/hook-session-start.json" + PLUGIN_ROOT="$REPO_ROOT/plugins/ndf-claude" CLAUDE_PLUGIN_ROOT="$REPO_ROOT/plugins/ndf-claude" \ + node "$REPO_ROOT/plugins/ndf-claude/scripts/slack-notify.js" session_end < "$REPO_ROOT/tests/runtime-smoke/fixtures/hook-stop.json" >/dev/null + ;; + codex) + PLUGIN_ROOT="$REPO_ROOT/plugins/ndf-codex" \ + node "$REPO_ROOT/plugins/ndf-codex/scripts/codex-slack-notify.js" < "$REPO_ROOT/tests/runtime-smoke/fixtures/hook-stop.json" >/dev/null + ;; + kiro) + test -f "$PROJECT_DIR/.kiro/agents/default.json" + jq -e '.hooks.agentSpawn[0].command' "$PROJECT_DIR/.kiro/agents/default.json" >/dev/null + ;; + *) echo "unknown runtime: $runtime" >&2; exit 2 ;; +esac diff --git a/tests/runtime-smoke/assertions/assert-mcp-config.sh b/tests/runtime-smoke/assertions/assert-mcp-config.sh new file mode 100755 index 0000000..bea3dec --- /dev/null +++ b/tests/runtime-smoke/assertions/assert-mcp-config.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${1:?runtime is required}" +config="${2:?config path is required}" + +python3 - "$runtime" "$config" <<'PY' +import json +import os +import re +import sys +from pathlib import Path + +runtime = sys.argv[1] +path = Path(sys.argv[2]) +data = json.loads(path.read_text(encoding="utf-8")) +text = json.dumps(data, ensure_ascii=False) +if "bigquery" not in text: + raise SystemExit(f"{runtime}: bigquery MCP config not found in {path}") +for name in ("BIGQUERY_PROJECT", "BIGQUERY_LOCATION", "BIGQUERY_DATASET", "BIGQUERY_KEY_FILE"): + if name not in text: + raise SystemExit(f"{runtime}: missing env placeholder {name}") + value = os.environ.get(name) + if value and len(value) >= 8 and value in text: + raise SystemExit(f"{runtime}: secret value leaked into {path}") +if "/tmp/runtime-secrets" in text: + raise SystemExit(f"{runtime}: runtime secret path leaked into {path}") +if not re.search(r"\$\{BIGQUERY_[A-Z_]+\}", text): + raise SystemExit(f"{runtime}: expected ${...} placeholders in {path}") +PY diff --git a/tests/runtime-smoke/assertions/assert-no-host-contamination.sh b/tests/runtime-smoke/assertions/assert-no-host-contamination.sh new file mode 100755 index 0000000..0a0b630 --- /dev/null +++ b/tests/runtime-smoke/assertions/assert-no-host-contamination.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${1:?runtime is required}" +: "${REPO_ROOT:=/workspace/ai-plugins}" +: "${PROJECT_DIR:=/tmp/runtime-project}" +: "${HOME:=/tmp/runtime-home}" + +case "$HOME" in + /tmp/runtime-home*) ;; + *) echo "$runtime: HOME is not isolated: $HOME" >&2; exit 1 ;; +esac +test "$PROJECT_DIR" = /tmp/runtime-project + +for path in "$REPO_ROOT/.claude" "$REPO_ROOT/.codex" "$REPO_ROOT/.kiro" "$REPO_ROOT/.mcp.json"; do + if [ -e "$path" ]; then + echo "$runtime: runtime config contaminated repo root: $path" >&2 + exit 1 + fi +done +for forbidden in /root/.claude /root/.codex /root/.kiro /root/.config /root/.aws /root/.ssh; do + if [ -e "$forbidden" ]; then + echo "$runtime: runtime touched forbidden host-like path: $forbidden" >&2 + exit 1 + fi +done +if find /tmp/runtime-artifacts -path '/tmp/runtime-artifacts/*runtime-secrets*' -print | grep -q .; then + echo "$runtime: secret path included in artifacts" >&2 + exit 1 +fi diff --git a/tests/runtime-smoke/assertions/assert-plugin-files.sh b/tests/runtime-smoke/assertions/assert-plugin-files.sh new file mode 100755 index 0000000..869fca1 --- /dev/null +++ b/tests/runtime-smoke/assertions/assert-plugin-files.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${1:?runtime is required}" +: "${REPO_ROOT:=/workspace/ai-plugins}" +: "${PROJECT_DIR:=/tmp/runtime-project}" +: "${HOME:=/tmp/runtime-home}" + +case "$runtime" in + claude) + find "$HOME" -path '*/.claude-plugin/plugin.json' -print | grep -q . + find "$HOME" -path '*/skills/*/SKILL.md' -print | grep -q . + find "$HOME" -path '*/agents/*.md' -print | grep -q . + find "$HOME" -path '*/hooks/hooks.json' -print | grep -q . + ;; + codex) + find "$HOME" -path '*/.codex-plugin/plugin.json' -print | grep -q . + find "$HOME" -path '*/skills/*/SKILL.md' -print | grep -q . + find "$HOME" -path '*/hooks/hooks.json' -print | grep -q . + ;; + kiro) + test -f "$PROJECT_DIR/.kiro/agents/default.json" + find -L "$PROJECT_DIR/.kiro/skills" -path '*/SKILL.md' -print | grep -q . + test -f "$PROJECT_DIR/.kiro/prompts/pr.md" + test -L "$PROJECT_DIR/.kiro/mcp_runtime/mcp-bigquery" + ;; + *) echo "unknown runtime: $runtime" >&2; exit 2 ;; +esac diff --git a/tests/runtime-smoke/fixtures/hook-session-start.json b/tests/runtime-smoke/fixtures/hook-session-start.json new file mode 100644 index 0000000..38731f2 --- /dev/null +++ b/tests/runtime-smoke/fixtures/hook-session-start.json @@ -0,0 +1,5 @@ +{ + "hook_event_name": "SessionStart", + "session_id": "runtime-smoke-session", + "cwd": "/tmp/runtime-project" +} diff --git a/tests/runtime-smoke/fixtures/hook-stop.json b/tests/runtime-smoke/fixtures/hook-stop.json new file mode 100644 index 0000000..d857c04 --- /dev/null +++ b/tests/runtime-smoke/fixtures/hook-stop.json @@ -0,0 +1,6 @@ +{ + "hook_event_name": "Stop", + "session_id": "runtime-smoke-session", + "cwd": "/tmp/runtime-project", + "transcript_path": "/tmp/runtime-project/transcript.jsonl" +} diff --git a/tests/runtime-smoke/fixtures/mcp-env.example b/tests/runtime-smoke/fixtures/mcp-env.example new file mode 100644 index 0000000..d4bf91d --- /dev/null +++ b/tests/runtime-smoke/fixtures/mcp-env.example @@ -0,0 +1,4 @@ +BIGQUERY_PROJECT=example-project +BIGQUERY_LOCATION=US +BIGQUERY_DATASET=example_dataset +BIGQUERY_KEY_FILE=/tmp/runtime-secrets/bigquery-key-file diff --git a/tests/runtime-smoke/lib/common.sh b/tests/runtime-smoke/lib/common.sh new file mode 100755 index 0000000..3504326 --- /dev/null +++ b/tests/runtime-smoke/lib/common.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${REPO_ROOT:=/workspace/ai-plugins}" +: "${PROJECT_DIR:=/tmp/runtime-project}" +: "${ARTIFACT_DIR:=/tmp/runtime-artifacts}" +: "${HOME:=/tmp/runtime-home}" + +export PATH="$HOME/.local/bin:/root/.local/bin:$PATH" + +mkdir -p "$ARTIFACT_DIR" +cd "$REPO_ROOT" + +if [ -f /tmp/runtime-secrets/raw-env ]; then + # shellcheck disable=SC1091 + source /tmp/runtime-secrets/raw-env +fi + +run_step() { + local name="$1" + shift + echo "==> $name" + "$@" +} + +record_version() { + echo "==> $*" | tee -a "$ARTIFACT_DIR/version.log" + "$@" 2>&1 | tee -a "$ARTIFACT_DIR/version.log" +} + +write_junit() { + local runtime="$1" + cat > "$ARTIFACT_DIR/junit.xml" < + + + +XML +} diff --git a/tests/runtime-smoke/secrets-files.allowlist b/tests/runtime-smoke/secrets-files.allowlist new file mode 100644 index 0000000..72c85f4 --- /dev/null +++ b/tests/runtime-smoke/secrets-files.allowlist @@ -0,0 +1,4 @@ +google-application-credentials +bigquery-key-file +aws-credentials +aws-config