先日、Kiroが一般提供開始(GA)となりました。プレビュー版公開初日にダウンロードして使っていた身からすると感慨深いものがあります。
本記事ではGAに合わせて紹介されていた新機能を見ていきたいと思います。
Kiroとは
改めてになりますが、Kiroとは仕様駆動開発(Spec-Driven Development, SDD)を支援するIDE(今回GAしたことによりCLIも提供)です。これまでAIを活用した開発というとVibe Codingのようにチャットベースで開発したいものを指示して作成するものが一般的でしたが、この仕様駆動開発ではコードを生成する前にまず要件(requirements)を作成し、次に設計(design)を行い、最後にタスク(task)分割を行います。その後、分割されたタスクを順次行い、コードが生成されていきます。
Kiroではこれらのワークフローを実行し、ステアリングやフックといった機能も備えています。
GAにより追加された機能
GA前にもリモートMCPサーバー対応などの機能が追加されていましたが、GA時の追加機能としては以下の内容です。
プロパティベーステスト
仕様に沿った実装がされているか?という問いに対して、その根拠になり得るテストがプロパティベーステスト(PBT)です。PBT自体はKiro登場以前から存在するテスト手法です。
プログラムにおけるテストは一般的に単体(ユニット)テストなどがありますが、あらかじめ決められたテストケースに対してテストを行うものです。対してPBTはさまざまな入力値の組み合わせを自動的に変えながらテストを行うテスト手法です。システムのあるべき姿に対して期待される動作(=プロパティ)ができているかを判定することから、プロパティベーステストと呼ばれています。
Kiroではrequirements.mdと同じように、EARS記法で記述されます。
試してみる
簡単な電卓ツール作成を依頼しました。
requirements.mdには特にこれまでと大きな変化は無いように見えます。

次にdesign.mdを作成してもらいました。眺めていると、PBTの要件になりそうな記述を発見しました。

