0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kiro Skills でELT単体テストをスキル化してみた(Redshift Serverless)

0
Posted at

背景・目的

普段からKiroを使っているが、Skills機能についてはあまり触ってきませんでした。
チームメンバーが作成したスキルを見る機会があり、これを機に概念を整理し、実際に自分でスキルを作って動かしてみます。

まとめ

特徴 説明
Skillsとは 構造化されたプロンプトを再利用可能な単位としてパッケージ化したもの
配置場所 .kiro/skills/ (ワークスペース) または ~/.kiro/skills/ (グローバル)
必須ファイル SKILL.md のみ。references/ や assets/ はオプション
呼び出し方 自動発動(descriptionマッチ)またはスラッシュコマンド(/スキル名
本質 チームの暗黙知をAIが実行可能な形式知にしたもの

概要

Skillsとは

下記を基に整理する。

Skillsは、AIへの指示(プロンプト)を再利用可能な単位としてパッケージ化したものである。

Skills are portable instruction packages that extend what Kiro knows how to do. Each skill contains instructions for a specific workflow, whether that's reviewing pull requests, deploying infrastructure, or following your team's coding standards.

通常、AIに作業を依頼する際は毎回プロンプトを手書きする。Skillsを使うと、「いつ発動するか」「何を入力とするか」「どう処理するか」「どう出力するか」をファイルに定義しておき、決まったフレーズやスラッシュコマンドで呼び出せる。

下記に筆者の理解としてプロンプトとスキルの違いを整理する。

プロンプト スキル
定義 AIへの指示文 プロンプトを再利用可能な単位としてパッケージ化したもの
構成 自由記述 発動条件・インプット・実行手順・出力フォーマットをセットにしたもの
作成 その場で都度書く 事前に定義しておく
利用 書いた本人がその場で使う チーム内の誰でも同じフレーズで呼び出せる
共有 コピペ .kiro/skills/ に置いてリポジトリにコミット

一言で言えば、「毎回プロンプトを書き直さなくていいように、チームで共有できる形にしたプロンプト」である。

なお、Agent Skills は Kiro 独自の機能ではなく、Anthropic が開発しオープンスタンダードとして公開した仕様である。同じ SKILL.md フォーマットが複数のコードエージェントで利用できる。

エージェント 提供元 Skills対応
Kiro AWS
Claude Code Anthropic ✅(発祥)
Codex OpenAI
VS Code (Copilot) Microsoft/GitHub

スキルの構成

下記を基に整理する。

スキルは SKILL.md を含むディレクトリである。

skill-name/
├── SKILL.md          # 必須:メタデータ + 手順
├── scripts/          # 任意:実行可能コード
├── references/       # 任意:追加ドキュメント
├── assets/           # 任意:テンプレート等
ディレクトリ 役割
SKILL.md スキルの定義本体。frontmatter(メタデータ)+ Markdownの手順
scripts/ エージェントが実行できるコード。Python, Bash, JavaScript等
references/ エージェントが必要に応じて読む追加ドキュメント
assets/ テンプレート、画像、データファイル等の静的リソース

Progressive Disclosure(段階的読み込み)

Agent Skills の設計の核心は、コンテキストウィンドウを節約しつつ多数のスキルを登録できる仕組みにある。

  1. Discovery(起動時): namedescription だけ読む(~100トークン)
  2. Activation(発動時): SKILL.md 本文を全て読む(5000トークン以下推奨)
  3. Execution(実行時): scripts/ references/ assets/ を必要に応じて読む

このため、SKILL.md は 500行以下に抑え、詳細なリファレンスは references/ に分離することが推奨されている。

SKILL.md のフォーマット

SKILL.md は YAML frontmatter + Markdown 本文で構成される。

Frontmatter

フィールド 必須 制約
name 64文字以内。小文字英数字+ハイフンのみ。ハイフン始まり/終わり不可。親ディレクトリ名と一致する必要あり
description 1024文字以内。何をするか+いつ使うかを記述。エージェントがタスクマッチに使う
license - ライセンス名またはライセンスファイルへの参照
compatibility - 500文字以内。環境要件(必要なツール、ネットワークアクセス等)
metadata - 任意のキーバリュー(author, version等)
allowed-tools - スペース区切りの事前承認ツール一覧(実験的)

最小構成の例

---
name: run-elt-test
description: ELTの単体テストを実行する。テスト実行を依頼されたときに使う。
---

オプションフィールドを含む例

---
name: pdf-processing
description: Extract PDF text, fill forms, merge files. Use when handling PDFs.
license: Apache-2.0
compatibility: Requires Python 3.14+ and uv
metadata:
  author: example-org
  version: "1.0"
---

本文(Body)

frontmatter の後に Markdown で手順を記述する。フォーマットに制約はなく、エージェントがタスクを実行するのに役立つ内容を書く。推奨セクション:

  • ステップバイステップの手順
  • 入出力の例
  • よくあるエッジケース

ファイル参照

スキル内の他ファイルを参照する場合は、スキルルートからの相対パスを使う。

詳細は [リファレンスガイド](references/REFERENCE.md) を参照。

抽出スクリプトを実行:
scripts/extract.py

配置と呼び出し

下記を基に整理する。

配置場所

場所 スコープ 用途
.kiro/skills/ ワークスペース プロジェクト固有のワークフロー、チーム規約
~/.kiro/skills/ グローバル 個人用ワークフロー(全プロジェクト共通)

同名のスキルがある場合、ワークスペース側が優先される。

呼び出し方

スキルは2通りの方法で呼び出せる。

1. 自動発動

Kiroがユーザーのリクエストとスキルの description を照合し、マッチしたスキルを自動でロードする。

> ELTテスト実行して

I'll run the ELT test using the test script...

2. スラッシュコマンド

/スキル名 で明示的に呼び出す。name フィールドがそのままコマンド名になる。

> /run-elt-test rep001 tc1

I'll run the ELT test for rep001/tc1...

スラッシュコマンドの後に続けたテキストは、スキルへの引数として渡される。

確認方法

現在ロードされているスキル一覧は /context show で確認できる。

> /context show

使い方のパターン

パターン 複雑さ
スクリプト実行 「テスト実行して」→ シェルスクリプト実行 ★☆☆
コード生成 「CRUDのAPI作って」→ テンプレートに沿って生成 ★★☆
チェックリスト 「レビュー前チェック」→ 観点に沿って検査・指摘 ★★☆
ワークフロー 「PRレビューして」→ 情報収集→判断→レポート出力 ★★★
ドキュメント生成 「設計書書いて」→ 規約に沿った文書作成 ★★☆
ナレッジ参照 「このエラーの対処法は?」→ 蓄積知識から回答 ★☆☆

実践

ELTの単体テストを実行するシェルスクリプトをスキル化し、Kiroから /run-elt-test で呼び出せるようにする。
スキルの作成から実行までの一連の流れを確認することが目的である。
Redshift Serverless を構築し、Redshift Data API 経由で実際に SQL 実行・結果比較まで行う。

前提

  • Kiro CLI がインストール済み
  • 作業ディレクトリにプロジェクトが存在する

今回下記のような構成で検証する。

プロジェクト/
├── .kiro/
│   └── skills/
│       └── run-elt-test/
│           ├── SKILL.md
│           └── scripts/
│               └── run_elt_test.sh
├── ddl/
│   ├── sales.sql
│   └── daily_sales_summary.sql
├── sql/
│   └── daily_sales_summary.sql
└── testdata/
    └── rep001/
        └── tc1/
            ├── input.csv
            └── expected.csv

Redshift Serverlessの構築

AWSにRedshift Serverlessを構築します

1. cdk-stack.tsを用意します

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as redshiftserverless from 'aws-cdk-lib/aws-redshiftserverless';

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const namespace = new redshiftserverless.CfnNamespace(this, 'Namespace', {
      namespaceName: 'skills-lab-ns',
      dbName: 'skillsdb',
      adminUsername: 'admin',
      manageAdminPassword: true,
    });

    const workgroup = new redshiftserverless.CfnWorkgroup(this, 'Workgroup', {
      workgroupName: 'skills-lab-wg',
      namespaceName: namespace.attrNamespaceNamespaceName,
      baseCapacity: 4,
      publiclyAccessible: false,
    });

    workgroup.addDependency(namespace);
  }
}

