chktime のタスクと git 履歴から下書きを作り、まとめて Redmine へ登録できる ようにした。外部通信はこの機能を使ったときだけで、使わなければ従来どおり ローカルの jsonl を読むだけ。 - Redmine の REST API を叩く最小クライアント (標準ライブラリのみ) - 下書きは「タスクから」(1タスク=1チケット) と「git 履歴から」 (日ごと / 日 x 種別 / 期間一括) の 2 系統 - 起票は件数 x 間隔ぶん時間がかかるのでジョブにし、進捗だけ見に行く - 作成時の status_id は Redmine 側で無視されるため、作成後に付け直す - 連続アクセスに 403 を返すサーバがあるので 1 件ごとに間隔をあける (既定 7 秒) - 自己署名証明書が普通なので、既定は証明書の検証なし - API キーは redmine.json に置き、git 管理外にする Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
chktime
Claude Code の会話ログ(~/.claude/projects/<プロジェクト>/<セッションID>.jsonl)を解析して、
タスクごとのやりとりの時刻を表示するローカル Web アプリ。使い方は README.md に書いてある。
構成
chktime.py サーバ + jsonl 解析 + Redmine 連携(これ1本で完結)
web/index.html 画面の骨組み
web/app.js 表示ロジック(フレームワークなし・素の DOM 操作)
web/style.css スタイル(ダークテーマ固定)
run.bat コンソール表示で起動
stop.cmd 起動中の chktime を終了
redmine.json Redmine 連携の設定(自動生成・git 管理外)
chktime.py の中身は上から
「解析ロジック → Store(キャッシュ)/ Watcher(更新検出)→ Redmine 連携 → HTTP」の順。
守ること
- 標準ライブラリのみ。外部パッケージも CDN も使わない。ブラウザ側もフレームワークなし。
- 外部通信は Redmine 連携だけ。それ以外はローカルの jsonl を読むだけで、どこにも接続しない。 新しい通信先を増やすときは、この行を書き換えるところから始める。
- API キーをリポジトリに入れない。
redmine.jsonは.gitignore済み。ログにも出さない (/api/redmine/configはキーの有無だけ返す)。 - 会話ログは読み取り専用。検証で書き換えるときはスクラッチパッドにコピーしてから行う。
.bat/.cmdは cmd の既定コードページで読まれるので中身は ASCII のみ(日本語を書かない)。- コメント・コミットメッセージ・UI の文言は日本語。
- 機能を足したら README.md も更新する。
動かす
python chktime.py --port 8799 --no-browser
ユーザーは 8787 でデスクトップショートカットから常用しているので、検証は 8799 など別ポートで立てる。
停止は stop.cmd(ただし chktime.py を実行中の python/pythonw をすべて終了するので、
検証用だけを止めたいときはコマンドラインでポートを見て個別に kill する)。
API
| ルート | 内容 |
|---|---|
/api/projects |
プロジェクト一覧 |
/api/sessions?project= |
そのプロジェクトのタスク一覧 |
/api/all |
全プロジェクトのタスク一覧(横断モード) |
/api/session?project=&id= |
1タスクのターン一覧 |
/api/daily?project=&days=&from=&to=&exclude= |
日別集計 |
/api/timeline?project=&days= |
ガント(タスク単位)。ターン単位は /api/session から描く |
/api/watch |
SSE。ログが変わったら change を流す |
/api/rev /api/reload |
現在の版 / キャッシュ破棄 |
いずれも &format=csv を付けると CSV を返す(Excel 用に BOM 付き)。
Redmine 連携(POST を使う唯一の系統):
| ルート | 内容 |
|---|---|
GET/POST /api/redmine/config |
接続設定の取得(キーは返さない)/ 保存 |
GET /api/redmine/meta |
プロジェクト・トラッカー・ステータス・自分の情報 |
POST /api/redmine/draft |
下書きを作る(source=tasks / source=git) |
POST /api/redmine/issues |
起票をジョブとして開始。返り値の id で進捗を見る |
GET /api/redmine/job?id= |
起票ジョブの進捗と結果 |
解析の前提
- ターンの起点は人間の発言だけ。ツール実行結果・システム挿入・サブエージェント(
isSidechain)は起点にしない。 - 再開時に書かれるメタ行(attachment / system / mode / queue-operation)はターンの所要時間に数えない。 セッション全体の開始・終了時刻には反映する。
tool_resultを含む行は巨大なので JSON パースせず、正規表現でtimestampだけ拾う。- 解析結果は
(mtime, size)でキャッシュし、追記されただけのファイルは増えたぶんだけ読み足す (SessionParser.read())。書きかけの行は消費せず次回に回す。先頭と解析済み末尾のバイト列が 変わっていたら書き換えとみなして頭から読み直す。 - 追記解析は状態を持つので、同じファイルを複数スレッドで同時に解析しない(
Entry.lock)。
画面
- 3ペイン(プロジェクト / タスク一覧 / 詳細)。右ペインは やりとり・日別集計・ガントのタブ。
- 状態はすべて
app.js冒頭のstateオブジェクト。再描画はrender*()が#detail-bodyを作り直す方式。 - 自動追従(SSE)で作り直すときも、選択・スクロール位置・絞り込み・タブは保つ(
refreshFollow())。 - ガントの軸・背景・枠組みは
timeScale/axisSvg/gridSvg/ganttFrameをタスク単位と ターン単位で共有している。 /api/timelineは表示範囲より前から続くセッションの全体時間・往復数・ツール数も範囲内に切り詰め、clippedを立てて返す(作業時間だけ切り詰めると数字が食い違うため)。- Redmine 起票のダイアログは
rmオブジェクトが状態を持ち、renderRedmine()が中身を作り直す。 HTML 属性に NUL は入れられない(パーサが置換する)ので、タスクの識別はdata-project/data-idの 2 属性で持つ。
Redmine 連携の落とし穴
実際に踏んだもの。同じところで転ばないように。
- 作成時の
status_idは無視される(既定の New で入る)。状態を指定したいなら作成後にPUT /issues/<id>.jsonで付け直す(Redmine.create()がやっている)。 - 連続アクセスに 403 を返すサーバがある。7〜8 秒あければ通る。
Redmine.throttle()が クライアント単位で間隔を保つので、クライアントは作り直さず使い回す。 - 自己署名証明書が普通なので、既定は検証なし(
verifySsl: false)。 - 起票は件数×間隔ぶん時間がかかるので HTTP を掴んだままにしない。
Jobsに投げて進捗を取りに来てもらう。