Kiro × Slackで仕様のすり合わせを簡単に

はじめに

プロダクト開発では、PO・エンジニア・デザイナーなど複数のステークホルダー間で仕様の認識を揃えることが不可欠です。認識が揃っていないまま開発が進むと、手戻りによるスケジュール遅延や、考慮漏れによる品質低下につながります。しかし、多忙なチームではミーティングの頻度を上げることが難しく、週に一度程度しか全員が揃わないケースも珍しくありません。そうした環境では、ミーティング後に仕様の抜け漏れに気づいても、すぐに追加のミーティングを設定できず、Slack 上で補足の議論を行うことになります。

そこで、なるべくミーティング中に仕様の認識を揃えられるよう、Slack のスレッド内で /kiroコマンドを実行するだけで、スレッドの文脈から構造化された仕様書の叩き台を自動生成できる Bot を考案しました。本記事では、技術選定や実装の詳細について説明します。

概要

アーキテクチャ

各コンポーネントの役割

1. Slack App

  • /kiro Slash Command を設定し、ALB のエンドポイントへ POST
  • スレッドでコマンドを実行すると Slack が自動で thread_tsを付与

2. Internet Gateway + Application Load Balancer

  • Internet Gateway: VPC とインターネット間の通信を可能にする
  • ALB: Public Subnet に配置されインターネットからのリクエストを受け付ける
  • 受信したリクエストを Private Subnet の Fargate タスクへ転送

3. Fargate

  • Hono + Node.js サーバーを常駐
  • Slash Command の処理から Kiro CLI 呼び出し、Slack API への返信までを同期的に実行

4. Amazon EFS

  • /root/.kiro をマウントし、Kiro CLI の認証情報を永続化
  • タスク再起動後もログインが不要

5. NAT Gateway

  • Private Subnet の Fargate から **アウトバウンド通信** を可能にする
  • Slack Web API (`conversations.replies`, `chat.postMessage`) へのアクセスに使用

6. Kiro CLI

  • kiro-cli-chat--no-interactiveオプションで起動
  • スレッドの文脈 + 追加指示を含むプロンプトを渡して仕様書を生成

7. Slack Web API

  • conversations.repliesでスレッドの文脈を取得
  • 生成された仕様書を chat.postMessageで同じスレッドに返信

実装

Slash Command ハンドラ

app.post('/slack/commands', async (req, res) => {
  const payload = parseSlackPayload(req.body);

  if (!payload.thread_ts) {
    return res.json({
      response_type: 'ephemeral',
      text: 'このコマンドはスレッド内で実行してください'
    });
  }

  // スレッドの文脈を取得
  const slackClient = new WebClient(process.env.SLACK_BOT_TOKEN);
  const messages = await fetchThreadMessages(
    slackClient,
    payload.channel_id,
    payload.thread_ts
  );

  // Kiro CLI 用のプロンプトを構築
  const context = formatThreadContext(messages);
  const prompt = buildSpecificationPrompt(context, payload.text);

  // Kiro CLI で仕様書を生成(同期処理)
  const spec = await generateSpecification(prompt);

  // 同じスレッドに返信
  await slackClient.chat.postMessage({
    channel: payload.channel_id,
    thread_ts: payload.thread_ts,
    text: spec,
    mrkdwn: true,
  });

  // 処理完了後に 200 を返す
  res.json({
    response_type: 'ephemeral',
    text: '仕様書を生成しました'
  });
});
  • Fargate 上で常駐するため、Slash Command のレスポンス前に仕様生成まで完了できる
  • thread_tsが無い場合はエラーを返し、運用ルールとして「スレッドで /kiro を実行する」ことを徹底

Kiro CLI 呼び出し

async function generateSpecification(prompt: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const cli = spawn('/usr/local/bin/kiro-cli-chat', [
      'chat',
      '--no-interactive',
      '--wrap=never',
      prompt,
    ], {
      env: { ...process.env, HOME: '/root' },
    });

    let output = '';
    cli.stdout.on('data', (data) => {
      output += data.toString();
    });

    cli.on('close', (code) => {
      if (code === 0) {
        const cleanOutput = stripAnsiCodes(output);
        const spec = extractSpecification(cleanOutput);
        resolve(spec);
      } else {
        reject(new Error(`Kiro CLI failed with code ${code}`));
      }
    });
  });
}
  • HOMEを EFS のマウント先に設定し、認証状態を共有
  • CLI からの出力は ANSI コードを削除してから Slack へ投稿

EFS への認証情報マウント

CloudFormation設定

