做研究時算法經常要迭代許多次,換一個估計方法、調一個參數、改一段清理的規則,每一次都會改變結果。然而多數人沒有備份的概念,有的也多半是以複製檔案並改名來做,資料夾裡於是並列著 analysis、analysis_v2、analysis_final 與 analysis_final_v2 幾份腳本,過幾個版本後,報告裡那張圖是哪一份畫的,連本人也說不清。
檔名只放得下一個序號,改了什麼、為什麼改都沒有記下;複製的通常只有一支腳本,它讀的資料與用到的其他腳本並未一起留下,舊檔即使找到,也未必還能執行出當時的結果;而有沒有複製全看當下是否想起,最需要舊檔的那一次,往往正好沒有複製。
改由 agent 動手之後,一則 prompt 之下可以改動數個檔案,如果仍靠複製留底,便須每次複製整個資料夾,這個做法更難維持。CS 領域處理這件事的方式稱為版本控制,即由工具替專案記下每一次改動,並可退回其中任何一次;本篇介紹其中最通行的工具 Git,操作則全部交給 agent。
Rewind 的能力與局限
Claude Code 本身也有退回的功能。使用者每送出一則 prompt,它便自動記下 agent 即將修改的檔案在當時的內容,這份紀錄稱為 checkpoint;事後選定先前的某一則訊息,即可將檔案退回至送出該訊息之前的樣子,這個操作稱為 rewind。
Rewind 的操作位置依介面而異。依官方文件,VS Code 擴充套件裡有 rewind 按鈕,操作見「Claude Code 與 Codex 的介面」一節;Claude 桌面 app 的文件則沒有提到 rewind 或 checkpoint。
Rewind 適用於收回剛發生的一兩步改動,然而依官方文件,其適用範圍有限。Checkpoint 只記錄 agent 以檔案編輯工具所做的修改,而刪除、搬移、複製檔案都是透過指令完成的,不在紀錄之內;使用者在編輯器裡手動改過的檔案,一般也不在,agent 中途另外啟動的 agent(稱為 subagent)所做的修改通常也是如此。紀錄隨對話保存,每個對話只留最近的 100 個,約 30 天後清除,較久以前的狀態因此退不回去;checkpoint 也沒有說明文字,要找回「改寫資料清理的做法之前」的那一刻,只能憑記憶翻找當時的對話。
其中對資料分析影響最大的是指令造成的變動。Agent 執行一支腳本,腳本將清理後的資料寫回原本的 CSV,這次覆寫是執行指令的結果,並非出自檔案編輯工具,因此無法 rewind,而資料分析與科學計算裡的檔案變動,有很大一部分正是這樣產生的。
官方文件也寫明,checkpoint 是為單次對話之內的快速復原而設計的,並不能取代版本控制。
提交的概念
Git 會在專案資料夾內建立一個名為 .git 的隱藏資料夾,專案的歷史都存在其中,平時不必理會,也不應手動更動。它只在本機運作,不需註冊任何帳號,也不會上傳任何內容。
Git 記錄歷史的單位稱為 commit,中文稱為提交,即專案在某一刻的完整快照,附有一段說明文字、提交的時間與一串識別碼;提交也作動詞用,存下一份快照便是提交一次。這與單機遊戲的存檔相同,關卡之前存檔一次,其後無論進行得如何,都可讀檔重來。
前述複製檔案的缺點,提交都沒有。每次提交存下的是整個專案,退回時腳本、資料與文件一起回到那一刻,腳本讀到的仍是當時的資料;說明文字寫下改了什麼、為什麼改,日後依此尋找,不必靠檔名猜測;歷史都在 .git 之內,資料夾裡每個檔案只有目前的一份,final_v2 之類的檔名也就不再需要。
Git 比對的是檔案的內容,agent 以編輯工具改的、執行指令造成的、使用者自己在編輯器裡改的,都在紀錄之內,rewind 無法復原的變動因此都可退回。其中純文字檔的改動可以逐行查看,Excel、Word 與圖片則只看得出檔案變了,這也是CSV、TeX 與 Markdown一篇主張純文字優先的原因之一。
但 Git 不會自動提交,沒有提交過的狀態無法退回;它也只保護納入提交的檔案。
Git 的指令繁多,過去是初學者的門檻,如今可以全部交給 agent,使用者以平常的話交代即可。留給使用者的是幾項判斷:何時該提交、提交之前看過改動、說明文字是否寫清楚了,以及要不要退回。
安裝 Git for Windows
Windows 沒有內建 Git,須自官方下載頁取得 Git for Windows 的安裝程式。安裝過程中的選項很多,全部維持預設值即可。安裝完成後將桌面 app 完全關閉再重新開啟,然後問 agent 一句:
確認這台電腦是否已經安裝 Git,並告訴我版本。Claude 桌面 app 本身並不要求 Git,沒有安裝時 agent 仍可照常工作,只是無法提交。Codex 相同,OpenAI 的文件也建議 Windows 使用者另行安裝 Git。
不納入提交的檔案
Git 預設專案資料夾裡全部的檔案都是可以提交的對象,agent 提交時通常也是整批存入。但並非每個檔案都適合留在歷史裡,Git 因此有忽略的功能:在專案裡放一個名為 .gitignore 的純文字檔,列出不納入的檔案或資料夾,Git 此後便略過它們,提交時不會存入,也不會記錄任何改動。
檔案一經提交便留在歷史裡,日後即使刪除,舊的提交仍保有其內容,.gitignore 因此應在首次提交之前建立;此後專案裡出現新的一類不該納入的檔案時,例如加入一份大型資料或改用另一套工具,再補上一行。
不應納入的首先是體積大的資料檔,這類檔案每變動一次,Git 便完整再存一份,.git 會迅速膨脹,模擬或反覆計算存下的大量中間結果也屬此類。其次是含個人資料或受保密協議限制的資料,專案日後如果放上 GitHub,歷史裡的內容會隨之上傳。此外還有工具自動產生的檔案,例如 R 的 .Rhistory 與 .RData、Python 的 __pycache__、Jupyter 的 .ipynb_checkpoints,這些是執行時留下的紀錄與暫存,與分析的內容無關。
腳本輸出的結果表格這類小型的文字檔,則建議納入。它們雖可由腳本重新產生,但納入之後,每次重新執行,Git 便會指出其內容有沒有變,等於多了一項檢查:只調整了圖的配色,結果表格卻出現在改動清單裡,即表示結果已受影響。圖檔也可以納入,只是圖無法逐行比對,每次重新執行又可能都出現在改動清單裡,如果覺得雜亂,也可排除。
.gitignore 也可以交給 agent 建立:
替這個專案建立 .gitignore。建立之前先列出你打算排除的項目與各自的大小,並說明理由。被排除的檔案不在任何一次提交之內,也就不受保護,被覆寫或刪除之後,Git 無法退回。因此原始資料如果被排除,應在專案之外另存一份不再更動的複本,並交代 agent 只讀取原始資料、不得寫入,清理後的結果另存新檔。
提交改動
首次提交會存入專案裡所有未被排除的檔案,提交之前先請 agent 列出清單:
我想用 Git 管理這個專案。先不要提交,列出這個資料夾裡有哪些檔案會被存進去。看清單時,留意其中是否仍有體積很大的檔案或含個人資料的檔案,有的話先回頭補進 .gitignore。清單沒有問題之後再提交:
替這個專案啟用 Git,並做第一次提交,說明文字用繁體中文寫。完成後告訴我存進了哪些檔案。Git 要求每次提交記下提交者的姓名與 email,初次使用的電腦還沒有這項設定,agent 會先詢問,或在提交失敗之後回頭詢問。這兩項只是寫在紀錄裡的署名,Git 不會驗證,也不會寄信;但專案日後如果放上 GitHub,署名會隨之公開,填一個不介意公開的 email 即可。這項設定每台電腦只需做一次。
Manual 模式下,agent 每執行一道 Git 指令之前都會徵求同意,提示裡的指令看不懂時,照「權限提示與 plan mode」一節的做法,先拒絕並請它說明。完成之後,核對 agent 回報的檔案是否與先前的清單相符。
首次之後,每次提交之前先問一遍目前的檔案與上一次提交相比改了什麼:
跟上一次提交相比,現在有哪些檔案被改過?逐一說明改了什麼,不要修改任何檔案。此時看的是有變動的檔案是否都在預期之內。只交代修改一張圖,清單裡卻出現讀取資料的腳本或資料檔本身,便應先問清楚原因再提交;輸出的結果表格如果在 code 沒有相關改動的情況下變了,也應先查明,常見的原因是某處用到亂數而沒有固定種子。
Agent 的說明是它對改動的轉述,「結果須自行驗證」一節已說明轉述可能有誤,要緊的檔案應看原文。依官方文件,Claude 桌面 app 在 agent 修改檔案之後會顯示一個標有增減行數的標示,形如 +12 -1,點選即可逐檔查看改動前後的內容;也可以直接請 agent 顯示某個檔案改動前後的原文。
看過之後的提交只需一句話,說明文字由 agent 依這次的改動撰寫:
提交。提交之後須查看 agent 所寫的說明文字。讀得出改了什麼、為什麼改便可以;如果只寫著「更新檔案」這類未說明內容的文字,請它重寫,日後翻找提交紀錄時靠的就是這段文字。
何時該提交,可依兩個時機判斷。其一是完成了一件說得清楚的事,例如清理腳本通過了核對、加入了一項分析、一張圖調整好了。這件事不必大,修正一處錯誤、改一個參數,同樣可以單獨提交;提交不嫌多,間隔越短,日後能退回的位置越細。不相干的事則應分開提交,例如修正了清理腳本的一處錯誤,又順手調了圖的配色,便提交兩次;兩者如果混在同一次提交裡,日後要退回配色,修正也只能跟著一起退。其二是大範圍的改動之前,例如重寫整支腳本、搬移或刪除檔案、執行會覆寫資料檔的腳本,此時先將目前的狀態提交,改壞時便可退回。
至今有過哪些提交,同樣可以詢問 agent:
列出這個專案所有的提交,包含時間與說明文字。Agent 列出提交時通常會附上各自的識別碼,即一串英文字母與數字。之後要指定某一次提交,可以引用這串識別碼,也可以直接描述,例如「昨天改寫資料清理的那一次」。
退回至先前的提交
退回有輕重之別,動手之前先分清需要的是哪一種。
最輕的是只看不動,請 agent 找出某個檔案在先前某一次提交的內容,目前的檔案完全不受影響。只想確認舊的做法,或只需取回其中幾行時,這樣便已足夠。
我想看清理資料的腳本在加入新的分析之前的那次提交是什麼內容。只顯示,不要修改任何檔案。其次是只退回一個檔案,其餘檔案維持現狀。
只把畫趨勢圖的腳本退回到上一次提交的內容,其他檔案不要動。最重的是將整個專案退回至某一次提交。
把整個專案退回到加入新的分析之前的那次提交。動手之前,先告訴我哪些檔案會被改變,以及有沒有尚未提交的改動會因此消失。後半句不可省略,因為退回是以舊的內容覆寫目前的檔案,尚未提交的改動一旦被覆寫,Git 也無法復原。因此退回之前,先將目前的狀態也提交一次,即使它是壞的;這就像讀取舊存檔之前,先把目前的進度另存一格,退回本身因此也可以反悔。
將整個專案退回時,Git 有兩種做法。一種是丟棄該次提交之後的所有紀錄,歷史就此截斷;另一種是保留全部紀錄,在最後新增一次內容與舊提交相同的提交。後者不會遺失任何東西,被捨棄的那幾步裡日後如果發現有用得上的部分,仍可取回,因此交代時指明採用這一種:
退回的時候保留所有既有的提交,用新增一次提交的方式回到舊的內容。- clean
- tidy
- model
- log
- plot
- clean
- tidy
- model
- log
- plot
- = tidy
退回之後,再問一次「提交改動」一節查看改動的問題,確認檔案已是預期的內容。未納入提交的檔案不會跟著退回,被排除的輸出此時仍是退回之前產生的,須重新執行腳本才會與 code 相符;重新執行之後,已納入的結果表格如果沒有出現在改動清單裡,即表示結果與當時相同。
由 agent 負責提交
提交須有人記得做,而最容易忘記的正是使用者本人,因此將這件事寫成規矩,交給 agent 負責。規矩寫在規範檔裡,即放在專案資料夾內、agent 每個對話開始時都會讀取的一份檔案,Claude 的是 CLAUDE.md,Codex 的是 AGENTS.md,詳見「規範檔的作用與位置」一節。
## 提交
- 本專案以 Git 管理。每完成一件可以獨立說明的改動,就提交一次,一次提交只放一件事,說明文字用繁體中文寫清楚改了什麼、為什麼改。- 大範圍的改動之前(例如重寫整支腳本、搬移或刪除檔案、執行會覆寫資料檔的腳本),先確認目前的狀態已經提交;如果有尚未提交的改動,先告訴我,由我決定要不要先提交。- 提交之前,列出有變動的檔案;其中如果有我沒交代要改的檔案,或輸出的結果變了,先指出來。- 退回先前的提交之前,先說明哪些檔案會被覆寫、哪些尚未提交的改動會消失,等我確認。退回時保留既有的提交。- 不要把 `.gitignore` 排除的檔案加進提交。這幾條也可以直接交代 agent 寫入,專案裡還沒有規範檔時它會一併建立,寫完再打開檔案確認內容。
寫入之後,agent 會在每段工作結束時自行提交,使用者要做的只剩提交前查看它列出的改動,以及偶爾請它列出提交紀錄,看說明文字是否仍讀得懂。