feat(wiki): 目錄頁專用存取庫、HASH 去除截短與 H 前綴

What:CONTENTS 成為第 15 種頁面類型,解析鏈為 JSC_WIKI_REPO_CONTENTS 到
JSC_WIKI_REPO,刻意不退回型別變數。hash-id 拿掉 8 碼截短與 H 前綴改寫,只留大寫
轉換,輸出完整 40 碼;空輸入改成用法錯誤。新增 page-name.sh、wiki-contents.sh、
migrate-wiki.sh 與 wiki-delete 子命令。

Why:H 前綴會命中 16 個首碼裡的 13 個,還丟掉第 8 碼,把有效熵壓到 28 位元,而且
全庫查不到任何理由紀錄。目錄頁的整列 upsert 原本 14 處只有一處寫成程式,同一段判斷
做 14 次,錯一次就少一筆紀錄。

How:頁名樣式仍收 H 加 7 碼的舊頁,遷移期間讀得到舊頁。wiki-contents.sh 建新頁時
剝掉範本的示範列,否則每個目錄頁第一次建立都會留一列佔位死連結。migrate-wiki.sh
預設只印對照表,--apply 先寫新頁、確認寫成、才刪舊頁;孤兒頁只列不猜,因為 SHA-1
不可逆,新頁名只能靠候選鍵正推。

