雲端第一則 prompt 的主線是現在這條:兩份主線檔都不在時 refresh 去 Gitea 讀 InkStoneCo main 那份一次(inkstone/ISEP#140)

薄殼 SessionStart 跑在 bootstrap.sh 之前,InkStoneCo/ 還沒 clone、家目錄空 ⇒ 兩份都不在 ⇒
countdown.py 退到「期限最近的 milestone」⇒ 第一則 prompt 印逾期 341 小時的舊線。機制沒壞,是順序。

- hooks/lib/mainline.py:第三個來源 fetch_gitea()(本檔唯一會打網路的函式,只有 SessionStart 的
  `mainline refresh` 叫它;load()/line() 照舊零網路)。優先序寫進檔頭:家目錄 > repo 那份 > Gitea 那份。
  匿名優先,匿名被拒(401/403/404)且拿得到 token 才帶 token 再問一次;讀不到回 None,逾時有上限。
- scripts/mainline refresh:兩份都不在 ⇒ fetch_gitea 一次、只寫家目錄(不 mirror);token 拿不到不炸、不抱怨。
- hooks/tests/mainline-gitea-fallback.test.sh:31 條,假 Gitea 有 public/private/500/garbage/notitle/hang 六種回法。

假設(票上紅線寫「一律匿名不帶 token」,其前提「Gitea 匿名讀得到」實測為假):
inkstone/InkStoneCo 在 Gitea 是 private——匿名打 raw/contents/repo API 都 404,ISEP 同端點 200。
⇒ 照字面做這張票在真環境永遠讀不到。改成匿名優先、被拒才帶 token(同一條 SessionStart 鏈上
countdown-milestone-refresh.sh 本來就帶 GITEA_TOKEN_CLAUDE_CODE 打 Gitea;D20 管的是 GitHub)。
真跑:帶 token 2.06s 讀到 inkstone/Arcrun#48;拔 token rc 0、零檔案、「現在沒有主線」。

版本:待總管定版。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178ef1fGw3XeZtpN7LaZrm4
This commit is contained in:
2026-09-07 06:19:39 +00:00
parent 1c0012107f
commit 48befe8906
7 changed files with 389 additions and 11 deletions
+39 -4
View File
@@ -24,7 +24,9 @@ leo 2026-08-27:「**14 個里程碑同時亮著卻不知道該看哪個**」
── 答案為什麼唯一 ─────────────────────────────────────────────────
主線住在一個檔裡(`~/.claude/isep-countdown/mainline.json`),那個檔**放得下一條**。
inkstone/ISEP#130 起它有一份隨 repo 走的鏡像:`InkStoneCo/system-dev/mainline.json`——
雲端沒有家目錄那份,讀的就是它;`set`/`adopt``clear` 兩份一起寫,`refresh` 只寫家目錄。
雲端沒有家目錄那份,讀的就是它;`set`/`adopt``clear` 兩份一起寫,`refresh` 只寫家目錄。
inkstone/ISEP#140 起:兩份都不在時(雲端薄殼 bootstrap 之前的形狀),`refresh` 會去 Gitea
讀 `inkstone/InkStoneCo` main 上那份一次、寫進家目錄——優先序寫在 `hooks/lib/mainline.py` 檔頭。)
沒有清單、沒有優先序、沒有「前三名」——`set` 覆蓋掉的那條就不再是主線。
⇒ 問一次只會拿到 0 或 1 個答案,不會拿到 14 個。
@@ -144,6 +146,8 @@ def cmd_show(argv):
print(" 要標一條:`scripts/mainline list` 看有哪些 → `scripts/mainline set <owner/repo#id>`")
print()
print("📌 在標之前,每回合的 🎯 那一行也會照樣講「現在沒有主線」,不會編一條出來。")
print(" 雲端:SessionStart 的 `mainline refresh` 會先去 Gitea 讀 inkstone/InkStoneCo main 的")
print(" system-dev/mainline.json;還是沒有 ⇒ 那份不存在,或這台拿不到 Giteainkstone/ISEP#140)。")
return
c, total, pct = ML.progress(ms)
print("🎯 現在的主線:%s「%s」" % (ML.ref_of(ms), ms.get("title")))
@@ -247,12 +251,43 @@ def cmd_clear(argv):
# ── refresh ──────────────────────────────────────────────────────────────
def soft_token():
"""拿得到就給,拿不到回空字串。`refresh` 跑在 SessionStart**不准因為沒 token 就整支炸**
——雲端薄殼的 remote 是 GitHub、家目錄是空的,那正是它最需要活著的時候。"""
if T is None:
return ""
import contextlib
import io
try:
with contextlib.redirect_stderr(io.StringIO()): # token() 拿不到會先抱怨再 exit:兩個都吞掉
return T.token() or ""
except (SystemExit, Exception):
return ""
def cmd_refresh(argv):
"""SessionStart 跑一次。**拿不到就原封不動**——寧可資料舊,不要把主線弄丟。"""
"""SessionStart 跑一次。**拿不到就原封不動**——寧可資料舊,不要把主線弄丟。
inkstone/ISEP#140:家目錄與 repo 那份都不在(雲端薄殼跑 SessionStart 時 `InkStoneCo/`
還沒 clone)⇒ 去 Gitea 讀 `inkstone/InkStoneCo` main 上那份**一次**,寫進家目錄。
之後每一則 UserPromptSubmit 讀的是家目錄,路徑上照舊沒有網路。
讀不到 ⇒ 什麼都不寫,退回現行行為(`countdown.py` 的第二基準)。
"""
ms = ML.load()
if not ms:
return # 沒有主線就沒有東西要更新,安靜結束
d = T.api_soft("/repos/%s/%s/milestones/%s" % (ms["owner"], ms["repo"], ms["id"]))
got = ML.fetch_gitea(token=soft_token) # 傳函式:匿名讀得到就不去碰 token
if not got:
return # 兩份都沒有、Gitea 也問不到 ⇒ 真的沒有主線,安靜結束
ML.save(got) # 只寫家目錄:雲端沒有 InkStoneCo/ 可寫,本機不弄髒工作樹
ms = ML.load()
if not ms:
return
if T is None:
return
try:
d = T.api_soft("/repos/%s/%s/milestones/%s" % (ms["owner"], ms["repo"], ms["id"]))
except SystemExit:
return # 拿不到 token:主線已經在了,進度晚點再更新,不炸
if not isinstance(d, dict) or not d.get("title"):
return # 讀不到 ≠ 主線不存在
ms.update({