最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Claude Code Edit工具的工作流程詳解

  發(fā)布時(shí)間:2026-05-22 10:59:40   作者:candyTong   我要評(píng)論
Claude Code 修改文件的方式不是傳行號(hào),也不是打 AST patch,它讓模型輸出一段要替換的原文 old_string 和替換后的文本 new_string,由 Edit 工具完成實(shí)際寫入,本文給大家介紹了Claude Code Edit工具的詳細(xì)工作流程,需要的朋友可以參考下

Claude Code 修改文件的方式不是傳行號(hào),也不是打 AST patch。它讓模型輸出一段要替換的原文 old_string 和替換后的文本 new_string,由 Edit 工具完成實(shí)際寫入。

這個(gè)接口看起來簡(jiǎn)單——告訴工具"把這段文字換成那段文字"就行了。但真正要把它做穩(wěn)定,需要回答兩個(gè)問題:

  1. 模型看到的文件內(nèi)容和執(zhí)行編輯時(shí)的文件內(nèi)容之間存在時(shí)間差。如果文件在這段時(shí)間里被改了,怎么辦?
  2. 模型輸出的文本和文件里的真實(shí)文本不完全一樣,怎么辦?

下面先看 Edit 的基本結(jié)構(gòu),然后圍繞這兩個(gè)問題展開。

Edit 工具的基本實(shí)現(xiàn)

Edit 通過 buildTool 注冊(cè)為一個(gè)可被模型調(diào)用的工具。核心接口包括三部分:給模型看的 Prompt、約束參數(shù)的 schema、以及真正執(zhí)行替換的 call。

export const FileEditTool = buildTool({
  name: FILE_EDIT_TOOL_NAME,

  async prompt() {
    return getEditToolDescription();
  },

  get inputSchema() {
    return z.strictObject({
      file_path: z.string().describe('The absolute path to the file to modify'),
      old_string: z.string().describe('The text to replace'),
      new_string: z.string().describe('The text to replace it with'),
      replace_all: semanticBoolean(z.boolean().default(false).optional()),
    });
  },

  async call(input, context, _, parentMessage) {
    const { file_path, old_string, new_string, replace_all = false } = input;
    const fileContent = readTextContent(file_path);

    const updatedFile = replace_all
      ? fileContent.replaceAll(old_string, new_string)
      : fileContent.replace(old_string, new_string);

    writeTextContent(file_path, updatedFile);
    return { updatedFile };
  },
});

四個(gè)參數(shù)的語義:

字段含義
file_path要修改的文件絕對(duì)路徑
old_string要被替換的原文
new_string替換后的文本
replace_all是否替換所有匹配項(xiàng),默認(rèn) false

call 的邏輯很直接:讀文件、找 old_string、替換成 new_string、寫回磁盤。但 inputSchema 只能約束字段形狀,不能告訴模型怎么寫參數(shù)。所以同一個(gè)工具定義里還有 Prompt,把調(diào)用規(guī)則寫清楚:先讀文件、保留縮進(jìn)、默認(rèn)要求 old_string 唯一、需要全局替換時(shí)再使用 replace_all。

function getEditToolDescription(): string {
  return `Performs exact string replacements in files.

Usage:
- You must use your \`Read\` tool at least once in the conversation before editing.
- When editing text from Read tool output, ensure you preserve the exact indentation.
- The edit will FAIL if \`old_string\` is not unique in the file.
- Use \`replace_all\` for replacing and renaming strings across the file.`;
}

這個(gè)最小版本能工作,但它默認(rèn)了兩件事:文件不會(huì)在讀寫之間被改,模型輸出的文本一定能和文件內(nèi)容精確匹配。真實(shí)環(huán)境里,這兩個(gè)默認(rèn)都不成立。

文件在生成過程中被改了怎么辦

模型讀文件和實(shí)際執(zhí)行編輯之間存在時(shí)間窗口。在這個(gè)窗口里,用戶可能手動(dòng)改了代碼,linter 可能自動(dòng)格式化了文件,編輯器可能保存了新的內(nèi)容。如果 Edit 工具不做任何檢查,它會(huì)基于過時(shí)的文件內(nèi)容執(zhí)行替換,把用戶或 linter 的改動(dòng)覆蓋掉。

