Files
arcrun-collector/cardloop_test.go
T

119 lines
5.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// cardloop_test.go — 「卡片自我繁殖」迴歸網(inkstone/Arcrun#134 → comment 5025)。
//
// 🔴 2026-08-28 在 leo 的 `~/Desktop/youlinhsieh-test1` 實測到的現象:
// 一份 20 KB 的劇本原稿,`.wiki/` 只落了 4 張卡;而 `system-dev/wiki/cards/` 底下
// 卻在 20 分鐘內從 9 個檔長到 55 個,出現 `arcrun-換柱(arcrun-換柱).md`
// 這種第三代檔名。那不是萃取品質問題,是**產物被自己的整理程序拖回原稿區**:
//
// ① 萃取寫 `<node>/.wiki/換柱.md`(隱藏目錄,Scan 整棵跳過 ⇒ 不會被當原稿)
// ② MigrateCardNames 每輪自動跑,遞迴掃 `system-dev/wiki/cards/`——
// **它沒有跳過隱藏目錄**,於是把 `cards/.wiki/換柱.md`
// 「歸位」成 `cards/arcrun-換柱.md`
// ③ 那個位置 Scan 看得見 ⇒ 下一輪當成新原稿再萃一次 ⇒ 回到 ①
//
// 每一圈都燒一次 Workers AI 額度、往雲端推一批「卡片的卡片」,而且不會停。
//
// 四支測試各釘住迴圈的一個環節(tidy 自動路/tidy 手動路/收檔判準/claude 路),
// 缺一個就會復發。
package collector
import (
"os"
"path/filepath"
"testing"
)
// TestMigrateCardNamesLeavesWikiDirAlone`.wiki/` 是卡片的家,不是「位置不對的舊卡」。
// MigrateCardNames 一個檔都不准動它——動了就等於把產物送回原稿區(迴圈的第②環)。
func TestMigrateCardNamesLeavesWikiDirAlone(t *testing.T) {
root := t.TempDir()
// 卡片產物區底下的 `.wiki/`:萃取正常的落點。
wikiDir := filepath.Join(root, filepath.FromSlash(cardsRelDir), wikiRelDir)
if err := os.MkdirAll(wikiDir, 0o755); err != nil {
t.Fatal(err)
}
card := filepath.Join(wikiDir, "換柱.md")
if err := os.WriteFile(card, []byte("# 換柱\n"), 0o644); err != nil {
t.Fatal(err)
}
mig := MigrateCardNames(root)
if _, err := os.Stat(card); err != nil {
t.Fatalf("`.wiki/` 裡的卡被搬走了(MigrateCardNames 回報 Moved=%d)——"+
"它下一輪就會被當成新原稿再萃一次,迴圈就是這樣長出來的", mig.Moved)
}
dragged := filepath.Join(root, filepath.FromSlash(cardsRelDir), MarkName("換柱.md"))
if _, err := os.Stat(dragged); err == nil {
t.Fatalf("卡片被拖回原稿區:%s", dragged)
}
if mig.Moved != 0 {
t.Fatalf("Moved = %d,期望 0`.wiki/` 不該有任何東西被搬)", mig.Moved)
}
}
// TestTidyLeavesWikiDirAlone:手動 `collector tidy --apply` 走的是另一條路,同樣不准碰。
func TestTidyLeavesWikiDirAlone(t *testing.T) {
root := t.TempDir()
wikiDir := filepath.Join(root, filepath.FromSlash(cardsRelDir), wikiRelDir)
if err := os.MkdirAll(wikiDir, 0o755); err != nil {
t.Fatal(err)
}
card := filepath.Join(wikiDir, "官架子.md")
if err := os.WriteFile(card, []byte("# 官架子\n"), 0o644); err != nil {
t.Fatal(err)
}
rep, err := Tidy(root, true)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(card); err != nil {
t.Fatalf("tidy --apply 搬走了 `.wiki/` 裡的卡;報告列了 %d 項", len(rep.Items))
}
for _, it := range rep.Items {
if it.Kind == TidyKindCard && filepath.Base(filepath.Dir(it.Rel)) == wikiRelDir {
t.Fatalf("tidy 把 `.wiki/` 裡的卡列成待整理項:%s → %s", it.Rel, it.To)
}
}
}
// TestMachineOwnedCardsAreNotIngestedAsSourcecurated-wiki 模式收的是「他自己寫的 wiki」,
// 但 `system-dev/wiki/cards/arcrun-*.md` 是 **daemon 上一版親手產的卡**
// (檔名帶 MachineMark 就是它的身分證)。把自己的產物當原稿再萃一次
// =迴圈的第③環,也是這次「卡片的卡片」的第一代來源。
func TestMachineOwnedCardsAreNotIngestedAsSource(t *testing.T) {
plan := IngestPlan{Mode: IngestCuratedWiki, WikiRelDir: "system-dev/wiki"}
userCard := "system-dev/wiki/cards/我自己寫的.md"
if !plan.KeepsFile(userCard) {
t.Fatalf("使用者自己寫的卡被排除了:%s(curated-wiki 要收的就是這個)", userCard)
}
machineCard := "system-dev/wiki/cards/" + MarkName("換柱.md")
if plan.KeepsFile(machineCard) {
t.Fatalf("daemon 自己產的卡被當成原稿收進去:%s——"+
"它會被再萃一次,產出「卡片的卡片」", machineCard)
}
}
// TestSnapshotCardsIgnoresWikiDirclaude 路的同一個洞。
// snapshotCards → diffCards → enforceCardMarks 會把「這一輪新出現的卡」歸位到
// cardRelFor(搬出 `.wiki/`、加前綴)。`.wiki/` 不進快照,就沒有東西可以被搬。
func TestSnapshotCardsIgnoresWikiDir(t *testing.T) {
root := t.TempDir()
wikiDir := filepath.Join(root, filepath.FromSlash(cardsRelDir), wikiRelDir)
if err := os.MkdirAll(wikiDir, 0o755); err != nil {
t.Fatal(err)
}
before := snapshotCards(root)
if err := os.WriteFile(filepath.Join(wikiDir, "Pixel手機.md"), []byte("# Pixel手機\n"), 0o644); err != nil {
t.Fatal(err)
}
changed := diffCards(before, snapshotCards(root))
if len(changed) != 0 {
t.Fatalf("`.wiki/` 裡的新卡進了快照差集:%v——enforceCardMarks 會把它搬出隱藏目錄", changed)
}
}