
本文摘要大倉庫里讓 AI 修一個(gè)模塊git diff 卻跨了七個(gè)無關(guān)目錄。把允許改動(dòng)的路徑寫進(jìn)根目錄CLAUDE.md約束才能跨會(huì)話生效。一、問題與結(jié)論環(huán)境先交代macOSzshtmuxClaude Code 在倉庫根目錄啟動(dòng)內(nèi)部 monorepo約 7.3 萬行 TypeScript、14 個(gè)packages/*子包帶根pnpm-lock.yaml和dist/。一次任務(wù)是「修packages/auth的登錄超時(shí)」改完后的git status --short實(shí)際輸出M packages/auth/src/login.ts M packages/auth/src/http-client.ts M packages/billing/src/invoice.ts M packages/shared/src/retry.ts M pnpm-lock.yaml M tsconfig.json ?? packages/auth/src/login.test.ts7 個(gè)變更里只有 3 個(gè)屬于任務(wù)范圍pnpm-lock.yaml和tsconfig.json完全不該動(dòng)。另一次它自行執(zhí)行了git commit提交落在我當(dāng)時(shí)臨時(shí)切出的實(shí)驗(yàn)分支上工作區(qū)還殘留未跟蹤文件。那輪對話里我明確說過「只改 auth」所以問題不在提示詞寫得不夠細(xì)這句話只活在當(dāng)輪會(huì)話任務(wù)一復(fù)雜或換會(huì)話就失效。根目錄CLAUDE.md只有項(xiàng)目約定、沒有邊界聲明約束沒沉淀成項(xiàng)目基礎(chǔ)設(shè)施。補(bǔ)一段邊界聲明再用.claude/settings.json的permissions.deny兜住 git 寫操作改動(dòng)范圍才穩(wěn)定收斂。二、排查與選擇依據(jù)三步排查結(jié)論是約束寫錯(cuò)了位置不是模型能力問題。翻會(huì)話歷史——提示詞里有邊界但只在單次會(huì)話生效看根目錄CLAUDE.md——只有編碼約定沒有「改哪些、不改哪些」看.claude/settings.json——沒有permissions.deny文件編輯與 bash 默認(rèn)全放行g(shù)it commit、git push都能直接執(zhí)行。判斷依據(jù)來自官方倉庫對它的定位見參考資料這是一個(gè)agentic coding toolunderstands your codebase并handling git workflows——寫文件、動(dòng) git 是默認(rèn)行為沒有顯式約束時(shí)「該動(dòng)哪些」由模型自行判斷。替代方案與取舍方案選擇條件代價(jià)邊界根目錄CLAUDE.md邊界聲明本文單團(tuán)隊(duì)、路徑規(guī)則穩(wěn)定、要求跨會(huì)話生效每次會(huì)話都會(huì)讀靠模型遵守只收窄意圖不阻斷執(zhí)行.claude/settings.jsondeny規(guī)則需要硬阻斷特定路徑與 git 寫命令glob 語法隨版本變化路徑變動(dòng)要維護(hù)只擋白名單命令擋不住「該改沒改」pre-commit hook CI 檢查多人協(xié)作、需要機(jī)器強(qiáng)制CI 耗時(shí)與維護(hù)成本事后攔截文件已被改過人工git diff后合并低頻、高風(fēng)險(xiǎn)倉庫每次都要人工依賴審查者熟悉目錄職責(zé)cc-switch 等桌面工具多個(gè) AI 編碼工具混用、需統(tǒng)一切換配置第三方工具配置切換與路徑邊界無關(guān)不限制改動(dòng)范圍不能替代聲明不該用本文方案的兩種情況任務(wù)本身就是跨包重構(gòu)改packages/shared才正確邊界聲明會(huì)把正確改動(dòng)一起攔下倉庫只有幾千行、目錄職責(zé)一眼可見維護(hù)邊界聲明的成本大于收益。需要硬阻斷時(shí)如 lock 文件絕對不能碰只寫CLAUDE.md不夠必須疊deny規(guī)則。三、關(guān)鍵原理CLAUDE.md是每次會(huì)話都會(huì)加載的項(xiàng)目上下文支持多級倉庫根目錄、子目錄、全局~/.claude/CLAUDE.md。邊界寫在根目錄這一級才具備跨會(huì)話的重復(fù)約束力只寫在對話里的邊界是一次性的。若放在子目錄從packages/auth里啟動(dòng)會(huì)話時(shí)不一定能加載到。官方描述明確它在executing routine tasks也就是說routine例行改動(dòng)的范圍由模型自己劃。倉庫一大模型傾向于「順手保持一致」改了packages/auth就去看packages/shared的retry.ts發(fā)現(xiàn)依賴沒同步就動(dòng)pnpm-lock.yaml。這不是幻覺是這類 AI Agent 在無邊界時(shí)對「理解整個(gè) codebase」的正常發(fā)揮——能力與風(fēng)險(xiǎn)是同一個(gè)硬幣的兩面。四、可運(yùn)行示例環(huán)境macOS zsh、node 20一個(gè)不含真實(shí)業(yè)務(wù)代碼的最小 monorepo 復(fù)現(xiàn)倉庫。準(zhǔn)備可直接復(fù)制mkdirdemo-repocddemo-repogitinit-qmkdir-ppackages/auth/src packages/billing/src packages/shared/srcprintfconsole.log(auth);\npackages/auth/src/index.jsprintfconsole.log(billing);\npackages/billing/src/index.jsprintfconsole.log(shared);\npackages/shared/src/util.jsprintf{private: true}\npackage.jsonprintflock-v1\npackage-lock.jsongitadd-Agitcommit-qminit實(shí)際輸出準(zhǔn)備腳本跑完后的基線$ git status --short 無輸出工作區(qū)干凈改前的CLAUDE.md只有約定沒有邊界# 項(xiàng)目約定 - 使用 JavaScript - 代碼風(fēng)格遵循 ESLint 默認(rèn)配置 - 提交信息使用 conventional commits改后補(bǔ)上邊界段第一句就是那條關(guān)鍵聲明# 項(xiàng)目約定 - 使用 JavaScript - 代碼風(fēng)格遵循 ESLint 默認(rèn)配置 - 提交信息使用 conventional commits # 修改邊界優(yōu)先級高于上面的約定 - 默認(rèn)只允許修改 packages/auth/ 下的文件 - 禁止修改 packages/billing/、packages/shared/ 以及根目錄 package.json、package-lock.json - 禁止修改 dist/、node_modules/、.github/ 下的任何文件 - 不要執(zhí)行 git commit、git push、git checkout、git stash只允許 git diff、git status、git log - 不要運(yùn)行 npm install、npm ci依賴由我手動(dòng)更新 - 如果任務(wù)必須改到上述路徑先停下并列出清單問我不要直接改 - 提交由我來做你只給建議的 commit message關(guān)鍵權(quán)限配置對 git 寫操作做硬阻斷與CLAUDE.md同目錄生效cat.claude/settings.jsonEOF { permissions: { deny: [ Bash(git commit:*), Bash(git push:*), Bash(git checkout:*), Edit(package-lock.json), Edit(dist/**) ] } } EOF字段名與 glob 寫法隨版本變化本文未逐條驗(yàn)證以當(dāng)前版本官方文檔為準(zhǔn)這條命令至少保證配置文件落在正確位置。跑同一個(gè)任務(wù)claude給 auth 模塊加一個(gè)導(dǎo)出gitstatus--shortgitdiff--stat預(yù)期輸出屬預(yù)期未在本機(jī)復(fù)跑$ git diff --stat packages/auth/src/index.js | 1 1 file changed, 1 insertion()常見失敗及其修復(fù)某次運(yùn)行后git diff --stat里出現(xiàn)了package-lock.json。原因是邊界段只寫了路徑、沒寫「不要自己裝依賴」模型把同步 lock 文件當(dāng)成「保持一致」的一部分。修復(fù)方式是在邊界段補(bǔ)一行不要運(yùn)行 npm install / npm ci依賴由我手動(dòng)更新同時(shí)把Edit(package-lock.json)加進(jìn)deny。五、驗(yàn)證結(jié)果與邊界驗(yàn)證方法同一句任務(wù)在改前、改后的CLAUDE.md下各跑一次跑完立刻看git status --short與git diff --stat變更文件數(shù)從 7 降到 1–2 視為達(dá)到預(yù)期再用git log --oneline -1確認(rèn) HEAD 沒被移動(dòng)、當(dāng)前分支沒被切走。能保證的部分邊界聲明讓每次會(huì)話都讀到約束配合deny規(guī)則lock 文件寫入與git push這類操作會(huì)被硬攔。不能保證的部分模型仍可能漏改該改的沒改deny只擋寫入、不擋「少改」邊界路徑寫錯(cuò)反而會(huì)擋住正確改動(dòng)。上文兩次模型運(yùn)行的git diff對比標(biāo)注為預(yù)期、未驗(yàn)證只有準(zhǔn)備腳本與基線git status是實(shí)際跑通的輸出。所以提交前的git diff --stat審查不能省——邊界聲明負(fù)責(zé)把誤改概率壓低不負(fù)責(zé)替代 review。思考跨包重構(gòu)任務(wù)里邊界聲明該臨時(shí)放開路徑還是干脆不寫死CLAUDE.md約束與deny規(guī)則沖突時(shí)應(yīng)以哪一層為準(zhǔn)參考資料anthropics/claude-codefarion1231/cc-switch