Merge pull request 'release: wiki 目錄頁專用存取庫、HASH 完整 40 碼、閘門依 CLI 分流' (#32) from develop into master

Reviewed-on: #32
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
This commit was merged in pull request #32.
This commit is contained in:
2026-09-02 04:21:02 +00:00
12 changed files with 165 additions and 61 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-log",
"version": "0.1.4",
"version": "0.1.5",
"description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-log",
"version": "0.1.4",
"version": "0.1.5",
"description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)",
"skills": "./skills",
"jsc": {
+10 -6
View File
@@ -1,6 +1,6 @@
# jsc-log — 工作日誌與統計
jsc 技能組的 log domain:工作完成後把十項資訊寫入 wiki 日誌頁(`LOG_{HASH}`,`HASH` 依共享規則計算;首碼若是數字或 `A`/`B`/`C`,改成 `H` 加上原前 7 碼;頁內仍按工作週週五整理),統計技能使用次數與呼叫鏈次數,並把技能執行的教訓記到 `LEARN_{HASH}`,供下次執行前查閱。
jsc 技能組的 log domain:工作完成後把十項資訊寫入 wiki 日誌頁(`LOG_{HASH}`,`HASH` 依共享規則計算,取 `{owner}/{repo}` 的完整 SHA-1 四十碼、a-f 轉大寫,不截短也不加前綴;頁內仍按工作週週五整理),統計技能使用次數與呼叫鏈次數,並把技能執行的教訓記到 `LEARN_{HASH}`,供下次執行前查閱。目錄頁(`LOG_CONTENTS`、`LEARN_CONTENTS`、`REPORT_CONTENTS`)另住一個專用存取庫,與內容頁分開。
## 安裝、更新、移除
@@ -24,7 +24,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| --- | --- |
| `tools/usage-stats.sh` | 聚合 `$JSC_HOME/usage/*.jsonl`:`skills` 列技能使用次數、`chains` 列呼叫鏈次數(皆降冪),`--cli <name>` 過濾 |
| `tools/worklog-target.sh` | 接收已由 `jsc-gitea/tools/hash-id` 算好的 `HASH`,組出 `LOG_{HASH}`、`LOG_CONTENTS`(本身不再計算 SHA-1)。`friday [yyyy-MM-dd]` 印出該工作週的週五,週界向 `report-range.sh weekly` 取得,這裡只加四天——跨月、跨年那一週交給人算就會差一天 |
| `tools/worklog-pending.sh` | 待寫入日誌的暫存區,存放於 `$JSC_HOME/worklog-pending/{HASH}/`。`add {HASH} {檔案}` 存一段內容(`jsc-sdlc` 的階段回報發現沒寫日誌時會呼叫),`cat {HASH}` 依時間印出全部、`list` 列路徑、`clear` 清掉全部。寫日誌走三段式:`merge {HASH} {本次條目檔}` 合成「暫存內容在前、本次條目在後」並印出 `MERGED=`、`CLAIM=`、`PENDING=`;wiki 寫入成功後 `commit {HASH} {CLAIM}` 只清掉併入清單上那幾個檔;寫入失敗就 `abort {HASH} {CLAIM}`,暫存一個都不刪。結束碼 `3` 代表沒有暫存內容(`merge` 沒暫存仍是 `0`)。**清除只發生在寫進 wiki 成功之後**,先清再寫會兩邊都沒有 |
| `tools/worklog-pending.sh` | 待寫入日誌的暫存區,存放於 `$JSC_HOME/worklog-pending/{HASH}/`。`{HASH}` 只收三種:40 碼大寫十六進位(現行),或 8 碼大寫十六進位、`H` 加 7 碼大寫十六進位(尚未遷移的舊暫存;舊規則把首碼落在 `0-9ABC` 的 hash 改寫成 `H` 加原前 7 碼,所以舊暫存大多長這樣),其餘一律 `2`——這道格式檢查擋的是路徑穿越。`add {HASH} {檔案}` 存一段內容(`jsc-sdlc` 的階段回報發現沒寫日誌時會呼叫),`cat {HASH}` 依時間印出全部、`list` 列路徑、`clear` 清掉全部。寫日誌走三段式:`merge {HASH} {本次條目檔}` 合成「暫存內容在前、本次條目在後」並印出 `MERGED=`、`CLAIM=`、`PENDING=`;wiki 寫入成功後 `commit {HASH} {CLAIM}` 只清掉併入清單上那幾個檔;寫入失敗就 `abort {HASH} {CLAIM}`,暫存一個都不刪。結束碼 `3` 代表沒有暫存內容(`merge` 沒暫存仍是 `0`)。**清除只發生在寫進 wiki 成功之後**,先清再寫會兩邊都沒有 |
| `tools/report-range.sh` | 算報表期間:`report-range.sh {daily\|weekly\|monthly\|yearly} [yyyy-MM-dd]` 印出「起<TAB>訖<TAB>標籤<TAB>期間」,含頭含尾。週採 ISO-8601(週一起算),標籤如 `2026-W35`。日期運算交給系統的 `date`,不自己算閏年 |
| `tools/report-template.sh` | 解析報表範本位置:`resolve {period}` 印出「路徑<TAB>project\|skill」,`list` 一次列四種期間。工作目錄的 `.jsc/templates/report-{period}.md` 優先於技能自帶的 `templates/` |
| `tools/log-aggregate.sh` | 把日誌頁彙總成報表數字:`log-aggregate.sh {起} {訖} [日誌頁檔案 ...]`(省略檔案就讀標準輸入),印出 `ENTRIES=`、`REPOS=`、`REPO=`、`ELAPSED_MINUTES=`、`ELAPSED_ENTRIES=`、`ELAPSED_MISSING=`、`TOKEN=`、`TOKEN_MISSING=`、`STATUS=`。「無資料不估算」寫在腳本裡:沒填花費時間的條目不進總和,只進 `ELAPSED_MISSING`;整段期間都沒有時間就印「無資料」,不印 `0`。結束碼 `3` 代表期間內沒有條目,全零結果照樣印出來 |
@@ -40,7 +40,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
每完成一個任務就寫一筆日誌。任務有三種:一個工作包、一輪 PR 留言修正、一個獨立的修正提交。下一個任務開始前先把這一筆寫完,同一個工作包跑五輪留言修正就是五筆,各自帶自己的花費時間與 token 用量,附加到同一頁 `LOG_{HASH}`——連「試了卻沒改到檔案」的那一輪也留下來,那段時間才看得見。
每筆蒐集十項資訊(存取庫、分支、計畫連結、工作包連結、花費時間、token 用量、任務狀態、執行細節、困難與解決、PR 目標分支)。計畫連結、工作包連結、花費時間、token 用量四項來源互不相依,併行取得;存取庫解析與 `HASH` 計算也併行。用 `tools/worklog-target.sh` 產生目標頁,工作週的週五由同一支的 `friday` 子命令算出,套範本後附加到 `LOG_{HASH}` 與 `LOG_CONTENTS`。寫入前跑 `tools/worklog-pending.sh merge {HASH} {本次條目檔}`:之前有階段跑完沒寫日誌,內容暫存在那裡,這次一併寫進去;wiki 寫入成功才 `commit` 清掉暫存,失敗就 `abort` 保留。
每筆蒐集十項資訊(存取庫、分支、計畫連結、工作包連結、花費時間、token 用量、任務狀態、執行細節、困難與解決、PR 目標分支)。計畫連結、工作包連結、花費時間、token 用量四項來源互不相依,併行取得;存取庫解析與 `HASH` 計算也併行。用 `tools/worklog-target.sh` 產生目標頁,工作週的週五由同一支的 `friday` 子命令算出,套範本後附加到 `LOG_{HASH}`。目錄頁 `LOG_CONTENTS` 住在 `JSC_WIKI_REPO_CONTENTS` 解出的專用存取庫,與 `LOG_{HASH}` 不同 wiki,改由 `jsc-gitea/tools/wiki-contents.sh upsert LOG 2 {HASH}` 單列寫回:鍵取第 2 欄的裸 HASH,第 1 欄的絕對網址(`gitea.sh wiki-url` 給的)只給人點。網址帶主機名,換主機就比不到鍵,同一頁會多一列。寫入前跑 `tools/worklog-pending.sh merge {HASH} {本次條目檔}`:之前有階段跑完沒寫日誌,內容暫存在那裡,這次一併寫進去;wiki 寫入成功才 `commit` 清掉暫存,失敗就 `abort` 保留。
### `stats`
@@ -48,11 +48,11 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `learn`
技能執行後把教訓(日期、技能、CLI、情境、教訓、下次做法)附加到 `LEARN_{HASH}` 並更新 `LEARN_CONTENTS`;技能執行前查閱同兩頁,套用相符的「下次做法」。
技能執行後把教訓(日期、技能、CLI、情境、教訓、下次做法)附加到 `LEARN_{HASH}`,並用 `jsc-gitea/tools/wiki-contents.sh upsert` 更新 `LEARN_CONTENTS`;技能執行前查閱同兩頁,套用相符的「下次做法」。兩頁分屬不同存取庫:`LEARN_{HASH}` 取 `JSC_WIKI_REPO_LEARN`,`LEARN_CONTENTS` 取 `JSC_WIKI_REPO_CONTENTS`,列裡的連結用絕對網址。
### `report`
把工作日誌總結成年報、月報、週報或日報。期間由 `tools/report-range.sh` 算出(週次採 ISO-8601),範本由 `tools/report-template.sh` 解析——工作目錄的 `.jsc/templates/report-{period}.md` 優先,沒有才用技能自帶的那份。範本解析、日誌頁讀取、教訓頁讀取三線併行,各頁也一頁一個 sub agent 同時讀。讀 `LOG_CONTENTS` 列出的所有日誌頁後,交給 `tools/log-aggregate.sh` 算出條目數、涵蓋存取庫、花費時間、各 CLI token 用量與狀態計數,填進範本後寫入 wiki `REPORT_{HASH}`(`HASH` 取 `{owner}/{repo}/{期間}`,這裡的 `{owner}/{repo}` 取 REPORT wiki 存取庫,不是程式碼存取庫——本頁其他 `HASH` 取的是程式碼存取庫,只有這一處不同),同一期間重跑只換掉那一節。年報的教訓頁另解 `JSC_WIKI_REPO_LEARN`,不沿用日誌頁的存取庫。單筆工作紀錄請用 `worklog`。
把工作日誌總結成年報、月報、週報或日報。期間由 `tools/report-range.sh` 算出(週次採 ISO-8601),範本由 `tools/report-template.sh` 解析——工作目錄的 `.jsc/templates/report-{period}.md` 優先,沒有才用技能自帶的那份。範本解析、日誌頁讀取、教訓頁讀取三線併行,各頁也一頁一個 sub agent 同時讀。目錄頁 `LOG_CONTENTS` 取 `JSC_WIKI_REPO_CONTENTS` 的專用存取庫,它列出的日誌頁用絕對網址逐頁讀回;讀完交給 `tools/log-aggregate.sh` 算出條目數、涵蓋存取庫、花費時間、各 CLI token 用量與狀態計數,填進範本後寫入 wiki `REPORT_{HASH}`(`HASH` 取 `{owner}/{repo}/{期間}` 的完整 40 碼,這裡的 `{owner}/{repo}` 取 REPORT wiki 存取庫,不是程式碼存取庫——本頁其他 `HASH` 取的是程式碼存取庫,只有這一處不同),同一期間重跑只換掉那一節。`REPORT_CONTENTS` 那一列改由 `jsc-gitea/tools/wiki-contents.sh upsert REPORT 2 {HASH}` 寫回,鍵同樣取裸 HASH 那一欄。年報的教訓「內容頁」另解 `JSC_WIKI_REPO_LEARN`,不沿用日誌頁的存取庫;目錄頁同住 CONTENTS 存取庫是另一回事,兩者別混。單筆工作紀錄請用 `worklog`。
<!-- JSC-SKILLS:END -->
@@ -74,7 +74,11 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
## 環境變數
Wiki 位置:日誌頁用 `JSC_WIKI_REPO_LOG`、教訓頁用 `JSC_WIKI_REPO_LEARN`、報表頁用 `JSC_WIKI_REPO_REPORT`,未設定退回 `JSC_WIKI_REPO`,再未設定就詢問(見 `jsc-gitea`)。先讀目前 shell 繼承的環境變數,缺值才詢問。
Wiki 位置分兩層:**內容頁**一頁型一個變數——日誌頁 `LOG_{HASH}` 用 `JSC_WIKI_REPO_LOG`、教訓頁 `LEARN_{HASH}` 用 `JSC_WIKI_REPO_LEARN`、報表頁 `REPORT_{HASH}` 用 `JSC_WIKI_REPO_REPORT`;**目錄頁**三頁共用一個變數——`LOG_CONTENTS`、`LEARN_CONTENTS`、`REPORT_CONTENTS` 一律用 `JSC_WIKI_REPO_CONTENTS`。
以上每個變數未設定都退回 `JSC_WIKI_REPO`,再未設定就詢問(見 `jsc-gitea`)。先讀目前 shell 繼承的環境變數,缺值才詢問。
目錄頁與內容頁從此分屬不同存取庫,兩件事跟著改:`JSC_WIKI_REPO_CONTENTS` 不會退回頁型變數,`JSC_WIKI_REPO_LOG` 之類也頂不了目錄頁的位;目錄頁指向內容頁的連結一律用 `gitea.sh wiki-url` 的絕對網址,`[[頁名]]` 只在同一個 wiki 內解得開,跨庫就是死連結。
## 相關 domain
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-log",
"version": "0.1.4",
"version": "0.1.5",
"description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)",
"skills": "./skills/",
"jsc": {
+12 -12
View File
@@ -7,20 +7,20 @@
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 一次技能執行留下可重用的教訓時,用 record 模式記錄。要跑某支技能之前,用 consult 模式查過去的教訓。工時與 Token 紀錄不走這支,走 worklog |
| 關鍵步驟 | record 模式收齊日期、技能、CLI、情境、教訓、下次做法這六欄、從 `git remote get-url origin` 解出 `{owner}/{repo}`、用 `hash-id` 算出 `{HASH}`、開 sub agent 讀 `LEARN_{HASH}`、依 `wiki-get` 的退出碼分支(0 在表尾追加一列、4 才用 `templates/learn-page.md` 建頁、7 與 8 停止並回報)、同一輪更新 `LEARN_CONTENTS`、寫入失敗重試一次;consult 模式算出 `{HASH}`、讀 `LEARN_CONTENTS` 與 `LEARN_{HASH}`、挑出「技能」欄相符的列、整理每一列的「下次做法」交給呼叫端 |
| 外部呼叫 | `jsc-gitea/tools/hash-id`、`jsc-gitea/tools/gitea.sh wiki-repo LEARN`、`jsc-gitea:wiki`(全部 wiki 讀寫)、`git remote get-url origin`、`templates/learn-page.md`、`templates/learn-contents.md` |
| 完成條件 | record 模式要 sub agent 回報兩頁都寫入成功,主代理確認新列在 `LEARN_{HASH}` 上,原有的列一列不少。consult 模式要兩頁都讀到,或以退出碼 4 回報頁面不存在,或在 5、7、8 停止並回報狀態 |
| 可驗證跡象 | wiki 的 `LEARN_{HASH}` 表尾多一列教訓,`LEARN_CONTENTS` 上該 repo 那一列的「最後更新時間」換新。頁面原本不存在時,會新建 `LEARN_{HASH}` 或 `LEARN_CONTENTS`。consult 模式無寫入跡象,只有回報內容 |
| 關鍵步驟 | record 模式收齊日期、技能、CLI、情境、教訓、下次做法這六欄、從 `git remote get-url origin` 解出 `{owner}/{repo}`、用 `hash-id` 算出完整 40 碼大寫的 `{HASH}`、用 `wiki-repo LEARN` 解出教訓頁存取庫、開 sub agent 讀 `LEARN_{HASH}`、依 `wiki-get` 的退出碼分支(0 在表尾追加一列、4 才用 `templates/learn-page.md` 建頁、7 與 8 停止並回報)、取 `wiki-url` 的絕對網址、同一輪跑 `wiki-contents.sh upsert LEARN 1 {owner}/{repo}` 更新落在 CONTENTS 存取庫的 `LEARN_CONTENTS`、依它的退出碼分流(0 已寫、1 寫入失敗、2 參數錯、3 未設 `JSC_WIKI_REPO_CONTENTS`、4 缺範本、7 金鑰失效、8 其他 API 失敗)、寫入失敗重試一次;consult 模式算出 `{HASH}`、從 CONTENTS 存取庫讀 `LEARN_CONTENTS`、從 LEARN 存取庫讀 `LEARN_{HASH}`、挑出「技能」欄相符的列、整理每一列的「下次做法」交給呼叫端 |
| 外部呼叫 | `jsc-gitea/tools/hash-id`、`jsc-gitea/tools/gitea.sh wiki-repo LEARN` 與 `wiki-repo CONTENTS` 與 `wiki-url`、`jsc-gitea/tools/wiki-contents.sh upsert`(目錄頁那一列)、`jsc-gitea:wiki`(其餘 wiki 讀寫)、`git remote get-url origin`、`templates/learn-page.md`、`templates/learn-contents.md` |
| 完成條件 | record 模式要 sub agent 回報兩頁都寫入成功,主代理確認新列在 `LEARN_{HASH}` 上,原有的列一列不少,`wiki-contents.sh` 回 0 並印出 `updated` 或 `added`。consult 模式要兩頁都讀到,或以退出碼 4 回報頁面不存在,或在 5、7、8 停止並回報狀態 |
| 可驗證跡象 | LEARN 存取庫的 `LEARN_{HASH}` 表尾多一列教訓;CONTENTS 存取庫的 `LEARN_CONTENTS` 上該 repo 那一列的「最後更新時間」換新,且「教訓紀錄」欄是絕對網址,不是 `[[頁名]]`。頁面原本不存在時,會新建 `LEARN_{HASH}` 或 `LEARN_CONTENTS`。consult 模式無寫入跡象,只有回報內容 |
## report
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 有人要一段期間的工作總結時用,期間分年、月、週、日四種。單一工作包的紀錄不走這支,走 worklog |
| 關鍵步驟 | 呼叫端沒指定期間就依 `jsc-ask:ask` 問、跑 `tools/report-range.sh` 取得起訖日與標籤、平行做四件事(`tools/report-template.sh resolve` 解出範本、讀 `LOG_CONTENTS` 與它列出的每一頁工作日誌、年報另外讀 `LEARN_CONTENTS`、解出 REPORT 的 wiki repo)、把頁面內容存成檔案後跑 `tools/log-aggregate.sh` 算出條目數、涵蓋 repo、花費時間、Token 用量、任務狀態、從存活條目挑出阻塞與未完成工作包、照範本的標題與表格填出報告、讀 `REPORT_{HASH}` 後把本期當成新章節追加在最前、更新 `REPORT_CONTENTS` |
| 外部呼叫 | `tools/report-range.sh`、`tools/report-template.sh`、`tools/log-aggregate.sh`、`jsc-gitea/tools/gitea.sh wiki-repo`(LOG、LEARN、REPORT)與 `hash-id`、`jsc-gitea:wiki`、`jsc-ask:ask`、`templates/report-{period}.md`、`templates/report-contents.md` |
| 完成條件 | 回報頁面 URL,或回報跳過寫入與它的原因,或在讀取回 7、8 時停下並回報狀態。收尾要講出期間標籤、條目數、涵蓋的 repo、範本來源、頁面 URL、帶到下一期的未完成工作包清單,以及 `ELAPSED_MISSING` 與 `TOKEN_MISSING` |
| 可驗證跡象 | wiki 的 `REPORT_{HASH}`(雜湊取自 `{owner}/{repo}/{period}`)多一個本期章節,`REPORT_CONTENTS` 該列的「最新一期」、「期數」、「最後更新」換新。本機留下工作日誌頁面內容的暫存檔,供 `log-aggregate.sh` 讀取。沒設定 REPORT wiki repo 時不寫 wiki,只印出報告本文 |
| 關鍵步驟 | 呼叫端沒指定期間就依 `jsc-ask:ask` 問、跑 `tools/report-range.sh` 取得起訖日與標籤、平行做四件事(`tools/report-template.sh resolve` 解出範本、從 CONTENTS 存取庫讀 `LOG_CONTENTS` 並照列上的絕對網址逐頁讀回工作日誌、年報另外讀 `LEARN_CONTENTS` 並用 `wiki-repo LEARN` 解出教訓內容頁的存取庫、解出 REPORT 的 wiki repo)、把頁面內容存成檔案後跑 `tools/log-aggregate.sh` 算出條目數、涵蓋 repo、花費時間、Token 用量、任務狀態、從存活條目挑出阻塞與未完成工作包、照範本的標題與表格填出報告、用 `hash-id "{REPORT wiki 存取庫}/{period}"` 算出完整 40 碼頁名、讀 `REPORT_{HASH}` 後把本期當成新章節追加在最前、取 `wiki-url` 的絕對網址並依它的退出碼分流(4 是頁還沒寫、5 是頁上沒有 `html_url`、7 與 8 一律停下並回報金鑰或 API 狀態,不得當成 4)、最後跑 `wiki-contents.sh upsert REPORT 2 {HASH}` 更新落在 CONTENTS 存取庫的 `REPORT_CONTENTS`(鍵取第 2 欄的裸 HASH,不取第 1 欄那個帶主機名的連結) |
| 外部呼叫 | `tools/report-range.sh`、`tools/report-template.sh`、`tools/log-aggregate.sh`、`jsc-gitea/tools/gitea.sh wiki-repo`(CONTENTS、LOG、LEARN、REPORT)與 `hash-id` 與 `wiki-url`、`jsc-gitea/tools/wiki-contents.sh upsert`(目錄頁那一列)、`jsc-gitea:wiki`、`jsc-ask:ask`、`templates/report-{period}.md`、`templates/report-contents.md` |
| 完成條件 | 回報頁面 URL,或回報跳過寫入與它的原因,或在讀取回 7、8 時停下並回報狀態。`wiki-contents.sh upsert` 要回 0,回 1、2、3、4、7、8 就照該碼回報且不得謊報已寫入。收尾要講出期間標籤、條目數、涵蓋的 repo、範本來源、頁面 URL、帶到下一期的未完成工作包清單,以及 `ELAPSED_MISSING` 與 `TOKEN_MISSING` |
| 可驗證跡象 | REPORT 存取庫的 `REPORT_{HASH}`(雜湊取自 REPORT wiki 存取庫的 `{owner}/{repo}` 加期間,不是程式碼存取庫)多一個本期章節;CONTENTS 存取庫的 `REPORT_CONTENTS` 該列的「最新一期」、「期數」、「最後更新」換新,且「報表頁」欄是絕對網址、「HASH」欄是不帶連結的裸 HASH。重跑同一期間只換掉那一列,不會多出第二列。本機留下工作日誌頁面內容的暫存檔,供 `log-aggregate.sh` 讀取。沒設定 REPORT wiki repo 時不寫 wiki,只印出報告本文;沒設定 `JSC_WIKI_REPO_CONTENTS` 時內容頁照寫,只有目錄頁那一列沒動 |
## stats
@@ -37,7 +37,7 @@
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | implement 或 maintain 階段每結束一項任務就寫一筆。一項任務是一個工作包、一輪 PR 意見修正,或一次獨立的修正提交。下一項任務開始前就要寫完。規劃階段的筆記不走這支 |
| 關鍵步驟 | 全程由 sub agent 收集與寫入、平行取得十項事實(`git remote get-url origin` 的 repo、`git branch --show-current` 的分支、PLAN 頁絕對連結、ANALYZE 頁工作包絕對連結、`session-timer.sh report` 的花費時間、`token-usage.sh` 的各 CLI Token、任務狀態、細節與產出、困難與解法、PR 目標分支)、平行解出 LOG wiki repo 與 `{HASH}`、跑 `tools/worklog-target.sh` 取得頁名與本週五日期、用 `templates/log-entry.md` 填出單筆條目檔、跑 `tools/worklog-pending.sh merge` 併入待寫內容、讀 `PAGE` 後依退出碼分支(0 追加在頁尾、4 才建頁、7 與 8 停止且不建頁)、同一輪更新 `CONTENTS`、最後依成敗跑 `worklog-pending.sh commit` 或 `abort` |
| 外部呼叫 | `jsc-hooks/hooks/session-timer.sh report`、`tools/token-usage.sh`、`tools/worklog-target.sh`、`tools/worklog-pending.sh`、`jsc-gitea/tools/gitea.sh wiki-repo` 與 `wiki-url`、`jsc-gitea/tools/hash-id`、`jsc-gitea:wiki`、`jsc-ask:ask`、`git remote get-url origin`、`git branch --show-current`、`templates/log-entry.md`、`templates/log-contents.md` |
| 完成條件 | `MERGED` 的每一筆條目都在 `PAGE` 上,原有條目逐字不動,`CONTENTS` 該列帶著本週五日期且其他列不動,`worklog-pending.sh` 的 `commit` 或 `abort` 其中一個跑過並回報退出碼 |
| 可驗證跡象 | wiki 的 `LOG_{HASH}` 頁尾多一筆條目,`LOG_CONTENTS` 該列的「條目數」與「最後更新」換新。`$JSC_HOME/worklog-pending/{HASH}` 底下的待寫檔在 `commit` 後清空,`abort` 後原樣保留。本機留下填好的條目檔 |
| 關鍵步驟 | 全程由 sub agent 收集與寫入、平行取得十項事實(`git remote get-url origin` 的 repo、`git branch --show-current` 的分支、PLAN 頁絕對連結、ANALYZE 頁工作包絕對連結、`session-timer.sh report` 的花費時間、`token-usage.sh` 的各 CLI Token、任務狀態、細節與產出、困難與解法、PR 目標分支)、平行解出 LOG wiki repo(只供內容頁)與完整 40 碼大寫的 `{HASH}`、跑 `tools/worklog-target.sh` 取得頁名與本週五日期、用 `templates/log-entry.md` 填出單筆條目檔、跑 `tools/worklog-pending.sh merge` 併入待寫內容、讀 `PAGE` 後依退出碼分支(0 追加在頁尾、4 才建頁、7 與 8 停止且不建頁)、同一輪用 `wiki-repo CONTENTS` 解出目錄頁存取庫、取 `wiki-url` 的絕對網址、跑 `wiki-contents.sh upsert LOG 2 {HASH}` 更新 `LOG_CONTENTS`(鍵取第 2 欄的裸 HASH,不取第 1 欄那個帶主機名的連結)並依 0、1、2、3、4、7、8 各自分流、最後依成敗跑 `worklog-pending.sh commit` 或 `abort` |
| 外部呼叫 | `jsc-hooks/hooks/session-timer.sh report`、`tools/token-usage.sh`、`tools/worklog-target.sh`、`tools/worklog-pending.sh`、`jsc-gitea/tools/gitea.sh wiki-repo`(LOG 與 CONTENTS)與 `wiki-url`、`jsc-gitea/tools/wiki-contents.sh upsert`(目錄頁那一列)、`jsc-gitea/tools/hash-id`、`jsc-gitea:wiki`、`jsc-ask:ask`、`git remote get-url origin`、`git branch --show-current`、`templates/log-entry.md`、`templates/log-contents.md` |
| 完成條件 | `MERGED` 的每一筆條目都在 `PAGE` 上,原有條目逐字不動,`wiki-contents.sh upsert` 回 0 且 `CONTENTS` 該列帶著本週五日期、其他列不動,`worklog-pending.sh` 的 `commit` 或 `abort` 其中一個跑過並回報退出碼。`upsert` 回非 0 就照該碼回報,並把步驟七當成失敗處理 |
| 可驗證跡象 | LOG 存取庫的 `LOG_{HASH}` 頁尾多一筆條目;CONTENTS 存取庫的 `LOG_CONTENTS` 該列的「條目數」與「最後更新」換新,且「日誌頁」欄是絕對網址,不是 `[[頁名]]`,「HASH」欄是不帶連結的裸 HASH。重跑同一頁只換掉那一列,不會多出第二列。`$JSC_HOME/worklog-pending/{HASH}` 底下的待寫檔在 `commit` 後清空,`abort` 後原樣保留;該目錄名是 40 碼大寫十六進位,或尚未遷移的舊暫存那種 8 碼大寫十六進位、`H` 加 7 碼大寫十六進位。本機留下填好的條目檔 |
+23 -7
View File
@@ -1,6 +1,6 @@
---
name: learn
description: Record a lesson learned after a skill run to wiki LEARN_{HASH} plus LEARN_CONTENTS, or consult past lessons before a skill run. Each entry is one table row with date, skill, CLI, situation, lesson, and next-time approach. HASH follows the shared 8-char rule with the H-prefix fallback. Use when a skill run produced a reusable lesson, or before running a skill to consult past lessons; not for work-time logs (see worklog).
description: Record a lesson learned after a skill run to wiki LEARN_{HASH} plus LEARN_CONTENTS, or consult past lessons before a skill run. Each entry is one table row with date, skill, CLI, situation, lesson, and next-time approach. HASH is the full 40-character uppercase SHA-1 of {owner}/{repo}; LEARN_{HASH} sits in the LEARN wiki repo while LEARN_CONTENTS sits in the separate CONTENTS repo, so the directory row goes through jsc-gitea/tools/wiki-contents.sh upsert and links the lesson page by its absolute wiki-url. Use when a skill run produced a reusable lesson, or before running a skill to consult past lessons; not for work-time logs (see worklog).
---
# learn — lessons learned
@@ -10,9 +10,10 @@ Close the loop on skill runs: record what a run taught you, consult it before th
## Target pages
- Directory page: `LEARN_CONTENTS`. Content page: `LEARN_{HASH}`, one page per repository.
- Compute `{HASH}` from `{owner}/{repo}` with `jsc-gitea/tools/hash-id`. Exit 1 means no SHA-1 helper on this machine: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash.
- Wiki repo: run `jsc-gitea/tools/gitea.sh wiki-repo LEARN`. Exit 3 hands the question to `jsc-gitea:wiki`, which owns the resolution order and the wording; exit 2 means the type argument was misspelled, so fix it and rerun.
- All wiki reads and writes go through `jsc-gitea:wiki`.
- The two pages live in different wikis. `LEARN_{HASH}` goes to `jsc-gitea/tools/gitea.sh wiki-repo LEARN`; `LEARN_CONTENTS` goes to `gitea.sh wiki-repo CONTENTS` (`JSC_WIKI_REPO_CONTENTS`, then `JSC_WIKI_REPO`, then exit 3), which never falls back to the LEARN repo. Exit 3 on either hands the question to `jsc-gitea:wiki`, which owns the resolution order and the wording; exit 2 means the type argument was misspelled, so fix it and rerun.
- Because they sit in different wikis, the directory row links the lesson page by the absolute URL from `gitea.sh wiki-url <LEARN repo> LEARN_{HASH}`. `[[LEARN_{HASH}]]` resolves only inside one wiki and would dead-link from the directory. `wiki-url` exit 4 means the lesson page is not written yet, so write it first; exit 5 means the page carries no `html_url`, so stop and report it and never assemble the URL by hand; exit 7 or 8 means the token or the API failed, so stop and report that status.
- Compute `{HASH}` from `{owner}/{repo}` with `jsc-gitea/tools/hash-id`. It prints the full 40-character uppercase SHA-1 — no truncation and no prefix rewrite, so never shorten it. Exit 1 means no SHA-1 helper on this machine: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash. Exit 2 means the input was empty, so fix the `{owner}/{repo}` parse and rerun.
- All wiki reads and writes go through `jsc-gitea:wiki`, except the `LEARN_CONTENTS` row, which goes through `jsc-gitea/tools/wiki-contents.sh`.
## Mode: record
@@ -40,8 +41,23 @@ Run after a skill run that produced a reusable lesson.
| 7 | The token is invalid or lacks permission, so the old rows are unknown. Stop and report the token problem, and create no page |
| 8 | Some other API failure. Stop and report that status, and create no page |
- Update `LEARN_CONTENTS` in the same pass (apply `templates/learn-contents.md`; add the repo row if missing, otherwise refresh its 最後更新時間), branching on its read exactly as above: only exit 4 creates the directory page from the template, while 7 and 8 stop the run instead of rebuilding a directory whose other repos' rows were never read. Touch no row that belongs to another repository.
- A failed `jsc-gitea:wiki` write on either page: retry once. Still failing, stop and report which page was not written (`LEARN_{HASH}` or `LEARN_CONTENTS`) together with the row content that was meant to go in, so the lesson is not lost. Never report a page as written when it was not.
- Update `LEARN_CONTENTS` in the same pass, and let `jsc-gitea/tools/wiki-contents.sh` do the row work — never hand-edit the directory page. Build one file holding the single row from `templates/learn-contents.md` (the repository name, the absolute link from `gitea.sh wiki-url <LEARN repo> LEARN_{HASH}`, and the update time), then run:
`jsc-gitea/tools/wiki-contents.sh upsert LEARN 1 "{owner}/{repo}" {row file} templates/learn-contents.md`
Column 1 is the repository name, so the key stays the same string across every run and one repository keeps exactly one row. The script reads the whole page, replaces the matching row and appends when none matches, so every row that belongs to another repository stays as it was.
| Exit | Do |
| --- | --- |
| 0 | The row is in place. It prints `updated` or `added` plus the page it wrote |
| 1 | The write failed, or the directory page holds no markdown table. Report `LEARN_CONTENTS` as not written, together with the row content |
| 2 | An argument was rejected. Fix the argument and rerun this bullet; nothing was written |
| 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set. The lesson itself is on `LEARN_{HASH}` and stays there |
| 4 | The directory page is absent and the script received no template. The call above always passes one, so this code means `templates/learn-contents.md` is not at that path — a partial plugin install, not a missing argument. Stop and report the path; rerunning the same command changes nothing. Reinstall the plugin, confirm the file is there, then rerun. A mistyped template path exits 2, not 4 |
| 7 | The token is invalid or lacks permission, so the other repositories' rows are unknown. Stop and report the token problem; the script wrote nothing, which is what keeps those rows alive |
| 8 | Some other API failure. Stop and report that status and retry only after the API is back |
- A failed write on either page: retry once. Still failing, stop and report which page was not written (`LEARN_{HASH}` or `LEARN_CONTENTS`) together with the row content that was meant to go in, so the lesson is not lost. Never report a page as written when it was not.
- Done when the sub agent reports both pages written, the main agent has confirmed the row exists on `LEARN_{HASH}`, and the rows that were there before are still there.
## Mode: consult
@@ -49,7 +65,7 @@ Run after a skill run that produced a reusable lesson.
Run before a skill run, to apply past lessons.
1. Resolve `{owner}/{repo}` and compute `{HASH}` as in record mode. Done when `LEARN_{HASH}` is known.
2. Read `LEARN_CONTENTS` and the repo's `LEARN_{HASH}` via `jsc-gitea:wiki`, and branch on the exit code `jsc-gitea/tools/gitea.sh` returned. Only 4 means the page is absent; every other failure code means the read never happened, so an empty page must never be inferred from it.
2. Read `LEARN_CONTENTS` from the CONTENTS repo and the repo's `LEARN_{HASH}` from the LEARN repo via `jsc-gitea:wiki` — two `wiki-repo` calls, two different wikis — and branch on the exit code `jsc-gitea/tools/gitea.sh` returned. Only 4 means the page is absent; every other failure code means the read never happened, so an empty page must never be inferred from it. The rows on `LEARN_CONTENTS` point at lesson pages by absolute URL, and rows for other repositories point outside the LEARN repo resolved here, so follow each link as given rather than treating the page name as local.
| Exit | Do |
| --- | --- |
+24 -7
View File
@@ -1,6 +1,6 @@
---
name: report
description: Summarise work logs into a yearly, monthly, weekly or daily report. Resolve the period with tools/report-range.sh, resolve the template with tools/report-template.sh - a project's .jsc/templates/report-{period}.md wins over the skill's own copy - then read every log page listed in LOG_CONTENTS and keep the entries dated inside the range. Fill the template with real aggregates (entry count, repositories, elapsed time, token usage, blockers, carry-overs) and write it to wiki REPORT_{HASH}, hashed from {owner}/{repo}/{period}, appending the period as a new section. Use when someone asks for a work summary over a period; not for recording a single work package, which is jsc-log:worklog.
description: Summarise work logs into a yearly, monthly, weekly or daily report. Resolve the period with tools/report-range.sh, resolve the template with tools/report-template.sh - a project's .jsc/templates/report-{period}.md wins over the skill's own copy - then read every log page listed in LOG_CONTENTS and keep the entries dated inside the range. Fill the template with real aggregates (entry count, repositories, elapsed time, token usage, blockers, carry-overs) and write it to wiki REPORT_{HASH}, whose full 40-character uppercase hash comes from the REPORT wiki repo's own {owner}/{repo} plus the period rather than from a code repo, appending the period as a new section. Directory pages LOG_CONTENTS, LEARN_CONTENTS and REPORT_CONTENTS all sit in the shared CONTENTS wiki repo while every content page stays in its own type's repo, so the REPORT_CONTENTS row goes through jsc-gitea/tools/wiki-contents.sh upsert and links the report page by its absolute wiki-url. Use when someone asks for a work summary over a period; not for recording a single work package, which is jsc-log:worklog.
---
# report — summarise work logs by period
@@ -23,11 +23,13 @@ Done when start, end and label are known.
## 2. Resolve and collect
Directory pages and content pages no longer share a wiki. Every `*_CONTENTS` page — `LOG_CONTENTS`, `LEARN_CONTENTS`, `REPORT_CONTENTS` — lives in the one repo that `jsc-gitea/tools/gitea.sh wiki-repo CONTENTS` resolves (`JSC_WIKI_REPO_CONTENTS`, then `JSC_WIKI_REPO`, then exit 3; it never falls back to a page type's own variable). Each content page still lives in its own type's repo: log pages in `wiki-repo LOG`, lesson pages in `wiki-repo LEARN`, the report page in `wiki-repo REPORT`. Keep the two apart — one shared directory repo, one repo per content type — and resolve every one of them on its own.
Run these four lines of work in parallel — none of them consumes another's output, and the log pages are the slow one:
1. **Template.** `tools/report-template.sh resolve {period}` from the working directory prints `{path}<TAB>{project|skill}`.
2. **Log pages.** `jsc-gitea/tools/gitea.sh wiki-repo LOG`, then read `LOG_CONTENTS` through `jsc-gitea:wiki`, then read **every** log page it lists, one sub agent per page.
3. **Lessons (yearly only).** `gitea.sh wiki-repo LEARN`, then read `LEARN_CONTENTS` through `jsc-gitea:wiki` for the 全年教訓 section. Resolve `JSC_WIKI_REPO_LEARN` on its own: the LOG repo resolved in line 2 never stands in for it, and LOG and LEARN pages routinely live in different wiki repos. Other periods skip this line.
2. **Log pages.** `gitea.sh wiki-repo CONTENTS`, then read `LOG_CONTENTS` through `jsc-gitea:wiki`, then read **every** log page it lists, one sub agent per page. The rows link their pages by absolute URL, so follow each link as given; `gitea.sh wiki-repo LOG` names the repo the log pages of this working directory sit in, and a row pointing elsewhere is another repo's log page, not a broken link.
3. **Lessons (yearly only).** Read `LEARN_CONTENTS` from the same CONTENTS repo, then read the lesson pages it lists for the 全年教訓 section. Resolve the lesson pages' own repo with `gitea.sh wiki-repo LEARN`, never with the LOG repo of line 2: the two directory pages now share a repo, but LOG and LEARN **content** pages routinely live in different ones, and reusing the LOG repo reads the wrong wiki. Other periods skip this line.
4. **Report repo.** `gitea.sh wiki-repo REPORT`, so step 3 has its target ready.
Exit branches for the external calls above:
@@ -37,6 +39,7 @@ Exit branches for the external calls above:
| `report-template.sh resolve` | 0 | Use the path; name the `project` or `skill` source in the final report. `project` means the working directory holds `.jsc/templates/report-{period}.md` and that file wins — the same period rendered from two templates has to be traceable to the file that shaped it |
| `report-template.sh resolve` | 2 | Period name or start directory rejected. Rerun from the working directory with the period from step 1 |
| `report-template.sh resolve` | 3 | Neither the project copy nor the skill's own copy exists. Stop and report that `templates/report-{period}.md` is missing from the plugin; do not invent a layout |
| `gitea.sh wiki-repo CONTENTS` | 3 | Stop and report that no wiki repo is configured for the directory pages, naming `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO`. Ask per the `jsc-ask:ask` rules, then rerun. Without `LOG_CONTENTS` there is no list of log pages to read |
| `gitea.sh wiki-repo LOG` | 3 | Stop and report that no wiki repo is configured for LOG, naming `JSC_WIKI_REPO_LOG` and `JSC_WIKI_REPO`. Ask per the `jsc-ask:ask` rules, then rerun. Without log pages there is nothing to summarise |
| `gitea.sh wiki-repo LEARN` | 3 | Fill the 全年教訓 section with 無 and say the LEARN wiki repo is unset. The rest of the yearly report still stands |
| `gitea.sh wiki-repo REPORT` | 3 | Carry on collecting; step 3 handles the skipped write |
@@ -68,19 +71,33 @@ Follow the template's headings and tables exactly, including ones with no data:
Write through `jsc-gitea:wiki`:
- Repo: the REPORT repo from step 2, line 4.
- Page: `REPORT_` plus `gitea.sh hash-id "{owner}/{repo}/{period}"`, where `{owner}/{repo}` is the REPORT wiki repo. Year, month, week and day each get their own page.
- Repo: the REPORT repo from step 2, line 4. It hosts the content page only; `REPORT_CONTENTS` goes to the CONTENTS repo instead.
- Page: `REPORT_` plus `gitea.sh hash-id "{owner}/{repo}/{period}"`. Here `{owner}/{repo}` is **the REPORT wiki repo itself** — the value `gitea.sh wiki-repo REPORT` printed — and not the code repo the logs came from. Every other page in this skill set hashes the code repo; this one page does not, because a report spans every code repo whose logs landed in the range, so no single code repo names it. Feed `hash-id` the exact string `{REPORT wiki owner}/{REPORT wiki repo}/{period}`, with `{period}` being the literal `daily`, `weekly`, `monthly` or `yearly` — so year, month, week and day each get their own page. `hash-id` prints the full 40-character uppercase SHA-1: use it whole, never shortened and never prefixed.
- Read the page first and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet** and may be built from scratch. On exit 0 the existing sections are in hand, so append into them. On exit 7 the token is invalid or lacks permission, and on exit 8 the API failed some other way: both leave the earlier periods unknown, so stop, report the status and write nothing — a page rebuilt on top of an unread read loses every period already on it.
- Append this period as a new section, newest first. Rerunning the same period replaces that period's section only, leaving the other periods untouched.
- Refresh the page's row in `REPORT_CONTENTS` from `templates/report-contents.md`: add the row if missing, otherwise refresh its 最新一期、期數 and 最後更新. Its read branches the same way — only exit 4 creates the directory page from the template, while 7 and 8 stop the run. Touch no row that belongs to another report page.
- Refresh the page's row in `REPORT_CONTENTS` with `jsc-gitea/tools/wiki-contents.sh` — never hand-edit the directory page. It sits in the CONTENTS repo, not the REPORT repo, so the row links the report page by the absolute URL from `gitea.sh wiki-url <REPORT repo> REPORT_{HASH}`; `[[REPORT_{HASH}]]` resolves only inside one wiki and would dead-link from here. Build one file holding the single row from `templates/report-contents.md` (the absolute link, the bare `{HASH}`, the period, the newest label, the section count and the update time), then run:
`jsc-gitea/tools/wiki-contents.sh upsert REPORT 2 "{HASH}" {row file} templates/report-contents.md`
The key is column 2, the bare 40-character `{HASH}` this step already computed, with no link markup around it. Column 1 carries the same page as a link for a human to click, and that link is exactly what must not be the key: it embeds the host and the encoded page name, so one change of `GITEA_HOST` or one difference in how Gitea encodes the page name makes this run's cell differ from the last run's, the match fails, the row is appended, and the same report page now owns two rows of which the older is never updated again. The script replaces the matching row and appends when none matches, so every row that belongs to another report page stays as it was.
| Call | Exit | Do |
| --- | --- | --- |
| `gitea.sh wiki-repo REPORT` | 3 | Print the finished report and say the write was skipped because no wiki repo is configured for REPORT. The report itself is still the deliverable |
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed. Never hand-compute the hash |
| `gitea.sh hash-id` | 2 | The input was empty, which means the REPORT wiki repo or the period never reached it. Fix the string and rerun; the empty string has a valid SHA-1 and would file the report on a page nobody reads |
| `gitea.sh wiki-url` | 4 / 5 | 4 means the report page write has not landed, so write it first; 5 means the page carries no `html_url`, so stop and report it and never assemble the URL by hand |
| `gitea.sh wiki-url` | 7 / 8 | 7 means the token is invalid or lacks permission (HTTP 401/403), 8 means some other API failure. Both leave it unknown whether the page is there, so stop and report the token or API status. Never fold either into 4: reading an invalid key as a missing page is the same misread this table separates 7 from 4 to prevent, and here it would send the run back to rewrite a report page that is already on the server |
| `jsc-gitea:wiki` write | failure | Retry once. Still failing, stop and report the page name that was not written, and print the report body so the work is not lost. Never report a page as written when it was not |
| `wiki-contents.sh upsert` | 0 | The row is in place. It prints `updated` or `added` plus the page it wrote |
| `wiki-contents.sh upsert` | 1 | The write failed, or the directory page holds no markdown table. Report `REPORT_CONTENTS` as not written, together with the row content |
| `wiki-contents.sh upsert` | 2 | An argument was rejected. Fix the argument and rerun this bullet; nothing was written |
| `wiki-contents.sh upsert` | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set. The report itself is on `REPORT_{HASH}` and stays there |
| `wiki-contents.sh upsert` | 4 | The directory page is absent and the script received no template. The call above always passes one, so this code means `templates/report-contents.md` is not at that path — a partial plugin install, not a missing argument. Stop and report the path; rerunning the same command changes nothing. Reinstall the plugin, confirm the file is there, then rerun. A mistyped template path exits 2, not 4 |
| `wiki-contents.sh upsert` | 7 | The token is invalid or lacks permission, so the other rows are unknown. Stop and report the token problem; the script wrote nothing, which is what keeps those rows alive |
| `wiki-contents.sh upsert` | 8 | Some other API failure. Stop and report that status and retry only after the API is back |
Write the content page before its row in `REPORT_CONTENTS`, never the two at once: a directory row pointing at a page whose write failed is worse than a missing row.
Write the content page before its row in `REPORT_CONTENTS`, never the two at once: a directory row pointing at a page whose write failed is worse than a missing row, and `wiki-url` cannot name a page that is not there yet.
Done when the page URL is reported, or the skipped write is reported with its reason, or the run stopped on a read that returned 7 or 8 and that status was reported.
+28 -8
View File
@@ -1,6 +1,6 @@
---
name: worklog
description: Append one work-log entry to wiki LOG_{HASH} plus LOG_CONTENTS as soon as a task ends, where a task is one work package, one round of PR-comment fixes, or one standalone fix commit — one task, one entry, appended to the same page. Every entry carries the ten facts (repo, branch, plan link, work package link, elapsed time from session-timer, token usage per CLI, status, details, difficulties, PR target); HASH follows the shared 8-char rule with the H-prefix fallback and the work-week Friday drives the page content. Merge whatever tools/worklog-pending.sh holds for that HASH into the same write, then clear the pending area once that write succeeded. Trigger at the end of every such task in implement or maintain; not for planning notes.
description: Append one work-log entry to wiki LOG_{HASH} plus LOG_CONTENTS as soon as a task ends, where a task is one work package, one round of PR-comment fixes, or one standalone fix commit — one task, one entry, appended to the same page. Every entry carries the ten facts (repo, branch, plan link, work package link, elapsed time from session-timer, token usage per CLI, status, details, difficulties, PR target); HASH is the full 40-character uppercase SHA-1 of {owner}/{repo} and the work-week Friday drives the page content. LOG_{HASH} sits in the LOG wiki repo while LOG_CONTENTS sits in the separate CONTENTS repo, so the directory row goes through jsc-gitea/tools/wiki-contents.sh upsert and links the log page by its absolute wiki-url. Merge whatever tools/worklog-pending.sh holds for that HASH into the same write, then clear the pending area once that write succeeded. Trigger at the end of every such task in implement or maintain; not for planning notes.
---
# worklog — work log
@@ -36,15 +36,15 @@ Rows 3, 4, 5 and 6 each hit a different source and none of them reads another's
| 9 | Difficulties and resolutions | One pair per line. Ask via `jsc-ask:ask` when the session does not show them |
| 10 | PR target branch | Link to the PR page |
The `{HASH}` in every page name above is computed with `jsc-gitea/tools/hash-id`.
The `{HASH}` in every page name above is computed with `jsc-gitea/tools/hash-id`, which prints the full 40-character uppercase SHA-1 of its input — no truncation to 8 characters and no prefix rewrite. A page name shortened by hand points at a page nobody else writes to.
## Write the entry
1. Resolve the LOG wiki repo and the entry's `{HASH}` **in parallel** — neither needs the other.
- Repo: `gitea.sh wiki-repo LOG`. Exit 3 means no LOG wiki repo is configured; hand that to `jsc-gitea:wiki`, which owns the resolution order and the question to ask. Exit 2 means the type argument was misspelled, so fix it and rerun.
- Hash: `jsc-gitea/tools/hash-id "{owner}/{repo}"` on the code repo from row 1. Exit 1 means this machine has no SHA-1 helper: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash.
- Repo: `gitea.sh wiki-repo LOG`. This repo hosts the content page `LOG_{HASH}` only. The directory page `LOG_CONTENTS` lives in a different repo and is resolved in step 6, so never reuse this value for it. Exit 3 means no LOG wiki repo is configured; hand that to `jsc-gitea:wiki`, which owns the resolution order and the question to ask. Exit 2 means the type argument was misspelled, so fix it and rerun.
- Hash: `jsc-gitea/tools/hash-id "{owner}/{repo}"` on the code repo from row 1. Exit 1 means this machine has no SHA-1 helper: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash. Exit 2 means the input was empty, which happens when row 1 failed to parse `{owner}/{repo}`: fix row 1 and rerun, because the empty string has a valid SHA-1 and would file this entry on a page nobody reads.
Done when the hosting `{owner}/{repo}` and the 8-character `{HASH}` are both known.
Done when the hosting `{owner}/{repo}` and the full 40-character uppercase `{HASH}` are both known.
2. Run `tools/worklog-target.sh "{HASH}" all` and `tools/worklog-target.sh friday` in parallel. `all` prints `PAGE=LOG_{HASH}` and `CONTENTS=LOG_CONTENTS`; `friday` prints the Friday of the current work week as `yyyy-MM-dd`, which drives the page content and the row dates. Exit 2 means the arguments were rejected, so fix them and rerun. Exit 4 from `friday` means this machine's `date` does no date arithmetic: stop and report it, because a hand-picked Friday is exactly what goes wrong across a month or year boundary. Done when both page names and that Friday date are known.
3. Fill `templates/log-entry.md` with the ten facts of this one task and save it to a file. Done when that file holds exactly one entry.
4. Run `tools/worklog-pending.sh merge {HASH} {entry file}`. It prints `MERGED=` (pending content in time order, then this task's entry), `CLAIM=` (the pending files it took) and `PENDING=` (how many). Pending content was written by an earlier stage that ended without a work log, so it belongs in **this** write.
@@ -53,10 +53,10 @@ The `{HASH}` in every page name above is computed with `jsc-gitea/tools/hash-id`
| --- | --- |
| 0 | Carry on with `MERGED` and `CLAIM`. `PENDING=0` is normal and still exit 0 |
| 1 | A read or write under `$JSC_HOME/worklog-pending` failed. Stop and report the path from the message; nothing was deleted, so a rerun loses nothing |
| 2 | The `{HASH}` is not 8 uppercase alphanumerics, or the entry file argument is missing. Fix the argument and rerun from step 1 |
| 2 | The `{HASH}` is none of the three accepted shapes — 40 uppercase hex characters, 8 uppercase hex characters, or `H` plus 7 uppercase hex characters — or the entry file argument is missing. The last two are old pending directories left by the previous hash rule and are accepted only until the migration finishes. Pass the value `hash-id` printed, unshortened, and rerun from step 1 |
Done when `MERGED` and `CLAIM` are known.
5. Read `PAGE` via `jsc-gitea:wiki`, and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet.** Reading any other code as "it does not exist" builds a fresh page from `templates/log-entry.md` and appends to that — which replaces the whole existing work log with this one entry, and no entry on it can be recovered from the wiki afterwards.
5. Read `PAGE` from the LOG wiki repo of step 1 via `jsc-gitea:wiki`, and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet.** Reading any other code as "it does not exist" builds a fresh page from `templates/log-entry.md` and appends to that — which replaces the whole existing work log with this one entry, and no entry on it can be recovered from the wiki afterwards.
| Exit | Do |
| --- | --- |
@@ -66,7 +66,27 @@ The `{HASH}` in every page name above is computed with `jsc-gitea/tools/hash-id`
| 8 | Some other API failure. Stop and report that status, write nothing and create no page. Retry only after the API is back |
A failed write stops the run and goes to step 7 as a failure — never report the page as written when it was not. Done when every entry in `MERGED` is on `PAGE`, every entry that was already there is still there, and any 4 / 7 / 8 branch was followed as stated.
6. Update `CONTENTS` in the same pass (apply `templates/log-contents.md`; add the row if missing, otherwise refresh its 條目數 and 最後更新). Its read branches exactly as step 5 does: only exit 4 creates the directory page from the template, while 7 and 8 stop the run rather than rebuild a directory whose other rows were never read. Touch no row that belongs to another page. Done when the row for `PAGE` carries this week's Friday date from step 2 and every other row is unchanged.
6. Update `CONTENTS` in the same pass, and let `jsc-gitea/tools/wiki-contents.sh` do the row work — never hand-edit the directory page.
`LOG_CONTENTS` lives in the CONTENTS wiki repo that `gitea.sh wiki-repo CONTENTS` resolves (`JSC_WIKI_REPO_CONTENTS`, then `JSC_WIKI_REPO`), which is **not** the LOG repo of step 1 and never falls back to it. Because the two pages sit in different wikis, the row's link to the log page is the absolute URL from `gitea.sh wiki-url <LOG repo> LOG_{HASH}` — `[[LOG_{HASH}]]` resolves only inside one wiki and would dead-link from here. `wiki-url` exit 4 means the step 5 write has not landed yet, so stop and rerun step 5 before this one; exit 5 means the page carries no `html_url`, so stop and report it and never assemble the URL by hand; exit 7 or 8 means the token or the API failed, so stop and report that status.
Build one file holding the single row from `templates/log-contents.md` — the absolute link, the bare `{HASH}` of step 1, this week's Friday date from step 2, the entry count and the update time — then run:
`jsc-gitea/tools/wiki-contents.sh upsert LOG 2 "{HASH}" {row file} templates/log-contents.md`
The key is column 2, the bare 40-character `{HASH}` with no link markup around it. Column 1 carries the same page as a link for a human to click, and that link is exactly what must not be the key: it embeds the host and the encoded page name, so one change of `GITEA_HOST`, one move of `JSC_WIKI_REPO_LOG`, or one difference in how Gitea encodes the page name makes this run's cell differ from the last run's, the match fails, the row is appended, and the same log page now owns two rows of which the older is never updated again. The bare hash depends only on `{owner}/{repo}`. The script reads the whole page, replaces the matching row and appends when none matches, so every row that belongs to another log page stays as it was.
| Exit | Do |
| --- | --- |
| 0 | The row is in place. It prints `updated` or `added` plus the page it wrote — carry that word into the close-out |
| 1 | The write failed, or the directory page holds no markdown table. Stop and report it as a failed write, and go to step 7 as a failure |
| 2 | An argument was rejected (unknown type, key column, missing row file). Fix the argument and rerun this step; nothing was written |
| 3 | No CONTENTS wiki repo is configured. Stop and report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set, and go to step 7 as a failure. The log entry itself is on `PAGE` and stays there |
| 4 | The directory page is absent and the script received no template. The call above always passes one, so this code means `templates/log-contents.md` is not at that path — a partial plugin install, not a missing argument. Stop and report the path; rerunning the same command changes nothing. Reinstall the plugin, confirm the file is there, then rerun. A mistyped template path exits 2, not 4 |
| 7 | The key is invalid or lacks permission, so the other rows are unknown. Stop and report the key problem; the script wrote nothing, which is what keeps the other pages' rows alive |
| 8 | Some other API failure. Stop and report that status and retry only after the API is back |
Done when the run exited 0 and the row for `PAGE` carries this week's Friday date from step 2, or a non-zero code was reported and step 7 ran as a failure.
7. Close the pending area on the result of steps 5 and 6: `tools/worklog-pending.sh commit {HASH} {CLAIM}` after both succeeded, or `tools/worklog-pending.sh abort {HASH} {CLAIM}` after either failed. `abort` keeps every pending file for the retry, so keep the entry file too and rerun from step 4.
| Exit | Do |
+6 -3
View File
@@ -1,9 +1,12 @@
# 教訓目錄
> 由 `jsc-log:learn` 維護。這是教訓目錄頁 `LEARN_CONTENTS`。每個存取庫一列;`LEARN_{HASH}` 的 `{HASH}` 依共用 wiki hash 規則產生:先取 `{owner}/{repo}` 的 SHA-1 前 8 碼並轉成大寫;首碼若是 `0-9`、`A`、`B`、`C`,改用 `H` 加上原前 7 碼,總長維持 8 碼。
> 寫入語意:一列代表一個存取庫。先讀整頁,找得到該存取庫既有的那一列就更新那一列,找不到才新增一列。
> 由 `jsc-log:learn` 維護。這是教訓目錄頁 `LEARN_CONTENTS`。每個存取庫一列;`LEARN_{HASH}` 的 `{HASH}` 依共用 wiki hash 規則產生:取 `{owner}/{repo}` 的完整 SHA-1 四十碼,a-f 轉大寫,不截短、不加前綴。
> 本頁落在 `JSC_WIKI_REPO_CONTENTS` 解出的專用存取庫,教訓頁 `LEARN_{HASH}` 落在 `JSC_WIKI_REPO_LEARN` 的存取庫,兩者分屬不同 wiki。
> 所以指向教訓頁的連結一律用 `gitea.sh wiki-url` 取得的絕對網址。`[[LEARN_{HASH}]]` 只在同一個 wiki 內解得開,寫在這裡就是死連結。
> 寫入語意:一列代表一個存取庫。一律用 `jsc-gitea/tools/wiki-contents.sh upsert LEARN 1 {owner}/{repo} {列檔} {本範本}` 單列整頁寫回——它讀整頁、找得到該存取庫既有的那一列就換掉那一列,找不到才附加。
> 鍵取第 1 欄的存取庫名稱,不取第 2 欄的連結。存取庫名稱每一輪都一樣,連結會隨主機名與頁名編碼變動。
> 禁止整頁覆蓋,也不得改動別人的列。
| 存取庫名稱 | 教訓紀錄 | 最後更新時間 |
| --- | --- | --- |
| {owner}/{repo} | [[LEARN_{HASH}]] | {yyyy-MM-dd HH:mm} |
| {owner}/{repo} | [LEARN_{HASH}]({教訓頁絕對網址}) | {yyyy-MM-dd HH:mm} |
+8 -5
View File
@@ -1,9 +1,12 @@
# 日誌目錄
> 由 `jsc-log:worklog` 維護。每個日誌頁一列;頁名使用共享 `HASH` 規則,頁內仍依該週五日期整理。
> 寫入語意:一列代表一個日誌頁。先讀整頁,找得到該頁既有的那一列就更新那一列,找不到才新增一列。
> 由 `jsc-log:worklog` 維護。每個日誌頁一列;`{HASH}` 是 `{owner}/{repo}` 的完整 40 碼大寫十六進位 SHA-1,頁內仍依該週五日期整理。
> 本頁落在 `JSC_WIKI_REPO_CONTENTS` 解出的專用存取庫,日誌頁 `LOG_{HASH}` 落在 `JSC_WIKI_REPO_LOG` 的存取庫,兩者分屬不同 wiki。
> 所以指向日誌頁的連結一律用 `gitea.sh wiki-url` 取得的絕對網址。`[[LOG_{HASH}]]` 只在同一個 wiki 內解得開,寫在這裡就是死連結。
> 寫入語意:一列代表一個日誌頁。一律用 `jsc-gitea/tools/wiki-contents.sh upsert LOG 2 {HASH} {列檔} {本範本}` 單列整頁寫回——它讀整頁、找得到該頁既有的那一列就換掉那一列,找不到才附加。
> 鍵取第 2 欄的裸 HASH,不取第 1 欄的連結。第 1 欄的連結帶著主機名與存取庫名:`GITEA_HOST` 一換、`JSC_WIKI_REPO_LOG` 改指別的存取庫,或 Gitea 對頁名的編碼有差,連結就跟上一輪寫的不一樣,鍵比不到就走附加,同一個日誌頁多出第二列,舊列從此不再更新。裸 HASH 只跟 `{owner}/{repo}` 有關,這三件事都動不到它。
> 禁止整頁覆蓋,也不得改動別人的列。
| 日誌頁 | 週五日期 | 條目數 | 最後更新 |
| --- | --- | --- | --- |
| [[LOG_{HASH}]] | {yyyy-MM-dd} | {n} | {yyyy-MM-dd HH:mm} |
| 日誌頁 | HASH | 週五日期 | 條目數 | 最後更新 |
| --- | --- | --- | --- | --- |
| [LOG_{HASH}]({日誌頁絕對網址}) | {HASH} | {yyyy-MM-dd} | {n} | {yyyy-MM-dd HH:mm} |
+9 -5
View File
@@ -1,9 +1,13 @@
# 報表目錄
> 由 `jsc-log:report` 維護。年、月、週、日各一頁;`HASH` 取 `{owner}/{repo}/{期間}`,算法與其他頁面共用。
> 寫入語意:一列代表一個報表頁,也就是一個存取庫的一種期間。先讀整頁,找得到該報表頁既有的那一列就更新那一列,找不到才新增一列。
> 由 `jsc-log:report` 維護。年、月、週、日各一頁;`HASH` 取 `{owner}/{repo}/{期間}` 的完整 40 碼大寫十六進位 SHA-1,算法與其他頁面共用,但這裡的 `{owner}/{repo}` 取 REPORT wiki 存取庫,不是程式碼存取庫。
> 本頁落在 `JSC_WIKI_REPO_CONTENTS` 解出的專用存取庫,報表頁 `REPORT_{HASH}` 落在 `JSC_WIKI_REPO_REPORT` 的存取庫,兩者分屬不同 wiki。
> 所以指向報表頁的連結一律用 `gitea.sh wiki-url` 取得的絕對網址。`[[REPORT_{HASH}]]` 只在同一個 wiki 內解得開,寫在這裡就是死連結。
> 寫入語意:一列代表一個報表頁,也就是一個存取庫的一種期間。一律用 `jsc-gitea/tools/wiki-contents.sh upsert REPORT 2 {HASH} {列檔} {本範本}` 單列整頁寫回——它讀整頁、找得到該報表頁既有的那一列就換掉那一列,找不到才附加。
> 鍵取第 2 欄的裸 HASH,不取第 1 欄的連結。第 1 欄的連結帶著主機名與頁名編碼,`GITEA_HOST` 一換或 Gitea 對頁名的編碼有差,連結就跟上一輪寫的不一樣,鍵比不到就走附加,同一個報表頁多出第二列,舊列從此不再更新。裸 HASH 不受這兩件事影響。
> 但 `JSC_WIKI_REPO_REPORT` 改指別的存取庫是另一回事:HASH 本身就取自 REPORT wiki 存取庫,換庫等於換頁,四個期間會各多一列。那是換庫的本意,不是鍵失準,舊列請人工清掉。
> 禁止整頁覆蓋,也不得改動別人的列。
| 報表頁 | 期間 | 最新一期 | 期數 | 最後更新 |
| --- | --- | --- | --- | --- |
| [[REPORT_{HASH}]] | {daily、weekly、monthly、yearly 四選一} | {最新一期的標籤} | {n} | {yyyy-MM-dd HH:mm} |
| 報表頁 | HASH | 期間 | 最新一期 | 期數 | 最後更新 |
| --- | --- | --- | --- | --- | --- |
| [REPORT_{HASH}]({報表頁絕對網址}) | {HASH} | {daily、weekly、monthly、yearly 四選一} | {最新一期的標籤} | {n} | {yyyy-MM-dd HH:mm} |
+42 -5
View File
@@ -21,7 +21,10 @@
# worklog-pending.sh commit <hash> <claim> 寫入成功後清掉併入清單上的暫存檔
# worklog-pending.sh abort <hash> <claim> 寫入失敗後保留暫存,只丟掉合併檔與併入清單
#
# <hash> 為 jsc-gitea/tools/hash-id 算出的 8 碼工作日誌 hash,也就是 LOG_{HASH} 的 HASH。
# <hash> 為 jsc-gitea/tools/hash-id 算出的工作日誌 hash,也就是 LOG_{HASH} 的 HASH。
# 現行是完整 40 碼大寫十六進位;8 碼那種是舊規則留下的暫存檔,還沒遷移完之前一併收。
# 舊規則的首碼落在 0-9ABC 就改寫成 H 加原前 7 碼,十六個首碼有十三個會命中,所以既有的
# 8 碼暫存大多長成 H1A2B3C4。不收這種,遷移期間存進去的內容就再也取不回來。
# 存放位置: $JSC_HOME/worklog-pending/{hash}/{UTC 時間}-{pid}.md(JSC_HOME 預設 ~/.jsc)
# 合併檔: $JSC_HOME/worklog-pending/.merge/{hash}-{UTC 時間}-{pid}.md
# 併入清單: 同名換副檔名 .claim,一行一個被併走的暫存檔路徑
@@ -55,11 +58,45 @@ EOF
exit 2
}
valid_hash() { # 只收 8 碼大寫英數,擋掉路徑穿越
case "$1" in
[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]) return 0 ;;
*) echo "[jsc][工作日誌暫存][ERR]:hash 須為 8 碼大寫英數,收到「${1:-空值}」。" >&2; exit 2 ;;
# hash 會直接拼進暫存目錄路徑,這裡是整支腳本唯一的格式驗證,擋的是路徑穿越。
# 放寬長度不等於放寬字元集:先確認每一個字都是十六進位,再比對長度。字元集這一關先過,
# 「.」與「/」才進不了路徑;只看長度就會讓 ../ 那類值溜進來。
# 字元檢查逐字剝,不呼叫 grep 之類逐行比對的工具:值裡夾一個換行,逐行比對會拿第一行
# 當整個值判過,那正是路徑穿越要的缺口。
#
# 這份樣式與 jsc-gitea/tools/page-name.sh、jsc-hooks/hooks/comment-scope.sh 是三份各自
# 獨立的定義,故意不共用:hook 與這個暫存區都必須自足,執行期不能相依別的 plugin 的安裝
# 路徑——那個路徑每個 CLI 不一樣,也可能根本沒裝,抓不到就等於整道護欄失效。三份的一致性
# 改在稽核時比對。
valid_hash() {
raw="${1:-}"
# H 開頭先剝掉再驗其餘:舊規則把首碼落在 0-9ABC 的 hash 改寫成 H 加原前 7 碼,
# H 本身不是十六進位字元,不先剝就會被逐字剝那一關擋掉。剝的是固定字面 H,
# 不是一個字元集,所以放進來的字仍然只有 H 與十六進位這兩種。
case "$raw" in
H*) rest="${raw#H}"; hprefixed=1 ;;
*) rest="$raw"; hprefixed=0 ;;
esac
while :; do
case "$rest" in
[0-9A-F]*) rest="${rest#?}" ;;
*) break ;;
esac
done
if [ -z "$rest" ]; then
if [ "$hprefixed" -eq 1 ]; then
case "$raw" in
????????) return 0 ;;
esac
else
case "$raw" in
????????????????????????????????????????) return 0 ;;
????????) return 0 ;;
esac
fi
fi
echo "[jsc][工作日誌暫存][ERR]:hash 須為 40 碼大寫十六進位(現行),或 8 碼大寫十六進位、H 加 7 碼大寫十六進位(尚未遷移的舊暫存),收到「${1:-空值}」。" >&2
exit 2
}
valid_claim() { # 併入清單只認 merge 產出的那一份,擋掉拿別處的清單來刪檔