不再把開發用 template 塞進使用者資料夾;上游錯誤不再被退避訊息吞掉

## leo 三條裁決,逐條落實
① 「**別人的錯誤一律要顯示給用戶看,不然就會變成我的錯誤,導致客服**」
   → manifest entry 新增 `LastError`(存**原文**),退避訊息改成
     「上次失敗(第 N 次),58m 後重試|**原因:**<上游原文>」。
     成功後清空,不留舊錯誤嚇人。
     測試 `TestUpstreamErrorSurvivesBackoff` 從上游錯誤字串出發,
     驗它活到給使用者看的那句話;反向驗證拿掉即紅。

② 「**拿來開發一般人用不到的根本別安裝**」
   → daemon 不再代裝 system-dev template(`CLAUDE.md`/`scripts/`/`system-dev/`,37 檔)。
     兩層傷害:把人家資料夾弄亂 + 那些檔被當知識吃進去
     ⇒ 知識庫長出 `kb`/`t195-watch` 這種不是使用者內容的庫(leo 實撞)。
     `template-install` 子命令保留,開發者情境不受影響。

③ 「**它也不能只看隱藏檔內,因為 template 在我所有的 repo 裡不是隱藏的**」
   → 排除規則用**路徑身分**(`TemplateOwns()`,來源是內嵌 templatefs 的實際路徑
     + `system-dev/`・`scripts/` 目錄前綴),**不是**「有沒有以點開頭」。
     測試刻意把 template 檔放成**不隱藏**(就像 leo 的 repo),驗它仍被排除;反向驗證即紅。
     這些檔也不計進「有 N 個檔案沒有被整理」——那欄是給使用者看他自己的檔案的。

## 順帶修 leo 截圖上的重複
「有 2 個檔案沒有被整理」底下 `scripts/sdd-active-check.sh` 出現兩次
——多帳號時同一個資料夾被掃多輪、每輪都 append。已去重。

