mcpField
I
invoke-task-conventions

invoke-task-conventions

github.com/theodoread開発039 回インストール4 週間前に更新
リポジトリ

invoke-task-conventions について

Pythonのinvoke CLIにおけるタスクの命名と配線を整えるスキルです。読みやすいコマンド名やモジュールの名前空間に関する規約に加え、タスクのインポート、改名、対話型サブプロセスの注意点をまとめています。

主な機能

  1. 01動詞を先頭に置くタスク命名規約
  2. 02定着したCLIコマンド名の維持
  3. 03モジュール内で明示的なタスク名前空間を定義
  4. 04ソースモジュールとタスクリストの照合
  5. 05invoke実行時の対話入力を回避

活用シーン

  1. 01複数リポジトリのタスク名を標準化
  2. 02依存関係更新後にinvokeのタスク配線を確認
  3. 03参照とドキュメントを確認しながらタスクを改名
  4. 04対話型コマンドを起動するタスクを点検

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/theodoread/agent-skills --skill invoke-task-conventions

インストール後、Claude Code で/invoke-task-conventionsを入力して読み込まれたか確認します。

同梱ファイルreferences/rationale.md

詳細

invoke タスクの命名と配線

inv <namespace>.<task> は命令形のコマンドとして読めるようにします。名前空間が主体で、タスクが動作です。inv apt.install-base は「apt:ベースをインストール」、inv zsh.fix-history は「zsh:履歴を修正」となります。

これは一般的な CLI の形式であり、このプロジェクト独自の考えではありません。gh pr create、docker image prune、kubectl config set-context はすべて <subject> <verb> の形式です。また、Azure CLI ではこれを明示的に必須としています(「コマンドは [noun] [noun] [verb] パターンに従う必要があります」)。各ルールの先行事例と根拠については、references/rationale.md を参照してください。

例と測定結果は、この著者自身のリポジトリ群(power-user-linux-setup、repo-tasks、scaffoldapy で生成されたプロジェクト)から得たものです。ルールを試したのがこれらのリポジトリだったためです。これらは invoke のルールであり、どの tasks.py にも当てはまります。リポジトリ名が出てきたら、ご自身のリポジトリに置き換えてください。

3 つのルール

1. タスク名は動詞で始めます。 生成するものではなく、動作を名前にします。apt.base ではなく apt.install-base、ai.skills ではなく ai.install-skills とします。Azure CLI でも同じルールを「すべてのコマンド名に動詞を含める」としています。

複数語のタスク名では、動詞を先頭に置きます(zsh.configure-omz、ide.configure-pycharm)。ただし、inv --list で一連の名前として並び、その隣接性が重要な場合は、目的語を先頭にしたままにします(format-check/format-apply、lint-check/lint-apply)。これはケースごとの判断であり、2 つ目のルールではありません。

2. コミュニティの慣習をルールより優先します。 CLI の慣習として定着している名前(status、list、version、check、diff)は、そのまま使います。読者が自然に思い浮かべるのは inv gnome.status であり、gnome.show-status のほうが一貫性は高くても、使い勝手は劣ります。名前付きサブコマンドをラップするタスクも同様です。deps.tree は uv tree をラップするため、その名前を維持します。

これらの名前は、読みやすいだけではなく、重要な役割を担っています。2026-08-25 以降、マシン上の Claude Code の許可リスト(power-user-linux-setup の cli-allowlist/tools.toml にある [inv] エントリの allow_overrides。inv allowlist.apply がハーネスごとにレンダリングします)は、慣習のみに基づき、すべてのリポジトリで inv <ns>.status|check|diff|list|show、<ns>.print-*、<ns>.check-*、<ns>.list-*、<ns>.*-check を自動承認します。これらの名前を持つタスクは、状態を調べるだけで、変更を加えてはいけません。検出した問題も修正する deploy.check は、確認なしに実行されてしまいます。変更を加えるタスクには、変更を示す名前(fix、apply、sync、configure)を付けてください。