MountPoints:
  - SourceVolume: kiro-auth
    ContainerPath: /root/.kiro
    ReadOnly: false

Volumes:
  - Name: kiro-auth
    EFSVolumeConfiguration:
      FilesystemId: !Ref KiroAuthFileSystem
      TransitEncryption: ENABLED
      AuthorizationConfig:
        AccessPointId: !Ref KiroAuthAccessPoint

初回ログイン

# ECS Exec でコンテナに接続
aws ecs execute-command \
  --cluster slack-spec-bot-fargate \
  --task <task-id> \
  --container app \
  --interactive \
  --command "/bin/bash"

# Kiro CLI にログイン
kiro-cli-chat login --use-device-flow

初回のみ kiro-cli-chat login --use-device-flowを実行すれば、その後は再ログインが不要になります。

つまづいたポイント

Bad file descriptorエラー: EFS × flock

最大のハマりどころは Kiro CLI が設定ファイルを flock しようとして EFS で失敗する点でした。

用語

  • Bad file descriptor: ファイルの扱い方が間違っているというエラーメッセージ。
  • EFS (Amazon Elastic File System): AWS が提供する、ネットワーク経由で使えるファイルストレージ。複数のサーバーから同じファイルにアクセスできる便利なサービス。
  • flock(ファイルロック): 複数のプログラムが同時に同じファイルを書き換えないようにする仕組み。「今このファイルは自分が使っているから、他の人は待ってね」という札を立てるイメージ。

何が起きたのか

「EBADF error (Bad file descriptor) 」が発生しました。これは、Kiro CLI(コマンドラインツール)が Amazon EFS 上でファイルをロック(占有)しようとした時に起きる問題です。

なぜ起きたのか

  • flockはLinuxのファイルロック機能で、BSDロックと呼ばれる方式。プロセスAがflockを呼び、カーネルがファイルにロック済みマークをつけると、プロセスBはプロセスAがファイルを閉じるかロックを解除するまで待たなければならない。この仕組みはローカルのカーネル内で完結する。
  • NFS(Network File System) はネットワーク越しにファイルを共有する仕組み。複数のサーバーが同じファイルにアクセスできる。

Amazon EFS は NFSv4.1 ベースで、POSIX 的な fcntlロック(byte-range locks)はサポートしますが、Linux カーネル側での flock() の扱いはローカル FS と同一とは限りません。今回の環境では、ローカルディスク上では問題なく動いていた同じコードが、EFS 上のファイルに対してだけ flock(fd, LOCK_EX) で EBADF を返す挙動になっていました。

内部実装までは追いきれていませんが、少なくとも「EFS 上の設定ファイルを直接 flock する」前提では Kiro CLI を安定稼働させられない、ということがわかりました。

対処法

対処法は「flockを使わないよう CLI を修正する」か「対象ファイルを NFS 以外(tmpfs やローカル)へ移す」のどちらかです。今回は後者を選択し、EFS → /tmpへコピーして CLI を実行しています。

Node.js サービス内で次のような仕組みを入れ、CLI からは常にローカルファイルシステムを参照させています。

// 起動時
await initializeKiroHome(); // /root/.kiro → /tmp/kiro-local をコピー
process.env.KIRO_HOME = '/tmp/kiro-local';

// CLI 実行後
await syncKiroHomeToPersistent(); // /tmp → /root/.kiro を再同期

これで flockの対象が EFS からローカルに変わり、Bad file descriptor が消えました。

実際の動作

Slack内での仕様の相談をイメージして実演してみました。

Slackへの投稿

スレッドで会話

/kiroを実行

Kiroによる仕様書の叩き全文

# リアルタイム分析ダッシュボード 仕様書

## 機能概要
スプリント中のチームがKPIをリアルタイムで追跡できる分析ダッシュボードを管理画面に追加します。カード型ウィジェットで主要指標を可視化し、チーム・リージョン・期間でフィルタリング可能。閾値超過時にはSlack通知を送信します。

## ゴール / 非ゴール

### ゴール
- チームメンバーがスプリント中にKPIを一目で把握できる
- 1分以内の遅延でリアルタイムにデータを更新する
- チーム/リージョン/期間の組み合わせでフィルタリングし、検索条件を保存できる
- エラー率などの重要指標が閾値を超えた際に自動でSlack通知を送る
- 将来的に新しいKPIを追加しやすい拡張可能な設計にする

### 非ゴール
- 過去データの詳細分析やレポート出力機能(既存の分析ツールで対応)
- ダッシュボードのカスタマイズ機能(ウィジェットの並び替えや追加/削除)
- リアルタイム通知以外のアラート手段(メール、SMS等)