Who:jsc-gitea
This commit is contained in:
2026-09-02 11:02:07 +08:00
parent a3ce17f98d
commit c40b561589
13 changed files with 826 additions and 53 deletions
+38 -8
View File
@@ -1,6 +1,6 @@
---
name: wiki
description: Read or write a Gitea wiki page through tools/gitea.sh and tools/hash-id. Resolve the wiki repo per page type with JSC_WIKI_REPO_{TYPE} first, then JSC_WIKI_REPO, and ask only when neither is set. Page content is chart-first - prefer mermaid diagrams and markdown tables over plain prose. Callers are jsc-ask, jsc-sdlc, jsc-log, jsc-hooks (ERROR), jsc-cli (CHECK), jsc-meta (SKILLSET, TOOLING) and jsc-assist (MONITOR). Use for any wiki page in the skill set; not for repo code files.
description: Read or write a Gitea wiki page through tools/gitea.sh, tools/hash-id and tools/page-name.sh. Resolve the wiki repo per page type with JSC_WIKI_REPO_{TYPE} first, then JSC_WIKI_REPO, and ask only when neither is set - every *_CONTENTS page resolves through type CONTENTS and is updated with tools/wiki-contents.sh, and {HASH} is the full 40-char uppercase SHA-1 from tools/hash-id. Page content is chart-first - prefer mermaid diagrams and markdown tables over plain prose. Callers are jsc-ask, jsc-sdlc, jsc-log, jsc-hooks (ERROR), jsc-cli (CHECK), jsc-meta (SKILLSET, TOOLING) and jsc-assist (MONITOR). Use for any wiki page in the skill set; not for repo code files.
---
# wiki — read and write Gitea wiki pages
@@ -12,8 +12,8 @@ Every wiki operation in the jsc skill set goes through this skill. One entry poi
Different page types can live in different `{owner}/{repo}` repos, classified by the page-name prefix.
1. **Host gate.** Confirm `GITEA_HOST` holds a value in the current shell. When it is missing, ask for it per the `jsc-ask:ask` rules before any `tools/gitea.sh` call that reaches the API; otherwise the first thing the user sees is the script's `GITEA_HOST is required` line instead of a decision-tree question. `GITEA_TOKEN` needs no inventory here — the script resolves it, retries once with the tea CLI login token, and exits 7 when neither works. Done when `GITEA_HOST` holds a value.
2. Run `tools/gitea.sh wiki-repo {TYPE}` (TYPE = the page-name prefix). Allowed types are `QUESTION`, `PLAN`, `ANALYZE`, `DELIVER`, `MAINTAIN`, `REPO`, `LOG`, `LEARN`, `ERROR`, `CHECK`, `REPORT`, `SKILLSET`, `TOOLING`, and `MONITOR`. The script reads `JSC_WIKI_REPO_{TYPE}` first and `JSC_WIKI_REPO` second, straight from the inherited environment, so take no separate inventory of those two variables. Never borrow another type's repo. Done when the command has printed exactly one `{owner}/{repo}`, or exited 3 and sent this page type to step 3, or exited 2 on a type outside the list above and stopped the run.
3. On exit 3 (neither variable is set), ask the user for that page type's `{owner}/{repo}` per the `jsc-ask:ask` rules, and suggest setting `JSC_WIKI_REPO_{TYPE}` (can differ per type) or `JSC_WIKI_REPO` (shared default). Done when the user has supplied one `{owner}/{repo}` for that page type.
2. Run `tools/gitea.sh wiki-repo {TYPE}`. Allowed types are `QUESTION`, `PLAN`, `ANALYZE`, `DELIVER`, `MAINTAIN`, `REPO`, `LOG`, `LEARN`, `ERROR`, `CHECK`, `REPORT`, `SKILLSET`, `TOOLING`, `MONITOR`, and `CONTENTS`. **Every `*_CONTENTS` page resolves through `CONTENTS` — all contents pages live in one dedicated repo, so never pass a contents page its own prefix. A content page (`*_{HASH}`) resolves through its own type.** The script reads `JSC_WIKI_REPO_{TYPE}` first and `JSC_WIKI_REPO` second, straight from the inherited environment, so take no separate inventory of those two variables. Never borrow another type's repo. Done when the command has printed exactly one `{owner}/{repo}`, or exited 3 and sent this page type to step 3, or exited 2 on a type outside the list above and stopped the run.
3. On exit 3 (neither variable is set), ask the user for that page type's `{owner}/{repo}` per the `jsc-ask:ask` rules, and suggest setting `JSC_WIKI_REPO_{TYPE}` (can differ per type, and `JSC_WIKI_REPO_CONTENTS` holds every contents page) or `JSC_WIKI_REPO` (shared default). Done when the user has supplied one `{owner}/{repo}` for that page type.
## Operations
@@ -22,9 +22,13 @@ Different page types can live in different `{owner}/{repo}` repos, classified by
| list pages | `tools/gitea.sh wiki-list {owner}/{repo}` |
| read page | `tools/gitea.sh wiki-get {owner}/{repo} {page}` |
| write page | write the content to a temp file first, then `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}` (asks for confirmation first, then creates or updates) |
| delete page | `tools/gitea.sh wiki-delete {owner}/{repo} {page}` — asks for the same confirmation as a write. Only a migration or an explicit user request may call it |
| page URL | `tools/gitea.sh wiki-url {owner}/{repo} {page}` — the page's absolute URL, taken from the API's `html_url` |
| update a contents page | `tools/wiki-contents.sh upsert {TYPE} {key-col} {key} {row-file} [template-file]` — resolves the CONTENTS repo, replaces the row whose key column matches, appends when none does, writes the whole page back. `{key-col}` is the column's 1-based position number, not the column name |
| check a page name | `tools/page-name.sh check {page}` — the single source of the page-name pattern; `tools/page-name.sh regex` prints it |
| move pages to the current rules | `tools/migrate-wiki.sh [--apply] [--key {key}]...` — prints the mapping table and the orphan list; writes only with `--apply` |
When writing a page, link same-type pages with the `[[display|page]]` form (display text on the LEFT) and cross-type pages with the absolute URL from `wiki-url`, because `[[...]]` resolves only inside one wiki. Full rules and the direction trap: `references/wiki-links.md`.
Contents pages and content pages no longer share a repo. Link between them — and between any two different types — with the absolute URL from `wiki-url`. Keep `[[display|page]]` (display text on the LEFT) for two pages that resolve to the same repo: contents page to contents page, or same-type content page to same-type content page. Full rules and the direction trap: `references/wiki-links.md`.
## Exit codes
@@ -35,20 +39,46 @@ Route every `tools/gitea.sh` call in this skill on its exit code. A code with no
| 0 | success | use the output |
| 2 | usage error, or a page type outside the allowed list | fix the arguments, then call again; never repeat the same call unchanged |
| 3 | `wiki-repo`: neither `JSC_WIKI_REPO_{TYPE}` nor `JSC_WIKI_REPO` is set | go to step 3 and ask |
| 4 | HTTP 404: `wiki-get` and `wiki-url` found no such page | for a read the caller expects to succeed, stop and report the page name; this is the **only** code that opens the create path of rule 4 — write the page from the template instead of appending |
| 4 | HTTP 404: `wiki-get` and `wiki-url` found no such page, or `wiki-delete` found nothing to delete | for a read the caller expects to succeed, stop and report the page name; this is the **only** code that opens the create path of rule 4 — write the page from the template instead of appending. For `wiki-delete` it means the page is already gone: report it and move on, do not retry |
| 5 | `wiki-url`: the page exists but the API returned no `html_url` | stop and report it. Link inside the same wiki with `[[display\|page]]`; a cross-repo link has no absolute URL to point at, so do not fabricate one |
| 7 | HTTP 401 or 403 after the tea-token retry: the key is invalid or lacks permission | **stop the whole operation and report the key problem.** Never read this as an empty or missing page, and never take the create path of rule 4: writing a fresh page over one you could not read destroys the record that is still there |
| 8 | any other API failure, HTTP status in the message | stop and report that status; call again only after the cause is fixed |
### Helper scripts
Every code below gets its own branch. Nothing here is retried unchanged.
| Script | Code | Meaning | What this skill does |
| --- | --- | --- | --- |
| `tools/hash-id` | 0 | the full 40-char uppercase hash | use it as `{HASH}` |
| | 1 | this machine has neither `sha1sum` nor `shasum` | stop, report that one of them has to be installed, compute no hash by hand |
| | 2 | no text given, or the text was empty | fix the key you passed, then call again; never fall back to a hand-made page name |
| `tools/page-name.sh` | 0 | the page name follows the pattern | continue with that page name |
| | 1 | the page name breaks the pattern | stop and report the name; build the correct one instead of writing to a wrong page |
| | 2 | usage error | fix the arguments, then call again |
| `tools/wiki-contents.sh` | 0 | the row was updated or added | report which of the two, and the page |
| | 1 | the write failed, or the page held no markdown table | stop and report; fix the page or the row before calling again |
| | 2 | usage error, or an unknown page type | fix the arguments, then call again |
| | 3 | the CONTENTS repo is not configured | go to step 3 and ask for `JSC_WIKI_REPO_CONTENTS` |
| | 4 | the page is not there and no template was given | supply the template for that page type, then call again |
| | 7 | the key is invalid or lacks permission | stop the whole operation and report the key problem; create no page |
| | 8 | any other API failure | stop and report the status |
| `tools/migrate-wiki.sh` | 0 | every page moved, or the preview found nothing to move | report the mapping table |
| | 1 | at least one page failed to move | report the failure list; the old pages of the failed entries stay in place |
| | 2 | usage error, including an unconfigured CONTENTS repo | fix the arguments or set `JSC_WIKI_REPO_CONTENTS`, then call again |
| | 3 | something needs manual handling: an orphan page, a page that links to a moved page without being moved itself, or a destination page that already holds content | report those lists and hand them to the user; guess no key, and rewrite no link the script left alone |
## Rules
1. Page names must follow the wiki naming table in the skill guidelines (see `jsc-meta/references/guidelines.md`).
2. Use `tools/hash-id` for `{HASH}` values. It returns the first 8 uppercase SHA-1 hex chars, or `H` plus the first 7 chars when the raw hash starts with `0-9`, `A`, `B`, or `C`. Exit 1 means this machine has neither `sha1sum` nor `shasum`: stop, report that one of them has to be installed, and compute no hash by hand — a hand-made page name lands the content on a page nobody else reads.
3. To update a contents page (`*_CONTENTS`): `wiki-get` it first, apply the template to append or modify, then `wiki-put` the whole page back — the write asks for confirmation before it goes out. Never overwrite entries owned by others. Whether the page may be created from the template instead is decided by rule 4, and by nothing else.
1. Page names must follow the wiki naming table in the skill guidelines (see `jsc-meta/references/guidelines.md`). Check any page name you build with `tools/page-name.sh check {page}` before it reaches an API call.
2. Use `tools/hash-id` for `{HASH}` values. It returns the full 40 uppercase SHA-1 hex chars — no truncation, no prefix. Never compute a hash by hand: a hand-made page name lands the content on a page nobody else reads.
3. To update a contents page (`*_CONTENTS`), run `tools/wiki-contents.sh upsert {TYPE} {key-col} {key} {row-file} [template-file]`, where `{key-col}` is the column's 1-based position number, not the column name. It reads the page, replaces the row whose key column equals the key, appends the row when no line matches, and writes the whole page back through the same confirmation. Never overwrite entries owned by others. Whether the page may be created from the template instead is decided by rule 4, and by nothing else.
4. **Only exit 4 means the page is not there yet — this rule binds every "create it if it does not exist" path, without exception.** It is not limited to contents pages: a content page (`*_{HASH}`), a work log, an error page, a report, any page at all, follows the same branch.
- **Correct branch.** Read the page with `wiki-get`. Exit 0 means the page exists, so append or modify the content that came back and `wiki-put` the whole page. Exit 4 (HTTP 404) is the one and only code that permits creating a new page from the template.
- **Exit 7 and exit 8 abort.** Exit 7 (HTTP 401 or 403) and exit 8 (any other API failure) both mean the old content is unknown, never that the page is missing. Stop the operation and report the exit code with its cause. Create no page, write nothing, and do not retry the same call unchanged.
- **Why.** Wiki writes in this skill set are append-not-overwrite, and that semantics rests entirely on reading the old page back first. Reading a 401 as a 404 makes the caller believe it holds a brand-new page and `wiki-put` a fresh template over a live one, and the whole earlier record is gone — the write carries no merge and no backup.
- `tools/wiki-contents.sh` implements exactly this branch for contents pages and reports the same codes.
5. Write all wiki content in UTF-8 Traditional Chinese, per the STE100 output rule.
6. Prefer visual forms for page content: use mermaid diagrams (flowchart, sequence, gantt, pie) and markdown tables wherever the information allows. Prose is capped at 3 sentences per section, and a sentence stays only when neither a mermaid diagram nor a markdown table can carry the same information.
7. `tools/gitea.sh` retries once with the tea CLI login token when `GITEA_TOKEN` is missing or the response is 401/403. Report a failure only after that retry also fails.
8. `wiki-delete` removes a page for good. Call it only from `tools/migrate-wiki.sh --apply`, or when the user has asked for that exact page to go. In a migration the delete comes last: write the new page, read it back, then delete the old one.