開始探索 是做什麼的?
以一次性的方式,根據證據在程式碼庫中定位,無需漫無目的地瀏覽。
這個技能存在的目的是防止以下失敗:AI 代理開啟一個儲存庫,搜尋一個聽起來合理的詞,閱讀三個恰好匹配的檔案,然後給出一個自信的架構摘要,但只描述了系統的 4%。begin 用測量取代猜測,找出重要程式碼的位置,然後只讀取測量結果指向的內容。
合約
- 不回憶,一切皆推導。 重要性來自於真實匯入圖(import graph)的 PageRank 演算法;複雜度來自於計數決策點;焦點來自於 Git 和 PR 記錄。如果你無法指向一個
檔案:行程式碼或一個提交,就不要說出來。 - 每個主張都有錨點。 BEGIN.md 中的每個陳述都引用
檔案:行程式碼、一個提交或一個 PR 編號。一個無錨點的句子就是猜測,而這個技能的存在就是為了消滅猜測。 - 雙層結構,其中一層是活的。
.begin/是機器輸出,可重新產生且會自我忽略。BEGIN.md是你撰寫、提交,並標記其衍生來源提交的散文。一個 post-commit 鉤子會重新衍生機器層,並報告哪些散文章節已被程式碼超越。此技能從不編輯已追蹤的檔案 —.begin/自身帶有.gitignore,因此不會觸動儲存庫的.gitignore。 - 代理(Proxy)必須被明確命名。
complexityProxy是決策點計數,不是語法解析。巢狀結構是透過縮排來測量的。請明確說明;不要將代理偽裝成事實。 - 降級的測量結果必須被宣告,絕不能默默使用。 掃描時若遇到淺層複製、無變更記錄視窗、遺失檔案、完全無法解析的語言,或解析失敗但可部分處理的語言,會印出
[!WARNING]區塊。在閱讀表格之前,請先閱讀該區塊,並在 BEGIN.md 中重複其內容。 建立在破碎圖形上的排名看起來和好的排名一模一樣 — 這就是全部的危險所在。
執行指令
S=~/.claude/skills/begin/scripts
bash $S/recon.sh # 誰/什麼/何時:PR、你的提交、即時編輯、文件
node $S/scan.mjs # 結構:匯入圖、熱點、前端(surface)->引擎(engine)
兩者都會寫入 .begin/ 並印出摘要。在儲存庫的任何位置執行 — 掃描會自動定位到儲存庫根目錄。recon.sh 在沒有 gh、沒有網路和沒有遠端的情況下也能優雅地降級,但它需要 node(編輯功能是強制的)。量測到的熱快取時間:259 個檔案 0.24 秒 · 3,384 個檔案 1.9 秒 · 2,809 個檔案 1.9 秒 · 1,819 個檔案 2.5 秒 · 1,397 個檔案 3.5 秒。檔案數量無法預測時間;內容才會。
有用的旗標:
--since "90 days ago"— 變更記錄視窗(兩個腳本預設為 90 天)。--top 40— 列印多少個熱點(預設為 25)。--author "..."— 一個正規表達式。你很少需要它:它預設為儲存庫的user.name,而recon.sh會回退到儲存庫中最活躍的作者,並在你沒有提交時告知。這在你剛克隆的儲存庫中是正常情況。--exclude 'examples/**,site/**'— 將供應商(vendored)目錄和文件網站排除在前端(surface)計數之外。模式在兩端固定,並且會印出被移除的檔案數量,因為靜默的排除會在後續表現為遺失的邊緣(edge)。--root DIR— 掃描不同的儲存庫。
操作順序 — 請嚴格遵守
順序錯誤是導致 AI 代理最終讀錯檔案的原因。
1. 先進行偵查(Recon),再分析結構
先執行 recon.sh。它會回答「這個人一直在做什麼?」— 未提交的編輯、分支與主分支的提交、最近約 20 個已合併的 PR(包含它們的內文),以及每個 PR 更改了什麼。這就是關注焦點的先驗資訊。完整閱讀 .begin/recon.md;它很短。
未提交的差異(diff)通常是儲存庫中最有資訊價值的東西 — 它是使用者當前持有的問題。偵查(Recon)會標記三件事:當工作目錄乾淨時,它會明確說明(而不是省略該章節讓你去尋找);當差異僅包含鎖定檔案和建置輸出時,它會告訴你不要在第一輪閱讀中浪費時間;當你的簽出(checkout)落後於 origin 時,它會告知落後了多少個提交,並列出近期 PR 中不在你工作目錄中的檔案名稱。
最後一點是前提條件,而非附帶條件。 如果偵查報告你落後了,則下面的步驟 3.2 是不安全的:PR 檔案列表描述的是你尚未擁有的程式碼。請先執行 git pull,或者跳過 PR 閱讀步驟,並在 BEGIN.md 中說明你跳過了。
2. 再分析結構
執行 scan.mjs 並閱讀 .begin/scan.md,從任何 [!WARNING] 區塊開始。接著,你將獲得基於測量而非假設的以下資訊:
- 前端(Surfaces) — 人類或呼叫者實際觸碰系統的每個地方,按種類分組(
web-page、ui-component、http-route、cli-command、mcp-tool、worker、public-api、migration,以及example、config和test,這些雖然是前端但不是產品本身)。這是地圖的使用者介面端。 - 熱點(Hotspots) — 排名,附帶一個
why欄位,說明是哪個術語將該檔案置於該位置:depended-on、churn、complex或recent。一個因churn而位於頂部的檔案與因depended-on而位於頂部的檔案是截然不同的動物;該欄位會告訴你該進行哪種對話。 - 前端到引擎的路徑(Surface → engine paths) — 從端點或按鈕到核心部分的實際匯入鏈,由 BFS 計算。跨越惰性
await import()邊緣的路徑會被標記,因為它是在呼叫時而非載入時解析的。 - 匯入循環群組(Import cycle groups) — 基於靜態值邊緣的強連通集合,並展示一個真實的有向循環。僅型別和惰性(
await import(...))邊緣被排除,因為兩者都不會構成真正的初始化循環。 - 無法觸及的檔案(Unreachable files) — 死程式碼,或啟發式方法遺漏的前端。請判斷是哪一種,並說明你的判斷。
2.5. 閱讀儲存庫已為你撰寫的地圖
在打開任何熱點檔案之前,請檢查 AGENTS.md、CLAUDE.md、ARCHITECTURE.md 或每個目錄的 README — recon.md 會列出它們。一個提供了子系統地圖的儲存庫,能在三十行程式碼中給你掃描需要兩秒推斷、而散文需要一小時重建的資訊。先閱讀這些文件,然後使用掃描結果來驗證它們:文件會過時,圖形不會。
3. 只讀取測量結果指向的內容
按此順序閱讀,然後停止:
- 未提交的差異和當前分支的提交(他們現在正在做什麼)。
- 最接近的 3–5 個已合併 PR 所更改的檔案(他們剛剛決定了什麼)。
- 來自
scan.md的前 8–15 個熱點檔案。 - 對於前 3 個熱點中的每一個,沿著其
reachedVia前端向內走訪,以便你能夠陳述從使用者操作到引擎的完整路徑。 README、步驟 2.5 中的代理說明文件,以及任何標題與熱點匹配的文件。
第一輪不要開啟這個集合之外的檔案。如果某些東西看起來遺漏了,那是一個需要報告的發現,而不是隨意瀏覽的許可。
4. 在撰寫之前進行驗證
表格是一組線索,而非結論。在將每個主張寫入 BEGIN.md 之前:
- 開啟檔案並確認你將引用的那行程式碼確實如你所想。
- 對於前端到引擎的路徑,確認該匯入確實存在於原始碼中。
- 對於任何與行為相關的內容,優先選擇一個證明該行為的測試,而不是一個看起來像的函式。
- 使用
grep搜尋熱點路徑中是否存在被吞沒的錯誤 —catch {}、.catch(() => {})、as any、as never、丟棄的回傳值。錯誤存在於異常被拋棄的地方,而這些是你第一天就能回饋的最高價值發現。 - 至少花一輪嘗試反駁你的主要主張,而不是確認它。一份好的 BEGIN.md 中最有價值的句子是那些與掃描結果矛盾的句子。
如果掃描結果在某件事上是錯誤的,請在 BEGIN.md 中說明並修正它。一個圖形認為是死的、但實際上透過 HTTP 或佇列觸及的檔案,是你所能記錄的最有用的東西,因為其他人也不會再找到它。將此類入口點宣告在 .begin/surfaces.txt 中(每行一個路徑,可選 path<TAB>kind),下一次執行時就會將它們視為前端。
5. 撰寫 BEGIN.md
使用 references/BEGIN-template.md,並先閱讀 references/EXAMPLE-BEGIN.md 來校準 — 這是一個真實的範例,不是模擬的。結構,按順序:
- 用一段話說明此系統的功能 — 使用使用者的語言,而非程式碼的術語。
- 基本機制 — 第一原理。核心轉換是什麼?讓其他一切變得顯而易見的一個想法是什麼?少了它什麼會崩潰?
- 前端到引擎 — 來自
scan.md路徑的表格,每一行都經過驗證。如果沒有行跨越進程或網路邊界,請明確說明:在一個拆分語言(split-language)的儲存庫中,真正的接縫通常是 任何匯入圖都無法看到的 HTTP 或佇列呼叫。 - 複雜部分排名 — 先寫發現,後寫測量。每個條目需要一個陷阱,引用一個尚未在其測量行中的
檔案:行程式碼。如果你無法提供這樣的條目,代表你並未真正閱讀它:請刪除它。 - 決策及其原因 — 每個都連結到它的 PR 或提交,並附上 PR 內文中的推理。這使得文件成為知識圖譜,而不是程式碼導覽。
- 你上次離開的地方 — 或者,對於你不擁有的儲存庫,誰擁有這個以及它在哪裡成長:活躍的作者、最新的開放 PR 正在添加什麼、應該先閱讀哪一個。
- 機器層在此無法看到的內容 — 以及開放問題,包括那個承載最多負荷的單一假設。永遠不要省略這個。
同時記錄如何執行和驗證它(recon.md 從 package.json 提取的腳本)以及一個詞彙表,用於任何在此儲存庫中被重載的詞彙。這兩者都是新手最先需要的,也是每次程式碼導覽都會忘記的。
使用 [[wiki-style]] 錨點進行交叉連結。為每個章節蓋上印章:
<!-- begin:section id="ingestion" sha="0055f01" files="src/adapter.ts,src/identity.ts" -->
sha = 你推導該章節時(而非後來新增 BEGIN.md 的提交)的 git rev-parse --short HEAD。files = 該章節主張所依賴的檔案;像 src/engine/** 這樣的 glob 模式也可以。一個沒有 files= 的章節,或 files= 指向一個不存在路徑的章節,永遠不會過時,並會被報告為無法驗證 — 而這正是這個技能要防止的失敗,所以請確保路徑是正確的。
6. 使其成為活的文件
bash $S/install-hook.sh # post-commit;針對另一個觸發器使用 --pre-push
該鉤子會重新執行掃描,並列印一行通知,告知有多少章節已過時,詳細資訊在 .begin/stale.md 中。它會遵守 core.hooksPath(husky)和 Git 工作樹,追加到現有鉤子而不會破壞它,拒絕追加到非 shell 鉤子,並且永遠不會觸碰已追蹤的檔案。使用 --uninstall 移除它,這只會移除其自己的區塊。
/begin update
當使用者返回一個已經有 BEGIN.md 的儲存庫時:
node $S/refresh.mjs && cat .begin/stale.md
只重寫過時的章節,重新執行 recon.sh 以重新整理第 6 章節,並用新的 HEAD sha 重新蓋章每個重寫的章節。保持目前未更改的章節逐位元組一致 — 一個重寫所有內容的更新會同時破壞讀者對變更內容的感覺以及 Git 歷史記錄的實用性。
如果還沒有 BEGIN.md,那麼 /begin update 就等同於 /begin:寫一個。
回饋報告
一段簡短的段落:基本機制、最重要的一兩個檔案及其原因、使用者上次離開的地方,以及唯一一個你無法驗證的事情。細節存在於 BEGIN.md 中,而不是回覆中。
以你沒有檢查的內容作為結尾。一個聲稱有完整覆蓋率的 begin 初步檢查是在說謊。
已知限制 — 請陳述這些限制,不要掩蓋它們
- 匯入圖是靜態的。一個字面上的
import('./x')會被解析(並標記為惰性);一個計算出的import(expr)、一個 DI 容器,或一個由字串鍵入的註冊表不會產生任何邊緣(edge),因此這樣的檔案可能看起來無法觸及,但實際上是承擔重要職責的。在一個拆分語言(split-language)的儲存庫中,前端↔後端的接縫是 HTTP 或 WebSocket 呼叫,並且在建構上是不可見的。掃描會精確地標記無法觸及的檔案,以便人類進行判斷。 - 完整的 JS/TS 和 Python 解析 — 包括 tsconfig
paths(樹中的每個設定檔,而不只是根目錄)、extends、專案references、NodeNext./x.js→x.ts,以及針對推斷根目錄(針對 CPythonast基礎事實測量約 98% 的召回率和精確度)的 Python 絕對、相對和子模組匯入。Go、Rust、Ruby、Java、Kotlin、Swift、PHP、C# 和 C/C++ 完全不貢獻任何邊緣。 在這樣的儲存庫中,每個檔案都顯示出基礎層級的重要性,並顯示為「無法觸及」;掃描會發出警告,你必須重複該警告,而不是將一個空圖形作為一個發現來報告。 complexityProxy會針對每種語言計算決策點,在移除註解和單行字串的程式碼中進行。標記語言和 schema 檔案預設得分為 0;供應商(vendored)、最小化(minified)和產生的檔案會被偵測並單獨排名。多行模板字面量仍會被計算,因此一個模板較多的檔案會顯得稍微熱一些。- 變更記錄(Churn)受
--since限制,而淺層或全新的克隆則沒有任何變更記錄 — 掃描會大聲說明這一點。一個在兩年前被重寫一次的檔案,分數仍然接近於零,而且仍然可能是儲存庫中最重要的檔案。
測試
node $S/test-scan.mjs # 圖形、循環、前端、可達性、Python
node $S/test-scan.mjs --mutate pyDotDot # ...並證明斷言可以被觸發為紅色
bash $S/test-living.sh # 過時、鉤子、工作目錄整潔度
node $S/redact.mjs --self-test # 秘密被捕獲且乾淨文字不受影響
每個斷言的存在都是因為那個錯誤曾經是真實的並被交付過一次。突變:alias、jsToTs、valueCycle、typeCycle、pyAbsolute、pyDotDot — 每個都必須將其斷言變成紅色,如果沒有,CI 就會失敗。