2. cdk.tsを用意します

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib/core';
import { Aspects } from 'aws-cdk-lib/core';
import { AwsSolutionsChecks } from 'cdk-nag';
import { CdkStack } from '../lib/cdk-stack';

const app = new cdk.App();
new CdkStack(app, 'SkillsLabRedshiftStack', {
  env: { region: 'ap-northeast-1' },
});

Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));

3. テストクラス用意します

import * as cdk from 'aws-cdk-lib/core';
import { Template } from 'aws-cdk-lib/assertions';
import { CdkStack } from '../lib/cdk-stack';

describe('SkillsLabRedshiftStack', () => {
  const app = new cdk.App();
  const stack = new CdkStack(app, 'TestStack');
  const template = Template.fromStack(stack);

  test('Namespace が作成される', () => {
    template.hasResourceProperties('AWS::RedshiftServerless::Namespace', {
      NamespaceName: 'skills-lab-ns',
      DbName: 'skillsdb',
      AdminUsername: 'admin',
      ManageAdminPassword: true,
    });
  });

  test('Workgroup が作成される', () => {
    template.hasResourceProperties('AWS::RedshiftServerless::Workgroup', {
      WorkgroupName: 'skills-lab-wg',
      BaseCapacity: 4,
      PubliclyAccessible: false,
    });
  });

  test('Workgroup は Namespace に依存する', () => {
    template.hasResource('AWS::RedshiftServerless::Workgroup', {
      DependsOn: ['Namespace'],
    });
  });
});

