feat(worklog): 新增 token 用量統計、支援 Copilot 自動記錄,worklog 腳本改以 Node 實作

- transcript.py/wiki_api.py 等價移植為 transcript.mjs/wiki_api.mjs:extract/duration/redact/probe/pages/show/append 等子命令行為與 Python 版對拍一致;transcript.mjs 額外處理三個 Python→JS 移植地雷(json.dumps 間距、Unicode 碼點切片、Python repr 格式)。
- 新增 tokens 子命令與 turnTokens()/formatTokens():統計本輪 token 用量,Claude Code/Codex/Copilot 三種 transcript 格式皆支援(Copilot 逐輪只有 outputTokens,輸入固定「未判定」)。
- worklog.sh 條目改為七個固定 bullet(新增「token 用量」),hook 輸入解析同時接受 snake_case 與 Copilot 的 camelCase 欄位。
- hooks.json 的腳本定位邏輯補上 ~/.copilot/installed-plugins 搜尋路徑:先前只搜尋 .claude/.codex 的 plugins cache,導致 Copilot 上 hook 事件雖有觸發(agentStop 會被 Copilot 對應到 hooks.json 的 Stop key)卻找不到 worklog.sh。
- SKILL.md/README.md 同步更新腳本檔名、各助理支援範圍(Copilot 改為 ✅ 並附實測說明,Antigravity/OpenCode 補上不支援的具體原因)。
This commit is contained in:
2026-08-12 05:55:10 +00:00
parent ed7f29c10f
commit 0a992ba534
8 changed files with 1174 additions and 861 deletions
+20 -15
View File
@@ -11,24 +11,28 @@ description: 工作證明自動記錄(worklog)的操作與維護 skill。搭
| --- | --- | --- |
| `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束抽本輪內容 → 濃縮 → 遮蔽 → 追加到當週頁 |
| 本 skill `/jsc-doc:worklog` | 使用者/助理手動 | `--init`/`--tune`/`--diagnose`/`--append`/`--show` |
| `scripts/worklog/worklog.sh` | 上述兩者共用 | 主流程(單一實作,避免漂移):依 `WORKLOG_CLI` 呼叫 headless CLI,每筆整理成六個固定欄位 |
| `scripts/worklog/wiki_api.py` | 上述兩者共用 | token 解析、wiki 讀寫、append 重試、週頁命名 |
| `scripts/worklog/transcript.py` | 上述兩者共用 | 抽本輪片段、估算花費時間、機密遮蔽 |
| `scripts/worklog/worklog.sh` | 上述兩者共用 | 主流程(單一實作,避免漂移):依 `WORKLOG_CLI` 呼叫 headless CLI,每筆整理成七個固定欄位(含 token 用量) |
| `scripts/worklog/wiki_api.mjs` | 上述兩者共用 | token 解析、wiki 讀寫、append 重試、週頁命名 |
| `scripts/worklog/transcript.mjs` | 上述兩者共用 | 抽本輪片段、估算花費時間、統計 token 用量、機密遮蔽 |
### 各助理支援範圍
| 功能 | Claude Code | Codex | Antigravity | OpenCode | GitHub Copilot |
| --- | --- | --- | --- | --- | --- |
| `Stop` hook 自動記錄 | ✅ | ✅ 需可讀 Codex session JSONL | ❌ | ❌ | ❌ |
| `Stop` hook 自動記錄 | ✅ | ✅ 需可讀 Codex session JSONL | ❌ 無 hook 機制 | ❌ 無 hook 機制 | ✅(2026/08/11 實測確認,見下方說明) |
| `--init`/`--diagnose`/`--append`/`--show` | ✅ | ⚠️ 需 plugin 目錄保留 `scripts/`(安裝後請實測一次) | ⚠️ 同左 | ⚠️ 需完整 plugin 目錄 | ⚠️ 需 plugin 目錄保留 `scripts/` |
| 摘要 CLI | `claude -p` | `codex exec` | `agy -p` | `opencode run` | `copilot -p` |
| `--tune` | ✅ | ❌ 無 `claude-api` skill 可載入 | ❌ 同左 | ❌ | ❌ |
| token 用量統計 | ✅ 輸入/輸出皆可得 | ✅ 輸入/輸出皆可得 | ❌ 無 transcript 解析器 | ❌ 無 transcript 解析器 | ⚠️ 只有輸出,輸入固定「未判定」(見下方說明) |
兩個限制的來源:
限制與實測結果的來源:
- **`Stop` hook 只有相容 hook 環境實際執行**;Claude Code 先用 `CLAUDE_PLUGIN_ROOT` 定位腳本,找不到時再掃 `~/.claude/plugins/cache` 與 `~/.codex/plugins/cache`,最後命中 `*/jsc-doc/*/scripts/worklog/worklog.sh`。`transcript.py` 目前支援 Claude Code transcript JSONL(`type` / `message.content` blocks)與 Codex session JSONL(`payload` events / response items),其他助理若提供等效 hook,必須先補對應 transcript 解析器。
- **`Stop` hook 只有相容 hook 環境實際執行**;Claude Code 先用 `CLAUDE_PLUGIN_ROOT` 定位腳本,找不到時依序掃 `~/.claude/plugins/cache`、`~/.codex/plugins/cache`、`~/.copilot/installed-plugins`,最後命中 `*/jsc-doc/*/scripts/worklog/worklog.sh`。`transcript.mjs` 支援 Claude Code transcript JSONL(`type` / `message.content` blocks)、Codex session JSONL(`payload` events / response items)與 Copilot events.jsonl(`type` 為 `user.message`/`assistant.message`/`tool.execution_complete` 的 `data.*` 欄位),其他助理若提供等效 hook,必須先補對應 transcript 解析器。
- **Copilot 支援細節(2026/08/11 實測)**:Copilot CLI 內部事件名稱是 `agentStop`(不是 `Stop`),欄位為 camelCase(`sessionId`/`transcriptPath`,只有 `stop_hook_active` 例外仍是 snake_case);但實測確認 **Copilot 的 plugin 載入器會把 `hooks/hooks.json` 裡的 `Stop` key 自動對應到它自己的 `agentStop` 事件**,本檔不需要另外宣告 `agentStop` key。transcript 路徑固定為 `~/.copilot/session-state/<sessionId>/events.jsonl`,且 hook payload 直接帶 `transcriptPath`,不需要像 Codex 分支那樣自己用 session id 反查檔案。**Copilot 逐輪只記錄 `assistant.message.data.outputTokens`,沒有對應的輸入 token 欄位**;`session.shutdown.modelMetrics` 雖然有完整輸入/輸出,但那是整個 session 結束才寫的累計值,語意不是「本輪」,故 Copilot 的輸入 token 一律固定輸出「未判定」,不得用該欄位冒充本輪數字。
- **Antigravity(`agy`)不支援自動記錄的兩個具體原因**:(1) `agy --help` 沒有任何 hook 相關子指令或設定項,CLI 本身不提供事件觸發點(觸發點缺);(2) 就算有觸發點,`agy` 的對話記錄存在 `~/.gemini/antigravity-cli/conversations/*.db` 內,內容是 `step_payload`/`gen_metadata` 等欄位的 protobuf 二進位 blob、`step_type` 是數字 enum,沒有公開 `.proto` schema 可解析(資料缺)。兩個條件都不成立,之後要支援得兩者都解決,不是單純補一支 transcript 解析器就好。
- **OpenCode 不支援自動記錄的原因**:目前的 skill 目錄安裝法本來就不含 `scripts/`,且其 CLI 同樣未見 hook 機制文件;若之後提供對應的 hook 機制與可讀的 transcript 格式,才有辦法補上,純粹「以完整 plugin 目錄執行」不足以讓自動記錄運作。
- **OpenCode 以「複製 `skills/` 目錄」安裝**時不會帶入 `scripts/`,本 skill 的所有模式都無法執行;若以完整 plugin 目錄執行並能解析 `scripts/worklog`,可用 `WORKLOG_CLI=opencode` 作為摘要 CLI。
- 其他助理若要用 `--append`/`--show` 等純 wiki 操作,只需 `python3`;摘要路徑需要 README 定義的任一 headless CLI。`--tune` 仍是 Claude Code 專屬,其他 CLI 使用各自預設模型或手動設定其 CLI 行為。
- 其他助理若要用 `--append`/`--show` 等純 wiki 操作,只需 `node`;摘要路徑需要 README 定義的任一 headless CLI。`--tune` 仍是 Claude Code 專屬,其他 CLI 使用各自預設模型或手動設定其 CLI 行為。
### 腳本路徑解析(重要)
@@ -79,8 +83,8 @@ WORKLOG_DIR="<skill base directory>/../../scripts/worklog"
### `--init`
1. 執行 `python3 "${WORKLOG_DIR}/wiki_api.py" probe`,回報 token 來源、Gitea 版本、當週頁狀態。
2. 當週頁不存在 → 執行 `python3 "${WORKLOG_DIR}/wiki_api.py" init` 建立(wiki 尚未初始化時一併初始化)。
1. 執行 `node "${WORKLOG_DIR}/wiki_api.mjs" probe`,回報 token 來源、Gitea 版本、當週頁狀態。
2. 當週頁不存在 → 執行 `node "${WORKLOG_DIR}/wiki_api.mjs" init` 建立(wiki 尚未初始化時一併初始化)。
3. 以表格印出應寫入 `~/.bashrc` 的 `WORKLOG_*` 變數清單;**不自動改使用者的 shell profile**(需人工確認的狀態變更)。
### `--tune`(Claude Code 專屬)
@@ -103,10 +107,10 @@ WORKLOG_DIR="<skill base directory>/../../scripts/worklog"
| 檢查項 | 判準 |
| --- | --- |
| `python3`/摘要 CLI | `python3` 與 `WORKLOG_CLI` 指定或 auto 選到的 CLI 是否找得到 |
| `node`/摘要 CLI | `node` 與 `WORKLOG_CLI` 指定或 auto 選到的 CLI 是否找得到 |
| `WORKLOG_*` 變數 | 必要三項是否齊全、`WORKLOG_SCOPE` 是否把當前路徑排除 |
| `scripts/worklog` 目錄 | `${WORKLOG_DIR}` 是否解析成功且三支腳本存在(不存在=此助理不支援) |
| token | `python3 "${WORKLOG_DIR}/wiki_api.py" probe` 的 token 來源與驗證結果 |
| token | `node "${WORKLOG_DIR}/wiki_api.mjs" probe` 的 token 來源與驗證結果 |
| wiki API | Gitea 版本、`repos/<repo>` 與當週頁狀態 |
| 摘要設定 | `WORKLOG_CLI`、選到的 CLI、Claude 模型快取是否存在與是否過期 |
| hook 註冊 | `hooks/hooks.json` 是否存在且 plugin 已啟用 |
@@ -123,13 +127,14 @@ WORKLOG_DIR="<skill base directory>/../../scripts/worklog"
- 任務狀態:<完成/進行中/待確認/受阻>
- 遇到的困難:<困難或未遇到明確困難>
- 解決方式:<處理方式或不需額外處理>
- token 用量:<輸入/輸出或未判定>
```
專案取當前工作目錄的 `<owner>/<repo>`;內容仍會過 `python3 "${WORKLOG_DIR}/transcript.py" redact` 遮蔽後才寫入。
專案取當前工作目錄的 `<owner>/<repo>`;內容仍會過 `node "${WORKLOG_DIR}/transcript.mjs" redact` 遮蔽後才寫入。手動補寫沒有 transcript 可解析,`token 用量` 固定寫「未判定」。
### `--show`
讀當週頁(`python3 "${WORKLOG_DIR}/wiki_api.py" show`)並以表格摘要本週工作,用於回顧與週報。
讀當週頁(`node "${WORKLOG_DIR}/wiki_api.mjs" show`)並以表格摘要本週工作,用於回顧與週報。
---
@@ -139,7 +144,7 @@ WORKLOG_DIR="<skill base directory>/../../scripts/worklog"
- 週頁名稱:`Worklog-<yyyy>-<MM>-W<n>`,`n` =該週起始的星期六是當月第幾個星期六(例:`2026/07/29 三` 屬於 `07/25 六` 那一週 → `Worklog-2026-07-W4`)。
- 跨月的一週歸屬**起始星期六**所在的月份,確保同一週只有一頁(例:`2026/08/29 六 ~ 09/04 五` 全部寫入 `Worklog-2026-08-W5`)。
- 頁首標題:`# <yyyy> 年 <MM> 月 第 <n> 週工作紀錄(<起始日> 六 ~ <結束日> 五)`,日期範圍讓人一眼看出這頁涵蓋哪幾天。
- 每筆條目:`## <時間> — <專案>` +六個固定 bullet(專案/任務名稱、執行細節與產出、花費時間、任務狀態、遇到的困難、解決方式);標題行尾帶 HTML 註解 marker(`<!-- worklog:… -->`)供寫後驗證與去重,wiki 渲染時不顯示。
- 每筆條目:`## <時間> — <專案>` +七個固定 bullet(專案/任務名稱、執行細節與產出、花費時間、任務狀態、遇到的困難、解決方式、token 用量);標題行尾帶 HTML 註解 marker(`<!-- worklog:… -->`)供寫後驗證與去重,wiki 渲染時不顯示。
- 多 session 同時寫入:`append_entry` 採「讀取 → 合併 → 寫回 → 寫後讀取驗證 marker」,未落地則重讀最新內容重試,最多 3 次。
---
@@ -149,7 +154,7 @@ WORKLOG_DIR="<skill base directory>/../../scripts/worklog"
| 防線 | 位置 | 內容 |
| --- | --- | --- |
| 1 | 濃縮提示詞 | 明令不得輸出 token/密碼/API key/連線字串/Email/電話/姓名/身分證號 |
| 2 | `transcript.py` 的 `redact` | 正則遮蔽:URL 內嵌憑證、40 字元 hex token、`gh?_`/`sk-` token、`token=`/`password=`、`Authorization:`、Email、台灣手機、身分證號 |
| 2 | `transcript.mjs` 的 `redact` | 正則遮蔽:URL 內嵌憑證、40 字元 hex token、`gh?_`/`sk-` token、`token=`/`password=`、`Authorization:`、Email、台灣手機、身分證號 |
第二道防線不可移除 —— 模型有可能沒遵守指令,而 wiki 一旦寫入就留在 git 歷史裡。