5a140faf1f
leo 2026-08-12:「我的 Logseq 又被覆蓋⋯⋯**不只是加上 journal,可能所有的檔案都加一個前後綴,比如「wiki」**。」 不是資料被蓋掉,是機器產出用了跟他一樣的命名空間(status.md、日期檔那些) ⇒ 他打開資料夾分不出哪些是自己的。**心理上的覆蓋跟實際覆蓋一樣糟。**
103 lines
4.4 KiB
Go
103 lines
4.4 KiB
Go
// extract_test.go — task 3 claude 萃取路(用 stub 執行檔驗呼叫契約,不需真 claude)。
|
||
package collector
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"testing"
|
||
)
|
||
|
||
// writeStubClaude 產生一支假 claude:驗證收到的參數與 cwd,並寫一張卡進 cards/。
|
||
func writeStubClaude(t *testing.T, dir string, script string) string {
|
||
t.Helper()
|
||
if runtime.GOOS == "windows" {
|
||
t.Skip("stub 腳本測試僅跑 unix")
|
||
}
|
||
p := filepath.Join(dir, "claude")
|
||
if err := os.WriteFile(p, []byte("#!/bin/sh\n"+script), 0o755); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return p
|
||
}
|
||
|
||
// 正常路:stub 收到 -p "/rag-extract-file <檔>"、cwd=監看根,寫卡後被 diff 偵測到。
|
||
func TestExtractWithClaudeHappyPath(t *testing.T) {
|
||
root := t.TempDir()
|
||
if err := os.MkdirAll(filepath.Join(root, "system-dev", "wiki", "cards"), 0o755); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
stub := writeStubClaude(t, t.TempDir(), fmt.Sprintf(`
|
||
[ "$1" = "-p" ] || { echo "第一參數應為 -p,got $1" >&2; exit 9; }
|
||
case "$2" in "/rag-extract-file 我的筆記.md") ;; *) echo "prompt 錯:$2" >&2; exit 9;; esac
|
||
[ "$(pwd)" = "%s" ] || { echo "cwd 錯:$(pwd)" >&2; exit 9; }
|
||
mkdir -p system-dev/wiki/cards
|
||
printf '# 我的筆記\n\n## 摘要\nstub 卡\n' > "system-dev/wiki/cards/我的筆記.md"
|
||
`, root))
|
||
cards, err := ExtractWithClaude(stub, root, "我的筆記.md")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// stub(模擬 /rag-extract-file skill)寫的是**沒有前綴**的檔名——這正是實況:
|
||
// 那條路的檔名由 prompt 決定,不歸 Go 管。enforceCardMarks 必須把它補正,
|
||
// 讓 claude 路與 gemma/workers-ai 路交出同一種名字(arcrun-rag#60 紅線:只准一種)。
|
||
if len(cards) != 1 || cards[0] != "system-dev/wiki/cards/arcrun-我的筆記.md" {
|
||
t.Fatalf("cards=%v,want [system-dev/wiki/cards/arcrun-我的筆記.md]", cards)
|
||
}
|
||
if _, err := os.Stat(filepath.Join(root, "system-dev", "wiki", "cards", "arcrun-我的筆記.md")); err != nil {
|
||
t.Fatalf("補正後的卡片檔不存在:%v", err)
|
||
}
|
||
if _, err := os.Stat(filepath.Join(root, "system-dev", "wiki", "cards", "我的筆記.md")); err == nil {
|
||
t.Fatal("沒帶前綴的原檔還在——補正應該是改名,不是複製一份")
|
||
}
|
||
}
|
||
|
||
// arcrun-rag#60 第二輪:vault 上,skill 仍會把卡寫進**可見的** system-dev/wiki/cards/
|
||
// (那個位置寫死在 prompt 裡,Go 改不到它)⇒ Go 這一側必須把它歸位到隱藏目錄並補標記。
|
||
// 這是「破口在 prompt 裡、只能在 Go 收口」的那一類,不補就等於 claude 路照樣污染筆記庫。
|
||
func TestExtractWithClaude_VaultRelocatesAndMarks(t *testing.T) {
|
||
root := t.TempDir()
|
||
mustMkdir(t, filepath.Join(root, "logseq")) // 這是個 Logseq vault
|
||
stub := writeStubClaude(t, t.TempDir(), `
|
||
mkdir -p system-dev/wiki/cards
|
||
printf '# 我的筆記\n\n## 摘要\nstub 卡\n' > "system-dev/wiki/cards/我的筆記.md"
|
||
`)
|
||
cards, err := ExtractWithClaude(stub, root, "我的筆記.md")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
want := ".arcrun-rag/wiki/cards/arcrun-我的筆記.md"
|
||
if len(cards) != 1 || cards[0] != want {
|
||
t.Fatalf("cards=%v,want [%s]", cards, want)
|
||
}
|
||
if _, err := os.Stat(filepath.Join(root, filepath.FromSlash(want))); err != nil {
|
||
t.Fatalf("歸位後的卡片不存在:%v", err)
|
||
}
|
||
// 可見目錄裡不准留下任何東西——vault 的頁面數不能因為 daemon 跑過而增加。
|
||
if _, err := os.Stat(filepath.Join(root, "system-dev", "wiki", "cards", "我的筆記.md")); err == nil {
|
||
t.Fatal("卡片還留在可見的 system-dev/wiki/cards/,vault 會多出一頁")
|
||
}
|
||
}
|
||
|
||
// claude 跑完但沒寫卡=誠實報錯(不假綠)。
|
||
func TestExtractWithClaudeNoCard(t *testing.T) {
|
||
root := t.TempDir()
|
||
stub := writeStubClaude(t, t.TempDir(), "exit 0\n")
|
||
if _, err := ExtractWithClaude(stub, root, "x.md"); err == nil {
|
||
t.Fatal("沒寫卡應報錯")
|
||
}
|
||
}
|
||
|
||
// 找不到執行檔=引導改用 gemma 路的錯誤訊息。
|
||
// t92:覆蓋 fallback 清單為空,確保即使本機有 claude 安裝,測試仍能驗到「找不到」路徑。
|
||
func TestExtractWithClaudeMissingBin(t *testing.T) {
|
||
orig := claudeFallbackPaths
|
||
claudeFallbackPaths = nil // 停用 fallback,讓找不到 /no/such/claude-bin 就直接報錯
|
||
defer func() { claudeFallbackPaths = orig }()
|
||
|
||
if _, err := ExtractWithClaude("/no/such/claude-bin", t.TempDir(), "x.md"); err == nil {
|
||
t.Fatal("缺執行檔應報錯")
|
||
}
|
||
}
|