577d883e94
leo 2026-08-16:「你不需要判斷有沒有 git,我的 KB 筆記庫也有 git, 是否用 github/gitea 追蹤完全沒意義。」 第一層(80aed82)已把「哪些目錄算雜訊」改成不看版控;本輪處理 「這個資料夾整體該用哪種收法」(all/curated-wiki/docs-only)那一層。 定時炸彈(實測,非推測):`~/Documents/KB` 是 leo 的真知識庫,今天沒有 `.git` ⇒ all ⇒ 全收;而它**真的有 system-dev/wiki**(他在筆記庫裡也裝過 template)⇒ 只要有人跑一次 `git init`,就翻成 curated-wiki,靜默塌成十幾張。 新判準(collector/foldershape.go):三條同時成立才算軟體專案—— ① 找得到專案檔 ② 原始碼 ≥ 20 ③ 原始碼 × 4 ≥ 文件數。三條都不看版控。 第③條擋的是「筆記庫裡放了一整份下載回來的範例專案」。 實測分得很開:KB=0/15/5915(筆記庫)|InkStoneCo=213/3355/6115(專案) |pms=21/37/22(專案)。InkStoneCo 與 pms 的模式都不變。 判準換成內容,證據也換成內容:`IngestPlan.RepoRoot`(.git 在哪)改成 `IngestPlan.Shape`(實測形狀),Reason 句子改成「我看到 go.mod 等專案檔, 還有 3,355 個原始碼檔」——使用者看得出憑什麼那樣判。 - 既有那條迴歸測試就地擴充到涵蓋模式選擇(不另開平行的一條), 判準升成「同一棵樹有無版控,收到的檔案集合必須一模一樣」 - 加上三種收法各自仍正確、筆記庫有像產物的資料夾與零星腳本不准翻成專案、 理由裡不准再出現版控字眼、以及「ingestplan.go 不准再引用 DetectRepoRoot」 的接線測試(反面同時釘住 #105 在 tidy.go 的正當用途不准被順手拆掉) - collector/probe_real_manual_test.go:唯讀探針,留著給下一個人重量存量影響 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
148 lines
4.9 KiB
Go
148 lines
4.9 KiB
Go
package collector
|
||
|
||
// probe_real_manual_test.go — 手動探針(**唯讀**):拿真實資料夾對照
|
||
// 「舊判準(看 `.git`)」與「新判準(看內容)」,arcrun-rag#104 第二層。
|
||
//
|
||
// 🔴 為什麼留在 repo 裡而不是用完就丟:這一票的驗收條件是「**leo 真實的資料夾
|
||
// 換判準之後會收到什麼**」——那不是 fixture 答得出來的問題。下一個人要重新確認
|
||
// 存量影響(誰會多收、誰會少收),跑這兩支就有答案,不必再從頭想一次怎麼量。
|
||
// 預設 skip,不進 CI;`Scan` 全程不寫檔(scan.go 裡沒有任何 os.Write/Remove/Rename)。
|
||
//
|
||
// PROBE_REAL=1 PROBE_SCAN=1 PROBE_ROOTS="/路徑A,/路徑B" go test -run TestProbeRealFolders -v ./
|
||
// PROBE_REAL=1 PROBE_MINE="$HOME/Documents/KB" go test -run TestProbeMine -v ./
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// oldMode 重現 2026-08-16 之前的模式選擇:用 DetectRepoRoot(找 .git)判是不是專案。
|
||
func oldMode(root string) IngestMode {
|
||
if DetectRepoRoot(root) == "" {
|
||
return IngestAll
|
||
}
|
||
if findCuratedWiki(root) != "" {
|
||
return IngestCuratedWiki
|
||
}
|
||
return IngestDocsOnly
|
||
}
|
||
|
||
// mirrorTree 把 src 的**結構**(每個目錄、每個檔名)複製到 dst,內容一律換成一個字。
|
||
// 用途:拿真實資料夾的形狀當地雷測試的素材,而**完全不碰真實資料夾**。
|
||
func mirrorTree(t *testing.T, src, dst string) int {
|
||
t.Helper()
|
||
n := 0
|
||
err := filepath.WalkDir(src, func(p string, d os.DirEntry, err error) error {
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
rel, rerr := filepath.Rel(src, p)
|
||
if rerr != nil {
|
||
return nil
|
||
}
|
||
target := filepath.Join(dst, rel)
|
||
if d.IsDir() {
|
||
return os.MkdirAll(target, 0o755)
|
||
}
|
||
if !d.Type().IsRegular() {
|
||
return nil
|
||
}
|
||
n++
|
||
return os.WriteFile(target, []byte("x\n"), 0o644)
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return n
|
||
}
|
||
|
||
// 🔴 地雷測試(真實形狀版):拿 `~/Documents/KB` 的**結構**造兩份拋棄式副本,
|
||
// 一份有 `.git`、一份沒有,比對兩邊收到的檔案集合。**全程不碰 KB 本身。**
|
||
//
|
||
// PROBE_REAL=1 PROBE_MINE="$HOME/Documents/KB" go test -run TestProbeMine -v ./ -timeout 30m
|
||
func TestProbeMine(t *testing.T) {
|
||
if os.Getenv("PROBE_REAL") != "1" || os.Getenv("PROBE_MINE") == "" {
|
||
t.Skip("手動探針")
|
||
}
|
||
src := os.Getenv("PROBE_MINE")
|
||
base := t.TempDir()
|
||
|
||
noGit := filepath.Join(base, "nogit")
|
||
withGit := filepath.Join(base, "withgit")
|
||
n := mirrorTree(t, src, noGit)
|
||
mirrorTree(t, src, withGit)
|
||
if err := os.MkdirAll(filepath.Join(withGit, ".git"), 0o755); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Logf("拿 %s 的結構造了兩份拋棄式副本(各 %d 個檔),一份跑過 git init", src, n)
|
||
|
||
var first []string
|
||
for _, tc := range []struct{ name, root string }{
|
||
{"沒有版控(今天的 KB)", noGit}, {"有版控(有人跑了一次 git init)", withGit},
|
||
} {
|
||
plan := PlanIngest(tc.root)
|
||
m := &Manifest{Entries: map[string]*ManifestEntry{}}
|
||
payload, err := Scan(tc.root, m, ScanOptions{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got := eventPaths(payload)
|
||
t.Logf("%s:舊模式=%s|新模式=%s|送出 %d 個檔",
|
||
tc.name, oldMode(tc.root), plan.Mode, len(got))
|
||
if first == nil {
|
||
first = got
|
||
continue
|
||
}
|
||
if strings.Join(got, "\n") != strings.Join(first, "\n") {
|
||
t.Fatalf("🔴 跑一次 git init 就改變了收到的東西:%d → %d 個檔", len(first), len(got))
|
||
}
|
||
t.Logf("✅ 兩邊收到的檔案集合完全相同(%d 個)", len(got))
|
||
}
|
||
}
|
||
|
||
func TestProbeRealFolders(t *testing.T) {
|
||
if os.Getenv("PROBE_REAL") != "1" {
|
||
t.Skip("手動探針")
|
||
}
|
||
for _, root := range strings.Split(os.Getenv("PROBE_ROOTS"), ",") {
|
||
root = strings.TrimSpace(root)
|
||
if root == "" {
|
||
continue
|
||
}
|
||
shape := InspectFolder(root)
|
||
plan := PlanIngest(root)
|
||
t.Logf("═══ %s ═══", root)
|
||
t.Logf(" 版控訊號(舊判準用的):DetectRepoRoot=%q", DetectRepoRoot(root))
|
||
t.Logf(" 實測形狀:專案檔 %d 個 %v/原始碼 %d/文件 %d(truncated=%v)",
|
||
shape.ManifestCount, shape.ManifestRels, shape.CodeFiles, shape.DocFiles, shape.Truncated)
|
||
t.Logf(" 舊模式=%s → 新模式=%s", oldMode(root), plan.Mode)
|
||
t.Logf(" 理由:%s", plan.Reason)
|
||
|
||
if os.Getenv("PROBE_SCAN") == "1" {
|
||
m := &Manifest{Entries: map[string]*ManifestEntry{}}
|
||
payload, err := Scan(root, m, ScanOptions{})
|
||
if err != nil {
|
||
t.Logf(" 掃描失敗:%v", err)
|
||
continue
|
||
}
|
||
t.Logf(" ⇒ 這一輪會送出 %d 個檔(逐檔擋掉 %d,整棵跳過 %d 個資料夾)",
|
||
len(payload.Events), payload.ExcludedByPlan, payload.ExcludedDirCount)
|
||
for i, e := range payload.Events {
|
||
if i >= 12 {
|
||
t.Logf(" …等 %d 個", len(payload.Events))
|
||
break
|
||
}
|
||
t.Logf(" ✓ %s", e.Path)
|
||
}
|
||
for i, d := range payload.ExcludedDirs {
|
||
if i >= 8 {
|
||
break
|
||
}
|
||
t.Logf(" ✗ %s — %s", d.Path, d.Reason)
|
||
}
|
||
}
|
||
}
|
||
}
|