## ユーザーストーリー
- **As a** プロダクトマネージャー, I want スプリント中のKPIをリアルタイムで確認できる, so that 迅速に意思決定し、問題に早期対応できる
- **As a** 開発チームリーダー, I want 自分のチームだけのデータにフィルタリングできる, so that チーム固有のパフォーマンスを追跡できる
- **As a** βテスター, I want よく使うフィルタ条件を保存できる, so that 毎回同じ条件を入力する手間を省ける
- **As a** オペレーションチーム, I want エラー率が閾値を超えたらSlackで通知を受け取る, so that 即座に障害対応を開始できる

## 画面・フロー

### 画面構成
管理画面
└── Analytics タブ(新規追加)
    ├── フィルタエリア
    │   ├── チーム選択(ドロップダウン)
    │   ├── リージョン選択(ドロップダウン)
    │   ├── 期間選択(日付ピッカー)
    │   └── 保存ボタン / 保存済み条件ロード
    └── KPIカードエリア(グリッドレイアウト)
        ├── DAU カード
        ├── セッション長 カード
        ├── エラー率 カード(閾値超過時バッジ表示)
        └── 完了率 カード

### 操作フロー
1. ユーザーが管理画面の「Analytics」タブをクリック
2. デフォルトで全チーム・全リージョン・直近24時間のKPIが表示される
3. フィルタを変更すると、リアルタイムでカードが更新される
4. 「保存」ボタンでフィルタ条件を保存(ローカルストレージまたはユーザー設定API)
5. 次回訪問時に保存済み条件をロード可能
6. エラー率が閾値を超えると、カードにバッジが表示され、Slack通知が送信される

## API / データモデル

### APIエンドポイント
GET /api/analytics/kpis
  Query params: team_id, region, start_date, end_date
  Response: { dau, avg_session_length, error_rate, completion_rate, last_updated }
GET /api/analytics/filters
  Response: { saved_filters: [...] }
POST /api/analytics/filters
  Body: { name, team_id, region, date_range }
  Response: { filter_id }
WebSocket /ws/analytics
  Subscribe: { team_id, region }
  Push: KPI更新イベント

### データフロー
Kafka イベント → Redis(キャッシュ) → API → WebSocket → フロントエンド(SWR)
                                      ↓
                                  閾値チェック → Slack Webhook

### データモデル(Redis)
Key: analytics:{team_id}:{region}:{date}
Value: {
  dau: number,
  avg_session_length: number,
  error_rate: number,
  completion_rate: number,
  updated_at: timestamp
}

### 設定管理(config)
yaml
thresholds:
  error_rate: 5.0  # パーセント
  completion_rate: 80.0
slack:
  webhook_url: <WEBHOOK_URL>
  channel: "#alerts"

## オープンな論点
1. フィルタ条件の保存先: ユーザー設定をローカルストレージに保存するか、バックエンドのユーザープロファイルAPIで管理するか?複数デバイス間での同期が必要か?
2. KPIの優先順位と初期表示: 4つのKPI(DAU、セッション長、エラー率、完了率)以外に初期リリースで追加すべき指標はあるか?カードの表示順序に優先度はあるか?
3. 閾値の設定権限: エラー率などの閾値は誰が設定・変更できるべきか?管理者のみか、チームリーダーも変更可能にするか?UI上で変更可能にするか、config管理のみか?
4. Slack通知の頻度制御: 同じ閾値超過が継続する場合、通知を何分おきに送るか?通知の重複を防ぐクールダウン期間は必要か?
5. リージョンとチームの定義: 「リージョン」と「チーム」の具体的な値はどこから取得するか?既存のユーザー管理システムやプロジェクト管理ツールと連携するか、新規にマスタデータを作成するか?

まとめ

今回は、Slack と Kiro を組み合わせ、Slack のスレッド内で /kiro コマンドを実行するだけで、そのスレッドの文脈から構造化された仕様書の叩き台を自動生成できる Bot を設計しました。
この仕組みにより、Slack 上の議論から即座に仕様化へ移行できる体験を実現できました。
一方で、Kiro を継続的に利用するためには Fargate・ALB・EFS・NAT Gateway などの常駐インフラが必要となり、運用コストが高くなる点が懸念として残ります。今後は、より軽量かつ低コストで運用できるアーキテクチャ(API Key ベースの認証提供や、Lambda での実行が可能になる構成)を検討していきたいと思います。