このドキュメントは、現在 ~/.claude/scripts/ と ~/secretary-state/ に置かれている hook・運用スクリプトを全件棚卸しし、その上にダッシュボード再設計案A/B/C1+C2を重ねて、Codex(GPT-5.4)に妥当性レビューを依頼した結果をまとめたものです。実装は一切していません。
~/.claude/hooks/ ディレクトリは存在しない。すべての hook は ~/.claude/settings.json の hooks セクションから ~/.claude/scripts/ 配下のスクリプトを呼び出す形に統一されている。秘書セッション固有の運用スクリプトは ~/secretary-state/ に分けて置かれている。
| script | hook event | 目的(一言) | 判定 |
|---|---|---|---|
| instructions-log.sh | InstructionsLoaded | CLAUDE.md/MEMORY.md の読込タイミングをTSVで記録 | 維持 |
| convert-plan.sh | PostToolUse(Write|Edit) | ~/plans/*.md を plan-viewer の HTML に自動変換し index.html に追記 |
維持 |
| plan-write-marker.py | PostToolUse(Write|Edit) | plan ファイルのバリデーション(Pythonツール) | 維持 |
| record-outbound.sh | PostToolUse(telegram reply|edit_message) | 送信した本文を Second-Brain に記録 | 維持 |
| inject-datetime.sh | PreToolUse(全) | Bash 実行時に現在時刻を context に注入 | 維持 |
| rm-rf-guard.sh | PreToolUse(Bash) | rm -rf 危険パスを exit 2 でブロック |
維持 |
| notify-secretary-blocked.sh | PreToolUse(AskUserQuestion) | AskUserQuestion 検知で秘書に転送通知 | 維持 |
| (inline prompt) | PreToolUse(Write plan-viewer/.html) | テンプレ Read 確認の質問プロンプト | 維持 |
| compact-init.sh / compact-counter.sh | SessionStart / PreCompact | compact 回数の初期化と加算 | 維持 |
| load-persona.sh | SessionStart | ペルソナ読込 (kurisu モードなど) | 維持 |
| session-bootstrap-hook.sh | SessionStart | session-bootstrap エージェント使用を促す注入 | 維持 |
| claude-stop.sh | Stop | TTS 通知(disabled)+secretaryに完了通知+Obsidian同期 | 整理候補(後述) |
| auto-approve-claude-dir.sh | PermissionRequest(Edit|Write) | .claude/ 配下を realpath 検証で自動承認 |
維持 |
| notify-secretary-permission.sh | PermissionRequest(全) | permission dialog で秘書に通知 | 維持 |
| promo-video-compact-hook.sh | PreCompact | プロモ動画スキル開発時のコンテキスト保持 | 維持(プロジェクト限定) |
| inject-datetime-prompt.sh | UserPromptSubmit | 現在時刻をプロンプトに注入 | 維持 |
| plugin-dev-prompt-hook.sh | UserPromptSubmit | /plugin-dev: コマンドリマインダー |
維持 |
| session-reminder.sh | UserPromptSubmit | 20ターンごとに Obsidian 書き出しリマインド | 維持 |
| notify-secretary.sh | Notification(idle_prompt) | idle で秘書を起こす | 維持 |
| notify-secretary-failure.sh | PostToolUseFailure | 失敗を秘書に通知 | 維持 |
| notify-secretary-session-end.sh | SessionEnd | registry更新+秘書通知 | 維持 |
| rate-limit-recovery.sh | StopFailure | rate-limit 自動復帰スケジューラ | 維持 |
| script | 想定役割 | 状態 |
|---|---|---|
| secretary-reply-length-guard.sh | reply text 200字超を block する PreToolUse hook (j-20260503-003 で新設) | 未配線 ← 再設計案Bの土台 |
| intent-hook.sh | Telegram メッセージを intents テーブルに記録する UserPromptSubmit hook | telegram plugin 内で動く想定で配線 |
| close-intent.sh | reply で intent を closed に遷移 (PostToolUse) | telegram plugin 内 |
| check-pending-intents.sh | Bash 完了後に pending intent をリマインド (PostToolUse) | telegram plugin 内 |
| bootstrap-pending-intents.sh | SessionStart で pending intents を提示 | telegram plugin 内 |
| telegram-ack-hook.sh | UserPromptSubmit で配信を ack | telegram plugin 内 |
| dual-deliver-observe.sh | spool ポーリング観察 (j-20260427-006 Phase 1) | 観察ジョブ |
| patch-telegram-plugin-v2.sh / patch-telegram-dual-deliver.sh | telegram plugin への適用パッチ | ワンショット |
| compile-wiki.sh | raw→wiki 直接書き込み (Karpathy式KB) | launchctl compile-wiki で動く |
| generic-convert-md.sh | WATCH_DIR を walk して plan-viewer HTML に配置 | launchctl WatchPaths |
| dream-* 一式 | dreaming アーキテクチャ (compile/promote/quarterly-reset/to-plan-viewer) | launchctl 配信 |
| intents-timer.sh / intents-timer-watchdog.sh | 60秒/5分の launchctl 監視 | launchctl 配信 |
| daily-report.sh | デイリーレポート Telegram 配信 | launchctl 配信 |
| claude-daily-summary.sh | recall ベースの日次サマリ(cron用) | 起動経路要確認 |
| claude-daily-summary.sh.bak | 同上の旧版バックアップ | 廃止候補 |
| warmup-voice-cache.sh | TTS キャッシュ温め | TTS disabled で意義喪失 → 廃止候補 |
| backup-auth.sh / restore-auth.sh | OAuth backup/restore | 維持 |
| import-inbody.sh / parse-training.sh / training-report.sh | 健康データ系 | 維持(無関係) |
| check-h1-basename-consistency.sh | Second-Brain の h1/basename 整合性検出 | 維持 |
| sync-codex-to-obsidian.sh / sync-recall-to-obsidian.sh | ai-second-brain repo への symlink | 維持 |
| test-* (5本) | hook の単体テスト群 | 維持(test) |
| script | 役割 | 判定 |
|---|---|---|
| dispatch-job.sh | 記録→確認→送信→更新を不可分で実行する dispatch 本体 | 維持 |
| report-done-v2.sh / report-propose-v2.sh / report-question-v2.sh | 3行+URL+ボタン テンプレ (j-20260503-003) | 維持(現役) |
| report-done.sh / report-propose.sh / report-question.sh | 旧 4見出しテンプレ (角括弧見出し+多段箇条書き) | 廃止候補 |
| complete-job.sh | DONE 遷移+refresh-dashboard | 維持(C1案で拡張) |
| refresh-dashboard.sh | Telegram pin message のジョブ一覧更新 | 廃止候補(plan-viewer/mini-apps-uiに統合) |
| dispatch-queue-poller.sh / approve.sh / reject.sh | 承認キューの poll/approve/reject | 維持 |
| cleanup-jobs.sh | DONE/FAILED を 30日後に削除 (cron) | 維持 |
| reminder-checker.sh | reminders.json 監視 (launchctl) | 維持 |
| scheduled-reminder.sh / standup.sh | 朝/昼/夕の定時 Telegram (cron) | 維持 |
| restart-tmux.sh | tmux サーバー再起動(ワンショット運用) | 維持 |
| setup-telegram-commands.sh | Bot のコマンド補完登録 | 維持 |
| telegram-lib.sh | source 用ライブラリ | 維持 |
| migrations/2026-05-03_dashboard_revamp.sql | jobs に read_at + plan_viewer_url 追加済 | 適用済 |
jobs.read_at と jobs.plan_viewer_url は 5/3 の migration で追加済(A案の土台)verification_summary.next_action は json_valid + json_type='array' の CHECK 制約はあるが、空配列を許容する (C2案で塞ぐ余地)intents テーブルがあり pending/linked/closed/ignored 状態を持つ。やりかけ発掘の既存仕組みとして稼働中(C1案と接続できる)廃止可能なもの:
統合検討:
claude-stop.sh の整理候補:
狙い: 複数端末(MacBook + iPhone)で同じ既読状態を共有する。localStorage は端末ローカルなので Mac で開いた既読が iPhone に反映されない問題を解消する。
棚卸しとの接続: jobs テーブルには既に read_at カラム追加済(5/3 migration)。job 単位の既読は対応済み。
追加で必要なもの:
items(id, job_id, kind, read_at) を追加/app/api/items/:id/read を立て、PATCH でサーバ側 read_at を更新read_event{item_id, read_at} をブロードキャスト論点: 「項目」の粒度。job 全体だけで足りるなら items テーブルは不要で、jobs.read_at を使い回せる。もしジョブ詳細ページで「この箇条書きは読んだ」までやるなら items が要る。前者で開始するのが KISS。
狙い: 「気をつける」依存を排除し、長文 reply を仕組みで禁止する。詳細はダッシュボードに集約させる。
棚卸しとの接続:
完了 を含む or 100字超 → block」だけでなく、block 時のメッセージで report-done-v3.sh への誘導文言を入れる追加で必要なもの:
PreToolUse に secretary-reply-length-guard.sh を追加allow_long=true を秘書が明示する。逸脱が起きたら別 hook で fail-closed に倒す論点: PreToolUse は MCP tool 単位でしか matcher を設定できない。秘書が report-done-v2.sh を Bash 経由で呼ぶときは guard を素通りするのか/text パラメタが渡されないので素通りするのか、を確認。素通りで良いはず。
狙い: 「完了」を DB に書く瞬間に、関連未完了ジョブ・派生タスク・pending intents を SQL で強制クエリし、確認ステップを挟む(スキップ不可)。
棚卸しとの接続:
SELECT * FROM intents WHERE status='pending' AND received_at < (job.created_at OR done_at) で「このジョブの最中に来た未対応 intent」が取れる追加で必要なもの:
COMPLETE_JOB_FORCE=1 環境変数なしでは exit 2 する強制ステップ--ack-pending="intent_ids,..." を必須化、未指定なら fail-closed)verification_checks テーブルに condition='related_pending_check', result='pass', evidence='intent ids: ...' として残す論点:
狙い: DB レベルで「next_action 空のまま DONE」を構造的に禁止する。コード側の確認に頼らず、SQL の制約で fail-closed にする。
棚卸しとの接続:
CHECK(next_action IS NULL OR (json_valid(next_action) AND json_type(next_action) = 'array'))[] は json_type='array' を通過するのでブロックされない追加で必要なもの:
CHECK(next_action IS NULL OR (json_valid(next_action) AND json_type(next_action)='array' AND json_array_length(next_action) > 0))next_action_kind='none' (本当に次がない正当ケース) は空配列を許す必要があるかもしれない。その場合 CHECK は next_action_kind='none' OR json_array_length(...) > 0 の二択論点: 既存ジョブ(過去の DONE)で next_action が NULL or 空のものがある場合、migration が UPDATE で躓く。先に既存データのクリーンアップが要る。
「launchctl 朝push」案は健人が却下済み。今回の実装範囲には含めない。
(Codex 公式 plugin /codex:adversarial-review にこの md を添えて投げる予定。
このセクションは Codex 応答が返ってきたら引用形式で追記する。)
1. hook棚卸し結果(Phase 1)の妥当性
対象: Phase 1-B/C/E、Phase 4-1(L55-L58, L68-L72, L90-L105, L201-L203)
refresh-dashboard.shの削除判断はこの文書の中だけで既に矛盾している。complete-job.shは現状DONE更新+refresh-dashboardだと書いてあるので、代替が入る前に消すと完了処理の副作用が欠落する。これは「廃止候補」ではなく、現時点では依存先そのものだ。- 旧
report-*.shは「grep で 0 件確認後」と条件付きだが、refresh-dashboard.sh、claude-daily-summary.sh.bak、warmup-voice-cache.shには同水準の呼び出し証明がない。特にこの文書はlaunchctl/cron/手動運用の存在を自分で列挙しており(L215-L220)、settings.jsonに未配線だから安全、とは言えていない。claude-stop.shの TTS dead code 削除も根拠不足だ。L104-L105 は「disabled だから dead」と結論しているが、「実行時に TTS を再有効化する経路がない」「列挙した関数群が通知本文整形など他用途で再利用されていない」という不変条件が示されていない。- 仮説:
extract_*_paragraphのような名前の関数は音声専用ではなく通知要約にも使われがちだ。文書に呼び出しグラフがない以上、ここを dead code と断定するのは危険だ。根拠は「disabled 状態の説明しかなく、参照先解析がない」ことだ。2. 再設計案A(既読同期)の妥当性
対象: 案A(L108-L121)
itemsを作るかjobs.read_atを使い回すかの選択根拠は足りない。L121 は「前者で開始するのが KISS」と言うが、同じ段落で既に「項目」には完了報告、選択肢、URL が含まれると定義している(L116-L118)。その粒度を持つ UI なのに job 単位既読へ潰すと、「1項目だけ見た」が「ジョブ全体を読んだ」に化ける。これは簡略化ではなく意味の破壊だ。- SSE 設計には整合性の不変条件が欠けている。
PATCH /readとread_event{item_id, read_at}だけでは、「接続直後の初期スナップショット」「再接続時の取りこぼし補填」「同時更新時の単調増加(古いread_atで戻さない)」が定義されていない。複数端末同時アクセスでは stale 状態を確実に起こす。- 仮説: iPhone が古い画面状態のまま
PATCHし、Mac の新しいread_atを後から潰す競合が起こる。理由は、この文書にサーバ側の勝ち方(MAX(read_at)を取るなど)が一切書かれていないからだ。- 「同じ
chat_idにぶら下がるクライアント全員へ broadcast」(L118)も粗い。閲覧権限やセッション分離の前提が書かれていないので、同一 chat 内の別ビューまで一律同期される設計に見える。3. 再設計案B(報告形式の強制力)の妥当性
対象: 案B、残論点(L123-L142, L209-L213)
- この設計は「強制」と書いているが、文書自身が主要な抜け道を認めている。L138-L142 で、
report-done-v2.shを Bash 経由で呼ぶと guard を素通りする可能性を前提にしている。つまり「正規経路」と「手書き経路」の区別を hook が構造的に判定できていない。これは強制ではなく、運用依存だ。- 閾値 100字ブロックでは報告形式は強制できない。100字未満の手書き reply、例えば「確認済。後で整理する」のような非テンプレ報告は普通に通る。
「完了」を含む or 100字超(L131)は穴だらけだ。allow_long=trueは例外ではなく常用バイパスになる。L139, L211 には、誰が、どの条件で、どこに監査証跡を残し、いつ失効させるかがない。これでは「長文必須でした」で全部抜ける。- MCP tool 単位 matcher の限界(L141-L142)を認識しているのに、その制約の上で「気をつける依存を排除する」と言っている。ここは論理破綻だ。
4. 再設計案C1(complete-job.shで強制クエリ)の妥当性
対象: 案C1、残論点(L143-L163, L213)
COMPLETE_JOB_FORCE=1は fail-closed を一発で壊す万能鍵だ(L156-L157)。一度運用に乗ったら、詰まった時の常套句になる。「確認ステップをスキップ不可」にしたい設計と真っ向から矛盾する。--ack-pending="intent_ids,..."も形骸化を防げない。文字列を入れれば通るだけで、「その ID 群が本当にクエリ結果と一致していたか」「確認時点で stale ではなかったか」の照合条件がない。監査証跡を残しても、嘘の証跡を残せるなら意味がない。- fail-closed の詰まりリスクは高いのに、クエリ条件の正当性が未確定だ。L150-L152 の SQL 条件は概念だけで、L213 では
linked_job_idの運用実態確認が必要と自認している。データモデル未監査のまま完了ゲートに載せるのは危険だ。intents-timer.shと「衝突しない」は証明されていない(L163)。同じ pending intent がタイマ通知でも DONE 前ゲートでも出るなら、秘書は同じものを二重に処理させられる。これは確認品質を上げず、ack 疲れだけ増やす。5. 再設計案C2(next_action 空配列を CHECK制約で禁止)の妥当性
対象: 案C2(L165-L180)
- 提案されている CHECK は目的に対して弱い。L167 は「DONE を禁止」と言うのに、実際の制約案は
next_action IS NULLを許している(L176)。これでは空配列だけ塞いで NULL は通る。DONE 時の不変条件になっていない。next_action_kind='none'の扱いが未決着のまま migration を語っているのも危うい(L177)。「本当に次がない」ケースを許すのか禁止するのかで、業務意味が真逆になる。ここを「かもしれない」のまま DB 制約に落とすのは雑だ。- 既存データのクリーンアップも破壊半径が大きい。L180 は migration が躓くことだけ気にしているが、もっと悪いのは「制約を通すために偽の
next_actionを埋める」ことだ。履歴データの意味が壊れ、後続の分析や UI 表示まで汚染する。- 仮説:
report-done-v2.shがFAILEDにしているルール(L178)と DB 制約の条件がずれると、アプリ層では許可したのに DB で落ちる、またはその逆が起こる。理由は、片方はdone_whenを見ており、提案中の CHECK はそこを見ていないからだ。6. 「気をつける」依存排除が機能しているか
対象: 案B全体、残論点(L125-L142, L211-L212)
- 機能していない。文書の中心目標は「手書き報告ループを塞ぐ」ことだが、実際の制御は「長文を一部の tool で弾く」だけだ。これは経路制御ではなく文面制御に過ぎない。
- 塞ぎ漏れは少なくとも3本ある。
100字未満の手書き報告、Bash 経由の Telegram API 直叩き、allow_long=trueだ。どれも文書内で示されている経路だけで成立する。- 特に L138-L139 は、「見分けられなければ運用ルールを徹底」と書いている。ここで既に構造的封鎖を諦めている。
- 何も懸念がない点はない。むしろこの章は、設計思想と実装手段が一番ずれている。
7. 抜け道・反復暴走・データ整合性リスク
対象: Phase 1-A/B、案A/B/C1/C2(L18-L21, L33-L36, L53, L117-L119, L129-L142, L156-L163)
- 直接の「hook が自分自身を再起動する」型の無限ループは、この文書だけでは確認できない。そこは根拠不足なので断定しない。
- ただし通知増幅ループはある。
complete-job.shが C1 で exit 2 →PostToolUseFailureが秘書へ通知(L34)→ 秘書の応答が新たな intent/pending を増やす → 次回の C1 クエリでさらに引っかかる、という自己増殖経路だ。- B 案の guard と unguarded Bash 経路が併存すると、同じ種類の報告が「hook で監査される経路」と「素通り経路」に分裂する。これは整合性事故だ。監査ログの完全性が壊れる。
- A 案の SSE は競合制御未定義なので、複数端末で
read_atが前後し、UI と DB が食い違う。既読は一方向フラグに見えて実際は競合データだ。intents-timer.shと C1 の二重検出は、同一 pending を別タイミングで何度も突く。文書は補完関係と言うが、重複抑止キーや「この intent は C1 で確認済み」の状態がない以上、反復暴走の種だ。8. 推奨実装順序
対象: Phase 4(L197-L207)
- 現在の順序
断捨離→C2→C1→B→Aは妥当ではない。特にrefresh-dashboard.shを 1 手目で断捨離対象に入れているのが壊れている。現状complete-job.shが依存しているからだ。- より安全な順序はこうなる:
- 断捨離 — ただし
refresh-dashboard.shは除外する。削ってよいのは依存証明済みのものだけだ。- C1 を観測モードで先行。fail-closed にせず、まず関連 intent/job の検出結果を記録して false positive を洗う。
linked_job_id実態未確認のままゲート化するな。- B は「正規の送信経路を1本に固定できる」ことが確認できてからだ。今のまま配線しても enforcement theater(強制しているふり) にしかならない。
- C2 は最後に近い。既存データ cleanup 方針と
next_action_kind=noneの意味を固定し、書き込み側スクリプトが新制約を満たすと確認してから入れるべきだ。- A は現行案どおり最後でよい。競合面積が広く、前段の業務フロー制約より後回しが妥当だ。
- 要するに、この文書の Phase 4 は「壊しても戻せる粒度に切る」と書いているが、1 手目から依存中の部品を消そうとしている。順序設計の時点で fail-safe ではない。
refresh-dashboard.sh を消そうとしているのは依存先を壊す自爆。先に置換が要る。allow_long=true の3本の抜け道を文書自身が認めている。COMPLETE_JOB_FORCE=1 万能鍵と --ack-pending 文字列は fail-closed を骨抜きにする。観測モードで false positive 洗ってからゲート化が安全。next_action IS NULL を許す書き方で、目的の DONE 不変条件になっていない。MAX(read_at) 単調増加・初期スナップショット・再接続補填が未定義で、複数端末で stale を確実に起こす。intents-timer.sh の二重検出は ack 疲れの原因になる。重複抑止キーが要る。これらを踏まえて Phase 4 を書き直すのが次の作業になる(健人の判断後)。
健人の指示「断捨離と合わせてならあり」を尊重し、断捨離→骨格→肉付け の順で並べる。
各ステップは独立にロールバック可能な migration/コミット粒度に切る。
allow_long=true の運用が形骸化しないルールが必要これらは hook ではないが、再設計の影響範囲(特に refresh-dashboard 廃止時の依存先)を見るために併記した。