readFileState:記住每個(gè)文件的最后讀取狀態(tài)

Edit 工具用一個(gè) LRU 緩存 readFileState 跟蹤每個(gè)文件的最后讀取狀態(tài):

type FileState = {
  content: string; // 讀取時(shí)的文件內(nèi)容
  timestamp: number; // Math.floor(mtimeMs)
  offset: number | undefined; // 讀取范圍起始(全文讀取時(shí)為 undefined)
  limit: number | undefined; // 讀取范圍長(zhǎng)度(全文讀取時(shí)為 undefined)
  isPartialView?: boolean; // 自動(dòng)注入的內(nèi)容與磁盤不一致時(shí)為 true
};

Read 工具讀取文件后會(huì)寫入這個(gè)緩存,Edit 工具寫入成功后也會(huì)更新它。這個(gè)緩存是后續(xù)所有過期檢測(cè)的基礎(chǔ)。

第一道防線:validateInput 執(zhí)行前檢查

validateInput 在編輯執(zhí)行之前運(yùn)行,不寫文件,只判斷這次編輯是否滿足安全執(zhí)行條件。它同時(shí)檢查兩個(gè)問題:

  1. 文件有沒有被改過
  2. old_string 能不能匹配上。
async function validateInput(input, toolUseContext) {
  const { file_path, old_string, replace_all } = input;
  const fullFilePath = expandPath(file_path);
  const fileContent = readCurrentTextFile(fullFilePath);

  // 1. 文件必須被讀過(不能編輯模型沒見過的文件)
  const readTimestamp = toolUseContext.readFileState.get(fullFilePath);
  if (!readTimestamp || readTimestamp.isPartialView) {
    return {
      result: false,
      message: 'File has not been read yet.',
      errorCode: 6,
    };
  }

  // 2. 文件自讀取后不能被改過
  const lastWriteTime = getFileModificationTime(fullFilePath);
  if (lastWriteTime > readTimestamp.timestamp) {
    const isFullRead =
      readTimestamp.offset === undefined && readTimestamp.limit === undefined;
    const contentUnchanged =
      isFullRead && fileContent === readTimestamp.content;
    if (!contentUnchanged) {
      return {
        result: false,
        message: 'File has been modified since read.',
        errorCode: 7,
      };
    }
  }

  // 3. old_string 必須能匹配到文件內(nèi)容
  const actualOldString = findActualString(fileContent, old_string);
  if (!actualOldString) {
    return {
      result: false,
      message: 'String to replace not found in file.',
      errorCode: 8,
    };
  }

  // 4. 默認(rèn)只允許唯一匹配
  const matches = fileContent.split(actualOldString).length - 1;
  if (matches > 1 && !replace_all) {
    return {
      result: false,
      message: `Found ${matches} matches.`,
      errorCode: 9,
    };
  }
}

前兩步檢查文件是否被改過。有幾個(gè)細(xì)節(jié)值得注意:

  • 時(shí)間戳比較用的是 Math.floor(mtimeMs),去掉亞毫秒精度,減少時(shí)間戳抖動(dòng)造成的誤報(bào)。
  • Windows 上云同步、殺毒軟件等可能只改時(shí)間戳不改內(nèi)容。所以即使時(shí)間戳變了,如果文件是完整讀取的且內(nèi)容沒變,仍然允許編輯。
  • isPartialView 標(biāo)記自動(dòng)注入的內(nèi)容(如 CLAUDE.md)與磁盤文件不一致的情況,強(qiáng)制用戶先 Read 再編輯。

