Plan Canvas
計画とビジュアルアーティファクトのためのレビューループ:あなたがアーティファクトを作成し、人間がブラウザでそれをレビューします。人間は、意味する正確な要素に注釈を付け、チャットし、計画を承認/変更を要求の判定を下し、その間あなたは単一のCLI呼び出しでブロックし、フィードバックをJSONとして受け取ります。
lavish-axi に触発されました。ECCネイティブとして /plan 確認ゲートを中心に再構築され、依存関係はゼロです。
使用するタイミング
- あなたが計画アーティファクト(
/planから生成された.claude/plans/*.plan.md)を書いたばかりで、CONFIRM/承認の決定が必要な場合 — キャンバスの判定が、タイプ入力による「はい/進める」を置き換えます。 - ユーザーが「どこを変更するか」を指し示す必要がある場合:デザイン、比較、レポート、またはローカルの
.md/.htmlアーティファクトのレビュー。 - ユーザーが
/plan-canvas、ビジュアルレビュー、または「ブラウザで開いて」を要求した場合。
使用しない場合:差分のコードレビュー(/code-review)、実行中のWebアプリ、リモートURL。キャンバスはローカルアーティファクトファイルのみを提供します。
仕組み
CLIは ecc-plan-canvas として呼び出します — ecc-universal パッケージによって提供されるバイナリです(グローバルまたはプラグインインストール後にPATHに追加されます。プラグインインストールの場合は node "$CLAUDE_PLUGIN_ROOT/scripts/plan-canvas.js" も使用できます)。レビュー対象のプロジェクトから実行します。どのワーキングディレクトリからでも動作します。すべてのセッションで共有されるデタッチされたループバックサーバー(127.0.0.1:4517)を管理し、アーティファクトパスをキーとします — セッションIDを追跡する必要はありません。
ワークフローはプレーンなCLIとJSONのループであるため、モデルやハーネスに依存しません:シェルコマンドを実行しstdoutを読み取れるエージェントなら、同じ方法で動作します(Claude Code、Codex、Cursor、Gemini、OpenCode、Copilot)。ハーネスがスキルを公開する方法に応じてトリガーします — 例えば、Claude Codeでは /plan-canvas、Codexでは $plan-canvas — または ecc-plan-canvas コマンドを直接実行するだけです。
# 1. アーティファクトをユーザーのブラウザで開く(すぐに戻る)
ecc-plan-canvas open .claude/plans/feature.plan.md
# 2. 人間が応答するまでブロックする。実行したままにしておく;中断された場合は再実行する:
# キューに入ったフィードバックは決して失われない。
ecc-plan-canvas await .claude/plans/feature.plan.md
リッスンし続けること、さもなければ人間は空の椅子に話しかけることになる
フィードバックがあなたに届くのは、await が実際にセッションに待機している間だけです。あなたのターンが何もリッスンしていない状態で終了すると、メッセージはキューに残り、人間の側から見ると、送信しても何も起こっていないように見えます。
そのため、ハーネスがバックグラウンドタスクをサポートしている場合は、await をバックグラウンドタスクとして実行してください(Claude Codeでは、run_in_background: true を指定したBash呼び出し)。フィードバックが到着するとすぐに終了し、ハーネスがJSONをあなたに渡します。これにより、フォアグラウンド呼び出しで終了する代わりに、ターンをまたいでループを生かし続けることができます。フォアグラウンドの await も動作しますが、ハーネスが時間制限をかけるまでです。
2つのバックストップが存在しますが、どちらも上記をスキップする言い訳にはなりません:
ecc-plan-canvas pendingは、リスナーなしでキューに入れられたフィードバックを一覧表示します。何かを見逃したかどうかわからないときにいつでも確認してください。stop:plan-canvas-pendingフックは、キャンバスのフィードバックが未配信である間はあなたのターンが終了するのを防ぎ、メッセージをあなたに渡します。そのフックからフィードバックを読んでいる場合、あなたはリッスンの開始が早すぎたということです。
await は人間が行動したときにJSONを出力します:
{
"status": "feedback",
"items": [
{ "kind": "annotation", "text": "これを2つのフェーズに分割してください",
"anchor": { "selector": "h2:nth-of-type(3)", "tag": "h2", "snippet": "フェーズ2: 移行" } },
{ "kind": "verdict", "verdict": "request-changes" }
]
}
kind: "chat"— 自由形式のメッセージ;キャンバス内で返信し、ターミナルでは返信しないでください。kind: "annotation"— 要素にアンカーされたフィードバック(anchor.selector、anchor.snippetはユーザーが指したものを示します;ユーザーがテキスト範囲をハイライトした場合はanchor.textRange.text)。kind: "verdict"—approveは計画が確認されたことを意味します:ポーリングを停止し、セッションを終了し、実装を開始します。request-changesはアーティファクトを修正する(キャンバスはライブリロードされます)ことを意味し、ループを継続します。
3. 常にキャンバス内で応答し、その後リッスンを続けます。1つのコマンドで両方を実行できます:
ecc-plan-canvas await <file> --reply "ご依頼の通り、フェーズ2を分割しました。ご確認ください。"
すべての人間のメッセージには、キャンバス内で返信します。「今すぐリスクテーブルを書き直します」のような一言でも構いません。チャットパネル内の沈黙は、壊れたキャンバスと区別がつかず、まさにこのループが防止しようとしている障害です。ターミナルだけでなく、必ずキャンバス内で応答してください。
作業中は、アクティビティインジケーターでチャットの誠実さを保ちます:
# アニメーションする「エージェントが考えています...」バブル;長時間の作業中は更新する
ecc-plan-canvas typing <file> --state thinking
# 返信が届く直前に「エージェントが入力中...」に切り替える
ecc-plan-canvas typing <file> --state typing
await は、バッチをあなたに渡した時点で thinking を設定し、--reply はそれをクリアします。両方の状態は自動的に期限切れになるため、クラッシュしたエージェントは、人間に永遠にドットを見せ続ける代わりに、誠実な「キューイング中」状態に移行します。修正に1分以上かかる場合は、thinking を更新してください。
4. レビューが終了したら 終了します:ecc-plan-canvas end <file>。
図(Mermaid)
計画の一部がフロー、アーキテクチャ、シーケンス、ステートマシン、ERモデル、または依存関係グラフである場合、ASCIIアートや散文の代わりに、フェンス付きの ```mermaid ブロックとして作成してください — キャンバスはそれをテーマ化された図としてレンダリングし、人間が指し示すことができます。画像が段落よりも速く読める場合に使用し、単純なリストやテーブルの場合は省略します。
```mermaid
flowchart LR
A[市場が確定] --> B{ウォッチャーは?}
B -->|はい| C[ジョブをキューに入れる] --> D[ファンアウトワーカー]
```
図はACCセントパレットを使用したECCダークテーマでレンダリングされます。Mermaidは固定CDNからブラウザにロードされます;それが利用できない場合(オフライン)、ブロックはソースを表示するように劣化するため、レビューが妨げられることはありません。エアギャップ環境で使用するには、ECC_PLAN_CANVAS_MERMAID_URL にローカルミラーを指定します。
ルール
- MarkdownアーティファクトはECCの計画テンプレート(Mermaidブロックを含む)でレンダリングされます;
.htmlアーティファクトはそのままレンダリングされ、注釈レイヤーが注入されます。HTML作成のガイダンスについては、frontend-design-directionおよびartifact-designスキルを参照してください。 - 修正するにはアーティファクトファイルを編集します — キャンバスは保存時にライブリロードされます。更新のために
openを再実行しないでください。 {"status": "ended", "endedBy": "user"}(またはフィードバックバッチのsessionEnded: true)は、ユーザーがレビューを閉じたことを意味します:ポーリングを停止し、残りの更新をチャットで配信し、再開しないでください。そのセッションに対する単純なopenは拒否されます;ユーザーが再開を要求した場合にのみ--reopenを渡してください。- 関連するアセット(画像、CSS)はアーティファクトと同じ場所に配置し、相対パスで参照する必要があります。
- サーバーはループバックのみで、30分間アイドル状態が続くと終了します(
ECC_PLAN_CANVAS_IDLE_MS);stopは明示的にシャットダウンします。状態は~/.claude/plan-canvas/(ECC_PLAN_CANVAS_STATE_DIR)に保存されます。
例
計画承認フロー — /plan が .claude/plans/notifications.plan.md を作成し、確認を待つ必要があります:
ecc-plan-canvas open .claude/plans/notifications.plan.md
ecc-plan-canvas await .claude/plans/notifications.plan.md
# → {"status":"feedback","items":[{"kind":"verdict","verdict":"approve"}]}
ecc-plan-canvas end .claude/plans/notifications.plan.md
# 計画が確認されました — 実装を開始
修正ループ — フィードバックが届き、ファイルを編集し、返信し、リッスンを続ける:
# awaitが注釈を返した → .plan.mdを編集する(キャンバスはライブリロードされる)
ecc-plan-canvas await <file> --reply "リスクテーブルをやり直しました。"
# → 次の応答まで再度ブロックされる
アンチパターン
--timeout-msを使用したループでのポーリング。テスト用に存在します。代わりにプレーンなawaitを実行したままにしてください。- レビューがまだ開いている間に、リッスン中の
awaitがない状態でターンを終了すること。これこそが、人間が「メッセージを送ったのに何も起こらなかった」と感じる唯一の障害です。 - フィードバックを読んだが、ターミナルでのみ応答すること。人間はキャンバスを見ています。
- ユーザー主導で終了した後に、「ちょっと見せるためだけに」再開すること。
- 計画全体をチャットに貼り付け、かつ キャンバスを開くこと — キャンバスを選び、ターミナルの要約は1行に抑えてください。
- 状態ファイルからキャンバスのチャットを解析すること — 必要なものはすべて
awaitを介して届きます。
デザインノートと起源: docs/design/plan-canvas.md。