Files
arcrun-collector/machinemark.go
T
Claude 129fe79ecb fix(collector): daemon 不再改用戶版控中的檔案,接 repo 只讀整理好的 wiki(#105/#104)
## #105 daemon 會改用戶的檔案(不可逆,先修)

2026-08-14 21:45 實撞:InkStoneCo 在看守清單裡,daemon 回報「已把 1 張舊卡片歸位」,
實際把 system-dev/wiki/cards/autonomy/ 整個子目錄壓平改名,16 個版控中的檔案變成刪除。

真兇是 MigrateCardNames 的一句假設:「那兩個目錄從頭到尾只有 daemon 會寫」。
那句話在 vault 上成立,在 repo 上不成立——system-dev/wiki/cards/ 是 template 的規約
路徑,而 template 就是要裝進開發者自己的 repo,那裡本來就有人家自己的檔案。

- 新增 repoguard.go:`.git` 判準(含 linked worktree),與 #104 共用同一個判準源
- 版控中的資料夾一個檔都不自動動;改成記帳+講給使用者聽,出口是 collector tidy --apply
- Blocked 只算「確定是我們寫的」(帶 arcrun- 標記)——報使用者自己的卡等於發假訊息
- 落卡與工作區改走 .arcrun-rag/(與 vault 同待遇),自帶 .gitignore(*) 讓它對 git 隱形
- 非版控資料夾(含 vault)行為與 #60 第三輪完全一致,不推翻前兩輪的成果

## #104 接上開發 repo 會把上萬個原始檔排進佇列

leo 的規格:「它要辨識這個庫已經有 wiki,那就直接 ingest 了」「只有文件要讀,程式碼不用讀」。

- 新增 ingestplan.go:掃描前先問「這個資料夾是什麼」——all/curated-wiki/docs-only
- 排除靠路徑身分不靠副檔名:依賴、建置產物、templatefs 範本、linked worktree、巢狀子 repo
- 策略與擋掉的數字經 TriggerPayload.Plan 走進 status.json,CLI 走 stderr(排除規則要看得見)
- 子專案自己的 wiki 刻意不收,但一定列出來讓使用者知道去哪裡找
- 與 TemplateOwns(把 system-dev/ 整棵當開發用的)的對撞用身分化解,不拿掉任一條:
  我們代裝的資料夾沒有 .git(舊規則照舊),他自己的 repo 有(收那份 wiki)

## 實測

#105:舊版 daemon 對真 repo 跑一輪 → git status 32 行(16 個 D + 16 個 ??,與 21:45
      撞到的一字不差);新版跑同一輪 → 0 行。另有真 git init 的端到端測試。
#104:造出 leo 那棵樹的形狀(InkStoneCo + products/arcrun-rag + 3 份出貨 worktree,
      967 個文件檔)→ 舊版送 91 個檔(多收的全是 -wt*ship/ 裡的 templatefs 範本、
      benchmark 結果、docs-site 產物),新版送 32 個,全部落在 system-dev/wiki/ 底下。

MachineMark 規約新增唯一例外 IsMachineOwnedRel:.arcrun-rag/ 底下的檔以目錄名為標記
(.gitignore 的檔名是 git 定的,改不得)。迴歸網判準同步換過去。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUwsLkFEGN8496bZTqhjFq
2026-08-14 14:41:08 +00:00

