Files
chktime/CLAUDE.md
T

114 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chktime
Claude Code の会話ログ(`~/.claude/projects/<プロジェクト>/<セッションID>.jsonl`)を解析して、
タスクごとのやりとりの時刻を表示するローカル Web アプリ。使い方は [README.md](README.md) に書いてある。
## 構成
```
chktime.py サーバ + jsonl 解析 + Redmine 連携 + n8n 連携(これ1本で完結)
web/index.html 画面の骨組み
web/app.js 表示ロジック(フレームワークなし・素の DOM 操作)
web/style.css スタイル(ダークテーマ固定)
web/n8n.js 「n8n へ送る」タブ(app.js の後に読む)
run.bat コンソール表示で起動
stop.cmd 起動中の chktime を終了
redmine.json Redmine 連携の設定(自動生成・git 管理外)
n8n.json n8n 連携の設定(合言葉を含む・git 管理外)
tests/ unittest(python -m unittest discover -s tests -v)
```
`chktime.py` の中身は上から
「解析ロジック → Store(キャッシュ)/ Watcher(更新検出)→ Redmine 連携 → HTTP」の順。
## 守ること
- **標準ライブラリのみ**。外部パッケージも CDN も使わない。ブラウザ側もフレームワークなし。
- **外部通信は Redmine 連携と n8n への送信だけ**。それ以外はローカルの jsonl を読むだけで、どこにも接続しない。
新しい通信先を増やすときは、この行を書き換えるところから始める。
- **API キーをリポジトリに入れない**。`redmine.json` は `.gitignore` 済み。ログにも出さない
(`/api/redmine/config` はキーの有無だけ返す)。
- 会話ログは**読み取り専用**。検証で書き換えるときはスクラッチパッドにコピーしてから行う。
- `.bat` / `.cmd` は cmd の既定コードページで読まれるので**中身は ASCII のみ**(日本語を書かない)。
- コメント・コミットメッセージ・UI の文言は日本語。
- 機能を足したら README.md も更新する。
## 動かす
```bash
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 連携:
| ルート | 内容 |
| --- | --- |
| `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=` | 起票ジョブの進捗と結果 |
| `GET /api/redmine/search?q=` | チケット選択用の検索(未完了のみ。数字だけなら番号で引く) |
n8n 連携(work_log への取り込み。送り先は n8n の `work-tracker--import`):
| ルート | 内容 |
| --- | --- |
| `GET/POST /api/n8n/config` | 接続設定の取得(合言葉は返さない)/ 保存 |
| `GET /api/n8n/blocks?from=&to=&project=` | 作業区間の一覧。n8n の送信済み・削除済み・重なりを付ける |
| `POST /api/n8n/send` | 選んだ区間を送る。時刻はサーバーがログから計算し直す |
## 解析の前提
- ターンの起点は**人間の発言だけ**。ツール実行結果・システム挿入・サブエージェント(`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` に投げて進捗を取りに来てもらう。