# chktime から n8n の work_log へ作業区間を送る — 設計 日付: 2026-10-12 対象: chktime (このリポジトリ) と n8n (https://n8n.yfox.jp) の work-tracker ## 目的 Claude Code での作業を、手で「作業開始・終了」を記録しなくても n8n の `work_log` に入るようにする。 入った記録は、手で記録したものと同じく、日報・月報・ガント・ダッシュボード・Redmine 工数同期 (work-tracker--redmine-sync) で使われる。 ### 成功の条件 - chktime の画面で期間を選ぶと、Claude のセッションから作った「作業区間」が一覧で出る。 - 区間ごとにタスク名 (Redmine のチケットを選べる) を決め、選んだものだけを `work_log` に送れる。 - 同じ区間を二度送っても、`work_log` に二重に入らない。 - すでにある記録 (手で記録したもの・他の区間) と時間が重なる区間は、画面で分かり、最初は送らない状態になっている。 - chktime に渡す権限は「work_log への取り込み」だけ。n8n の API キーは chktime に持たせない。 ### 前提と決めたこと | 項目 | 決定 | |---|---| | 目的 | 記録の手間を減らす | | 時間の数え方 | 連続した区間ごと (空き 30 分以上で切る) | | タスク名 | 送る前に chktime の画面で決める | | 重なり | 画面で知らせ、最初は送らない。送るかは利用者が決める | | 送り方 | 案 A: n8n に受け口のワークフローを作り、chktime から呼ぶ | ## 1. 区間の作り方 (chktime) 材料は chktime の既存の「ターン」(人間の発言 1 回と、それに対する応答一式)。 - 同じセッションの中で、前のターンの応答完了から次の発言までの空きが **30 分未満** ならつなぐ。 30 分以上なら区間を切る。 - 開始 = 区間の最初の発言の時刻、終了 = 区間の最後のターンの応答完了の時刻。 - **5 分未満** の区間は一覧から外す。 - ターンの起点は既存の規則どおり (人間の発言だけ。サブエージェント・再開時のメタ行は起点にしない)。 - `date` は区間の開始時刻のローカル日付 (日本時間)。日をまたぐ区間も開始日の記録。 - 30 分と 5 分は設定 (`n8n.json` の `gapMinutes` / `minMinutes`) で変えられる。画面から変更できる。 - セッションの最後の区間で、最後のターンがまだ応答中 (ログの最終更新から `gapMinutes` 経っていない、 または最後のターンに応答完了が無い) のものは「進行中」とし、送れない。 - `source` = `chktime:<セッションID>:<区間開始の epoch ミリ秒>`。同じログからは毎回同じ値になる。 区間の計算は、HTTP やファイル読み込みから切り離した純粋な関数 (ターンの列と設定値 → 区間の列) にする。 ## 2. n8n 側 ### データテーブル - `work_log` に `source` (string) の列を足す。chktime から入れた行だけ値が入り、手で記録した行は空。 既存のワークフローはこの列を書かないので影響しない。 - `work_log_deleted` にも `source` を足し、work-tracker--delete の `Backup Row` で一緒にコピーする。 ### ワークフロー `work-tracker--import` 両方の Webhook に Header Auth の認証情報を付ける (ヘッダー `X-WT-Token`)。違えば n8n が 403 を返す。 **`GET /webhook/wt-import-rows?from=YYYY-MM-DD&to=YYYY-MM-DD`** - `from` / `to` を確かめる (形式、`from <= to`、最大 62 日)。不正なら `{ok:false, error}`。 - `work_log` から `date` が `from の前日` 〜 `to` の行を返す (前日を含めるのは、日をまたぐ記録との重なりを見るため)。 - 返す値: `{ok:true, rows:[{id, date, start_time, end_time, task, status, source}], deletedSources:[...]}` `deletedSources` は同じ期間の `work_log_deleted` の `source` (空でないもの)。 **`POST /webhook/wt-import`** - 本文: `{rows:[{source, date, start_time, end_time, task, memo}]}`、1 回に最大 200 件。 - 1 行ずつ確かめる: - `source` が `chktime:` で始まる (500 文字以内) - `date` が `YYYY-MM-DD`、`start_time` / `end_time` が ISO 8601 で、終了 > 開始、長さ 24 時間以内 - `task` が空でない (500 文字以内)、`memo` は 2000 文字以内 - `work_log` に同じ `source` がある行はスキップ。 - 新しい行は `status: "done"`、`duration_min` = 終了 − 開始 (分、四捨五入、丸めなし) で追加。 - 返す値: `{ok, inserted:[{source, id}], skipped:[{source, reason}], errors:[{source, error}]}`。 `ok` は errors が 0 件のとき true。 ### 認証情報 合言葉は利用者が決める。利用者が n8n の画面で Header Auth の認証情報 (例: 名前 `wt-import token`、 Name `X-WT-Token`) を作り、chktime の設定画面にも同じ値を入れる。Claude は合言葉を作らず、入力もしない。 認証情報の id は一覧 (中身を含まない) から取ってワークフローに設定する。 ## 3. chktime 側 ### 設定 - `n8n.json` (git 管理外、`.gitignore` に追加。場所は `--n8n-config` で変更可): `{"url": "https://n8n.yfox.jp", "token": "...", "verifySsl": false, "gapMinutes": 30, "minMinutes": 5}` - `GET /api/n8n/config` は `token` の中身を返さず、`hasToken` だけ返す。`POST /api/n8n/config` で保存。 Redmine 設定 (`RedmineHolder`) と同じ作り。 - `verifySsl` の既定は false (LAN からは n8n.yfox.jp の証明書名が一致しないため)。 ### サーバーの API (chktime 内) | ルート | 内容 | |---|---| | `GET/POST /api/n8n/config` | 設定の取得 (合言葉は返さない) / 保存 | | `GET /api/n8n/blocks?from=&to=&project=` | 期間の区間を計算し、n8n の `wt-import-rows` の結果で「送信済み / 削除済み / 重なり」を付けて返す。n8n に届かなくても区間は返し、`n8nError` を付ける | | `POST /api/n8n/send` | `{items:[{source, task, memo}]}` を受け、時刻はサーバーが `source` から区間を計算し直して n8n の `wt-import` に送る。結果をそのまま返す | | `GET /api/redmine/issues?project_id=&q=` | 既存の Redmine 連携で未完了チケットを検索 (チケット選択用) | 重なりの判定: 区間 [開始, 終了) と、`work_log` の行 (進行中は終了 = 今) または一覧の他の区間が 1 秒でも重なれば「重なりあり」。同じ `source` の行 (送信済みの自分) は除く。 ### 画面 - 右ペインのタブに「n8n へ送る」を足す。左で選んでいるプロジェクト (または すべて) が対象。 - 期間: 初期値は直近 7 日。日付入力で指定もできる。 - 一覧の 1 行 = 1 区間。列: 送る (チェック) / 日付 / 開始〜終了 / 時間 / プロジェクト / タスク名 (編集可) / チケット (選ぶ) / 状態。 - タスク名の初期値は `[作業フォルダ名] セッションのタイトル`。チケットを選ぶと `[Redmine のプロジェクト名] #番号 題名` にする (rich-start と同じ形。工数同期とガントの実績に載る)。 - タスク名・チケットは、同じセッションの他の区間にも入れる (切り替え可)。セッションごとに ブラウザの localStorage に覚える (読み書きは try/catch で囲み、使えなくても動く)。 - 状態の印: 送信済み / 削除済み / 重なりあり (カーソルで相手を表示) / 進行中。 - 送るチェックの初期値: 問題の無い区間だけ on。送信済み・削除済み・重なりあり・進行中は off (送信済みと進行中はチェックできない)。 - 「選んだ区間を送る」で送信し、追加・スキップ・エラーの件数と内訳を表示して一覧を読み込み直す。 - 設定 (URL・合言葉・証明書の検証・30 分・5 分) は Redmine 設定と同じ形のダイアログ。 ## 4. エラーの扱い - n8n に届かない / 403 / 5xx: 一覧は出し、上部に原因を表示して送信ボタンを無効にする。送信時の失敗は n8n の応答をそのまま表示。 - 一部の行だけ失敗: 行ごとの結果を表示。`source` で二重登録を防ぐので、再送で失敗分だけ入る。 - 同時に 2 回送った場合: n8n 側の「確認 → 追加」は一括の処理ではないので、二重に入る可能性がある。 使用者は 1 人なので許容し、README に書く。 - 合言葉はログにも画面にも出さない。`n8n.json` は git に入れない。 ## 5. テスト ### chktime - 区間の計算を標準ライブラリの `unittest` で確かめる (`tests/test_blocks.py`)。 入力は手で組んだターンの列、またはスクラッチパッドに作ったテスト用 jsonl (実ログは書き換えない)。 - 空き 29 分はつながり、30 分で切れる - 5 分未満は除外、5 分ちょうどは残る - 進行中の区間 - サブエージェントの行を起点にしない - 日をまたぐ区間の `date` は開始日 - `source` が同じ入力で毎回同じ - 重なりの判定 (接しているだけは重ならない、1 秒重なれば重なる、自分の `source` は除く) - サーバーは検証用に `python chktime.py --port 8799 --no-browser` で起動する (常用の 8787 には触れない)。 ### n8n - 2000-01-01 付けのダミー (`source: chktime:test:...`) で: 追加される / 再送でスキップ / 合言葉違いで 403 / 不正値でエラー / `wt-import-rows` に出る / 削除すると `deletedSources` に出る。 - 終わったらダミーを `work_log` と `work_log_deleted` から直接削除する (削除 Webhook は使わない)。 ### 全体 - 8799 の chktime から、利用者が選んだ実際の区間を 1 件送り、ダッシュボードと日報に出ることを確かめる。 ## 6. 文書 - `CLAUDE.md`: 「外部通信は Redmine 連携だけ」を「Redmine 連携と n8n への送信だけ」に。API の表に n8n の系統を足す。 - `README.md`: 「n8n へ送る」の使い方、設定、同時送信の注意。 - `.gitignore`: `n8n.json`。 - 保管庫: chktime と n8n のノートに追記。 ## 対象外 - 自動で定期的に送ること (送るのは利用者が画面で選んだときだけ)。 - 送信後に chktime から `work_log` の行を編集・削除すること (編集・削除は既存の work-tracker の画面で行う)。 - 他の Webhook への認証の追加 (今回の 2 本だけ。広げるのは別の作業)。