3. 名前空間自体が動作を表す場合があります。 setup、verify、clean、deploy、test などです。この場合、末尾の名前はスコープまたは対象を表します。たとえば、verify.all、clean.caches、deploy.all、test.unit です。これはルール 1 の明示された例外であり、違反ではありません。ルール 1 だけを見た読者は test.unit を逆の順序だと指摘しますが、実際に一度そうなったことがあります。

適切な場合は、主体を表す名前空間を優先してください。その配下のタスクが、動作ではなくスコープによって本当に異なる場合に限り、動作を表す名前空間を使います。

動詞の使い方を統一する

意味ごとに動詞を 1 つ選び、類義語を交互に使うのではなく、名前空間をまたいで再利用してください。install、configure、set、write、create、clean、fix、check、apply、print、render などです。Azure CLI のガイドラインも同じ点を指摘し(「異なる種類のオブジェクト間で使う動詞に一貫性を持たせる」)、標準的な意味と衝突する動詞を避けるよう、さらに注意を促しています。

タスクの docstring がすでに動詞で始まっている場合、その動詞を通常は名前にします。docstring が「Cap persistent journal size」で始まるタスクは cap-journal-size に、「List a project's published versions」で始まるタスクは list-versions にするのが適切です。このリポジトリ群での名前変更のいくつかは、まさにこの方法で見つかりました。

名前変更は影響範囲が広いコード変更です

