用 Forge 的 `:fixme` 自定义命令自动扫描并修复代码中的 FIXME 注释

原创2026-09-26 13:53:041,448 阅读
文章标签:人工智能AI Agent代码智能体AI 应用CLI开发工具

用 Forge 的 :fixme 自定义命令自动扫描并修复代码中的 FIXME 注释

fixme 是 Forge 内置的快捷命令(slash command),它让 AI 自动遍历当前项目源码文件中的 FIXME 注释,并逐一尝试修复对应代码。本文以 .forge/commands/fixme.md 为骨架,结合仓库中命令加载、解析与优先级的源码实现,讲解该命令的文件格式、工作原理、注册机制,以及如何举一反三编写属于自己的自定义命令。

1. fixme 命令是什么

fixme 是 Forge(AI enabled pair programmer)预置的一条内置命令。它的完整定义存放在仓库根目录的 .forge/commands/fixme.md,全文如下:

---
name: fixme
description: Looks for all the fixme comments in the code and attempts to fix them
---

Find all the FIXME comments in source-code files and attempt to fix them.

它的语义非常直接:扫描源码文件里所有 FIXME 注释,然后尝试修复它们。也就是说,当你在 Forge 对话中敲入 :fixme,Agent 会先通过文件搜索定位所有包含 FIXME 标记的源码文件,再逐条分析注释上下文、判断遗留问题,并直接动手修改代码把问题修掉。

在 crates/forge_app/src/fixtures/git_ls_files_many_extensions.txt 这个仓库自带的目录清单 fixture 中,可以看到 .forge/commands/fixme.md 与 check.md、pr_description.md 一同出现在 .forge/commands/ 目录下,印证了这类命令文件正是以独立 Markdown 文件的形式存在于该目录中。

2. 命令文件的结构:YAML frontmatter + Markdown 正文

从 fixme.md 可以看出,Forge 的每条自定义命令都由两部分组成:

  1. YAML frontmatter(元数据区):位于文档开头的 --- 与 --- 之间,用于声明命令的 name 与 description。
  2. Markdown 正文(提示词区):frontmatter 之后的所有内容,就是真正发给模型的提示词(prompt),指导 Agent 如何执行该命令。

这套格式在源码中被严格建模。Command 结构体定义于 crates/forge_domain/src/command.rs,注释明确写着:命令从 Forge 的命令目录中发现的 .md 文件中加载,name 和 description 来自 YAML frontmatter,prompt 则是文件正文:

pub struct Command {
    /// 调用该命令时使用的名字(如 `github-pr-description`)
    pub name: String,
    /// 在命令列表中展示的简短描述
    pub description: String,
    /// 提示词模板正文(frontmatter 之后的 Markdown 内容)
    pub prompt: Option<String>,
}

解析逻辑在 crates/forge_services/src/command.rs 的 parse_command_file 函数中:先用 gray_matter 库以 YAML 引擎解析 frontmatter,再做类型安全的反序列化,最后把正文部分挂到 prompt 字段上:

fn parse_command_file(content: &str) -> Result<Command> {
    let gray_matter = Matter::<YAML>::new();
    let result = gray_matter.parse::<Command>(content)?;
    let command = result
        .data
        .context("Empty command frontmatter")?
        .prompt(result.content);
    Ok(command)
}

Command 结构体上标注了 #[derive(Deserialize)],因此 frontmatter 中的 name、description 会被自动映射为结构体字段;frontmatter 缺失时解析会报错(Empty command frontmatter)。这一点与 fixme.md 严格成对出现的 name/description 键完全一致。

仓库测试 crates/forge_services/src/command.rs 中的用例也验证了这一格式:test_parse_basic_command 用 fixture basic.md 断言 name、description 与 prompt 都被正确解析;test_parse_command_with_multiline_prompt 则用 multiline.md 验证正文支持多行 Markdown(步骤列表等),也就是说像 fixme 这样的正文完全可以写得非常详细,Agent 会完整读取。

3. fixme 命令的注册与加载机制

