ref-output-routing
Purpose & Output Contract
タスクロジックと出力先を直交分離する。 workflow skill は本スキルに task_kind を渡すだけで、出力先 (Notion DB / Sheets / Local / HTTP / Slack 等) を意識しない。新出力先の追加は adapter 追加のみで完結する (Open/Closed原則)。
入力: task_kind (例: task-spec, meeting-minutes, audit-log)
出力:
{
"adapter": "notion|sheets|local|http|slack",
"params": {...},
"fallback": "local",
"multi": false
}
完了条件: output-routing.json から該当route解決 → adapter名とparams返却 → workflow側が dispatch.py 経由でadapterを起動。
Sink Contract (全adapter共通)
入力payload (workflow → adapter)
{
"schema_version": "1.0",
"kind": "<task_kind>",
"title": "<string>",
"body": "<markdown>",
"metadata": {
"tags": [],
"timestamp": "ISO8601",
"source_skill": "<skill_name>"
},
"attachments": []
}
出力result (adapter → workflow)
{
"status": "success|failure",
"adapter": "<name>",
"location": "<URL or path>",
"external_id": "<opaque ID>",
"errors": []
}
設定ファイル (3点)
.claude/config/output-routing.json — task_kind → adapter マッピング正本
.claude/config/adapter-registry.json — 利用可能adapter宣言
- macOS Keychain — APIキー等の秘匿情報 (リポジトリには絶対保存しない)
Key Rules
- API key は絶対にClaude context に乗らない: adapter scriptが subprocess 内で Keychain から取得し、HTTP呼出しに直接使う。Claude は key を見ない。
- output-routing.json にはIDのみ: database_id, spreadsheet_id 等の非機密IDは記載可。トークン/secret は
keychain:{{SECRET_NAMESPACE}}/<account> 形式で参照のみ記載。env: / file: は使わない。
- adapter追加はJSON登録のみ: コード変更なしで
adapter-registry.json に登録すれば output-routing.json で使える (Open/Closed)。
- multi-sink対応:
adapters: [notion, local] で複数同時出力可能。
- fallback必須: 各routeに
fallback: local 推奨。外部API障害時にローカル退避でwork継続。
使い方 (workflow側)
# Step 1: routing解決
python3 scripts/adapters/resolve_route.py --kind task-spec
# 出力: {"adapter":"notion","params":{"database_id":"..."},"fallback":"local"}
# Step 2: 解決されたadapter起動
python3 scripts/adapters/dispatch.py \
--kind task-spec \
--payload payload.json \
# 出力: {"status":"success","location":"{{OUTPUT_ROUTE}}","external_id":"..."}
adapter 追加手順 (例: Linear連携)
scripts/adapters/sink_linear.py を作成 (Sink Contract準拠)
.claude/config/adapter-registry.json に追加:
{
"name": "linear",
"script": "scripts/adapters/sink_linear.py",
"requires_secret": true,
"params_schema": {
"team_id": { "type": "string", "required": true },
"token_ref": { "type": "string", "required": true }
}
}
.claude/config/output-routing.json の該当 task_kind に "adapter": "linear" を指定
- Keychainに API key を登録:
security add-generic-password -s {{SECRET_PREFIX}}-linear -a api-key -w <KEY>
workflow Skill のコード変更はゼロ。adapter script 追加、registry、routing のJSON更新のみで新出力先が量産可能。
Gotchas
- API keyをroutingに直書きしない:
params: { token: "sk-..." } は禁止。必ず params: { token_ref: "keychain:{{SECRET_NAMESPACE}}/<account>" } で参照のみ。
- output-routing.jsonのコミット: 非機密ID (database_id等) はコミット可だがレビュー必須。tokenが紛れていないか lint で検証。
- fallback先のpath: ローカルフォールバックの保存先は
eval-log/ 等のrepo内に固定。外部書き出しは禁止。
- adapter内部のstdout汚染禁止: adapter は最終JSON以外を stdout に出さない。debug は stderr へ。Claude が adapter出力を読むためstdoutにkey/secretが混入すると context漏洩。
- Sheets/HTTP のスキーマ互換: 同じpayloadを異なるadapterで処理できるか lint で確認 (
schema_version 不一致を検出)。
Additional Resources
references/sink-contract.md — Sink Contract詳細仕様
references/security-model.md — APIキー管理モデル (Keychain中心)
.claude/config/output-routing.json.example — routing雛形
.claude/config/adapter-registry.json — adapter宣言正本
scripts/adapters/ — adapter実装群
scripts/secrets/README.md — Keychain登録手順