「本当に動いている?」が確かめられるようになる
「このファイル、本当に読んでいる?」「テストはあるけど、ちゃんと動いている?」。AI の作業を支えるスキルや workflow、実行設定といったハーネスを直していると、こうした点が気になることがある。
何を確かめたいかによって、見る場所は変わる。
- スキルが、指示したファイルを本当に読んだか → 実行記録に、そのファイルを読んだ操作が残っているか
- AI への依頼に、伝えたい条件がちゃんと入っているか → AI に送った依頼文そのもの
- 用意したテストが、本当に実行されたか → CI の実行履歴に、そのテストが出ているか
そのうち、コードで繰り返し確かめられる部分をテストにしておく。ハーネスを変えるたびに「今回も大丈夫かな」と一から調べ直す手間を減らせる。
そもそもworkflowって何?
Claude Code の Dynamic workflows は、複数の AI エージェントへの仕事の割り振りや実行順序を JavaScript で制御する機能だ。ファイルを分担して調べたり、修正と確認を繰り返したりできる。公式ドキュメントに、使い方とコードの例がある。
スキルからレビュー用のworkflowを呼ぶ例
例えば、「この基準でファイルをレビューして」と頼めるスキルを作る。レビュー基準は別ファイルの review-rules.md に書いておく。スキルがそのファイルを読み、対象ファイルと一緒に workflow へ渡す構成だ。workflow のスクリプト自体はファイルを直接読めないので、読むのはスキル側の仕事になる。
.claude/skills/review-files/
├── SKILL.md
├── review-rules.md
├── workflow.js
└── workflow.test.cjs
SKILL.md は次のようになる。最後の workflow.test.cjs は、この記事で追加するテスト用だ。
---
name: review-files
description: 指定されたファイルを分担してレビューし、問題点を報告する。
---
1. ユーザーが指定したレビュー対象のファイルを確認する。
2. このスキルと同じディレクトリの review-rules.md を読み込む。
3. 同じディレクトリの workflow.js を Workflow ツールで実行する。
対象ファイルのパスを args.files に配列で渡す。
読み込んだレビュー基準の全文を args.reviewRules に文字列で渡す。
4. 返ってきたレビュー結果を、ファイルごとに整理して報告する。
実行に失敗した場合は、レビューが完了していないことを伝える。
レビュー基準の review-rules.md は、例えば次の内容にする。
# レビュー基準
- 入力が空の場合にも、意図した動きになるか確認する。
- エラーを握りつぶして、成功したように見せていないか確認する。
/review-files a.js b.js と依頼すると、Claude がこの基準を読み、対象ファイルと一緒に次の workflow.js へ渡す。workflow は、各 AI への依頼文に基準を入れる。
export const meta = {
name: "review-files",
description: "ファイルごとにレビューし、結果を集める",
};
if (!args?.reviewRules?.trim()) {
throw new Error("レビュー基準が渡されていません");
}
if (!Array.isArray(args.files) || args.files.length === 0) {
throw new Error("レビュー対象のファイルが渡されていません");
}
const reviews = await pipeline(args.files, file =>
agent(
args.reviewRules + "\n\nこの基準で " + file + " をレビューしてください。",
{ label: file }
)
);
if (reviews.some(review => review === null)) {
throw new Error("未完了のレビューがあります");
}
return reviews;
このコードは、同じ基準でファイルごとにレビューし、結果を集める。次の場合は、成功扱いにせずエラーで止まる。
- 基準やレビュー対象が渡されていない
- 途中で止まった AI がいる(
agent()は、止められたりエラーが起きたりするとnullを返す)
各関数の仕様は公式のコード例と説明を参照。
スキルで仕事の進め方を伝え、その中の繰り返しや判定をコードに任せる、という関係だ。この配置はスキルから呼ぶ例で、公式の保存機能では .claude/workflows/ に保存してコマンドとして再利用することもできる。
どうテストを書くのか?
変更するたびに AI を動かし、結果を目で確かめるだけではもったいない。
先ほどのレビュー用の例なら、読み込んだレビュー基準を各 AI に忘れずに渡したい。AI を呼ぶ関数を差し替えれば、実際に送る依頼文を手元で確かめられる。
スキルが呼ぶ workflow を、テストから呼び出す
普段は、Claude がスキルの指示に従って基準ファイルを読み、workflow.js を呼び出す。テストでは、その準備をテストコードが代わりに行い、同じ workflow.js に基準と対象ファイルを渡して実行する。
workflow 内の agent() も、テスト用の関数に置き換える。この関数は AI に依頼を送る代わりに、依頼文を記録し、決めておいた返答を返す。記録した依頼文を見れば、各 AI に基準を渡そうとしているかが分かる。
例えば workflow.js から基準を渡す処理が抜ければ、記録した依頼文にも基準が入らなくなり、テストで気づける。
テストは workflow.test.cjs の 1 ファイルにまとめる。前半の run() が、AI の代わりに依頼文を記録しながら workflow を動かす関数で、後半がテスト本体だ。
const { test } = require("node:test");
const assert = require("node:assert/strict");
const { readFileSync } = require("node:fs");
// workflow.js を読み込み、agent と pipeline を引数で受け取る関数にする
const rules = readFileSync(`${__dirname}/review-rules.md`, "utf8");
const source = readFileSync(`${__dirname}/workflow.js`, "utf8").replace("export ", "");
const AsyncFunction = (async () => {}).constructor;
const workflow = new AsyncFunction("args", "agent", "pipeline", source);
// AI を呼ばずに動かし、送った依頼文を返す
async function run(args, reply = () => "レビュー結果") {
const prompts = [];
const agent = async (prompt, { label }) => {
prompts.push(prompt);
return reply(label);
};
// 本物と同じく、失敗した項目は null にして続ける
const pipeline = (items, task) =>
Promise.all(items.map(item => task(item).catch(() => null)));
await workflow(args, agent, pipeline);
return prompts;
}
const files = ["a.js", "b.js"];
test("各AIへの依頼にレビュー基準が入る", async () => {
const prompts = await run({ files, reviewRules: rules });
assert.equal(prompts.length, 2);
for (const prompt of prompts) {
assert.ok(prompt.includes(rules), "レビュー基準が依頼文にありません");
}
});
test("レビュー基準がなければ止まる", () =>
assert.rejects(run({ files, reviewRules: "" }), /レビュー基準が渡されていません/));
test("レビュー対象がなければ止まる", () =>
assert.rejects(run({ files: [], reviewRules: rules }), /レビュー対象のファイルが渡されていません/));
test("未完了のレビューがあれば止まる", () =>
assert.rejects(
run({ files, reviewRules: rules }, file => (file === "b.js" ? null : "レビュー結果")),
/未完了のレビューがあります/
));
run() の中の agent は、AI を呼ぶ代わりに依頼文を prompts に残し、決まった返答を返す。reply を渡せば、特定のファイルだけ結果が返らない(null)状況も作れる。
pipeline も差し替えるが、本物と同じく、失敗した項目を null にして続ける形にしておく。
テストは 4 つ。1 つ目は、レビュー基準が両方の AI への依頼に入るか。残りの 3 つは、基準がないとき、対象がないとき、結果を返さない AI がいるときに止まるか。
このテストで分かるのは、渡された基準を workflow が依頼文に含めることまでだ。ファイルの読み込みはテスト側で代行しているため、Claude が SKILL.md の指示どおりに読んだことまでは確認できない。そこは実際にスキルを動かし、読み込みの実行記録と workflow に渡した値を確認する。AI が基準を守ってレビューできるかは、後半の「AIへの指示や、使うモデルを見直すとき」のように、実際に AI を動かして確かめる。
また、run() が渡しているのは args・agent・pipeline だけだ。phase() や log() を使う workflow なら、それらも差し替えて渡す。本物の実行環境で使えない Date.now() なども、Node.js 上では動いてしまうので、テストでは気づけない。
テストを実行してみる
上の構成でファイルを保存し、そのディレクトリから次を実行する。Node.js 標準のテスト機能を使うので、追加パッケージは不要だ。
node workflow.test.cjs
実行結果は次のとおり。時間などの表示は省略している。
✔ 各AIへの依頼にレビュー基準が入る
✔ レビュー基準がなければ止まる
✔ レビュー対象がなければ止まる
✔ 未完了のレビューがあれば止まる
ℹ tests 4
ℹ pass 4
ℹ fail 0
次は、わざと間違えてみる。workflow からレビュー基準を送る部分を削り、ただ「このファイルをレビューしてください」とだけ頼むようにした。
agent(
file + " をレビューしてください。",
{ label: file }
)
スキルは基準を読んでいても、レビューする AI に届くのは「a.js をレビューしてください」という依頼だけになる。「入力が空の場合も確認してほしい」と書いた基準は、どこにも入っていない。
これでもレビュー結果は返ってくるかもしれない。だから、結果が返ることだけを確認しても、渡し忘れには気づけない。依頼文の中身を見るテストが、ここで失敗する。
✖ 各AIへの依頼にレビュー基準が入る
✔ レビュー基準がなければ止まる
✔ レビュー対象がなければ止まる
✔ 未完了のレビューがあれば止まる
ℹ tests 4
ℹ pass 3
ℹ fail 1
AssertionError [ERR_ASSERTION]: レビュー基準が依頼文にありません
依頼文に基準を戻せば、また 4 件とも通る。ほかの判定(基準の確認、対象の確認、null の確認)を 1 つずつ消した場合も、対応するテストが失敗することを確かめた。スキルに「基準を読む」と書いてあっても、その先で渡し忘れることはある。こうしたつなぎ目を、AI を動かす前に確かめられる。
テストが求められる場面
workflowのコードを変更したとき
レビュー対象の分け方や条件分岐、結果のまとめ方を変えたら、それまで守っていた動作が変わっていないかを見るために、テストを回す。
手元だけでなく、PR を出したときに GitHub Actions で自動実行すると、確認漏れを減らせる。実行対象のパス設定は GitHub Actions の公式ドキュメントで確認できる。
以前、GitHub Actions で「どのファイルが変わったらテストを動かすか」を絞りすぎたことがある。workflow を直しても、そのファイルが対象に入っていなかったため、用意したテストは一度も動いていなかった。
そこで、.claude/ 以下のファイルや GitHub Actions の設定を変えたときにもテストが動くようにした。設定を追加しただけで安心せず、実行履歴を開いて、目的のテストが走ったことまで確認している。
スキルを改修したとき
参照ファイルの場所や、workflow の呼び出し方を変えたときにも確認したい。例えばレビュー基準を別のフォルダに移したら、スキルが移動先を読み、その内容を workflow に渡せているかを見る。
この記事のテストではスキル自体を動かしていないため、ここは実際にスキルを実行して確かめる。workflow のテストが通っていても、呼び出す側でファイルの場所を間違えていれば、基準は届かない。
処理を速くしたり、コストを減らしたりしたとき
複数の処理をまとめる、AI を呼ぶ回数を減らす、といった変更の後にもテストを回す。速くなった理由が、必要な確認まで省いてしまったからでは困る。
同じ対象を使い、変更前後で必要な処理と結果が保たれているかを確かめる。実際の時間やトークン数、AI の出力への影響は、実行して比較する。
AIへの指示や、使うモデルを見直すとき
同じ課題を試せば、指示やモデルを変えた前後の成果を比べられる。例えば、既知の不具合があるコードをレビューさせて、変更後もその不具合を指摘できるかを見る。
モデルを変えるなら指示を、指示を変えるならモデルをそろえる。作業開始時のファイルも同じ状態にする。これは実際に AI を動かす確認なので、この記事のコードのテストとは別に行う。
この記事で使うスキル・workflow・テストのサンプル一式は、GitHub リポジトリに置いときます。
参考
- skill-creator の SKILL.md:スキル評価で、変更前後の成果を比べる進め方
- aggregate_benchmark.py:評価結果を集計するコード