feat(gitea): 新增 wiki 轉議題技能

What:新增 wiki-to-issue 技能,把一頁 wiki 轉成同一個存取庫的議題;配套新增 tools/gitea-link.sh 解析連結、tools/issue.sh 讀寫議題。

Why:wiki 頁要變成可追蹤的工作時,原本得自己拼 API:組 JSON、查標籤 id、挑回應欄位,跳脫一錯就把對外的議題內容寫壞。

How:連結是唯一入口,gitea-link.sh 解不出 wiki 頁就中止,不猜存取庫也不猜頁名;標籤只從既有的挑;站台沒有專案看板 API 時據實回報請使用者手動拖,不假裝關聯成功。

Who:使用 jsc 技能組、需要把 wiki 內容交辦出去的人。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 09:19:08 +08:00
co-authored by Claude Opus 5
parent 8f94069ceb
commit f8123e829b
3 changed files with 257 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env sh
# gitea-link.sh — 解析 Gitea 的 wiki 頁或議題連結(供 jsc-gitea:wiki-to-issue、html-export 使用)。
#
# 為什麼要有這支腳本:兩支技能都以「一條連結」為唯一入口,沒有連結就中斷。連結長什麼樣、
# 哪一段是存取庫、哪一段是頁名,是固定的字串規則,交給模型每次自己拆,拆錯就寫到別的存取庫去。
#
# 用法:
# gitea-link.sh parse <url> 解析連結,印出可直接 eval 的欄位
#
# parse 輸出(每行一個 key=value,值已加單引號):
# kind='wiki' repo='owner/repo' page='PAGE_NAME' host='https://gitea.example'
# kind='issue' repo='owner/repo' index='12' host='https://gitea.example'
#
# 認得的形式:
# https://host/{owner}/{repo}/wiki/{page} (page 可含 %XX 編碼,會還原)
# https://host/{owner}/{repo}/wiki/{page}/_edit (尾巴的 _edit、_new 會去掉)
# https://host/{owner}/{repo}/issues/{index}
# https://host/{owner}/{repo}/issues/{index}#issuecomment-123
#
# 結束碼: 0=解析成功 2=用法錯誤 3=不是認得的 wiki 或議題連結
#
# 陷阱:
# - 結束碼 3 由呼叫端當成「沒有連結」處理,直接中斷流程,不要退回去猜存取庫或頁名。
# - 只解析連結本身,不打網路,也不檢查頁面存不存在;那是 wiki-get 與 issue.sh 的事。
set -eu
usage() {
echo 'usage: gitea-link.sh parse <url>' >&2
exit 2
}
[ "$#" -eq 2 ] || usage
[ "$1" = parse ] || usage
url="$2"
[ -n "$url" ] || usage
python3 - "$url" <<'PY'
import re, sys
from urllib.parse import urlsplit, unquote
url = sys.argv[1].strip()
if not re.match(r'^https?://', url):
print('[jsc][連結解析][ERR]:連結要以 http:// 或 https:// 開頭。', file=sys.stderr)
sys.exit(3)
u = urlsplit(url)
host = f'{u.scheme}://{u.netloc}'
parts = [p for p in u.path.split('/') if p]
if len(parts) < 4:
print('[jsc][連結解析][ERR]:認不出存取庫與頁面,需要 /{owner}/{repo}/wiki/{page} 或 /{owner}/{repo}/issues/{index}。', file=sys.stderr)
sys.exit(3)
owner, repo, kind = parts[0], parts[1], parts[2]
rest = parts[3:]
def out(**kv):
for k, v in kv.items():
print(f"{k}='{str(v)}'")
if kind == 'wiki':
while rest and rest[-1] in ('_edit', '_new', '_pages'):
rest.pop()
if not rest:
print('[jsc][連結解析][ERR]:wiki 連結少了頁名。', file=sys.stderr)
sys.exit(3)
page = unquote('/'.join(rest))
out(kind='wiki', repo=f'{owner}/{repo}', page=page, host=host)
elif kind == 'issues':
idx = rest[0].split('#')[0]
if not idx.isdigit():
print('[jsc][連結解析][ERR]:議題編號不是數字。', file=sys.stderr)
sys.exit(3)
out(kind='issue', repo=f'{owner}/{repo}', index=idx, host=host)
else:
print(f'[jsc][連結解析][ERR]:只認得 wiki 與 issues 連結,收到「{kind}」。', file=sys.stderr)
sys.exit(3)
PY