第三步檢查 old_string 能不能匹配上——這里用的 findActualString 會(huì)先試精確匹配,失敗后把彎引號(hào)轉(zhuǎn)成直引號(hào)再試,因?yàn)?Claude 只能輸出直引號(hào)但文件里可能用彎引號(hào)。 如果引號(hào)規(guī)范化后仍然匹配不到,直接拒絕。匹配成功后返回的是原始文件里的實(shí)際文本,后續(xù)替換用真實(shí)字符。如果文件用的是彎引號(hào),preserveQuoteStyle 會(huì)把 new_string 里的直引號(hào)轉(zhuǎn)回彎引號(hào),保持風(fēng)格一致。

四步檢查,每一步失敗都有明確的錯(cuò)誤碼和錯(cuò)誤消息,模型可以根據(jù)錯(cuò)誤信息決定下一步行動(dòng):

錯(cuò)誤碼含義模型的下一步
6文件沒讀過先 Read 文件
7文件被改過了重新 Read 文件
8old_string 找不到換更準(zhǔn)確的 old_string
9匹配到多處擴(kuò)大上下文或使用 replace_all

第二道防線:call 寫入前再檢查一次

validateInput 通過不代表文件就安全了。校驗(yàn)通過到真正寫入之間仍然有時(shí)間窗口。所以 call 在寫入前會(huì)重新讀取文件并再次檢查:

async function call(input, { readFileState }) {
  const { file_path, old_string, new_string, replace_all } = input;
  const absoluteFilePath = expandPath(file_path);

  // 重新讀取磁盤上的當(dāng)前內(nèi)容
  const {
    content: originalFileContents,
    encoding,
    lineEndings,
  } = readFileForEdit(absoluteFilePath);

  // 寫入前再次做過期檢測(cè)
  const lastRead = readFileState.get(absoluteFilePath);
  const lastWriteTime = getFileModificationTime(absoluteFilePath);
  if (!lastRead || lastWriteTime > lastRead.timestamp) {
    const isFullRead =
      lastRead?.offset === undefined && lastRead?.limit === undefined;
    const contentUnchanged =
      isFullRead && originalFileContents === lastRead.content;
    if (!contentUnchanged) {
      // 'File has been unexpectedly modified. Read it again before attempting to write it.'
      throw new Error(FILE_UNEXPECTEDLY_MODIFIED_ERROR);
    }
  }

  // 執(zhí)行替換并寫入...
}

call 把文件讀取、過期檢查、替換計(jì)算、磁盤寫入放在一個(gè)同步段里,不允許任何異步操作插入到檢查和寫入之間。目錄創(chuàng)建、文件歷史備份等需要 await 的步驟全部安排在這個(gè)段之前完成。 檢查通過之后如果讓出事件循環(huán)(比如 await 一個(gè)異步操作),別的代碼就有機(jī)會(huì)在這段時(shí)間里修改文件,第二道防線就白做了。

為什么只有第一道防線不夠?

因?yàn)?validateInputcall 之間不是連續(xù)執(zhí)行的。validateInput 返回通過之后,運(yùn)行時(shí)還要做權(quán)限檢查、等待用戶確認(rèn)、執(zhí)行 hook 等操作,這些步驟可能耗時(shí)數(shù)百毫秒甚至更長(zhǎng)。在這個(gè)窗口里,用戶的編輯器可能自動(dòng)保存了文件,linter 可能格式化了代碼,甚至另一個(gè) Claude Code 會(huì)話可能剛剛寫入了同一個(gè)文件。如果只靠 validateInput 的檢查結(jié)果就直接寫入,這些并發(fā)修改會(huì)被靜默覆蓋。第二道防線的意義在于:真正寫入之前,用同步讀取拿到最新的文件內(nèi)容,再做一次判斷——文件變了就拒絕,沒變才寫入。

寫入后更新 readFileState

編輯成功后,call 會(huì)更新 readFileState,把文件內(nèi)容和時(shí)間戳設(shè)為寫入后的值:

readFileState.set(absoluteFilePath, {
  content: updatedFile,
  timestamp: getFileModificationTime(absoluteFilePath),
});

這一步容易被忽略,但很關(guān)鍵:如果不更新,下一次連續(xù)編輯會(huì)把自己剛寫入的文件誤判為"外部修改",導(dǎo)致所有連續(xù)編輯都失敗。

