昨天用一張圖、0 token,找出三個檔。今天換 AI 讀同一個 repo —— 不給圖、也不讓它讀 Package.swift。讓它寫一份架構文件,然後做兩件事:逐句核對它有沒有編造;再看它挑的入口檔,跟昨天那三個對不對得上。
因為架構文件是最容易寫得漂亮、最難被抓到的一種產出。
它天生充滿「這個模組負責處理容器生命週期」這類句子 —— 聽起來專業、讀起來順、而且你很難一眼判斷它是讀出來的還是猜出來的。一份程式碼跑不跑得起來一試就知道;一份架構文件錯了,可能三個月後才有人發現。
而 apple/container 剛好是最理想的對象:Apple 自己在 Package.swift 裡把模組邊界一條一條宣告出來了。 有了標準答案,核對才有依據。
實驗之前先把答案封存起來,免得事後配合結論調整。以下全部取自 commit d6de5694,發文前重新量過的:
| 來源 | 事實 |
|---|---|
Package.swift |
swift-tools-version 6.2 |
Package.swift |
.target 28 個、.executableTarget 7 個、.testTarget 15 個,合計 50 |
Package.swift |
products 裡宣告的 library 含 ContainerCommands、ContainerBuild、ContainerAPIService、ContainerAPIClient、ContainerResource、ContainerPersistence 等 |
docs/ |
頂層 16 份官方 md 文件(遞迴含 docs/tutorials/ 是 18 份) |
| 原始碼 | 444 個 .swift 檔 |
**注意 Package.swift 宣告 50 個 target,但 Day 2 從檔案路徑只數出 35 個。**這個差距本身就是一個陷阱題:如果 AI 講出「這個專案有 N 個模組」,N 要對到哪一個才算對?——**兩種數法都成立,但它得說清楚自己在數什麼。**說不清楚的,我一律記成「查無」。
讓 Claude(模型是 Opus 5,在 Claude Code 裡另開的乾淨 session)讀這個 repo(不給圖、也不讓它讀 Package.swift),產出一份架構文件。然後把文件拆成句子,每一句貼一個標籤:
| 標籤 | 定義 | 判準 |
|---|---|---|
| ✅ 可驗證 | 能在 Package.swift、原始碼或官方 docs/ 裡找到直接證據 |
我要能指出行號 |
| ⚠️ 查無 | 目前找不到足以判定對錯的證據,也找不到直接反證 | 常見於模糊的職責描述 |
| ❌ 編造 | 與證據直接矛盾,或指涉不存在的東西 | 例如講出不存在的 target |
編造率 = ❌ 的數量 ÷ 可對賬的宣稱數。 模糊句進不了分母 —— 它們只能記查無。
三條紀律,少一條,這個數字就失去意義:
Package.swift。還有第四條,是我做到一半才發現的:產文件的不能是「我平常用的那個 session」。
我寫這 30 天的那個 Claude Code session,昨天才剛掃完這個 repo、算過 35 個 target、知道 Services 有 1,099 個節點。**它不是受試者,它是知情者。**所以這一輪要另外開一個乾淨的 session,連 Package.swift 都明講不准讀 —— 不然它交出來的不是「它讀完這個 repo 的理解」,是「它記得我告訴過它什麼」。
2026-09-10 實跑。 無菌室有兩層:拿得掉的就拿掉 —— 全新 clone、釘在 d6de5694、沒有 graphify-out/、沒有 CLAUDE.md;拿不掉的(Package.swift 是 build 必需)就在 prompt 裡明講別讀。prompt 裡沒有任何標準答案,也沒說這是實驗 —— 不然它可能會全篇加保留語。
它交出 193 行、約 2,300 字。我從裡面篩出 16 項可以查證的宣稱,逐項對賬:
| 宣稱 | 實際 | |
|---|---|---|
| 7 個可執行檔 | .executableTarget = 7 |
✅ |
| 二進位名稱(6 個) | 逐一相符 | ✅ |
container-k8s |
實際是 k8s |
❌ 名稱不存在;它自己標了「[不確定名稱]」 |
| import 名稱 → 目錄對應表 10 條 | Package.swift 逐條相符 |
✅ |
| 15 個測試 target | .testTarget = 15 |
✅ |
ContainerCommands 88 檔 / 8.3k 行 |
88 / 8,325 | ✅ |
Sources/Services 16k 行 |
16,166 | ✅ |
| Plugins 各 84–180 行 | 最小 84、最大 180 | ✅ |
K8s 是唯一沒有 servicesConfig 的 plugin |
其餘四個都有 | ✅ |
XPCKeys 一百多個 case |
實際 82;同檔的 XPCRoute 36 個,合計 118 |
❌ 把兩個 enum 加在一起算;沒標不確定 |
| DNS 埠 2053 / 1053 | APIServer+Start.swift:39-40 |
✅ |
| 記憶體下限 200 MiB | ContainersService.swift:328 |
✅ |
| init image 512 MiB / 一般 512 GiB | SnapshotStore.swift:45,47 |
✅ |
DaemonPluginType 四種 |
runtime / network / core / auxiliary | ✅ |
openat(O_NOFOLLOW) 擋路徑穿越 |
BuildPipelineHandler.swift:46 |
✅ |
| euid 比對是唯一的安全邊界 | XPCServer.swift:178 |
✅ |
16 項裡錯 2 處,編造率 2/16。 一處是二進位名稱,它自己標了「[不確定名稱]」;另一處是 XPCKeys 的 case 數,它沒標。
第二處要老實交代:9/10 第一次對賬我記的是 ✅ —— 我數的是整個 XPC+.swift 的 case 行(119,還多算了一行註解),跟它犯了同一個錯。發文前重數才發現 XPCKeys 只有 82 個。驗收的人也會錯,所以驗收也要驗。
上面那個 50 vs 35 的陷阱,它沒有直接給一個 N。 它列出七個可執行檔、再把 import 名稱對到實際目錄 —— 一張可以逐條查的表,不是一個可以含糊的數字。而那張表,是在被要求別讀 Package.swift 的情況下寫的,卻 10 條全對。
「會不會編」到這裡有答案了。接下來才是我真正想問的。
昨天的圖用「連接度 + method 佔比 + 兩道過濾器」給出三個檔。今天這份文件最後一節叫「新人該從哪裡開始讀」,它自己給了一條 9 步的路線 —— 跟著一次 container run 走一遍:README 與技術概覽 → 命令樹 Application.swift → ContainerRun.swift → XPC 的 API 目錄 XPC+.swift(它寫「一定要看」)→ XPCServer.swift → daemon 啟動 APIServer+Start.swift(它寫「資訊密度最高」)→ ContainersService.swift → RuntimeService.swift → plugin 骨架。
兩邊沒有互相看過:圖是 0 token 純結構算的,文件是在無菌室裡它自己讀的。擺在一起:
| 昨天圖挑的 | 今天 AI 的路線 |
|---|---|
ContainersService.swift |
✅ 第 7 步 —— harness / service 分工的範本 |
RuntimeService.swift |
✅ 第 8 步 —— 跨程序、跨 VM 邊界的那一跳 |
Parser.swift |
❌ 193 行裡一次都沒提 |
三個對上兩個。 對上的那兩個,是兩種完全不同的方法各自獨立得到的答案,可信度比任何一邊單獨講都高 —— 至少可以把它當成這個 repo 的高可信入口。
漏的那個更有意思。 Parser.swift 是一支 1,138 行、33 個 static func 的工具箱,把 CLI 收到的字串(記憶體大小、使用者、平台、環境變數、volume)解析成設定值 —— 每個指令都要經過它,所以圖上它有 186 條跨模組的邊,排得很前面。但它是水管,不是心智模型:讀完它你知道 --memory 2g 怎麼變成位元組,不知道這個專案怎麼跑。AI 的路線裡佔掉這個位置的,是 XPC+.swift 和 APIServer+Start.swift —— 圖上都沒排進前段,但它們是「誰接誰」的接線圖。
所以兩套方法量的是不同的東西:**圖量「誰被依賴得最多」,AI 量「先看哪個才看得懂」。**前者容易把工具箱型的檔案排得太前,後者則可能遺漏它 —— 而一個真的要讀這個 repo 的人,兩個都需要。
寫到這裡自然會想:既然兩邊各看到一半,把圖交給 AI 一起讀,路線會不會更好?我補跑了一次 —— 同一個無菌室、多放一份未過濾的 graphify-out/,只要它給閱讀路線。花費 $2.53、耗時 197 秒、32 個回合,交出 9 步。
骨架一模一樣,檔案 9 個重疊 8 個;圖帶來的只有兩處:多了 Package.swift (目錄名 ≠ target 名的對照表)當第 2 步,多了 ContainerConfiguration 基礎層當第 8 步 —— 它明講是看到圖上 105 條邊。Parser.swift 還是沒進路線。
比較有意思的是它怎麼用那份圖。「我沒把握的地方」第 1 點寫著:god nodes 前五名(ContainerizationError、Foundation、FileManager、Int、ParserTest)是雜訊,只採信跟 Package.swift依賴方向吻合的兩個。它自己重做了昨天那兩道過濾器。 圖沒有讓路線變好,只是讓它多花一段力氣丟掉雜訊。
我原本要問的是「它會不會編」。16 項對賬、錯兩處:一處它自己圈了,一處是數字算錯、沒圈 —— 答案是:會,但很少。但量完之後,那不是最有用的問題。更有用的是拿它的答案跟另一套方法對:重疊的地方最可信,不重疊的地方告訴你兩套方法各自看不見什麼。 而把圖餵給它,換不到更好的路線 —— 圖的價值,在 0 token 就給出候選與依賴方向;不一定要把它當成 AI 的輸入。
那份文件 193 行錯兩處,其中一處它自己標了不確定。這不是它比較厲害,是我在 prompt 裡要求它把沒把握的地方另開一節,它就照做了。**這個習慣是可以抄的。**沒標的那一處,是靠重數抓到的 —— 而第一次數的人是我,也數錯了。
這一篇留下的心法:
讀陌生專案,三件事:AST 先掃(0 token,拿到誰被依賴最多);再讓 AI 讀 README 與原始碼(拿到先看哪個才看得懂);最後把兩邊對起來 —— 重疊的,就是高可信入口。 只有一邊提到的,再問它:是工具箱,還是「誰接誰」的接線圖?
驗它寫的東西,挑有標準答案的題目;而驗完的表,發文前再數一次。如果你只能在 prompt 裡加一句話:不確定的地方要說你不確定。
明天:今天那間「無菌室」,正式一點叫隔離環境。我只寫了怎麼準備,它其實有三級 —— 每一級擋住什麼、又漏掉什麼,我都親手踩過。
apple/container,commit d6de5694200468d99a61662bfb9bb3aba763e3e5:github.com/apple/container
Package.swift(核對用的標準答案,50 個 target 的宣告在這裡)docs/,頂層 16 份官方文件(遞迴 18 份)—— 第二層標準答案XPCKeys 那列的更正)