leeovery/portal/tree/main/.claude/skills/workflow-gates
workflow-gates
Claude Code のプラグインです。ワークフローエンジンのゲートをプロンプト上部の帯に操作可能な行として描き、モデルが再現するテキストメニューの代わりに、選択・キュー・永続化・再開を扱います。
この mod について
workflow-gates
ワークフローエンジンのゲートをプロンプト上部の帯に描く Claude Code mod です。モデルに再現させるのではありません。
エンジンは、組み立てたメニューの横に各ゲートをデータとして置きます。この mod はセッション開始時に自分の存在を知らせるため、エンジンがデータを集め、データを運んだ Bash 結果からゲートを有効にし、モデルが読む内容からメニューを切り出し、トランスクリプトがスクロールしても行をその場所に描き続けられます。接続されている画面がターミナル以外の場合は、すべての画面で見えるようメニューをテキストとして残します。ただし、ターミナルでメニューを描いた後に接続した画面には、そのメニューは渡りません。ゲートの文章が mod の名前を出すことはありません。名前を出すのは workflow-start のセットアップ手順だけで、Claude Code のターミナルアプリで mod が動くまでセッションを止めます。
行をクリックすると答えがプロンプトボックスに入り、もう一度クリックすると次のメッセージとして送信されます。ワークフローはそれを答えとして読みます。クリックで帯がキーボードを受け取ると、矢印キーで行を移動でき、Enter または行固有のキーで選択でき、選択した行で Enter を押すと送信します。送信した答えはプラグイン名の下に入り、モデル向けに枠付けされ、トランスクリプトではプラグインの内容として表示されます。mod は送信内容を会話専用のフォルダー(下記参照)に sent.json として残します。2つ目のプラグイン workflow-gates-rows(../workflow-gates-rows/)はこの記録を読み、行を質問と答えとして描きます。送信したプロンプトの行をプラグイン自身が描き直すことはできないからです。答えは入力もできます。プロンプトでキーと Enter を押すか、選択後に Esc、Enter の順で操作します。入力だけで答えられる行——Ask、Comment、範囲——は暗く表示され、クリックするとプロンプトへ入力するよう示されます。行の下のフッターには、答え方、プロンプト内の内容、入力場所のどれかが表示されます。
帯の高さは Claude Code が与える行の高さを超えないため、スクロールしません。収まるゲートは、規則、説明と質問、行、フッターをすべて表示します。収まらないゲートは規則、質問、フッターを固定し、行を同じ高さのページに分けて表示します。下には ↑ previous ↓ next page 1 of 3 と表示されます。どちらかをクリックするとページが切り替わり、最初の行にカーソルが置かれます。矢印キーはページをまたいでカーソルを動かし、次のページにも進めます。行固有のキーなら、その行がどのページにあっても選べます。
本人が始めていないターン——バックグラウンドエージェントの報告、通知、スケジュール——では帯をそのままにし、行も生かしておきます。本人がターンを開始したとき、実行中のターンに返信したとき、または別のゲートを描くターンが始まったときに帯を外します。そのターンで Esc を押しても帯はそのままです。ただし、ターンがすでに別のゲートを描いていた場合は、モデルがそのゲートの停止点で待つため帯を空にします。Claude の作業中にもう一度クリックすると、答えを送らず保留します。行は · queued と表示され、フッターは Claude の完了時に送ると示します。キューに入った行をクリックすると選択に戻り、別の行をクリックするとそちらを選びます。ターンの終了時、同じゲートが帯に残っていれば保留した答えを送ります。別のゲートが描かれていれば答えは送らずに破棄し、新しいゲートのフッターがそのことを示します。Esc でターンを止め、同じゲートが残っていれば答えを選択状態としてプロンプトボックスへ戻します。選択中のゲートが消えると答えもプロンプトボックスから消えますが、本人がそこで編集していた場合は除きます。Claude の作業中に入力した答えは Claude Code 自身のキューに入ります。
答えが開始したターン、または答えが加わったターンで Esc を押すと、そのゲートを戻し、ターンが描いたものを捨てます。ただし、そのターンでまだツールが実行されていない場合に限ります。ツールが一度でも実行されると、mod には読み取りと書き込みを区別できないため帯は空のままです。/clear は帯からゲートを外します。
各ターンの終わりと会話の終了時に、mod は帯に表示されているもの(ゲート、または何もない状態)を会話専用のフォルダーへ保存します。場所は ~/.config/workflows/conversations/{session-id}/gate.json で、WORKFLOWS_CONFIG_DIR が設定されていればワークフローのシステム設定の横に置かれます。セッションの作業ディレクトリが移動していても、セッション ID で見つけます。中断されたターンの周囲に Claude Code が書いた行は数えず、トランスクリプトがどこで終わったかも記録します。claude --resume や /resume で再開した会話、再起動や mod ファイルの再読み込みで続行された会話は、トランスクリプトがそこまでで終わっていればゲートを戻します。選択や保留はありません。保留した答えは戻ってこないターンを待つためです。mod がロードされていない間に先へ進んだ会話には何も戻しません。開始後に mod をロードしたセッションには告知がないため、何も保存せず読み戻しません。ワークフローを実行しない会話にはフォルダーがなく、保存もしません。ホームディレクトリも WORKFLOWS_CONFIG_DIR も指定しないプロセスの会話も同じです。フォルダーが持つゲートは1つだけで、Claude Code が会話のトランスクリプトを削除するとフォルダーも消えます。そのため保存されたゲートは会話を再開できる間だけ残ります。例外が1つあります。session-end hook が終了を見ていない会話はフォルダーを残します。最初に hook をインストールしたセッション(Claude Code がセッション開始時にだけ取得するもの)や、クラッシュしたセッションがこれに当たります。
エンジンは mod がオンでもオフでもメニューを出します。mod がない場所やオフの場所でモデルが読むのは、エンジンが書いたテキストメニューです。
この mod はワークフローの一部です。最初の /workflow-start は、実行できる場所——2.1.282 以降の Claude Code ターミナルアプリで、このディレクトリがプロジェクトにインストールされている場合——で mod をオンにします。Claude Code は CLAUDE_CODE_ENABLE_FUNCTION_HOOKS を本人の設定、管理対象の設定、またはシェルから取得し、プロジェクトの設定からは取得しません。そのためターミナルアプリでは、各 /workflow-start が本人の Claude Code 設定の env に "1" を書きます。CLAUDE_CONFIG_DIR が設定されていればその中の settings.json、なければ ~/.claude/settings.json です。Claude Code が設定を読むのは起動時だけなので、設定を書き込んだ開始処理は再起動を求めて終わり、次のセッションで mod がロードされます。ターミナルアプリのワークフローは mod が動いている場合だけ実行されます。flag があるのに mod が動いていない、ファイルを読み書きできない、または 2.1.282 より古い Claude Code で動いている場合、開始処理は停止して理由を示します。ウェブ、IDE 拡張、別のエントリポイント、このディレクトリがないプロジェクトでは何も書かず、ワークフローはテキストメニューで続きます。
Function hooks は mod が実行できない場所でもオンにできます。本人の設定にある flag は、それを読むすべての Claude Code アプリとバージョンに届き、誰でも自分の設定やシェルで設定できます。セッション開始時、mod は起動時の規則を適用します。CLAUDE_CODE_ENTRYPOINT が cli 以外、CLAUDE_CODE_REMOTE が設定済み、バージョンが 2.1.282 より古い、またはリリース版でない場合(開発ビルドを含む)、mod は何も告知せず、描画、保存、設定もしません。メニューはテキストのままで、Claude Code は mod がない場合と同じように動きます。
Claude Code に設定するもの
2.1.282 以降の Claude Code ターミナルアプリの各セッションでは、Claude Code の SendUserMessage ツールをオンにします(CLAUDE_CODE_PEWTER_OWL_TOOL=true)。Claude Code はセッション開始直後にツール一覧を作るため、スイッチが効くのはその時だけです。mod はそのようなセッションすべてでツールを ToolSearch の後ろに置きます。答えは変わらないため、プロンプトキャッシュを消費しません。通常のセッションのツール一覧は Claude Code 自身のものです。
そこでワークフローを動かす会話では、mod は CLAUDE_CODE_THINKING_DISPLAY_UPDATES=false を設定し、Claude の思考の一行要約が出力のように印刷されるのを止めます。また CLAUDE_CODE_SILENT_TURN_REMINDER=false を設定し、Claude に何をしているか言わせる促しを止めます。2つ目はプロジェクト設定では設定できません。Claude Code は両方をリクエストごとに読みます。各エンジン呼び出しは、それを作った会話を印で示します。会話フォルダー内に workflow ファイルを置き、Claude Code が各コマンドに渡すセッション ID を名前にします。mod は各 Bash 呼び出しの後と起動時に自分のセッション ID で印を読みます。そのためエンジンに触れるだけのコマンドは印を付けません。設定が置き換えた値(本人の値、または値なし)はプロセス環境の WORKFLOWS_HARNESS_REPLACED に保存されます。mod ファイルを再読み込みしても残り、/clear や再開で正確に戻ります。印のある会話は、claude --resume、再起動、同じプロセスでの /resume のどれで戻っても、mod が次に追跡したときワークフロー値を取り戻します。同じプロジェクトの通常の会話は Claude Code のデフォルトと本人の設定を保ち、mod はどちらにも触れません。
開発
npm run mod:types # API 宣言を types/ に取得(gitignore 対象)
npm run typecheck:mod # それらの宣言に対して tsc
npm run test:mod # claude plugin test
宣言は Claude Code リポジトリ由来で再生成できるため、コミットしません。最初の typecheck 前に取得してください。
Function hooks はアーリーアクセスです。オンになっている場所だけで Claude Code がこの mod をロードします。環境の CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1、プロジェクト以外の設定ファイルでの有効化、またはアカウントでの有効化が条件で、テストスクリプトは自分用に flag を設定します。
インストール
まず作者の README で marketplace とプラグイン名を確認してください。コマンドはリポジトリの構成によって変わる場合があります。
claude plugin marketplace add leeovery/portal claude plugin install workflow-gates
原文 / README
workflow-gates
A Claude Code mod that draws the workflow engine's gates in the band above the prompt instead of leaving the model to reproduce them.
The engine states each gate as data beside the menu it composed. This mod announces itself at the session's start so the engine collects that data, arms the gate off the Bash result that carried it, cuts the menu out of what the model reads, and draws the rows where they stay put while the transcript scrolls; while any screen but the terminal is attached, it leaves the menu as text so every screen shows it, though a screen that attaches after a menu was drawn on the terminal does not get that menu. No gate's prose names the mod; only workflow-start's setup step does, which stops the session until the mod is running in Claude Code's terminal app.
A click on a row puts its answer in the prompt box; a second click on it sends
it as the next message, which the workflows read as the answer. Once a click
has given the band the keyboard, the arrows move between rows, Enter or a row's
own key picks, and Enter on the picked row sends. A sent answer enters under
the plugin's name, framed for the model and labelled in the transcript as the
plugin's; the mod leaves what it sent in the conversation's own folder (see
below) as sent.json. A second plugin, workflow-gates-rows
(../workflow-gates-rows/), reads that record to draw the row as the question
and the answer, since no plugin can redraw the row of a prompt it submitted.
Typing answers too: a key and Enter at the prompt, or Esc then Enter after a
pick. Rows only typing can answer — Ask, Comment, a range — draw dim, and a
click on one says to type it in the prompt. The footer under the rows says
which: how to answer, what is in the prompt, or where to type.
The band is never taller than the rows Claude Code gives it, so it never
scrolls. A gate that fits shows whole: a rule, the statement and the question,
the rows, the footer. One that does not keeps its rule, question and footer in
place and shows its rows a page at a time, every page the same height, over a
line reading ↑ previous ↓ next page 1 of 3; a click on either turns the
page and puts the cursor on its first row. The arrows carry the cursor across
pages, the page following it, and a row's own key picks that row whichever
page it is on.
A turn the person did not start — a background agent's report, a
notification, a schedule — leaves the band as it is, its rows live. The band
comes off when the person starts a turn or replies into a running one, or when
a turn draws a different gate over it. Esc on such a turn leaves the band as it
is, unless the turn had already rendered a different gate: the model now waits
at that gate's stop, so the band empties. A second click while Claude works
holds the answer instead of sending it: its row reads · queued, and the
footer says it sends when Claude finishes. A click on the queued row takes it
back to a pick, and a click on another row picks that one instead. As the turn
ends, the held answer sends if the same gate is still on the band; if the turn
rendered a different gate, the answer is dropped unsent, and the new gate's
footer says so; if Esc stopped the turn with the same gate still up, the
answer goes back into the prompt box as a pick. When a pick's gate goes, its
answer leaves the prompt box too, unless the person has edited it there.
Typing while Claude works joins Claude Code's own queue.
Esc on a turn an answer started or joined puts its gate back, dropping
whatever that turn drew, as long as no tool has run in it; once one has, the
band stays empty, since the mod cannot tell a read from a write. A /clear
takes the gate off the band.
At the end of every turn, and as the conversation ends, the mod keeps what the
band shows — the gate, or nothing — in the conversation's own folder,
~/.config/workflows/conversations/{session-id}/gate.json (under
WORKFLOWS_CONFIG_DIR where that is set, beside the workflows' system
config), found by the session id wherever the session's working directory has
moved, and stamped with where the transcript ends, not counting the lines
Claude Code writes around an interrupted turn. A conversation resumed with
claude --resume or /resume, or picked up again by a restart or a reload of
the mod's files, gets its gate back as long as its transcript still ends
there, with nothing picked or held — a held answer waits on a turn that does
not come back; one that moved on while the mod was not loaded gets nothing. A
session the mod was loaded into after it started carries no announcement, so
there it keeps and reads back nothing, and a conversation that does not run
the workflows has no folder and keeps nothing — nor does one in a process
that names neither a home directory nor WORKFLOWS_CONFIG_DIR. The folder
holds one gate, and goes once Claude Code has deleted the conversation's
transcript, so a kept gate lives as long as its conversation can be resumed,
with one exception: a conversation whose end the session-end hook never saw
keeps its folder — the session that first installed the hook, which Claude
Code picks up only as a session starts, or one that crashed.
The engine emits the menu regardless, so where the mod is off or absent the model reads the text menu the engine wrote.
The mod is part of the workflows, and the first /workflow-start switches it
on wherever it can run: Claude Code's terminal app, from 2.1.282, with this
directory installed in the project. Claude Code takes
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS from the person's own settings, managed
settings or the shell, never from a project's settings, so there every
/workflow-start makes it "1" in the env of the person's Claude Code
settings — settings.json in CLAUDE_CONFIG_DIR where that is set, else
~/.claude/settings.json. Claude Code reads its settings only when it starts,
so a start that writes it ends by asking for a restart, and the next session
loads the mod. In the terminal app the workflows run only with the mod: a
start that finds the flag there with the mod not running, that cannot read or
write that file, or that runs on a Claude Code older than 2.1.282 stops and
says why. Anywhere else — the web, an IDE extension, another entrypoint, a
project without this directory — nothing is written, and the workflows carry
on with the text menus.
Function hooks can be on where the mod cannot run — the flag in the person's
settings reaches every Claude Code app and version that reads them, and
anyone's own settings or shell can set it — so at the session's start the mod
applies the boot's rules: where CLAUDE_CODE_ENTRYPOINT is other than cli,
CLAUDE_CODE_REMOTE is set, or the version the session reports is older than
2.1.282 or not a release's (a development build among them), it announces
nothing, so it draws, keeps and sets nothing — the menus stay text, and Claude
Code runs as it would without it.
What it sets in Claude Code
Every session in Claude Code's terminal app, from 2.1.282, starts with Claude
Code's SendUserMessage tool switched on (CLAUDE_CODE_PEWTER_OWL_TOOL=true):
Claude Code builds its tool list just after the session starts, so that is
the only moment the switch counts. The mod keeps the tool behind ToolSearch
in every such session, one answer that never changes and so never spends the
prompt cache; a plain session's tool list is Claude Code's own.
In a conversation that runs the workflows there, the mod sets
CLAUDE_CODE_THINKING_DISPLAY_UPDATES=false, which stops one-line summaries of
Claude's thinking printing as if they were output, and
CLAUDE_CODE_SILENT_TURN_REMINDER=false, which stops the nudge to say what
Claude is doing; project settings cannot set the second. Claude Code reads
both per request. Every engine call marks the conversation that made it — a
workflow file in its folder, named by the session id Claude Code hands every
command — and the mod reads that mark by its own session id after each of the
conversation's Bash calls and when it starts, so a command that only mentions
the engine marks nothing. What the settings replace, the person's own value or
none, is kept in the process's environment (WORKFLOWS_HARNESS_REPLACED),
which a reload of the mod's files keeps, and a /clear or a resume puts it
back exactly. A marked conversation gets the workflow values back when the mod
next follows it, whether claude --resume, a restart or /resume in the same
process brings it back. A plain conversation in the same project keeps Claude
Code's defaults and the person's own settings: the mod never touches either
there.
Working on it
npm run mod:types # fetch the API declarations into types/ (gitignored)
npm run typecheck:mod # tsc against those declarations
npm run test:mod # claude plugin test
The declarations come from the Claude Code repository and are regenerable, so they are not committed. Fetch them before the first typecheck.
Function hooks are early access: Claude Code loads this mod only where they
are on — CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 in the environment or in any
settings file but a project's, or switched on for the account — and the test
script sets the flag for itself.
同名の他の作品
- workflow-gatesleeovery · ★ 2