fixme 不是写在二进制里的硬编码逻辑,而是以标准命令文件的形式被加载器统一发现并注册。整条链路在 crates/forge_services/src/command.rs 的 CommandLoaderService 中实现:

  1. 加载内置命令:init_default() 先把二进制内嵌的命令(如 github-pr-description,源码见 commands/github-pr-description.md)加载进来,作为最低优先级。
  2. 加载全局自定义命令:从 command_path()(默认 base_path/commands,即 ~/forge/commands)读取所有 *.md 文件。
  3. 加载项目本地命令:从 command_path_local() 读取项目根目录下的 .forge/commands/ 目录,fixme.md 正是在这一层被发现的。
  4. 解决命名冲突:resolve_command_conflicts 用 HashMap 按名字去重、保留最后一条,从而形成 项目本地 > 全局自定义 > 内置 的优先级顺序。

命令目录的路径定义在 crates/forge_domain/src/env.rs:

/// 全局命令目录(base_path/commands)
pub fn command_path(&self) -> PathBuf {
    self.base_path.join("commands")
}

/// 项目本地命令目录(.forge/commands)
pub fn command_path_local(&self) -> PathBuf {
    self.cwd.join(".forge/commands")
}

目录扫描由 init_command_dir 完成:目录不存在时直接返回空;存在时通过 DirectoryReaderInfra 并行读取目录下所有 *.md 文件,并以文件名(去掉扩展名)作为命令名——这正是 fixme.md 对应命令名为 fixme 的原因。也就是说,如果你想新增一条命令,只需在 .forge/commands/ 下新建一个 Markdown 文件即可,文件名即命令名。

项目根目录的 README.md 也给出了同样的使用说明:“Custom commands: Place YAML files in .forge/commands/ (project) or ~/forge/commands/ (global) to define shortcut commands available via :commandname.” 即命令可通过 :命令名 的形式在对话中直接触发。

test_parse_builtin_commands 测试用例把 fixme 当作内置命令 fixture 来解析并断言其 name、description、prompt 均非空,进一步确认 fixme 是经过测试保障的标准内置命令。

4. 实际使用方式

在 Forge 会话中直接输入:

:fixme

Forge 会解析出 fixme 命令的提示词,即“在源码文件中找出所有 FIXME 注释并尝试修复”,随后 Agent 按提示词执行:

  1. 定位:搜索当前项目源码中的 FIXME 注释(大小写通常不敏感,README 中 :fixme 的说明原文即为 fixme comments)。
  2. 分析:逐个阅读注释上下文,判断该处遗留的问题性质与修复意图。
  3. 修复:在保证不破坏其他逻辑的前提下直接修改源码,把注释对应的缺陷或待办事项落地解决。
  4. 验证:修复后可结合 :check(同样注册在内置命令列表中的检查命令)或项目测试来确认改动没有引入回归。

使用前提是当前会话所在的目录是一个 Forge 项目(即存在 .forge/ 配置),并且项目源码可被 Agent 通过文件工具正常读写。

5. 举一反三:编写你自己的自定义命令

理解了 fixme.md 的格式与加载机制后,完全可以自己编写类似的命令。例如在项目根目录 .forge/commands/ 下新建 refactor.md:

---
name: refactor
description: Find duplicated code blocks and suggest refactoring
---

Scan the source-code files for duplicated code blocks that appear in three or
more places. For each duplication found:
1. Summarize the repeated logic and its locations.
2. Propose a refactoring that extracts the shared logic.
3. Apply the refactoring and verify the code still compiles.

保存后,在新的 Forge 会话中直接输入 :refactor 即可触发。需要注意:

  • 命令名即文件名(去掉 .md 后缀);
  • 前端 name 字段应与文件名保持一致,避免歧义;
  • description 会展示在命令列表中,建议写清楚命令的作用;
  • 正文 prompt 可以像 multiline.md 那样使用多行列表,让 Agent 按步骤执行;
  • 若与内置命令重名,项目本地命令会覆盖全局与内置版本(优先级:项目本地 > 全局自定义 > 内置)。

6. 小结

fixme 命令是 Forge 自定义命令机制的一个典型样本:它由 .forge/commands/fixme.md 这样一个“YAML frontmatter + Markdown 正文”的文件定义,经 crates/forge_services/src/command.rs 的加载器扫描、解析与去重后注册为 :fixme 快捷命令,最终把“扫描并修复 FIXME 注释”的意图作为提示词交给 Agent 执行。掌握这套文件格式与加载优先级,你就能像 fixme 一样,把团队里高频的编码巡检、重构、代码生成等重复工作沉淀为一条条可复用的 Forge 命令。

登录后查看全文
forgecode