99 lines
5.3 KiB
Go
Raw 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.
// machinemark.go — daemon 寫進使用者資料夾的每一個檔案,檔名都帶同一個標記
// arcrun-rag#60 第二輪,2026-08-12 leo 實撞)。
//
// 🔴 為什麼上一輪不夠:
//
// 第一輪(1e36bb1)只改了「卡片**落在哪個目錄**」——vault 就改落隱藏的
// `.arcrun-rag/wiki/cards/`。但 Logseq/Obsidian 的**頁名是從 basename 來的**
// 而卡片的 basename 一直是「原稿的 basename」:使用者的 `journals/2026_08_10.md`
// 萃出來的卡就叫 `2026_08_10.md`,跟他自己的日誌頁**同名**。
// ⇒ 只要那個目錄有任何一刻被看見(他自己翻資料夾、備份工具攤平、之後改成非隱藏、
// 或是**非 vault 的一般資料夾**——那裡卡片本來就是可見的),撞名就回來了。
// leo 原話:「不只是加上 journal,**可能所有的檔案都加一個前後綴,比如「wiki」**。」
//
// 🔴 規約(本檔是唯一真相源,不准有第二種標記):
//
// **daemon 在使用者的監看根底下產生的每一個檔案,basename 一律以 MachineMark 開頭。**
//
// 一種就是一種——「一部分加一部分不加」比全不加更難分辨(leo 的紅線)。
// 所以任何新的寫檔點都必須經過 MarkName(),不得自己拼檔名。
// 迴歸網=vault_footprint_test.go:它 walk 整個監看根,只要有任何新檔沒帶標記就紅,
// **與寫檔點的數量無關**——這正是上一輪缺的那張網(上一輪只點測了一個常數)。
//
// 為什麼是「前綴」不是「後綴」:`ls`/Finder/Logseq 的頁面清單都是按名字排序的,
// 前綴會讓機器寫的東西**全部聚成一叢**,一眼就掃得完;後綴則散落在他自己的檔案之間,
// 等於還是要一個一個看。leo 要的是「打開資料夾一眼分得出」,那就是前綴。
//
// 為什麼標記字串是 `arcrun-` 而不是 leo 舉例的 `wiki`:`wiki` 是他自己筆記裡會用的
// 普通詞(`wiki-整理術.md` 是他的、不是我們的),拿它當標記等於製造新的誤認;
// `arcrun-` 直接說出「這是 Arcrun 寫的」,且與既有的 `.arcrun-rag/` 目錄同一組字。
// 這是一行常數,leo 覺得該用別的字就改這裡一個地方(連舊產物一起 tidy 重跑即可)。
package collector
import "strings"
// MachineMark 是 daemon 產物的**唯一**標記。改這裡=改全部(含 tidy 的認人判準)。
const MachineMark = "arcrun-"
// MarkName 把一個 basename 加上標記。冪等:已經帶標記的原樣回傳,
// 不會疊成 `arcrun-arcrun-x.md`tidy 重跑、daemon 重跑都會走到這條)。
func MarkName(base string) string {
if IsMarked(base) {
return base
}
return MachineMark + base
}
// IsMarked 回答「這個 basename 是不是機器寫的」。
// ⚠️ 只吃 basename,不吃路徑——路徑中間的目錄名不算數(目錄是不是機器的,
// 由 cardsRelDirFor 那組常數決定,不由這裡猜)。
func IsMarked(base string) bool {
return strings.HasPrefix(base, MachineMark)
}
// UnmarkName 去掉標記,回傳原本的名字。tidy 拿來對照「這張卡對應哪一份原稿」。
func UnmarkName(base string) string {
return strings.TrimPrefix(base, MachineMark)
}
// IsMachineOwnedRel 回答「這個相對路徑是不是我們寫的東西」——**規約的完整判準**,
// 迴歸網(vault_footprint_test.govault_subdir_test.go)用的就是這一支。
//
// 兩條路,第二條是規約唯一認可的例外:
//
// ① basename 帶 MachineMark ⇒ 走在使用者的檔案之間也一眼分得出(規約主體)
// ② 路徑上**任何一層**是 `.arcrun-rag/` ⇒ **那個目錄名自己就是標記**,且它是隱藏目錄
//
// ②「任何一層」而不是「開頭」:呼叫端給的相對起點不一定是監看根——迴歸網比對整個
// 筆記庫時,同一個檔的相對路徑是 `docs/.arcrun-rag/…`(監看根是 vault 底下的 docs/)。
// 判「誰擁有這個檔」不該受呼叫端從哪裡起算影響。
//
// 🔴 為什麼需要 ②(arcrun-rag#105):我們得在使用者的 repo 裡放一份 `.gitignore`
// (讓整個工作區對 git 隱形,他跑一輪 `git status` 才會是乾淨的)。那個檔名是
// **git 定的,我們改不得**——叫 `arcrun-.gitignore` 的話 git 根本不認,功能等於沒做。
//
// 這不是替規約開後門:規約的目的是「打開資料夾一眼分得出哪些是機器寫的」,
// 而 `.arcrun-rag/` 這個目錄名把整叢東西一次講完,比逐檔前綴**更強**。
// ⚠️ 例外只有這一個。監看根底下**其他任何位置**的新檔,一律得帶前綴——
// 想在別處寫一個「名字不能改」的檔時,先想清楚那個檔為什麼不能住進 `.arcrun-rag/`。
func IsMachineOwnedRel(relSlash string) bool {
if IsMarked(pathBase(relSlash)) {
return true
}
for _, seg := range strings.Split(relSlash, "/") {
if seg == workspaceRelDir {
return true
}
}
return false
}
// pathBase 取斜線路徑的最後一段(不用 filepath.Base:這裡的輸入一律是斜線分隔的
// 相對路徑,不該受執行平台的分隔符影響)。
func pathBase(relSlash string) string {
if i := strings.LastIndex(relSlash, "/"); i >= 0 {
return relSlash[i+1:]
}
return relSlash
}