## 殘項(誠實)
App 端失敗卡還沒接上這條線 ⇒ **畫面尚未真的顯示原因**。未打包、未送達。
This commit is contained in:
2026-08-06 21:33:37 +08:00
parent 6e0b65a2bb
commit c7675a10a0
7 changed files with 217 additions and 26 deletions
+41 -19
View File
@@ -648,6 +648,18 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
// 排序=畫面每輪穩定(map 迭代順序隨機,不排的話清單會自己跳動)。
st.SkippedOtherCount = skippedOther
// 少量時點名(maxOtherNames 個以內)——leo 08-06 封測:只報「1 個」等於沒說。
// 🔴 leo 2026-08-06 截圖:「有 2 個檔案沒有被整理」底下同一個檔名出現**兩次**。
// 真兇:多帳號時同一個資料夾會被掃很多輪,每輪都把檔名 append 進來。
// ⇒ 去重(順帶讓總數與清單一致,不然使用者會覺得我們在亂數)。
seen := map[string]bool{}
uniq := skippedOtherNames[:0]
for _, n := range skippedOtherNames {
if !seen[n] {
seen[n] = true
uniq = append(uniq, n)
}
}
skippedOtherNames = uniq
sort.Strings(skippedOtherNames)
if len(skippedOtherNames) > maxOtherNames {
skippedOtherNames = skippedOtherNames[:maxOtherNames]
@@ -744,13 +756,24 @@ func retrySkipReason(m *Manifest, path string, now int64) string {
return "暫時跳過"
}
if e.FailCount >= MaxFailBeforeSkip {
return fmt.Sprintf("連續失敗 %d 次,已暫停自動重試(改檔或按「立刻同步」會再試)", e.FailCount)
msg := fmt.Sprintf("連續失敗 %d 次,已暫停自動重試(改檔或按「立刻同步」會再試)", e.FailCount)
if e.LastError != "" {
msg += "|原因:" + e.LastError
}
return msg
}
wait := e.NextRetry - now
if wait < 0 {
wait = 0
}
return fmt.Sprintf("上次失敗(第 %d 次),%s 後重試", e.FailCount, (time.Duration(wait) * time.Second).String())
msg := fmt.Sprintf("上次失敗(第 %d 次),%s 後重試", e.FailCount, (time.Duration(wait) * time.Second).String())
// 🔴 leo 2026-08-06:「別人的錯誤一律要顯示給用戶看,不然就會變成我的錯誤,導致客服」。
// 只講「幾分鐘後重試」等於把上游的錯(Cloudflare 額度用完、檔案本身沒文字)
// 藏起來 ⇒ 使用者只看得到我們在失敗。真因要一路帶到畫面上。
if e.LastError != "" {
msg += "|原因:" + e.LastError
}
return msg
}
// accountsConnected 回答「這份設定連得上知識庫嗎」——**根層有 或 任一帳號有**。
@@ -822,7 +845,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
content, rerr := os.ReadFile(full)
if rerr != nil {
res.Status, res.Error = "failed", "讀檔失敗:"+rerr.Error()
m.MarkFailed(ev.Path, now) // t195:讀不到的檔也退避(權限/被鎖/壞掉的外接碟)
m.MarkFailed(ev.Path, now, res.Error) // t195:讀不到的檔也退避(權限/被鎖/壞掉的外接碟)
results = append(results, res)
exit = 1
continue
@@ -858,7 +881,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
res.Status, res.Error = "failed", "本地萃取失敗:"+xerr.Error()
// t195:萃取階段失敗同樣要記退避。**這條路徑比上傳更早**,
// 漏記的話(連不上知識庫、金鑰壞、模型錯)照樣每輪重撞。
m.MarkFailed(ev.Path, now)
m.MarkFailed(ev.Path, now, res.Error)
results = append(results, res)
exit = 1
continue
@@ -894,7 +917,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
} else {
// t195:記下失敗並排定退避,否則下輪又把它當新檔重試
//(實撞:1387 輪 × 11 小時全在撞同一面 401 的牆,還拖住整個佇列)。
m.MarkFailed(ev.Path, now)
m.MarkFailed(ev.Path, now, res.Error)
exit = 1
}
results = append(results, res)
@@ -1027,20 +1050,19 @@ func runDirect(args []string) int {
return exit
}
// 四步定稿第 1 步:daemon 代裝 template——常駐看守前確保每根都鋪好(冪等,不覆寫既有檔)。
// dry-run/--once 測試情境不代裝(不留副作用),由 template-install 子命令顯式做。
if !*once && !*dryRun {
for _, root := range cfg.Folders() {
if TemplateInstalled(root) {
continue
}
if res, ierr := InstallTemplate(root); ierr != nil {
fmt.Fprintf(os.Stderr, "template 代裝失敗(%s):%v\n", root, ierr)
} else {
fmt.Fprintf(os.Stderr, "template v%s 已鋪進 %s(新 %d 檔)\n", res.Version, root, len(res.Installed))
}
}
}
// 🔴 2026-08-06 leo 拍板:**「拿來開發一般人用不到的根本別安裝」**
//
// 原本 daemon 會把 system-dev template`CLAUDE.md``scripts/``system-dev/`
// 共 37 檔)代裝進**使用者的文件資料夾**。那是給開發者寫 SDD/wiki 用的東西,
// RAG 的一般使用者完全用不到,而且兩層傷害:
// ① 把人家的資料夾弄亂(leo:「他原本的資料夾就不會看起來亂掉」)
// ② 那些檔案會被當成知識吃進去 ⇒ 知識庫長出 `kb`/`t195-watch` 這種
// 不是使用者內容的庫(leo 實撞,見庫目錄管理截圖)
// ⇒ **看守資料夾一律不代裝**。要裝的人自己跑 `collector template-install`
// (子命令仍在,開發者情境不受影響)。
//
// 另外還要把「已經被鋪進去的」擋在知識之外——那不是靠隱藏檔判斷,
// 因為 leo 自己的 repo 裡 template 本來就不是隱藏的。判準是**路徑身分**,見 scan.go。
if *once {
return runOne()