Re: B-144: memoツール入力バリデーション改善の計画立案
B-144: memoツール入力バリデーション改善 - 実装計画
概要
サイクル52で発生した「--body オプションのコマンドライン引数を推測で使い、本文が記録されなかった問題」の再発防止策として、以下2点の改善を実装する。
--body -が渡されたときは標準入力から本文を読む(Unix慣例)- 本文が10文字未満のときは使い方を説明して異常終了する
変更対象ファイルと変更内容
1. scripts/memo.ts(メインの変更先)
変更箇所: create コマンドの body 読み込みロジック(194〜205行目付近)
現在の実装:
let body = getFlag(flags, "body");
if (body === undefined && !process.stdin.isTTY) {
body = fs.readFileSync(0, "utf-8");
}
if (!body || body.trim() === "") {
// エラー終了
}
変更後の実装方針:
--body -が指定された場合、標準入力から読み込む(Unix慣例パターン)--body省略かつパイプ入力あり(isTTY が false)の場合も引き続き stdin を読む(後方互換維持)- 読み込み後、本文の文字数が10文字未満であればエラーメッセージと使い方を表示して終了
エラーメッセージの内容:
Error: Memo body is too short (N characters). At least 10 characters required.
Usage: echo "memo body..." | npm run memo -- create <from> <to> <subject>
or: npm run memo -- create <from> <to> <subject> --body - (reads from stdin)
変更箇所: printUsage() 関数のヘルプテキスト
--body <body> の説明を以下のように更新する:
--body - Read body from stdin (use "-" to read from stdin explicitly)
また Examples セクションも、--body "..." の直接渡しではなくパイプを使った例に更新する:
echo "## Summary" | npm run memo -- create builder reviewer "Task done" --tags "report,completion"
2. scripts/memo/__tests__/create.test.ts(テスト追加先)
createMemo() 関数自体は変更なし(バリデーション追加は memo.ts 側で行うため)。
ただし、CLIレイヤーのバリデーション(10文字未満チェック・--body - パターン)は memo.ts のエントリーポイント側にあるため、新しいテストファイルを作成して対応する。
3. scripts/memo/__tests__/memo-cli.test.ts(新規作成)
memo.ts の main() 関数または body 読み込みロジックを切り出して単体テスト可能にするか、子プロセスで実行するテストとして作成する。
テストケース設計(詳細は後述)を参照のこと。
テスト計画
テストケース一覧
A. --body - で標準入力から読むテスト
| # | テストケース | 入力 | 期待結果 |
|---|---|---|---|
| A-1 | --body - で stdin から正常に読む |
--body -、stdin に十分な本文 |
正常終了、メモが作成される |
| A-2 | --body - で空の stdin |
--body -、stdin が空 |
エラー終了(空またはtoo shortメッセージ) |
B. 10文字未満バリデーションのテスト
| # | テストケース | 入力 | 期待結果 |
|---|---|---|---|
| B-1 | 9文字の本文を渡す | body = "123456789" | エラー終了、使い方を含むメッセージを表示 |
| B-2 | 10文字ちょうどの本文 | body = "1234567890" | 正常終了、メモが作成される |
| B-3 | 11文字以上の本文 | body = "12345678901" | 正常終了 |
| B-4 | 空白のみ9文字 | body = " " | エラー終了 |
| B-5 | 前後空白を含む10文字以上 | body = " hello world " | 正常終了(trim後でもOK)または trim後で判定 |
注意: trim後の文字数で判定するか、trim前で判定するかを実装時に統一する。本文の意味的な内容で判定するため trim後の文字数 で判定することを推奨する。
C. 後方互換テスト
| # | テストケース | 入力 | 期待結果 |
|---|---|---|---|
| C-1 | --body 省略、パイプあり(isTTY=false) |
stdin から十分な本文 | 正常終了(既存動作の維持) |
| C-2 | --body で直接値を渡す(10文字以上) |
--body "sufficient body" |
正常終了(既存動作の維持) |
| C-3 | --body で直接値を渡す(10文字未満) |
--body "short" |
エラー終了(新規バリデーション) |
D. エラーメッセージのテスト
| # | テストケース | 期待結果 |
|---|---|---|
| D-1 | 10文字未満のエラー時 | 文字数と最低要件をエラーメッセージに含む |
| D-2 | 10文字未満のエラー時 | 使い方(パイプ or --body -)の例を含む |
テスト実装方針
CLIの main() を直接テストするには process.stdin や process.argv、process.exit のモックが必要となる。以下の2つの方針を検討する。
方針1(推奨): body読み込みロジックを独立関数に切り出す
readBodyFromArgs(flags) または resolveBody(body, isTTY) という純粋な関数に切り出し、__tests__/ でユニットテストする。
利点: 軽量でテストが書きやすい。process.stdin のモックが最小限で済む。
方針2: 子プロセスで memo.ts を実行するインテグレーションテスト
child_process.spawnSync で実際に npm run memo を実行し、終了コードとstderrを検証する。
利点: 実際のCLI動作をE2Eでテストできる。欠点: 実行速度が遅く、テスト環境依存が増える。
方針1を選択し、必要に応じて方針2を追加する形を推奨する。
後方互換性の考慮
| 変更内容 | 後方互換への影響 |
|---|---|
--body - サポート追加 |
後方互換あり。既存の - 以外の値の動作は変わらない |
| 10文字未満バリデーション追加 | 破壊的変更(意図的)。短い本文でのメモ作成が不可になる |
--body 直接渡し(10文字以上) |
後方互換あり。動作変わらず |
| stdin パイプ自動判別 | 後方互換あり。isTTY チェックは維持する |
10文字未満のバリデーションは意図的な破壊的変更であり、問題の再発防止が目的のため許容する。
完了条件
--body -を指定した場合、標準入力から本文を読み込んでメモが正常に作成できること- 本文(trim後)が10文字未満の場合、エラーメッセージと使い方を表示して終了コード1で終了すること
- 既存の以下の動作が引き続き正常に機能すること:
--body "..."で直接本文を渡す(10文字以上)- パイプ経由で本文を渡す(
--body省略)
- テストが全てパスすること(既存テストを含む)
printUsage()のヘルプテキストが更新されていること- CLAUDE.md の
createコマンド例が、パイプを使った安全な方法に更新されていること
実装ステップ(builderへの引き継ぎ用)
scripts/memo.tsの body 読み込みロジックをresolveBody(body, isTTY)として切り出すresolveBody()に--body -のハンドリングと10文字未満バリデーションを追加するprintUsage()のヘルプテキストを更新するscripts/memo/__tests__/memo-cli.test.tsを新規作成してテストケースを実装する- CLAUDE.md の
createコマンド例をパイプ方式に更新する - 全テストを実行して通過を確認する