文件歷史:最后一道恢復(fù)線

即使所有檢查都通過了,寫入仍然可能不是用戶期望的。Edit 工具在真正寫入之前會(huì)調(diào)用文件歷史機(jī)制備份編輯前的內(nèi)容:

await fileHistoryTrackEdit(absoluteFilePath);

備份使用 fs.copyFile() 而不是把文件讀內(nèi)存,存儲(chǔ)在 ~/.claude/file-history/ 下。這不是校驗(yàn)機(jī)制的一部分,而是恢復(fù)機(jī)制:前面盡量避免錯(cuò)誤寫入,后面仍然保留回滾能力。

小結(jié)

階段檢查失敗行為
執(zhí)行前validateInput 檢查 mtime、匹配和唯一性拒絕編輯,返回對(duì)應(yīng)錯(cuò)誤碼
寫入前call 重新讀取文件并再次比較 mtime拋出 FILE_UNEXPECTEDLY_MODIFIED_ERROR
寫入后更新 readFileState后續(xù)編輯基于新內(nèi)容繼續(xù)
寫入前(備份)fileHistoryTrackEdit 備份原文件保留恢復(fù)能力

總結(jié)

Edit 工具的實(shí)現(xiàn)揭示了一個(gè)更一般的道理:寫一個(gè)讓 LLM 使用的工具,不能信任模型的輸出,也要考慮環(huán)境的變化,最終靠驗(yàn)證來保證正確性。

不能信任模型的輸出,因?yàn)槟P吞烊徊环€(wěn)定。它可能記錯(cuò)文件內(nèi)容,可能輸出和原文不完全一致的文本,可能在不該加空格的地方加了空格。Prompt 可以引導(dǎo)它,但無法保證它每次都對(duì)。

要考慮環(huán)境的變化,因?yàn)槟P妥x取文件和執(zhí)行工具之間存在時(shí)間差。在這個(gè)窗口里,用戶可能改了代碼,linter 可能格式化了文件,甚至另一個(gè)會(huì)話可能剛剛寫入了同一個(gè)文件。工具執(zhí)行的時(shí)候,世界已經(jīng)不是模型看到的樣子了。工具必須意識(shí)到這一點(diǎn),在關(guān)鍵操作前重新確認(rèn)環(huán)境狀態(tài)。

最終靠驗(yàn)證來保證正確性。工具層拿到模型的輸出后,可以檢查文件是否被改過,可以規(guī)范化文本后再匹配,可以在寫入前再讀一次最新內(nèi)容。能確認(rèn)安全的,執(zhí)行;不能確認(rèn)的,拒絕。已經(jīng)完成的寫入,更新狀態(tài)并保留恢復(fù)入口。

Edit 工具的每一層機(jī)制——readFileState 跟蹤、mtime 檢查、引號(hào)規(guī)范化、二次讀取、文件歷史備份——都是這個(gè)原則的具體體現(xiàn)。不是讓模型永遠(yuǎn)不犯錯(cuò),而是在模型輸出不可靠、環(huán)境隨時(shí)可能變化的前提下,通過驗(yàn)證保證最終結(jié)果的正確性。

