shinchoku.

進捗 — バッチCLIのための小さなNDJSONプロトコル

CLIがstdoutに数行のJSONを流すだけで、あらゆる観測者——GUI・スケジューラ・ログ収集——が 業務の中身を一切知らないまま、進捗バーを描き、失敗を通知し、履歴を残せるようになります。

「進捗どうですか?」に、CLIが自分で答えるためのプロトコル。

$ your-cli --out result.jsonl {"v":1,"event":"start","title":"取り込み","total":120} ← パネルが生まれる {"v":1,"event":"progress","current":3,"total":120} ← バーが動く {"v":1,"event":"log","level":"warn","msg":"42行目をスキップ"} ← ログに色が付く {"v":1,"event":"artifact","path":"result.jsonl"} ← 保存ボタンが生える {"v":1,"event":"done","summary":"4,520件"} ← 通知が飛び、履歴に残る

なぜプロトコルなのか

宅配便の追跡番号を思い浮かべてください。荷物の中身は本でも食品でも服でもバラバラですが、 追跡画面は1つで全部に対応できます。ステータスの書式が共有されているからで、 中身が統一されているからではありません。shinchoku はバッチ処理——クロール、エクスポート、 動画変換、バックアップ、AI推論——のための追跡番号です。観測側が理解するのは 封筒(7種類のイベント)だけ。中身(あなたの業務)はあなたのものです。

パイプ・ネイティブ

輸送路はstdout。パイプ・SSH・WebSocket中継——行を運べるものなら何でも、プロトコルは無変更で通る。

printfで準拠できる

printf '{"v":1,"event":"done"}\n' だけで正規のプロデューサー。ライブラリは便利グッズであって契約ではない。

寛容さが契約

JSONでない行はただのログ。未知のイベントとフィールドは無視。既存ツールは今日から「まだバーの出ない」正規プロデューサー。

語彙は7つだけ

ドメインの言葉は値として流れ、語彙には決して入らない。だから1つの観測者があらゆるツールに対応できる。

それ、OpenTelemetry の仕事では?

答えている問いが違います。OpenTelemetry が答えるのは「サービス群全体で何が起きているか」—— トレース・メトリクス・ログをネットワーク経由でコレクタへ送り、あとで分析するための仕組みです。 shinchoku が答えるのは「この1プロセスはどこまで進んだか」——spawnした親に向かって、 stdoutで実況することです。

shinchokuOpenTelemetry
関心の単位1プロセスの1回の実行サービス群・分散システム
進捗(current/total一級のイベント概念が存在しない
輸送路stdout → 親プロセスネットワーク → コレクタ/バックエンド
導入コストprintf で足りるSDK + exporter(+ コレクタ)
クラッシュ時終了コードが最終判定末尾のテレメトリは失われうる

両者は競合ではなく合成できます——消費者が受け取ったイベントを OTel パイプラインへ転送すればいいだけです。 宅配便の例えで言えば、shinchoku は荷物の追跡バーコード、OpenTelemetry は物流会社の基幹システムです。 規範的な適用範囲は SPEC §10(Non-goals)へ。

新しいことは何もしていない——意図的に

shinchoku は、実績のある4つの古い知恵を組み合わせただけで、独自の発明を足していません。 唯一の貢献はその組み合わせです——昔ながらの輸送路の上に、寛容でドメイン非依存の語彙を1つだけ置く。 それで初めて、1つの観測者があらゆるツールに対応できるようになります。

テストハーネスの知恵

プロセスは stdout に行を書いて報告し、読む側は寛容にパースする。数十年の実績がある機構——ただしそこでの語彙は合否に縛られている。

CIランナーの知恵

進捗・成果物・注釈を特別な書式の行として流し、ランナーがUIに変える。正しい語彙——ただし方言が特定ベンダーのものになっている。

--json モードの知恵

機械可読なイベントをJSON行で流す。正しい輸送路——ただし各ツールが私家版の語彙を発明するので、観測者がツールを横断できない。

監督プロセスの知恵

子は状態を実況し、親が判定を握る。「終了コードが最終判定」はこの伝統の直系。

語彙

eventフィールド意味
starttitle?, total?実行の開始宣言。最初に出す
progresscurrent, total?, msg?完了した作業単位数
loglevel, msginfo / warn / error(非致命)
metrickey, value名前付きの数値。観測側はそのまま表示
artifactpath, label?成果物ファイルの存在宣言
donesummary?正常終了。最後に出す
failedmsg致命的失敗。最後に出す

将来のための予約語: ask(対話)・row(表データ)・heartbeat(常駐監視)。 追加は "v" を上げません——下の消費者ルールが互換性を構造的に保証します。

1つのプロトコル、5つのヘルパ

Rust / Go / Node / PHP / Python の emit ヘルパを同梱しています。いずれも約100行・依存ほぼゼロの薄いラッパーです。

# Cargo.toml
[dependencies]
shinchoku = "0.1"

use shinchoku::Event;

Event::start().title("取り込み").total(120).emit();
Event::progress(3).total(120).emit();
Event::done().summary("4,520件").emit();

// 消費者側: features = ["parse"] で寛容な parse_line() が使える
// go get github.com/uiuifree/shinchoku/go

import shinchoku "github.com/uiuifree/shinchoku/go"

shinchoku.Start("取り込み", 120)
shinchoku.Progress(3, 120)
shinchoku.Done("4,520件")
// npm install shinchoku(ESM)

import * as shinchoku from "shinchoku";

shinchoku.start({ title: "取り込み", total: 120 });
shinchoku.progress(3, 120);
shinchoku.done("4,520件");
// composer require shinchoku/shinchoku

use Shinchoku\Shinchoku;

Shinchoku::start('取り込み', 120);
Shinchoku::progress(3, 120);
Shinchoku::done('4,520件');
# pip install shinchoku

import shinchoku

shinchoku.start(title="取り込み", total=120)
shinchoku.progress(3, total=120)
shinchoku.done("4,520件")

消費者ルール — 寛容さこそが契約

  1. JSONとしてパースできない行 → ただのログとして表示。決してエラーにしない
  2. 未知の event → 無視するかログ表示。決してエラーにしない
  3. 未知のフィールド → 無視する
  4. stderr → ただのログとして表示
  5. 終了コードが最終判定。failed を見ないまま非ゼロ終了 → 失敗として合成する。プロトコルは実行を実況し、終了コードが裁く

この5つのルールがあるから、イベントの追加は互換な変更になり、 ルール5だけ実装した観測者も既に正規の最小消費者です。バー・メトリクス・成果物ボタンは すべてその上の漸進的強化にすぎません。