2 Commits

Author SHA1 Message Date
Leo 2f33324d3f fix(collector): #60 監看的是筆記庫底下的子資料夾時,保護整個失效
真正的根因不是「vault 判斷漏了子庫」,是**判斷的方向搞反了**:
前兩輪問的都是「監看根**這一層**是不是 vault」,但 daemon 的產物一律落在
監看根底下——這兩件事只有在「監看根 == 庫根」時才等價,而那正好是前兩輪
唯一測過、也唯一不會出事的擺法。

使用者只要把庫底下的某一層加進監看(`KB/docs`、`KB/pages`、Obsidian 庫裡的
某個專案夾——很自然的用法),DetectVaultType 就回 VaultNone,整套保護退回
一般資料夾模式,卡片落在 `<監看根>/system-dev/wiki/cards/`:那個路徑就在
使用者的 graph 裡面,而且看得見,Logseq/Obsidian 每一張卡都收編成一頁。
前綴(第二輪)只擋得住撞名,擋不住「多出一堆機器頁」。

改法:把「這一層是不是庫」與「我寫的東西會不會落進誰的庫」拆成兩個判準。

- vault.go:新增 DetectVaultContext(往上找到最近的庫根)與 VaultDirUnder
  (往下擋:寫入目標會不會踩進子庫)。DetectVaultType 一字未改,繼續與
  install.sh 對齊——往上找用較嚴的判準(logseq/ 要有 config.edn 或
  journals//pages/ 佐證),因為那是替使用者猜、而且一次猜好幾層。
  停在家目錄與檔案系統根,避免 `~/logseq` 這種常見資料夾把整個家目錄判成庫。
- extract.go:cardsRelDirFor 改用 DetectVaultContext。
- safewrite.go:落卡前過 ensureWritable 機械閘——目標踩進子庫就中止,
  不靜靜寫進去。今天不會觸發,它防的是以後新增的寫檔點。
- tidy.go:收拾判準從「有沒有帶標記」擴充成「位置對不對 + 有沒有帶標記」,
  舊版留在看得見位置的卡會被搬進隱藏目錄;MigrateCardNames 每輪自動做,
  使用者不必下任何指令。報告多一個 VaultRoot,說清楚是誰的庫。

leo 派工單上的線索(庫在監看根**底下**)實測不成立:產物一律錨在監看根,
不會落進子庫。但那個「本來就沒破」原本沒有任何機制保證,所以照樣把兩種
格式的子庫情境永久寫進測試,加上 ensureWritable 當第二道保險。

驗證缺口(票上第 6 條):第二輪的足跡測試方向是對的,漏的是**觀測窗**——
snapshotTree 只拍監看根,而災情發生在監看根外面、庫裡面;且 fixture 只有
`root := vault` 一種擺法,測試與被測程式犯了同一個假設,所以永遠是綠的。
vault_subdir_test.go 把快照邊界改成筆記庫,並把「監看根與庫根的關係」升格
成測試維度(庫在上/庫在下/庫就是它/沒有庫 × Logseq/Obsidian)。

全程只用 t.TempDir() 與 mktemp -d;沒碰任何真實筆記庫、沒重啟任何 daemon。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 18:23:49 +08:00
Leo f10d10747c fix(collector): 萃取產物加 vault 辨識+落卡不再無條件覆蓋(arcrun-rag#60)
事故:daemon 完全沒有 vault 辨識,把萃取卡片寫進使用者的 Logseq vault
(system-dev/wiki/cards/ 對一般資料夾沒事,對 vault=憑空多出頁面,
2026-08-10 leo 實撞:25 個簡體字卡片污染 ~/Documents/KB)。

修法(照票上指示搬現成的,不重新設計):
- collector/vault.go:DetectVaultType/IsVault,判準逐條抄自
  system-dev-template/scripts/install.sh:209-221(先查 logseq/、再查 .obsidian/)。
  已用同一批 fixture 資料夾跑過 install.sh 與這支 Go 版,五種情境(logseq/
  obsidian/一般資料夾/兩者皆有/空資料夾)IS_VAULT 判斷逐一比對一致。
- extract.go:cardsRelDirFor() 依 IsVault 決定卡片相對路徑——非 vault 不變
  (system-dev/wiki/cards/),vault 改落 .arcrun-rag/wiki/cards/(點開頭隱藏
  目錄,Logseq/Obsidian 預設不掃描,跟 daemon 自己 scan.go 的隱藏目錄跳過規則
  一致)。extract_workersai.go/extract_gemma.go/direct.go 的落卡與下架清除
  都改用這個函式,三處對同一個 absRoot 保證同一個答案。
- safewrite.go:safeWriteCard() 取代兩處無條件 os.WriteFile——目標已存在且
  內容不同就先備份成 <dest>.bak-<unixnano> 才覆寫;內容相同則不動(不產生
  垃圾備份);備份失敗就整個中止,不無聲蓋掉使用者機器上已有的東西。

驗證(見 PR/commit 說明附的實測輸出):
- 用 fake Logseq vault fixture 重現舊行為(卡片確實落在 system-dev/wiki/cards/),
  再用同一份 fixture 驗新行為(卡片改落 .arcrun-rag/wiki/cards/,vault 根目錄
  非隱藏 .md 數量不變、journals/ 原稿位元不動)。
- 故意放同名既有卡片,跑完既有內容被備份、新內容確實寫入,未無聲遺失。
- 非 vault 既有測試(extract_gemma_test.go 原有三支)全數不動照過,確認
  一般資料夾行為零改變。

範圍外(留給下一輪):ExtractWithClaude(extract.go 的 claude 路)與其
templatefs/.claude/commands/rag-extract-file.md 技能檔仍硬寫 system-dev/wiki/cards/,
但這條路目前在 direct.go 的 RunDirectOnce 是不支援狀態(cfg.Extractor 只認
workers-ai/gemma),非本次事故的作用路徑,故未動。

不影響:leo 機器上的 daemon(未重啟、未重新打包);此修復要生效還要
①總管審過併 main ②重新打包桌面版裝上他機器 ③總管用新二進位實測,
三件缺一不可(見 issue #60 leo 的重啟條件)。
2026-08-11 12:12:51 +08:00