以上就是Claude Code Edit工具的工作流程詳解的詳細(xì)內(nèi)容,更多關(guān)于Claude Code Edit工具工作流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Claude Code在大型項(xiàng)目中的最佳實(shí)踐指南

    本文基于 Anthropic 官方博客 How Claude Code works in large codebases 整理,結(jié)合實(shí)際工程場(chǎng)景深度解讀,并補(bǔ)充大量可直接復(fù)用的配置案例,需要的朋友可以參考下
    2026-05-21
  • Claude Code安裝完全指南(Mac版):Git,環(huán)境變量,PATH與常見報(bào)錯(cuò)一次講清

    如果你是第一次從零配置 Claude Code,最容易失敗的不是安裝命令本身,而是整個(gè)環(huán)境鏈條沒有打通,這篇文章就專門講這個(gè)鏈條,而且盡量講全,有需要的小伙伴可以參考一下
    2026-05-21
  • Claude Code CLI 使用完整指南

    Claude Code 是 Anthropic 官方的命令行 AI 編程助手,像在終端里有一個(gè)懂你整個(gè)代碼庫的高級(jí)工程師,本文給大家介紹Claude Code CLI 使用完整指南,感興趣的朋友跟隨小編一
    2026-05-21
  • Claude Code cli 及vscode版本的各種命令參考手冊(cè)(最新推薦)

    Claudede是 Anthropic 提供的一個(gè)命令行接口,用于與 Claude AI 交互,它提供了超過70個(gè)內(nèi)置命令和綁定技能,這篇文章給大家介紹了Claude Code cli 及vscode版本的各種命令參
    2026-05-21
  • Claude code相關(guān)的skill是干什么以及有什么作用詳解

    Skills是一種可復(fù)用的能力模塊,你可以把它理解成給Claude Code安裝的插件或技能包,這篇文章主要介紹了Claude code相關(guān)的skill是干什么以及有什么作用的相關(guān)資料,文中通過代
    2026-05-20
  • Claude Code深度集成VS Code的完整指南

    裝好 Claude Code 插件那一刻,大多數(shù)人就覺得集成完成了,其實(shí)那只是安裝完成,真正的集成是Claude 知道你的代碼風(fēng)格,你的項(xiàng)目結(jié)構(gòu),你用的技術(shù)棧,下面小編就和大家詳細(xì)
    2026-05-20
  • Claude Code 與 Codex Harness 設(shè)計(jì)對(duì)比分析:一種加法,一種減法

    文章對(duì)比了ClaudeCode和CodexCLI的設(shè)計(jì)哲學(xué),從技術(shù)棧、主循環(huán)、工具系統(tǒng)、壓縮、權(quán)限、子agent、擴(kuò)展機(jī)制、跨端、成本與可觀測(cè)性等8個(gè)維度進(jìn)行了深入分析,感興趣的朋友跟隨
    2026-05-20
  • Claude Code對(duì)話自動(dòng)導(dǎo)入的完全指南

    文章介紹了如何使用ChatCrystal導(dǎo)入和處理ClaudeCode的對(duì)話數(shù)據(jù),包括數(shù)據(jù)存儲(chǔ)位置、導(dǎo)入流程、噪音消息過濾、內(nèi)容清理、項(xiàng)目名提取、自定義數(shù)據(jù)目錄設(shè)置、自動(dòng)導(dǎo)入機(jī)制等步
    2026-05-19
  • 一文徹底掌握.claude/目錄(讓Claude Code真正懂你的項(xiàng)目)

    如果你曾經(jīng)用過Claude Code,或許會(huì)發(fā)現(xiàn)項(xiàng)目根目錄下突然多出一個(gè)名為.claude的文件夾,下面這篇文章主要介紹了ClaudeCode中.claude/目錄的相關(guān)資料,文中通過代碼介紹的非常
    2026-05-19
  • 在Claude Code設(shè)置MCP服務(wù)器

    MCP是一種為Claude提供外部能力的機(jī)制,通過安裝不同功能的MCP服務(wù)器,可賦予Claude文件系統(tǒng)訪問、網(wǎng)頁抓取、瀏覽器自動(dòng)化等能力,下面就來詳細(xì)的介紹一下如何安裝,感興趣
    2026-05-19

最新評(píng)論

大荔县| 通许县| 加查县| 洞口县| 湖北省| 长春市| 丰镇市| 登封市| 西城区| 左贡县| 逊克县| 巫溪县| 毕节市| 泽州县| 霍林郭勒市| 新巴尔虎左旗| 平顺县| 南汇区| 泰宁县| 石台县| 盐源县| 宜川县| 尼木县| 安达市| 东乡族自治县| 阳曲县| 元谋县| 大理市| 屯昌县| 溧阳市| 施秉县| 尉犁县| 于都县| 峨山| 平凉市| 从化市| 汾阳市| 武夷山市| 侯马市| 荣昌县| 蓝田县|