TL;DR
- kaji v0.18.0のlocal providerを、外部GitHubへ変更を送らない分離環境で検証した
-
design -> review-design -> baseline -> implement -> review-code -> final-check -> closeを実行した - 初回は
pytest-xdist不足、2回目は.kaji-artifacts/によるdirty worktreeでABORTした - 環境を修正し、
--from baselineで再開できた - 最終的に既存9件と新規3件の計12テスト、ruff、format、mypyがPASSした
- feature branchはmainへ非FFマージされ、local Issue、worktree、branchもクローズ・削除された
- 今回確認したのは外部I/Oのない小さなPython関数1件であり、本番運用の安全性は未検証
対象読者
- kajiのlocal providerを小さく試したい方
- AI coding agentの設計・実装・レビューを明示的なworkflowにしたい方
- baseline gateとstep-level resumeの実際の挙動を知りたい方
検証環境
検証日は2026年8月19日です。
| 項目 | 値 |
|---|---|
| OS | Windows 11上のWSL2 Ubuntu |
| kaji | 0.18.0 |
| Python | 3.12.3 |
| uv | 0.9.2 |
| tmux | 3.4 |
| Claude Code | 2.1.218 |
| provider | local |
| model | Claude Sonnet |
| workflow | .kaji/wf/custom/dev/dev-local-lab.yaml |
| Issue | local-lab1-1 |
外部starter remoteはfetch専用のupstreamとし、push URLをDISABLEDにしました。originはWSL内のlocal bare repositoryです。GitHub Issue、Pull Request、外部repositoryへのpushは行っていません。
検証課題
starter_appへ次の公開関数を追加します。
def greet(name: str) -> str:
...
完了条件は次のとおりです。
greet("海") == "こんにちは、海さん"- 名前の前後の空白を除去する
- 空白除去後に空なら
ValueErrorを送出する - 正常系、空白除去、異常系をSmallテストで確認する
- 既存の
helloと__version__を維持する -
make checkを通す
workflow
検証用workflowでは、各review/fix/verify loopの最大反復回数を1に制限しました。
実行コマンドは次のとおりです。
uv run kaji run .kaji/wf/custom/dev/dev-local-lab.yaml local-lab1-1
今回使用した主なstepは次のとおりです。
| step | 役割 |
|---|---|
design |
実装前の設計とテスト戦略を作る |
review-design |
provenance、完了条件、テスト戦略を確認する |
baseline |
変更前commitのpytest結果を記録する |
implement |
テストを先に追加して実装する |
review-code |
diff、設計整合、独立テストを確認する |
final-check |
make checkと前段証跡を再確認する |
close |
mainへマージし、Issueとworktreeを片付ける |
1回目のABORT:pytest -nを認識できない
初回runではdesignとreview-designはPASSしましたが、baselineでABORTしました。
stderrは次の内容です。
ERROR: usage: python -m pytest [options] [file_or_dir] [...]
python -m pytest: error: unrecognized arguments: -n
verdictに記録された主な値は次のとおりです。
baseline_status=invalid
pytest_exit_code=4
failures=0
failures=0でも、pytestの終了コード4はテスト成功ではありません。テスト実行方法が不正なため、baselineはinvalidとして停止しました。
原因は、baselineが並列実行用の-nを使用する一方、dev依存にpytest-xdistがなかったことです。
pyproject.tomlへ次を追加しました。
[dependency-groups]
dev = [
"pytest>=8.0.0",
"pytest-xdist>=3.6.0",
]
uv lockとuv syncの後、次を実行しました。
uv run pytest -n auto -q
変更前の9テストがすべてPASSしました。
2回目のABORT:.kaji-artifacts/でdirty worktreeになる
依存関係を追加し、次のコマンドでbaselineから再開しました。
uv run kaji run .kaji/wf/custom/dev/dev-local-lab.yaml local-lab1-1 --from baseline
2回目はdirty_worktreeで即時ABORTしました。
feature worktreeには次の一時成果物が作成されていました。
.kaji-artifacts/baseline/baseline.json
このディレクトリがGitの無視対象ではなかったため、baselineのclean worktree gateに反しました。
.gitignoreへ次を追加しました。
.kaji-artifacts/
mainをfeature branchへマージし、WSL Gitでworktreeがcleanであることを確認しました。
3回目:baselineからCOMPLETE
もう一度--from baselineで再開すると、baselineは次の判定になりました。
status: PASS
baseline_status=clean
pytest_exit_code=0
failures=0
implementでは新しいテストを先に追加し、greetが存在しないためImportErrorになることを確認しました。その後、次の実装を追加しました。
def greet(name: str) -> str:
"""Return a Japanese greeting for the given name.
Raises:
ValueError: If `name` is empty after stripping surrounding whitespace.
"""
stripped = name.strip()
if not stripped:
raise ValueError("name must not be empty after stripping whitespace")
return f"こんにちは、{stripped}さん"
追加したテストは3件です。
@pytest.mark.small
def test_greet() -> None:
assert greet("海") == "こんにちは、海さん"
@pytest.mark.small
def test_greet_strips_surrounding_whitespace() -> None:
assert greet(" 海 ") == "こんにちは、海さん"
@pytest.mark.small
def test_greet_raises_on_blank_name() -> None:
with pytest.raises(ValueError):
greet(" ")
最終結果
uv run make checkの結果は次のとおりです。
ruff check: PASS
ruff format --check: PASS
mypy: PASS
workflow validation: PASS
pytest: 12 passed
review-codeでは次を独立確認しました。
- pytest 12/12 PASS
- baseline比較でregressions 0
- Must Fix 0件
- Should Fix 0件
- 設計と実装の差異なし
最終的にfeature branchはmainへ非FFマージされ、local Issueはclosedになりました。feature worktreeとbranchも削除され、mainはlocal origin/mainへpushされました。
runごとの結果
| run ID | 結果 | 時間 | 停止・完了理由 |
|---|---|---|---|
260819102651 |
ABORT | 219,041 ms |
pytest-xdist不足でbaseline invalid |
260819103347 |
ABORT | 1,168 ms |
.kaji-artifacts/によるdirty worktree |
260819103421 |
COMPLETE | 482,053 ms | baseline以降の全stepがPASS |
最初の実行開始から最終完了までは約15分55秒でした。providerログ上の推定コスト合計は約5.40 USDです。これは実際の請求額ではなく、Claude Sonnet、小さな課題1件、今回のworkflowに限った推定値です。
分かったこと
baseline gateは「テスト失敗0件」だけを見ていない
pytestの起動エラーを成功扱いせず、終了コード4とbaseline_status=invalidを理由に停止しました。変更前の状態を測定できていない場合に実装へ進まないことを確認できました。
step-level resumeが機能した
設計と設計レビューをやり直さず、--from baselineで停止地点から再開できました。
starterとworkflowの接続確認が必要
今回の組み合わせでは、pytest-xdistと.kaji-artifacts/のignore設定を追加する必要がありました。導入時はworkflowが実行するコマンドと生成物を事前確認する必要があります。
制約と未検証事項
今回の結果は、本番環境や大規模repositoryでの安全性を証明するものではありません。
未検証事項は次のとおりです。
- 外部I/Oを含む実装
- GitHub Issue、Pull Request、branch protectionとの統合
- 複数agentの同時実行と競合解決
- timeout、rate limit、通信断からの回復
- flaky test
- secretsや顧客情報を含むrepository
- 長時間実行時のコスト上限
また、make checkを仮想環境外から直接実行するとmypyが見つかりませんでした。今回の環境ではuv run make check、または仮想環境を有効化した状態で実行しています。
まとめ
kajiのlocal providerで、小さなissueを設計からマージ・クローズまで進められました。
特に確認できたのは、正常系の完走だけではありません。pytestを開始できない状態と、worktreeがdirtyな状態で理由付きABORTになり、修正後は同じbaseline stepから再開できました。
guarded loopを導入するときは、agentの性能だけでなく、次を事前に確認する必要があります。
- baselineが実際にテストを開始できるか
- workflowの生成物がGitをdirtyにしないか
- ABORT時のartifactが残るか
- 停止地点から再開できるか
- 最終結果を別のCIまたは人間が再確認できるか