Files
ISEP/hooks/session-start-recall.sh
claude-code 67f5f0fa5e 舊票每天有固定管道被撈出來(inkstone/ISEP#83)
leo 2026-08-27:「現在每天都在追新的,所以要用 Routine 去消化舊的,可以有多個條件」

兩條線各用各的判準(他當場訂正的那件事):
  緊急線=掛 open milestone 的,用「距離目標的遠近」排
  還債線=沒掛的那 188 張,用多條件排(事故/擋人/承諾/票齡/估工)← 本次做的

新增 scripts/debt-worklist(list/claim/forget):
  · 撈全 org 15 個 repo 的 open issue + open PR,逐一列出當作「我查了哪些」的證明
  · 扣掉掛 open milestone 的(緊急線在管)、等 leo 的(Human/human-exec/s/stage,
    那些要催不是要領,催辦是 inkstone/ISEP#93)、已領走的
  · 每張票旁邊印出「為什麼排這裡」的分項,排錯可以指著某一項說它給太多
  · 成包只認強訊號:互相指著、或 Gitea 原生相依(母子票)。
    第一版用單向引用成包,190 張裡 121 張黏成一坨——一包沒有人領得動
  · 空清單會明講「查詢跑過了,結果是零」,不默默結束
  · claim 之後那些票不再出現在清單上,並在隔天的清單頂端交代它們關了沒

hooks/session-start-recall.sh 加一段(不打網路,只看狀態檔日期):
  今天還沒撈過就說一句,並給出要跑的那一行。開 session 自動去撈=輪詢,紅線擋著。

測試 scripts/test-debt-worklist.sh:40 條全離線,票上四條驗收條件各一組。
真實資料唯讀實跑過:229 張 → 可領 188 張/185 包,領走兩張後隔天清單少掉它們。
2026-08-28 00:38:02 +00:00

180 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# SessionStart hook — 開 session 就把「全局」推到眼前(InkStoneCo#17 / D69 / D70 / D71
#
# 掛在 settings.json 的 SessionStartmatcher: startup|resume|clear)。stdout 會被當成 context 注入。
#
# ── 這支在整個機制裡的位置(leo 2026-08-11 拍板)─────────────────────────────
# 算 雲端的 Arcrun 工作流 `global_index`(讀 Gitea 五個 repo 的票 + 讀 KBDB 藏書地圖)
# 存 = 雲端(總圖住雲端,不推回地端;沒有東西需要同步,所以沒有漂移源)
# 拿 = 本檔。**由機制保證取一次**,不靠 AI 記得去打 MCP
# openhuman SuperContext 的教訓:靠模型自己選擇要不要查,就是會漏)
#
# ⚠️ 例外=下面 push 2/5 的「各 repo wiki 全景」:那段**算在地端**。
# 不是不想放雲端,是雲端拿不到 repo 樹(只有 API 列檔一條路,撞 principles 紅線)。
#
# ── 為什麼不再 cat status.md ────────────────────────────────────────────────
# 2026-08-11 實測:舊版這支吐 235,880 bytes(因為它 cat 整份 216 KB 的落帳流水),
# 真正進到 context 的只有 2 KB 預覽 + 一個要自己去 Read 的檔名。
# ⇒ 「開場就知道」等於沒發生。**注入通道的預算是有限的,落帳流水不該佔著它。**
# 進度/狀態一律去 Gitea(D52 + D72),不在地端養第二份會漂的快照。
#
# 順序=重要性:全局總圖最前面(它最可能被截斷保護到),其次原則,最後踩坑標題。
set -euo pipefail
GLOBAL_INDEX_URL="https://arcrun-cypher-executor.leo21c.workers.dev/webhooks/named/leo/global_index/query"
PANORAMA_FILE="system-dev/wiki/PANORAMA.md"
PRINCIPLES_FILE="system-dev/wiki/principles.md"
MISTAKES_FILE="system-dev/wiki/mistakes.md"
MISTAKES_TAIL=20 # 只推最近 N 條標題(全份 96 條/11 KB 會吃掉總圖的預算)
# ── push 1/5:全局總圖(雲端現算)────────────────────────────────────────────
# 降級規格(D71,票與知識不對稱):
# 算得出來 → 注入
# 算不出來/破損/空 → **完全不注入內容**,只誠實說一句「這次沒有」
# (寧可冷啟動,不要注入垃圾;票尤其不能給一份看起來像現況的舊東西)
GLOBAL_MD=""
GLOBAL_ERR=""
if RESP=$(curl -sS -m 45 -X POST "$GLOBAL_INDEX_URL" \
-H 'Content-Type: application/json' -d '{}' 2>/dev/null); then
GLOBAL_MD=$(printf '%s' "$RESP" | python3 -c '
import sys, json
try:
d = json.load(sys.stdin)
except Exception:
sys.exit(0)
def find_md(x, depth=0):
# 工作流的最終輸出可能被包一層 data(code 零件的回應信封),兩種形狀都收
if depth > 3 or not isinstance(x, dict):
return None
if x.get("success") is True and isinstance(x.get("md"), str) and x["md"].strip():
return x["md"]
return find_md(x.get("data"), depth + 1)
md = find_md(d)
if md:
sys.stdout.write(md)
' 2>/dev/null || true)
if [ -z "$GLOBAL_MD" ]; then
GLOBAL_ERR=$(printf '%s' "$RESP" | python3 -c '
import sys, json
try:
d = json.load(sys.stdin)
except Exception:
sys.stdout.write("回應不是 JSON"); sys.exit(0)
sys.stdout.write(str((d or {}).get("error") or "工作流沒有交出 md")[:200])
' 2>/dev/null || echo "回應解析失敗")
fi
else
GLOBAL_ERR="連不上雲端(curl 失敗或逾時 45 秒)"
fi
echo "════════════════════════════════════════════════"
if [ -n "$GLOBAL_MD" ]; then
printf '%s\n' "$GLOBAL_MD"
else
echo "⚠️ 這次拿不到全局總圖:${GLOBAL_ERR}"
echo " 刻意不給你一份舊的——票的狀態放舊的比沒有更危險(會去重做已經做完的事)。"
echo " ⇒ 你現在**不知道有哪些票、庫裡有哪些知識**。動手前先自己撈:"
echo " GiteaGET https://git.uncle6.me/api/v1/repos/Leo/<repo>/issues?state=open"
echo " (五個 repoInkStoneCo / Arcrun / arcrun-rag / mira / system-dev-template"
echo " 知識:kbdb_get_map()"
echo " 修這條管路:工作流 leo:wf:global_index(雲端),本 hook 只負責取來印。"
fi
echo "════════════════════════════════════════════════"
echo ""
# ── push 2/5:各 repo 的 wiki 全景(leo 2026-08-12「一次看到全景」的另一半)──
# 上面那段是**票**(雲端現算)+ KBDB 藏書地圖;這段是**各 repo 真正的 wiki**。
# 為什麼不塞進雲端那支工作流:工作流跑在 Cloudflare 上,要拿 repo 樹只有
# 「Gitea contents API 列檔」一條路,而 principles 紅線寫死
# 「讀 repo 走 git clone/fetch,不走 API 列檔」。
# ⇒ 在有 git 的地方算好(`scripts/wiki-panorama.sh`,人發起、不輪詢),
# 存成 repo 裡的一份 md,本檔只負責印。
# 只印到 `panorama:inject-end` 為止——標記以下是給 grep 的完整卡片清單,
# 全倒進來會撐爆 context(全檔 ~28 KB,注入段 ~6 KB)。
if [ -f "$PANORAMA_FILE" ]; then
echo "════════════════════════════════════════════════"
awk '/panorama:inject-end/{exit} {print}' "$PANORAMA_FILE"
# 過期就講出來(不自動重算:重算要連網 clone 十幾個 repo,不該卡在開場)
P_GEN=$(grep -m1 -oE '[0-9]{4}-[0-9]{2}-[0-9]{2} 產生' "$PANORAMA_FILE" | cut -d' ' -f1 || true)
W_LAST=$(git log -1 --format=%ad --date=short -- system-dev/wiki 2>/dev/null || true)
if [ -n "$P_GEN" ] && [ -n "$W_LAST" ] && [ "$W_LAST" \> "$P_GEN" ]; then
echo ""
echo "⚠️ 這張全景圖是 ${P_GEN} 算的,但 wiki 在 ${W_LAST} 又被改過 ⇒ 它已經不準了。"
echo " 重算:bash scripts/wiki-panorama.sh --write"
fi
echo "════════════════════════════════════════════════"
echo ""
else
echo "────────────────────────────────────────────────"
echo "⚠️ 沒有各 repo 的 wiki 全景圖($PANORAMA_FILE)⇒ 你**不知道哪些知識記在哪個 repo**。"
echo " 產生(約一分鐘,git clone 各 repo 的 wiki 目錄,不走 API):"
echo " bash scripts/wiki-panorama.sh --write"
echo ""
fi
# ── push 3/5:進度不在地端(D72)────────────────────────────────────────────
echo "────────────────────────────────────────────────"
echo "📍 進度/狀態一律去 Gitea 撈,不要在任何檔案裡找(D52 + D72)"
echo " 等 leo labels=s/stage 可開工 s/todo 進行中 s/doing"
echo " 知識庫(system-dev/wiki/)只放**事實**:踩過的坑、決策、憑證在哪。**不放進度**。"
echo " 🔴 不要在對話或任何檔案裡另養一份「等 leo 的清單」——它一定會漂。"
echo ""
# ── push 4/5principles(全文,行動前必服從)────────────────────────────────
if [ -f "$PRINCIPLES_FILE" ] && grep -q '^- ' "$PRINCIPLES_FILE" 2>/dev/null; then
echo "════════════════════════════════════════════════"
echo "📐 設計原則(行動前必服從,來自 principles.md"
echo "════════════════════════════════════════════════"
grep '^- ' "$PRINCIPLES_FILE"
P_COUNT=$(grep -c '^- ' "$PRINCIPLES_FILE")
if [ "$P_COUNT" -gt 15 ]; then
echo ""
echo "(⚠️ principles 已 ${P_COUNT} 條 > 15:請考慮合併相近原則、或把較細的下放成 card)"
fi
echo ""
fi
# ── push 5/5mistakes(只推最近 N 條標題)──────────────────────────────────
if [ -f "$MISTAKES_FILE" ] && grep -q 'MISTAKE' "$MISTAKES_FILE" 2>/dev/null; then
M_TOTAL=$(grep -cE 'MISTAKE' "$MISTAKES_FILE")
echo "────────────────────────────────────────────────"
echo "⚠️ 已知踩坑:最近 ${MISTAKES_TAIL} 條(全部 ${M_TOTAL} 條在 $MISTAKES_FILE,撞到就 grep 全文)"
grep -E 'MISTAKE' "$MISTAKES_FILE" | tail -n "$MISTAKES_TAIL" | sed 's/^/ /'
echo ""
fi
# ── push 6:還債線今天撈過了沒(inkstone/ISEP#83)──────────────────────────
# 🔴 **這裡不打網路、不撈票**——只看狀態檔的日期,講一句話。
# 要真的撈是人(或這個 session)自己跑那一行;SessionStart 只負責讓它被想起來。
# 「每天都在追新的,所以要用 Routine 去消化舊的」(leo 2026-08-27)——
# 沒有這一句,沒掛 milestone 的那 170 多張永遠不會被任何人拿起來。
DEBT_STATE="${ISEP_DEBT_STATE_DIR:-$HOME/.claude/isep-debt}/claimed.json"
DEBT_LINE=$(python3 - "$DEBT_STATE" <<'PY' 2>/dev/null || true
import json, sys, datetime
try:
d = json.load(open(sys.argv[1]))
except Exception:
d = {}
last = (d.get("last_run") or "")[:10]
today = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
n = len(d.get("claims") or {})
if last == today:
print(f"✅ 今天撈過了(手上領著 {n} 張)")
else:
print("🔴 今天還沒撈過還債清單" + (f"(手上還領著 {n} 張沒關)" if n else ""))
PY
)
echo "────────────────────────────────────────────────"
echo "🧾 還債線(inkstone/ISEP#83):${DEBT_LINE:-🔴 今天還沒撈過還債清單}"
echo " 撈一次 python3 ${CLAUDE_PLUGIN_ROOT:-.}/scripts/debt-worklist list"
echo " 它排的是「沒掛 milestone 的舊票」——掛著的歸緊急線(milestone)管,兩條線不互搶。"
echo ""
echo "────────────────────────────────────────────────"
echo "以上是 index,不是內容——它只讓你知道「有這件事、去哪裡找」。"
echo "要完整脈絡(decisions / mistakes / SDD)→ 執行 /wiki-recall"
echo "════════════════════════════════════════════════"
exit 0