日本語で出力しているので、少しわかりづらいですが、「正確性プロパティ」と呼ばれるものが今回の新機能に該当します。Kiroのドキュメントを見ると「Correctness」という単語がありますが、これが正確性に相当するものです。
例えば、要件3.1には下記のような記載があります。
### 要件3
**ユーザーストーリー:** ユーザーとして、イコールボタンをクリックして計算結果を取得したい。そうすることで入力した式の答えを確認できる。
#### 受入基準
1. WHEN ユーザーがEquals Buttonをクリックする, THE Calculator System SHALL 入力された数値と選択された演算に基づいて計算を実行する
(後略)
この要件に対するPBTの要件は下記のとおりです。
### プロパティ4: 四則演算の正確性
*任意の*2つの数値と演算子に対して、計算結果は数学的に正しい値と等しくなるべきです。
- 加算: a + b
- 減算: a - b
- 乗算: a * b
- 除算: a / b(b ≠ 0の場合)
**検証対象: 要件 3.1, 3.2**
このように、要件作成フェーズで作成された要件に対するテストの設計を行なってくれます。あらかじめテストケースを作成してから実装をするTDDに近しいものはありますが、前述のとおり要件ベースにテストケースを考えてくれる点が大きく異なります。
この調子でtasks.mdも作成してもらいます。
できたものを確認してみると、PBTだけでなくユニットテストも作成してくれそうです。
今回はTask1だけをPBTまで実装してもらいます。
ユニットテスト
/**
* Calculatorクラスのユニットテスト
* 要件: 1.1, 2.2, 3.1, 4.2
*/
// Calculatorクラスをインポート(Node.js環境用)
// ブラウザ環境では、calculator.jsを先に読み込む必要があります
/**
* テストヘルパー関数
*/
function assertEquals(actual, expected, message) {
if (actual !== expected) {
throw new Error(`${message}\n期待値: ${expected}\n実際の値: ${actual}`);
}
}
function assertNull(actual, message) {
if (actual !== null) {
throw new Error(`${message}\n期待値: null\n実際の値: ${actual}`);
}
}
/**
* テストスイート
*/
const CalculatorTests = {
/**
* clear()メソッドのテスト
*/
testClear() {
console.log('テスト: clear()メソッド');
const calc = new Calculator();
// 初期状態のテスト
assertEquals(calc.currentOperand, '', 'clear後: currentOperandは空文字列であるべき');
assertEquals(calc.previousOperand, '', 'clear後: previousOperandは空文字列であるべき');
assertNull(calc.operation, 'clear後: operationはnullであるべき');
// 値を設定してからクリア
calc.currentOperand = '123';
calc.previousOperand = '456';
calc.operation = '+';
calc.clear();
assertEquals(calc.currentOperand, '', 'clear後: currentOperandは空文字列であるべき');
assertEquals(calc.previousOperand, '', 'clear後: previousOperandは空文字列であるべき');
assertNull(calc.operation, 'clear後: operationはnullであるべき');
console.log('✓ clear()メソッドのテストが成功しました');
},
/**
* appendNumber()メソッドのテスト
*/
testAppendNumber() {
console.log('テスト: appendNumber()メソッド');
const calc = new Calculator();
// 単一の数字を追加
calc.appendNumber('5');
assertEquals(calc.currentOperand, '5', '数字5を追加後: currentOperandは"5"であるべき');
// 複数の数字を連続して追加
calc.appendNumber('3');
assertEquals(calc.currentOperand, '53', '数字3を追加後: currentOperandは"53"であるべき');
calc.appendNumber('7');
assertEquals(calc.currentOperand, '537', '数字7を追加後: currentOperandは"537"であるべき');
// 空の状態から数字を追加
calc.clear();
calc.appendNumber('0');
assertEquals(calc.currentOperand, '0', '空の状態から数字0を追加: currentOperandは"0"であるべき');
// 0から始まる数字列
calc.appendNumber('9');
assertEquals(calc.currentOperand, '09', '数字9を追加後: currentOperandは"09"であるべき');
console.log('✓ appendNumber()メソッドのテストが成功しました');
},
/**
* chooseOperation()メソッドのテスト
*/
testChooseOperation() {
console.log('テスト: chooseOperation()メソッド');
const calc = new Calculator();
// 数字を入力してから演算子を選択
calc.appendNumber('5');
calc.chooseOperation('+');
assertEquals(calc.operation, '+', '演算子選択後: operationは"+"であるべき');
assertEquals(calc.previousOperand, '5', '演算子選択後: previousOperandは"5"であるべき');
assertEquals(calc.currentOperand, '', '演算子選択後: currentOperandは空文字列であるべき');
// 空の状態で演算子を選択(無視されるべき)
calc.clear();
calc.chooseOperation('-');
assertNull(calc.operation, '空の状態で演算子選択: operationはnullのままであるべき');
assertEquals(calc.previousOperand, '', '空の状態で演算子選択: previousOperandは空文字列のままであるべき');
// 連続して演算子を選択(先に計算が実行される)
calc.clear();
calc.appendNumber('10');
calc.chooseOperation('+');
calc.appendNumber('5');
calc.chooseOperation('*');
assertEquals(calc.operation, '*', '2回目の演算子選択後: operationは"*"であるべき');
assertEquals(calc.previousOperand, '15', '2回目の演算子選択後: previousOperandは"15"(10+5の計算結果)であるべき');
assertEquals(calc.currentOperand, '', '2回目の演算子選択後: currentOperandは空文字列であるべき');
console.log('✓ chooseOperation()メソッドのテストが成功しました');
},
/**
* compute()メソッドのテスト
*/
testCompute() {
console.log('テスト: compute()メソッド');
const calc = new Calculator();
// 加算のテスト
calc.appendNumber('10');
calc.chooseOperation('+');
calc.appendNumber('5');
calc.compute();
assertEquals(calc.currentOperand, '15', '加算: 10 + 5 = 15');
assertEquals(calc.previousOperand, '', '計算後: previousOperandは空文字列であるべき');
assertNull(calc.operation, '計算後: operationはnullであるべき');
// 減算のテスト
calc.clear();
calc.appendNumber('20');
calc.chooseOperation('-');
calc.appendNumber('8');
calc.compute();
assertEquals(calc.currentOperand, '12', '減算: 20 - 8 = 12');
// 乗算のテスト
calc.clear();
calc.appendNumber('6');
calc.chooseOperation('*');
calc.appendNumber('7');
calc.compute();
assertEquals(calc.currentOperand, '42', '乗算: 6 * 7 = 42');
// 除算のテスト
calc.clear();
calc.appendNumber('15');
calc.chooseOperation('/');
calc.appendNumber('3');
calc.compute();
assertEquals(calc.currentOperand, '5', '除算: 15 / 3 = 5');
// ゼロ除算のテスト
calc.clear();
calc.appendNumber('10');
calc.chooseOperation('/');
calc.appendNumber('0');
calc.compute();
assertEquals(calc.currentOperand, 'Error', 'ゼロ除算: 結果は"Error"であるべき');
assertEquals(calc.previousOperand, '', 'ゼロ除算後: previousOperandは空文字列であるべき');
assertNull(calc.operation, 'ゼロ除算後: operationはnullであるべき');
// 小数を含む計算のテスト
calc.clear();
calc.appendNumber('2');
calc.appendDecimal();
calc.appendNumber('5');
calc.chooseOperation('+');
calc.appendNumber('1');
calc.appendDecimal();
calc.appendNumber('5');
calc.compute();
assertEquals(calc.currentOperand, '4', '小数の加算: 2.5 + 1.5 = 4');
// 無効な入力のテスト(previousOperandが空)
calc.clear();
calc.appendNumber('5');
calc.compute();
assertEquals(calc.currentOperand, '5', '無効な計算: currentOperandは変更されないべき');
console.log('✓ compute()メソッドのテストが成功しました');
},
/**
* 全てのテストを実行
*/
runAll() {
console.log('=== Calculatorクラスのユニットテスト開始 ===\n');
try {
this.testClear();
console.log('');
this.testAppendNumber();
console.log('');
this.testChooseOperation();
console.log('');
this.testCompute();
console.log('');
console.log('=== 全てのテストが成功しました ===');
return true;
} catch (error) {
console.error('テスト失敗:', error.message);
console.error(error.stack);
return false;
}
}
};
// Node.js環境での実行
if (typeof module !== 'undefined' && module.exports) {
module.exports = CalculatorTests;
}
// ブラウザ環境での実行
if (typeof window !== 'undefined') {
window.CalculatorTests = CalculatorTests;
}
ユニットテストを確認すると、10+5のテストを書いているように、あらかじめ決めた値をテストしています。
続いてはプロパティベーステスト。
プロパティベーステスト
/**
* Calculatorクラスのプロパティベーステスト
* fast-checkライブラリを使用
*/
const fc = require('fast-check');
// Calculatorクラスの定義
class Calculator {
constructor() {
this.clear();
}
clear() {
this.currentOperand = '';
this.previousOperand = '';
this.operation = null;
}
appendNumber(number) {
this.currentOperand = this.currentOperand + number.toString();
}
appendDecimal() {
if (this.currentOperand.includes('.')) {
return;
}
if (this.currentOperand === '') {
this.currentOperand = '0.';
} else {
this.currentOperand = this.currentOperand + '.';
}
}
chooseOperation(operation) {
if (this.currentOperand === '') {
return;
}
if (this.previousOperand !== '') {
this.compute();
}
this.operation = operation;
this.previousOperand = this.currentOperand;
this.currentOperand = '';
}
compute() {
let computation;
const prev = parseFloat(this.previousOperand);
const current = parseFloat(this.currentOperand);
if (isNaN(prev) || isNaN(current)) {
return;
}
switch (this.operation) {
case '+':
computation = prev + current;
break;
case '-':
computation = prev - current;
break;
case '*':
computation = prev * current;
break;
case '/':
if (current === 0) {
this.currentOperand = 'Error';
this.previousOperand = '';
this.operation = null;
return;
}
computation = prev / current;
break;
default:
return;
}
this.currentOperand = computation.toString();
this.operation = null;
this.previousOperand = '';
}
getCurrentDisplay() {
if (this.currentOperand === '') {
return '0';
}
return this.currentOperand;
}
}
/**
* Feature: calculator-tool, Property 1: 数字追加の累積性
* Validates: Requirements 1.1, 1.2
*
* 任意の初期表示状態と任意の数字に対して、その数字を追加した後の表示は、
* 元の表示にその数字を文字列として連結したものと等しくなるべきです。
*/
function testProperty1_NumberAppendAccumulation() {
console.log('プロパティベーステスト: プロパティ1 - 数字追加の累積性');
console.log('Feature: calculator-tool, Property 1: 数字追加の累積性');
console.log('Validates: Requirements 1.1, 1.2\n');
const result = fc.assert(
fc.property(
// 初期状態として0-9の数字の配列を生成
fc.array(fc.integer({ min: 0, max: 9 }), { minLength: 0, maxLength: 10 }),
// 追加する数字を生成
fc.integer({ min: 0, max: 9 }),
(initialNumbers, numberToAdd) => {
const calc = new Calculator();
// 初期状態を構築
let expectedDisplay = '';
for (const num of initialNumbers) {
calc.appendNumber(num);
expectedDisplay += num.toString();
}
// 現在の表示を記録
const currentDisplay = calc.currentOperand;
// 数字を追加
calc.appendNumber(numberToAdd);
// 期待される表示を計算
const expectedAfterAppend = currentDisplay + numberToAdd.toString();
// 実際の表示と期待される表示を比較
const actualDisplay = calc.currentOperand;
if (actualDisplay !== expectedAfterAppend) {
console.error(`失敗: 初期状態=[${initialNumbers}], 追加する数字=${numberToAdd}`);
console.error(`期待値: "${expectedAfterAppend}"`);
console.error(`実際の値: "${actualDisplay}"`);
return false;
}
return true;
}
),
{ numRuns: 100 } // 最低100回の反復を実行
);
console.log('✓ プロパティ1のテストが成功しました(100回の反復)\n');
return result;
}
/**
* Feature: calculator-tool, Property 4: 四則演算の正確性
* Validates: Requirements 3.1, 3.2
*
* 任意の2つの数値と演算子に対して、計算結果は数学的に正しい値と等しくなるべきです。
* - 加算: a + b
* - 減算: a - b
* - 乗算: a * b
* - 除算: a / b(b ≠ 0の場合)
*/
function testProperty4_ArithmeticAccuracy() {
console.log('プロパティベーステスト: プロパティ4 - 四則演算の正確性');
console.log('Feature: calculator-tool, Property 4: 四則演算の正確性');
console.log('Validates: Requirements 3.1, 3.2\n');
const result = fc.assert(
fc.property(
// 2つの数値を生成(-1000から1000の範囲)
fc.float({ min: -1000, max: 1000, noNaN: true }),
fc.float({ min: -1000, max: 1000, noNaN: true }),
// 演算子を生成
fc.constantFrom('+', '-', '*', '/'),
(num1, num2, operation) => {
const calc = new Calculator();
// 除算の場合、ゼロ除算を除外
if (operation === '/' && num2 === 0) {
// ゼロ除算の場合はエラーハンドリングをテスト
calc.appendNumber(num1.toString());
calc.chooseOperation('/');
calc.appendNumber('0');
calc.compute();
// エラー状態になることを確認
if (calc.currentOperand !== 'Error') {
console.error(`失敗: ゼロ除算でエラーが返されませんでした`);
console.error(`num1=${num1}, num2=0, operation=/`);
console.error(`実際の値: "${calc.currentOperand}"`);
return false;
}
return true;
}
// 計算機で計算を実行
calc.appendNumber(num1.toString());
calc.chooseOperation(operation);
calc.appendNumber(num2.toString());
calc.compute();
// 期待される結果を計算
let expected;
switch (operation) {
case '+':
expected = num1 + num2;
break;
case '-':
expected = num1 - num2;
break;
case '*':
expected = num1 * num2;
break;
case '/':
expected = num1 / num2;
break;
}
// 実際の結果を取得
const actual = parseFloat(calc.currentOperand);
// 浮動小数点の精度を考慮した比較
// 相対誤差が0.0001%未満であればOK
const tolerance = Math.abs(expected) * 0.000001 + 1e-10;
const difference = Math.abs(actual - expected);
if (difference > tolerance) {
console.error(`失敗: num1=${num1}, num2=${num2}, operation=${operation}`);
console.error(`期待値: ${expected}`);
console.error(`実際の値: ${actual}`);
console.error(`差分: ${difference}, 許容誤差: ${tolerance}`);
return false;
}
return true;
}
),
{ numRuns: 100 } // 最低100回の反復を実行
);
console.log('✓ プロパティ4のテストが成功しました(100回の反復)\n');
return result;
}
// テストを実行
try {
console.log('=== プロパティベーステスト開始 ===\n');
testProperty1_NumberAppendAccumulation();
testProperty4_ArithmeticAccuracy();
console.log('=== 全てのプロパティテストが成功しました ===');
process.exit(0);
} catch (error) {
console.error('プロパティテスト失敗:', error.message);
console.error(error.stack);
process.exit(1);
}
こちらのテストを眺めると、-1000から1000の範囲でランダムな2つの値を生成し、それに対して期待される計算結果と比較していることがわかります。(0除算の場合も想定してテストが書かれています。)
チェックポイント
チェックポイント機能は作成したコードや仕様の巻き戻し機能です。ここまでの画像にも一部映っていましたが、「Checkpoint」の文字が見えます。
この部分のRestoreを押すと、削除されるファイルのガイダンスが表示され、Confirmすれば削除ができます。
マルチルートワークスペースサポート
既存のプロジェクトとは違うプロジェクトを、同じウィンドウで開くことができます。Macの場合、メニューバーからファイル>フォルダーをワークスペースに追加...で他のプロジェクトを開くことができます。この時、ステアリングルールやフックなどが共有されており、どちらのプロジェクトにも適用されていそうです。
Kiro CLI
KiroといえばこれまではIDEでしたが、CLIツールも追加されました。
Macの場合は下記コマンドでインストールできます。
curl -fsSL https://cli.kiro.dev/install | bash
Amazon Q CLIがインストールされていると特別(?)なメッセージが表示されます。
$ curl -fsSL https://cli.kiro.dev/install | bash
Kiro CLI installer:
Detected existing Amazon Q CLI installation
Kiro CLI is the new version of Amazon Q CLI. Updating to latest version.
██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ 100/100
🎉 Installation complete! Happy coding!
Next steps:
Use the command "kiro-cli" to get started!
Kiro CLIの利用には別途認証が必要になります。
チーム向けKiro
最後にチーム向けKiroです。AWSコンソール上からもKiroが選択できるようになっています。
コンソール自体はバージニア北部かフランクフルトのみで提供されています。

コンソール上からKiroをEnableすることでUserの追加ができるようになります。(別途AWS IAM Identity Centerを有効にする必要があります)
AWSアカウントに登録されているユーザーであること、Proプラン以上を選択することが条件になります。
まとめ
ついにGAとなったKiroですが、これからもアップデートは続いていきそうですね。皆さんも良き仕様駆動開発ライフを!
参考