4. テストをします

npx jest
PASS test/cdk.test.ts (7.84 s)
  SkillsLabRedshiftStack
    ✓ Namespace が作成される (7 ms)
    ✓ Workgroup が作成される
    ✓ Workgroup は Namespace に依存する

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        8.004 s
Ran all test suites.
 - Completed in 9.495s

> 全テスト通りました。

✓ Namespace が作成される
✓ Workgroup が作成される
✓ Workgroup は Namespace に依存する

5. デプロイします

npx cdk deploy --require-approval never

6. デプロイが成功しました
image.png

DDLの作成

下記のようなDDLを配置します

ddl/
├── sales.sql                  ← ソーステーブル
└── daily_sales_summary.sql    ← ELT結果テーブル

1. ソーステーブルを作成します


CREATE TABLE IF NOT EXISTS public.sales (
  sale_date DATE,
  product VARCHAR(50),
  quantity INT,
  unit_price INT
);


2. ELT結果テーブルを作成します

CREATE TABLE IF NOT EXISTS public.daily_sales_summary (
  sale_date DATE,
  product VARCHAR(50),
  total_quantity INT,
  total_amount INT
);

3. DDLを作成します

SQL=$(cat ddl/sales.sql ddl/daily_sales_summary.sql)
aws redshift-data execute-statement \
  --workgroup-name skills-lab-wg \
  --database skillsdb \
  --sql "$SQL" \
  --region ap-northeast-1 \
  --output text --query 'Id'

4. 確認します。成功しました

✓ Trusted: AWS_PROFILE=XXXXXXX aws redshift-data describe-statement \
  --id XXXXXXX \
  --region ap-northeast-1 --query '[Status, Error]' --output json
[
    "FINISHED",
    null
]

5. 下記が出来ていることがわかりました
image.png

image.png

DMLの作成

1. sql/ の下に集計用のクエリを用意します

INSERT INTO public.daily_sales_summary
SELECT
  sale_date,
  product,
  SUM(quantity) AS total_quantity,
  SUM(quantity * unit_price) AS total_amount
FROM public.sales
GROUP BY sale_date, product;

スキルの作成

SKILL.md

1. 下記を用意します。

---
name: run-elt-test
description: ELTの単体テストを実行する。テスト実行、ELTテスト、単体テストを依頼されたときに使う。
---

