feat(wiki): 三個目錄頁改走專用存取庫,比對鍵改用裸 HASH

What:LOG_CONTENTS、LEARN_CONTENTS、REPORT_CONTENTS 改由 wiki-repo CONTENTS
解析並透過 wiki-contents.sh upsert 寫入,內容頁仍各走自己的型別。LOG 與 REPORT
的目錄頁新增一欄裸 HASH 當比對鍵。

Why:比對鍵原本是含網址的儲存格,換主機、換存取庫或 URL 編碼有差就比對不到,
upsert 會走附加分支,同一頁多出第二列而舊列永遠不再更新。REPORT 更脆:存取庫一換,
雜湊與網址同時變,四個期間的列會一次全部重複。

How:worklog-pending.sh 的 valid_hash 放寬成 40 碼、8 碼與 H 加 7 碼三種形狀。
放寬的是長度不是字元集——先剝字元再比長度的順序保留,路徑穿越與換行注入照樣擋下。
年報不得沿用日誌存取庫那條規則收斂到內容頁,目錄頁同住一庫是另一回事。

Who:jsc-log
This commit is contained in:
2026-09-02 11:02:21 +08:00
parent 39bbf3c505
commit aee17e45c7
12 changed files with 165 additions and 61 deletions
+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.