fix(t89 🔴🔴): 純中文資料夾名不再靜默併進 kb 庫——slug 空改生路徑穩定雜湊鍵

leo 07-28 實測:「官方總圖」被顯示成「知識庫」=slug 後為空退到 kb;
兩個純中文資料夾會塌縮同庫(靜默混庫,B5 分庫隔離形同虛設)。
修:libraryFor 第三優先從退 c.Library/kb 改為 lib_+sha256(絕對路徑)前6hex——
路徑穩定故鍵穩定、不同路徑必不同鍵;後端庫名鍵限 A-Za-z0-9_- 相容。
測試(t52_library_test.go):純中文→lib_雜湊非kb/兩中文夾不同鍵/同夾重跑鍵穩定/
ASCII 行為不變/明列對映仍最優先。collector go test 全綠(總管親跑)。
(實作=子 CC headless;審查+commit=總管)
This commit is contained in:
2026-07-28 12:03:20 +08:00
parent 59cf9825d1
commit fb63d9f0c4
2 changed files with 87 additions and 11 deletions
+9 -6
View File
@@ -61,7 +61,7 @@ type DirectConfig struct {
}
// librarySlug 把資料夾名轉成合法庫名(A-Za-z0-9_-;中文等非 ASCII 轉為底線分段)。
// 空結果(如純中文名)回 "",由 libraryFor 退回後備值——不硬造出無意義的庫名
// 空結果(如純中文名)回 "",由 libraryFor 改生路徑穩定雜湊鍵(t89
func librarySlug(folder string) string {
base := filepath.Base(strings.TrimRight(folder, string(filepath.Separator)))
var b strings.Builder
@@ -85,7 +85,12 @@ func librarySlug(folder string) string {
}
// libraryFor 決定某個看守資料夾的資料該蓋哪個庫章(t52)。
// 優先序:config.Libraries 明列 > 資料夾名 slug > config.Library(後備,預設 kb)。
// 優先序:config.Libraries 明列 > 資料夾名 slug > 路徑穩定雜湊鍵(t89)。
//
// t89leo 2026-07-28 實測):純中文資料夾名(如「官方總圖」)slug 後為空字串,
// 若退到 c.Library/"kb",兩個不同純中文資料夾會塌縮進同一個庫(靜默混庫)。
// 後端庫名鍵限 A-Za-z0-9_-Arcrun KBDB 約束),故改生 lib_+sha256(絕對路徑) 前 6 hex——
// 路徑穩定所以鍵穩定;兩個不同路徑必然不同鍵。
func (c *DirectConfig) libraryFor(absRoot string) string {
if c.Libraries != nil {
if lib, ok := c.Libraries[absRoot]; ok && strings.TrimSpace(lib) != "" {
@@ -95,10 +100,8 @@ func (c *DirectConfig) libraryFor(absRoot string) string {
if slug := librarySlug(absRoot); slug != "" {
return slug
}
if c.Library != "" {
return c.Library
}
return "kb"
sum := sha256.Sum256([]byte(absRoot))
return "lib_" + hex.EncodeToString(sum[:3])
}
// expandHome 把開頭的 `~/`(或單獨的 `~`)展開成使用者家目錄的絕對路徑。
+78 -5
View File
@@ -1,14 +1,25 @@
package main
import "testing"
import (
"crypto/sha256"
"encoding/hex"
"testing"
)
// stableHashLib 重現 libraryFor 對純中文資料夾的穩定雜湊鍵(t89 驗收用)。
func stableHashLib(absPath string) string {
sum := sha256.Sum256([]byte(absPath))
return "lib_" + hex.EncodeToString(sum[:3])
}
func TestLibraryPerFolder(t *testing.T) {
cfg := &DirectConfig{Library: "kb"}
cases := map[string]string{
"/Users/x/Desktop/finance": "finance",
"/Users/x/Desktop/HR Docs": "hr_docs",
"/Users/x/Desktop/官方總圖": "kb",
"/Users/x/Desktop/test 01": "test_01",
"/Users/x/Desktop/finance": "finance",
"/Users/x/Desktop/HR Docs": "hr_docs",
// t89:純中文資料夾名不再退 kb,改生穩定唯一鍵
"/Users/x/Desktop/官方總圖": stableHashLib("/Users/x/Desktop/官方總圖"),
"/Users/x/Desktop/test 01": "test_01",
}
for in, want := range cases {
if got := cfg.libraryFor(in); got != want {
@@ -20,3 +31,65 @@ func TestLibraryPerFolder(t *testing.T) {
t.Errorf("明列對映應優先,got %q", got)
}
}
// ── t89 五條驗收測試 ────────────────────────────────────────────────────────
// ①純中文資料夾 → lib_+6hex,非 "kb"
func TestT89ChineseFolderNotKb(t *testing.T) {
cfg := &DirectConfig{Library: "kb"}
got := cfg.libraryFor("/Users/x/Desktop/官方總圖")
if got == "kb" {
t.Errorf("純中文資料夾不應退到 'kb'got %q", got)
}
if len(got) != len("lib_")+6 || got[:4] != "lib_" {
t.Errorf("預期格式 lib_<6hex>got %q", got)
}
}
// ②兩個不同純中文資料夾 → 不同鍵(不混庫)
func TestT89TwoChineseFoldersDifferentKeys(t *testing.T) {
cfg := &DirectConfig{Library: "kb"}
k1 := cfg.libraryFor("/Users/x/Desktop/官方總圖")
k2 := cfg.libraryFor("/Users/x/Desktop/人事資料")
if k1 == k2 {
t.Errorf("不同純中文資料夾必須對映不同鍵,got k1=%q k2=%q", k1, k2)
}
}
// ③同一路徑重跑 → 鍵穩定(冪等)
func TestT89SameFolderStableKey(t *testing.T) {
cfg := &DirectConfig{Library: "kb"}
k1 := cfg.libraryFor("/Users/x/Desktop/官方總圖")
k2 := cfg.libraryFor("/Users/x/Desktop/官方總圖")
if k1 != k2 {
t.Errorf("同路徑重跑必須回傳同一鍵,got %q vs %q", k1, k2)
}
}
// ④ASCII 資料夾行為不變(test 01 → test_01
func TestT89ASCIIFolderUnchanged(t *testing.T) {
cfg := &DirectConfig{Library: "kb"}
cases := map[string]string{
"/Users/x/Desktop/finance": "finance",
"/Users/x/Desktop/test 01": "test_01",
"/Users/x/Desktop/HR Docs": "hr_docs",
}
for in, want := range cases {
if got := cfg.libraryFor(in); got != want {
t.Errorf("libraryFor(%q) = %q, want %q", in, got, want)
}
}
}
// ⑤明列 Libraries 對映仍最優先(即使是純中文資料夾)
func TestT89ExplicitMappingWins(t *testing.T) {
cfg := &DirectConfig{
Library: "kb",
Libraries: map[string]string{
"/Users/x/Desktop/官方總圖": "official",
},
}
if got := cfg.libraryFor("/Users/x/Desktop/官方總圖"); got != "official" {
t.Errorf("明列對映應優先,got %q", got)
}
}