主線成員讀不到任何一條 /dependencies 時不准印出完整名單:set 不印 並離開碼 3、refresh/adopt 保留舊快取並講出來(inkstone/ISEP#130 → c6973)

總管 09-13 實撞:Gitea 握手逾時,hub 票 InkStoneCo#132 本身讀得到、它的 /dependencies 讀不到,
`mainline set` 仍印「 成員 6 張」,底下三張前置全漏;`refresh --members` 零輸出、名單沒變。
雲端工人靠這份名單抓票,漏收=以為抓盡=收工,等於 v0.30.0 換個入口又停。

- collect_members:清單讀取拿不到就重讀(ISEP_MEMBER_RETRIES 預設 2、退避 2s×n);
  仍讀不到的票記進 info["unread"],info["complete"]=沒有 unread 且沒被上限截斷
- set:不完整時印 ⚠️🔴 成員不完整)+點名讀不到的票,主線檔寫 members_complete/members_unread,
  同一條線保留舊快取成員,離開碼 3(主線本身照樣標定)
- refresh --members:完整才蓋快取並印成員張數;不完整保留舊快取並講出來;讀不到里程碑也講
- adopt:不完整時與舊快取取聯集

重現(改之前跑新測試):F1–F5、F9–F11、F13–F14 共 11 條紅——set 印 、arcrun-rag#104 漏收、
refresh --members 零輸出且把快取蓋成少兩張的名單。
修後:mainline-members-by-dependency 70/71(唯一紅的 ㊽ 在 main 上同樣紅)、repo-mirror 17/17、
gitea-fallback 31/31、focus-guard 24/24、ticket-pick 58/58、debt-worklist 47/47。
真 Gitea 唯讀跑(不寫主線檔):InkStoneCo#65 成員 9 張、complete=True、hub #132 底下三張都收到。
沒動 InkStoneCo 的 system-dev/mainline.json。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 13:57:44 +08:00
parent 0d49c0dcd7
commit c0eec76b52
3 changed files with 122 additions and 13 deletions
+51 -7
View File
@@ -252,21 +252,50 @@ def cmd_set(argv):
"members": [],
}
refs, info = collect_members(ms)
complete = bool(info.get("reached") and info.get("complete"))
if not complete:
# 名單不完整:同一條線重標時,舊快取裡有的成員先留著(讀不到 ≠ 沒有)
prev = ML.load() or {}
if prev.get("ref") == ms["ref"]:
refs = sorted(set(refs) | set(prev.get("members") or []))
ms["members"] = refs
ms["members_complete"] = complete
ms["members_unread"] = incomplete_what(info)
now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M")
ms["set_at"] = ms["refreshed_at"] = now
if not ML.save(ms, mirror=True):
die("🔴 寫不進 %s——主線沒有被標上。" % ML.path())
print("✅ 主線已標定:%s/%s#%d「%s」" % (owner, repo, mid, title))
# 🔴 inkstone/ISEP#130 c6973:名單不完整時不准印 ✅——雲端工人靠這份名單抓票,
# 漏收=以為抓盡=收工。主線本身照樣標定,壞的只有成員那一格,所以講清楚是哪一格。
print("%s 主線已標定:%s/%s#%d「%s」%s" % (
"✅" if complete else "⚠️", owner, repo, mid, title, "" if complete else "(🔴 成員不完整)"))
if hub:
print(" hub 票:%s" % hub)
print(members_line(refs, info) if info.get("reached")
else " ⚠️ 讀不到 hub 里程碑裡的票,成員先留空(讀不到 ≠ 沒有)")
if info.get("capped"):
print(" ⚠️ 相依走到 %d 張就停了(上限),剩下的沒收" % ML.MEMBER_WALK_CAP)
if not complete:
print(incomplete_line(info))
print(mirror_line())
print()
cmd_show([])
if not complete:
sys.exit(3)
def incomplete_what(info):
"""名單不完整的原因,寫進主線檔(讀檔的人不必重跑也看得出)。完整時回 []。"""
what = list(info.get("unread") or [])
if not info.get("reached"):
what.insert(0, "hub 里程碑那一頁)")
if info.get("capped"):
what.append("(相依走到 %d 張上限)" % ML.MEMBER_WALK_CAP)
return what
def incomplete_line(info):
return (" 🔴 成員不完整:讀不到 %s——那底下的前置沒收到。\n"
" 雲端工人靠這份名單抓票;Gitea 恢復後重跑 `mainline refresh --members`(或同一行 set)補齊。"
% "、".join(incomplete_what(info)))
def mirror_line():
@@ -328,6 +357,9 @@ def cmd_refresh(argv):
except SystemExit:
return # 拿不到 token:主線已經在了,進度晚點再更新,不炸
if not isinstance(d, dict) or not d.get("title"):
if "--members" in argv: # 明講要成員的人要看得到結果(c6973:原本零輸出)
print(" ⚠️ 讀不到主線里程碑 %s——成員沒更新,沿用舊快取 %d 張"
% (ML.ref_of(ms), len(ms.get("members") or [])))
return # 讀不到 ≠ 主線不存在
ms.update({
"title": d.get("title") or ms["title"],
@@ -342,12 +374,19 @@ def cmd_refresh(argv):
# 真 Gitea 實測 25 張成員要 19 秒 ⇒ SessionStart 那條不帶 --members(成本不變);
# `set``adopt` 會重收,`pick``has`focus guard 本來就每次現問 Gitea,快取只餵 `show`。
# 讀不到(reached=False)就保留舊快取——寧可資料舊,不要把成員弄丟。
# 🔴 inkstone/ISEP#130 c6973:只有**完整**的名單才准蓋掉快取;不完整就留舊的並講出來。
try:
refs, info = collect_members(ms)
except SystemExit:
refs, info = [], {"reached": False}
if info.get("reached"):
if info.get("reached") and info.get("complete"):
ms["members"] = refs
ms["members_complete"], ms["members_unread"] = True, []
print(members_line(refs, info))
else:
ms["members_complete"], ms["members_unread"] = False, incomplete_what(info)
print(" ⚠️ 成員不完整,沿用舊快取 %d 張(沒有拿殘缺名單蓋掉)" % len(ms.get("members") or []))
print(incomplete_line(info))
ML.save(ms)
@@ -391,13 +430,18 @@ hub 里程碑裡現在有:%s
T.add_dependency(vparts, (owner, repo, num))
how = "成為 hub 票 %s 的相依(%s/%s 沒有多出任何里程碑)" % (via, owner, repo)
refs, info = collect_members(ms)
if info.get("reached"):
complete = bool(info.get("reached") and info.get("complete"))
if complete:
ms["members"] = sorted(set(refs) | {ref})
else:
ms["members"] = sorted(set(ms.get("members") or []) | {ref})
# 不完整(c6973):新收到的與舊快取取聯集,不拿殘缺名單蓋掉
ms["members"] = sorted(set(ms.get("members") or []) | set(refs) | {ref})
ms["members_complete"], ms["members_unread"] = complete, incomplete_what(info)
ML.save(ms, mirror=True)
print("✅ 補收:%s → %s" % (ref, how))
print(members_line(ms["members"], info) if info.get("reached") else " (成員清單沿用舊快取+這一張)")
print(members_line(ms["members"], info) if complete else " (成員清單舊快取+這次讀得到的+這一張)")
if not complete:
print(incomplete_line(info))
print(mirror_line())