Files
chktime/CLAUDE.md
T
yoshikazuandClaude Opus 5 a458509014 Redmine へチケットを起票するボタンを追加
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>
2026-09-02 13:55:46 +09:00

6.3 KiB
Raw Blame History

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 に投げて進捗を取りに来てもらう。