feat(daemon-beta t2): template 代裝——embed v1.18.0 快照(36檔)/冪等不覆寫/daemon 啟動自動鋪/template-install 子命令;scan 加 SkipDirNames 防 system-dev 與 CLAUDE.md 被當知識掃

- 測試 3 支新增全綠(首鋪/冪等護用戶檔/掃描跳 template 產物——第三支先紅抓到 CLAUDE.md 洩入再修綠)
- CLI 實跑:新鋪 36 檔→重跑 0 新檔(冪等證據)
This commit is contained in:
2026-07-24 12:18:37 +08:00
parent b355165180
commit 73a5a67b72
41 changed files with 2917 additions and 1 deletions
+82
View File
@@ -0,0 +1,82 @@
// template_install_test.go — daemon-beta task 2(代裝冪等/wiki 產物區不被掃)。
package main
import (
"os"
"path/filepath"
"testing"
)
// 空資料夾一鍵鋪好:關鍵檔齊、版本讀得到。
func TestInstallTemplateFresh(t *testing.T) {
root := t.TempDir()
res, err := InstallTemplate(root)
if err != nil {
t.Fatal(err)
}
if len(res.Installed) == 0 || len(res.Skipped) != 0 {
t.Fatalf("首鋪帳目異常:installed=%d skipped=%d", len(res.Installed), len(res.Skipped))
}
if res.Version == "unknown" || res.Version == "" {
t.Fatalf("版本讀不到:%q", res.Version)
}
for _, must := range []string{
"system-dev/wiki/status.md",
"system-dev/wiki/mistakes.md",
".claude/commands/wiki-capture.md", // claude 萃取路(task 3)依賴它
} {
if _, err := os.Stat(filepath.Join(root, must)); err != nil {
t.Fatalf("缺關鍵檔 %s%v", must, err)
}
}
if !TemplateInstalled(root) {
t.Fatal("TemplateInstalled 應為 true")
}
}
// 冪等:重跑不覆寫;用戶改過的檔保持原樣。
func TestInstallTemplateIdempotent(t *testing.T) {
root := t.TempDir()
if _, err := InstallTemplate(root); err != nil {
t.Fatal(err)
}
marker := filepath.Join(root, "system-dev", "wiki", "status.md")
if err := os.WriteFile(marker, []byte("用戶自己的進度,不准動"), 0o644); err != nil {
t.Fatal(err)
}
res, err := InstallTemplate(root)
if err != nil {
t.Fatal(err)
}
if len(res.Installed) != 0 {
t.Fatalf("重跑不應再鋪檔:installed=%v", res.Installed)
}
data, _ := os.ReadFile(marker)
if string(data) != "用戶自己的進度,不准動" {
t.Fatal("用戶檔被覆寫=冪等鐵律破功")
}
}
// 代裝後的 system-dev/ 不得被 direct 掃描當成原稿。
func TestScanSkipsTemplateArtifacts(t *testing.T) {
root := t.TempDir()
if _, err := InstallTemplate(root); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "我的筆記.md"), []byte("# hi"), 0o644); err != nil {
t.Fatal(err)
}
cfg := &DirectConfig{
WatchFolders: []string{root},
Manifest: filepath.Join(t.TempDir(), "m.json"),
CypherURL: "https://x.example", Namespace: "demo",
MaxRemoved: DefaultMaxRemovedRatio,
}
results, exit, _ := RunDirectOnce(cfg, true)
if exit != 0 {
t.Fatalf("dry-run 失敗:%+v", results)
}
if len(results) != 1 || results[0].Path != "我的筆記.md" {
t.Fatalf("應只掃到用戶檔(template 產物須跳過),got %+v", results)
}
}