From d4466dfba8c78d0284c09d4f067cb7a21f44d367 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 28 Aug 2026 17:54:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(gitea):=20=E5=AF=AB=E5=85=A5=E5=89=8D?= =?UTF-8?q?=E5=85=88=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++------ skills/wiki-to-issue/SKILL.md | 2 +- skills/wiki/SKILL.md | 4 ++-- tools/gitea.sh | 9 +++++++ tools/issue.sh | 4 ++++ tools/write-confirm.sh | 44 +++++++++++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 10 deletions(-) create mode 100755 tools/write-confirm.sh diff --git a/README.md b/README.md index 4122a19..b6fd862 100644 --- a/README.md +++ b/README.md @@ -31,20 +31,20 @@ gitea.sh hash-id # 產生 8 碼大寫 SHA-1; gitea.sh wiki-repo # 解析頁面類型的 wiki 位置(TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT / SKILLSET) gitea.sh wiki-list / gitea.sh wiki-get / # 不存在 exit 4 -gitea.sh wiki-put / # 自動判斷新建或更新 +gitea.sh wiki-put / # 自動判斷新建或更新;會先要求確認 gitea.sh wiki-url / # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用 -gitea.sh pr-create / <body-file> +gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 會先要求確認 gitea.sh pr-status <owner>/<repo> <pr-index> # 印出 {state} {merged} {mergeable} gitea.sh pr-get <owner>/<repo> <pr-index> # 印出 PR 的標題、base 分支與描述,供呼叫端比對有沒有差 # 前三行固定 title、base、body 三個標記,第 4 行起是描述原文 -gitea.sh pr-edit <owner>/<repo> <pr-index> <title> <body-file> +gitea.sh pr-edit <owner>/<repo> <pr-index> <title> <body-file> # 會先要求確認 # 更新 PR 的標題與描述;描述從檔案讀,裝得下多行 gitea.sh pr-comments <owner>/<repo> <pr-index> # 印出所有留言(issue 留言、審查評語、行內留言),第三欄帶 #id,依時間排序 -gitea.sh comment-reply <owner>/<repo> <pr-index> <issue|review|inline> <comment-id> <body-file> +gitea.sh comment-reply <owner>/<repo> <pr-index> <issue|review|inline> <comment-id> <body-file> # 會先要求確認 # 回覆本輪處理過的 PR 留言;inline 走 review comment reply,其餘補一則 PR 留言 -gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index> +gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index> # 會先要求確認 # 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併 -gitea.sh repo-set <owner>/<repo> <description> [website] # 設定 repo 描述與網頁 +gitea.sh repo-set <owner>/<repo> <description> [website] # 會先要求確認 gitea.sh markdown <file> # markdown 檔渲染成 HTML 片段(走 /markdown/raw) gitea.sh api <METHOD> <path> [json-file] hash-id <text> # 與 gitea.sh hash-id 相同 @@ -65,7 +65,7 @@ pr-watch.sh <owner>/<repo> <pr-index> [state-file] gitea-link.sh parse <url> # 解析 wiki 或議題連結;不是這兩種就 exit 3(呼叫端據此中止) issue.sh labels|label-ids|projects <owner>/<repo> issue.sh title|body|labels-of <owner>/<repo> <index> -issue.sh create <owner>/<repo> <title> <body-file> [--labels <ids>] [--milestone <id>] +issue.sh create <owner>/<repo> <title> <body-file> [--labels <ids>] [--milestone <id>] # 會先要求確認 html-style.sh get|set|unset|list|layouts|styles # 種類對版型與風格的設定 html-render.sh --markdown <檔案> --title <標題> --out <輸出檔> [--layout] [--style] [--subtitle] [--source-url] ``` diff --git a/skills/wiki-to-issue/SKILL.md b/skills/wiki-to-issue/SKILL.md index 24dc6b0..40f8ce2 100644 --- a/skills/wiki-to-issue/SKILL.md +++ b/skills/wiki-to-issue/SKILL.md @@ -11,7 +11,7 @@ The wiki link is the only input. Everything else — repository, page name, host 1. **Link gate.** Run `tools/gitea-link.sh parse {url}` on the link the user gave. Exit 3, no link in the request, or `kind=issue` (this skill reads wiki pages, not issues) all mean the same thing: **stop and report which one it was**. Never ask for a repository name instead, and never fall back to the working directory's remote — a page written into the wrong repository's issue tracker is public and hard to take back. Completion condition: `kind=wiki`, and `repo`, `page` and `host` are known. 2. Read the page with `jsc-gitea:wiki` (`wiki-get {repo} {page}`). Exit 4 means the page does not exist — stop and report the page name. Take the page's absolute URL from `wiki-url` in the same pass; it goes into the issue body. Completion condition: the page's markdown and its absolute URL are both in hand. -3. **Draft the issue — this step MUST run as a sub agent.** Title: the page's first heading, or the page name when it has none. Body: the page content in Traditional Chinese, opening with a 「來源:{絕對網址}」 line so the issue points back at the wiki. Convert `[[display|page]]` links to absolute URLs (`wiki-url`), because `[[...]]` resolves only inside a wiki. Drop personal data — an issue is read by more people than a wiki page. Completion condition: title and body file exist, the body carries the source line, and no `[[...]]` link is left in it. +3. **Draft the issue — this step MUST run as a sub agent.** Title: the page's first heading, or the page name when it has none. Body: the page content in Traditional Chinese, opening with a 「來源:{絕對網址}」 line so the issue points back at the wiki. Convert `[[display|page]]` links to absolute URLs (`wiki-url`), because `[[...]]` resolves only inside a wiki. Drop personal data — an issue is read by more people than a wiki page. Write `issue.sh create` 前先確認,因為建立議題本身也會要求確認。Completion condition: title and body file exist, the body carries the source line, and no `[[...]]` link is left in it. 4. **Labels come from what the repository already has.** Run `tools/issue.sh labels {repo}`, propose the fitting ones with a reason each, and confirm per `jsc-ask:ask` rules — every option states its impact scope (a label drives filters and board rules, so a wrong one routes the work to the wrong queue). Turn the confirmed names into ids with `tools/issue.sh label-ids`. An empty label list, or nothing fitting: ask whether to create the issue with no label, and record that answer. **Never invent a label that the repository does not have.** Completion condition: the user has confirmed a label set — possibly empty — and its ids are resolved. 5. **Project board.** Run `tools/issue.sh projects {repo}`. Exit 3 means this Gitea has no board API: say so plainly, and hand the user the board URL the script printed so they can drag the issue in themselves. A board list comes back: let the user pick one per `jsc-ask:ask` rules, attach it, and report the failure verbatim if the attach call is refused. Completion condition: the issue is either attached to a board, or the report states in one line that the board link is still outstanding and who has to do it. 6. Create the issue: `tools/issue.sh create {repo} {title} {body-file} [--labels {ids}]`. Report the `index=` and `url=` it prints. Completion condition: the issue URL is reported to the user, together with the labels applied and the board status from step 5. diff --git a/skills/wiki/SKILL.md b/skills/wiki/SKILL.md index 7b5a641..b9f3402 100644 --- a/skills/wiki/SKILL.md +++ b/skills/wiki/SKILL.md @@ -21,7 +21,7 @@ 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}` (exit 4 when missing) | -| write page | write the content to a temp file first, then `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}` (creates or updates automatically) | +| write page | 先寫入暫存檔,再呼叫 `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}`(先確認,再建立或更新) | | page URL | `tools/gitea.sh wiki-url {owner}/{repo} {page}` — the page's absolute URL, taken from the API's `html_url` (exit 4 when the page is missing) | 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`. @@ -30,7 +30,7 @@ When writing a page, link same-type pages with the `[[display|page]]` form (disp 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`. -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. Never overwrite entries owned by others. +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(寫入前會先確認)。不要覆寫別人的列。 4. Write all wiki content in UTF-8 Traditional Chinese, per the STE100 output rule. 5. 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. 6. `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. diff --git a/tools/gitea.sh b/tools/gitea.sh index 929dcf8..73cb38a 100755 --- a/tools/gitea.sh +++ b/tools/gitea.sh @@ -31,6 +31,9 @@ set -eu script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +confirm_write() { + sh "$script_dir/write-confirm.sh" "$1" "$2" +} resolve_wiki_repo() { # TYPE -> JSC_WIKI_REPO_{TYPE} -> JSC_WIKI_REPO type=$(printf '%s' "${1:?TYPE required}" | tr a-z A-Z) @@ -190,6 +193,7 @@ sys.stdout.write(json.load(sys.stdin).get("html_url") or "") printf '%s\n' "$url" ;; wiki-put) or="${1:?owner/repo required}"; page="${2:?page required}"; file="${3:?content file required}" + confirm_write "寫入 wiki 頁" "$page" tmp=$(mktemp) python3 -c ' import json,sys,base64 @@ -207,6 +211,7 @@ print(json.dumps({"title":title,"content_base64":base64.b64encode(content).decod pr-create) or="${1:?owner/repo required}"; head="${2:?head required}"; base="${3:?base required}" title="${4:?title required}"; body_file="${5:?body file required}" + confirm_write "建立 PR" "$or#$title" tmp=$(mktemp) python3 -c ' import json,sys @@ -249,6 +254,7 @@ sys.stdout.write(p.get("body") or "") or="${1:?owner/repo required}"; idx="${2:?pr index required}" title="${3:?title required}"; body_file="${4:?body file required}" [ -f "$body_file" ] || { echo "找不到描述檔: $body_file" >&2; exit 2; } + confirm_write "更新 PR" "$or#$idx" tmp=$(mktemp) python3 -c ' import json,sys @@ -301,6 +307,7 @@ for c in d: kind="${3:?kind required}"; cid="${4:?comment id required}"; body_file="${5:?body file required}" [ -f "$body_file" ] || { echo "找不到回覆檔: $body_file" >&2; exit 2; } case "$kind" in issue|review|inline) ;; *) echo "kind must be issue, review, or inline" >&2; exit 2 ;; esac + confirm_write "回覆留言" "$or#$idx/$kind#$cid" tmp=$(mktemp) python3 -c ' import json,sys @@ -320,6 +327,7 @@ print(json.dumps({"body":open(sys.argv[1],encoding="utf-8").read()})) pr-depend) or="${1:?owner/repo required}"; idx="${2:?pr index required}" dep_or="${3:?dep owner/repo required}"; dep_idx="${4:?dep index required}" + confirm_write "設定 PR 依賴" "$or#$idx -> $dep_or#$dep_idx" tmp=$(mktemp) python3 -c ' import json,sys @@ -333,6 +341,7 @@ print(json.dumps({"owner":o,"repo":r,"index":int(sys.argv[2])})) echo "OK $or#$idx depends on $dep_or#$dep_idx" ;; repo-set) or="${1:?owner/repo required}"; desc="${2:?description required}"; site="${3:-}" + confirm_write "更新 repo 資訊" "$or" tmp=$(mktemp) python3 -c ' import json,sys diff --git a/tools/issue.sh b/tools/issue.sh index 2fab5cd..9369ee6 100755 --- a/tools/issue.sh +++ b/tools/issue.sh @@ -133,6 +133,10 @@ for l in json.load(sys.stdin).get("labels") or []: *) echo "[jsc][議題][ERR]:不認得的選項「$1」。" >&2; usage ;; esac done + confirm_target="議題「$title」" + [ -n "$labels" ] && confirm_target="$confirm_target,標籤:$labels" + [ -n "$milestone" ] && confirm_target="$confirm_target,里程碑:$milestone" + sh "$script_dir/write-confirm.sh" "建立議題" "$confirm_target" payload=$(mktemp) trap 'rm -f "$payload"' EXIT python3 -c ' diff --git a/tools/write-confirm.sh b/tools/write-confirm.sh new file mode 100755 index 0000000..38ab911 --- /dev/null +++ b/tools/write-confirm.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh +# write-confirm.sh — Gitea 寫入前的人工確認。 +# +# 為什麼要有這支腳本:wiki 與議題都是對外寫入。先確認,才能避免把 +# 錯頁、錯內容、錯標籤直接送出去。 +# +# 用法: +# write-confirm.sh <動作> <目標> +# +# 規則: +# - JSC_GITEA_CONFIRM=yes:直接放行。 +# - JSC_GITEA_CONFIRM=no:直接拒絕。 +# - 互動式執行:要求輸入「確認」。 +# - 沒有互動終端:拒絕,避免默默寫出錯資料。 +set -eu + +action="${1:-}" +target="${2:-}" + +[ -n "$action" ] || { echo '[jsc][Gitea][ERR]:缺少動作名稱。' >&2; exit 2; } +[ -n "$target" ] || { echo '[jsc][Gitea][ERR]:缺少目標名稱。' >&2; exit 2; } + +case "${JSC_GITEA_CONFIRM:-}" in + yes|YES|1|true|TRUE) + exit 0 ;; + no|NO|0|false|FALSE) + echo "[jsc][Gitea][ERR]:已取消寫入「$target」。" >&2 + exit 2 ;; +esac + +if [ -t 0 ]; then + printf '[jsc][Gitea] 即將%s「%s」。輸入「確認」繼續:' "$action" "$target" >&2 + IFS= read -r answer || exit 2 + case "$answer" in + 確認) + exit 0 ;; + *) + echo "[jsc][Gitea][ERR]:已取消寫入「$target」。" >&2 + exit 2 ;; + esac +fi + +echo "[jsc][Gitea][ERR]:寫入「$target」需要先確認;請改用互動式執行,或先設 JSC_GITEA_CONFIRM=yes。" >&2 +exit 2 -- 2.53.0