# ELT単体テスト実行

## 手順

1. ユーザーから REPORT_ID と テストケース名(TC)を受け取る
2. `scripts/run_elt_test.sh <REPORT_ID> <TC>` を実行する
3. 結果を確認し、成功/失敗を報告する

## 使い方

/run-elt-test rep001 tc1

## 補足

- テストデータは `testdata/{REPORT_ID}/{TC}/` に配置する
- 期待値は `expected.csv`、実績は `actual.csv` として出力される
- 本番環境では Redshift Serverless に接続して SQL を実行する

scripts

1. .kiro/skills/run-elt-test/scripts/run_elt_test.sh を作成する。

.kiro/skills/run-elt-test/scripts/run_elt_test.sh を作成する。Redshift Data API を使い、テストデータのロード → ELT SQL実行 → 結果比較を行う

#!/bin/bash
# ELT単体テスト実行スクリプト(Redshift Data API版)
# Usage: ./run_elt_test.sh <REPORT_ID> <TC>
# 例: ./run_elt_test.sh rep001 tc1
#
# 環境変数:
#   AWS_PROFILE: AWSプロファイル(必須)
#   WORKGROUP:   Redshift Serverless ワークグループ(デフォルト: skills-lab-wg)
#   DATABASE:    データベース名(デフォルト: skillsdb)
#   REGION:      AWSリージョン(デフォルト: ap-northeast-1)

set -euo pipefail

REPORT_ID="${1:?Usage: $0 <REPORT_ID> <TC> (例: rep001 tc1)}"
TC="${2:?Usage: $0 <REPORT_ID> <TC> (例: rep001 tc1)}"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
TESTDATA_DIR="$PROJECT_ROOT/testdata"
SQL_DIR="$PROJECT_ROOT/sql"
TC_DIR="$TESTDATA_DIR/${REPORT_ID}/${TC}"

WORKGROUP="${WORKGROUP:-skills-lab-wg}"
DATABASE="${DATABASE:-skillsdb}"
REGION="${REGION:-ap-northeast-1}"
PROFILE="${AWS_PROFILE:?環境変数 AWS_PROFILE を設定してください}"

echo "=========================================="
echo "ELT単体テスト: ${REPORT_ID} / ${TC}"
echo "WORKGROUP: $WORKGROUP"
echo "DATABASE:  $DATABASE"
echo "=========================================="

# 存在チェック
[[ -d "$TC_DIR" ]] || { echo "❌ テストケースが見つかりません: $TC_DIR"; exit 1; }
[[ -f "$TC_DIR/expected.csv" ]] || { echo "❌ expected.csv が見つかりません"; exit 1; }

# SQL実行関数(ポーリング付き)
exec_sql() {
  local label="$1"
  local sql="$2"
  echo ""
  echo "--- $label ---"
  local RESULT
  RESULT=$(aws redshift-data execute-statement \
    --workgroup-name "$WORKGROUP" \
    --database "$DATABASE" \
    --sql "$sql" \
    --profile "$PROFILE" \
    --region "$REGION" \
    --output json)
  local STMT_ID
  STMT_ID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Id'])")
  while true; do
    local STATE
    STATE=$(aws redshift-data describe-statement --id "$STMT_ID" \
      --profile "$PROFILE" --region "$REGION" --query 'Status' --output text)
    if [[ "$STATE" == "FINISHED" ]]; then
      echo "  ✅ $STATE"
      echo "$STMT_ID"
      return 0
    elif [[ "$STATE" == "FAILED" ]]; then
      local ERR
      ERR=$(aws redshift-data describe-statement --id "$STMT_ID" \
        --profile "$PROFILE" --region "$REGION" --query 'Error' --output text)
      echo "  ❌ $STATE: $ERR"
      return 1
    fi
    sleep 2
  done
}

# Step 1: TRUNCATE
echo ""
echo "===== Step 1: 環境クリア ====="
exec_sql "TRUNCATE sales" "TRUNCATE TABLE public.sales;" > /dev/null
exec_sql "TRUNCATE daily_sales_summary" "TRUNCATE TABLE public.daily_sales_summary;" > /dev/null

