2f33324d3f
真正的根因不是「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>
200 lines
8.8 KiB
Bash
200 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
||
# verify-60.sh — arcrun-rag#60 的驗收,一次跑完並把實測輸出印出來。
|
||
#
|
||
# 第三輪(2026-08-12)加了第 ⑤ 段:**監看根是筆記庫底下的子資料夾**。
|
||
# 前兩輪的 fixture 一律是「監看根 == 庫根」,而那正好是唯一不會出事的擺法。
|
||
#
|
||
# 用法(在 repo 任何位置都可以):
|
||
# bash collector/verify-60.sh 2>&1 | tee /tmp/verify-60.log
|
||
#
|
||
# 這支腳本不碰網路、不碰任何真實資料夾——fixture 全建在自己的暫存目錄裡,
|
||
# 跑完就刪。它印出的東西就是要貼進 issue #60 的證據。
|
||
#
|
||
# ⚠️ collector/ 自己是一個獨立的 Go module(module arcrun-rag/collector),
|
||
# 所有 go 指令都必須在 collector/ 裡面跑,不是在 repo 根目錄。
|
||
set -uo pipefail
|
||
|
||
cd "$(dirname "$0")" || exit 1 # ← collector/(module 根)
|
||
FAIL=0
|
||
|
||
hr() { printf '\n══════════════════════════════════════════════════════════════\n%s\n══════════════════════════════════════════════════════════════\n' "$1"; }
|
||
|
||
hr "① 格式與編譯"
|
||
if [ -n "$(gofmt -l .)" ]; then
|
||
echo "⚠️ 下列檔案未照 gofmt 格式化,跑一次 gofmt -w ."; gofmt -l .
|
||
else
|
||
echo "gofmt:乾淨"
|
||
fi
|
||
go vet ./... || FAIL=1
|
||
go build ./... || { echo "❌ 編譯失敗"; exit 1; }
|
||
echo "編譯:通過"
|
||
|
||
hr "② 既有 Go 測試全綠(驗收條件⑤)"
|
||
go test ./... || FAIL=1
|
||
|
||
hr "③ 足跡測試:跑之前/跑之後的完整檔案清單(驗收條件①②④)"
|
||
# 第三輪起觀測窗是**筆記庫**而不是監看根——見 vault_subdir_test.go 開頭。
|
||
go test . -run 'Footprint|VaultContext|VaultDirUnder|MigrateCardNames' -v || FAIL=1
|
||
|
||
hr "④ 舊產物收拾:在一個「已經被舊版 daemon 弄髒」的真 vault 上實跑(驗收條件③)"
|
||
VAULT="$(mktemp -d)"
|
||
trap 'rm -rf "$VAULT"' EXIT
|
||
|
||
# ── 使用者自己的東西 ────────────────────────────────────────────
|
||
mkdir -p "$VAULT/logseq" "$VAULT/pages" "$VAULT/journals"
|
||
echo '{:default-home {:page "contents"}}' > "$VAULT/logseq/config.edn"
|
||
echo '- 我自己的目錄頁' > "$VAULT/pages/contents.md"
|
||
echo '- 我自己寫的讀書筆記' > "$VAULT/pages/讀書筆記.md"
|
||
echo '- 我自己寫的日記' > "$VAULT/journals/2026_08_10.md"
|
||
echo '- 昨天的日記' > "$VAULT/journals/2026_08_11.md"
|
||
|
||
# ── 舊版 daemon 已經弄進去的產物(leo 實際看到的那批)─────────────
|
||
mkdir -p "$VAULT/system-dev/wiki/cards" "$VAULT/.arcrun-rag/wiki/cards" "$VAULT/scripts"
|
||
echo '# status' > "$VAULT/system-dev/wiki/status.md"
|
||
echo '# mistakes' > "$VAULT/system-dev/wiki/mistakes.md"
|
||
echo '#!/bin/sh' > "$VAULT/scripts/sdd-active-check.sh"
|
||
echo '# 2026_08_10' > "$VAULT/.arcrun-rag/wiki/cards/2026_08_10.md"
|
||
echo '# 舊卡' > "$VAULT/.arcrun-rag/wiki/cards/2026_08_10.md.bak-1723459200000000000"
|
||
echo '# 會議記錄' > "$VAULT/system-dev/wiki/cards/會議記錄.md"
|
||
|
||
# 使用者檔案的內容指紋,最後要逐一比對「一個都沒被動到」
|
||
BEFORE_HASH="$(find "$VAULT/pages" "$VAULT/journals" "$VAULT/logseq" -type f -exec shasum {} \; | sed "s|$VAULT/||" | sort)"
|
||
|
||
echo "── 收拾之前的完整檔案清單 ──"
|
||
LIST_BEFORE="$(cd "$VAULT" && find . -type f | sort)"
|
||
echo "$LIST_BEFORE"
|
||
|
||
echo
|
||
echo "── collector tidy(預設只看不動)──"
|
||
go run ./cmd/collector tidy --folder "$VAULT" || FAIL=1
|
||
|
||
echo
|
||
echo "── 確認 dry-run 真的什麼都沒動 ──"
|
||
LIST_AFTER_DRYRUN="$(cd "$VAULT" && find . -type f | sort)"
|
||
if [ "$LIST_BEFORE" = "$LIST_AFTER_DRYRUN" ]; then
|
||
echo "✅ dry-run 後檔案清單與之前逐字相同"
|
||
else
|
||
echo "❌ dry-run 竟然動了東西:"; diff <(echo "$LIST_BEFORE") <(echo "$LIST_AFTER_DRYRUN"); FAIL=1
|
||
fi
|
||
|
||
echo
|
||
echo "── collector tidy --apply(真的動手)──"
|
||
go run ./cmd/collector tidy --folder "$VAULT" --apply || FAIL=1
|
||
|
||
echo
|
||
echo "── 收拾之後的完整檔案清單 ──"
|
||
(cd "$VAULT" && find . -type f | sort)
|
||
|
||
echo
|
||
echo "── 檢查一:使用者原本的檔案,一個都沒被動到 ──"
|
||
AFTER_HASH="$(find "$VAULT/pages" "$VAULT/journals" "$VAULT/logseq" -type f -exec shasum {} \; | sed "s|$VAULT/||" | sort)"
|
||
if [ "$BEFORE_HASH" = "$AFTER_HASH" ]; then
|
||
echo "✅ 使用者的 5 個檔案內容與路徑完全未變"
|
||
else
|
||
echo "❌ 使用者的檔案被動過:"; diff <(echo "$BEFORE_HASH") <(echo "$AFTER_HASH"); FAIL=1
|
||
fi
|
||
|
||
echo
|
||
echo "── 檢查二:機器產物一律帶 arcrun- 前綴,且沒有任何一個與使用者頁面同名 ──"
|
||
STRAY=0
|
||
while IFS= read -r f; do
|
||
base="$(basename "$f")"
|
||
case "$base" in arcrun-*) ;; *) echo " ❌ 沒帶前綴:$f"; STRAY=1;; esac
|
||
done < <(cd "$VAULT" && find ./.arcrun-rag/wiki/cards ./system-dev/wiki/cards -type f 2>/dev/null)
|
||
[ "$STRAY" -eq 0 ] && echo "✅ 卡片產物區全部帶前綴" || FAIL=1
|
||
|
||
echo
|
||
echo "── 檢查三:template 殘留被搬走而不是被刪掉(東西還在) ──"
|
||
for f in system-dev/wiki/status.md system-dev/wiki/mistakes.md scripts/sdd-active-check.sh; do
|
||
if [ -f "$VAULT/.arcrun-rag/legacy-template/$f" ]; then
|
||
echo " ✅ $f → .arcrun-rag/legacy-template/$f(內容還在)"
|
||
else
|
||
echo " ❌ $f 不見了"; FAIL=1
|
||
fi
|
||
done
|
||
|
||
echo
|
||
echo "── 檢查四:冪等(再跑一次不該有任何動作) ──"
|
||
go run ./cmd/collector tidy --folder "$VAULT" --apply || FAIL=1
|
||
|
||
hr "⑤ 第三輪:監看根是**筆記庫底下的子資料夾**(Logseq 與 Obsidian 各一組)"
|
||
# 這一段用真的 CLI 在真的暫存筆記庫上跑,證明:
|
||
# · 使用者在庫裡的檔案(journals/pages)一個都沒被動到
|
||
# · 舊版留在看得見位置的機器產物被搬進隱藏目錄,庫裡不再有機器頁面
|
||
# · 故意擺的同名檔(跟機器產物撞名的那個)原封不動
|
||
subdir_case() {
|
||
local kind="$1"
|
||
local BASE; BASE="$(mktemp -d)"
|
||
local V="$BASE/我的筆記庫"
|
||
local W="$V/docs"
|
||
mkdir -p "$W" "$V/journals" "$V/pages"
|
||
|
||
if [ "$kind" = "logseq" ]; then
|
||
mkdir -p "$V/logseq"; echo '{:default-home {:page "contents"}}' > "$V/logseq/config.edn"
|
||
else
|
||
mkdir -p "$V/.obsidian"; echo '{}' > "$V/.obsidian/app.json"
|
||
fi
|
||
|
||
# 使用者自己的東西,含一個**故意跟機器產物撞名**的檔
|
||
echo '- 我自己寫的日記' > "$V/journals/2026_08_12.md"
|
||
echo '- 我自己的讀書筆記' > "$V/pages/讀書筆記.md"
|
||
echo '- 這個名字是我先取的' > "$V/pages/arcrun-會議記錄.md"
|
||
# 舊版 daemon 留在**看得見**位置的機器產物(就在使用者的庫裡)
|
||
mkdir -p "$W/system-dev/wiki/cards"
|
||
echo '# 會議記錄' > "$W/system-dev/wiki/cards/arcrun-會議記錄.md"
|
||
echo '# 2026_08_12' > "$W/system-dev/wiki/cards/2026_08_12.md"
|
||
|
||
echo "── [$kind] 收拾之前(整個筆記庫)──"
|
||
(cd "$V" && find . -type f | sort)
|
||
local MINE_BEFORE; MINE_BEFORE="$(find "$V/journals" "$V/pages" -type f -exec shasum {} \; | sed "s|$V/||" | sort)"
|
||
|
||
echo
|
||
echo "── [$kind] collector tidy --folder <庫>/docs(先預覽)──"
|
||
go run ./cmd/collector tidy --folder "$W" || FAIL=1
|
||
echo
|
||
echo "── [$kind] collector tidy --folder <庫>/docs --apply ──"
|
||
go run ./cmd/collector tidy --folder "$W" --apply || FAIL=1
|
||
|
||
echo
|
||
echo "── [$kind] 收拾之後(整個筆記庫)──"
|
||
(cd "$V" && find . -type f | sort)
|
||
|
||
echo
|
||
local MINE_AFTER; MINE_AFTER="$(find "$V/journals" "$V/pages" -type f -exec shasum {} \; | sed "s|$V/||" | sort)"
|
||
if [ "$MINE_BEFORE" = "$MINE_AFTER" ]; then
|
||
echo "✅ [$kind] 使用者在庫裡的檔案(含同名的 arcrun-會議記錄.md)內容與路徑完全未變"
|
||
else
|
||
echo "❌ [$kind] 使用者的檔案被動過:"; diff <(echo "$MINE_BEFORE") <(echo "$MINE_AFTER"); FAIL=1
|
||
fi
|
||
|
||
# 庫裡不准再有「看得見的」機器產物:路徑中沒有任何 . 開頭的段、卻帶 arcrun- 前綴的檔
|
||
local VISIBLE=0
|
||
while IFS= read -r f; do
|
||
case "$f" in */.*) continue;; esac # 隱藏路徑,筆記軟體不掃
|
||
case "$(basename "$f")" in
|
||
arcrun-會議記錄.md) [ "$f" = "./pages/arcrun-會議記錄.md" ] && continue;; # 這是使用者自己的
|
||
esac
|
||
case "$(basename "$f")" in
|
||
arcrun-*) echo " ❌ [$kind] 庫裡還有看得見的機器產物:$f"; VISIBLE=1;;
|
||
esac
|
||
done < <(cd "$V" && find . -type f | sort)
|
||
if [ "$VISIBLE" -eq 0 ]; then
|
||
echo "✅ [$kind] 庫裡沒有任何看得見的機器產物(筆記軟體不會多出頁面)"
|
||
else
|
||
FAIL=1
|
||
fi
|
||
rm -rf "$BASE"
|
||
}
|
||
|
||
subdir_case logseq
|
||
echo
|
||
subdir_case obsidian
|
||
|
||
hr "結果"
|
||
if [ "$FAIL" -eq 0 ]; then
|
||
echo "✅ 全部通過"
|
||
else
|
||
echo "❌ 有項目未通過,往上找 ❌"
|
||
fi
|
||
exit "$FAIL"
|