名前変更を単なる文字列置換として扱わないでください。

  • Python 関数名は CLI 名と一緒に変更されます(invoke は一方から他方を導出します)。また、フェーズのリストや pre= チェーンは関数オブジェクトを直接参照します。名前の変更漏れがあると、インポート時に AttributeError が発生します。これはむしろ好都合です。inv --list の読み込みが成功すれば、コード側の変更が完了したと確認できます。
  • タスク名はモジュールの外でも、さまざまな文章で参照されています。 24 個のタスクを一括変更した際は、docs/*.md の全ファイル、setup.toml のヘッダーコメント、Dockerfile、CI ワークフロー、ブートストラップスクリプト、他のリポジトリのドキュメントなど、53 個のファイルが対象になりました。
  • 機械的な置換では、CLI 名と Python の識別子を見分けられません。 同じタスクについて、両方の表記が同じファイルに現れることもあります。ある名前変更スクリプトは、ユーザー向けの出力ラベルに [ai.install_skills] を書き込んでしまいました。テストではラベルのテキストを検証していないため、どのテストでも検出されませんでした。スクリプトで名前を変更した後は、文字列リテラル内にあるスネークケース表記を grep し、タスクを実行して出力を確認してください。
  • 見出しも確認してください。 Markdown の見出しにあるタスク名を変更すると、そのページのアンカーも変わり、厳密なドキュメントビルドでリンク切れが発生します。編集前に、参照元となる <page>.md# リンクを grep してください。
  • デプロイ済みのコピーも再デプロイが必要です。 名前を変更したタスクが、どこかにインストールされるファイル(常時読み込まれる指示ファイルや、インストール済みのスキルなど)で参照されている場合、リポジトリのソースを編集しただけでは変更の半分しか完了していません。

古い名前を @task(aliases=[...]) で残すのではなく、名前を完全に変更してください。エイリアスを残すと CLI の表面が恒久的に二倍になり、今後の検索でもすべてのタスクに別名が一つずつ増えます。きれいに切り替えるためのコストは、1 セッション分のタイプミスだけです。

配線上の落とし穴

インポートされたタスクは公開タスクになります。 Collection.from_module はモジュールの名前空間で見つけたすべての Task オブジェクトを追加します。インポートされたタスクも定義されたタスクと区別されないため、pre= チェーン用に取り込まれたタスクが、インポート先モジュールの名前で二重に公開されます。この結果、誰も宣言していないタスクが 4 つ生成されました(test.unit と並ぶ quality.unit、および、それぞれを所有する venv/direnv/agents タスクと並ぶ dev-env.create、dev-env.allow、dev-env.claude-hook)。さらに、別のリポジトリがそのうち 1 つを正式な名前としてドキュメントに記載するまで、問題は見過ごされていました。

タスクをインポートするモジュールでは、明示的なモジュールレベルの名前空間を宣言する必要があります。from_module は自動スキャンよりもこちらを優先します。

ns = Collection(setup)  # only what this module actually owns

別のリポジトリのタスクを名前で参照する場合は、コマンドがたまたま実行できるかではなく、そのリポジトリのモジュールのソースを確認してください。 コマンドが動作しても、誰かの import 文による偶然かもしれません。

名前空間は配線されて初めて存在します。 選択した上流モジュールから独自の Collection を構築する利用側のリポジトリでは、インポートしていないモジュール内のタスクが気付かないうちに失われます。依存関係を更新したら、テストスイートだけでなく inv --list も実行してください。

タスクでは、入力を待つ処理を実行してはいけません

c.run では、pty=True を指定しても実行できません。 原因は独立して 2 つあり、後者はこのファミリーが標準で使うインタープリターでは致命的です。

  1. invoke は stdin をエコーし、子プロセスと stdin の取り合いになります。 Runner.should_echo_stdin() は (not using_pty) and isatty(stdin) なので、ターミナルから非 pty の c.run を実行すると、読み取ったすべてのバイトが表示されます。そうしないと、ターミナルを cbreak モードにしているため、ユーザーには何も見えないからです。一方、sudo は同じターミナルである /dev/tty から読み取ります。読み取りで先に勝った方がバイトを受け取るため、パスワードが表示される うえに 子プロセスには届かず、再度入力を求められて、実行が停止したように見えます。タスク本体が c.run("head -n 1 > /dev/null") だけの場合に再現でき、SUPERSECRET と入力するとそのまま表示されます。
  2. Python 3.14 では、invoke は stdin を転送できません。 terminals.bytes_to_read() は fcntl.ioctl(stdin, FIONREAD, b" ") を呼び出しますが、4 バイトの結果に対して 2 バイトのバッファーを渡しています。Python 3.14 より前のバージョンではオーバーフローが黙って発生していましたが、3.14 では fcntl.ioctl が強化され、最初のキー入力で SystemError: buffer overflow が発生し、invoke の stdin スレッドが終了します。pty の有無にかかわらず、子プロセスには何も届きません。上流の pyinvoke/invoke#1070 で修正作業中ですが、invoke 3.0.3 時点では未リリースです。
Pythonstdin の転送が必要な c.run
3.10–13動作しますが、入力したテキストがエコーされます(原因 1)
3.14停止します — 子プロセスには何も転送されません

pty=True は注意すべき落とし穴です。 子プロセスが自身の pty でエコーを制御するため、原因 1 は解消されるように見えますが、原因 2 には何も対処しません。そのため、インタープリターが変わるまでは正しいパターンに見えますが、変わると出力のないまま停止します。

このルールを満たす方法は 2 つあります。対話型の子プロセスは、実際のターミナルを継承する通常の subprocess として実行し、それ以外はすべて非対話型にします。最初に sudo で認証し、その後は sudo -n を使います。また、apt には DEBIAN_FRONTEND=noninteractive と --force-confold を指定します。

リポジトリ内を rg 'pty=True' で監査し、該当箇所をすべて確認してください — このパターンは認証情報の入力プロンプトで最もよく使われていますが、まさに停止を避けたい場面です。最初の inv wsl.install で問題に遭遇し、sudo のプロンプトの後に、パスワードが平文で表示されたまま停止しました。その後、コンテナー内で 3.10〜3.14 の各バージョンを使い、2 つの原因を再現しました。

既存のリポジトリを監査する

inv --list | grep -oP '^ [a-z0-9.-]+' で全体像を把握できます。記憶に頼らず、モジュールのソースと照らし合わせて確認してください。この比較によって、ルール違反と、宣言されていないタスクの両方を見つけられます。次に各名前を、動詞を先頭に置いたもの、コミュニティの慣例(ルール 2)、アクションの名前空間(ルール 3)、実際の違反のいずれかに分類します。意図的に準拠しているケースは、後から確認できる場所に記録してください。そうしないと、次に読む人が「修正」してしまいます — gitflow.feature-start は、git flow feature start を反映しているとわかるまでは違反に見えます。

コメント