# Step 2: テストデータロード
echo ""
echo "===== Step 2: テストデータロード ====="
INPUT_CSV="$TC_DIR/input.csv"
[[ -f "$INPUT_CSV" ]] || { echo "❌ input.csv が見つかりません"; exit 1; }

INSERT_SQL=$(python3 -c "
import csv
with open('$INPUT_CSV', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    cols = reader.fieldnames
    rows = list(reader)
if not rows:
    exit(0)
col_list = ', '.join(cols)
values_list = []
for row in rows:
    vals = []
    for c in cols:
        v = row[c]
        if v == '\\\\N':
            vals.append('NULL')
        else:
            vals.append(\"'\" + v.replace(\"'\", \"''\") + \"'\")
    values_list.append('(' + ', '.join(vals) + ')')
print(f'INSERT INTO public.sales ({col_list}) VALUES ' + ', '.join(values_list) + ';')
")

exec_sql "INSERT sales" "$INSERT_SQL" > /dev/null

ROW_COUNT=$(python3 -c "
import csv
with open('$INPUT_CSV', 'r', encoding='utf-8') as f:
    print(sum(1 for _ in csv.DictReader(f)))
")
echo "  sales: ${ROW_COUNT} rows loaded"

# Step 3: ELT SQL実行
echo ""
echo "===== Step 3: ELT SQL実行 ====="
ELT_SQL=$(cat "$SQL_DIR/daily_sales_summary.sql")
exec_sql "daily_sales_summary.sql" "$ELT_SQL" > /dev/null

# Step 4: 結果比較
echo ""
echo "===== Step 4: 結果比較 ====="
EXPECTED_CSV="$TC_DIR/expected.csv"
COLUMNS=$(head -1 "$EXPECTED_CSV")

SELECT_SQL="SELECT ${COLUMNS} FROM public.daily_sales_summary ORDER BY 1,2;"
RESULT=$(aws redshift-data execute-statement \
  --workgroup-name "$WORKGROUP" \
  --database "$DATABASE" \
  --sql "$SELECT_SQL" \
  --profile "$PROFILE" \
  --region "$REGION" \
  --output json)
STMT_ID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Id'])")

while true; do
  STATE=$(aws redshift-data describe-statement --id "$STMT_ID" \
    --profile "$PROFILE" --region "$REGION" --query 'Status' --output text)
  if [[ "$STATE" == "FINISHED" ]]; then break; fi
  if [[ "$STATE" == "FAILED" ]]; then echo "  ❌ SELECT failed"; exit 1; fi
  sleep 2
done

ACTUAL_CSV="$TC_DIR/actual.csv"
aws redshift-data get-statement-result --id "$STMT_ID" \
  --profile "$PROFILE" --region "$REGION" --output json \
| python3 -c "
import csv, sys, json

data = json.load(sys.stdin)
columns = [c['name'] for c in data['ColumnMetadata']]
rows = []
for record in data['Records']:
    row = {}
    for i, col in enumerate(columns):
        cell = record[i]
        if 'isNull' in cell and cell['isNull']:
            val = ''
        elif 'stringValue' in cell:
            val = cell['stringValue']
        elif 'longValue' in cell:
            val = str(cell['longValue'])
        elif 'doubleValue' in cell:
            val = str(cell['doubleValue'])
        else:
            val = ''
        row[col] = val.strip()
    rows.append(row)

with open('$ACTUAL_CSV', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=columns, lineterminator='\n')
    writer.writeheader()
    writer.writerows(rows)

print(f'  実績: {len(rows)}件')
"

# 比較
python3 -c "
import csv, sys

def load_csv(path):
    with open(path, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        fields = [c.strip() for c in reader.fieldnames]
        rows = sorted([{k.strip(): v.strip() for k, v in r.items()} for r in reader], key=lambda r: tuple(r.values()))
    return rows, fields

expected, fields = load_csv('$EXPECTED_CSV')
actual, _ = load_csv('$ACTUAL_CSV')

print(f'  期待件数: {len(expected)}')
print(f'  実績件数: {len(actual)}')

if len(expected) != len(actual):
    print('  ❌ 件数不一致')
    sys.exit(1)

errors = []
for i, (e, a) in enumerate(zip(expected, actual)):
    for col in fields:
        ev = str(e.get(col, '')).strip()
        av = str(a.get(col, '')).strip()
        if ev != av:
            errors.append(f'    行{i+1} [{col}]: 期待=\"{ev}\" 実績=\"{av}\"')

if errors:
    print(f'  ❌ {len(errors)}件の差分:')
    for err in errors:
        print(err)
    sys.exit(1)
else:
    print(f'  ✅ 全{len(expected)}行×{len(fields)}列 一致(テスト成功)')
"

テストデータの準備

testdata/rep001/tc1/ に配置する。

input.csv(salesテーブルへの投入データ)

sale_date,product,quantity,unit_price
2026-01-01,商品A,3,1000
2026-01-01,商品A,2,1000
2026-01-01,商品B,1,2000
2026-01-02,商品A,5,1000
2026-01-02,商品B,3,2000

expected.csv(期待値)

sale_date,product,total_quantity,total_amount
2026-01-01,商品A,5,5000
2026-01-01,商品B,1,2000
2026-01-02,商品A,5,5000
2026-01-02,商品B,3,6000

実行

テストケース1

  1. 下記を実行します
7% > run-elt-test rep001 tc1

Running tool SkillsTool with the param (from mcp server: builder-mcp){"skillName": "run-elt-test"}

Allow this action? Use 't' to trust (always allow) the 'SkillsTool' tool for the session. [y/n/t]:

9% > t

 - Completed in 0.471s

> The skill tool doesn't have run-elt-test registered, but there's a skill file at the path indicated. Let me read it 
directly.
Reading file: /XXXXX/XXXXX/XXXXX/XXXXX/XXXXX/skills/.kiro/skills/run-elt-test/SKILL.md, all lines (using tool: read)
 ✓ Successfully read 907 bytes from /XXXXX/XXXXX/XXXXX/XXXXX/XXXXX/skills/.kiro/skills/run-elt-test/SKILL.md
 - Completed in 0.1s

> スキルの手順に従い、テストスクリプトを実行します。
I will run the following command: .kiro/skills/run-elt-test/scripts/run_elt_test.sh rep001 tc1 (using tool: shell)
Purpose: ELT単体テスト rep001 tc1 を実行


Allow this action? Use 't' to trust (always allow) the 'shell' tool for the session. [y/n/t]:

9% > t

✓ Trusted: .kiro/skills/run-elt-test/scripts/run_elt_test.sh rep001 tc1

==========================================
ELT単体テスト: rep001 / tc1
WORKGROUP: skills-lab-wg
DATABASE:  skillsdb
==========================================

===== Step 1: 環境クリア =====

===== Step 2: テストデータロード =====
  sales: 5 rows loaded

===== Step 3: ELT SQL実行 =====

===== Step 4: 結果比較 =====
  実績: 4件
  期待件数: 4
  実績件数: 4
  ✅ 全4行×4列 一致(テスト成功)
 - Completed in 47.856s

> テスト成功 ✅

- **REPORT_ID**: rep001
- **テストケース**: tc1
- テストデータ(sales): 5行ロード
- 実績: 4件、期待値と全4行×4列が一致

テストはすべてパスしました。

 ▸ Time: 1m 12s

9% > 


2. 念のため2つのファイルを確認します

% ls -l           
total 24
-rw-r--r--@ 1 XXXXX  XXXXX  150  5 30 21:40 actual.csv
-rw-r--r--@ 1 XXXXX  XXXXX  150  5 30 20:52 expected.csv
-rw-r--r--@ 1 XXXXX  XXXXX  168  5 30 20:52 input.csv
% diff expected.csv actual.csv
% 

テストケース2

1. 失敗するケースを実行してみます。

run-elt-test rep001 tc2

テストを実行して確認します。
I will run the following command: .kiro/skills/run-elt-test/scripts/run_elt_test.sh rep001 tc2 2>&1 || true (using tool: shell)
Purpose: ELT単体テスト rep001 tc2 を実行(エラー出力も取得)


Allow this action? Use 't' to trust (always allow) the 'shell' tool for the session. [y/n/t]:

10% > t

✓ Trusted: true

==========================================
ELT単体テスト: rep001 / tc2
WORKGROUP: skills-lab-wg
DATABASE:  skillsdb
==========================================

===== Step 1: 環境クリア =====

===== Step 2: テストデータロード =====
  sales: 2 rows loaded

===== Step 3: ELT SQL実行 =====

===== Step 4: 結果比較 =====
  実績: 2件
  期待件数: 2
  実績件数: 2
  ❌ 1件の差分:
    行2 [total_amount]: 期待="9999" 実績="2000"
 - Completed in 15.942s

> テスト失敗 ❌

今回はテスト自体は最後まで実行されましたが、結果比較で差分が検出されました。

結果:
- 期待件数: 2件 / 実績件数: 2件(件数は一致)
- **差分**: 行2 の total_amount — 期待値 9999、実績 2000

ELT SQL の集計ロジックか、テストデータ(input.csv / expected.csv)の期待値に問題がある可能性があります。テストデータと SQL
を確認しますか?

 ▸ Time: 28s

10% > 

2. ファイルを比較します

% ls -ltr
total 24
-rw-r--r--@ 1 XXXXXXX  XXXXXXX  90  5 30 21:47 input.csv
-rw-r--r--@ 1 XXXXXXX  XXXXXXX  98  5 30 21:47 expected.csv
-rw-r--r--@ 1 XXXXXXX  XXXXXXX  98  5 30 21:47 actual.csv
% diff expected.csv actual.csv
3c3
< 2026-01-01,商品B,1,9999
---
> 2026-01-01,商品B,1,2000
% 

考察

Skills は「チームの暗黙知をAIが実行可能な形式知にする」仕組みである。スクリプト自体は従来通り書けばよく、SKILL.md はそのスクリプトへの「入口」を定義するだけ。導入コストは低い。

ただし、Kiroは指示を「文字通り」ではなく「意図」で解釈するため、確実にスクリプトを実行させたい場合は強制的な表現が必要になる。この点はプロンプトエンジニアリングと同じ感覚で調整する。

スキル化して何が嬉しかったか

観点 スキル化前 スキル化後
実行方法 スクリプトのパスを覚えて手動実行 /run-elt-test rep001 tc1 で完結
引数の説明 READMEを読む or スクリプト冒頭のコメントを見る SKILL.md に使い方が書いてあり、Kiroが案内してくれる
結果の解釈 ターミナル出力を自分で読む Kiroが成功/失敗を判定し、差分があれば原因を示唆してくれる
チーム共有 「このスクリプトこう使って」と口頭で伝える リポジトリにコミットすれば誰でも同じ体験

一番の価値は「スクリプトの存在を知らなくても、自然言語で呼び出せる」こと。新メンバーが「ELTテスト実行して」と言えば動く。

どういう業務に向いているか

  • 繰り返し実行する定型作業(テスト実行、デプロイ、データ投入)
  • 手順が決まっているが引数が毎回変わる作業(レポートID × テストケースの組み合わせ)
  • 結果の判定ロジックがある作業(diff、閾値チェック、ステータス確認)

逆に向かないのは、毎回やることが違う探索的な作業や、人間の判断が多く入る作業。

ハマりポイント

問題 原因 対策
No such file or directory SKILL.md 内のパスがスキルルート相対だが、Kiroはプロジェクトルートから実行する プロジェクトルートからのフルパスで記述する
スクリプトが実行されず diff だけで済まされる Kiroは「目的達成」を優先し、既存の actual.csv があるとスクリプトをスキップする 必ずスクリプトを実行すること」と明記 + actual.csv を削除
SKILL.md の変更が反映されない セッション中はキャッシュされる セッションを再起動する
環境変数未設定でスクリプトが失敗 set -euo pipefail + ${AWS_PROFILE:?...} SKILL.md の前提条件に環境変数を明記する

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?